commit 077b79eb9f2870e64f604bcab0cdbbf7b85d6400 Author: status-im-auto Date: Sun Jul 26 05:59:32 2026 +0000 Update documentation diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 00000000..e69de29b diff --git a/404.html b/404.html new file mode 100644 index 00000000..f096ec83 --- /dev/null +++ b/404.html @@ -0,0 +1,4 @@ +Logos documentation + + +
Skip to main content

Page Not Found

We could not find what you were looking for.

Please contact the owner of the site that linked you to the original URL and let them know their link is broken.

\ No newline at end of file diff --git a/CNAME b/CNAME new file mode 100644 index 00000000..9eccf8c1 --- /dev/null +++ b/CNAME @@ -0,0 +1 @@ +dev-docs.logos.co \ No newline at end of file diff --git a/assets/css/styles.e45c7411.css b/assets/css/styles.e45c7411.css new file mode 100644 index 00000000..6cf28ac9 --- /dev/null +++ b/assets/css/styles.e45c7411.css @@ -0,0 +1 @@ +@layer docusaurus.infima{:root{--ifm-color-scheme:light;--ifm-dark-value:10%;--ifm-darker-value:15%;--ifm-darkest-value:30%;--ifm-light-value:15%;--ifm-lighter-value:30%;--ifm-lightest-value:50%;--ifm-contrast-background-value:90%;--ifm-contrast-foreground-value:70%;--ifm-contrast-background-dark-value:70%;--ifm-contrast-foreground-dark-value:90%;--ifm-color-primary:#3578e5;--ifm-color-secondary:#ebedf0;--ifm-color-success:#00a400;--ifm-color-info:#54c7ec;--ifm-color-warning:#ffba00;--ifm-color-danger:#fa383e;--ifm-color-primary-dark:#306cce;--ifm-color-primary-darker:#2d66c3;--ifm-color-primary-darkest:#2554a0;--ifm-color-primary-light:#538ce9;--ifm-color-primary-lighter:#72a1ed;--ifm-color-primary-lightest:#9abcf2;--ifm-color-primary-contrast-background:#ebf2fc;--ifm-color-primary-contrast-foreground:#102445;--ifm-color-secondary-dark:#d4d5d8;--ifm-color-secondary-darker:#c8c9cc;--ifm-color-secondary-darkest:#a4a6a8;--ifm-color-secondary-light:#eef0f2;--ifm-color-secondary-lighter:#f1f2f5;--ifm-color-secondary-lightest:#f5f6f8;--ifm-color-secondary-contrast-background:#fdfdfe;--ifm-color-secondary-contrast-foreground:#474748;--ifm-color-success-dark:#009400;--ifm-color-success-darker:#008b00;--ifm-color-success-darkest:#007300;--ifm-color-success-light:#26b226;--ifm-color-success-lighter:#4dbf4d;--ifm-color-success-lightest:#80d280;--ifm-color-success-contrast-background:#e6f6e6;--ifm-color-success-contrast-foreground:#003100;--ifm-color-info-dark:#4cb3d4;--ifm-color-info-darker:#47a9c9;--ifm-color-info-darkest:#3b8ba5;--ifm-color-info-light:#6ecfef;--ifm-color-info-lighter:#87d8f2;--ifm-color-info-lightest:#aae3f6;--ifm-color-info-contrast-background:#eef9fd;--ifm-color-info-contrast-foreground:#193c47;--ifm-color-warning-dark:#e6a700;--ifm-color-warning-darker:#d99e00;--ifm-color-warning-darkest:#b38200;--ifm-color-warning-light:#ffc426;--ifm-color-warning-lighter:#ffcf4d;--ifm-color-warning-lightest:#ffdd80;--ifm-color-warning-contrast-background:#fff8e6;--ifm-color-warning-contrast-foreground:#4d3800;--ifm-color-danger-dark:#e13238;--ifm-color-danger-darker:#d53035;--ifm-color-danger-darkest:#af272b;--ifm-color-danger-light:#fb565b;--ifm-color-danger-lighter:#fb7478;--ifm-color-danger-lightest:#fd9c9f;--ifm-color-danger-contrast-background:#ffebec;--ifm-color-danger-contrast-foreground:#4b1113;--ifm-color-white:#fff;--ifm-color-black:#000;--ifm-color-gray-0:var(--ifm-color-white);--ifm-color-gray-100:#f5f6f7;--ifm-color-gray-200:#ebedf0;--ifm-color-gray-300:#dadde1;--ifm-color-gray-400:#ccd0d5;--ifm-color-gray-500:#bec3c9;--ifm-color-gray-600:#8d949e;--ifm-color-gray-700:#606770;--ifm-color-gray-800:#444950;--ifm-color-gray-900:#1c1e21;--ifm-color-gray-1000:var(--ifm-color-black);--ifm-color-emphasis-0:var(--ifm-color-gray-0);--ifm-color-emphasis-100:var(--ifm-color-gray-100);--ifm-color-emphasis-200:var(--ifm-color-gray-200);--ifm-color-emphasis-300:var(--ifm-color-gray-300);--ifm-color-emphasis-400:var(--ifm-color-gray-400);--ifm-color-emphasis-500:var(--ifm-color-gray-500);--ifm-color-emphasis-600:var(--ifm-color-gray-600);--ifm-color-emphasis-700:var(--ifm-color-gray-700);--ifm-color-emphasis-800:var(--ifm-color-gray-800);--ifm-color-emphasis-900:var(--ifm-color-gray-900);--ifm-color-emphasis-1000:var(--ifm-color-gray-1000);--ifm-color-content:var(--ifm-color-emphasis-900);--ifm-color-content-inverse:var(--ifm-color-emphasis-0);--ifm-color-content-secondary:#525860;--ifm-background-color:transparent;--ifm-background-surface-color:var(--ifm-color-content-inverse);--ifm-global-border-width:1px;--ifm-global-radius:.4rem;--ifm-hover-overlay:rgba(0,0,0,.05);--ifm-font-color-base:var(--ifm-color-content);--ifm-font-color-base-inverse:var(--ifm-color-content-inverse);--ifm-font-color-secondary:var(--ifm-color-content-secondary);--ifm-font-family-base:system-ui,-apple-system,Segoe UI,Roboto,Ubuntu,Cantarell,Noto Sans,sans-serif,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";--ifm-font-family-monospace:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--ifm-font-size-base:100%;--ifm-font-weight-light:300;--ifm-font-weight-normal:400;--ifm-font-weight-semibold:500;--ifm-font-weight-bold:700;--ifm-font-weight-base:var(--ifm-font-weight-normal);--ifm-line-height-base:1.65;--ifm-global-spacing:1rem;--ifm-spacing-vertical:var(--ifm-global-spacing);--ifm-spacing-horizontal:var(--ifm-global-spacing);--ifm-transition-fast:.2s;--ifm-transition-slow:.4s;--ifm-transition-timing-default:cubic-bezier(.08,.52,.52,1);--ifm-global-shadow-lw:0 1px 2px 0 rgba(0,0,0,.1);--ifm-global-shadow-md:0 5px 40px rgba(0,0,0,.2);--ifm-global-shadow-tl:0 12px 28px 0 rgba(0,0,0,.2),0 2px 4px 0 rgba(0,0,0,.1);--ifm-z-index-dropdown:100;--ifm-z-index-fixed:200;--ifm-z-index-overlay:400;--ifm-container-width:1140px;--ifm-container-width-xl:1320px;--ifm-code-background:#f6f7f8;--ifm-code-border-radius:var(--ifm-global-radius);--ifm-code-font-size:90%;--ifm-code-padding-horizontal:.1rem;--ifm-code-padding-vertical:.1rem;--ifm-pre-background:var(--ifm-code-background);--ifm-pre-border-radius:var(--ifm-code-border-radius);--ifm-pre-color:inherit;--ifm-pre-line-height:1.45;--ifm-pre-padding:1rem;--ifm-heading-color:inherit;--ifm-heading-margin-top:0;--ifm-heading-margin-bottom:var(--ifm-spacing-vertical);--ifm-heading-font-family:var(--ifm-font-family-base);--ifm-heading-font-weight:var(--ifm-font-weight-bold);--ifm-heading-line-height:1.25;--ifm-h1-font-size:2rem;--ifm-h2-font-size:1.5rem;--ifm-h3-font-size:1.25rem;--ifm-h4-font-size:1rem;--ifm-h5-font-size:.875rem;--ifm-h6-font-size:.85rem;--ifm-image-alignment-padding:1.25rem;--ifm-leading-desktop:1.25;--ifm-leading:calc(var(--ifm-leading-desktop)*1rem);--ifm-list-left-padding:2rem;--ifm-list-margin:1rem;--ifm-list-item-margin:.25rem;--ifm-list-paragraph-margin:1rem;--ifm-table-cell-padding:.75rem;--ifm-table-background:transparent;--ifm-table-stripe-background:rgba(0,0,0,.03);--ifm-table-border-width:1px;--ifm-table-border-color:var(--ifm-color-emphasis-300);--ifm-table-head-background:inherit;--ifm-table-head-color:inherit;--ifm-table-head-font-weight:var(--ifm-font-weight-bold);--ifm-table-cell-color:inherit;--ifm-link-color:var(--ifm-color-primary);--ifm-link-decoration:none;--ifm-link-hover-color:var(--ifm-link-color);--ifm-link-hover-decoration:underline;--ifm-paragraph-margin-bottom:var(--ifm-leading);--ifm-blockquote-font-size:var(--ifm-font-size-base);--ifm-blockquote-border-left-width:2px;--ifm-blockquote-padding-horizontal:var(--ifm-spacing-horizontal);--ifm-blockquote-padding-vertical:0;--ifm-blockquote-shadow:none;--ifm-blockquote-color:var(--ifm-color-emphasis-800);--ifm-blockquote-border-color:var(--ifm-color-emphasis-300);--ifm-hr-background-color:var(--ifm-color-emphasis-500);--ifm-hr-height:1px;--ifm-hr-margin-vertical:1.5rem;--ifm-scrollbar-size:7px;--ifm-scrollbar-track-background-color:#f1f1f1;--ifm-scrollbar-thumb-background-color:silver;--ifm-scrollbar-thumb-hover-background-color:#a7a7a7;--ifm-alert-background-color:inherit;--ifm-alert-border-color:inherit;--ifm-alert-border-radius:var(--ifm-global-radius);--ifm-alert-border-width:0px;--ifm-alert-border-left-width:5px;--ifm-alert-color:var(--ifm-font-color-base);--ifm-alert-padding-horizontal:var(--ifm-spacing-horizontal);--ifm-alert-padding-vertical:var(--ifm-spacing-vertical);--ifm-alert-shadow:var(--ifm-global-shadow-lw);--ifm-avatar-intro-margin:1rem;--ifm-avatar-intro-alignment:inherit;--ifm-avatar-photo-size:3rem;--ifm-badge-background-color:inherit;--ifm-badge-border-color:inherit;--ifm-badge-border-radius:var(--ifm-global-radius);--ifm-badge-border-width:var(--ifm-global-border-width);--ifm-badge-color:var(--ifm-color-white);--ifm-badge-padding-horizontal:calc(var(--ifm-spacing-horizontal)*.5);--ifm-badge-padding-vertical:calc(var(--ifm-spacing-vertical)*.25);--ifm-breadcrumb-border-radius:1.5rem;--ifm-breadcrumb-spacing:.5rem;--ifm-breadcrumb-color-active:var(--ifm-color-primary);--ifm-breadcrumb-item-background-active:var(--ifm-hover-overlay);--ifm-breadcrumb-padding-horizontal:.8rem;--ifm-breadcrumb-padding-vertical:.4rem;--ifm-breadcrumb-size-multiplier:1;--ifm-breadcrumb-separator:url("data:image/svg+xml;utf8,");--ifm-breadcrumb-separator-filter:none;--ifm-breadcrumb-separator-size:.5rem;--ifm-breadcrumb-separator-size-multiplier:1.25;--ifm-button-background-color:inherit;--ifm-button-border-color:var(--ifm-button-background-color);--ifm-button-border-width:var(--ifm-global-border-width);--ifm-button-color:var(--ifm-font-color-base-inverse);--ifm-button-font-weight:var(--ifm-font-weight-bold);--ifm-button-padding-horizontal:1.5rem;--ifm-button-padding-vertical:.375rem;--ifm-button-size-multiplier:1;--ifm-button-transition-duration:var(--ifm-transition-fast);--ifm-button-border-radius:calc(var(--ifm-global-radius)*var(--ifm-button-size-multiplier));--ifm-button-group-spacing:2px;--ifm-card-background-color:var(--ifm-background-surface-color);--ifm-card-border-radius:calc(var(--ifm-global-radius)*2);--ifm-card-horizontal-spacing:var(--ifm-global-spacing);--ifm-card-vertical-spacing:var(--ifm-global-spacing);--ifm-toc-border-color:var(--ifm-color-emphasis-300);--ifm-toc-link-color:var(--ifm-color-content-secondary);--ifm-toc-padding-vertical:.5rem;--ifm-toc-padding-horizontal:.5rem;--ifm-dropdown-background-color:var(--ifm-background-surface-color);--ifm-dropdown-font-weight:var(--ifm-font-weight-semibold);--ifm-dropdown-link-color:var(--ifm-font-color-base);--ifm-dropdown-hover-background-color:var(--ifm-hover-overlay);--ifm-footer-background-color:var(--ifm-color-emphasis-100);--ifm-footer-color:inherit;--ifm-footer-link-color:var(--ifm-color-emphasis-700);--ifm-footer-link-hover-color:var(--ifm-color-primary);--ifm-footer-link-horizontal-spacing:.5rem;--ifm-footer-padding-horizontal:calc(var(--ifm-spacing-horizontal)*2);--ifm-footer-padding-vertical:calc(var(--ifm-spacing-vertical)*2);--ifm-footer-title-color:inherit;--ifm-footer-logo-max-width:min(30rem,90vw);--ifm-hero-background-color:var(--ifm-background-surface-color);--ifm-hero-text-color:var(--ifm-color-emphasis-800);--ifm-menu-color:var(--ifm-color-emphasis-700);--ifm-menu-color-active:var(--ifm-color-primary);--ifm-menu-color-background-active:var(--ifm-hover-overlay);--ifm-menu-color-background-hover:var(--ifm-hover-overlay);--ifm-menu-link-padding-horizontal:.75rem;--ifm-menu-link-padding-vertical:.375rem;--ifm-menu-link-sublist-icon:url("data:image/svg+xml;utf8,");--ifm-menu-link-sublist-icon-filter:none;--ifm-navbar-background-color:var(--ifm-background-surface-color);--ifm-navbar-height:3.75rem;--ifm-navbar-item-padding-horizontal:.75rem;--ifm-navbar-item-padding-vertical:.25rem;--ifm-navbar-link-color:var(--ifm-font-color-base);--ifm-navbar-link-hover-color:var(--ifm-color-primary);--ifm-navbar-link-active-color:var(--ifm-link-color);--ifm-navbar-padding-horizontal:var(--ifm-spacing-horizontal);--ifm-navbar-padding-vertical:calc(var(--ifm-spacing-vertical)*.5);--ifm-navbar-shadow:var(--ifm-global-shadow-lw);--ifm-navbar-search-input-background-color:var(--ifm-color-emphasis-200);--ifm-navbar-search-input-color:var(--ifm-color-emphasis-800);--ifm-navbar-search-input-placeholder-color:var(--ifm-color-emphasis-500);--ifm-navbar-search-input-icon:url("data:image/svg+xml;utf8,");--ifm-navbar-sidebar-width:83vw;--ifm-pagination-border-radius:var(--ifm-global-radius);--ifm-pagination-color-active:var(--ifm-color-primary);--ifm-pagination-font-size:1rem;--ifm-pagination-item-active-background:var(--ifm-hover-overlay);--ifm-pagination-page-spacing:.2em;--ifm-pagination-padding-horizontal:calc(var(--ifm-spacing-horizontal)*1);--ifm-pagination-padding-vertical:calc(var(--ifm-spacing-vertical)*.25);--ifm-pagination-nav-border-radius:var(--ifm-global-radius);--ifm-pagination-nav-color-hover:var(--ifm-color-primary);--ifm-pills-color-active:var(--ifm-color-primary);--ifm-pills-color-background-active:var(--ifm-hover-overlay);--ifm-pills-spacing:.125rem;--ifm-tabs-color:var(--ifm-font-color-secondary);--ifm-tabs-color-active:var(--ifm-color-primary);--ifm-tabs-color-active-border:var(--ifm-tabs-color-active);--ifm-tabs-padding-horizontal:1rem;--ifm-tabs-padding-vertical:1rem}*{box-sizing:border-box}html{background-color:var(--ifm-background-color);color:var(--ifm-font-color-base);color-scheme:var(--ifm-color-scheme);font:var(--ifm-font-size-base)/var(--ifm-line-height-base)var(--ifm-font-family-base);-webkit-font-smoothing:antialiased;-webkit-tap-highlight-color:transparent;text-rendering:optimizelegibility;-webkit-text-size-adjust:100%;text-size-adjust:100%}body{word-wrap:break-word;margin:0}iframe{color-scheme:normal;border:0}.container{max-width:var(--ifm-container-width);padding:0 var(--ifm-spacing-horizontal);width:100%;margin:0 auto}.container--fluid{max-width:inherit}.row{margin:0 calc(var(--ifm-spacing-horizontal)*-1);flex-wrap:wrap;display:flex}.row--no-gutters{margin-left:0;margin-right:0}.row--no-gutters>.col{padding-left:0;padding-right:0}.row--align-top{align-items:flex-start}.row--align-bottom{align-items:flex-end}.row--align-center{align-items:center}.row--align-stretch{align-items:stretch}.row--align-baseline{align-items:baseline}.col{--ifm-col-width:100%;max-width:var(--ifm-col-width);padding:0 var(--ifm-spacing-horizontal);flex:1 0;width:100%;margin-left:0}.col[class*=col--]{flex:0 0 var(--ifm-col-width)}.col--1{--ifm-col-width:calc(1/12*100%)}.col--offset-1{margin-left:8.33333%}.col--2{--ifm-col-width:calc(2/12*100%)}.col--offset-2{margin-left:16.6667%}.col--3{--ifm-col-width:calc(3/12*100%)}.col--offset-3{margin-left:25%}.col--4{--ifm-col-width:calc(4/12*100%)}.col--offset-4{margin-left:33.3333%}.col--5{--ifm-col-width:calc(5/12*100%)}.col--offset-5{margin-left:41.6667%}.col--6{--ifm-col-width:calc(6/12*100%)}.col--offset-6{margin-left:50%}.col--7{--ifm-col-width:calc(7/12*100%)}.col--offset-7{margin-left:58.3333%}.col--8{--ifm-col-width:calc(8/12*100%)}.col--offset-8{margin-left:66.6667%}.col--9{--ifm-col-width:calc(9/12*100%)}.col--offset-9{margin-left:75%}.col--10{--ifm-col-width:calc(10/12*100%)}.col--offset-10{margin-left:83.3333%}.col--11{--ifm-col-width:calc(11/12*100%)}.col--offset-11{margin-left:91.6667%}.col--12{--ifm-col-width:calc(12/12*100%)}.col--offset-12{margin-left:100%}.margin--none{margin:0!important}.margin-top--none{margin-top:0!important}.margin-left--none{margin-left:0!important}.margin-bottom--none{margin-bottom:0!important}.margin-right--none{margin-right:0!important}.margin-vert--none{margin-top:0!important;margin-bottom:0!important}.margin-horiz--none{margin-left:0!important;margin-right:0!important}.margin--xs{margin:.25rem!important}.margin-top--xs{margin-top:.25rem!important}.margin-left--xs{margin-left:.25rem!important}.margin-bottom--xs{margin-bottom:.25rem!important}.margin-right--xs{margin-right:.25rem!important}.margin-vert--xs{margin-top:.25rem!important;margin-bottom:.25rem!important}.margin-horiz--xs{margin-left:.25rem!important;margin-right:.25rem!important}.margin--sm{margin:.5rem!important}.margin-top--sm{margin-top:.5rem!important}.margin-left--sm{margin-left:.5rem!important}.margin-bottom--sm{margin-bottom:.5rem!important}.margin-right--sm{margin-right:.5rem!important}.margin-vert--sm{margin-top:.5rem!important;margin-bottom:.5rem!important}.margin-horiz--sm{margin-left:.5rem!important;margin-right:.5rem!important}.margin--md{margin:1rem!important}.margin-top--md{margin-top:1rem!important}.margin-left--md{margin-left:1rem!important}.margin-bottom--md{margin-bottom:1rem!important}.margin-right--md{margin-right:1rem!important}.margin-vert--md{margin-top:1rem!important;margin-bottom:1rem!important}.margin-horiz--md{margin-left:1rem!important;margin-right:1rem!important}.margin--lg{margin:2rem!important}.margin-top--lg{margin-top:2rem!important}.margin-left--lg{margin-left:2rem!important}.margin-bottom--lg{margin-bottom:2rem!important}.margin-right--lg{margin-right:2rem!important}.margin-vert--lg{margin-top:2rem!important;margin-bottom:2rem!important}.margin-horiz--lg{margin-left:2rem!important;margin-right:2rem!important}.margin--xl{margin:5rem!important}.margin-top--xl{margin-top:5rem!important}.margin-left--xl{margin-left:5rem!important}.margin-bottom--xl{margin-bottom:5rem!important}.margin-right--xl{margin-right:5rem!important}.margin-vert--xl{margin-top:5rem!important;margin-bottom:5rem!important}.margin-horiz--xl{margin-left:5rem!important;margin-right:5rem!important}.padding--none{padding:0!important}.padding-top--none{padding-top:0!important}.padding-left--none{padding-left:0!important}.padding-bottom--none{padding-bottom:0!important}.padding-right--none{padding-right:0!important}.padding-vert--none{padding-top:0!important;padding-bottom:0!important}.padding-horiz--none{padding-left:0!important;padding-right:0!important}.padding--xs{padding:.25rem!important}.padding-top--xs{padding-top:.25rem!important}.padding-left--xs{padding-left:.25rem!important}.padding-bottom--xs{padding-bottom:.25rem!important}.padding-right--xs{padding-right:.25rem!important}.padding-vert--xs{padding-top:.25rem!important;padding-bottom:.25rem!important}.padding-horiz--xs{padding-left:.25rem!important;padding-right:.25rem!important}.padding--sm{padding:.5rem!important}.padding-top--sm{padding-top:.5rem!important}.padding-left--sm{padding-left:.5rem!important}.padding-bottom--sm{padding-bottom:.5rem!important}.padding-right--sm{padding-right:.5rem!important}.padding-vert--sm{padding-top:.5rem!important;padding-bottom:.5rem!important}.padding-horiz--sm{padding-left:.5rem!important;padding-right:.5rem!important}.padding--md{padding:1rem!important}.padding-top--md{padding-top:1rem!important}.padding-left--md{padding-left:1rem!important}.padding-bottom--md{padding-bottom:1rem!important}.padding-right--md{padding-right:1rem!important}.padding-vert--md{padding-top:1rem!important;padding-bottom:1rem!important}.padding-horiz--md{padding-left:1rem!important;padding-right:1rem!important}.padding--lg{padding:2rem!important}.padding-top--lg{padding-top:2rem!important}.padding-left--lg{padding-left:2rem!important}.padding-bottom--lg{padding-bottom:2rem!important}.padding-right--lg{padding-right:2rem!important}.padding-vert--lg{padding-top:2rem!important;padding-bottom:2rem!important}.padding-horiz--lg{padding-left:2rem!important;padding-right:2rem!important}.padding--xl{padding:5rem!important}.padding-top--xl{padding-top:5rem!important}.padding-left--xl{padding-left:5rem!important}.padding-bottom--xl{padding-bottom:5rem!important}.padding-right--xl{padding-right:5rem!important}.padding-vert--xl{padding-top:5rem!important;padding-bottom:5rem!important}.padding-horiz--xl{padding-left:5rem!important;padding-right:5rem!important}code{background-color:var(--ifm-code-background);border-radius:var(--ifm-code-border-radius);font-family:var(--ifm-font-family-monospace);font-size:var(--ifm-code-font-size);padding:var(--ifm-code-padding-vertical)var(--ifm-code-padding-horizontal);vertical-align:middle;border:.1rem solid rgba(0,0,0,.1)}a code{color:inherit}pre{background-color:var(--ifm-pre-background);border-radius:var(--ifm-pre-border-radius);color:var(--ifm-pre-color);font:var(--ifm-code-font-size)/var(--ifm-pre-line-height)var(--ifm-font-family-monospace);margin:0 0 var(--ifm-spacing-vertical);padding:var(--ifm-pre-padding);overflow:auto}pre code{font-size:100%;line-height:inherit;background-color:transparent;border:none;padding:0}kbd{background-color:var(--ifm-color-emphasis-0);border:1px solid var(--ifm-color-emphasis-400);box-shadow:inset 0 -1px 0 var(--ifm-color-emphasis-400);color:var(--ifm-color-emphasis-800);font:80% var(--ifm-font-family-monospace);border-radius:.2rem;padding:.15rem .3rem}h1,h2,h3,h4,h5,h6{color:var(--ifm-heading-color);font-family:var(--ifm-heading-font-family);font-weight:var(--ifm-heading-font-weight);line-height:var(--ifm-heading-line-height);margin:var(--ifm-heading-margin-top)0 var(--ifm-heading-margin-bottom)0}h1{font-size:var(--ifm-h1-font-size)}h2{font-size:var(--ifm-h2-font-size)}h3{font-size:var(--ifm-h3-font-size)}h4{font-size:var(--ifm-h4-font-size)}h5{font-size:var(--ifm-h5-font-size)}h6{font-size:var(--ifm-h6-font-size)}img{max-width:100%}img[align=right]{padding-left:var(--image-alignment-padding)}img[align=left]{padding-right:var(--image-alignment-padding)}.markdown{--ifm-h1-vertical-rhythm-top:3;--ifm-h2-vertical-rhythm-top:2;--ifm-h3-vertical-rhythm-top:1.5;--ifm-heading-vertical-rhythm-top:1.25;--ifm-h1-vertical-rhythm-bottom:1.25;--ifm-heading-vertical-rhythm-bottom:1}.markdown:before{content:"";display:table}.markdown:after{clear:both;content:"";display:table}.markdown>:last-child{margin-bottom:0!important}.markdown h1:first-child{--ifm-h1-font-size:3rem;margin-bottom:calc(var(--ifm-h1-vertical-rhythm-bottom)*var(--ifm-leading))}.markdown>h2{--ifm-h2-font-size:2rem;margin-bottom:calc(var(--ifm-heading-vertical-rhythm-bottom)*var(--ifm-leading));margin-top:calc(var(--ifm-h2-vertical-rhythm-top)*var(--ifm-leading))}.markdown>h3{--ifm-h3-font-size:1.5rem;margin-bottom:calc(var(--ifm-heading-vertical-rhythm-bottom)*var(--ifm-leading));margin-top:calc(var(--ifm-h3-vertical-rhythm-top)*var(--ifm-leading))}.markdown>h4,.markdown>h5,.markdown>h6{margin-bottom:calc(var(--ifm-heading-vertical-rhythm-bottom)*var(--ifm-leading));margin-top:calc(var(--ifm-heading-vertical-rhythm-top)*var(--ifm-leading))}.markdown>pre,.markdown>ul,.markdown>p{margin-bottom:var(--ifm-leading)}.markdown li{word-wrap:break-word}.markdown li>p{margin-top:var(--ifm-list-paragraph-margin)}.markdown li+li{margin-top:var(--ifm-list-item-margin)}ul,ol{margin:0 0 var(--ifm-list-margin);padding-left:var(--ifm-list-left-padding)}ol ol,ul ol{list-style-type:lower-roman}ul ul,ul ol,ol ol,ol ul{margin:0}ul ul ol,ul ol ol,ol ul ol,ol ol ol{list-style-type:lower-alpha}table{border-collapse:collapse;margin-bottom:var(--ifm-spacing-vertical);display:block;overflow:auto}table thead tr{border-bottom:2px solid var(--ifm-table-border-color)}table thead{background-color:var(--ifm-table-stripe-background)}table tr{background-color:var(--ifm-table-background);border-top:var(--ifm-table-border-width)solid var(--ifm-table-border-color)}table tr:nth-child(2n){background-color:var(--ifm-table-stripe-background)}table th,table td{border:var(--ifm-table-border-width)solid var(--ifm-table-border-color);padding:var(--ifm-table-cell-padding)}table th{background-color:var(--ifm-table-head-background);color:var(--ifm-table-head-color);font-weight:var(--ifm-table-head-font-weight)}table td{color:var(--ifm-table-cell-color)}strong{font-weight:var(--ifm-font-weight-bold)}a{color:var(--ifm-link-color);-webkit-text-decoration:var(--ifm-link-decoration);text-decoration:var(--ifm-link-decoration);transition:color var(--ifm-transition-fast)var(--ifm-transition-timing-default)}a:hover{color:var(--ifm-link-hover-color);-webkit-text-decoration:var(--ifm-link-hover-decoration);text-decoration:var(--ifm-link-hover-decoration)}a:not([href]){-webkit-text-decoration:none;text-decoration:none}p{margin:0 0 var(--ifm-paragraph-margin-bottom)}blockquote{border-left:var(--ifm-blockquote-border-left-width)solid var(--ifm-blockquote-border-color);box-shadow:var(--ifm-blockquote-shadow);color:var(--ifm-blockquote-color);font-size:var(--ifm-blockquote-font-size);margin:0 0 var(--ifm-spacing-vertical);padding:var(--ifm-blockquote-padding-vertical)var(--ifm-blockquote-padding-horizontal)}blockquote>:first-child{margin-top:0}blockquote>:last-child{margin-bottom:0}hr{background-color:var(--ifm-hr-background-color);height:var(--ifm-hr-height);margin:var(--ifm-hr-margin-vertical)0;border:0}.shadow--lw{box-shadow:var(--ifm-global-shadow-lw)!important}.shadow--md{box-shadow:var(--ifm-global-shadow-md)!important}.shadow--tl{box-shadow:var(--ifm-global-shadow-tl)!important}.text--primary{color:var(--ifm-color-primary)}.text--secondary{color:var(--ifm-color-secondary)}.text--success{color:var(--ifm-color-success)}.text--info{color:var(--ifm-color-info)}.text--warning{color:var(--ifm-color-warning)}.text--danger{color:var(--ifm-color-danger)}.text--center{text-align:center}.text--left{text-align:left}.text--justify{text-align:justify}.text--right{text-align:right}.text--capitalize{text-transform:capitalize}.text--lowercase{text-transform:lowercase}.text--uppercase{text-transform:uppercase}.text--light{font-weight:var(--ifm-font-weight-light)}.text--normal{font-weight:var(--ifm-font-weight-normal)}.text--semibold{font-weight:var(--ifm-font-weight-semibold)}.text--bold{font-weight:var(--ifm-font-weight-bold)}.text--italic{font-style:italic}.text--truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.text--break{word-wrap:break-word!important;word-break:break-word!important}.text--no-decoration,.text--no-decoration:hover{-webkit-text-decoration:none;text-decoration:none}.clean-btn{color:inherit;cursor:pointer;background:0 0;border:none;padding:0;font-family:inherit}.clean-list{padding-left:0;list-style:none}.alert--primary{--ifm-alert-background-color:var(--ifm-color-primary-contrast-background);--ifm-alert-background-color-highlight:rgba(53,120,229,.15);--ifm-alert-foreground-color:var(--ifm-color-primary-contrast-foreground);--ifm-alert-border-color:var(--ifm-color-primary-dark)}.alert--secondary{--ifm-alert-background-color:var(--ifm-color-secondary-contrast-background);--ifm-alert-background-color-highlight:rgba(235,237,240,.15);--ifm-alert-foreground-color:var(--ifm-color-secondary-contrast-foreground);--ifm-alert-border-color:var(--ifm-color-secondary-dark)}.alert--success{--ifm-alert-background-color:var(--ifm-color-success-contrast-background);--ifm-alert-background-color-highlight:rgba(0,164,0,.15);--ifm-alert-foreground-color:var(--ifm-color-success-contrast-foreground);--ifm-alert-border-color:var(--ifm-color-success-dark)}.alert--info{--ifm-alert-background-color:var(--ifm-color-info-contrast-background);--ifm-alert-background-color-highlight:rgba(84,199,236,.15);--ifm-alert-foreground-color:var(--ifm-color-info-contrast-foreground);--ifm-alert-border-color:var(--ifm-color-info-dark)}.alert--warning{--ifm-alert-background-color:var(--ifm-color-warning-contrast-background);--ifm-alert-background-color-highlight:rgba(255,186,0,.15);--ifm-alert-foreground-color:var(--ifm-color-warning-contrast-foreground);--ifm-alert-border-color:var(--ifm-color-warning-dark)}.alert--danger{--ifm-alert-background-color:var(--ifm-color-danger-contrast-background);--ifm-alert-background-color-highlight:rgba(250,56,62,.15);--ifm-alert-foreground-color:var(--ifm-color-danger-contrast-foreground);--ifm-alert-border-color:var(--ifm-color-danger-dark)}.alert{--ifm-code-background:var(--ifm-alert-background-color-highlight);--ifm-link-color:var(--ifm-alert-foreground-color);--ifm-link-hover-color:var(--ifm-alert-foreground-color);--ifm-link-decoration:underline;--ifm-tabs-color:var(--ifm-alert-foreground-color);--ifm-tabs-color-active:var(--ifm-alert-foreground-color);--ifm-tabs-color-active-border:var(--ifm-alert-border-color);background-color:var(--ifm-alert-background-color);border:var(--ifm-alert-border-width)solid var(--ifm-alert-border-color);border-left-width:var(--ifm-alert-border-left-width);border-radius:var(--ifm-alert-border-radius);box-shadow:var(--ifm-alert-shadow);color:var(--ifm-alert-foreground-color);padding:var(--ifm-alert-padding-vertical)var(--ifm-alert-padding-horizontal)}.alert__heading{font:bold var(--ifm-h5-font-size)/var(--ifm-heading-line-height)var(--ifm-heading-font-family);text-transform:uppercase;align-items:center;margin-bottom:.5rem;display:flex}.alert__icon{margin-right:.4em;display:inline-flex}.alert__icon svg{fill:var(--ifm-alert-foreground-color);stroke:var(--ifm-alert-foreground-color);stroke-width:0}.alert .close{color:var(--ifm-alert-foreground-color);margin:calc(var(--ifm-alert-padding-vertical)*-1)calc(var(--ifm-alert-padding-horizontal)*-1)0 0;opacity:.75}.alert .close:hover,.alert .close:focus{opacity:1}.alert a{-webkit-text-decoration-color:var(--ifm-alert-border-color);text-decoration-color:var(--ifm-alert-border-color)}.alert a:hover{text-decoration-thickness:2px}.avatar{column-gap:var(--ifm-avatar-intro-margin);display:flex}.avatar__photo{height:var(--ifm-avatar-photo-size);width:var(--ifm-avatar-photo-size);border-radius:50%;display:block;overflow:hidden}.avatar__photo--sm{--ifm-avatar-photo-size:2rem}.avatar__photo--lg{--ifm-avatar-photo-size:4rem}.avatar__photo--xl{--ifm-avatar-photo-size:6rem}.avatar__intro{text-align:var(--ifm-avatar-intro-alignment);flex-direction:column;flex:1;justify-content:center;display:flex}.avatar__name{font:bold var(--ifm-h4-font-size)/var(--ifm-heading-line-height)var(--ifm-font-family-base)}.avatar__subtitle{margin-top:.25rem}.avatar--vertical{--ifm-avatar-intro-alignment:center;--ifm-avatar-intro-margin:.5rem;flex-direction:column;align-items:center}.badge{background-color:var(--ifm-badge-background-color);border:var(--ifm-badge-border-width)solid var(--ifm-badge-border-color);border-radius:var(--ifm-badge-border-radius);color:var(--ifm-badge-color);font-size:75%;font-weight:var(--ifm-font-weight-bold);padding:var(--ifm-badge-padding-vertical)var(--ifm-badge-padding-horizontal);line-height:1;display:inline-block}.badge--primary{--ifm-badge-background-color:var(--ifm-color-primary);--ifm-badge-border-color:var(--ifm-badge-background-color)}.badge--secondary{--ifm-badge-background-color:var(--ifm-color-secondary);--ifm-badge-border-color:var(--ifm-badge-background-color);color:var(--ifm-color-black)}.badge--success{--ifm-badge-background-color:var(--ifm-color-success);--ifm-badge-border-color:var(--ifm-badge-background-color)}.badge--info{--ifm-badge-background-color:var(--ifm-color-info);--ifm-badge-border-color:var(--ifm-badge-background-color)}.badge--warning{--ifm-badge-background-color:var(--ifm-color-warning);--ifm-badge-border-color:var(--ifm-badge-background-color)}.badge--danger{--ifm-badge-background-color:var(--ifm-color-danger);--ifm-badge-border-color:var(--ifm-badge-background-color)}.breadcrumbs{margin-bottom:0;padding-left:0}.breadcrumbs__item{display:inline-block}.breadcrumbs__item:not(:last-child):after{background:var(--ifm-breadcrumb-separator)center;content:" ";filter:var(--ifm-breadcrumb-separator-filter);height:calc(var(--ifm-breadcrumb-separator-size)*var(--ifm-breadcrumb-size-multiplier)*var(--ifm-breadcrumb-separator-size-multiplier));margin:0 var(--ifm-breadcrumb-spacing);opacity:.5;width:calc(var(--ifm-breadcrumb-separator-size)*var(--ifm-breadcrumb-size-multiplier)*var(--ifm-breadcrumb-separator-size-multiplier));display:inline-block}.breadcrumbs__item--active .breadcrumbs__link{background:var(--ifm-breadcrumb-item-background-active);color:var(--ifm-breadcrumb-color-active)}.breadcrumbs__link{border-radius:var(--ifm-breadcrumb-border-radius);color:var(--ifm-font-color-base);font-size:calc(1rem*var(--ifm-breadcrumb-size-multiplier));padding:calc(var(--ifm-breadcrumb-padding-vertical)*var(--ifm-breadcrumb-size-multiplier))calc(var(--ifm-breadcrumb-padding-horizontal)*var(--ifm-breadcrumb-size-multiplier));transition-property:background,color;transition-duration:var(--ifm-transition-fast);transition-timing-function:var(--ifm-transition-timing-default);display:inline-block}.breadcrumbs__link:link:hover,.breadcrumbs__link:visited:hover,area[href].breadcrumbs__link:hover{background:var(--ifm-breadcrumb-item-background-active);-webkit-text-decoration:none;text-decoration:none}.breadcrumbs__link:any-link:hover{background:var(--ifm-breadcrumb-item-background-active);-webkit-text-decoration:none;text-decoration:none}.breadcrumbs--sm{--ifm-breadcrumb-size-multiplier:.8}.breadcrumbs--lg{--ifm-breadcrumb-size-multiplier:1.2}.button{background-color:var(--ifm-button-background-color);border:var(--ifm-button-border-width)solid var(--ifm-button-border-color);border-radius:var(--ifm-button-border-radius);color:var(--ifm-button-color);cursor:pointer;font-size:calc(.875rem*var(--ifm-button-size-multiplier));font-weight:var(--ifm-button-font-weight);padding:calc(var(--ifm-button-padding-vertical)*var(--ifm-button-size-multiplier))calc(var(--ifm-button-padding-horizontal)*var(--ifm-button-size-multiplier));text-align:center;-webkit-user-select:none;user-select:none;vertical-align:middle;white-space:nowrap;transition-property:color,background,border-color;transition-duration:var(--ifm-button-transition-duration);transition-timing-function:var(--ifm-transition-timing-default);line-height:1.5;display:inline-block}.button:hover{color:var(--ifm-button-color);-webkit-text-decoration:none;text-decoration:none}.button--outline{--ifm-button-background-color:transparent;--ifm-button-color:var(--ifm-button-border-color)}.button--outline:hover{--ifm-button-background-color:var(--ifm-button-border-color)}.button--outline:hover,.button--outline:active,.button--outline.button--active{--ifm-button-color:var(--ifm-font-color-base-inverse)}.button--link{--ifm-button-background-color:transparent;--ifm-button-border-color:transparent;color:var(--ifm-link-color);-webkit-text-decoration:var(--ifm-link-decoration);text-decoration:var(--ifm-link-decoration)}.button--link:hover,.button--link:active,.button--link.button--active{color:var(--ifm-link-hover-color);-webkit-text-decoration:var(--ifm-link-hover-decoration);text-decoration:var(--ifm-link-hover-decoration)}.button.disabled,.button:disabled,.button[disabled]{opacity:.65;pointer-events:none}.button--sm{--ifm-button-size-multiplier:.8}.button--lg{--ifm-button-size-multiplier:1.35}.button--block{width:100%;display:block}.button.button--secondary{color:var(--ifm-color-gray-900)}.button.button--secondary.button--outline:not(.button--active):not(:hover){color:var(--ifm-font-color-base)}:where(.button--primary){--ifm-button-background-color:var(--ifm-color-primary);--ifm-button-border-color:var(--ifm-color-primary)}:where(.button--primary):not(.button--outline):hover{--ifm-button-background-color:var(--ifm-color-primary-dark);--ifm-button-border-color:var(--ifm-color-primary-dark)}.button--primary:active,.button--primary.button--active{--ifm-button-background-color:var(--ifm-color-primary-darker);--ifm-button-border-color:var(--ifm-color-primary-darker)}:where(.button--secondary){--ifm-button-background-color:var(--ifm-color-secondary);--ifm-button-border-color:var(--ifm-color-secondary)}:where(.button--secondary):not(.button--outline):hover{--ifm-button-background-color:var(--ifm-color-secondary-dark);--ifm-button-border-color:var(--ifm-color-secondary-dark)}.button--secondary:active,.button--secondary.button--active{--ifm-button-background-color:var(--ifm-color-secondary-darker);--ifm-button-border-color:var(--ifm-color-secondary-darker)}:where(.button--success){--ifm-button-background-color:var(--ifm-color-success);--ifm-button-border-color:var(--ifm-color-success)}:where(.button--success):not(.button--outline):hover{--ifm-button-background-color:var(--ifm-color-success-dark);--ifm-button-border-color:var(--ifm-color-success-dark)}.button--success:active,.button--success.button--active{--ifm-button-background-color:var(--ifm-color-success-darker);--ifm-button-border-color:var(--ifm-color-success-darker)}:where(.button--info){--ifm-button-background-color:var(--ifm-color-info);--ifm-button-border-color:var(--ifm-color-info)}:where(.button--info):not(.button--outline):hover{--ifm-button-background-color:var(--ifm-color-info-dark);--ifm-button-border-color:var(--ifm-color-info-dark)}.button--info:active,.button--info.button--active{--ifm-button-background-color:var(--ifm-color-info-darker);--ifm-button-border-color:var(--ifm-color-info-darker)}:where(.button--warning){--ifm-button-background-color:var(--ifm-color-warning);--ifm-button-border-color:var(--ifm-color-warning)}:where(.button--warning):not(.button--outline):hover{--ifm-button-background-color:var(--ifm-color-warning-dark);--ifm-button-border-color:var(--ifm-color-warning-dark)}.button--warning:active,.button--warning.button--active{--ifm-button-background-color:var(--ifm-color-warning-darker);--ifm-button-border-color:var(--ifm-color-warning-darker)}:where(.button--danger){--ifm-button-background-color:var(--ifm-color-danger);--ifm-button-border-color:var(--ifm-color-danger)}:where(.button--danger):not(.button--outline):hover{--ifm-button-background-color:var(--ifm-color-danger-dark);--ifm-button-border-color:var(--ifm-color-danger-dark)}.button--danger:active,.button--danger.button--active{--ifm-button-background-color:var(--ifm-color-danger-darker);--ifm-button-border-color:var(--ifm-color-danger-darker)}.button-group{gap:var(--ifm-button-group-spacing);display:inline-flex}.button-group>.button:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.button-group>.button:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.button-group--block{justify-content:stretch;display:flex}.button-group--block>.button{flex-grow:1}.card{background-color:var(--ifm-card-background-color);border-radius:var(--ifm-card-border-radius);box-shadow:var(--ifm-global-shadow-lw);flex-direction:column;display:flex;overflow:hidden}.card--full-height{height:100%}.card__image{padding-top:var(--ifm-card-vertical-spacing)}.card__image:first-child{padding-top:0}.card__header,.card__body,.card__footer{padding:var(--ifm-card-vertical-spacing)var(--ifm-card-horizontal-spacing)}.card__header:not(:last-child),.card__body:not(:last-child),.card__footer:not(:last-child){padding-bottom:0}.card__header>:last-child,.card__body>:last-child,.card__footer>:last-child{margin-bottom:0}.card__footer{margin-top:auto}.table-of-contents{padding:var(--ifm-toc-padding-vertical)0;margin-bottom:0;font-size:.8rem}.table-of-contents,.table-of-contents ul{padding-left:var(--ifm-toc-padding-horizontal);list-style:none}.table-of-contents li{margin:var(--ifm-toc-padding-vertical)var(--ifm-toc-padding-horizontal)}.table-of-contents__left-border{border-left:1px solid var(--ifm-toc-border-color)}.table-of-contents__link{color:var(--ifm-toc-link-color);display:block}.table-of-contents__link:hover,.table-of-contents__link:hover code,.table-of-contents__link--active,.table-of-contents__link--active code{color:var(--ifm-color-primary);-webkit-text-decoration:none;text-decoration:none}.close{color:var(--ifm-color-black);float:right;font-size:1.5rem;font-weight:var(--ifm-font-weight-bold);opacity:.5;transition:opacity var(--ifm-transition-fast)var(--ifm-transition-timing-default);padding:1rem;line-height:1}.close:hover{opacity:.7}.close:focus{opacity:.8}.dropdown{font-weight:var(--ifm-dropdown-font-weight);vertical-align:top;display:inline-flex;position:relative}.dropdown--hoverable:hover .dropdown__menu,.dropdown--show .dropdown__menu{opacity:1;pointer-events:all;visibility:visible;transform:translateY(-1px)}.dropdown--right .dropdown__menu{left:inherit;right:0}.dropdown--nocaret .navbar__link:after{content:none!important}.dropdown__menu{background-color:var(--ifm-dropdown-background-color);border-radius:var(--ifm-global-radius);box-shadow:var(--ifm-global-shadow-md);opacity:0;pointer-events:none;min-width:10rem;max-height:80vh;left:0;top:calc(100% - var(--ifm-navbar-item-padding-vertical) + .3rem);visibility:hidden;z-index:var(--ifm-z-index-dropdown);transition-property:opacity,transform,visibility;transition-duration:var(--ifm-transition-fast);transition-timing-function:var(--ifm-transition-timing-default);padding:.5rem;list-style:none;position:absolute;overflow-y:auto;transform:translateY(-.625rem)}.dropdown__link{color:var(--ifm-dropdown-link-color);white-space:nowrap;border-radius:.25rem;margin-top:.2rem;padding:.25rem .5rem;font-size:.875rem;display:block}.dropdown__link:hover,.dropdown__link--active{background-color:var(--ifm-dropdown-hover-background-color);color:var(--ifm-dropdown-link-color);-webkit-text-decoration:none;text-decoration:none}.dropdown__link--active,.dropdown__link--active:hover{--ifm-dropdown-link-color:var(--ifm-link-color)}.dropdown>.navbar__link:after{content:"";border:.4em solid transparent;border-top-color:currentColor;border-bottom:0 solid;margin-left:.3em;display:inline-block;position:relative;top:2px;transform:translateY(-50%)}.footer{background-color:var(--ifm-footer-background-color);color:var(--ifm-footer-color);padding:var(--ifm-footer-padding-vertical)var(--ifm-footer-padding-horizontal)}.footer--dark{--ifm-footer-background-color:#303846;--ifm-footer-color:var(--ifm-footer-link-color);--ifm-footer-link-color:var(--ifm-color-secondary);--ifm-footer-title-color:var(--ifm-color-white)}.footer__links{margin-bottom:1rem}.footer__link-item{color:var(--ifm-footer-link-color);line-height:2}.footer__link-item:hover{color:var(--ifm-footer-link-hover-color)}.footer__link-separator{margin:0 var(--ifm-footer-link-horizontal-spacing)}.footer__logo{max-width:var(--ifm-footer-logo-max-width);margin-top:1rem}.footer__title{color:var(--ifm-footer-title-color);font:bold var(--ifm-h4-font-size)/var(--ifm-heading-line-height)var(--ifm-font-family-base);margin-bottom:var(--ifm-heading-margin-bottom)}.footer__item{margin-top:0}.footer__items{margin-bottom:0}[type=checkbox]{padding:0}.hero{background-color:var(--ifm-hero-background-color);color:var(--ifm-hero-text-color);align-items:center;padding:4rem 2rem;display:flex}.hero--primary{--ifm-hero-background-color:var(--ifm-color-primary);--ifm-hero-text-color:var(--ifm-font-color-base-inverse)}.hero--dark{--ifm-hero-background-color:#303846;--ifm-hero-text-color:var(--ifm-color-white)}.hero__title{font-size:3rem}.hero__subtitle{font-size:1.5rem}.menu{font-weight:var(--ifm-font-weight-semibold);overflow-x:hidden}.menu__list{margin:0;padding-left:0;list-style:none}.menu__list .menu__list{padding-left:var(--ifm-menu-link-padding-horizontal);flex:0 0 100%;margin-top:.25rem}.menu__list-item:not(:first-child){margin-top:.25rem}.menu__list-item--collapsed .menu__list{height:0;overflow:hidden}.menu__list-item--collapsed .menu__link--sublist:after,.menu__list-item--collapsed .menu__caret:before{transform:rotate(90deg)}.menu__list-item-collapsible{transition:background var(--ifm-transition-fast)var(--ifm-transition-timing-default);border-radius:.25rem;flex-wrap:wrap;display:flex;position:relative}.menu__list-item-collapsible:hover,.menu__list-item-collapsible--active{background:var(--ifm-menu-color-background-hover)}.menu__list-item-collapsible .menu__link:hover,.menu__list-item-collapsible .menu__link--active{background:0 0!important}.menu__link,.menu__caret{transition:background var(--ifm-transition-fast)var(--ifm-transition-timing-default);border-radius:.25rem;align-items:center;display:flex}.menu__link:hover,.menu__caret:hover{background:var(--ifm-menu-color-background-hover)}.menu__link{color:var(--ifm-menu-color);padding:var(--ifm-menu-link-padding-vertical)var(--ifm-menu-link-padding-horizontal);flex:1;line-height:1.25}.menu__link:hover{color:var(--ifm-menu-color);transition:color var(--ifm-transition-fast)var(--ifm-transition-timing-default);-webkit-text-decoration:none;text-decoration:none}.menu__link--sublist-caret:after{content:"";background:var(--ifm-menu-link-sublist-icon)50%/2rem 2rem;min-width:1.25rem;filter:var(--ifm-menu-link-sublist-icon-filter);width:1.25rem;height:1.25rem;transition:transform var(--ifm-transition-fast)linear;margin-left:auto;transform:rotate(180deg)}.menu__link--active,.menu__link--active:hover{color:var(--ifm-menu-color-active)}.menu__link--active:not(.menu__link--sublist){background-color:var(--ifm-menu-color-background-active)}.menu__caret{padding:var(--ifm-menu-link-padding-vertical)var(--ifm-menu-link-padding-horizontal)}.menu__caret:before{content:"";background:var(--ifm-menu-link-sublist-icon)50%/2rem 2rem;filter:var(--ifm-menu-link-sublist-icon-filter);width:1.25rem;height:1.25rem;transition:transform var(--ifm-transition-fast)linear;transform:rotate(180deg)}html[data-theme=dark],.navbar--dark{--ifm-menu-link-sublist-icon-filter:invert(100%)sepia(94%)saturate(17%)hue-rotate(223deg)brightness(104%)contrast(98%)}.navbar{background-color:var(--ifm-navbar-background-color);box-shadow:var(--ifm-navbar-shadow);height:var(--ifm-navbar-height);padding:var(--ifm-navbar-padding-vertical)var(--ifm-navbar-padding-horizontal);display:flex}.navbar>.container,.navbar>.container-fluid{display:flex}.navbar--fixed-top{z-index:var(--ifm-z-index-fixed);position:sticky;top:0}.navbar__inner{flex-wrap:wrap;justify-content:space-between;width:100%;display:flex}.navbar__brand{color:var(--ifm-navbar-link-color);align-items:center;min-width:0;margin-right:1rem;display:flex}.navbar__brand:hover{color:var(--ifm-navbar-link-hover-color);-webkit-text-decoration:none;text-decoration:none}.navbar__title{flex:auto}.navbar__toggle{margin-right:.5rem;display:none}.navbar__logo{flex:none;height:2rem;margin-right:.5rem}.navbar__logo img{height:100%}.navbar__items{flex:1;align-items:center;min-width:0;display:flex}.navbar__items--center{flex:none}.navbar__items--center .navbar__brand{margin:0}.navbar__items--center+.navbar__items--right{flex:1}.navbar__items--right{flex:none;justify-content:flex-end}.navbar__items--right>:last-child{padding-right:0}.navbar__item{padding:var(--ifm-navbar-item-padding-vertical)var(--ifm-navbar-item-padding-horizontal);display:inline-block}.navbar__item.dropdown .navbar__link:not([href]){pointer-events:none}.navbar__link{color:var(--ifm-navbar-link-color);font-weight:var(--ifm-font-weight-semibold)}.navbar__link:hover,.navbar__link--active{color:var(--ifm-navbar-link-hover-color);-webkit-text-decoration:none;text-decoration:none}.navbar--dark,.navbar--primary{--ifm-menu-color:var(--ifm-color-gray-300);--ifm-navbar-link-color:var(--ifm-color-gray-100);--ifm-navbar-search-input-background-color:rgba(255,255,255,.1);--ifm-navbar-search-input-placeholder-color:rgba(255,255,255,.5);color:var(--ifm-color-white)}.navbar--dark{--ifm-navbar-background-color:#242526;--ifm-navbar-link-hover-color:var(--ifm-color-primary);--ifm-menu-color-background-active:rgba(255,255,255,.05);--ifm-navbar-search-input-color:var(--ifm-color-white)}.navbar--primary{--ifm-navbar-background-color:var(--ifm-color-primary);--ifm-navbar-link-hover-color:var(--ifm-color-white);--ifm-menu-color-active:var(--ifm-color-white);--ifm-navbar-search-input-color:var(--ifm-color-emphasis-500)}.navbar__search-input{appearance:none;background:var(--ifm-navbar-search-input-background-color)var(--ifm-navbar-search-input-icon)no-repeat .75rem center/1rem 1rem;color:var(--ifm-navbar-search-input-color);cursor:text;border:none;border-radius:2rem;width:12.5rem;height:2rem;padding:0 .5rem 0 2.25rem;font-size:1rem;display:inline-block}.navbar__search-input::placeholder{color:var(--ifm-navbar-search-input-placeholder-color)}.navbar-sidebar{background-color:var(--ifm-navbar-background-color);box-shadow:var(--ifm-global-shadow-md);opacity:0;visibility:hidden;width:var(--ifm-navbar-sidebar-width);transition-property:opacity,visibility,transform;transition-duration:var(--ifm-transition-fast);transition-timing-function:ease-in-out;position:fixed;top:0;bottom:0;left:0;overflow-x:hidden;transform:translate(-100%)}.navbar-sidebar--show .navbar-sidebar,.navbar-sidebar--show .navbar-sidebar__backdrop{opacity:1;visibility:visible}.navbar-sidebar--show .navbar-sidebar{transform:translate(0,0)}.navbar-sidebar__backdrop{opacity:0;visibility:hidden;transition-property:opacity,visibility;transition-duration:var(--ifm-transition-fast);background-color:rgba(0,0,0,.6);transition-timing-function:ease-in-out;position:fixed;inset:0}.navbar-sidebar__brand{box-shadow:var(--ifm-navbar-shadow);height:var(--ifm-navbar-height);padding:var(--ifm-navbar-padding-vertical)var(--ifm-navbar-padding-horizontal);flex:1;align-items:center;display:flex}.navbar-sidebar__items{height:calc(100% - var(--ifm-navbar-height));transition:transform var(--ifm-transition-fast)ease-in-out;display:flex;transform:translateZ(0)}.navbar-sidebar__items--show-secondary{transform:translate3d(calc((var(--ifm-navbar-sidebar-width))*-1),0,0)}.navbar-sidebar__item{width:calc(var(--ifm-navbar-sidebar-width));flex-shrink:0;padding:.5rem}.navbar-sidebar__back{background:var(--ifm-menu-color-background-active);font-size:15px;font-weight:var(--ifm-button-font-weight);text-align:left;width:calc(100% + 1rem);margin:0 0 .2rem -.5rem;padding:.6rem 1.5rem;position:relative;top:-.5rem}.navbar-sidebar__close{margin-left:auto;display:flex}.pagination{column-gap:var(--ifm-pagination-page-spacing);font-size:var(--ifm-pagination-font-size);padding-left:0;display:flex}.pagination--sm{--ifm-pagination-font-size:.8rem;--ifm-pagination-padding-horizontal:.8rem;--ifm-pagination-padding-vertical:.2rem}.pagination--lg{--ifm-pagination-font-size:1.2rem;--ifm-pagination-padding-horizontal:1.2rem;--ifm-pagination-padding-vertical:.3rem}.pagination__item{display:inline-flex}.pagination__item>span{padding:var(--ifm-pagination-padding-vertical)}.pagination__item--active .pagination__link{background:var(--ifm-pagination-item-active-background);color:var(--ifm-pagination-color-active)}.pagination__item:not(.pagination__item--active):hover .pagination__link{background:var(--ifm-pagination-item-active-background)}.pagination__item--disabled,.pagination__item[disabled]{opacity:.25;pointer-events:none}.pagination__link{border-radius:var(--ifm-pagination-border-radius);color:var(--ifm-font-color-base);padding:var(--ifm-pagination-padding-vertical)var(--ifm-pagination-padding-horizontal);transition:background var(--ifm-transition-fast)var(--ifm-transition-timing-default);display:inline-block}.pagination__link:hover{-webkit-text-decoration:none;text-decoration:none}.pagination-nav{grid-gap:var(--ifm-spacing-horizontal);gap:var(--ifm-spacing-horizontal);grid-template-columns:repeat(2,1fr);display:grid}.pagination-nav__link{border:1px solid var(--ifm-color-emphasis-300);border-radius:var(--ifm-pagination-nav-border-radius);height:100%;line-height:var(--ifm-heading-line-height);padding:var(--ifm-global-spacing);transition:border-color var(--ifm-transition-fast)var(--ifm-transition-timing-default);display:block}.pagination-nav__link:hover{border-color:var(--ifm-pagination-nav-color-hover);-webkit-text-decoration:none;text-decoration:none}.pagination-nav__link--next{text-align:right;grid-column:2/3}.pagination-nav__label{font-size:var(--ifm-h4-font-size);font-weight:var(--ifm-heading-font-weight);word-break:break-word}.pagination-nav__link--prev .pagination-nav__label:before{content:"« "}.pagination-nav__link--next .pagination-nav__label:after{content:" »"}.pagination-nav__sublabel{color:var(--ifm-color-content-secondary);font-size:var(--ifm-h5-font-size);font-weight:var(--ifm-font-weight-semibold);margin-bottom:.25rem}.pills{gap:var(--ifm-pills-spacing);padding-left:0;display:flex}.pills__item{cursor:pointer;font-weight:var(--ifm-font-weight-bold);transition:background var(--ifm-transition-fast)var(--ifm-transition-timing-default);border-radius:.5rem;padding:.25rem 1rem;display:inline-block}.pills__item--active{background:var(--ifm-pills-color-background-active);color:var(--ifm-pills-color-active)}.pills__item:not(.pills__item--active):hover{background:var(--ifm-pills-color-background-active)}.pills--block{justify-content:stretch}.pills--block .pills__item{text-align:center;flex-grow:1}.tabs{color:var(--ifm-tabs-color);font-weight:var(--ifm-font-weight-bold);margin-bottom:0;padding-left:0;display:flex;overflow-x:auto}.tabs__item{border-radius:var(--ifm-global-radius);cursor:pointer;padding:var(--ifm-tabs-padding-vertical)var(--ifm-tabs-padding-horizontal);transition:background-color var(--ifm-transition-fast)var(--ifm-transition-timing-default);border-bottom:3px solid transparent;display:inline-flex}.tabs__item--active{border-bottom-color:var(--ifm-tabs-color-active-border);color:var(--ifm-tabs-color-active);border-bottom-right-radius:0;border-bottom-left-radius:0}.tabs__item:hover{background-color:var(--ifm-hover-overlay)}.tabs--block{justify-content:stretch}.tabs--block .tabs__item{flex-grow:1;justify-content:center}html[data-theme=dark]{--ifm-color-scheme:dark;--ifm-color-emphasis-0:var(--ifm-color-gray-1000);--ifm-color-emphasis-100:var(--ifm-color-gray-900);--ifm-color-emphasis-200:var(--ifm-color-gray-800);--ifm-color-emphasis-300:var(--ifm-color-gray-700);--ifm-color-emphasis-400:var(--ifm-color-gray-600);--ifm-color-emphasis-500:var(--ifm-color-gray-500);--ifm-color-emphasis-600:var(--ifm-color-gray-400);--ifm-color-emphasis-700:var(--ifm-color-gray-300);--ifm-color-emphasis-800:var(--ifm-color-gray-200);--ifm-color-emphasis-900:var(--ifm-color-gray-100);--ifm-color-emphasis-1000:var(--ifm-color-gray-0);--ifm-background-color:#1b1b1d;--ifm-background-surface-color:#242526;--ifm-hover-overlay:rgba(255,255,255,.05);--ifm-color-content:#e3e3e3;--ifm-color-content-secondary:#fff;--ifm-breadcrumb-separator-filter:invert(64%)sepia(11%)saturate(0%)hue-rotate(149deg)brightness(99%)contrast(95%);--ifm-code-background:rgba(255,255,255,.1);--ifm-scrollbar-track-background-color:#444;--ifm-scrollbar-thumb-background-color:#686868;--ifm-scrollbar-thumb-hover-background-color:#7a7a7a;--ifm-table-stripe-background:rgba(255,255,255,.07);--ifm-toc-border-color:var(--ifm-color-emphasis-200);--ifm-color-primary-contrast-background:#102445;--ifm-color-primary-contrast-foreground:#ebf2fc;--ifm-color-secondary-contrast-background:#474748;--ifm-color-secondary-contrast-foreground:#fdfdfe;--ifm-color-success-contrast-background:#003100;--ifm-color-success-contrast-foreground:#e6f6e6;--ifm-color-info-contrast-background:#193c47;--ifm-color-info-contrast-foreground:#eef9fd;--ifm-color-warning-contrast-background:#4d3800;--ifm-color-warning-contrast-foreground:#fff8e6;--ifm-color-danger-contrast-background:#4b1113;--ifm-color-danger-contrast-foreground:#ffebec}@media (width>=1440px){.container{max-width:var(--ifm-container-width-xl)}}@media (width<=996px){.col{--ifm-col-width:100%;flex-basis:var(--ifm-col-width);margin-left:0}.footer{--ifm-footer-padding-horizontal:0}.footer__link-separator{display:none}.footer__col{margin-bottom:calc(var(--ifm-spacing-vertical)*3)}.footer__link-item{width:max-content;display:block}.hero{padding-left:0;padding-right:0}.navbar>.container,.navbar>.container-fluid{padding:0}.navbar__toggle{display:inherit}.navbar__item{display:none}.navbar__search-input{width:9rem}.pills--block,.tabs--block{flex-direction:column}}@media (width<=576px){.markdown h1:first-child{--ifm-h1-font-size:2rem}.markdown>h2{--ifm-h2-font-size:1.5rem}.markdown>h3{--ifm-h3-font-size:1.25rem}}@media (pointer:fine){.thin-scrollbar{scrollbar-width:thin}.thin-scrollbar::-webkit-scrollbar{height:var(--ifm-scrollbar-size);width:var(--ifm-scrollbar-size)}.thin-scrollbar::-webkit-scrollbar-track{background:var(--ifm-scrollbar-track-background-color);border-radius:10px}.thin-scrollbar::-webkit-scrollbar-thumb{background:var(--ifm-scrollbar-thumb-background-color);border-radius:10px}.thin-scrollbar::-webkit-scrollbar-thumb:hover{background:var(--ifm-scrollbar-thumb-hover-background-color)}}@media (prefers-reduced-motion:reduce){:root{--ifm-transition-fast:0s;--ifm-transition-slow:0s}}@media print{.table-of-contents,.footer,.menu,.navbar,.pagination-nav{display:none}.tabs{page-break-inside:avoid}}}@layer docusaurus.theme-common{.themedComponent_mlkZ{display:none}[data-theme=light] .themedComponent--light_NVdE,[data-theme=dark] .themedComponent--dark_xIcU,html:not([data-theme]) .themedComponent--light_NVdE{display:initial}.errorBoundaryError_a6uf{white-space:pre-wrap;color:red}.errorBoundaryFallback_VBag{color:red;padding:.55rem}.anchorTargetStickyNavbar_Vzrq{scroll-margin-top:calc(var(--ifm-navbar-height) + .5rem)}.anchorTargetHideOnScrollNavbar_vjPI{scroll-margin-top:.5rem}.details_lb9f{--docusaurus-details-summary-arrow-size:.38rem;--docusaurus-details-transition:transform .2s ease;--docusaurus-details-decoration-color:grey}.details_lb9f>summary{cursor:pointer;padding-left:1rem;list-style:none;position:relative}.details_lb9f>summary::-webkit-details-marker{display:none}.details_lb9f>summary:before{content:"";border-width:var(--docusaurus-details-summary-arrow-size);border-style:solid;border-color:transparent transparent transparent var(--docusaurus-details-decoration-color);transition:var(--docusaurus-details-transition);transform-origin:calc(var(--docusaurus-details-summary-arrow-size)/2)50%;position:absolute;top:.45rem;left:0;transform:rotate(0)}.details_lb9f[open]:not(.isBrowser_bmU9)>summary:before,.details_lb9f[data-collapsed=false].isBrowser_bmU9>summary:before{transform:rotate(90deg)}.collapsibleContent_i85q{border-top:1px solid var(--docusaurus-details-decoration-color);margin-top:1rem;padding-top:1rem}.collapsibleContent_i85q p:last-child,.details_lb9f>summary>p:last-child{margin-bottom:0}}@layer docusaurus.theme-classic{:root{--docusaurus-progress-bar-color:var(--ifm-color-primary)}#nprogress{pointer-events:none}#nprogress .bar{background:var(--docusaurus-progress-bar-color);z-index:1031;width:100%;height:2px;position:fixed;top:0;left:0}#nprogress .peg{width:100px;height:100%;box-shadow:0 0 10px var(--docusaurus-progress-bar-color),0 0 5px var(--docusaurus-progress-bar-color);opacity:1;position:absolute;right:0;transform:rotate(3deg)translateY(-4px)}.skipToContent_fXgn{z-index:calc(var(--ifm-z-index-fixed) + 1);padding:calc(var(--ifm-global-spacing)/2)var(--ifm-global-spacing);color:var(--ifm-color-emphasis-900);background-color:var(--ifm-background-surface-color);position:fixed;top:1rem;left:100%}.skipToContent_fXgn:focus{box-shadow:var(--ifm-global-shadow-md);left:1rem}.closeButton_CVFx{padding:0;line-height:0}.content_knG7{text-align:center;padding:5px 0;font-size:85%}.content_knG7 a{color:inherit;-webkit-text-decoration:underline;text-decoration:underline}:root{--docusaurus-announcement-bar-height:auto}.announcementBar_mb4j{height:var(--docusaurus-announcement-bar-height);background-color:var(--ifm-color-white);color:var(--ifm-color-black);border-bottom:1px solid var(--ifm-color-emphasis-100);align-items:center;display:flex}html[data-announcement-bar-initially-dismissed=true] .announcementBar_mb4j{display:none}.announcementBarPlaceholder_vyr4{flex:0 0 10px}.announcementBarClose_gvF7{flex:0 0 30px;align-self:stretch}.announcementBarContent_xLdY{flex:auto}@media print{.announcementBar_mb4j{display:none}}@media (width>=997px){:root{--docusaurus-announcement-bar-height:30px}.announcementBarPlaceholder_vyr4,.announcementBarClose_gvF7{flex-basis:50px}}.toggle_vylO{width:2rem;height:2rem}.toggleButton_gllP{-webkit-tap-highlight-color:transparent;width:100%;height:100%;transition:background var(--ifm-transition-fast);border-radius:50%;justify-content:center;align-items:center;display:flex}.toggleButton_gllP:hover{background:var(--ifm-color-emphasis-200)}.toggleIcon_g3eP{display:none}[data-theme-choice=system] .systemToggleIcon_QzmC,[data-theme-choice=light] .lightToggleIcon_pyhR,[data-theme-choice=dark] .darkToggleIcon_wfgR{display:initial}.toggleButtonDisabled_aARS{cursor:not-allowed}.darkNavbarColorModeToggle_X3D1:hover{background:var(--ifm-color-gray-800)}.iconExternalLink_nPIU{margin-left:.3rem}.dropdownNavbarItemMobile_J0Sd{cursor:pointer}.iconLanguage_nlXk{vertical-align:text-bottom;margin-right:5px}.navbarSearchContainer_Bca1:empty{display:none}@media (width<=996px){.navbarSearchContainer_Bca1{right:var(--ifm-navbar-padding-horizontal);position:absolute}}@media (width>=997px){.navbarSearchContainer_Bca1{padding:0 var(--ifm-navbar-item-padding-horizontal)}}.navbarHideable_m1mJ{transition:transform var(--ifm-transition-fast)ease}.navbarHidden_jGov{transform:translateY(calc(-100% - 2px))}@media (width<=996px){.colorModeToggle_DEke{display:none}}.navbar__items--right>:last-child{padding-right:0}.footerLogoLink_BH7S{opacity:.5;transition:opacity var(--ifm-transition-fast)var(--ifm-transition-timing-default)}.footerLogoLink_BH7S:hover{opacity:1}.hash-link{opacity:0;transition:opacity var(--ifm-transition-fast);-webkit-user-select:none;user-select:none;padding-left:.5rem}.hash-link:before{content:"#"}.hash-link:focus,:hover>.hash-link{opacity:1}html,body{height:100%}.mainWrapper_z2l0{flex-direction:column;flex:1 0 auto;display:flex}.docusaurus-mt-lg{margin-top:3rem}#__docusaurus{flex-direction:column;min-height:100%;display:flex}.cardTitleIcon_GcdP{margin-right:.6rem;font-size:1.6rem}.cardTitleText_nuEl{font-size:1.2rem}.cardTitle_mcqP{align-items:center;display:inline-flex}.cardDescription_L2fP{font-size:.8rem}.cardContainer_KhuF{--ifm-link-color:var(--ifm-color-emphasis-800);--ifm-link-hover-color:var(--ifm-color-emphasis-700);--ifm-link-hover-decoration:none;border:1px solid var(--ifm-color-emphasis-200);transition:all var(--ifm-transition-fast)ease;transition-property:border,box-shadow;box-shadow:0 1.5px 3px rgba(0,0,0,.15)}.cardContainer_KhuF:hover{border-color:var(--ifm-color-primary);box-shadow:0 3px 6px rgba(0,0,0,.2)}.cardContainer_KhuF :last-child{margin-bottom:0}.docCardListItem_W1sv{margin-bottom:2rem}.docCardListItem_W1sv>*{height:100%}:root{--docusaurus-tag-list-border:var(--ifm-color-emphasis-300)}.tag_zVej{border:1px solid var(--docusaurus-tag-list-border);transition:border var(--ifm-transition-fast)}.tag_zVej:hover{--docusaurus-tag-list-border:var(--ifm-link-color);-webkit-text-decoration:none;text-decoration:none}.tagRegular_sFm0{border-radius:var(--ifm-global-radius);padding:.2rem .5rem .3rem;font-size:90%}.tagWithCount_h2kH{border-left:0;align-items:center;padding:0 .5rem 0 1rem;display:flex;position:relative}.tagWithCount_h2kH:before,.tagWithCount_h2kH:after{content:"";border:1px solid var(--docusaurus-tag-list-border);transition:inherit;position:absolute;top:50%}.tagWithCount_h2kH:before{border-bottom:0;border-right:0;width:1.18rem;height:1.18rem;right:100%;transform:translate(50%,-50%)rotate(-45deg)}.tagWithCount_h2kH:after{border-radius:50%;width:.5rem;height:.5rem;left:0;transform:translateY(-50%)}.tagWithCount_h2kH span{background:var(--ifm-color-secondary);color:var(--ifm-color-black);border-radius:var(--ifm-global-radius);margin-left:.3rem;padding:.1rem .4rem;font-size:.7rem;line-height:1.2}.tags_jXut{display:inline}.tag_QGVx{margin:0 .4rem .5rem 0;display:inline-block}.iconEdit_Z9Sw{vertical-align:sub;margin-right:.3em}.lastUpdated_JAkA{margin-top:.2rem;font-size:smaller;font-style:italic}@media (width>=997px){.lastUpdated_JAkA{text-align:right}}@media print{.noPrint_WFHX{display:none}}.tocCollapsibleButton_TO0P{font-size:inherit;justify-content:space-between;align-items:center;width:100%;padding:.4rem .8rem;display:flex}.tocCollapsibleButton_TO0P:after{content:"";background:var(--ifm-menu-link-sublist-icon)50% 50%/2rem 2rem no-repeat;filter:var(--ifm-menu-link-sublist-icon-filter);width:1.25rem;height:1.25rem;transition:transform var(--ifm-transition-fast);transform:rotate(180deg)}.tocCollapsibleButtonExpanded_MG3E:after{transform:none}.tocCollapsible_ETCw{background-color:var(--ifm-menu-color-background-active);border-radius:var(--ifm-global-radius);margin:1rem 0}.tocCollapsibleContent_vkbj>ul{border-left:none;border-top:1px solid var(--ifm-color-emphasis-300);padding:.2rem 0;font-size:15px}.tocCollapsibleContent_vkbj ul li{margin:.4rem .8rem}.tocCollapsibleContent_vkbj a{display:block}.tocCollapsibleExpanded_sAul{transform:none}@media (width>=997px){.tocMobile_ITEo{display:none}}@media print{.tocMobile_ITEo{display:none}}.tableOfContents_bqdL{max-height:calc(100vh - (var(--ifm-navbar-height) + 2rem));top:calc(var(--ifm-navbar-height) + 1rem);position:sticky;overflow-y:auto}@media (width<=996px){.tableOfContents_bqdL{display:none}.docItemContainer_F8PC{padding:0 .3rem}}.codeBlockContainer_Ckt0{background:var(--prism-background-color);color:var(--prism-color);margin-bottom:var(--ifm-leading);box-shadow:var(--ifm-global-shadow-lw);border-radius:var(--ifm-code-border-radius)}.codeBlock_bY9V{--ifm-pre-background:var(--prism-background-color);margin:0;padding:0}.codeBlockStandalone_MEMb{padding:0}.codeBlockLines_e6Vv{font:inherit;float:left;min-width:100%;padding:var(--ifm-pre-padding)}.codeBlockLinesWithNumbering_o6Pm{padding:var(--ifm-pre-padding)0;display:table}@media print{.codeBlockLines_e6Vv{white-space:pre-wrap}}:where(:root){--docusaurus-highlighted-code-line-bg:#484d5b}:where([data-theme=dark]){--docusaurus-highlighted-code-line-bg:#646464}.theme-code-block-highlighted-line{background-color:var(--docusaurus-highlighted-code-line-bg);margin:0 calc(-1*var(--ifm-pre-padding));padding:0 var(--ifm-pre-padding);display:block}.codeLine_lJS_{counter-increment:line-count;display:table-row}.codeLineNumber_Tfdd{text-align:right;width:1%;padding:0 var(--ifm-pre-padding);background:var(--ifm-pre-background);overflow-wrap:normal;display:table-cell;position:sticky;left:0}.codeLineNumber_Tfdd:before{content:counter(line-count);opacity:.4}.theme-code-block-highlighted-line .codeLineNumber_Tfdd:before{opacity:.8}.codeLineContent_feaV{padding-right:var(--ifm-pre-padding)}.theme-code-block:hover .copyButtonCopied_Vdqa{opacity:1!important}.copyButtonIcons_IEyt{width:1.125rem;height:1.125rem;position:relative}.copyButtonIcon_TrPX,.copyButtonSuccessIcon_cVMy{fill:currentColor;opacity:inherit;width:inherit;height:inherit;transition:all var(--ifm-transition-fast)ease;position:absolute;top:0;left:0}.copyButtonSuccessIcon_cVMy{opacity:0;color:#00d600;top:50%;left:50%;transform:translate(-50%,-50%)scale(.33)}.copyButtonCopied_Vdqa .copyButtonIcon_TrPX{opacity:0;transform:scale(.33)}.copyButtonCopied_Vdqa .copyButtonSuccessIcon_cVMy{opacity:1;transition-delay:75ms;transform:translate(-50%,-50%)scale(1)}.wordWrapButtonIcon_b1P5{width:1.2rem;height:1.2rem}.wordWrapButtonEnabled_uzNF .wordWrapButtonIcon_b1P5{color:var(--ifm-color-primary)}.buttonGroup_M5ko{right:calc(var(--ifm-pre-padding)/2);top:calc(var(--ifm-pre-padding)/2);column-gap:.2rem;display:flex;position:absolute}.buttonGroup_M5ko button{background:var(--prism-background-color);color:var(--prism-color);border:1px solid var(--ifm-color-emphasis-300);border-radius:var(--ifm-global-radius);transition:opacity var(--ifm-transition-fast)ease-in-out;opacity:0;align-items:center;padding:.4rem;line-height:0;display:flex}.buttonGroup_M5ko button:hover{opacity:1!important}.buttonGroup_M5ko button:focus-visible{opacity:1!important}.theme-code-block:hover .buttonGroup_M5ko button{opacity:.4}.codeBlockContent_QJqH{border-radius:inherit;direction:ltr;position:relative}.codeBlockTitle_OeMC{border-bottom:1px solid var(--ifm-color-emphasis-300);font-size:var(--ifm-code-font-size);padding:.75rem var(--ifm-pre-padding);border-top-left-radius:inherit;border-top-right-radius:inherit;font-weight:500}.codeBlockTitle_OeMC+.codeBlockContent_QJqH .codeBlock_a8dz{border-top-left-radius:0;border-top-right-radius:0}.details_b_Ee{--docusaurus-details-decoration-color:var(--ifm-alert-border-color);--docusaurus-details-transition:transform var(--ifm-transition-fast)ease;margin:0 0 var(--ifm-spacing-vertical);border:1px solid var(--ifm-alert-border-color)}.containsTaskList_mC6p{list-style:none}:not(.containsTaskList_mC6p>li)>.containsTaskList_mC6p{padding-left:0}.img_ev3q{height:auto}.admonition_xJq3{margin-bottom:1em}.admonitionHeading_Gvgb{font:var(--ifm-heading-font-weight)var(--ifm-h5-font-size)/var(--ifm-heading-line-height)var(--ifm-heading-font-family);text-transform:uppercase}.admonitionHeading_Gvgb:not(:last-child){margin-bottom:.3rem}.admonitionHeading_Gvgb code{text-transform:none}.admonitionIcon_Rf37{vertical-align:middle;margin-right:.4em;display:inline-block}.admonitionIcon_Rf37 svg{width:1.6em;height:1.6em;fill:var(--ifm-alert-foreground-color);display:inline-block}.admonitionContent_BuS1>:last-child{margin-bottom:0}.breadcrumbHomeIcon_YNFT{vertical-align:top;width:1.1rem;height:1.1rem;position:relative;top:1px}.breadcrumbsContainer_Z_bl{--ifm-breadcrumb-size-multiplier:.8;margin-bottom:.8rem}@media (width>=997px){.generatedIndexPage_vN6x{max-width:75%!important}}.title_kItE{--ifm-h1-font-size:3rem;margin-bottom:calc(1.25*var(--ifm-leading))}.docItemContainer_Djhp header+*,.docItemContainer_Djhp article>:first-child{margin-top:0}@media (width>=997px){.docItemCol_VOVn{max-width:75%!important}}.backToTopButton_sjWU{background-color:var(--ifm-color-emphasis-200);width:3rem;height:3rem;z-index:calc(var(--ifm-z-index-fixed) - 1);box-shadow:var(--ifm-global-shadow-lw);transition:all var(--ifm-transition-fast)var(--ifm-transition-timing-default);opacity:0;visibility:hidden;border-radius:50%;position:fixed;bottom:1.3rem;right:1.3rem;transform:scale(0)}.backToTopButton_sjWU:after{content:" ";-webkit-mask:var(--ifm-menu-link-sublist-icon)50%/2rem 2rem no-repeat;-webkit-mask:var(--ifm-menu-link-sublist-icon)50%/2rem 2rem no-repeat;mask:var(--ifm-menu-link-sublist-icon)50%/2rem 2rem no-repeat;background-color:var(--ifm-color-emphasis-1000);width:100%;height:100%;display:inline-block}@media (hover:hover){.backToTopButton_sjWU:hover{background-color:var(--ifm-color-emphasis-300)}}.backToTopButtonShow_xfvO{opacity:1;visibility:visible;transform:scale(1)}:root{--docusaurus-collapse-button-bg:transparent;--docusaurus-collapse-button-bg-hover:rgba(0,0,0,.1)}[data-theme=dark]:root{--docusaurus-collapse-button-bg:rgba(255,255,255,.05);--docusaurus-collapse-button-bg-hover:rgba(255,255,255,.1)}@media (width>=997px){.collapseSidebarButton_PEFL{background-color:var(--docusaurus-collapse-button-bg);border:1px solid var(--ifm-toc-border-color);border-radius:0;height:40px;position:sticky;bottom:0;display:block!important}.collapseSidebarButtonIcon_kv0_{margin-top:4px;transform:rotate(180deg)}[dir=rtl] .collapseSidebarButtonIcon_kv0_{transform:rotate(0)}.collapseSidebarButton_PEFL:hover,.collapseSidebarButton_PEFL:focus{background-color:var(--docusaurus-collapse-button-bg-hover)}}.collapseSidebarButton_PEFL{margin:0;display:none}.menuExternalLink_NmtK{align-items:center}.linkLabel_WmDU{line-clamp:2;-webkit-line-clamp:2;-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden}.categoryLink_byQd{overflow:hidden}.menu__link--sublist-caret:after{margin-left:var(--ifm-menu-link-padding-vertical)}.categoryLinkLabel_W154{line-clamp:2;-webkit-line-clamp:2;-webkit-box-orient:vertical;flex:1;display:-webkit-box;overflow:hidden}@media (width>=997px){.menuHtmlItem_M9Kj{padding:var(--ifm-menu-link-padding-vertical)var(--ifm-menu-link-padding-horizontal)}.menu_SIkG{flex-grow:1;padding:.5rem}@supports (scrollbar-gutter:stable){.menu_SIkG{scrollbar-gutter:stable;padding:.5rem 0 .5rem .5rem}}.menuWithAnnouncementBar_GW3s{margin-bottom:var(--docusaurus-announcement-bar-height)}.sidebar_njMd{height:100%;padding-top:var(--ifm-navbar-height);width:var(--doc-sidebar-width);flex-direction:column;display:flex}.sidebarWithHideableNavbar_wUlq{padding-top:0}.sidebarHidden_VK0M{opacity:0;visibility:hidden}.sidebarLogo_isFc{margin:0 var(--ifm-navbar-padding-horizontal);min-height:var(--ifm-navbar-height);max-height:var(--ifm-navbar-height);align-items:center;color:inherit!important;-webkit-text-decoration:none!important;text-decoration:none!important;display:flex!important}.sidebarLogo_isFc img{height:2rem;margin-right:.5rem}}.sidebarLogo_isFc{display:none}@media (width>=997px){.expandButton_TmdG{width:100%;height:100%;transition:background-color var(--ifm-transition-fast)ease;background-color:var(--docusaurus-collapse-button-bg);justify-content:center;align-items:center;display:flex;position:absolute;top:0;right:0}.expandButton_TmdG:hover,.expandButton_TmdG:focus{background-color:var(--docusaurus-collapse-button-bg-hover)}.expandButtonIcon_i1dp{transform:rotate(0)}[dir=rtl] .expandButtonIcon_i1dp{transform:rotate(180deg)}}:root{--doc-sidebar-width:300px;--doc-sidebar-hidden-width:30px}.docSidebarContainer_YfHR{display:none}@media (width>=997px){.docSidebarContainer_YfHR{width:var(--doc-sidebar-width);margin-top:calc(-1*var(--ifm-navbar-height));border-right:1px solid var(--ifm-toc-border-color);will-change:width;transition:width var(--ifm-transition-fast)ease;clip-path:inset(0);display:block}.docSidebarContainerHidden_DPk8{width:var(--doc-sidebar-hidden-width);cursor:pointer}.sidebarViewport_aRkj{height:100%;max-height:100vh;position:sticky;top:0}}.docMainContainer_TBSr{width:100%;display:flex}@media (width>=997px){.docMainContainer_TBSr{max-width:calc(100% - var(--doc-sidebar-width));flex-grow:1}.docMainContainerEnhanced_lQrH{max-width:calc(100% - var(--doc-sidebar-hidden-width))}.docItemWrapperEnhanced_JWYK{max-width:calc(var(--ifm-container-width) + var(--doc-sidebar-width))!important}}.docRoot_UBD9{width:100%;display:flex}.docsWrapper_hBAB{flex:1 0 auto;display:flex}}@layer docusaurus.core{#__docusaurus-base-url-issue-banner-container{display:none}}@layer docusaurus.plugin-debug,docusaurus.theme-mermaid,docusaurus.theme-live-codeblock,docusaurus.theme-search-algolia.docsearch,docusaurus.theme-search-algolia;:root{--ifm-color-primary:#2e8555;--ifm-color-primary-dark:#29784c;--ifm-color-primary-darker:#277148;--ifm-color-primary-darkest:#205d3b;--ifm-color-primary-light:#33925d;--ifm-color-primary-lighter:#359962;--ifm-color-primary-lightest:#3cad6e;--ifm-code-font-size:95%;--docusaurus-highlighted-code-line-bg:rgba(0,0,0,.1)}[data-theme=dark]{--ifm-color-primary:#25c2a0;--ifm-color-primary-dark:#21af90;--ifm-color-primary-darker:#1fa588;--ifm-color-primary-darkest:#1a8870;--ifm-color-primary-light:#29d5b0;--ifm-color-primary-lighter:#32d8b4;--ifm-color-primary-lightest:#4fddbf;--docusaurus-highlighted-code-line-bg:rgba(0,0,0,.3)}.hero_aEcG{align-items:center;gap:3rem;padding:3rem 0;display:flex}.heroImage_xZN7{flex:1;min-width:0}.heroImage_xZN7 img{border-radius:var(--ifm-card-border-radius);width:100%;height:auto}.heroText_ryRM{flex:1;min-width:0}.section_Q9Zo{max-width:calc(960px + 3rem);margin-left:auto;margin-right:auto}.sectionTitle_Ut5p{margin-top:3rem;margin-bottom:1.5rem}.cardGrid_nYON{grid-template-columns:repeat(4,240px);gap:1rem;display:grid}@media (width<=768px){.section_Q9Zo{max-width:calc(480px + 1rem)}.cardGrid_nYON{grid-template-columns:repeat(2,240px)}}@media (width<=520px){.section_Q9Zo{max-width:240px}.cardGrid_nYON{grid-template-columns:240px}}.card_M5pr{border:1px solid var(--ifm-color-emphasis-300);border-radius:var(--ifm-card-border-radius);color:var(--ifm-font-color-base);flex-direction:column;gap:.5rem;padding:1.25rem;-webkit-text-decoration:none;text-decoration:none;transition:border-color .15s,transform .15s;display:flex}.card_M5pr:hover{border-color:var(--ifm-color-primary);color:var(--ifm-font-color-base);-webkit-text-decoration:none;text-decoration:none;transform:translateY(-2px)}.cardIcon_I6MN{width:32px;height:32px}.cardTitle_tke3{margin:0;font-size:1.05rem}.cardDescription_PVP5{color:var(--ifm-color-emphasis-700);margin:0;font-size:.9rem}@media (width<=768px){.hero_aEcG{flex-direction:column}} \ No newline at end of file diff --git a/assets/images/01-config-setup-d3235cdcb5a0f479ec6ea64a73f4e145.png b/assets/images/01-config-setup-d3235cdcb5a0f479ec6ea64a73f4e145.png new file mode 100644 index 00000000..4ec182e2 Binary files /dev/null and b/assets/images/01-config-setup-d3235cdcb5a0f479ec6ea64a73f4e145.png differ diff --git a/assets/images/03-node-running-10c46ddae2f6c04619f40ec81b0245a8.png b/assets/images/03-node-running-10c46ddae2f6c04619f40ec81b0245a8.png new file mode 100644 index 00000000..28ef9200 Binary files /dev/null and b/assets/images/03-node-running-10c46ddae2f6c04619f40ec81b0245a8.png differ diff --git a/assets/images/04-accounts-before-claim-db4ae12b4c14618ede4d7043d684ad12.png b/assets/images/04-accounts-before-claim-db4ae12b4c14618ede4d7043d684ad12.png new file mode 100644 index 00000000..d1a19d24 Binary files /dev/null and b/assets/images/04-accounts-before-claim-db4ae12b4c14618ede4d7043d684ad12.png differ diff --git a/assets/images/05-claimable-vouchers-45183086be2f12f41e7bedf4b437e513.png b/assets/images/05-claimable-vouchers-45183086be2f12f41e7bedf4b437e513.png new file mode 100644 index 00000000..b5d92cfa Binary files /dev/null and b/assets/images/05-claimable-vouchers-45183086be2f12f41e7bedf4b437e513.png differ diff --git a/assets/images/06-claim-submitted-aef27febfd0c2e5ce94ed86b8251b9b6.png b/assets/images/06-claim-submitted-aef27febfd0c2e5ce94ed86b8251b9b6.png new file mode 100644 index 00000000..d31935e3 Binary files /dev/null and b/assets/images/06-claim-submitted-aef27febfd0c2e5ce94ed86b8251b9b6.png differ diff --git a/assets/images/07-claim-tx-in-block-31f6d0a60c02f9e64a47bedb74ce8664.png b/assets/images/07-claim-tx-in-block-31f6d0a60c02f9e64a47bedb74ce8664.png new file mode 100644 index 00000000..7e5e77c6 Binary files /dev/null and b/assets/images/07-claim-tx-in-block-31f6d0a60c02f9e64a47bedb74ce8664.png differ diff --git a/assets/images/08-accounts-after-claim-16248912ad5366599bd9edfd1d4c64eb.png b/assets/images/08-accounts-after-claim-16248912ad5366599bd9edfd1d4c64eb.png new file mode 100644 index 00000000..aa1c38db Binary files /dev/null and b/assets/images/08-accounts-after-claim-16248912ad5366599bd9edfd1d4c64eb.png differ diff --git a/assets/images/account-details-aba1467c4d6729a85e626e0e6044a3f8.png b/assets/images/account-details-aba1467c4d6729a85e626e0e6044a3f8.png new file mode 100644 index 00000000..868932cb Binary files /dev/null and b/assets/images/account-details-aba1467c4d6729a85e626e0e6044a3f8.png differ diff --git a/assets/images/basecamp-install-packages-55eacac9e46011841160caab73d481db.png b/assets/images/basecamp-install-packages-55eacac9e46011841160caab73d481db.png new file mode 100644 index 00000000..8bdcb1af Binary files /dev/null and b/assets/images/basecamp-install-packages-55eacac9e46011841160caab73d481db.png differ diff --git a/assets/images/basecamp-package-manager-fb8c5213bbfea9e8b310e61a044936ad.png b/assets/images/basecamp-package-manager-fb8c5213bbfea9e8b310e61a044936ad.png new file mode 100644 index 00000000..5ae8f6b8 Binary files /dev/null and b/assets/images/basecamp-package-manager-fb8c5213bbfea9e8b310e61a044936ad.png differ diff --git a/assets/images/block-details-6baf61eea5cadd486cfeb906aaba6667.png b/assets/images/block-details-6baf61eea5cadd486cfeb906aaba6667.png new file mode 100644 index 00000000..b0652f2d Binary files /dev/null and b/assets/images/block-details-6baf61eea5cadd486cfeb906aaba6667.png differ diff --git a/assets/images/calc-cpp-buttons-78885e103cdbbb8e471c0fbc6fbfdc01.png b/assets/images/calc-cpp-buttons-78885e103cdbbb8e471c0fbc6fbfdc01.png new file mode 100644 index 00000000..858f5b1f Binary files /dev/null and b/assets/images/calc-cpp-buttons-78885e103cdbbb8e471c0fbc6fbfdc01.png differ diff --git a/assets/images/calc-cpp-result-c33b1cae801fad3be492d3ad9b1a829d.png b/assets/images/calc-cpp-result-c33b1cae801fad3be492d3ad9b1a829d.png new file mode 100644 index 00000000..69f3a4d2 Binary files /dev/null and b/assets/images/calc-cpp-result-c33b1cae801fad3be492d3ad9b1a829d.png differ diff --git a/assets/images/chat-app-screenshot-60c3c1178fc42c4798eebdb6dbdde82e.png b/assets/images/chat-app-screenshot-60c3c1178fc42c4798eebdb6dbdde82e.png new file mode 100644 index 00000000..97d8636a Binary files /dev/null and b/assets/images/chat-app-screenshot-60c3c1178fc42c4798eebdb6dbdde82e.png differ diff --git a/assets/images/cover-traffic-hiding-effect-06146ae7dd2c61d56938f3727631cdca.png b/assets/images/cover-traffic-hiding-effect-06146ae7dd2c61d56938f3727631cdca.png new file mode 100644 index 00000000..c34f8fa4 Binary files /dev/null and b/assets/images/cover-traffic-hiding-effect-06146ae7dd2c61d56938f3727631cdca.png differ diff --git a/assets/images/create-account-611ab7ff4c84c1c68009b7dee647759c.png b/assets/images/create-account-611ab7ff4c84c1c68009b7dee647759c.png new file mode 100644 index 00000000..5d5a63bb Binary files /dev/null and b/assets/images/create-account-611ab7ff4c84c1c68009b7dee647759c.png differ diff --git a/assets/images/create-wallet-52bf24a016f710d8f9773307f6323d53.png b/assets/images/create-wallet-52bf24a016f710d8f9773307f6323d53.png new file mode 100644 index 00000000..34b414b3 Binary files /dev/null and b/assets/images/create-wallet-52bf24a016f710d8f9773307f6323d53.png differ diff --git a/assets/images/dissemination-63373bcaafe873d19e7eace3cb8d25ea.png b/assets/images/dissemination-63373bcaafe873d19e7eace3cb8d25ea.png new file mode 100644 index 00000000..60c8ea7c Binary files /dev/null and b/assets/images/dissemination-63373bcaafe873d19e7eace3cb8d25ea.png differ diff --git a/assets/images/fork-choice-c6b7cc5ee6b45dafeaf8d66efd2d935e.png b/assets/images/fork-choice-c6b7cc5ee6b45dafeaf8d66efd2d935e.png new file mode 100644 index 00000000..61bc0028 Binary files /dev/null and b/assets/images/fork-choice-c6b7cc5ee6b45dafeaf8d66efd2d935e.png differ diff --git a/assets/images/logos-tech-diagram-919265ff862934cbbf1a60d48f18a3c9.png b/assets/images/logos-tech-diagram-919265ff862934cbbf1a60d48f18a3c9.png new file mode 100644 index 00000000..cba20b93 Binary files /dev/null and b/assets/images/logos-tech-diagram-919265ff862934cbbf1a60d48f18a3c9.png differ diff --git a/assets/images/new-account-a29a45ff1e159f0ee28ac86b706da51e.png b/assets/images/new-account-a29a45ff1e159f0ee28ac86b706da51e.png new file mode 100644 index 00000000..2a24aead Binary files /dev/null and b/assets/images/new-account-a29a45ff1e159f0ee28ac86b706da51e.png differ diff --git a/assets/images/no-blocks-found-f3d4874412ebfadc41523d4574353eb6.png b/assets/images/no-blocks-found-f3d4874412ebfadc41523d4574353eb6.png new file mode 100644 index 00000000..5072da84 Binary files /dev/null and b/assets/images/no-blocks-found-f3d4874412ebfadc41523d4574353eb6.png differ diff --git a/assets/images/node-faucet-ea6535ffbfd684702139e140a632bbc8.png b/assets/images/node-faucet-ea6535ffbfd684702139e140a632bbc8.png new file mode 100644 index 00000000..54690d7b Binary files /dev/null and b/assets/images/node-faucet-ea6535ffbfd684702139e140a632bbc8.png differ diff --git a/assets/images/public-account-6a32dafc6c48ff4c791cc507afe7f257.png b/assets/images/public-account-6a32dafc6c48ff4c791cc507afe7f257.png new file mode 100644 index 00000000..99b43cfb Binary files /dev/null and b/assets/images/public-account-6a32dafc6c48ff4c791cc507afe7f257.png differ diff --git a/assets/images/sync-done-2dbab2c6951344c2811cd697344b0c66.png b/assets/images/sync-done-2dbab2c6951344c2811cd697344b0c66.png new file mode 100644 index 00000000..26b7d7b5 Binary files /dev/null and b/assets/images/sync-done-2dbab2c6951344c2811cd697344b0c66.png differ diff --git a/assets/images/sync-progress-23b76c27f31c0c7d53fd0d51b71f4979.png b/assets/images/sync-progress-23b76c27f31c0c7d53fd0d51b71f4979.png new file mode 100644 index 00000000..a4a26851 Binary files /dev/null and b/assets/images/sync-progress-23b76c27f31c0c7d53fd0d51b71f4979.png differ diff --git a/assets/images/time-units-2b8ac53c78fa8b6b06fc1892dfb88ea2.png b/assets/images/time-units-2b8ac53c78fa8b6b06fc1892dfb88ea2.png new file mode 100644 index 00000000..aabbcb0f Binary files /dev/null and b/assets/images/time-units-2b8ac53c78fa8b6b06fc1892dfb88ea2.png differ diff --git a/assets/images/transaction-details-6aa7a80ca79c330c300ba09416746863.png b/assets/images/transaction-details-6aa7a80ca79c330c300ba09416746863.png new file mode 100644 index 00000000..bae95528 Binary files /dev/null and b/assets/images/transaction-details-6aa7a80ca79c330c300ba09416746863.png differ diff --git a/assets/images/two-channels-ef7a3f4432344168849aef090377f87e.png b/assets/images/two-channels-ef7a3f4432344168849aef090377f87e.png new file mode 100644 index 00000000..1e207134 Binary files /dev/null and b/assets/images/two-channels-ef7a3f4432344168849aef090377f87e.png differ diff --git a/assets/images/tx-finalized-eff75e5978815979bc4f2f523c69d926.png b/assets/images/tx-finalized-eff75e5978815979bc4f2f523c69d926.png new file mode 100644 index 00000000..854c1b86 Binary files /dev/null and b/assets/images/tx-finalized-eff75e5978815979bc4f2f523c69d926.png differ diff --git a/assets/images/updated-page-1936af3c9c3552cd02cd3bb895548a50.png b/assets/images/updated-page-1936af3c9c3552cd02cd3bb895548a50.png new file mode 100644 index 00000000..18b33da2 Binary files /dev/null and b/assets/images/updated-page-1936af3c9c3552cd02cd3bb895548a50.png differ diff --git a/assets/js/00254a8b.8d3a3594.js b/assets/js/00254a8b.8d3a3594.js new file mode 100644 index 00000000..dd90b934 --- /dev/null +++ b/assets/js/00254a8b.8d3a3594.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunklogos_docs=self.webpackChunklogos_docs||[]).push([["1492"],{6920(e,n,o){o.r(n),o.d(n,{metadata:()=>r,default:()=>h,frontMatter:()=>t,contentTitle:()=>i,toc:()=>a,assets:()=>c});var r=JSON.parse('{"id":"messaging/concepts/understand-peer-discovery/about-dns-discovery","title":"About DNS discovery","description":"Understand how a node retrieves peer connection details from an ENR tree published in DNS.","source":"@site/docs/messaging/concepts/understand-peer-discovery/about-dns-discovery.md","sourceDirName":"messaging/concepts/understand-peer-discovery","slug":"/messaging/concepts/understand-peer-discovery/dns-discovery","permalink":"/messaging/concepts/understand-peer-discovery/dns-discovery","draft":false,"unlisted":false,"editUrl":"https://github.com/logos-co/logos-docs/tree/main/docs/messaging/concepts/understand-peer-discovery/about-dns-discovery.md","tags":[],"version":"current","sidebarPosition":3,"frontMatter":{"title":"About DNS discovery","doc_type":"concept","product":"messaging","topics":"peer-discovery, dns-discovery, networking","authors":"LordGhostX, fryorcraken","owner":"logos","doc_version":1,"slug":"dns-discovery","sidebar_position":3},"sidebar":"messagingSidebar","previous":{"title":"About static peers","permalink":"/messaging/concepts/understand-peer-discovery/static-peers"},"next":{"title":"About Discv5","permalink":"/messaging/concepts/understand-peer-discovery/discv5"}}'),s=o(4848),d=o(8453);let t={title:"About DNS discovery",doc_type:"concept",product:"messaging",topics:"peer-discovery, dns-discovery, networking",authors:"LordGhostX, fryorcraken",owner:"logos",doc_version:1,slug:"dns-discovery",sidebar_position:3},i="About DNS discovery",c={},a=[{value:"Understand how a node retrieves peer connection details from an ENR tree published in DNS.",id:"understand-how-a-node-retrieves-peer-connection-details-from-an-enr-tree-published-in-dns",level:4},{value:"How DNS discovery works",id:"how-dns-discovery-works",level:2},{value:"Pros and cons",id:"pros-and-cons",level:2}];function l(e){let n={a:"a",code:"code",h1:"h1",h2:"h2",h4:"h4",header:"header",li:"li",ol:"ol",p:"p",pre:"pre",ul:"ul",...(0,d.R)(),...e.components};return(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(n.header,{children:(0,s.jsx)(n.h1,{id:"about-dns-discovery",children:"About DNS discovery"})}),"\n",(0,s.jsx)(n.h4,{id:"understand-how-a-node-retrieves-peer-connection-details-from-an-enr-tree-published-in-dns",children:"Understand how a node retrieves peer connection details from an ENR tree published in DNS."}),"\n",(0,s.jsxs)(n.p,{children:["Built upon the foundation of ",(0,s.jsx)(n.a,{href:"https://eips.ethereum.org/EIPS/eip-1459",children:"EIP-1459: Node Discovery via DNS"}),", ",(0,s.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#dns-discovery",children:"DNS Discovery"})," allows the retrieval of an ",(0,s.jsx)(n.code,{children:"ENR"})," tree from the ",(0,s.jsx)(n.code,{children:"TXT"})," field of a domain name. This approach enables the storage of essential node connection details, including IP, port, and multiaddr. This ",(0,s.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#bootstrapping",children:"bootstrapping"})," method allows anyone to register and publish a domain name for the network, promoting increased decentralisation."]}),"\n",(0,s.jsx)(n.h2,{id:"how-dns-discovery-works",children:"How DNS discovery works"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-mermaid",children:"sequenceDiagram\n Node->>DNS Server: (1) Lookup TXT example.com\n DNS Server--\x3e>Node: (2) enrtree-root:v1 e=U3...3Y ...\n Node->>DNS Server: (3) Lookup TXT U3...3Y.example.com\n DNS Server--\x3e>Node: (4) enrtree-branch:DU...VQ,J3..HU,IC...WE\n Node->> DNS Server: (5) Lookup TXT DU...VQ.example.com\n DNS Server--\x3e>Node: (6) enr:-M-4QLdAB-Kyz...Wt1Mg8\n Node ->> Node: (7) Decode ENR: Peer's connection details\n"})}),"\n",(0,s.jsxs)(n.ol,{children:["\n",(0,s.jsxs)(n.li,{children:["DNS lookup query to retrieve TXT data stored on ",(0,s.jsx)(n.code,{children:"example.com"})," domain."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"enrtree-root"})," is returned, and the value of ",(0,s.jsx)(n.code,{children:"e"})," is the ",(0,s.jsx)(n.code,{children:"enr-root"}),", the root hash of the node subtree."]}),"\n",(0,s.jsxs)(n.li,{children:["DNS lookup query to retrieve TXT data stored on ",(0,s.jsx)(n.code,{children:".example.com"})," domain."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"enrtree-branch"})," is returned; this tree contains hashes of node subtrees."]}),"\n",(0,s.jsxs)(n.li,{children:["DNS lookup query to retrieve TXT data stored on ",(0,s.jsx)(n.code,{children:"DU...VQ.example.com"})," domain, the first leaf of ",(0,s.jsx)(n.code,{children:"enrtree-branch"}),"."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"enr"})," record is returned."]}),"\n",(0,s.jsx)(n.li,{children:"Returned value is decoded, and peer connection details such as IP address and port are learned."}),"\n"]}),"\n",(0,s.jsx)(n.h2,{id:"pros-and-cons",children:"Pros and cons"}),"\n",(0,s.jsx)(n.p,{children:"Pros:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"Low latency, low resource requirements."}),"\n",(0,s.jsx)(n.li,{children:"Easy bootstrap list updates by modifying the domain name, eliminating the need for code changes."}),"\n",(0,s.jsxs)(n.li,{children:["Ability to reference a larger list of nodes by including other domain names in the code or ",(0,s.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#enr",children:"ENR"})," tree."]}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"Cons:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"Vulnerable to censorship: Domain names can be blocked or restricted."}),"\n",(0,s.jsxs)(n.li,{children:["Limited scalability: The listed nodes are at risk of being overwhelmed by receiving all queries. Also, operators must provide their ",(0,s.jsx)(n.code,{children:"ENR"})," to the domain owner for listing."]}),"\n"]})]})}function h(e={}){let{wrapper:n}={...(0,d.R)(),...e.components};return n?(0,s.jsx)(n,{...e,children:(0,s.jsx)(l,{...e})}):l(e)}},8453(e,n,o){o.d(n,{R:()=>t,x:()=>i});var r=o(6540);let s={},d=r.createContext(s);function t(e){let n=r.useContext(d);return r.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function i(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(s):e.components||s:t(e.components),r.createElement(d.Provider,{value:n},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/04e28314.91306938.js b/assets/js/04e28314.91306938.js new file mode 100644 index 00000000..7aad47e9 --- /dev/null +++ b/assets/js/04e28314.91306938.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunklogos_docs=self.webpackChunklogos_docs||[]).push([["4595"],{8054(e,n,t){t.r(n),t.d(n,{metadata:()=>o,default:()=>h,frontMatter:()=>c,contentTitle:()=>i,toc:()=>l,assets:()=>r});var o=JSON.parse('{"id":"lez/transfer-tokens/create-and-transfer-custom-tokens-on-the-logos-execution-zone","title":"Create and transfer custom tokens on the Logos Execution Zone","description":"Use the wallet CLI to create custom tokens and transfer them between public and private accounts.","source":"@site/docs/lez/transfer-tokens/create-and-transfer-custom-tokens-on-the-logos-execution-zone.md","sourceDirName":"lez/transfer-tokens","slug":"/lez/transfer-tokens/create-and-transfer-custom-tokens-on-the-logos-execution-zone","permalink":"/lez/transfer-tokens/create-and-transfer-custom-tokens-on-the-logos-execution-zone","draft":false,"unlisted":false,"editUrl":"https://github.com/logos-co/logos-docs/tree/main/docs/lez/transfer-tokens/create-and-transfer-custom-tokens-on-the-logos-execution-zone.md","tags":[],"version":"current","sidebarPosition":2,"frontMatter":{"title":"Create and transfer custom tokens on the Logos Execution Zone","doc_type":"procedure","product":"lez","topics":"lez","steps_layout":"flat","authors":"cheny0, jorge-campo, moudyellaz","owner":"logos","doc_version":1,"slug":"create-and-transfer-custom-tokens-on-the-logos-execution-zone","sidebar_position":2},"sidebar":"lezSidebar","previous":{"title":"Transfer native tokens on the Logos Execution Zone","permalink":"/lez/transfer-tokens/transfer-native-tokens-on-the-logos-execution-zone"},"next":{"title":"Transfer native LEZ tokens between public and private states","permalink":"/lez/transfer-tokens/transfer-native-lez-tokens-between-public-private-states"}}'),s=t(4848),a=t(8453);let c={title:"Create and transfer custom tokens on the Logos Execution Zone",doc_type:"procedure",product:"lez",topics:"lez",steps_layout:"flat",authors:"cheny0, jorge-campo, moudyellaz",owner:"logos",doc_version:1,slug:"create-and-transfer-custom-tokens-on-the-logos-execution-zone",sidebar_position:2},i="Create and transfer custom tokens on the Logos Execution Zone",r={},l=[{value:"Use the wallet CLI to create custom tokens and transfer them between public and private accounts.",id:"use-the-wallet-cli-to-create-custom-tokens-and-transfer-them-between-public-and-private-accounts",level:4},{value:"What to expect",id:"what-to-expect",level:2},{value:"Step 1: Create a token",id:"step-1-create-a-token",level:2},{value:"Step 2: Transfer tokens",id:"step-2-transfer-tokens",level:3}];function d(e){let n={a:"a",admonition:"admonition",code:"code",h1:"h1",h2:"h2",h3:"h3",h4:"h4",header:"header",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,a.R)(),...e.components};return(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(n.header,{children:(0,s.jsx)(n.h1,{id:"create-and-transfer-custom-tokens-on-the-logos-execution-zone",children:"Create and transfer custom tokens on the Logos Execution Zone"})}),"\n",(0,s.jsx)(n.h4,{id:"use-the-wallet-cli-to-create-custom-tokens-and-transfer-them-between-public-and-private-accounts",children:"Use the wallet CLI to create custom tokens and transfer them between public and private accounts."}),"\n",(0,s.jsx)(n.admonition,{type:"warning",children:(0,s.jsx)(n.p,{children:"This page is an early draft and may be incomplete or incorrect. Expect changes, missing prerequisites, and commands that might not work in your setup. We are actively working to complete and verify this content."})}),"\n",(0,s.jsx)(n.admonition,{type:"info",children:(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Permissions"}),": No special permissions required."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Product"}),": ",(0,s.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#logos-execution-zone",children:"Logos Execution Zone"})," wallet CLI."]}),"\n"]})}),"\n",(0,s.jsxs)(n.p,{children:["The Logos Execution Zone (",(0,s.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#lez",children:"LEZ"}),") is a programmable blockchain that cleanly separates public and private state while keeping them fully interoperable. It's a component of the ",(0,s.jsx)(n.a,{href:"https://github.com/logos-co/logos-docs/blob/main/README.md",children:"Logos project"}),". You can use the wallet CLI to invoke LEZ's ",(0,s.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#token-program",children:"token program"})," to create and transfer custom tokens between ",(0,s.jsx)(n.a,{href:"/lez/transfer-tokens/transfer-native-tokens-on-the-logos-execution-zone",children:"public and private accounts"})," on LEZ."]}),"\n",(0,s.jsxs)(n.p,{children:["The token program is a built-in LEZ ",(0,s.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#program",children:"program"})," that provides standard token functionality, including defining new token assets and transferring balances. It uses a single shared program rather than requiring a separate contract deployment for each token. The token program is privacy-agnostic. You use the same instructions whether execution is public (on-chain) or privacy-preserving (off-chain with a zero-knowledge proof). The protocol decides the execution mode, and the token logic is unchanged."]}),"\n",(0,s.jsx)(n.p,{children:"Token program accounts fall into two types:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#token-definition-account",children:"Token definition account"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"Each token has exactly one token definition account."}),"\n",(0,s.jsx)(n.li,{children:"It defines the token globally (the \u201Ctoken type\u201D)."}),"\n",(0,s.jsx)(n.li,{children:"Its address is the token identifier (similar to a mint address)."}),"\n",(0,s.jsx)(n.li,{children:"The token program owns it."}),"\n",(0,s.jsx)(n.li,{children:"It stores metadata including the token name and total supply."}),"\n",(0,s.jsx)(n.li,{children:"It can be public or private."}),"\n"]}),"\n"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#token-holding-account",children:"Token holding account"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"Each token can have multiple token holding accounts."}),"\n",(0,s.jsxs)(n.li,{children:["Any ",(0,s.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#account",children:"account"})," that holds a balance of a token is a token holding account for that token."]}),"\n",(0,s.jsx)(n.li,{children:"It stores the token definition ID and the balance the account currently controls."}),"\n",(0,s.jsx)(n.li,{children:"The token program owns it."}),"\n",(0,s.jsx)(n.li,{children:"Only the token program can modify it. Modifications happen through token program executions authorised by the account\u2019s keys."}),"\n",(0,s.jsx)(n.li,{children:"It can be public or private."}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,s.jsx)(n.admonition,{type:"danger",children:(0,s.jsx)(n.p,{children:"Transfers are irreversible. Double-check all details before proceeding."})}),"\n",(0,s.jsx)(n.p,{children:"Before you begin, ensure that you have the following:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:["The ",(0,s.jsx)(n.a,{href:"/lez/get-started/lez-quickstart#step-2-start-the-lez-sequencer-in-standalone-mode",children:"LEZ sequencer running in standalone mode"})," on your computer"]}),"\n",(0,s.jsxs)(n.li,{children:["The ",(0,s.jsx)(n.a,{href:"/lez/get-started/lez-quickstart#set-up-the-wallet-binary-prerequisites-and-build-the-wallet",children:"Wallet CLI installed"})," on your computer"]}),"\n"]}),"\n",(0,s.jsx)(n.h2,{id:"what-to-expect",children:"What to expect"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"The token program can move balances between token holding accounts."}),"\n",(0,s.jsx)(n.li,{children:"If the recipient account is uninitialised, the token program will automatically claim it. After initialisation, only the token program can modify the account."}),"\n",(0,s.jsxs)(n.li,{children:["You can transfer custom tokens to any uninitialised public or ",(0,s.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#private-account",children:"private account"}),". Once the account is initialised, it can only receive tokens of the same definition ID. (A token holding account stores the balance for exactly one token definition ID.)"]}),"\n"]}),"\n",(0,s.jsx)(n.admonition,{type:"info",children:(0,s.jsx)(n.p,{children:"Currently, it's impossible to change the token name or total supply after you create the token."})}),"\n",(0,s.jsx)(n.h2,{id:"step-1-create-a-token",children:"Step 1: Create a token"}),"\n",(0,s.jsxs)(n.ol,{children:["\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsx)(n.p,{children:"Create two new, uninitialised accounts: one token definition account and one token holding account to receive the total supply. Both accounts can be public or private, depending on your needs."}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#public-account",children:"Public account"}),":"]}),"\n"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-sh",children:"wallet account new public\n"})}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"Private account:"}),"\n"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-sh",children:"wallet account new private\n"})}),"\n",(0,s.jsxs)(n.p,{children:["If you create a public account, the output is the account ID. If you create a private account, the output includes the account ID, ",(0,s.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#nullifier-public-key",children:"nullifier public key"})," (",(0,s.jsx)(n.code,{children:"npk"}),"), and ",(0,s.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#viewing-public-key",children:"viewing public key"})," (",(0,s.jsx)(n.code,{children:"vpk"}),")."]}),"\n"]}),"\n"]}),"\n",(0,s.jsx)(n.admonition,{type:"info",children:(0,s.jsxs)(n.p,{children:["Your account keys and data are stored in the local file ",(0,s.jsx)(n.code,{children:"$HOME/.nssa/wallet/storage.json"}),"."]})}),"\n",(0,s.jsxs)(n.ol,{children:["\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsxs)(n.p,{children:["Use the ",(0,s.jsx)(n.code,{children:"wallet account ls"})," command to confirm the accounts are created successfully. You should see a list showing all of your accounts."]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-sh",children:"wallet account ls\n"})}),"\n"]}),"\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsxs)(n.p,{children:["Create the token using the token definition account and token holding account you just created. Replace ",(0,s.jsx)(n.code,{children:"NAME"})," with your token name, ",(0,s.jsx)(n.code,{children:"AMOUNT"})," with the total supply, ",(0,s.jsx)(n.code,{children:"ACCOUNT-TYPE"})," with the type of the account (public or private), and ",(0,s.jsx)(n.code,{children:"ACCOUNT-ID"})," with the appropriate account IDs."]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-sh",children:"wallet token new \\\n--name NAME \\\n--total-supply AMOUNT \\\n--definition-account-id ACCOUNT-TYPE/ACCOUNT-ID \\\n--supply-account-id ACCOUNT-TYPE/ACCOUNT-ID\n"})}),"\n"]}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"For example,"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{children:"```sh\nwallet token new \\\n--name TOKENA \\\n--total-supply 1337 \\\n--definition-account-id Public/4X9kAcnCZ1Ukkbm3nywW9xfCNPK8XaMWCk3zfs1sP4J7 \\\n--supply-account-id Public/9RRSMm3w99uCD2Jp2Mqqf6dfc8me2tkFRE9HeU2DFftw\n```\n"})}),"\n",(0,s.jsxs)(n.ol,{children:["\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsxs)(n.p,{children:["Use the ",(0,s.jsx)(n.code,{children:"wallet account get"})," command to check the status of the token definition account and token holding account. You can see their account types and the token metadata."]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-sh",children:"wallet account get --account-id ACCOUNT-TYPE/ACCOUNT-ID\n"})}),"\n"]}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"The output for the definition account and holding account looks like the following, respectively:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{children:'```text\nDefinition account owned by token program\n{"Fungible":{"name":"TOKENA","total_supply":1337,"metadata_id":null}}\n```\n\n```text\nHolding account owned by token program\n{"Fungible":{"definition_id":"4X9kAcnCZ1Ukkbm3nywW9xfCNPK8XaMWCk3zfs1sP4J7","balance":1337}}\n```\n'})}),"\n",(0,s.jsx)(n.admonition,{type:"tip",children:(0,s.jsxs)(n.p,{children:["When checking the status of a private account, the ",(0,s.jsx)(n.code,{children:"wallet account get"})," command doesn't query the network. It works offline because private account data lives only in your wallet storage. Other users cannot read your private balances using this command and your private account ID."]})}),"\n",(0,s.jsx)(n.h3,{id:"step-2-transfer-tokens",children:"Step 2: Transfer tokens"}),"\n",(0,s.jsxs)(n.p,{children:["When transferring custom tokens using the ",(0,s.jsx)(n.code,{children:"wallet token send"})," command, you specify the sender and recipient accounts with the account IDs. Both accounts can be public or private, but they must have the same token definition ID."]}),"\n",(0,s.jsx)(n.admonition,{type:"info",children:(0,s.jsx)(n.p,{children:"Transfers involving private accounts may take a few minutes because the wallet needs to generate a local proof."})}),"\n",(0,s.jsxs)(n.ol,{children:["\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsxs)(n.p,{children:["Use the ",(0,s.jsx)(n.code,{children:"wallet token send"})," command to transfer custom tokens. Replace ",(0,s.jsx)(n.code,{children:"ACCOUNT-TYPE"})," with the type of the account (public or private) and ",(0,s.jsx)(n.code,{children:"TOKEN-AMOUNT"})," with the amount of tokens to transfer."]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-sh",children:"wallet token send \\\n --from ACCOUNT-TYPE/SENDER-ACCOUNT-ID \\\n --to ACCOUNT-TYPE/RECIPIENT-ACCOUNT-ID \\\n --amount TOKEN-AMOUNT\n"})}),"\n"]}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"For example, to transfer 1000 tokens from a private token holding account to a public recipient account, run:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{children:"```sh\nwallet token send \\\n --from Private/HMRHZdPw4pbyPVZHNGrV6K5AA95wACFsHTRST84fr3CF \\\n --to Public/88f2zeTgiv9LUthQwPJbrmufb9SiDfmpCs47B7vw6Gd6 \\\n --amount 1000\n```\n"})}),"\n",(0,s.jsxs)(n.ol,{children:["\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsxs)(n.p,{children:["Use the ",(0,s.jsx)(n.code,{children:"wallet account get"})," command to check the status of the sender and recipient accounts. You can see the updated token balances."]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-sh",children:"wallet account get --account-id ACCOUNT-TYPE/ACCOUNT-ID\n"})}),"\n"]}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"The output looks like this:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{children:'```text\nHolding account owned by token program\n{"Fungible":{"definition_id":"4X9kAcnCZ1Ukkbm3nywW9xfCNPK8XaMWCk3zfs1sP4J7","balance":1000}}\n```\n'})}),"\n",(0,s.jsx)(n.admonition,{type:"tip",children:(0,s.jsxs)(n.p,{children:["You can also use the ",(0,s.jsx)(n.code,{children:"wallet account ls -l"})," command to check the balances of all your accounts at once."]})})]})}function h(e={}){let{wrapper:n}={...(0,a.R)(),...e.components};return n?(0,s.jsx)(n,{...e,children:(0,s.jsx)(d,{...e})}):d(e)}},8453(e,n,t){t.d(n,{R:()=>c,x:()=>i});var o=t(6540);let s={},a=o.createContext(s);function c(e){let n=o.useContext(a);return o.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function i(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(s):e.components||s:c(e.components),o.createElement(a.Provider,{value:n},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/0ac18de3.80b6624b.js b/assets/js/0ac18de3.80b6624b.js new file mode 100644 index 00000000..6db6332a --- /dev/null +++ b/assets/js/0ac18de3.80b6624b.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunklogos_docs=self.webpackChunklogos_docs||[]).push([["5803"],{6378(e,t,n){n.r(t),n.d(t,{metadata:()=>s,default:()=>d,frontMatter:()=>l,contentTitle:()=>r,toc:()=>c,assets:()=>o});var s=JSON.parse('{"id":"lez/get-started/initiate-native-token-transfers-on-lez-with-wallet-ui","title":"Initiate native token transfers on the LEZ with the wallet UI","description":"Use the wallet UI to set up accounts and try every type of native token transfer on the LEZ testnet.","source":"@site/docs/lez/get-started/initiate-native-token-transfers-on-lez-with-wallet-ui.md","sourceDirName":"lez/get-started","slug":"/lez/get-started/initiate-native-token-transfers-on-lez-with-wallet-ui","permalink":"/lez/get-started/initiate-native-token-transfers-on-lez-with-wallet-ui","draft":false,"unlisted":false,"editUrl":"https://github.com/logos-co/logos-docs/tree/main/docs/lez/get-started/initiate-native-token-transfers-on-lez-with-wallet-ui.md","tags":[],"version":"current","sidebarPosition":4,"frontMatter":{"title":"Initiate native token transfers on the LEZ with the wallet UI","doc_type":"procedure","product":"lez","topics":"lez","steps_layout":"flat","authors":"schohy, kashepavadan","owner":"logos","doc_version":1,"slug":"initiate-native-token-transfers-on-lez-with-wallet-ui","sidebar_position":4},"sidebar":"lezSidebar","previous":{"title":"Connect the wallet CLI to the LEZ testnet","permalink":"/lez/get-started/connect-wallet-cli-to-lez-testnet"},"next":{"title":"Transfer native tokens on the Logos Execution Zone","permalink":"/lez/transfer-tokens/transfer-native-tokens-on-the-logos-execution-zone"}}'),i=n(4848),a=n(8453);let l={title:"Initiate native token transfers on the LEZ with the wallet UI",doc_type:"procedure",product:"lez",topics:"lez",steps_layout:"flat",authors:"schohy, kashepavadan",owner:"logos",doc_version:1,slug:"initiate-native-token-transfers-on-lez-with-wallet-ui",sidebar_position:4},r="Initiate native token transfers on the LEZ with the wallet UI",o={},c=[{value:"Use the wallet UI to set up accounts and try every type of native token transfer on the LEZ testnet.",id:"use-the-wallet-ui-to-set-up-accounts-and-try-every-type-of-native-token-transfer-on-the-lez-testnet",level:4},{value:"What to expect",id:"what-to-expect",level:2},{value:"Set up and sync the wallet UI",id:"set-up-and-sync-the-wallet-ui",level:2},{value:"Create a new public account",id:"create-a-new-public-account",level:2},{value:"Send a public transfer",id:"send-a-public-transfer",level:2},{value:"Verify L1 finalisation",id:"verify-l1-finalisation",level:2},{value:"Try private, shielded, and deshielded transfers",id:"try-private-shielded-and-deshielded-transfers",level:2},{value:"Troubleshooting wallet UI setup",id:"troubleshooting-wallet-ui-setup",level:2},{value:"Why doesn't the setup screen show?",id:"why-doesnt-the-setup-screen-show",level:3}];function h(e){let t={a:"a",admonition:"admonition",code:"code",h1:"h1",h2:"h2",h3:"h3",h4:"h4",header:"header",img:"img",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,a.R)(),...e.components};return(0,i.jsxs)(i.Fragment,{children:[(0,i.jsx)(t.header,{children:(0,i.jsx)(t.h1,{id:"initiate-native-token-transfers-on-the-lez-with-the-wallet-ui",children:"Initiate native token transfers on the LEZ with the wallet UI"})}),"\n",(0,i.jsx)(t.h4,{id:"use-the-wallet-ui-to-set-up-accounts-and-try-every-type-of-native-token-transfer-on-the-lez-testnet",children:"Use the wallet UI to set up accounts and try every type of native token transfer on the LEZ testnet."}),"\n",(0,i.jsxs)(t.p,{children:["The wallet UI is a simple entrypoint for getting started on the ",(0,i.jsx)(t.a,{href:"https://docs.logos.co/get-started/glossary#lez",children:"LEZ"}),". This procedure walks you through running the wallet UI on your machine, syncing it with the public LEZ testnet, creating accounts, and executing all combinations of public, private, shielded, and deshielded native token transfers. The LEZ testnet runs the centralised LEZ sequencer, which processes the transactions the wallet UI submits, while the wallet UI itself manages your accounts locally and can execute transfers with any combination of private and public accounts."]}),"\n",(0,i.jsx)(t.admonition,{type:"info",children:(0,i.jsx)(t.p,{children:"Recovery from mnemonic and withdrawals to the L1 are not yet supported."})}),"\n",(0,i.jsx)(t.p,{children:"Before you begin, ensure you have:"}),"\n",(0,i.jsxs)(t.ul,{children:["\n",(0,i.jsxs)(t.li,{children:["\n",(0,i.jsxs)(t.p,{children:[(0,i.jsx)(t.strong,{children:"Nix"})," with flakes enabled. Install from ",(0,i.jsx)(t.a,{href:"https://nixos.org/download.html",children:"nixos.org"}),", then enable flakes:"]}),"\n",(0,i.jsx)(t.pre,{children:(0,i.jsx)(t.code,{className:"language-bash",children:"mkdir -p ~/.config/nix\necho 'experimental-features = nix-command flakes' >> ~/.config/nix/nix.conf\n"})}),"\n",(0,i.jsxs)(t.p,{children:["Verify: ",(0,i.jsx)(t.code,{children:'nix flake --help >/dev/null 2>&1 && echo "Flakes enabled"'})]}),"\n"]}),"\n"]}),"\n",(0,i.jsx)(t.h2,{id:"what-to-expect",children:"What to expect"}),"\n",(0,i.jsxs)(t.ul,{children:["\n",(0,i.jsx)(t.li,{children:"You can run the wallet UI locally and sync it with the live LEZ testnet."}),"\n",(0,i.jsx)(t.li,{children:"You have created and funded accounts that you can use to test transfers."}),"\n",(0,i.jsx)(t.li,{children:"You can complete public, private, shielded, and deshielded token transfers and verify finalisation on the L1."}),"\n"]}),"\n",(0,i.jsx)(t.h2,{id:"set-up-and-sync-the-wallet-ui",children:"Set up and sync the wallet UI"}),"\n",(0,i.jsx)(t.p,{children:"This task clones the wallet UI, starts it, and syncs it with the LEZ testnet."}),"\n",(0,i.jsxs)(t.ol,{children:["\n",(0,i.jsxs)(t.li,{children:["\n",(0,i.jsx)(t.p,{children:"Clone the repository and start the wallet UI."}),"\n",(0,i.jsx)(t.pre,{children:(0,i.jsx)(t.code,{className:"language-bash",children:"git clone https://github.com/logos-blockchain/logos-execution-zone-wallet-ui.git\ncd logos-execution-zone-wallet-ui\nnix run\n"})}),"\n"]}),"\n"]}),"\n",(0,i.jsx)(t.admonition,{type:"info",children:(0,i.jsx)(t.p,{children:"On a cold Nix cache, the first run compiles the wallet UI from source (Qt/C++ and Rust dependencies). This can take 20\u201360 minutes. Subsequent runs are instant from cache."})}),"\n",(0,i.jsxs)(t.ol,{children:["\n",(0,i.jsxs)(t.li,{children:["\n",(0,i.jsx)(t.p,{children:"In the setup screen, enter paths for the config file and the storage file."}),"\n",(0,i.jsxs)(t.ul,{children:["\n",(0,i.jsx)(t.li,{children:"The wallet UI creates these files at the paths you enter if they don't already exist."}),"\n",(0,i.jsx)(t.li,{children:"After the first run, the wallet UI remembers these paths and skips the setup screen on subsequent runs."}),"\n"]}),"\n",(0,i.jsx)(t.p,{children:(0,i.jsx)(t.img,{alt:"Wallet setup screen",src:n(3027).A+"",width:"1160",height:"670"})}),"\n"]}),"\n",(0,i.jsxs)(t.li,{children:["\n",(0,i.jsx)(t.p,{children:"Choose a password."}),"\n"]}),"\n",(0,i.jsxs)(t.li,{children:["\n",(0,i.jsxs)(t.p,{children:["Click ",(0,i.jsx)(t.strong,{children:"Create wallet"}),"."]}),"\n"]}),"\n",(0,i.jsxs)(t.li,{children:["\n",(0,i.jsxs)(t.p,{children:["The wallet UI ships with a set of predefined, funded public and private accounts that are shared between all wallet UI users on the testnet and act as a common faucet. Wait for the wallet UI to sync these ",(0,i.jsx)(t.a,{href:"https://docs.logos.co/get-started/glossary#private-account",children:"private account"})," values from the genesis block."]}),"\n",(0,i.jsx)(t.p,{children:"A progress bar shows the number of blocks processed, and this step can take a few minutes."}),"\n",(0,i.jsx)(t.p,{children:(0,i.jsx)(t.img,{alt:"Sync progress screen",src:n(9678).A+"",width:"1160",height:"670"})}),"\n",(0,i.jsx)(t.p,{children:"The list of accounts and their balances displays once the sync finishes."}),"\n",(0,i.jsx)(t.p,{children:(0,i.jsx)(t.img,{alt:"Account list with balances",src:n(1911).A+"",width:"1160",height:"670"})}),"\n"]}),"\n"]}),"\n",(0,i.jsx)(t.h2,{id:"create-a-new-public-account",children:"Create a new public account"}),"\n",(0,i.jsx)(t.p,{children:"Unlike the predefined accounts on the wallet, accounts you create yourself are controlled only by you and aren't shared."}),"\n",(0,i.jsxs)(t.ol,{children:["\n",(0,i.jsxs)(t.li,{children:["\n",(0,i.jsxs)(t.p,{children:["Click ",(0,i.jsx)(t.strong,{children:"Create"})," ",(0,i.jsx)(t.a,{href:"https://docs.logos.co/get-started/glossary#account",children:(0,i.jsx)(t.strong,{children:"account"})}),"."]}),"\n"]}),"\n",(0,i.jsxs)(t.li,{children:["\n",(0,i.jsxs)(t.p,{children:["Select ",(0,i.jsx)(t.strong,{children:"Public"}),"."]}),"\n",(0,i.jsx)(t.p,{children:(0,i.jsx)(t.img,{alt:"Create account menu",src:n(275).A+"",width:"1160",height:"670"})}),"\n"]}),"\n",(0,i.jsxs)(t.li,{children:["\n",(0,i.jsxs)(t.p,{children:["Click ",(0,i.jsx)(t.strong,{children:"Create"}),"."]}),"\n",(0,i.jsxs)(t.p,{children:["The new ",(0,i.jsx)(t.a,{href:"https://docs.logos.co/get-started/glossary#public-account",children:"public account"})," will appear in your account list."]}),"\n",(0,i.jsx)(t.p,{children:(0,i.jsx)(t.img,{alt:"New public account in the list",src:n(5177).A+"",width:"1160",height:"670"})}),"\n"]}),"\n"]}),"\n",(0,i.jsx)(t.h2,{id:"send-a-public-transfer",children:"Send a public transfer"}),"\n",(0,i.jsx)(t.p,{children:"This task moves tokens from a funded predefined account to the public account you created."}),"\n",(0,i.jsxs)(t.ol,{children:["\n",(0,i.jsxs)(t.li,{children:["\n",(0,i.jsxs)(t.p,{children:["In the ",(0,i.jsx)(t.strong,{children:"From"})," field, choose an account with a positive balance."]}),"\n"]}),"\n",(0,i.jsxs)(t.li,{children:["\n",(0,i.jsx)(t.p,{children:"Next to your newly created account, click the copy button to copy its account ID."}),"\n"]}),"\n",(0,i.jsxs)(t.li,{children:["\n",(0,i.jsx)(t.p,{children:"In the recipient field, paste the account ID."}),"\n"]}),"\n",(0,i.jsxs)(t.li,{children:["\n",(0,i.jsxs)(t.p,{children:["Click ",(0,i.jsx)(t.strong,{children:"Send"}),"."]}),"\n",(0,i.jsx)(t.p,{children:(0,i.jsx)(t.img,{alt:"Public transfer screen",src:n(5668).A+"",width:"1160",height:"670"})}),"\n",(0,i.jsxs)(t.p,{children:["A transaction hash displays below the ",(0,i.jsx)(t.strong,{children:"Send"})," button. Use its copy button to copy the hash for the next task."]}),"\n"]}),"\n"]}),"\n",(0,i.jsx)(t.h2,{id:"verify-l1-finalisation",children:"Verify L1 finalisation"}),"\n",(0,i.jsxs)(t.ol,{children:["\n",(0,i.jsxs)(t.li,{children:["\n",(0,i.jsxs)(t.p,{children:["In your browser, navigate to ",(0,i.jsx)(t.code,{children:"https://explorer.testnet.lez.logos.co/transaction/"}),", replacing ",(0,i.jsx)(t.code,{children:""})," with the transaction hash from the previous task."]}),"\n",(0,i.jsx)(t.p,{children:"The transaction details display once the transaction is finalised on the L1. Finalisation isn't immediate; it can take around 10 minutes to display."}),"\n",(0,i.jsx)(t.p,{children:(0,i.jsx)(t.img,{alt:"Transaction details on the explorer",src:n(2108).A+"",width:"1247",height:"906"})}),"\n"]}),"\n"]}),"\n",(0,i.jsx)(t.h2,{id:"try-private-shielded-and-deshielded-transfers",children:"Try private, shielded, and deshielded transfers"}),"\n",(0,i.jsx)(t.p,{children:"Repeat the create-account and transfer tasks for the other account types and transfer flows."}),"\n",(0,i.jsxs)(t.ol,{children:["\n",(0,i.jsxs)(t.li,{children:["Create one or more private accounts using the same ",(0,i.jsx)(t.strong,{children:"Create account"})," flow, selecting ",(0,i.jsx)(t.strong,{children:"Private"})," instead of ",(0,i.jsx)(t.strong,{children:"Public"}),"."]}),"\n",(0,i.jsx)(t.li,{children:"Send a private transfer from a private sender to a private recipient."}),"\n",(0,i.jsx)(t.li,{children:"Send a shielded transfer from a public sender to a private recipient."}),"\n",(0,i.jsx)(t.li,{children:"Send a deshielded transfer from a private sender to a public recipient."}),"\n"]}),"\n",(0,i.jsx)(t.h2,{id:"troubleshooting-wallet-ui-setup",children:"Troubleshooting wallet UI setup"}),"\n",(0,i.jsx)(t.h3,{id:"why-doesnt-the-setup-screen-show",children:"Why doesn't the setup screen show?"}),"\n",(0,i.jsxs)(t.p,{children:["The setup screen is skipped if the config file at ",(0,i.jsx)(t.code,{children:"~/.config/Logos/ExecutionZoneWalletUI.conf"})," already lists paths for the storage and config files, typically from an earlier run of the wallet UI. Remove ",(0,i.jsx)(t.code,{children:"~/.config/Logos/ExecutionZoneWalletUI.conf"})," to see the setup screen again."]})]})}function d(e={}){let{wrapper:t}={...(0,a.R)(),...e.components};return t?(0,i.jsx)(t,{...e,children:(0,i.jsx)(h,{...e})}):h(e)}},275(e,t,n){n.d(t,{A:()=>s});let s=n.p+"assets/images/create-account-611ab7ff4c84c1c68009b7dee647759c.png"},3027(e,t,n){n.d(t,{A:()=>s});let s=n.p+"assets/images/create-wallet-52bf24a016f710d8f9773307f6323d53.png"},5177(e,t,n){n.d(t,{A:()=>s});let s=n.p+"assets/images/new-account-a29a45ff1e159f0ee28ac86b706da51e.png"},5668(e,t,n){n.d(t,{A:()=>s});let s=n.p+"assets/images/public-account-6a32dafc6c48ff4c791cc507afe7f257.png"},1911(e,t,n){n.d(t,{A:()=>s});let s=n.p+"assets/images/sync-done-2dbab2c6951344c2811cd697344b0c66.png"},9678(e,t,n){n.d(t,{A:()=>s});let s=n.p+"assets/images/sync-progress-23b76c27f31c0c7d53fd0d51b71f4979.png"},2108(e,t,n){n.d(t,{A:()=>s});let s=n.p+"assets/images/tx-finalized-eff75e5978815979bc4f2f523c69d926.png"},8453(e,t,n){n.d(t,{R:()=>l,x:()=>r});var s=n(6540);let i={},a=s.createContext(i);function l(e){let t=s.useContext(a);return s.useMemo(function(){return"function"==typeof e?e(t):{...t,...e}},[t,e])}function r(e){let t;return t=e.disableParentContext?"function"==typeof e.components?e.components(i):e.components||i:l(e.components),s.createElement(a.Provider,{value:t},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/0e1b61e3.d375f498.js b/assets/js/0e1b61e3.d375f498.js new file mode 100644 index 00000000..74765695 --- /dev/null +++ b/assets/js/0e1b61e3.d375f498.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunklogos_docs=self.webpackChunklogos_docs||[]).push([["1581"],{6765(e,t,s){s.r(t),s.d(t,{metadata:()=>o,default:()=>h,frontMatter:()=>r,contentTitle:()=>a,toc:()=>l,assets:()=>d});var o=JSON.parse('{"id":"get-started/introduction-to-logos","title":"Introduction to Logos","description":"Learn how the Logos technology stack is organised as a modular operating system for decentralised applications.","source":"@site/docs/get-started/introduction-to-logos.md","sourceDirName":"get-started","slug":"/get-started/what-is-logos","permalink":"/get-started/what-is-logos","draft":false,"unlisted":false,"editUrl":"https://github.com/logos-co/logos-docs/tree/main/docs/get-started/introduction-to-logos.md","tags":[],"version":"current","sidebarPosition":1,"frontMatter":{"title":"Introduction to Logos","doc_type":"concept","product":"logos","topics":"architecture","authors":"jorge-campo, kashepavadan","owner":"logos","doc_version":1,"slug":"what-is-logos","sidebar_position":1},"sidebar":"getStartedSidebar","next":{"title":"Logos ecosystem repositories","permalink":"/get-started/logos-ecosystem-repositories"}}'),i=s(4848),n=s(8453);let r={title:"Introduction to Logos",doc_type:"concept",product:"logos",topics:"architecture",authors:"jorge-campo, kashepavadan",owner:"logos",doc_version:1,slug:"what-is-logos",sidebar_position:1},a="Introduction to Logos",d={},l=[{value:"Learn how the Logos technology stack is organised as a modular operating system for decentralised applications.",id:"learn-how-the-logos-technology-stack-is-organised-as-a-modular-operating-system-for-decentralised-applications",level:4},{value:"The basics",id:"the-basics",level:2},{value:"Design principles",id:"design-principles",level:2},{value:"Architecture",id:"architecture",level:2},{value:"The runtime",id:"the-runtime",level:3},{value:"The networking layer",id:"the-networking-layer",level:3},{value:"Modules",id:"modules",level:3},{value:"Dapps",id:"dapps",level:3}];function c(e){let t={a:"a",h1:"h1",h2:"h2",h3:"h3",h4:"h4",header:"header",img:"img",li:"li",p:"p",strong:"strong",ul:"ul",...(0,n.R)(),...e.components};return(0,i.jsxs)(i.Fragment,{children:[(0,i.jsx)(t.header,{children:(0,i.jsx)(t.h1,{id:"introduction-to-logos",children:"Introduction to Logos"})}),"\n",(0,i.jsx)(t.h4,{id:"learn-how-the-logos-technology-stack-is-organised-as-a-modular-operating-system-for-decentralised-applications",children:"Learn how the Logos technology stack is organised as a modular operating system for decentralised applications."}),"\n",(0,i.jsx)(t.p,{children:"Logos is a modular, microkernel-based stack for decentralised infrastructure, built for privacy, composability, and resilience. Its structure can be compared to the Linux operating system, which consists of a kernel, a networking stack, a set of system services, and the applications that run on top of them. Different distributions share the same kernel but assemble different services and applications on top of it."}),"\n",(0,i.jsxs)(t.p,{children:["Logos follows the same structure. At its foundation sits the ",(0,i.jsx)(t.strong,{children:"Logos runtime"}),", a microkernel-style core that provides the primitives a decentralised application requires. Above the runtime sits a ",(0,i.jsx)(t.strong,{children:"networking layer"}),", which provides peer discovery, connection management, and privacy-preserving communication through a mixnet. Above that sit the ",(0,i.jsx)(t.strong,{children:"modules"})," - pluggable components that provide a specific capability, such as storage, messaging, or blockchain consensus. At the top of the stack sit the ",(0,i.jsx)(t.strong,{children:"dApps"})," that compose these modules into applications."]}),"\n",(0,i.jsx)(t.p,{children:"Logos ships with a default configuration consisting of the runtime plus storage, messaging, and blockchain modules. This configuration is not fixed: developers can assemble other distributions from a different selection of modules."}),"\n",(0,i.jsx)(t.h2,{id:"the-basics",children:"The basics"}),"\n",(0,i.jsxs)(t.ul,{children:["\n",(0,i.jsx)(t.li,{children:"Logos is organised into four layers: a runtime at the base, a privacy-preserving networking layer above it, a set of pluggable modules that provide specific capabilities, and decentralised applications on top that compose those modules."}),"\n",(0,i.jsxs)(t.li,{children:[(0,i.jsx)(t.a,{href:"https://docs.logos.co/basecamp",children:"Logos Basecamp"})," is the default launcher for the stack. It runs the Logos runtime locally, loads a configured module profile, and provides a unified interface. The ",(0,i.jsx)(t.strong,{children:"Logos Node"})," runs the same runtime without a user interface, for validators, operators, or backend services."]}),"\n",(0,i.jsx)(t.li,{children:"Privacy is implemented at the infrastructure layer rather than the application layer. An application built on Logos inherits metadata protection and support for private state regardless of whether its developer has implemented anonymity measures directly."}),"\n"]}),"\n",(0,i.jsx)(t.h2,{id:"design-principles",children:"Design principles"}),"\n",(0,i.jsx)(t.p,{children:"Logos was designed around modularity, structural privacy, and sovereignty."}),"\n",(0,i.jsx)(t.p,{children:"Modularity determines how the system tolerates failure and change. Each layer and module can be developed, audited, and upgraded independently of the others. A defect in the storage module does not affect the messaging layer, and an upgrade to consensus does not require changes to the networking layer."}),"\n",(0,i.jsxs)(t.p,{children:["Structural privacy means that privacy guarantees are properties of the infrastructure rather than features that individual applications must implement. The mixnet obscures communication patterns at the networking layer, ",(0,i.jsx)(t.a,{href:"https://press.logos.co/article/why-proposer-anonymity",children:"Cryptarchia and the Blend Network"})," hide validator identities and stake amounts at the consensus layer, and the Logos Execution Zone supports private state at the execution layer."]}),"\n",(0,i.jsx)(t.p,{children:"Sovereignty refers to the operator's control over the infrastructure they run. A Logos node does not route messages through a third-party server, does not store files in a third-party data centre, and does not require permission from an intermediary to process a transaction. Because the stack is modular, an operator selects only the capabilities their use case requires."}),"\n",(0,i.jsx)(t.h2,{id:"architecture",children:"Architecture"}),"\n",(0,i.jsx)(t.p,{children:"Logos is organised into distinct layers, each with a defined responsibility: the runtime, the networking layer, modules, and dApps."}),"\n",(0,i.jsx)(t.p,{children:(0,i.jsx)(t.img,{alt:"The Logos architecture.",src:s(3614).A+"",width:"2441",height:"1844"})}),"\n",(0,i.jsx)(t.h3,{id:"the-runtime",children:"The runtime"}),"\n",(0,i.jsxs)(t.p,{children:["The ",(0,i.jsx)(t.strong,{children:"Logos runtime"})," follows a microkernel design: it performs a minimal set of functions and delegates all other responsibilities to modules that run on top of it. This differs from a monolithic kernel such as Linux, where the networking stack and many other services run inside the kernel itself. The runtime's responsibilities are limited to loading and unloading modules, managing each module's lifecycle, and providing a mechanism for modules to locate and call one another."]}),"\n",(0,i.jsxs)(t.p,{children:["The runtime is implemented in a C++ library called ",(0,i.jsx)(t.a,{href:"https://github.com/logos-co/logos-liblogos",children:"liblogos"}),". Three supporting modules operate alongside it. The ",(0,i.jsx)(t.strong,{children:"Process Manager Module"})," runs each loaded module in its own operating system process. If a module fails, the Process Manager Module records the failure and restarts the module using an exponential backoff, so that a fault in one module does not affect the rest of the system. The ",(0,i.jsx)(t.strong,{children:"Developer Module"})," provides visibility into loaded modules, their registered objects, and their interactions, together with tracing, hot reload, and live debugging through a JSON-RPC interface. The ",(0,i.jsx)(t.strong,{children:"Package Manager Module"})," retrieves modules, currently from GitHub and in future from peer-to-peer storage, verifies them against cryptographic hashes, resolves dependencies, and applies incremental updates rather than re-downloading a module in full each time it changes."]}),"\n",(0,i.jsx)(t.p,{children:"This separation allows individual modules to be developed, tested, and updated independently of the runtime and of one another."}),"\n",(0,i.jsx)(t.h3,{id:"the-networking-layer",children:"The networking layer"}),"\n",(0,i.jsx)(t.p,{children:"The networking layer sits above the runtime and is represented in the stack as discovery, peering, and mixnet. It determines how Logos nodes locate one another, establish connections, and exchange messages. Unlike a conventional networking stack, this layer is designed to prevent observers from determining who is communicating with whom, as a property of the network rather than of any individual application."}),"\n",(0,i.jsxs)(t.p,{children:["This layer comprises several components. ",(0,i.jsx)(t.strong,{children:"Service Discovery"})," allows a node to locate other nodes that offer a required capability without consulting a central directory. It adapts the DISC-NG protocol for use with libp2p's Kademlia-DHT: each node publishes a signed record describing its address and capabilities, and other nodes query for records that match the capabilities they require without participating in the full distributed hash table."]}),"\n",(0,i.jsxs)(t.p,{children:[(0,i.jsx)(t.strong,{children:"The libp2p mixnet"})," is the privacy-preserving transport layer, based on the design principles used in anonymity networks such as Tor and Nym. Messages are routed through a sequence of intermediary relay nodes that shuffle and delay traffic before forwarding it, preventing an observer from correlating senders with receivers using timing or volume analysis. The protocol supports both one-way messages and request-response interactions, and includes cover traffic to maintain a sufficient set of plausible senders."]}),"\n",(0,i.jsxs)(t.p,{children:[(0,i.jsx)(t.strong,{children:"RLN and Zerokit"})," add spam protection to the anonymous network. A Rate Limiting Nullifier allows each network participant to send messages up to a defined rate while remaining anonymous. If a participant exceeds that rate, the protocol reveals their identity, allowing them to be slashed or removed. This cryptography is implemented in ",(0,i.jsx)(t.a,{href:"https://github.com/vacp2p/zerokit",children:"Zerokit"})," and is used both for denial-of-service protection on the mixnet and for rate limiting within Logos Delivery."]}),"\n",(0,i.jsxs)(t.p,{children:[(0,i.jsx)(t.strong,{children:"De-MLS"})," extends private messaging from one-to-one conversations to group conversations. It is a decentralised implementation of Messaging Layer Security in which a group is managed by multiple stewards rather than a single administrator."]}),"\n",(0,i.jsx)(t.p,{children:"The networking layer is itself packaged as a module and loaded by the runtime in the same way as storage or messaging. Once loaded, it provides the private transport used by every other module to communicate across the network, independent of whether that traffic consists of file storage requests, chat messages, or transactions."}),"\n",(0,i.jsx)(t.h3,{id:"modules",children:"Modules"}),"\n",(0,i.jsx)(t.p,{children:"Modules are self-contained components that sit above the networking layer, each providing a defined capability. Logos includes three foundational modules, and the architecture supports the addition of further modules."}),"\n",(0,i.jsxs)(t.p,{children:["The ",(0,i.jsx)(t.a,{href:"https://docs.logos.co/blockchain",children:(0,i.jsx)(t.strong,{children:"Blockchain"})})," module provides decentralised compute and consensus. It is built on ",(0,i.jsx)(t.strong,{children:"Cryptarchia"}),", a private proof-of-stake consensus mechanism in which block proposers cannot be linked to their proposals, with additional privacy by the ",(0,i.jsx)(t.strong,{children:"Blend Network"}),". The ",(0,i.jsx)(t.strong,{children:"Logos Execution Zone (LEZ)"}),", a Layer 2 execution environment, supports program deployment with both public and private account states."]}),"\n",(0,i.jsxs)(t.p,{children:["The ",(0,i.jsx)(t.a,{href:"https://docs.logos.co/messaging",children:(0,i.jsx)(t.strong,{children:"Messaging"})})," module provides private, censorship-resistant communication between parties and consists of two components. ",(0,i.jsx)(t.a,{href:"https://github.com/logos-messaging/logos-delivery",children:"Logos Delivery"})," provides the transport layer, implementing publish-subscribe messaging for reliable delivery. ",(0,i.jsx)(t.a,{href:"https://github.com/logos-messaging/nim-chat-poc",children:"Logos Chat"})," uses Delivery as its transport and provides encrypted one-to-one conversations, implemented with double-ratchet encryption, with support for group conversations under development using de-MLS."]}),"\n",(0,i.jsxs)(t.p,{children:["The ",(0,i.jsx)(t.a,{href:"https://docs.logos.co/storage",children:(0,i.jsx)(t.strong,{children:"Storage"})})," module provides decentralised file storage and retrieval using content-addressed data. It exposes an API for storing a file and receiving a content identifier (CID) in return, and for retrieving a file given its CID."]}),"\n",(0,i.jsxs)(t.p,{children:[(0,i.jsx)(t.strong,{children:"User modules"})," extend the architecture beyond the foundational set. Any developer can build a module that integrates with the same infrastructure. The runtime loads user modules, manages their lifecycle, and enables them to communicate with other modules, whether those modules are provided by Logos or by third parties."]}),"\n",(0,i.jsx)(t.h3,{id:"dapps",children:"Dapps"}),"\n",(0,i.jsx)(t.p,{children:"Dapps are the decentralised applications that compose the modules described above. A chat application could use the messaging and storage modules; a decentralised finance application could use the blockchain module and the LEZ; a file-sharing application could use the storage module."}),"\n",(0,i.jsxs)(t.p,{children:["The ",(0,i.jsx)(t.a,{href:"https://docs.logos.co/basecamp",children:"Logos Basecamp"}),", is the default launcher for the stack. It starts the runtime, loads the configured module profile, and provides a unified interface. By default, it includes a set of applications for each foundational module: a wallet for managing tokens, a chat interface for encrypted messaging, a file-sharing tool, and an explorer for inspecting blockchain and LEZ activity. The ",(0,i.jsx)(t.a,{href:"https://docs.logos.co/run-a-node",children:"Logos Node"})," provides an alternative entry point, starting the same runtime without a user interface. Because a distribution is defined by its selected modules rather than by its launcher, developers can assemble distributions for purposes other than the default configuration."]})]})}function h(e={}){let{wrapper:t}={...(0,n.R)(),...e.components};return t?(0,i.jsx)(t,{...e,children:(0,i.jsx)(c,{...e})}):c(e)}},3614(e,t,s){s.d(t,{A:()=>o});let o=s.p+"assets/images/logos-tech-diagram-919265ff862934cbbf1a60d48f18a3c9.png"},8453(e,t,s){s.d(t,{R:()=>r,x:()=>a});var o=s(6540);let i={},n=o.createContext(i);function r(e){let t=o.useContext(n);return o.useMemo(function(){return"function"==typeof e?e(t):{...t,...e}},[t,e])}function a(e){let t;return t=e.disableParentContext?"function"==typeof e.components?e.components(i):e.components||i:r(e.components),o.createElement(n.Provider,{value:t},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/102caa12.af3dc56f.js b/assets/js/102caa12.af3dc56f.js new file mode 100644 index 00000000..4434878c --- /dev/null +++ b/assets/js/102caa12.af3dc56f.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunklogos_docs=self.webpackChunklogos_docs||[]).push([["5539"],{3078(e,o,t){t.r(o),t.d(o,{metadata:()=>r,default:()=>p,frontMatter:()=>c,contentTitle:()=>i,toc:()=>l,assets:()=>d});var r=JSON.parse('{"id":"peer-discovery/welcome","title":"Welcome","description":"Welcome to the Logos peer discovery documentation! We\'re working on the content.","source":"@site/docs/peer-discovery/welcome.md","sourceDirName":"peer-discovery","slug":"/peer-discovery/welcome","permalink":"/peer-discovery/welcome","draft":false,"unlisted":false,"editUrl":"https://github.com/logos-co/logos-docs/tree/main/docs/peer-discovery/welcome.md","tags":[],"version":"current","sidebarPosition":1,"frontMatter":{"sidebar_position":1},"sidebar":"peerDiscoverySidebar"}'),n=t(4848),s=t(8453);let c={sidebar_position:1},i="Welcome",d={},l=[];function a(e){let o={h1:"h1",header:"header",p:"p",...(0,s.R)(),...e.components};return(0,n.jsxs)(n.Fragment,{children:[(0,n.jsx)(o.header,{children:(0,n.jsx)(o.h1,{id:"welcome",children:"Welcome"})}),"\n",(0,n.jsx)(o.p,{children:"Welcome to the Logos peer discovery documentation! We're working on the content."})]})}function p(e={}){let{wrapper:o}={...(0,s.R)(),...e.components};return o?(0,n.jsx)(o,{...e,children:(0,n.jsx)(a,{...e})}):a(e)}},8453(e,o,t){t.d(o,{R:()=>c,x:()=>i});var r=t(6540);let n={},s=r.createContext(n);function c(e){let o=r.useContext(s);return r.useMemo(function(){return"function"==typeof e?e(o):{...o,...e}},[o,e])}function i(e){let o;return o=e.disableParentContext?"function"==typeof e.components?e.components(n):e.components||n:c(e.components),r.createElement(s.Provider,{value:o},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/1132b708.3d1fdaf9.js b/assets/js/1132b708.3d1fdaf9.js new file mode 100644 index 00000000..379dd367 --- /dev/null +++ b/assets/js/1132b708.3d1fdaf9.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunklogos_docs=self.webpackChunklogos_docs||[]).push([["1276"],{6767(e,n,o){o.r(n),o.d(n,{metadata:()=>t,default:()=>p,frontMatter:()=>r,contentTitle:()=>c,toc:()=>l,assets:()=>a});var t=JSON.parse('{"id":"messaging/concepts/about-content-topics","title":"About content topics","description":"Understand how content topics filter and route messages, and how naming choices affect privacy.","source":"@site/docs/messaging/concepts/about-content-topics.md","sourceDirName":"messaging/concepts","slug":"/messaging/concepts/content-topics","permalink":"/messaging/concepts/content-topics","draft":false,"unlisted":false,"editUrl":"https://github.com/logos-co/logos-docs/tree/main/docs/messaging/concepts/about-content-topics.md","tags":[],"version":"current","sidebarPosition":2,"frontMatter":{"title":"About content topics","doc_type":"concept","product":"messaging","topics":"content-topics, messaging, privacy","authors":"LordGhostX, fryorcraken","owner":"logos","doc_version":1,"slug":"content-topics","sidebar_position":2},"sidebar":"messagingSidebar","previous":{"title":"Understand Logos Delivery protocols","permalink":"/messaging/concepts/protocols"},"next":{"title":"About network domains","permalink":"/messaging/concepts/network-domains"}}'),i=o(4848),s=o(8453);let r={title:"About content topics",doc_type:"concept",product:"messaging",topics:"content-topics, messaging, privacy",authors:"LordGhostX, fryorcraken",owner:"logos",doc_version:1,slug:"content-topics",sidebar_position:2},c="About content topics",a={},l=[{value:"Understand how content topics filter and route messages, and how naming choices affect privacy.",id:"understand-how-content-topics-filter-and-route-messages-and-how-naming-choices-affect-privacy",level:4},{value:"The basics",id:"the-basics",level:2},{value:"Naming format",id:"naming-format",level:2},{value:"Naming considerations",id:"naming-considerations",level:2},{value:"Protocols disclose content topics to peers",id:"protocols-disclose-content-topics-to-peers",level:3},{value:"Increasing k-anonymity preserves user anonymity",id:"increasing-k-anonymity-preserves-user-anonymity",level:3},{value:"Creating buckets help in distributing traffic",id:"creating-buckets-help-in-distributing-traffic",level:3}];function d(e){let n={a:"a",admonition:"admonition",code:"code",h1:"h1",h2:"h2",h3:"h3",h4:"h4",header:"header",li:"li",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,s.R)(),...e.components};return(0,i.jsxs)(i.Fragment,{children:[(0,i.jsx)(n.header,{children:(0,i.jsx)(n.h1,{id:"about-content-topics",children:"About content topics"})}),"\n",(0,i.jsx)(n.h4,{id:"understand-how-content-topics-filter-and-route-messages-and-how-naming-choices-affect-privacy",children:"Understand how content topics filter and route messages, and how naming choices affect privacy."}),"\n",(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.code,{children:"Content Topics"})," are metadata strings set by developers on outgoing messages to facilitate protocol-level features like selectively processing incoming messages (",(0,i.jsx)(n.a,{href:"/messaging/concepts/protocols#relay",children:"Relay"})," or ",(0,i.jsx)(n.a,{href:"/messaging/concepts/protocols#filter",children:"Filter"}),") and retrieving historical messages (",(0,i.jsx)(n.a,{href:"/messaging/concepts/protocols#store",children:"Store"}),") that meet specific filtering criteria."]}),"\n",(0,i.jsx)(n.admonition,{type:"info",children:(0,i.jsxs)(n.p,{children:["Check out the ",(0,i.jsx)(n.a,{href:"https://lip.logos.co/messaging/informational/draft/23/topics.html#content-topics",children:"WAKU2-TOPICS"})," specification to learn more."]})}),"\n",(0,i.jsx)(n.h2,{id:"the-basics",children:"The basics"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#content-topic",children:"Content topics"})," are developer-set metadata strings that let protocols filter, route, and retrieve messages."]}),"\n",(0,i.jsxs)(n.li,{children:["The recommended ",(0,i.jsx)(n.a,{href:"#naming-format",children:"naming format"})," combines an application name, version, content topic name, and encoding."]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.a,{href:"#naming-considerations",children:"Naming considerations"})," matter for privacy, because some protocols disclose content topics to peers."]}),"\n"]}),"\n",(0,i.jsx)(n.h2,{id:"naming-format",children:"Naming format"}),"\n",(0,i.jsx)(n.p,{children:"Here is the recommended format for content topics:"}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.code,{children:"/{application-name}/{version}/{content-topic-name}/{encoding}"})}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"application-name"}),": This is the unique name of your decentralised application (DApp) to prevent conflicts with other DApps."]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"version"}),": Typically starting at ",(0,i.jsx)(n.code,{children:"1"}),", this field helps track breaking changes in your messages."]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"content-topic-name"}),": The specific name of the content topic used for filtering."]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"encoding"}),": The message encoding or serialisation format, with ",(0,i.jsx)(n.a,{href:"https://protobuf.dev/",children:"Protocol Buffers"})," (",(0,i.jsx)(n.code,{children:"proto"}),") being the recommended choice."]}),"\n"]}),"\n",(0,i.jsxs)(n.p,{children:["For example, if your DApp is called ",(0,i.jsx)(n.code,{children:"SuperCrypto"})," and it allows users to receive notifications and send private messages, you can consider using the following content topics:"]}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsx)(n.li,{children:(0,i.jsx)(n.code,{children:"/supercrypto/1/notification/proto"})}),"\n",(0,i.jsx)(n.li,{children:(0,i.jsx)(n.code,{children:"/supercrypto/1/private-message/proto"})}),"\n"]}),"\n",(0,i.jsx)(n.admonition,{type:"tip",children:(0,i.jsxs)(n.p,{children:["While you can choose any encoding format for your ",(0,i.jsx)(n.code,{children:"Content Topic"}),", we highly recommend using Protocol Buffers (",(0,i.jsx)(n.code,{children:"proto"}),") because of its efficiency. Choosing a lightweight format ensures optimal performance of your DApp."]})}),"\n",(0,i.jsx)(n.h2,{id:"naming-considerations",children:"Naming considerations"}),"\n",(0,i.jsx)(n.p,{children:"When choosing a content topic, it is essential to consider the privacy implications. Here are several factors to keep in mind:"}),"\n",(0,i.jsx)(n.h3,{id:"protocols-disclose-content-topics-to-peers",children:"Protocols disclose content topics to peers"}),"\n",(0,i.jsxs)(n.p,{children:["The ",(0,i.jsx)(n.code,{children:"Filter"}),", ",(0,i.jsx)(n.code,{children:"Store"}),", and ",(0,i.jsx)(n.code,{children:"Light Push"})," protocols share content topics with peers, allowing them to link IP and content topic interests. The ",(0,i.jsx)(n.code,{children:"Relay"})," protocol, using ",(0,i.jsx)(n.code,{children:"GossipSub"}),", provides recipient anonymity, which can be compromised if the content topic exposes user information."]}),"\n",(0,i.jsx)(n.p,{children:"For example, instead of using Personally Identifiable Information (PII) in the content topic (e.g., a public key), you can create buckets (e.g., based on the first 4 bytes of the public key hash)."}),"\n",(0,i.jsx)(n.admonition,{type:"info",children:(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#logos-delivery",children:"Logos Delivery"})," is developing privacy-preserving features like ",(0,i.jsx)(n.a,{href:"https://lip.logos.co/messaging/core/draft/12/previous-versions/00/filter.html#future-work",children:"Anonymous Filter Subscription"})," for the ",(0,i.jsx)(n.code,{children:"Filter"})," protocol and ",(0,i.jsx)(n.a,{href:"https://lip.logos.co/messaging/core/draft/13/store.html#future-work",children:"Anonymous Query"})," for the ",(0,i.jsx)(n.code,{children:"Store"})," protocol to hide content topics from potential adversaries."]})}),"\n",(0,i.jsx)(n.h3,{id:"increasing-k-anonymity-preserves-user-anonymity",children:"Increasing k-anonymity preserves user anonymity"}),"\n",(0,i.jsxs)(n.p,{children:["You can increase ",(0,i.jsx)(n.a,{href:"https://www.privitar.com/blog/k-anonymity-an-introduction/",children:"k-anonymity"})," within the network by using a unified content topic across the entire application or targeting specific features like notifications or private messages, allowing multiple users to share it."]}),"\n",(0,i.jsxs)(n.p,{children:["We recommend switching functionality using the Protocol Buffer (",(0,i.jsx)(n.code,{children:"proto"}),") message format. By doing so, applications can retain a high granularity and functionality while using a single content topic, preserving user privacy. For example:"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-js",children:"message NotificationPayload {\n...\n}\n\nmessage FeatureAbcPayload {\n...\n}\n\n// By default, all fields in protobuf are optional so only field may be encoded at a time\nmessage Payload {\n NotificationPayload notification = 1;\n FeatureAbcPayload feature_abc = 2;\n}\n"})}),"\n",(0,i.jsx)(n.h3,{id:"creating-buckets-help-in-distributing-traffic",children:"Creating buckets help in distributing traffic"}),"\n",(0,i.jsxs)(n.p,{children:["When an application uses a single content topic, all users using ",(0,i.jsx)(n.a,{href:"/messaging/concepts/network-domains#requestresponse-domain",children:"request/response protocols"})," (",(0,i.jsx)(n.code,{children:"Filter"}),", ",(0,i.jsx)(n.code,{children:"Store"}),") receive all its messages. For heavy traffic, developers can create buckets by hashing a unique identifier (e.g., recipient's ID, public key, or app domain topic) and adding its first byte to the content topic, like ",(0,i.jsx)(n.code,{children:"/my-app/0/a/proto"}),"."]}),"\n",(0,i.jsx)(n.p,{children:"This approach divides traffic into multiple topics, reducing the messages users have to download. Developers can add more first bytes to the content topic over time to improve efficiency and privacy based on messages and user needs."}),"\n",(0,i.jsx)(n.admonition,{type:"info",children:(0,i.jsxs)(n.p,{children:["The ",(0,i.jsx)(n.strong,{children:"k"})," value of ",(0,i.jsx)(n.strong,{children:"k-anonymity"})," equals the number of IDs for which the first character of the hash is ",(0,i.jsx)(n.code,{children:'"a"'}),". For example, using a single content topic in an application with 10,000 users results in ",(0,i.jsx)(n.strong,{children:"k = 10,000"}),". However, using the hash ID's first character, ",(0,i.jsx)(n.strong,{children:"k"})," reduces to ",(0,i.jsx)(n.strong,{children:"10,000 / 16 = 625"}),"."]})})]})}function p(e={}){let{wrapper:n}={...(0,s.R)(),...e.components};return n?(0,i.jsx)(n,{...e,children:(0,i.jsx)(d,{...e})}):d(e)}},8453(e,n,o){o.d(n,{R:()=>r,x:()=>c});var t=o(6540);let i={},s=t.createContext(i);function r(e){let n=t.useContext(s);return t.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function c(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(i):e.components||i:r(e.components),t.createElement(s.Provider,{value:n},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/14eb3368.d320f215.js b/assets/js/14eb3368.d320f215.js new file mode 100644 index 00000000..53535e72 --- /dev/null +++ b/assets/js/14eb3368.d320f215.js @@ -0,0 +1,4 @@ +"use strict";(self.webpackChunklogos_docs=self.webpackChunklogos_docs||[]).push([["7234"],{919(e,t,n){n.d(t,{A:()=>j});var s=n(4848);n(6540);var r=n(4164),i=n(8287),a=n(6877),l=n(9209),o=n(5310),c=n(568),d=n(6497);function u(e){return(0,s.jsx)("svg",{viewBox:"0 0 24 24",...e,children:(0,s.jsx)("path",{d:"M10 19v-5h4v5c0 .55.45 1 1 1h3c.55 0 1-.45 1-1v-7h1.7c.46 0 .68-.57.33-.87L12.67 3.6c-.38-.34-.96-.34-1.34 0l-8.36 7.53c-.34.3-.13.87.33.87H5v7c0 .55.45 1 1 1h3c.55 0 1-.45 1-1z",fill:"currentColor"})})}function m(){let e=(0,d.Ay)("/");return(0,s.jsx)("li",{className:"breadcrumbs__item",children:(0,s.jsx)(o.A,{"aria-label":(0,c.T)({id:"theme.docs.breadcrumbs.home",message:"Home page",description:"The ARIA label for the home page in the breadcrumbs"}),className:"breadcrumbs__link",href:e,children:(0,s.jsx)(u,{className:"breadcrumbHomeIcon_YNFT"})})})}var h=n(3572),x=n(898);function b(e){let t=function({breadcrumbs:e}){let{siteConfig:t}=(0,x.A)();return{"@context":"https://schema.org","@type":"BreadcrumbList",itemListElement:e.filter(e=>e.href).map((e,n)=>({"@type":"ListItem",position:n+1,name:e.label,item:`${t.url}${e.href}`}))}}({breadcrumbs:e.breadcrumbs});return(0,s.jsx)(h.A,{children:(0,s.jsx)("script",{type:"application/ld+json",children:JSON.stringify(t)})})}function p({children:e,href:t,isLast:n}){let r="breadcrumbs__link";return n?(0,s.jsx)("span",{className:r,children:e}):t?(0,s.jsx)(o.A,{className:r,href:t,children:(0,s.jsx)("span",{children:e})}):(0,s.jsx)("span",{className:r,children:e})}function g({children:e,active:t}){return(0,s.jsx)("li",{className:(0,r.A)("breadcrumbs__item",{"breadcrumbs__item--active":t}),children:e})}function j(){let e=(0,a.OF)(),t=(0,l.Dt)();return e?(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(b,{breadcrumbs:e}),(0,s.jsx)("nav",{className:(0,r.A)(i.G.docs.docBreadcrumbs,"breadcrumbsContainer_Z_bl"),"aria-label":(0,c.T)({id:"theme.docs.breadcrumbs.navAriaLabel",message:"Breadcrumbs",description:"The ARIA label for the breadcrumbs"}),children:(0,s.jsxs)("ul",{className:"breadcrumbs",children:[t&&(0,s.jsx)(m,{}),e.map((t,n)=>{let r=n===e.length-1,i="category"===t.type&&t.linkUnlisted?void 0:t.href;return(0,s.jsx)(g,{active:r,children:(0,s.jsx)(p,{href:i,isLast:r,children:t.label})},n)})]})})]}):null}},4922(e,t,n){n.r(t),n.d(t,{default:()=>M});var s=n(4848),r=n(6540),i=n(4308),a=n(6877),l=n(6497),o=n(4164);let c=new Intl.Segmenter(void 0,{granularity:"grapheme"});var d=n(568),u=n(898);let m=["zero","one","two","few","many","other"];function h(e){return m.filter(t=>e.includes(t))}let x={locale:"en",pluralForms:h(["one","other"]),select:e=>1===e?"one":"other"};var b=n(8310),p=n(5310),g=n(8287),j=n(2072);function v({icon:e}){return(0,s.jsx)("span",{className:(0,o.A)(g.G.docs.docCard.icon,"cardTitleIcon_GcdP"),children:e})}function f({title:e}){return(0,s.jsx)("span",{className:(0,o.A)("text--truncate",g.G.docs.docCard.title,"cardTitleText_nuEl"),children:e})}function A({item:e,title:t,icon:n}){return(0,s.jsxs)(j.A,{as:"h2",className:(0,o.A)(g.G.docs.docCard.heading,"cardTitle_mcqP"),title:t,children:[n&&(0,s.jsx)(v,{item:e,icon:n}),(0,s.jsx)(f,{item:e,title:t})]})}function N({description:e}){return(0,s.jsx)("p",{className:(0,o.A)("text--truncate",g.G.docs.docCard.description,"cardDescription_L2fP"),title:e,children:e})}function _({className:e,href:t,children:n}){return(0,s.jsx)(p.A,{href:t,className:(0,o.A)("card padding--lg",g.G.docs.docCard.container,"cardContainer_KhuF",e),children:n})}function y({item:e,className:t,href:n,icon:r,title:i,description:a}){return(0,s.jsxs)(_,{href:n,className:t,children:[(0,s.jsx)(A,{item:e,icon:r,title:i}),a&&(0,s.jsx)(N,{item:e,description:a})]})}function L(e){var t;let n,s,r=(t=e.label,n=c.segment(t)[Symbol.iterator](),(s=n.next().value?.segment)&&RegExp("^\\p{RGI_Emoji}$","v").test(s)?{emoji:s,rest:t.slice(s.length)}:{emoji:null,rest:t});return{icon:r.emoji??("category"===e.type?"\u{1F5C3}":(0,b.A)(e.href)?"\u{1F4C4}\uFE0F":"\u{1F517}"),title:r.rest.trim()}}function T({item:e}){let t=(0,a.Nr)(e),n=function(){let e,{selectMessage:t}=(e=function(){let{i18n:{currentLocale:e}}=(0,u.A)();return(0,r.useMemo)(()=>{try{let t;return t=new Intl.PluralRules(e),{locale:e,pluralForms:h(t.resolvedOptions().pluralCategories),select:e=>t.select(e)}}catch(t){return console.error(`Failed to use Intl.PluralRules for locale "${e}". +Docusaurus will fallback to the default (English) implementation. +Error: ${t.message} +`),x}},[e])}(),{selectMessage:(t,n)=>(function(e,t,n){let s=e.split("|");if(1===s.length)return s[0];s.length>n.pluralForms.length&&console.error(`For locale=${n.locale}, a maximum of ${n.pluralForms.length} plural forms are expected (${n.pluralForms.join(",")}), but the message contains ${s.length}: ${e}`);let r=n.select(t);return s[Math.min(n.pluralForms.indexOf(r),s.length-1)]})(n,t,e)});return e=>t(e,(0,d.T)({message:"1 item|{count} items",id:"theme.docs.DocCard.categoryDescription.plurals",description:"The default description for a category card in the generated index about how many items this category includes"},{count:e}))}();return t?(0,s.jsx)(y,{item:e,className:e.className,href:t,description:e.description??n(e.items.length),...L(e)}):null}function k({item:e}){let t=(0,a.cC)(e.docId??void 0);return(0,s.jsx)(y,{item:e,className:e.className,href:e.href,description:e.description??t?.description,...L(e)})}function w({item:e}){switch(e.type){case"link":return(0,s.jsx)(k,{item:e});case"category":return(0,s.jsx)(T,{item:e});default:throw Error(`unknown item type ${JSON.stringify(e)}`)}}function C({className:e}){let t=(0,a.a4)();return(0,s.jsx)(F,{items:t,className:e})}function I({item:e}){return(0,s.jsx)("article",{className:(0,o.A)("docCardListItem_W1sv","col col--6"),children:(0,s.jsx)(w,{item:e})})}function F(e){let{items:t,className:n}=e;if(!t)return(0,s.jsx)(C,{...e});let r=(0,a.d1)(t);return(0,s.jsx)("section",{className:(0,o.A)("row",n),children:r.map((e,t)=>(0,s.jsx)(I,{item:e},t))})}var $=n(1660),G=n(5886),P=n(5587),V=n(919);let E={generatedIndexPage:"generatedIndexPage_vN6x",title:"title_kItE"};function B({categoryGeneratedIndex:e}){return(0,s.jsx)(i.be,{title:e.title,description:e.description,keywords:e.keywords,image:(0,l.Ay)(e.image)})}function D({categoryGeneratedIndex:e}){let t=(0,a.$S)();return(0,s.jsxs)("div",{className:E.generatedIndexPage,children:[(0,s.jsx)(G.A,{}),(0,s.jsx)(V.A,{}),(0,s.jsx)(P.A,{}),(0,s.jsxs)("header",{children:[(0,s.jsx)(j.A,{as:"h1",className:E.title,children:e.title}),e.description&&(0,s.jsx)("p",{children:e.description})]}),(0,s.jsx)("article",{className:"margin-top--lg",children:(0,s.jsx)(F,{items:t.items,className:E.list})}),(0,s.jsx)("footer",{className:"margin-top--md",children:(0,s.jsx)($.A,{previous:e.navigation.previous,next:e.navigation.next})})]})}function M(e){return(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(B,{...e}),(0,s.jsx)(D,{...e})]})}},1660(e,t,n){n.d(t,{A:()=>o});var s=n(4848);n(6540);var r=n(4164),i=n(568),a=n(5310);function l(e){let{permalink:t,title:n,subLabel:i,isNext:l}=e;return(0,s.jsxs)(a.A,{className:(0,r.A)("pagination-nav__link",l?"pagination-nav__link--next":"pagination-nav__link--prev"),to:t,children:[i&&(0,s.jsx)("div",{className:"pagination-nav__sublabel",children:i}),(0,s.jsx)("div",{className:"pagination-nav__label",children:n})]})}function o(e){let{className:t,previous:n,next:a}=e;return(0,s.jsxs)("nav",{className:(0,r.A)(t,"pagination-nav"),"aria-label":(0,i.T)({id:"theme.docs.paginator.navAriaLabel",message:"Docs pages",description:"The ARIA label for the docs pagination"}),children:[n&&(0,s.jsx)(l,{...n,subLabel:(0,s.jsx)(i.A,{id:"theme.docs.paginator.previous",description:"The label used to navigate to the previous doc",children:"Previous"})}),a&&(0,s.jsx)(l,{...a,subLabel:(0,s.jsx)(i.A,{id:"theme.docs.paginator.next",description:"The label used to navigate to the next doc",children:"Next"}),isNext:!0})]})}},5587(e,t,n){n.d(t,{A:()=>o});var s=n(4848);n(6540);var r=n(4164),i=n(568),a=n(8287),l=n(921);function o({className:e}){let t=(0,l.r)();return t.badge?(0,s.jsx)("span",{className:(0,r.A)(e,a.G.docs.docVersionBadge,"badge badge--secondary"),children:(0,s.jsx)(i.A,{id:"theme.docs.versionBadge.label",values:{versionLabel:t.label},children:"Version: {versionLabel}"})}):null}},5886(e,t,n){n.d(t,{A:()=>p});var s=n(4848);n(6540);var r=n(4164),i=n(898),a=n(5310),l=n(568),o=n(698),c=n(8287),d=n(2086),u=n(921);let m={unreleased:function({siteTitle:e,versionMetadata:t}){return(0,s.jsx)(l.A,{id:"theme.docs.versions.unreleasedVersionLabel",description:"The label used to tell the user that he's browsing an unreleased doc version",values:{siteTitle:e,versionLabel:(0,s.jsx)("b",{children:t.label})},children:"This is unreleased documentation for {siteTitle} {versionLabel} version."})},unmaintained:function({siteTitle:e,versionMetadata:t}){return(0,s.jsx)(l.A,{id:"theme.docs.versions.unmaintainedVersionLabel",description:"The label used to tell the user that he's browsing an unmaintained doc version",values:{siteTitle:e,versionLabel:(0,s.jsx)("b",{children:t.label})},children:"This is documentation for {siteTitle} {versionLabel}, which is no longer actively maintained."})}};function h(e){let t=m[e.versionMetadata.banner];return(0,s.jsx)(t,{...e})}function x({versionLabel:e,to:t,onClick:n}){return(0,s.jsx)(l.A,{id:"theme.docs.versions.latestVersionSuggestionLabel",description:"The label used to tell the user to check the latest version",values:{versionLabel:e,latestVersionLink:(0,s.jsx)("b",{children:(0,s.jsx)(a.A,{to:t,onClick:n,children:(0,s.jsx)(l.A,{id:"theme.docs.versions.latestVersionLinkLabel",description:"The label used for the latest version suggestion link label",children:"latest version"})})})},children:"For up-to-date documentation, see the {latestVersionLink} ({versionLabel})."})}function b({className:e,versionMetadata:t}){let{siteConfig:{title:n}}=(0,i.A)(),{pluginId:a}=(0,o.vT)({failfast:!0}),{savePreferredVersionName:l}=(0,d.g1)(a),{latestDocSuggestion:u,latestVersionSuggestion:m}=(0,o.HW)(a),p=u??m.docs.find(e=>e.id===m.mainDocId);return(0,s.jsxs)("div",{className:(0,r.A)(e,c.G.docs.docVersionBanner,"alert alert--warning margin-bottom--md"),role:"alert",children:[(0,s.jsx)("div",{children:(0,s.jsx)(h,{siteTitle:n,versionMetadata:t})}),(0,s.jsx)("div",{className:"margin-top--md",children:(0,s.jsx)(x,{versionLabel:m.label,to:p.path,onClick:()=>l(m.name)})})]})}function p({className:e}){let t=(0,u.r)();return t.banner?(0,s.jsx)(b,{className:e,versionMetadata:t}):null}}}]); \ No newline at end of file diff --git a/assets/js/15d6e2f3.57316c06.js b/assets/js/15d6e2f3.57316c06.js new file mode 100644 index 00000000..94ecdaa2 --- /dev/null +++ b/assets/js/15d6e2f3.57316c06.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunklogos_docs=self.webpackChunklogos_docs||[]).push([["9460"],{6568(e,n,t){t.r(n),t.d(n,{metadata:()=>s,default:()=>h,frontMatter:()=>a,contentTitle:()=>o,toc:()=>d,assets:()=>c});var s=JSON.parse('{"id":"lez/transfer-tokens/transfer-native-lez-tokens-between-public-private-states","title":"Transfer native LEZ tokens between public and private states","description":"Get started with private transfers to accounts you don\'t control, using a recipient-published keypair.","source":"@site/docs/lez/transfer-tokens/transfer-native-lez-tokens-between-public-private-states.md","sourceDirName":"lez/transfer-tokens","slug":"/lez/transfer-tokens/transfer-native-lez-tokens-between-public-private-states","permalink":"/lez/transfer-tokens/transfer-native-lez-tokens-between-public-private-states","draft":false,"unlisted":false,"editUrl":"https://github.com/logos-co/logos-docs/tree/main/docs/lez/transfer-tokens/transfer-native-lez-tokens-between-public-private-states.md","tags":[],"version":"current","sidebarPosition":3,"frontMatter":{"title":"Transfer native LEZ tokens between public and private states","doc_type":"procedure","product":"lez","topics":"lez","steps_layout":"sectioned","authors":"moudyellaz, kashepavadan","owner":"logos","doc_version":1,"slug":"transfer-native-lez-tokens-between-public-private-states","sidebar_position":3},"sidebar":"lezSidebar","previous":{"title":"Create and transfer custom tokens on the Logos Execution Zone","permalink":"/lez/transfer-tokens/create-and-transfer-custom-tokens-on-the-logos-execution-zone"},"next":{"title":"Run an LEZ Indexer","permalink":"/lez/follow-chain/run-lez-indexer"}}'),i=t(4848),r=t(8453);let a={title:"Transfer native LEZ tokens between public and private states",doc_type:"procedure",product:"lez",topics:"lez",steps_layout:"sectioned",authors:"moudyellaz, kashepavadan",owner:"logos",doc_version:1,slug:"transfer-native-lez-tokens-between-public-private-states",sidebar_position:3},o="Transfer native LEZ tokens between public and private states",c={},d=[{value:"Get started with private transfers to accounts you don't control, using a recipient-published keypair.",id:"get-started-with-private-transfers-to-accounts-you-dont-control-using-a-recipient-published-keypair",level:4},{value:"What to expect",id:"what-to-expect",level:2},{value:"Step 1: Publish a reusable keypair as the recipient",id:"step-1-publish-a-reusable-keypair-as-the-recipient",level:2},{value:"Step 2: Send funds to the recipient's private account",id:"step-2-send-funds-to-the-recipients-private-account",level:2},{value:"Step 3: Discover and spend the incoming funds as the recipient",id:"step-3-discover-and-spend-the-incoming-funds-as-the-recipient",level:2},{value:"Troubleshooting private foreign transfers",id:"troubleshooting-private-foreign-transfers",level:2},{value:"Why does the recipient not see the funds after sync-private?",id:"why-does-the-recipient-not-see-the-funds-after-sync-private",level:3},{value:"Why does the second transfer to the same (NPK, identifier) fail?",id:"why-does-the-second-transfer-to-the-same-npk-identifier-fail",level:3},{value:"Why does spending multiple incoming transfers become expensive?",id:"why-does-spending-multiple-incoming-transfers-become-expensive",level:3}];function l(e){let n={a:"a",admonition:"admonition",code:"code",h1:"h1",h2:"h2",h3:"h3",h4:"h4",header:"header",li:"li",ol:"ol",p:"p",pre:"pre",ul:"ul",...(0,r.R)(),...e.components};return(0,i.jsxs)(i.Fragment,{children:[(0,i.jsx)(n.header,{children:(0,i.jsx)(n.h1,{id:"transfer-native-lez-tokens-between-public-and-private-states",children:"Transfer native LEZ tokens between public and private states"})}),"\n",(0,i.jsx)(n.h4,{id:"get-started-with-private-transfers-to-accounts-you-dont-control-using-a-recipient-published-keypair",children:"Get started with private transfers to accounts you don't control, using a recipient-published keypair."}),"\n",(0,i.jsxs)(n.p,{children:["This procedure covers how to credit a ",(0,i.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#private-account",children:"private account"})," \u2014 regular or a ",(0,i.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#program-derived-address",children:"Program Derived Address"})," (",(0,i.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#pda",children:"PDA"}),") \u2014 that you do not control, using only the recipient's published keypair and the sender's chosen identifier. It is intended for wallet users on testnet v0.2 who need to make private payments without interactive setup or per-sender ",(0,i.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#account",children:"account"})," registration. For example, a recipient can publish one keypair and receive from many independent senders, each into a separate account."]}),"\n",(0,i.jsx)(n.p,{children:"Before you start, make sure you have the following:"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsx)(n.li,{children:"Linux or macOS \u2014 macOS requires full Xcode with the Metal toolchain for the Risc0 guest build, not just command-line tools"}),"\n",(0,i.jsxs)(n.li,{children:["Rust toolchain, ",(0,i.jsx)(n.code,{children:"cargo"})]}),"\n",(0,i.jsxs)(n.li,{children:["A local clone of the ",(0,i.jsx)(n.a,{href:"https://github.com/logos-blockchain/logos-execution-zone",children:"LEZ repository"})," and ",(0,i.jsx)(n.a,{href:"/lez/get-started/connect-wallet-cli-to-lez-testnet",children:"wallet"})," set up and funded"]}),"\n"]}),"\n",(0,i.jsx)(n.h2,{id:"what-to-expect",children:"What to expect"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:["You can credit a recipient's private account using only their published ",(0,i.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#npk",children:"NPK"})," and ",(0,i.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#vpk",children:"VPK"}),", with no interactive setup required."]}),"\n",(0,i.jsx)(n.li,{children:"You can receive tokens into up to 2^128 distinct accounts from the same keypair by varying the identifier, so one published key serves many independent senders."}),"\n",(0,i.jsxs)(n.li,{children:["You can discover and spend incoming funds with ",(0,i.jsx)(n.code,{children:"wallet account sync-private"})," after the sender's transaction is confirmed."]}),"\n"]}),"\n",(0,i.jsx)(n.h2,{id:"step-1-publish-a-reusable-keypair-as-the-recipient",children:"Step 1: Publish a reusable keypair as the recipient"}),"\n",(0,i.jsx)(n.p,{children:"The recipient generates a reusable keypair (NPK and VPK) and shares it with senders."}),"\n",(0,i.jsxs)(n.ol,{children:["\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsx)(n.p,{children:"Generate and publish a reusable keypair:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-sh",children:"wallet account new private-accounts-key\n"})}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:["This prints the ",(0,i.jsx)(n.code,{children:"chain_index"}),", NPK, and VPK. No account is bound to the keypair yet."]}),"\n"]}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsx)(n.p,{children:"Export the keys to share with senders:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-sh",children:"wallet account show-keys --account-id \n"})}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:["This prints NPK (hex) then VPK (hex). Save the output to a file to use with ",(0,i.jsx)(n.code,{children:"--to-keys"})," later."]}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,i.jsx)(n.h2,{id:"step-2-send-funds-to-the-recipients-private-account",children:"Step 2: Send funds to the recipient's private account"}),"\n",(0,i.jsx)(n.p,{children:"The sender credits the recipient's account at a chosen identifier. The circuit initialises the account, emits its commitment and a deterministic nullifier, and encrypts the post-state to the recipient via ephemeral ECDH against the recipient VPK."}),"\n",(0,i.jsx)(n.admonition,{type:"warning",children:(0,i.jsx)(n.p,{children:"Two senders who independently pick the same identifier for the same NPK target the same account. The second transfer fails at the commitment/nullifier layer. Use high-entropy identifiers to avoid collisions."})}),"\n",(0,i.jsxs)(n.ol,{children:["\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsx)(n.p,{children:"Send funds to the recipient's private account using their published keys:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-sh",children:"wallet auth-transfer send \\\n --from \\\n --to-npk \\\n --to-vpk \\\n --to-identifier \\\n --amount 100\n"})}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:["Pass ",(0,i.jsx)(n.code,{children:"--to-keys "})," instead of ",(0,i.jsx)(n.code,{children:"--to-npk"}),"/",(0,i.jsx)(n.code,{children:"--to-vpk"})," if you saved the keys to a file in Step 1."]}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,i.jsx)(n.h2,{id:"step-3-discover-and-spend-the-incoming-funds-as-the-recipient",children:"Step 3: Discover and spend the incoming funds as the recipient"}),"\n",(0,i.jsxs)(n.p,{children:["The recipient scans for incoming transfers, decrypting the ",(0,i.jsx)(n.code,{children:"PrivateAccountKind"})," header to reconstruct the account ID, then spends from the discovered account."]}),"\n",(0,i.jsxs)(n.ol,{children:["\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsx)(n.p,{children:"Scan and decrypt incoming funds:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-sh",children:"wallet account sync-private\n"})}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:["The wallet decrypts the ciphertext header for each incoming transfer and reconstructs ",(0,i.jsx)(n.code,{children:"AccountId = SHA256(prefix || npk || identifier)"})," for accounts where the VPK matches."]}),"\n",(0,i.jsxs)(n.li,{children:["Each incoming transfer appears as a separate account (",(0,i.jsx)(n.code,{children:"PrivateOwned"})," or ",(0,i.jsx)(n.code,{children:"PrivatePdaOwned"}),"), distinguished by identifier."]}),"\n"]}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsx)(n.p,{children:"Spend from the discovered account:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-sh",children:"wallet auth-transfer send \\\n --from \\\n --to-npk \\\n --to-vpk \\\n --to-identifier \\\n --amount 50\n"})}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:["The recipient account must have been discovered by ",(0,i.jsx)(n.code,{children:"sync-private"})," in the previous step before it can be used as a ",(0,i.jsx)(n.code,{children:"--from"})," source."]}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,i.jsx)(n.h2,{id:"troubleshooting-private-foreign-transfers",children:"Troubleshooting private foreign transfers"}),"\n",(0,i.jsxs)(n.h3,{id:"why-does-the-recipient-not-see-the-funds-after-sync-private",children:["Why does the recipient not see the funds after ",(0,i.jsx)(n.code,{children:"sync-private"}),"?"]}),"\n",(0,i.jsxs)(n.p,{children:["The wallet reconstructs the account ID from the decrypted ",(0,i.jsx)(n.code,{children:"PrivateAccountKind"})," header using the exact NPK, VPK, and identifier the sender used. Confirm that the sender used the NPK and VPK published by the recipient in Step 1 and the agreed-upon identifier, then re-run ",(0,i.jsx)(n.code,{children:"wallet account sync-private"}),"."]}),"\n",(0,i.jsx)(n.h3,{id:"why-does-the-second-transfer-to-the-same-npk-identifier-fail",children:"Why does the second transfer to the same (NPK, identifier) fail?"}),"\n",(0,i.jsxs)(n.p,{children:["Two transfers to the same ",(0,i.jsx)(n.code,{children:"(npk, identifier)"})," pair resolve to the same ",(0,i.jsx)(n.code,{children:"AccountId"})," and collide at the commitment/nullifier layer (",(0,i.jsx)(n.code,{children:"check_commitments_are_new"})," / ",(0,i.jsx)(n.code,{children:"check_nullifiers_are_valid"})," in ",(0,i.jsx)(n.code,{children:"lee/state_machine/src/state.rs"}),"). Each sender must use a unique identifier for a given recipient NPK."]}),"\n",(0,i.jsx)(n.h3,{id:"why-does-spending-multiple-incoming-transfers-become-expensive",children:"Why does spending multiple incoming transfers become expensive?"}),"\n",(0,i.jsx)(n.p,{children:"There is no automatic balance consolidation. N transfers to the same keypair produce N separate accounts, and spending all of them requires N inputs, so proof and transaction cost grows linearly with N. Plan identifier allocation to minimize the number of accounts that need to be spent together."})]})}function h(e={}){let{wrapper:n}={...(0,r.R)(),...e.components};return n?(0,i.jsx)(n,{...e,children:(0,i.jsx)(l,{...e})}):l(e)}},8453(e,n,t){t.d(n,{R:()=>a,x:()=>o});var s=t(6540);let i={},r=s.createContext(i);function a(e){let n=s.useContext(r);return s.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function o(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(i):e.components||i:a(e.components),s.createElement(r.Provider,{value:n},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/17896441.17fe2fbd.js b/assets/js/17896441.17fe2fbd.js new file mode 100644 index 00000000..37f69fd0 --- /dev/null +++ b/assets/js/17896441.17fe2fbd.js @@ -0,0 +1 @@ +(self.webpackChunklogos_docs=self.webpackChunklogos_docs||[]).push([["106"],{8426(e,t){function n(e){let t,n=[];for(let s of e.split(",").map(e=>e.trim()))if(/^-?\d+$/.test(s))n.push(parseInt(s,10));else if(t=s.match(/^(-?\d+)(-|\.\.\.?|\u2025|\u2026|\u22EF)(-?\d+)$/)){let[e,s,r,a]=t;if(s&&a){let e=(s=parseInt(s))<(a=parseInt(a))?1:-1;("-"===r||".."===r||"\u2025"===r)&&(a+=e);for(let t=s;t!==a;t+=e)n.push(t)}}return n}t.default=n,e.exports=n},919(e,t,n){"use strict";n.d(t,{A:()=>b});var s=n(4848);n(6540);var r=n(4164),a=n(8287),i=n(6877),l=n(9209),o=n(5310),c=n(568),d=n(6497);function u(e){return(0,s.jsx)("svg",{viewBox:"0 0 24 24",...e,children:(0,s.jsx)("path",{d:"M10 19v-5h4v5c0 .55.45 1 1 1h3c.55 0 1-.45 1-1v-7h1.7c.46 0 .68-.57.33-.87L12.67 3.6c-.38-.34-.96-.34-1.34 0l-8.36 7.53c-.34.3-.13.87.33.87H5v7c0 .55.45 1 1 1h3c.55 0 1-.45 1-1z",fill:"currentColor"})})}function m(){let e=(0,d.Ay)("/");return(0,s.jsx)("li",{className:"breadcrumbs__item",children:(0,s.jsx)(o.A,{"aria-label":(0,c.T)({id:"theme.docs.breadcrumbs.home",message:"Home page",description:"The ARIA label for the home page in the breadcrumbs"}),className:"breadcrumbs__link",href:e,children:(0,s.jsx)(u,{className:"breadcrumbHomeIcon_YNFT"})})})}var h=n(3572),f=n(898);function x(e){let t=function({breadcrumbs:e}){let{siteConfig:t}=(0,f.A)();return{"@context":"https://schema.org","@type":"BreadcrumbList",itemListElement:e.filter(e=>e.href).map((e,n)=>({"@type":"ListItem",position:n+1,name:e.label,item:`${t.url}${e.href}`}))}}({breadcrumbs:e.breadcrumbs});return(0,s.jsx)(h.A,{children:(0,s.jsx)("script",{type:"application/ld+json",children:JSON.stringify(t)})})}function p({children:e,href:t,isLast:n}){let r="breadcrumbs__link";return n?(0,s.jsx)("span",{className:r,children:e}):t?(0,s.jsx)(o.A,{className:r,href:t,children:(0,s.jsx)("span",{children:e})}):(0,s.jsx)("span",{className:r,children:e})}function j({children:e,active:t}){return(0,s.jsx)("li",{className:(0,r.A)("breadcrumbs__item",{"breadcrumbs__item--active":t}),children:e})}function b(){let e=(0,i.OF)(),t=(0,l.Dt)();return e?(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(x,{breadcrumbs:e}),(0,s.jsx)("nav",{className:(0,r.A)(a.G.docs.docBreadcrumbs,"breadcrumbsContainer_Z_bl"),"aria-label":(0,c.T)({id:"theme.docs.breadcrumbs.navAriaLabel",message:"Breadcrumbs",description:"The ARIA label for the breadcrumbs"}),children:(0,s.jsxs)("ul",{className:"breadcrumbs",children:[t&&(0,s.jsx)(m,{}),e.map((t,n)=>{let r=n===e.length-1,a="category"===t.type&&t.linkUnlisted?void 0:t.href;return(0,s.jsx)(j,{active:r,children:(0,s.jsx)(p,{href:a,isLast:r,children:t.label})},n)})]})})]}):null}},2467(e,t,n){"use strict";n.r(t),n.d(t,{default:()=>ts});var s=n(4848),r=n(6540),a=n(4308),i=n(2260);let l=r.createContext(null);function o({children:e,content:t}){let n=(0,r.useMemo)(()=>({metadata:t.metadata,frontMatter:t.frontMatter,assets:t.assets,contentTitle:t.contentTitle,toc:t.toc}),[t]);return(0,s.jsx)(l.Provider,{value:n,children:e})}function c(){let e=(0,r.useContext)(l);if(null===e)throw new i.dV("DocProvider");return e}function d(){let{metadata:e,frontMatter:t,assets:n}=c();return(0,s.jsx)(a.be,{title:e.title,description:e.description,keywords:t.keywords,image:n.image??t.image})}var u=n(4164),m=n(3741),h=n(1660);function f(){let{metadata:e}=c();return(0,s.jsx)(h.A,{className:"docusaurus-mt-lg",previous:e.previous,next:e.next})}var x=n(5886),p=n(5587),j=n(8287),b=n(568),g=n(5310);function v({permalink:e,label:t,count:n,description:r}){return(0,s.jsxs)(g.A,{rel:"tag",href:e,title:r,className:(0,u.A)("tag_zVej",n?"tagWithCount_h2kH":"tagRegular_sFm0"),children:[t,n&&(0,s.jsx)("span",{children:n})]})}function N({tags:e}){return(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)("b",{children:(0,s.jsx)(b.A,{id:"theme.tags.tagsListLabel",description:"The label alongside a tag list",children:"Tags:"})}),(0,s.jsx)("ul",{className:(0,u.A)("tags_jXut","padding--none","margin-left--sm"),children:e.map(e=>(0,s.jsx)("li",{className:"tag_QGVx",children:(0,s.jsx)(v,{...e})},e.permalink))})]})}function A({className:e,...t}){return(0,s.jsx)("svg",{fill:"currentColor",height:"20",width:"20",viewBox:"0 0 40 40",className:(0,u.A)("iconEdit_Z9Sw",e),"aria-hidden":"true",...t,children:(0,s.jsx)("g",{children:(0,s.jsx)("path",{d:"m34.5 11.7l-3 3.1-6.3-6.3 3.1-3q0.5-0.5 1.2-0.5t1.1 0.5l3.9 3.9q0.5 0.4 0.5 1.1t-0.5 1.2z m-29.5 17.1l18.4-18.5 6.3 6.3-18.4 18.4h-6.3v-6.2z"})})})}function y({editUrl:e}){return(0,s.jsxs)(g.A,{to:e,className:j.G.common.editThisPage,children:[(0,s.jsx)(A,{}),(0,s.jsx)(b.A,{id:"theme.common.editThisPage",description:"The link label to edit the current page",children:"Edit this page"})]})}var _=n(898);function w({lastUpdatedAt:e}){let t=new Date(e),n=(function(e={}){let{i18n:{currentLocale:t}}=(0,_.A)(),n=function(){let{i18n:{currentLocale:e,localeConfigs:t}}=(0,_.A)();return t[e].calendar}();return new Intl.DateTimeFormat(t,{calendar:n,...e})})({day:"numeric",month:"short",year:"numeric",timeZone:"UTC"}).format(t);return(0,s.jsx)(b.A,{id:"theme.lastUpdated.atDate",description:"The words used to describe on which date a page has been last updated",values:{date:(0,s.jsx)("b",{children:(0,s.jsx)("time",{dateTime:t.toISOString(),itemProp:"dateModified",children:n})})},children:" on {date}"})}function C({lastUpdatedBy:e}){return(0,s.jsx)(b.A,{id:"theme.lastUpdated.byUser",description:"The words used to describe by who the page has been last updated",values:{user:(0,s.jsx)("b",{children:e})},children:" by {user}"})}function k({lastUpdatedAt:e,lastUpdatedBy:t}){return(0,s.jsxs)("span",{className:j.G.common.lastUpdated,children:[(0,s.jsx)(b.A,{id:"theme.lastUpdated.lastUpdatedAtBy",description:"The sentence used to display when a page has been last updated, and by who",values:{atDate:e?(0,s.jsx)(w,{lastUpdatedAt:e}):"",byUser:t?(0,s.jsx)(C,{lastUpdatedBy:t}):""},children:"Last updated{atDate}{byUser}"}),!1]})}function L({className:e,editUrl:t,lastUpdatedAt:n,lastUpdatedBy:r}){return(0,s.jsxs)("div",{className:(0,u.A)("row",e),children:[(0,s.jsx)("div",{className:(0,u.A)("col","noPrint_WFHX"),children:t&&(0,s.jsx)(y,{editUrl:t})}),(0,s.jsx)("div",{className:(0,u.A)("col","lastUpdated_JAkA"),children:(n||r)&&(0,s.jsx)(k,{lastUpdatedAt:n,lastUpdatedBy:r})})]})}function T(){let{metadata:e}=c(),{editUrl:t,lastUpdatedAt:n,lastUpdatedBy:r,tags:a}=e,i=a.length>0,l=!!(t||n||r);return i||l?(0,s.jsxs)("footer",{className:(0,u.A)(j.G.docs.docFooter,"docusaurus-mt-lg"),children:[i&&(0,s.jsx)("div",{className:(0,u.A)("row margin-top--sm",j.G.docs.docFooterTagsRow),children:(0,s.jsx)("div",{className:"col",children:(0,s.jsx)(N,{tags:a})})}),l&&(0,s.jsx)(L,{className:(0,u.A)("margin-top--sm",j.G.docs.docFooterEditMetaRow),editUrl:t,lastUpdatedAt:n,lastUpdatedBy:r})]}):null}var B=n(9510),E=n(1022);function M(e){let t=e.getBoundingClientRect();return t.top===t.bottom?M(e.parentNode):t}let H=r.memo(function e({toc:t,className:n,linkClassName:r,isChild:a}){return t.length?(0,s.jsx)("ul",{className:a?void 0:n,children:t.map(t=>(0,s.jsxs)("li",{children:[(0,s.jsx)(g.A,{to:`#${t.id}`,className:r??void 0,dangerouslySetInnerHTML:{__html:t.value}}),(0,s.jsx)(e,{isChild:!0,toc:t.children,className:n,linkClassName:r})]},t.id))}):null});function I({toc:e,className:t="table-of-contents table-of-contents__left-border",linkClassName:n="table-of-contents__link",linkActiveClassName:a,minHeadingLevel:i,maxHeadingLevel:l,...o}){var c;let d,u,m=(0,E.p)(),h=i??m.tableOfContents.minHeadingLevel,f=l??m.tableOfContents.maxHeadingLevel,x=function({toc:e,minHeadingLevel:t,maxHeadingLevel:n}){return(0,r.useMemo)(()=>{let s,r,a;return function e({toc:t,minHeadingLevel:n,maxHeadingLevel:s}){return t.flatMap(t=>{let r=e({toc:t.children,minHeadingLevel:n,maxHeadingLevel:s});return t.level>=n&&t.level<=s?[{...t,children:r}]:r})}({toc:(s=e.map(e=>({...e,parentIndex:-1,children:[]})),r=Array(7).fill(-1),s.forEach((e,t)=>{let n=r.slice(2,e.level);e.parentIndex=Math.max(...n),r[e.level]=t}),a=[],s.forEach(e=>{let{parentIndex:t,...n}=e;t>=0?s[t].children.push(n):a.push(n)}),a),minHeadingLevel:t,maxHeadingLevel:n})},[e,t,n])}({toc:e,minHeadingLevel:h,maxHeadingLevel:f});return c=(0,r.useMemo)(()=>{if(n&&a)return{linkClassName:n,linkActiveClassName:a,minHeadingLevel:h,maxHeadingLevel:f}},[n,a,h,f]),d=(0,r.useRef)(void 0),u=function(){let e=(0,r.useRef)(0),{navbar:{hideOnScroll:t}}=(0,E.p)();return(0,r.useEffect)(()=>{e.current=t?0:document.querySelector(".navbar").clientHeight},[t]),e}(),(0,r.useEffect)(()=>{if(!c)return()=>{};let{linkClassName:e,linkActiveClassName:t,minHeadingLevel:n,maxHeadingLevel:s}=c;function r(){let r=Array.from(document.getElementsByClassName(e)),a=function(e,{anchorTopOffset:t}){let n=e.find(e=>M(e).top>=t);if(n){var s;return(s=M(n)).top>0&&s.bottoma&&a.id===decodeURIComponent(e.href.substring(e.href.indexOf("#")+1)));r.forEach(e=>{e===i?(d.current&&d.current!==e&&d.current.classList.remove(t),e.classList.add(t),d.current=e):e.classList.remove(t)})}return document.addEventListener("scroll",r),document.addEventListener("resize",r),r(),()=>{document.removeEventListener("scroll",r),document.removeEventListener("resize",r)}},[c,u]),(0,s.jsx)(H,{toc:x,className:t,linkClassName:n,...o})}function V({collapsed:e,...t}){return(0,s.jsx)("button",{type:"button",...t,className:(0,u.A)("clean-btn","tocCollapsibleButton_TO0P",!e&&"tocCollapsibleButtonExpanded_MG3E",t.className),children:(0,s.jsx)(b.A,{id:"theme.TOCCollapsible.toggleButtonLabel",description:"The label used by the button on the collapsible TOC component",children:"On this page"})})}function S({toc:e,className:t,minHeadingLevel:n,maxHeadingLevel:r}){let{collapsed:a,toggleCollapsed:i}=(0,B.u)({initialState:!0});return(0,s.jsxs)("div",{className:(0,u.A)("tocCollapsible_ETCw",!a&&"tocCollapsibleExpanded_sAul",t),children:[(0,s.jsx)(V,{collapsed:a,onClick:i}),(0,s.jsx)(B.N,{lazy:!0,className:"tocCollapsibleContent_vkbj",collapsed:a,children:(0,s.jsx)(I,{toc:e,minHeadingLevel:n,maxHeadingLevel:r})})]})}function z(){let{toc:e,frontMatter:t}=c();return(0,s.jsx)(S,{toc:e,minHeadingLevel:t.toc_min_heading_level,maxHeadingLevel:t.toc_max_heading_level,className:(0,u.A)(j.G.docs.docTocMobile,"tocMobile_ITEo")})}function R({className:e,...t}){return(0,s.jsx)("div",{className:(0,u.A)("tableOfContents_bqdL","thin-scrollbar",e),children:(0,s.jsx)(I,{...t,linkClassName:"table-of-contents__link toc-highlight",linkActiveClassName:"table-of-contents__link--active"})})}function O(){let{toc:e,frontMatter:t}=c();return(0,s.jsx)(R,{toc:e,minHeadingLevel:t.toc_min_heading_level,maxHeadingLevel:t.toc_max_heading_level,className:j.G.docs.docTocDesktop})}var U=n(2072),D=n(8453),G=n(3572),$=n(9863),F=n(3941);function P(){let{prism:e}=(0,E.p)(),{colorMode:t}=(0,F.G)(),n=e.theme,s=e.darkTheme||n;return"dark"===t?s:n}var q=n(8426),W=n.n(q);let J=/title=(?["'])(?.*?)\1/,Z=/\{(?<range>[\d,-]+)\}/,X={js:{start:"\\/\\/",end:""},jsBlock:{start:"\\/\\*",end:"\\*\\/"},jsx:{start:"\\{\\s*\\/\\*",end:"\\*\\/\\s*\\}"},bash:{start:"#",end:""},html:{start:"\x3c!--",end:"--\x3e"}},Y={...X,lua:{start:"--",end:""},wasm:{start:"\\;\\;",end:""},tex:{start:"%",end:""},vb:{start:"['\u2018\u2019]",end:""},vbnet:{start:"(?:_\\s*)?['\u2018\u2019]",end:""},rem:{start:"[Rr][Ee][Mm]\\b",end:""},f90:{start:"!",end:""},ml:{start:"\\(\\*",end:"\\*\\)"},cobol:{start:"\\*>",end:""}},Q=Object.keys(X);function K(e,t){let n=e.map(e=>{let{start:n,end:s}=Y[e];return`(?:${n}\\s*(${t.flatMap(e=>[e.line,e.block?.start,e.block?.end].filter(Boolean)).join("|")})\\s*${s})`}).join("|");return RegExp(`^\\s*(?:${n})\\s*$`)}let ee=(0,r.createContext)(null);function et({metadata:e,wordWrap:t,children:n}){let a=(0,r.useMemo)(()=>({metadata:e,wordWrap:t}),[e,t]);return(0,s.jsx)(ee.Provider,{value:a,children:n})}function en(){let e=(0,r.useContext)(ee);if(null===e)throw new i.dV("CodeBlockContextProvider");return e}function es({as:e,...t}){let n,r,a=(n={color:"--prism-color",backgroundColor:"--prism-background-color"},r={},Object.entries(P().plain).forEach(([e,t])=>{let s=n[e];s&&"string"==typeof t&&(r[s]=t)}),r);return(0,s.jsx)(e,{...t,style:a,className:(0,u.A)(t.className,"codeBlockContainer_Ckt0",j.G.common.codeBlock)})}let er="codeBlockLines_e6Vv";function ea({children:e,className:t}){return(0,s.jsx)(es,{as:"pre",tabIndex:0,className:(0,u.A)("codeBlockStandalone_MEMb","thin-scrollbar",t),children:(0,s.jsx)("code",{className:er,children:e})})}let ei={attributes:!0,characterData:!0,childList:!0,subtree:!0};function el({children:e}){return e}var eo=n(1765);function ec({line:e,token:t,...n}){return(0,s.jsx)("span",{...n})}function ed(){return(0,s.jsx)("br",{})}function eu({line:e,classNames:t,showLineNumbers:n,getLineProps:r,getTokenProps:a}){let i,l=(i=1===e.length&&"\n"===e[0].content?e[0]:void 0)?[{...i,content:""}]:e,o=r({line:l,className:(0,u.A)(t,n&&"codeLine_lJS_")}),c=l.map((e,t)=>{let n=a({token:e});return(0,s.jsx)(ec,{...n,line:l,token:e,children:n.children},t)});return(0,s.jsxs)("div",{...o,children:[n?(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)("span",{className:"codeLineNumber_Tfdd"}),(0,s.jsx)("span",{className:"codeLineContent_feaV",children:c})]}):c,(0,s.jsx)(ed,{})]})}let em=r.forwardRef((e,t)=>(0,s.jsx)("pre",{ref:t,tabIndex:0,...e,className:(0,u.A)(e.className,"codeBlock_bY9V","thin-scrollbar")}));function eh(e){let{metadata:t}=en();return(0,s.jsx)("code",{...e,className:(0,u.A)(e.className,er,void 0!==t.lineNumbersStart&&"codeBlockLinesWithNumbering_o6Pm"),style:{...e.style,counterReset:void 0===t.lineNumbersStart?void 0:`line-count ${t.lineNumbersStart-1}`}})}function ef({className:e}){let{metadata:t,wordWrap:n}=en(),r=P(),{code:a,language:i,lineNumbersStart:l,lineClassNames:o}=t;return(0,s.jsx)(eo.f4,{theme:r,code:a,language:i,children:({className:t,style:r,tokens:a,getLineProps:i,getTokenProps:c})=>(0,s.jsx)(em,{ref:n.codeBlockRef,className:(0,u.A)(e,t),style:r,children:(0,s.jsx)(eh,{children:a.map((e,t)=>(0,s.jsx)(eu,{line:e,getLineProps:i,getTokenProps:c,classNames:o[t],showLineNumbers:void 0!==l},t))})})})}function ex({children:e,fallback:t}){return(0,$.A)()?(0,s.jsx)(s.Fragment,{children:e?.()}):t??null}function ep({className:e,...t}){return(0,s.jsx)("button",{type:"button",...t,className:(0,u.A)("clean-btn",e)})}function ej(e){return(0,s.jsx)("svg",{viewBox:"0 0 24 24",...e,children:(0,s.jsx)("path",{fill:"currentColor",d:"M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"})})}function eb(e){return(0,s.jsx)("svg",{viewBox:"0 0 24 24",...e,children:(0,s.jsx)("path",{fill:"currentColor",d:"M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"})})}let eg={copyButtonCopied:"copyButtonCopied_Vdqa",copyButtonIcons:"copyButtonIcons_IEyt",copyButtonIcon:"copyButtonIcon_TrPX",copyButtonSuccessIcon:"copyButtonSuccessIcon_cVMy"};async function ev(e){if(navigator.clipboard)return navigator.clipboard.writeText(e);let{default:t}=await n.e("3693").then(n.bind(n,3436));return t(e)}function eN({className:e}){let{copyCode:t,isCopied:n}=function(){let{metadata:{code:e}}=en(),[t,n]=(0,r.useState)(!1),s=(0,r.useRef)(void 0),a=(0,r.useCallback)(()=>{ev(e).then(()=>{n(!0),s.current=window.setTimeout(()=>{n(!1)},1e3)})},[e]);return(0,r.useEffect)(()=>()=>window.clearTimeout(s.current),[]),{copyCode:a,isCopied:t}}();return(0,s.jsx)(ep,{"aria-label":n?(0,b.T)({id:"theme.CodeBlock.copied",message:"Copied",description:"The copied button label on code blocks"}):(0,b.T)({id:"theme.CodeBlock.copyButtonAriaLabel",message:"Copy code to clipboard",description:"The ARIA label for copy code blocks button"}),title:(0,b.T)({id:"theme.CodeBlock.copy",message:"Copy",description:"The copy button label on code blocks"}),className:(0,u.A)(e,eg.copyButton,n&&eg.copyButtonCopied),onClick:t,children:(0,s.jsxs)("span",{className:eg.copyButtonIcons,"aria-hidden":"true",children:[(0,s.jsx)(ej,{className:eg.copyButtonIcon}),(0,s.jsx)(eb,{className:eg.copyButtonSuccessIcon})]})})}function eA(e){return(0,s.jsx)("svg",{viewBox:"0 0 24 24",...e,children:(0,s.jsx)("path",{fill:"currentColor",d:"M4 19h6v-2H4v2zM20 5H4v2h16V5zm-3 6H4v2h13.25c1.1 0 2 .9 2 2s-.9 2-2 2H15v-2l-3 3l3 3v-2h2c2.21 0 4-1.79 4-4s-1.79-4-4-4z"})})}function ey({className:e}){let{wordWrap:t}=en();if(!(t.isEnabled||t.isCodeScrollable))return!1;let n=(0,b.T)({id:"theme.CodeBlock.wordWrapToggle",message:"Toggle word wrap",description:"The title attribute for toggle word wrapping button of code block lines"});return(0,s.jsx)(ep,{onClick:()=>t.toggle(),className:(0,u.A)(e,t.isEnabled&&"wordWrapButtonEnabled_uzNF"),"aria-label":n,title:n,children:(0,s.jsx)(eA,{className:"wordWrapButtonIcon_b1P5","aria-hidden":"true"})})}function e_({className:e}){return(0,s.jsx)(ex,{children:()=>(0,s.jsxs)("div",{className:(0,u.A)(e,"buttonGroup_M5ko"),children:[(0,s.jsx)(ey,{}),(0,s.jsx)(eN,{})]})})}function ew({className:e}){let{metadata:t}=en();return(0,s.jsxs)(es,{as:"div",className:(0,u.A)(e,t.className),children:[t.title&&(0,s.jsx)("div",{className:"codeBlockTitle_OeMC",children:(0,s.jsx)(el,{children:t.title})}),(0,s.jsxs)("div",{className:"codeBlockContent_QJqH",children:[(0,s.jsx)(ef,{}),(0,s.jsx)(e_,{})]})]})}function eC(e){let t=function(e){let{prism:t}=(0,E.p)();return function(e){var t,n,s,r,a;let i,l=(n=(t={language:e.language,defaultLanguage:e.defaultLanguage,className:e.className}).language??function(e){if(!e)return;let t=e.split(" ").find(e=>e.startsWith("language-"));return t?.replace(/language-/,"")}(t.className)??t.defaultLanguage,n?.toLowerCase()??"text"),{lineClassNames:o,code:c}=(s=e.code,r={metastring:e.metastring,magicComments:e.magicComments,language:l},function(e,{metastring:t,magicComments:n}){if(t&&Z.test(t)){let s=t.match(Z).groups.range;if(0===n.length)throw Error(`A highlight range has been given in code block's metastring (\`\`\` ${t}), but no magic comment config is available. Docusaurus applies the first magic comment entry's className for metastring ranges.`);let r=n[0].className;return{lineClassNames:Object.fromEntries(W()(s).filter(e=>e>0).map(e=>[e-1,[r]])),code:e}}return null}(i=s.replace(/\r?\n$/,""),{...r})??function(e,t){let{language:n,magicComments:s}=t;if(void 0===n)return{lineClassNames:{},code:e};let r=function(e,t){switch(e){case"js":case"javascript":case"ts":case"typescript":return K(["js","jsBlock"],t);case"jsx":case"tsx":return K(["js","jsBlock","jsx"],t);case"html":return K(["js","jsBlock","html"],t);case"python":case"py":case"bash":return K(["bash"],t);case"markdown":case"md":return K(["html","jsx","bash"],t);case"tex":case"latex":case"matlab":return K(["tex"],t);case"lua":case"haskell":return K(["lua"],t);case"sql":return K(["lua","jsBlock"],t);case"wasm":return K(["wasm"],t);case"vb":case"vba":case"visual-basic":return K(["vb","rem"],t);case"vbnet":return K(["vbnet","rem"],t);case"batch":return K(["rem"],t);case"basic":return K(["rem","f90"],t);case"fsharp":return K(["js","ml"],t);case"ocaml":case"sml":return K(["ml"],t);case"fortran":return K(["f90"],t);case"cobol":return K(["cobol"],t);default:return K(Q,t)}}(n,s),a=e.split(/\r?\n/),i=Object.fromEntries(s.map(e=>[e.className,{start:0,range:""}])),l=Object.fromEntries(s.filter(e=>e.line).map(({className:e,line:t})=>[t,e])),o=Object.fromEntries(s.filter(e=>e.block).map(({className:e,block:t})=>[t.start,e])),c=Object.fromEntries(s.filter(e=>e.block).map(({className:e,block:t})=>[t.end,e]));for(let e=0;e<a.length;){let t=a[e].match(r);if(!t){e+=1;continue}let n=t.slice(1).find(e=>void 0!==e);l[n]?i[l[n]].range+=`${e},`:o[n]?i[o[n]].start=e:c[n]&&(i[c[n]].range+=`${i[c[n]].start}-${e-1},`),a.splice(e,1)}let d={};return Object.entries(i).forEach(([e,{range:t}])=>{W()(t).forEach(t=>{d[t]??=[],d[t].push(e)})}),{code:a.join("\n"),lineClassNames:d}}(i,{...r})),d=function({className:e,language:t}){return(0,u.A)(e,t&&!e?.includes(`language-${t}`)&&`language-${t}`)}({className:e.className,language:l}),m=(a=e.metastring,(a?.match(J)?.groups.title??"")||e.title),h=function({showLineNumbers:e,metastring:t}){if("boolean"==typeof e)return e?1:void 0;if("number"==typeof e)return e;let n=t?.split(" ").find(e=>e.startsWith("showLineNumbers"));if(n)return n.startsWith("showLineNumbers=")?parseInt(n.replace("showLineNumbers=",""),10):1}({showLineNumbers:e.showLineNumbers,metastring:e.metastring});return{codeInput:e.code,code:c,className:d,language:l,title:m,lineNumbersStart:h,lineClassNames:o}}({code:e.children,className:e.className,metastring:e.metastring,magicComments:t.magicComments,defaultLanguage:t.defaultLanguage,language:e.language,title:e.title,showLineNumbers:e.showLineNumbers})}(e),n=function(){let[e,t]=(0,r.useState)(!1),[n,s]=(0,r.useState)(!1),a=(0,r.useRef)(null),l=(0,r.useCallback)(()=>{let n=a.current.querySelector("code");e?n.removeAttribute("style"):(n.style.whiteSpace="pre-wrap",n.style.overflowWrap="anywhere"),t(e=>!e)},[a,e]),o=(0,r.useCallback)(()=>{let{scrollWidth:e,clientWidth:t}=a.current;s(e>t||a.current.querySelector("code").hasAttribute("style"))},[a]);return!function(e,t){let[n,s]=(0,r.useState)(),a=(0,r.useCallback)(()=>{s(e.current?.closest("[role=tabpanel][hidden]"))},[e,s]);(0,r.useEffect)(()=>{a()},[a]),function(e,t,n=ei){let s=(0,i._q)(t),a=(0,i.Be)(n);(0,r.useEffect)(()=>{let t=new MutationObserver(s);return e&&t.observe(e,a),()=>t.disconnect()},[e,s,a])}(n,e=>{e.forEach(e=>{"attributes"===e.type&&"hidden"===e.attributeName&&(t(),a())})},{attributes:!0,characterData:!1,childList:!1,subtree:!1})}(a,o),(0,r.useEffect)(()=>{o()},[e,o]),(0,r.useEffect)(()=>(window.addEventListener("resize",o,{passive:!0}),()=>{window.removeEventListener("resize",o)}),[o]),{codeBlockRef:a,isEnabled:e,isCodeScrollable:n,toggle:l}}();return(0,s.jsx)(et,{metadata:t,wordWrap:n,children:(0,s.jsx)(ew,{})})}function ek({children:e,...t}){let n=(0,$.A)(),a=r.Children.toArray(e).some(e=>(0,r.isValidElement)(e))?e:Array.isArray(e)?e.join(""):e;return(0,s.jsx)("string"==typeof a?eC:ea,{...t,children:a},String(n))}function eL(e){return(0,s.jsx)("code",{...e})}var eT=n(4727),eB=n(5916);function eE(e){return!!e&&("SUMMARY"===e.tagName||eE(e.parentElement))}function eM({summary:e,children:t,...n}){(0,eB.A)().collectAnchor(n.id);let a=(0,$.A)(),i=(0,r.useRef)(null),{collapsed:l,setCollapsed:o}=(0,B.u)({initialState:!n.open}),[c,d]=(0,r.useState)(n.open),m=r.isValidElement(e)?e:(0,s.jsx)("summary",{children:e??"Details"});return(0,s.jsxs)("details",{...n,ref:i,open:c,"data-collapsed":l,className:(0,u.A)("details_lb9f",a&&"isBrowser_bmU9",n.className),onMouseDown:e=>{eE(e.target)&&e.detail>1&&e.preventDefault()},onClick:e=>{e.stopPropagation();let t=e.target;eE(t)&&function e(t,n){return!!t&&(t===n||e(t.parentElement,n))}(t,i.current)&&(e.preventDefault(),l?(o(!1),d(!0)):o(!0))},children:[m,(0,s.jsx)(B.N,{lazy:!1,collapsed:l,onCollapseTransitionEnd:e=>{o(e),d(!e)},children:(0,s.jsx)("div",{className:"collapsibleContent_i85q",children:t})})]})}function eH({...e}){return(0,s.jsx)(eM,{...e,className:(0,u.A)("alert alert--info","details_b_Ee",e.className)})}function eI(e){let t=r.Children.toArray(e.children),n=t.find(e=>r.isValidElement(e)&&"summary"===e.type),a=(0,s.jsx)(s.Fragment,{children:t.filter(e=>e!==n)});return(0,s.jsx)(eH,{...e,summary:n,children:a})}function eV(e){return(0,s.jsx)(U.A,{...e})}function eS({type:e,className:t,children:n,id:r}){return(0,s.jsx)("div",{className:(0,u.A)(j.G.common.admonition,j.G.common.admonitionType(e),"admonition_xJq3",t),id:r,children:n})}function ez({icon:e,title:t}){return(0,s.jsxs)("div",{className:"admonitionHeading_Gvgb",children:[(0,s.jsx)("span",{className:"admonitionIcon_Rf37",children:e}),t]})}function eR({children:e}){return e?(0,s.jsx)("div",{className:"admonitionContent_BuS1",children:e}):null}function eO(e){let{type:t,icon:n,title:r,children:a,className:i,id:l}=e;return(0,s.jsxs)(eS,{type:t,className:i,id:l,children:[r||n?(0,s.jsx)(ez,{title:r,icon:n}):null,(0,s.jsx)(eR,{children:a})]})}let eU={icon:(0,s.jsx)(function(e){return(0,s.jsx)("svg",{viewBox:"0 0 14 16",...e,children:(0,s.jsx)("path",{fillRule:"evenodd",d:"M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z"})})},{}),title:(0,s.jsx)(b.A,{id:"theme.admonition.note",description:"The default label used for the Note admonition (:::note)",children:"note"})};function eD(e){return(0,s.jsx)(eO,{...eU,...e,className:(0,u.A)("alert alert--secondary",e.className),children:e.children})}let eG={icon:(0,s.jsx)(function(e){return(0,s.jsx)("svg",{viewBox:"0 0 12 16",...e,children:(0,s.jsx)("path",{fillRule:"evenodd",d:"M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z"})})},{}),title:(0,s.jsx)(b.A,{id:"theme.admonition.tip",description:"The default label used for the Tip admonition (:::tip)",children:"tip"})};function e$(e){return(0,s.jsx)(eO,{...eG,...e,className:(0,u.A)("alert alert--success",e.className),children:e.children})}let eF={icon:(0,s.jsx)(function(e){return(0,s.jsx)("svg",{viewBox:"0 0 14 16",...e,children:(0,s.jsx)("path",{fillRule:"evenodd",d:"M7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 0 1 1.3 8c0-3.14 2.56-5.7 5.7-5.7zM7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7zm1 3H6v5h2V4zm0 6H6v2h2v-2z"})})},{}),title:(0,s.jsx)(b.A,{id:"theme.admonition.info",description:"The default label used for the Info admonition (:::info)",children:"info"})};function eP(e){return(0,s.jsx)(eO,{...eF,...e,className:(0,u.A)("alert alert--info",e.className),children:e.children})}function eq(e){return(0,s.jsx)("svg",{viewBox:"0 0 16 16",...e,children:(0,s.jsx)("path",{fillRule:"evenodd",d:"M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z"})})}let eW={icon:(0,s.jsx)(eq,{}),title:(0,s.jsx)(b.A,{id:"theme.admonition.warning",description:"The default label used for the Warning admonition (:::warning)",children:"warning"})},eJ={icon:(0,s.jsx)(function(e){return(0,s.jsx)("svg",{viewBox:"0 0 12 16",...e,children:(0,s.jsx)("path",{fillRule:"evenodd",d:"M5.05.31c.81 2.17.41 3.38-.52 4.31C3.55 5.67 1.98 6.45.9 7.98c-1.45 2.05-1.7 6.53 3.53 7.7-2.2-1.16-2.67-4.52-.3-6.61-.61 2.03.53 3.33 1.94 2.86 1.39-.47 2.3.53 2.27 1.67-.02.78-.31 1.44-1.13 1.81 3.42-.59 4.78-3.42 4.78-5.56 0-2.84-2.53-3.22-1.25-5.61-1.52.13-2.03 1.13-1.89 2.75.09 1.08-1.02 1.8-1.86 1.33-.67-.41-.66-1.19-.06-1.78C8.18 5.31 8.68 2.45 5.05.32L5.03.3l.02.01z"})})},{}),title:(0,s.jsx)(b.A,{id:"theme.admonition.danger",description:"The default label used for the Danger admonition (:::danger)",children:"danger"})},eZ={icon:(0,s.jsx)(eq,{}),title:(0,s.jsx)(b.A,{id:"theme.admonition.caution",description:"The default label used for the Caution admonition (:::caution)",children:"caution"})},eX={note:eD,tip:e$,info:eP,warning:function(e){return(0,s.jsx)(eO,{...eW,...e,className:(0,u.A)("alert alert--warning",e.className),children:e.children})},danger:function(e){return(0,s.jsx)(eO,{...eJ,...e,className:(0,u.A)("alert alert--danger",e.className),children:e.children})},secondary:e=>(0,s.jsx)(eD,{title:"secondary",...e}),important:e=>(0,s.jsx)(eP,{title:"important",...e}),success:e=>(0,s.jsx)(e$,{title:"success",...e}),caution:function(e){return(0,s.jsx)(eO,{...eZ,...e,className:(0,u.A)("alert alert--warning",e.className),children:e.children})}};function eY(e){var t;let n,a=function(e){var t;let n,a,i,{mdxAdmonitionTitle:l,rest:o}=(t=e.children,a=(n=r.Children.toArray(t)).find(e=>r.isValidElement(e)&&"mdxAdmonitionTitle"===e.type),i=n.filter(e=>e!==a),{mdxAdmonitionTitle:a?.props.children,rest:i.length>0?(0,s.jsx)(s.Fragment,{children:i}):null}),c=e.title??l;return{...e,...c&&{title:c},children:o}}(e),i=(n=eX[t=a.type])||(console.warn(`No admonition component found for admonition type "${t}". Using Info as fallback.`),eX.info);return(0,s.jsx)(i,{...a})}var eQ=n(9034);let eK={Head:G.A,details:eI,Details:eI,code:function(e){return void 0!==e.children&&r.Children.toArray(e.children).every(e=>"string"==typeof e&&!e.includes("\n"))?(0,s.jsx)(eL,{...e}):(0,s.jsx)(ek,{...e})},a:function(e){let t=(0,eT.v)(e.id);return(0,s.jsx)(g.A,{...e,className:(0,u.A)(t,e.className)})},pre:function(e){return(0,s.jsx)(s.Fragment,{children:e.children})},ul:function(e){return(0,s.jsx)("ul",{...e,className:function(e){if(void 0!==e)return(0,u.A)(e,e?.includes("contains-task-list")&&"containsTaskList_mC6p")}(e.className)})},li:function(e){(0,eB.A)().collectAnchor(e.id);let t=(0,eT.v)(e.id);return(0,s.jsx)("li",{className:(0,u.A)(t,e.className),...e})},img:function(e){var t;return(0,s.jsx)("img",{decoding:"async",loading:"lazy",...e,className:(t=e.className,(0,u.A)(t,"img_ev3q"))})},h1:e=>(0,s.jsx)(eV,{as:"h1",...e}),h2:e=>(0,s.jsx)(eV,{as:"h2",...e}),h3:e=>(0,s.jsx)(eV,{as:"h3",...e}),h4:e=>(0,s.jsx)(eV,{as:"h4",...e}),h5:e=>(0,s.jsx)(eV,{as:"h5",...e}),h6:e=>(0,s.jsx)(eV,{as:"h6",...e}),admonition:eY,mermaid:eQ.A};function e0({children:e}){return(0,s.jsx)(D.x,{components:eK,children:e})}function e1({children:e}){let t=function(){let{metadata:e,frontMatter:t,contentTitle:n}=c();return t.hide_title||void 0!==n?null:e.title}();return(0,s.jsxs)("div",{className:(0,u.A)(j.G.docs.docMarkdown,"markdown"),children:[t&&(0,s.jsx)("header",{children:(0,s.jsx)(U.A,{as:"h1",children:t})}),(0,s.jsx)(e0,{children:e})]})}var e2=n(919);function e5(){return(0,s.jsx)(b.A,{id:"theme.contentVisibility.unlistedBanner.title",description:"The unlisted content banner title",children:"Unlisted page"})}function e3(){return(0,s.jsx)(b.A,{id:"theme.contentVisibility.unlistedBanner.message",description:"The unlisted content banner message",children:"This page is unlisted. Search engines will not index it, and only users having a direct link can access it."})}function e4(){return(0,s.jsx)(G.A,{children:(0,s.jsx)("meta",{name:"robots",content:"noindex, nofollow"})})}function e7(){return(0,s.jsx)(b.A,{id:"theme.contentVisibility.draftBanner.title",description:"The draft content banner title",children:"Draft page"})}function e8(){return(0,s.jsx)(b.A,{id:"theme.contentVisibility.draftBanner.message",description:"The draft content banner message",children:"This page is a draft. It will only be visible in dev and be excluded from the production build."})}function e6({className:e}){return(0,s.jsx)(eY,{type:"caution",title:(0,s.jsx)(e7,{}),className:(0,u.A)(e,j.G.common.draftBanner),children:(0,s.jsx)(e8,{})})}function e9({className:e}){return(0,s.jsx)(eY,{type:"caution",title:(0,s.jsx)(e5,{}),className:(0,u.A)(e,j.G.common.unlistedBanner),children:(0,s.jsx)(e3,{})})}function te(e){return(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(e4,{}),(0,s.jsx)(e9,{...e})]})}function tt({metadata:e}){let{unlisted:t,frontMatter:n}=e;return(0,s.jsxs)(s.Fragment,{children:[(t||n.unlisted)&&(0,s.jsx)(te,{}),n.draft&&(0,s.jsx)(e6,{})]})}function tn({children:e}){let t=function(){let{frontMatter:e,toc:t}=c(),n=(0,m.l)(),r=e.hide_table_of_contents,a=!r&&t.length>0;return{hidden:r,mobile:a?(0,s.jsx)(z,{}):void 0,desktop:a&&("desktop"===n||"ssr"===n)?(0,s.jsx)(O,{}):void 0}}(),{metadata:n}=c();return(0,s.jsxs)("div",{className:"row",children:[(0,s.jsxs)("div",{className:(0,u.A)("col",!t.hidden&&"docItemCol_VOVn"),children:[(0,s.jsx)(tt,{metadata:n}),(0,s.jsx)(x.A,{}),(0,s.jsxs)("div",{className:"docItemContainer_Djhp",children:[(0,s.jsxs)("article",{children:[(0,s.jsx)(e2.A,{}),(0,s.jsx)(p.A,{}),t.mobile,(0,s.jsx)(e1,{children:e}),(0,s.jsx)(T,{})]}),(0,s.jsx)(f,{})]})]}),t.desktop&&(0,s.jsx)("div",{className:"col col--3",children:t.desktop})]})}function ts(e){let t=`docs-doc-id-${e.content.metadata.id}`,n=e.content;return(0,s.jsx)(o,{content:e.content,children:(0,s.jsxs)(a.e3,{className:t,children:[(0,s.jsx)(d,{}),(0,s.jsx)(tn,{children:(0,s.jsx)(n,{})})]})})}},1660(e,t,n){"use strict";n.d(t,{A:()=>o});var s=n(4848);n(6540);var r=n(4164),a=n(568),i=n(5310);function l(e){let{permalink:t,title:n,subLabel:a,isNext:l}=e;return(0,s.jsxs)(i.A,{className:(0,r.A)("pagination-nav__link",l?"pagination-nav__link--next":"pagination-nav__link--prev"),to:t,children:[a&&(0,s.jsx)("div",{className:"pagination-nav__sublabel",children:a}),(0,s.jsx)("div",{className:"pagination-nav__label",children:n})]})}function o(e){let{className:t,previous:n,next:i}=e;return(0,s.jsxs)("nav",{className:(0,r.A)(t,"pagination-nav"),"aria-label":(0,a.T)({id:"theme.docs.paginator.navAriaLabel",message:"Docs pages",description:"The ARIA label for the docs pagination"}),children:[n&&(0,s.jsx)(l,{...n,subLabel:(0,s.jsx)(a.A,{id:"theme.docs.paginator.previous",description:"The label used to navigate to the previous doc",children:"Previous"})}),i&&(0,s.jsx)(l,{...i,subLabel:(0,s.jsx)(a.A,{id:"theme.docs.paginator.next",description:"The label used to navigate to the next doc",children:"Next"}),isNext:!0})]})}},5587(e,t,n){"use strict";n.d(t,{A:()=>o});var s=n(4848);n(6540);var r=n(4164),a=n(568),i=n(8287),l=n(921);function o({className:e}){let t=(0,l.r)();return t.badge?(0,s.jsx)("span",{className:(0,r.A)(e,i.G.docs.docVersionBadge,"badge badge--secondary"),children:(0,s.jsx)(a.A,{id:"theme.docs.versionBadge.label",values:{versionLabel:t.label},children:"Version: {versionLabel}"})}):null}},5886(e,t,n){"use strict";n.d(t,{A:()=>p});var s=n(4848);n(6540);var r=n(4164),a=n(898),i=n(5310),l=n(568),o=n(698),c=n(8287),d=n(2086),u=n(921);let m={unreleased:function({siteTitle:e,versionMetadata:t}){return(0,s.jsx)(l.A,{id:"theme.docs.versions.unreleasedVersionLabel",description:"The label used to tell the user that he's browsing an unreleased doc version",values:{siteTitle:e,versionLabel:(0,s.jsx)("b",{children:t.label})},children:"This is unreleased documentation for {siteTitle} {versionLabel} version."})},unmaintained:function({siteTitle:e,versionMetadata:t}){return(0,s.jsx)(l.A,{id:"theme.docs.versions.unmaintainedVersionLabel",description:"The label used to tell the user that he's browsing an unmaintained doc version",values:{siteTitle:e,versionLabel:(0,s.jsx)("b",{children:t.label})},children:"This is documentation for {siteTitle} {versionLabel}, which is no longer actively maintained."})}};function h(e){let t=m[e.versionMetadata.banner];return(0,s.jsx)(t,{...e})}function f({versionLabel:e,to:t,onClick:n}){return(0,s.jsx)(l.A,{id:"theme.docs.versions.latestVersionSuggestionLabel",description:"The label used to tell the user to check the latest version",values:{versionLabel:e,latestVersionLink:(0,s.jsx)("b",{children:(0,s.jsx)(i.A,{to:t,onClick:n,children:(0,s.jsx)(l.A,{id:"theme.docs.versions.latestVersionLinkLabel",description:"The label used for the latest version suggestion link label",children:"latest version"})})})},children:"For up-to-date documentation, see the {latestVersionLink} ({versionLabel})."})}function x({className:e,versionMetadata:t}){let{siteConfig:{title:n}}=(0,a.A)(),{pluginId:i}=(0,o.vT)({failfast:!0}),{savePreferredVersionName:l}=(0,d.g1)(i),{latestDocSuggestion:u,latestVersionSuggestion:m}=(0,o.HW)(i),p=u??m.docs.find(e=>e.id===m.mainDocId);return(0,s.jsxs)("div",{className:(0,r.A)(e,c.G.docs.docVersionBanner,"alert alert--warning margin-bottom--md"),role:"alert",children:[(0,s.jsx)("div",{children:(0,s.jsx)(h,{siteTitle:n,versionMetadata:t})}),(0,s.jsx)("div",{className:"margin-top--md",children:(0,s.jsx)(f,{versionLabel:m.label,to:p.path,onClick:()=>l(m.name)})})]})}function p({className:e}){let t=(0,u.r)();return t.banner?(0,s.jsx)(x,{className:e,versionMetadata:t}):null}},8453(e,t,n){"use strict";n.d(t,{R:()=>i,x:()=>l});var s=n(6540);let r={},a=s.createContext(r);function i(e){let t=s.useContext(a);return s.useMemo(function(){return"function"==typeof e?e(t):{...t,...e}},[t,e])}function l(e){let t;return t=e.disableParentContext?"function"==typeof e.components?e.components(r):e.components||r:i(e.components),s.createElement(a.Provider,{value:t},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/19597d10.cfc7ef79.js b/assets/js/19597d10.cfc7ef79.js new file mode 100644 index 00000000..d9d6d100 --- /dev/null +++ b/assets/js/19597d10.cfc7ef79.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunklogos_docs=self.webpackChunklogos_docs||[]).push([["7022"],{1116(e,n,a){a.r(n),a.d(n,{metadata:()=>o,default:()=>d,frontMatter:()=>t,contentTitle:()=>c,toc:()=>l,assets:()=>r});var o=JSON.parse('{"id":"blockchain/node-app/claim-leader-rewards-in-logos-blockchain-ui-app","title":"Claim leader rewards in the Logos Blockchain UI app","description":"Learn how to get rewarded for having your Logos Blockchain node propose blocks","source":"@site/docs/blockchain/node-app/claim-leader-rewards-in-logos-blockchain-ui-app.md","sourceDirName":"blockchain/node-app","slug":"/blockchain/node-app/claim-leader-rewards-in-logos-blockchain-ui-app","permalink":"/blockchain/node-app/claim-leader-rewards-in-logos-blockchain-ui-app","draft":false,"unlisted":false,"editUrl":"https://github.com/logos-co/logos-docs/tree/main/docs/blockchain/node-app/claim-leader-rewards-in-logos-blockchain-ui-app.md","tags":[],"version":"current","sidebarPosition":4,"frontMatter":{"title":"Claim leader rewards in the Logos Blockchain UI app","doc_type":"procedure","product":"blockchain","topics":[],"steps_layout":"flat","authors":"andrussal, kashepavadan","owner":"logos","doc_version":1,"slug":"claim-leader-rewards-in-logos-blockchain-ui-app","sidebar_position":4},"sidebar":"blockchainSidebar","previous":{"title":"Bridge assets from Logos Blockchain to a Zone using the Logos Blockchain app","permalink":"/blockchain/node-app/bridge-assets-from-logos-blockchain-to-zone-using-app"},"next":{"title":"Inscribe data on-chain using the Zone SDK","permalink":"/blockchain/zone-sdk/inscribe-data-on-chain-using-zone-sdk"}}'),i=a(4848),s=a(8453);let t={title:"Claim leader rewards in the Logos Blockchain UI app",doc_type:"procedure",product:"blockchain",topics:[],steps_layout:"flat",authors:"andrussal, kashepavadan",owner:"logos",doc_version:1,slug:"claim-leader-rewards-in-logos-blockchain-ui-app",sidebar_position:4},c="Claim leader rewards in the Logos Blockchain UI app",r={},l=[{value:"Learn how to get rewarded for having your Logos Blockchain node propose blocks",id:"learn-how-to-get-rewarded-for-having-your-logos-blockchain-node-propose-blocks",level:4},{value:"What to expect",id:"what-to-expect",level:2},{value:"Claim a reward voucher",id:"claim-a-reward-voucher",level:2},{value:"Troubleshooting leader rewards",id:"troubleshooting-leader-rewards",level:2},{value:"Why does the voucher count show 0?",id:"why-does-the-voucher-count-show-0",level:3},{value:"Why does clicking <strong>Claim</strong> fail?",id:"why-does-clicking-claim-fail",level:3},{value:"Why is the transaction hash not showing in the block list yet?",id:"why-is-the-transaction-hash-not-showing-in-the-block-list-yet",level:3},{value:"Why hasn't the account balance updated after claiming?",id:"why-hasnt-the-account-balance-updated-after-claiming",level:3}];function h(e){let n={a:"a",admonition:"admonition",code:"code",h1:"h1",h2:"h2",h3:"h3",h4:"h4",header:"header",img:"img",li:"li",ol:"ol",p:"p",strong:"strong",ul:"ul",...(0,s.R)(),...e.components};return(0,i.jsxs)(i.Fragment,{children:[(0,i.jsx)(n.header,{children:(0,i.jsx)(n.h1,{id:"claim-leader-rewards-in-the-logos-blockchain-ui-app",children:"Claim leader rewards in the Logos Blockchain UI app"})}),"\n",(0,i.jsx)(n.h4,{id:"learn-how-to-get-rewarded-for-having-your-logos-blockchain-node-propose-blocks",children:"Learn how to get rewarded for having your Logos Blockchain node propose blocks"}),"\n",(0,i.jsxs)(n.p,{children:["This procedure covers how to claim rewards for participating in the consensus protocol of the ",(0,i.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#logos-blockchain",children:"Logos Blockchain"}),". It is intended for node operators running their node via the Blockchain UI app."]}),"\n",(0,i.jsx)(n.p,{children:"Before you start, make sure you have the following:"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:["A ",(0,i.jsx)(n.a,{href:"/blockchain/node-app/build-and-run-logos-blockchain-node-app-ui",children:"Logos Blockchain node running via the UI app"})]}),"\n",(0,i.jsx)(n.li,{children:"A wallet key that can receive leader reward claims"}),"\n"]}),"\n",(0,i.jsx)(n.h2,{id:"what-to-expect",children:"What to expect"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:["You can view available reward vouchers in ",(0,i.jsx)(n.strong,{children:"Leader Rewards"})," and claim one with a single button press."]}),"\n",(0,i.jsx)(n.li,{children:"You can verify the claim by matching the transaction hash in the block list and confirming the reward account balance has increased."}),"\n"]}),"\n",(0,i.jsx)(n.h2,{id:"claim-a-reward-voucher",children:"Claim a reward voucher"}),"\n",(0,i.jsx)(n.p,{children:"Check the account balance before claiming, submit a claim, then confirm the transaction and balance update."}),"\n",(0,i.jsxs)(n.ol,{children:["\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsxs)(n.p,{children:["In ",(0,i.jsx)(n.strong,{children:"Operations"}),", open ",(0,i.jsx)(n.strong,{children:"Accounts"})," and click ",(0,i.jsx)(n.strong,{children:"Refresh"})," to load the current wallet state."]}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.img,{alt:"Check accounts",src:a(4700).A+"",width:"2016",height:"1568"})}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsx)(n.li,{children:"Note the balance of the account that will receive the reward before claiming."}),"\n"]}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsxs)(n.p,{children:["In ",(0,i.jsx)(n.strong,{children:"Operations"}),", open ",(0,i.jsx)(n.strong,{children:"Leader Rewards"}),"."]}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.img,{alt:"Claimable vouchers",src:a(6948).A+"",width:"2042",height:"1616"})}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:["The panel shows the number of available claimable vouchers, the tip used for the query, and voucher details including ",(0,i.jsx)(n.code,{children:"cm"})," and ",(0,i.jsx)(n.code,{children:"nf"})," values."]}),"\n"]}),"\n",(0,i.jsx)(n.admonition,{type:"info"}),"\n"]}),"\n"]}),"\n",(0,i.jsxs)(n.p,{children:["If the voucher count shows ",(0,i.jsx)(n.code,{children:"0"}),", wait for more blocks and refresh the ",(0,i.jsx)(n.strong,{children:"Leader Rewards"})," panel before proceeding.\n:::"]}),"\n",(0,i.jsxs)(n.ol,{children:["\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsxs)(n.p,{children:["Click ",(0,i.jsx)(n.strong,{children:"Claim"}),"."]}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.img,{alt:"Claim submitted",src:a(9093).A+"",width:"2034",height:"1590"})}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsx)(n.li,{children:"The UI displays a transaction hash. Note this hash to confirm inclusion in the next step."}),"\n"]}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsxs)(n.p,{children:["Return to the ",(0,i.jsx)(n.strong,{children:"Node"})," tab and inspect the block list. Find a block that includes the claim transaction, expand it, and confirm the transaction hash matches the one shown after clicking ",(0,i.jsx)(n.strong,{children:"Claim"}),"."]}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.img,{alt:"Claim transaction as seen in a block",src:a(6035).A+"",width:"2040",height:"1560"})}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsx)(n.li,{children:"On shared networks the block may contain other transactions too."}),"\n"]}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsxs)(n.p,{children:["Return to ",(0,i.jsx)(n.strong,{children:"Operations"}),", then ",(0,i.jsx)(n.strong,{children:"Accounts"}),", and click ",(0,i.jsx)(n.strong,{children:"Refresh"}),"."]}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.img,{alt:"Accounts after the claim",src:a(1221).A+"",width:"2028",height:"1568"})}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsx)(n.li,{children:"Confirm the reward account balance has increased. The balance updates after the claim transaction is processed."}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,i.jsx)(n.h2,{id:"troubleshooting-leader-rewards",children:"Troubleshooting leader rewards"}),"\n",(0,i.jsx)(n.h3,{id:"why-does-the-voucher-count-show-0",children:"Why does the voucher count show 0?"}),"\n",(0,i.jsxs)(n.p,{children:["The node has not yet produced enough blocks for vouchers to be available. Keep the node running and refresh ",(0,i.jsx)(n.strong,{children:"Leader Rewards"})," after more blocks appear."]}),"\n",(0,i.jsxs)(n.h3,{id:"why-does-clicking-claim-fail",children:["Why does clicking ",(0,i.jsx)(n.strong,{children:"Claim"})," fail?"]}),"\n",(0,i.jsxs)(n.p,{children:["Either the node is not running or the voucher count is ",(0,i.jsx)(n.code,{children:"0"}),". Confirm the node status shows ",(0,i.jsx)(n.strong,{children:"Running"})," and that at least one voucher is listed before retrying."]}),"\n",(0,i.jsx)(n.h3,{id:"why-is-the-transaction-hash-not-showing-in-the-block-list-yet",children:"Why is the transaction hash not showing in the block list yet?"}),"\n",(0,i.jsx)(n.p,{children:"Transaction inclusion is not always immediate. Wait for more blocks to appear in the block list and check again."}),"\n",(0,i.jsx)(n.h3,{id:"why-hasnt-the-account-balance-updated-after-claiming",children:"Why hasn't the account balance updated after claiming?"}),"\n",(0,i.jsxs)(n.p,{children:["Refresh ",(0,i.jsx)(n.strong,{children:"Accounts"})," only after the claim transaction appears in a block. The balance reflects on-chain state and does not update until the transaction is included."]})]})}function d(e={}){let{wrapper:n}={...(0,s.R)(),...e.components};return n?(0,i.jsx)(n,{...e,children:(0,i.jsx)(h,{...e})}):h(e)}},4700(e,n,a){a.d(n,{A:()=>o});let o=a.p+"assets/images/04-accounts-before-claim-db4ae12b4c14618ede4d7043d684ad12.png"},6948(e,n,a){a.d(n,{A:()=>o});let o=a.p+"assets/images/05-claimable-vouchers-45183086be2f12f41e7bedf4b437e513.png"},9093(e,n,a){a.d(n,{A:()=>o});let o=a.p+"assets/images/06-claim-submitted-aef27febfd0c2e5ce94ed86b8251b9b6.png"},6035(e,n,a){a.d(n,{A:()=>o});let o=a.p+"assets/images/07-claim-tx-in-block-31f6d0a60c02f9e64a47bedb74ce8664.png"},1221(e,n,a){a.d(n,{A:()=>o});let o=a.p+"assets/images/08-accounts-after-claim-16248912ad5366599bd9edfd1d4c64eb.png"},8453(e,n,a){a.d(n,{R:()=>t,x:()=>c});var o=a(6540);let i={},s=o.createContext(i);function t(e){let n=o.useContext(s);return o.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function c(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(i):e.components||i:t(e.components),o.createElement(s.Provider,{value:n},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/1995774a.18209123.js b/assets/js/1995774a.18209123.js new file mode 100644 index 00000000..94435963 --- /dev/null +++ b/assets/js/1995774a.18209123.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunklogos_docs=self.webpackChunklogos_docs||[]).push([["3109"],{9737(e,o,s){s.r(o),s.d(o,{metadata:()=>n,default:()=>h,frontMatter:()=>i,contentTitle:()=>r,toc:()=>l,assets:()=>c});var n=JSON.parse('{"id":"blockchain/concepts/about-zones","title":"About Zones","description":"Understand how Zones run custom, high-performance applications on the Logos Blockchain.","source":"@site/docs/blockchain/concepts/about-zones.md","sourceDirName":"blockchain/concepts","slug":"/blockchain/concepts/about-zones","permalink":"/blockchain/concepts/about-zones","draft":false,"unlisted":false,"editUrl":"https://github.com/logos-co/logos-docs/tree/main/docs/blockchain/concepts/about-zones.md","tags":[],"version":"current","sidebarPosition":5,"frontMatter":{"title":"About Zones","doc_type":"concept","product":"blockchain","topics":"zones","authors":"kashepavadan","owner":"logos","doc_version":1,"slug":"about-zones","sidebar_position":5},"sidebar":"blockchainSidebar","previous":{"title":"About the Blend Network","permalink":"/blockchain/concepts/about-the-blend-network"}}'),t=s(4848),a=s(8453);let i={title:"About Zones",doc_type:"concept",product:"blockchain",topics:"zones",authors:"kashepavadan",owner:"logos",doc_version:1,slug:"about-zones",sidebar_position:5},r="About Zones",c={},l=[{value:"Understand how Zones run custom, high-performance applications on the Logos Blockchain.",id:"understand-how-zones-run-custom-high-performance-applications-on-the-logos-blockchain",level:4},{value:"The basics",id:"the-basics",level:2},{value:"How Zones work",id:"how-zones-work",level:2},{value:"Additional features",id:"additional-features",level:2},{value:"Decentralised sequencing",id:"decentralised-sequencing",level:3},{value:"Token bridging",id:"token-bridging",level:3},{value:"Asynchronous messaging",id:"asynchronous-messaging",level:3},{value:"Use cases",id:"use-cases",level:2},{value:"Logos Execution Zone",id:"logos-execution-zone",level:3},{value:"Other uses",id:"other-uses",level:3}];function d(e){let o={a:"a",h1:"h1",h2:"h2",h3:"h3",h4:"h4",header:"header",li:"li",p:"p",ul:"ul",...(0,a.R)(),...e.components};return(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)(o.header,{children:(0,t.jsx)(o.h1,{id:"about-zones",children:"About Zones"})}),"\n",(0,t.jsx)(o.h4,{id:"understand-how-zones-run-custom-high-performance-applications-on-the-logos-blockchain",children:"Understand how Zones run custom, high-performance applications on the Logos Blockchain."}),"\n",(0,t.jsxs)(o.p,{children:["Zones are customisable, high-performance blockchains for applications built on Logos. A ",(0,t.jsx)(o.a,{href:"https://docs.logos.co/get-started/glossary#zone",children:"Zone"})," defines its own state and execution environment, while relying on the ",(0,t.jsx)(o.a,{href:"https://docs.logos.co/get-started/glossary#logos-blockchain",children:"Logos Blockchain"})," for consensus and data availability. Zones can also use optional ",(0,t.jsx)(o.a,{href:"/blockchain/concepts/about-bedrock",children:"Bedrock"})," features such as token bridging and cross-Zone messaging. A sequencer updates a Zone's state by submitting state updates to the blockchain. Zones suit applications that need speed and customisation more than they need interoperability."]}),"\n",(0,t.jsx)(o.h2,{id:"the-basics",children:"The basics"}),"\n",(0,t.jsxs)(o.ul,{children:["\n",(0,t.jsx)(o.li,{children:"A Zone defines its own state and execution environment, while the Logos Blockchain provides consensus and data availability."}),"\n",(0,t.jsx)(o.li,{children:"A sequencer, or a committee of sequencers, handles a Zone's transactions and submits state updates to the blockchain."}),"\n",(0,t.jsxs)(o.li,{children:["Zones can use tokens bridged from ",(0,t.jsx)(o.a,{href:"https://docs.logos.co/get-started/glossary#bedrock",children:"Bedrock"})," and support cross-Zone transactions."]}),"\n"]}),"\n",(0,t.jsx)(o.h2,{id:"how-zones-work",children:"How Zones work"}),"\n",(0,t.jsxs)(o.p,{children:["Zones let developers build performant applications with the freedom to define an execution environment that suits their needs, while the Logos Blockchain provides economic security and data availability that a Zone could not provide on its own. ",(0,t.jsx)(o.a,{href:"/blockchain/concepts/about-bedrock",children:"Bedrock"})," does not interpret Zone data. Instead, it provides operations through ",(0,t.jsx)(o.a,{href:"/blockchain/concepts/about-mantle",children:"Mantle"})," that let Zone sequencers inscribe their state updates permanently to the blockchain."]}),"\n",(0,t.jsx)(o.p,{children:"Unlike Ethereum Layer 2s, Zones do not rely on a smart contract on the Layer 1 to ensure the correctness of their state. A Zone can ensure correctness in several ways, including publishing zero knowledge validity proofs for its validators to verify, providing a challenge window for fraud proofs, or requiring Zone nodes to re-execute the state transition function."}),"\n",(0,t.jsx)(o.h2,{id:"additional-features",children:"Additional features"}),"\n",(0,t.jsxs)(o.p,{children:["The Zone model described above is very similar to that of conventional ",(0,t.jsx)(o.a,{href:"https://dba.xyz/rollups-are-l1s-l2s-a-k-a-how-rollups-actually-actually-actually-work/?ref=blog.nomos.tech",children:"Sovereign Rollups"}),". In fact, Zones can be built as pure sovereign rollups, with Bedrock only used for ordering and data availability guarantees. However, Bedrock also provides some optional functionality that somewhat increases the decentralisation and interoperability of Zones without significantly compromising the benefits of the sovereign rollup design."]}),"\n",(0,t.jsx)(o.h3,{id:"decentralised-sequencing",children:"Decentralised sequencing"}),"\n",(0,t.jsx)(o.p,{children:"While a Zone can be implemented with just one sequencer updating the Zone state, this design allows the sequencer to take full advantage of any MEV and represents a centralised point of failure. On Logos, it is also possible to distribute sequencing rights to a permissioned set of parties. These sequencers can compete to be the first to publish the next state update, or take turns sequencing the Zone according to a round-robin schedule. This latter model provides more Bedrock-level protections for Zone users, including an automated timeout for unresponsive sequencers. Decentralised sequencing also allows a given threshold of sequencers to update the sequencer list to remove a malicious sequencer. All these operations are handled by Logos channels."}),"\n",(0,t.jsx)(o.h3,{id:"token-bridging",children:"Token bridging"}),"\n",(0,t.jsxs)(o.p,{children:["Users can also take advantage of built-in token bridging to transfer value between Bedrock and Zones. A Zone\u2019s associated ",(0,t.jsx)(o.a,{href:"https://docs.logos.co/get-started/glossary#channel",children:"channel"})," maintains a token balance, which keeps track of the total token value stored in the Zone. This balance is updated as value is deposited to or withdrawn from a Zone, with notes of equivalent value destroyed or created on Bedrock as a result of these operations."]}),"\n",(0,t.jsx)(o.p,{children:"Note that it is the Zone\u2019s responsibility to ensure that token transfers are executed correctly within its own state. Bedrock only ensures that the total token value withdrawn from a Zone does not exceed the value deposited."}),"\n",(0,t.jsx)(o.h3,{id:"asynchronous-messaging",children:"Asynchronous messaging"}),"\n",(0,t.jsxs)(o.p,{children:["Zone sequencers can use channels to submit messages with arbitrary data, allowing them to communicate on-chain with other sequencers. Sequencers can use these messages to coordinate actions with other sequencers of the same Zone, for example to agree to sign a ",(0,t.jsx)(o.a,{href:"https://docs.logos.co/get-started/glossary#message",children:"message"})," modifying the sequencer list. They can also be used to arrange action across several Zones, such as atomic token transfers or other coordinated state transitions. In the latter case, sequencers must interpret all new messages and determine whether the message is directed to them. The Logos Blockchain provides a suggested message standard to make it easier for sequencers to interpret messages from other Zones, but anybody can define their own standard if they so desire."]}),"\n",(0,t.jsx)(o.h2,{id:"use-cases",children:"Use cases"}),"\n",(0,t.jsx)(o.p,{children:"Zones can implement almost anything, ranging from applications to virtual machines that are home to many different applications. They are best suited for applications that require high performance and do not need strong interoperability."}),"\n",(0,t.jsx)(o.h3,{id:"logos-execution-zone",children:"Logos Execution Zone"}),"\n",(0,t.jsxs)(o.p,{children:["The ",(0,t.jsx)(o.a,{href:"https://github.com/logos-co/logos-docs/blob/main/docs/lez/get-started/introduction-to-the-logos-execution-zone.md",children:"Logos Execution Zone (LEZ)"})," is the flagship Zone on the Logos Blockchain. It serves as the home for applications built using the complete Logos stack, including its messaging and storage modules. It also provides support for private accounts and ",(0,t.jsx)(o.a,{href:"https://docs.logos.co/get-started/glossary#program",children:"program"})," execution, with the state being verified via zero knowledge proofs. For more information about the ",(0,t.jsx)(o.a,{href:"https://docs.logos.co/get-started/glossary#logos-execution-zone",children:"Logos Execution Zone"}),", see the page below:"]}),"\n",(0,t.jsx)(o.h3,{id:"other-uses",children:"Other uses"}),"\n",(0,t.jsxs)(o.p,{children:["In addition to the ",(0,t.jsx)(o.a,{href:"https://docs.logos.co/get-started/glossary#lez",children:"LEZ"}),", anybody can make a Zone on Logos. The Logos Team has already prepared a ",(0,t.jsx)(o.a,{href:"https://github.com/logos-blockchain/logos-blockchain/tree/master/zone-sdk",children:"Zone SDK"})," that makes it easy to build a Logos Zone. Some possibilities of Sovereign Zone applications include:"]}),"\n",(0,t.jsxs)(o.ul,{children:["\n",(0,t.jsxs)(o.li,{children:["Self-Sovereign Digital Nations/Communities: Logos Blockchain can power the core identity, communication, and governance layers for nascent digital nations. This would involve:","\n",(0,t.jsxs)(o.ul,{children:["\n",(0,t.jsx)(o.li,{children:"Self-Sovereign Identity (SSI) Solutions: Users can control their digital identities and share verifiable credentials privately and selectively."}),"\n",(0,t.jsx)(o.li,{children:"Secure Communication Channels: The Logos ecosystem provides encrypted and censorship-resistant collaboration tools for citizens of network states."}),"\n",(0,t.jsx)(o.li,{children:"Decentralised Justice Systems: Building frameworks for dispute resolution and legal agreements within a network state, potentially leveraging zero-knowledge proofs for privacy."}),"\n"]}),"\n"]}),"\n",(0,t.jsx)(o.li,{children:"Gaming Applications: Games often rely on rapid state changes to function correctly, making them ideal for implementation as Zone applications on Logos."}),"\n",(0,t.jsx)(o.li,{children:"Small-scale Networked Applications: Networked applications can use the Logos Blockchain to store their state updates on-chain, with users recreating the state locally based on these updates. This makes the Logos Blockchain a low cost, easy alternative for self-hosting - especially for small-scale applications."}),"\n"]})]})}function h(e={}){let{wrapper:o}={...(0,a.R)(),...e.components};return o?(0,t.jsx)(o,{...e,children:(0,t.jsx)(d,{...e})}):d(e)}},8453(e,o,s){s.d(o,{R:()=>i,x:()=>r});var n=s(6540);let t={},a=n.createContext(t);function i(e){let o=n.useContext(a);return n.useMemo(function(){return"function"==typeof e?e(o):{...o,...e}},[o,e])}function r(e){let o;return o=e.disableParentContext?"function"==typeof e.components?e.components(t):e.components||t:i(e.components),n.createElement(a.Provider,{value:o},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/1df93b7f.38dea343.js b/assets/js/1df93b7f.38dea343.js new file mode 100644 index 00000000..b4dd1eee --- /dev/null +++ b/assets/js/1df93b7f.38dea343.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunklogos_docs=self.webpackChunklogos_docs||[]).push([["9452"],{5741(e,s,t){t.r(s),t.d(s,{default:()=>p});var o=t(4848),i=t(5310),n=t(1085),a=t(2072);let r="section_Q9Zo",c="sectionTitle_Ut5p",d=[{title:"Run an app",description:"Run modules inside Basecamp, as standalone apps, or headlessly",to:"/run-an-app",icon:"/img/home/run-an-app.png"},{title:"Run a node",description:"Run one service or all three: blockchain, delivery, and storage",to:"/run-a-node/get-started/run-logos-node-blockchain-storage-delivery",icon:"/img/home/run-a-node.png"},{title:"Build an app",description:"Build and ship apps on Logos",to:"/build-an-app",icon:"/img/home/build-an-app.png"},{title:"Contribute",description:"Improve the protocol, the docs, or the tooling",to:"/contribute/welcome",icon:"/img/home/contribute.png"}],l=[{title:"\u03BB Basecamp",description:"The desktop shell for Logos",to:"/basecamp/get-started/install-logos-basecamp"},{title:"\u03BB Blockchain",description:"A sovereign, censorship-resistant base layer for building apps",to:"/blockchain/get-started/introduction-to-the-logos-blockchain"},{title:"\u03BB LEZ",description:"The flagship execution environment built on the Logos Blockchain",to:"/lez/get-started/introduction-to-the-logos-execution-zone"},{title:"\u03BB Core",description:"The headless CLI runtime",to:"/core/build-modules/start-a-logos-module-from-the-cli"},{title:"\u03BB Messaging",description:"Private, censorship-resistant communication",to:"/messaging/get-started/introduction-to-logos-messaging"},{title:"\u03BB Storage",description:"Decentralised, content-addressed file storage and retrieval",to:"/storage/get-started/run-logos-storage-node"},{title:"\u03BB Mixnet",description:"Traffic mixing that hides communication patterns from network observers",to:"/mixnet/get-started/discover-nodes-and-send-messages-via-the-anoncomms-mixnet-demo-app"},{title:"\u03BB Peer discovery",description:"Peer discovery and connection management without central registries",to:"/peer-discovery/welcome"}];function h({title:e,description:s,to:t,icon:n}){return(0,o.jsxs)(i.A,{className:"card_M5pr",to:t,children:[n&&(0,o.jsx)("img",{className:"cardIcon_I6MN",src:n,alt:""}),(0,o.jsx)("h4",{className:"cardTitle_tke3",children:e}),(0,o.jsx)("p",{className:"cardDescription_PVP5",children:s})]})}function m({items:e}){return(0,o.jsx)("div",{className:"cardGrid_nYON",children:e.map(e=>(0,o.jsx)(h,{...e},e.to))})}function p(){return(0,o.jsx)(n.A,{title:"Logos documentation",description:"Guides and references for the Logos stack.",children:(0,o.jsxs)("main",{className:"container",children:[(0,o.jsxs)("div",{className:"hero_aEcG",children:[(0,o.jsx)("div",{className:"heroImage_xZN7",children:(0,o.jsx)("img",{src:"/img/home/understand-logos.webp",alt:""})}),(0,o.jsxs)("div",{className:"heroText_ryRM",children:[(0,o.jsx)(a.A,{as:"h2",children:"Understand Logos"}),(0,o.jsx)("p",{children:"Learn what Logos is, how the stack works, and the ideas behind it."}),(0,o.jsx)(i.A,{className:"button button--primary",to:"/get-started/what-is-logos",children:"Get started \u2192"})]})]}),(0,o.jsxs)("div",{className:r,children:[(0,o.jsx)(a.A,{as:"h3",className:c,children:"Choose your path"}),(0,o.jsx)(m,{items:d})]}),(0,o.jsxs)("div",{className:r,children:[(0,o.jsx)(a.A,{as:"h3",className:c,children:"Explore Logos"}),(0,o.jsx)(m,{items:l})]}),(0,o.jsx)("div",{style:{height:"3rem"}})]})})}}}]); \ No newline at end of file diff --git a/assets/js/22dd74f7.4faa173f.js b/assets/js/22dd74f7.4faa173f.js new file mode 100644 index 00000000..d2b183a9 --- /dev/null +++ b/assets/js/22dd74f7.4faa173f.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunklogos_docs=self.webpackChunklogos_docs||[]).push([["1924"],{5226(e){e.exports=JSON.parse('{"version":{"pluginId":"default","version":"current","label":"Next","banner":null,"badge":false,"noIndex":false,"className":"docs-version-current","isLast":true,"docsSidebars":{"runAnAppSidebar":[{"type":"category","label":"Run an app","items":[{"type":"link","label":"Install Logos Basecamp","href":"/basecamp/get-started/install-logos-basecamp"},{"type":"category","label":"Blockchain UI app","items":[{"type":"link","label":"Build and run the Logos Blockchain UI app","href":"/blockchain/node-app/build-and-run-logos-blockchain-node-app-ui"},{"type":"link","label":"Move assets using the Logos Blockchain UI app","href":"/blockchain/node-app/move-assets-using-logos-blockchain-app"},{"type":"link","label":"Bridge assets from Logos Blockchain to a Zone using the Logos Blockchain app","href":"/blockchain/node-app/bridge-assets-from-logos-blockchain-to-zone-using-app"},{"type":"link","label":"Claim leader rewards in the Logos Blockchain UI app","href":"/blockchain/node-app/claim-leader-rewards-in-logos-blockchain-ui-app"}],"collapsed":true,"collapsible":true},{"type":"category","label":"LEZ wallet UI","items":[{"type":"link","label":"Initiate native token transfers on the LEZ with the wallet UI","href":"/lez/get-started/initiate-native-token-transfers-on-lez-with-wallet-ui"}],"collapsed":true,"collapsible":true}],"collapsed":true,"collapsible":true,"href":"/run-an-app"}],"runANodeSidebar":[{"type":"link","href":"/run-a-node/get-started/run-logos-node-blockchain-storage-delivery","label":"Run a Logos node with blockchain, storage, and delivery","docId":"run-a-node/get-started/run-logos-node-blockchain-storage-delivery","unlisted":false},{"type":"category","label":"Blockchain node","items":[{"type":"link","label":"Run a Logos Blockchain node on the public testnet from the CLI","href":"/blockchain/get-started/run-a-logos-blockchain-node-from-cli"}],"collapsed":true,"collapsible":true},{"type":"category","label":"Delivery node","items":[{"type":"link","label":"Run a Logos delivery node","href":"/messaging/delivery/run-logos-delivery-node"}],"collapsed":true,"collapsible":true},{"type":"category","label":"Storage node","items":[{"type":"link","label":"Run a Logos storage node","href":"/storage/get-started/run-logos-storage-node"}],"collapsed":true,"collapsible":true}],"buildAnAppSidebar":[{"type":"category","label":"Build an app","items":[{"type":"category","label":"Build modules","items":[{"type":"link","label":"Build and run a Logos core module","href":"/core/build-modules/build-and-run-a-logos-core-module"},{"type":"link","label":"Wrap a C library as a Logos core module","href":"/core/build-modules/wrap-a-c-library-as-a-logos-core-module"},{"type":"link","label":"Build a Logos C++ UI module","href":"/core/build-modules/build-a-logos-cpp-ui-module"},{"type":"link","label":"Install and load a module in Logos Basecamp","href":"/core/build-modules/install-and-load-a-module-in-logos-basecamp"}],"collapsed":true,"collapsible":true},{"type":"category","label":"Tutorials","items":[{"type":"link","href":"/build-an-app/tutorials/create-and-use-an-amm-liquidity-pool-on-the-logos-execution-zone","label":"Create and use an AMM liquidity pool on the Logos Execution Zone","docId":"build-an-app/tutorials/create-and-use-an-amm-liquidity-pool-on-the-logos-execution-zone","unlisted":false},{"type":"link","href":"/build-an-app/tutorials/build-dapp-with-logos-zone-sdk","label":"Build a dApp with the Logos Zone SDK","docId":"build-an-app/tutorials/build-dapp-with-logos-zone-sdk","unlisted":false}],"collapsed":true,"collapsible":true}],"collapsed":true,"collapsible":true,"href":"/build-an-app"}],"getStartedSidebar":[{"type":"link","href":"/get-started/what-is-logos","label":"Introduction to Logos","docId":"get-started/introduction-to-logos","unlisted":false},{"type":"link","href":"/get-started/logos-ecosystem-repositories","label":"Logos ecosystem repositories","docId":"get-started/logos-ecosystem-repositories","unlisted":false},{"type":"link","href":"/get-started/glossary","label":"Glossary","docId":"get-started/glossary","unlisted":false}],"basecampSidebar":[{"type":"category","label":"Get started","items":[{"type":"link","href":"/basecamp/get-started/install-logos-basecamp","label":"Install Logos Basecamp","docId":"basecamp/get-started/install-logos-basecamp","unlisted":false},{"type":"link","href":"/core/build-modules/install-and-load-a-module-in-logos-basecamp","label":"Install and load a module in Logos Basecamp","docId":"core/build-modules/install-and-load-a-module-in-logos-basecamp","unlisted":false}],"collapsed":true,"collapsible":true}],"blockchainSidebar":[{"type":"category","label":"Get started","collapsible":true,"collapsed":true,"items":[{"type":"link","href":"/blockchain/get-started/introduction-to-the-logos-blockchain","label":"Introduction to the Logos Blockchain","docId":"blockchain/get-started/introduction-to-the-logos-blockchain","unlisted":false},{"type":"link","href":"/blockchain/get-started/run-a-logos-blockchain-node-from-basecamp","label":"Run a Logos Blockchain node from Basecamp","docId":"blockchain/get-started/run-a-logos-blockchain-node-from-basecamp","unlisted":false},{"type":"link","href":"/blockchain/get-started/run-a-logos-blockchain-node-from-cli","label":"Run a Logos Blockchain node on the public testnet from the CLI","docId":"blockchain/get-started/run-a-logos-blockchain-node-from-cli","unlisted":false}]},{"type":"category","label":"Blockchain UI app","collapsible":true,"collapsed":true,"items":[{"type":"link","href":"/blockchain/node-app/build-and-run-logos-blockchain-node-app-ui","label":"Build and run the Logos Blockchain UI app","docId":"blockchain/node-app/build-and-run-logos-blockchain-node-app-ui","unlisted":false},{"type":"link","href":"/blockchain/node-app/move-assets-using-logos-blockchain-app","label":"Move assets using the Logos Blockchain UI app","docId":"blockchain/node-app/move-assets-using-logos-blockchain-app","unlisted":false},{"type":"link","href":"/blockchain/node-app/bridge-assets-from-logos-blockchain-to-zone-using-app","label":"Bridge assets from Logos Blockchain to a Zone using the Logos Blockchain app","docId":"blockchain/node-app/bridge-assets-from-logos-blockchain-to-zone-using-app","unlisted":false},{"type":"link","href":"/blockchain/node-app/claim-leader-rewards-in-logos-blockchain-ui-app","label":"Claim leader rewards in the Logos Blockchain UI app","docId":"blockchain/node-app/claim-leader-rewards-in-logos-blockchain-ui-app","unlisted":false}]},{"type":"category","label":"Zone SDK","collapsible":true,"collapsed":true,"items":[{"type":"link","href":"/blockchain/zone-sdk/inscribe-data-on-chain-using-zone-sdk","label":"Inscribe data on-chain using the Zone SDK","docId":"blockchain/zone-sdk/inscribe-data-on-chain-using-zone-sdk","unlisted":false},{"type":"link","href":"/blockchain/zone-sdk/bridge-assets-between-blockchain-and-zone","label":"Bridge assets between Logos Blockchain and a Zone","docId":"blockchain/zone-sdk/bridge-assets-between-blockchain-and-zone","unlisted":false},{"type":"link","href":"/blockchain/zone-sdk/operate-decentralised-zone-with-round-robin-sequencer-rotation","label":"Operate a decentralised Zone with round-robin sequencer rotation","docId":"blockchain/zone-sdk/operate-decentralised-zone-with-round-robin-sequencer-rotation","unlisted":false}]},{"type":"category","label":"Blend","collapsible":true,"collapsed":true,"items":[{"type":"link","href":"/blockchain/blend/join-the-blend-network-as-a-core-node","label":"Join the Blend network as a core node","docId":"blockchain/blend/join-the-blend-network-as-a-core-node","unlisted":false}]},{"type":"category","label":"Concepts","collapsible":true,"collapsed":true,"items":[{"type":"link","href":"/blockchain/concepts/about-bedrock","label":"About Bedrock","docId":"blockchain/concepts/about-bedrock","unlisted":false},{"type":"link","href":"/blockchain/concepts/about-cryptarchia","label":"About Cryptarchia","docId":"blockchain/concepts/about-cryptarchia","unlisted":false},{"type":"link","href":"/blockchain/concepts/about-mantle","label":"About Mantle","docId":"blockchain/concepts/about-mantle","unlisted":false},{"type":"link","href":"/blockchain/concepts/about-the-blend-network","label":"About the Blend Network","docId":"blockchain/concepts/about-the-blend-network","unlisted":false},{"type":"link","href":"/blockchain/concepts/about-zones","label":"About Zones","docId":"blockchain/concepts/about-zones","unlisted":false}]}],"lezSidebar":[{"type":"category","label":"Get started","collapsible":true,"collapsed":true,"items":[{"type":"link","href":"/lez/get-started/introduction-to-the-logos-execution-zone","label":"Introduction to the Logos Execution Zone","docId":"lez/get-started/introduction-to-the-logos-execution-zone","unlisted":false},{"type":"link","href":"/lez/get-started/lez-quickstart","label":"Quickstart for the Logos Execution Zone wallet","docId":"lez/get-started/quickstart-for-the-logos-execution-zone-wallet","unlisted":false},{"type":"link","href":"/lez/get-started/connect-wallet-cli-to-lez-testnet","label":"Connect the wallet CLI to the LEZ testnet","docId":"lez/get-started/connect-wallet-cli-to-lez-testnet","unlisted":false},{"type":"link","href":"/lez/get-started/initiate-native-token-transfers-on-lez-with-wallet-ui","label":"Initiate native token transfers on the LEZ with the wallet UI","docId":"lez/get-started/initiate-native-token-transfers-on-lez-with-wallet-ui","unlisted":false}]},{"type":"category","label":"Transfer tokens","collapsible":true,"collapsed":true,"items":[{"type":"link","href":"/lez/transfer-tokens/transfer-native-tokens-on-the-logos-execution-zone","label":"Transfer native tokens on the Logos Execution Zone","docId":"lez/transfer-tokens/transfer-native-tokens-on-the-logos-execution-zone","unlisted":false},{"type":"link","href":"/lez/transfer-tokens/create-and-transfer-custom-tokens-on-the-logos-execution-zone","label":"Create and transfer custom tokens on the Logos Execution Zone","docId":"lez/transfer-tokens/create-and-transfer-custom-tokens-on-the-logos-execution-zone","unlisted":false},{"type":"link","href":"/lez/transfer-tokens/transfer-native-lez-tokens-between-public-private-states","label":"Transfer native LEZ tokens between public and private states","docId":"lez/transfer-tokens/transfer-native-lez-tokens-between-public-private-states","unlisted":false}]},{"type":"category","label":"Follow chain","collapsible":true,"collapsed":true,"items":[{"type":"link","href":"/lez/follow-chain/run-lez-indexer","label":"Run an LEZ Indexer","docId":"lez/follow-chain/run-lez-indexer","unlisted":false},{"type":"link","href":"/lez/follow-chain/start-and-use-instance-of-lez-explorer","label":"Start and use an instance of the LEZ Explorer","docId":"lez/follow-chain/start-and-use-instance-of-lez-explorer","unlisted":false}]},{"type":"category","label":"Accounts","collapsible":true,"collapsed":true,"items":[{"type":"link","href":"/lez/accounts/set-up-shared-private-lez-account","label":"Set up a shared private account","docId":"lez/accounts/set-up-shared-private-lez-account","unlisted":false}]}],"coreSidebar":[{"type":"category","label":"Build modules","collapsible":true,"collapsed":true,"items":[{"type":"link","href":"/core/build-modules/start-a-logos-module-from-the-cli","label":"Start a Logos module from the CLI","docId":"core/build-modules/start-a-logos-module-from-the-cli","unlisted":false},{"type":"link","href":"/core/build-modules/build-and-run-a-logos-core-module","label":"Build and run a Logos core module","docId":"core/build-modules/build-and-run-a-logos-core-module","unlisted":false},{"type":"link","href":"/core/build-modules/wrap-a-c-library-as-a-logos-core-module","label":"Wrap a C library as a Logos module","docId":"core/build-modules/wrap-a-c-library-as-a-logos-core-module","unlisted":false},{"type":"link","href":"/core/build-modules/build-a-logos-cpp-ui-module","label":"Build a Logos C++ UI module","docId":"core/build-modules/build-a-logos-cpp-ui-module","unlisted":false},{"type":"link","href":"/core/build-modules/install-and-load-a-module-in-logos-basecamp","label":"Install and load a module in Logos Basecamp","docId":"core/build-modules/install-and-load-a-module-in-logos-basecamp","unlisted":false},{"type":"link","href":"/core/build-modules/build-logos-core-module-that-uses-service-discovery-api","label":"Build a Logos Core module that uses the Service Discovery API","docId":"core/build-modules/build-logos-core-module-that-uses-service-discovery-api","unlisted":false}]},{"type":"category","label":"Reference","collapsible":true,"collapsed":true,"items":[{"type":"link","href":"/core/reference/lgx-package-format-and-bundling-reference","label":"LGX package format and bundling reference","docId":"core/reference/lgx-package-format-and-bundling-reference","unlisted":false},{"type":"link","href":"/core/reference/logos-cli-reference","label":"Logos CLI Reference","docId":"core/reference/logos-cli-reference","unlisted":false}]}],"messagingSidebar":[{"type":"category","label":"Get started","collapsible":true,"collapsed":true,"items":[{"type":"link","href":"/messaging/get-started/introduction-to-logos-messaging","label":"Introduction to Logos Messaging","docId":"messaging/get-started/introduction-to-logos-messaging","unlisted":false},{"type":"link","href":"/messaging/get-started/send-1-1-messages-logos-chat","label":"Send 1:1 messages with the Logos Chat app","docId":"messaging/get-started/send-1-1-messages-logos-chat","unlisted":false},{"type":"link","href":"/messaging/get-started/faq","label":"FAQ: Logos Messaging","docId":"messaging/get-started/faq-logos-messaging","unlisted":false}]},{"type":"category","label":"Delivery module","collapsible":true,"collapsed":true,"items":[{"type":"link","href":"/messaging/delivery/use-logos-delivery-module-api-from-app","label":"Use the Logos Delivery module API from an app","docId":"messaging/delivery/use-logos-delivery-module-api-from-app","unlisted":false},{"type":"link","href":"/messaging/delivery/run-logos-delivery-node","label":"Run a Logos delivery node","docId":"messaging/delivery/run-logos-delivery-node","unlisted":false}]},{"type":"category","label":"Concepts","collapsible":true,"collapsed":true,"items":[{"type":"link","href":"/messaging/concepts/protocols","label":"Understand Logos Delivery protocols","docId":"messaging/concepts/understand-logos-delivery-protocols","unlisted":false},{"type":"link","href":"/messaging/concepts/content-topics","label":"About content topics","docId":"messaging/concepts/about-content-topics","unlisted":false},{"type":"link","href":"/messaging/concepts/network-domains","label":"About network domains","docId":"messaging/concepts/about-network-domains","unlisted":false},{"type":"link","href":"/messaging/concepts/transports","label":"About transports","docId":"messaging/concepts/about-transports","unlisted":false},{"type":"category","label":"Understand peer discovery","collapsible":true,"collapsed":true,"items":[{"type":"link","href":"/messaging/concepts/understand-peer-discovery/static-peers","label":"About static peers","docId":"messaging/concepts/understand-peer-discovery/about-static-peers","unlisted":false},{"type":"link","href":"/messaging/concepts/understand-peer-discovery/dns-discovery","label":"About DNS discovery","docId":"messaging/concepts/understand-peer-discovery/about-dns-discovery","unlisted":false},{"type":"link","href":"/messaging/concepts/understand-peer-discovery/discv5","label":"About Discv5","docId":"messaging/concepts/understand-peer-discovery/about-discv5","unlisted":false},{"type":"link","href":"/messaging/concepts/understand-peer-discovery/peer-exchange","label":"About peer exchange","docId":"messaging/concepts/understand-peer-discovery/about-peer-exchange","unlisted":false}],"href":"/messaging/concepts/understand-peer-discovery/peer-discovery"},{"type":"link","href":"/messaging/concepts/security-features","label":"Understand Logos Delivery security features","docId":"messaging/concepts/understand-logos-delivery-security-features","unlisted":false},{"type":"link","href":"/messaging/concepts/logos-delivery-and-libp2p","label":"Learn the differences between Logos Delivery and libp2p","docId":"messaging/concepts/learn-the-differences-between-logos-delivery-and-libp2p","unlisted":false}]},{"type":"category","label":"Chat module","collapsible":true,"collapsed":true,"items":[{"type":"link","href":"/messaging/chat-module/build-logos-module-that-uses-chat-module-api","label":"Build a Logos module that uses the Chat module API","docId":"messaging/chat-module/build-logos-module-that-uses-chat-module-api","unlisted":false},{"type":"link","href":"/messaging/chat-module/send-anonymised-messages-over-the-mix-network","label":"Send anonymised messages over the mix network","docId":"messaging/chat-module/send-anonymised-messages-over-the-mix-network","unlisted":false}]}],"storageSidebar":[{"type":"category","label":"Get started","collapsible":true,"collapsed":true,"items":[{"type":"link","href":"/storage/get-started/run-logos-storage-node","label":"Run a Logos storage node","docId":"storage/get-started/run-logos-storage-node","unlisted":false},{"type":"link","href":"/storage/get-started/set-up-and-use-logos-storage-ui","label":"Set up and use the Logos Storage UI","docId":"storage/get-started/set-up-and-use-logos-storage-ui","unlisted":false}]}],"mixnetSidebar":[{"type":"category","label":"Get started","collapsible":true,"collapsed":true,"items":[{"type":"link","href":"/mixnet/get-started/discover-nodes-and-send-messages-via-the-anoncomms-mixnet-demo-app","label":"Discover nodes and send messages via the AnonComms Mixnet demo app","docId":"mixnet/get-started/discover-nodes-and-send-messages-via-the-anoncomms-mixnet-demo-app","unlisted":false}]}],"peerDiscoverySidebar":[{"type":"link","href":"/peer-discovery/welcome","label":"Welcome","docId":"peer-discovery/welcome","unlisted":false}],"contributeSidebar":[{"type":"link","href":"/contribute/welcome","label":"Welcome","docId":"contribute/welcome","unlisted":false}]},"docs":{"basecamp/get-started/install-logos-basecamp":{"id":"basecamp/get-started/install-logos-basecamp","title":"Install Logos Basecamp","description":"Get Logos Basecamp running on your desktop.","sidebar":"basecampSidebar"},"blockchain/blend/join-the-blend-network-as-a-core-node":{"id":"blockchain/blend/join-the-blend-network-as-a-core-node","title":"Join the Blend network as a core node","description":"Connect your blockchain node to Blend to contribute to proposer privacy.","sidebar":"blockchainSidebar"},"blockchain/concepts/about-bedrock":{"id":"blockchain/concepts/about-bedrock","title":"About Bedrock","description":"Understand how Bedrock provides the consensus and shared context that Zones build on.","sidebar":"blockchainSidebar"},"blockchain/concepts/about-cryptarchia":{"id":"blockchain/concepts/about-cryptarchia","title":"About Cryptarchia","description":"Understand how Cryptarchia reaches consensus while keeping block proposers private.","sidebar":"blockchainSidebar"},"blockchain/concepts/about-mantle":{"id":"blockchain/concepts/about-mantle","title":"About Mantle","description":"Understand how Mantle lets nodes and Zones interact with Bedrock.","sidebar":"blockchainSidebar"},"blockchain/concepts/about-the-blend-network":{"id":"blockchain/concepts/about-the-blend-network","title":"About the Blend Network","description":"Understand how the Blend Network hides the link between a block proposer and their proposal.","sidebar":"blockchainSidebar"},"blockchain/concepts/about-zones":{"id":"blockchain/concepts/about-zones","title":"About Zones","description":"Understand how Zones run custom, high-performance applications on the Logos Blockchain.","sidebar":"blockchainSidebar"},"blockchain/get-started/introduction-to-the-logos-blockchain":{"id":"blockchain/get-started/introduction-to-the-logos-blockchain","title":"Introduction to the Logos Blockchain","description":"Learn how Logos provides a private, resilient foundation for decentralised applications.","sidebar":"blockchainSidebar"},"blockchain/get-started/run-a-logos-blockchain-node-from-basecamp":{"id":"blockchain/get-started/run-a-logos-blockchain-node-from-basecamp","title":"Run a Logos Blockchain node from Basecamp","description":"Install the blockchain modules and run a syncing testnet node from the Basecamp desktop app.","sidebar":"blockchainSidebar"},"blockchain/get-started/run-a-logos-blockchain-node-from-cli":{"id":"blockchain/get-started/run-a-logos-blockchain-node-from-cli","title":"Run a Logos Blockchain node on the public testnet from the CLI","description":"Start a node and verify runtime and consensus signals.","sidebar":"blockchainSidebar"},"blockchain/node-app/bridge-assets-from-logos-blockchain-to-zone-using-app":{"id":"blockchain/node-app/bridge-assets-from-logos-blockchain-to-zone-using-app","title":"Bridge assets from Logos Blockchain to a Zone using the Logos Blockchain app","description":"Get started locking wallet notes into a Logos Zone, including the LEZ, using the Logos Blockchain UI app.","sidebar":"blockchainSidebar"},"blockchain/node-app/build-and-run-logos-blockchain-node-app-ui":{"id":"blockchain/node-app/build-and-run-logos-blockchain-node-app-ui","title":"Build and run the Logos Blockchain UI app","description":"Run a node that participates in consensus via a standalone application.","sidebar":"blockchainSidebar"},"blockchain/node-app/claim-leader-rewards-in-logos-blockchain-ui-app":{"id":"blockchain/node-app/claim-leader-rewards-in-logos-blockchain-ui-app","title":"Claim leader rewards in the Logos Blockchain UI app","description":"Learn how to get rewarded for having your Logos Blockchain node propose blocks","sidebar":"blockchainSidebar"},"blockchain/node-app/move-assets-using-logos-blockchain-app":{"id":"blockchain/node-app/move-assets-using-logos-blockchain-app","title":"Move assets using the Logos Blockchain UI app","description":"Get started with token transfers between wallet accounts directly from the dashboard UI.","sidebar":"blockchainSidebar"},"blockchain/zone-sdk/bridge-assets-between-blockchain-and-zone":{"id":"blockchain/zone-sdk/bridge-assets-between-blockchain-and-zone","title":"Bridge assets between Logos Blockchain and a Zone","description":"Move tokens between Logos Blockchain and a Zone using channels and the Zone SDK.","sidebar":"blockchainSidebar"},"blockchain/zone-sdk/inscribe-data-on-chain-using-zone-sdk":{"id":"blockchain/zone-sdk/inscribe-data-on-chain-using-zone-sdk","title":"Inscribe data on-chain using the Zone SDK","description":"Learn how to write plain text as on-chain inscriptions using a simple Logos Zone.","sidebar":"blockchainSidebar"},"blockchain/zone-sdk/operate-decentralised-zone-with-round-robin-sequencer-rotation":{"id":"blockchain/zone-sdk/operate-decentralised-zone-with-round-robin-sequencer-rotation","title":"Operate a decentralised Zone with round-robin sequencer rotation","description":"Get started with multi-sequencer channels using the Zone SDK and round-robin slot windows.","sidebar":"blockchainSidebar"},"build-an-app/tutorials/build-dapp-with-logos-zone-sdk":{"id":"build-an-app/tutorials/build-dapp-with-logos-zone-sdk","title":"Build a dApp with the Logos Zone SDK","description":"Learn how to use the Zone SDK to implement a decentralised password manager application.","sidebar":"buildAnAppSidebar"},"build-an-app/tutorials/create-and-use-an-amm-liquidity-pool-on-the-logos-execution-zone":{"id":"build-an-app/tutorials/create-and-use-an-amm-liquidity-pool-on-the-logos-execution-zone","title":"Create and use an AMM liquidity pool on the Logos Execution Zone","description":"This page is an early draft and may be incomplete or incorrect. Expect changes, missing prerequisites, and commands that might not work in your setup. We are actively working to complete and verify this content.","sidebar":"buildAnAppSidebar"},"contribute/welcome":{"id":"contribute/welcome","title":"Welcome","description":"Welcome to the contributor documentation! We\'re working on the content.","sidebar":"contributeSidebar"},"core/build-modules/build-a-logos-cpp-ui-module":{"id":"core/build-modules/build-a-logos-cpp-ui-module","title":"Build a Logos C++ UI module","description":"Get started building a ui\\\\_qml module with a C++ backend that runs in a separate process.","sidebar":"coreSidebar"},"core/build-modules/build-and-run-a-logos-core-module":{"id":"core/build-modules/build-and-run-a-logos-core-module","title":"Build and run a Logos core module","description":"Scaffold, build, package, and test a core module on Logos.","sidebar":"coreSidebar"},"core/build-modules/build-logos-core-module-that-uses-service-discovery-api":{"id":"core/build-modules/build-logos-core-module-that-uses-service-discovery-api","title":"Build a Logos Core module that uses the Service Discovery API","description":"Get started with typed, service-keyed peer lookups in a live Logos network.","sidebar":"coreSidebar"},"core/build-modules/install-and-load-a-module-in-logos-basecamp":{"id":"core/build-modules/install-and-load-a-module-in-logos-basecamp","title":"Install and load a module in Logos Basecamp","description":"Access features and functionalities through modules in Logos Basecamp.","sidebar":"coreSidebar"},"core/build-modules/start-a-logos-module-from-the-cli":{"id":"core/build-modules/start-a-logos-module-from-the-cli","title":"Start a Logos module from the CLI","description":"Explore how to load and call a Logos module from the command line using logoscore.","sidebar":"coreSidebar"},"core/build-modules/wrap-a-c-library-as-a-logos-core-module":{"id":"core/build-modules/wrap-a-c-library-as-a-logos-core-module","title":"Wrap a C library as a Logos module","description":"Expose functions from a C shared library through a Logos core module.","sidebar":"coreSidebar"},"core/reference/lgx-package-format-and-bundling-reference":{"id":"core/reference/lgx-package-format-and-bundling-reference","title":"LGX package format and bundling reference","description":"We\'re working on the content.","sidebar":"coreSidebar"},"core/reference/logos-cli-reference":{"id":"core/reference/logos-cli-reference","title":"Logos CLI Reference","description":"We\'re working on the content.","sidebar":"coreSidebar"},"get-started/glossary":{"id":"get-started/glossary","title":"Glossary","description":"A list of some common terms and phrases specific to the Logos ecosystem.","sidebar":"getStartedSidebar"},"get-started/introduction-to-logos":{"id":"get-started/introduction-to-logos","title":"Introduction to Logos","description":"Learn how the Logos technology stack is organised as a modular operating system for decentralised applications.","sidebar":"getStartedSidebar"},"get-started/logos-ecosystem-repositories":{"id":"get-started/logos-ecosystem-repositories","title":"Logos ecosystem repositories","description":"Review the key repositories across the logos organisations.","sidebar":"getStartedSidebar"},"lez/accounts/set-up-shared-private-lez-account":{"id":"lez/accounts/set-up-shared-private-lez-account","title":"Set up a shared private account","description":"Get started with group-owned private accounts where every member independently derives the same keys.","sidebar":"lezSidebar"},"lez/follow-chain/run-lez-indexer":{"id":"lez/follow-chain/run-lez-indexer","title":"Run an LEZ Indexer","description":"Get started with the LEZ Indexer and query finalized LEZ state over HTTP RPC.","sidebar":"lezSidebar"},"lez/follow-chain/start-and-use-instance-of-lez-explorer":{"id":"lez/follow-chain/start-and-use-instance-of-lez-explorer","title":"Start and use an instance of the LEZ Explorer","description":"Get a clear view of on-chain activity including blocks, transactions, and accounts.","sidebar":"lezSidebar"},"lez/get-started/connect-wallet-cli-to-lez-testnet":{"id":"lez/get-started/connect-wallet-cli-to-lez-testnet","title":"Connect the wallet CLI to the LEZ testnet","description":"Try the wallet CLI against the live LEZ testnet instead of a local sequencer.","sidebar":"lezSidebar"},"lez/get-started/initiate-native-token-transfers-on-lez-with-wallet-ui":{"id":"lez/get-started/initiate-native-token-transfers-on-lez-with-wallet-ui","title":"Initiate native token transfers on the LEZ with the wallet UI","description":"Use the wallet UI to set up accounts and try every type of native token transfer on the LEZ testnet.","sidebar":"lezSidebar"},"lez/get-started/introduction-to-the-logos-execution-zone":{"id":"lez/get-started/introduction-to-the-logos-execution-zone","title":"Introduction to the Logos Execution Zone","description":"Understand how the Logos Execution Zone runs general-purpose applications with selective privacy.","sidebar":"lezSidebar"},"lez/get-started/quickstart-for-the-logos-execution-zone-wallet":{"id":"lez/get-started/quickstart-for-the-logos-execution-zone-wallet","title":"Quickstart for the Logos Execution Zone wallet","description":"Set up the wallet, connect to a sequencer, and run a minimal transfer flow.","sidebar":"lezSidebar"},"lez/transfer-tokens/create-and-transfer-custom-tokens-on-the-logos-execution-zone":{"id":"lez/transfer-tokens/create-and-transfer-custom-tokens-on-the-logos-execution-zone","title":"Create and transfer custom tokens on the Logos Execution Zone","description":"Use the wallet CLI to create custom tokens and transfer them between public and private accounts.","sidebar":"lezSidebar"},"lez/transfer-tokens/transfer-native-lez-tokens-between-public-private-states":{"id":"lez/transfer-tokens/transfer-native-lez-tokens-between-public-private-states","title":"Transfer native LEZ tokens between public and private states","description":"Get started with private transfers to accounts you don\'t control, using a recipient-published keypair.","sidebar":"lezSidebar"},"lez/transfer-tokens/transfer-native-tokens-on-the-logos-execution-zone":{"id":"lez/transfer-tokens/transfer-native-tokens-on-the-logos-execution-zone","title":"Transfer native tokens on the Logos Execution Zone","description":"Use the wallet CLI to send native tokens to public and private accounts.","sidebar":"lezSidebar"},"messaging/chat-module/build-logos-module-that-uses-chat-module-api":{"id":"messaging/chat-module/build-logos-module-that-uses-chat-module-api","title":"Build a Logos module that uses the Chat module API","description":"Get started with private 1:1 end-to-end encrypted messaging in your own Logos module.","sidebar":"messagingSidebar"},"messaging/chat-module/send-anonymised-messages-over-the-mix-network":{"id":"messaging/chat-module/send-anonymised-messages-over-the-mix-network","title":"Send anonymised messages over the mix network","description":"Get started sending and receiving messages with sender anonymity using the Logos Chat UI.","sidebar":"messagingSidebar"},"messaging/concepts/about-content-topics":{"id":"messaging/concepts/about-content-topics","title":"About content topics","description":"Understand how content topics filter and route messages, and how naming choices affect privacy.","sidebar":"messagingSidebar"},"messaging/concepts/about-network-domains":{"id":"messaging/concepts/about-network-domains","title":"About network domains","description":"Understand the discovery, gossip, and request/response domains that structure how peers interact.","sidebar":"messagingSidebar"},"messaging/concepts/about-transports":{"id":"messaging/concepts/about-transports","title":"About transports","description":"Understand how nodes move data between peers and which transports suit each environment.","sidebar":"messagingSidebar"},"messaging/concepts/learn-the-differences-between-logos-delivery-and-libp2p":{"id":"messaging/concepts/learn-the-differences-between-logos-delivery-and-libp2p","title":"Learn the differences between Logos Delivery and libp2p","description":"Understand what Logos Delivery adds on top of libp2p and where the two differ.","sidebar":"messagingSidebar"},"messaging/concepts/understand-logos-delivery-protocols":{"id":"messaging/concepts/understand-logos-delivery-protocols","title":"Understand Logos Delivery protocols","description":"Understand the modular protocols that let applications balance anonymity, scalability, and latency.","sidebar":"messagingSidebar"},"messaging/concepts/understand-logos-delivery-security-features":{"id":"messaging/concepts/understand-logos-delivery-security-features","title":"Understand Logos Delivery security features","description":"See the services that keep Logos Delivery communication private and abuse-resistant.","sidebar":"messagingSidebar"},"messaging/concepts/understand-peer-discovery/about-discv5":{"id":"messaging/concepts/understand-peer-discovery/about-discv5","title":"About Discv5","description":"Understand how Discv5 uses a distributed hash table to find peers across the network.","sidebar":"messagingSidebar"},"messaging/concepts/understand-peer-discovery/about-dns-discovery":{"id":"messaging/concepts/understand-peer-discovery/about-dns-discovery","title":"About DNS discovery","description":"Understand how a node retrieves peer connection details from an ENR tree published in DNS.","sidebar":"messagingSidebar"},"messaging/concepts/understand-peer-discovery/about-peer-exchange":{"id":"messaging/concepts/understand-peer-discovery/about-peer-exchange","title":"About peer exchange","description":"Understand how light nodes request peers from other nodes without relying on Discv5.","sidebar":"messagingSidebar"},"messaging/concepts/understand-peer-discovery/about-static-peers":{"id":"messaging/concepts/understand-peer-discovery/about-static-peers","title":"About static peers","description":"Understand how hard-coded bootstrap peers work and the trade-offs they carry.","sidebar":"messagingSidebar"},"messaging/concepts/understand-peer-discovery/understand-peer-discovery":{"id":"messaging/concepts/understand-peer-discovery/understand-peer-discovery","title":"Understand peer discovery","description":"Learn how a node bootstraps and finds the peers it needs to operate.","sidebar":"messagingSidebar"},"messaging/delivery/run-logos-delivery-node":{"id":"messaging/delivery/run-logos-delivery-node","title":"Run a Logos delivery node","description":"Use the Logos Delivery module to run a Logos delivery node","sidebar":"messagingSidebar"},"messaging/delivery/use-logos-delivery-module-api-from-app":{"id":"messaging/delivery/use-logos-delivery-module-api-from-app","title":"Use the Logos Delivery module API from an app","description":"Get started integrating Logos messaging into a C++ module.","sidebar":"messagingSidebar"},"messaging/get-started/faq-logos-messaging":{"id":"messaging/get-started/faq-logos-messaging","title":"FAQ: Logos Messaging","description":"Review common questions about how Logos Messaging works and how to build on it.","sidebar":"messagingSidebar"},"messaging/get-started/introduction-to-logos-messaging":{"id":"messaging/get-started/introduction-to-logos-messaging","title":"Introduction to Logos Messaging","description":"Learn how Logos provides private, censorship-resistant communication for decentralised applications.","sidebar":"messagingSidebar"},"messaging/get-started/send-1-1-messages-logos-chat":{"id":"messaging/get-started/send-1-1-messages-logos-chat","title":"Send 1:1 messages with the Logos Chat app","description":"Try out end-to-end encrypted private messaging over the Logos network.","sidebar":"messagingSidebar"},"mixnet/get-started/discover-nodes-and-send-messages-via-the-anoncomms-mixnet-demo-app":{"id":"mixnet/get-started/discover-nodes-and-send-messages-via-the-anoncomms-mixnet-demo-app","title":"Discover nodes and send messages via the AnonComms Mixnet demo app","description":"This page is an early draft and may be incomplete or incorrect. Expect changes, missing prerequisites, and commands that might not work in your setup. We are actively working to complete and verify this content.","sidebar":"mixnetSidebar"},"peer-discovery/welcome":{"id":"peer-discovery/welcome","title":"Welcome","description":"Welcome to the Logos peer discovery documentation! We\'re working on the content.","sidebar":"peerDiscoverySidebar"},"run-a-node/get-started/run-logos-node-blockchain-storage-delivery":{"id":"run-a-node/get-started/run-logos-node-blockchain-storage-delivery","title":"Run a Logos node with blockchain, storage, and delivery","description":"Get started running a full Logos node with all three core modules on testnet v0.2.","sidebar":"runANodeSidebar"},"storage/get-started/run-logos-storage-node":{"id":"storage/get-started/run-logos-storage-node","title":"Run a Logos storage node","description":"Get started running a Logos storage node and uploading your first file to the Logos network.","sidebar":"storageSidebar"},"storage/get-started/set-up-and-use-logos-storage-ui":{"id":"storage/get-started/set-up-and-use-logos-storage-ui","title":"Set up and use the Logos Storage UI","description":"Get started sharing and downloading files on the Logos Storage network","sidebar":"storageSidebar"}}}}')}}]); \ No newline at end of file diff --git a/assets/js/25c725b0.d8e5280e.js b/assets/js/25c725b0.d8e5280e.js new file mode 100644 index 00000000..07caead7 --- /dev/null +++ b/assets/js/25c725b0.d8e5280e.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunklogos_docs=self.webpackChunklogos_docs||[]).push([["7868"],{1156(e,t,o){o.r(t),o.d(t,{metadata:()=>n,default:()=>d,frontMatter:()=>i,contentTitle:()=>s,toc:()=>u,assets:()=>l});var n=JSON.parse('{"id":"contribute/welcome","title":"Welcome","description":"Welcome to the contributor documentation! We\'re working on the content.","source":"@site/docs/contribute/welcome.md","sourceDirName":"contribute","slug":"/contribute/welcome","permalink":"/contribute/welcome","draft":false,"unlisted":false,"editUrl":"https://github.com/logos-co/logos-docs/tree/main/docs/contribute/welcome.md","tags":[],"version":"current","sidebarPosition":1,"frontMatter":{"sidebar_position":1},"sidebar":"contributeSidebar"}'),r=o(4848),c=o(8453);let i={sidebar_position:1},s="Welcome",l={},u=[];function a(e){let t={h1:"h1",header:"header",p:"p",...(0,c.R)(),...e.components};return(0,r.jsxs)(r.Fragment,{children:[(0,r.jsx)(t.header,{children:(0,r.jsx)(t.h1,{id:"welcome",children:"Welcome"})}),"\n",(0,r.jsx)(t.p,{children:"Welcome to the contributor documentation! We're working on the content."})]})}function d(e={}){let{wrapper:t}={...(0,c.R)(),...e.components};return t?(0,r.jsx)(t,{...e,children:(0,r.jsx)(a,{...e})}):a(e)}},8453(e,t,o){o.d(t,{R:()=>i,x:()=>s});var n=o(6540);let r={},c=n.createContext(r);function i(e){let t=n.useContext(c);return n.useMemo(function(){return"function"==typeof e?e(t):{...t,...e}},[t,e])}function s(e){let t;return t=e.disableParentContext?"function"==typeof e.components?e.components(r):e.components||r:i(e.components),n.createElement(c.Provider,{value:t},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/2a349706.2d5e5d68.js b/assets/js/2a349706.2d5e5d68.js new file mode 100644 index 00000000..b021f7d1 --- /dev/null +++ b/assets/js/2a349706.2d5e5d68.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunklogos_docs=self.webpackChunklogos_docs||[]).push([["5980"],{9278(e,n,s){s.r(n),s.d(n,{metadata:()=>t,default:()=>h,frontMatter:()=>o,contentTitle:()=>l,toc:()=>a,assets:()=>c});var t=JSON.parse('{"id":"lez/follow-chain/run-lez-indexer","title":"Run an LEZ Indexer","description":"Get started with the LEZ Indexer and query finalized LEZ state over HTTP RPC.","source":"@site/docs/lez/follow-chain/run-lez-indexer.md","sourceDirName":"lez/follow-chain","slug":"/lez/follow-chain/run-lez-indexer","permalink":"/lez/follow-chain/run-lez-indexer","draft":false,"unlisted":false,"editUrl":"https://github.com/logos-co/logos-docs/tree/main/docs/lez/follow-chain/run-lez-indexer.md","tags":[],"version":"current","sidebarPosition":1,"frontMatter":{"title":"Run an LEZ Indexer","doc_type":"procedure","product":"lez","topics":"lez","steps_layout":"flat","authors":"Pravdyvy, kashepavadan","owner":"logos","doc_version":1,"slug":"run-lez-indexer","sidebar_position":1},"sidebar":"lezSidebar","previous":{"title":"Transfer native LEZ tokens between public and private states","permalink":"/lez/transfer-tokens/transfer-native-lez-tokens-between-public-private-states"},"next":{"title":"Start and use an instance of the LEZ Explorer","permalink":"/lez/follow-chain/start-and-use-instance-of-lez-explorer"}}'),i=s(4848),r=s(8453);let o={title:"Run an LEZ Indexer",doc_type:"procedure",product:"lez",topics:"lez",steps_layout:"flat",authors:"Pravdyvy, kashepavadan",owner:"logos",doc_version:1,slug:"run-lez-indexer",sidebar_position:1},l="Run an LEZ Indexer",c={},a=[{value:"Get started with the LEZ Indexer and query finalized LEZ state over HTTP RPC.",id:"get-started-with-the-lez-indexer-and-query-finalized-lez-state-over-http-rpc",level:4},{value:"What to expect",id:"what-to-expect",level:2},{value:"Start the LEZ stack and run the indexer",id:"start-the-lez-stack-and-run-the-indexer",level:2},{value:"Verify the indexer is progressing",id:"verify-the-indexer-is-progressing",level:2},{value:"Query account state using the wallet and RPC",id:"query-account-state-using-the-wallet-and-rpc",level:2},{value:"Frequently asked questions",id:"frequently-asked-questions",level:2},{value:"Why is there a delay between sending a transaction and seeing it in the indexer?",id:"why-is-there-a-delay-between-sending-a-transaction-and-seeing-it-in-the-indexer",level:3},{value:"How do I change the indexer configuration?",id:"how-do-i-change-the-indexer-configuration",level:3}];function d(e){let n={a:"a",admonition:"admonition",code:"code",h1:"h1",h2:"h2",h3:"h3",h4:"h4",header:"header",li:"li",ol:"ol",p:"p",pre:"pre",ul:"ul",...(0,r.R)(),...e.components};return(0,i.jsxs)(i.Fragment,{children:[(0,i.jsx)(n.header,{children:(0,i.jsx)(n.h1,{id:"run-an-lez-indexer",children:"Run an LEZ Indexer"})}),"\n",(0,i.jsx)(n.h4,{id:"get-started-with-the-lez-indexer-and-query-finalized-lez-state-over-http-rpc",children:"Get started with the LEZ Indexer and query finalized LEZ state over HTTP RPC."}),"\n",(0,i.jsxs)(n.p,{children:["The ",(0,i.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#lez",children:"LEZ"})," Indexer is a service that reads the finalized LEZ state from the ",(0,i.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#logos-blockchain",children:"Logos Blockchain"})," node and exposes it over an HTTP RPC interface. Any application can use this interface to query blocks, transactions, and ",(0,i.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#account",children:"account"})," state. This procedure walks you through running an LEZ Indexer locally alongside its dependencies, verifying it is indexing blocks, and querying account state through the RPC."]}),"\n",(0,i.jsx)(n.p,{children:"Before you start, make sure you have the following:"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsx)(n.li,{children:"Linux or macOS operating system"}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.a,{href:"https://docs.docker.com/get-docker/",children:"Docker"})," installed"]}),"\n",(0,i.jsxs)(n.li,{children:["Rust 1.94.0 and ",(0,i.jsx)(n.code,{children:"cargo"})," installed"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.a,{href:"https://github.com/casey/just",children:(0,i.jsx)(n.code,{children:"just"})})," installed"]}),"\n",(0,i.jsxs)(n.li,{children:["A local clone of the ",(0,i.jsx)(n.a,{href:"https://github.com/logos-blockchain/logos-execution-zone",children:"LEZ repository"})," and ",(0,i.jsx)(n.a,{href:"/lez/get-started/connect-wallet-cli-to-lez-testnet",children:"wallet"})," set up"]}),"\n",(0,i.jsxs)(n.li,{children:["A running ",(0,i.jsx)(n.a,{href:"https://github.com/logos-co/logos-docs/blob/main/docs/blockchain/get-started/run-a-logos-blockchain-node-from-cli.md",children:"Logos Blockchain node"})," (if not deploying locally)"]}),"\n",(0,i.jsxs)(n.li,{children:["Port ",(0,i.jsx)(n.code,{children:"8779"})," free on your machine"]}),"\n"]}),"\n",(0,i.jsx)(n.h2,{id:"what-to-expect",children:"What to expect"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:["You can query finalized LEZ blockchain state including blocks, transactions, and accounts via HTTP RPC on ",(0,i.jsx)(n.code,{children:"http://localhost:8779"}),"."]}),"\n",(0,i.jsxs)(n.li,{children:["You have a running local stack with the Logos Blockchain node, LEZ Sequencer, and LEZ Indexer coordinated through ",(0,i.jsx)(n.code,{children:"just"})," commands."]}),"\n",(0,i.jsxs)(n.li,{children:["You can verify indexer health and confirm it is progressing by calling the ",(0,i.jsx)(n.code,{children:"getLastFinalizedBlockId"})," and ",(0,i.jsx)(n.code,{children:"checkHealth"})," RPC methods."]}),"\n"]}),"\n",(0,i.jsx)(n.h2,{id:"start-the-lez-stack-and-run-the-indexer",children:"Start the LEZ stack and run the indexer"}),"\n",(0,i.jsx)(n.p,{children:"The indexer depends on the Logos Blockchain Node and benefits from an LEZ Sequencer running alongside it. For a local deployment, start each service in its own terminal."}),"\n",(0,i.jsxs)(n.ol,{children:["\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsxs)(n.p,{children:["In the LEZ repository, run ",(0,i.jsx)(n.code,{children:"just clean"})," to remove any leftover artifacts from previous runs."]}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsxs)(n.p,{children:["Replace the default value of ",(0,i.jsx)(n.code,{children:"bedrock_config.addr"})," in ",(0,i.jsx)(n.code,{children:"lez/indexer/service/configs/debug/indexer_config.json"})," with your Logos Blockchain endpoint and replace the default ",(0,i.jsx)(n.code,{children:"channel_id"})," there with the LEZ's ",(0,i.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#channel",children:"channel"})," ID. Get the channel ID from the public LEZ sequencer by running:"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:'curl https://testnet.lez.logos.co/ \\\n -H "Content-Type: application/json" \\\n -d "{ \\\n \\"jsonrpc\\": \\"2.0\\", \\\n \\"method\\": \\"getChannelId\\", \\\n \\"params\\": {}, \\\n \\"id\\": 1 \\\n }"\n'})}),"\n",(0,i.jsx)(n.p,{children:"For a local deployment, run the following commands in two separate terminal windows:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-sh",children:"# Run node, in terminal 1\njust run-bedrock\n"})}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-sh",children:"# Terminal 2\njust run-sequencer\n"})}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:["Test configs are located ",(0,i.jsx)(n.a,{href:"https://github.com/logos-blockchain/logos-execution-zone/tree/main/bedrock",children:"here for the node"})," and ",(0,i.jsx)(n.a,{href:"https://github.com/logos-blockchain/logos-execution-zone/blob/main/lez/sequencer/service/configs/debug/sequencer_config.json",children:"here for the sequencer"}),"."]}),"\n"]}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsx)(n.p,{children:"In a third terminal window, start the LEZ Indexer:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-sh",children:"just run-indexer\n"})}),"\n",(0,i.jsxs)(n.p,{children:["The indexer serves JSON-RPC over HTTP (and WebSocket) on ",(0,i.jsx)(n.code,{children:"http://localhost:8779"})," by default. At startup, you will see:"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{children:"[2026-06-18T14:21:59Z INFO indexer_service] Starting Indexer Service RPC server on 0.0.0.0:8779\n[2026-06-18T14:21:59Z INFO indexer_core] Starting indexer from beginning of channel\n"})}),"\n",(0,i.jsx)(n.p,{children:"The indexer then listens for block finalizations."}),"\n"]}),"\n"]}),"\n",(0,i.jsx)(n.h2,{id:"verify-the-indexer-is-progressing",children:"Verify the indexer is progressing"}),"\n",(0,i.jsxs)(n.ol,{children:["\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsxs)(n.p,{children:["Call ",(0,i.jsx)(n.code,{children:"getLastFinalizedBlockId"})," to confirm the indexer is receiving blocks:"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-sh",children:'curl -X POST http://localhost:8779 \\\n -H "Content-Type: application/json" \\\n -d \'{"jsonrpc":"2.0","method":"getLastFinalizedBlockId","params":[],"id":1}\'\n'})}),"\n",(0,i.jsx)(n.p,{children:"You will receive a response similar to:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-sh",children:'{"jsonrpc":"2.0","id":1,"result":15}\n'})}),"\n",(0,i.jsxs)(n.p,{children:["The ",(0,i.jsx)(n.code,{children:"result"})," value should increase each time you repeat the call."]}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsx)(n.p,{children:"Run the health check to confirm the indexer can reconstruct the full state from the database:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-sh",children:'curl -X POST http://localhost:8779 \\\n -H "Content-Type: application/json" \\\n -d \'{"jsonrpc":"2.0","method":"checkHealth","params":[],"id":1}\'\n'})}),"\n",(0,i.jsx)(n.p,{children:"A healthy indexer returns:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-sh",children:'{"jsonrpc":"2.0","id":1,"result":null}\n'})}),"\n",(0,i.jsx)(n.admonition,{type:"info",children:(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.code,{children:"checkHealth"})," reconstructs the full LEZ state from the database and can produce heavy disk usage. Run it sparingly."]})}),"\n"]}),"\n"]}),"\n",(0,i.jsx)(n.h2,{id:"query-account-state-using-the-wallet-and-rpc",children:"Query account state using the wallet and RPC"}),"\n",(0,i.jsxs)(n.ol,{children:["\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsxs)(n.p,{children:["In a fourth terminal window, query an account state by calling ",(0,i.jsx)(n.code,{children:"getAccount"})," with the account ID:"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-sh",children:'curl -X POST http://localhost:8779 \\\n -H "Content-Type: application/json" \\\n -d \'{"jsonrpc":"2.0","method":"getAccount","params":{"account_id":"2RHZhw9h534Zr3eq2RGhQete2Hh667foECzXPmSkGni2"},"id":1}\'\n'})}),"\n",(0,i.jsx)(n.p,{children:"You will receive a response similar to:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-sh",children:'{"jsonrpc":"2.0","id":1,"result":{"program_owner":"FeYNA4PVvs1SsSuECrmQNnJqtB1jA6CbVBBSVGos7qcu","balance":20000,"data":"","nonce":1}}\n'})}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsxs)(n.p,{children:["Run ",(0,i.jsx)(n.code,{children:"just run-wallet --help"})," to see all available wallet commands."]}),"\n"]}),"\n"]}),"\n",(0,i.jsx)(n.h2,{id:"frequently-asked-questions",children:"Frequently asked questions"}),"\n",(0,i.jsx)(n.h3,{id:"why-is-there-a-delay-between-sending-a-transaction-and-seeing-it-in-the-indexer",children:"Why is there a delay between sending a transaction and seeing it in the indexer?"}),"\n",(0,i.jsx)(n.p,{children:"The delay is caused by Logos Blockchain's block finality time. The indexer only processes finalized blocks from the L1 node, so transactions sent through the sequencer are not visible until the corresponding block is finalized. This is expected behavior."}),"\n",(0,i.jsx)(n.h3,{id:"how-do-i-change-the-indexer-configuration",children:"How do I change the indexer configuration?"}),"\n",(0,i.jsxs)(n.p,{children:["The indexer config used by ",(0,i.jsx)(n.code,{children:"just run-indexer"})," is located at ",(0,i.jsx)(n.a,{href:"https://github.com/logos-blockchain/logos-execution-zone/blob/main/lez/indexer/service/configs/debug/indexer_config.json",children:"lez/indexer/service/configs/debug/indexer_config.json"})," (a ",(0,i.jsx)(n.code,{children:"configs/docker/indexer_config.json"})," variant is used for the Docker setup). Two fields control connectivity:"]}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"bedrock_config"})," \u2014 the HTTP address of the Logos Blockchain node."]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"channel_id"})," \u2014 the channel that LEZ Sequencer writes into. This must match the sequencer you are using."]}),"\n"]})]})}function h(e={}){let{wrapper:n}={...(0,r.R)(),...e.components};return n?(0,i.jsx)(n,{...e,children:(0,i.jsx)(d,{...e})}):d(e)}},8453(e,n,s){s.d(n,{R:()=>o,x:()=>l});var t=s(6540);let i={},r=t.createContext(i);function o(e){let n=t.useContext(r);return t.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function l(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(i):e.components||i:o(e.components),t.createElement(r.Provider,{value:n},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/2c805987.989509cd.js b/assets/js/2c805987.989509cd.js new file mode 100644 index 00000000..3eb86e02 --- /dev/null +++ b/assets/js/2c805987.989509cd.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunklogos_docs=self.webpackChunklogos_docs||[]).push([["256"],{3769(e,s,t){t.r(s),t.d(s,{metadata:()=>o,default:()=>h,frontMatter:()=>r,contentTitle:()=>l,toc:()=>c,assets:()=>a});var o=JSON.parse('{"id":"messaging/concepts/understand-logos-delivery-protocols","title":"Understand Logos Delivery protocols","description":"Understand the modular protocols that let applications balance anonymity, scalability, and latency.","source":"@site/docs/messaging/concepts/understand-logos-delivery-protocols.md","sourceDirName":"messaging/concepts","slug":"/messaging/concepts/protocols","permalink":"/messaging/concepts/protocols","draft":false,"unlisted":false,"editUrl":"https://github.com/logos-co/logos-docs/tree/main/docs/messaging/concepts/understand-logos-delivery-protocols.md","tags":[],"version":"current","sidebarPosition":1,"frontMatter":{"title":"Understand Logos Delivery protocols","doc_type":"concept","product":"messaging","topics":"protocols, architecture, privacy","authors":"LordGhostX, fryorcraken","owner":"logos","doc_version":1,"slug":"protocols","sidebar_position":1},"sidebar":"messagingSidebar","previous":{"title":"Run a Logos delivery node","permalink":"/messaging/delivery/run-logos-delivery-node"},"next":{"title":"About content topics","permalink":"/messaging/concepts/content-topics"}}'),n=t(4848),i=t(8453);let r={title:"Understand Logos Delivery protocols",doc_type:"concept",product:"messaging",topics:"protocols, architecture, privacy",authors:"LordGhostX, fryorcraken",owner:"logos",doc_version:1,slug:"protocols",sidebar_position:1},l="Understand Logos Delivery protocols",a={},c=[{value:"Understand the modular protocols that let applications balance anonymity, scalability, and latency.",id:"understand-the-modular-protocols-that-let-applications-balance-anonymity-scalability-and-latency",level:4},{value:"The basics",id:"the-basics",level:2},{value:"Relay",id:"relay",level:2},{value:"RLN Relay",id:"rln-relay",level:2},{value:"Filter",id:"filter",level:2},{value:"Store",id:"store",level:2},{value:"Light Push",id:"light-push",level:2},{value:"Waku message",id:"waku-message",level:2}];function d(e){let s={a:"a",admonition:"admonition",code:"code",h1:"h1",h2:"h2",h4:"h4",header:"header",li:"li",p:"p",ul:"ul",...(0,i.R)(),...e.components};return(0,n.jsxs)(n.Fragment,{children:[(0,n.jsx)(s.header,{children:(0,n.jsx)(s.h1,{id:"understand-logos-delivery-protocols",children:"Understand Logos Delivery protocols"})}),"\n",(0,n.jsx)(s.h4,{id:"understand-the-modular-protocols-that-let-applications-balance-anonymity-scalability-and-latency",children:"Understand the modular protocols that let applications balance anonymity, scalability, and latency."}),"\n",(0,n.jsxs)(s.p,{children:[(0,n.jsx)(s.a,{href:"https://docs.logos.co/get-started/glossary#logos-delivery",children:"Logos Delivery"})," takes a modular approach, providing a range of protocols that enable applications to control the trade-offs involved in the ",(0,n.jsx)(s.a,{href:"https://eprint.iacr.org/2017/954.pdf",children:"Anonymity Trilemma"}),". This flexibility empowers applications to make informed choices regarding the desired balance between anonymity, scalability, and latency."]}),"\n",(0,n.jsx)(s.h2,{id:"the-basics",children:"The basics"}),"\n",(0,n.jsxs)(s.ul,{children:["\n",(0,n.jsx)(s.li,{children:"Logos Delivery is modular, so applications combine protocols to balance anonymity, scalability, and latency."}),"\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.a,{href:"#relay",children:"Relay"})," provides the base publish/subscribe messaging, and ",(0,n.jsx)(s.a,{href:"#rln-relay",children:"RLN relay"})," extends it with economic spam prevention."]}),"\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.a,{href:"#filter",children:"Filter"}),", ",(0,n.jsx)(s.a,{href:"#store",children:"Store"}),", and ",(0,n.jsx)(s.a,{href:"#light-push",children:"Light push"})," adapt access for nodes with limited bandwidth or intermittent connections."]}),"\n"]}),"\n",(0,n.jsx)(s.h2,{id:"relay",children:"Relay"}),"\n",(0,n.jsxs)(s.p,{children:[(0,n.jsxs)(s.a,{href:"https://lip.logos.co/messaging/core/stable/11/relay.html",children:[(0,n.jsx)(s.code,{children:"Relay"})," protocol"]})," employs a Pub/Sub architecture to facilitate the sending and receiving of messages among peers. It extends the ",(0,n.jsx)(s.a,{href:"https://github.com/libp2p/specs/blob/master/pubsub/gossipsub/README.md",children:"libp2p GossipSub protocol"})," to create a privacy-focused peer-to-peer messaging protocol that enables secure communication channels, encryption, and protection against censorship."]}),"\n",(0,n.jsx)(s.h2,{id:"rln-relay",children:"RLN Relay"}),"\n",(0,n.jsxs)(s.p,{children:[(0,n.jsxs)(s.a,{href:"https://lip.logos.co/messaging/core/draft/17/rln-relay.html",children:[(0,n.jsx)(s.code,{children:"RLN Relay"})," protocol"]})," extends the ",(0,n.jsx)(s.code,{children:"Relay"})," protocol by using ",(0,n.jsx)(s.a,{href:"https://lip.logos.co/anoncomms/draft/32/rln-v1.html",children:"Rate Limit Nullifiers (RLN)"})," to provide efficient and economic spam-prevention. It enforces a rate limit on messages over time for all peers in the network, economically preventing spam, and imposes financial penalties and network removal for spammers. You can find more details in the ",(0,n.jsx)(s.a,{href:"https://research.logos.co/rlog/rln-relay",children:"RLN Relay blog post"}),"."]}),"\n",(0,n.jsx)(s.h2,{id:"filter",children:"Filter"}),"\n",(0,n.jsxs)(s.p,{children:[(0,n.jsxs)(s.a,{href:"https://lip.logos.co/messaging/core/draft/12/filter.html",children:[(0,n.jsx)(s.code,{children:"Filter"})," protocol"]})," allows ",(0,n.jsx)(s.a,{href:"https://docs.logos.co/get-started/glossary#light-node",children:"light nodes"})," to selectively subscribe to specific messages relayed by other peers using ",(0,n.jsx)(s.a,{href:"/messaging/concepts/content-topics",children:"content topics"}),". It is designed to be a lightweight alternative for accessing the ",(0,n.jsx)(s.code,{children:"Relay"})," network, particularly tailored for devices with limited bandwidth."]}),"\n",(0,n.jsx)(s.admonition,{type:"info",children:(0,n.jsxs)(s.p,{children:[(0,n.jsx)(s.code,{children:"Filter"})," protocol helps optimise bandwidth usage, but it has fewer privacy guarantees as it must disclose the ",(0,n.jsx)(s.a,{href:"https://docs.logos.co/get-started/glossary#content-topic",children:"content topic"})," to its peers to retrieve messages."]})}),"\n",(0,n.jsx)(s.h2,{id:"store",children:"Store"}),"\n",(0,n.jsxs)(s.p,{children:[(0,n.jsxs)(s.a,{href:"https://lip.logos.co/messaging/core/draft/13/store.html",children:[(0,n.jsx)(s.code,{children:"Store"})," protocol"]})," is responsible for storing messages relayed in the network, making it possible to query and retrieve them later. This functionality benefits offline peers by enabling them to retrieve missed messages upon reconnection."]}),"\n",(0,n.jsx)(s.admonition,{type:"info",children:(0,n.jsxs)(s.p,{children:["Using ",(0,n.jsx)(s.code,{children:"Relay"})," and ",(0,n.jsx)(s.code,{children:"Filter"})," protocols is recommended when a node is online, as ",(0,n.jsx)(s.code,{children:"Store"})," does not guarantee data availability. The ",(0,n.jsx)(s.code,{children:"Store"})," protocol is suitable for retrieving messages when connecting to the network, like when a DApp starts."]})}),"\n",(0,n.jsx)(s.h2,{id:"light-push",children:"Light Push"}),"\n",(0,n.jsxs)(s.p,{children:[(0,n.jsx)(s.a,{href:"https://lip.logos.co/messaging/core/draft/19/lightpush.html",children:(0,n.jsx)(s.code,{children:"Light Push"})})," is a ",(0,n.jsx)(s.a,{href:"https://docs.waku.org/learn/concepts/network-domains#requestresponse-domain",children:"Request/Response"})," protocol for nodes with limited bandwidth and short connection windows. It allows a client to receive an acknowledgement when sending messages, indicating that at least one peer has received them. Subsequently, the remote peer forwards these messages to the ",(0,n.jsx)(s.code,{children:"Relay"})," network."]}),"\n",(0,n.jsx)(s.admonition,{type:"info",children:(0,n.jsxs)(s.p,{children:["While the ",(0,n.jsx)(s.code,{children:"Light Push"})," protocol acknowledges the receipt by the remote peer, it does not guarantee network-wide propagation."]})}),"\n",(0,n.jsx)(s.h2,{id:"waku-message",children:"Waku message"}),"\n",(0,n.jsxs)(s.p,{children:[(0,n.jsx)(s.a,{href:"https://lip.logos.co/messaging/core/stable/14/message.html",children:(0,n.jsx)(s.code,{children:"Waku message"})})," specifies the structure and format of messages. It includes the following attributes:"]}),"\n",(0,n.jsxs)(s.ul,{children:["\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.code,{children:"content_topic"})," attribute for ",(0,n.jsx)(s.a,{href:"/messaging/concepts/content-topics",children:"content-based filtering"}),"."]}),"\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.code,{children:"payload"})," attribute containing the message data payload to be sent."]}),"\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.code,{children:"meta"})," attribute for conveying additional details to various protocols for application-specific processing."]}),"\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.code,{children:"timestamp"})," attribute signifying the time at which the message was generated by its sender."]}),"\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.code,{children:"ephemeral"})," attribute specifying whether the network should not store the message."]}),"\n"]})]})}function h(e={}){let{wrapper:s}={...(0,i.R)(),...e.components};return s?(0,n.jsx)(s,{...e,children:(0,n.jsx)(d,{...e})}):d(e)}},8453(e,s,t){t.d(s,{R:()=>r,x:()=>l});var o=t(6540);let n={},i=o.createContext(n);function r(e){let s=o.useContext(i);return o.useMemo(function(){return"function"==typeof e?e(s):{...s,...e}},[s,e])}function l(e){let s;return s=e.disableParentContext?"function"==typeof e.components?e.components(n):e.components||n:r(e.components),o.createElement(i.Provider,{value:s},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/2d108750.acbca250.js b/assets/js/2d108750.acbca250.js new file mode 100644 index 00000000..992cfb06 --- /dev/null +++ b/assets/js/2d108750.acbca250.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunklogos_docs=self.webpackChunklogos_docs||[]).push([["8327"],{8611(e,n,o){o.r(n),o.d(n,{metadata:()=>t,default:()=>h,frontMatter:()=>a,contentTitle:()=>l,toc:()=>r,assets:()=>c});var t=JSON.parse('{"id":"blockchain/node-app/build-and-run-logos-blockchain-node-app-ui","title":"Build and run the Logos Blockchain UI app","description":"Run a node that participates in consensus via a standalone application.","source":"@site/docs/blockchain/node-app/build-and-run-logos-blockchain-node-app-ui.md","sourceDirName":"blockchain/node-app","slug":"/blockchain/node-app/build-and-run-logos-blockchain-node-app-ui","permalink":"/blockchain/node-app/build-and-run-logos-blockchain-node-app-ui","draft":false,"unlisted":false,"editUrl":"https://github.com/logos-co/logos-docs/tree/main/docs/blockchain/node-app/build-and-run-logos-blockchain-node-app-ui.md","tags":[],"version":"current","sidebarPosition":1,"frontMatter":{"title":"Build and run the Logos Blockchain UI app","doc_type":"procedure","product":"blockchain","topics":"blockchain","steps_layout":"sectioned","authors":"davidrusu, cheny0","owner":"logos","doc_version":1,"slug":"build-and-run-logos-blockchain-node-app-ui","sidebar_position":1},"sidebar":"blockchainSidebar","previous":{"title":"Run a Logos Blockchain node on the public testnet from the CLI","permalink":"/blockchain/get-started/run-a-logos-blockchain-node-from-cli"},"next":{"title":"Move assets using the Logos Blockchain UI app","permalink":"/blockchain/node-app/move-assets-using-logos-blockchain-app"}}'),i=o(4848),s=o(8453);let a={title:"Build and run the Logos Blockchain UI app",doc_type:"procedure",product:"blockchain",topics:"blockchain",steps_layout:"sectioned",authors:"davidrusu, cheny0",owner:"logos",doc_version:1,slug:"build-and-run-logos-blockchain-node-app-ui",sidebar_position:1},l="Build and run the Logos Blockchain UI app",c={},r=[{value:"Run a node that participates in consensus via a standalone application.",id:"run-a-node-that-participates-in-consensus-via-a-standalone-application",level:4},{value:"What to expect",id:"what-to-expect",level:2},{value:"Step 1: Run the Logos Blockchain app",id:"step-1-run-the-logos-blockchain-app",level:2},{value:"Step 2: Generate and load a node config",id:"step-2-generate-and-load-a-node-config",level:2},{value:"Step 3: Request testnet funds from the faucet",id:"step-3-request-testnet-funds-from-the-faucet",level:2},{value:"Step 4: Verify the node is healthy",id:"step-4-verify-the-node-is-healthy",level:2},{value:"Troubleshooting starting a node",id:"troubleshooting-starting-a-node",level:2},{value:"The wallet balance does not update after a faucet request",id:"the-wallet-balance-does-not-update-after-a-faucet-request",level:3},{value:"<code>nix build</code> is taking a long time to build",id:"nix-build-is-taking-a-long-time-to-build",level:3}];function d(e){let n={a:"a",admonition:"admonition",code:"code",h1:"h1",h2:"h2",h3:"h3",h4:"h4",header:"header",img:"img",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,s.R)(),...e.components};return(0,i.jsxs)(i.Fragment,{children:[(0,i.jsx)(n.header,{children:(0,i.jsx)(n.h1,{id:"build-and-run-the-logos-blockchain-ui-app",children:"Build and run the Logos Blockchain UI app"})}),"\n",(0,i.jsx)(n.h4,{id:"run-a-node-that-participates-in-consensus-via-a-standalone-application",children:"Run a node that participates in consensus via a standalone application."}),"\n",(0,i.jsxs)(n.p,{children:["The ",(0,i.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#logos-blockchain",children:"Logos Blockchain"})," is the blockchain ",(0,i.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#module",children:"module"})," of the Logos technology stack, providing a privacy-preserving and censorship-resistant framework for decentralised network states. You can run a Logos Blockchain node ",(0,i.jsx)(n.a,{href:"/blockchain/get-started/run-a-logos-blockchain-node-from-cli",children:"using the CLI"})," or a standalone application."]}),"\n",(0,i.jsx)(n.admonition,{type:"info",children:(0,i.jsx)(n.p,{children:"For testnet v0.1, you must manually edit the config file and restart the node to add new keys."})}),"\n",(0,i.jsx)(n.p,{children:"Before you start, make sure you have the following:"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsx)(n.p,{children:"A supported OS: Linux x86_64 or macOS"}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.strong,{children:"Nix"})," with flakes enabled. Install from ",(0,i.jsx)(n.a,{href:"https://nixos.org/download.html",children:"nixos.org"}),", then enable flakes:"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:"mkdir -p ~/.config/nix\necho 'experimental-features = nix-command flakes' >> ~/.config/nix/nix.conf\n"})}),"\n"]}),"\n"]}),"\n",(0,i.jsx)(n.h2,{id:"what-to-expect",children:"What to expect"}),"\n",(0,i.jsx)(n.p,{children:"By the end of this tutorial:"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsx)(n.li,{children:"You will have a Logos Blockchain node running and connected to peers."}),"\n",(0,i.jsx)(n.li,{children:"Your wallet will hold testnet funds requested from the faucet."}),"\n",(0,i.jsx)(n.li,{children:"Your node will be eligible to participate in the consensus lottery after the UTXO ages for approximately two hours."}),"\n"]}),"\n",(0,i.jsx)(n.h2,{id:"step-1-run-the-logos-blockchain-app",children:"Step 1: Run the Logos Blockchain app"}),"\n",(0,i.jsxs)(n.ol,{children:["\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsx)(n.p,{children:"Clone the repository:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-sh",children:"git clone https://github.com/logos-blockchain/logos-blockchain-ui.git\ncd logos-blockchain-ui\n"})}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsx)(n.p,{children:"Build and run the standalone app:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-sh",children:"nix run\n"})}),"\n",(0,i.jsx)(n.admonition,{type:"info",children:(0,i.jsx)(n.p,{children:"On a cold Nix cache, the first run compiles the blockchain UI from source (Qt/C++ and Rust dependencies). This can take 20\u201360 minutes. Subsequent runs are instant from cache."})}),"\n"]}),"\n"]}),"\n",(0,i.jsx)(n.h2,{id:"step-2-generate-and-load-a-node-config",children:"Step 2: Generate and load a node config"}),"\n",(0,i.jsxs)(n.ol,{children:["\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsxs)(n.p,{children:["In the app, click ",(0,i.jsx)(n.strong,{children:"Generate Config"}),"."]}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.img,{alt:"Initial screen",src:o(686).A+"",width:"2042",height:"1586"})}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsxs)(n.p,{children:["In the ",(0,i.jsx)(n.a,{href:"https://github.com/logos-blockchain/logos-blockchain/releases",children:"Logos Blockchain release notes"}),', search for "testnet bootstrap peers" and copy the initial trusted peer set.']}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsxs)(n.p,{children:["In the app, paste the peer set information in the ",(0,i.jsx)(n.strong,{children:"Initial peers"})," and click ",(0,i.jsx)(n.strong,{children:"Generate Config"})," on the bottom."]}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsxs)(n.p,{children:["Click ",(0,i.jsx)(n.strong,{children:"Load Config"})," and select the generated config file."]}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsxs)(n.p,{children:["Click ",(0,i.jsx)(n.strong,{children:"Start Node"}),". A green indicator shows the node is running, and the wallet appears with a balance of ",(0,i.jsx)(n.code,{children:"0"}),"."]}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.img,{alt:"Running node",src:o(2572).A+"",width:"2012",height:"1546"})}),"\n"]}),"\n"]}),"\n",(0,i.jsx)(n.h2,{id:"step-3-request-testnet-funds-from-the-faucet",children:"Step 3: Request testnet funds from the faucet"}),"\n",(0,i.jsxs)(n.ol,{children:["\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsx)(n.p,{children:"From the wallet section of the UI, copy one of your keys."}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsxs)(n.p,{children:["Go to the ",(0,i.jsx)(n.a,{href:"https://testnet.blockchain.logos.co/web/faucet/",children:"testnet faucet"}),", paste your key, and click ",(0,i.jsx)(n.strong,{children:"Request Funds"}),"."]}),"\n",(0,i.jsx)(n.admonition,{type:"info",children:(0,i.jsx)(n.p,{children:"The transaction can take up to a minute to confirm and appear in your wallet."})}),"\n"]}),"\n"]}),"\n",(0,i.jsx)(n.h2,{id:"step-4-verify-the-node-is-healthy",children:"Step 4: Verify the node is healthy"}),"\n",(0,i.jsxs)(n.ol,{children:["\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsx)(n.p,{children:"Check that the blockchain height is increasing:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-sh",children:"curl localhost:8080/cryptarchia/info\n"})}),"\n",(0,i.jsx)(n.p,{children:"Example response:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-json",children:'{"lib":"3d0c...4e6d","tip":"f44d...e2f5","slot":70899,"height":120,"mode":"Bootstrapping"}\n'})}),"\n",(0,i.jsxs)(n.p,{children:["Expect ",(0,i.jsx)(n.code,{children:"height"})," to increase at an average rate of one block every 10 seconds. The timing is probabilistic, so some variance is normal."]}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsx)(n.p,{children:"Check that the node is connected to peers:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-sh",children:"curl localhost:8080/network/info\n"})}),"\n",(0,i.jsx)(n.p,{children:"Example response:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-json",children:'{"listen_addresses":["/ip4/127.0.0.1/udp/3001/quic-v1"],"peer_id":"12D3...fuS2","n_peers":16,"n_connections":19,"n_pending_connections":0}\n'})}),"\n",(0,i.jsxs)(n.p,{children:["Confirm that ",(0,i.jsx)(n.code,{children:"n_peers"})," is greater than ",(0,i.jsx)(n.code,{children:"0"}),"."]}),"\n"]}),"\n"]}),"\n",(0,i.jsx)(n.p,{children:"Once the node is funded and the UTXO has aged for approximately two hours, the node automatically participates in the consensus lottery and starts producing blocks."}),"\n",(0,i.jsx)(n.h2,{id:"troubleshooting-starting-a-node",children:"Troubleshooting starting a node"}),"\n",(0,i.jsx)(n.h3,{id:"the-wallet-balance-does-not-update-after-a-faucet-request",children:"The wallet balance does not update after a faucet request"}),"\n",(0,i.jsx)(n.p,{children:"Only one faucet transaction can be included per block, so transactions may be dropped during periods of high demand. Retry the request from the faucet."}),"\n",(0,i.jsxs)(n.h3,{id:"nix-build-is-taking-a-long-time-to-build",children:[(0,i.jsx)(n.code,{children:"nix build"})," is taking a long time to build"]}),"\n",(0,i.jsx)(n.p,{children:"This is expected behaviour. The build could take over an hour in some cases."})]})}function h(e={}){let{wrapper:n}={...(0,s.R)(),...e.components};return n?(0,i.jsx)(n,{...e,children:(0,i.jsx)(d,{...e})}):d(e)}},686(e,n,o){o.d(n,{A:()=>t});let t=o.p+"assets/images/01-config-setup-d3235cdcb5a0f479ec6ea64a73f4e145.png"},2572(e,n,o){o.d(n,{A:()=>t});let t=o.p+"assets/images/03-node-running-10c46ddae2f6c04619f40ec81b0245a8.png"},8453(e,n,o){o.d(n,{R:()=>a,x:()=>l});var t=o(6540);let i={},s=t.createContext(i);function a(e){let n=t.useContext(s);return t.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function l(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(i):e.components||i:a(e.components),t.createElement(s.Provider,{value:n},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/2d9cc0e4.832d1eab.js b/assets/js/2d9cc0e4.832d1eab.js new file mode 100644 index 00000000..71ad2400 --- /dev/null +++ b/assets/js/2d9cc0e4.832d1eab.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunklogos_docs=self.webpackChunklogos_docs||[]).push([["3684"],{5956(e,n,t){t.r(n),t.d(n,{metadata:()=>s,default:()=>h,frontMatter:()=>i,contentTitle:()=>r,toc:()=>l,assets:()=>c});var s=JSON.parse('{"id":"blockchain/concepts/about-mantle","title":"About Mantle","description":"Understand how Mantle lets nodes and Zones interact with Bedrock.","source":"@site/docs/blockchain/concepts/about-mantle.md","sourceDirName":"blockchain/concepts","slug":"/blockchain/concepts/about-mantle","permalink":"/blockchain/concepts/about-mantle","draft":false,"unlisted":false,"editUrl":"https://github.com/logos-co/logos-docs/tree/main/docs/blockchain/concepts/about-mantle.md","tags":[],"version":"current","sidebarPosition":3,"frontMatter":{"title":"About Mantle","doc_type":"concept","product":"blockchain","topics":"Mantle","authors":"kashepavadan","owner":"logos","doc_version":1,"slug":"about-mantle","sidebar_position":3},"sidebar":"blockchainSidebar","previous":{"title":"About Cryptarchia","permalink":"/blockchain/concepts/about-cryptarchia"},"next":{"title":"About the Blend Network","permalink":"/blockchain/concepts/about-the-blend-network"}}'),o=t(4848),a=t(8453);let i={title:"About Mantle",doc_type:"concept",product:"blockchain",topics:"Mantle",authors:"kashepavadan",owner:"logos",doc_version:1,slug:"about-mantle",sidebar_position:3},r="About Mantle",c={},l=[{value:"Understand how Mantle lets nodes and Zones interact with Bedrock.",id:"understand-how-mantle-lets-nodes-and-zones-interact-with-bedrock",level:4},{value:"The basics",id:"the-basics",level:2},{value:"Operations and transactions",id:"operations-and-transactions",level:2},{value:"Mantle ledger",id:"mantle-ledger",level:2},{value:"Logos channels",id:"logos-channels",level:2},{value:"Immediate ordering",id:"immediate-ordering",level:3},{value:"Decentralised sequencing",id:"decentralised-sequencing",level:3},{value:"Token bridging",id:"token-bridging",level:3},{value:"Cross-Zone messaging",id:"cross-zone-messaging",level:3}];function d(e){let n={a:"a",h1:"h1",h2:"h2",h3:"h3",h4:"h4",header:"header",img:"img",li:"li",p:"p",ul:"ul",...(0,a.R)(),...e.components};return(0,o.jsxs)(o.Fragment,{children:[(0,o.jsx)(n.header,{children:(0,o.jsx)(n.h1,{id:"about-mantle",children:"About Mantle"})}),"\n",(0,o.jsx)(n.h4,{id:"understand-how-mantle-lets-nodes-and-zones-interact-with-bedrock",children:"Understand how Mantle lets nodes and Zones interact with Bedrock."}),"\n",(0,o.jsxs)(n.p,{children:[(0,o.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#mantle",children:"Mantle"})," is a ",(0,o.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#bedrock",children:"Bedrock"})," component that serves as the operating system of Logos. It provides operations that allow nodes to participate in the ",(0,o.jsx)(n.a,{href:"/blockchain/concepts/about-the-blend-network",children:"Blend Network"}),", as well as to enable ",(0,o.jsx)(n.a,{href:"/blockchain/concepts/about-zones",children:"Zones"})," to interact with Bedrock. Mantle is also responsible for handling Logos notes, which are Bedrock-native fungible tokens."]}),"\n",(0,o.jsx)(n.h2,{id:"the-basics",children:"The basics"}),"\n",(0,o.jsxs)(n.ul,{children:["\n",(0,o.jsx)(n.li,{children:"Mantle serves as Bedrock's execution layer, enabling nodes to participate in Bedrock Services and facilitating interactions between Zones and Bedrock."}),"\n",(0,o.jsx)(n.li,{children:"Mantle maintains a limited ledger for fee payment, tracking fungible assets called notes with UTXO-based transactions."}),"\n",(0,o.jsx)(n.li,{children:"Mantle supports channels, which enforce immediate transaction ordering for Zones and facilitate decentralised sequencing and token bridging."}),"\n"]}),"\n",(0,o.jsx)(n.h2,{id:"operations-and-transactions",children:"Operations and transactions"}),"\n",(0,o.jsxs)(n.p,{children:["Mantle operations are the way Logos nodes interact with Bedrock Services, and allow Zones to interact with Bedrock. Operations are submitted to Mantle via transactions, with each transaction potentially including several operations. The cost of executing operations is paid for by gas fees, denoted in ",(0,o.jsx)(n.a,{href:"/blockchain/concepts/about-mantle#mantle-ledger",children:"notes"}),". All Logos nodes re-execute Mantle operations independently to ensure that the ledger state is updated correctly."]}),"\n",(0,o.jsxs)(n.p,{children:["Logos nodes use Mantle operations to indicate their participation in the ",(0,o.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#blend-network",children:"Blend Network"}),", with Mantle providing a locking mechanism used to incentivise correct behaviour by participants. The operations supported by Mantle include transferring notes, staking notes for participation in the Blend Network, paying rewards to compensate participants, and unstaking notes."]}),"\n",(0,o.jsxs)(n.p,{children:[(0,o.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#inscription",children:"Inscription"})," operations can be used to write arbitrary data to the ",(0,o.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#logos-blockchain",children:"Logos Blockchain"}),", enabling a variety of ways to leverage the blockchain as a permanent, decentralised record. Crucially, Mantle can be used as a censorship resistant ",(0,o.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#message",children:"message"})," delivery mechanism for Zones, allowing ",(0,o.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#zone",children:"Zone"})," sequencers to send information to each other and engage in coordinated action. This form of message passing facilitates cross-Zone interaction including arranging atomic transactions and state updates."]}),"\n",(0,o.jsx)(n.h2,{id:"mantle-ledger",children:"Mantle ledger"}),"\n",(0,o.jsxs)(n.p,{children:["Mantle maintains a restricted ledger that keeps track of fungible assets known as notes, which are bound to their owners. Notes are primarily used to pay for Mantle operations, but can also be bridged to equivalent Zone tokens. Mantle notes are stored by Logos nodes in a dictionary mapping notes to their unique ",(0,o.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#note",children:"note"})," identifiers."]}),"\n",(0,o.jsx)(n.p,{children:"Note transfers are effected by using transfer operations, which are based on the UTXO model. To transfer value, a sender spends their note and creates an equivalent new note belonging to the recipient. A spent note can never be spent again. These notes each have their own secret key for spending, and a corresponding public key for receiving new notes."}),"\n",(0,o.jsx)(n.h2,{id:"logos-channels",children:"Logos channels"}),"\n",(0,o.jsx)(n.p,{children:"Logos channels, or channels for short, are lightweight virtual chains overlaid on top of the Logos Blockchain. The purpose of channels is to immediately enforce the correct ordering of transactions from Zones. These channels are implemented as permissioned, ordered logs of messages signed by a sequencer. These messages usually take the form of state updates from a particular Zone. Channels also provide several key features to Zones, which are described below."}),"\n",(0,o.jsx)(n.h3,{id:"immediate-ordering",children:"Immediate ordering"}),"\n",(0,o.jsx)(n.p,{children:"Logos channels ensure that their transactions will eventually be included on-chain in the correct order, regardless of how the Logos Blockchain may fork or reorganise. This allows new transactions that depend on earlier ones to be submitted immediately, without waiting for finality. An example with two channels is shown below:"}),"\n",(0,o.jsx)(n.p,{children:(0,o.jsx)(n.img,{alt:"A diagram illustrating how two channels are included in the Logos Blockchain.",src:t(6860).A+"",width:"2174",height:"1338"})}),"\n",(0,o.jsx)(n.p,{children:"It is important to note that Logos channels can only be relied on if the sequencer is trusted to act honestly. In the absence of this assumption, users must wait for the true blockchain finality."}),"\n",(0,o.jsx)(n.h3,{id:"decentralised-sequencing",children:"Decentralised sequencing"}),"\n",(0,o.jsxs)(n.p,{children:["A ",(0,o.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#channel",children:"channel"}),' may have one or several sequencers; in the latter case, authorised sequencers may take turns publishing messages according to a round-robin schedule, with unresponsive sequencers losing their turn via an automated timeout. Alternatively, sequencers can compete to update the Zone state via a "first write wins" model.']}),"\n",(0,o.jsx)(n.p,{children:"A given threshold of sequencers may also modify the sequencer list or change other channel properties by signing a message together, allowing an honest majority to remove a malicious sequencer. This form of decentralised Zone sequencing distributes the potential for MEV extraction across a set of parties rather than concentrating it as in the single-sequencer rollup design."}),"\n",(0,o.jsx)(n.h3,{id:"token-bridging",children:"Token bridging"}),"\n",(0,o.jsx)(n.p,{children:"Logos channels also enable token bridging and inter-Zone token transfers for Zones via the use of token balances. A channel\u2019s balance represents the total token value bridged to that Zone, which increases when an equivalent-value note is destroyed on Mantle as part of a deposit operation. Conversely, a new Mantle note is created when a Zone token balance is decreased as a result of a withdrawal operation. Logos channel balances can also be used to effect atomic token transfers between Zones."}),"\n",(0,o.jsx)(n.h3,{id:"cross-zone-messaging",children:"Cross-Zone messaging"}),"\n",(0,o.jsx)(n.p,{children:"Since channel messages can include arbitrary data and are publicly visible, they can also be used for asynchronous or synchronous messaging. Used as a messaging platform, Logos channels can be used to coordinate action between sequencers of a single channel, for example to agree to sign a message modifying the sequencer list. They can also be used to arrange action across several Zones, such as atomic token transfers or other coordinated state transitions."})]})}function h(e={}){let{wrapper:n}={...(0,a.R)(),...e.components};return n?(0,o.jsx)(n,{...e,children:(0,o.jsx)(d,{...e})}):d(e)}},6860(e,n,t){t.d(n,{A:()=>s});let s=t.p+"assets/images/two-channels-ef7a3f4432344168849aef090377f87e.png"},8453(e,n,t){t.d(n,{R:()=>i,x:()=>r});var s=t(6540);let o={},a=s.createContext(o);function i(e){let n=s.useContext(a);return s.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function r(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(o):e.components||o:i(e.components),s.createElement(a.Provider,{value:n},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/33fcb833.2e59f648.js b/assets/js/33fcb833.2e59f648.js new file mode 100644 index 00000000..7cd53e07 --- /dev/null +++ b/assets/js/33fcb833.2e59f648.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunklogos_docs=self.webpackChunklogos_docs||[]).push([["3029"],{2174(e,o,t){t.r(o),t.d(o,{metadata:()=>r,default:()=>d,frontMatter:()=>a,contentTitle:()=>n,toc:()=>h,assets:()=>c});var r=JSON.parse('{"id":"blockchain/concepts/about-cryptarchia","title":"About Cryptarchia","description":"Understand how Cryptarchia reaches consensus while keeping block proposers private.","source":"@site/docs/blockchain/concepts/about-cryptarchia.md","sourceDirName":"blockchain/concepts","slug":"/blockchain/concepts/about-cryptarchia","permalink":"/blockchain/concepts/about-cryptarchia","draft":false,"unlisted":false,"editUrl":"https://github.com/logos-co/logos-docs/tree/main/docs/blockchain/concepts/about-cryptarchia.md","tags":[],"version":"current","sidebarPosition":2,"frontMatter":{"title":"About Cryptarchia","doc_type":"concept","product":"blockchain","topics":"Cryptarchia","authors":"kashepavadan","owner":"logos","doc_version":1,"slug":"about-cryptarchia","sidebar_position":2},"sidebar":"blockchainSidebar","previous":{"title":"About Bedrock","permalink":"/blockchain/concepts/about-bedrock"},"next":{"title":"About Mantle","permalink":"/blockchain/concepts/about-mantle"}}'),s=t(4848),i=t(8453);let a={title:"About Cryptarchia",doc_type:"concept",product:"blockchain",topics:"Cryptarchia",authors:"kashepavadan",owner:"logos",doc_version:1,slug:"about-cryptarchia",sidebar_position:2},n="About Cryptarchia",c={},h=[{value:"Understand how Cryptarchia reaches consensus while keeping block proposers private.",id:"understand-how-cryptarchia-reaches-consensus-while-keeping-block-proposers-private",level:4},{value:"The basics",id:"the-basics",level:2},{value:"Properties of Cryptarchia",id:"properties-of-cryptarchia",level:2},{value:"Resilience via liveness",id:"resilience-via-liveness",level:3},{value:"Proposer privacy",id:"proposer-privacy",level:3},{value:"Low barrier to entry",id:"low-barrier-to-entry",level:3},{value:"How Cryptarchia works",id:"how-cryptarchia-works",level:2},{value:"Time units",id:"time-units",level:3},{value:"Fork choice rule",id:"fork-choice-rule",level:3},{value:"Leadership election",id:"leadership-election",level:3}];function l(e){let o={a:"a",h1:"h1",h2:"h2",h3:"h3",h4:"h4",header:"header",img:"img",li:"li",p:"p",ul:"ul",...(0,i.R)(),...e.components};return(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(o.header,{children:(0,s.jsx)(o.h1,{id:"about-cryptarchia",children:"About Cryptarchia"})}),"\n",(0,s.jsx)(o.h4,{id:"understand-how-cryptarchia-reaches-consensus-while-keeping-block-proposers-private",children:"Understand how Cryptarchia reaches consensus while keeping block proposers private."}),"\n",(0,s.jsxs)(o.p,{children:[(0,s.jsx)(o.a,{href:"https://docs.logos.co/get-started/glossary#cryptarchia",children:"Cryptarchia"}),", the ",(0,s.jsx)(o.a,{href:"https://docs.logos.co/get-started/glossary#logos-blockchain",children:"Logos Blockchain"}),"'s consensus protocol, ensures that the entire Logos network reaches an agreement on the correct state of the blockchain. Cryptarchia uses a ",(0,s.jsx)(o.a,{href:"https://docs.logos.co/get-started/glossary#private-proof-of-stake",children:"Private Proof of Stake"})," (",(0,s.jsx)(o.a,{href:"https://docs.logos.co/get-started/glossary#ppos",children:"PPoS"}),") consensus mechanism, ensuring that nodes cannot be linked to blocks they propose. PPoS also ensures that the proposer's relative stake cannot be deduced based on its activity. This separation reinforces the neutrality of the network, since nodes cannot be linked to any specific activity on the network. At the same time, Cryptarchia has no stake barrier for participating in consensus, fostering further decentralisation."]}),"\n",(0,s.jsx)(o.h2,{id:"the-basics",children:"The basics"}),"\n",(0,s.jsxs)(o.ul,{children:["\n",(0,s.jsx)(o.li,{children:"Cryptarchia is a Private Proof of Stake (PPoS) consensus protocol, so nodes cannot be linked to the blocks they propose and their relative stake cannot be deduced from activity."}),"\n",(0,s.jsx)(o.li,{children:"Cryptarchia maintains block production during failures, allowing the network to keep operating even if competing forks emerge temporarily."}),"\n",(0,s.jsx)(o.li,{children:"Cryptarchia has low entry barriers, so validator nodes can run on a basic laptop with no minimum stake requirement."}),"\n"]}),"\n",(0,s.jsx)(o.h2,{id:"properties-of-cryptarchia",children:"Properties of Cryptarchia"}),"\n",(0,s.jsx)(o.h3,{id:"resilience-via-liveness",children:"Resilience via liveness"}),"\n",(0,s.jsxs)(o.p,{children:["In designing a resilient consensus protocol to use for the Logos Blockchain, it was necessary to decide whether to prioritise ",(0,s.jsx)(o.a,{href:"https://eprint.iacr.org/2016/889.pdf",children:"safety or liveness"})," in the event of a catastrophic failure. If safety is deemed a priority, the chain can never fork in order to ensure that the blockchain state is always agreed-upon, even if activity has to halt while the network recovers. Protocols that provide such safety guarantees generally rely on quorum-based consensus, which requires a permissioned set of participants with extensive communication and a low fault tolerance. These requirements introduce high barriers to entry that are antithetical to the Logos vision."]}),"\n",(0,s.jsx)(o.p,{children:"Prioritising liveness means that block production will continue during a failure, but competing forks will be created before the network settles on one honest chain. These protocols involve participants systematically making local choices about which fork to follow, with no need for a permissioned network or for extensive communication. Accordingly, Cryptarchia was designed to prioritise liveness during a failure."}),"\n",(0,s.jsx)(o.h3,{id:"proposer-privacy",children:"Proposer privacy"}),"\n",(0,s.jsx)(o.p,{children:"Cryptarchia provides PPoS consensus that hides the identity of a block proposer both before and after a proposal. Hiding a proposer\u2019s identity before the proposal is accomplished by using a secret leadership election. This mechanism selects consensus leaders without revealing the leader schedule ahead of time."}),"\n",(0,s.jsxs)(o.p,{children:["For full proposer privacy, a secret leadership election is not enough: once a leader proposes a block, it is ",(0,s.jsx)(o.a,{href:"https://eprint.iacr.org/2021/409.pdf",children:"trivial to link them"})," to their proposed block without additional privacy measures in place. Cryptarchia is designed to work together with the ",(0,s.jsx)(o.a,{href:"/blockchain/concepts/about-the-blend-network",children:"Blend Network"}),", which obfuscates the link between a proposer and their block. This property creates a much more powerful layer of privacy, resilience and neutrality."]}),"\n",(0,s.jsx)(o.h3,{id:"low-barrier-to-entry",children:"Low barrier to entry"}),"\n",(0,s.jsxs)(o.p,{children:["In order to achieve maximum decentralisation, Cryptarchia was designed to have low barriers to entry to encourage a greater circle of contributors. Participating as a Cryptarchia validator node is as simple as having the ",(0,s.jsx)(o.a,{href:"https://docs.logos.co/get-started/glossary#logos-node",children:"Logos node"})," application run in the background on a laptop, with a \u201Cset it and forget it\u201D approach to maintenance. This design makes it easy to contribute to the security and continued operation of the Logos Blockchain."]}),"\n",(0,s.jsx)(o.h2,{id:"how-cryptarchia-works",children:"How Cryptarchia works"}),"\n",(0,s.jsx)(o.h3,{id:"time-units",children:"Time units"}),"\n",(0,s.jsxs)(o.p,{children:["Following the ",(0,s.jsx)(o.a,{href:"https://www.drwx.org/papers/crypsinous.pdf",children:"Ouroboros"})," model, Cryptarchia divides time into basic units called slots that are grouped into larger units called epochs. Each ",(0,s.jsx)(o.a,{href:"https://docs.logos.co/get-started/glossary#slot",children:"slot"})," allows for the addition of at most one block to a given chain, while each new ",(0,s.jsx)(o.a,{href:"https://docs.logos.co/get-started/glossary#epoch",children:"epoch"})," refreshes the randomness and eligibility set used for the leadership election. Every Cryptarchia slot is 1 second long, and an epoch is about 7.5 days long. These time units are illustrated in the diagram below."]}),"\n",(0,s.jsx)(o.p,{children:(0,s.jsx)(o.img,{alt:"The time units used by the Logos Blockchain.",src:t(3415).A+"",width:"2000",height:"615"})}),"\n",(0,s.jsx)(o.h3,{id:"fork-choice-rule",children:"Fork choice rule"}),"\n",(0,s.jsx)(o.p,{children:"As mentioned earlier, Cryptarchia\u2019s preference for liveness produces competing forks fairly often - even under honest behaviour. The way Logos nodes decide on which fork represents the correct, or canonical, blockchain depends on how long that node has been offline. The node will compare parallel chains it sees on the network to its own preferred chain, switching to the observed chain if it is selected by the fork choice rule."}),"\n",(0,s.jsx)(o.p,{children:"When a Logos node is connected to the broader network and sees new honest blocks relatively quickly, it will use the online fork choice rule to select the honest chain. Under this rule, the node will select the chain with the most blocks - the \u201Clongest chain\u201D - as long as it diverges from the node\u2019s chain less than $k$ blocks ago. This $k$ parameter, known as the security parameter, describes how many blocks deep a transaction needs to be before it is considered immutable."}),"\n",(0,s.jsxs)(o.p,{children:["When a Logos node is joining the network for the first time, or after a prolonged absence, it must use the boostrapping fork choice rule. Like the online rule, the ",(0,s.jsx)(o.a,{href:"https://docs.logos.co/get-started/glossary#bootstrapping",children:"bootstrapping"})," rule selects the longest chain when competing chains diverged from the node\u2019s preferred chain less than $k$ blocks ago. However, forks diverging more than $k$ blocks ago are not dismissed. On the contrary, such a chain is selected if it has the most blocks (i.e. is the most dense) in the period immediately after the fork. The contrast between these two rules can be seen in the diagram below."]}),"\n",(0,s.jsx)(o.p,{children:(0,s.jsx)(o.img,{alt:"The Cryptarchia bootstrapping fork choice rule.",src:t(1340).A+"",width:"2000",height:"1426"})}),"\n",(0,s.jsx)(o.p,{children:"The bootstrapping rule ensures that honest parties can join or rejoin the protocol after prolonged absences without having to trust another node to select their canonical chain. During normal operations, such a costly fork choice rule is not needed, allowing active nodes to use the online rule without compromising on security."}),"\n",(0,s.jsx)(o.h3,{id:"leadership-election",children:"Leadership election"}),"\n",(0,s.jsxs)(o.p,{children:["Cryptarchia uses Logos notes (fungible assets) to select block proposers. Each ",(0,s.jsx)(o.a,{href:"https://docs.logos.co/get-started/glossary#note",children:"note"})," that has existed since the beginning of the previous epoch is eligible to win the Cryptarchia slot lottery. The owner of a winning note can then propose a block. Notes held by anyone can be used for consensus, with no minimum value requirement."]}),"\n",(0,s.jsx)(o.p,{children:"Each slot presents an opportunity for a block proposer to add a block to the chain, so long as they win the leadership election for that slot. The leadership election is run locally by each individual eligible note, without any public leadership schedule. Whether a particular note wins the leadership election for a given slot is determined by comparing a random \u201Cticket\u201D value to a threshold derived from the note\u2019s relative stake. Due to the privacy properties of Cryptarchia, this relative stake relies on an estimate of the total participating stake derived from the block production rate."}),"\n",(0,s.jsxs)(o.p,{children:["If the ticket, generated by hashing the lottery randomness together with the note data, is less than the threshold value, that note is eligible to serve as a leader for that slot. The owner of a note that won an election can submit a block proposal, which will contain a zero-knowledge ",(0,s.jsx)(o.a,{href:"https://docs.logos.co/get-started/glossary#proof-of-leadership",children:"Proof of Leadership"})," (",(0,s.jsx)(o.a,{href:"https://docs.logos.co/get-started/glossary#pol",children:"PoL"}),"). Most slots will have no leader in order to allow parties to synchronise, while some will have one or even several leaders. A ",(0,s.jsx)(o.a,{href:"https://www.drwx.org/papers/crypsinous.pdf",children:"key deletion protocol"})," used in the maintenance of the note\u2019s secret key ensures that an adaptive adversary who corrupts an honest participant will not be able to generate proofs of leadership for past slots."]})]})}function d(e={}){let{wrapper:o}={...(0,i.R)(),...e.components};return o?(0,s.jsx)(o,{...e,children:(0,s.jsx)(l,{...e})}):l(e)}},1340(e,o,t){t.d(o,{A:()=>r});let r=t.p+"assets/images/fork-choice-c6b7cc5ee6b45dafeaf8d66efd2d935e.png"},3415(e,o,t){t.d(o,{A:()=>r});let r=t.p+"assets/images/time-units-2b8ac53c78fa8b6b06fc1892dfb88ea2.png"},8453(e,o,t){t.d(o,{R:()=>a,x:()=>n});var r=t(6540);let s={},i=r.createContext(s);function a(e){let o=r.useContext(i);return r.useMemo(function(){return"function"==typeof e?e(o):{...o,...e}},[o,e])}function n(e){let o;return o=e.disableParentContext?"function"==typeof e.components?e.components(s):e.components||s:a(e.components),r.createElement(i.Provider,{value:o},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/3693.544af2eb.js b/assets/js/3693.544af2eb.js new file mode 100644 index 00000000..991ad6b8 --- /dev/null +++ b/assets/js/3693.544af2eb.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunklogos_docs=self.webpackChunklogos_docs||[]).push([["3693"],{3436(e,t,n){n.d(t,{default:()=>o});function o(e,{target:t=document.body}={}){if("string"!=typeof e)throw TypeError(`Expected parameter \`text\` to be a \`string\`, got \`${typeof e}\`.`);let n=document.createElement("textarea"),l=document.activeElement;n.value=e,n.setAttribute("readonly",""),n.style.all="unset",n.style.contain="strict",n.style.position="absolute",n.style.left="-9999px",n.style.width="2em",n.style.height="2em",n.style.padding="0",n.style.border="none",n.style.outline="none",n.style.boxShadow="none",n.style.background="transparent",n.style.fontSize="12pt",n.style.whiteSpace="pre";let s=document.getSelection(),a=s.rangeCount>0&&s.getRangeAt(0);t.append(n),n.select(),n.selectionStart=0,n.selectionEnd=e.length;let c=!1;try{c=document.execCommand("copy")}catch{}return n.remove(),a&&(s.removeAllRanges(),s.addRange(a)),l&&l.focus(),c}}}]); \ No newline at end of file diff --git a/assets/js/3e6f6f2a.0be811ac.js b/assets/js/3e6f6f2a.0be811ac.js new file mode 100644 index 00000000..1834021e --- /dev/null +++ b/assets/js/3e6f6f2a.0be811ac.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunklogos_docs=self.webpackChunklogos_docs||[]).push([["2997"],{9026(e,o,s){s.r(o),s.d(o,{metadata:()=>n,default:()=>p,frontMatter:()=>i,contentTitle:()=>l,toc:()=>d,assets:()=>a});var n=JSON.parse('{"id":"messaging/concepts/learn-the-differences-between-logos-delivery-and-libp2p","title":"Learn the differences between Logos Delivery and libp2p","description":"Understand what Logos Delivery adds on top of libp2p and where the two differ.","source":"@site/docs/messaging/concepts/learn-the-differences-between-logos-delivery-and-libp2p.md","sourceDirName":"messaging/concepts","slug":"/messaging/concepts/logos-delivery-and-libp2p","permalink":"/messaging/concepts/logos-delivery-and-libp2p","draft":false,"unlisted":false,"editUrl":"https://github.com/logos-co/logos-docs/tree/main/docs/messaging/concepts/learn-the-differences-between-logos-delivery-and-libp2p.md","tags":[],"version":"current","sidebarPosition":7,"frontMatter":{"title":"Learn the differences between Logos Delivery and libp2p","doc_type":"concept","product":"messaging","topics":"libp2p, architecture","authors":"LordGhostX, fryorcraken","owner":"logos","doc_version":1,"slug":"logos-delivery-and-libp2p","sidebar_position":7},"sidebar":"messagingSidebar","previous":{"title":"Understand Logos Delivery security features","permalink":"/messaging/concepts/security-features"},"next":{"title":"Build a Logos module that uses the Chat module API","permalink":"/messaging/chat-module/build-logos-module-that-uses-chat-module-api"}}'),t=s(4848),r=s(8453);let i={title:"Learn the differences between Logos Delivery and libp2p",doc_type:"concept",product:"messaging",topics:"libp2p, architecture",authors:"LordGhostX, fryorcraken",owner:"logos",doc_version:1,slug:"logos-delivery-and-libp2p",sidebar_position:7},l="Learn the differences between Logos Delivery and libp2p",a={},d=[{value:"Understand what Logos Delivery adds on top of libp2p and where the two differ.",id:"understand-what-logos-delivery-adds-on-top-of-libp2p-and-where-the-two-differ",level:4},{value:"The basics",id:"the-basics",level:2},{value:"Logos Delivery as a service network",id:"logos-delivery-as-a-service-network",level:2},{value:"Logos Delivery as a turnkey solution",id:"logos-delivery-as-a-turnkey-solution",level:2},{value:"Economic spam protection",id:"economic-spam-protection",level:2}];function c(e){let o={a:"a",h1:"h1",h2:"h2",h4:"h4",header:"header",li:"li",p:"p",ul:"ul",...(0,r.R)(),...e.components};return(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)(o.header,{children:(0,t.jsx)(o.h1,{id:"learn-the-differences-between-logos-delivery-and-libp2p",children:"Learn the differences between Logos Delivery and libp2p"})}),"\n",(0,t.jsx)(o.h4,{id:"understand-what-logos-delivery-adds-on-top-of-libp2p-and-where-the-two-differ",children:"Understand what Logos Delivery adds on top of libp2p and where the two differ."}),"\n",(0,t.jsxs)(o.p,{children:["Since ",(0,t.jsx)(o.a,{href:"/get-started/glossary#logos-delivery",children:"Logos Delivery"})," is built on top of libp2p, they share a lot of concepts and terminologies between them. However, there are key differences between them that are worth noting."]}),"\n",(0,t.jsx)(o.h2,{id:"the-basics",children:"The basics"}),"\n",(0,t.jsxs)(o.ul,{children:["\n",(0,t.jsx)(o.li,{children:"Logos Delivery is built on top of libp2p, so the two share many concepts and terms."}),"\n",(0,t.jsxs)(o.li,{children:["Unlike libp2p, Logos Delivery is a ",(0,t.jsx)(o.a,{href:"#logos-delivery-as-a-service-network",children:"service network"})," and a ",(0,t.jsx)(o.a,{href:"#logos-delivery-as-a-turnkey-solution",children:"turnkey solution"}),"."]}),"\n",(0,t.jsxs)(o.li,{children:["Logos Delivery adds ",(0,t.jsx)(o.a,{href:"#economic-spam-protection",children:"economic spam protection"}),", which libp2p does not provide."]}),"\n"]}),"\n",(0,t.jsx)(o.h2,{id:"logos-delivery-as-a-service-network",children:"Logos Delivery as a service network"}),"\n",(0,t.jsxs)(o.p,{children:["Logos Delivery intends to incentivise mechanisms to run nodes, but it is not part of libp2p's scope. Additionally, users or developers do not have to deploy their infrastructure as a prerequisite to use Logos Delivery. It is a service network. However, you are encouraged to ",(0,t.jsx)(o.a,{href:"/messaging/delivery/run-logos-delivery-node",children:"run a node"})," to support and decentralise the network."]}),"\n",(0,t.jsx)(o.h2,{id:"logos-delivery-as-a-turnkey-solution",children:"Logos Delivery as a turnkey solution"}),"\n",(0,t.jsx)(o.p,{children:"Logos Delivery includes various protocols covering the following domains: privacy preservation, censorship resistance, and platform agnosticism, allowing it to run on any platform or environment."}),"\n",(0,t.jsxs)(o.p,{children:["Logos Delivery provides out-of-the-box protocols to enable ",(0,t.jsx)(o.a,{href:"https://docs.logos.co/get-started/glossary#mostly-offline",children:"mostly offline"}),"/resource-limited devices, ",(0,t.jsx)(o.a,{href:"/messaging/concepts/protocols#store",children:"Store"}),"/",(0,t.jsx)(o.a,{href:"/messaging/concepts/protocols#light-push",children:"Light Push"}),"/",(0,t.jsx)(o.a,{href:"/messaging/concepts/protocols#filter",children:"Filter"})," caters to those use cases."]}),"\n",(0,t.jsx)(o.h2,{id:"economic-spam-protection",children:"Economic spam protection"}),"\n",(0,t.jsxs)(o.p,{children:["libp2p does not have strong spam protection guarantees, ",(0,t.jsx)(o.a,{href:"/messaging/concepts/protocols#rln-relay",children:"RLN Relay"})," is a protocol being developed by the Logos Delivery team towards this goal."]})]})}function p(e={}){let{wrapper:o}={...(0,r.R)(),...e.components};return o?(0,t.jsx)(o,{...e,children:(0,t.jsx)(c,{...e})}):c(e)}},8453(e,o,s){s.d(o,{R:()=>i,x:()=>l});var n=s(6540);let t={},r=n.createContext(t);function i(e){let o=n.useContext(r);return n.useMemo(function(){return"function"==typeof e?e(o):{...o,...e}},[o,e])}function l(e){let o;return o=e.disableParentContext?"function"==typeof e.components?e.components(t):e.components||t:i(e.components),n.createElement(r.Provider,{value:o},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/470027ea.0278dbe3.js b/assets/js/470027ea.0278dbe3.js new file mode 100644 index 00000000..7f640ce8 --- /dev/null +++ b/assets/js/470027ea.0278dbe3.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunklogos_docs=self.webpackChunklogos_docs||[]).push([["7348"],{8446(e,n,o){o.r(n),o.d(n,{metadata:()=>s,default:()=>h,frontMatter:()=>c,contentTitle:()=>r,toc:()=>a,assets:()=>l});var s=JSON.parse('{"id":"blockchain/get-started/run-a-logos-blockchain-node-from-cli","title":"Run a Logos Blockchain node on the public testnet from the CLI","description":"Start a node and verify runtime and consensus signals.","source":"@site/docs/blockchain/get-started/run-a-logos-blockchain-node-from-cli.md","sourceDirName":"blockchain/get-started","slug":"/blockchain/get-started/run-a-logos-blockchain-node-from-cli","permalink":"/blockchain/get-started/run-a-logos-blockchain-node-from-cli","draft":false,"unlisted":false,"editUrl":"https://github.com/logos-co/logos-docs/tree/main/docs/blockchain/get-started/run-a-logos-blockchain-node-from-cli.md","tags":[],"version":"current","sidebarPosition":3,"frontMatter":{"title":"Run a Logos Blockchain node on the public testnet from the CLI","doc_type":"procedure","product":"blockchain","topics":"blockchain","steps_layout":"sectioned","authors":"kashepavadan, davidrusu","owner":"logos","doc_version":1,"slug":"run-a-logos-blockchain-node-from-cli","sidebar_position":3},"sidebar":"blockchainSidebar","previous":{"title":"Run a Logos Blockchain node from Basecamp","permalink":"/blockchain/get-started/run-a-logos-blockchain-node-from-basecamp"},"next":{"title":"Build and run the Logos Blockchain UI app","permalink":"/blockchain/node-app/build-and-run-logos-blockchain-node-app-ui"}}'),t=o(4848),i=o(8453);let c={title:"Run a Logos Blockchain node on the public testnet from the CLI",doc_type:"procedure",product:"blockchain",topics:"blockchain",steps_layout:"sectioned",authors:"kashepavadan, davidrusu",owner:"logos",doc_version:1,slug:"run-a-logos-blockchain-node-from-cli",sidebar_position:3},r="Run a Logos Blockchain node on the public testnet from the CLI",l={},a=[{value:"Start a node and verify runtime and consensus signals.",id:"start-a-node-and-verify-runtime-and-consensus-signals",level:4},{value:"What to expect",id:"what-to-expect",level:2},{value:"Step 1: Install Logos core tools",id:"step-1-install-logos-core-tools",level:2},{value:"Step 2: Load the Logos Blockchain module",id:"step-2-load-the-logos-blockchain-module",level:2},{value:"Step 3: Configure and start the node",id:"step-3-configure-and-start-the-node",level:2},{value:"Step 4: Verify that your node is running and connected to peers",id:"step-4-verify-that-your-node-is-running-and-connected-to-peers",level:2},{value:"Step 5: Request tokens from the faucet",id:"step-5-request-tokens-from-the-faucet",level:2},{value:"Troubleshooting the Logos Blockchain node",id:"troubleshooting-the-logos-blockchain-node",level:2},{value:"The testnet explorer shows an error when I click on a transaction?",id:"the-testnet-explorer-shows-an-error-when-i-click-on-a-transaction",level:3},{value:"My wallet balance is not updating after requesting tokens?",id:"my-wallet-balance-is-not-updating-after-requesting-tokens",level:3}];function d(e){let n={a:"a",admonition:"admonition",code:"code",h1:"h1",h2:"h2",h3:"h3",h4:"h4",header:"header",img:"img",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,i.R)(),...e.components};return(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)(n.header,{children:(0,t.jsx)(n.h1,{id:"run-a-logos-blockchain-node-on-the-public-testnet-from-the-cli",children:"Run a Logos Blockchain node on the public testnet from the CLI"})}),"\n",(0,t.jsx)(n.h4,{id:"start-a-node-and-verify-runtime-and-consensus-signals",children:"Start a node and verify runtime and consensus signals."}),"\n",(0,t.jsxs)(n.p,{children:["With this tutorial, you will install the ",(0,t.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#logos-blockchain",children:"Logos Blockchain"})," node, connect to the public testnet, and verify that your node is running. The Logos Blockchain is the blockchain component of the Logos technology stack, providing a privacy-preserving and censorship-resistant framework for decentralised applications. This procedure is for node operators setting up a node for the first time."]}),"\n",(0,t.jsxs)(n.p,{children:["There is currently no dynamic wallet key management. To add new keys you must manually edit ",(0,t.jsx)(n.code,{children:"user_config.yaml"})," and restart the node. If the node is restarted while ",(0,t.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#bootstrapping",children:"bootstrapping"}),", it does not save sync progress and restarts from the beginning."]}),"\n",(0,t.jsx)(n.p,{children:"Before you start, ensure you have:"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:["Linux x86_64, macOS, or a Raspberry Pi 5 with ",(0,t.jsx)(n.a,{href:"https://www.raspberrypi.com/software/",children:"Raspberry Pi OS"})," installed"]}),"\n",(0,t.jsx)(n.li,{children:"glibc version 2.39 or later (Linux only)"}),"\n",(0,t.jsx)(n.li,{children:"At least 64 GB of storage"}),"\n"]}),"\n",(0,t.jsx)(n.h2,{id:"what-to-expect",children:"What to expect"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsx)(n.li,{children:"You can install the node binary, generate a configuration, and join the public testnet."}),"\n",(0,t.jsx)(n.li,{children:"You can verify that your node is syncing and connected to peers using the local API."}),"\n",(0,t.jsx)(n.li,{children:"You can receive test tokens from the faucet and automatically participate in the consensus lottery once your stake matures."}),"\n"]}),"\n",(0,t.jsx)(n.h2,{id:"step-1-install-logos-core-tools",children:"Step 1: Install Logos core tools"}),"\n",(0,t.jsxs)(n.ol,{children:["\n",(0,t.jsxs)(n.li,{children:["\n",(0,t.jsxs)(n.p,{children:["Use the ",(0,t.jsx)(n.code,{children:"install-node-tools.sh"})," helper script to install ",(0,t.jsx)(n.a,{href:"https://github.com/logos-co/logos-logoscore-cli/releases/tag/0.2.0",children:(0,t.jsx)(n.code,{children:"logoscore"})}),", ",(0,t.jsx)(n.a,{href:"https://github.com/logos-co/logos-package-downloader/releases/tag/0.2.0",children:(0,t.jsx)(n.code,{children:"lgpd"})}),", and ",(0,t.jsx)(n.a,{href:"https://github.com/logos-co/logos-package-manager/releases/tag/0.2.0",children:(0,t.jsx)(n.code,{children:"lgpm"})})," into ",(0,t.jsx)(n.code,{children:"./bin"}),":"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-bash",children:'curl -fsSL https://raw.githubusercontent.com/logos-co/logos-docs/main/resources/scripts/install-node-tools.sh | sh\nexport PATH="$PWD/bin:$PATH"\n'})}),"\n"]}),"\n"]}),"\n",(0,t.jsx)(n.h2,{id:"step-2-load-the-logos-blockchain-module",children:"Step 2: Load the Logos Blockchain module"}),"\n",(0,t.jsxs)(n.p,{children:["Download the Logos Blockchain ",(0,t.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#module",children:"module"})," with ",(0,t.jsx)(n.code,{children:"lgpd"}),", then install it with ",(0,t.jsx)(n.code,{children:"lgpm"})," before loading it with ",(0,t.jsx)(n.code,{children:"logoscore"}),"."]}),"\n",(0,t.jsxs)(n.ol,{children:["\n",(0,t.jsxs)(n.li,{children:["\n",(0,t.jsx)(n.p,{children:"Download the module:"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-bash",children:"lgpd download blockchain_module --version 0.2.0 --output ./\n# writes ./blockchain_module-0.2.0.lgx\n"})}),"\n"]}),"\n",(0,t.jsxs)(n.li,{children:["\n",(0,t.jsx)(n.p,{children:"Install the module:"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-bash",children:"lgpm --modules-dir ./modules install --file blockchain_module-0.2.0.lgx\n"})}),"\n"]}),"\n",(0,t.jsxs)(n.li,{children:["\n",(0,t.jsxs)(n.p,{children:["Launch ",(0,t.jsx)(n.code,{children:"logoscore"})," in daemon mode and load the Logos Blockchain module:"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-bash",children:"logoscore -m ./modules -D &\nlogoscore load-module blockchain_module\n"})}),"\n"]}),"\n"]}),"\n",(0,t.jsx)(n.h2,{id:"step-3-configure-and-start-the-node",children:"Step 3: Configure and start the node"}),"\n",(0,t.jsxs)(n.p,{children:["The ",(0,t.jsx)(n.code,{children:"generate_user_config"})," subcommand generates a user configuration that includes per-node settings such as keys, ports, and peer addresses, along with fresh cryptographic keys and an auto-detected public IP."]}),"\n",(0,t.jsx)(n.admonition,{type:"info",children:(0,t.jsxs)(n.p,{children:["Make sure to use the current bootstrap peer addresses in the ",(0,t.jsx)(n.a,{href:"https://github.com/logos-blockchain/logos-blockchain/releases/latest",children:"Logos Blockchain Node release notes"})," for your selected release."]})}),"\n",(0,t.jsxs)(n.ol,{children:["\n",(0,t.jsxs)(n.li,{children:["\n",(0,t.jsxs)(n.p,{children:["Generate your ",(0,t.jsx)(n.code,{children:"user_config.yaml"})," by running ",(0,t.jsx)(n.code,{children:"generate_user_config"})," with the bootstrap peer addresses. For example, for release 0.2.0:"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-sh",children:'logoscore call blockchain_module generate_user_config \'{\n "initial_peers": [\n "/ip4/65.109.51.37/udp/3000/quic-v1/p2p/12D3KooWFrouXfmrR4nsLMtE7wu15DoMJ6VtoUtHinREZCvbWHar",\n "/ip4/65.109.51.37/udp/3001/quic-v1/p2p/12D3KooWJRGau8M1rjT7R5e4YYsgdFhsMX35nRDtMwCDjxQkXAHz",\n "/ip4/65.109.51.37/udp/3002/quic-v1/p2p/12D3KooWQXJavMDTRscjauFSgVAB1VLB6Rzpy2uY5SU9Tk7927tb",\n "/ip4/65.109.51.37/udp/50001/quic-v1/p2p/12D3KooWSQc7CcGtvWDPF1yCbBthFnQjprfCVHmfmNDUrSmqQsU1"\n ]\n}\'\n'})}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:["To change the API port, set ",(0,t.jsx)(n.code,{children:"api.backend.listen_address"})," in ",(0,t.jsx)(n.code,{children:"user_config.yaml"})," before starting. The default is ",(0,t.jsx)(n.code,{children:"8080"}),"."]}),"\n"]}),"\n",(0,t.jsxs)(n.admonition,{type:"info",children:[(0,t.jsxs)(n.p,{children:["To migrate an existing version 0.1.2 blockchain config to version 0.2.0, run the ",(0,t.jsx)(n.code,{children:"migrate_user_config_0_1_2"})," command of the blockchain module and stop the module before restarting it in the next step:"]}),(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-bash",children:"logoscore call blockchain_module migrate_user_config_0_1_2 \\\n user_config.yaml \\\n old_user_config.yaml \\\n my_keystore.yaml\n\nlogoscore call blockchain_module stop\n"})})]}),"\n"]}),"\n",(0,t.jsxs)(n.li,{children:["\n",(0,t.jsx)(n.p,{children:"Start the node:"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-sh",children:'logoscore call blockchain_module start user_config.yaml ""\n'})}),"\n"]}),"\n"]}),"\n",(0,t.jsx)(n.h2,{id:"step-4-verify-that-your-node-is-running-and-connected-to-peers",children:"Step 4: Verify that your node is running and connected to peers"}),"\n",(0,t.jsxs)(n.p,{children:["Wait for your node to finish syncing and reach ",(0,t.jsx)(n.code,{children:"Online"})," mode before requesting tokens. Pipe the ",(0,t.jsx)(n.code,{children:"get_cryptarchia_info"})," command through ",(0,t.jsx)(n.code,{children:"jq ."})," to format JSON output."]}),"\n",(0,t.jsxs)(n.ol,{children:["\n",(0,t.jsxs)(n.li,{children:["\n",(0,t.jsx)(n.p,{children:"Check the consensus state:"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-sh",children:"logoscore call blockchain_module get_cryptarchia_info | jq -r .result.value | jq .\n\n# Alternatively, send a request directly to your node port\ncurl -s http://localhost:8080/cryptarchia/info | jq .\n"})}),"\n",(0,t.jsx)(n.p,{children:"Example response:"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-json",children:'{\n "lib": "3d0c...4e6d",\n "lib_slot": 0,\n "tip": "f44d...e2f5",\n "slot": 70899,\n "height": 120,\n "mode": "Bootstrapping"\n}\n'})}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"mode"})," starts as ",(0,t.jsx)(n.code,{children:"Bootstrapping"})," while syncing and transitions to ",(0,t.jsx)(n.code,{children:"Online"})," once caught up."]}),"\n",(0,t.jsxs)(n.li,{children:["Confirm ",(0,t.jsx)(n.code,{children:"slot"})," and ",(0,t.jsx)(n.code,{children:"height"})," are increasing. ",(0,t.jsx)(n.code,{children:"height"})," counts confirmed blocks; ",(0,t.jsx)(n.code,{children:"slot"})," counts elapsed time intervals, with a new block expected roughly every 10 seconds."]}),"\n"]}),"\n"]}),"\n",(0,t.jsxs)(n.li,{children:["\n",(0,t.jsx)(n.p,{children:"Check peer connectivity:"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-sh",children:"curl -s http://localhost:8080/network/info | jq .\n"})}),"\n",(0,t.jsx)(n.p,{children:"Example response:"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-json",children:'{\n "listen_addresses": ["/ip4/127.0.0.1/udp/3001/quic-v1"],\n "peer_id": "12D3...fuS2",\n "n_peers": 16,\n "n_connections": 19,\n "n_pending_connections": 0\n}\n'})}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:["Confirm ",(0,t.jsx)(n.code,{children:"n_peers"})," is greater than ",(0,t.jsx)(n.code,{children:"0"}),"."]}),"\n"]}),"\n"]}),"\n",(0,t.jsxs)(n.li,{children:["\n",(0,t.jsxs)(n.p,{children:["After 30\u201360 seconds, run the ",(0,t.jsx)(n.code,{children:"get_cryptarchia_info"})," command again and confirm ",(0,t.jsx)(n.code,{children:"slot"})," and ",(0,t.jsx)(n.code,{children:"height"})," have increased."]}),"\n"]}),"\n",(0,t.jsxs)(n.li,{children:["\n",(0,t.jsxs)(n.p,{children:["Wait until ",(0,t.jsx)(n.code,{children:"mode"})," transitions to ",(0,t.jsx)(n.code,{children:"Online"})," before continuing. Bootstrapping should take approximately 1 hour."]}),"\n"]}),"\n"]}),"\n",(0,t.jsx)(n.h2,{id:"step-5-request-tokens-from-the-faucet",children:"Step 5: Request tokens from the faucet"}),"\n",(0,t.jsxs)(n.p,{children:["A faucet distributes free tokens on test networks so you can experiment without financial risk. Navigate to the ",(0,t.jsx)(n.a,{href:"https://testnet.blockchain.logos.co/web/faucet/",children:"public faucet site"})," after your node reaches ",(0,t.jsx)(n.code,{children:"Online"})," mode."]}),"\n",(0,t.jsxs)(n.ol,{children:["\n",(0,t.jsxs)(n.li,{children:["\n",(0,t.jsx)(n.p,{children:"Find the keys associated with your node:"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-sh",children:"grep -A3 known_keys user_config.yaml\n"})}),"\n",(0,t.jsx)(n.p,{children:"Example output:"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{children:"known_keys:\n 57364103d3ff29c35d2073cba0526ef729b8e08490bddfc6b74128b6613fe923: ...\n de3233cec107e6589f83d4f3094caa65c633b5b33601211353779dc01972ca14: ...\nvoucher_master_key_id: de3233cec107e6589f83d4f3094caa65c633b5b33601211353779dc01972ca14\n"})}),"\n"]}),"\n",(0,t.jsxs)(n.li,{children:["\n",(0,t.jsxs)(n.p,{children:["Choose any key from ",(0,t.jsx)(n.code,{children:"known_keys"}),", enter it in ",(0,t.jsx)(n.strong,{children:"Destination Public Key (Hex)"})," on the faucet site, and press ",(0,t.jsx)(n.strong,{children:"Request Funds"}),"."]}),"\n",(0,t.jsx)(n.p,{children:(0,t.jsx)(n.img,{alt:"Image of the faucet UI after requesting funds with a public key",src:o(3526).A+"",width:"857",height:"712"})}),"\n"]}),"\n",(0,t.jsxs)(n.li,{children:["\n",(0,t.jsxs)(n.p,{children:["Wait 1 to 2 minutes, then check your balance. Replace ",(0,t.jsx)(n.code,{children:"<your-chosen-key>"})," with the key you used:"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-sh",children:"curl -s http://localhost:8080/wallet/<your-chosen-key>/balance | jq .\n"})}),"\n",(0,t.jsx)(n.p,{children:"Example response:"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-json",children:'{\n "tip": "5d16d4bd3712dc5869fc624e59774552b4fb0c974a6efa516563b3778bac9258",\n "balance": 1000,\n "address": "57364103d3ff29c35d2073cba0526ef729b8e08490bddfc6b74128b6613fe923"\n}\n'})}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsx)(n.li,{children:"Only one faucet transaction can be included per block. During high demand, your transaction may be dropped; retry the request and wait 1 to 2 minutes before checking again."}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,t.jsxs)(n.admonition,{type:"info",children:[(0,t.jsxs)(n.p,{children:["Your tokens become eligible for consensus after 3.5 hours. Confirm that your node is participating by checking that ",(0,t.jsx)(n.code,{children:"mode"})," remains ",(0,t.jsx)(n.code,{children:"Online"})," and ",(0,t.jsx)(n.code,{children:"height"})," continues to increase."]}),(0,t.jsxs)(n.p,{children:["Block proposal is probabilistic. Your node will not propose on every ",(0,t.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#slot",children:"slot"}),"; participation depends on your stake relative to total active stake in the network."]})]}),"\n",(0,t.jsx)(n.h2,{id:"troubleshooting-the-logos-blockchain-node",children:"Troubleshooting the Logos Blockchain node"}),"\n",(0,t.jsx)(n.h3,{id:"the-testnet-explorer-shows-an-error-when-i-click-on-a-transaction",children:"The testnet explorer shows an error when I click on a transaction?"}),"\n",(0,t.jsxs)(n.p,{children:["The ",(0,t.jsx)(n.a,{href:"https://testnet.blockchain.logos.co/web/",children:"testnet explorer"})," does not support clicking on individual transactions. Searching by address is also not supported. Transaction hashes returned by the faucet may appear truncated and may not be immediately findable."]}),"\n",(0,t.jsx)(n.h3,{id:"my-wallet-balance-is-not-updating-after-requesting-tokens",children:"My wallet balance is not updating after requesting tokens?"}),"\n",(0,t.jsx)(n.p,{children:"Only one faucet transaction can be included per block. During high demand, your transaction may be dropped. Retry the request and wait 1 to 2 minutes before checking your balance again."})]})}function h(e={}){let{wrapper:n}={...(0,i.R)(),...e.components};return n?(0,t.jsx)(n,{...e,children:(0,t.jsx)(d,{...e})}):d(e)}},3526(e,n,o){o.d(n,{A:()=>s});let s=o.p+"assets/images/node-faucet-ea6535ffbfd684702139e140a632bbc8.png"},8453(e,n,o){o.d(n,{R:()=>c,x:()=>r});var s=o(6540);let t={},i=s.createContext(t);function c(e){let n=s.useContext(i);return s.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function r(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(t):e.components||t:c(e.components),s.createElement(i.Provider,{value:n},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/49b24bc7.e87dd520.js b/assets/js/49b24bc7.e87dd520.js new file mode 100644 index 00000000..3e0a7017 --- /dev/null +++ b/assets/js/49b24bc7.e87dd520.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunklogos_docs=self.webpackChunklogos_docs||[]).push([["3523"],{238(e,s,n){n.r(s),n.d(s,{metadata:()=>i,default:()=>h,frontMatter:()=>t,contentTitle:()=>c,toc:()=>a,assets:()=>d});var i=JSON.parse('{"id":"messaging/concepts/understand-peer-discovery/about-discv5","title":"About Discv5","description":"Understand how Discv5 uses a distributed hash table to find peers across the network.","source":"@site/docs/messaging/concepts/understand-peer-discovery/about-discv5.md","sourceDirName":"messaging/concepts/understand-peer-discovery","slug":"/messaging/concepts/understand-peer-discovery/discv5","permalink":"/messaging/concepts/understand-peer-discovery/discv5","draft":false,"unlisted":false,"editUrl":"https://github.com/logos-co/logos-docs/tree/main/docs/messaging/concepts/understand-peer-discovery/about-discv5.md","tags":[],"version":"current","sidebarPosition":4,"frontMatter":{"title":"About Discv5","doc_type":"concept","product":"messaging","topics":"peer-discovery, discv5, networking","authors":"LordGhostX, fryorcraken","owner":"logos","doc_version":1,"slug":"discv5","sidebar_position":4},"sidebar":"messagingSidebar","previous":{"title":"About DNS discovery","permalink":"/messaging/concepts/understand-peer-discovery/dns-discovery"},"next":{"title":"About peer exchange","permalink":"/messaging/concepts/understand-peer-discovery/peer-exchange"}}'),o=n(4848),r=n(8453);let t={title:"About Discv5",doc_type:"concept",product:"messaging",topics:"peer-discovery, discv5, networking",authors:"LordGhostX, fryorcraken",owner:"logos",doc_version:1,slug:"discv5",sidebar_position:4},c="About Discv5",d={},a=[{value:"Understand how Discv5 uses a distributed hash table to find peers across the network.",id:"understand-how-discv5-uses-a-distributed-hash-table-to-find-peers-across-the-network",level:4},{value:"How Discv5 works",id:"how-discv5-works",level:2},{value:"Pros and cons",id:"pros-and-cons",level:2}];function l(e){let s={a:"a",code:"code",h1:"h1",h2:"h2",h4:"h4",header:"header",li:"li",ol:"ol",p:"p",pre:"pre",ul:"ul",...(0,r.R)(),...e.components};return(0,o.jsxs)(o.Fragment,{children:[(0,o.jsx)(s.header,{children:(0,o.jsx)(s.h1,{id:"about-discv5",children:"About Discv5"})}),"\n",(0,o.jsx)(s.h4,{id:"understand-how-discv5-uses-a-distributed-hash-table-to-find-peers-across-the-network",children:"Understand how Discv5 uses a distributed hash table to find peers across the network."}),"\n",(0,o.jsxs)(s.p,{children:[(0,o.jsx)(s.code,{children:"Discv5"})," is a decentralised and efficient ",(0,o.jsx)(s.a,{href:"https://docs.logos.co/get-started/glossary#peer-discovery",children:"peer discovery"})," mechanism for the ",(0,o.jsx)(s.a,{href:"https://docs.logos.co/get-started/glossary#logos-delivery",children:"Logos Delivery"})," Network. It uses a ",(0,o.jsx)(s.a,{href:"https://en.wikipedia.org/wiki/Distributed_hash_table",children:"Distributed Hash Table (DHT)"})," for storing ",(0,o.jsx)(s.code,{children:"ENR"})," records, providing resistance to censorship. ",(0,o.jsx)(s.code,{children:"Discv5"})," offers a global view of participating nodes, enabling random sampling for load distribution. It uses bootstrap nodes as an entry point to the network, providing randomised sets of nodes for mesh expansion. Have a look at the ",(0,o.jsx)(s.a,{href:"https://lip.logos.co/messaging/core/draft/33/discv5.html",children:"Discv5"})," specification to learn more."]}),"\n",(0,o.jsx)(s.h2,{id:"how-discv5-works",children:"How Discv5 works"}),"\n",(0,o.jsx)(s.pre,{children:(0,o.jsx)(s.code,{className:"language-mermaid",children:"sequenceDiagram\n Alice->>DNS Server: (1) Execute DNS Discovery\n DNS Server--\x3e>Alice: (2) Bob's connection details (UDP + TCP)\n activate Alice\n Alice->>Bob: (3) Execute Discv5 over UDP\n Bob--\x3e>Alice: (4) ENRs (UDP + TCP): Carol, David, Erin\n Alice->>Alice: (5) Decode ENRs\n Alice->>Carol: (6) Execute Discv5 over UDP\n Carol--\x3e>Alice: (7) ENRs (UDP + TCP): Frank, Gwen, Harry\n Alice->>Alice: (8) Decode ENRs\n deactivate Alice\n Alice->>Alice: (9) Select peers to dial\n Alice->>David: (10) Libp2p dial (TCP)\n Alice->>Frank: (10) Libp2p dial (TCP)\n Alice->>Gwen: (10) Libp2p dial (TCP)\n"})}),"\n",(0,o.jsxs)(s.ol,{children:["\n",(0,o.jsxs)(s.li,{children:[(0,o.jsx)(s.a,{href:"https://docs.logos.co/get-started/glossary#dns-discovery",children:"DNS Discovery"})," protocol is executed."]}),"\n",(0,o.jsxs)(s.li,{children:["Alice retrieves Bob's ",(0,o.jsx)(s.a,{href:"https://docs.logos.co/get-started/glossary#enr",children:"ENR"})," (Ethereum Node Record) from DNS Server."]}),"\n",(0,o.jsx)(s.li,{children:"Alice executes the Discv5 protocol with Bob using UDP connection details from ENR."}),"\n",(0,o.jsx)(s.li,{children:"Bob returns Carol's, David's and Erin's ENRs to Alice."}),"\n",(0,o.jsx)(s.li,{children:"Alice decodes ENRs and extracts the details of Carol, David, and Erin's TCP and UDP connections."}),"\n",(0,o.jsx)(s.li,{children:"Alice executes the Discv5 protocol with Carol using UDP connection details from ENR."}),"\n",(0,o.jsx)(s.li,{children:"Carol returns Frank's, Gwen's and Harry's ENRs to Alice."}),"\n",(0,o.jsx)(s.li,{children:"Alice decodes ENRs and extracts Frank's, Gwen's and Harry's TCP and UDP connection details."}),"\n",(0,o.jsx)(s.li,{children:"Alice selects to dial David, Frank and Gwen."}),"\n",(0,o.jsx)(s.li,{children:"Alice dials David, Frank and Gwen over libp2p using TCP connection details from ENRs."}),"\n"]}),"\n",(0,o.jsx)(s.h2,{id:"pros-and-cons",children:"Pros and cons"}),"\n",(0,o.jsx)(s.p,{children:"Pros:"}),"\n",(0,o.jsxs)(s.ul,{children:["\n",(0,o.jsx)(s.li,{children:"Decentralised with random sampling from a global view."}),"\n",(0,o.jsx)(s.li,{children:"Continuously researched and improved."}),"\n"]}),"\n",(0,o.jsx)(s.p,{children:"Cons:"}),"\n",(0,o.jsxs)(s.ul,{children:["\n",(0,o.jsx)(s.li,{children:"Requires lots of connections and involves frequent churn."}),"\n",(0,o.jsx)(s.li,{children:"Relies on User Datagram Protocol (UDP), which is not supported in web browsers."}),"\n"]})]})}function h(e={}){let{wrapper:s}={...(0,r.R)(),...e.components};return s?(0,o.jsx)(s,{...e,children:(0,o.jsx)(l,{...e})}):l(e)}},8453(e,s,n){n.d(s,{R:()=>t,x:()=>c});var i=n(6540);let o={},r=i.createContext(o);function t(e){let s=i.useContext(r);return i.useMemo(function(){return"function"==typeof e?e(s):{...s,...e}},[s,e])}function c(e){let s;return s=e.disableParentContext?"function"==typeof e.components?e.components(o):e.components||o:t(e.components),i.createElement(r.Provider,{value:s},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/5062.a3990a67.js b/assets/js/5062.a3990a67.js new file mode 100644 index 00000000..f8b09f8f --- /dev/null +++ b/assets/js/5062.a3990a67.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunklogos_docs=self.webpackChunklogos_docs||[]).push([["5062"],{955(e,t,o){o.d(t,{A:()=>a});var n=o(4848);o(6540);var i=o(4164),s=o(568),r=o(2072);function a({className:e}){return(0,n.jsx)("main",{className:(0,i.A)("container margin-vert--xl",e),children:(0,n.jsx)("div",{className:"row",children:(0,n.jsxs)("div",{className:"col col--6 col--offset-3",children:[(0,n.jsx)(r.A,{as:"h1",className:"hero__title",children:(0,n.jsx)(s.A,{id:"theme.NotFound.title",description:"The title of the 404 page",children:"Page Not Found"})}),(0,n.jsx)("p",{children:(0,n.jsx)(s.A,{id:"theme.NotFound.p1",description:"The first paragraph of the 404 page",children:"We could not find what you were looking for."})}),(0,n.jsx)("p",{children:(0,n.jsx)(s.A,{id:"theme.NotFound.p2",description:"The 2nd paragraph of the 404 page",children:"Please contact the owner of the site that linked you to the original URL and let them know their link is broken."})})]})})})}},629(e,t,o){o.r(t),o.d(t,{default:()=>l});var n=o(4848);o(6540);var i=o(568),s=o(4308),r=o(1085),a=o(955);function l(){let e=(0,i.T)({id:"theme.NotFound.title",message:"Page Not Found"});return(0,n.jsxs)(n.Fragment,{children:[(0,n.jsx)(s.be,{title:e}),(0,n.jsx)(r.A,{children:(0,n.jsx)(a.A,{})})]})}}}]); \ No newline at end of file diff --git a/assets/js/589ec14f.cc8ba260.js b/assets/js/589ec14f.cc8ba260.js new file mode 100644 index 00000000..e3f1dcc5 --- /dev/null +++ b/assets/js/589ec14f.cc8ba260.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunklogos_docs=self.webpackChunklogos_docs||[]).push([["4745"],{543(e,o,s){s.r(o),s.d(o,{metadata:()=>r,default:()=>h,frontMatter:()=>d,contentTitle:()=>l,toc:()=>c,assets:()=>t});var r=JSON.parse('{"id":"core/build-modules/build-logos-core-module-that-uses-service-discovery-api","title":"Build a Logos Core module that uses the Service Discovery API","description":"Get started with typed, service-keyed peer lookups in a live Logos network.","source":"@site/docs/core/build-modules/build-logos-core-module-that-uses-service-discovery-api.md","sourceDirName":"core/build-modules","slug":"/core/build-modules/build-logos-core-module-that-uses-service-discovery-api","permalink":"/core/build-modules/build-logos-core-module-that-uses-service-discovery-api","draft":false,"unlisted":false,"editUrl":"https://github.com/logos-co/logos-docs/tree/main/docs/core/build-modules/build-logos-core-module-that-uses-service-discovery-api.md","tags":[],"version":"current","sidebarPosition":6,"frontMatter":{"title":"Build a Logos Core module that uses the Service Discovery API","doc_type":"procedure","product":"core","topics":"core","steps_layout":"sectioned","authors":"gmelodie, kashepavadan","owner":"logos","doc_version":1,"slug":"build-logos-core-module-that-uses-service-discovery-api","sidebar_position":6},"sidebar":"coreSidebar","previous":{"title":"Install and load a module in Logos Basecamp","permalink":"/core/build-modules/install-and-load-a-module-in-logos-basecamp"},"next":{"title":"LGX package format and bundling reference","permalink":"/core/reference/lgx-package-format-and-bundling-reference"}}'),n=s(4848),i=s(8453);let d={title:"Build a Logos Core module that uses the Service Discovery API",doc_type:"procedure",product:"core",topics:"core",steps_layout:"sectioned",authors:"gmelodie, kashepavadan",owner:"logos",doc_version:1,slug:"build-logos-core-module-that-uses-service-discovery-api",sidebar_position:6},l="Build a Logos Core module that uses the Service Discovery API",t={},c=[{value:"Get started with typed, service-keyed peer lookups in a live Logos network.",id:"get-started-with-typed-service-keyed-peer-lookups-in-a-live-logos-network",level:4},{value:"What to expect",id:"what-to-expect",level:2},{value:"Step 1: Smoke-test the bundled example binary (Optional)",id:"step-1-smoke-test-the-bundled-example-binary-optional",level:2},{value:"Step 2: Scaffold the new Logos Core module",id:"step-2-scaffold-the-new-logos-core-module",level:2},{value:"Step 3: Build both modules",id:"step-3-build-both-modules",level:2},{value:"Step 4: Load the modules and verify the single-node flow",id:"step-4-load-the-modules-and-verify-the-single-node-flow",level:2},{value:"Step 5: Run three-node local discovery",id:"step-5-run-three-node-local-discovery",level:2},{value:"Troubleshooting service discovery",id:"troubleshooting-service-discovery",level:2},{value:"Why does <code>discover</code> return <code>[]</code> even after waiting?",id:"why-does-discover-return--even-after-waiting",level:3},{value:"Why does a <code>logoscore call</code> hang with a timeout error?",id:"why-does-a-logoscore-call-hang-with-a-timeout-error",level:3}];function a(e){let o={a:"a",admonition:"admonition",code:"code",h1:"h1",h2:"h2",h3:"h3",h4:"h4",header:"header",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,i.R)(),...e.components};return(0,n.jsxs)(n.Fragment,{children:[(0,n.jsx)(o.header,{children:(0,n.jsx)(o.h1,{id:"build-a-logos-core-module-that-uses-the-service-discovery-api",children:"Build a Logos Core module that uses the Service Discovery API"})}),"\n",(0,n.jsx)(o.h4,{id:"get-started-with-typed-service-keyed-peer-lookups-in-a-live-logos-network",children:"Get started with typed, service-keyed peer lookups in a live Logos network."}),"\n",(0,n.jsxs)(o.p,{children:["Applications on the Logos network need a protocol-agnostic way to find peers offering specific services \u2014 ",(0,n.jsx)(o.a,{href:"https://docs.logos.co/get-started/glossary#mix",children:"mix"})," nodes, relay nodes, storage providers \u2014 at runtime without hard-coding topology or peer lists. The Service Discovery API enables any Logos Core ",(0,n.jsx)(o.a,{href:"https://docs.logos.co/get-started/glossary#module",children:"module"})," to perform typed, service-keyed peer lookups that work from lightweight client nodes that do not participate in DHT routing, unblocking any app that needs to wire itself into a live Logos network service. This procedure covers how to write and run a Logos Core module that calls the ",(0,n.jsx)(o.code,{children:"libp2p_module"})," Service Discovery API to advertise a named service to the network and discover other peers offering that same service."]}),"\n",(0,n.jsx)(o.p,{children:"Before you start, make sure you have the following:"}),"\n",(0,n.jsxs)(o.ul,{children:["\n",(0,n.jsxs)(o.li,{children:["\n",(0,n.jsx)(o.p,{children:"Linux (Ubuntu 22.04+) or macOS 14+"}),"\n"]}),"\n",(0,n.jsxs)(o.li,{children:["\n",(0,n.jsxs)(o.p,{children:[(0,n.jsx)(o.strong,{children:"Nix"})," with flakes enabled. Install from ",(0,n.jsx)(o.a,{href:"https://nixos.org/download.html",children:"nixos.org"}),", then enable flakes:"]}),"\n",(0,n.jsx)(o.pre,{children:(0,n.jsx)(o.code,{className:"language-bash",children:"mkdir -p ~/.config/nix\necho 'experimental-features = nix-command flakes' >> ~/.config/nix/nix.conf\n"})}),"\n"]}),"\n",(0,n.jsxs)(o.li,{children:["\n",(0,n.jsx)(o.p,{children:"2 GB RAM (sufficient for a local two-module test)"}),"\n"]}),"\n",(0,n.jsxs)(o.li,{children:["\n",(0,n.jsxs)(o.p,{children:[(0,n.jsx)(o.a,{href:"https://github.com/logos-co/logos-logoscore-cli/releases/tag/0.2.0",children:(0,n.jsx)(o.code,{children:"logoscore"})})," installed. To install it, use the ",(0,n.jsx)(o.code,{children:"install-node-tools.sh"})," helper script:"]}),"\n",(0,n.jsx)(o.pre,{children:(0,n.jsx)(o.code,{className:"language-bash",children:'curl -fsSL https://raw.githubusercontent.com/logos-co/logos-docs/main/resources/scripts/install-node-tools.sh | sh\nexport PATH="$PWD/bin:$PATH"\n'})}),"\n"]}),"\n"]}),"\n",(0,n.jsx)(o.h2,{id:"what-to-expect",children:"What to expect"}),"\n",(0,n.jsxs)(o.ul,{children:["\n",(0,n.jsx)(o.li,{children:"You can advertise a named service from a Logos Core module and discover peers offering the same service via the Kad-DHT, without hard-coding peer addresses."}),"\n",(0,n.jsxs)(o.li,{children:["You can verify the full discovery flow locally across three ",(0,n.jsx)(o.code,{children:"logoscore"})," daemon instances, each with its own config directory and listen port."]}),"\n",(0,n.jsxs)(o.li,{children:["You have a reusable module scaffold with typed ",(0,n.jsx)(o.code,{children:"disco*"})," wrappers that you can extend for production service types."]}),"\n"]}),"\n",(0,n.jsx)(o.h2,{id:"step-1-smoke-test-the-bundled-example-binary-optional",children:"Step 1: Smoke-test the bundled example binary (Optional)"}),"\n",(0,n.jsx)(o.p,{children:"Build and run the self-contained two-node demo to confirm the module and its C bindings are working before writing your own module."}),"\n",(0,n.jsxs)(o.ol,{children:["\n",(0,n.jsxs)(o.li,{children:["\n",(0,n.jsx)(o.p,{children:"Clone the repository and enter the project directory:"}),"\n",(0,n.jsx)(o.pre,{children:(0,n.jsx)(o.code,{className:"language-sh",children:"git clone https://github.com/logos-co/logos-libp2p-module\ncd logos-libp2p-module\n"})}),"\n"]}),"\n",(0,n.jsxs)(o.li,{children:["\n",(0,n.jsx)(o.p,{children:"Build the module:"}),"\n",(0,n.jsx)(o.pre,{children:(0,n.jsx)(o.code,{className:"language-sh",children:"nix build -L\n"})}),"\n",(0,n.jsx)(o.admonition,{type:"info"}),"\n"]}),"\n"]}),"\n",(0,n.jsx)(o.p,{children:"The first-run Nix build can take 5\u201320 minutes to fetch dependencies; subsequent builds are cached. Estimated total time is 15\u201325 minutes.\n:::"}),"\n",(0,n.jsxs)(o.ol,{children:["\n",(0,n.jsxs)(o.li,{children:["\n",(0,n.jsxs)(o.p,{children:["Vendor the C-binding header and shared library that ",(0,n.jsx)(o.code,{children:"logos_module()"})," expects in ",(0,n.jsx)(o.code,{children:"./lib"}),":"]}),"\n",(0,n.jsx)(o.pre,{children:(0,n.jsx)(o.code,{className:"language-sh",children:"CBIND=$(find /nix/store -maxdepth 4 -name libp2p.h -path '*cbind*' | head -1 | xargs dirname)\nmkdir -p lib\ncp \"$CBIND/libp2p.h\" lib/\nfind /nix/store -name libp2p.so -path '*cbind*' -exec cp {} lib/ \\;\n"})}),"\n"]}),"\n",(0,n.jsxs)(o.li,{children:["\n",(0,n.jsx)(o.p,{children:"Build the example target from the root project and run it:"}),"\n",(0,n.jsx)(o.pre,{children:(0,n.jsx)(o.code,{className:"language-sh",children:"nix develop --command bash -c 'cmake -B build -S . && cmake --build build --target example_service_discovery -j'\n./build/examples/example_service_discovery\n"})}),"\n",(0,n.jsx)(o.p,{children:"Expected output:"}),"\n",(0,n.jsx)(o.pre,{children:(0,n.jsx)(o.code,{children:"Starting nodes...\nAdvertiser: advertising demo-service\nDiscoverer: registering interest in demo-service\nDiscoverer: looking up demo-service\nDiscoverer found 1 peer(s) advertising demo-service\n peer: 16Uiu2HAk... seq: 1359 addrs: 1\nDiscoverer matched the advertiser: 16Uiu2HAk...\nAdvertiser: random lookup\nRandom lookup returned 2 peer(s)\nAdvertiser: building a signed Extended Peer Record\nSigned XPR is 288 bytes\nDiscoverer: unregistering interest in demo-service\nAdvertiser: stopping advertising demo-service\nDone\n"})}),"\n",(0,n.jsxs)(o.ul,{children:["\n",(0,n.jsx)(o.li,{children:"Peer IDs are non-deterministic across runs."}),"\n"]}),"\n",(0,n.jsx)(o.admonition,{type:"info"}),"\n"]}),"\n"]}),"\n",(0,n.jsxs)(o.p,{children:["The demo runs a bootstrap node plus an advertiser and a discoverer, so the discoverer finds the advertiser through the DHT \u2014 a successful run prints ",(0,n.jsx)(o.code,{children:"found 1 peer(s)"})," and ",(0,n.jsx)(o.code,{children:"matched the advertiser"}),". Exact peer counts, IDs, and the XPR byte size vary per run.\n:::"]}),"\n",(0,n.jsx)(o.h2,{id:"step-2-scaffold-the-new-logos-core-module",children:"Step 2: Scaffold the new Logos Core module"}),"\n",(0,n.jsxs)(o.p,{children:["Run the scaffold tool from the parent directory to generate the module skeleton, then declare the ",(0,n.jsx)(o.code,{children:"libp2p_module"})," dependency."]}),"\n",(0,n.jsxs)(o.ol,{children:["\n",(0,n.jsxs)(o.li,{children:["\n",(0,n.jsx)(o.p,{children:"From the parent directory, scaffold the module:"}),"\n",(0,n.jsx)(o.pre,{children:(0,n.jsx)(o.code,{className:"language-sh",children:"cd ..\nnix run github:logos-co/logos-dev-boost -- init my_service_module --type module\ncd logos-my-service-module\n"})}),"\n",(0,n.jsxs)(o.ul,{children:["\n",(0,n.jsxs)(o.li,{children:["The scaffold tool prefixes the output directory with ",(0,n.jsx)(o.code,{children:"logos-"}),", producing ",(0,n.jsx)(o.code,{children:"logos-my-service-module"}),"."]}),"\n"]}),"\n"]}),"\n",(0,n.jsxs)(o.li,{children:["\n",(0,n.jsxs)(o.p,{children:["Replace the contents of ",(0,n.jsx)(o.code,{children:"metadata.json"})," with the following to declare the ",(0,n.jsx)(o.code,{children:"libp2p_module"})," dependency:"]}),"\n",(0,n.jsx)(o.pre,{children:(0,n.jsx)(o.code,{className:"language-json",children:'{\n "name": "my_service_module",\n "version": "1.0.0",\n "description": "Service discovery demo module",\n "type": "core",\n "interface": "universal",\n "main": "my_service_module_plugin",\n "dependencies": ["libp2p_module"]\n}\n'})}),"\n"]}),"\n",(0,n.jsxs)(o.li,{children:["\n",(0,n.jsxs)(o.p,{children:["Replace the contents of ",(0,n.jsx)(o.code,{children:"src/my_service_module_impl.h"})," with the following:"]}),"\n",(0,n.jsx)(o.pre,{children:(0,n.jsx)(o.code,{className:"language-cpp",children:'// my_service_module_impl.h \u2014 inherit LogosModuleContext:\n#pragma once\n#include <string>\n#include "logos_module_context.h"\n\nclass MyServiceModuleImpl : public LogosModuleContext {\npublic:\n std::string startDiscovery();\n std::string getPeerInfo();\n std::string advertise(const std::string& serviceId, const std::string& serviceData);\n std::string discover(const std::string& serviceId);\n std::string stopDiscovery();\n};\n'})}),"\n"]}),"\n",(0,n.jsxs)(o.li,{children:["\n",(0,n.jsxs)(o.p,{children:["Replace the contents of ",(0,n.jsx)(o.code,{children:"src/my_service_module_impl.cpp"})," with the following:"]}),"\n",(0,n.jsx)(o.pre,{children:(0,n.jsx)(o.code,{className:"language-cpp",children:'#include "my_service_module_impl.h"\n#include "logos_sdk.h" // generated; defines: struct LogosModules { Libp2pModule libp2p_module; };\n#include <thread>\n#include <chrono>\n\nstd::string MyServiceModuleImpl::startDiscovery() {\n auto r = modules().libp2p_module.start();\n if (!r.success) return "start failed: " + r.error;\n auto r2 = modules().libp2p_module.discoStart();\n if (!r2.success) return "discoStart failed: " + r2.error;\n return "discovery started";\n}\n\n// Returns this node\'s own peer record (peerId + listen addrs). Use it on a\n// bootstrap node to obtain the peerId/addr that other instances bootstrap against.\nstd::string MyServiceModuleImpl::getPeerInfo() {\n auto r = modules().libp2p_module.peerInfo();\n if (!r.success) return "peerInfo failed: " + r.error;\n return r.value.is_string() ? r.value.get<std::string>() : r.value.dump();\n}\n\nstd::string MyServiceModuleImpl::advertise(const std::string& serviceId,\n const std::string& serviceData) {\n // discoStartAdvertising requires BOTH serviceId and serviceData.\n auto r = modules().libp2p_module.discoStartAdvertising(serviceId, serviceData);\n if (!r.success) return "advertise failed: " + r.error;\n return "advertising " + serviceId;\n}\n\nstd::string MyServiceModuleImpl::discover(const std::string& serviceId) {\n auto r = modules().libp2p_module.discoRegisterInterest(serviceId);\n if (!r.success) return "registerInterest failed: " + r.error;\n std::this_thread::sleep_for(std::chrono::milliseconds(500));\n // "" serviceData = match any advertisement of this serviceId.\n auto r2 = modules().libp2p_module.discoLookup(serviceId, "");\n if (!r2.success) return "lookup failed: " + r2.error;\n return r2.value.is_string() ? r2.value.get<std::string>() : r2.value.dump();\n}\n\nstd::string MyServiceModuleImpl::stopDiscovery() {\n auto r = modules().libp2p_module.discoStop();\n if (!r.success) return "discoStop failed: " + r.error;\n auto r2 = modules().libp2p_module.stop();\n if (!r2.success) return "stop failed: " + r2.error;\n return "discovery stopped";\n}\n'})}),"\n"]}),"\n",(0,n.jsxs)(o.li,{children:["\n",(0,n.jsxs)(o.p,{children:["Add ",(0,n.jsx)(o.code,{children:"libp2p_module"})," as a flake input in ",(0,n.jsx)(o.code,{children:"flake.nix"}),":"]}),"\n",(0,n.jsx)(o.pre,{children:(0,n.jsx)(o.code,{className:"language-nix",children:'inputs = {\n logos-module-builder.url = "github:logos-co/logos-module-builder";\n libp2p_module.url = "github:logos-co/logos-libp2p-module";\n};\n'})}),"\n",(0,n.jsx)(o.admonition,{type:"info"}),"\n"]}),"\n"]}),"\n",(0,n.jsxs)(o.p,{children:["For local development, override",(0,n.jsx)(o.code,{children:"flake.nix"})," at build time:"]}),"\n",(0,n.jsx)(o.pre,{children:(0,n.jsx)(o.code,{className:"language-bash",children:"nix build --override-input libp2p_module path:../logos-libp2p-module\n"})}),"\n",(0,n.jsx)(o.p,{children:":::"}),"\n",(0,n.jsx)(o.h2,{id:"step-3-build-both-modules",children:"Step 3: Build both modules"}),"\n",(0,n.jsxs)(o.p,{children:["The ",(0,n.jsx)(o.code,{children:".#install"})," target runs ",(0,n.jsx)(o.code,{children:"lgpm"})," internally and produces the directory structure ",(0,n.jsx)(o.code,{children:"logoscore"})," requires. You only write ",(0,n.jsx)(o.code,{children:"metadata.json"}),"; the install target generates ",(0,n.jsx)(o.code,{children:"manifest.json"}),", ",(0,n.jsx)(o.code,{children:"variant"}),", and co-locates all ",(0,n.jsx)(o.code,{children:".so"})," files automatically."]}),"\n",(0,n.jsxs)(o.ol,{children:["\n",(0,n.jsxs)(o.li,{children:["\n",(0,n.jsxs)(o.p,{children:["In ",(0,n.jsx)(o.code,{children:"logos-my-service-module"}),", initialize a Git repository and run the install build:"]}),"\n",(0,n.jsx)(o.pre,{children:(0,n.jsx)(o.code,{className:"language-sh",children:"git init && git add -A\nnix build .#install -L\n"})}),"\n",(0,n.jsx)(o.p,{children:"This produces:"}),"\n",(0,n.jsx)(o.pre,{children:(0,n.jsx)(o.code,{children:"result/modules/my_service_module/\n \u251C\u2500\u2500 manifest.json\n \u251C\u2500\u2500 variant\n \u2514\u2500\u2500 my_service_module_plugin.so\n"})}),"\n"]}),"\n",(0,n.jsxs)(o.li,{children:["\n",(0,n.jsxs)(o.p,{children:["In ",(0,n.jsx)(o.code,{children:"logos-libp2p-module"}),", run the install build:"]}),"\n",(0,n.jsx)(o.pre,{children:(0,n.jsx)(o.code,{className:"language-sh",children:"cd ../logos-libp2p-module\nnix build .#install -L\n"})}),"\n",(0,n.jsx)(o.p,{children:"This produces:"}),"\n",(0,n.jsx)(o.pre,{children:(0,n.jsx)(o.code,{children:"result/modules/libp2p_module/\n \u251C\u2500\u2500 manifest.json\n \u251C\u2500\u2500 variant\n \u251C\u2500\u2500 libp2p_module_plugin.so\n \u2514\u2500\u2500 libp2p.so\n"})}),"\n",(0,n.jsxs)(o.ul,{children:["\n",(0,n.jsxs)(o.li,{children:[(0,n.jsx)(o.code,{children:"libp2p.so"})," is co-located automatically so the ",(0,n.jsx)(o.code,{children:"$ORIGIN"})," RUNPATH resolves at runtime."]}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,n.jsx)(o.h2,{id:"step-4-load-the-modules-and-verify-the-single-node-flow",children:"Step 4: Load the modules and verify the single-node flow"}),"\n",(0,n.jsxs)(o.ol,{children:["\n",(0,n.jsxs)(o.li,{children:["\n",(0,n.jsxs)(o.p,{children:["Return to ",(0,n.jsx)(o.code,{children:"logos-my-service-module"})," and pin a known, fixed listen address via the ",(0,n.jsx)(o.code,{children:"LIBP2P_MODULE_CONFIG"})," environment variable:"]}),"\n",(0,n.jsx)(o.pre,{children:(0,n.jsx)(o.code,{className:"language-sh",children:'cd ../logos-my-service-module\nexport LIBP2P_MODULE_CONFIG=\'{"addrs":["/ip4/127.0.0.1/tcp/9000"]}\'\n'})}),"\n"]}),"\n",(0,n.jsxs)(o.li,{children:["\n",(0,n.jsx)(o.p,{children:"Start the daemon in the background, pointing at both module directories:"}),"\n",(0,n.jsx)(o.pre,{children:(0,n.jsx)(o.code,{className:"language-sh",children:"logoscore -D \\\n -m ../logos-libp2p-module/result/modules \\\n -m ./result/modules &\n"})}),"\n"]}),"\n",(0,n.jsxs)(o.li,{children:["\n",(0,n.jsx)(o.p,{children:"Load both modules:"}),"\n",(0,n.jsx)(o.pre,{children:(0,n.jsx)(o.code,{className:"language-sh",children:"logoscore load-module libp2p_module\nlogoscore load-module my_service_module\n"})}),"\n"]}),"\n",(0,n.jsxs)(o.li,{children:["\n",(0,n.jsx)(o.p,{children:"Drive the discovery lifecycle and verify each call returns immediately:"}),"\n",(0,n.jsx)(o.pre,{children:(0,n.jsx)(o.code,{className:"language-sh",children:'logoscore call my_service_module startDiscovery\n# \u2192 discovery started\n\nlogoscore call my_service_module getPeerInfo\n# \u2192 {"peerId":"16Uiu2\u2026","addrs":["/ip4/127.0.0.1/tcp/9000"]}\n\nlogoscore call my_service_module advertise myservice/v1 version=1\n# \u2192 advertising myservice/v1\n\nlogoscore call my_service_module discover myservice/v1\n# \u2192 [] (single node: no second advertiser)\n\nlogoscore call my_service_module stopDiscovery\n# \u2192 discovery stopped\n'})}),"\n",(0,n.jsxs)(o.ul,{children:["\n",(0,n.jsxs)(o.li,{children:[(0,n.jsx)(o.code,{children:"discover"})," returning ",(0,n.jsx)(o.code,{children:"[]"})," is expected with a single node. ",(0,n.jsx)(o.code,{children:"getPeerInfo"})," prints this node's ",(0,n.jsx)(o.code,{children:"peerId"})," and listen address, which you need in ",(0,n.jsx)(o.a,{href:"#step-5-run-three-node-local-discovery",children:"Step 5"})," to bootstrap other instances."]}),"\n"]}),"\n"]}),"\n",(0,n.jsxs)(o.li,{children:["\n",(0,n.jsx)(o.p,{children:"Shut down the daemon:"}),"\n",(0,n.jsx)(o.pre,{children:(0,n.jsx)(o.code,{className:"language-sh",children:"logoscore stop\n"})}),"\n"]}),"\n"]}),"\n",(0,n.jsx)(o.h2,{id:"step-5-run-three-node-local-discovery",children:"Step 5: Run three-node local discovery"}),"\n",(0,n.jsxs)(o.p,{children:["Run three ",(0,n.jsx)(o.code,{children:"logoscore"})," daemon instances on one machine to see the Service Discovery API work: a bootstrap node, an advertiser, and a discoverer. The advertiser and discoverer are configured only with the bootstrap node as their bootstrap node \u2014 when the discoverer's lookup returns the advertiser's peer record, the advertisement provably travelled through the DHT, not over a direct A\u2194B link."]}),"\n",(0,n.jsxs)(o.p,{children:["Each daemon needs its own ",(0,n.jsx)(o.code,{children:"--config-dir"})," and ",(0,n.jsx)(o.code,{children:"LIBP2P_MODULE_CONFIG"})," with a distinct listen port. Run each block in a separate terminal window."]}),"\n",(0,n.jsxs)(o.ol,{children:["\n",(0,n.jsxs)(o.li,{children:["\n",(0,n.jsxs)(o.p,{children:["In ",(0,n.jsx)(o.strong,{children:"Terminal 1"}),", start the bootstrap node:"]}),"\n",(0,n.jsx)(o.pre,{children:(0,n.jsx)(o.code,{className:"language-sh",children:'cd ../logos-my-service-module\nexport LIBP2P_MODULE_CONFIG=\'{"addrs":["/ip4/127.0.0.1/tcp/9000"]}\'\nlogoscore -D --config-dir ~/.logoscore-bootstrap \\\n -m ../logos-libp2p-module/result/modules -m ./result/modules &\nlogoscore --config-dir ~/.logoscore-bootstrap load-module libp2p_module\nlogoscore --config-dir ~/.logoscore-bootstrap load-module my_service_module\nlogoscore --config-dir ~/.logoscore-bootstrap call my_service_module startDiscovery\nlogoscore --config-dir ~/.logoscore-bootstrap call my_service_module getPeerInfo\n# \u2192 note the "peerId" value; the bootstrap node listens on /ip4/127.0.0.1/tcp/9000\n'})}),"\n",(0,n.jsxs)(o.ul,{children:["\n",(0,n.jsxs)(o.li,{children:["Copy the bootstrap node's ",(0,n.jsx)(o.code,{children:"peerId"})," from the ",(0,n.jsx)(o.code,{children:"getPeerInfo"})," output. Substitute it for ",(0,n.jsx)(o.code,{children:"<BOOTSTRAP_PEER_ID>"})," in the next two terminals."]}),"\n"]}),"\n"]}),"\n",(0,n.jsxs)(o.li,{children:["\n",(0,n.jsxs)(o.p,{children:["In ",(0,n.jsx)(o.strong,{children:"Terminal 2"}),", start the advertiser:"]}),"\n",(0,n.jsx)(o.pre,{children:(0,n.jsx)(o.code,{className:"language-sh",children:'cd ../logos-my-service-module\nexport LIBP2P_MODULE_CONFIG=\'{"addrs":["/ip4/127.0.0.1/tcp/9001"],"bootstrapNodes":[{"peerId":"<BOOTSTRAP_PEER_ID>","addrs":["/ip4/127.0.0.1/tcp/9000"]}]}\'\nlogoscore -D --config-dir ~/.logoscore-advertiser \\\n -m ../logos-libp2p-module/result/modules -m ./result/modules &\nlogoscore --config-dir ~/.logoscore-advertiser load-module libp2p_module\nlogoscore --config-dir ~/.logoscore-advertiser load-module my_service_module\nlogoscore --config-dir ~/.logoscore-advertiser call my_service_module startDiscovery\nlogoscore --config-dir ~/.logoscore-advertiser call my_service_module advertise myservice/v1 version=1\n'})}),"\n"]}),"\n",(0,n.jsxs)(o.li,{children:["\n",(0,n.jsxs)(o.p,{children:["In ",(0,n.jsx)(o.strong,{children:"Terminal 3"}),", start the discoverer and look up the service:"]}),"\n",(0,n.jsx)(o.pre,{children:(0,n.jsx)(o.code,{className:"language-sh",children:'cd ../logos-my-service-module\nexport LIBP2P_MODULE_CONFIG=\'{"addrs":["/ip4/127.0.0.1/tcp/9002"],"bootstrapNodes":[{"peerId":"<BOOTSTRAP_PEER_ID>","addrs":["/ip4/127.0.0.1/tcp/9000"]}]}\'\nlogoscore -D --config-dir ~/.logoscore-discoverer \\\n -m ../logos-libp2p-module/result/modules -m ./result/modules &\nlogoscore --config-dir ~/.logoscore-discoverer load-module libp2p_module\nlogoscore --config-dir ~/.logoscore-discoverer load-module my_service_module\nlogoscore --config-dir ~/.logoscore-discoverer call my_service_module startDiscovery\nlogoscore --config-dir ~/.logoscore-discoverer call my_service_module discover myservice/v1\n'})}),"\n",(0,n.jsx)(o.p,{children:"Expected output:"}),"\n",(0,n.jsx)(o.pre,{children:(0,n.jsx)(o.code,{className:"language-json",children:'{"method":"discover","module":"my_service_module",\n "result":"[{\\"addrs\\":[\\"/ip4/127.0.0.1/tcp/9001\\"],\\"peerId\\":\\"<ADVERTISER_PEER_ID>\\",\\"seqNo\\":1536,\\"services\\":[{\\"data\\":\\"version=1\\",\\"id\\":\\"myservice/v1\\"}]}]",\n "status":"ok"}\n'})}),"\n",(0,n.jsxs)(o.ul,{children:["\n",(0,n.jsxs)(o.li,{children:["The returned ",(0,n.jsx)(o.code,{children:"peerId"})," is the advertiser's, and ",(0,n.jsx)(o.code,{children:"addrs"})," shows its listen port (",(0,n.jsx)(o.code,{children:"9001"}),"), even though the discoverer only knew about the bootstrapping node. The ",(0,n.jsx)(o.code,{children:"services"})," entry carries the ",(0,n.jsx)(o.code,{children:"serviceData"})," (",(0,n.jsx)(o.code,{children:"version=1"}),") that A advertised."]}),"\n"]}),"\n",(0,n.jsx)(o.admonition,{type:"info"}),"\n"]}),"\n"]}),"\n",(0,n.jsxs)(o.p,{children:["A first ",(0,n.jsx)(o.code,{children:"discover"})," returning ",(0,n.jsx)(o.code,{children:"[]"})," means the advertisement has not yet propagated. Repeat the call after a few seconds.\n:::"]}),"\n",(0,n.jsxs)(o.ol,{children:["\n",(0,n.jsxs)(o.li,{children:["\n",(0,n.jsx)(o.p,{children:"Tear down all three daemons:"}),"\n",(0,n.jsx)(o.pre,{children:(0,n.jsx)(o.code,{className:"language-sh",children:"for D in bootstrap advertiser discoverer; do\n logoscore --config-dir ~/.logoscore-$D call my_service_module stopDiscovery\n logoscore --config-dir ~/.logoscore-$D stop\ndone\n"})}),"\n"]}),"\n"]}),"\n",(0,n.jsx)(o.h2,{id:"troubleshooting-service-discovery",children:"Troubleshooting service discovery"}),"\n",(0,n.jsxs)(o.h3,{id:"why-does-discover-return--even-after-waiting",children:["Why does ",(0,n.jsx)(o.code,{children:"discover"})," return ",(0,n.jsx)(o.code,{children:"[]"})," even after waiting?"]}),"\n",(0,n.jsxs)(o.p,{children:["The Kad-DHT needs a few seconds to propagate the advertisement from the advertiser through the bootstrap node to the discoverer. Repeat ",(0,n.jsx)(o.code,{children:"logoscore --config-dir ~/.logoscore-discoverer call my_service_module discover myservice/v1"})," after 5\u201310 seconds. If it still returns empty, confirm that the advertiser's ",(0,n.jsx)(o.code,{children:"advertise"})," call succeeded and that both the advertiser and the discoverer share the same ",(0,n.jsx)(o.code,{children:"<BOOTSTRAP_PEER_ID>"})," for the bootstrap node."]}),"\n",(0,n.jsxs)(o.h3,{id:"why-does-a-logoscore-call-hang-with-a-timeout-error",children:["Why does a ",(0,n.jsx)(o.code,{children:"logoscore call"})," hang with a timeout error?"]}),"\n",(0,n.jsxs)(o.p,{children:["You may be using the one-shot ",(0,n.jsx)(o.code,{children:'-c "module.method()"'})," or ",(0,n.jsx)(o.code,{children:"--quit-on-finish"})," form instead of the ",(0,n.jsx)(o.code,{children:"logoscore call"})," subcommand. The one-shot client does not await async lifecycle calls such as ",(0,n.jsx)(o.code,{children:"start()"}),", which causes a spurious ",(0,n.jsx)(o.code,{children:"Timeout waiting for \u2026"})," before the node finishes starting up. Use ",(0,n.jsx)(o.code,{children:"logoscore call my_service_module <method>"})," for all lifecycle and discovery calls."]})]})}function h(e={}){let{wrapper:o}={...(0,i.R)(),...e.components};return o?(0,n.jsx)(o,{...e,children:(0,n.jsx)(a,{...e})}):a(e)}},8453(e,o,s){s.d(o,{R:()=>d,x:()=>l});var r=s(6540);let n={},i=r.createContext(n);function d(e){let o=r.useContext(i);return r.useMemo(function(){return"function"==typeof e?e(o):{...o,...e}},[o,e])}function l(e){let o;return o=e.disableParentContext?"function"==typeof e.components?e.components(n):e.components||n:d(e.components),r.createElement(i.Provider,{value:o},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/5a6d5130.4963629f.js b/assets/js/5a6d5130.4963629f.js new file mode 100644 index 00000000..014a5c4f --- /dev/null +++ b/assets/js/5a6d5130.4963629f.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunklogos_docs=self.webpackChunklogos_docs||[]).push([["2573"],{4933(e,t,n){n.r(t),n.d(t,{metadata:()=>o,default:()=>h,frontMatter:()=>a,contentTitle:()=>r,toc:()=>c,assets:()=>i});var o=JSON.parse('{"id":"lez/follow-chain/start-and-use-instance-of-lez-explorer","title":"Start and use an instance of the LEZ Explorer","description":"Get a clear view of on-chain activity including blocks, transactions, and accounts.","source":"@site/docs/lez/follow-chain/start-and-use-instance-of-lez-explorer.md","sourceDirName":"lez/follow-chain","slug":"/lez/follow-chain/start-and-use-instance-of-lez-explorer","permalink":"/lez/follow-chain/start-and-use-instance-of-lez-explorer","draft":false,"unlisted":false,"editUrl":"https://github.com/logos-co/logos-docs/tree/main/docs/lez/follow-chain/start-and-use-instance-of-lez-explorer.md","tags":[],"version":"current","sidebarPosition":2,"frontMatter":{"title":"Start and use an instance of the LEZ Explorer","doc_type":"procedure","product":"lez","topics":"lez","steps_layout":"flat","authors":"arjentix, kashepavadan","owner":"logos","doc_version":1,"slug":"start-and-use-instance-of-lez-explorer","sidebar_position":2},"sidebar":"lezSidebar","previous":{"title":"Run an LEZ Indexer","permalink":"/lez/follow-chain/run-lez-indexer"},"next":{"title":"Set up a shared private account","permalink":"/lez/accounts/set-up-shared-private-lez-account"}}'),s=n(4848),l=n(8453);let a={title:"Start and use an instance of the LEZ Explorer",doc_type:"procedure",product:"lez",topics:"lez",steps_layout:"flat",authors:"arjentix, kashepavadan",owner:"logos",doc_version:1,slug:"start-and-use-instance-of-lez-explorer",sidebar_position:2},r="Start and use an instance of the LEZ Explorer",i={},c=[{value:"Get a clear view of on-chain activity including blocks, transactions, and accounts.",id:"get-a-clear-view-of-on-chain-activity-including-blocks-transactions-and-accounts",level:4},{value:"What to expect",id:"what-to-expect",level:2},{value:"Start the LEZ Explorer",id:"start-the-lez-explorer",level:2},{value:"Browse blocks, transactions, and accounts",id:"browse-blocks-transactions-and-accounts",level:2},{value:"Troubleshooting LEZ Explorer",id:"troubleshooting-lez-explorer",level:2},{value:"Why does the Explorer show "No blocks found" after startup?",id:"why-does-the-explorer-show-no-blocks-found-after-startup",level:3},{value:"Why does LEZ Explorer fail to load block data?",id:"why-does-lez-explorer-fail-to-load-block-data",level:3}];function d(e){let t={a:"a",admonition:"admonition",code:"code",h1:"h1",h2:"h2",h3:"h3",h4:"h4",header:"header",img:"img",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,l.R)(),...e.components};return(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(t.header,{children:(0,s.jsx)(t.h1,{id:"start-and-use-an-instance-of-the-lez-explorer",children:"Start and use an instance of the LEZ Explorer"})}),"\n",(0,s.jsx)(t.h4,{id:"get-a-clear-view-of-on-chain-activity-including-blocks-transactions-and-accounts",children:"Get a clear view of on-chain activity including blocks, transactions, and accounts."}),"\n",(0,s.jsxs)(t.p,{children:["The ",(0,s.jsx)(t.a,{href:"https://docs.logos.co/get-started/glossary#lez",children:"LEZ"})," Explorer lets you inspect the state of the ",(0,s.jsx)(t.a,{href:"https://docs.logos.co/get-started/glossary#logos-execution-zone",children:"Logos Execution Zone"})," in real time. This procedure walks you through starting all required services locally and using the Explorer UI to browse blocks, search transactions, and look up ",(0,s.jsx)(t.a,{href:"https://docs.logos.co/get-started/glossary#account",children:"account"})," balances. It is intended for developers working on testnet v0.2 who want to verify on-chain state or test wallet interactions."]}),"\n",(0,s.jsx)(t.admonition,{type:"info",children:(0,s.jsxs)(t.p,{children:["If you don't want to run your own LEZ explorer instance, navigate to the ",(0,s.jsx)(t.a,{href:"https://explorer.testnet.lez.logos.co/",children:"public LEZ explorer"}),"."]})}),"\n",(0,s.jsx)(t.p,{children:"Before you start, make sure you have the following:"}),"\n",(0,s.jsxs)(t.ul,{children:["\n",(0,s.jsx)(t.li,{children:"Linux or macOS operating system"}),"\n",(0,s.jsxs)(t.li,{children:[(0,s.jsx)(t.a,{href:"https://docs.docker.com/get-docker/",children:"Docker"}),", ",(0,s.jsx)(t.a,{href:"https://www.rust-lang.org/tools/install",children:"Rust toolchain"}),", and ",(0,s.jsx)(t.code,{children:"cargo"})," installed"]}),"\n",(0,s.jsxs)(t.li,{children:[(0,s.jsx)(t.a,{href:"https://crates.io/crates/cargo-leptos",children:(0,s.jsx)(t.code,{children:"cargo-leptos"})})," installed"]}),"\n",(0,s.jsxs)(t.li,{children:[(0,s.jsx)(t.a,{href:"https://github.com/casey/just",children:(0,s.jsx)(t.code,{children:"just"})})," installed"]}),"\n",(0,s.jsxs)(t.li,{children:["A local clone of the ",(0,s.jsx)(t.a,{href:"https://github.com/logos-blockchain/logos-execution-zone/",children:"LEZ repository"})]}),"\n",(0,s.jsxs)(t.li,{children:["A running instance of an ",(0,s.jsx)(t.a,{href:"/lez/follow-chain/run-lez-indexer",children:"LEZ Indexer"})]}),"\n"]}),"\n",(0,s.jsx)(t.h2,{id:"what-to-expect",children:"What to expect"}),"\n",(0,s.jsxs)(t.ul,{children:["\n",(0,s.jsx)(t.li,{children:"You can browse all committed blocks and click through to inspect individual block details."}),"\n",(0,s.jsx)(t.li,{children:"You can search for transactions and accounts and view their current state and balances."}),"\n"]}),"\n",(0,s.jsx)(t.h2,{id:"start-the-lez-explorer",children:"Start the LEZ Explorer"}),"\n",(0,s.jsxs)(t.ol,{children:["\n",(0,s.jsxs)(t.li,{children:["\n",(0,s.jsxs)(t.p,{children:["Navigate to your local clone of the ",(0,s.jsx)(t.a,{href:"https://github.com/logos-blockchain/logos-execution-zone/",children:"LEZ repository"}),"."]}),"\n"]}),"\n",(0,s.jsxs)(t.li,{children:["\n",(0,s.jsx)(t.p,{children:"In a new terminal window, start the LEZ Explorer:"}),"\n",(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-bash",children:"just run-explorer\n"})}),"\n",(0,s.jsx)(t.admonition,{type:"info",children:(0,s.jsxs)(t.p,{children:["By default, the LEZ Explorer connects to the LEZ Indexer at ",(0,s.jsx)(t.code,{children:"http://localhost:8779"}),". Set the ",(0,s.jsx)(t.code,{children:"INDEXER_RPC_URL"})," environment variable or pass ",(0,s.jsx)(t.code,{children:"--indexer-rpc-url"})," to use a different address."]})}),"\n"]}),"\n",(0,s.jsxs)(t.li,{children:["\n",(0,s.jsxs)(t.p,{children:["Open ",(0,s.jsx)(t.code,{children:"http://localhost:3000/"})," in your browser."]}),"\n",(0,s.jsxs)(t.p,{children:["You will see a ",(0,s.jsx)(t.strong,{children:"No blocks found"})," message initially:"]}),"\n",(0,s.jsx)(t.p,{children:(0,s.jsx)(t.img,{alt:"No blocks found screen",src:n(8914).A+"",width:"1691",height:"1353"})}),"\n",(0,s.jsx)(t.p,{children:"After approximately one minute, refresh the page. Blocks will appear:"}),"\n",(0,s.jsx)(t.p,{children:(0,s.jsx)(t.img,{alt:"Blocks visible after refresh",src:n(7950).A+"",width:"1691",height:"1353"})}),"\n"]}),"\n"]}),"\n",(0,s.jsx)(t.h2,{id:"browse-blocks-transactions-and-accounts",children:"Browse blocks, transactions, and accounts"}),"\n",(0,s.jsxs)(t.ol,{children:["\n",(0,s.jsxs)(t.li,{children:["\n",(0,s.jsx)(t.p,{children:"Click any block in the list to view its details."}),"\n",(0,s.jsx)(t.p,{children:(0,s.jsx)(t.img,{alt:"Block detail view",src:n(4595).A+"",width:"1691",height:"1353"})}),"\n"]}),"\n",(0,s.jsxs)(t.li,{children:["\n",(0,s.jsx)(t.p,{children:"Use the search bar to look up a block, transaction, or account by ID."}),"\n",(0,s.jsx)(t.p,{children:(0,s.jsx)(t.img,{alt:"Transaction search result",src:n(8252).A+"",width:"1691",height:"1353"})}),"\n",(0,s.jsx)(t.p,{children:(0,s.jsx)(t.img,{alt:"Account detail view",src:n(1661).A+"",width:"1691",height:"1353"})}),"\n"]}),"\n"]}),"\n",(0,s.jsx)(t.h2,{id:"troubleshooting-lez-explorer",children:"Troubleshooting LEZ Explorer"}),"\n",(0,s.jsx)(t.h3,{id:"why-does-the-explorer-show-no-blocks-found-after-startup",children:'Why does the Explorer show "No blocks found" after startup?'}),"\n",(0,s.jsx)(t.p,{children:"Block production takes up to one minute after the stack starts. Refresh the page manually; live updates are not yet implemented."}),"\n",(0,s.jsx)(t.h3,{id:"why-does-lez-explorer-fail-to-load-block-data",children:"Why does LEZ Explorer fail to load block data?"}),"\n",(0,s.jsxs)(t.p,{children:["Confirm that the LEZ Indexer is running and reachable. By default, the Explorer connects to ",(0,s.jsx)(t.code,{children:"http://localhost:8779"}),". Check that ",(0,s.jsx)(t.code,{children:"just run-indexer"})," is active in its terminal and that no firewall rule is blocking port ",(0,s.jsx)(t.code,{children:"8779"}),"."]})]})}function h(e={}){let{wrapper:t}={...(0,l.R)(),...e.components};return t?(0,s.jsx)(t,{...e,children:(0,s.jsx)(d,{...e})}):d(e)}},1661(e,t,n){n.d(t,{A:()=>o});let o=n.p+"assets/images/account-details-aba1467c4d6729a85e626e0e6044a3f8.png"},4595(e,t,n){n.d(t,{A:()=>o});let o=n.p+"assets/images/block-details-6baf61eea5cadd486cfeb906aaba6667.png"},8914(e,t,n){n.d(t,{A:()=>o});let o=n.p+"assets/images/no-blocks-found-f3d4874412ebfadc41523d4574353eb6.png"},8252(e,t,n){n.d(t,{A:()=>o});let o=n.p+"assets/images/transaction-details-6aa7a80ca79c330c300ba09416746863.png"},7950(e,t,n){n.d(t,{A:()=>o});let o=n.p+"assets/images/updated-page-1936af3c9c3552cd02cd3bb895548a50.png"},8453(e,t,n){n.d(t,{R:()=>a,x:()=>r});var o=n(6540);let s={},l=o.createContext(s);function a(e){let t=o.useContext(l);return o.useMemo(function(){return"function"==typeof e?e(t):{...t,...e}},[t,e])}function r(e){let t;return t=e.disableParentContext?"function"==typeof e.components?e.components(s):e.components||s:a(e.components),o.createElement(l.Provider,{value:t},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/5b9d1339.6131e2fb.js b/assets/js/5b9d1339.6131e2fb.js new file mode 100644 index 00000000..9cef8d0c --- /dev/null +++ b/assets/js/5b9d1339.6131e2fb.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunklogos_docs=self.webpackChunklogos_docs||[]).push([["4462"],{3080(e,n,t){t.r(n),t.d(n,{metadata:()=>s,default:()=>h,frontMatter:()=>a,contentTitle:()=>r,toc:()=>c,assets:()=>l});var s=JSON.parse('{"id":"build-an-app/tutorials/build-dapp-with-logos-zone-sdk","title":"Build a dApp with the Logos Zone SDK","description":"Learn how to use the Zone SDK to implement a decentralised password manager application.","source":"@site/docs/build-an-app/tutorials/build-dapp-with-logos-zone-sdk.md","sourceDirName":"build-an-app/tutorials","slug":"/build-an-app/tutorials/build-dapp-with-logos-zone-sdk","permalink":"/build-an-app/tutorials/build-dapp-with-logos-zone-sdk","draft":false,"unlisted":false,"editUrl":"https://github.com/logos-co/logos-docs/tree/main/docs/build-an-app/tutorials/build-dapp-with-logos-zone-sdk.md","tags":[],"version":"current","frontMatter":{"title":"Build a dApp with the Logos Zone SDK","doc_type":"procedure","product":"blockchain","topics":["channels","zones"],"steps_layout":"sectioned","authors":"kashepavadan","owner":"logos","doc_version":1,"slug":"build-dapp-with-logos-zone-sdk"},"sidebar":"buildAnAppSidebar","previous":{"title":"Create and use an AMM liquidity pool on the Logos Execution Zone","permalink":"/build-an-app/tutorials/create-and-use-an-amm-liquidity-pool-on-the-logos-execution-zone"}}'),i=t(4848),o=t(8453);let a={title:"Build a dApp with the Logos Zone SDK",doc_type:"procedure",product:"blockchain",topics:["channels","zones"],steps_layout:"sectioned",authors:"kashepavadan",owner:"logos",doc_version:1,slug:"build-dapp-with-logos-zone-sdk"},r="Build a dApp with the Logos Zone SDK",l={},c=[{value:"Learn how to use the Zone SDK to implement a decentralised password manager application.",id:"learn-how-to-use-the-zone-sdk-to-implement-a-decentralised-password-manager-application",level:4},{value:"What to expect",id:"what-to-expect",level:2},{value:"Set up the repository",id:"set-up-the-repository",level:2},{value:"Sequencer",id:"sequencer",level:2},{value:"Step 1: Initialise the <code>ZoneSequencer</code> struct",id:"step-1-initialise-the-zonesequencer-struct",level:3},{value:"Step 2: Publish data to the channel",id:"step-2-publish-data-to-the-channel",level:3},{value:"Step 3: Handle channel events",id:"step-3-handle-channel-events",level:3},{value:"Indexer",id:"indexer",level:2},{value:"Step 1: Initialise the <code>ZoneIndexer</code> struct",id:"step-1-initialise-the-zoneindexer-struct",level:3},{value:"Step 2: Follow the channel",id:"step-2-follow-the-channel",level:3},{value:"Frequently asked questions",id:"frequently-asked-questions",level:2},{value:"What other Zones can I build with the SDK?",id:"what-other-zones-can-i-build-with-the-sdk",level:3},{value:"Where do I find instructions for running the demo?",id:"where-do-i-find-instructions-for-running-the-demo",level:3}];function d(e){let n={a:"a",admonition:"admonition",code:"code",h1:"h1",h2:"h2",h3:"h3",h4:"h4",header:"header",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,o.R)(),...e.components};return(0,i.jsxs)(i.Fragment,{children:[(0,i.jsx)(n.header,{children:(0,i.jsx)(n.h1,{id:"build-a-dapp-with-the-logos-zone-sdk",children:"Build a dApp with the Logos Zone SDK"})}),"\n",(0,i.jsx)(n.h4,{id:"learn-how-to-use-the-zone-sdk-to-implement-a-decentralised-password-manager-application",children:"Learn how to use the Zone SDK to implement a decentralised password manager application."}),"\n",(0,i.jsxs)(n.p,{children:["This tutorial covers building a Logos ",(0,i.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#zone",children:"Zone"})," from scratch using the ",(0,i.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#zone-sdk",children:"Zone SDK"}),". Zones are execution environments built on the ",(0,i.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#logos-blockchain",children:"Logos Blockchain"})," that post data as on-chain inscriptions via Logos channels. A Zone can host a versatile rollup with thousands of applications, such as the ",(0,i.jsx)(n.a,{href:"/lez/get-started/lez-quickstart",children:"Logos Execution Zone"}),", or a simple standalone Zone tracking the state of a single application."]}),"\n",(0,i.jsxs)(n.p,{children:["The Zone SDK provides a ready-to-use toolbox for basic interactions with a Logos Zone. Every Zone is operated by one or more ",(0,i.jsx)(n.strong,{children:"sequencers"}),", which collect transactions, batch them, and publish them as inscriptions on the Logos Blockchain. ",(0,i.jsx)(n.strong,{children:"Indexers"})," are nodes that follow a Zone's updates on-chain, re-executing them locally to maintain an up-to-date copy of the Zone state. The Zone SDK supports building both."]}),"\n",(0,i.jsxs)(n.p,{children:["The tutorial uses a ",(0,i.jsx)(n.a,{href:"https://github.com/H2CO3/steelsafe",children:"password manager"})," Zone as the running example: a single sequencer posts SQL transactions on-chain, and one or more indexers replay those transactions to sync their local state. The password manager maintains state in a SQLite database, with updates taking the form of SQL transactions applied to that database. The Zone design maps onto those concepts as follows:"]}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:["The user interacts with the password manager on the main device (in the ",(0,i.jsx)(n.code,{children:"sequencer"})," folder) to add or update passwords."]}),"\n",(0,i.jsxs)(n.li,{children:["The main device operates as a ",(0,i.jsx)(n.strong,{children:"sequencer"}),", posting SQL transactions as inscriptions to its ",(0,i.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#channel",children:"channel"}),"."]}),"\n",(0,i.jsxs)(n.li,{children:["Secondary devices operate as ",(0,i.jsx)(n.strong,{children:"indexers"}),", following the channel and obtaining SQL transactions from the chain."]}),"\n",(0,i.jsxs)(n.li,{children:["Secondary devices run a read-only version of the password manager (in the ",(0,i.jsx)(n.code,{children:"indexer"})," folder), applying SQL transactions from the channel to update their local state."]}),"\n"]}),"\n",(0,i.jsx)(n.admonition,{type:"info",children:(0,i.jsxs)(n.p,{children:["Read ",(0,i.jsx)(n.a,{href:"https://press.logos.co/article/decentralise-log-not-server",children:(0,i.jsx)(n.strong,{children:"Decentralise the Log, Not the Server"})})," for the motivation behind this design."]})}),"\n",(0,i.jsxs)(n.p,{children:["The implementation skeleton already has most of the password manager code written. This tutorial will focus on using the Zone SDK to write the sequencer and indexer functionality, contained primarily in the ",(0,i.jsx)(n.code,{children:"sequencer/src/sequencer.rs"})," and ",(0,i.jsx)(n.code,{children:"indexer/src/indexer.rs"})," files."]}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.strong,{children:"Before you start:"})}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsx)(n.li,{children:(0,i.jsx)(n.a,{href:"https://git-scm.com/",children:"Git"})}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.a,{href:"https://www.rust-lang.org/tools/install",children:"Rust toolchain"})," (stable)"]}),"\n",(0,i.jsxs)(n.li,{children:["Access to a running Logos Blockchain node (see ",(0,i.jsx)(n.a,{href:"/blockchain/get-started/run-a-logos-blockchain-node-from-cli",children:"documentation"}),")"]}),"\n"]}),"\n",(0,i.jsx)(n.h2,{id:"what-to-expect",children:"What to expect"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:["You will configure a ",(0,i.jsx)(n.code,{children:"ZoneSequencer"})," that signs and publishes SQL transactions as on-chain inscriptions."]}),"\n",(0,i.jsxs)(n.li,{children:["You will configure a ",(0,i.jsx)(n.code,{children:"ZoneIndexer"})," that follows a channel and applies incoming inscriptions to a local database."]}),"\n",(0,i.jsx)(n.li,{children:"You will have a compilable password manager Zone demo that illustrates core Zone SDK patterns."}),"\n"]}),"\n",(0,i.jsx)(n.h2,{id:"set-up-the-repository",children:"Set up the repository"}),"\n",(0,i.jsxs)(n.p,{children:["Clone the ",(0,i.jsx)(n.a,{href:"https://github.com/logos-blockchain/logos-sql-zone",children:"Logos SQL Zone"})," repository, which contains all dependencies and password manager code unrelated to Zone functionality."]}),"\n",(0,i.jsxs)(n.ol,{children:["\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsx)(n.p,{children:"Clone the repository:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:"git clone https://github.com/logos-blockchain/logos-sql-zone.git\n"})}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsxs)(n.p,{children:["Switch to the ",(0,i.jsx)(n.code,{children:"tutorial"})," branch and initialise submodules:"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:"cd logos-sql-zone\ngit checkout tutorial\n\ngit submodule update --init --recursive\n"})}),"\n",(0,i.jsxs)(n.p,{children:["The complete demo is available in the ",(0,i.jsx)(n.code,{children:"master"})," branch."]}),"\n"]}),"\n"]}),"\n",(0,i.jsx)(n.h2,{id:"sequencer",children:"Sequencer"}),"\n",(0,i.jsx)(n.p,{children:"The sequencer is the node that accepts user transactions, batches them, and posts them as inscriptions to the Logos Blockchain. The steps in this section implement the sequencer using the Zone SDK, using the password manager Zone as the example."}),"\n",(0,i.jsxs)(n.h3,{id:"step-1-initialise-the-zonesequencer-struct",children:["Step 1: Initialise the ",(0,i.jsx)(n.code,{children:"ZoneSequencer"})," struct"]}),"\n",(0,i.jsxs)(n.p,{children:["Your sequencer wraps the ",(0,i.jsx)(n.code,{children:"ZoneSequencer"})," struct from the Zone SDK, found in ",(0,i.jsx)(n.code,{children:"logos-blockchain/zone-sdk/src/sequencer/zone_sequencer.rs"}),". Initialising it requires these arguments:"]}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"channel_id: ChannelId"})," \u2014 the ID of the channel associated with the Zone."]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"signing_key: Ed25519Key"})," \u2014 a key authorised to post updates to the channel."]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"node: Node"})," \u2014 a ",(0,i.jsx)(n.code,{children:"Node"})," struct referring to your Logos Blockchain node, created by ",(0,i.jsx)(n.code,{children:"NodeHttpClient::new()"}),"."]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"checkpoint: Option<SequencerCheckpoint>"})," \u2014 (optional) the checkpoint representing the most recently pushed channel update."]}),"\n"]}),"\n",(0,i.jsxs)(n.p,{children:["Before posting to a new channel, the sequencer must generate an Ed25519 public/private key pair. ",(0,i.jsx)(n.strong,{children:"The initial public key defines the channel ID; the private key becomes the first authorised signing key."})," The channel is created when the sequencer posts a ",(0,i.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#message",children:"message"})," with this channel ID, unless the channel already exists. Additional keys can be authorised via the ",(0,i.jsx)(n.a,{href:"https://nomos-tech.notion.site/v1-2-Mantle-Specification-2ce261aa09df805ea358d80c2046cf95",children:"CHANNEL_CONFIG"})," ",(0,i.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#mantle",children:"Mantle"})," Operation."]}),"\n",(0,i.jsxs)(n.p,{children:["After the first channel message, each subsequent message includes a hash reference to the previous one. A message with an incorrect parent hash is rejected. ",(0,i.jsx)(n.strong,{children:"Providing a checkpoint lets the sequencer resume posting after a restart."})," A checkpoint is not required during the session that creates a new channel."]}),"\n",(0,i.jsxs)(n.ol,{children:["\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsxs)(n.p,{children:["In ",(0,i.jsx)(n.code,{children:"sequencer/src/sequencer.rs"}),", add the ",(0,i.jsx)(n.code,{children:"Sequencer"})," struct definition:"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-rust",children:"// The sequencer that handles transactions using the Zone SDK\n//\n// sequencer: The ZoneSequencer instance used by our wrapper\n// client: Async handle for submitting requests to Zone SDK sequencer\n// state: A helper struct for keeping track of transaction state,\n// see more on this below.\n// queue_file: The path to a file that holds SQL transactions not yet posted to the channel\n// checkpoint_path: The path to the channel checkpoint file\npub struct Sequencer {\n sequencer: ZoneSequencer<NodeHttpClient>,\n client: SequencerClient,\n state: InMemoryZoneState,\n queue_file: String,\n checkpoint_path: String,\n}\n"})}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsx)(n.p,{children:"Add a function to load or generate a private signing key:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-rust",children:'// Load signing key from file or generate a new one if it doesn\'t exist\n//\n// path: The path to the signing key file\nfn load_or_create_signing_key(path: &Path) -> Ed25519Key {\n if path.exists() {\n let key_bytes = fs::read(path).expect("failed to read key file");\n assert!(\n key_bytes.len() == ED25519_SECRET_KEY_SIZE,\n "invalid key file: expected {} bytes, got {}",\n ED25519_SECRET_KEY_SIZE,\n key_bytes.len()\n );\n let key_array: [u8; ED25519_SECRET_KEY_SIZE] =\n key_bytes.try_into().expect("length already checked");\n Ed25519Key::from_bytes(&key_array)\n } else {\n let mut key_bytes = [0u8; ED25519_SECRET_KEY_SIZE];\n rand::RngCore::fill_bytes(&mut rand::rng(), &mut key_bytes);\n fs::write(path, key_bytes).expect("failed to write key file");\n Ed25519Key::from_bytes(&key_bytes)\n }\n}\n'})}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsx)(n.p,{children:"Add a function to restore from a saved checkpoint:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-rust",children:'// Restore from saved checkpoint\n//\n// path: Path to checkpoint file\nfn load_checkpoint(path: &Path) -> Option<SequencerCheckpoint> {\n if !path.exists() {\n return None;\n }\n let data = fs::read(path).expect("failed to read checkpoint file");\n Some(serde_json::from_slice(&data).expect("failed to deserialize checkpoint"))\n}\n'})}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsxs)(n.p,{children:["Add the ",(0,i.jsx)(n.code,{children:"new"})," function to the ",(0,i.jsx)(n.code,{children:"Sequencer"})," struct implementation:"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-rust",children:'impl Sequencer {\n\n // Create a new Sequencer\n //\n // node_endpoint: Address of Logos Blockchain node\n // signing_key_path: Path to file containing signing key\n // node_auth_username: Username to access node\n // node_auth_password: Password to access node\n // queue_file: Path to file storing queued SQL statements\n // checkpoint_path: Path to file containing latest channel checkpoint\n // channel_path: Path to file with channel ID\n pub async fn new(\n node_endpoint: &str,\n signing_key_path: &str,\n node_auth_username: Option<String>,\n node_auth_password: Option<String>,\n queue_file: &str,\n checkpoint_path: &str,\n channel_path: &str,\n ) -> Result<Self> {\n let node_url = Url::parse(node_endpoint).map_err(|e| SequencerError::Url(e.to_string()))?;\n\n let basic_auth = node_auth_username\n .map(|username| BasicAuthCredentials::new(username, node_auth_password));\n\n // Create files from paths if they don\'t exist\n for path in [signing_key_path, checkpoint_path, channel_path] {\n if let Some(parent) = Path::new(path).parent() {\n fs::create_dir_all(parent)?;\n }\n }\n\n let checkpoint = load_checkpoint(Path::new(checkpoint_path));\n if checkpoint.is_some() {\n println!(" Restored checkpoint from {checkpoint_path}");\n }\n\n // Produce channel ID from signing key\n let signing_key = load_or_create_signing_key(Path::new(signing_key_path));\n let channel_id = ChannelId::from(signing_key.public_key().to_bytes());\n fs::write(channel_path, hex::encode(channel_id.as_ref()))\n .expect("failed to write channel id");\n\n // Initialise the ZoneSequencer\n let node = NodeHttpClient::new(CommonHttpClient::new(basic_auth), node_url);\n let sequencer = ZoneSequencer::init(channel_id, signing_key, node, checkpoint);\n let client = sequencer.client();\n\n Ok(Self {\n sequencer,\n client,\n state: InMemoryZoneState::default(),\n queue_file: queue_file.to_owned(),\n checkpoint_path: checkpoint_path.to_owned(),\n })\n }\n\n ...\n\n}\n'})}),"\n"]}),"\n"]}),"\n",(0,i.jsx)(n.h3,{id:"step-2-publish-data-to-the-channel",children:"Step 2: Publish data to the channel"}),"\n",(0,i.jsxs)(n.p,{children:["Once the ",(0,i.jsx)(n.code,{children:"ZoneSequencer"})," is set up, posting data to the channel is as simple as passing a byte vector to the sequencer's ",(0,i.jsx)(n.code,{children:"publish"})," function, which returns an ",(0,i.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#inscription",children:"inscription"})," ID and the current checkpoint."]}),"\n",(0,i.jsxs)(n.p,{children:["Whenever the password manager's database is updated, the SQL transactions are also written to a queue file. This functionality is already implemented in the ",(0,i.jsx)(n.code,{children:"sequencer/src/db.rs"})," file. A processing loop continuously checks for new SQL transactions from the password database and submits them as plain text to the Zone."]}),"\n",(0,i.jsxs)(n.ol,{children:["\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsxs)(n.p,{children:["In ",(0,i.jsx)(n.code,{children:"sequencer/src/sequencer.rs"}),", add a function to save checkpoints after each published update:"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-rust",children:'// Write latest checkpoint to file\n//\n// path: Path to checkpoint file\n// checkpoint: Checkpoint message\nfn save_checkpoint(path: &Path, checkpoint: &SequencerCheckpoint) {\n let data = serde_json::to_vec(checkpoint).expect("failed to serialize checkpoint");\n fs::write(path, data).expect("failed to write checkpoint file");\n}\n'})}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsx)(n.p,{children:"Add a function to read pending SQL transactions from the queue file and clear it:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-rust",children:"// Drain the queue file and return all pending queries\n//\n// queue_file: File for queued SQL statements\nfn queue_drain(queue_file: &str) -> Result<Vec<String>> {\n\n // Check if queue_file is empty\n let file = match OpenOptions::new().read(true).write(true).open(queue_file) {\n Ok(f) => f,\n Err(e) if e.kind() == io::ErrorKind::NotFound => return Ok(Vec::new()),\n Err(e) => return Err(SequencerError::Io(e)),\n };\n\n file.lock_exclusive()?;\n\n let reader = BufReader::new(&file);\n let mut queue_vec = Vec::new();\n for query in reader.lines() {\n queue_vec.push(query?);\n }\n\n // Clear queue file\n file.set_len(0)?;\n\n Ok(queue_vec)\n}\n"})}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsx)(n.p,{children:"Add a function to publish the pending SQL transactions as a single inscription:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-rust",children:'// Process all pending queries as a single inscription\n//\n// queue_file: File for queued SQL statements\nasync fn process_pending_batch(queue_file: &str, client: &SequencerClient) -> Result<()> {\n\n // Read list of pending SQL statements\n let pending = queue_drain(queue_file)?;\n if pending.is_empty() {\n return Ok(());\n }\n\n let count = pending.len();\n debug!("Processing batch of {} queries", count);\n\n // Publish batch of statements\n let sql_bytes = pending.join("\\n").into_bytes();\n let inscription = Inscription::try_from(sql_bytes)\n .map_err(|e| SequencerError::InscriptionTooLarge(e.to_string()))?;\n if let Err(e) = client.publish(inscription).await {\n error!("failed to publish batch: {e}");\n } else {\n info!("Submitted batch of {} statement(s)", count);\n }\n\n Ok(())\n}\n'})}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsxs)(n.p,{children:["Add the ",(0,i.jsx)(n.code,{children:"run"})," function to the ",(0,i.jsx)(n.code,{children:"Sequencer"})," struct implementation to poll the queue and publish its contents:"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-rust",children:'impl Sequencer {\n\n ...\n\n // Processing loop\n pub async fn run(self) {\n let Self { mut sequencer, client, mut state, queue_file, checkpoint_path } = self;\n\n // Loop to check queue and publish\n let batch_client = client;\n tokio::spawn(async move {\n // Wait until the sequencer completes cold-start backfill before publishing.\n let mut ready_rx = batch_client.subscribe_ready();\n drop(ready_rx.wait_for(|r| *r).await);\n\n let mut interval = tokio::time::interval(Duration::from_millis(100));\n loop {\n interval.tick().await;\n if let Err(e) = process_pending_batch(&queue_file, &batch_client).await {\n error!("Batch processing failed: {e}");\n }\n }\n });\n\n ...\n\n }\n}\n'})}),"\n"]}),"\n"]}),"\n",(0,i.jsx)(n.h3,{id:"step-3-handle-channel-events",children:"Step 3: Handle channel events"}),"\n",(0,i.jsxs)(n.p,{children:["The sequencer must track the Zone state on the blockchain so it can detect reorgs, finalized blocks, and updates from other sequencers. In our example, there is only one sequencer, and therefore the state only has to track which inscriptions have finalized. The Zone SDK communicates these via ",(0,i.jsx)(n.code,{children:"Event"}),":"]}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"Ready"})," \u2014 the sequencer is caught up and ready to accept updates."]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"BlocksProcessed"})," \u2014 a new block was processed. Includes the latest ",(0,i.jsx)(n.code,{children:"checkpoint"})," and the list of ",(0,i.jsx)(n.code,{children:"finalized"})," transactions. It also carries a ",(0,i.jsx)(n.code,{children:"channel_update"})," (",(0,i.jsx)(n.code,{children:"adopted"})," / ",(0,i.jsx)(n.code,{children:"orphaned"})," inscriptions), but that is only relevant when several sequencers share a channel \u2014 ",(0,i.jsx)(n.strong,{children:"a centralized Zone ignores it"}),"."]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"MempoolPending"})," \u2014 transaction was accepted by node API and is waiting in mempool."]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"TurnNotification"})," \u2014 the sequencer's turn to write in a turn-based decentralised sequencing scenario (not applicable to our example)."]}),"\n"]}),"\n",(0,i.jsxs)(n.ol,{children:["\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsxs)(n.p,{children:["In ",(0,i.jsx)(n.code,{children:"common/src/state.rs"}),", add the ",(0,i.jsx)(n.code,{children:"InMemoryZoneState"})," struct to track inscription IDs:"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-rust",children:"// Keep track of Zone state in memory\n//\n// published: Inscriptions published by your sequencer but not yet finalised or orphaned\n// finalized: All finalised inscriptions\n #[derive(Default)]\n pub struct InMemoryZoneState {\n published: Vec<Msg>,\n finalized: Vec<Msg>,\n }\n\n impl ZoneState for InMemoryZoneState {\n fn on_published(&mut self, info: &InscriptionInfo) {\n self.published\n .push(Msg::from_payload(info.this_msg, &info.payload));\n }\n \n // Move our finalised publishes out of `published` and into `finalized`.\n fn on_finalized(&mut self, inscriptions: &[InscriptionInfo]) {\n for info in inscriptions {\n if let Some(i) = self\n .published\n .iter()\n .position(|m| m.msg_id == info.this_msg)\n {\n self.published.remove(i);\n }\n if !self.finalized.iter().any(|m| m.msg_id == info.this_msg) {\n self.finalized\n .push(Msg::from_payload(info.this_msg, &info.payload));\n }\n }\n }\n\n fn published(&self) -> &[Msg] {\n &self.published\n }\n\n fn finalized(&self) -> &[Msg] {\n &self.finalized\n }\n }\n"})}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsxs)(n.p,{children:["In ",(0,i.jsx)(n.code,{children:"sequencer/src/sequencer.rs"}),", add a function to handle events:"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-rust",children:'// Handle channel events\n//\n// event: Channel event enum\n// state: Zone state struct from common/src/state.rs\n// checkpoint_path: Path to checkpoint file\nfn handle_event(\n event: Event,\n state: &mut InMemoryZoneState,\n checkpoint_path: &str,\n) {\n match event {\n Event::Ready => {\n info!("Sequencer ready");\n }\n Event::BlocksProcessed { checkpoint, channel_update, finalized } => {\n\n // Add newly-finalized inscriptions to the finalized list\n let inscriptions: Vec<_> = finalized\n .into_iter()\n .flat_map(|tx| tx.ops.into_iter())\n .filter_map(|op| match op {\n FinalizedOp::Inscription(info) => Some(info),\n _ => None,\n })\n .collect();\n state.on_finalized(&inscriptions);\n save_checkpoint(Path::new(checkpoint_path), &checkpoint);\n }\n Event::MempoolPending(_) | Event::TurnNotification { .. } => {}\n }\n}\n'})}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsxs)(n.p,{children:["Add an event loop to the ",(0,i.jsx)(n.code,{children:"run"})," function in the ",(0,i.jsx)(n.code,{children:"Sequencer"})," struct:"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-rust",children:"impl Sequencer {\n\n ...\n\n // Processing loop\n pub async fn run(self) {\n\n ...\n\n loop {\n let event = sequencer.next_event().await;\n handle_event(event, &mut state, &checkpoint_path);\n }\n }\n}\n"})}),"\n"]}),"\n"]}),"\n",(0,i.jsx)(n.h2,{id:"indexer",children:"Indexer"}),"\n",(0,i.jsxs)(n.p,{children:["The indexer is the node that follows a Zone's channel on the Logos Blockchain and re-executes updates locally to maintain a current copy of the Zone state. The steps in this section implement the indexer using the Zone SDK, continuing the password manager example. The indexer must obtain the sequencer's channel ID via an ",(0,i.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#out-of-band",children:"out-of-band"})," mechanism (in our case, the ",(0,i.jsx)(n.code,{children:"channel_path"})," file) before it can start."]}),"\n",(0,i.jsxs)(n.h3,{id:"step-1-initialise-the-zoneindexer-struct",children:["Step 1: Initialise the ",(0,i.jsx)(n.code,{children:"ZoneIndexer"})," struct"]}),"\n",(0,i.jsxs)(n.p,{children:["Your indexer wraps the ",(0,i.jsx)(n.code,{children:"ZoneIndexer"})," struct from the Zone SDK, found in ",(0,i.jsx)(n.code,{children:"logos-blockchain/zone-sdk/src/indexer.rs"}),". Initialising it requires:"]}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"channel_id: ChannelId"})," \u2014 the ID of the channel associated with the Zone."]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"node: Node"})," \u2014 a ",(0,i.jsx)(n.code,{children:"Node"})," struct referring to your Logos Blockchain node, created by ",(0,i.jsx)(n.code,{children:"NodeHttpClient::new()"}),"."]}),"\n"]}),"\n",(0,i.jsxs)(n.ol,{children:["\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsxs)(n.p,{children:["In ",(0,i.jsx)(n.code,{children:"indexer/src/indexer.rs"}),", add the ",(0,i.jsx)(n.code,{children:"Indexer"})," struct definition:"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-rust",children:"// Indexer struct\n//\n// zone_indexer: SDK indexer\n// db_path: Path to password database file\npub struct Indexer {\n zone_indexer: ZoneIndexer<NodeHttpClient>,\n db_path: String,\n}\n"})}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsx)(n.p,{children:"Add a helper function to parse a channel ID from a hex string:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-rust",children:'// Parse channel ID from string\n//\n// channel_id_str: channel ID\nfn parse_channel_id(channel_id_str: &str) -> Result<ChannelId> {\n\n // string to bytes\n let decoded = hex::decode(channel_id_str).map_err(|_| {\n Error::InvalidChannelId(format!(\n "INDEXER_CHANNEL_ID must be a valid hex string, got: \'{channel_id_str}\'"\n ))\n })?;\n\n // to 32 bytes\n let channel_bytes: [u8; 32] = decoded.try_into().map_err(|v: Vec<u8>| {\n Error::InvalidChannelId(format!(\n "INDEXER_CHANNEL_ID must be exactly 64 hex characters (32 bytes), got {} characters ({} bytes)",\n v.len() * 2,\n v.len()\n ))\n })?;\n\n Ok(ChannelId::from(channel_bytes))\n}\n'})}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsxs)(n.p,{children:["Add the ",(0,i.jsx)(n.code,{children:"new"})," function to the ",(0,i.jsx)(n.code,{children:"Indexer"})," struct implementation:"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-rust",children:'impl Indexer {\n\n // Create new indexer\n //\n // db_path: Path to local password db\n // node_endpoint: Address of Logos Blockchain node\n // channel_path: Path to file with channel ID\n // node_auth_username: Username to access node\n // node_auth_password: Password to access node\n pub fn new(\n db_path: &str,\n node_endpoint: &str,\n channel_path: &str,\n node_auth_username: Option<String>,\n node_auth_password: Option<String>,\n ) -> Result<Self> {\n let node_url = Url::parse(node_endpoint).map_err(|e| Error::Url(e.to_string()))?;\n\n let basic_auth = node_auth_username\n .map(|username| BasicAuthCredentials::new(username, node_auth_password));\n\n // Parse channel ID\n let channel_id_str = fs::read_to_string(channel_path).map_err(|e| {\n Error::InvalidChannelId(format!("Failed to read channel path \'{channel_path}\': {e}"))\n })?;\n let channel_id = parse_channel_id(channel_id_str.trim())?;\n\n info!("Channel ID: {}", hex::encode(channel_id.as_ref()));\n\n // New ZoneIndexer\n let node = NodeHttpClient::new(CommonHttpClient::new(basic_auth), node_url);\n let zone_indexer = ZoneIndexer::new(channel_id, node);\n\n Ok(Self { zone_indexer, db_path: db_path.to_owned() })\n }\n\n ...\n\n}\n'})}),"\n"]}),"\n"]}),"\n",(0,i.jsx)(n.h3,{id:"step-2-follow-the-channel",children:"Step 2: Follow the channel"}),"\n",(0,i.jsxs)(n.p,{children:["An indexer uses the ",(0,i.jsx)(n.code,{children:"follow"})," function to check new blocks for channel updates. It returns a stream that can be iterated to obtain the latest messages. Once a block with a new channel message is finalised, the indexer applies the message to update its local state."]}),"\n",(0,i.jsxs)(n.ol,{children:["\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsxs)(n.p,{children:["Add the ",(0,i.jsx)(n.code,{children:"run"})," function to the ",(0,i.jsx)(n.code,{children:"Indexer"})," struct implementation:"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-rust",children:'impl Indexer {\n\n ...\n\n // Follow the Zone channel & apply updates\n pub async fn run(self) {\n\n // Open database at db_path\n let db = match DatabaseReadOnly::open(&self.db_path) {\n Ok(db) => db,\n Err(e) => {\n error!("Failed to open database: {e}");\n return;\n }\n };\n\n loop {\n\n // Follow channel & create stream\n info!("Connecting to zone block stream...");\n let stream = match self.zone_indexer.follow().await {\n Ok(s) => s,\n Err(e) => {\n error!("Failed to connect to block stream: {e}");\n tokio::time::sleep(std::time::Duration::from_secs(5)).await;\n continue;\n }\n };\n info!("Connected to zone block stream");\n\n // Get next message from stream\n futures::pin_mut!(stream);\n while let Some(zone_msg) = stream.next().await {\n\n // Ensure zone_block includes the inscriptions but not the deposit operations in the block\n let logos_blockchain_zone_sdk::ZoneMessage::Block(zone_block) = zone_msg else {\n continue;\n };\n \n let sql_text = match String::from_utf8(Vec::from(zone_block.data)) {\n Ok(s) => s,\n Err(e) => {\n error!("Zone block data is not valid UTF-8: {e}");\n continue;\n }\n };\n\n // Extract SQL statements from message\n let statements: Vec<&str> = sql_text\n .lines()\n .map(|l: &str| l.trim().trim_end_matches(\';\').trim())\n .filter(|s: &&str| !s.is_empty())\n .collect();\n\n if statements.is_empty() {\n continue;\n }\n\n info!("Applying {} SQL statement(s)", statements.len());\n\n // Apply statements to db\n for stmt in &statements {\n if let Err(e) = db.execute_batch(stmt) {\n error!("Failed to execute SQL \'{}\': {e}", stmt);\n }\n }\n info!("Applied {} statement(s)", statements.len());\n }\n\n error!("Zone block stream ended, reconnecting...");\n tokio::time::sleep(std::time::Duration::from_secs(5)).await;\n }\n }\n}\n'})}),"\n"]}),"\n"]}),"\n",(0,i.jsx)(n.h2,{id:"frequently-asked-questions",children:"Frequently asked questions"}),"\n",(0,i.jsx)(n.h3,{id:"what-other-zones-can-i-build-with-the-sdk",children:"What other Zones can I build with the SDK?"}),"\n",(0,i.jsx)(n.p,{children:"This tutorial covered a simple single-sequencer appchain, but the Zone SDK supports a much wider range. You could build traditional ZK or optimistic rollups, high-throughput appchains, or applications compartmentalised across several Zones. Logos Zones also support interoperability features such as bridging Layer 1 tokens into Zones, on-chain message passing between Zones, and complex cross-Zone transaction coordination."}),"\n",(0,i.jsx)(n.h3,{id:"where-do-i-find-instructions-for-running-the-demo",children:"Where do I find instructions for running the demo?"}),"\n",(0,i.jsxs)(n.p,{children:["Instructions for building and interacting with the sequencer and indexer applications are in the ",(0,i.jsx)(n.code,{children:"README.md"})," file in the repository root."]})]})}function h(e={}){let{wrapper:n}={...(0,o.R)(),...e.components};return n?(0,i.jsx)(n,{...e,children:(0,i.jsx)(d,{...e})}):d(e)}},8453(e,n,t){t.d(n,{R:()=>a,x:()=>r});var s=t(6540);let i={},o=s.createContext(i);function a(e){let n=s.useContext(o);return s.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function r(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(i):e.components||i:a(e.components),s.createElement(o.Provider,{value:n},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/5e7d7155.00f26f15.js b/assets/js/5e7d7155.00f26f15.js new file mode 100644 index 00000000..765439d9 --- /dev/null +++ b/assets/js/5e7d7155.00f26f15.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunklogos_docs=self.webpackChunklogos_docs||[]).push([["6511"],{1182(e,o,t){t.r(o),t.d(o,{metadata:()=>n,default:()=>h,frontMatter:()=>r,contentTitle:()=>c,toc:()=>l,assets:()=>i});var n=JSON.parse('{"id":"blockchain/concepts/about-bedrock","title":"About Bedrock","description":"Understand how Bedrock provides the consensus and shared context that Zones build on.","source":"@site/docs/blockchain/concepts/about-bedrock.md","sourceDirName":"blockchain/concepts","slug":"/blockchain/concepts/about-bedrock","permalink":"/blockchain/concepts/about-bedrock","draft":false,"unlisted":false,"editUrl":"https://github.com/logos-co/logos-docs/tree/main/docs/blockchain/concepts/about-bedrock.md","tags":[],"version":"current","sidebarPosition":1,"frontMatter":{"title":"About Bedrock","doc_type":"concept","product":"blockchain","topics":"Bedrock","authors":"kashepavadan","owner":"logos","doc_version":1,"slug":"about-bedrock","sidebar_position":1},"sidebar":"blockchainSidebar","previous":{"title":"Join the Blend network as a core node","permalink":"/blockchain/blend/join-the-blend-network-as-a-core-node"},"next":{"title":"About Cryptarchia","permalink":"/blockchain/concepts/about-cryptarchia"}}'),s=t(4848),a=t(8453);let r={title:"About Bedrock",doc_type:"concept",product:"blockchain",topics:"Bedrock",authors:"kashepavadan",owner:"logos",doc_version:1,slug:"about-bedrock",sidebar_position:1},c="About Bedrock",i={},l=[{value:"Understand how Bedrock provides the consensus and shared context that Zones build on.",id:"understand-how-bedrock-provides-the-consensus-and-shared-context-that-zones-build-on",level:4},{value:"The basics",id:"the-basics",level:2},{value:"Bedrock components",id:"bedrock-components",level:2},{value:"Cryptarchia",id:"cryptarchia",level:3},{value:"Mantle",id:"mantle",level:3}];function d(e){let o={a:"a",h1:"h1",h2:"h2",h3:"h3",h4:"h4",header:"header",li:"li",p:"p",ul:"ul",...(0,a.R)(),...e.components};return(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(o.header,{children:(0,s.jsx)(o.h1,{id:"about-bedrock",children:"About Bedrock"})}),"\n",(0,s.jsx)(o.h4,{id:"understand-how-bedrock-provides-the-consensus-and-shared-context-that-zones-build-on",children:"Understand how Bedrock provides the consensus and shared context that Zones build on."}),"\n",(0,s.jsxs)(o.p,{children:["The ",(0,s.jsx)(o.a,{href:"https://docs.logos.co/get-started/glossary#bedrock",children:"Bedrock"})," layer is the basis on top of which the broader ",(0,s.jsx)(o.a,{href:"https://docs.logos.co/get-started/glossary#logos-blockchain",children:"Logos Blockchain"})," is built, operating as a large and decentralised peer-to-peer blockchain network. Bedrock functions as a solid foundation for ",(0,s.jsx)(o.a,{href:"/blockchain/concepts/about-zones",children:"Zones"})," built on Logos, allowing ",(0,s.jsx)(o.a,{href:"https://docs.logos.co/get-started/glossary#zone",children:"Zone"})," applications to leverage the Logos Blockchain's consensus guarantees. Bedrock also facilitates interoperability between Zones and Bedrock itself with support for asynchronous messaging and token bridging. By handling the logic allowing several parties to participate in sequencing a Zone, Bedrock contributes to the decentralisation of the Logos stack."]}),"\n",(0,s.jsx)(o.h2,{id:"the-basics",children:"The basics"}),"\n",(0,s.jsxs)(o.ul,{children:["\n",(0,s.jsx)(o.li,{children:"Bedrock is the foundational layer of the Logos Blockchain, operating as a decentralised peer-to-peer network."}),"\n",(0,s.jsx)(o.li,{children:"Bedrock provides consensus on transaction ordering to Zones, as well as a common context for Zone interaction."}),"\n",(0,s.jsx)(o.li,{children:"Participation in Bedrock is credibly neutral and private, ensuring open access for all participants."}),"\n"]}),"\n",(0,s.jsx)(o.h2,{id:"bedrock-components",children:"Bedrock components"}),"\n",(0,s.jsx)(o.p,{children:"Bedrock is organised into three key components. The most basic of these is the peer-to-peer network that allows Logos nodes to communicate with each other in a decentralised way."}),"\n",(0,s.jsx)(o.h3,{id:"cryptarchia",children:"Cryptarchia"}),"\n",(0,s.jsxs)(o.p,{children:["On top of the peer-to-peer network sits ",(0,s.jsx)(o.a,{href:"/blockchain/concepts/about-cryptarchia",children:"Cryptarchia"}),", the Logos Blockchain\u2019s consensus protocol used to reach an agreement about the state of the blockchain. As a ",(0,s.jsx)(o.a,{href:"https://docs.logos.co/get-started/glossary#private-proof-of-stake",children:"Private Proof of Stake"})," (",(0,s.jsx)(o.a,{href:"https://docs.logos.co/get-started/glossary#ppos",children:"PPoS"}),") protocol, ",(0,s.jsx)(o.a,{href:"https://docs.logos.co/get-started/glossary#cryptarchia",children:"Cryptarchia"})," gives all participants a proportional chance to propose a block, while ensuring that blocks cannot be linked to their proposers both before and after the proposal."]}),"\n",(0,s.jsx)(o.h3,{id:"mantle",children:"Mantle"}),"\n",(0,s.jsxs)(o.p,{children:[(0,s.jsx)(o.a,{href:"/blockchain/concepts/about-mantle",children:"Mantle"}),", a Bedrock component that serves as the operating system of the Logos Blockchain, provides a minimal shared execution environment for Zones that allows them to interact with Bedrock. This includes operations like writing data to the blockchain, as well as a restricted ledger of notes to support payments and staking."]}),"\n",(0,s.jsxs)(o.p,{children:["Zones make use of ",(0,s.jsx)(o.a,{href:"https://docs.logos.co/get-started/glossary#mantle",children:"Mantle"})," operations when posting their updates to the Logos Blockchain, as well as to communicate asynchronously with other Zones. Mantle ensures that Zones state updates are correctly ordered via the use of Logos channels. Logos channels also allow for several sequencers to sequence a Zone, and enable token bridging between Zones and Bedrock."]})]})}function h(e={}){let{wrapper:o}={...(0,a.R)(),...e.components};return o?(0,s.jsx)(o,{...e,children:(0,s.jsx)(d,{...e})}):d(e)}},8453(e,o,t){t.d(o,{R:()=>r,x:()=>c});var n=t(6540);let s={},a=n.createContext(s);function r(e){let o=n.useContext(a);return n.useMemo(function(){return"function"==typeof e?e(o):{...o,...e}},[o,e])}function c(e){let o;return o=e.disableParentContext?"function"==typeof e.components?e.components(s):e.components||s:r(e.components),n.createElement(a.Provider,{value:o},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/5e95c892.e6d38e0d.js b/assets/js/5e95c892.e6d38e0d.js new file mode 100644 index 00000000..c26c55f6 --- /dev/null +++ b/assets/js/5e95c892.e6d38e0d.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunklogos_docs=self.webpackChunklogos_docs||[]).push([["1668"],{6569(s,e,r){r.r(e),r.d(e,{default:()=>n});var c=r(4848);r(6540);var o=r(4164),a=r(4308),u=r(8287),l=r(2831),d=r(1085);function n(s){return(0,c.jsx)(a.e3,{className:(0,o.A)(u.G.wrapper.docsPages),children:(0,c.jsx)(d.A,{children:(0,l.v)(s.route.routes)})})}}}]); \ No newline at end of file diff --git a/assets/js/638fbe8d.aa124cf3.js b/assets/js/638fbe8d.aa124cf3.js new file mode 100644 index 00000000..396dbb97 --- /dev/null +++ b/assets/js/638fbe8d.aa124cf3.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunklogos_docs=self.webpackChunklogos_docs||[]).push([["2256"],{5244(e){e.exports=JSON.parse('{"categoryGeneratedIndex":{"title":"Build an app","description":"Build and ship apps on Logos.","slug":"/build-an-app","permalink":"/build-an-app","sidebar":"buildAnAppSidebar","navigation":{"next":{"title":"Create and use an AMM liquidity pool on the Logos Execution Zone","permalink":"/build-an-app/tutorials/create-and-use-an-amm-liquidity-pool-on-the-logos-execution-zone"}}}}')}}]); \ No newline at end of file diff --git a/assets/js/669488b6.7f03cb79.js b/assets/js/669488b6.7f03cb79.js new file mode 100644 index 00000000..6d1e7c26 --- /dev/null +++ b/assets/js/669488b6.7f03cb79.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunklogos_docs=self.webpackChunklogos_docs||[]).push([["7009"],{2628(e,n,s){s.r(n),s.d(n,{metadata:()=>o,default:()=>h,frontMatter:()=>a,contentTitle:()=>i,toc:()=>r,assets:()=>c});var o=JSON.parse('{"id":"blockchain/get-started/run-a-logos-blockchain-node-from-basecamp","title":"Run a Logos Blockchain node from Basecamp","description":"Install the blockchain modules and run a syncing testnet node from the Basecamp desktop app.","source":"@site/docs/blockchain/get-started/run-a-logos-blockchain-node-from-basecamp.md","sourceDirName":"blockchain/get-started","slug":"/blockchain/get-started/run-a-logos-blockchain-node-from-basecamp","permalink":"/blockchain/get-started/run-a-logos-blockchain-node-from-basecamp","draft":false,"unlisted":false,"editUrl":"https://github.com/logos-co/logos-docs/tree/main/docs/blockchain/get-started/run-a-logos-blockchain-node-from-basecamp.md","tags":[],"version":"current","sidebarPosition":2,"frontMatter":{"title":"Run a Logos Blockchain node from Basecamp","doc_type":"procedure","product":"blockchain","topics":"blockchain","steps_layout":"sectioned","authors":"xalisher","owner":"logos","doc_version":1,"slug":"run-a-logos-blockchain-node-from-basecamp","sidebar_position":2},"sidebar":"blockchainSidebar","previous":{"title":"Introduction to the Logos Blockchain","permalink":"/blockchain/get-started/introduction-to-the-logos-blockchain"},"next":{"title":"Run a Logos Blockchain node on the public testnet from the CLI","permalink":"/blockchain/get-started/run-a-logos-blockchain-node-from-cli"}}'),t=s(4848),l=s(8453);let a={title:"Run a Logos Blockchain node from Basecamp",doc_type:"procedure",product:"blockchain",topics:"blockchain",steps_layout:"sectioned",authors:"xalisher",owner:"logos",doc_version:1,slug:"run-a-logos-blockchain-node-from-basecamp",sidebar_position:2},i="Run a Logos Blockchain node from Basecamp",c={},r=[{value:"Install the blockchain modules and run a syncing testnet node from the Basecamp desktop app.",id:"install-the-blockchain-modules-and-run-a-syncing-testnet-node-from-the-basecamp-desktop-app",level:4},{value:"Before you start",id:"before-you-start",level:2},{value:"What to expect",id:"what-to-expect",level:2},{value:"Step 1: Install prerequisites",id:"step-1-install-prerequisites",level:2},{value:"Step 2: Download and launch Basecamp",id:"step-2-download-and-launch-basecamp",level:2},{value:"Step 3: Install the blockchain module",id:"step-3-install-the-blockchain-module",level:2},{value:"Step 4: Install the blockchain UI",id:"step-4-install-the-blockchain-ui",level:2},{value:"Step 5: Configure the node",id:"step-5-configure-the-node",level:2},{value:"Step 6: Start the node",id:"step-6-start-the-node",level:2},{value:"Step 7: Verify that your node is syncing",id:"step-7-verify-that-your-node-is-syncing",level:2},{value:"Fund your node and propose blocks",id:"fund-your-node-and-propose-blocks",level:2},{value:"Keeping your node running",id:"keeping-your-node-running",level:2},{value:"Troubleshooting",id:"troubleshooting",level:2},{value:"The node is Online but Consensus shows "Call failed", or Accounts won't load",id:"the-node-is-online-but-consensus-shows-call-failed-or-accounts-wont-load",level:3},{value:"Height stays at 0",id:"height-stays-at-0",level:3},{value:"I need my wallet address but the Accounts panel is blank",id:"i-need-my-wallet-address-but-the-accounts-panel-is-blank",level:3},{value:"Basecamp will not launch",id:"basecamp-will-not-launch",level:3}];function d(e){let n={a:"a",admonition:"admonition",code:"code",em:"em",h1:"h1",h2:"h2",h3:"h3",h4:"h4",header:"header",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,l.R)(),...e.components};return(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)(n.header,{children:(0,t.jsx)(n.h1,{id:"run-a-logos-blockchain-node-from-basecamp",children:"Run a Logos Blockchain node from Basecamp"})}),"\n",(0,t.jsx)(n.h4,{id:"install-the-blockchain-modules-and-run-a-syncing-testnet-node-from-the-basecamp-desktop-app",children:"Install the blockchain modules and run a syncing testnet node from the Basecamp desktop app."}),"\n",(0,t.jsxs)(n.p,{children:["The ",(0,t.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#logos-blockchain",children:"Logos Blockchain"})," is the blockchain ",(0,t.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#module",children:"module"})," of the Logos technology stack. You can run a node ",(0,t.jsx)(n.a,{href:"/blockchain/get-started/run-a-logos-blockchain-node-from-cli",children:"from the CLI"}),", from a ",(0,t.jsx)(n.a,{href:"/blockchain/node-app/build-and-run-logos-blockchain-node-app-ui",children:"standalone application"}),", or \u2014 as described here \u2014 from ",(0,t.jsx)(n.strong,{children:"Basecamp"}),", the modular Logos desktop app, by installing the blockchain module and its UI."]}),"\n",(0,t.jsx)(n.p,{children:"This procedure covers Linux, Windows (via WSL), and macOS on Apple Silicon. It requires no terminal beyond a single prerequisite-install command on Linux."}),"\n",(0,t.jsx)(n.h2,{id:"before-you-start",children:"Before you start"}),"\n",(0,t.jsx)(n.p,{children:"Make sure you have the following:"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:["A graphical desktop session on one of: ",(0,t.jsx)(n.strong,{children:"Linux"})," (tested on Ubuntu 24.04), ",(0,t.jsx)(n.strong,{children:"Windows 11 with WSL2"})," (plus WSLg for the GUI), or an ",(0,t.jsx)(n.strong,{children:"Apple Silicon Mac"})," (M1 or later). Basecamp is a desktop application and needs a real display."]}),"\n",(0,t.jsxs)(n.li,{children:["Around ",(0,t.jsx)(n.strong,{children:"8 GB of RAM"})," and a few GB of free disk. The chain downloads on first sync and grows over time."]}),"\n",(0,t.jsx)(n.li,{children:"An always-on machine is recommended \u2014 a laptop that sleeps will fall behind and resync on wake."}),"\n"]}),"\n",(0,t.jsxs)(n.p,{children:["Port forwarding is ",(0,t.jsx)(n.strong,{children:"not"})," required. A blockchain node participates outbound-only: it syncs, validates, and (once funded) proposes blocks. Forwarding only makes your node ",(0,t.jsx)(n.em,{children:"reachable"})," so others can use it, which is out of scope here."]}),"\n",(0,t.jsxs)(n.p,{children:[":::info Pin the versions\nInstall ",(0,t.jsxs)(n.strong,{children:[(0,t.jsx)(n.code,{children:"blockchain_module"})," ",(0,t.jsx)(n.code,{children:"0.2.0"})]})," and ",(0,t.jsxs)(n.strong,{children:[(0,t.jsx)(n.code,{children:"blockchain_ui"})," ",(0,t.jsx)(n.code,{children:"0.2.0"})]}),". In the Package Manager, ",(0,t.jsx)(n.code,{children:"blockchain_module"})," may pre-select ",(0,t.jsx)(n.code,{children:"0.0.999"}),", which is a ",(0,t.jsx)(n.em,{children:"devnet"})," build and will not sync the public testnet. Always select ",(0,t.jsx)(n.code,{children:"0.2.0"})," explicitly (Step 3).\n:::"]}),"\n",(0,t.jsx)(n.h2,{id:"what-to-expect",children:"What to expect"}),"\n",(0,t.jsx)(n.p,{children:"By the end of this procedure:"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:["Basecamp will be running the ",(0,t.jsx)(n.code,{children:"blockchain_module"})," and ",(0,t.jsx)(n.code,{children:"blockchain_ui"})," modules, both at ",(0,t.jsx)(n.code,{children:"0.2.0"}),"."]}),"\n",(0,t.jsx)(n.li,{children:"Your node will be connected to testnet peers and syncing the chain."}),"\n",(0,t.jsx)(n.li,{children:"You will confirm sync by observing an advancing block height."}),"\n",(0,t.jsx)(n.li,{children:"Optionally, you can fund your wallet from the faucet so your node proposes blocks."}),"\n"]}),"\n",(0,t.jsx)(n.h2,{id:"step-1-install-prerequisites",children:"Step 1: Install prerequisites"}),"\n",(0,t.jsxs)(n.p,{children:[(0,t.jsx)(n.strong,{children:"On Linux"}),", install the libraries Basecamp's AppImage requires:"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-bash",children:"sudo apt-get install -y libfuse2t64 libegl1 libopengl0\n"})}),"\n",(0,t.jsxs)(n.p,{children:["On Ubuntu 22.04 and earlier, use ",(0,t.jsx)(n.code,{children:"libfuse2"})," instead of ",(0,t.jsx)(n.code,{children:"libfuse2t64"}),"."]}),"\n",(0,t.jsxs)(n.p,{children:[(0,t.jsx)(n.strong,{children:"On Windows"}),", run every step inside a ",(0,t.jsx)(n.strong,{children:"WSL2 Ubuntu"})," shell, on Windows 11 (or Windows 10 21H2+ with WSLg for GUI support). Use the prebuilt AppImage rather than building from source. Default NAT networking is sufficient."]}),"\n",(0,t.jsxs)(n.p,{children:[(0,t.jsx)(n.strong,{children:"On macOS (Apple Silicon)"}),", no prerequisite command is needed. Intel Macs are not supported \u2014 there is no x86_64 build."]}),"\n",(0,t.jsx)(n.h2,{id:"step-2-download-and-launch-basecamp",children:"Step 2: Download and launch Basecamp"}),"\n",(0,t.jsxs)(n.p,{children:["Download the Basecamp ",(0,t.jsx)(n.code,{children:"0.2.1"})," release for your platform from the ",(0,t.jsx)(n.a,{href:"https://github.com/logos-co/logos-basecamp/releases/tag/0.2.1",children:"releases page"}),". On Linux, use the ",(0,t.jsx)(n.code,{children:"x86_64"})," (or ",(0,t.jsx)(n.code,{children:"aarch64"})," on ARM) AppImage."]}),"\n",(0,t.jsx)(n.p,{children:"Make it executable and launch it:"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-bash",children:"chmod +x LogosBasecamp-Desktop-v0.2.1-*-x86_64.AppImage\n./LogosBasecamp-Desktop-v0.2.1-*-x86_64.AppImage\n"})}),"\n",(0,t.jsx)(n.p,{children:"Basecamp opens."}),"\n",(0,t.jsx)(n.h2,{id:"step-3-install-the-blockchain-module",children:"Step 3: Install the blockchain module"}),"\n",(0,t.jsxs)(n.ol,{children:["\n",(0,t.jsxs)(n.li,{children:["\n",(0,t.jsxs)(n.p,{children:["In the sidebar, open ",(0,t.jsx)(n.strong,{children:"Package Manager"})," and find ",(0,t.jsx)(n.strong,{children:(0,t.jsx)(n.code,{children:"blockchain_module"})})," (category: Blockchain)."]}),"\n",(0,t.jsxs)(n.p,{children:[":::caution Use Package Manager, not Applications\nInstall blockchain modules from ",(0,t.jsx)(n.strong,{children:"Package Manager"}),". Open the version dropdown and ",(0,t.jsxs)(n.strong,{children:["select ",(0,t.jsx)(n.code,{children:"0.2.0"})]})," before installing \u2014 the pre-selected version is ",(0,t.jsx)(n.code,{children:"0.0.999"}),", the devnet build.\n:::"]}),"\n"]}),"\n",(0,t.jsxs)(n.li,{children:["\n",(0,t.jsxs)(n.p,{children:["Select ",(0,t.jsx)(n.code,{children:"0.2.0"}),", then press ",(0,t.jsx)(n.strong,{children:"Install"})," (~142 MB). When it completes, the Details pane shows ",(0,t.jsx)(n.code,{children:"Installed version: 0.2.0"}),". If it shows ",(0,t.jsx)(n.code,{children:"0.0.999"}),", select ",(0,t.jsx)(n.code,{children:"0.2.0"})," again and reinstall \u2014 Basecamp will offer to upgrade, which is safe."]}),"\n"]}),"\n"]}),"\n",(0,t.jsx)(n.h2,{id:"step-4-install-the-blockchain-ui",children:"Step 4: Install the blockchain UI"}),"\n",(0,t.jsxs)(n.ol,{children:["\n",(0,t.jsxs)(n.li,{children:["\n",(0,t.jsxs)(n.p,{children:["On the same screen, select ",(0,t.jsx)(n.strong,{children:(0,t.jsx)(n.code,{children:"blockchain_ui"})})," at version ",(0,t.jsx)(n.strong,{children:(0,t.jsx)(n.code,{children:"0.2.0"})})," (already the default) and press ",(0,t.jsx)(n.strong,{children:"Install"}),"."]}),"\n"]}),"\n",(0,t.jsxs)(n.li,{children:["\n",(0,t.jsxs)(n.p,{children:["A dependency dialog appears. Choose ",(0,t.jsx)(n.strong,{children:"\"Install just 'blockchain_ui'\""})," \u2014 installing with dependencies replaces your ",(0,t.jsx)(n.code,{children:"0.2.0"})," core with the ",(0,t.jsx)(n.code,{children:"0.0.999"})," devnet build."]}),"\n"]}),"\n"]}),"\n",(0,t.jsx)(n.h2,{id:"step-5-configure-the-node",children:"Step 5: Configure the node"}),"\n",(0,t.jsxs)(n.p,{children:["Open the ",(0,t.jsx)(n.strong,{children:"blockchain"})," module from the sidebar to reach the node view, then generate a config"]}),"\n",(0,t.jsxs)(n.ol,{children:["\n",(0,t.jsxs)(n.li,{children:["\n",(0,t.jsxs)(n.p,{children:["In the ",(0,t.jsx)(n.strong,{children:"Initial peers"})," field, add the testnet bootstrap peers (one per line). For example, for release 0.2.0:"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-text",children:"/ip4/65.109.51.37/udp/3000/quic-v1/p2p/12D3KooWFrouXfmrR4nsLMtE7wu15DoMJ6VtoUtHinREZCvbWHar\n/ip4/65.109.51.37/udp/3001/quic-v1/p2p/12D3KooWJRGau8M1rjT7R5e4YYsgdFhsMX35nRDtMwCDjxQkXAHz\n/ip4/65.109.51.37/udp/3002/quic-v1/p2p/12D3KooWQXJavMDTRscjauFSgVAB1VLB6Rzpy2uY5SU9Tk7927tb\n/ip4/65.109.51.37/udp/50001/quic-v1/p2p/12D3KooWSQc7CcGtvWDPF1yCbBthFnQjprfCVHmfmNDUrSmqQsU1\n"})}),"\n",(0,t.jsx)(n.admonition,{type:"info",children:(0,t.jsxs)(n.p,{children:["Make sure to use the current bootstrap peer addresses in the ",(0,t.jsx)(n.a,{href:"https://github.com/logos-blockchain/logos-blockchain/releases/latest",children:"Logos Blockchain Node release notes"})," for your selected release."]})}),"\n"]}),"\n",(0,t.jsxs)(n.li,{children:["\n",(0,t.jsxs)(n.p,{children:["Leave the other fields at their defaults and select ",(0,t.jsx)(n.strong,{children:"Generate config"}),"."]}),"\n",(0,t.jsxs)(n.p,{children:[":::caution Peers are required\nA config generated with an empty ",(0,t.jsx)(n.strong,{children:"Initial peers"})," field produces a node that starts and reports success but never syncs. Make sure the peers above are present before generating.\n:::"]}),"\n"]}),"\n"]}),"\n",(0,t.jsx)(n.h2,{id:"step-6-start-the-node",children:"Step 6: Start the node"}),"\n",(0,t.jsxs)(n.p,{children:["On the node view, select ",(0,t.jsx)(n.strong,{children:"Start Node"}),". The status moves to ",(0,t.jsx)(n.em,{children:"Starting"}),", then ",(0,t.jsx)(n.em,{children:"Bootstrapping"}),", and the node begins connecting to peers."]}),"\n",(0,t.jsx)(n.h2,{id:"step-7-verify-that-your-node-is-syncing",children:"Step 7: Verify that your node is syncing"}),"\n",(0,t.jsx)(n.p,{children:"Confirm sync by evidence rather than the status label. Query the node's HTTP API and check that the height advances across two polls:"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-bash",children:"curl -s http://127.0.0.1:8080/cryptarchia/info\n"})}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:["A syncing node returns a ",(0,t.jsx)(n.code,{children:"tip"})," that is not genesis and a ",(0,t.jsx)(n.code,{children:"height"}),"/",(0,t.jsx)(n.code,{children:"slot"})," that increases between calls. The block list in the UI also begins filling within a minute or two."]}),"\n"]}),"\n",(0,t.jsxs)(n.p,{children:[":::note Bootstrapping can take time\nIf you were offline for a while, expect the node to sit in ",(0,t.jsx)(n.em,{children:"Bootstrapping"})," while it catches up before it reports ",(0,t.jsx)(n.em,{children:"Online"}),". A height that is far below the current slot during initial sync is normal.\n:::"]}),"\n",(0,t.jsx)(n.h2,{id:"fund-your-node-and-propose-blocks",children:"Fund your node and propose blocks"}),"\n",(0,t.jsxs)(n.p,{children:["A synced node validates the chain but does not ",(0,t.jsx)(n.strong,{children:"propose"})," blocks until its wallet has a balance. For chain leadership, your wallet balance counts as stake automatically \u2014 there is no separate lock step."]}),"\n",(0,t.jsxs)(n.ol,{children:["\n",(0,t.jsxs)(n.li,{children:["In the node view, open ",(0,t.jsx)(n.strong,{children:"Operations \u2192 Accounts"})," and copy one of your wallet keys."]}),"\n",(0,t.jsxs)(n.li,{children:["Go to the ",(0,t.jsx)(n.a,{href:"https://testnet.blockchain.logos.co/web/faucet/",children:"testnet faucet"}),", paste the key, and select ",(0,t.jsx)(n.strong,{children:"Request Funds"})," (do this once your node is ",(0,t.jsx)(n.em,{children:"Online"}),")."]}),"\n",(0,t.jsx)(n.li,{children:"Once the funds arrive they auto-stake. Your node starts winning leader slots proportional to your balance and proposes blocks on its own."}),"\n",(0,t.jsxs)(n.li,{children:["Rewards appear under ",(0,t.jsx)(n.strong,{children:"Operations \u2192 Leader Rewards"}),", where ",(0,t.jsx)(n.strong,{children:"Claim"})," redeems them into your wallet."]}),"\n"]}),"\n",(0,t.jsxs)(n.p,{children:[":::note Blend staking is separate\nParticipating in the ",(0,t.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#blend",children:"Blend network"})," as a ",(0,t.jsx)(n.em,{children:"Core"})," node requires a separate locked note (staked funds) and does not affect block proposing. See ",(0,t.jsx)(n.a,{href:"/blockchain/blend/join-the-blend-network-as-a-core-node",children:"Join the Blend network as a Core node"}),".\n:::"]}),"\n",(0,t.jsx)(n.h2,{id:"keeping-your-node-running",children:"Keeping your node running"}),"\n",(0,t.jsxs)(n.p,{children:["The node runs only while Basecamp is open, and it does not resume automatically after a restart \u2014 reopen Basecamp, open the blockchain module, and select ",(0,t.jsx)(n.strong,{children:"Start Node"})," again. For an unattended, always-on node, use the ",(0,t.jsx)(n.a,{href:"/blockchain/get-started/run-a-logos-blockchain-node-from-cli",children:"CLI setup"})," with a service manager instead."]}),"\n",(0,t.jsx)(n.h2,{id:"troubleshooting",children:"Troubleshooting"}),"\n",(0,t.jsx)(n.h3,{id:"the-node-is-online-but-consensus-shows-call-failed-or-accounts-wont-load",children:'The node is Online but Consensus shows "Call failed", or Accounts won\'t load'}),"\n",(0,t.jsx)(n.p,{children:"This is a UI-side issue, not a node fault \u2014 the core is healthy but the UI cannot render the call. Recover least-destructively first:"}),"\n",(0,t.jsxs)(n.ol,{children:["\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.strong,{children:"Fully quit Basecamp and relaunch"})," (Quit, not just close the window), then reopen the node. This clears most cases with no data loss."]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.strong,{children:"Check the versions match"})," \u2014 both ",(0,t.jsx)(n.code,{children:"blockchain_module"})," and ",(0,t.jsx)(n.code,{children:"blockchain_ui"})," should be ",(0,t.jsx)(n.code,{children:"0.2.0"}),". A version mismatch is a common cause."]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.strong,{children:"If it persists"}),", reset the chain state while keeping your keys: quit Basecamp, delete ",(0,t.jsx)(n.code,{children:"db/"})," and ",(0,t.jsx)(n.code,{children:"state/"})," inside ",(0,t.jsx)(n.code,{children:"module_data/blockchain_module/<id>/"})," (not the whole folder \u2014 that removes your wallet keystore), relaunch, and Start. The node re-syncs from genesis."]}),"\n"]}),"\n",(0,t.jsxs)(n.p,{children:["If ",(0,t.jsx)(n.code,{children:"curl http://127.0.0.1:8080/cryptarchia/info"})," shows an advancing height, the node is syncing and a restart (step 1) is enough \u2014 do not wipe a working node."]}),"\n",(0,t.jsx)(n.h3,{id:"height-stays-at-0",children:"Height stays at 0"}),"\n",(0,t.jsxs)(n.p,{children:["Your Initial peers are empty. Quit Basecamp, delete ",(0,t.jsx)(n.code,{children:"user_config.yaml"}),", ",(0,t.jsx)(n.code,{children:"db/"}),", and ",(0,t.jsx)(n.code,{children:"state/"})," inside ",(0,t.jsx)(n.code,{children:"module_data/blockchain_module/<id>/"})," (leave ",(0,t.jsx)(n.code,{children:"keystore.yaml"}),"), relaunch, and redo Step 5 with the peers present."]}),"\n",(0,t.jsx)(n.h3,{id:"i-need-my-wallet-address-but-the-accounts-panel-is-blank",children:"I need my wallet address but the Accounts panel is blank"}),"\n",(0,t.jsx)(n.p,{children:"Your keys are safe in the keystore regardless of the UI. Retrieve an address directly:"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-bash",children:"cd ~/.local/share/Logos/LogosBasecamp/module_data/blockchain_module/<id>/\ngrep -A20 public_keys keystore.yaml\n"})}),"\n",(0,t.jsx)(n.h3,{id:"basecamp-will-not-launch",children:"Basecamp will not launch"}),"\n",(0,t.jsxs)(n.p,{children:["On Linux, confirm the Step 1 prerequisites are installed. Clearing the QML cache can also help: ",(0,t.jsx)(n.code,{children:"rm -rf ~/.cache/Logos"}),"."]})]})}function h(e={}){let{wrapper:n}={...(0,l.R)(),...e.components};return n?(0,t.jsx)(n,{...e,children:(0,t.jsx)(d,{...e})}):d(e)}},8453(e,n,s){s.d(n,{R:()=>a,x:()=>i});var o=s(6540);let t={},l=o.createContext(t);function a(e){let n=o.useContext(l);return o.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function i(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(t):e.components||t:a(e.components),o.createElement(l.Provider,{value:n},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/68fc9db9.c442194f.js b/assets/js/68fc9db9.c442194f.js new file mode 100644 index 00000000..f30747ae --- /dev/null +++ b/assets/js/68fc9db9.c442194f.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunklogos_docs=self.webpackChunklogos_docs||[]).push([["8753"],{6837(e,s,o){o.r(s),o.d(s,{metadata:()=>t,default:()=>h,frontMatter:()=>i,contentTitle:()=>a,toc:()=>d,assets:()=>c});var t=JSON.parse('{"id":"messaging/concepts/about-network-domains","title":"About network domains","description":"Understand the discovery, gossip, and request/response domains that structure how peers interact.","source":"@site/docs/messaging/concepts/about-network-domains.md","sourceDirName":"messaging/concepts","slug":"/messaging/concepts/network-domains","permalink":"/messaging/concepts/network-domains","draft":false,"unlisted":false,"editUrl":"https://github.com/logos-co/logos-docs/tree/main/docs/messaging/concepts/about-network-domains.md","tags":[],"version":"current","sidebarPosition":3,"frontMatter":{"title":"About network domains","doc_type":"concept","product":"messaging","topics":"network-domains, architecture","authors":"LordGhostX, fryorcraken","owner":"logos","doc_version":1,"slug":"network-domains","sidebar_position":3},"sidebar":"messagingSidebar","previous":{"title":"About content topics","permalink":"/messaging/concepts/content-topics"},"next":{"title":"About transports","permalink":"/messaging/concepts/transports"}}'),n=o(4848),r=o(8453);let i={title:"About network domains",doc_type:"concept",product:"messaging",topics:"network-domains, architecture",authors:"LordGhostX, fryorcraken",owner:"logos",doc_version:1,slug:"network-domains",sidebar_position:3},a="About network domains",c={},d=[{value:"Understand the discovery, gossip, and request/response domains that structure how peers interact.",id:"understand-the-discovery-gossip-and-requestresponse-domains-that-structure-how-peers-interact",level:4},{value:"The basics",id:"the-basics",level:2},{value:"Discovery domain",id:"discovery-domain",level:2},{value:"Gossip domain",id:"gossip-domain",level:2},{value:"Request/response domain",id:"requestresponse-domain",level:2},{value:"Overview of protocol interaction",id:"overview-of-protocol-interaction",level:2}];function l(e){let s={a:"a",code:"code",h1:"h1",h2:"h2",h4:"h4",header:"header",li:"li",ol:"ol",p:"p",pre:"pre",ul:"ul",...(0,r.R)(),...e.components};return(0,n.jsxs)(n.Fragment,{children:[(0,n.jsx)(s.header,{children:(0,n.jsx)(s.h1,{id:"about-network-domains",children:"About network domains"})}),"\n",(0,n.jsx)(s.h4,{id:"understand-the-discovery-gossip-and-requestresponse-domains-that-structure-how-peers-interact",children:"Understand the discovery, gossip, and request/response domains that structure how peers interact."}),"\n",(0,n.jsxs)(s.p,{children:[(0,n.jsx)(s.a,{href:"https://docs.logos.co/get-started/glossary#logos-delivery",children:"Logos Delivery"})," is a unified and cohesive entity that offers a rich ecosystem with three distinct network interaction domains. These domains serve specialised purposes and contribute to the robust functionality of Logos Delivery, forming its foundation."]}),"\n",(0,n.jsx)(s.h2,{id:"the-basics",children:"The basics"}),"\n",(0,n.jsxs)(s.ul,{children:["\n",(0,n.jsxs)(s.li,{children:["The ",(0,n.jsx)(s.a,{href:"#discovery-domain",children:"discovery domain"})," helps nodes locate other peers in the network."]}),"\n",(0,n.jsxs)(s.li,{children:["The ",(0,n.jsx)(s.a,{href:"#gossip-domain",children:"gossip domain"})," distributes messages across the network through ",(0,n.jsx)(s.a,{href:"https://docs.logos.co/get-started/glossary#relay",children:"Relay"}),"."]}),"\n",(0,n.jsxs)(s.li,{children:["The ",(0,n.jsx)(s.a,{href:"#requestresponse-domain",children:"request/response domain"})," serves resource-limited nodes with ",(0,n.jsx)(s.a,{href:"https://docs.logos.co/get-started/glossary#store",children:"Store"}),", ",(0,n.jsx)(s.a,{href:"https://docs.logos.co/get-started/glossary#filter",children:"Filter"}),", and ",(0,n.jsx)(s.a,{href:"https://docs.logos.co/get-started/glossary#light-push",children:"Light Push"}),"."]}),"\n"]}),"\n",(0,n.jsx)(s.h2,{id:"discovery-domain",children:"Discovery domain"}),"\n",(0,n.jsxs)(s.p,{children:[(0,n.jsx)(s.a,{href:"https://docs.logos.co/get-started/glossary#peer-discovery",children:"Peer discovery"})," in Logos Delivery facilitates locating other nodes within the network. As a modular protocol, Logos Delivery incorporates various discovery mechanisms, such as ",(0,n.jsx)(s.a,{href:"/messaging/concepts/understand-peer-discovery/discv5",children:"Discv5"})," and ",(0,n.jsx)(s.a,{href:"/messaging/concepts/understand-peer-discovery/peer-exchange",children:"Peer Exchange"}),". These mechanisms allow developers to choose the most suitable option(s) for their specific use cases and user environments, including mobile phones, desktop browsers, servers, and more."]}),"\n",(0,n.jsx)(s.h2,{id:"gossip-domain",children:"Gossip domain"}),"\n",(0,n.jsxs)(s.p,{children:[(0,n.jsx)(s.a,{href:"https://docs.logos.co/get-started/glossary#gossipsub",children:"GossipSub"})," derives its name from the practice within Pub/Sub networks where peers gossip about the messages they have encountered, thus establishing a message delivery network."]}),"\n",(0,n.jsxs)(s.p,{children:["Logos Delivery employs gossiping through ",(0,n.jsx)(s.a,{href:"https://docs.waku.org/learn/concepts/protocols#relay",children:"Relay"})," to distribute messages across the network. Additionally, Logos Delivery introduces ",(0,n.jsx)(s.a,{href:"https://docs.waku.org/learn/concepts/protocols#rln-relay",children:"RLN Relay"}),", an experimental mechanism that combines privacy preservation and economic spam protection."]}),"\n",(0,n.jsx)(s.h2,{id:"requestresponse-domain",children:"Request/response domain"}),"\n",(0,n.jsxs)(s.p,{children:["Logos Delivery provides a set of protocols to optimise its performance in resource-limited environments like low bandwidth or ",(0,n.jsx)(s.a,{href:"https://docs.logos.co/get-started/glossary#mostly-offline",children:"mostly offline"})," scenarios for multiple purposes."]}),"\n",(0,n.jsxs)(s.ul,{children:["\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.a,{href:"https://docs.waku.org/learn/concepts/protocols#store",children:"Store"})," enables the retrieval of historical messages."]}),"\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.a,{href:"https://docs.waku.org/learn/concepts/protocols#filter",children:"Filter"})," efficiently retrieves a subset of messages to conserve bandwidth."]}),"\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.a,{href:"https://docs.waku.org/learn/concepts/protocols#light-push",children:"Light Push"})," facilitates message publication for nodes with limited bandwidth and short connection windows."]}),"\n"]}),"\n",(0,n.jsx)(s.h2,{id:"overview-of-protocol-interaction",children:"Overview of protocol interaction"}),"\n",(0,n.jsx)(s.p,{children:"Here is a diagram illustrating the interaction between different protocols."}),"\n",(0,n.jsx)(s.pre,{children:(0,n.jsx)(s.code,{className:"language-mermaid",children:"sequenceDiagram\n participant A as A relay\n participant B as B relay(pubtopic1)\n participant C as C relay(pubtopic1)\n participant D as D relay(pubtopic1), store(pubtopic1), filter\n participant E as E relay, store\n participant F as F filter\n\n A ->> A: msg1=WakuMessage(contentTopic1, data) (1)\n F ->> D: FilterRequest(pubtopic1, contentTopic1) (2)\n D ->> D: Subscribe F to filter (2)\n A ->> B: Publish msg1 on pubtopic1 (3)\n B ->> D: relay msg1 on pubtopic1 (3)\n D ->> D: store: saves msg1 (4)\n D ->> C: relay msg1 on pubtopic1 (4)\n D ->> F: MessagePush(msg1) (5)\n E ->> E: E comes online (6)\n E ->> D: HistoryQuery(pubtopic1, contentTopic1) (6)\n D ->> E: HistoryResponse(msg1, ...) (6)\n"})}),"\n",(0,n.jsxs)(s.p,{children:["The Pub/Sub topic ",(0,n.jsx)(s.code,{children:"pubtopic1"})," serves as a means of routing messages (the network employs a default Pub/Sub topic) and indicates that it is subscribed to messages on that topic for a relay. Node D serves as a ",(0,n.jsx)(s.code,{children:"Store"})," and is responsible for storing messages."]}),"\n",(0,n.jsxs)(s.ol,{children:["\n",(0,n.jsxs)(s.li,{children:["Node A creates a WakuMessage ",(0,n.jsx)(s.code,{children:"msg1"})," with ",(0,n.jsx)(s.a,{href:"https://docs.waku.org/learn/concepts/content-topics",children:"Content Topic"})," ",(0,n.jsx)(s.code,{children:"contentTopic1"}),"."]}),"\n",(0,n.jsxs)(s.li,{children:["Node F requests to get messages filtered by Pub/Sub topic ",(0,n.jsx)(s.code,{children:"pubtopic1"})," and ",(0,n.jsx)(s.a,{href:"https://docs.logos.co/get-started/glossary#content-topic",children:"Content Topic"})," ",(0,n.jsx)(s.code,{children:"contentTopic1"}),". Node D subscribes F to this filter and will forward messages that match that filter in the future."]}),"\n",(0,n.jsxs)(s.li,{children:["Node A publishes ",(0,n.jsx)(s.code,{children:"msg1"})," on ",(0,n.jsx)(s.code,{children:"pubtopic1"}),". The message is sent from Node A to Node B and then forwarded to Node D."]}),"\n",(0,n.jsxs)(s.li,{children:["Node D, upon receiving ",(0,n.jsx)(s.code,{children:"msg1"}),", stores the message for future retrieval by other nodes and forwards it to Node C."]}),"\n",(0,n.jsxs)(s.li,{children:["Node D also pushes ",(0,n.jsx)(s.code,{children:"msg1"})," to Node F, informing it about the arrival of a new message."]}),"\n",(0,n.jsxs)(s.li,{children:["At a later time, Node E comes online and requests messages matching ",(0,n.jsx)(s.code,{children:"pubtopic1"})," and ",(0,n.jsx)(s.code,{children:"contentTopic1"})," from Node D. Node D responds with ",(0,n.jsx)(s.code,{children:"msg1"})," and potentially other messages that match the query."]}),"\n"]})]})}function h(e={}){let{wrapper:s}={...(0,r.R)(),...e.components};return s?(0,n.jsx)(s,{...e,children:(0,n.jsx)(l,{...e})}):l(e)}},8453(e,s,o){o.d(s,{R:()=>i,x:()=>a});var t=o(6540);let n={},r=t.createContext(n);function i(e){let s=t.useContext(r);return t.useMemo(function(){return"function"==typeof e?e(s):{...s,...e}},[s,e])}function a(e){let s;return s=e.disableParentContext?"function"==typeof e.components?e.components(n):e.components||n:i(e.components),t.createElement(r.Provider,{value:s},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/6f4e7109.0ff12c24.js b/assets/js/6f4e7109.0ff12c24.js new file mode 100644 index 00000000..e2bb75b4 --- /dev/null +++ b/assets/js/6f4e7109.0ff12c24.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunklogos_docs=self.webpackChunklogos_docs||[]).push([["6524"],{9106(e,n,s){s.r(n),s.d(n,{metadata:()=>t,default:()=>c,frontMatter:()=>a,contentTitle:()=>i,toc:()=>d,assets:()=>l});var t=JSON.parse('{"id":"blockchain/node-app/move-assets-using-logos-blockchain-app","title":"Move assets using the Logos Blockchain UI app","description":"Get started with token transfers between wallet accounts directly from the dashboard UI.","source":"@site/docs/blockchain/node-app/move-assets-using-logos-blockchain-app.md","sourceDirName":"blockchain/node-app","slug":"/blockchain/node-app/move-assets-using-logos-blockchain-app","permalink":"/blockchain/node-app/move-assets-using-logos-blockchain-app","draft":false,"unlisted":false,"editUrl":"https://github.com/logos-co/logos-docs/tree/main/docs/blockchain/node-app/move-assets-using-logos-blockchain-app.md","tags":[],"version":"current","sidebarPosition":2,"frontMatter":{"title":"Move assets using the Logos Blockchain UI app","doc_type":"procedure","product":"blockchain","topics":["blockchain-app"],"steps_layout":"flat","authors":"danielsanchezq, kashepavadan","owner":"logos","doc_version":1,"slug":"move-assets-using-logos-blockchain-app","sidebar_position":2},"sidebar":"blockchainSidebar","previous":{"title":"Build and run the Logos Blockchain UI app","permalink":"/blockchain/node-app/build-and-run-logos-blockchain-node-app-ui"},"next":{"title":"Bridge assets from Logos Blockchain to a Zone using the Logos Blockchain app","permalink":"/blockchain/node-app/bridge-assets-from-logos-blockchain-to-zone-using-app"}}'),o=s(4848),r=s(8453);let a={title:"Move assets using the Logos Blockchain UI app",doc_type:"procedure",product:"blockchain",topics:["blockchain-app"],steps_layout:"flat",authors:"danielsanchezq, kashepavadan",owner:"logos",doc_version:1,slug:"move-assets-using-logos-blockchain-app",sidebar_position:2},i="Move assets using the Logos Blockchain UI app",l={},d=[{value:"Get started with token transfers between wallet accounts directly from the dashboard UI.",id:"get-started-with-token-transfers-between-wallet-accounts-directly-from-the-dashboard-ui",level:4},{value:"What to expect",id:"what-to-expect",level:2},{value:"Start the node and open the transfer panel",id:"start-the-node-and-open-the-transfer-panel",level:2},{value:"Read the transfer result",id:"read-the-transfer-result",level:2},{value:"Troubleshooting dashboard transfers",id:"troubleshooting-dashboard-transfers",level:2},{value:"Why is the Operations tab disabled?",id:"why-is-the-operations-tab-disabled",level:3},{value:"Why does the result row show "Module not initialized"?",id:"why-does-the-result-row-show-module-not-initialized",level:3},{value:"Why does the result row show "Call failed"?",id:"why-does-the-result-row-show-call-failed",level:3},{value:"Why does the result row show "Error: \u2026"?",id:"why-does-the-result-row-show-error-",level:3}];function h(e){let n={a:"a",admonition:"admonition",code:"code",h1:"h1",h2:"h2",h3:"h3",h4:"h4",header:"header",li:"li",ol:"ol",p:"p",strong:"strong",ul:"ul",...(0,r.R)(),...e.components};return(0,o.jsxs)(o.Fragment,{children:[(0,o.jsx)(n.header,{children:(0,o.jsx)(n.h1,{id:"move-assets-using-the-logos-blockchain-ui-app",children:"Move assets using the Logos Blockchain UI app"})}),"\n",(0,o.jsx)(n.h4,{id:"get-started-with-token-transfers-between-wallet-accounts-directly-from-the-dashboard-ui",children:"Get started with token transfers between wallet accounts directly from the dashboard UI."}),"\n",(0,o.jsxs)(n.p,{children:["This procedure covers how to send funds from one of your wallet addresses to a recipient on the ",(0,o.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#logos-blockchain",children:"Logos Blockchain"})," using the Logos Blockchain dashboard. It is intended for users who need to move tokens between accounts without using the CLI or crafting transactions manually."]}),"\n",(0,o.jsx)(n.p,{children:"Before you start, make sure you have the following:"}),"\n",(0,o.jsxs)(n.ul,{children:["\n",(0,o.jsxs)(n.li,{children:["A built ",(0,o.jsx)(n.a,{href:"/blockchain/node-app/build-and-run-logos-blockchain-node-app-ui",children:"Blockchain node app"})]}),"\n",(0,o.jsxs)(n.li,{children:["At least one wallet address with a positive balance, for example one funded via the faucet at ",(0,o.jsx)(n.a,{href:"https://testnet.blockchain.logos.co/web/faucet/",children:(0,o.jsx)(n.code,{children:"testnet.blockchain.logos.co"})})]}),"\n",(0,o.jsx)(n.li,{children:"The recipient's 64-hex-character public key"}),"\n"]}),"\n",(0,o.jsx)(n.h2,{id:"what-to-expect",children:"What to expect"}),"\n",(0,o.jsxs)(n.ul,{children:["\n",(0,o.jsxs)(n.li,{children:["You can select any funded wallet address as the sender and see its live balance in the ",(0,o.jsx)(n.strong,{children:"From address"})," dropdown."]}),"\n",(0,o.jsxs)(n.li,{children:["You can submit a transfer and receive a transaction ID in the result row directly below the ",(0,o.jsx)(n.strong,{children:"Send"})," button."]}),"\n",(0,o.jsx)(n.li,{children:"Your sender balance updates after a refresh once the transfer is confirmed."}),"\n"]}),"\n",(0,o.jsx)(n.h2,{id:"start-the-node-and-open-the-transfer-panel",children:"Start the node and open the transfer panel"}),"\n",(0,o.jsxs)(n.p,{children:["The ",(0,o.jsx)(n.strong,{children:"Operations"})," tab is disabled until the node is running and synced. Start the node first, then navigate to the transfer panel."]}),"\n",(0,o.jsxs)(n.ol,{children:["\n",(0,o.jsxs)(n.li,{children:["\n",(0,o.jsxs)(n.p,{children:["In the ",(0,o.jsx)(n.strong,{children:"Node"})," tab, start the node and wait until the status reads ",(0,o.jsx)(n.strong,{children:"Running"}),"."]}),"\n",(0,o.jsxs)(n.ul,{children:["\n",(0,o.jsxs)(n.li,{children:["Stopping the node mid-session disables the ",(0,o.jsx)(n.strong,{children:"Operations"})," tab and returns the UI to the ",(0,o.jsx)(n.strong,{children:"Node"})," tab."]}),"\n"]}),"\n"]}),"\n",(0,o.jsxs)(n.li,{children:["\n",(0,o.jsxs)(n.p,{children:["Click ",(0,o.jsx)(n.strong,{children:"Operations"})," in the top navigation, then click ",(0,o.jsx)(n.strong,{children:"Transfer"})," in the sidebar."]}),"\n",(0,o.jsxs)(n.ul,{children:["\n",(0,o.jsxs)(n.li,{children:["The ",(0,o.jsx)(n.strong,{children:"Transfer funds"})," panel appears."]}),"\n"]}),"\n"]}),"\n",(0,o.jsxs)(n.li,{children:["\n",(0,o.jsxs)(n.p,{children:["Select a sender address in the ",(0,o.jsx)(n.strong,{children:"From address"})," dropdown."]}),"\n",(0,o.jsxs)(n.ul,{children:["\n",(0,o.jsx)(n.li,{children:"The address's current balance is shown read-only next to the dropdown."}),"\n",(0,o.jsxs)(n.li,{children:["A balance that could not be fetched is displayed as ",(0,o.jsx)(n.code,{children:"---"}),"."]}),"\n"]}),"\n"]}),"\n",(0,o.jsxs)(n.li,{children:["\n",(0,o.jsxs)(n.p,{children:["Enter the recipient's public key in the ",(0,o.jsx)(n.strong,{children:"To key (64 hex chars)"})," field."]}),"\n"]}),"\n",(0,o.jsxs)(n.li,{children:["\n",(0,o.jsxs)(n.p,{children:["Enter the amount to send in the ",(0,o.jsx)(n.strong,{children:"Amount"})," field."]}),"\n"]}),"\n",(0,o.jsxs)(n.li,{children:["\n",(0,o.jsxs)(n.p,{children:["Click ",(0,o.jsx)(n.strong,{children:"Send"}),"."]}),"\n",(0,o.jsx)(n.admonition,{type:"warning"}),"\n"]}),"\n"]}),"\n",(0,o.jsxs)(n.p,{children:["Transfers are irreversible; double-check the 64-hex recipient key and the amount before pressing ",(0,o.jsx)(n.strong,{children:"Send"}),".\n:::"]}),"\n",(0,o.jsx)(n.h2,{id:"read-the-transfer-result",children:"Read the transfer result"}),"\n",(0,o.jsxs)(n.ol,{children:["\n",(0,o.jsxs)(n.li,{children:["\n",(0,o.jsxs)(n.p,{children:["Check the result row directly below the ",(0,o.jsx)(n.strong,{children:"Send"})," button:"]}),"\n",(0,o.jsxs)(n.ul,{children:["\n",(0,o.jsxs)(n.li,{children:["On success, the row displays the transaction ID or hash returned by the ",(0,o.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#module",children:"module"}),". Click the copy button to copy it to the clipboard."]}),"\n",(0,o.jsxs)(n.li,{children:["On failure, the row displays ",(0,o.jsx)(n.code,{children:"Error: <message>"})," with the rejection reason from the module."]}),"\n"]}),"\n"]}),"\n",(0,o.jsxs)(n.li,{children:["\n",(0,o.jsxs)(n.p,{children:["Confirm the sender's balance decreased by pressing ",(0,o.jsx)(n.strong,{children:"Refresh"})," in the ",(0,o.jsx)(n.strong,{children:"Accounts"})," panel."]}),"\n"]}),"\n"]}),"\n",(0,o.jsx)(n.h2,{id:"troubleshooting-dashboard-transfers",children:"Troubleshooting dashboard transfers"}),"\n",(0,o.jsx)(n.h3,{id:"why-is-the-operations-tab-disabled",children:"Why is the Operations tab disabled?"}),"\n",(0,o.jsxs)(n.p,{children:["The ",(0,o.jsx)(n.strong,{children:"Operations"})," tab is gated on the node status being ",(0,o.jsx)(n.strong,{children:"Running"}),". Start the node from the ",(0,o.jsx)(n.strong,{children:"Node"})," tab and wait for the status to change before attempting a transfer."]}),"\n",(0,o.jsx)(n.h3,{id:"why-does-the-result-row-show-module-not-initialized",children:'Why does the result row show "Module not initialized"?'}),"\n",(0,o.jsx)(n.p,{children:"The backend client is null, which means the module did not initialise correctly at startup. Restart the app and try again."}),"\n",(0,o.jsx)(n.h3,{id:"why-does-the-result-row-show-call-failed",children:'Why does the result row show "Call failed"?'}),"\n",(0,o.jsx)(n.p,{children:"The remote call timed out or returned no reply. Confirm the node is still running and the network connection is healthy, then retry."}),"\n",(0,o.jsx)(n.h3,{id:"why-does-the-result-row-show-error-",children:'Why does the result row show "Error: \u2026"?'}),"\n",(0,o.jsx)(n.p,{children:"The module rejected the transaction. Common causes are an insufficient balance, an invalid recipient key, or a malformed amount string. Verify the recipient key is exactly 64 hex characters and the amount is a valid number, then retry."})]})}function c(e={}){let{wrapper:n}={...(0,r.R)(),...e.components};return n?(0,o.jsx)(n,{...e,children:(0,o.jsx)(h,{...e})}):h(e)}},8453(e,n,s){s.d(n,{R:()=>a,x:()=>i});var t=s(6540);let o={},r=t.createContext(o);function a(e){let n=t.useContext(r);return t.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function i(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(o):e.components||o:a(e.components),t.createElement(r.Provider,{value:n},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/6fc42ee4.91606ac4.js b/assets/js/6fc42ee4.91606ac4.js new file mode 100644 index 00000000..ccee5471 --- /dev/null +++ b/assets/js/6fc42ee4.91606ac4.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunklogos_docs=self.webpackChunklogos_docs||[]).push([["15"],{2896(e,n,s){s.r(n),s.d(n,{metadata:()=>t,default:()=>h,frontMatter:()=>a,contentTitle:()=>r,toc:()=>c,assets:()=>d});var t=JSON.parse('{"id":"messaging/chat-module/send-anonymised-messages-over-the-mix-network","title":"Send anonymised messages over the mix network","description":"Get started sending and receiving messages with sender anonymity using the Logos Chat UI.","source":"@site/docs/messaging/chat-module/send-anonymised-messages-over-the-mix-network.md","sourceDirName":"messaging/chat-module","slug":"/messaging/chat-module/send-anonymised-messages-over-the-mix-network","permalink":"/messaging/chat-module/send-anonymised-messages-over-the-mix-network","draft":false,"unlisted":false,"editUrl":"https://github.com/logos-co/logos-docs/tree/main/docs/messaging/chat-module/send-anonymised-messages-over-the-mix-network.md","tags":[],"version":"current","sidebarPosition":2,"frontMatter":{"title":"Send anonymised messages over the mix network","doc_type":"procedure","product":"messaging","topics":["chat","mix"],"steps_layout":"flat","authors":"chaitanyaprem, kashepavadan","owner":"logos","doc_version":1,"slug":"send-anonymised-messages-over-the-mix-network","sidebar_position":2},"sidebar":"messagingSidebar","previous":{"title":"Build a Logos module that uses the Chat module API","permalink":"/messaging/chat-module/build-logos-module-that-uses-chat-module-api"}}'),i=s(4848),o=s(8453);let a={title:"Send anonymised messages over the mix network",doc_type:"procedure",product:"messaging",topics:["chat","mix"],steps_layout:"flat",authors:"chaitanyaprem, kashepavadan",owner:"logos",doc_version:1,slug:"send-anonymised-messages-over-the-mix-network",sidebar_position:2},r="Send anonymised messages over the mix network",d={},c=[{value:"Get started sending and receiving messages with sender anonymity using the Logos Chat UI.",id:"get-started-sending-and-receiving-messages-with-sender-anonymity-using-the-logos-chat-ui",level:4},{value:"What to expect",id:"what-to-expect",level:2},{value:"Start the first instance and connect to the mix fleet",id:"start-the-first-instance-and-connect-to-the-mix-fleet",level:2},{value:"Start a second instance and exchange messages",id:"start-a-second-instance-and-exchange-messages",level:2},{value:"Troubleshooting mix chat",id:"troubleshooting-mix-chat",level:2},{value:"Why does the app fail to launch after a previous run?",id:"why-does-the-app-fail-to-launch-after-a-previous-run",level:3},{value:"Why does the mix pool stay at <code>MIX 0/4</code> or fill slowly?",id:"why-does-the-mix-pool-stay-at-mix-04-or-fill-slowly",level:3},{value:"Why does the send succeed but the recipient never sees the message?",id:"why-does-the-send-succeed-but-the-recipient-never-sees-the-message",level:3},{value:"Why do two instances on the same machine collide?",id:"why-do-two-instances-on-the-same-machine-collide",level:3}];function l(e){let n={a:"a",admonition:"admonition",code:"code",h1:"h1",h2:"h2",h3:"h3",h4:"h4",header:"header",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,o.R)(),...e.components};return(0,i.jsxs)(i.Fragment,{children:[(0,i.jsx)(n.header,{children:(0,i.jsx)(n.h1,{id:"send-anonymised-messages-over-the-mix-network",children:"Send anonymised messages over the mix network"})}),"\n",(0,i.jsx)(n.h4,{id:"get-started-sending-and-receiving-messages-with-sender-anonymity-using-the-logos-chat-ui",children:"Get started sending and receiving messages with sender anonymity using the Logos Chat UI."}),"\n",(0,i.jsxs)(n.p,{children:["This procedure covers how to run the ",(0,i.jsx)(n.a,{href:"https://github.com/logos-co/logos-chat-ui/tree/feat/logos-testnetv02-mix",children:(0,i.jsx)(n.code,{children:"chat_ui_mix"})})," desktop app, connect to the testnet-0.2 mix fleet, and exchange messages between two instances with sender unlinkability. No configuration is required \u2014 the app ships with shared testnet credentials and discovers the fleet automatically."]}),"\n",(0,i.jsx)(n.p,{children:"Before you start, make sure you have the following:"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsx)(n.li,{children:"macOS or Linux"}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.a,{href:"https://nixos.org/download",children:"Nix"})," with flakes enabled \u2014 add ",(0,i.jsx)(n.code,{children:"experimental-features = nix-command flakes"})," to ",(0,i.jsx)(n.code,{children:"~/.config/nix/nix.conf"})," (create it if needed)"]}),"\n",(0,i.jsxs)(n.li,{children:["Outbound TCP access to the fleet on port ",(0,i.jsx)(n.code,{children:"30304"})]}),"\n"]}),"\n",(0,i.jsx)(n.h2,{id:"what-to-expect",children:"What to expect"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:["You can connect to the testnet-0.2 mix fleet and watch the status bar reach ",(0,i.jsx)(n.strong,{children:"MIX 5/4"}),", which confirms the mix node pool is ready for anonymous sends."]}),"\n",(0,i.jsx)(n.li,{children:"You can exchange intro bundles between two instances and send messages that are routed sender-unlinkably through the 3-hop mixnet."}),"\n",(0,i.jsxs)(n.li,{children:["You can confirm end-to-end delivery by matching the sender's ",(0,i.jsx)(n.code,{children:"Message sent via mix successfully"})," log with the recipient's ",(0,i.jsx)(n.code,{children:"ChatBackend: New message: \u2026"})," log."]}),"\n"]}),"\n",(0,i.jsx)(n.h2,{id:"start-the-first-instance-and-connect-to-the-mix-fleet",children:"Start the first instance and connect to the mix fleet"}),"\n",(0,i.jsxs)(n.p,{children:["The mix chat app can be found in the ",(0,i.jsx)(n.code,{children:"logos-chat-ui"})," repository."]}),"\n",(0,i.jsxs)(n.ol,{children:["\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsxs)(n.p,{children:["Clone the repository and check out the ",(0,i.jsx)(n.code,{children:"feat/logos-testnetv02-mix"})," branch:"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-sh",children:"git clone https://github.com/logos-co/logos-chat-ui.git\ncd logos-chat-ui\ngit checkout feat/logos-testnetv02-mix\n"})}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsx)(n.p,{children:"Start the app:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-sh",children:"nix run --accept-flake-config\n"})}),"\n",(0,i.jsx)(n.admonition,{type:"info"}),"\n"]}),"\n"]}),"\n",(0,i.jsx)(n.p,{children:"The first build pulls the full dependency chain through Nix and takes approximately 15\u201320 minutes. Subsequent launches use the cache and start in seconds.\n:::"}),"\n",(0,i.jsxs)(n.ol,{children:["\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsxs)(n.p,{children:["In the startup popup, pick a demo user (1\u201310) and click ",(0,i.jsx)(n.strong,{children:"Start"}),"."]}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsx)(n.li,{children:"The app loads the bundled testnet credentials and begins discovering the fleet mix nodes via libp2p Kademlia service discovery."}),"\n"]}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsx)(n.p,{children:"Wait for the mix pool to reach the minimum. Check the status bar and console for the following:"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:["Status bar shows ",(0,i.jsx)(n.code,{children:"Connected"}),", ",(0,i.jsx)(n.code,{children:"MIX: ENABLED"}),", and ",(0,i.jsx)(n.code,{children:"USER: <n>"})," immediately after launch. Console shows ",(0,i.jsx)(n.code,{children:"Kademlia mix discovery mounted"})," and ",(0,i.jsx)(n.code,{children:"Connected to static peers"}),"."]}),"\n",(0,i.jsxs)(n.li,{children:["Over the next 1\u20132 minutes the mix pool fills. Status bar moves from ",(0,i.jsx)(n.code,{children:"MIX 0/4"})," toward ",(0,i.jsx)(n.strong,{children:(0,i.jsx)(n.code,{children:"MIX 5/4"})})," (pool size 5 / minimum 4). Console shows ",(0,i.jsx)(n.code,{children:"Waiting for mix node pool"})," then ",(0,i.jsx)(n.strong,{children:(0,i.jsx)(n.code,{children:"Mix node pool ready poolSize=5"})}),"."]}),"\n",(0,i.jsx)(n.li,{children:"The send box remains disabled until the pool reaches the minimum."}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,i.jsx)(n.h2,{id:"start-a-second-instance-and-exchange-messages",children:"Start a second instance and exchange messages"}),"\n",(0,i.jsx)(n.p,{children:"Run the second instance in its own folder with a different demo user and port to avoid credential collisions and RLN rate-limit conflicts."}),"\n",(0,i.jsx)(n.admonition,{type:"warning",children:(0,i.jsxs)(n.p,{children:["Never run two instances in the same working directory. The app stages credential files (",(0,i.jsx)(n.code,{children:"rln_membership.json"}),", ",(0,i.jsx)(n.code,{children:"rln_tree.db"}),", and others) into the current folder; two instances sharing the same folder race on identity. If a previous launch left orphaned processes behind, run ",(0,i.jsx)(n.code,{children:"pkill -9 -f logos_host_qt"})," before starting a new instance."]})}),"\n",(0,i.jsxs)(n.ol,{children:["\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsx)(n.p,{children:"In a new terminal, create a separate working directory for the second instance and start it:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-sh",children:"mkdir ../chat2 && cd ../chat2 # ../ steps out of the logos-chat-ui clone\nCHAT_DEMO_USER=2 CHAT_PORT=62002 nix run ../logos-chat-ui --accept-flake-config\n"})}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"CHAT_DEMO_USER"})," pre-selects a different demo user (use any index other than the one chosen in the first instance)."]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"CHAT_PORT"})," sets the libp2p listen port to avoid a port conflict with the first instance (",(0,i.jsx)(n.code,{children:"0"})," selects a random port)."]}),"\n",(0,i.jsxs)(n.li,{children:["Wait for this instance's status bar to also reach ",(0,i.jsx)(n.strong,{children:(0,i.jsx)(n.code,{children:"MIX 5/4"})})," before sending."]}),"\n"]}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsx)(n.p,{children:"Exchange intro bundles between the two instances:"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:["In one instance, click ",(0,i.jsx)(n.strong,{children:"Get Intro Bundle"}),", then click ",(0,i.jsx)(n.strong,{children:"Copy to Clipboard"})," in the ",(0,i.jsx)(n.strong,{children:"My Bundle"})," dialog that opens."]}),"\n",(0,i.jsxs)(n.li,{children:["In the other instance, click ",(0,i.jsx)(n.strong,{children:"+ new"}),", paste the bundle and enter a first message, then click ",(0,i.jsx)(n.strong,{children:"Create"}),"."]}),"\n"]}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsx)(n.p,{children:"Send a message from one instance and confirm it arrives in the other:"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Sender console:"})," ",(0,i.jsx)(n.code,{children:"Sending via mix (lightpushPublish)"})," \u2192 ",(0,i.jsx)(n.strong,{children:(0,i.jsx)(n.code,{children:"Message sent via mix successfully"})})]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Recipient console:"})," ",(0,i.jsx)(n.code,{children:"ChatBackend: New message: \u2026"})]}),"\n",(0,i.jsx)(n.li,{children:"The message appears in the recipient's conversation within approximately 1\u20132 seconds."}),"\n",(0,i.jsxs)(n.li,{children:["A ",(0,i.jsx)(n.code,{children:"Mix lightpush: no SURB reply within 60s"})," warning in the sender console is benign \u2014 delivery uses the forward path, which is independent of the SURB reply."]}),"\n"]}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsx)(n.p,{children:"Confirm the final status bar state on both instances:"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:["Status bar reads ",(0,i.jsx)(n.strong,{children:(0,i.jsx)(n.code,{children:"MIX 5/4"})})," and ",(0,i.jsx)(n.strong,{children:(0,i.jsx)(n.code,{children:"Connected"})}),"."]}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,i.jsx)(n.h2,{id:"troubleshooting-mix-chat",children:"Troubleshooting mix chat"}),"\n",(0,i.jsx)(n.h3,{id:"why-does-the-app-fail-to-launch-after-a-previous-run",children:"Why does the app fail to launch after a previous run?"}),"\n",(0,i.jsxs)(n.p,{children:["A previous instance was killed while ",(0,i.jsx)(n.code,{children:"logos_host_qt"})," child processes were still running. These orphaned processes block the next launch. Run ",(0,i.jsx)(n.code,{children:"pkill -9 -f logos_host_qt"})," to clear them, then try again."]}),"\n",(0,i.jsxs)(n.h3,{id:"why-does-the-mix-pool-stay-at-mix-04-or-fill-slowly",children:["Why does the mix pool stay at ",(0,i.jsx)(n.code,{children:"MIX 0/4"})," or fill slowly?"]}),"\n",(0,i.jsxs)(n.p,{children:["The pool takes 1\u20132 minutes to fill on first launch. Wait and watch the status bar. If the pool does not reach ",(0,i.jsx)(n.strong,{children:(0,i.jsx)(n.code,{children:"MIX 5/4"})})," after several minutes, the fleet may be temporarily degraded \u2014 the mix fleet is a shared testnet of 5 nodes and connectivity depends on all of them being reachable."]}),"\n",(0,i.jsx)(n.h3,{id:"why-does-the-send-succeed-but-the-recipient-never-sees-the-message",children:"Why does the send succeed but the recipient never sees the message?"}),"\n",(0,i.jsxs)(n.p,{children:["The app receives as a light client and must stay subscribed to a healthy fleet node. If fleet connectivity is degraded, the round-trip may not complete even though the send path works. Retry after confirming the pool is at ",(0,i.jsx)(n.strong,{children:(0,i.jsx)(n.code,{children:"MIX 5/4"})})," on both instances. If the problem persists, the fleet may be temporarily unavailable."]}),"\n",(0,i.jsx)(n.h3,{id:"why-do-two-instances-on-the-same-machine-collide",children:"Why do two instances on the same machine collide?"}),"\n",(0,i.jsxs)(n.p,{children:["Either both instances share the same working directory, the same demo-user index, or both are listening on the same port. Run each instance in its own folder (",(0,i.jsx)(n.code,{children:"chat2"}),", and so on), use a different ",(0,i.jsx)(n.code,{children:"CHAT_DEMO_USER"})," value for each, and set distinct ",(0,i.jsx)(n.code,{children:"CHAT_PORT"})," values."]})]})}function h(e={}){let{wrapper:n}={...(0,o.R)(),...e.components};return n?(0,i.jsx)(n,{...e,children:(0,i.jsx)(l,{...e})}):l(e)}},8453(e,n,s){s.d(n,{R:()=>a,x:()=>r});var t=s(6540);let i={},o=t.createContext(i);function a(e){let n=t.useContext(o);return t.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function r(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(i):e.components||i:a(e.components),t.createElement(o.Provider,{value:n},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/7057f613.4ac33364.js b/assets/js/7057f613.4ac33364.js new file mode 100644 index 00000000..a3cbe17c --- /dev/null +++ b/assets/js/7057f613.4ac33364.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunklogos_docs=self.webpackChunklogos_docs||[]).push([["5479"],{3376(e,s,n){n.r(s),n.d(s,{metadata:()=>t,default:()=>d,frontMatter:()=>i,contentTitle:()=>r,toc:()=>c,assets:()=>l});var t=JSON.parse('{"id":"messaging/get-started/send-1-1-messages-logos-chat","title":"Send 1:1 messages with the Logos Chat app","description":"Try out end-to-end encrypted private messaging over the Logos network.","source":"@site/docs/messaging/get-started/send-1-1-messages-logos-chat.md","sourceDirName":"messaging/get-started","slug":"/messaging/get-started/send-1-1-messages-logos-chat","permalink":"/messaging/get-started/send-1-1-messages-logos-chat","draft":false,"unlisted":false,"editUrl":"https://github.com/logos-co/logos-docs/tree/main/docs/messaging/get-started/send-1-1-messages-logos-chat.md","tags":[],"version":"current","sidebarPosition":2,"frontMatter":{"title":"Send 1:1 messages with the Logos Chat app","doc_type":"procedure","product":"messaging","topics":"messaging","steps_layout":"sectioned","authors":"igor-sirotin, Khushboo-dev-cpp, kashepavadan","owner":"logos","doc_version":1,"slug":"send-1-1-messages-logos-chat","sidebar_position":2},"sidebar":"messagingSidebar","previous":{"title":"Introduction to Logos Messaging","permalink":"/messaging/get-started/introduction-to-logos-messaging"},"next":{"title":"FAQ: Logos Messaging","permalink":"/messaging/get-started/faq"}}'),o=n(4848),a=n(8453);let i={title:"Send 1:1 messages with the Logos Chat app",doc_type:"procedure",product:"messaging",topics:"messaging",steps_layout:"sectioned",authors:"igor-sirotin, Khushboo-dev-cpp, kashepavadan",owner:"logos",doc_version:1,slug:"send-1-1-messages-logos-chat",sidebar_position:2},r="Send 1:1 messages with the Logos Chat app",l={},c=[{value:"Try out end-to-end encrypted private messaging over the Logos network.",id:"try-out-end-to-end-encrypted-private-messaging-over-the-logos-network",level:4},{value:"What to expect",id:"what-to-expect",level:2},{value:"Step 1: Run the Logos Chat app",id:"step-1-run-the-logos-chat-app",level:2},{value:"Option A \u2014 Run in Logos Basecamp",id:"option-a--run-in-logos-basecamp",level:3},{value:"Option B \u2014 Build and run locally with Nix",id:"option-b--build-and-run-locally-with-nix",level:3},{value:"Step 2: Exchange intro bundles",id:"step-2-exchange-intro-bundles",level:2},{value:"Step 3: Send and receive messages",id:"step-3-send-and-receive-messages",level:2},{value:"Troubleshooting Logos Chat",id:"troubleshooting-logos-chat",level:2},{value:"Messages never arrive and the left panel shows "Waiting for connection\u2026"",id:"messages-never-arrive-and-the-left-panel-shows-waiting-for-connection",level:3},{value:"A previously open conversation has disappeared",id:"a-previously-open-conversation-has-disappeared",level:3}];function h(e){let s={a:"a",admonition:"admonition",code:"code",h1:"h1",h2:"h2",h3:"h3",h4:"h4",header:"header",img:"img",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,a.R)(),...e.components};return(0,o.jsxs)(o.Fragment,{children:[(0,o.jsx)(s.header,{children:(0,o.jsx)(s.h1,{id:"send-11-messages-with-the-logos-chat-app",children:"Send 1:1 messages with the Logos Chat app"})}),"\n",(0,o.jsx)(s.h4,{id:"try-out-end-to-end-encrypted-private-messaging-over-the-logos-network",children:"Try out end-to-end encrypted private messaging over the Logos network."}),"\n",(0,o.jsxs)(s.p,{children:["This procedure shows how to use the ",(0,o.jsx)(s.a,{href:"https://docs.logos.co/get-started/glossary#logos-chat",children:"Logos Chat"})," app to exchange encrypted 1:1 messages between two running instances. The app is a QML and C++ UI built on top of the ",(0,o.jsx)(s.a,{href:"https://github.com/logos-co/logos-chat-module",children:(0,o.jsx)(s.code,{children:"logos-chat-module"})}),", which wraps the ",(0,o.jsx)(s.a,{href:"https://github.com/logos-messaging/logos-chat",children:"Logos Chat SDK"}),". It demonstrates the basic private-messaging capabilities of the Logos Chat ",(0,o.jsx)(s.a,{href:"https://docs.logos.co/get-started/glossary#module",children:"Module"}),": ephemeral identity, intro-bundle handshake, and encrypted messaging with no central server. Use this procedure to verify the setup works or to explore the messaging flow for development purposes."]}),"\n",(0,o.jsx)(s.admonition,{type:"info",children:(0,o.jsx)(s.p,{children:"Identity, conversations, and message history exist only while the app is running. Restarting an instance gives it a new identity and clears all conversations."})}),"\n",(0,o.jsx)(s.p,{children:"You need the following to complete this procedure:"}),"\n",(0,o.jsxs)(s.ul,{children:["\n",(0,o.jsx)(s.li,{children:"Linux or macOS"}),"\n",(0,o.jsx)(s.li,{children:"Network access so both instances can reach each other"}),"\n",(0,o.jsx)(s.li,{children:"Two running instances of the app \u2014 on two separate machines, or in two terminals on the same machine"}),"\n"]}),"\n",(0,o.jsx)(s.h2,{id:"what-to-expect",children:"What to expect"}),"\n",(0,o.jsxs)(s.ul,{children:["\n",(0,o.jsxs)(s.li,{children:["You can run the Logos Chat app without building from source by installing it through Logos ",(0,o.jsx)(s.a,{href:"https://docs.logos.co/get-started/glossary#basecamp",children:"Basecamp"}),"."]}),"\n",(0,o.jsx)(s.li,{children:"You can exchange encrypted messages between two instances in real time after completing the intro-bundle handshake."}),"\n",(0,o.jsx)(s.li,{children:"You can verify delivery by confirming each message appears on the receiving instance within a few seconds."}),"\n"]}),"\n",(0,o.jsx)(s.h2,{id:"step-1-run-the-logos-chat-app",children:"Step 1: Run the Logos Chat app"}),"\n",(0,o.jsx)(s.p,{children:"You need two running instances to complete this procedure. Each instance can use either of the options below independently."}),"\n",(0,o.jsx)(s.admonition,{type:"info",children:(0,o.jsxs)(s.p,{children:["When using Nix, all build dependencies \u2014 including Qt6, ",(0,o.jsx)(s.code,{children:"logos-chat-module"}),", and ",(0,o.jsx)(s.code,{children:"liblogoschat"})," \u2014 are fetched automatically."]})}),"\n",(0,o.jsx)(s.h3,{id:"option-a--run-in-logos-basecamp",children:"Option A \u2014 Run in Logos Basecamp"}),"\n",(0,o.jsxs)(s.ol,{children:["\n",(0,o.jsxs)(s.li,{children:["\n",(0,o.jsxs)(s.p,{children:["Download and ",(0,o.jsx)(s.a,{href:"/basecamp/get-started/install-logos-basecamp",children:"install"})," the latest release of Logos Basecamp from ",(0,o.jsx)(s.code,{children:"github.com/logos-co/logos-basecamp/releases"}),"."]}),"\n"]}),"\n",(0,o.jsxs)(s.li,{children:["\n",(0,o.jsxs)(s.p,{children:["In the left bar, select ",(0,o.jsx)(s.strong,{children:"Package Manager"}),"."]}),"\n",(0,o.jsx)(s.p,{children:(0,o.jsx)(s.img,{alt:"Logos Basecamp screenshot",src:n(7720).A+"",width:"1022",height:"806"})}),"\n"]}),"\n",(0,o.jsxs)(s.li,{children:["\n",(0,o.jsxs)(s.p,{children:["Select ",(0,o.jsx)(s.code,{children:"logos-chat-module"})," and ",(0,o.jsx)(s.code,{children:"logos-chat-ui"}),", then click ",(0,o.jsx)(s.strong,{children:"Install"}),"."]}),"\n",(0,o.jsx)(s.p,{children:(0,o.jsx)(s.img,{alt:"Logos Basecamp package installation screenshot",src:n(1581).A+"",width:"1022",height:"806"})}),"\n"]}),"\n",(0,o.jsxs)(s.li,{children:["\n",(0,o.jsxs)(s.p,{children:["Wait until a green ",(0,o.jsx)(s.strong,{children:"Installed"})," label appears next to both modules."]}),"\n"]}),"\n",(0,o.jsxs)(s.li,{children:["\n",(0,o.jsxs)(s.p,{children:["In the left bar, select ",(0,o.jsx)(s.strong,{children:"chat"})," to launch the Logos Chat app."]}),"\n"]}),"\n"]}),"\n",(0,o.jsx)(s.h3,{id:"option-b--build-and-run-locally-with-nix",children:"Option B \u2014 Build and run locally with Nix"}),"\n",(0,o.jsxs)(s.ol,{children:["\n",(0,o.jsxs)(s.li,{children:["\n",(0,o.jsx)(s.p,{children:"Clone the repository and check out the target release:"}),"\n",(0,o.jsx)(s.pre,{children:(0,o.jsx)(s.code,{className:"language-bash",children:"git clone https://github.com/logos-co/logos-chatsdk-ui\ncd logos-chatsdk-ui\ngit checkout v0.1.0\n"})}),"\n"]}),"\n",(0,o.jsxs)(s.li,{children:["\n",(0,o.jsx)(s.p,{children:"Run the standalone app:"}),"\n",(0,o.jsx)(s.pre,{children:(0,o.jsx)(s.code,{className:"language-bash",children:"# Nix fetches all dependencies automatically\nnix run\n"})}),"\n"]}),"\n"]}),"\n",(0,o.jsx)(s.h2,{id:"step-2-exchange-intro-bundles",children:"Step 2: Exchange intro bundles"}),"\n",(0,o.jsxs)(s.p,{children:["The app auto-initialises on launch and displays your identity ID in the bottom status bar. Perform the steps below on ",(0,o.jsx)(s.strong,{children:"both"})," instances \u2014 referred to here as ",(0,o.jsx)(s.strong,{children:"A"})," and ",(0,o.jsx)(s.strong,{children:"B"}),"."]}),"\n",(0,o.jsx)(s.p,{children:(0,o.jsx)(s.img,{alt:"Logos Chat App UI screenshot",src:n(8308).A+"",width:"2148",height:"1524"})}),"\n",(0,o.jsx)(s.p,{children:(0,o.jsx)(s.strong,{children:"On instance A:"})}),"\n",(0,o.jsxs)(s.ol,{children:["\n",(0,o.jsxs)(s.li,{children:["\n",(0,o.jsxs)(s.p,{children:["Click ",(0,o.jsx)(s.strong,{children:"Get Intro Bundle"}),", then click ",(0,o.jsx)(s.strong,{children:"Copy to Clipboard"}),"."]}),"\n",(0,o.jsxs)(s.p,{children:["The bundle is a string starting with ",(0,o.jsx)(s.code,{children:"logos_chatintro\u2026"}),"."]}),"\n"]}),"\n",(0,o.jsxs)(s.li,{children:["\n",(0,o.jsxs)(s.p,{children:["Send the copied bundle to instance B through any ",(0,o.jsx)(s.a,{href:"https://docs.logos.co/get-started/glossary#out-of-band",children:"out-of-band"})," ",(0,o.jsx)(s.a,{href:"https://docs.logos.co/get-started/glossary#channel",children:"channel"}),"."]}),"\n"]}),"\n",(0,o.jsxs)(s.li,{children:["\n",(0,o.jsxs)(s.p,{children:["Close the ",(0,o.jsx)(s.strong,{children:"My Bundle"})," popup."]}),"\n"]}),"\n"]}),"\n",(0,o.jsx)(s.p,{children:(0,o.jsx)(s.strong,{children:"On instance B:"})}),"\n",(0,o.jsxs)(s.ol,{children:["\n",(0,o.jsxs)(s.li,{children:["Click ",(0,o.jsx)(s.strong,{children:"+ new"}),"."]}),"\n",(0,o.jsxs)(s.li,{children:["Paste A's intro bundle into the dialogue, then type an intro message (default: ",(0,o.jsx)(s.code,{children:"Hello!"}),")."]}),"\n",(0,o.jsx)(s.li,{children:"Confirm. A new conversation appears in B's conversation list."}),"\n"]}),"\n",(0,o.jsx)(s.p,{children:(0,o.jsx)(s.strong,{children:"Back on instance A:"})}),"\n",(0,o.jsxs)(s.ol,{children:["\n",(0,o.jsx)(s.li,{children:"Confirm the new conversation appears automatically in A's conversation list, then select it."}),"\n",(0,o.jsx)(s.li,{children:"Verify that B's intro message is visible in the chat panel."}),"\n"]}),"\n",(0,o.jsx)(s.h2,{id:"step-3-send-and-receive-messages",children:"Step 3: Send and receive messages"}),"\n",(0,o.jsxs)(s.ol,{children:["\n",(0,o.jsxs)(s.li,{children:["\n",(0,o.jsx)(s.p,{children:"Select the shared conversation in either instance."}),"\n"]}),"\n",(0,o.jsxs)(s.li,{children:["\n",(0,o.jsxs)(s.p,{children:["Type a message in the message input field, then press ",(0,o.jsx)(s.code,{children:"Enter"})," or click ",(0,o.jsx)(s.code,{children:">>"}),"."]}),"\n",(0,o.jsx)(s.p,{children:"Your messages appear right-aligned in the chat panel; the counterparty's messages appear left-aligned, each with a timestamp."}),"\n"]}),"\n",(0,o.jsxs)(s.li,{children:["\n",(0,o.jsx)(s.p,{children:"From instance A, send a message and observe instance B."}),"\n",(0,o.jsxs)(s.p,{children:[(0,o.jsx)(s.strong,{children:"Expected result:"})," the exact message text appears as an incoming (left-aligned) bubble in B's chat panel within a few seconds. A reply from B appears as an incoming bubble in A."]}),"\n"]}),"\n"]}),"\n",(0,o.jsx)(s.h2,{id:"troubleshooting-logos-chat",children:"Troubleshooting Logos Chat"}),"\n",(0,o.jsx)(s.h3,{id:"messages-never-arrive-and-the-left-panel-shows-waiting-for-connection",children:'Messages never arrive and the left panel shows "Waiting for connection\u2026"'}),"\n",(0,o.jsxs)(s.p,{children:["Both instances need to reach a shared bootstrap peer to connect to the peer-to-peer network and discover each other. Ensure that both instances have a stable internet connection for ",(0,o.jsx)(s.a,{href:"https://docs.logos.co/get-started/glossary#peer-discovery",children:"peer discovery"}),"."]}),"\n",(0,o.jsx)(s.h3,{id:"a-previously-open-conversation-has-disappeared",children:"A previously open conversation has disappeared"}),"\n",(0,o.jsx)(s.p,{children:"Conversations are ephemeral and are not persisted between sessions. Re-exchange intro bundles between the two instances to open a new conversation."})]})}function d(e={}){let{wrapper:s}={...(0,a.R)(),...e.components};return s?(0,o.jsx)(s,{...e,children:(0,o.jsx)(h,{...e})}):h(e)}},1581(e,s,n){n.d(s,{A:()=>t});let t=n.p+"assets/images/basecamp-install-packages-55eacac9e46011841160caab73d481db.png"},7720(e,s,n){n.d(s,{A:()=>t});let t=n.p+"assets/images/basecamp-package-manager-fb8c5213bbfea9e8b310e61a044936ad.png"},8308(e,s,n){n.d(s,{A:()=>t});let t=n.p+"assets/images/chat-app-screenshot-60c3c1178fc42c4798eebdb6dbdde82e.png"},8453(e,s,n){n.d(s,{R:()=>i,x:()=>r});var t=n(6540);let o={},a=t.createContext(o);function i(e){let s=t.useContext(a);return t.useMemo(function(){return"function"==typeof e?e(s):{...s,...e}},[s,e])}function r(e){let s;return s=e.disableParentContext?"function"==typeof e.components?e.components(o):e.components||o:i(e.components),t.createElement(a.Provider,{value:s},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/718f62c6.d013b70a.js b/assets/js/718f62c6.d013b70a.js new file mode 100644 index 00000000..bb25f6d4 --- /dev/null +++ b/assets/js/718f62c6.d013b70a.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunklogos_docs=self.webpackChunklogos_docs||[]).push([["5367"],{3965(e,o,s){s.r(o),s.d(o,{metadata:()=>n,default:()=>h,frontMatter:()=>i,contentTitle:()=>c,toc:()=>l,assets:()=>d});var n=JSON.parse('{"id":"messaging/concepts/understand-peer-discovery/about-peer-exchange","title":"About peer exchange","description":"Understand how light nodes request peers from other nodes without relying on Discv5.","source":"@site/docs/messaging/concepts/understand-peer-discovery/about-peer-exchange.md","sourceDirName":"messaging/concepts/understand-peer-discovery","slug":"/messaging/concepts/understand-peer-discovery/peer-exchange","permalink":"/messaging/concepts/understand-peer-discovery/peer-exchange","draft":false,"unlisted":false,"editUrl":"https://github.com/logos-co/logos-docs/tree/main/docs/messaging/concepts/understand-peer-discovery/about-peer-exchange.md","tags":[],"version":"current","sidebarPosition":5,"frontMatter":{"title":"About peer exchange","doc_type":"concept","product":"messaging","topics":"peer-discovery, peer-exchange","authors":"LordGhostX, fryorcraken","owner":"logos","doc_version":1,"slug":"peer-exchange","sidebar_position":5},"sidebar":"messagingSidebar","previous":{"title":"About Discv5","permalink":"/messaging/concepts/understand-peer-discovery/discv5"},"next":{"title":"Understand Logos Delivery security features","permalink":"/messaging/concepts/security-features"}}'),r=s(4848),t=s(8453);let i={title:"About peer exchange",doc_type:"concept",product:"messaging",topics:"peer-discovery, peer-exchange",authors:"LordGhostX, fryorcraken",owner:"logos",doc_version:1,slug:"peer-exchange",sidebar_position:5},c="About peer exchange",d={},l=[{value:"Understand how light nodes request peers from other nodes without relying on Discv5.",id:"understand-how-light-nodes-request-peers-from-other-nodes-without-relying-on-discv5",level:4},{value:"How peer exchange works",id:"how-peer-exchange-works",level:2},{value:"Pros and cons",id:"pros-and-cons",level:2}];function a(e){let o={a:"a",admonition:"admonition",code:"code",h1:"h1",h2:"h2",h4:"h4",header:"header",li:"li",ol:"ol",p:"p",pre:"pre",ul:"ul",...(0,t.R)(),...e.components};return(0,r.jsxs)(r.Fragment,{children:[(0,r.jsx)(o.header,{children:(0,r.jsx)(o.h1,{id:"about-peer-exchange",children:"About peer exchange"})}),"\n",(0,r.jsx)(o.h4,{id:"understand-how-light-nodes-request-peers-from-other-nodes-without-relying-on-discv5",children:"Understand how light nodes request peers from other nodes without relying on Discv5."}),"\n",(0,r.jsxs)(o.p,{children:["The primary objective of this protocol is to facilitate peer connectivity for resource-limited devices without relying on ",(0,r.jsx)(o.code,{children:"Discv5"}),". The ",(0,r.jsx)(o.a,{href:"https://docs.logos.co/get-started/glossary#peer-exchange",children:"peer exchange"})," protocol enables ",(0,r.jsx)(o.a,{href:"https://docs.logos.co/get-started/glossary#light-node",children:"light nodes"})," to request peers from other nodes within the network."]}),"\n",(0,r.jsx)(o.admonition,{type:"info",children:(0,r.jsxs)(o.p,{children:[(0,r.jsx)(o.code,{children:"Peer Exchange"})," enables requesting random peers from other network nodes without revealing information about their connectivity or neighbourhood."]})}),"\n",(0,r.jsx)(o.h2,{id:"how-peer-exchange-works",children:"How peer exchange works"}),"\n",(0,r.jsx)(o.pre,{children:(0,r.jsx)(o.code,{className:"language-mermaid",children:"sequenceDiagram\n Alice->>DNS Server: (1) Execute DNS Discovery\n DNS Server--\x3e>Alice: (2) Bob's multiaddr (websocket)\n Alice--\x3e>Bob: (3) Dial\n Alice->>Bob: (4) Peer Exchange Query\n Bob--\x3e>Alice: (5) Carol's ENR, David's ENR\n Alice->>Alice: (6) Decode ENRs\n Alice->>Carol: (7) Dial\n Alice->>David: (7) Dial\n Alice--\x3e>Bob: (8) Disconnect\n"})}),"\n",(0,r.jsxs)(o.ol,{children:["\n",(0,r.jsxs)(o.li,{children:[(0,r.jsx)(o.a,{href:"https://docs.logos.co/get-started/glossary#dns-discovery",children:"DNS Discovery"})," protocol is executed."]}),"\n",(0,r.jsx)(o.li,{children:"Alice retrieves Bob's websocket multiaddr from DNS Server."}),"\n",(0,r.jsx)(o.li,{children:"Alice dials Bob using libp2p protocols."}),"\n",(0,r.jsx)(o.li,{children:"Alice executes a Peer Exchange query to Bob."}),"\n",(0,r.jsxs)(o.li,{children:["Bob returns Carol's and David's ",(0,r.jsx)(o.a,{href:"https://docs.logos.co/get-started/glossary#enr",children:"ENR"})," to Alice."]}),"\n",(0,r.jsx)(o.li,{children:"Alice decodes ENRs and extracts Carol's and David's websocket multiaddrs."}),"\n",(0,r.jsx)(o.li,{children:"Alice dials Carol and David."}),"\n",(0,r.jsxs)(o.li,{children:["Alice can now drop the connection with Bob (bootstrap node); Alice has 2 connections to the ",(0,r.jsx)(o.a,{href:"https://docs.logos.co/get-started/glossary#logos-delivery",children:"Logos Delivery"})," Network."]}),"\n"]}),"\n",(0,r.jsx)(o.h2,{id:"pros-and-cons",children:"Pros and cons"}),"\n",(0,r.jsx)(o.p,{children:"Pros:"}),"\n",(0,r.jsxs)(o.ul,{children:["\n",(0,r.jsx)(o.li,{children:"Low resource requirements."}),"\n",(0,r.jsxs)(o.li,{children:["Decentralised with random sampling of nodes from a global view using ",(0,r.jsx)(o.code,{children:"Discv5"}),"."]}),"\n"]}),"\n",(0,r.jsx)(o.p,{children:"Cons:"}),"\n",(0,r.jsxs)(o.ul,{children:["\n",(0,r.jsx)(o.li,{children:"Decreased anonymity."}),"\n",(0,r.jsx)(o.li,{children:"Imposes additional load on responder nodes."}),"\n"]})]})}function h(e={}){let{wrapper:o}={...(0,t.R)(),...e.components};return o?(0,r.jsx)(o,{...e,children:(0,r.jsx)(a,{...e})}):a(e)}},8453(e,o,s){s.d(o,{R:()=>i,x:()=>c});var n=s(6540);let r={},t=n.createContext(r);function i(e){let o=n.useContext(t);return n.useMemo(function(){return"function"==typeof e?e(o):{...o,...e}},[o,e])}function c(e){let o;return o=e.disableParentContext?"function"==typeof e.components?e.components(r):e.components||r:i(e.components),n.createElement(t.Provider,{value:o},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/808de28f.6973f419.js b/assets/js/808de28f.6973f419.js new file mode 100644 index 00000000..31d7adc2 --- /dev/null +++ b/assets/js/808de28f.6973f419.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunklogos_docs=self.webpackChunklogos_docs||[]).push([["5281"],{44(e,s,r){r.r(s),r.d(s,{metadata:()=>n,default:()=>l,frontMatter:()=>i,contentTitle:()=>c,toc:()=>d,assets:()=>a});var n=JSON.parse('{"id":"messaging/concepts/understand-peer-discovery/understand-peer-discovery","title":"Understand peer discovery","description":"Learn how a node bootstraps and finds the peers it needs to operate.","source":"@site/docs/messaging/concepts/understand-peer-discovery/understand-peer-discovery.md","sourceDirName":"messaging/concepts/understand-peer-discovery","slug":"/messaging/concepts/understand-peer-discovery/peer-discovery","permalink":"/messaging/concepts/understand-peer-discovery/peer-discovery","draft":false,"unlisted":false,"editUrl":"https://github.com/logos-co/logos-docs/tree/main/docs/messaging/concepts/understand-peer-discovery/understand-peer-discovery.md","tags":[],"version":"current","sidebarPosition":1,"frontMatter":{"title":"Understand peer discovery","doc_type":"concept","product":"messaging","topics":"peer-discovery, networking","authors":"LordGhostX, fryorcraken","owner":"logos","doc_version":1,"slug":"peer-discovery","sidebar_position":1},"sidebar":"messagingSidebar","previous":{"title":"About transports","permalink":"/messaging/concepts/transports"},"next":{"title":"About static peers","permalink":"/messaging/concepts/understand-peer-discovery/static-peers"}}'),o=r(4848),t=r(8453);let i={title:"Understand peer discovery",doc_type:"concept",product:"messaging",topics:"peer-discovery, networking",authors:"LordGhostX, fryorcraken",owner:"logos",doc_version:1,slug:"peer-discovery",sidebar_position:1},c="Understand peer discovery",a={},d=[{value:"Learn how a node bootstraps and finds the peers it needs to operate.",id:"learn-how-a-node-bootstraps-and-finds-the-peers-it-needs-to-operate",level:4},{value:"Why a node seeks more peers",id:"why-a-node-seeks-more-peers",level:2},{value:"Discovery mechanisms",id:"discovery-mechanisms",level:2}];function p(e){let s={a:"a",h1:"h1",h2:"h2",h4:"h4",header:"header",li:"li",p:"p",ul:"ul",...(0,t.R)(),...e.components};return(0,o.jsxs)(o.Fragment,{children:[(0,o.jsx)(s.header,{children:(0,o.jsx)(s.h1,{id:"understand-peer-discovery",children:"Understand peer discovery"})}),"\n",(0,o.jsx)(s.h4,{id:"learn-how-a-node-bootstraps-and-finds-the-peers-it-needs-to-operate",children:"Learn how a node bootstraps and finds the peers it needs to operate."}),"\n",(0,o.jsxs)(s.p,{children:["When initialising a ",(0,o.jsx)(s.a,{href:"https://docs.logos.co/get-started/glossary#logos-delivery",children:"Logos Delivery"})," node, it must connect with other peers to enable message sending, receiving, and retrieval. To achieve this, a discovery mechanism is employed to locate other peers in the network. This process is known as ",(0,o.jsx)(s.a,{href:"https://docs.logos.co/get-started/glossary#bootstrapping",children:"bootstrapping"}),"."]}),"\n",(0,o.jsx)(s.h2,{id:"why-a-node-seeks-more-peers",children:"Why a node seeks more peers"}),"\n",(0,o.jsx)(s.p,{children:"Once a connection is established, the node must actively seek out additional peers to have:"}),"\n",(0,o.jsxs)(s.ul,{children:["\n",(0,o.jsxs)(s.li,{children:["Sufficient peers in the ",(0,o.jsx)(s.a,{href:"/messaging/concepts/protocols#relay",children:"Relay"})," mesh: The goal is to have at least 6 peers in the mesh. This ensures a robust network where messages can be efficiently relayed."]}),"\n",(0,o.jsx)(s.li,{children:"Reserve peers for backup: It is essential to have a surplus of peers available as reserves. These reserves are backups when the current peers become overloaded or experience unexpected disconnections."}),"\n",(0,o.jsxs)(s.li,{children:["Peers with specific capabilities: The node seeks out peers with specific capabilities, such as ",(0,o.jsx)(s.a,{href:"/messaging/concepts/protocols#store",children:"Store"}),", ",(0,o.jsx)(s.a,{href:"/messaging/concepts/protocols#light-push",children:"Light Push"}),", or ",(0,o.jsx)(s.a,{href:"/messaging/concepts/protocols#filter",children:"Filter"}),". This allows for targeted interactions and enhanced functionality based on the desired capabilities."]}),"\n"]}),"\n",(0,o.jsx)(s.h2,{id:"discovery-mechanisms",children:"Discovery mechanisms"}),"\n",(0,o.jsxs)(s.p,{children:["Logos Delivery supports multiple ",(0,o.jsx)(s.a,{href:"https://docs.logos.co/get-started/glossary#peer-discovery",children:"peer discovery"})," mechanisms, such as:"]}),"\n",(0,o.jsxs)(s.ul,{children:["\n",(0,o.jsx)(s.li,{children:(0,o.jsx)(s.a,{href:"/messaging/concepts/understand-peer-discovery/static-peers",children:"Configuring Static Peers"})}),"\n",(0,o.jsx)(s.li,{children:(0,o.jsx)(s.a,{href:"/messaging/concepts/understand-peer-discovery/dns-discovery",children:"Peer Discovery via DNS"})}),"\n",(0,o.jsx)(s.li,{children:(0,o.jsx)(s.a,{href:"/messaging/concepts/understand-peer-discovery/discv5",children:"Discv5 Ambient Peer Discovery"})}),"\n",(0,o.jsx)(s.li,{children:(0,o.jsx)(s.a,{href:"/messaging/concepts/understand-peer-discovery/peer-exchange",children:"Peer Exchange"})}),"\n"]})]})}function l(e={}){let{wrapper:s}={...(0,t.R)(),...e.components};return s?(0,o.jsx)(s,{...e,children:(0,o.jsx)(p,{...e})}):p(e)}},8453(e,s,r){r.d(s,{R:()=>i,x:()=>c});var n=r(6540);let o={},t=n.createContext(o);function i(e){let s=n.useContext(t);return n.useMemo(function(){return"function"==typeof e?e(s):{...s,...e}},[s,e])}function c(e){let s;return s=e.disableParentContext?"function"==typeof e.components?e.components(o):e.components||o:i(e.components),n.createElement(t.Provider,{value:s},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/83f53bbb.acae4b54.js b/assets/js/83f53bbb.acae4b54.js new file mode 100644 index 00000000..bf3178de --- /dev/null +++ b/assets/js/83f53bbb.acae4b54.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunklogos_docs=self.webpackChunklogos_docs||[]).push([["593"],{1483(e,o,l){l.r(o),l.d(o,{metadata:()=>s,default:()=>h,frontMatter:()=>t,contentTitle:()=>r,toc:()=>i,assets:()=>c});var s=JSON.parse('{"id":"core/build-modules/start-a-logos-module-from-the-cli","title":"Start a Logos module from the CLI","description":"Explore how to load and call a Logos module from the command line using logoscore.","source":"@site/docs/core/build-modules/start-a-logos-module-from-the-cli.md","sourceDirName":"core/build-modules","slug":"/core/build-modules/start-a-logos-module-from-the-cli","permalink":"/core/build-modules/start-a-logos-module-from-the-cli","draft":false,"unlisted":false,"editUrl":"https://github.com/logos-co/logos-docs/tree/main/docs/core/build-modules/start-a-logos-module-from-the-cli.md","tags":[],"version":"current","sidebarPosition":1,"frontMatter":{"title":"Start a Logos module from the CLI","doc_type":"procedure","product":"core","topics":"core","steps_layout":"flat","authors":"iurimatias, kashepavadan","owner":"logos","doc_version":1,"slug":"start-a-logos-module-from-the-cli","sidebar_position":1},"sidebar":"coreSidebar","next":{"title":"Build and run a Logos core module","permalink":"/core/build-modules/build-and-run-a-logos-core-module"}}'),n=l(4848),d=l(8453);let t={title:"Start a Logos module from the CLI",doc_type:"procedure",product:"core",topics:"core",steps_layout:"flat",authors:"iurimatias, kashepavadan",owner:"logos",doc_version:1,slug:"start-a-logos-module-from-the-cli",sidebar_position:1},r="Start a Logos module from the CLI",c={},i=[{value:"Explore how to load and call a Logos module from the command line using <code>logoscore</code>.",id:"explore-how-to-load-and-call-a-logos-module-from-the-command-line-using-logoscore",level:4},{value:"What to expect",id:"what-to-expect",level:2},{value:"Install the module",id:"install-the-module",level:2},{value:"Call module methods",id:"call-module-methods",level:2},{value:"Troubleshooting <code>logoscore</code> module startup",id:"troubleshooting-logoscore-module-startup",level:2},{value:"The modules directory is not found",id:"the-modules-directory-is-not-found",level:3},{value:"The platform key in <code>manifest.json</code> does not match",id:"the-platform-key-in-manifestjson-does-not-match",level:3},{value:"The daemon does not respond after <code>sleep 3</code>",id:"the-daemon-does-not-respond-after-sleep-3",level:3}];function a(e){let o={a:"a",code:"code",h1:"h1",h2:"h2",h3:"h3",h4:"h4",header:"header",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,d.R)(),...e.components};return(0,n.jsxs)(n.Fragment,{children:[(0,n.jsx)(o.header,{children:(0,n.jsx)(o.h1,{id:"start-a-logos-module-from-the-cli",children:"Start a Logos module from the CLI"})}),"\n",(0,n.jsxs)(o.h4,{id:"explore-how-to-load-and-call-a-logos-module-from-the-command-line-using-logoscore",children:["Explore how to load and call a Logos module from the command line using ",(0,n.jsx)(o.code,{children:"logoscore"}),"."]}),"\n",(0,n.jsxs)(o.p,{children:["This guide covers how to build and install a Logos ",(0,n.jsx)(o.a,{href:"https://docs.logos.co/get-started/glossary#module",children:"module"}),", start the ",(0,n.jsx)(o.code,{children:"logoscore"})," daemon, and call module methods from the command line. It is intended for users who want to run an existing module, or developers who have already built a module binary and want to run it locally for testing or development. By the end you will have a running ",(0,n.jsx)(o.code,{children:"logoscore"})," instance that loads ",(0,n.jsx)(o.a,{href:"https://github.com/logos-co/logos-accounts-module",children:(0,n.jsx)(o.code,{children:"accounts_module"})})," as an example module and returns results for mnemonic generation and relative strength."]}),"\n",(0,n.jsxs)(o.p,{children:[(0,n.jsx)(o.strong,{children:"Before you start"}),", make sure you have the following:"]}),"\n",(0,n.jsxs)(o.ul,{children:["\n",(0,n.jsxs)(o.li,{children:["\n",(0,n.jsxs)(o.p,{children:[(0,n.jsx)(o.strong,{children:"Nix"})," with flakes enabled. Install from ",(0,n.jsx)(o.a,{href:"https://nixos.org/download.html",children:"nixos.org"}),", then enable flakes:"]}),"\n",(0,n.jsx)(o.pre,{children:(0,n.jsx)(o.code,{className:"language-bash",children:"mkdir -p ~/.config/nix\necho 'experimental-features = nix-command flakes' >> ~/.config/nix/nix.conf\n"})}),"\n",(0,n.jsxs)(o.p,{children:["Verify: ",(0,n.jsx)(o.code,{children:'nix flake --help >/dev/null 2>&1 && echo "Flakes enabled"'})]}),"\n"]}),"\n",(0,n.jsxs)(o.li,{children:["\n",(0,n.jsx)(o.p,{children:"Git"}),"\n"]}),"\n",(0,n.jsxs)(o.li,{children:["\n",(0,n.jsx)(o.p,{children:"Linux or macOS"}),"\n"]}),"\n",(0,n.jsxs)(o.li,{children:["\n",(0,n.jsxs)(o.p,{children:[(0,n.jsx)(o.a,{href:"https://github.com/logos-co/logos-logoscore-cli/releases/tag/0.2.0",children:(0,n.jsx)(o.code,{children:"logoscore"})}),", ",(0,n.jsx)(o.a,{href:"https://github.com/logos-co/logos-package-downloader/releases/tag/0.2.0",children:(0,n.jsx)(o.code,{children:"lgpd"})}),", and ",(0,n.jsx)(o.a,{href:"https://github.com/logos-co/logos-package-manager/releases/tag/0.2.0",children:(0,n.jsx)(o.code,{children:"lgpm"})})," installed. To install these tools, use the ",(0,n.jsx)(o.code,{children:"install-node-tools.sh"})," helper script:"]}),"\n",(0,n.jsx)(o.pre,{children:(0,n.jsx)(o.code,{className:"language-bash",children:'curl -fsSL https://raw.githubusercontent.com/logos-co/logos-docs/main/resources/scripts/install-node-tools.sh | sh\nexport PATH="$PWD/bin:$PATH"\n'})}),"\n"]}),"\n"]}),"\n",(0,n.jsx)(o.h2,{id:"what-to-expect",children:"What to expect"}),"\n",(0,n.jsxs)(o.ul,{children:["\n",(0,n.jsxs)(o.li,{children:["You can load ",(0,n.jsx)(o.code,{children:"accounts_module"})," into a running ",(0,n.jsx)(o.code,{children:"logoscore"})," daemon and call its methods."]}),"\n",(0,n.jsx)(o.li,{children:"You can generate a BIP-39 mnemonic phrase and measure its entropy strength with no prior setup."}),"\n",(0,n.jsxs)(o.li,{children:["You have a working local ",(0,n.jsx)(o.code,{children:"modules"})," directory that ",(0,n.jsx)(o.code,{children:"logoscore"})," can scan for future modules."]}),"\n"]}),"\n",(0,n.jsx)(o.h2,{id:"install-the-module",children:"Install the module"}),"\n",(0,n.jsxs)(o.p,{children:[(0,n.jsx)(o.code,{children:"logoscore"})," expects each module in its own subdirectory containing a ",(0,n.jsx)(o.code,{children:"manifest.json"}),". The ",(0,n.jsx)(o.code,{children:"lgpm"})," package manager handles this layout automatically when given an LGX package."]}),"\n",(0,n.jsxs)(o.ol,{children:["\n",(0,n.jsxs)(o.li,{children:["\n",(0,n.jsxs)(o.p,{children:["Clone the ",(0,n.jsx)(o.code,{children:"logos-accounts-module"})," repository and build the LGX package:"]}),"\n",(0,n.jsx)(o.pre,{children:(0,n.jsx)(o.code,{className:"language-bash",children:"git clone https://github.com/logos-co/logos-accounts-module.git\ncd logos-accounts-module\n\nnix build '.#lgx'\n\ncd ..\n"})}),"\n"]}),"\n",(0,n.jsxs)(o.li,{children:["\n",(0,n.jsxs)(o.p,{children:["Create the ",(0,n.jsx)(o.code,{children:"modules"})," directory and copy the pre-loaded logos modules to it:"]}),"\n",(0,n.jsx)(o.pre,{children:(0,n.jsx)(o.code,{className:"language-bash",children:"mkdir -p modules\ncp -RL ./logos/modules/. ./modules/\n"})}),"\n"]}),"\n",(0,n.jsxs)(o.li,{children:["\n",(0,n.jsxs)(o.p,{children:["Install the LGX package into the ",(0,n.jsx)(o.code,{children:"modules"})," directory:"]}),"\n",(0,n.jsx)(o.pre,{children:(0,n.jsx)(o.code,{className:"language-bash",children:"lgpm --modules-dir ./modules install --file ./logos-accounts-module/result/*.lgx\n"})}),"\n",(0,n.jsx)(o.p,{children:"After installation, the directory structure looks like this:"}),"\n",(0,n.jsx)(o.pre,{children:(0,n.jsx)(o.code,{children:"modules/accounts_module/\n\u251C\u2500\u2500 accounts_module_plugin.dylib # (or .so on Linux)\n\u251C\u2500\u2500 manifest.json # Auto-generated by lgx\n\u2514\u2500\u2500 variant # Platform variant identifier\n"})}),"\n"]}),"\n",(0,n.jsxs)(o.li,{children:["\n",(0,n.jsx)(o.p,{children:"Confirm the module was installed correctly:"}),"\n",(0,n.jsx)(o.pre,{children:(0,n.jsx)(o.code,{className:"language-bash",children:"lgpm --modules-dir ./modules list\n"})}),"\n"]}),"\n"]}),"\n",(0,n.jsx)(o.h2,{id:"call-module-methods",children:"Call module methods"}),"\n",(0,n.jsxs)(o.p,{children:["With the module installed, start the ",(0,n.jsx)(o.a,{href:"https://github.com/logos-co/logos-logoscore-cli",children:(0,n.jsx)(o.code,{children:"logoscore"})})," daemon, load the module, and call its methods."]}),"\n",(0,n.jsxs)(o.ol,{children:["\n",(0,n.jsxs)(o.li,{children:["\n",(0,n.jsx)(o.p,{children:"Start the daemon in the background, pointing it at the modules directory, and wait to initialise:"}),"\n",(0,n.jsx)(o.pre,{children:(0,n.jsx)(o.code,{className:"language-bash",children:"logoscore -D -m ./modules &\nsleep 3\n"})}),"\n"]}),"\n",(0,n.jsxs)(o.li,{children:["\n",(0,n.jsxs)(o.p,{children:["Check the status of ",(0,n.jsx)(o.code,{children:"logoscore"}),":"]}),"\n",(0,n.jsx)(o.pre,{children:(0,n.jsx)(o.code,{className:"language-bash",children:"logoscore status\n"})}),"\n"]}),"\n",(0,n.jsxs)(o.li,{children:["\n",(0,n.jsx)(o.p,{children:"Load the module and confirm that it was loaded:"}),"\n",(0,n.jsx)(o.pre,{children:(0,n.jsx)(o.code,{className:"language-bash",children:"logoscore load-module accounts_module\n\nlogoscore list-modules\n"})}),"\n"]}),"\n",(0,n.jsxs)(o.li,{children:["\n",(0,n.jsx)(o.p,{children:"Generate a random BIP-39 mnemonic phrase with 12 words:"}),"\n",(0,n.jsx)(o.pre,{children:(0,n.jsx)(o.code,{className:"language-bash",children:"logoscore call accounts_module createRandomMnemonic 12\n"})}),"\n"]}),"\n",(0,n.jsxs)(o.li,{children:["\n",(0,n.jsx)(o.p,{children:"Map a mnemonic word count to its entropy strength in bits \u2014 for example, 12 words is 128 bits."}),"\n",(0,n.jsx)(o.pre,{children:(0,n.jsx)(o.code,{className:"language-bash",children:"logoscore call accounts_module lengthToEntropyStrength 12\n"})}),"\n"]}),"\n",(0,n.jsxs)(o.li,{children:["\n",(0,n.jsxs)(o.p,{children:["Inspect all available methods in ",(0,n.jsx)(o.code,{children:"accounts_module"})," with ",(0,n.jsx)(o.code,{children:"module_info"}),":"]}),"\n",(0,n.jsx)(o.pre,{children:(0,n.jsx)(o.code,{className:"language-bash",children:"logoscore module-info accounts_module\n"})}),"\n"]}),"\n",(0,n.jsxs)(o.li,{children:["\n",(0,n.jsx)(o.p,{children:"Stop the daemon:"}),"\n",(0,n.jsx)(o.pre,{children:(0,n.jsx)(o.code,{className:"language-bash",children:"logoscore stop\nsleep 2\n"})}),"\n",(0,n.jsxs)(o.p,{children:["For inline (legacy) mode and other ",(0,n.jsx)(o.code,{children:"logoscore"})," options, see the ",(0,n.jsx)(o.a,{href:"https://github.com/logos-co/logos-tutorial/blob/tutorial-v4/logos-developer-guide.md#61-running-with-logoscore",children:"Developer Guide \u2014 Running with logoscore"}),"."]}),"\n"]}),"\n"]}),"\n",(0,n.jsxs)(o.h2,{id:"troubleshooting-logoscore-module-startup",children:["Troubleshooting ",(0,n.jsx)(o.code,{children:"logoscore"})," module startup"]}),"\n",(0,n.jsx)(o.h3,{id:"the-modules-directory-is-not-found",children:"The modules directory is not found"}),"\n",(0,n.jsxs)(o.p,{children:["Confirm the module subdirectory exists and contains a ",(0,n.jsx)(o.code,{children:"manifest.json"})," file. Installing the package again with the correct ",(0,n.jsx)(o.code,{children:"--modules-dir"})," path resolves this in most cases."]}),"\n",(0,n.jsxs)(o.h3,{id:"the-platform-key-in-manifestjson-does-not-match",children:["The platform key in ",(0,n.jsx)(o.code,{children:"manifest.json"})," does not match"]}),"\n",(0,n.jsxs)(o.p,{children:[(0,n.jsx)(o.code,{children:"logoscore"})," matches the ",(0,n.jsx)(o.code,{children:"main"})," object in ",(0,n.jsx)(o.code,{children:"manifest.json"})," against your OS and architecture (for example, ",(0,n.jsx)(o.code,{children:"linux-aarch64"})," or ",(0,n.jsx)(o.code,{children:"darwin-arm64"}),"). Rebuild the LGX package on the target platform and reinstall."]}),"\n",(0,n.jsxs)(o.h3,{id:"the-daemon-does-not-respond-after-sleep-3",children:["The daemon does not respond after ",(0,n.jsx)(o.code,{children:"sleep 3"})]}),"\n",(0,n.jsx)(o.p,{children:"Increase the sleep duration if your machine is slow to initialise, or check for port conflicts by inspecting the daemon's stderr output."})]})}function h(e={}){let{wrapper:o}={...(0,d.R)(),...e.components};return o?(0,n.jsx)(o,{...e,children:(0,n.jsx)(a,{...e})}):a(e)}},8453(e,o,l){l.d(o,{R:()=>t,x:()=>r});var s=l(6540);let n={},d=s.createContext(n);function t(e){let o=s.useContext(d);return s.useMemo(function(){return"function"==typeof e?e(o):{...o,...e}},[o,e])}function r(e){let o;return o=e.disableParentContext?"function"==typeof e.components?e.components(n):e.components||n:t(e.components),s.createElement(d.Provider,{value:o},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/862b4f69.e0a93c92.js b/assets/js/862b4f69.e0a93c92.js new file mode 100644 index 00000000..3c87f316 --- /dev/null +++ b/assets/js/862b4f69.e0a93c92.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunklogos_docs=self.webpackChunklogos_docs||[]).push([["1701"],{2337(e,t,s){s.r(t),s.d(t,{metadata:()=>o,default:()=>g,frontMatter:()=>r,contentTitle:()=>n,toc:()=>c,assets:()=>d});var o=JSON.parse('{"id":"get-started/logos-ecosystem-repositories","title":"Logos ecosystem repositories","description":"Review the key repositories across the logos organisations.","source":"@site/docs/get-started/logos-ecosystem-repositories.md","sourceDirName":"get-started","slug":"/get-started/logos-ecosystem-repositories","permalink":"/get-started/logos-ecosystem-repositories","draft":false,"unlisted":false,"editUrl":"https://github.com/logos-co/logos-docs/tree/main/docs/get-started/logos-ecosystem-repositories.md","tags":[],"version":"current","sidebarPosition":2,"frontMatter":{"title":"Logos ecosystem repositories","doc_type":"reference","product":"logos","topics":"Repositories","authors":"cheny0","owner":"logos","doc_version":1,"slug":"logos-ecosystem-repositories","sidebar_position":2},"sidebar":"getStartedSidebar","previous":{"title":"Introduction to Logos","permalink":"/get-started/what-is-logos"},"next":{"title":"Glossary","permalink":"/get-started/glossary"}}'),l=s(4848),i=s(8453);let r={title:"Logos ecosystem repositories",doc_type:"reference",product:"logos",topics:"Repositories",authors:"cheny0",owner:"logos",doc_version:1,slug:"logos-ecosystem-repositories",sidebar_position:2},n="Logos ecosystem repositories",d={},c=[{value:"Review the key repositories across the logos organisations.",id:"review-the-key-repositories-across-the-logos-organisations",level:4},{value:"Orientation and contribution resources",id:"orientation-and-contribution-resources",level:2},{value:"Logos Basecamp",id:"logos-basecamp",level:2},{value:"Logos Core",id:"logos-core",level:2},{value:"Core modules and applications",id:"core-modules-and-applications",level:2},{value:"Logos Blockchain",id:"logos-blockchain",level:2},{value:"Logos Execution Zone",id:"logos-execution-zone",level:2},{value:"Logos messaging",id:"logos-messaging",level:2},{value:"Logos Storage",id:"logos-storage",level:2},{value:"AnonComms",id:"anoncomms",level:2},{value:"Standards, governance, and verification",id:"standards-governance-and-verification",level:2}];function h(e){let t={a:"a",code:"code",h1:"h1",h2:"h2",h4:"h4",header:"header",p:"p",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",...(0,i.R)(),...e.components};return(0,l.jsxs)(l.Fragment,{children:[(0,l.jsx)(t.header,{children:(0,l.jsx)(t.h1,{id:"logos-ecosystem-repositories",children:"Logos ecosystem repositories"})}),"\n",(0,l.jsx)(t.h4,{id:"review-the-key-repositories-across-the-logos-organisations",children:"Review the key repositories across the logos organisations."}),"\n",(0,l.jsx)(t.p,{children:"The Logos project is organised into several GitHub organisations, each focused on one part of the system. This page highlights the most important repositories for understanding the project and getting started with development. The selection is curated rather than exhaustive."}),"\n",(0,l.jsx)(t.h2,{id:"orientation-and-contribution-resources",children:"Orientation and contribution resources"}),"\n",(0,l.jsx)(t.p,{children:"These repositories explain what Logos is building and how to take part, with no code to run."}),"\n",(0,l.jsxs)(t.table,{children:[(0,l.jsx)(t.thead,{children:(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.th,{style:{textAlign:"left"},children:"Repository"}),(0,l.jsx)(t.th,{style:{textAlign:"left"},children:"Description"})]})}),(0,l.jsxs)(t.tbody,{children:[(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.a,{href:"https://github.com/logos-co/logos-docs",children:"logos-docs"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"Documentation, standards, and templates for Logos projects."})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.a,{href:"https://github.com/logos-co/roadmap",children:"roadmap"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"A Markdown-based view of the Logos roadmap."})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.a,{href:"https://github.com/logos-co/ideas",children:"ideas"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"Community-sourced project ideas to build on top of Logos."})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.a,{href:"https://github.com/logos-co/rfp",children:"rfp"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"Grants for building in the Logos ecosystem."})]})]})]}),"\n",(0,l.jsx)(t.h2,{id:"logos-basecamp",children:"Logos Basecamp"}),"\n",(0,l.jsxs)(t.p,{children:["Logos ",(0,l.jsx)(t.a,{href:"/get-started/glossary#basecamp",children:"Basecamp"})," is the desktop shell for Logos."]}),"\n",(0,l.jsxs)(t.table,{children:[(0,l.jsx)(t.thead,{children:(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.th,{style:{textAlign:"left"},children:"Repository"}),(0,l.jsx)(t.th,{style:{textAlign:"left"},children:"Description"})]})}),(0,l.jsx)(t.tbody,{children:(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.a,{href:"https://github.com/logos-co/logos-basecamp",children:"logos-basecamp"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"The Logos Basecamp repository."})]})})]}),"\n",(0,l.jsx)(t.h2,{id:"logos-core",children:"Logos Core"}),"\n",(0,l.jsx)(t.p,{children:"Logos Core is the modular runtime that ties applications together."}),"\n",(0,l.jsxs)(t.table,{children:[(0,l.jsx)(t.thead,{children:(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.th,{style:{textAlign:"left"},children:"Repository"}),(0,l.jsx)(t.th,{style:{textAlign:"left"},children:"Description"})]})}),(0,l.jsxs)(t.tbody,{children:[(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.a,{href:"https://github.com/logos-co/scaffold",children:"scaffold"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"CLI that bootstraps a runnable Logos application environment."})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.a,{href:"https://github.com/logos-co/logos-liblogos",children:"logos-liblogos"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"The core runtime library for the Logos modular application platform."})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.a,{href:"https://github.com/logos-co/logos-logoscore-cli",children:"logos-logoscore-cli"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"The headless CLI runtime for the Logos modular application platform."})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.a,{href:"https://github.com/logos-co/logos-logoscore-py",children:"logos-logoscore-py"})}),(0,l.jsxs)(t.td,{style:{textAlign:"left"},children:["Python wrapper for the ",(0,l.jsx)(t.a,{href:"/get-started/glossary#logoscore",children:"logoscore"})," CLI."]})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.a,{href:"https://github.com/logos-co/logos-package-manager",children:"logos-package-manager"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"C++ library and CLI for local Logos package management."})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.a,{href:"https://github.com/logos-co/logos-package-downloader",children:"logos-package-downloader"})}),(0,l.jsxs)(t.td,{style:{textAlign:"left"},children:[(0,l.jsx)(t.a,{href:"/get-started/glossary#module",children:"Module"})," download tooling. No official description; purpose inferred."]})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.a,{href:"https://github.com/logos-co/logos-module-builder",children:"logos-module-builder"})}),(0,l.jsxs)(t.td,{style:{textAlign:"left"},children:["C++ library and CLI (",(0,l.jsx)(t.a,{href:"/get-started/glossary#lgpd",children:"lgpd"}),") for fetching Logos module ",(0,l.jsx)(t.a,{href:"/get-started/glossary#catalogue",children:"catalogs"})," and downloading ",(0,l.jsx)(t.code,{children:".lgx"})," packages."]})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.a,{href:"https://github.com/logos-co/logos-app-builder",children:"logos-app-builder"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"Shared Nix library for building Logos UI applications."})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.a,{href:"https://github.com/logos-co/Overwatch",children:"Overwatch"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"Self-contained services application framework."})]})]})]}),"\n",(0,l.jsx)(t.h2,{id:"core-modules-and-applications",children:"Core modules and applications"}),"\n",(0,l.jsx)(t.p,{children:"Modules plug into Logos Core to provide specific capabilities, and user-interface repositories are the front-end applications built on those modules. Modules and applications live in both organisations."}),"\n",(0,l.jsxs)(t.table,{children:[(0,l.jsx)(t.thead,{children:(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.th,{style:{textAlign:"left"},children:"Repository"}),(0,l.jsx)(t.th,{style:{textAlign:"left"},children:"Description"})]})}),(0,l.jsxs)(t.tbody,{children:[(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.a,{href:"https://github.com/logos-co/logos-chat-module",children:"logos-chat-module"})}),(0,l.jsxs)(t.td,{style:{textAlign:"left"},children:["A Logos Core module that exposes ",(0,l.jsx)(t.a,{href:"/get-started/glossary#logos-chat",children:"Logos Chat"}),"."]})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.a,{href:"https://github.com/logos-co/logos-chat-ui",children:"logos-chat-ui"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"UI application for the Logos Chat SDK proof of concept."})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.a,{href:"https://github.com/logos-co/logos-wallet-module",children:"logos-wallet-module"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"Early-stage wallet module built on go-wallet-sdk."})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.a,{href:"https://github.com/logos-co/logos-accounts-module",children:"logos-accounts-module"})}),(0,l.jsxs)(t.td,{style:{textAlign:"left"},children:["Early-stage ",(0,l.jsx)(t.a,{href:"/get-started/glossary#account",children:"account"}),"-management module built on go-wallet-sdk."]})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.a,{href:"https://github.com/logos-blockchain/logos-blockchain-module",children:"logos-blockchain-module"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"Core module wrapping logos-blockchain-c."})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.a,{href:"https://github.com/logos-blockchain/logos-blockchain-ui",children:"logos-blockchain-ui"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"Blockchain UI application for Logos Core."})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.a,{href:"https://github.com/logos-blockchain/logos-execution-zone-module",children:"logos-execution-zone-module"})}),(0,l.jsxs)(t.td,{style:{textAlign:"left"},children:[(0,l.jsx)(t.a,{href:"/get-started/glossary#logos-execution-zone",children:"Logos Execution Zone"})," module for Logos Core. No official description; purpose inferred."]})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.a,{href:"https://github.com/logos-blockchain/logos-execution-zone-wallet-ui",children:"logos-execution-zone-wallet-ui"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"Logos Execution Zone wallet UI. No official description; purpose inferred."})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.a,{href:"https://github.com/logos-co/logos-libp2p-module",children:"logos-libp2p-module"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"A Logos module that integrates libp2p networking capabilities."})]})]})]}),"\n",(0,l.jsx)(t.h2,{id:"logos-blockchain",children:"Logos Blockchain"}),"\n",(0,l.jsxs)(t.p,{children:["The ",(0,l.jsx)(t.a,{href:"/get-started/glossary#logos-blockchain",children:"Logos Blockchain"})," is the foundational infrastructure of the Logos technology stack."]}),"\n",(0,l.jsxs)(t.table,{children:[(0,l.jsx)(t.thead,{children:(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.th,{style:{textAlign:"left"},children:"Repository"}),(0,l.jsx)(t.th,{style:{textAlign:"left"},children:"Description"})]})}),(0,l.jsxs)(t.tbody,{children:[(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.a,{href:"https://github.com/logos-blockchain/logos-blockchain",children:"logos-blockchain"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"The Logos blockchain repository."})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.a,{href:"https://github.com/logos-blockchain/logos-blockchain-block-explorer-template",children:"logos-blockchain-block-explorer-template"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"Python block explorer and indexer template."})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.a,{href:"https://github.com/logos-co/nimbos",children:"nimbos"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"Nim client for the Logos chain."})]})]})]}),"\n",(0,l.jsx)(t.h2,{id:"logos-execution-zone",children:"Logos Execution Zone"}),"\n",(0,l.jsxs)(t.p,{children:["The Logos Execution Zone (",(0,l.jsx)(t.a,{href:"/get-started/glossary#lez",children:"LEZ"}),") is the primary execution layer for applications built on the Logos stack."]}),"\n",(0,l.jsxs)(t.table,{children:[(0,l.jsx)(t.thead,{children:(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.th,{style:{textAlign:"left"},children:"Repository"}),(0,l.jsx)(t.th,{style:{textAlign:"left"},children:"Description"})]})}),(0,l.jsxs)(t.tbody,{children:[(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.a,{href:"https://github.com/logos-blockchain/logos-execution-zone",children:"logos-execution-zone"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"The LEZ repository."})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.a,{href:"https://github.com/logos-blockchain/lez-programs",children:"lez-programs"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"Essential Logos Execution Zone programs built by Logos."})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.a,{href:"https://github.com/logos-co/lez-multisig",children:"lez-multisig"})}),(0,l.jsxs)(t.td,{style:{textAlign:"left"},children:["Multisig built with the lez-framework ",(0,l.jsx)(t.a,{href:"/get-started/glossary#program",children:"program"})," macro."]})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.a,{href:"https://github.com/logos-co/logos-lez-rln",children:"logos-lez-rln"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"Logos Execution Zone program for RLN registration."})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.a,{href:"https://github.com/logos-blockchain/logos-sql-zone",children:"logos-sql-zone"})}),(0,l.jsxs)(t.td,{style:{textAlign:"left"},children:["Password-manager demo built with the ",(0,l.jsx)(t.a,{href:"/get-started/glossary#zone-sdk",children:"Zone SDK"}),"."]})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.a,{href:"https://github.com/logos-blockchain/lez-fuzzing",children:"lez-fuzzing"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"Adversarial testing and fuzzing infrastructure for LEZ."})]})]})]}),"\n",(0,l.jsx)(t.h2,{id:"logos-messaging",children:"Logos messaging"}),"\n",(0,l.jsxs)(t.p,{children:[(0,l.jsx)(t.a,{href:"/get-started/glossary#logos-messaging",children:"Logos Messaging"})," is the project's decentralised communication system."]}),"\n",(0,l.jsxs)(t.table,{children:[(0,l.jsx)(t.thead,{children:(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.th,{style:{textAlign:"left"},children:"Repository"}),(0,l.jsx)(t.th,{style:{textAlign:"left"},children:"Description"})]})}),(0,l.jsxs)(t.tbody,{children:[(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.a,{href:"https://github.com/logos-messaging/logos-delivery",children:"logos-delivery"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"Logos messaging protocols implemented in Nim, formerly nwaku."})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.a,{href:"https://github.com/logos-messaging/logos-delivery-js",children:"logos-delivery-js"})}),(0,l.jsxs)(t.td,{style:{textAlign:"left"},children:["Logos messaging protocols implemented in JavaScript and TypeScript, formerly ",(0,l.jsx)(t.a,{href:"/get-started/glossary#waku",children:"Waku"})," v2."]})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.a,{href:"https://github.com/logos-messaging/logos-delivery-python-bindings",children:"logos-delivery-python-bindings"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"Python wrapper around the node."})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.a,{href:"https://github.com/logos-messaging/sds-go-bindings",children:"sds-go-bindings"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"Go bindings for the SDS reliability protocol. No official description; purpose inferred."})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.a,{href:"https://github.com/logos-messaging/nim-sds",children:"nim-sds"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"Nim implementation of the end-to-end reliability protocol."})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.a,{href:"https://github.com/logos-messaging/nim-segmentation",children:"nim-segmentation"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"Message segmentation. No official description; purpose inferred."})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.a,{href:"https://github.com/logos-messaging/logos-delivery-rlnv2-contract",children:"logos-delivery-rlnv2-contract"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"RLN-v2 smart contracts, written in Solidity."})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.a,{href:"https://github.com/logos-messaging/specs",children:"specs"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"Protocol specifications. No official description; purpose inferred."})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.a,{href:"https://github.com/logos-messaging/logos-delivery-compose",children:"logos-delivery-compose"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"Docker-compose files to deploy a node."})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.a,{href:"https://github.com/logos-messaging/logos-delivery-simulator",children:"logos-delivery-simulator"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"Simulates a network with multiple nodes, traffic, and users."})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.a,{href:"https://github.com/logos-messaging/logos-delivery-interop-tests",children:"logos-delivery-interop-tests"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"Interoperability tests. No official description; purpose inferred."})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.a,{href:"https://github.com/logos-messaging/libchat",children:"libchat"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"Supporting library for Logos Chat, written in Rust."})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.a,{href:"https://github.com/logos-messaging/awesome-logos-delivery",children:"awesome-logos-delivery"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"Community-curated list of resources."})]})]})]}),"\n",(0,l.jsx)(t.h2,{id:"logos-storage",children:"Logos Storage"}),"\n",(0,l.jsxs)(t.p,{children:[(0,l.jsx)(t.a,{href:"/get-started/glossary#logos-storage",children:"Logos Storage"})," is the project's privacy-preserving, decentralised storage system."]}),"\n",(0,l.jsxs)(t.table,{children:[(0,l.jsx)(t.thead,{children:(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.th,{style:{textAlign:"left"},children:"Repository"}),(0,l.jsx)(t.th,{style:{textAlign:"left"},children:"Description"})]})}),(0,l.jsxs)(t.tbody,{children:[(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.a,{href:"https://github.com/logos-storage/logos-storage-nim",children:"logos-storage-nim"})}),(0,l.jsxs)(t.td,{style:{textAlign:"left"},children:["The storage node and durability engine, formerly nim-",(0,l.jsx)(t.a,{href:"/get-started/glossary#codex",children:"codex"}),"."]})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.a,{href:"https://github.com/logos-storage/logos-storage-nim-dht",children:"logos-storage-nim-dht"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"A DHT based on Discv5 with libp2p provider-record support."})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.a,{href:"https://github.com/logos-storage/circom-compat-ffi",children:"circom-compat-ffi"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"FFI for circom-compat (ark-circom), used for zero-knowledge proving."})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.a,{href:"https://github.com/logos-storage/logos-storage-research",children:"logos-storage-research"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"Durability-engine research."})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.a,{href:"https://github.com/logos-storage/logos-storage-nim-cs-dist-tests",children:"logos-storage-nim-cs-dist-tests"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"Distributed system tests for the node."})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.a,{href:"https://github.com/logos-storage/logos-storage-local-harness",children:"logos-storage-local-harness"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"A bash harness for running the node without containers."})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.a,{href:"https://github.com/logos-storage/transport-over-mix",children:"transport-over-mix"})}),(0,l.jsxs)(t.td,{style:{textAlign:"left"},children:["Transport-layer abstraction over ",(0,l.jsx)(t.a,{href:"/get-started/glossary#mix",children:"Mix"}),", with spec and reference implementation."]})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.a,{href:"https://github.com/logos-storage/libp2p-storage-mix-transport",children:"libp2p-storage-mix-transport"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"Experiments with a storage transport over the libp2p Mix protocol."})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.a,{href:"https://github.com/logos-storage/mix-hidden-services",children:"mix-hidden-services"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"Hidden services over Mix."})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.a,{href:"https://github.com/logos-storage/lioness_blockcipher",children:"lioness_blockcipher"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"Rust implementation of the LIONESS wide-block cipher."})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.a,{href:"https://github.com/logos-storage/logos-storage-docs-obsidian",children:"logos-storage-docs-obsidian"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"Indexed, searchable documentation as an Obsidian vault."})]})]})]}),"\n",(0,l.jsx)(t.h2,{id:"anoncomms",children:"AnonComms"}),"\n",(0,l.jsxs)(t.p,{children:["AnonComms focuses on building the core elements that allow privacy-preserving communication and coordination across the Logos stack. The work is at early stages, see the latest update in the ",(0,l.jsx)(t.a,{href:"https://roadmap.logos.co/anoncomms/",children:"roadmap"}),"."]}),"\n",(0,l.jsx)(t.h2,{id:"standards-governance-and-verification",children:"Standards, governance, and verification"}),"\n",(0,l.jsx)(t.p,{children:"These repositories define how Logos documents, proposes, and verifies changes."}),"\n",(0,l.jsxs)(t.table,{children:[(0,l.jsx)(t.thead,{children:(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.th,{style:{textAlign:"left"},children:"Repository"}),(0,l.jsx)(t.th,{style:{textAlign:"left"},children:"Description"})]})}),(0,l.jsxs)(t.tbody,{children:[(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.a,{href:"https://github.com/logos-co/logos-lips",children:"logos-lips"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"Logos Improvement Proposals Index."})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.a,{href:"https://github.com/logos-co/logos-doctest",children:"logos-doctest"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"Executable documentation that can be verified and rendered."})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.a,{href:"https://github.com/logos-co/logos-tutorial",children:"logos-tutorial"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"Tutorial materials"})]})]})]})]})}function g(e={}){let{wrapper:t}={...(0,i.R)(),...e.components};return t?(0,l.jsx)(t,{...e,children:(0,l.jsx)(h,{...e})}):h(e)}},8453(e,t,s){s.d(t,{R:()=>r,x:()=>n});var o=s(6540);let l={},i=o.createContext(l);function r(e){let t=o.useContext(i);return o.useMemo(function(){return"function"==typeof e?e(t):{...t,...e}},[t,e])}function n(e){let t;return t=e.disableParentContext?"function"==typeof e.components?e.components(l):e.components||l:r(e.components),o.createElement(i.Provider,{value:t},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/867de415.6d308577.js b/assets/js/867de415.6d308577.js new file mode 100644 index 00000000..0d20ac39 --- /dev/null +++ b/assets/js/867de415.6d308577.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunklogos_docs=self.webpackChunklogos_docs||[]).push([["5286"],{86(e,o,s){s.r(o),s.d(o,{metadata:()=>i,default:()=>c,frontMatter:()=>r,contentTitle:()=>a,toc:()=>g,assets:()=>l});var i=JSON.parse('{"id":"messaging/get-started/faq-logos-messaging","title":"FAQ: Logos Messaging","description":"Review common questions about how Logos Messaging works and how to build on it.","source":"@site/docs/messaging/get-started/faq-logos-messaging.md","sourceDirName":"messaging/get-started","slug":"/messaging/get-started/faq","permalink":"/messaging/get-started/faq","draft":false,"unlisted":false,"editUrl":"https://github.com/logos-co/logos-docs/tree/main/docs/messaging/get-started/faq-logos-messaging.md","tags":[],"version":"current","sidebarPosition":3,"frontMatter":{"title":"FAQ: Logos Messaging","doc_type":"concept","product":"messaging","topics":"faq","authors":"LordGhostX, fryorcraken","owner":"logos","doc_version":1,"slug":"faq","sidebar_position":3},"sidebar":"messagingSidebar","previous":{"title":"Send 1:1 messages with the Logos Chat app","permalink":"/messaging/get-started/send-1-1-messages-logos-chat"},"next":{"title":"Use the Logos Delivery module API from an app","permalink":"/messaging/delivery/use-logos-delivery-module-api-from-app"}}'),t=s(4848),n=s(8453);let r={title:"FAQ: Logos Messaging",doc_type:"concept",product:"messaging",topics:"faq",authors:"LordGhostX, fryorcraken",owner:"logos",doc_version:1,slug:"faq",sidebar_position:3},a="FAQ: Logos Messaging",l={},g=[{value:"Review common questions about how Logos Messaging works and how to build on it.",id:"review-common-questions-about-how-logos-messaging-works-and-how-to-build-on-it",level:4},{value:"What is Logos Messaging,Logos Delivery?",id:"what-is-logos-messaginglogos-delivery",level:2},{value:"Does messaging require a gas fee?",id:"does-messaging-require-a-gas-fee",level:2},{value:"What encryption does Logos Delivery use?",id:"what-encryption-does-logos-delivery-use",level:2},{value:"Where does Logos Delivery store the messages?",id:"where-does-logos-delivery-store-the-messages",level:2},{value:"Can Logos Delivery only be used for wallet-to-wallet messaging?",id:"can-logos-delivery-only-be-used-for-wallet-to-wallet-messaging",level:2},{value:"How does Logos Delivery differ from IPFS?",id:"how-does-logos-delivery-differ-from-ipfs",level:2},{value:"What are Rate Limiting Nullifiers (RLN)?",id:"what-are-rate-limiting-nullifiers-rln",level:2}];function d(e){let o={a:"a",h1:"h1",h2:"h2",h4:"h4",header:"header",p:"p",...(0,n.R)(),...e.components};return(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)(o.header,{children:(0,t.jsx)(o.h1,{id:"faq-logos-messaging",children:"FAQ: Logos Messaging"})}),"\n",(0,t.jsx)(o.h4,{id:"review-common-questions-about-how-logos-messaging-works-and-how-to-build-on-it",children:"Review common questions about how Logos Messaging works and how to build on it."}),"\n",(0,t.jsx)(o.h2,{id:"what-is-logos-messaginglogos-delivery",children:"What is Logos Messaging,Logos Delivery?"}),"\n",(0,t.jsxs)(o.p,{children:[(0,t.jsx)(o.a,{href:"https://docs.logos.co/get-started/glossary#logos-messaging",children:"Logos Messaging"})," is the messaging component of the Logos technology stack, composed of ",(0,t.jsx)(o.a,{href:"https://docs.logos.co/get-started/glossary#logos-delivery",children:"Logos Delivery"})," and ",(0,t.jsx)(o.a,{href:"https://docs.logos.co/get-started/glossary#logos-chat",children:"Logos Chat"}),". Logos Delivery is a peer-to-peer messaging network with DoS protection via RLN (Rate Limiting Nullifiers). Any application can use it to send and receive messages over an open, censorship-resistant network."]}),"\n",(0,t.jsx)(o.p,{children:"Logos Chat is a protocol and library that provides 1:1 and group conversations with end-to-end encryption using de-MLS - everything an application needs to add private chat without rolling its own protocol."}),"\n",(0,t.jsx)(o.h2,{id:"does-messaging-require-a-gas-fee",children:"Does messaging require a gas fee?"}),"\n",(0,t.jsx)(o.p,{children:"No, sending and receiving messages using Logos Messaging involves no gas fee."}),"\n",(0,t.jsx)(o.h2,{id:"what-encryption-does-logos-delivery-use",children:"What encryption does Logos Delivery use?"}),"\n",(0,t.jsx)(o.p,{children:"Logos Delivery uses libp2p noise encryption for node-to-node connections. However, no default encryption method is applied to the data sent over the network. This design choice enhances Logos Delivery's encryption flexibility, encouraging developers to use custom protocols or Logos Delivery message payload encryption methods freely."}),"\n",(0,t.jsx)(o.h2,{id:"where-does-logos-delivery-store-the-messages",children:"Where does Logos Delivery store the messages?"}),"\n",(0,t.jsxs)(o.p,{children:["Logos Delivery's ",(0,t.jsx)(o.a,{href:"/messaging/concepts/protocols#store",children:"Store protocol"})," is designed to temporarily store messages within the network. However, Logos Delivery does not guarantee the message's availability and recommends using ",(0,t.jsx)(o.a,{href:"https://logos.co/technology-stack/storage",children:"Logos Storage"})," for long-term storage."]}),"\n",(0,t.jsx)(o.h2,{id:"can-logos-delivery-only-be-used-for-wallet-to-wallet-messaging",children:"Can Logos Delivery only be used for wallet-to-wallet messaging?"}),"\n",(0,t.jsx)(o.p,{children:"No, Logos Delivery is flexible and imposes no specific rules on identifiers."}),"\n",(0,t.jsx)(o.h2,{id:"how-does-logos-delivery-differ-from-ipfs",children:"How does Logos Delivery differ from IPFS?"}),"\n",(0,t.jsx)(o.p,{children:"Logos Delivery focuses on short, ephemeral, real-time messages, while IPFS focuses on large, long-term data storage. Although there's an overlap between the two technologies, Logos Delivery does not currently support large data for privacy reasons."}),"\n",(0,t.jsx)(o.h2,{id:"what-are-rate-limiting-nullifiers-rln",children:"What are Rate Limiting Nullifiers (RLN)?"}),"\n",(0,t.jsxs)(o.p,{children:[(0,t.jsx)(o.a,{href:"/messaging/concepts/protocols#rln-relay",children:"Rate Limiting Nullifier"})," is a zero-knowledge (ZK) protocol enabling spam protection in a decentralised network while preserving privacy. Each message must be accompanied by a ZK proof, which ",(0,t.jsx)(o.a,{href:"/messaging/concepts/protocols#relay",children:"Relay"})," nodes verify to ensure the publishers do not send more messages than they are allowed. The ZK proof does not leak any private information about message publishers - it only proves they are members of a set of users allowed to publish a certain number of messages per given time frame."]})]})}function c(e={}){let{wrapper:o}={...(0,n.R)(),...e.components};return o?(0,t.jsx)(o,{...e,children:(0,t.jsx)(d,{...e})}):d(e)}},8453(e,o,s){s.d(o,{R:()=>r,x:()=>a});var i=s(6540);let t={},n=i.createContext(t);function r(e){let o=i.useContext(n);return i.useMemo(function(){return"function"==typeof e?e(o):{...o,...e}},[o,e])}function a(e){let o;return o=e.disableParentContext?"function"==typeof e.components?e.components(t):e.components||t:r(e.components),i.createElement(n.Provider,{value:o},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/8a80b850.93bd8f32.js b/assets/js/8a80b850.93bd8f32.js new file mode 100644 index 00000000..d8e7405c --- /dev/null +++ b/assets/js/8a80b850.93bd8f32.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunklogos_docs=self.webpackChunklogos_docs||[]).push([["5052"],{3343(e,o,n){n.r(o),n.d(o,{metadata:()=>s,default:()=>h,frontMatter:()=>r,contentTitle:()=>d,toc:()=>a,assets:()=>i});var s=JSON.parse('{"id":"storage/get-started/run-logos-storage-node","title":"Run a Logos storage node","description":"Get started running a Logos storage node and uploading your first file to the Logos network.","source":"@site/docs/storage/get-started/run-logos-storage-node.md","sourceDirName":"storage/get-started","slug":"/storage/get-started/run-logos-storage-node","permalink":"/storage/get-started/run-logos-storage-node","draft":false,"unlisted":false,"editUrl":"https://github.com/logos-co/logos-docs/tree/main/docs/storage/get-started/run-logos-storage-node.md","tags":[],"version":"current","sidebarPosition":1,"frontMatter":{"title":"Run a Logos storage node","doc_type":"procedure","product":"storage","topics":["storage","node"],"steps_layout":"flat","authors":"gmega, kashepavadan","owner":"logos","doc_version":1,"slug":"run-logos-storage-node","sidebar_position":1},"sidebar":"storageSidebar","next":{"title":"Set up and use the Logos Storage UI","permalink":"/storage/get-started/set-up-and-use-logos-storage-ui"}}'),t=n(4848),l=n(8453);let r={title:"Run a Logos storage node",doc_type:"procedure",product:"storage",topics:["storage","node"],steps_layout:"flat",authors:"gmega, kashepavadan",owner:"logos",doc_version:1,slug:"run-logos-storage-node",sidebar_position:1},d="Run a Logos storage node",i={},a=[{value:"Get started running a Logos storage node and uploading your first file to the Logos network.",id:"get-started-running-a-logos-storage-node-and-uploading-your-first-file-to-the-logos-network",level:4},{value:"What to expect",id:"what-to-expect",level:2},{value:"Build and install the storage module",id:"build-and-install-the-storage-module",level:2},{value:"Start the daemon and load the storage module",id:"start-the-daemon-and-load-the-storage-module",level:2},{value:"Publish and download a file",id:"publish-and-download-a-file",level:2},{value:"Remove content and destroy the storage node",id:"remove-content-and-destroy-the-storage-node",level:2},{value:"Troubleshooting Logos Storage",id:"troubleshooting-logos-storage",level:2},{value:"Why does <code>downloadToUrl</code> time out when downloading from a different machine?",id:"why-does-downloadtourl-time-out-when-downloading-from-a-different-machine",level:3}];function c(e){let o={a:"a",admonition:"admonition",code:"code",h1:"h1",h2:"h2",h3:"h3",h4:"h4",header:"header",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,l.R)(),...e.components};return(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)(o.header,{children:(0,t.jsx)(o.h1,{id:"run-a-logos-storage-node",children:"Run a Logos storage node"})}),"\n",(0,t.jsx)(o.h4,{id:"get-started-running-a-logos-storage-node-and-uploading-your-first-file-to-the-logos-network",children:"Get started running a Logos storage node and uploading your first file to the Logos network."}),"\n",(0,t.jsxs)(o.p,{children:["This procedure covers how to build and run the ",(0,t.jsx)(o.a,{href:"https://github.com/logos-co/logos-storage-module/",children:"Logos Storage Module"}),", connect it to the testnet bootstrap nodes, publish a file, and verify that the file can be downloaded. It is intended for node operators on testnet v0.2 who want to contribute storage capacity to the Logos network."]}),"\n",(0,t.jsx)(o.p,{children:"Before you start, make sure you have the following:"}),"\n",(0,t.jsxs)(o.ul,{children:["\n",(0,t.jsxs)(o.li,{children:["\n",(0,t.jsx)(o.p,{children:"Linux (tested on Ubuntu 22.04)"}),"\n"]}),"\n",(0,t.jsxs)(o.li,{children:["\n",(0,t.jsxs)(o.p,{children:[(0,t.jsx)(o.strong,{children:"Nix"})," with flakes enabled. Install from ",(0,t.jsx)(o.a,{href:"https://nixos.org/download.html",children:"nixos.org"}),", then enable flakes:"]}),"\n",(0,t.jsx)(o.pre,{children:(0,t.jsx)(o.code,{className:"language-bash",children:"mkdir -p ~/.config/nix\necho 'experimental-features = nix-command flakes' >> ~/.config/nix/nix.conf\n"})}),"\n"]}),"\n",(0,t.jsxs)(o.li,{children:["\n",(0,t.jsxs)(o.p,{children:[(0,t.jsx)(o.a,{href:"https://github.com/logos-co/logos-logoscore-cli/releases/tag/0.2.0",children:(0,t.jsx)(o.code,{children:"logoscore"})}),", and ",(0,t.jsx)(o.a,{href:"https://github.com/logos-co/logos-package-manager/releases/tag/0.2.0",children:(0,t.jsx)(o.code,{children:"lgpm"})})," installed. To install these tools, use the ",(0,t.jsx)(o.code,{children:"install-node-tools.sh"})," helper script:"]}),"\n",(0,t.jsx)(o.pre,{children:(0,t.jsx)(o.code,{className:"language-bash",children:'curl -fsSL https://raw.githubusercontent.com/logos-co/logos-docs/main/resources/scripts/install-node-tools.sh | sh\nexport PATH="$PWD/bin:$PATH"\n'})}),"\n"]}),"\n",(0,t.jsxs)(o.li,{children:["\n",(0,t.jsxs)(o.p,{children:[(0,t.jsx)(o.code,{children:"jq"})," on your ",(0,t.jsx)(o.code,{children:"PATH"})," \u2014 used to pull the uploaded ",(0,t.jsx)(o.a,{href:"https://docs.logos.co/get-started/glossary#cid",children:"CID"})," out of the manifests JSON. Verify: ",(0,t.jsx)(o.code,{children:"jq --version"})]}),"\n"]}),"\n"]}),"\n",(0,t.jsx)(o.h2,{id:"what-to-expect",children:"What to expect"}),"\n",(0,t.jsxs)(o.ul,{children:["\n",(0,t.jsxs)(o.li,{children:["You can connect a ",(0,t.jsx)(o.a,{href:"https://docs.logos.co/get-started/glossary#logos-storage",children:"Logos Storage"})," node to the testnet and have it listed among the bootstrap peers."]}),"\n",(0,t.jsx)(o.li,{children:"You can publish a file to the network and retrieve a content address to share with other nodes."}),"\n",(0,t.jsx)(o.li,{children:"You can download the file back from the network and confirm it lands on disk."}),"\n"]}),"\n",(0,t.jsx)(o.h2,{id:"build-and-install-the-storage-module",children:"Build and install the storage module"}),"\n",(0,t.jsxs)(o.ol,{children:["\n",(0,t.jsxs)(o.li,{children:["\n",(0,t.jsxs)(o.p,{children:["Build the ",(0,t.jsx)(o.a,{href:"https://docs.logos.co/get-started/glossary#module",children:"module"})," package with Nix:"]}),"\n",(0,t.jsx)(o.pre,{children:(0,t.jsx)(o.code,{className:"language-sh",children:"nix build 'github:logos-co/logos-storage-module/v2.0.1#lgx' -o storage-lgx\n"})}),"\n",(0,t.jsxs)(o.ul,{children:["\n",(0,t.jsxs)(o.li,{children:["This produces a ",(0,t.jsx)(o.code,{children:"logos-storage_module-module-lib.lgx"})," package in ",(0,t.jsx)(o.code,{children:"./storage-lgx/"}),"."]}),"\n"]}),"\n",(0,t.jsx)(o.admonition,{type:"info",children:(0,t.jsx)(o.p,{children:"The initial Nix build takes 15\u201320 minutes on first run. Subsequent builds use the Nix cache and complete in seconds."})}),"\n"]}),"\n",(0,t.jsxs)(o.li,{children:["\n",(0,t.jsxs)(o.p,{children:["Install the package into a local modules directory using ",(0,t.jsx)(o.code,{children:"lgpm"}),":"]}),"\n",(0,t.jsx)(o.pre,{children:(0,t.jsx)(o.code,{className:"language-sh",children:"lgpm --modules-dir ./modules install --file storage-lgx/*.lgx\n"})}),"\n"]}),"\n"]}),"\n",(0,t.jsx)(o.h2,{id:"start-the-daemon-and-load-the-storage-module",children:"Start the daemon and load the storage module"}),"\n",(0,t.jsxs)(o.p,{children:["Run ",(0,t.jsx)(o.code,{children:"logoscore"})," with the modules directory, then load and initialise the ",(0,t.jsx)(o.a,{href:"https://docs.logos.co/get-started/glossary#storage-module",children:"storage module"})," against the testnet config."]}),"\n",(0,t.jsxs)(o.ol,{children:["\n",(0,t.jsxs)(o.li,{children:["\n",(0,t.jsxs)(o.p,{children:["Start the ",(0,t.jsx)(o.code,{children:"logoscore"})," daemon in background mode:"]}),"\n",(0,t.jsx)(o.pre,{children:(0,t.jsx)(o.code,{className:"language-sh",children:"logoscore -D -m ./modules\n"})}),"\n"]}),"\n",(0,t.jsxs)(o.li,{children:["\n",(0,t.jsx)(o.p,{children:"Verify the daemon is running:"}),"\n",(0,t.jsx)(o.pre,{children:(0,t.jsx)(o.code,{className:"language-sh",children:"logoscore status\n"})}),"\n"]}),"\n",(0,t.jsxs)(o.li,{children:["\n",(0,t.jsx)(o.p,{children:"Create the storage config:"}),"\n",(0,t.jsx)(o.pre,{children:(0,t.jsx)(o.code,{className:"language-sh",children:'mkdir -p storage-data\ncat > config.json <<EOF\n{\n "data-dir": "./storage-data",\n "log-level": "INFO",\n "listen-ip": "0.0.0.0",\n "listen-port": 8091,\n "disc-port": 8090,\n "nat": "extip:<public-ip>",\n "network": "logos.test"\n}\nEOF\n'})}),"\n",(0,t.jsxs)(o.ul,{children:["\n",(0,t.jsxs)(o.li,{children:[(0,t.jsx)(o.code,{children:"config.json"})," includes the following fields:"]}),"\n"]}),"\n",(0,t.jsxs)(o.table,{children:[(0,t.jsx)(o.thead,{children:(0,t.jsxs)(o.tr,{children:[(0,t.jsx)(o.th,{children:"Field"}),(0,t.jsx)(o.th,{children:"Purpose"})]})}),(0,t.jsxs)(o.tbody,{children:[(0,t.jsxs)(o.tr,{children:[(0,t.jsx)(o.td,{children:(0,t.jsx)(o.code,{children:"data-dir"})}),(0,t.jsx)(o.td,{children:"Storage repository path"})]}),(0,t.jsxs)(o.tr,{children:[(0,t.jsx)(o.td,{children:(0,t.jsx)(o.code,{children:"log-level"})}),(0,t.jsx)(o.td,{children:"Log verbosity"})]}),(0,t.jsxs)(o.tr,{children:[(0,t.jsx)(o.td,{children:(0,t.jsx)(o.code,{children:"listen-ip"})}),(0,t.jsx)(o.td,{children:"Local TCP bind address"})]}),(0,t.jsxs)(o.tr,{children:[(0,t.jsx)(o.td,{children:(0,t.jsx)(o.code,{children:"listen-port"})}),(0,t.jsx)(o.td,{children:"Public TCP libp2p port"})]}),(0,t.jsxs)(o.tr,{children:[(0,t.jsx)(o.td,{children:(0,t.jsx)(o.code,{children:"disc-port"})}),(0,t.jsx)(o.td,{children:"Public UDP discovery port"})]}),(0,t.jsxs)(o.tr,{children:[(0,t.jsx)(o.td,{children:(0,t.jsx)(o.code,{children:"nat"})}),(0,t.jsx)(o.td,{children:"Public IP advertisement mode"})]}),(0,t.jsxs)(o.tr,{children:[(0,t.jsx)(o.td,{children:(0,t.jsx)(o.code,{children:"network"})}),(0,t.jsx)(o.td,{children:"Storage network preset"})]})]})]}),"\n",(0,t.jsxs)(o.ul,{children:["\n",(0,t.jsxs)(o.li,{children:["Use fixed ",(0,t.jsx)(o.code,{children:"listen-port"})," and ",(0,t.jsx)(o.code,{children:"disc-port"}),"; do not leave public nodes on random ports."]}),"\n",(0,t.jsxs)(o.li,{children:["The ",(0,t.jsx)(o.code,{children:"logos.test"})," preset provides the storage bootstrap settings."]}),"\n",(0,t.jsx)(o.li,{children:"Fields"}),"\n"]}),"\n",(0,t.jsxs)(o.admonition,{type:"info",children:[(0,t.jsxs)(o.p,{children:["To run storage with ",(0,t.jsx)(o.a,{href:"https://docs.logos.co/get-started/glossary#mix",children:"mix"})," support, generate the config from the published mix bootstrap data:"]}),(0,t.jsx)(o.pre,{children:(0,t.jsx)(o.code,{className:"language-sh",children:'cat > make-mix-storage-config.sh <<\'EOF\'\n#!/usr/bin/env bash\nset -e\n\ndata_dir=${1:-"./logos-storage-data"}\nudp_spr_json=$(curl -s https://logos-storage-network.fra1.digitaloceanspaces.com/v0.2/udp-sprs.json)\ntcp_spr_json=$(curl -s https://logos-storage-network.fra1.digitaloceanspaces.com/v0.2/tcp-sprs.json)\n\nwget https://logos-storage-network.fra1.digitaloceanspaces.com/v0.2/mix-pool.json\nmp_path=$(realpath "./mix-pool.json")\n\ncat <<JSON\n{\n "nat": "any",\n "log-level": "DEBUG",\n "mix-enabled": true,\n "listen-port": 8080,\n "disc-port": 8090,\n "bootstrap-node": $udp_spr_json,\n "dht-mix-proxy": $tcp_spr_json,\n "data-dir": "${data_dir}",\n "mix-pool": "${mp_path}"\n}\nJSON\nEOF\n\nchmod 755 make-mix-storage-config.sh\n./make-mix-storage-config.sh > config.json\n'})})]}),"\n"]}),"\n",(0,t.jsxs)(o.li,{children:["\n",(0,t.jsx)(o.p,{children:"Load the storage module, initialise it with the testnet configuration, and start it:"}),"\n",(0,t.jsx)(o.pre,{children:(0,t.jsx)(o.code,{className:"language-sh",children:"logoscore load-module storage_module\nlogoscore call storage_module init @config.json\nlogoscore call storage_module start\n"})}),"\n",(0,t.jsxs)(o.ul,{children:["\n",(0,t.jsxs)(o.li,{children:["\n",(0,t.jsx)(o.p,{children:"If using the mix config, also enable private queries and verify with a test download:"}),"\n",(0,t.jsx)(o.pre,{children:(0,t.jsx)(o.code,{className:"language-sh",children:"logoscore call storage_module togglePrivateQueries true\nlogoscore call storage_module downloadToUrl zDvZRwzkzrrYB6sS1rRpRLt4gBhc1pWoyTSjkfszfmj1seaYYLCZ ./farewell-to-westphalia.pdf false 65536\n"})}),"\n"]}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,t.jsx)(o.h2,{id:"publish-and-download-a-file",children:"Publish and download a file"}),"\n",(0,t.jsx)(o.p,{children:"Once the node is running and connected to the testnet, publish a file and verify the round-trip."}),"\n",(0,t.jsxs)(o.ol,{children:["\n",(0,t.jsxs)(o.li,{children:["\n",(0,t.jsxs)(o.p,{children:["Upload a file to the network with ",(0,t.jsx)(o.code,{children:"uploadUrl"}),":"]}),"\n",(0,t.jsx)(o.pre,{children:(0,t.jsx)(o.code,{className:"language-sh",children:"logoscore call storage_module uploadUrl <file-path-or-url> <chunk-size-in-bytes>\n"})}),"\n",(0,t.jsx)(o.admonition,{type:"info",children:(0,t.jsx)(o.p,{children:"The default chunk size is 65536."})}),"\n"]}),"\n",(0,t.jsxs)(o.li,{children:["\n",(0,t.jsxs)(o.p,{children:["After a second, extract the content ID (CID) from the first ",(0,t.jsx)(o.code,{children:"manifests"})," entry:"]}),"\n",(0,t.jsx)(o.pre,{children:(0,t.jsx)(o.code,{className:"language-sh",children:"sleep 1\n\nlogoscore call storage_module manifests \\\n | jq -er '.result.value[0].cid' > cid.txt\n"})}),"\n"]}),"\n",(0,t.jsxs)(o.li,{children:["\n",(0,t.jsxs)(o.p,{children:["Download the file back from the network with ",(0,t.jsx)(o.code,{children:"downloadToUrl"}),". You need the CID for this step."]}),"\n",(0,t.jsx)(o.pre,{children:(0,t.jsx)(o.code,{className:"language-sh",children:'logoscore call storage_module downloadToUrl "$(cat cid.txt)" <destination-path> false <chunk-size-in-bytes>\n'})}),"\n",(0,t.jsxs)(o.ul,{children:["\n",(0,t.jsxs)(o.li,{children:[(0,t.jsx)(o.code,{children:"downloadToUrl"})," takes a ",(0,t.jsx)(o.code,{children:"local"})," flag which reads only from locally cached data if set to ",(0,t.jsx)(o.code,{children:"true"}),"."]}),"\n"]}),"\n"]}),"\n",(0,t.jsxs)(o.li,{children:["\n",(0,t.jsxs)(o.p,{children:["Confirm the downloaded file is present at ",(0,t.jsx)(o.code,{children:"<output-path>"})," and matches the original."]}),"\n"]}),"\n"]}),"\n",(0,t.jsx)(o.h2,{id:"remove-content-and-destroy-the-storage-node",children:"Remove content and destroy the storage node"}),"\n",(0,t.jsx)(o.p,{children:"To clear your local storage and destroy the storage node, follow the steps below."}),"\n",(0,t.jsxs)(o.ol,{children:["\n",(0,t.jsxs)(o.li,{children:["\n",(0,t.jsx)(o.p,{children:"Remove content from local storage by its CID:"}),"\n",(0,t.jsx)(o.pre,{children:(0,t.jsx)(o.code,{className:"language-sh",children:'logoscore call storage_module remove "$(cat cid.txt)"\n'})}),"\n"]}),"\n",(0,t.jsxs)(o.li,{children:["\n",(0,t.jsx)(o.p,{children:"Confirm content is gone:"}),"\n",(0,t.jsx)(o.pre,{children:(0,t.jsx)(o.code,{className:"language-sh",children:'sleep 1\n\nlogoscore call storage_module exists "$(cat cid.txt)"\n# should return false\n'})}),"\n"]}),"\n",(0,t.jsxs)(o.li,{children:["\n",(0,t.jsx)(o.p,{children:"Stop the storage node:"}),"\n",(0,t.jsx)(o.pre,{children:(0,t.jsx)(o.code,{className:"language-sh",children:"logoscore call storage_module stop\n"})}),"\n"]}),"\n",(0,t.jsxs)(o.li,{children:["\n",(0,t.jsx)(o.p,{children:"Wait a bit and destroy the entire storage context:"}),"\n",(0,t.jsx)(o.pre,{children:(0,t.jsx)(o.code,{className:"language-sh",children:"sleep 2\n\nlogoscore call storage_module destroy\n"})}),"\n"]}),"\n"]}),"\n",(0,t.jsx)(o.h2,{id:"troubleshooting-logos-storage",children:"Troubleshooting Logos Storage"}),"\n",(0,t.jsxs)(o.h3,{id:"why-does-downloadtourl-time-out-when-downloading-from-a-different-machine",children:["Why does ",(0,t.jsx)(o.code,{children:"downloadToUrl"})," time out when downloading from a different machine?"]}),"\n",(0,t.jsxs)(o.p,{children:["NAT is blocking the peer connection. When running multiple nodes across machines, the external IP and port mapping must be configured in ",(0,t.jsx)(o.code,{children:"config.json"}),". See the NAT section of the ",(0,t.jsx)(o.a,{href:"https://logos-co.github.io/logos-storage-module/latest/",children:"Logos Storage Module documentation"})," for guidance."]})]})}function h(e={}){let{wrapper:o}={...(0,l.R)(),...e.components};return o?(0,t.jsx)(o,{...e,children:(0,t.jsx)(c,{...e})}):c(e)}},8453(e,o,n){n.d(o,{R:()=>r,x:()=>d});var s=n(6540);let t={},l=s.createContext(t);function r(e){let o=s.useContext(l);return s.useMemo(function(){return"function"==typeof e?e(o):{...o,...e}},[o,e])}function d(e){let o;return o=e.disableParentContext?"function"==typeof e.components?e.components(t):e.components||t:r(e.components),s.createElement(l.Provider,{value:o},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/8e906247.18607bd8.js b/assets/js/8e906247.18607bd8.js new file mode 100644 index 00000000..521e9a35 --- /dev/null +++ b/assets/js/8e906247.18607bd8.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunklogos_docs=self.webpackChunklogos_docs||[]).push([["8847"],{1310(e,n,s){s.r(n),s.d(n,{metadata:()=>l,default:()=>h,frontMatter:()=>i,contentTitle:()=>t,toc:()=>c,assets:()=>r});var l=JSON.parse('{"id":"basecamp/get-started/install-logos-basecamp","title":"Install Logos Basecamp","description":"Get Logos Basecamp running on your desktop.","source":"@site/docs/basecamp/get-started/install-logos-basecamp.md","sourceDirName":"basecamp/get-started","slug":"/basecamp/get-started/install-logos-basecamp","permalink":"/basecamp/get-started/install-logos-basecamp","draft":false,"unlisted":false,"editUrl":"https://github.com/logos-co/logos-docs/tree/main/docs/basecamp/get-started/install-logos-basecamp.md","tags":[],"version":"current","sidebarPosition":1,"frontMatter":{"title":"Install Logos Basecamp","doc_type":"procedure","product":"core","topics":"core","steps_layout":"flat","authors":"iurimatias, cheny0","owner":"logos","doc_version":1,"slug":"install-logos-basecamp","sidebar_position":1},"sidebar":"basecampSidebar","next":{"title":"Install and load a module in Logos Basecamp","permalink":"/core/build-modules/install-and-load-a-module-in-logos-basecamp"}}'),a=s(4848),o=s(8453);let i={title:"Install Logos Basecamp",doc_type:"procedure",product:"core",topics:"core",steps_layout:"flat",authors:"iurimatias, cheny0",owner:"logos",doc_version:1,slug:"install-logos-basecamp",sidebar_position:1},t="Install Logos Basecamp",r={},c=[{value:"Get Logos Basecamp running on your desktop.",id:"get-logos-basecamp-running-on-your-desktop",level:4},{value:"Install from a prebuilt release",id:"install-from-a-prebuilt-release",level:2},{value:"Build and run Logos Basecamp from source",id:"build-and-run-logos-basecamp-from-source",level:2},{value:"Troubleshooting Basecamp",id:"troubleshooting-basecamp",level:2},{value:"Experimental Nix feature 'nix-command' when building the AppImage",id:"experimental-nix-feature-nix-command-when-building-the-appimage",level:3},{value:"I see an <code>libEGL.so.1 / libOpenGL.so.0 missing</code> error when trying to launch the AppImage on Linux?",id:"i-see-an-libeglso1--libopenglso0-missing-error-when-trying-to-launch-the-appimage-on-linux",level:3}];function d(e){let n={a:"a",admonition:"admonition",blockquote:"blockquote",code:"code",h1:"h1",h2:"h2",h3:"h3",h4:"h4",header:"header",li:"li",ol:"ol",p:"p",pre:"pre",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,o.R)(),...e.components};return(0,a.jsxs)(a.Fragment,{children:[(0,a.jsx)(n.header,{children:(0,a.jsx)(n.h1,{id:"install-logos-basecamp",children:"Install Logos Basecamp"})}),"\n",(0,a.jsx)(n.h4,{id:"get-logos-basecamp-running-on-your-desktop",children:"Get Logos Basecamp running on your desktop."}),"\n",(0,a.jsxs)(n.p,{children:["Logos ",(0,a.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#basecamp",children:"Basecamp"})," is the desktop shell for Logos. You can discover, install, and run Logos modules and apps using its graphical interface as an alternative to the command line."]}),"\n",(0,a.jsx)(n.p,{children:"You can install Logos Basecamp in two ways:"}),"\n",(0,a.jsxs)(n.table,{children:[(0,a.jsx)(n.thead,{children:(0,a.jsxs)(n.tr,{children:[(0,a.jsx)(n.th,{style:{textAlign:"left"},children:"Method"}),(0,a.jsx)(n.th,{style:{textAlign:"left"},children:"When to use it"}),(0,a.jsx)(n.th,{style:{textAlign:"left"},children:"Tooling required"})]})}),(0,a.jsxs)(n.tbody,{children:[(0,a.jsxs)(n.tr,{children:[(0,a.jsx)(n.td,{style:{textAlign:"left"},children:(0,a.jsx)(n.a,{href:"#install-from-a-prebuilt-release",children:"Prebuilt release (AppImage or DMG)"})}),(0,a.jsx)(n.td,{style:{textAlign:"left"},children:"End users"}),(0,a.jsx)(n.td,{style:{textAlign:"left"},children:"None"})]}),(0,a.jsxs)(n.tr,{children:[(0,a.jsx)(n.td,{style:{textAlign:"left"},children:(0,a.jsx)(n.a,{href:"#build-and-run-logos-basecamp-from-source",children:"Build from source with Nix"})}),(0,a.jsx)(n.td,{style:{textAlign:"left"},children:"Contributors, custom builds, unsupported platforms"}),(0,a.jsx)(n.td,{style:{textAlign:"left"},children:"Nix with flakes enabled"})]})]})]}),"\n",(0,a.jsx)(n.admonition,{type:"info",children:(0,a.jsxs)(n.p,{children:["To enable flakes in nix, add ",(0,a.jsx)(n.code,{children:"experimental-features = nix-command flakes"})," to ",(0,a.jsx)(n.code,{children:"/etc/nix/nix.conf"}),"."]})}),"\n",(0,a.jsx)(n.p,{children:"Before you start, make sure you have the following:"}),"\n",(0,a.jsxs)(n.ul,{children:["\n",(0,a.jsx)(n.li,{children:"Internet access."}),"\n",(0,a.jsx)(n.li,{children:"A supported OS: Linux x86_64 or aarch64 (tested on Ubuntu 22.04+), or macOS aarch64 (recent versions)."}),"\n",(0,a.jsx)(n.li,{children:"4 GB RAM minimum (8 GB recommended) and ~2 GB free disk space."}),"\n",(0,a.jsxs)(n.li,{children:["For the source build only: ",(0,a.jsx)(n.a,{href:"https://github.com/NixOS/nix-installer",children:"Nix"})," installed with flakes enabled."]}),"\n"]}),"\n",(0,a.jsx)(n.admonition,{type:"info",children:(0,a.jsx)(n.p,{children:"Internet access is required to download the binary or clone the repository, but not to launch Logos Basecamp afterward. Logos Basecamp itself opens no inbound ports."})}),"\n",(0,a.jsx)(n.h2,{id:"install-from-a-prebuilt-release",children:"Install from a prebuilt release"}),"\n",(0,a.jsxs)(n.ol,{children:["\n",(0,a.jsxs)(n.li,{children:["\n",(0,a.jsxs)(n.p,{children:["Go to the ",(0,a.jsx)(n.a,{href:"https://github.com/logos-co/logos-basecamp/releases/latest",children:"latest release page"})," and download the artifact for your OS:"]}),"\n",(0,a.jsxs)(n.ul,{children:["\n",(0,a.jsxs)(n.li,{children:["Linux: the ",(0,a.jsx)(n.code,{children:"AppImage"})," files"]}),"\n",(0,a.jsxs)(n.li,{children:["macOS: the ",(0,a.jsx)(n.code,{children:".dmg"})," files"]}),"\n"]}),"\n"]}),"\n",(0,a.jsxs)(n.li,{children:["\n",(0,a.jsx)(n.p,{children:"Depending on your OS, install and launch Basecamp as follows:"}),"\n"]}),"\n"]}),"\n",(0,a.jsxs)(n.ul,{children:["\n",(0,a.jsxs)(n.li,{children:["\n",(0,a.jsxs)(n.p,{children:["On macOS, drag the ",(0,a.jsx)(n.code,{children:".dmg"})," file into ",(0,a.jsx)(n.code,{children:"/Applications"}),". Then launch Basecamp from ",(0,a.jsx)(n.code,{children:"/Applications"}),"."]}),"\n"]}),"\n",(0,a.jsxs)(n.li,{children:["\n",(0,a.jsx)(n.p,{children:"On Linux, grant execute permission to the downloaded AppImage and launch it:"}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-bash",children:"chmod +x LogosBasecamp-Desktop-*.AppImage\n./LogosBasecamp-Desktop-x86_64.AppImage # or logos-basecamp-aarch64.AppImage\n"})}),"\n"]}),"\n"]}),"\n",(0,a.jsx)(n.h2,{id:"build-and-run-logos-basecamp-from-source",children:"Build and run Logos Basecamp from source"}),"\n",(0,a.jsxs)(n.ol,{children:["\n",(0,a.jsxs)(n.li,{children:["\n",(0,a.jsx)(n.p,{children:"Clone the repository and enter it:"}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-bash",children:"git clone https://github.com/logos-co/logos-basecamp.git\ncd logos-basecamp\n"})}),"\n"]}),"\n",(0,a.jsxs)(n.li,{children:["\n",(0,a.jsx)(n.p,{children:"Build Basecamp with Nix flake:"}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-bash",children:"nix build '.#bin-appimage'\n"})}),"\n"]}),"\n",(0,a.jsxs)(n.li,{children:["\n",(0,a.jsx)(n.p,{children:"Run the resulting binary:"}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-bash",children:"./result/logos-basecamp.AppImage\n"})}),"\n"]}),"\n"]}),"\n",(0,a.jsx)(n.h2,{id:"troubleshooting-basecamp",children:"Troubleshooting Basecamp"}),"\n",(0,a.jsx)(n.h3,{id:"experimental-nix-feature-nix-command-when-building-the-appimage",children:"Experimental Nix feature 'nix-command' when building the AppImage"}),"\n",(0,a.jsx)(n.p,{children:"The following error might appear while building the AppImage:"}),"\n",(0,a.jsxs)(n.blockquote,{children:["\n",(0,a.jsx)(n.p,{children:"error: experimental Nix feature 'nix-command' is disabled; add '--extra-experimental-features nix-command' to enable it"}),"\n"]}),"\n",(0,a.jsx)(n.p,{children:"in which case the command should be changed to enable flakes:"}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-bash",children:"nix build --extra-experimental-features 'nix-command flakes' '.#bin-appimage'\n"})}),"\n",(0,a.jsxs)(n.h3,{id:"i-see-an-libeglso1--libopenglso0-missing-error-when-trying-to-launch-the-appimage-on-linux",children:["I see an ",(0,a.jsx)(n.code,{children:"libEGL.so.1 / libOpenGL.so.0 missing"})," error when trying to launch the AppImage on Linux?"]}),"\n",(0,a.jsx)(n.p,{children:"Try running the following command:"}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-bash",children:"sudo apt-get install -y fuse libegl1 libopengl0\n"})})]})}function h(e={}){let{wrapper:n}={...(0,o.R)(),...e.components};return n?(0,a.jsx)(n,{...e,children:(0,a.jsx)(d,{...e})}):d(e)}},8453(e,n,s){s.d(n,{R:()=>i,x:()=>t});var l=s(6540);let a={},o=l.createContext(a);function i(e){let n=l.useContext(o);return l.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function t(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(a):e.components||a:i(e.components),l.createElement(o.Provider,{value:n},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/9003ff05.060dddfe.js b/assets/js/9003ff05.060dddfe.js new file mode 100644 index 00000000..801e26f7 --- /dev/null +++ b/assets/js/9003ff05.060dddfe.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunklogos_docs=self.webpackChunklogos_docs||[]).push([["6111"],{1569(e,n,s){s.r(n),s.d(n,{metadata:()=>i,default:()=>h,frontMatter:()=>l,contentTitle:()=>t,toc:()=>c,assets:()=>d});var i=JSON.parse('{"id":"mixnet/get-started/discover-nodes-and-send-messages-via-the-anoncomms-mixnet-demo-app","title":"Discover nodes and send messages via the AnonComms Mixnet demo app","description":"This page is an early draft and may be incomplete or incorrect. Expect changes, missing prerequisites, and commands that might not work in your setup. We are actively working to complete and verify this content.","source":"@site/docs/mixnet/get-started/discover-nodes-and-send-messages-via-the-anoncomms-mixnet-demo-app.md","sourceDirName":"mixnet/get-started","slug":"/mixnet/get-started/discover-nodes-and-send-messages-via-the-anoncomms-mixnet-demo-app","permalink":"/mixnet/get-started/discover-nodes-and-send-messages-via-the-anoncomms-mixnet-demo-app","draft":false,"unlisted":false,"editUrl":"https://github.com/logos-co/logos-docs/tree/main/docs/mixnet/get-started/discover-nodes-and-send-messages-via-the-anoncomms-mixnet-demo-app.md","tags":[],"version":"current","sidebarPosition":1,"frontMatter":{"sidebar_position":1},"sidebar":"mixnetSidebar"}'),r=s(4848),o=s(8453);let l={sidebar_position:1},t="Discover nodes and send messages via the AnonComms Mixnet demo app",d={},c=[{value:"A. Outcome + value (required)",id:"a-outcome--value-required",level:2},{value:"B. Scope + ownership",id:"b-scope--ownership",level:2},{value:"C. Runnable happy path",id:"c-runnable-happy-path",level:2},{value:"D. Configuration",id:"d-configuration",level:2},{value:"E. Hardware requirements (required for node/client journeys)",id:"e-hardware-requirements-required-for-nodeclient-journeys",level:2},{value:"F. Verify + troubleshoot",id:"f-verify--troubleshoot",level:2},{value:"G. Limits for v0.1",id:"g-limits-for-v01",level:2},{value:"H. References",id:"h-references",level:2},{value:"Time and Security (optional)",id:"time-and-security-optional",level:3}];function a(e){let n={a:"a",admonition:"admonition",code:"code",h1:"h1",h2:"h2",h3:"h3",header:"header",hr:"hr",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,o.R)(),...e.components};return(0,r.jsxs)(r.Fragment,{children:[(0,r.jsx)(n.header,{children:(0,r.jsx)(n.h1,{id:"discover-nodes-and-send-messages-via-the-anoncomms-mixnet-demo-app",children:"Discover nodes and send messages via the AnonComms Mixnet demo app"})}),"\n",(0,r.jsx)(n.admonition,{type:"warning",children:(0,r.jsx)(n.p,{children:"This page is an early draft and may be incomplete or incorrect. Expect changes, missing prerequisites, and commands that might not work in your setup. We are actively working to complete and verify this content."})}),"\n",(0,r.jsx)(n.h2,{id:"a-outcome--value-required",children:"A. Outcome + value (required)"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Outcome (end goal):"})," User will send anonymous chat messages over the mixnet by discovering ",(0,r.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#mix",children:"mix"})," nodes through Logos capability discovery."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Why it matters:"})," Proves two core testnet v0.1 primitives work end-to-end: capability-based node discovery and anonymous message routing via the mixnet."]}),"\n"]}),"\n",(0,r.jsx)(n.h2,{id:"b-scope--ownership",children:"B. Scope + ownership"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:["\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"Journey name:"})," Use the Mix Push Message App to send messages over the libp2p mixnet"]}),"\n"]}),"\n",(0,r.jsxs)(n.li,{children:["\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"Owner (GitHub + Discord):"})," ",(0,r.jsx)(n.a,{href:"https://github.com/chaitanyaprem",children:"GitHub"}),", Discord: prem_chaitanya"]}),"\n"]}),"\n",(0,r.jsxs)(n.li,{children:["\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:"Applies to:"})}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.a,{href:"https://github.com/logos-co/logos-chat-ui",children:"Chat UI App"}),", branch: logos-testnet-demo"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.a,{href:"https://github.com/logos-co/logos-chat-module",children:"Chat module"}),", branch: logos-testnet-demo"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.a,{href:"https://github.com/logos-co/logos-waku-module",children:"Logos Messaging (Waku) Module"}),", branch: logos-testnet-demo"]}),"\n"]}),"\n"]}),"\n",(0,r.jsxs)(n.li,{children:["\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"Runtime target:"})," Logos testnet v0.1."]}),"\n"]}),"\n"]}),"\n",(0,r.jsx)(n.h2,{id:"c-runnable-happy-path",children:"C. Runnable happy path"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:["\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:"Prereqs:"})}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"Operating Systems: Linux or macOS"}),"\n",(0,r.jsxs)(n.li,{children:["Build Tools:","\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"CMake (3.16 or later)"}),"\n",(0,r.jsx)(n.li,{children:"Ninja build system"}),"\n",(0,r.jsx)(n.li,{children:"pkg-config"}),"\n",(0,r.jsx)(n.li,{children:"nix"}),"\n"]}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,r.jsxs)(n.li,{children:["\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:"Commands:"})}),"\n",(0,r.jsxs)(n.ol,{children:["\n",(0,r.jsxs)(n.li,{children:["\n",(0,r.jsx)(n.p,{children:"Clone the chat-ui repo"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-shell",children:"git clone https://github.com/logos-co/logos-chat-ui.git\n"})}),"\n"]}),"\n",(0,r.jsxs)(n.li,{children:["\n",(0,r.jsx)(n.p,{children:"Change into the repo directory and check out the demo branch"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-shell",children:"cd logos-chat-ui\ngit checkout logos-testnet-demo\n"})}),"\n"]}),"\n",(0,r.jsxs)(n.li,{children:["\n",(0,r.jsx)(n.p,{children:"Run the standalone application. You should not see any errors in the console."}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-shell",children:"nix run '.'\n"})}),"\n",(0,r.jsx)(n.p,{children:"If you don't have flakes enabled globally, add experimental flags:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-shell",children:"nix run '.' --extra-experimental-features 'nix-command flakes'\n"})}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,r.jsxs)(n.li,{children:["\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:"Expected outputs:"})}),"\n",(0,r.jsxs)(n.ol,{children:["\n",(0,r.jsxs)(n.li,{children:["\n",(0,r.jsx)(n.p,{children:"Once the app is up and running, the user should see the UI with the following:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"Status shown as Ready"}),"\n",(0,r.jsx)(n.li,{children:"LP Peer count increasing over time before stabilising"}),"\n",(0,r.jsx)(n.li,{children:"Mix peer count increasing over time before stabilising"}),"\n",(0,r.jsxs)(n.li,{children:["Warning message ",(0,r.jsx)(n.code,{children:"Waiting for network peers..."})," disappears once 3 mix nodes are discovered."]}),"\n"]}),"\n"]}),"\n",(0,r.jsxs)(n.li,{children:["\n",(0,r.jsxs)(n.p,{children:["After sending a message from the app (which is possible once the warning message disappears), the app should display the same message in the ",(0,r.jsx)(n.code,{children:"Messages"})," section.\nNote that it takes some time for this to happen due to latency introduced by mix nodes."]}),"\n"]}),"\n",(0,r.jsxs)(n.li,{children:["\n",(0,r.jsx)(n.p,{children:"After sending a message, there are two methods to check if the message was delivered successfully:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:["\n",(0,r.jsxs)(n.p,{children:["The message appears in the ",(0,r.jsx)(n.code,{children:"Messages"})," section in the UI."]}),"\n"]}),"\n",(0,r.jsxs)(n.li,{children:["\n",(0,r.jsx)(n.p,{children:"The following logs appear in the console indicating success:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{children:'[LOGOS_HOST "waku_module" ]: "WakuModulePlugin: Lightpush publish initiated successfully for topic: \\"\\""\nChatBackend: Sent message to channel "baixa-chiado"\n'})}),"\n"]}),"\n"]}),"\n"]}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,r.jsx)(n.h2,{id:"d-configuration",children:"D. Configuration"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Required env vars / flags / config keys:"})," Not Applicable"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Example config snippet:"})," No config, everything should work out of the box."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Default ports/endpoints:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:["The user's machine should be able to connect to external fleet nodes using ",(0,r.jsx)(n.code,{children:"TCP"})," port ",(0,r.jsx)(n.code,{children:"60000"})," for discovery, sending, and receiving messages."]}),"\n",(0,r.jsx)(n.li,{children:"Connectivity and message reception will improve if the user's machine is reachable externally (even if behind a NAT)."}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,r.jsx)(n.h2,{id:"e-hardware-requirements-required-for-nodeclient-journeys",children:"E. Hardware requirements (required for node/client journeys)"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Target devices:"})," Nothing specific for this app. Any general restrictions placed by Logos modules should be considered."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Minimum requirements:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"CPU \u2014 1 core"}),"\n",(0,r.jsx)(n.li,{children:"RAM \u2014 2 GB"}),"\n",(0,r.jsx)(n.li,{children:"Storage (type + free space) \u2014 at least 20 GB of free space for building everything"}),"\n",(0,r.jsx)(n.li,{children:"Network bandwidth \u2014 No specific requirements"}),"\n"]}),"\n"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Recommended requirements:"})," Same as minimum requirements"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Storage profile:"})," No specific storage required."]}),"\n"]}),"\n",(0,r.jsx)(n.h2,{id:"f-verify--troubleshoot",children:"F. Verify + troubleshoot"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Verification step:"})," Run the app and confirm a sent message appears in the ",(0,r.jsx)(n.code,{children:"Messages"})," section."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Known failures (top 3-5):"}),"\n",(0,r.jsxs)(n.ol,{children:["\n",(0,r.jsxs)(n.li,{children:["\n",(0,r.jsx)(n.p,{children:"Orphan processes when killing the app via CLI or GUI"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:["\n",(0,r.jsx)(n.p,{children:"Symptom \u2014 Sometimes the child processes are orphaned when the app is killed. Check using the command below:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-shell",children:"ps -eaf | grep -i logos_host\n"})}),"\n"]}),"\n",(0,r.jsxs)(n.li,{children:["\n",(0,r.jsx)(n.p,{children:"Cause (if known) \u2014 Issue in liblogos"}),"\n"]}),"\n",(0,r.jsxs)(n.li,{children:["\n",(0,r.jsx)(n.p,{children:"Fix/workaround \u2014 Kill orphaned processes:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-shell",children:"pkill -f logos_host\n"})}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,r.jsxs)(n.li,{children:["\n",(0,r.jsxs)(n.p,{children:["Sent messages may not appear in the received messages section if the underlying connectivity of ",(0,r.jsx)(n.code,{children:"Waku-relay"})," is not healthy."]}),"\n"]}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,r.jsx)(n.h2,{id:"g-limits-for-v01",children:"G. Limits for v0.1"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Known issues:"}),"\n",(0,r.jsxs)(n.ol,{children:["\n",(0,r.jsx)(n.li,{children:"It may take 30 seconds to 1 minute to discover mix nodes. Until then, no messages can be sent using the app."}),"\n",(0,r.jsxs)(n.li,{children:["If the app is killed, some child processes are orphaned and continue running in the background. They must be killed before restarting the app, otherwise it may not work properly. Note that this may also kill any other ",(0,r.jsx)(n.code,{children:"logos_host"})," processes running."]}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,r.jsx)(n.h2,{id:"h-references",children:"H. References"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Existing sources:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"https://lip.logos.co/ift-ts/raw/extended-kad-disco.html",children:"Extended Kademlia Discovery with capability filtering"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"https://lip.logos.co/ift-ts/raw/mix.html",children:"LIBP2P-MIX"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"https://github.com/logos-messaging/specs/blob/master/standards/core/mix.md",children:"Waku mix"})}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"time-and-security-optional",children:"Time and Security (optional)"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Estimated time-to-complete:"})," 15-20 minutes"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Security / safety notes:"})," None"]}),"\n"]})]})}function h(e={}){let{wrapper:n}={...(0,o.R)(),...e.components};return n?(0,r.jsx)(n,{...e,children:(0,r.jsx)(a,{...e})}):a(e)}},8453(e,n,s){s.d(n,{R:()=>l,x:()=>t});var i=s(6540);let r={},o=i.createContext(r);function l(e){let n=i.useContext(o);return i.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function t(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(r):e.components||r:l(e.components),i.createElement(o.Provider,{value:n},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/93168d98.ab00e330.js b/assets/js/93168d98.ab00e330.js new file mode 100644 index 00000000..f0badac3 --- /dev/null +++ b/assets/js/93168d98.ab00e330.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunklogos_docs=self.webpackChunklogos_docs||[]).push([["9034"],{6492(e,t,o){o.r(t),o.d(t,{metadata:()=>s,default:()=>d,frontMatter:()=>a,contentTitle:()=>r,toc:()=>l,assets:()=>c});var s=JSON.parse('{"id":"lez/get-started/introduction-to-the-logos-execution-zone","title":"Introduction to the Logos Execution Zone","description":"Understand how the Logos Execution Zone runs general-purpose applications with selective privacy.","source":"@site/docs/lez/get-started/introduction-to-the-logos-execution-zone.md","sourceDirName":"lez/get-started","slug":"/lez/get-started/introduction-to-the-logos-execution-zone","permalink":"/lez/get-started/introduction-to-the-logos-execution-zone","draft":false,"unlisted":false,"editUrl":"https://github.com/logos-co/logos-docs/tree/main/docs/lez/get-started/introduction-to-the-logos-execution-zone.md","tags":[],"version":"current","sidebarPosition":1,"frontMatter":{"title":"Introduction to the Logos Execution Zone","doc_type":"concept","product":"blockchain","topics":"Logos Execution Zone","authors":"kashepavadan","owner":"logos","doc_version":1,"slug":"introduction-to-the-logos-execution-zone","sidebar_position":1},"sidebar":"lezSidebar","next":{"title":"Quickstart for the Logos Execution Zone wallet","permalink":"/lez/get-started/lez-quickstart"}}'),n=o(4848),i=o(8453);let a={title:"Introduction to the Logos Execution Zone",doc_type:"concept",product:"blockchain",topics:"Logos Execution Zone",authors:"kashepavadan",owner:"logos",doc_version:1,slug:"introduction-to-the-logos-execution-zone",sidebar_position:1},r="Introduction to the Logos Execution Zone",c={},l=[{value:"Understand how the Logos Execution Zone runs general-purpose applications with selective privacy.",id:"understand-how-the-logos-execution-zone-runs-general-purpose-applications-with-selective-privacy",level:4},{value:"The basics",id:"the-basics",level:2},{value:"Accounts Model",id:"accounts-model",level:2},{value:"LEZ Node Architecture",id:"lez-node-architecture",level:2},{value:"Use Cases",id:"use-cases",level:2}];function h(e){let t={a:"a",admonition:"admonition",h1:"h1",h2:"h2",h4:"h4",header:"header",li:"li",p:"p",ul:"ul",...(0,i.R)(),...e.components};return(0,n.jsxs)(n.Fragment,{children:[(0,n.jsx)(t.header,{children:(0,n.jsx)(t.h1,{id:"introduction-to-the-logos-execution-zone",children:"Introduction to the Logos Execution Zone"})}),"\n",(0,n.jsx)(t.h4,{id:"understand-how-the-logos-execution-zone-runs-general-purpose-applications-with-selective-privacy",children:"Understand how the Logos Execution Zone runs general-purpose applications with selective privacy."}),"\n",(0,n.jsxs)(t.p,{children:["The ",(0,n.jsx)(t.a,{href:"https://docs.logos.co/get-started/glossary#logos-execution-zone",children:"Logos Execution Zone"})," (",(0,n.jsx)(t.a,{href:"https://docs.logos.co/get-started/glossary#lez",children:"LEZ"}),") is the primary execution layer for applications built on the Logos stack. It is implemented as a ",(0,n.jsx)(t.a,{href:"/blockchain/concepts/about-zones",children:"Zone"})," on the ",(0,n.jsx)(t.a,{href:"https://docs.logos.co/get-started/glossary#logos-blockchain",children:"Logos Blockchain"})," and uses zero knowledge proofs to ensure the correctness of its operations. The LEZ runs a Risc0-based virtual machine, the ",(0,n.jsx)(t.a,{href:"https://docs.logos.co/get-started/glossary#logos-execution-environment",children:"Logos Execution Environment"})," (",(0,n.jsx)(t.a,{href:"https://docs.logos.co/get-started/glossary#lee",children:"LEE"}),"), which separates state into public and private components that LEE programs can use and modify seamlessly. This selective privacy lets developers write generic programs while the LEE guarantees privacy and correctness."]}),"\n",(0,n.jsx)(t.h2,{id:"the-basics",children:"The basics"}),"\n",(0,n.jsxs)(t.ul,{children:["\n",(0,n.jsxs)(t.li,{children:["The LEZ is the flagship ",(0,n.jsx)(t.a,{href:"https://docs.logos.co/get-started/glossary#zone",children:"Zone"})," for general-purpose applications on Logos, with built-in support for private execution."]}),"\n",(0,n.jsx)(t.li,{children:"The LEZ separates state into public and private accounts. Public executions are validated by re-execution, and private executions are proven with zero knowledge proofs."}),"\n",(0,n.jsxs)(t.li,{children:["The LEZ supports applications with high transaction throughput, and inscribes its state updates on the Logos Blockchain through the LEZ ",(0,n.jsx)(t.a,{href:"https://docs.logos.co/get-started/glossary#channel",children:"channel"}),"."]}),"\n"]}),"\n",(0,n.jsx)(t.h2,{id:"accounts-model",children:"Accounts Model"}),"\n",(0,n.jsxs)(t.p,{children:["To achieve separation between public and private state while allowing for full programmability, the LEE uses a stateless ",(0,n.jsx)(t.a,{href:"https://docs.logos.co/get-started/glossary#program",children:"program"})," model. Under this model, all persistent data is stored in accounts, which can be either public or private. Public accounts are stored on-chain as a map between the ",(0,n.jsx)(t.a,{href:"https://docs.logos.co/get-started/glossary#account",children:"account"})," ID and its associated state (such as a token balance). A ",(0,n.jsx)(t.a,{href:"https://docs.logos.co/get-started/glossary#public-account",children:"public account"})," owner\u2019s private key signs transactions and authorises executions, while the derived account ID is publicly visible on-chain."]}),"\n",(0,n.jsxs)(t.p,{children:["Private accounts, by contrast, are stored locally on the account-holder\u2019s node, which publishes ",(0,n.jsx)(t.a,{href:"https://en.wikipedia.org/wiki/Commitment_scheme",children:"commitments"})," to the account state onto the chain whenever the state is updated. The latest commitment binds the current account state to the chain without revealing its data, while nullifiers for previous commitments ensure that old commitments are not used for program execution. Private accounts are created with two associated key pairs."]}),"\n",(0,n.jsxs)(t.ul,{children:["\n",(0,n.jsxs)(t.li,{children:[(0,n.jsx)(t.a,{href:"https://docs.logos.co/get-started/glossary#nullifier-keys",children:"Nullifier keys"}),": The private nullifier key is used by the account owner to sign transactions and authorise executions. The public nullifier key is used as the account ID for verifying ownership."]}),"\n",(0,n.jsxs)(t.li,{children:[(0,n.jsx)(t.a,{href:"https://docs.logos.co/get-started/glossary#viewing-keys",children:"Viewing keys"}),": The private viewing key is used by the account owner to create ZK proofs. The public viewing key is used to verify proofs without revealing the account owner."]}),"\n"]}),"\n",(0,n.jsx)(t.admonition,{type:"info",children:(0,n.jsxs)(t.p,{children:["A program must obtain a ",(0,n.jsx)(t.a,{href:"https://docs.logos.co/get-started/glossary#private-account",children:"private account"}),"\u2019s ",(0,n.jsx)(t.a,{href:"https://docs.logos.co/get-started/glossary#nullifier-public-key",children:"nullifier public key"}),", as well as its ",(0,n.jsx)(t.a,{href:"https://docs.logos.co/get-started/glossary#viewing-public-key",children:"viewing public key"}),", in order to modify a private account state. Therefore, you cannot transfer tokens to a private account without being provided this information by the owner."]})}),"\n",(0,n.jsx)(t.p,{children:"When an LEE program executes, it modifies the accounts provided to it. These accounts could be either public or private, as an LEE program can be executed over both types of accounts. In either case, the program is executed using the same bytecode, the main difference being whether the user who runs a program will generate a zero knowledge proof. Executions modifying public accounts are executed transparently by LEZ validators like any standard RISC-V VM call. On the other hand, private executions involve the generation of zero knowledge proofs via Risc0, which are then verified by LEZ validators."}),"\n",(0,n.jsx)(t.h2,{id:"lez-node-architecture",children:"LEZ Node Architecture"}),"\n",(0,n.jsxs)(t.p,{children:["The LEZ operates as a Zone, with updates to its state (both public and private) inscribed on the Logos Blockchain. The LEZ is sequenced in a decentralised manner by a permissioned set of sequencer nodes, who take turns posting the latest updates to the LEZ channel on ",(0,n.jsx)(t.a,{href:"/blockchain/concepts/about-mantle",children:"Mantle"}),". In addition to the sequencers, the LEZ maintains an open network of validators, which update the Zone state by transparently executing programs (for public executions) or by verifying zero knowledge proofs of correct state transitions (for private executions). Anybody can join the validator network and contribute to the correctness of the LEZ state, submitting the latest state to an LEZ sequencer to post on the blockchain. The LEZ also supports read-only indexer nodes that follow the LEZ channel for purposes such as block exploration and development."]}),"\n",(0,n.jsx)(t.h2,{id:"use-cases",children:"Use Cases"}),"\n",(0,n.jsx)(t.p,{children:"The LEZ is a ready-made platform for privacy-preserving applications built using the Logos stack. Since the same LEE program can be executed over both public and private accounts, computationally costly private executions can be used only when necessary to speed up execution. Together with Solana-style parallel execution, this feature makes the LEZ uniquely suited to applications requiring high throughput as well as private application state."}),"\n",(0,n.jsx)(t.p,{children:"As an example, the LEZ could be used to host private DeFi applications. With support for privacy built-in, the LEZ could be used to build protocols where transaction details, user balances, or trading strategies are kept confidential by default, only revealing necessary information to authorised parties. This is especially so when high volumes of transactions are expected by these applications. Examples may include:"}),"\n",(0,n.jsxs)(t.ul,{children:["\n",(0,n.jsx)(t.li,{children:"Private Exchanges: Trading platforms where order books or individual trades are obscured."}),"\n",(0,n.jsx)(t.li,{children:"Confidential Lending/Borrowing Protocols: Financial services where loan details or collateral information are private."}),"\n",(0,n.jsx)(t.li,{children:"Confidential Transfers: Securely sending and receiving digital assets with transaction details obscured using zero knowledge proofs, ensuring financial privacy for individuals and businesses."}),"\n"]}),"\n",(0,n.jsx)(t.admonition,{type:"tip",children:(0,n.jsxs)(t.p,{children:["Check out the tutorial on how to create your own ",(0,n.jsx)(t.a,{href:"/build-an-app/tutorials/create-and-use-an-amm-liquidity-pool-on-the-logos-execution-zone",children:"Automated Market Maker (AMM) trading pool"})," program on the LEZ."]})})]})}function d(e={}){let{wrapper:t}={...(0,i.R)(),...e.components};return t?(0,n.jsx)(t,{...e,children:(0,n.jsx)(h,{...e})}):h(e)}},8453(e,t,o){o.d(t,{R:()=>a,x:()=>r});var s=o(6540);let n={},i=s.createContext(n);function a(e){let t=s.useContext(i);return s.useMemo(function(){return"function"==typeof e?e(t):{...t,...e}},[t,e])}function r(e){let t;return t=e.disableParentContext?"function"==typeof e.components?e.components(n):e.components||n:a(e.components),s.createElement(i.Provider,{value:t},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/96794759.b016c423.js b/assets/js/96794759.b016c423.js new file mode 100644 index 00000000..31adc0c7 --- /dev/null +++ b/assets/js/96794759.b016c423.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunklogos_docs=self.webpackChunklogos_docs||[]).push([["9112"],{1801(e,n,o){o.r(n),o.d(n,{metadata:()=>s,default:()=>h,frontMatter:()=>r,contentTitle:()=>l,toc:()=>a,assets:()=>d});var s=JSON.parse('{"id":"storage/get-started/set-up-and-use-logos-storage-ui","title":"Set up and use the Logos Storage UI","description":"Get started sharing and downloading files on the Logos Storage network","source":"@site/docs/storage/get-started/set-up-and-use-logos-storage-ui.md","sourceDirName":"storage/get-started","slug":"/storage/get-started/set-up-and-use-logos-storage-ui","permalink":"/storage/get-started/set-up-and-use-logos-storage-ui","draft":false,"unlisted":false,"editUrl":"https://github.com/logos-co/logos-docs/tree/main/docs/storage/get-started/set-up-and-use-logos-storage-ui.md","tags":[],"version":"current","sidebarPosition":2,"frontMatter":{"title":"Set up and use the Logos Storage UI","doc_type":"procedure","product":"storage","topics":"storage","steps_layout":"sectioned","authors":"gmega, kashepavadan","owner":"logos","doc_version":1,"slug":"set-up-and-use-logos-storage-ui","sidebar_position":2},"sidebar":"storageSidebar","previous":{"title":"Run a Logos storage node","permalink":"/storage/get-started/run-logos-storage-node"}}'),t=o(4848),i=o(8453);let r={title:"Set up and use the Logos Storage UI",doc_type:"procedure",product:"storage",topics:"storage",steps_layout:"sectioned",authors:"gmega, kashepavadan",owner:"logos",doc_version:1,slug:"set-up-and-use-logos-storage-ui",sidebar_position:2},l="Set up and use the Logos Storage UI",d={},a=[{value:"Get started sharing and downloading files on the Logos Storage network",id:"get-started-sharing-and-downloading-files-on-the-logos-storage-network",level:4},{value:"What to expect",id:"what-to-expect",level:2},{value:"Step 1: Build the application",id:"step-1-build-the-application",level:2},{value:"Step 2: Configure network connectivity",id:"step-2-configure-network-connectivity",level:2},{value:"Step 3: Share a file",id:"step-3-share-a-file",level:2},{value:"Step 4: Download a file",id:"step-4-download-a-file",level:2},{value:"Step 5: Delete a file",id:"step-5-delete-a-file",level:2},{value:"Troubleshooting Logos Storage",id:"troubleshooting-logos-storage",level:2},{value:"Node has no peers",id:"node-has-no-peers",level:3},{value:"UPnP is not working",id:"upnp-is-not-working",level:3},{value:"Manual port forwarding is configured but node is unreachable",id:"manual-port-forwarding-is-configured-but-node-is-unreachable",level:3},{value:"Build fails with HTTP 500 on BoringSSL fetch",id:"build-fails-with-http-500-on-boringssl-fetch",level:3}];function c(e){let n={a:"a",admonition:"admonition",code:"code",h1:"h1",h2:"h2",h3:"h3",h4:"h4",header:"header",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,i.R)(),...e.components};return(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)(n.header,{children:(0,t.jsx)(n.h1,{id:"set-up-and-use-the-logos-storage-ui",children:"Set up and use the Logos Storage UI"})}),"\n",(0,t.jsx)(n.h4,{id:"get-started-sharing-and-downloading-files-on-the-logos-storage-network",children:"Get started sharing and downloading files on the Logos Storage network"}),"\n",(0,t.jsxs)(n.p,{children:["The ",(0,t.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#logos-storage",children:"Logos Storage"})," UI is a file-sharing application built on top of the ",(0,t.jsx)(n.a,{href:"https://github.com/logos-co/logos-storage-module",children:"Logos Storage Module"}),". This guide covers building the application with Nix, configuring your node's network connectivity, and using the UI to share, download, and delete files. It is intended for node operators running the application on Linux or macOS."]}),"\n",(0,t.jsx)(n.p,{children:"Before you start, have the following ready:"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:["\n",(0,t.jsxs)(n.p,{children:[(0,t.jsx)(n.strong,{children:"Nix"})," with flakes enabled. Install from ",(0,t.jsx)(n.a,{href:"https://nixos.org/download.html",children:"nixos.org"}),", then enable flakes:"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-bash",children:"mkdir -p ~/.config/nix\necho 'experimental-features = nix-command flakes' >> ~/.config/nix/nix.conf\n"})}),"\n",(0,t.jsxs)(n.p,{children:["Verify: ",(0,t.jsx)(n.code,{children:'nix flake --help >/dev/null 2>&1 && echo "Flakes enabled"'})]}),"\n"]}),"\n",(0,t.jsxs)(n.li,{children:["\n",(0,t.jsx)(n.p,{children:"Git with submodules support"}),"\n"]}),"\n",(0,t.jsxs)(n.li,{children:["\n",(0,t.jsx)(n.p,{children:"A router where you can configure port forwarding or that supports UPnP/NAT-PMP"}),"\n"]}),"\n"]}),"\n",(0,t.jsx)(n.h2,{id:"what-to-expect",children:"What to expect"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:["You can build and run a standalone Logos Storage UI application using a single ",(0,t.jsx)(n.code,{children:"nix build"})," command."]}),"\n",(0,t.jsx)(n.li,{children:"You can configure your node's network connectivity to make it reachable from the internet."}),"\n",(0,t.jsxs)(n.li,{children:["You can share files with other nodes and download files shared by others using a Content Identifier (",(0,t.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#cid",children:"CID"}),")."]}),"\n"]}),"\n",(0,t.jsx)(n.h2,{id:"step-1-build-the-application",children:"Step 1: Build the application"}),"\n",(0,t.jsx)(n.p,{children:"The application is built using Nix flakes. The output includes the storage UI plugin and supporting binaries."}),"\n",(0,t.jsx)(n.admonition,{type:"info",children:(0,t.jsxs)(n.p,{children:["If Nix flakes are not enabled globally, add ",(0,t.jsx)(n.code,{children:"experimental-features = nix-command flakes"})," to ",(0,t.jsx)(n.code,{children:"~/.config/nix/nix.conf"}),"."]})}),"\n",(0,t.jsxs)(n.ol,{children:["\n",(0,t.jsxs)(n.li,{children:["\n",(0,t.jsxs)(n.p,{children:["Clone the ",(0,t.jsx)(n.a,{href:"https://github.com/logos-co/logos-storage-ui",children:(0,t.jsx)(n.code,{children:"logos-storage-ui"})})," repository and enter the project directory:"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-bash",children:"git clone --recurse-submodules https://github.com/logos-co/logos-storage-ui.git\ncd logos-storage-ui\n"})}),"\n"]}),"\n",(0,t.jsxs)(n.li,{children:["\n",(0,t.jsx)(n.p,{children:"Run the build command:"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-bash",children:"nix build\n"})}),"\n"]}),"\n",(0,t.jsxs)(n.li,{children:["\n",(0,t.jsxs)(n.p,{children:["Confirm the build succeeded by checking the ",(0,t.jsx)(n.code,{children:"result/"})," directory for the following outputs on macOS (",(0,t.jsx)(n.code,{children:".dylib"})," files are replaced with ",(0,t.jsx)(n.code,{children:".so"})," files on Linux):"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{children:"result/\n\u2514\u2500\u2500 lib/\n \u251C\u2500\u2500 storage_ui_plugin.dylib # Qt plugin (loaded by the app)\n \u2514\u2500\u2500 storage_ui_replica_factory.dylib\n"})}),"\n"]}),"\n",(0,t.jsxs)(n.li,{children:["\n",(0,t.jsx)(n.p,{children:"Enter development shell (Optional)"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-bash",children:"# Enter development shell with all dependencies\nnix develop\n"})}),"\n"]}),"\n",(0,t.jsxs)(n.li,{children:["\n",(0,t.jsx)(n.p,{children:"Launch the application:"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-bash",children:"nix run\n"})}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:["\n",(0,t.jsxs)(n.p,{children:["To override a dependency with a local version, use ",(0,t.jsx)(n.code,{children:"--override-input"}),". For example:"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-bash",children:"nix run --override-input storage_module/logos-storage git+file:///somewhere/logos-storage-nim?submodules=1\n"})}),"\n"]}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,t.jsx)(n.h2,{id:"step-2-configure-network-connectivity",children:"Step 2: Configure network connectivity"}),"\n",(0,t.jsx)(n.p,{children:"Your node must be reachable from the internet. Choose the setup option that matches your network environment."}),"\n",(0,t.jsxs)(n.table,{children:[(0,t.jsx)(n.thead,{children:(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.th,{style:{textAlign:"left"},children:"Scenario"}),(0,t.jsx)(n.th,{style:{textAlign:"left"},children:"Setup option"}),(0,t.jsx)(n.th,{style:{textAlign:"left"},children:"Details"})]})}),(0,t.jsxs)(n.tbody,{children:[(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{style:{textAlign:"left"},children:"Behind NAT with UPnP or NAT-PMP"}),(0,t.jsxs)(n.td,{style:{textAlign:"left"},children:[(0,t.jsx)(n.code,{children:"Guided"})," followed by ",(0,t.jsx)(n.code,{children:"UPnP"})]}),(0,t.jsx)(n.td,{style:{textAlign:"left"},children:"Logos Storage configures the network automatically."})]}),(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{style:{textAlign:"left"},children:"Behind NAT, manual port forwarding available"}),(0,t.jsxs)(n.td,{style:{textAlign:"left"},children:[(0,t.jsx)(n.code,{children:"Guided"})," followed by ",(0,t.jsx)(n.code,{children:"Port Forwarding"})]}),(0,t.jsxs)(n.td,{style:{textAlign:"left"},children:["Requires forwarding one TCP port (chosen during onboarding) and UDP ",(0,t.jsx)(n.code,{children:"8090"}),"."]})]}),(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{style:{textAlign:"left"},children:"Custom or complex network"}),(0,t.jsx)(n.td,{style:{textAlign:"left"},children:(0,t.jsx)(n.code,{children:"Advanced"})}),(0,t.jsxs)(n.td,{style:{textAlign:"left"},children:["Displays a prepopulated configuration JSON you can edit manually. See the ",(0,t.jsx)(n.a,{href:"https://logos-co.github.io/logos-storage-module/latest/api_reference.html",children:"API reference"}),"."]})]})]})]}),"\n",(0,t.jsxs)(n.p,{children:["The active configuration is saved to ",(0,t.jsx)(n.code,{children:"${HOME}/.logos_storage/config.json"}),". Change this file and restart the ",(0,t.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#storage-module",children:"Storage Module"})," to apply the changes."]}),"\n",(0,t.jsxs)(n.p,{children:["The default settings are saved to ",(0,t.jsx)(n.code,{children:"~/.config/Logos/LogosStorage.conf"})," on Linux, ",(0,t.jsx)(n.code,{children:"~/Library/Preferences/Logos.LogosStorage.plist"})," on macOS, and ",(0,t.jsx)(n.code,{children:"HKCU\\Software\\Logos\\LogosStorage"})," (Registry) on Windows."]}),"\n",(0,t.jsxs)(n.ol,{children:["\n",(0,t.jsxs)(n.li,{children:["\n",(0,t.jsxs)(n.p,{children:["Launch the application and select the setup option that matches your network environment from the table above and click ",(0,t.jsx)(n.strong,{children:"Continue"}),"."]}),"\n"]}),"\n",(0,t.jsxs)(n.li,{children:["\n",(0,t.jsx)(n.p,{children:'Wait for the connectivity checker to confirm your node is reachable. A message reading "your node is up and reachable" confirms success.'}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:["If the node is unreachable, see ",(0,t.jsx)(n.a,{href:"#troubleshooting-logos-storage",children:"Troubleshooting Logos Storage"})," below."]}),"\n",(0,t.jsx)(n.li,{children:"You can choose to continue without connectivity, but you will only be able to download files from other nodes."}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,t.jsx)(n.h2,{id:"step-3-share-a-file",children:"Step 3: Share a file"}),"\n",(0,t.jsxs)(n.ol,{children:["\n",(0,t.jsxs)(n.li,{children:["\n",(0,t.jsx)(n.p,{children:"In the UI, locate and click the upload panel. A file selector opens."}),"\n"]}),"\n",(0,t.jsxs)(n.li,{children:["\n",(0,t.jsxs)(n.p,{children:["Select the file you want to share and click ",(0,t.jsx)(n.strong,{children:"Open"}),". The file is uploaded to your node and sharing begins automatically."]}),"\n"]}),"\n",(0,t.jsxs)(n.li,{children:["\n",(0,t.jsxs)(n.p,{children:["Copy the Content Identifier (CID) displayed in the upload panel \u2014 for example, ",(0,t.jsx)(n.code,{children:"zDvZRwzm49ZJLzxheYtydzx6AcNVSrf69LriUWjPr1SNLVnaXfj2"}),". Share this string with others so they can download the file."]}),"\n"]}),"\n"]}),"\n",(0,t.jsx)(n.h2,{id:"step-4-download-a-file",children:"Step 4: Download a file"}),"\n",(0,t.jsxs)(n.ol,{children:["\n",(0,t.jsxs)(n.li,{children:["\n",(0,t.jsxs)(n.p,{children:["Paste the file's CID into the ",(0,t.jsx)(n.strong,{children:"Fetch manifest"})," panel and click ",(0,t.jsx)(n.strong,{children:"Fetch"}),". The file's metadata downloads from the network and an entry appears in the ",(0,t.jsx)(n.strong,{children:"Manifests"})," list at the bottom of the UI."]}),"\n"]}),"\n",(0,t.jsxs)(n.li,{children:["\n",(0,t.jsxs)(n.p,{children:["Next to the manifest entry, click the ",(0,t.jsx)(n.strong,{children:"Download"})," icon. A file selector will open."]}),"\n"]}),"\n",(0,t.jsxs)(n.li,{children:["\n",(0,t.jsxs)(n.p,{children:["Choose a save location and click ",(0,t.jsx)(n.strong,{children:"Open"}),". The download progress widget will show progress in real-time."]}),"\n"]}),"\n"]}),"\n",(0,t.jsx)(n.h2,{id:"step-5-delete-a-file",children:"Step 5: Delete a file"}),"\n",(0,t.jsx)(n.p,{children:"To stop sharing a file, click the trash bin icon next to the manifest entry for that file. This removes the file from your node and stops sharing it."}),"\n",(0,t.jsx)(n.h2,{id:"troubleshooting-logos-storage",children:"Troubleshooting Logos Storage"}),"\n",(0,t.jsx)(n.p,{children:"Logos Storage requires two open ports to function:"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:[(0,t.jsxs)(n.strong,{children:["UDP (",(0,t.jsx)(n.code,{children:"8090"})," by default)"]})," \u2014 used for discovery and DHT operations."]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsxs)(n.strong,{children:["TCP (",(0,t.jsx)(n.code,{children:"8500"})," by default)"]})," \u2014 used for data transfer and peer connections."]}),"\n"]}),"\n",(0,t.jsx)(n.h3,{id:"node-has-no-peers",children:"Node has no peers"}),"\n",(0,t.jsxs)(n.p,{children:[(0,t.jsx)(n.strong,{children:"Symptom:"})," The node starts successfully but never connects to any peer."]}),"\n",(0,t.jsxs)(n.p,{children:[(0,t.jsx)(n.strong,{children:"Cause:"})," Discovery is unavailable, typically because another process is using UDP port ",(0,t.jsx)(n.code,{children:"8090"}),"."]}),"\n",(0,t.jsxs)(n.p,{children:[(0,t.jsx)(n.strong,{children:"Fix:"})," Ensure no other process is using port ",(0,t.jsx)(n.code,{children:"8090"}),", or change the default port in the advanced configuration JSON."]}),"\n",(0,t.jsx)(n.h3,{id:"upnp-is-not-working",children:"UPnP is not working"}),"\n",(0,t.jsxs)(n.p,{children:[(0,t.jsx)(n.strong,{children:"Symptom:"})," You selected the ",(0,t.jsx)(n.code,{children:"UPnP"})," option during setup but the node remains unreachable."]}),"\n",(0,t.jsxs)(n.p,{children:[(0,t.jsx)(n.strong,{children:"Cause:"})," Many routers have UPnP disabled by default."]}),"\n",(0,t.jsxs)(n.p,{children:[(0,t.jsx)(n.strong,{children:"Fix:"})," Enable UPnP on your router, or switch to the manual ",(0,t.jsx)(n.code,{children:"Port Forwarding"})," configuration."]}),"\n",(0,t.jsx)(n.h3,{id:"manual-port-forwarding-is-configured-but-node-is-unreachable",children:"Manual port forwarding is configured but node is unreachable"}),"\n",(0,t.jsxs)(n.p,{children:[(0,t.jsx)(n.strong,{children:"Symptom:"})," Both UDP and TCP ports are configured but the node is still unreachable."]}),"\n",(0,t.jsxs)(n.p,{children:[(0,t.jsx)(n.strong,{children:"Cause:"})," The ports are not open on your router."]}),"\n",(0,t.jsxs)(n.p,{children:[(0,t.jsx)(n.strong,{children:"Fix:"})," Enable port forwarding for UDP ",(0,t.jsx)(n.code,{children:"8090"})," and your chosen TCP port on your router."]}),"\n",(0,t.jsx)(n.h3,{id:"build-fails-with-http-500-on-boringssl-fetch",children:"Build fails with HTTP 500 on BoringSSL fetch"}),"\n",(0,t.jsxs)(n.p,{children:[(0,t.jsx)(n.strong,{children:"Symptom:"})," The build fails with the following error:"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{children:"error: Failed to fetch git repository https://boringssl.googlesource.com/boringssl : error: RPC failed; HTTP 500 curl 22 The requested URL returned error: 500\nfatal: unable to write request to remote: Broken pipe\n"})}),"\n",(0,t.jsxs)(n.p,{children:[(0,t.jsx)(n.strong,{children:"Cause:"})," Git's HTTP request size limits are too low for large repositories."]}),"\n",(0,t.jsxs)(n.p,{children:[(0,t.jsx)(n.strong,{children:"Fix:"})," Increase the limits and retry:"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-bash",children:"git config --global http.postBuffer 524288000\ngit config --global http.maxRequestBuffer 100M\n"})})]})}function h(e={}){let{wrapper:n}={...(0,i.R)(),...e.components};return n?(0,t.jsx)(n,{...e,children:(0,t.jsx)(c,{...e})}):c(e)}},8453(e,n,o){o.d(n,{R:()=>r,x:()=>l});var s=o(6540);let t={},i=s.createContext(t);function r(e){let n=s.useContext(i);return s.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function l(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(t):e.components||t:r(e.components),s.createElement(i.Provider,{value:n},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/9e12ce96.eb9de1e2.js b/assets/js/9e12ce96.eb9de1e2.js new file mode 100644 index 00000000..0e084092 --- /dev/null +++ b/assets/js/9e12ce96.eb9de1e2.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunklogos_docs=self.webpackChunklogos_docs||[]).push([["8894"],{1167(e,n,i){i.r(n),i.d(n,{metadata:()=>l,default:()=>h,frontMatter:()=>t,contentTitle:()=>c,toc:()=>d,assets:()=>r});var l=JSON.parse('{"id":"core/build-modules/build-a-logos-cpp-ui-module","title":"Build a Logos C++ UI module","description":"Get started building a ui\\\\_qml module with a C++ backend that runs in a separate process.","source":"@site/docs/core/build-modules/build-a-logos-cpp-ui-module.md","sourceDirName":"core/build-modules","slug":"/core/build-modules/build-a-logos-cpp-ui-module","permalink":"/core/build-modules/build-a-logos-cpp-ui-module","draft":false,"unlisted":false,"editUrl":"https://github.com/logos-co/logos-docs/tree/main/docs/core/build-modules/build-a-logos-cpp-ui-module.md","tags":[],"version":"current","sidebarPosition":4,"frontMatter":{"title":"Build a Logos C++ UI module","doc_type":"procedure","product":"core","topics":"core","steps_layout":"sectioned","authors":"iurimatias, kashepavadan","owner":"logos","doc_version":1,"slug":"build-a-logos-cpp-ui-module","sidebar_position":4},"sidebar":"coreSidebar","previous":{"title":"Wrap a C library as a Logos module","permalink":"/core/build-modules/wrap-a-c-library-as-a-logos-core-module"},"next":{"title":"Install and load a module in Logos Basecamp","permalink":"/core/build-modules/install-and-load-a-module-in-logos-basecamp"}}'),o=i(4848),s=i(8453);let t={title:"Build a Logos C++ UI module",doc_type:"procedure",product:"core",topics:"core",steps_layout:"sectioned",authors:"iurimatias, kashepavadan",owner:"logos",doc_version:1,slug:"build-a-logos-cpp-ui-module",sidebar_position:4},c="Build a Logos C++ UI module",r={},d=[{value:"Get started building a ui_qml module with a C++ backend that runs in a separate process.",id:"get-started-building-a-ui_qml-module-with-a-c-backend-that-runs-in-a-separate-process",level:4},{value:"What to expect",id:"what-to-expect",level:2},{value:"Step 1: Scaffold the project",id:"step-1-scaffold-the-project",level:2},{value:"Step 2: Configure the module metadata",id:"step-2-configure-the-module-metadata",level:2},{value:"Step 3: Define the remote interface",id:"step-3-define-the-remote-interface",level:2},{value:"Step 4: Write the interface header",id:"step-4-write-the-interface-header",level:2},{value:"Step 5: Configure the CMake build",id:"step-5-configure-the-cmake-build",level:2},{value:"Step 6: Write the C++ backend plugin",id:"step-6-write-the-c-backend-plugin",level:2},{value:"Step 7: Write the QML view",id:"step-7-write-the-qml-view",level:2},{value:"Step 7.5: Use the Logos Design System in your QML (Optional)",id:"step-75-use-the-logos-design-system-in-your-qml-optional",level:3},{value:"Step 8: Configure the Nix flake",id:"step-8-configure-the-nix-flake",level:2},{value:"Step 9: Build and run the module",id:"step-9-build-and-run-the-module",level:2},{value:"Step 10: Update view with live reloading (Optional)",id:"step-10-update-view-with-live-reloading-optional",level:2},{value:"Step 11: Add UI integration tests (Optional)",id:"step-11-add-ui-integration-tests-optional",level:2},{value:"Troubleshooting the C++ UI module build",id:"troubleshooting-the-c-ui-module-build",level:2},{value:"The plugin fails to load at runtime with no clear error",id:"the-plugin-fails-to-load-at-runtime-with-no-clear-error",level:3},{value:"Linker errors during <code>nix build</code>",id:"linker-errors-during-nix-build",level:3},{value:"<code>DEV_QML_PATH</code> does not seem to take effect",id:"dev_qml_path-does-not-seem-to-take-effect",level:3}];function a(e){let n={a:"a",admonition:"admonition",code:"code",h1:"h1",h2:"h2",h3:"h3",h4:"h4",header:"header",img:"img",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,s.R)(),...e.components};return(0,o.jsxs)(o.Fragment,{children:[(0,o.jsx)(n.header,{children:(0,o.jsx)(n.h1,{id:"build-a-logos-c-ui-module",children:"Build a Logos C++ UI module"})}),"\n",(0,o.jsx)(n.h4,{id:"get-started-building-a-ui_qml-module-with-a-c-backend-that-runs-in-a-separate-process",children:"Get started building a ui_qml module with a C++ backend that runs in a separate process."}),"\n",(0,o.jsxs)(n.p,{children:["This guide covers building a ",(0,o.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#module",children:"module"})," that pairs a QML user interface with a C++ backend plugin. The backend runs in a separate ",(0,o.jsx)(n.code,{children:"ui-host"})," process while the QML view loads inside the host app (",(0,o.jsx)(n.code,{children:"logos-basecamp"})," or ",(0,o.jsx)(n.code,{children:"logos-standalone-app"}),"), so a backend crash cannot bring down the host. This guide is intended for developers who have completed ",(0,o.jsx)(n.a,{href:"/core/build-modules/wrap-a-c-library-as-a-logos-core-module",children:"Part 1"})," and want typed, process-isolated inter-module calls from their UI layer."]}),"\n",(0,o.jsxs)(n.p,{children:[(0,o.jsx)(n.strong,{children:"Before you start"}),", make sure you have the following:"]}),"\n",(0,o.jsxs)(n.ul,{children:["\n",(0,o.jsxs)(n.li,{children:["Completed ",(0,o.jsx)(n.a,{href:"/core/build-modules/wrap-a-c-library-as-a-logos-core-module",children:"Part 1"})," \u2014 a working ",(0,o.jsx)(n.code,{children:"calc_module"})," with the shared library built in ",(0,o.jsx)(n.code,{children:"logos-calc-module/lib/"}),"."]}),"\n",(0,o.jsx)(n.li,{children:"Nix with flakes enabled."}),"\n",(0,o.jsxs)(n.li,{children:["Basic familiarity with ",(0,o.jsx)(n.a,{href:"https://doc.qt.io/qt-6/qmlapplications.html",children:"QML"})]}),"\n"]}),"\n",(0,o.jsx)(n.h2,{id:"what-to-expect",children:"What to expect"}),"\n",(0,o.jsxs)(n.ul,{children:["\n",(0,o.jsxs)(n.li,{children:["You can scaffold, configure, and build a ",(0,o.jsx)(n.code,{children:"calc_ui_cpp"})," module with a process-isolated C++ backend."]}),"\n",(0,o.jsxs)(n.li,{children:["You can call ",(0,o.jsx)(n.code,{children:"calc_module"})," methods from QML using a typed replica and ",(0,o.jsx)(n.code,{children:"logos.watch()"}),"."]}),"\n",(0,o.jsxs)(n.li,{children:["You will be able to build, run, and live-reload the module using ",(0,o.jsx)(n.code,{children:"nix run"}),"."]}),"\n"]}),"\n",(0,o.jsx)(n.h2,{id:"step-1-scaffold-the-project",children:"Step 1: Scaffold the project"}),"\n",(0,o.jsx)(n.p,{children:"Create a new directory and initialise it from the C++ backend UI template."}),"\n",(0,o.jsxs)(n.ol,{children:["\n",(0,o.jsxs)(n.li,{children:["\n",(0,o.jsx)(n.p,{children:"Create and enter the project directory:"}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{className:"language-bash",children:"mkdir logos-calc-ui-cpp && cd logos-calc-ui-cpp\n"})}),"\n"]}),"\n",(0,o.jsxs)(n.li,{children:["\n",(0,o.jsx)(n.p,{children:"Initialise from the template:"}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{className:"language-bash",children:"nix flake init -t github:logos-co/logos-module-builder/0.2.0#ui-qml-backend\n"})}),"\n"]}),"\n",(0,o.jsxs)(n.li,{children:["\n",(0,o.jsx)(n.p,{children:"Initialise a Git repository and stage all generated files:"}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{className:"language-bash",children:"git init && git add -A\n"})}),"\n"]}),"\n",(0,o.jsxs)(n.li,{children:["\n",(0,o.jsxs)(n.p,{children:["Remove the template's example sources. The scaffolded template includes ",(0,o.jsx)(n.code,{children:"ui_example"})," files with mismatched class names and IIDs; leaving them causes build errors or plugin-load failures at runtime:"]}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{className:"language-bash",children:"rm -f src/ui_example.rep src/ui_example_interface.h src/ui_example_plugin.h src/ui_example_plugin.cpp\n"})}),"\n"]}),"\n"]}),"\n",(0,o.jsx)(n.h2,{id:"step-2-configure-the-module-metadata",children:"Step 2: Configure the module metadata"}),"\n",(0,o.jsxs)(n.p,{children:["Replace the template ",(0,o.jsx)(n.code,{children:"metadata.json"})," with your plugin's details."]}),"\n",(0,o.jsx)(n.admonition,{type:"info",children:(0,o.jsxs)(n.p,{children:["The ",(0,o.jsx)(n.code,{children:"calc_module.url"})," input attribute name in ",(0,o.jsx)(n.code,{children:"flake.nix"})," must match the dependency name declared here in ",(0,o.jsx)(n.code,{children:'"dependencies"'}),"."]})}),"\n",(0,o.jsxs)(n.ol,{children:["\n",(0,o.jsxs)(n.li,{children:["\n",(0,o.jsxs)(n.p,{children:["Replace ",(0,o.jsx)(n.code,{children:"metadata.json"})," with the following:"]}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{className:"language-json",children:'{\n "name": "calc_ui_cpp",\n "version": "1.0.0",\n "type": "ui_qml",\n "category": "tools",\n "description": "Calculator C++ UI \u2014 QML view with process-isolated backend for calc_module",\n "main": "calc_ui_cpp_plugin",\n "view": "qml/Main.qml",\n "icon": "icons/calc.png",\n "dependencies": ["calc_module"],\n\n "nix": {\n "packages": { "build": [], "runtime": [] },\n "external_libraries": [],\n "cmake": {\n "find_packages": [],\n "extra_sources": [],\n "extra_include_dirs": [],\n "extra_link_libraries": []\n }\n }\n}\n'})}),"\n",(0,o.jsx)(n.p,{children:"Key fields:"}),"\n",(0,o.jsxs)(n.ul,{children:["\n",(0,o.jsxs)(n.li,{children:[(0,o.jsx)(n.code,{children:'"type": "ui_qml"'})," \u2014 tells the builder this is a QML view module."]}),"\n",(0,o.jsxs)(n.li,{children:[(0,o.jsx)(n.code,{children:'"main": "calc_ui_cpp_plugin"'})," \u2014 the backend Qt plugin library name (without extension)."]}),"\n",(0,o.jsxs)(n.li,{children:[(0,o.jsx)(n.code,{children:'"view": "qml/Main.qml"'})," \u2014 the QML entry point."]}),"\n",(0,o.jsxs)(n.li,{children:[(0,o.jsx)(n.code,{children:'"dependencies": ["calc_module"]'})," \u2014 ",(0,o.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#core-module",children:"core modules"})," the backend calls."]}),"\n"]}),"\n"]}),"\n",(0,o.jsxs)(n.li,{children:["\n",(0,o.jsxs)(n.p,{children:["Create the icons directory and add a placeholder icon (displayed in the ",(0,o.jsx)(n.code,{children:"logos-basecamp"})," sidebar when the module is loaded):"]}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{className:"language-bash",children:'mkdir -p icons\n# Copy any PNG here - or generate a 64x64 placeholder:\necho "iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAAmElEQVR4nO3QMREAIBDAsFeEN3ziCWRkoEP2XmedfX82OkBrgA7QGqADtAboAK0BOkBrgA7QGqADtAboAK0BOkBrgA7QGqADtAboAK0BOkBrgA7QGqADtAboAK0BOkBrgA7QGqADtAboAK0BOkBrgA7QGqADtAboAK0BOkBrgA7QGqADtAboAK0BOkBrgA7QGqADtAboAO0BN/SiO/PatoIAAAAASUVORK5CYII=" | base64 -d > icons/calc.png\n'})}),"\n"]}),"\n"]}),"\n",(0,o.jsx)(n.h2,{id:"step-3-define-the-remote-interface",children:"Step 3: Define the remote interface"}),"\n",(0,o.jsxs)(n.p,{children:["The ",(0,o.jsx)(n.code,{children:".rep"})," file is the single source of truth for the interface between the QML view and the C++ backend. ",(0,o.jsx)(n.code,{children:"repc"})," compiles it at build time into typed source and replica headers."]}),"\n",(0,o.jsxs)(n.ol,{children:["\n",(0,o.jsxs)(n.li,{children:["\n",(0,o.jsxs)(n.p,{children:["Create ",(0,o.jsx)(n.code,{children:"src/calc_ui_cpp.rep"}),":"]}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{className:"language-rep",children:"class CalcUiCpp\n{\n SLOT(int add(int a, int b))\n SLOT(int multiply(int a, int b))\n SLOT(int factorial(int n))\n SLOT(int fibonacci(int n))\n SLOT(QString libVersion())\n}\n"})}),"\n",(0,o.jsxs)(n.p,{children:[(0,o.jsx)(n.code,{children:"repc"})," generates two headers from this file:"]}),"\n",(0,o.jsxs)(n.ul,{children:["\n",(0,o.jsxs)(n.li,{children:[(0,o.jsx)(n.code,{children:"rep_calc_ui_cpp_source.h"})," \u2014 ",(0,o.jsx)(n.code,{children:"CalcUiCppSimpleSource"})," with virtual slots the backend overrides."]}),"\n",(0,o.jsxs)(n.li,{children:[(0,o.jsx)(n.code,{children:"rep_calc_ui_cpp_replica.h"})," \u2014 ",(0,o.jsx)(n.code,{children:"CalcUiCppReplica"})," with typed methods the QML view calls."]}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,o.jsx)(n.h2,{id:"step-4-write-the-interface-header",children:"Step 4: Write the interface header"}),"\n",(0,o.jsxs)(n.p,{children:["Create ",(0,o.jsx)(n.code,{children:"src/calc_ui_cpp_interface.h"}),":"]}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{className:"language-cpp",children:'#ifndef CALC_UI_CPP_INTERFACE_H\n#define CALC_UI_CPP_INTERFACE_H\n\n#include <QObject>\n#include <QString>\n#include "interface.h"\n\nclass CalcUiCppInterface : public PluginInterface\n{\npublic:\n virtual ~CalcUiCppInterface() = default;\n};\n\n#define CalcUiCppInterface_iid "org.logos.CalcUiCppInterface"\nQ_DECLARE_INTERFACE(CalcUiCppInterface, CalcUiCppInterface_iid)\n\n#endif // CALC_UI_CPP_INTERFACE_H\n'})}),"\n",(0,o.jsx)(n.h2,{id:"step-5-configure-the-cmake-build",children:"Step 5: Configure the CMake build"}),"\n",(0,o.jsxs)(n.p,{children:["Create ",(0,o.jsx)(n.code,{children:"CMakeLists.txt"}),":"]}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{className:"language-cmake",children:'cmake_minimum_required(VERSION 3.14)\nproject(CalcUiCppPlugin LANGUAGES CXX)\n\nif(DEFINED ENV{LOGOS_MODULE_BUILDER_ROOT})\n include($ENV{LOGOS_MODULE_BUILDER_ROOT}/cmake/LogosModule.cmake)\nelse()\n message(FATAL_ERROR "LogosModule.cmake not found. Set LOGOS_MODULE_BUILDER_ROOT.")\nendif()\n\nlogos_module(\n NAME calc_ui_cpp\n REP_FILE src/calc_ui_cpp.rep\n SOURCES\n src/calc_ui_cpp_interface.h\n src/calc_ui_cpp_plugin.h\n src/calc_ui_cpp_plugin.cpp\n)\n'})}),"\n",(0,o.jsxs)(n.p,{children:[(0,o.jsx)(n.code,{children:"REP_FILE"})," tells ",(0,o.jsx)(n.code,{children:"logos_module()"})," to run ",(0,o.jsx)(n.code,{children:"repc"})," to generate source/replica headers, generate ",(0,o.jsx)(n.code,{children:"CalcUiCppViewPluginBase"}),", and build a separate ",(0,o.jsx)(n.code,{children:"calc_ui_cpp_replica_factory"})," shared library."]}),"\n",(0,o.jsx)(n.h2,{id:"step-6-write-the-c-backend-plugin",children:"Step 6: Write the C++ backend plugin"}),"\n",(0,o.jsx)(n.p,{children:"The backend plugin inherits three base classes:"}),"\n",(0,o.jsxs)(n.ul,{children:["\n",(0,o.jsxs)(n.li,{children:[(0,o.jsx)(n.code,{children:"CalcUiCppSimpleSource"})," \u2014 generated from ",(0,o.jsx)(n.code,{children:".rep"}),", provides the typed source for Qt Remote Objects."]}),"\n",(0,o.jsxs)(n.li,{children:[(0,o.jsx)(n.code,{children:"CalcUiCppInterface"})," \u2014 standard Logos plugin interface (",(0,o.jsx)(n.code,{children:"name()"}),", ",(0,o.jsx)(n.code,{children:"version()"}),")."]}),"\n",(0,o.jsxs)(n.li,{children:[(0,o.jsx)(n.code,{children:"CalcUiCppViewPluginBase"})," \u2014 generated, provides ",(0,o.jsx)(n.code,{children:"setBackend()"})," and ",(0,o.jsx)(n.code,{children:"enableRemoting()"}),"."]}),"\n"]}),"\n",(0,o.jsxs)(n.ol,{children:["\n",(0,o.jsxs)(n.li,{children:["\n",(0,o.jsxs)(n.p,{children:["Create ",(0,o.jsx)(n.code,{children:"src/calc_ui_cpp_plugin.h"}),":"]}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{className:"language-cpp",children:'#ifndef CALC_UI_CPP_PLUGIN_H\n#define CALC_UI_CPP_PLUGIN_H\n\n#include <QString>\n#include <QVariantList>\n#include "calc_ui_cpp_interface.h"\n#include "LogosViewPluginBase.h"\n#include "rep_calc_ui_cpp_source.h"\n\nclass LogosAPI;\nclass LogosModules;\n\nclass CalcUiCppPlugin : public CalcUiCppSimpleSource,\n public CalcUiCppInterface,\n public CalcUiCppViewPluginBase\n{\n Q_OBJECT\n Q_PLUGIN_METADATA(IID CalcUiCppInterface_iid FILE "metadata.json")\n Q_INTERFACES(CalcUiCppInterface)\n\npublic:\n explicit CalcUiCppPlugin(QObject* parent = nullptr);\n ~CalcUiCppPlugin() override;\n\n QString name() const override { return "calc_ui_cpp"; }\n QString version() const override { return "1.0.0"; }\n\n Q_INVOKABLE void initLogos(LogosAPI* api);\n \n // Slots from calc_ui_cpp.rep \u2014 return values directly. The QML replica\n // receives QRemoteObjectPendingReply; use logos.watch() in QML to get the value.\n int add(int a, int b) override;\n int multiply(int a, int b) override;\n int factorial(int n) override;\n int fibonacci(int n) override;\n QString libVersion() override;\n\nsignals:\n void eventResponse(const QString& eventName, const QVariantList& args);\n\nprivate:\n LogosAPI* m_logosAPI = nullptr;\n LogosModules* m_logos = nullptr;\n};\n\n#endif // CALC_UI_CPP_PLUGIN_H\n'})}),"\n",(0,o.jsx)(n.admonition,{type:"info",children:(0,o.jsxs)(n.p,{children:["If the interface filename or IID symbol here doesn't match the names in ",(0,o.jsx)(n.code,{children:"src/calc_ui_cpp_interface.h"}),", you will get build errors or plugin-load failures at runtime."]})}),"\n"]}),"\n",(0,o.jsxs)(n.li,{children:["\n",(0,o.jsxs)(n.p,{children:["Create ",(0,o.jsx)(n.code,{children:"src/calc_ui_cpp_plugin.cpp"}),":"]}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{className:"language-cpp",children:'#include "calc_ui_cpp_plugin.h"\n#include "logos_api.h"\n#include "logos_sdk.h"\n\nCalcUiCppPlugin::CalcUiCppPlugin(QObject* parent) : CalcUiCppSimpleSource(parent) {}\nCalcUiCppPlugin::~CalcUiCppPlugin() { delete m_logos; }\n\nvoid CalcUiCppPlugin::initLogos(LogosAPI* api)\n{\n if (m_logos) return;\n m_logosAPI = api;\n m_logos = new LogosModules(api);\n\n // Register this object as the Remote Objects source so the QML replica\n // can see its properties and call its slots.\n setBackend(this);\n}\n\nint CalcUiCppPlugin::add(int a, int b) { return m_logos->calc_module.add(a, b); }\nint CalcUiCppPlugin::multiply(int a, int b) { return m_logos->calc_module.multiply(a, b); }\nint CalcUiCppPlugin::factorial(int n) { return m_logos->calc_module.factorial(n); }\nint CalcUiCppPlugin::fibonacci(int n) { return m_logos->calc_module.fibonacci(n); }\n\nQString CalcUiCppPlugin::libVersion()\n{\n return m_logos->calc_module.libVersion();\n}\n'})}),"\n"]}),"\n"]}),"\n",(0,o.jsx)(n.h2,{id:"step-7-write-the-qml-view",children:"Step 7: Write the QML view"}),"\n",(0,o.jsxs)(n.ol,{children:["\n",(0,o.jsxs)(n.li,{children:["\n",(0,o.jsxs)(n.p,{children:["Create ",(0,o.jsx)(n.code,{children:"src/qml/Main.qml"}),":"]}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{className:"language-qml",children:'import QtQuick\nimport QtQuick.Controls\nimport QtQuick.Layouts\n\nItem {\n id: root\n\n property string result: ""\n property string errorText: ""\n\n // Typed replica of the backend running in ui-host (generated from calc_ui_cpp.rep).\n readonly property var backend: logos.module("calc_ui_cpp")\n\n // The ui-host backend connects asynchronously, so the replica isn\'t\n // immediately usable. Track readiness reactively: isViewModuleReady()\n // is a Q_INVOKABLE (not a property), so we re-check it on the\n // onViewModuleReadyChanged signal and once at startup \u2014 never via a\n // plain property binding, which would not re-evaluate.\n property bool ready: false\n\n Connections {\n target: logos\n function onViewModuleReadyChanged(moduleName, isReady) {\n if (moduleName === "calc_ui_cpp")\n root.ready = isReady && root.backend !== null\n }\n }\n Component.onCompleted: {\n root.ready = root.backend !== null && logos.isViewModuleReady("calc_ui_cpp")\n }\n\n // logos.watch() delivers the result of a replica slot call via callbacks.\n function callCalc(method, args) {\n if (!root.ready) {\n root.errorText = "Backend not ready"\n return\n }\n root.errorText = ""\n root.result = "..."\n logos.watch(backend[method].apply(backend, args),\n function(value) { root.result = String(value) },\n function(error) { root.errorText = String(error) }\n )\n }\n\n ColumnLayout {\n anchors.fill: parent\n anchors.margins: 24\n spacing: 16\n\n Text {\n text: "Logos Calculator (C++ backend)"\n font.pixelSize: 20\n color: "#ffffff"\n Layout.alignment: Qt.AlignHCenter\n }\n\n // Reactive backend-connection indicator.\n Text {\n text: root.ready ? "Connected" : "Connecting to backend..."\n color: root.ready ? "#56d364" : "#f0883e"\n font.pixelSize: 12\n Layout.alignment: Qt.AlignHCenter\n }\n\n RowLayout {\n spacing: 12\n Layout.fillWidth: true\n\n TextField { id: inputA; placeholderText: "a"; Layout.preferredWidth: 80; validator: IntValidator {} }\n TextField { id: inputB; placeholderText: "b"; Layout.preferredWidth: 80; validator: IntValidator {} }\n\n Button {\n text: "Add"; enabled: root.ready\n onClicked: root.callCalc("add", [parseInt(inputA.text) || 0, parseInt(inputB.text) || 0])\n }\n Button {\n text: "Multiply"; enabled: root.ready\n onClicked: root.callCalc("multiply", [parseInt(inputA.text) || 0, parseInt(inputB.text) || 0])\n }\n }\n\n RowLayout {\n spacing: 12\n Layout.fillWidth: true\n\n TextField { id: inputN; placeholderText: "n"; Layout.preferredWidth: 80; validator: IntValidator { bottom: 0 } }\n\n Button { text: "Factorial"; enabled: root.ready; onClicked: root.callCalc("factorial", [parseInt(inputN.text) || 0]) }\n Button { text: "Fibonacci"; enabled: root.ready; onClicked: root.callCalc("fibonacci", [parseInt(inputN.text) || 0]) }\n Button { text: "libcalc version"; enabled: root.ready; onClicked: root.callCalc("libVersion", []) }\n }\n\n Rectangle {\n Layout.fillWidth: true\n height: 56\n color: root.errorText.length > 0 ? "#3d1a1a" : "#1a2d1a"\n radius: 8\n\n Text {\n anchors.centerIn: parent\n text: root.errorText.length > 0 ? root.errorText\n : (root.result.length > 0 ? root.result : "Enter values and press a button")\n color: root.errorText.length > 0 ? "#f85149" : "#56d364"\n font.pixelSize: 15\n }\n }\n\n Item { Layout.fillHeight: true }\n }\n}\n'})}),"\n",(0,o.jsx)(n.p,{children:"Key patterns:"}),"\n",(0,o.jsxs)(n.ul,{children:["\n",(0,o.jsxs)(n.li,{children:[(0,o.jsx)(n.code,{children:'logos.module("calc_ui_cpp")'})," \u2014 gets the typed replica, with auto-synced properties."]}),"\n",(0,o.jsxs)(n.li,{children:[(0,o.jsx)(n.code,{children:"logos.watch(backend.add(1, 2), ...)"})," \u2014 delivers a ",(0,o.jsx)(n.code,{children:"SLOT"})," return value as a JS Promise."]}),"\n",(0,o.jsxs)(n.li,{children:["The ",(0,o.jsx)(n.code,{children:"logos"})," object is injected by the host at runtime \u2014 no ",(0,o.jsx)(n.code,{children:"QtRemoteObjects"})," import is needed."]}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,o.jsx)(n.h3,{id:"step-75-use-the-logos-design-system-in-your-qml-optional",children:"Step 7.5: Use the Logos Design System in your QML (Optional)"}),"\n",(0,o.jsxs)(n.p,{children:["The QML view runs inside the ",(0,o.jsx)(n.a,{href:"https://github.com/logos-co/logos-standalone-app",children:(0,o.jsx)(n.code,{children:"logos-standalone-app"})})," host app, which already has ",(0,o.jsx)(n.a,{href:"https://github.com/logos-co/logos-design-system",children:(0,o.jsx)(n.code,{children:"logos-design-system"})})," on its import path. Use its themed components directly to automatically give your module a polished, consistent look."]}),"\n",(0,o.jsxs)(n.ol,{children:["\n",(0,o.jsxs)(n.li,{children:["\n",(0,o.jsxs)(n.p,{children:["In ",(0,o.jsx)(n.code,{children:"src/qml/Main.qml"}),", add the necessary imports and replace raw ",(0,o.jsx)(n.code,{children:"Button"})," and ",(0,o.jsx)(n.code,{children:"TextField"})," elements with design system equivalents:"]}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{className:"language-qml",children:'import Logos.Theme\nimport Logos.Controls\nimport Logos.Icons // optional shared icon assets\n\n...\n\n// Instead of Button:\nLogosButton {\n text: qsTr("Add")\n onClicked: root.callCalc("add", [parseInt(inputA.text) || 0,\n parseInt(inputB.text) || 0])\n}\n\n...\n\n// Instead of TextField:\nLogosTextField {\n id: inputA\n placeholderText: qsTr("a")\n}\n\n...\n\n// Use theme colors instead of hardcoded hex values:\nRectangle {\n color: Theme.palette.backgroundSecondary\n radius: Theme.spacing.radiusSmall\n LogosText { text: qsTr("Result"); color: Theme.palette.text }\n}\n'})}),"\n"]}),"\n",(0,o.jsxs)(n.li,{children:["\n",(0,o.jsx)(n.p,{children:"Explore available components by running the design system storybook in the logos-design-system repo:"}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{className:"language-bash",children:"git clone https://github.com/logos-co/logos-design-system.git\ncd logos-design-system && nix run\n"})}),"\n",(0,o.jsx)(n.p,{children:"The sidebar splits components into:"}),"\n",(0,o.jsxs)(n.ul,{children:["\n",(0,o.jsxs)(n.li,{children:[(0,o.jsx)(n.strong,{children:"Controls"})," \u2014 designed per Figma, production-ready (",(0,o.jsx)(n.code,{children:"LogosButton"}),", ",(0,o.jsx)(n.code,{children:"LogosBadge"}),", ",(0,o.jsx)(n.code,{children:"LogosCheckbox"}),", ",(0,o.jsx)(n.code,{children:"LogosComboBox"}),", ",(0,o.jsx)(n.code,{children:"LogosIconButton"}),", ",(0,o.jsx)(n.code,{children:"LogosPaginator"}),", ",(0,o.jsx)(n.code,{children:"LogosSearchBar"}),", ",(0,o.jsx)(n.code,{children:"LogosTabBar"}),", ",(0,o.jsx)(n.code,{children:"LogosTable"}),", ",(0,o.jsx)(n.code,{children:"LogosText"}),", ",(0,o.jsx)(n.code,{children:"LogosTextField"}),", ",(0,o.jsx)(n.code,{children:"LogosToolTip"}),", \u2026)."]}),"\n",(0,o.jsxs)(n.li,{children:[(0,o.jsx)(n.strong,{children:"Controls (not designed)"})," \u2014 placeholders with stable APIs but unstyled visuals (",(0,o.jsx)(n.code,{children:"LogosDialog"}),", ",(0,o.jsx)(n.code,{children:"LogosDrawer"}),", ",(0,o.jsx)(n.code,{children:"LogosScrollView"}),", ",(0,o.jsx)(n.code,{children:"LogosSpinner"}),", ",(0,o.jsx)(n.code,{children:"LogosTextArea"}),", ",(0,o.jsx)(n.code,{children:"LogosSwitch"}),", \u2026). You can ship with them; they'll get the polished look applied later without you having to change your QML."]}),"\n"]}),"\n",(0,o.jsxs)(n.p,{children:[(0,o.jsx)(n.strong,{children:"Theme tokens"})," (use these instead of hex literals or manual font sizes):"]}),"\n",(0,o.jsxs)(n.ul,{children:["\n",(0,o.jsxs)(n.li,{children:[(0,o.jsx)(n.code,{children:"Theme.palette.-"})," \u2014 ",(0,o.jsx)(n.code,{children:"background"}),", ",(0,o.jsx)(n.code,{children:"backgroundSecondary"}),", ",(0,o.jsx)(n.code,{children:"surface"}),", ",(0,o.jsx)(n.code,{children:"text"}),", ",(0,o.jsx)(n.code,{children:"textSecondary"}),", ",(0,o.jsx)(n.code,{children:"border"}),", ",(0,o.jsx)(n.code,{children:"primary"}),", ",(0,o.jsx)(n.code,{children:"success"}),", ",(0,o.jsx)(n.code,{children:"warning"}),", ",(0,o.jsx)(n.code,{children:"error"}),", ",(0,o.jsx)(n.code,{children:"info"}),", ",(0,o.jsx)(n.code,{children:"hover"}),", ",(0,o.jsx)(n.code,{children:"pressed"}),", \u2026"]}),"\n",(0,o.jsxs)(n.li,{children:[(0,o.jsx)(n.code,{children:"Theme.spacing.*"})," \u2014 ",(0,o.jsx)(n.code,{children:"tiny"}),", ",(0,o.jsx)(n.code,{children:"small"}),", ",(0,o.jsx)(n.code,{children:"medium"}),", ",(0,o.jsx)(n.code,{children:"large"}),", ",(0,o.jsx)(n.code,{children:"xlarge"}),", ",(0,o.jsx)(n.code,{children:"xxlarge"}),", ",(0,o.jsx)(n.code,{children:"radiusSmall"}),", ",(0,o.jsx)(n.code,{children:"radiusMedium"}),", ",(0,o.jsx)(n.code,{children:"radiusLarge"})]}),"\n",(0,o.jsxs)(n.li,{children:[(0,o.jsx)(n.code,{children:"Theme.typography.*"})," \u2014 ",(0,o.jsx)(n.code,{children:"pageTitleText"})," (36), ",(0,o.jsx)(n.code,{children:"titleText"})," (30), ",(0,o.jsx)(n.code,{children:"panelTitleText"})," (24), ",(0,o.jsx)(n.code,{children:"subtitleText"})," (16), ",(0,o.jsx)(n.code,{children:"primaryText"})," (14), ",(0,o.jsx)(n.code,{children:"secondaryText"})," (12); ",(0,o.jsx)(n.code,{children:"weightRegular"})," / ",(0,o.jsx)(n.code,{children:"weightMedium"})," / ",(0,o.jsx)(n.code,{children:"weightBold"}),"; ",(0,o.jsx)(n.code,{children:"publicSans"})]}),"\n",(0,o.jsxs)(n.li,{children:[(0,o.jsx)(n.code,{children:"Logos.Icons.LogosIcons.*"})," \u2014 ",(0,o.jsx)(n.code,{children:"arrowLeft"}),", ",(0,o.jsx)(n.code,{children:"arrowRight"}),", ",(0,o.jsx)(n.code,{children:"refresh"}),", ",(0,o.jsx)(n.code,{children:"install"}),", ",(0,o.jsx)(n.code,{children:"trash"}),", ",(0,o.jsx)(n.code,{children:"more"}),", ",(0,o.jsx)(n.code,{children:"search"}),", \u2026"]}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,o.jsx)(n.h2,{id:"step-8-configure-the-nix-flake",children:"Step 8: Configure the Nix flake"}),"\n",(0,o.jsxs)(n.p,{children:["The template already wires everything up. Update the description and point ",(0,o.jsx)(n.code,{children:"calc_module"})," at your dependency."]}),"\n",(0,o.jsxs)(n.ol,{children:["\n",(0,o.jsxs)(n.li,{children:["\n",(0,o.jsxs)(n.p,{children:["Replace ",(0,o.jsx)(n.code,{children:"flake.nix"})," with the following:"]}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{className:"language-nix",children:'{\n description = "Calculator C++ UI plugin for Logos - QML view with process-isolated backend for calc_module";\n\n inputs = {\n logos-module-builder.url = "github:logos-co/logos-module-builder/0.2.0";\n\n # Keep this placeholder as is - it gets locked to your real path in the next step.\n calc_module.url = "path:/path/to/your/calc_module";\n };\n\n outputs = inputs@{ logos-module-builder, calc_module, ... }:\n logos-module-builder.lib.mkLogosQmlModule {\n src = ./.;\n configFile = ./metadata.json;\n flakeInputs = inputs;\n };\n}\n'})}),"\n",(0,o.jsxs)(n.p,{children:["To point at a published repo instead of a local path, change the ",(0,o.jsx)(n.code,{children:"calc_module.url"})," input to a ",(0,o.jsx)(n.code,{children:"github:"})," URL, for example ",(0,o.jsx)(n.code,{children:'calc_module.url = "github:<your-org>/<your-calc-module>";'}),"."]}),"\n"]}),"\n"]}),"\n",(0,o.jsx)(n.h2,{id:"step-9-build-and-run-the-module",children:"Step 9: Build and run the module"}),"\n",(0,o.jsxs)(n.p,{children:["Before building, confirm the ",(0,o.jsx)(n.code,{children:"calc_module"})," shared library is present from ",(0,o.jsx)(n.a,{href:"/core/build-modules/wrap-a-c-library-as-a-logos-core-module",children:"Part 1"}),"."]}),"\n",(0,o.jsxs)(n.ol,{children:["\n",(0,o.jsxs)(n.li,{children:["\n",(0,o.jsx)(n.p,{children:"Confirm the shared library exists:"}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{className:"language-bash",children:"# Linux\nls ../logos-calc-module/lib/libcalc.so\n\n# macOS\nls ../logos-calc-module/lib/libcalc.dylib\n"})}),"\n",(0,o.jsx)(n.p,{children:"If the file is missing, build it first:"}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{className:"language-bash",children:"cd ../logos-calc-module/lib\ngcc -shared -fPIC -o libcalc.so libcalc.c # Linux\n# gcc -shared -fPIC -o libcalc.dylib libcalc.c # macOS\ncd ../../logos-calc-ui-cpp\n"})}),"\n"]}),"\n",(0,o.jsxs)(n.li,{children:["\n",(0,o.jsxs)(n.p,{children:["Stage all files, then lock ",(0,o.jsx)(n.code,{children:"calc_module"})," to your local Part 1 checkout. The ",(0,o.jsx)(n.code,{children:"--override-input"})," flag resolves ",(0,o.jsx)(n.code,{children:"../logos-calc-module"})," to an absolute path and records it in ",(0,o.jsx)(n.code,{children:"flake.lock"}),":"]}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{className:"language-bash",children:"git add -A\nnix flake update --override-input calc_module path:../logos-calc-module\ngit add flake.lock\n"})}),"\n"]}),"\n",(0,o.jsxs)(n.li,{children:["\n",(0,o.jsx)(n.p,{children:"Build and run the app. After the lock is in place, no override flag is needed on subsequent commands:"}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{className:"language-bash",children:"nix run\n"})}),"\n"]}),"\n",(0,o.jsxs)(n.li,{children:["\n",(0,o.jsxs)(n.p,{children:["Confirm the view loads with all controls visible, then click ",(0,o.jsx)(n.strong,{children:"Add"})," with values in the input fields to test it out:"]}),"\n",(0,o.jsx)(n.p,{children:(0,o.jsx)(n.img,{alt:"Operation buttons visible",src:i(7659).A+"",width:"1024",height:"768"})}),"\n",(0,o.jsx)(n.p,{children:(0,o.jsx)(n.img,{alt:"Result of 3 + 5 shows 8",src:i(5813).A+"",width:"1024",height:"768"})}),"\n"]}),"\n"]}),"\n",(0,o.jsx)(n.h2,{id:"step-10-update-view-with-live-reloading-optional",children:"Step 10: Update view with live reloading (Optional)"}),"\n",(0,o.jsxs)(n.p,{children:["To enable live updates to the UI, set ",(0,o.jsx)(n.code,{children:"DEV_QML_PATH"})," to the directory that contains your view entry's basename. This tutorial sets ",(0,o.jsx)(n.code,{children:'"view": "qml/Main.qml"'})," in ",(0,o.jsx)(n.code,{children:"metadata.json"}),", so the directory must contain ",(0,o.jsx)(n.code,{children:"Main.qml"})," (here: ",(0,o.jsx)(n.code,{children:"src/qml/"}),")."]}),"\n",(0,o.jsxs)(n.ol,{children:["\n",(0,o.jsxs)(n.li,{children:["\n",(0,o.jsxs)(n.p,{children:["Run with ",(0,o.jsx)(n.code,{children:"DEV_QML_PATH"})," set so that QML is loaded from your source tree at runtime:"]}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{className:"language-bash",children:"DEV_QML_PATH=$PWD/src/qml nix run .\n"})}),"\n"]}),"\n"]}),"\n",(0,o.jsx)(n.h2,{id:"step-11-add-ui-integration-tests-optional",children:"Step 11: Add UI integration tests (Optional)"}),"\n",(0,o.jsxs)(n.p,{children:["Add automated UI tests using the ",(0,o.jsx)(n.a,{href:"https://github.com/logos-co/logos-qt-mcp",children:"logos-qt-mcp"})," test framework."]}),"\n",(0,o.jsxs)(n.ol,{children:["\n",(0,o.jsxs)(n.li,{children:["\n",(0,o.jsxs)(n.p,{children:["Create ",(0,o.jsx)(n.code,{children:"tests/ui-tests.mjs"}),":"]}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{className:"language-javascript",children:'import { resolve } from "node:path";\n\n// CI sets LOGOS_QT_MCP automatically; for interactive use: nix build .#test-framework -o result-mcp\nconst root =\n process.env.LOGOS_QT_MCP ||\n new URL("../result-mcp", import.meta.url).pathname;\nconst { test, run } = await import(\n resolve(root, "test-framework/framework.mjs")\n);\n\ntest("calc_ui_cpp: loads and shows title", async (app) => {\n await app.waitFor(\n async () => {\n await app.expectTexts(["Logos Calculator (C++ backend)"]);\n },\n { timeout: 15000, interval: 500, description: "UI to load" },\n );\n});\n\ntest("calc_ui_cpp: operation buttons visible", async (app) => {\n await app.expectTexts(["Add", "Multiply", "Factorial", "Fibonacci"]);\n});\n\nrun();\n'})}),"\n"]}),"\n",(0,o.jsxs)(n.li,{children:["\n",(0,o.jsx)(n.p,{children:"Stage the test file and run the hermetic CI test:"}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{className:"language-bash",children:"git add tests/\nnix build .#integration-test -L\n"})}),"\n",(0,o.jsxs)(n.p,{children:["The ",(0,o.jsx)(n.code,{children:"integration-test"})," output launches ",(0,o.jsx)(n.code,{children:"logos-standalone-app"})," with ",(0,o.jsx)(n.code,{children:"QT_QPA_PLATFORM=offscreen"})," (no display needed), connects to the QML inspector, and runs all ",(0,o.jsx)(n.code,{children:".mjs"})," files in ",(0,o.jsx)(n.code,{children:"tests/"}),"."]}),"\n"]}),"\n",(0,o.jsxs)(n.li,{children:["\n",(0,o.jsx)(n.p,{children:"To run tests interactively against an already-running app, build the test framework and run the app and tests in separate terminals:"}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{className:"language-bash",children:"nix build .#test-framework -o result-mcp\nnix run . # terminal 1 \u2014 app with inspector on :3768\nnode tests/ui-tests.mjs # terminal 2\n"})}),"\n"]}),"\n"]}),"\n",(0,o.jsx)(n.h2,{id:"troubleshooting-the-c-ui-module-build",children:"Troubleshooting the C++ UI module build"}),"\n",(0,o.jsx)(n.h3,{id:"the-plugin-fails-to-load-at-runtime-with-no-clear-error",children:"The plugin fails to load at runtime with no clear error"}),"\n",(0,o.jsxs)(n.p,{children:["Confirm the ",(0,o.jsx)(n.code,{children:"Q_PLUGIN_METADATA"})," IID and ",(0,o.jsx)(n.code,{children:"Q_DECLARE_INTERFACE"})," macro in ",(0,o.jsx)(n.code,{children:"calc_ui_cpp_interface.h"})," both use ",(0,o.jsx)(n.code,{children:"CalcUiCppInterface_iid"}),". A mismatch causes silent load failures."]}),"\n",(0,o.jsxs)(n.h3,{id:"linker-errors-during-nix-build",children:["Linker errors during ",(0,o.jsx)(n.code,{children:"nix build"})]}),"\n",(0,o.jsxs)(n.p,{children:["Confirm ",(0,o.jsx)(n.code,{children:"../logos-calc-module/lib/libcalc.so"})," for Linux (or ",(0,o.jsx)(n.code,{children:".dylib"})," on macOS) exists and that ",(0,o.jsx)(n.code,{children:"flake.lock"})," was updated with ",(0,o.jsx)(n.code,{children:"--override-input calc_module path:../logos-calc-module"}),". A stale or placeholder lock file is the most common cause."]}),"\n",(0,o.jsxs)(n.h3,{id:"dev_qml_path-does-not-seem-to-take-effect",children:[(0,o.jsx)(n.code,{children:"DEV_QML_PATH"})," does not seem to take effect"]}),"\n",(0,o.jsxs)(n.p,{children:["Confirm the path points at the directory containing ",(0,o.jsx)(n.code,{children:"Main.qml"})," directly \u2014 not a parent directory. The host looks for the basename from ",(0,o.jsx)(n.code,{children:'"view"'})," in ",(0,o.jsx)(n.code,{children:"metadata.json"})," inside the directory you provide."]})]})}function h(e={}){let{wrapper:n}={...(0,s.R)(),...e.components};return n?(0,o.jsx)(n,{...e,children:(0,o.jsx)(a,{...e})}):a(e)}},7659(e,n,i){i.d(n,{A:()=>l});let l=i.p+"assets/images/calc-cpp-buttons-78885e103cdbbb8e471c0fbc6fbfdc01.png"},5813(e,n,i){i.d(n,{A:()=>l});let l=i.p+"assets/images/calc-cpp-result-c33b1cae801fad3be492d3ad9b1a829d.png"},8453(e,n,i){i.d(n,{R:()=>t,x:()=>c});var l=i(6540);let o={},s=l.createContext(o);function t(e){let n=l.useContext(s);return l.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function c(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(o):e.components||o:t(e.components),l.createElement(s.Provider,{value:n},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/a1c727d1.dae89da2.js b/assets/js/a1c727d1.dae89da2.js new file mode 100644 index 00000000..5524e9a2 --- /dev/null +++ b/assets/js/a1c727d1.dae89da2.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunklogos_docs=self.webpackChunklogos_docs||[]).push([["7680"],{814(e,n,l){l.r(n),l.d(n,{metadata:()=>i,default:()=>h,frontMatter:()=>c,contentTitle:()=>r,toc:()=>t,assets:()=>d});var i=JSON.parse('{"id":"core/build-modules/wrap-a-c-library-as-a-logos-core-module","title":"Wrap a C library as a Logos module","description":"Expose functions from a C shared library through a Logos core module.","source":"@site/docs/core/build-modules/wrap-a-c-library-as-a-logos-core-module.md","sourceDirName":"core/build-modules","slug":"/core/build-modules/wrap-a-c-library-as-a-logos-core-module","permalink":"/core/build-modules/wrap-a-c-library-as-a-logos-core-module","draft":false,"unlisted":false,"editUrl":"https://github.com/logos-co/logos-docs/tree/main/docs/core/build-modules/wrap-a-c-library-as-a-logos-core-module.md","tags":[],"version":"current","sidebarPosition":3,"frontMatter":{"title":"Wrap a C library as a Logos module","doc_type":"procedure","product":"core","topics":"core","steps_layout":"sectioned","authors":"iurimatias, kashepavadan","owner":"logos","doc_version":1,"slug":"wrap-a-c-library-as-a-logos-core-module","sidebar_position":3},"sidebar":"coreSidebar","previous":{"title":"Build and run a Logos core module","permalink":"/core/build-modules/build-and-run-a-logos-core-module"},"next":{"title":"Build a Logos C++ UI module","permalink":"/core/build-modules/build-a-logos-cpp-ui-module"}}'),s=l(4848),o=l(8453);let c={title:"Wrap a C library as a Logos module",doc_type:"procedure",product:"core",topics:"core",steps_layout:"sectioned",authors:"iurimatias, kashepavadan",owner:"logos",doc_version:1,slug:"wrap-a-c-library-as-a-logos-core-module",sidebar_position:3},r="Wrap a C library as a Logos module",d={},t=[{value:"Expose functions from a C shared library through a Logos core module.",id:"expose-functions-from-a-c-shared-library-through-a-logos-core-module",level:4},{value:"What to expect",id:"what-to-expect",level:2},{value:"Step 1: Scaffold the module project",id:"step-1-scaffold-the-module-project",level:2},{value:"Step 2: Write the C library",id:"step-2-write-the-c-library",level:2},{value:"Step 3: Configure the Logos module",id:"step-3-configure-the-logos-module",level:2},{value:"Step 4: Build the module",id:"step-4-build-the-module",level:2},{value:"Step 5: Inspect the module",id:"step-5-inspect-the-module",level:2},{value:"Step 6: Test with logoscore",id:"step-6-test-with-logoscore",level:2},{value:"Step 7: Unit-test the module",id:"step-7-unit-test-the-module",level:2},{value:"Troubleshooting Logos module wrapping",id:"troubleshooting-logos-module-wrapping",level:2},{value:"A method doesn't appear in <code>lm</code> or can't be called",id:"a-method-doesnt-appear-in-lm-or-cant-be-called",level:3},{value:"Build error: unknown type or generator can't parse a method",id:"build-error-unknown-type-or-generator-cant-parse-a-method",level:3},{value:""Cannot load library"",id:"cannot-load-library",level:3},{value:"Events never reach subscribers",id:"events-never-reach-subscribers",level:3},{value:"Plugin not discovered by logoscore",id:"plugin-not-discovered-by-logoscore",level:3},{value:"<code>nix build .#lib</code> does nothing or fails silently",id:"nix-build-lib-does-nothing-or-fails-silently",level:3},{value:"First build is slow",id:"first-build-is-slow",level:3},{value:"Symbol not found errors",id:"symbol-not-found-errors",level:3}];function a(e){let n={a:"a",admonition:"admonition",code:"code",h1:"h1",h2:"h2",h3:"h3",h4:"h4",header:"header",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,o.R)(),...e.components};return(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(n.header,{children:(0,s.jsx)(n.h1,{id:"wrap-a-c-library-as-a-logos-module",children:"Wrap a C library as a Logos module"})}),"\n",(0,s.jsx)(n.h4,{id:"expose-functions-from-a-c-shared-library-through-a-logos-core-module",children:"Expose functions from a C shared library through a Logos core module."}),"\n",(0,s.jsxs)(n.p,{children:["This tutorial walks you through wrapping a C shared library (",(0,s.jsx)(n.code,{children:".so"})," on Linux, ",(0,s.jsx)(n.code,{children:".dylib"})," on macOS) as a Logos ",(0,s.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#module",children:"module"}),". By the end, you will have a ",(0,s.jsx)(n.code,{children:"calc_module"})," that compiles, loads, and responds to method calls via ",(0,s.jsx)(n.code,{children:"logoscore"}),". You write one plain C++ class \u2014 no Qt, no plugin boilerplate \u2014 and the build system generates the Qt plugin around it."]}),"\n",(0,s.jsxs)(n.p,{children:["For an example used in production, refer to ",(0,s.jsx)(n.a,{href:"https://github.com/logos-co/logos-libp2p-module",children:"logos-lib2p2-module"})," - a module that wraps the ",(0,s.jsx)(n.code,{children:"nim-libp2p"})," library (compiled to a C shared library)."]}),"\n",(0,s.jsx)(n.p,{children:"You need:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsx)(n.p,{children:"OS: Linux (x86_64 or aarch64) or macOS (x86_64 or aarch64). Tested on Ubuntu 22.04+ and recent macOS."}),"\n"]}),"\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsx)(n.p,{children:"RAM: 4 GB minimum, 8 GB recommended."}),"\n"]}),"\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsx)(n.p,{children:"Disk: ~2 GB free for the application + installed modules."}),"\n"]}),"\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.strong,{children:"Nix"})," with flakes enabled. Install from ",(0,s.jsx)(n.a,{href:"https://nixos.org/download.html",children:"nixos.org"}),", then enable flakes:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"mkdir -p ~/.config/nix\necho 'experimental-features = nix-command flakes' >> ~/.config/nix/nix.conf\n"})}),"\n",(0,s.jsxs)(n.p,{children:["Verify: ",(0,s.jsx)(n.code,{children:'nix flake --help >/dev/null 2>&1 && echo "Flakes enabled"'})]}),"\n"]}),"\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.strong,{children:"A C compiler"})," (gcc or clang) for building the C library. Only needed if you are building the ",(0,s.jsx)(n.code,{children:".so"}),"/",(0,s.jsx)(n.code,{children:".dylib"})," yourself rather than using a pre-built library."]}),"\n"]}),"\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsx)(n.p,{children:"Basic familiarity with C and C++."}),"\n"]}),"\n"]}),"\n",(0,s.jsx)(n.h2,{id:"what-to-expect",children:"What to expect"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:["You will write a ",(0,s.jsx)(n.code,{children:"calc_module"})," that exposes arithmetic functions to Logos using the pure C++ (",(0,s.jsx)(n.code,{children:"interface: universal"}),") pattern."]}),"\n",(0,s.jsxs)(n.li,{children:["You will build, inspect, and call the module with ",(0,s.jsx)(n.code,{children:"lm"})," and ",(0,s.jsx)(n.code,{children:"logoscore"}),", seeing your ",(0,s.jsx)(n.code,{children:"int64_t"})," methods appear as Qt-typed signals."]}),"\n",(0,s.jsx)(n.li,{children:"You will unit-test the module directly against a link-time mock of the C library."}),"\n"]}),"\n",(0,s.jsx)(n.h2,{id:"step-1-scaffold-the-module-project",children:"Step 1: Scaffold the module project"}),"\n",(0,s.jsxs)(n.p,{children:["Before writing any C code, scaffold the Logos module project using the official template. This gives you the correct ",(0,s.jsx)(n.code,{children:"flake.nix"}),", ",(0,s.jsx)(n.code,{children:"metadata.json"}),", directory structure, and build configuration out of the box."]}),"\n",(0,s.jsxs)(n.ol,{children:["\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsx)(n.p,{children:"Create the project directory and run the module builder template:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"mkdir logos-calc-module && cd logos-calc-module\n\n# To wrap an external C library\nnix flake init -t github:logos-co/logos-module-builder/0.2.0#with-external-lib\n\n# Or for a plain module (no external library):\n# nix flake init -t github:logos-co/logos-module-builder/0.2.0\n"})}),"\n",(0,s.jsxs)(n.p,{children:["This generates skeleton files (",(0,s.jsx)(n.code,{children:"flake.nix"}),", ",(0,s.jsx)(n.code,{children:"metadata.json"}),", ",(0,s.jsx)(n.code,{children:"CMakeLists.txt"}),", and a ",(0,s.jsx)(n.code,{children:"src/"})," directory) pre-configured for the ",(0,s.jsx)(n.code,{children:"logos-module-builder"}),". You then customise them for your specific library."]}),"\n",(0,s.jsx)(n.admonition,{type:"info"}),"\n"]}),"\n"]}),"\n",(0,s.jsxs)(n.p,{children:["As the time of writing, ",(0,s.jsx)(n.code,{children:"nix flake init"})," scaffolds a hand-written Qt plugin (",(0,s.jsx)(n.code,{children:"*_interface.h"})," + ",(0,s.jsx)(n.code,{children:"*_plugin.h"})," + ",(0,s.jsx)(n.code,{children:"*_plugin.cpp"}),"). This tutorial uses the newer ",(0,s.jsx)(n.strong,{children:"pure-C++ pattern"})," instead: you write one plain ",(0,s.jsx)(n.code,{children:"*_impl.h"})," / ",(0,s.jsx)(n.code,{children:"*_impl.cpp"})," class with no Qt, set ",(0,s.jsx)(n.code,{children:'"interface": "universal"'})," in ",(0,s.jsx)(n.code,{children:"metadata.json"}),", and the build generates the Qt plugin wrapper for you. The steps below replace the template's ",(0,s.jsx)(n.code,{children:"src/"})," files entirely. The ",(0,s.jsx)(n.code,{children:"nix flake init"})," command is still used to get the ",(0,s.jsx)(n.code,{children:"flake.nix"})," / ",(0,s.jsx)(n.code,{children:"CMakeLists.txt"})," skeleton and directory layout.\n:::"]}),"\n",(0,s.jsxs)(n.ol,{children:["\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsxs)(n.p,{children:["Remove the template's example sources. The ",(0,s.jsx)(n.code,{children:"with-external-lib"})," template ships an example Qt plugin (",(0,s.jsx)(n.code,{children:"external_lib_*"}),"). Delete those files \u2014 this tutorial supplies its own pure-C++ ",(0,s.jsx)(n.code,{children:"src/"})," files:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"rm -f src/external_lib_interface.h src/external_lib_plugin.h src/external_lib_plugin.cpp\n"})}),"\n"]}),"\n"]}),"\n",(0,s.jsx)(n.h2,{id:"step-2-write-the-c-library",children:"Step 2: Write the C library"}),"\n",(0,s.jsxs)(n.p,{children:["Create the C library that your module will wrap. Place the header and implementation in the ",(0,s.jsx)(n.code,{children:"lib/"})," directory."]}),"\n",(0,s.jsxs)(n.ol,{children:["\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsxs)(n.p,{children:["Create the ",(0,s.jsx)(n.code,{children:"lib"})," directory:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"mkdir -p lib\n"})}),"\n"]}),"\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsxs)(n.p,{children:["Create ",(0,s.jsx)(n.code,{children:"lib/libcalc.h"}),":"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-c",children:'#ifndef LIBCALC_H\n#define LIBCALC_H\n\n#ifdef __cplusplus\nextern "C" {\n#endif\n\n/** Add two integers. */\nint calc_add(int a, int b);\n\n/** Multiply two integers. */\nint calc_multiply(int a, int b);\n\n/** Compute factorial of n (n must be >= 0). Returns -1 on error. */\nint calc_factorial(int n);\n\n/** Compute the nth Fibonacci number (n must be >= 0). Returns -1 on error. */\nint calc_fibonacci(int n);\n\n/** Return the library version string. Caller must NOT free. */\nconst char* calc_version(void);\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif /* LIBCALC_H */\n'})}),"\n",(0,s.jsxs)(n.p,{children:["The ",(0,s.jsx)(n.code,{children:'extern "C"'})," block is essential \u2014 it prevents C++ name mangling so the Logos module can find the symbols."]}),"\n"]}),"\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsxs)(n.p,{children:["Create ",(0,s.jsx)(n.code,{children:"lib/libcalc.c"}),":"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-c",children:'#include "libcalc.h"\n\nint calc_add(int a, int b)\n{\n return a + b;\n}\n\nint calc_multiply(int a, int b)\n{\n return a * b;\n}\n\nint calc_factorial(int n)\n{\n if (n < 0) return -1;\n if (n <= 1) return 1;\n int result = 1;\n for (int i = 2; i <= n; i++) {\n result *= i;\n }\n return result;\n}\n\nint calc_fibonacci(int n)\n{\n if (n < 0) return -1;\n if (n == 0) return 0;\n if (n == 1) return 1;\n int a = 0, b = 1;\n for (int i = 2; i <= n; i++) {\n int tmp = a + b;\n a = b;\n b = tmp;\n }\n return b;\n}\n\nconst char* calc_version(void)\n{\n return "1.0.0";\n}\n'})}),"\n"]}),"\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsx)(n.p,{children:"Build the shared library:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"cd lib\n\n# Linux\ngcc -shared -fPIC -o libcalc.so libcalc.c\n\n# macOS\n# gcc -shared -fPIC -o libcalc.dylib libcalc.c\n\ncd ..\n"})}),"\n"]}),"\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsx)(n.p,{children:"Verify the symbols are exported:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"# Linux\nnm -D lib/libcalc.so | grep calc\n\n# macOS\n# nm -gU lib/libcalc.dylib | grep calc\n"})}),"\n",(0,s.jsxs)(n.p,{children:["Each symbol should be marked with ",(0,s.jsx)(n.code,{children:"T"})," (text/code section). Addresses will vary:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{children:"0000000000001139 T calc_add (or _calc_add on macOS)\n0000000000001179 T calc_factorial\n00000000000011f5 T calc_fibonacci\n0000000000001159 T calc_multiply\n0000000000001299 T calc_version\n"})}),"\n",(0,s.jsx)(n.admonition,{type:"info"}),"\n"]}),"\n"]}),"\n",(0,s.jsxs)(n.p,{children:["If you are wrapping an existing library (for example, from a system package or a GitHub repo), you don't need to write the C code \u2014 just place the pre-built ",(0,s.jsx)(n.code,{children:".so"}),"/",(0,s.jsx)(n.code,{children:".dylib"})," and its header file in ",(0,s.jsx)(n.code,{children:"lib/"}),".\n:::"]}),"\n",(0,s.jsx)(n.h2,{id:"step-3-configure-the-logos-module",children:"Step 3: Configure the Logos module"}),"\n",(0,s.jsxs)(n.p,{children:["Write the files that turn your C library into a Logos module. With the pure-C++ (",(0,s.jsx)(n.code,{children:"universal"}),") pattern you only hand-write a single C++ class \u2014 ",(0,s.jsx)(n.code,{children:"metadata.json"}),", ",(0,s.jsx)(n.code,{children:"CMakeLists.txt"}),", and ",(0,s.jsx)(n.code,{children:"flake.nix"})," tell the build system the rest, and ",(0,s.jsx)(n.code,{children:"logos-cpp-generator"})," synthesises the Qt plugin wrapper."]}),"\n",(0,s.jsx)(n.p,{children:"After this step, your project will look like this:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{children:"logos-calc-module/\n\u251C\u2500\u2500 flake.nix # Nix build configuration (~10 lines)\n\u251C\u2500\u2500 metadata.json # Module metadata, build settings, and runtime config\n\u251C\u2500\u2500 CMakeLists.txt # CMake build file\n\u251C\u2500\u2500 lib/\n\u2502 \u251C\u2500\u2500 libcalc.h # C library header\n\u2502 \u2514\u2500\u2500 libcalc.c # C library source (compiled by CMake)\n\u2514\u2500\u2500 src/\n \u251C\u2500\u2500 calc_module_impl.h # Plain C++ class\n \u2514\u2500\u2500 calc_module_impl.cpp # Implementation (wrapping logic)\n"})}),"\n",(0,s.jsxs)(n.ol,{children:["\n",(0,s.jsxs)(n.li,{children:["Create ",(0,s.jsx)(n.code,{children:"metadata.json"}),". Set ",(0,s.jsx)(n.code,{children:"name"}),", ",(0,s.jsx)(n.code,{children:"description"}),", ",(0,s.jsx)(n.code,{children:"main"}),", add ",(0,s.jsx)(n.code,{children:'"interface": "universal"'}),", and declare your library under ",(0,s.jsx)(n.code,{children:"nix.external_libraries"}),". This file is the single source of truth: it is embedded into the generated plugin binary, read by ",(0,s.jsx)(n.code,{children:"logos-module-builder"})," to configure the Nix build, used by CMake to resolve and link external libraries, and used by ",(0,s.jsx)(n.code,{children:"nix-bundle-lgx"})," to generate the LGX manifest."]}),"\n"]}),"\n",(0,s.jsxs)(n.p,{children:["To fetch and build external libraries from source, add ",(0,s.jsx)(n.code,{children:'"build_command": "make shared"'})," and ",(0,s.jsx)(n.code,{children:'"output_pattern": "build/<libname>"'})," to ",(0,s.jsx)(n.code,{children:'"external_libraries"'}),"."]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-json",children:'{\n "name": "calc_module",\n "version": "1.0.0",\n "type": "core",\n "category": "general",\n "description": "Calculator module wrapping libcalc C library",\n "main": "calc_module_plugin",\n "interface": "universal",\n "dependencies": [],\n\n "nix": {\n "packages": {\n "build": [],\n "runtime": []\n },\n "external_libraries": [\n {\n "name": "calc",\n "vendor_path": "lib"\n }\n ],\n "cmake": {\n "find_packages": [],\n "extra_sources": [],\n "extra_include_dirs": ["lib"],\n "extra_link_libraries": []\n }\n }\n}\n'})}),"\n",(0,s.jsx)(n.p,{children:"Key fields explained:"}),"\n",(0,s.jsxs)(n.table,{children:[(0,s.jsx)(n.thead,{children:(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.th,{children:"Field"}),(0,s.jsx)(n.th,{children:"What it does"})]})}),(0,s.jsxs)(n.tbody,{children:[(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"name"})}),(0,s.jsx)(n.td,{children:"Module name \u2014 must be a valid C identifier (used in filenames, method calls)"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"main"})}),(0,s.jsxs)(n.td,{children:["The generated plugin's name, ",(0,s.jsx)(n.code,{children:"<name>_plugin"}),". You don't write this file; the builder produces ",(0,s.jsx)(n.code,{children:"calc_module_plugin.so"})," / ",(0,s.jsx)(n.code,{children:".dylib"})]})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"interface"})}),(0,s.jsxs)(n.td,{children:[(0,s.jsx)(n.code,{children:'"universal"'})," selects the pure-C++ pattern. The builder runs ",(0,s.jsx)(n.code,{children:"logos-cpp-generator --from-header"})," over ",(0,s.jsx)(n.code,{children:"src/calc_module_impl.h"})," and emits the Qt plugin, so you never touch Qt directly"]})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"nix.external_libraries"})}),(0,s.jsxs)(n.td,{children:["Declares C/C++ libraries vendored in the repo. Each entry has a ",(0,s.jsx)(n.code,{children:"name"})," (the CMake target) and ",(0,s.jsx)(n.code,{children:"vendor_path"})," (directory with the source/binary). The build compiles the library and links it into the plugin"]})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"nix.cmake.extra_include_dirs"})}),(0,s.jsxs)(n.td,{children:["Added to the include path so your C++ code can use ",(0,s.jsx)(n.code,{children:'#include "lib/libcalc.h"'})]})]})]})]}),"\n",(0,s.jsxs)(n.ol,{children:["\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsxs)(n.p,{children:["Create ",(0,s.jsx)(n.code,{children:"CMakeLists.txt"}),". Set ",(0,s.jsx)(n.code,{children:"project()"})," name, ",(0,s.jsx)(n.code,{children:"NAME"}),", the ",(0,s.jsx)(n.code,{children:"SOURCES"})," (your two implementation files), and ",(0,s.jsx)(n.code,{children:"EXTERNAL_LIBS"}),". For a universal module you list only your plain C++ source files; the generated glue (",(0,s.jsx)(n.code,{children:"generated_code/*.cpp"}),") is picked up automatically by ",(0,s.jsx)(n.code,{children:"LogosModule.cmake"}),"."]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-cmake",children:'cmake_minimum_required(VERSION 3.14)\nproject(CalcModulePlugin LANGUAGES CXX)\n\n# Include the Logos Module CMake helper (provided by logos-module-builder)\nif(DEFINED ENV{LOGOS_MODULE_BUILDER_ROOT})\n include($ENV{LOGOS_MODULE_BUILDER_ROOT}/cmake/LogosModule.cmake)\nelseif(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/cmake/LogosModule.cmake")\n include(cmake/LogosModule.cmake)\nelse()\n message(FATAL_ERROR "LogosModule.cmake not found")\nendif()\n\n# Define the module with its external library dependency.\n# Because metadata.json sets `interface: universal`, the builder runs\n# logos-cpp-generator over src/calc_module_impl.h before configuring,\n# and LogosModule.cmake compiles the generated glue automatically.\nlogos_module(\n NAME calc_module\n SOURCES\n src/calc_module_impl.h\n src/calc_module_impl.cpp\n EXTERNAL_LIBS\n calc\n)\n'})}),"\n",(0,s.jsxs)(n.p,{children:["Keep these three fields in sync with ",(0,s.jsx)(n.code,{children:"metadata.json"}),":"]}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:(0,s.jsx)(n.code,{children:"NAME"})})," \u2014 your module name (must match ",(0,s.jsx)(n.code,{children:"name"})," in ",(0,s.jsx)(n.code,{children:"metadata.json"}),", e.g., ",(0,s.jsx)(n.code,{children:"calc_module"}),")"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:(0,s.jsx)(n.code,{children:"SOURCES"})})," \u2014 your implementation files (",(0,s.jsx)(n.code,{children:"src/calc_module_impl.h"}),", ",(0,s.jsx)(n.code,{children:"src/calc_module_impl.cpp"}),")"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:(0,s.jsx)(n.code,{children:"EXTERNAL_LIBS"})})," \u2014 external libraries to link (must match ",(0,s.jsx)(n.code,{children:"nix.external_libraries[].name"})," in ",(0,s.jsx)(n.code,{children:"metadata.json"}),")"]}),"\n"]}),"\n",(0,s.jsxs)(n.p,{children:["The ",(0,s.jsx)(n.code,{children:"if/elseif/else"})," block is boilerplate \u2014 don't change it."]}),"\n"]}),"\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsxs)(n.p,{children:["Create ",(0,s.jsx)(n.code,{children:"flake.nix"})," and change ",(0,s.jsx)(n.code,{children:"description"}),". External libraries can be added in ",(0,s.jsx)(n.code,{children:"inputs"}),", allowing ",(0,s.jsx)(n.code,{children:"nix"})," to fetch and build them from source."]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-nix",children:'{\n description = "Calculator module - wraps libcalc C library for Logos";\n\n inputs = {\n logos-module-builder.url = "github:logos-co/logos-module-builder/0.2.0";\n\n # Fetch the library source (non-flake)\n # libfoo-src = {\n # url = "github:example/libfoo";\n # flake = false;\n # };\n };\n\n outputs = inputs@{ logos-module-builder, ... }:\n logos-module-builder.lib.mkLogosModule {\n src = ./.;\n configFile = ./metadata.json;\n flakeInputs = inputs;\n };\n}\n'})}),"\n",(0,s.jsx)(n.admonition,{type:"info"}),"\n"]}),"\n"]}),"\n",(0,s.jsxs)(n.p,{children:["When adding module dependencies, the flake input attribute name must match the ",(0,s.jsx)(n.code,{children:"name"})," field in that dependency's ",(0,s.jsx)(n.code,{children:"metadata.json"}),". For example, if you depend on a module whose ",(0,s.jsx)(n.code,{children:"metadata.json"})," has ",(0,s.jsx)(n.code,{children:'"name": "waku_module"'}),", your flake input must be ",(0,s.jsx)(n.code,{children:'waku_module.url = "github:logos-co/logos-waku-module"'}),".\n:::"]}),"\n",(0,s.jsxs)(n.ol,{children:["\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsxs)(n.p,{children:["Create ",(0,s.jsx)(n.code,{children:"src/calc_module_impl.h"}),". This is the only interface you need to write. It allows every ",(0,s.jsx)(n.code,{children:"public"})," method becomes callable by other modules and by ",(0,s.jsx)(n.code,{children:"logoscore"}),". The code generator parses this header as text to derive the wire signatures, so keep it to the supported types (see the table below). Inheriting ",(0,s.jsx)(n.code,{children:"LogosModuleContext"})," lets the class emit events and call other modules without touching the raw ",(0,s.jsx)(n.code,{children:"LogosAPI"}),"."]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-cpp",children:'#pragma once\n\n#include <cstdint>\n#include <string>\n\n#include <logos_module_context.h> // LogosModuleContext base + `logos_events:`\n\n// Include the C library header (extern "C" already in the header).\nextern "C" {\n #include "lib/libcalc.h"\n}\n\nclass CalcModuleImpl : public LogosModuleContext {\npublic:\n CalcModuleImpl() = default;\n ~CalcModuleImpl() = default;\n\n // \u2500\u2500 Public API \u2014 every method here is callable over IPC \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n // The generator maps C++ types onto the wire automatically:\n // int64_t \u2194 int std::string \u2194 QString bool \u2194 bool\n int64_t add(int64_t a, int64_t b);\n int64_t multiply(int64_t a, int64_t b);\n int64_t factorial(int64_t n);\n int64_t fibonacci(int64_t n);\n std::string libVersion();\n\n // Fire-and-forget: looks up the version, then emits it as an event\n // instead of returning it. Used by the QML tutorial (Part 2).\n void libVersionNotify();\n\n // \u2500\u2500 Events \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n // Declared like Qt signals. The generator emits the body (in\n // calc_module_events.cpp) that routes the typed args to subscribers\n // via the host\'s `eventResponse` mechanism. QML subscribes with\n // logos.onModuleEvent("calc_module", "versionReady").\nlogos_events:\n void versionReady(const std::string& version);\n};\n'})}),"\n",(0,s.jsx)(n.p,{children:"Supported parameter and return types:"}),"\n",(0,s.jsxs)(n.table,{children:[(0,s.jsx)(n.thead,{children:(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.th,{children:"C++ type"}),(0,s.jsx)(n.th,{children:"Qt"})]})}),(0,s.jsxs)(n.tbody,{children:[(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"void"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"void"})})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"bool"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"bool"})})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"int64_t"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"int"})})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"uint64_t"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"uint"})})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"double"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"double"})})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"std::string"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"QString"})})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"std::vector<std::string>"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"QStringList"})})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"std::vector<uint8_t>"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"QByteArray"})})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsxs)(n.td,{children:[(0,s.jsx)(n.code,{children:"LogosMap"})," / ",(0,s.jsx)(n.code,{children:"LogosList"})]}),(0,s.jsxs)(n.td,{children:[(0,s.jsx)(n.code,{children:"QVariantMap"})," / ",(0,s.jsx)(n.code,{children:"QVariantList"})," (from ",(0,s.jsx)(n.code,{children:"<logos_json.h>"}),")"]})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"StdLogosResult"})}),(0,s.jsxs)(n.td,{children:[(0,s.jsx)(n.code,{children:"LogosResult"})," (from ",(0,s.jsx)(n.code,{children:"<logos_result.h>"}),") \u2014 ",(0,s.jsx)(n.code,{children:"{ success, value, error }"})]})]})]})]}),"\n"]}),"\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsxs)(n.p,{children:["Create ",(0,s.jsx)(n.code,{children:"src/calc_module_impl.cpp"}),". Each method calls the corresponding C function and converts the result. No Qt types appear anywhere \u2014 you work in plain C++ and the generated glue handles the conversion."]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-cpp",children:'#include "calc_module_impl.h"\n\nint64_t CalcModuleImpl::add(int64_t a, int64_t b)\n{\n return calc_add(static_cast<int>(a), static_cast<int>(b));\n}\n\nint64_t CalcModuleImpl::multiply(int64_t a, int64_t b)\n{\n return calc_multiply(static_cast<int>(a), static_cast<int>(b));\n}\n\nint64_t CalcModuleImpl::factorial(int64_t n)\n{\n return calc_factorial(static_cast<int>(n));\n}\n\nint64_t CalcModuleImpl::fibonacci(int64_t n)\n{\n return calc_fibonacci(static_cast<int>(n));\n}\n\nstd::string CalcModuleImpl::libVersion()\n{\n return std::string(calc_version());\n}\n\nvoid CalcModuleImpl::libVersionNotify()\n{\n // Emit the event declared in `logos_events:`. When the module is\n // loaded by a host, this reaches every subscriber. When the class\n // is constructed outside a host (e.g. in unit tests), it is a\n // safe no-op.\n versionReady(std::string(calc_version()));\n}\n'})}),"\n",(0,s.jsxs)(n.p,{children:["The wrapping pattern is always the same: call the C function (converting ",(0,s.jsx)(n.code,{children:"int64_t"})," \u2192 ",(0,s.jsx)(n.code,{children:"int"})," for libcalc's ",(0,s.jsx)(n.code,{children:"int"})," API), convert the C result to a C++ type if needed (for example, ",(0,s.jsx)(n.code,{children:"const char*"})," \u2192 ",(0,s.jsx)(n.code,{children:"std::string"}),"), and return it."]}),"\n"]}),"\n"]}),"\n",(0,s.jsx)(n.h2,{id:"step-4-build-the-module",children:"Step 4: Build the module"}),"\n",(0,s.jsxs)(n.ol,{children:["\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsxs)(n.p,{children:["Initialise the Git repository. Nix flakes require a git repository. First create a ",(0,s.jsx)(n.code,{children:".gitignore"})," to exclude build artifacts:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-text",children:"# Nix build output\nresult\nresult-*\n\n# CMake build directory\nbuild/\n"})}),"\n",(0,s.jsx)(n.p,{children:"Then initialise the repo and stage all files:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"git init\ngit add -A\n\nnix flake update\n\ngit add flake.lock\n"})}),"\n"]}),"\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsxs)(n.p,{children:["Build just the plugin library (",(0,s.jsx)(n.code,{children:".so"})," / ",(0,s.jsx)(n.code,{children:".dylib"}),"):"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"nix build '.#lib'\n"})}),"\n",(0,s.jsx)(n.admonition,{type:"info"}),"\n"]}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"The first build takes 5\u201315 minutes as Nix downloads Qt, the Logos SDK, and other dependencies. Subsequent builds are fast due to caching.\n:::"}),"\n",(0,s.jsxs)(n.ol,{children:["\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsxs)(n.p,{children:["Build everything - both the library and generated SDK headers. For a ",(0,s.jsx)(n.code,{children:"universal"})," module this is also where ",(0,s.jsx)(n.code,{children:"logos-cpp-generator --from-header"})," runs over ",(0,s.jsx)(n.code,{children:"src/calc_module_impl.h"})," to produce the Qt plugin glue under ",(0,s.jsx)(n.code,{children:"generated_code/"})," before CMake compiles it:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"nix build\n"})}),"\n"]}),"\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsx)(n.p,{children:"Inspect the output:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"ls -la result/lib/\n"})}),"\n",(0,s.jsx)(n.p,{children:"You should see two files (extensions depend on your platform):"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{children:"# Linux\ncalc_module_plugin.so # Your Logos module plugin\nlibcalc.so # The C library (copied alongside)\n\n# macOS\ncalc_module_plugin.dylib\nlibcalc.dylib\n"})}),"\n",(0,s.jsx)(n.p,{children:"Both library files are placed together so the plugin can find the C library at runtime via RPATH."}),"\n"]}),"\n"]}),"\n",(0,s.jsx)(n.h2,{id:"step-5-inspect-the-module",children:"Step 5: Inspect the module"}),"\n",(0,s.jsxs)(n.p,{children:["Use the ",(0,s.jsx)(n.code,{children:"lm"})," CLI tool (from ",(0,s.jsx)(n.code,{children:"logos-module"}),") to inspect the compiled module binary."]}),"\n",(0,s.jsxs)(n.ol,{children:["\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsxs)(n.p,{children:["Build the ",(0,s.jsx)(n.code,{children:"lm"})," tool:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"nix build 'github:logos-co/logos-module/0.2.0#lm' --out-link ./lm\n"})}),"\n"]}),"\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsx)(n.p,{children:"View metadata:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"# Linux\n./lm/bin/lm metadata result/lib/calc_module_plugin.so\n\n# macOS\n./lm/bin/lm metadata result/lib/calc_module_plugin.dylib\n"})}),"\n",(0,s.jsx)(n.p,{children:"Expected output:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{children:"Plugin Metadata:\n================\nName: calc_module\nVersion: 1.0.0\nDescription: Calculator module wrapping libcalc C library\nAuthor:\nType: core\nDependencies: (none)\n"})}),"\n"]}),"\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsx)(n.p,{children:"List methods:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"# Linux\n./lm/bin/lm methods result/lib/calc_module_plugin.so\n\n# macOS\n./lm/bin/lm methods result/lib/calc_module_plugin.dylib\n\n# Add --json for scripting and CI\n\n# Linux\n./lm/bin/lm methods result/lib/calc_module_plugin.so --json\n\n# macOS\n./lm/bin/lm methods result/lib/calc_module_plugin.dylib --json\n"})}),"\n",(0,s.jsx)(n.p,{children:"Expected output:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{children:"Plugin Methods:\n===============\n\nint add(int a, int b)\n Signature: add(int,int)\n Invokable: yes\n\n ...\n"})}),"\n",(0,s.jsxs)(n.p,{children:["Expected output with ",(0,s.jsx)(n.code,{children:"--json"}),":"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-json",children:'[\n {\n "isInvokable": true,\n "name": "add",\n "parameters": [\n { "name": "a", "type": "int" },\n { "name": "b", "type": "int" }\n ],\n "returnType": "int",\n "signature": "add(int,int)"\n },\n ...\n]\n'})}),"\n"]}),"\n"]}),"\n",(0,s.jsx)(n.h2,{id:"step-6-test-with-logoscore",children:"Step 6: Test with logoscore"}),"\n",(0,s.jsxs)(n.ol,{children:["\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsxs)(n.p,{children:["Build ",(0,s.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#logoscore",children:"logoscore"}),":"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"nix build 'github:logos-co/logos-logoscore-cli/0.2.0' --out-link ./logos\n"})}),"\n"]}),"\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsxs)(n.p,{children:["Set up the modules directory. ",(0,s.jsx)(n.code,{children:"logoscore"})," expects modules in subdirectories, each with a ",(0,s.jsx)(n.code,{children:"manifest.json"}),". Use the Nix derivation to create an LGX package and install it with the package manager:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"nix build '.#lgx'\nnix build 'github:logos-co/logos-package-manager/0.2.0#cli' --out-link ./pm\n\nmkdir -p modules\n\n./pm/bin/lgpm --modules-dir ./modules install --file result/*.lgx\n"})}),"\n",(0,s.jsx)(n.p,{children:"This extracts the plugin, external libraries, and manifest into the correct directory structure:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{children:"modules/calc_module/\n\u251C\u2500\u2500 calc_module_plugin.dylib # (or .so on Linux)\n\u251C\u2500\u2500 libcalc.dylib # (or .so on Linux)\n\u251C\u2500\u2500 manifest.json # Auto-generated by lgx\n\u2514\u2500\u2500 variant # Platform variant identifier\n"})}),"\n"]}),"\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsx)(n.p,{children:"Start the daemon and call methods:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"./logos/bin/logoscore -D -m ./modules &\nsleep 3\n\n./logos/bin/logoscore load-module calc_module\n\n./logos/bin/logoscore call calc_module add 3 5\n./logos/bin/logoscore call calc_module factorial 5\n./logos/bin/logoscore call calc_module fibonacci 10\n./logos/bin/logoscore call calc_module libVersion\n\n./logos/bin/logoscore stop\n"})}),"\n"]}),"\n"]}),"\n",(0,s.jsx)(n.h2,{id:"step-7-unit-test-the-module",children:"Step 7: Unit-test the module"}),"\n",(0,s.jsxs)(n.p,{children:["Because your module is a plain C++ class, you can unit-test it directly. The ",(0,s.jsx)(n.a,{href:"https://github.com/logos-co/logos-test-framework",children:"Logos Test Framework"})," adds a tiny test runner (",(0,s.jsx)(n.code,{children:"LOGOS_TEST"})," and ",(0,s.jsx)(n.code,{children:"LOGOS_ASSERT_*"}),") and link-time mocking of your C library, so each test can make functions return whatever you want and allows you to assert how your wrapper should behave."]}),"\n",(0,s.jsxs)(n.ol,{children:["\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsxs)(n.p,{children:["Enable tests in ",(0,s.jsx)(n.code,{children:"flake.nix"}),". Add a ",(0,s.jsx)(n.code,{children:"tests"})," block to the ",(0,s.jsx)(n.code,{children:"mkLogosModule"})," call. ",(0,s.jsx)(n.code,{children:"mockCLibs"})," lists the external libraries to replace with link-time mocks:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-nix",children:'{\n description = "Calculator module - wraps libcalc C library for Logos";\n\n inputs = {\n logos-module-builder.url = "github:logos-co/logos-module-builder/0.2.0";\n };\n\n outputs = inputs@{ logos-module-builder, ... }:\n logos-module-builder.lib.mkLogosModule {\n src = ./.;\n configFile = ./metadata.json;\n flakeInputs = inputs;\n tests = {\n dir = ./tests;\n mockCLibs = [ "calc" ];\n };\n };\n}\n'})}),"\n"]}),"\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsxs)(n.p,{children:["Create ",(0,s.jsx)(n.code,{children:"tests/CMakeLists.txt"}),". The test harness configures and builds ",(0,s.jsx)(n.code,{children:"tests/"})," as its own CMake project. It includes ",(0,s.jsx)(n.code,{children:"LogosTest"})," (provided by the framework) and calls ",(0,s.jsx)(n.code,{children:"logos_test()"}),", listing your impl source (",(0,s.jsx)(n.code,{children:"MODULE_SOURCES"}),"), the test sources (",(0,s.jsx)(n.code,{children:"TEST_SOURCES"}),"), and the C-library mock (",(0,s.jsx)(n.code,{children:"MOCK_C_SOURCES"}),"):"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-cmake",children:"cmake_minimum_required(VERSION 3.14)\nproject(CalcModuleTests LANGUAGES CXX)\n\ninclude(LogosTest)\n\nlogos_test(\n NAME calc_module_tests\n MODULE_SOURCES\n ../src/calc_module_impl.cpp\n mocks/calc_module_events_stub.cpp\n TEST_SOURCES\n main.cpp\n test_calc.cpp\n MOCK_C_SOURCES\n mocks/mock_libcalc.cpp\n)\n"})}),"\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.code,{children:"logos_test()"})," automatically puts the repo root and ",(0,s.jsx)(n.code,{children:"../src"})," on the include path, so ",(0,s.jsx)(n.code,{children:'#include "calc_module_impl.h"'})," and ",(0,s.jsx)(n.code,{children:'#include "lib/libcalc.h"'})," both resolve."]}),"\n"]}),"\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsxs)(n.p,{children:["Create ",(0,s.jsx)(n.code,{children:"tests/mocks/calc_module_events_stub.cpp"}),". In a normal build, ",(0,s.jsx)(n.code,{children:"logos-cpp-generator"})," emits ",(0,s.jsx)(n.code,{children:"calc_module_events.cpp"})," containing the body of every ",(0,s.jsx)(n.code,{children:"logos_events:"})," method. The test harness runs the generator in a reduced mode that does not emit that file, so ",(0,s.jsx)(n.code,{children:"libVersionNotify()"})," would fail to link. Provide a no-op stub:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-cpp",children:'// Stub bodies for the impl\'s `logos_events:` methods.\n// In the real build the codegen generates calc_module_events.cpp with\n// bodies that route through LogosModuleContext. The test build skips\n// that codegen, so we provide no-op stubs to satisfy the linker.\n#include "calc_module_impl.h"\n\nvoid CalcModuleImpl::versionReady(const std::string&) {}\n'})}),"\n",(0,s.jsxs)(n.p,{children:["If you add more events to ",(0,s.jsx)(n.code,{children:"logos_events:"}),", add a matching no-op line here. A module with no events does not need this stub."]}),"\n"]}),"\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsxs)(n.p,{children:["Create ",(0,s.jsx)(n.code,{children:"tests/main.cpp"}),":"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-cpp",children:"#include <logos_test.h>\n\nLOGOS_TEST_MAIN()\n"})}),"\n"]}),"\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsxs)(n.p,{children:["Create ",(0,s.jsx)(n.code,{children:"tests/mocks/mock_libcalc.cpp"}),". When building tests, the real ",(0,s.jsx)(n.code,{children:"libcalc"})," is not linked. Instead you provide functions with the same signatures backed by the framework's mock store:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-cpp",children:'// Link-time replacement for libcalc. Each function records the call\n// and returns whatever the active test configured via mockCFunction().\n#include <logos_clib_mock.h>\n\nextern "C" {\n #include "lib/libcalc.h"\n}\n\nextern "C" int calc_add(int a, int b) {\n LOGOS_CMOCK_RECORD("calc_add");\n return LOGOS_CMOCK_RETURN(int, "calc_add");\n}\n\nextern "C" int calc_multiply(int a, int b) {\n LOGOS_CMOCK_RECORD("calc_multiply");\n return LOGOS_CMOCK_RETURN(int, "calc_multiply");\n}\n\nextern "C" int calc_factorial(int n) {\n LOGOS_CMOCK_RECORD("calc_factorial");\n return LOGOS_CMOCK_RETURN(int, "calc_factorial");\n}\n\nextern "C" int calc_fibonacci(int n) {\n LOGOS_CMOCK_RECORD("calc_fibonacci");\n return LOGOS_CMOCK_RETURN(int, "calc_fibonacci");\n}\n\nextern "C" const char* calc_version(void) {\n LOGOS_CMOCK_RECORD("calc_version");\n return LOGOS_CMOCK_RETURN_STRING("calc_version");\n}\n'})}),"\n"]}),"\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsxs)(n.p,{children:["Create ",(0,s.jsx)(n.code,{children:"tests/test_calc.cpp"}),". Each ",(0,s.jsx)(n.code,{children:"LOGOS_TEST"})," constructs your impl directly, configures the C-function return values, calls a method, and asserts. ",(0,s.jsx)(n.code,{children:"LogosTestContext"})," resets the mock store between tests:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-cpp",children:'#include <logos_test.h>\n#include "calc_module_impl.h"\n\nLOGOS_TEST(add_forwards_to_calc_add) {\n auto t = LogosTestContext("calc_module");\n t.mockCFunction("calc_add").returns(8);\n\n CalcModuleImpl calc;\n LOGOS_ASSERT_EQ(calc.add(3, 5), 8);\n LOGOS_ASSERT(t.cFunctionCalled("calc_add"));\n}\n\nLOGOS_TEST(multiply_forwards_to_calc_multiply) {\n auto t = LogosTestContext("calc_module");\n t.mockCFunction("calc_multiply").returns(42);\n\n CalcModuleImpl calc;\n LOGOS_ASSERT_EQ(calc.multiply(6, 7), 42);\n LOGOS_ASSERT(t.cFunctionCalled("calc_multiply"));\n}\n\nLOGOS_TEST(factorial_returns_mocked_value) {\n auto t = LogosTestContext("calc_module");\n t.mockCFunction("calc_factorial").returns(120);\n\n CalcModuleImpl calc;\n LOGOS_ASSERT_EQ(calc.factorial(5), 120);\n}\n\nLOGOS_TEST(libVersion_converts_cstring_to_string) {\n auto t = LogosTestContext("calc_module");\n t.mockCFunction("calc_version").returns("1.0.0");\n\n CalcModuleImpl calc;\n LOGOS_ASSERT_EQ(calc.libVersion(), std::string("1.0.0"));\n}\n'})}),"\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.code,{children:"LOGOS_ASSERT_EQ"}),", ",(0,s.jsx)(n.code,{children:"LOGOS_ASSERT"}),", ",(0,s.jsx)(n.code,{children:"LOGOS_ASSERT_TRUE/FALSE"}),", and ",(0,s.jsx)(n.code,{children:"LOGOS_ASSERT_NE/GT/GE/LT"})," are all available from ",(0,s.jsx)(n.code,{children:"<logos_test.h>"}),"."]}),"\n"]}),"\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsx)(n.p,{children:"Track the new files (Nix only sees git-tracked files), then build and run:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"git add tests/ flake.nix\nnix build '.#unit-tests' -L\n"})}),"\n",(0,s.jsxs)(n.p,{children:["The build compiles your impl against the mock library and the test sources, then runs every ",(0,s.jsx)(n.code,{children:"LOGOS_TEST"}),". A passing run ends with a summary line; a failed assertion prints the file/line and fails the build."]}),"\n"]}),"\n"]}),"\n",(0,s.jsx)(n.h2,{id:"troubleshooting-logos-module-wrapping",children:"Troubleshooting Logos module wrapping"}),"\n",(0,s.jsxs)(n.h3,{id:"a-method-doesnt-appear-in-lm-or-cant-be-called",children:["A method doesn't appear in ",(0,s.jsx)(n.code,{children:"lm"})," or can't be called"]}),"\n",(0,s.jsxs)(n.p,{children:["The generator only exposes ",(0,s.jsx)(n.code,{children:"public"})," methods whose parameter and return types it recognises. Check that the method is in the ",(0,s.jsx)(n.code,{children:"public:"})," section, that all types are supported (notably ",(0,s.jsx)(n.code,{children:"int64_t"})," not ",(0,s.jsx)(n.code,{children:"int"}),", ",(0,s.jsx)(n.code,{children:"std::string"})," not ",(0,s.jsx)(n.code,{children:"char*"})," or ",(0,s.jsx)(n.code,{children:"QString"}),"), and that each signature is on one line."]}),"\n",(0,s.jsx)(n.h3,{id:"build-error-unknown-type-or-generator-cant-parse-a-method",children:"Build error: unknown type or generator can't parse a method"}),"\n",(0,s.jsxs)(n.p,{children:["The ",(0,s.jsx)(n.code,{children:"--from-header"})," parser reads ",(0,s.jsx)(n.code,{children:"*_impl.h"})," as text. Pulling Qt types or unusual templates into a public method signature will confuse it. Keep Qt out of the impl header entirely and move helpers that need exotic types into the ",(0,s.jsx)(n.code,{children:"private:"})," section or the ",(0,s.jsx)(n.code,{children:".cpp"}),"."]}),"\n",(0,s.jsx)(n.h3,{id:"cannot-load-library",children:'"Cannot load library"'}),"\n",(0,s.jsxs)(n.p,{children:["Ensure ",(0,s.jsx)(n.code,{children:"libcalc.so"})," / ",(0,s.jsx)(n.code,{children:"libcalc.dylib"})," is in the same directory as the plugin. The build system sets RPATH to ",(0,s.jsx)(n.code,{children:"$ORIGIN"})," (Linux) or ",(0,s.jsx)(n.code,{children:"@loader_path"})," (macOS) so the plugin looks for libraries in its own directory."]}),"\n",(0,s.jsx)(n.h3,{id:"events-never-reach-subscribers",children:"Events never reach subscribers"}),"\n",(0,s.jsxs)(n.p,{children:["Check that the event is declared in a ",(0,s.jsx)(n.code,{children:"logos_events:"})," section and that the class inherits ",(0,s.jsx)(n.code,{children:"LogosModuleContext"}),". Events only fire when the module is loaded by a host; constructed standalone (for example in unit tests), emission is a safe no-op. The subscriber must use the exact event name string, for example ",(0,s.jsx)(n.code,{children:'logos.onModuleEvent("calc_module", "versionReady")'}),"."]}),"\n",(0,s.jsx)(n.h3,{id:"plugin-not-discovered-by-logoscore",children:"Plugin not discovered by logoscore"}),"\n",(0,s.jsxs)(n.p,{children:["Verify that the module is in a subdirectory of the modules dir (for example ",(0,s.jsx)(n.code,{children:"modules/calc_module/"}),"), that the subdirectory contains a ",(0,s.jsx)(n.code,{children:"manifest.json"})," with a valid ",(0,s.jsx)(n.code,{children:"main"})," object, and that the platform key in ",(0,s.jsx)(n.code,{children:"main"})," matches your OS/arch (for example ",(0,s.jsx)(n.code,{children:"linux-aarch64"}),", ",(0,s.jsx)(n.code,{children:"darwin-arm64"}),")."]}),"\n",(0,s.jsxs)(n.h3,{id:"nix-build-lib-does-nothing-or-fails-silently",children:[(0,s.jsx)(n.code,{children:"nix build .#lib"})," does nothing or fails silently"]}),"\n",(0,s.jsxs)(n.p,{children:["Some shells (notably zsh) treat ",(0,s.jsx)(n.code,{children:"#"})," as a comment character. Always put the flake reference in quotes, like so: ",(0,s.jsx)(n.code,{children:"nix build '.#lib'"}),"."]}),"\n",(0,s.jsx)(n.h3,{id:"first-build-is-slow",children:"First build is slow"}),"\n",(0,s.jsxs)(n.p,{children:["The first ",(0,s.jsx)(n.code,{children:"nix build"})," downloads Qt 6, the Logos C++ SDK, the code generator, and other dependencies. This is a one-time cost \u2014 subsequent builds use the Nix cache and are fast (usually under 30 seconds)."]}),"\n",(0,s.jsx)(n.h3,{id:"symbol-not-found-errors",children:"Symbol not found errors"}),"\n",(0,s.jsxs)(n.p,{children:['If you get "undefined symbol" errors for your C library functions, verify that the ',(0,s.jsx)(n.code,{children:".so"}),"/",(0,s.jsx)(n.code,{children:".dylib"})," is in ",(0,s.jsx)(n.code,{children:"lib/"})," before building, that the header has ",(0,s.jsx)(n.code,{children:'extern "C"'})," guards, and that the symbols are exported: ",(0,s.jsx)(n.code,{children:"nm -D lib/libcalc.so | grep calc"}),"."]})]})}function h(e={}){let{wrapper:n}={...(0,o.R)(),...e.components};return n?(0,s.jsx)(n,{...e,children:(0,s.jsx)(a,{...e})}):a(e)}},8453(e,n,l){l.d(n,{R:()=>c,x:()=>r});var i=l(6540);let s={},o=i.createContext(s);function c(e){let n=i.useContext(o);return i.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function r(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(s):e.components||s:c(e.components),i.createElement(o.Provider,{value:n},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/a239bc72.518f3e1b.js b/assets/js/a239bc72.518f3e1b.js new file mode 100644 index 00000000..8f48985c --- /dev/null +++ b/assets/js/a239bc72.518f3e1b.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunklogos_docs=self.webpackChunklogos_docs||[]).push([["3245"],{4682(e,r,o){o.r(r),o.d(r,{metadata:()=>n,default:()=>d,frontMatter:()=>s,contentTitle:()=>l,toc:()=>f,assets:()=>i});var n=JSON.parse('{"id":"core/reference/logos-cli-reference","title":"Logos CLI Reference","description":"We\'re working on the content.","source":"@site/docs/core/reference/logos-cli-reference.md","sourceDirName":"core/reference","slug":"/core/reference/logos-cli-reference","permalink":"/core/reference/logos-cli-reference","draft":false,"unlisted":false,"editUrl":"https://github.com/logos-co/logos-docs/tree/main/docs/core/reference/logos-cli-reference.md","tags":[],"version":"current","frontMatter":{"title":"Logos CLI Reference","doc_type":"reference","product":"core","topics":"core","authors":null,"owner":"logos","doc_version":1,"slug":"logos-cli-reference"},"sidebar":"coreSidebar","previous":{"title":"LGX package format and bundling reference","permalink":"/core/reference/lgx-package-format-and-bundling-reference"}}'),c=o(4848),t=o(8453);let s={title:"Logos CLI Reference",doc_type:"reference",product:"core",topics:"core",authors:null,owner:"logos",doc_version:1,slug:"logos-cli-reference"},l="Logos CLI Reference",i={},f=[];function a(e){let r={h1:"h1",header:"header",p:"p",...(0,t.R)(),...e.components};return(0,c.jsxs)(c.Fragment,{children:[(0,c.jsx)(r.header,{children:(0,c.jsx)(r.h1,{id:"logos-cli-reference",children:"Logos CLI Reference"})}),"\n",(0,c.jsx)(r.p,{children:"We're working on the content."})]})}function d(e={}){let{wrapper:r}={...(0,t.R)(),...e.components};return r?(0,c.jsx)(r,{...e,children:(0,c.jsx)(a,{...e})}):a(e)}},8453(e,r,o){o.d(r,{R:()=>s,x:()=>l});var n=o(6540);let c={},t=n.createContext(c);function s(e){let r=n.useContext(t);return n.useMemo(function(){return"function"==typeof e?e(r):{...r,...e}},[r,e])}function l(e){let r;return r=e.disableParentContext?"function"==typeof e.components?e.components(c):e.components||c:s(e.components),n.createElement(t.Provider,{value:r},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/a7456010.69111ac6.js b/assets/js/a7456010.69111ac6.js new file mode 100644 index 00000000..171b7910 --- /dev/null +++ b/assets/js/a7456010.69111ac6.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunklogos_docs=self.webpackChunklogos_docs||[]).push([["3056"],{8552(s){s.exports=JSON.parse('{"name":"docusaurus-plugin-content-pages","id":"default"}')}}]); \ No newline at end of file diff --git a/assets/js/a7bd4aaa.858d3a16.js b/assets/js/a7bd4aaa.858d3a16.js new file mode 100644 index 00000000..728fd5ea --- /dev/null +++ b/assets/js/a7bd4aaa.858d3a16.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunklogos_docs=self.webpackChunklogos_docs||[]).push([["5575"],{8146(e,n,s){s.r(n),s.d(n,{default:()=>d});var r=s(4848);s(6540);var o=s(4308),t=s(921),c=s(2831),l=s(4175);function a(e){var n,s;let{version:t}=e;return(0,r.jsxs)(r.Fragment,{children:[(0,r.jsx)(l.A,{version:t.version,tag:(n=t.pluginId,s=t.version,`docs-${n}-${s}`)}),(0,r.jsx)(o.be,{children:t.noIndex&&(0,r.jsx)("meta",{name:"robots",content:"noindex, nofollow"})})]})}function i(e){let{version:n,route:s}=e;return(0,r.jsx)(o.e3,{className:n.className,children:(0,r.jsx)(t.n,{version:n,children:(0,c.v)(s.routes)})})}function d(e){return(0,r.jsxs)(r.Fragment,{children:[(0,r.jsx)(a,{...e}),(0,r.jsx)(i,{...e})]})}}}]); \ No newline at end of file diff --git a/assets/js/a8fa9c9c.ddd4e4eb.js b/assets/js/a8fa9c9c.ddd4e4eb.js new file mode 100644 index 00000000..a27e8ed5 --- /dev/null +++ b/assets/js/a8fa9c9c.ddd4e4eb.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunklogos_docs=self.webpackChunklogos_docs||[]).push([["6620"],{3842(e,t,n){n.r(t),n.d(t,{metadata:()=>l,default:()=>d,frontMatter:()=>a,contentTitle:()=>c,toc:()=>r,assets:()=>i});var l=JSON.parse('{"id":"lez/get-started/connect-wallet-cli-to-lez-testnet","title":"Connect the wallet CLI to the LEZ testnet","description":"Try the wallet CLI against the live LEZ testnet instead of a local sequencer.","source":"@site/docs/lez/get-started/connect-wallet-cli-to-lez-testnet.md","sourceDirName":"lez/get-started","slug":"/lez/get-started/connect-wallet-cli-to-lez-testnet","permalink":"/lez/get-started/connect-wallet-cli-to-lez-testnet","draft":false,"unlisted":false,"editUrl":"https://github.com/logos-co/logos-docs/tree/main/docs/lez/get-started/connect-wallet-cli-to-lez-testnet.md","tags":[],"version":"current","sidebarPosition":3,"frontMatter":{"title":"Connect the wallet CLI to the LEZ testnet","doc_type":"procedure","product":"lez","topics":"lez","steps_layout":"flat","authors":"moudyellaz, kashepavadan","owner":"logos","doc_version":1,"slug":"connect-wallet-cli-to-lez-testnet","sidebar_position":3},"sidebar":"lezSidebar","previous":{"title":"Quickstart for the Logos Execution Zone wallet","permalink":"/lez/get-started/lez-quickstart"},"next":{"title":"Initiate native token transfers on the LEZ with the wallet UI","permalink":"/lez/get-started/initiate-native-token-transfers-on-lez-with-wallet-ui"}}'),s=n(4848),o=n(8453);let a={title:"Connect the wallet CLI to the LEZ testnet",doc_type:"procedure",product:"lez",topics:"lez",steps_layout:"flat",authors:"moudyellaz, kashepavadan",owner:"logos",doc_version:1,slug:"connect-wallet-cli-to-lez-testnet",sidebar_position:3},c="Connect the wallet CLI to the LEZ testnet",i={},r=[{value:"Try the wallet CLI against the live LEZ testnet instead of a local sequencer.",id:"try-the-wallet-cli-against-the-live-lez-testnet-instead-of-a-local-sequencer",level:4},{value:"What to expect",id:"what-to-expect",level:2},{value:"Install the wallet and connect it to the testnet",id:"install-the-wallet-and-connect-it-to-the-testnet",level:2},{value:"Verify the connection",id:"verify-the-connection",level:2}];function h(e){let t={a:"a",code:"code",h1:"h1",h2:"h2",h4:"h4",header:"header",li:"li",ol:"ol",p:"p",pre:"pre",ul:"ul",...(0,o.R)(),...e.components};return(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(t.header,{children:(0,s.jsx)(t.h1,{id:"connect-the-wallet-cli-to-the-lez-testnet",children:"Connect the wallet CLI to the LEZ testnet"})}),"\n",(0,s.jsx)(t.h4,{id:"try-the-wallet-cli-against-the-live-lez-testnet-instead-of-a-local-sequencer",children:"Try the wallet CLI against the live LEZ testnet instead of a local sequencer."}),"\n",(0,s.jsxs)(t.p,{children:["This procedure explains how to install the wallet CLI from the ",(0,s.jsx)(t.a,{href:"https://github.com/logos-blockchain/logos-execution-zone/",children:"LEZ repository"})," and point it at the ",(0,s.jsx)(t.a,{href:"https://docs.logos.co/get-started/glossary#lez",children:"LEZ"})," testnet sequencer. It is intended for developers who have previously run the wallet against a local sequencer and want to test against testnet v0.2 instead."]}),"\n",(0,s.jsx)(t.h2,{id:"what-to-expect",children:"What to expect"}),"\n",(0,s.jsxs)(t.ul,{children:["\n",(0,s.jsx)(t.li,{children:"You can run wallet commands against the live LEZ testnet sequencer."}),"\n",(0,s.jsxs)(t.li,{children:["Your wallet is configured to target ",(0,s.jsx)(t.code,{children:"https://testnet.lez.logos.co"})," as the sequencer address."]}),"\n"]}),"\n",(0,s.jsx)(t.h2,{id:"install-the-wallet-and-connect-it-to-the-testnet",children:"Install the wallet and connect it to the testnet"}),"\n",(0,s.jsxs)(t.ol,{children:["\n",(0,s.jsxs)(t.li,{children:["\n",(0,s.jsx)(t.p,{children:"In the LEZ repository, check out the correct release tag:"}),"\n",(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-sh",children:"git clone https://github.com/logos-blockchain/logos-execution-zone.git\ncd logos-execution-zone\ngit checkout v0.2.0\n"})}),"\n"]}),"\n",(0,s.jsxs)(t.li,{children:["\n",(0,s.jsx)(t.p,{children:"Rename the existing wallet directory (if you have one) to avoid conflicts:"}),"\n",(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-sh",children:"mv ~/.nssa/wallet ~/.nssa/wallet.old 2>/dev/null || true\n"})}),"\n"]}),"\n",(0,s.jsxs)(t.li,{children:["\n",(0,s.jsx)(t.p,{children:"Install the wallet CLI:"}),"\n",(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-sh",children:"cargo install --path lez/wallet --force\n"})}),"\n"]}),"\n",(0,s.jsxs)(t.li,{children:["\n",(0,s.jsx)(t.p,{children:"Set the testnet sequencer address:"}),"\n",(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-sh",children:"wallet config set sequencer_addr https://testnet.lez.logos.co\n"})}),"\n"]}),"\n"]}),"\n",(0,s.jsx)(t.h2,{id:"verify-the-connection",children:"Verify the connection"}),"\n",(0,s.jsxs)(t.ol,{children:["\n",(0,s.jsxs)(t.li,{children:["\n",(0,s.jsx)(t.p,{children:"Run the health check command:"}),"\n",(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-sh",children:"wallet check-health\n"})}),"\n",(0,s.jsx)(t.p,{children:"A successful connection returns:"}),"\n",(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-sh",children:"\u2705All looks good!\n"})}),"\n"]}),"\n"]})]})}function d(e={}){let{wrapper:t}={...(0,o.R)(),...e.components};return t?(0,s.jsx)(t,{...e,children:(0,s.jsx)(h,{...e})}):h(e)}},8453(e,t,n){n.d(t,{R:()=>a,x:()=>c});var l=n(6540);let s={},o=l.createContext(s);function a(e){let t=l.useContext(o);return l.useMemo(function(){return"function"==typeof e?e(t):{...t,...e}},[t,e])}function c(e){let t;return t=e.disableParentContext?"function"==typeof e.components?e.components(s):e.components||s:a(e.components),l.createElement(o.Provider,{value:t},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/a94703ab.81210dcc.js b/assets/js/a94703ab.81210dcc.js new file mode 100644 index 00000000..3727a4b8 --- /dev/null +++ b/assets/js/a94703ab.81210dcc.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunklogos_docs=self.webpackChunklogos_docs||[]).push([["6785"],{1399(e,t,n){n.r(t),n.d(t,{default:()=>ea});var a=n(4848),i=n(6540),r=n(4164),s=n(4308),l=n(8287),o=n(6877),d=n(2473),c=n(568),u=n(8584),m=n(8398);function b(){let{shown:e,scrollToTop:t}=function({threshold:e}){let[t,n]=(0,i.useState)(!1),a=(0,i.useRef)(!1),{startScroll:r,cancelScroll:s}=(0,u.gk)();return(0,u.Mq)(({scrollY:t},i)=>{let r=i?.scrollY;r&&(a.current?a.current=!1:t>=r?(s(),n(!1)):t<e?n(!1):t+window.innerHeight<document.documentElement.scrollHeight&&n(!0))}),(0,m.$)(e=>{e.location.hash&&(a.current=!0,n(!1))}),{shown:t,scrollToTop:()=>r(0)}}({threshold:300});return(0,a.jsx)("button",{"aria-label":(0,c.T)({id:"theme.BackToTopButton.buttonAriaLabel",message:"Scroll back to top",description:"The ARIA label for the back to top button"}),className:(0,r.A)("clean-btn",l.G.common.backToTopButton,"backToTopButton_sjWU",e&&"backToTopButtonShow_xfvO"),type:"button",onClick:t})}var h=n(269),p=n(6347),x=n(3741),j=n(1022),f=n(5153);function _(e){return(0,a.jsx)("svg",{width:"20",height:"20","aria-hidden":"true",...e,children:(0,a.jsxs)("g",{fill:"#7a7a7a",children:[(0,a.jsx)("path",{d:"M9.992 10.023c0 .2-.062.399-.172.547l-4.996 7.492a.982.982 0 01-.828.454H1c-.55 0-1-.453-1-1 0-.2.059-.403.168-.551l4.629-6.942L.168 3.078A.939.939 0 010 2.528c0-.548.45-.997 1-.997h2.996c.352 0 .649.18.828.45L9.82 9.472c.11.148.172.347.172.55zm0 0"}),(0,a.jsx)("path",{d:"M19.98 10.023c0 .2-.058.399-.168.547l-4.996 7.492a.987.987 0 01-.828.454h-3c-.547 0-.996-.453-.996-1 0-.2.059-.403.168-.551l4.625-6.942-4.625-6.945a.939.939 0 01-.168-.55 1 1 0 01.996-.997h3c.348 0 .649.18.828.45l4.996 7.492c.11.148.168.347.168.55zm0 0"})]})})}function g({onClick:e}){return(0,a.jsx)("button",{type:"button",title:(0,c.T)({id:"theme.docs.sidebar.collapseButtonTitle",message:"Collapse sidebar",description:"The title attribute for collapse button of doc sidebar"}),"aria-label":(0,c.T)({id:"theme.docs.sidebar.collapseButtonAriaLabel",message:"Collapse sidebar",description:"The title attribute for collapse button of doc sidebar"}),className:(0,r.A)("button button--secondary button--outline","collapseSidebarButton_PEFL"),onClick:e,children:(0,a.jsx)(_,{className:"collapseSidebarButtonIcon_kv0_"})})}var v=n(3337),A=n(2260);let k=Symbol("EmptyContext"),C=i.createContext(k);function N({children:e}){let[t,n]=(0,i.useState)(null),r=(0,i.useMemo)(()=>({expandedItem:t,setExpandedItem:n}),[t]);return(0,a.jsx)(C.Provider,{value:r,children:e})}var S=n(9510),T=n(9209),y=n(5310),I=n(9863),L=n(8310),w=n(7715);function B({label:e}){return(0,a.jsx)("span",{className:"linkLabel_WmDU",children:e})}function M({item:e,onItemClick:t,activePath:n,level:i,index:s,...d}){let{href:c,label:u,className:m,autoAddBaseUrl:b}=e,h=(0,o.w8)(e,n),p=(0,L.A)(c);return(0,a.jsx)("li",{className:(0,r.A)(l.G.docs.docSidebarItemLink,l.G.docs.docSidebarItemLinkLevel(i),"menu__list-item",m),children:(0,a.jsxs)(y.A,{className:(0,r.A)("menu__link",!p&&"menuExternalLink_NmtK",{"menu__link--active":h}),autoAddBaseUrl:b,"aria-current":h?"page":void 0,to:c,...p&&{onClick:t?()=>t(e):void 0},...d,children:[(0,a.jsx)(B,{label:u}),!p&&(0,a.jsx)(w.A,{})]})},u)}function E({collapsed:e,categoryLabel:t,onClick:n}){return(0,a.jsx)("button",{"aria-label":e?(0,c.T)({id:"theme.DocSidebarItem.expandCategoryAriaLabel",message:"Expand sidebar category '{label}'",description:"The ARIA label to expand the sidebar category"},{label:t}):(0,c.T)({id:"theme.DocSidebarItem.collapseCategoryAriaLabel",message:"Collapse sidebar category '{label}'",description:"The ARIA label to collapse the sidebar category"},{label:t}),"aria-expanded":!e,type:"button",className:"clean-btn menu__caret",onClick:n})}function G({label:e}){return(0,a.jsx)("span",{className:"categoryLinkLabel_W154",children:e})}function H(e){return 0===(0,o.Y)(e.item.items,e.activePath).length?(0,a.jsx)(W,{...e}):(0,a.jsx)(D,{...e})}function W({item:e,...t}){if("string"!=typeof e.href)return null;let{type:n,collapsed:i,collapsible:r,items:s,linkUnlisted:l,...o}=e,d={type:"link",...o};return(0,a.jsx)(M,{item:d,...t})}function D({item:e,onItemClick:t,activePath:n,level:s,index:d,...c}){let u,{items:m,label:b,collapsible:h,className:p,href:x}=e,{docs:{sidebar:{autoCollapseCategories:f}}}=(0,j.p)(),_=(u=(0,I.A)(),(0,i.useMemo)(()=>e.href&&!e.linkUnlisted?e.href:!u&&e.collapsible?(0,o.Nr)(e):void 0,[e,u])),g=(0,o.w8)(e,n),v=(0,T.ys)(x,n),{collapsed:N,setCollapsed:L}=(0,S.u)({initialState:()=>!!h&&!g&&e.collapsed}),{expandedItem:w,setExpandedItem:B}=function(){let e=(0,i.useContext)(C);if(e===k)throw new A.dV("DocSidebarItemsExpandedStateProvider");return e}(),M=(e=!N)=>{B(e?null:d),L(e)};return!function({isActive:e,collapsed:t,updateCollapsed:n,activePath:a}){let r=(0,A.ZC)(e),s=(0,A.ZC)(a);(0,i.useEffect)(()=>{let i=e&&!r,l=e&&r&&a!==s;(i||l)&&t&&n(!1)},[e,r,t,n,a,s])}({isActive:g,collapsed:N,updateCollapsed:M,activePath:n}),(0,i.useEffect)(()=>{h&&null!=w&&w!==d&&f&&L(!0)},[h,w,d,L,f]),(0,a.jsxs)("li",{className:(0,r.A)(l.G.docs.docSidebarItemCategory,l.G.docs.docSidebarItemCategoryLevel(s),"menu__list-item",{"menu__list-item--collapsed":N},p),children:[(0,a.jsxs)("div",{className:(0,r.A)("menu__list-item-collapsible",{"menu__list-item-collapsible--active":v}),children:[(0,a.jsx)(y.A,{className:(0,r.A)("categoryLink_byQd","menu__link",{"menu__link--sublist":h,"menu__link--sublist-caret":!x&&h,"menu__link--active":g}),onClick:n=>{t?.(e),h&&(x?v?(n.preventDefault(),M()):M(!1):(n.preventDefault(),M()))},"aria-current":v?"page":void 0,role:h&&!x?"button":void 0,"aria-expanded":h&&!x?!N:void 0,href:h?_??"#":_,...c,children:(0,a.jsx)(G,{label:b})}),x&&h&&(0,a.jsx)(E,{collapsed:N,categoryLabel:b,onClick:e=>{e.preventDefault(),M()}})]}),(0,a.jsx)(S.N,{lazy:!0,as:"ul",className:"menu__list",collapsed:N,children:(0,a.jsx)(F,{items:m,tabIndex:N?-1:0,onItemClick:t,activePath:n,level:s+1})})]})}function P({item:e,level:t,index:n}){let{value:i,defaultStyle:s,className:o}=e;return(0,a.jsx)("li",{className:(0,r.A)(l.G.docs.docSidebarItemLink,l.G.docs.docSidebarItemLinkLevel(t),s&&["menuHtmlItem_M9Kj","menu__list-item"],o),dangerouslySetInnerHTML:{__html:i}},n)}function R({item:e,...t}){switch(e.type){case"category":return(0,a.jsx)(H,{item:e,...t});case"html":return(0,a.jsx)(P,{item:e,...t});default:return(0,a.jsx)(M,{item:e,...t})}}let F=(0,i.memo)(function({items:e,...t}){let n=(0,o.Y)(e,t.activePath);return(0,a.jsx)(N,{children:n.map((e,n)=>(0,a.jsx)(R,{item:e,index:n,...t},n))})});function U({path:e,sidebar:t,className:n}){let s=function(){let{isActive:e}=(0,v.M)(),[t,n]=(0,i.useState)(e);return(0,u.Mq)(({scrollY:t})=>{e&&n(0===t)},[e]),e&&t}();return(0,a.jsx)("nav",{"aria-label":(0,c.T)({id:"theme.docs.sidebar.navAriaLabel",message:"Docs sidebar",description:"The ARIA label for the sidebar navigation"}),className:(0,r.A)("menu thin-scrollbar","menu_SIkG",s&&"menuWithAnnouncementBar_GW3s",n),children:(0,a.jsx)("ul",{className:(0,r.A)(l.G.docs.docSidebarMenu,"menu__list"),children:(0,a.jsx)(F,{items:t,activePath:e,level:1})})})}let V=i.memo(function({path:e,sidebar:t,onCollapse:n,isHidden:i}){let{navbar:{hideOnScroll:s},docs:{sidebar:{hideable:l}}}=(0,j.p)();return(0,a.jsxs)("div",{className:(0,r.A)("sidebar_njMd",s&&"sidebarWithHideableNavbar_wUlq",i&&"sidebarHidden_VK0M"),children:[s&&(0,a.jsx)(f.A,{tabIndex:-1,className:"sidebarLogo_isFc"}),(0,a.jsx)(U,{path:e,sidebar:t}),l&&(0,a.jsx)(g,{onClick:n})]})});var Y=n(7e3),K=n(9965);let z=({sidebar:e,path:t})=>{let n=(0,K.M)();return(0,a.jsx)("ul",{className:(0,r.A)(l.G.docs.docSidebarMenu,"menu__list"),children:(0,a.jsx)(F,{items:e,activePath:t,onItemClick:e=>{"category"===e.type&&e.href&&n.toggle(),"link"===e.type&&n.toggle()},level:1})})},q=i.memo(function(e){return(0,a.jsx)(Y.GX,{component:z,props:e})});function O(e){let t=(0,x.l)();return(0,a.jsxs)(a.Fragment,{children:[("desktop"===t||"ssr"===t)&&(0,a.jsx)(V,{...e}),"mobile"===t&&(0,a.jsx)(q,{...e})]})}function Q({toggleSidebar:e}){return(0,a.jsx)("div",{className:"expandButton_TmdG",title:(0,c.T)({id:"theme.docs.sidebar.expandButtonTitle",message:"Expand sidebar",description:"The ARIA label and title attribute for expand button of doc sidebar"}),"aria-label":(0,c.T)({id:"theme.docs.sidebar.expandButtonAriaLabel",message:"Expand sidebar",description:"The ARIA label and title attribute for expand button of doc sidebar"}),tabIndex:0,role:"button",onKeyDown:e,onClick:e,children:(0,a.jsx)(_,{className:"expandButtonIcon_i1dp"})})}let Z={docSidebarContainer:"docSidebarContainer_YfHR",docSidebarContainerHidden:"docSidebarContainerHidden_DPk8",sidebarViewport:"sidebarViewport_aRkj"};function J({children:e}){let t=(0,d.t)();return(0,a.jsx)(i.Fragment,{children:e},t?.name??"noSidebar")}function X({sidebar:e,hiddenSidebarContainer:t,setHiddenSidebarContainer:n}){let{pathname:s}=(0,p.zy)(),[o,d]=(0,i.useState)(!1),c=(0,i.useCallback)(()=>{o&&d(!1),!o&&(0,h.O)()&&d(!0),n(e=>!e)},[n,o]);return(0,a.jsx)("aside",{className:(0,r.A)(l.G.docs.docSidebarContainer,Z.docSidebarContainer,t&&Z.docSidebarContainerHidden),onTransitionEnd:e=>{e.currentTarget.classList.contains(Z.docSidebarContainer)&&t&&d(!0)},children:(0,a.jsx)(J,{children:(0,a.jsxs)("div",{className:(0,r.A)(Z.sidebarViewport,o&&Z.sidebarViewportHidden),children:[(0,a.jsx)(O,{sidebar:e,path:s,onCollapse:c,isHidden:o}),o&&(0,a.jsx)(Q,{toggleSidebar:c})]})})})}let $={docMainContainer:"docMainContainer_TBSr",docMainContainerEnhanced:"docMainContainerEnhanced_lQrH",docItemWrapperEnhanced:"docItemWrapperEnhanced_JWYK"};function ee({hiddenSidebarContainer:e,children:t}){let n=(0,d.t)();return(0,a.jsx)("main",{className:(0,r.A)($.docMainContainer,(e||!n)&&$.docMainContainerEnhanced),children:(0,a.jsx)("div",{className:(0,r.A)("container padding-top--md padding-bottom--lg",$.docItemWrapper,e&&$.docItemWrapperEnhanced),children:t})})}function et({children:e}){let t=(0,d.t)(),[n,r]=(0,i.useState)(!1);return(0,a.jsxs)("div",{className:"docsWrapper_hBAB",children:[(0,a.jsx)(b,{}),(0,a.jsxs)("div",{className:"docRoot_UBD9",children:[t&&(0,a.jsx)(X,{sidebar:t.items,hiddenSidebarContainer:n,setHiddenSidebarContainer:r}),(0,a.jsx)(ee,{hiddenSidebarContainer:n,children:e})]})]})}var en=n(955);function ea(e){let t=(0,o.B5)(e);if(!t)return(0,a.jsx)(en.A,{});let{docElement:n,sidebarName:i,sidebarItems:c}=t;return(0,a.jsx)(s.e3,{className:(0,r.A)(l.G.page.docsDocPage),children:(0,a.jsx)(d.V,{name:i,items:c,children:(0,a.jsx)(et,{children:n})})})}},955(e,t,n){n.d(t,{A:()=>l});var a=n(4848);n(6540);var i=n(4164),r=n(568),s=n(2072);function l({className:e}){return(0,a.jsx)("main",{className:(0,i.A)("container margin-vert--xl",e),children:(0,a.jsx)("div",{className:"row",children:(0,a.jsxs)("div",{className:"col col--6 col--offset-3",children:[(0,a.jsx)(s.A,{as:"h1",className:"hero__title",children:(0,a.jsx)(r.A,{id:"theme.NotFound.title",description:"The title of the 404 page",children:"Page Not Found"})}),(0,a.jsx)("p",{children:(0,a.jsx)(r.A,{id:"theme.NotFound.p1",description:"The first paragraph of the 404 page",children:"We could not find what you were looking for."})}),(0,a.jsx)("p",{children:(0,a.jsx)(r.A,{id:"theme.NotFound.p2",description:"The 2nd paragraph of the 404 page",children:"Please contact the owner of the site that linked you to the original URL and let them know their link is broken."})})]})})})}}}]); \ No newline at end of file diff --git a/assets/js/aba21aa0.f6603ff7.js b/assets/js/aba21aa0.f6603ff7.js new file mode 100644 index 00000000..4f209276 --- /dev/null +++ b/assets/js/aba21aa0.f6603ff7.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunklogos_docs=self.webpackChunklogos_docs||[]).push([["3747"],{7093(s){s.exports=JSON.parse('{"name":"docusaurus-plugin-content-docs","id":"default"}')}}]); \ No newline at end of file diff --git a/assets/js/aba21bf3.402178b0.js b/assets/js/aba21bf3.402178b0.js new file mode 100644 index 00000000..1bb23acf --- /dev/null +++ b/assets/js/aba21bf3.402178b0.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunklogos_docs=self.webpackChunklogos_docs||[]).push([["3264"],{771(e,o,l){l.r(o),l.d(o,{metadata:()=>s,default:()=>h,frontMatter:()=>t,contentTitle:()=>i,toc:()=>r,assets:()=>d});var s=JSON.parse('{"id":"core/build-modules/install-and-load-a-module-in-logos-basecamp","title":"Install and load a module in Logos Basecamp","description":"Access features and functionalities through modules in Logos Basecamp.","source":"@site/docs/core/build-modules/install-and-load-a-module-in-logos-basecamp.md","sourceDirName":"core/build-modules","slug":"/core/build-modules/install-and-load-a-module-in-logos-basecamp","permalink":"/core/build-modules/install-and-load-a-module-in-logos-basecamp","draft":false,"unlisted":false,"editUrl":"https://github.com/logos-co/logos-docs/tree/main/docs/core/build-modules/install-and-load-a-module-in-logos-basecamp.md","tags":[],"version":"current","sidebarPosition":5,"frontMatter":{"title":"Install and load a module in Logos Basecamp","doc_type":"procedure","product":"core","topics":"core","steps_layout":"sectioned","authors":"iurimatias, cheny0","owner":"logos","doc_version":1,"slug":"install-and-load-a-module-in-logos-basecamp","sidebar_position":5},"sidebar":"coreSidebar","previous":{"title":"Build a Logos C++ UI module","permalink":"/core/build-modules/build-a-logos-cpp-ui-module"},"next":{"title":"Build a Logos Core module that uses the Service Discovery API","permalink":"/core/build-modules/build-logos-core-module-that-uses-service-discovery-api"}}'),n=l(4848),a=l(8453);let t={title:"Install and load a module in Logos Basecamp",doc_type:"procedure",product:"core",topics:"core",steps_layout:"sectioned",authors:"iurimatias, cheny0",owner:"logos",doc_version:1,slug:"install-and-load-a-module-in-logos-basecamp",sidebar_position:5},i="Install and load a module in Logos Basecamp",d={},r=[{value:"Access features and functionalities through modules in Logos Basecamp.",id:"access-features-and-functionalities-through-modules-in-logos-basecamp",level:4},{value:"What to expect",id:"what-to-expect",level:2},{value:"Step 1: Install the module package",id:"step-1-install-the-module-package",level:2},{value:"Install from the online catalogue",id:"install-from-the-online-catalogue",level:3},{value:"Install from a local <code>.lgx</code> file",id:"install-from-a-local-lgx-file",level:3},{value:"Step 2: Load the module",id:"step-2-load-the-module",level:2},{value:"Troubleshooting",id:"troubleshooting",level:2},{value:"The installed module doesn't appear in the Modules view",id:"the-installed-module-doesnt-appear-in-the-modules-view",level:3},{value:"A QML-based UI App cannot reach the network",id:"a-qml-based-ui-app-cannot-reach-the-network",level:3}];function c(e){let o={a:"a",admonition:"admonition",code:"code",h1:"h1",h2:"h2",h3:"h3",h4:"h4",header:"header",img:"img",li:"li",ol:"ol",p:"p",strong:"strong",ul:"ul",...(0,a.R)(),...e.components};return(0,n.jsxs)(n.Fragment,{children:[(0,n.jsx)(o.header,{children:(0,n.jsx)(o.h1,{id:"install-and-load-a-module-in-logos-basecamp",children:"Install and load a module in Logos Basecamp"})}),"\n",(0,n.jsx)(o.h4,{id:"access-features-and-functionalities-through-modules-in-logos-basecamp",children:"Access features and functionalities through modules in Logos Basecamp."}),"\n",(0,n.jsxs)(o.p,{children:["In Logos ",(0,n.jsx)(o.a,{href:"https://docs.logos.co/get-started/glossary#basecamp",children:"Basecamp"}),", you can install and load modules that provide features like chat, storage, or wallets from the online ",(0,n.jsx)(o.a,{href:"https://docs.logos.co/get-started/glossary#catalogue",children:"catalogue"})," or local ",(0,n.jsx)(o.code,{children:".lgx"})," files."]}),"\n",(0,n.jsxs)(o.p,{children:["There are two types of modules in Logos Basecamp. ",(0,n.jsx)(o.a,{href:"https://docs.logos.co/get-started/glossary#core-module",children:"Core modules"})," are the headless background services that provide capabilities like messaging or storage, while ",(0,n.jsx)(o.a,{href:"https://docs.logos.co/get-started/glossary#ui-module",children:"UI modules"})," are the visual front-ends users interact with."]}),"\n",(0,n.jsx)(o.p,{children:"Before you start, make sure you have:"}),"\n",(0,n.jsxs)(o.ul,{children:["\n",(0,n.jsxs)(o.li,{children:["Logos Basecamp ",(0,n.jsx)(o.a,{href:"/basecamp/get-started/install-logos-basecamp",children:"installed and running"}),"."]}),"\n",(0,n.jsx)(o.li,{children:"Internet access for online catalogue install."}),"\n",(0,n.jsxs)(o.li,{children:[(0,n.jsxs)(o.a,{href:"/core/build-modules/build-and-run-a-logos-core-module",children:["An ",(0,n.jsx)(o.code,{children:".lgx"})," file"]})," for local install."]}),"\n"]}),"\n",(0,n.jsx)(o.admonition,{type:"info",children:(0,n.jsxs)(o.p,{children:["The ",(0,n.jsx)(o.code,{children:".lgx"})," file's archive must contain a variant matching your platform. For example, ",(0,n.jsx)(o.code,{children:"linux-x86_64"}),", ",(0,n.jsx)(o.code,{children:"linux-aarch64"}),", ",(0,n.jsx)(o.code,{children:"darwin-x86_64"}),", or ",(0,n.jsx)(o.code,{children:"darwin-arm64"}),"."]})}),"\n",(0,n.jsx)(o.h2,{id:"what-to-expect",children:"What to expect"}),"\n",(0,n.jsxs)(o.ul,{children:["\n",(0,n.jsxs)(o.li,{children:["You can install a ",(0,n.jsx)(o.a,{href:"https://docs.logos.co/get-started/glossary#module",children:"module"})," from the online catalogue or from a local ",(0,n.jsx)(o.code,{children:".lgx"})," file."]}),"\n",(0,n.jsxs)(o.li,{children:["You can load or unload a module from the ",(0,n.jsx)(o.strong,{children:"Modules"})," view."]}),"\n",(0,n.jsxs)(o.li,{children:["You can read a loading module's status, CPU, and memory in the ",(0,n.jsx)(o.strong,{children:"Modules"})," view."]}),"\n"]}),"\n",(0,n.jsx)(o.h2,{id:"step-1-install-the-module-package",children:"Step 1: Install the module package"}),"\n",(0,n.jsx)(o.p,{children:"When installing a module, Logos Basecamp extracts the variant for your platform into your user modules directory (core modules) or user plugins directory (UI modules)."}),"\n",(0,n.jsx)(o.h3,{id:"install-from-the-online-catalogue",children:"Install from the online catalogue"}),"\n",(0,n.jsxs)(o.ol,{children:["\n",(0,n.jsxs)(o.li,{children:["In the sidebar, click ",(0,n.jsx)(o.strong,{children:"Package Manager"})," ",(0,n.jsx)(o.img,{src:l(4856).A+"",width:"14",height:"17"}),"."]}),"\n",(0,n.jsx)(o.li,{children:"Browse and locate the module you want. You can click the module to view more details in the content area below."}),"\n",(0,n.jsxs)(o.li,{children:["Select the module to install and click ",(0,n.jsx)(o.strong,{children:"Install"}),"."]}),"\n",(0,n.jsxs)(o.li,{children:["At the bottom of the sidebar, click the ",(0,n.jsx)(o.strong,{children:"Modules"})," ",(0,n.jsx)(o.img,{src:l(3183).A+"",width:"42",height:"42"}),". The newly installed module appears under ",(0,n.jsx)(o.strong,{children:"UI Modules"})," or ",(0,n.jsx)(o.strong,{children:"Core Modules"})," depending on its type."]}),"\n"]}),"\n",(0,n.jsxs)(o.h3,{id:"install-from-a-local-lgx-file",children:["Install from a local ",(0,n.jsx)(o.code,{children:".lgx"})," file"]}),"\n",(0,n.jsxs)(o.ol,{children:["\n",(0,n.jsxs)(o.li,{children:["At the bottom of the sidebar, click ",(0,n.jsx)(o.strong,{children:"Modules"})," ",(0,n.jsx)(o.img,{src:l(3183).A+"",width:"42",height:"42"}),"."]}),"\n",(0,n.jsxs)(o.li,{children:["Click ",(0,n.jsx)(o.strong,{children:"Install LGX Package"}),"."]}),"\n",(0,n.jsxs)(o.li,{children:["Select the ",(0,n.jsx)(o.code,{children:".lgx"})," file and confirm."]}),"\n",(0,n.jsxs)(o.li,{children:["The newly installed module appears under ",(0,n.jsx)(o.strong,{children:"UI Modules"})," or ",(0,n.jsx)(o.strong,{children:"Core Modules"})," depending on its type."]}),"\n"]}),"\n",(0,n.jsx)(o.h2,{id:"step-2-load-the-module",children:"Step 2: Load the module"}),"\n",(0,n.jsxs)(o.p,{children:["Loading a module turns an installed module into a running service you can actually use. Each loaded Logos module runs in its own ",(0,n.jsx)(o.code,{children:"logos_host"})," process, so memory usage increases with the number of loaded modules."]}),"\n",(0,n.jsxs)(o.ol,{children:["\n",(0,n.jsxs)(o.li,{children:["At the bottom of the sidebar, click ",(0,n.jsx)(o.strong,{children:"Modules"})," ",(0,n.jsx)(o.img,{src:l(3183).A+"",width:"42",height:"42"}),"."]}),"\n",(0,n.jsxs)(o.li,{children:["Find the module to load under ",(0,n.jsx)(o.strong,{children:"UI Modules"})," or ",(0,n.jsx)(o.strong,{children:"Core Modules"})," depending on its type."]}),"\n",(0,n.jsxs)(o.li,{children:["Click ",(0,n.jsx)(o.strong,{children:"Load"})," next to the module."]}),"\n"]}),"\n",(0,n.jsx)(o.admonition,{type:"info",children:(0,n.jsxs)(o.p,{children:["You can click ",(0,n.jsx)(o.strong,{children:"Unload"})," in the Modules view or close the tab of a module to unload it. Unloading stops the module's host process but not its dependencies, which may still be in use by other modules or UI Apps."]})}),"\n",(0,n.jsx)(o.h2,{id:"troubleshooting",children:"Troubleshooting"}),"\n",(0,n.jsx)(o.h3,{id:"the-installed-module-doesnt-appear-in-the-modules-view",children:"The installed module doesn't appear in the Modules view"}),"\n",(0,n.jsxs)(o.p,{children:["The ",(0,n.jsx)(o.code,{children:".lgx"})," file probably does not contain a variant for your platform, or it was copied to a directory that Logos Basecamp doesn't scan. Confirm the archive includes a variant matching your platform (",(0,n.jsx)(o.code,{children:"linux-x86_64"}),", ",(0,n.jsx)(o.code,{children:"linux-aarch64"}),", ",(0,n.jsx)(o.code,{children:"darwin-x86_64"}),", or ",(0,n.jsx)(o.code,{children:"darwin-arm64"}),"), then reinstall using ",(0,n.jsx)(o.strong,{children:"Install LGX Package"})," in the ",(0,n.jsx)(o.strong,{children:"Modules"})," view rather than copying files manually so the package manager copies the files to the correct user modules directory."]}),"\n",(0,n.jsx)(o.h3,{id:"a-qml-based-ui-app-cannot-reach-the-network",children:"A QML-based UI App cannot reach the network"}),"\n",(0,n.jsxs)(o.p,{children:["By design, QML UI Apps run inside a sandboxed QML engine with a deny-all QNetworkAccessManager and a URL interceptor that whitelists only the app's own directory. To make network calls, route them through a Logos Module, which runs in its own unsandboxed process. Use ",(0,n.jsx)(o.code,{children:'logos.callModule("<module>", "<method>", [args])'})," from QML."]})]})}function h(e={}){let{wrapper:o}={...(0,a.R)(),...e.components};return o?(0,n.jsx)(o,{...e,children:(0,n.jsx)(c,{...e})}):c(e)}},3183(e,o,l){l.d(o,{A:()=>s});let s="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACoAAAAqCAYAAADFw8lbAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAOdEVYdFNvZnR3YXJlAEZpZ21hnrGWYwAAA8lJREFUeAHVWTtP40AQHjsuqMBUFLx8DXRcUoCuQZcUdCDuJKiggJbm7h9w/ANSUN+dBFJKCjqQMNBBAYKOBoeXRIHOoQIpj5tvtY6cy9Nrh+Q+afF6vcGfZ2dmZ2Y1UoRlWWY+n49rmjZfKpUsbnFd102+mnjO4y73He663I642ff39zYpQqOAGBoaSoIcd1c8Ui2/jMnzZbdYLKYfHh4uAv221YkgyJd1bkmKBrZhGKsOo5XJTYliiQuFwjpL7zu1ASzlzVgstsF83YbzGj1kkhaTPIQOUhvBZB0mm2okXb3eA+YYZ5Ln7SYJ4B0QyODgYLzenJoSlSQPgxpLBICnSNUytCqi77XcDeCykSX+VYMKotJwzjtIUkDqbMJvYBU6Kq3bog5D6uy6f6wsUSw57zQ31F1IebtZWaJM8id1H8pSFRKFlTPRc+pOCKka6LE+fKMIMDAwQDMzM/T6+koHBwfkui5FgC/cbCHR4eHhP2F8Zk9PD01PT9Pk5CS9vb0Jon19fXR1dUUnJydhCcNdfdBksHFIipiamhIkgdPTUzo7OxN9kMZ4LpcTZC8vLykEUrHe3t4VUoiIRkdHaWFhgSYmJujx8ZEymQxdX1/DKEW7vb0V5CBZEMY8kH5+fiYF5GD1n0kBs7OzQidBZnt7W5Co+u88tre3J0iDMPRXERaMSVk3QQSSgnRrLS/G8UEgCd1VBe9UHw3+gzSCVABiWG6oAAh5hAHcj4yMCGlCqujjo1QAQzfCRkhPT0+0tbUlSEAX19bWxDgkuL+/XzYuEA0B06CIAOlms1laXFyEBGhnZ0e4qahgyGwxkrgTOuuRi5Ikw9U5I4xk+2gzHE7F9UBpqx/Qy7GxsabzYGStzGsAV+dlz5ICsJcDsPi5uTkyzWrtwdjy8jItLS2Je1i/Io5Cb6GetcNXwjWNj48LY4LbwvYKHB8fl61fESlNph83YQzKv036AXIgH9KwXA7z+kX0xFLd5EvoUM/bJhFNwYfCx4YFe6Vfd3d3qx7RJIVY/naCVzqB9FmkIjIvsan7YHs5vj8L3aAuA4poXj/mdV5eXhyOTfu5+4m6A2kOaDLeTUVez1/wA8k/dRjgAC7+sQqiqEygqiYLrh2Br7JXwaGqmoeaT6fI4p387q+1yo81y44870KSdeidIElCkjVjj/+/kAtINUhwN03tQ1pW7pxGk1o+bJBFNNSnkhQNbG4brR7pBD6+kdVoHDzMqxzf8G9+c3c36JlTYKJ+yBgBDbUBU2a05QMxZA8IzGXMu8u+8aLZ6Uc9/AX8rgqqi230bgAAAABJRU5ErkJggg=="},4856(e,o,l){l.d(o,{A:()=>s});let s="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAARCAYAAADtyJ2fAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAOdEVYdFNvZnR3YXJlAEZpZ21hnrGWYwAAAWxJREFUeAGdU8txgzAQRUxOPrkEuwN3YLuEVGDraPvgpAJDBXEOfIYLpIN0gFMB7gBK4AYHPnmPCA9D5EzGO7NIWr23elotwtCY53krDCd4Brd3u102xojhIgzDaVEUJLy0bZsLIXLMZxgtkG0t0ff9Y13Xllq+TyaTc1mWUyRgbMPTMbcPh0PUESkLgZCZ4Z/I/jqWBgz3QuBWGK/APAvXddsugxARCNK4Y+oaMaYLnm6qOGVskSQNgmBhaIoFUkIS787YEz9YfJimSfKpqqoEwIjVbJpmivgbJULRBTEq2sKX/YkGZEb7/X7OAhAITwFOQF5glNhfwy893hzLQtUsANfGzxt+obpzJh3jfhHV6RnI9FRKmeswWuJ/rCMi80a91Z+mWnHZEXlxTlgMbIS6BIzBY2BidQV5aznHcSwMRwSp4Iw5nyAH+Mq2U71ro/LnTuU486A3b8YnYu8OCyXu3GXWy8JS6n6rh+0b/TzLdgZrjqwAAAAASUVORK5CYII="},8453(e,o,l){l.d(o,{R:()=>t,x:()=>i});var s=l(6540);let n={},a=s.createContext(n);function t(e){let o=s.useContext(a);return s.useMemo(function(){return"function"==typeof e?e(o):{...o,...e}},[o,e])}function i(e){let o;return o=e.disableParentContext?"function"==typeof e.components?e.components(n):e.components||n:t(e.components),s.createElement(a.Provider,{value:o},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/b00cc44c.3939f4f0.js b/assets/js/b00cc44c.3939f4f0.js new file mode 100644 index 00000000..5ca52a69 --- /dev/null +++ b/assets/js/b00cc44c.3939f4f0.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunklogos_docs=self.webpackChunklogos_docs||[]).push([["8881"],{744(e,t,o){o.r(t),o.d(t,{metadata:()=>s,default:()=>h,frontMatter:()=>a,contentTitle:()=>r,toc:()=>c,assets:()=>d});var s=JSON.parse('{"id":"blockchain/concepts/about-the-blend-network","title":"About the Blend Network","description":"Understand how the Blend Network hides the link between a block proposer and their proposal.","source":"@site/docs/blockchain/concepts/about-the-blend-network.md","sourceDirName":"blockchain/concepts","slug":"/blockchain/concepts/about-the-blend-network","permalink":"/blockchain/concepts/about-the-blend-network","draft":false,"unlisted":false,"editUrl":"https://github.com/logos-co/logos-docs/tree/main/docs/blockchain/concepts/about-the-blend-network.md","tags":[],"version":"current","sidebarPosition":4,"frontMatter":{"title":"About the Blend Network","doc_type":"concept","product":"blockchain","topics":"Blend Network","authors":"kashepavadan","owner":"logos","doc_version":1,"slug":"about-the-blend-network","sidebar_position":4},"sidebar":"blockchainSidebar","previous":{"title":"About Mantle","permalink":"/blockchain/concepts/about-mantle"},"next":{"title":"About Zones","permalink":"/blockchain/concepts/about-zones"}}'),n=o(4848),i=o(8453);let a={title:"About the Blend Network",doc_type:"concept",product:"blockchain",topics:"Blend Network",authors:"kashepavadan",owner:"logos",doc_version:1,slug:"about-the-blend-network",sidebar_position:4},r="About the Blend Network",d={},c=[{value:"Understand how the Blend Network hides the link between a block proposer and their proposal.",id:"understand-how-the-blend-network-hides-the-link-between-a-block-proposer-and-their-proposal",level:4},{value:"The basics",id:"the-basics",level:2},{value:"Objectives",id:"objectives",level:2},{value:"How the Blend Protocol works",id:"how-the-blend-protocol-works",level:2},{value:"Cover traffic",id:"cover-traffic",level:2},{value:"Participating in the Blend Network",id:"participating-in-the-blend-network",level:2},{value:"Service Declaration Protocol",id:"service-declaration-protocol",level:3},{value:"Service Reward Distribution Protocol",id:"service-reward-distribution-protocol",level:3}];function l(e){let t={a:"a",em:"em",h1:"h1",h2:"h2",h3:"h3",h4:"h4",header:"header",img:"img",li:"li",ol:"ol",p:"p",ul:"ul",...(0,i.R)(),...e.components};return(0,n.jsxs)(n.Fragment,{children:[(0,n.jsx)(t.header,{children:(0,n.jsx)(t.h1,{id:"about-the-blend-network",children:"About the Blend Network"})}),"\n",(0,n.jsx)(t.h4,{id:"understand-how-the-blend-network-hides-the-link-between-a-block-proposer-and-their-proposal",children:"Understand how the Blend Network hides the link between a block proposer and their proposal."}),"\n",(0,n.jsxs)(t.p,{children:["The ",(0,n.jsx)(t.a,{href:"https://docs.logos.co/get-started/glossary#blend-network",children:"Blend Network"})," adds a layer of anonymity for block proposers on top of ",(0,n.jsx)(t.a,{href:"/blockchain/concepts/about-cryptarchia",children:"Cryptarchia"}),", the Logos ",(0,n.jsx)(t.a,{href:"https://docs.logos.co/get-started/glossary#private-proof-of-stake",children:"Private Proof of Stake"})," consensus protocol. ",(0,n.jsx)(t.a,{href:"https://docs.logos.co/get-started/glossary#cryptarchia",children:"Cryptarchia"})," uses a private leadership election as a first line of defence against deanonymisation, but preventing adversaries from learning about proposers through network monitoring needs stronger obfuscation. The ",(0,n.jsx)(t.a,{href:"https://docs.logos.co/get-started/glossary#blend-protocol",children:"Blend Protocol"})," provides it: an anonymous broadcasting protocol that makes it hard to link a proposal to its proposer through network analysis. The Blend Network is the service made up of nodes that opt in to run the Blend Protocol, and those nodes are rewarded for participating."]}),"\n",(0,n.jsx)(t.h2,{id:"the-basics",children:"The basics"}),"\n",(0,n.jsxs)(t.ul,{children:["\n",(0,n.jsx)(t.li,{children:"The Blend Network adds anonymity for block proposers on top of Cryptarchia's Private Proof of Stake consensus."}),"\n",(0,n.jsx)(t.li,{children:"Proposals are wrapped in multiple layers of encryption and routed through random paths of nodes, each adding a random delay before forwarding."}),"\n",(0,n.jsxs)(t.li,{children:["Nodes must explicitly opt in through the ",(0,n.jsx)(t.a,{href:"https://docs.logos.co/get-started/glossary#service-declaration-protocol",children:"Service Declaration Protocol"})," and prove ownership of a ",(0,n.jsx)(t.a,{href:"https://docs.logos.co/get-started/glossary#note",children:"note"})," with a minimum stake."]}),"\n"]}),"\n",(0,n.jsx)(t.h2,{id:"objectives",children:"Objectives"}),"\n",(0,n.jsx)(t.p,{children:"The main objective of the Logos Blend Network is to reduce the probability of linking a block proposal with its proposer, which also translates to increasing the difficulty of learning the proposer\u2019s relative stake. At the same time, the Blend Protocol was designed to minimise bandwidth usage on the network compared to general-use mixnets, and to maximise decentralisation by involving all nodes in the obfuscation process. By doing so, the Blend Network increases the cost of attacking the network to deanonymise a block proposal without straining the network with high bandwidth usage."}),"\n",(0,n.jsxs)(t.p,{children:["The Blend Network supports the privacy of the ",(0,n.jsx)(t.a,{href:"https://docs.logos.co/get-started/glossary#logos-blockchain",children:"Logos Blockchain"})," as a whole, by providing additional anonymity to block proposers, protecting against ",(0,n.jsx)(t.a,{href:"https://gwern.net/doc/cs/security/2009-syverson.pdf",children:"adversaries"})," with both a complete (global) and partial (local) view of the Logos Blockchain. The anonymity provided by the Blend Network also substantially improves the privacy guarantees of Cryptarchia by making it even harder to learn a proposer\u2019s relative stake - which would allow an attacker to estimate the likelihood of that node winning the leadership election."]}),"\n",(0,n.jsx)(t.h2,{id:"how-the-blend-protocol-works",children:"How the Blend Protocol works"}),"\n",(0,n.jsx)(t.p,{children:"The Blend Network makes it difficult to link a block proposer to their proposal by having the message travel between several nodes before being revealed. Blend nodes must maintain a minimum number of connections with other nodes, and cannot exceed a maximum frequency of messages they can send - putting an upper bound on bandwidth usage."}),"\n",(0,n.jsx)(t.p,{children:"While dedicated participation in the Blend Network is reserved for declared Blend nodes, proposals can also be sent to it from regular Logos nodes. The following steps illustrate the process which a proposal message goes through before being broadcast and included in the chain."}),"\n",(0,n.jsxs)(t.ol,{children:["\n",(0,n.jsxs)(t.li,{children:["\n",(0,n.jsx)(t.p,{children:"The message sender selects a random path of nodes along which it will relay its message to the receiver, covering the message in layers of encryption for every node on the path."}),"\n"]}),"\n",(0,n.jsxs)(t.li,{children:["\n",(0,n.jsxs)(t.p,{children:["The sender sends the layered message to every ",(0,n.jsx)(t.a,{href:"https://docs.logos.co/get-started/glossary#blend-node",children:"Blend node"})," with which it maintains a peer-to-peer connection. This process, known as dissemination, is shown below."]}),"\n",(0,n.jsx)(t.p,{children:(0,n.jsx)(t.img,{alt:"The Blend Network using dissemination to relay proposal messages. Peer-to-peer relaying of messages is not shown for simplicity.",src:o(775).A+"",width:"726",height:"490"})}),"\n"]}),"\n",(0,n.jsxs)(t.li,{children:["\n",(0,n.jsx)(t.p,{children:"When a Blend node receives a message, it checks that the message is unique and has not yet been seen, relaying it to its own peers. This ensures that every message is ultimately disseminated to the entire network."}),"\n"]}),"\n",(0,n.jsxs)(t.li,{children:["\n",(0,n.jsx)(t.p,{children:"A Blend node that receives a message will also attempt to decrypt the message. If it is able to decrypt the outer layer of a message it receives, it relays this decrypted message (after a randomised delay) to its peers for the next node in the path to receive and decrypt."}),"\n"]}),"\n",(0,n.jsxs)(t.li,{children:["\n",(0,n.jsx)(t.p,{children:"When the receiver finally receives its message, it decrypts this message and is able to retrieve the original payload. This payload is then broadcast to the Logos Blockchain as a block proposal."}),"\n"]}),"\n"]}),"\n",(0,n.jsx)(t.p,{children:'This process of hiding messages under several layers of encryption and randomly delaying their propagation at each stage of dissemination is known as message blending. When a message is disseminated to all nodes, observers cannot determine which node was the intended receiver, even if they can identify the sender. Due to the layered encryption, observers also cannot determine which "hop" of the relay process the message is currently on. This decryption at each node in the path transforms messages, so the incoming and outgoing messages cannot be linked together based on their content.'}),"\n",(0,n.jsx)(t.p,{children:"Random delays are the other component of message blending. If messages are rare (as in a low bandwidth network like the Blend Network), then an observer may be able to link even encrypted messages if they are disseminated in close succession. To prevent this, every message is assigned a random delay by the node processing it, with each message being released in the order it is received once its delay period has passed. This ensures that incoming and outgoing messages cannot be linked together based on their timing."}),"\n",(0,n.jsx)(t.h2,{id:"cover-traffic",children:"Cover traffic"}),"\n",(0,n.jsx)(t.p,{children:'An important way that the Blend Network obscures network patterns is by producing indistinguishable messages within a "crowded" network. To increase this effect in an environment where proposals are relatively rare, the Blend Network also produces artificial cover messages. Cover messages do not contain any meaningful payload and are generated by Blend nodes to increase network noise and to blend in with data messages that contain real proposals.'}),"\n",(0,n.jsxs)(t.p,{children:["Cover messages mimic the behaviour of data messages, in that they are disseminated and processed by Blend nodes in the same manner. Blend nodes repeatedly encrypt random payload data to generate a cover message, which is then relayed to the next node in its selected path via dissemination. At each step in the transmission process, intended nodes decrypt, randomly delay, and disseminate cover messages without any indication that they may not be genuine. In fact, encrypted data and cover messages are completely indistinguishable even to adversary-controlled Blend nodes (a type of local observer). The hiding effect provided by ",(0,n.jsx)(t.a,{href:"https://docs.logos.co/get-started/glossary#cover-traffic",children:"cover traffic"})," is illustrated in the diagram below."]}),"\n",(0,n.jsx)(t.p,{children:(0,n.jsx)(t.img,{alt:"Disseminated Blend Network cover traffic (dotted lines) obscuring a data message (solid lines).",src:o(8895).A+"",width:"922",height:"570"})}),"\n",(0,n.jsx)(t.p,{children:(0,n.jsx)(t.em,{children:"Disseminated Blend Network cover traffic (dotted lines) obscuring a data message (solid lines)."})}),"\n",(0,n.jsx)(t.p,{children:"To avoid the network getting congested with too much message traffic, the Blend Network places a quota limiting the number of cover messages produced by each node, as well as the number of nodes in the path of each cover and data message. Adherence to this quota is verified via a zero knowledge proof."}),"\n",(0,n.jsx)(t.h2,{id:"participating-in-the-blend-network",children:"Participating in the Blend Network"}),"\n",(0,n.jsxs)(t.p,{children:["Participation in the Blend Network is more complex than running a ",(0,n.jsx)(t.a,{href:"https://docs.logos.co/get-started/glossary#logos-node",children:"Logos node"}),". The protocol relies on an agreed-upon sets of active participants to ensure its correct operation, as Blend nodes must keep track of connections to other Blend nodes for message relaying. As a result, it is not possible to allow dynamic participation in the Blend Network as it exists for ",(0,n.jsx)(t.a,{href:"https://docs.logos.co/get-started/glossary#bedrock",children:"Bedrock"}),". Participation in the Blend Network is therefore not incumbent upon a node unless it opts in."]}),"\n",(0,n.jsx)(t.h3,{id:"service-declaration-protocol",children:"Service Declaration Protocol"}),"\n",(0,n.jsxs)(t.p,{children:["Logos nodes that choose to participate in the Blend Network explicitly declare their intent by using the Service Declaration Protocol (",(0,n.jsx)(t.a,{href:"https://docs.logos.co/get-started/glossary#sdp",children:"SDP"}),"). The goal of the SDP is to create a single repository of identifiers to determine which nodes have opted into the Blend Network at a given time."]}),"\n",(0,n.jsx)(t.p,{children:"The SDP provides a standardised mechanism for Logos nodes to declare their participation, demonstrate activity, and withdraw when desired. It operates around a schedule measured by consensus epochs. This protocol creates a single repository of identifiers used to establish secure communication between nodes and manage service participation."}),"\n",(0,n.jsxs)(t.p,{children:["The SDP consists of three basic steps, each of which represents a type of message sent by a participating node to ",(0,n.jsx)(t.a,{href:"/blockchain/concepts/about-mantle",children:"Mantle"}),":"]}),"\n",(0,n.jsxs)(t.ul,{children:["\n",(0,n.jsx)(t.li,{children:"Declare: A node elects to participate in a given service."}),"\n",(0,n.jsx)(t.li,{children:'Active: To continue participating, the node must regularly send an "active" message. Nodes that have not sent an active message for a prolonged period of time have their declarations withdrawn.'}),"\n",(0,n.jsx)(t.li,{children:"Withdraw: A node withdraws its declaration and stops providing a service."}),"\n"]}),"\n",(0,n.jsxs)(t.p,{children:["To submit a service declaration, a node must prove that it owns a note with a service-dependent minimum stake value. This note is locked for the duration of the declaration, but remains eligible for consensus leadership. The stake requirement makes service declarations sufficiently expensive to avoid spamming or Sybil attacks. Nodes participating in services are assigned addresses (known as locators) based on the ",(0,n.jsx)(t.a,{href:"https://docs.libp2p.io/concepts/fundamentals/addressing/",children:"Multiaddr scheme"}),", allowing them to communicate securely while engaging in a service."]}),"\n",(0,n.jsx)(t.h3,{id:"service-reward-distribution-protocol",children:"Service Reward Distribution Protocol"}),"\n",(0,n.jsxs)(t.p,{children:["The ",(0,n.jsx)(t.a,{href:"https://docs.logos.co/get-started/glossary#service-reward-distribution-protocol",children:"Service Reward Distribution Protocol"})," (",(0,n.jsx)(t.a,{href:"https://docs.logos.co/get-started/glossary#srdp",children:"SRDP"}),") enables deterministic, efficient, and verifiable reward distribution to nodes based on their participation in Bedrock Services. Like the SDP, it also operates around epochs. The SRDP process unfolds over three key phases, distributing rewards based on node activity from previous epochs. These phases are:"]}),"\n",(0,n.jsxs)(t.ul,{children:["\n",(0,n.jsxs)(t.li,{children:["Activity tracking: Nodes participating in services submit active messages to attest to their participation in the previous ",(0,n.jsx)(t.a,{href:"https://docs.logos.co/get-started/glossary#epoch",children:"epoch"}),"."]}),"\n",(0,n.jsx)(t.li,{children:"Reward calculation: At the end of the epoch, the system calculates rewards for nodes that participated in the previous epoch."}),"\n",(0,n.jsxs)(t.li,{children:["Reward distribution: Starting immediately after the epoch when activity messages are submitted, rewards are gradually distributed to active service nodes. Each block includes one ",(0,n.jsx)(t.a,{href:"https://docs.logos.co/get-started/glossary#mantle",children:"Mantle"})," transaction that can distribute rewards to up to four nodes."]}),"\n"]}),"\n",(0,n.jsx)(t.p,{children:"The selection of nodes receiving rewards in each block follows a deterministic, pseudo-random process based on the Cryptarchia epoch randomness from the start of the epoch. This approach ensures fairness and prevents manipulation of the distribution order."})]})}function h(e={}){let{wrapper:t}={...(0,i.R)(),...e.components};return t?(0,n.jsx)(t,{...e,children:(0,n.jsx)(l,{...e})}):l(e)}},8895(e,t,o){o.d(t,{A:()=>s});let s=o.p+"assets/images/cover-traffic-hiding-effect-06146ae7dd2c61d56938f3727631cdca.png"},775(e,t,o){o.d(t,{A:()=>s});let s=o.p+"assets/images/dissemination-63373bcaafe873d19e7eace3cb8d25ea.png"},8453(e,t,o){o.d(t,{R:()=>a,x:()=>r});var s=o(6540);let n={},i=s.createContext(n);function a(e){let t=s.useContext(i);return s.useMemo(function(){return"function"==typeof e?e(t):{...t,...e}},[t,e])}function r(e){let t;return t=e.disableParentContext?"function"==typeof e.components?e.components(n):e.components||n:a(e.components),s.createElement(i.Provider,{value:t},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/b4479ab8.141d455e.js b/assets/js/b4479ab8.141d455e.js new file mode 100644 index 00000000..5b7c6ea8 --- /dev/null +++ b/assets/js/b4479ab8.141d455e.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunklogos_docs=self.webpackChunklogos_docs||[]).push([["6663"],{3064(e,n,t){t.r(n),t.d(n,{metadata:()=>s,default:()=>h,frontMatter:()=>a,contentTitle:()=>r,toc:()=>l,assets:()=>c});var s=JSON.parse('{"id":"lez/transfer-tokens/transfer-native-tokens-on-the-logos-execution-zone","title":"Transfer native tokens on the Logos Execution Zone","description":"Use the wallet CLI to send native tokens to public and private accounts.","source":"@site/docs/lez/transfer-tokens/transfer-native-tokens-on-the-logos-execution-zone.md","sourceDirName":"lez/transfer-tokens","slug":"/lez/transfer-tokens/transfer-native-tokens-on-the-logos-execution-zone","permalink":"/lez/transfer-tokens/transfer-native-tokens-on-the-logos-execution-zone","draft":false,"unlisted":false,"editUrl":"https://github.com/logos-co/logos-docs/tree/main/docs/lez/transfer-tokens/transfer-native-tokens-on-the-logos-execution-zone.md","tags":[],"version":"current","sidebarPosition":1,"frontMatter":{"title":"Transfer native tokens on the Logos Execution Zone","doc_type":"procedure","product":"LEZ","topics":"LEZ","steps_layout":"flat","authors":"cheny0, jorge-campo, moudyellaz","owner":"logos","doc_version":1,"slug":"transfer-native-tokens-on-the-logos-execution-zone","sidebar_position":1},"sidebar":"lezSidebar","previous":{"title":"Initiate native token transfers on the LEZ with the wallet UI","permalink":"/lez/get-started/initiate-native-token-transfers-on-lez-with-wallet-ui"},"next":{"title":"Create and transfer custom tokens on the Logos Execution Zone","permalink":"/lez/transfer-tokens/create-and-transfer-custom-tokens-on-the-logos-execution-zone"}}'),o=t(4848),i=t(8453);let a={title:"Transfer native tokens on the Logos Execution Zone",doc_type:"procedure",product:"LEZ",topics:"LEZ",steps_layout:"flat",authors:"cheny0, jorge-campo, moudyellaz",owner:"logos",doc_version:1,slug:"transfer-native-tokens-on-the-logos-execution-zone",sidebar_position:1},r="Transfer native tokens on the Logos Execution Zone",c={},l=[{value:"Use the wallet CLI to send native tokens to public and private accounts.",id:"use-the-wallet-cli-to-send-native-tokens-to-public-and-private-accounts",level:4},{value:"What to expect",id:"what-to-expect",level:2},{value:"Step 1: Create and fund a sender account",id:"step-1-create-and-fund-a-sender-account",level:2},{value:"Step 2: Transfer tokens",id:"step-2-transfer-tokens",level:2},{value:"Method 1: Transfer tokens using the recipient account ID",id:"method-1-transfer-tokens-using-the-recipient-account-id",level:3},{value:"Method 2: Transfer tokens using the recipient account <code>npk</code> and <code>vpk</code>",id:"method-2-transfer-tokens-using-the-recipient-account-npk-and-vpk",level:3},{value:"Step 3: Verify the transfer",id:"step-3-verify-the-transfer",level:2},{value:"Troubleshooting",id:"troubleshooting",level:2},{value:"Symptom",id:"symptom",level:3},{value:"Explanation",id:"explanation",level:3}];function d(e){let n={a:"a",admonition:"admonition",code:"code",h1:"h1",h2:"h2",h3:"h3",h4:"h4",header:"header",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,i.R)(),...e.components};return(0,o.jsxs)(o.Fragment,{children:[(0,o.jsx)(n.header,{children:(0,o.jsx)(n.h1,{id:"transfer-native-tokens-on-the-logos-execution-zone",children:"Transfer native tokens on the Logos Execution Zone"})}),"\n",(0,o.jsx)(n.h4,{id:"use-the-wallet-cli-to-send-native-tokens-to-public-and-private-accounts",children:"Use the wallet CLI to send native tokens to public and private accounts."}),"\n",(0,o.jsx)(n.admonition,{type:"warning",children:(0,o.jsx)(n.p,{children:"This page should be accurate for the specific version referenced in this doc, but it may not have been run end-to-end as written. Expect minor gaps (for example, missing environment details) and be prepared to troubleshoot. We are actively working to complete and verify this content."})}),"\n",(0,o.jsx)(n.admonition,{type:"info",children:(0,o.jsxs)(n.ul,{children:["\n",(0,o.jsxs)(n.li,{children:[(0,o.jsx)(n.strong,{children:"Permissions"}),": No special permissions required."]}),"\n",(0,o.jsxs)(n.li,{children:[(0,o.jsx)(n.strong,{children:"Product"}),": ",(0,o.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#logos-execution-zone",children:"Logos Execution Zone"})," wallet CLI."]}),"\n"]})}),"\n",(0,o.jsxs)(n.p,{children:["The Logos Execution Zone (",(0,o.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#lez",children:"LEZ"}),") is a programmable blockchain that cleanly separates public and private state while keeping them fully interoperable. It's a component of the ",(0,o.jsx)(n.a,{href:"https://github.com/logos-co/logos-docs/blob/main/README.md",children:"Logos project"}),". You can use the wallet CLI to invoke LEZ's authenticated-transfers ",(0,o.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#program",children:"program"})," to transfer native tokens between public and private accounts."]}),"\n",(0,o.jsx)(n.p,{children:"On LEZ, public and private accounts differ in where their state lives and how transfers update that state."}),"\n",(0,o.jsxs)(n.ul,{children:["\n",(0,o.jsxs)(n.li,{children:["Public accounts","\n",(0,o.jsxs)(n.ul,{children:["\n",(0,o.jsx)(n.li,{children:"Live on-chain."}),"\n",(0,o.jsxs)(n.li,{children:["Identified by a 32-byte ",(0,o.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#account",children:"account"})," ID derived from the public key"]}),"\n",(0,o.jsx)(n.li,{children:"The private key signs transactions and authorises program executions."}),"\n"]}),"\n"]}),"\n",(0,o.jsxs)(n.li,{children:["Transfers between public accounts","\n",(0,o.jsxs)(n.ul,{children:["\n",(0,o.jsx)(n.li,{children:"Validators execute the authenticated-transfers program transparently."}),"\n",(0,o.jsxs)(n.li,{children:["The public state (the on-chain map from ",(0,o.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#public-account",children:"public account"})," IDs to their account states) is updated in place by debiting the sender's public account and crediting the recipient's public account."]}),"\n"]}),"\n"]}),"\n",(0,o.jsxs)(n.li,{children:["Private accounts","\n",(0,o.jsxs)(n.ul,{children:["\n",(0,o.jsx)(n.li,{children:"Structurally identical to public accounts, but their values are stored off-chain."}),"\n",(0,o.jsxs)(n.li,{children:["Use two keypairs: ",(0,o.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#nullifier-keys",children:"nullifier keys"})," for privacy-preserving executions and ",(0,o.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#viewing-keys",children:"viewing keys"})," for encrypting and decrypting values."]}),"\n",(0,o.jsxs)(n.li,{children:["The ",(0,o.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#private-account",children:"private account"})," ID is derived from the ",(0,o.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#nullifier-public-key",children:"nullifier public key"}),"."]}),"\n",(0,o.jsx)(n.li,{children:"Anyone can initialise private accounts, but once initialised they can only be modified by the owner's keys."}),"\n"]}),"\n"]}),"\n",(0,o.jsxs)(n.li,{children:["Transfers involving any private account","\n",(0,o.jsxs)(n.ul,{children:["\n",(0,o.jsx)(n.li,{children:"The execution is privacy-preserving."}),"\n",(0,o.jsx)(n.li,{children:"The transfer generates a zero-knowledge proof of correct execution locally, and submits the proof for validators to verify. Each update to the private state produces a new commitment (a 32-byte, hash-like binding to the actual values), and the previous commitment is marked as spent via a nullifier set so only the latest version can be used, while the actual private values remain local to the account owner."}),"\n",(0,o.jsx)(n.li,{children:"Any public accounts involved are still updated visibly on-chain."}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,o.jsx)(n.admonition,{type:"danger",children:(0,o.jsx)(n.p,{children:"Transfers are irreversible. Double-check all details before proceeding."})}),"\n",(0,o.jsx)(n.p,{children:"Before you begin, ensure that you have the following:"}),"\n",(0,o.jsxs)(n.ul,{children:["\n",(0,o.jsxs)(n.li,{children:["The ",(0,o.jsx)(n.a,{href:"/lez/get-started/lez-quickstart#step-2-start-the-lez-sequencer-in-standalone-mode",children:"LEZ sequencer running in standalone mode"})," on your computer"]}),"\n",(0,o.jsxs)(n.li,{children:["The ",(0,o.jsx)(n.a,{href:"/lez/get-started/lez-quickstart#set-up-the-wallet-binary-prerequisites-and-build-the-wallet",children:"Wallet CLI installed"})," on your computer"]}),"\n"]}),"\n",(0,o.jsx)(n.h2,{id:"what-to-expect",children:"What to expect"}),"\n",(0,o.jsxs)(n.ul,{children:["\n",(0,o.jsx)(n.li,{children:"The authenticated-transfers program manages native token transfers and enforces authenticated debits. When making transfers, you use the wallet CLI to interact with the program."}),"\n",(0,o.jsx)(n.li,{children:"You can initialise accounts by sending tokens to them. The authenticated-transfers program claims any uninitialised account used in a transfer."}),"\n",(0,o.jsx)(n.li,{children:"You can transfer native tokens to public accounts and verify balances on-chain."}),"\n",(0,o.jsx)(n.li,{children:"Your private account balances are in your local wallet storage and rely on zero-knowledge proofs for privacy."}),"\n"]}),"\n",(0,o.jsx)(n.h2,{id:"step-1-create-and-fund-a-sender-account",children:"Step 1: Create and fund a sender account"}),"\n",(0,o.jsxs)(n.ol,{children:["\n",(0,o.jsxs)(n.li,{children:["\n",(0,o.jsx)(n.p,{children:"Create a public account or private account according to your needs."}),"\n",(0,o.jsxs)(n.ul,{children:["\n",(0,o.jsx)(n.li,{children:"Public account:"}),"\n"]}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{className:"language-sh",children:"wallet account new public\n"})}),"\n",(0,o.jsxs)(n.ul,{children:["\n",(0,o.jsx)(n.li,{children:"Private account:"}),"\n"]}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{className:"language-sh",children:"wallet account new private\n"})}),"\n",(0,o.jsxs)(n.p,{children:["If you create a public account, the output is the account ID. If you create a private account, the output includes the account ID, nullifier public key (",(0,o.jsx)(n.code,{children:"npk"}),"), and ",(0,o.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#viewing-public-key",children:"viewing public key"})," (",(0,o.jsx)(n.code,{children:"vpk"}),")."]}),"\n"]}),"\n"]}),"\n",(0,o.jsx)(n.admonition,{type:"info",children:(0,o.jsxs)(n.p,{children:["Your account keys and data are stored in the local file ",(0,o.jsx)(n.code,{children:"$HOME/.nssa/wallet/storage.json"}),"."]})}),"\n",(0,o.jsxs)(n.ol,{start:"2",children:["\n",(0,o.jsxs)(n.li,{children:["\n",(0,o.jsxs)(n.p,{children:["Use the ",(0,o.jsx)(n.code,{children:"wallet account ls"})," command to confirm the accounts are created successfully. You should see a list showing all of your accounts."]}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{className:"language-sh",children:"wallet account ls\n"})}),"\n"]}),"\n",(0,o.jsxs)(n.li,{children:["\n",(0,o.jsxs)(n.p,{children:["Initialise the sender account. Replace ",(0,o.jsx)(n.code,{children:"ACCOUNT-TYPE"})," with the type of the sender account (public or private) and ",(0,o.jsx)(n.code,{children:"ACCOUNT-ID"})," with the account ID you want to initialise."]}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{className:"language-sh",children:"wallet auth-transfer init --account-id ACCOUNT-TYPE/ACCOUNT-ID\n"})}),"\n",(0,o.jsxs)(n.p,{children:["For example, to initialise the public account with ID ",(0,o.jsx)(n.code,{children:"Ev1JprP9BmhbFVQyBcbznU8bAXcwrzwRoPTetXdQPAWS"}),", you run:"]}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{className:"language-sh",children:"wallet auth-transfer init --account-id Public/Ev1JprP9BmhbFVQyBcbznU8bAXcwrzwRoPTetXdQPAWS\n"})}),"\n"]}),"\n"]}),"\n",(0,o.jsxs)(n.admonition,{type:"info",children:[(0,o.jsx)(n.p,{children:"New accounts are created in an uninitialised state, which means no program on LEZ owns them yet. Any program can claim and own an uninitialised account. After initialisation, only the owning program can modify the account."}),(0,o.jsx)(n.p,{children:"The only exception is native token credits: any program can credit native tokens to any account, but only the owning program can debit native tokens."})]}),"\n",(0,o.jsxs)(n.ol,{start:"4",children:["\n",(0,o.jsxs)(n.li,{children:["\n",(0,o.jsx)(n.p,{children:"Fund the sender account using the Testnet Pi\xf1ata program. Your account receives 150 tokens every time you fund it."}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{className:"language-sh",children:"wallet pinata claim --to ACCOUNT-TYPE/ACCOUNT-ID\n"})}),"\n"]}),"\n",(0,o.jsxs)(n.li,{children:["\n",(0,o.jsxs)(n.p,{children:["Confirm your account balance after funding using the ",(0,o.jsx)(n.code,{children:"wallet account get"})," command:"]}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{className:"language-sh",children:"wallet account get --account-id ACCOUNT-TYPE/ACCOUNT-ID\n"})}),"\n",(0,o.jsx)(n.p,{children:"The output looks like this:"}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{children:'Account owned by authenticated-transfer program\n{..."balance":150...}\n'})}),"\n"]}),"\n"]}),"\n",(0,o.jsx)(n.h2,{id:"step-2-transfer-tokens",children:"Step 2: Transfer tokens"}),"\n",(0,o.jsxs)(n.p,{children:["When transferring native tokens using the ",(0,o.jsx)(n.code,{children:"wallet auth-transfer send"})," command, you specify the sender account with its account ID."]}),"\n",(0,o.jsx)(n.p,{children:"There are two ways to specify the recipient account depending on the account type:"}),"\n",(0,o.jsxs)(n.ul,{children:["\n",(0,o.jsx)(n.li,{children:(0,o.jsx)(n.a,{href:"/lez/transfer-tokens/transfer-native-tokens-on-the-logos-execution-zone#method-1-transfer-tokens-using-the-recipient-account-id",children:"Method 1: Use the recipient account ID"})}),"\n",(0,o.jsx)(n.li,{children:(0,o.jsxs)(n.a,{href:"/lez/transfer-tokens/transfer-native-tokens-on-the-logos-execution-zone#method-2-transfer-tokens-using-the-recipient-account-npk-and-vpk",children:["Method 2: Use the recipient account ",(0,o.jsx)(n.code,{children:"npk"})," and ",(0,o.jsx)(n.code,{children:"vpk"})]})}),"\n"]}),"\n",(0,o.jsx)(n.p,{children:"With the recipient account ID, you can transfer native tokens across the following account types:"}),"\n",(0,o.jsxs)(n.ul,{children:["\n",(0,o.jsx)(n.li,{children:"Public \u2192 public (yours or someone else's)"}),"\n",(0,o.jsx)(n.li,{children:"Private \u2192 public (yours or someone else's)"}),"\n",(0,o.jsx)(n.li,{children:"Public \u2192 your private"}),"\n",(0,o.jsx)(n.li,{children:"Private \u2192 your private"}),"\n"]}),"\n",(0,o.jsx)(n.admonition,{type:"info",children:(0,o.jsx)(n.p,{children:"Transfers involving private accounts may take a few minutes because the wallet needs to generate a local proof."})}),"\n",(0,o.jsxs)(n.p,{children:["With the ",(0,o.jsx)(n.code,{children:"npk"})," and ",(0,o.jsx)(n.code,{children:"vpk"})," of the recipient account, you can transfer native tokens across the following account types:"]}),"\n",(0,o.jsxs)(n.ul,{children:["\n",(0,o.jsx)(n.li,{children:"Public \u2192 uninitialised private (someone else's)"}),"\n",(0,o.jsx)(n.li,{children:"Private \u2192 uninitialised private (someone else's)"}),"\n"]}),"\n",(0,o.jsx)(n.admonition,{type:"info",children:(0,o.jsx)(n.p,{children:"Currently, only uninitialised private accounts can be modified without authorisation. Sending funds to initialised private accounts is not possible because only the owner can modify them."})}),"\n",(0,o.jsx)(n.h3,{id:"method-1-transfer-tokens-using-the-recipient-account-id",children:"Method 1: Transfer tokens using the recipient account ID"}),"\n",(0,o.jsxs)(n.p,{children:["Use the ",(0,o.jsx)(n.code,{children:"wallet auth-transfer send"})," to transfer tokens. Replace ",(0,o.jsx)(n.code,{children:"ACCOUNT-TYPE"})," with the type of the account (public or private) and ",(0,o.jsx)(n.code,{children:"TOKEN-AMOUNT"})," with the amount of tokens to transfer."]}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{children:"```sh\nwallet auth-transfer send \\\n --from ACCOUNT-TYPE/SENDER-ACCOUNT-ID \\\n --to ACCOUNT-TYPE/RECIPIENT-ACCOUNT-ID \\\n --amount TOKEN-AMOUNT\n```\n"})}),"\n",(0,o.jsxs)(n.p,{children:["For example, to transfer 17 tokens from the public account with ID ",(0,o.jsx)(n.code,{children:"Ev1JprP9BmhbFVQyBcbznU8bAXcwrzwRoPTetXdQPAWS"})," to the private account with ID ",(0,o.jsx)(n.code,{children:"HacPU3hakLYzWtSqUPw6TUr8fqoMieVWovsUR6sJf7cL"}),", you run:"]}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{children:"```sh\nwallet auth-transfer send \\\n --from Public/Ev1JprP9BmhbFVQyBcbznU8bAXcwrzwRoPTetXdQPAWS \\\n --to Private/HacPU3hakLYzWtSqUPw6TUr8fqoMieVWovsUR6sJf7cL \\\n --amount 17\n```\n"})}),"\n",(0,o.jsxs)(n.h3,{id:"method-2-transfer-tokens-using-the-recipient-account-npk-and-vpk",children:["Method 2: Transfer tokens using the recipient account ",(0,o.jsx)(n.code,{children:"npk"})," and ",(0,o.jsx)(n.code,{children:"vpk"})]}),"\n",(0,o.jsxs)(n.p,{children:["When transferring someone else's private account, you need their account ",(0,o.jsx)(n.code,{children:"npk"})," (nullifier public key)and ",(0,o.jsx)(n.code,{children:"vpk"})," (viewing public key) instead of the account ID because LEZ uses the ",(0,o.jsx)(n.code,{children:"npk"})," to confirm the ownership of the private account, and ",(0,o.jsx)(n.code,{children:"vpk"})," to verify transactions without revealing the account owner. These keys do not expose sensitive information and allow others to verify transaction validity."]}),"\n",(0,o.jsx)(n.admonition,{type:"tip",children:(0,o.jsxs)(n.p,{children:["Check your account ",(0,o.jsx)(n.code,{children:"npk"})," and ",(0,o.jsx)(n.code,{children:"vpk"})," using the ",(0,o.jsx)(n.code,{children:"wallet account get --account-id ACCOUNT-TYPE/ACCOUNT-ID"})," command."]})}),"\n",(0,o.jsxs)(n.ol,{children:["\n",(0,o.jsxs)(n.li,{children:["\n",(0,o.jsxs)(n.p,{children:["Use the ",(0,o.jsx)(n.code,{children:"wallet auth-transfer send"})," to transfer tokens to another user's private account. Replace ",(0,o.jsx)(n.code,{children:"ACCOUNT-TYPE"})," with the type of the sender account (public or private) and ",(0,o.jsx)(n.code,{children:"TOKEN-AMOUNT"})," with the amount of tokens to transfer."]}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{className:"language-sh",children:"wallet auth-transfer send \\\n --from ACCOUNT-TYPE/SENDER-ACCOUNT-ID \\\n --to-npk RECIPIENT-NPK \\\n --to-vpk RECIPIENT-VPK \\\n --amount TOKEN-AMOUNT\n"})}),"\n"]}),"\n",(0,o.jsxs)(n.li,{children:["\n",(0,o.jsx)(n.p,{children:"Once the transaction is accepted, run the following command to scan the chain for encrypted values in the transaction and update the local state accordingly."}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{className:"language-sh",children:"wallet account sync-private\n"})}),"\n"]}),"\n"]}),"\n",(0,o.jsx)(n.h2,{id:"step-3-verify-the-transfer",children:"Step 3: Verify the transfer"}),"\n",(0,o.jsxs)(n.p,{children:["Confirm the transfer by checking the balances of both accounts using the ",(0,o.jsx)(n.code,{children:"wallet account get"})," command."]}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{children:"```sh\nwallet account get --account-id ACCOUNT-TYPE/ACCOUNT-ID\n```\n"})}),"\n",(0,o.jsxs)(n.p,{children:["For example, to check the balance of the private account with ID ",(0,o.jsx)(n.code,{children:"HacPU3hakLYzWtSqUPw6TUr8fqoMieVWovsUR6sJf7cL"}),", you run:"]}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{children:"```sh\nwallet account get --account-id Private/HacPU3hakLYzWtSqUPw6TUr8fqoMieVWovsUR6sJf7cL\n```\n"})}),"\n",(0,o.jsx)(n.p,{children:"The output looks like this:"}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{children:'```text\nAccount owned by authenticated-transfer program\n{..."balance":BALANCE-AMOUNT...}\n```\n'})}),"\n",(0,o.jsx)(n.admonition,{type:"tip",children:(0,o.jsxs)(n.p,{children:["When checking the balance of a private account, the ",(0,o.jsx)(n.code,{children:"wallet account get"})," command doesn't query the network. It works offline because private account data lives only in your wallet storage. Other users cannot read your private balances using this command and your private account ID."]})}),"\n",(0,o.jsx)(n.admonition,{type:"tip",children:(0,o.jsxs)(n.p,{children:["You can also use the ",(0,o.jsx)(n.code,{children:"wallet account ls -l"})," command to check the balances of all your accounts at once."]})}),"\n",(0,o.jsx)(n.h2,{id:"troubleshooting",children:"Troubleshooting"}),"\n",(0,o.jsx)(n.h3,{id:"symptom",children:"Symptom"}),"\n",(0,o.jsx)(n.p,{children:"The wallet command panics and fails during proof generation with an assertion error when you try to send native tokens to an initialised private account."}),"\n",(0,o.jsx)(n.p,{children:"The error message looks like this:"}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{children:"thread 'main' panicked at .../privacy_preserving_circuit.rs:394:13:\nassertion `left == right` failed: Found new private account with non default values\n left: Account { ... }\n right: Account { ... }\n...\ncalled `Result::unwrap()` on an `Err` value: CircuitProvingError(\"Guest panicked: assertion `left == right` failed: Found new private account with non default values ...\")\n"})}),"\n",(0,o.jsx)(n.h3,{id:"explanation",children:"Explanation"}),"\n",(0,o.jsx)(n.p,{children:"The transfer fails because the privacy-preserving circuit expects the recipient private account to be uninitialised (default state), but an initialised private account already contains non-default values and can only be modified by its owner."})]})}function h(e={}){let{wrapper:n}={...(0,i.R)(),...e.components};return n?(0,o.jsx)(n,{...e,children:(0,o.jsx)(d,{...e})}):d(e)}},8453(e,n,t){t.d(n,{R:()=>a,x:()=>r});var s=t(6540);let o={},i=s.createContext(o);function a(e){let n=s.useContext(i);return s.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function r(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(o):e.components||o:a(e.components),s.createElement(i.Provider,{value:n},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/b9ebd1fd.5787e371.js b/assets/js/b9ebd1fd.5787e371.js new file mode 100644 index 00000000..4b1b0734 --- /dev/null +++ b/assets/js/b9ebd1fd.5787e371.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunklogos_docs=self.webpackChunklogos_docs||[]).push([["3319"],{5790(e,t,i){i.r(t),i.d(t,{metadata:()=>s,default:()=>h,frontMatter:()=>r,contentTitle:()=>a,toc:()=>l,assets:()=>c});var s=JSON.parse('{"id":"messaging/concepts/understand-logos-delivery-security-features","title":"Understand Logos Delivery security features","description":"See the services that keep Logos Delivery communication private and abuse-resistant.","source":"@site/docs/messaging/concepts/understand-logos-delivery-security-features.md","sourceDirName":"messaging/concepts","slug":"/messaging/concepts/security-features","permalink":"/messaging/concepts/security-features","draft":false,"unlisted":false,"editUrl":"https://github.com/logos-co/logos-docs/tree/main/docs/messaging/concepts/understand-logos-delivery-security-features.md","tags":[],"version":"current","sidebarPosition":6,"frontMatter":{"title":"Understand Logos Delivery security features","doc_type":"concept","product":"messaging","topics":"Delivery","authors":"LordGhostX, fryorcraken","owner":"logos","doc_version":1,"slug":"security-features","sidebar_position":6},"sidebar":"messagingSidebar","previous":{"title":"About peer exchange","permalink":"/messaging/concepts/understand-peer-discovery/peer-exchange"},"next":{"title":"Learn the differences between Logos Delivery and libp2p","permalink":"/messaging/concepts/logos-delivery-and-libp2p"}}'),n=i(4848),o=i(8453);let r={title:"Understand Logos Delivery security features",doc_type:"concept",product:"messaging",topics:"Delivery",authors:"LordGhostX, fryorcraken",owner:"logos",doc_version:1,slug:"security-features",sidebar_position:6},a="Understand Logos Delivery security features",c={},l=[{value:"See the services that keep Logos Delivery communication private and abuse-resistant.",id:"see-the-services-that-keep-logos-delivery-communication-private-and-abuse-resistant",level:4},{value:"Pseudonymity",id:"pseudonymity",level:2},{value:"Anonymity/unlinkability",id:"anonymityunlinkability",level:2},{value:"Spam protection",id:"spam-protection",level:2},{value:"Data confidentiality, integrity, and authenticity",id:"data-confidentiality-integrity-and-authenticity",level:2},{value:"Security considerations",id:"security-considerations",level:2}];function d(e){let t={a:"a",code:"code",h1:"h1",h2:"h2",h4:"h4",header:"header",li:"li",p:"p",ul:"ul",...(0,o.R)(),...e.components};return(0,n.jsxs)(n.Fragment,{children:[(0,n.jsx)(t.header,{children:(0,n.jsx)(t.h1,{id:"understand-logos-delivery-security-features",children:"Understand Logos Delivery security features"})}),"\n",(0,n.jsx)(t.h4,{id:"see-the-services-that-keep-logos-delivery-communication-private-and-abuse-resistant",children:"See the services that keep Logos Delivery communication private and abuse-resistant."}),"\n",(0,n.jsxs)(t.p,{children:[(0,n.jsx)(t.a,{href:"https://docs.logos.co/get-started/glossary#logos-delivery",children:"Logos Delivery"}),"'s protocol layers offer different services and security considerations, shaping the overall security of Logos Delivery. We document the security models in the ",(0,n.jsx)(t.a,{href:"https://rfc.vac.dev/",children:"RFCs of the protocols"}),", aiming to provide transparent and open-source references. This empowers Logos Delivery users to understand each protocol's security guarantees and limitations."]}),"\n",(0,n.jsx)(t.h2,{id:"pseudonymity",children:"Pseudonymity"}),"\n",(0,n.jsxs)(t.p,{children:["Logos Delivery ensures ",(0,n.jsx)(t.a,{href:"https://lip.logos.co/messaging/core/draft/10/waku2.html#pseudonymity",children:"pseudonymity"})," across its protocol layers, using libp2p ",(0,n.jsx)(t.code,{children:"PeerID"})," as identifiers instead of disclosing true identities. However, it is important to note that pseudonymity does not provide complete anonymity. Actions performed under the same pseudonym (",(0,n.jsx)(t.code,{children:"PeerID"}),") can be linked, leading to the potential re-identification of the actual actor."]}),"\n",(0,n.jsx)(t.h2,{id:"anonymityunlinkability",children:"Anonymity/unlinkability"}),"\n",(0,n.jsxs)(t.p,{children:[(0,n.jsx)(t.a,{href:"https://lip.logos.co/messaging/core/draft/10/waku2.html#anonymity--unlinkability",children:"Anonymity"})," means an adversary cannot connect an actor to their actions or data. To achieve anonymity, avoiding linking activities with actors or their Personally Identifiable Information (PII) is crucial. In Logos Delivery, the following anonymity features are provided:"]}),"\n",(0,n.jsxs)(t.ul,{children:["\n",(0,n.jsxs)(t.li,{children:[(0,n.jsx)(t.a,{href:"https://lip.logos.co/messaging/core/stable/11/relay.html#security-analysis",children:"Publisher-message unlinkability"}),": Ensures that the publisher of messages in the ",(0,n.jsx)(t.code,{children:"Relay"})," protocol cannot be linked to their published messages."]}),"\n",(0,n.jsxs)(t.li,{children:[(0,n.jsx)(t.a,{href:"https://lip.logos.co/messaging/core/stable/11/relay.html#security-analysis",children:"Subscriber-topic unlinkability"}),": Ensures that the subscriber of topics in the ",(0,n.jsx)(t.code,{children:"Relay"})," protocol cannot be linked to the topics they have subscribed to."]}),"\n"]}),"\n",(0,n.jsx)(t.h2,{id:"spam-protection",children:"Spam protection"}),"\n",(0,n.jsxs)(t.p,{children:["The ",(0,n.jsx)(t.a,{href:"https://lip.logos.co/messaging/core/draft/10/waku2.html#spam-protection",children:"spam protection"})," feature in ",(0,n.jsx)(t.code,{children:"Relay"})," ensures that no adversary can flood the system with many messages, intentionally or not, regardless of the content's validity or usefulness. This protection is achieved through the ",(0,n.jsx)(t.a,{href:"https://github.com/libp2p/specs/blob/master/pubsub/gossipsub/gossipsub-v1.1.md#spam-protection-measures",children:"scoring mechanism"})," of ",(0,n.jsx)(t.code,{children:"GossipSub v1.1"}),". Peers assign scores to their connections based on their behaviour and remove peers with low scores."]}),"\n",(0,n.jsxs)(t.p,{children:["Ongoing research is being conducted, including developing ",(0,n.jsx)(t.a,{href:"/messaging/concepts/protocols#rln-relay",children:"Rate Limit Nullifiers (RLN)"}),", which can be explored further at: ",(0,n.jsx)(t.a,{href:"https://github.com/vacp2p/research/issues/148",children:"https://github.com/vacp2p/research/issues/148"}),"."]}),"\n",(0,n.jsx)(t.h2,{id:"data-confidentiality-integrity-and-authenticity",children:"Data confidentiality, integrity, and authenticity"}),"\n",(0,n.jsxs)(t.p,{children:[(0,n.jsx)(t.a,{href:"https://lip.logos.co/messaging/core/draft/10/waku2.html#data-confidentiality-integrity-and-authenticity",children:"Confidentiality"})," in Logos Delivery is ensured through data encryption, while integrity and authenticity are achieved through digital signatures. These security measures are available in ",(0,n.jsx)(t.a,{href:"https://lip.logos.co/messaging/core/stable/14/message.html#payload-encryption",children:"Waku Message (version 1)"})," and Noise protocols, which offer payload encryption and encrypted signatures."]}),"\n",(0,n.jsx)(t.h2,{id:"security-considerations",children:"Security considerations"}),"\n",(0,n.jsxs)(t.p,{children:["In protocols like ",(0,n.jsx)(t.code,{children:"Store"})," and ",(0,n.jsx)(t.code,{children:"Filter"}),", where direct connections are required for the designated service, anonymity or unlinkability is not guaranteed. This is because nodes use their ",(0,n.jsx)(t.code,{children:"PeerID"})," to identify each other during direct connections, making the service obtained in these protocols linkable to the beneficiary's ",(0,n.jsx)(t.code,{children:"PeerID"}),", considered Personally Identifiable Information (PII). In ",(0,n.jsx)(t.code,{children:"Store"}),", the queried node can link the querying node's ",(0,n.jsx)(t.code,{children:"PeerID"})," to the topics being queried. Similarly, in ",(0,n.jsx)(t.code,{children:"Filter"}),", a node can link the ",(0,n.jsx)(t.code,{children:"PeerID"})," of a ",(0,n.jsx)(t.a,{href:"https://docs.logos.co/get-started/glossary#light-node",children:"light node"})," to its content filter. Check out the ",(0,n.jsx)(t.a,{href:"https://lip.logos.co/messaging/core/draft/10/waku2.html#security-considerations",children:"Security considerations"})," section in the Waku2 RFC for more details."]})]})}function h(e={}){let{wrapper:t}={...(0,o.R)(),...e.components};return t?(0,n.jsx)(t,{...e,children:(0,n.jsx)(d,{...e})}):d(e)}},8453(e,t,i){i.d(t,{R:()=>r,x:()=>a});var s=i(6540);let n={},o=s.createContext(n);function r(e){let t=s.useContext(o);return s.useMemo(function(){return"function"==typeof e?e(t):{...t,...e}},[t,e])}function a(e){let t;return t=e.disableParentContext?"function"==typeof e.components?e.components(n):e.components||n:r(e.components),s.createElement(o.Provider,{value:t},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/ba594660.19385d01.js b/assets/js/ba594660.19385d01.js new file mode 100644 index 00000000..94dd1918 --- /dev/null +++ b/assets/js/ba594660.19385d01.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunklogos_docs=self.webpackChunklogos_docs||[]).push([["1951"],{1042(e,s,t){t.r(s),t.d(s,{metadata:()=>o,default:()=>h,frontMatter:()=>r,contentTitle:()=>a,toc:()=>l,assets:()=>c});var o=JSON.parse('{"id":"messaging/get-started/introduction-to-logos-messaging","title":"Introduction to Logos Messaging","description":"Learn how Logos provides private, censorship-resistant communication for decentralised applications.","source":"@site/docs/messaging/get-started/introduction-to-logos-messaging.md","sourceDirName":"messaging/get-started","slug":"/messaging/get-started/introduction-to-logos-messaging","permalink":"/messaging/get-started/introduction-to-logos-messaging","draft":false,"unlisted":false,"editUrl":"https://github.com/logos-co/logos-docs/tree/main/docs/messaging/get-started/introduction-to-logos-messaging.md","tags":[],"version":"current","sidebarPosition":1,"frontMatter":{"sidebar_position":1},"sidebar":"messagingSidebar","next":{"title":"Send 1:1 messages with the Logos Chat app","permalink":"/messaging/get-started/send-1-1-messages-logos-chat"}}'),i=t(4848),n=t(8453);let r={sidebar_position:1},a="Introduction to Logos Messaging",c={},l=[{value:"Learn how Logos provides private, censorship-resistant communication for decentralised applications.",id:"learn-how-logos-provides-private-censorship-resistant-communication-for-decentralised-applications",level:4},{value:"The basics",id:"the-basics",level:2},{value:"Architecture",id:"architecture",level:2},{value:"Delivery",id:"delivery",level:3},{value:"Chat",id:"chat",level:3},{value:"Security features",id:"security-features",level:3},{value:"Logos Messaging and libp2p",id:"logos-messaging-and-libp2p",level:3}];function d(e){let s={a:"a",code:"code",h1:"h1",h2:"h2",h3:"h3",h4:"h4",header:"header",li:"li",p:"p",strong:"strong",ul:"ul",...(0,n.R)(),...e.components};return(0,i.jsxs)(i.Fragment,{children:[(0,i.jsx)(s.header,{children:(0,i.jsx)(s.h1,{id:"introduction-to-logos-messaging",children:"Introduction to Logos Messaging"})}),"\n",(0,i.jsx)(s.h4,{id:"learn-how-logos-provides-private-censorship-resistant-communication-for-decentralised-applications",children:"Learn how Logos provides private, censorship-resistant communication for decentralised applications."}),"\n",(0,i.jsxs)(s.p,{children:[(0,i.jsx)(s.a,{href:"https://docs.logos.co/get-started/glossary#logos-messaging",children:"Logos Messaging"})," is the messaging layer of the Logos technology stack. It lets applications send messages, relay data, and coordinate with one another without relying on centralised servers or exposing metadata to surveillance. It uses a peer-to-peer relay network where messages propagate across nodes with no central broker, supporting private one-to-one messaging, group messaging, and public broadcast channels."]}),"\n",(0,i.jsx)(s.h2,{id:"the-basics",children:"The basics"}),"\n",(0,i.jsxs)(s.ul,{children:["\n",(0,i.jsxs)(s.li,{children:["Logos Messaging comprises two modules: ",(0,i.jsx)(s.a,{href:"https://github.com/logos-messaging/logos-delivery",children:"Delivery"}),", a peer-to-peer transport layer with spam protection, and ",(0,i.jsx)(s.a,{href:"https://github.com/logos-messaging/logos-chat",children:"Chat"}),", a library for private, end-to-end encrypted conversations built on top of Delivery."]}),"\n",(0,i.jsx)(s.li,{children:"Messages propagate across a peer-to-peer relay network with no central broker. Anyone can run a node and there is no gas fee for sending or receiving messages."}),"\n",(0,i.jsxs)(s.li,{children:["Delivery is modular. Applications combine a set of protocols \u2014 ",(0,i.jsx)(s.a,{href:"/messaging/concepts/protocols#relay",children:"Relay"}),", ",(0,i.jsx)(s.a,{href:"/messaging/concepts/protocols#rln-relay",children:"RLN Relay"}),", ",(0,i.jsx)(s.a,{href:"/messaging/concepts/protocols#filter",children:"Filter"}),", ",(0,i.jsx)(s.a,{href:"/messaging/concepts/protocols#store",children:"Store"}),", and ",(0,i.jsx)(s.a,{href:"/messaging/concepts/protocols#light-push",children:"Light Push"})," \u2014 to balance anonymity, scalability, and latency for their own use case."]}),"\n",(0,i.jsxs)(s.li,{children:["Logos Messaging is built on top of ",(0,i.jsx)(s.a,{href:"/messaging/concepts/logos-delivery-and-libp2p",children:"libp2p"}),", but adds a service network that anyone can use without deploying their own infrastructure, along with economic spam protection that libp2p does not provide."]}),"\n"]}),"\n",(0,i.jsx)(s.h2,{id:"architecture",children:"Architecture"}),"\n",(0,i.jsxs)(s.p,{children:["Logos Messaging is implemented as two modules that sit on top of the Logos networking layer: ",(0,i.jsx)(s.a,{href:"https://github.com/logos-messaging/logos-delivery",children:"Delivery"}),", which handles peer-to-peer transport, and ",(0,i.jsx)(s.a,{href:"https://github.com/logos-messaging/logos-chat",children:"Chat"}),", which builds private conversations on top of it."]}),"\n",(0,i.jsx)(s.h3,{id:"delivery",children:"Delivery"}),"\n",(0,i.jsxs)(s.p,{children:["Delivery is the peer-to-peer messaging network at the base of the messaging stack, and the reference implementation of the Logos Messaging protocol. It provides publish-subscribe messaging over an open, censorship-resistant network with no central broker, and it protects the network from spam using ",(0,i.jsx)(s.a,{href:"/messaging/get-started/faq#what-are-rate-limiting-nullifiers-rln",children:"RLN"}),", which verifies a zero-knowledge proof rather than a publisher's identity."]}),"\n",(0,i.jsxs)(s.p,{children:["Delivery's functionality is split across a set of ",(0,i.jsx)(s.a,{href:"/messaging/concepts/protocols",children:"protocols"}),":"]}),"\n",(0,i.jsxs)(s.ul,{children:["\n",(0,i.jsxs)(s.li,{children:[(0,i.jsx)(s.strong,{children:"Relay"})," extends ",(0,i.jsx)(s.a,{href:"https://github.com/libp2p/specs/blob/master/pubsub/gossipsub/README.md",children:"libp2p GossipSub"})," to provide the base publish/subscribe messaging that peers use to send and receive messages."]}),"\n",(0,i.jsxs)(s.li,{children:[(0,i.jsx)(s.strong,{children:"RLN Relay"})," extends Relay with rate limiting nullifiers for economic, privacy-preserving spam prevention."]}),"\n",(0,i.jsxs)(s.li,{children:[(0,i.jsx)(s.strong,{children:"Filter"})," lets light nodes selectively subscribe to messages by content topic, trading some privacy for lower bandwidth use."]}),"\n",(0,i.jsxs)(s.li,{children:[(0,i.jsx)(s.strong,{children:"Store"})," temporarily retains messages so offline peers can retrieve what they missed, though it does not guarantee availability \u2014 applications that need durable storage should pair Delivery with Logos Storage instead."]}),"\n",(0,i.jsxs)(s.li,{children:[(0,i.jsx)(s.strong,{children:"Light Push"})," lets bandwidth-constrained nodes publish a message and receive an acknowledgement that at least one peer received it, without guaranteeing network-wide propagation."]}),"\n"]}),"\n",(0,i.jsxs)(s.p,{children:["These protocols interact across three ",(0,i.jsx)(s.a,{href:"/messaging/concepts/network-domains",children:"network domains"}),": the discovery domain, where nodes locate other peers; the gossip domain, where Relay and RLN Relay distribute messages; and the request/response domain, where Store, Filter, and Light Push serve resource-limited nodes directly."]}),"\n",(0,i.jsxs)(s.p,{children:["Messages are routed using ",(0,i.jsx)(s.a,{href:"/messaging/concepts/content-topics",children:"content topics"}),', developer-set metadata strings that let protocols filter, route, and retrieve messages.. How narrowly or broadly an application scopes its content topics is a direct privacy trade-off: Filter, Store, and Light Push all disclose content topics to peers, so grouping many users under shared topics increases k-anonymity, while splitting traffic into topic "buckets" helps distribute load for high-traffic applications.']}),"\n",(0,i.jsxs)(s.p,{children:["Delivery is ",(0,i.jsx)(s.a,{href:"/messaging/concepts/transports",children:"transport-agnostic"}),", so developers choose the protocol that fits their environment: TCP by default for service nodes, and secure WebSocket for browsers, with WebRTC, WebTransport, and QUIC under research for future support."]}),"\n",(0,i.jsxs)(s.p,{children:["To join the network, a node bootstraps using one of several ",(0,i.jsx)(s.a,{href:"/messaging/concepts/understand-peer-discovery/peer-discovery",children:"peer discovery"})," mechanisms \u2014 including static peers, DNS discovery, Discv5, and Peer Exchange \u2014 and then keeps looking for peers to maintain a healthy Relay mesh, reserve backup connections, and find peers with specific capabilities such as Store or Filter."]}),"\n",(0,i.jsx)(s.h3,{id:"chat",children:"Chat"}),"\n",(0,i.jsx)(s.p,{children:"Chat is a protocol and library that provides one-to-one and group conversations with end-to-end encryption, using de-MLS for group messaging. It uses Delivery as its transport, so applications get everything they need to add private chat without designing and securing their own messaging protocol from scratch."}),"\n",(0,i.jsx)(s.h3,{id:"security-features",children:"Security features"}),"\n",(0,i.jsxs)(s.p,{children:["Logos Messaging's ",(0,i.jsx)(s.a,{href:"/messaging/concepts/security-features",children:"security guarantees"})," vary by protocol and are documented openly in the underlying RFCs. Relay provides pseudonymity and unlinkability between publishers, subscribers, and their activity, backed by GossipSub's peer-scoring mechanism and ongoing RLN research for spam protection. Data confidentiality, integrity, and authenticity are available through the Waku Message format and Noise protocols. Protocols that require a direct connection, such as Store and Filter, trade away some of that unlinkability, since a node can associate a peer's ",(0,i.jsx)(s.code,{children:"PeerID"})," with the topics or filters it queries."]}),"\n",(0,i.jsx)(s.h3,{id:"logos-messaging-and-libp2p",children:"Logos Messaging and libp2p"}),"\n",(0,i.jsxs)(s.p,{children:["Logos Messaging is ",(0,i.jsx)(s.a,{href:"/messaging/concepts/logos-delivery-and-libp2p",children:"built on top of libp2p"})," and shares much of its terminology, but it goes further in two ways. It is a service network: developers can use it without deploying their own infrastructure, though anyone can choose to run a node to help decentralise the network. It is also a turnkey solution for privacy-preserving, censorship-resistant, platform-agnostic messaging, including out-of-the-box support for offline and resource-limited devices through Store, Filter, and Light Push, plus economic spam protection through RLN Relay, which libp2p does not provide on its own."]})]})}function h(e={}){let{wrapper:s}={...(0,n.R)(),...e.components};return s?(0,i.jsx)(s,{...e,children:(0,i.jsx)(d,{...e})}):d(e)}},8453(e,s,t){t.d(s,{R:()=>r,x:()=>a});var o=t(6540);let i={},n=o.createContext(i);function r(e){let s=o.useContext(n);return o.useMemo(function(){return"function"==typeof e?e(s):{...s,...e}},[s,e])}function a(e){let s;return s=e.disableParentContext?"function"==typeof e.components?e.components(i):e.components||i:r(e.components),o.createElement(n.Provider,{value:s},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/bb3f60c2.0a49c94e.js b/assets/js/bb3f60c2.0a49c94e.js new file mode 100644 index 00000000..426cf762 --- /dev/null +++ b/assets/js/bb3f60c2.0a49c94e.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunklogos_docs=self.webpackChunklogos_docs||[]).push([["4410"],{3190(e,n,i){i.r(n),i.d(n,{metadata:()=>t,default:()=>h,frontMatter:()=>a,contentTitle:()=>r,toc:()=>c,assets:()=>d});var t=JSON.parse('{"id":"blockchain/zone-sdk/bridge-assets-between-blockchain-and-zone","title":"Bridge assets between Logos Blockchain and a Zone","description":"Move tokens between Logos Blockchain and a Zone using channels and the Zone SDK.","source":"@site/docs/blockchain/zone-sdk/bridge-assets-between-blockchain-and-zone.md","sourceDirName":"blockchain/zone-sdk","slug":"/blockchain/zone-sdk/bridge-assets-between-blockchain-and-zone","permalink":"/blockchain/zone-sdk/bridge-assets-between-blockchain-and-zone","draft":false,"unlisted":false,"editUrl":"https://github.com/logos-co/logos-docs/tree/main/docs/blockchain/zone-sdk/bridge-assets-between-blockchain-and-zone.md","tags":[],"version":"current","sidebarPosition":2,"frontMatter":{"title":"Bridge assets between Logos Blockchain and a Zone","doc_type":"procedure","product":"blockchain","topics":["bridging","channels","zones","withdrawals","deposits"],"steps_layout":"sectioned","authors":"youngjoon-lee, kashepavadan","owner":"logos","doc_version":1,"slug":"bridge-assets-between-blockchain-and-zone","sidebar_position":2},"sidebar":"blockchainSidebar","previous":{"title":"Inscribe data on-chain using the Zone SDK","permalink":"/blockchain/zone-sdk/inscribe-data-on-chain-using-zone-sdk"},"next":{"title":"Operate a decentralised Zone with round-robin sequencer rotation","permalink":"/blockchain/zone-sdk/operate-decentralised-zone-with-round-robin-sequencer-rotation"}}'),s=i(4848),o=i(8453);let a={title:"Bridge assets between Logos Blockchain and a Zone",doc_type:"procedure",product:"blockchain",topics:["bridging","channels","zones","withdrawals","deposits"],steps_layout:"sectioned",authors:"youngjoon-lee, kashepavadan",owner:"logos",doc_version:1,slug:"bridge-assets-between-blockchain-and-zone",sidebar_position:2},r="Bridge assets between Logos Blockchain and a Zone",d={},c=[{value:"Move tokens between Logos Blockchain and a Zone using channels and the Zone SDK.",id:"move-tokens-between-logos-blockchain-and-a-zone-using-channels-and-the-zone-sdk",level:4},{value:"What to expect",id:"what-to-expect",level:2},{value:"Step 1: Create a channel",id:"step-1-create-a-channel",level:2},{value:"Step 2: Observe deposits from Bedrock",id:"step-2-observe-deposits-from-bedrock",level:2},{value:"Step 3: Submit a withdrawal",id:"step-3-submit-a-withdrawal",level:2},{value:"Option 1: Submit a single-sequencer withdrawal",id:"option-1-submit-a-single-sequencer-withdrawal",level:3},{value:"Option 2: Submit a multi-sequencer withdrawal",id:"option-2-submit-a-multi-sequencer-withdrawal",level:3},{value:"Step 4: Recover from a reorg",id:"step-4-recover-from-a-reorg",level:2},{value:"Frequently asked questions",id:"frequently-asked-questions",level:2},{value:"Does the reorg recovery path work for multi-sequencer withdrawals?",id:"does-the-reorg-recovery-path-work-for-multi-sequencer-withdrawals",level:3}];function l(e){let n={a:"a",admonition:"admonition",code:"code",h1:"h1",h2:"h2",h3:"h3",h4:"h4",header:"header",li:"li",ol:"ol",p:"p",pre:"pre",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,o.R)(),...e.components};return(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(n.header,{children:(0,s.jsx)(n.h1,{id:"bridge-assets-between-logos-blockchain-and-a-zone",children:"Bridge assets between Logos Blockchain and a Zone"})}),"\n",(0,s.jsx)(n.h4,{id:"move-tokens-between-logos-blockchain-and-a-zone-using-channels-and-the-zone-sdk",children:"Move tokens between Logos Blockchain and a Zone using channels and the Zone SDK."}),"\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.a,{href:"/blockchain/concepts/about-zones",children:"Logos Zones"})," are customizable, high-performance blockchains for applications built on Logos. ",(0,s.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#logos-blockchain",children:"Logos Blockchain"})," notes can be bridged from ",(0,s.jsx)(n.a,{href:"/blockchain/concepts/about-bedrock",children:"Bedrock"})," to Zones, with the ",(0,s.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#zone",children:"Zone"}),"'s associated ",(0,s.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#channel",children:"channel"})," maintaining a token balance that keeps track of the total token value stored in the Zone."]}),"\n",(0,s.jsxs)(n.p,{children:["This procedure covers creating a channel, depositing notes from the Blockchain into a Zone, and withdrawing notes from a Zone back to the Blockchain, using the ",(0,s.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#zone-sdk",children:"Zone SDK"}),"'s ",(0,s.jsx)(n.code,{children:"ZoneSequencer"}),". It applies to Zone developers building sequencers and indexers; the Zone itself defines how the channel balance maps to its internal accounts, while the SDK only surfaces the on-chain events."]}),"\n",(0,s.jsxs)(n.p,{children:["Bridging has two directions: deposit (Blockchain to Zone), where a user funds a channel and the Zone sequencer credits the user internally (",(0,s.jsx)(n.code,{children:"ChannelDeposit"}),"), and withdraw (Zone to Blockchain), where the sequencer submits a signed ",(0,s.jsx)(n.code,{children:"ChannelWithdraw"})," to debit the channel and mint fresh notes on-chain."]}),"\n",(0,s.jsx)(n.admonition,{type:"info",children:(0,s.jsxs)(n.p,{children:["The Zone SDK currently supports the bundled withdrawal API only for single-sequencer Zones (",(0,s.jsx)(n.code,{children:"ChannelState.withdraw_threshold == 1"}),"). Multi-sequencer Zones require building the threshold proof manually, as covered in a later step."]})}),"\n",(0,s.jsx)(n.p,{children:"Before you start, make sure you have:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"A running Logos Blockchain node reachable over HTTP"}),"\n",(0,s.jsxs)(n.li,{children:["The Zone SDK (",(0,s.jsx)(n.code,{children:"lb_zone_sdk"}),") and ",(0,s.jsx)(n.code,{children:"lb_core"})," crates added to your project"]}),"\n",(0,s.jsxs)(n.li,{children:["Completed the ",(0,s.jsx)(n.a,{href:"/blockchain/zone-sdk/inscribe-data-on-chain-using-zone-sdk",children:"Zone SDK inscription tutorial"})]}),"\n"]}),"\n",(0,s.jsx)(n.h2,{id:"what-to-expect",children:"What to expect"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"You can create a channel and have it recognized on-chain without a separate deployment transaction."}),"\n",(0,s.jsx)(n.li,{children:"You can observe finalized deposits on your channel as they are credited inside the zone."}),"\n",(0,s.jsx)(n.li,{children:"You can submit single- or multi-signature withdrawals and confirm them once finalized, including after a reorg."}),"\n"]}),"\n",(0,s.jsx)(n.h2,{id:"step-1-create-a-channel",children:"Step 1: Create a channel"}),"\n",(0,s.jsxs)(n.p,{children:["A channel is created automatically the first time an operation references a previously unseen ",(0,s.jsx)(n.code,{children:"ChannelId"}),"."]}),"\n",(0,s.jsxs)(n.ol,{children:["\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsxs)(n.p,{children:["Generate a ",(0,s.jsx)(n.a,{href:"https://github.com/logos-blockchain/logos-blockchain/blob/master/kms/keys/src/keys/ed25519/mod.rs#L31",children:(0,s.jsx)(n.code,{children:"Ed25519Key"})})," for your sequencer. The private key is used to sign inscriptions and channel operations, while the public key defines the ",(0,s.jsx)(n.code,{children:"ChannelId"}),". An example implementation is shown below:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-rust",children:'// Generate Ed25519 key pair\nlet mut key_bytes = [0u8; ED25519_SECRET_KEY_SIZE];\nrand::RngCore::fill_bytes(&mut rand::thread_rng(), &mut key_bytes);\nstd::fs::write(&your_key_file_path, key_bytes).expect("failed to write key file");\nlet signing_key = Ed25519Key::from_bytes(&key_bytes);\n\n// Derive channel ID\nlet channel_id = ChannelId::from(signing_key.public_key().to_bytes());\n'})}),"\n",(0,s.jsxs)(n.p,{children:["The bridging-relevant fields on ",(0,s.jsx)(n.a,{href:"https://app.notion.com/p/nomos-tech/1-5-0-Mantle-33d261aa09df8051b0d0cd4d5ddade85?source=copy_link#22b261aa09df8289a3f281de4aa8fdca",children:(0,s.jsx)(n.code,{children:"ChannelState"})})," in the ",(0,s.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#mantle",children:"Mantle"})," specification:"]}),"\n",(0,s.jsxs)(n.table,{children:[(0,s.jsx)(n.thead,{children:(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.th,{children:"Field"}),(0,s.jsx)(n.th,{children:"Purpose"})]})}),(0,s.jsxs)(n.tbody,{children:[(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"balance"})}),(0,s.jsx)(n.td,{children:"On-chain TokenValue held by the channel. Deposits add, withdrawals subtract. Defaults to 0."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"withdrawal_nonce"})}),(0,s.jsx)(n.td,{children:"Increments by 1 on every successful withdraw. Provides replay protection."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"withdraw_threshold"})}),(0,s.jsx)(n.td,{children:"Minimum number of accredited-key signatures needed to authorize a withdrawal. Defaults to 0."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"accredited_keys"})}),(0,s.jsx)(n.td,{children:"The committee that may sign withdrawals."})]})]})]}),"\n"]}),"\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsxs)(n.p,{children:["Initialize a ",(0,s.jsx)(n.code,{children:"ZoneSequencer"})," and publish the first ",(0,s.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#inscription",children:"inscription"})," inside your event loop once ",(0,s.jsx)(n.code,{children:"Event::Ready"})," has fired. The channel is created on-chain automatically, naming this sequencer's key as the sole accredited key."]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-rust",children:'use lb_zone_sdk::{\n CommonHttpClient, adapter::NodeHttpClient, sequencer::ZoneSequencer,\n};\n\n// Connect to the Logos Blockchain node.\nlet node = NodeHttpClient::new(\n CommonHttpClient::new(None),\n "http://localhost:8080".parse()?,\n);\n\n// Initialize the sequencer for this channel\nlet mut sequencer = ZoneSequencer::init(channel_id, signing_key, node, None);\n\n// Inside the event loop, once `Event::Ready` has fired:\n// Publishing the first inscription creates the channel just-in-time.\nlet (result, checkpoint) = sequencer.handle().publish(genesis_zone_block)?;\n'})}),"\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.code,{children:"publish"})," returns synchronously after enqueueing the transaction into the sequencer's pending set; the post reaches the node the next time the event loop polls ",(0,s.jsx)(n.code,{children:"next_event"}),". Persist the returned ",(0,s.jsx)(n.code,{children:"PublishResult"})," and ",(0,s.jsx)(n.code,{children:"SequencerCheckpoint"})," into your outbox."]}),"\n"]}),"\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsxs)(n.p,{children:["(Optional) Reconfigure the channel by calling ",(0,s.jsx)(n.code,{children:"sequencer.handle().channel_config(..)"})," with a ",(0,s.jsx)(n.a,{href:"https://app.notion.com/p/nomos-tech/1-5-0-Mantle-33d261aa09df8051b0d0cd4d5ddade85?source=copy_link#f96261aa09df826a93d801db1e432a54",children:(0,s.jsx)(n.code,{children:"ChannelConfig"})})," operation."]}),"\n"]}),"\n"]}),"\n",(0,s.jsx)(n.h2,{id:"step-2-observe-deposits-from-bedrock",children:"Step 2: Observe deposits from Bedrock"}),"\n",(0,s.jsxs)(n.p,{children:["A deposit happens when a ",(0,s.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#bedrock",children:"Bedrock"})," user submits a transaction with a ",(0,s.jsx)(n.a,{href:"https://app.notion.com/p/nomos-tech/1-5-0-Mantle-33d261aa09df8051b0d0cd4d5ddade85?source=copy_link#80b261aa09df8353814a81efe0fbd8ed",children:(0,s.jsx)(n.code,{children:"ChannelDeposit"})})," operation naming the target ",(0,s.jsx)(n.code,{children:"channel"}),", the consumed ",(0,s.jsx)(n.code,{children:"inputs"}),", and opaque ",(0,s.jsx)(n.code,{children:"metadata"})," that the Zone interprets, such as a recipient address."]}),"\n",(0,s.jsxs)(n.ol,{children:["\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsxs)(n.p,{children:["Watch for finalized deposits inside your events loop. The Zone SDK surfaces every finalized deposit on your channel as a ",(0,s.jsx)(n.code,{children:"FinalizedOp::Deposit(DepositInfo)"})," inside the ",(0,s.jsx)(n.code,{children:"finalized"})," field of ",(0,s.jsx)(n.code,{children:"Event::BlocksProcessed"}),"."]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-rust",children:'use lb_zone_sdk::sequencer::{Event, FinalizedOp};\n\nif let Event::BlocksProcessed { finalized, .. } = event {\n // Iterate over every finalized transaction in this batch of blocks.\n for tx in finalized {\n for op in tx.ops {\n if let FinalizedOp::Deposit(deposit) = op {\n // Credit the user inside the Zone according to the Zone\'s internal rules.\n println!(\n "Deposit of {} with metadata {:?}",\n deposit.amount, deposit.metadata,\n );\n }\n }\n }\n}\n'})}),"\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.code,{children:"Event::BlocksProcessed"})," fires once per ingested block, whether live or backfilled, and only carries finalized items at or below LIB, so deposits surfaced here cannot be re-orged off the chain."]}),"\n"]}),"\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsx)(n.p,{children:"Credit the user inside the Zone according to the Zone's internal state transition function rules."}),"\n"]}),"\n"]}),"\n",(0,s.jsx)(n.h2,{id:"step-3-submit-a-withdrawal",children:"Step 3: Submit a withdrawal"}),"\n",(0,s.jsx)(n.p,{children:"The process of withdrawing funds is different for single-sequencer Zones and multiple-sequencer Zones with a withdraw threshold greater than one."}),"\n",(0,s.jsx)(n.h3,{id:"option-1-submit-a-single-sequencer-withdrawal",children:"Option 1: Submit a single-sequencer withdrawal"}),"\n",(0,s.jsxs)(n.p,{children:["A withdrawal is initiated inside the zone and lands on-chain as a signed ",(0,s.jsx)(n.a,{href:"https://app.notion.com/p/nomos-tech/1-5-0-Mantle-33d261aa09df8051b0d0cd4d5ddade85?source=copy_link#5de261aa09df8321b05401f2e8dea08b",children:(0,s.jsx)(n.code,{children:"ChannelWithdraw"})})," operation. This step applies only when ",(0,s.jsx)(n.code,{children:"ChannelState.withdraw_threshold == 1"}),"."]}),"\n",(0,s.jsxs)(n.ol,{children:["\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsxs)(n.p,{children:["Describe the withdrawal by building a ",(0,s.jsx)(n.code,{children:"WithdrawArg"})," with the recipient ",(0,s.jsx)(n.code,{children:"Outputs"}),". The SDK fills in the ",(0,s.jsx)(n.code,{children:"channel_id"})," and reads the current ",(0,s.jsx)(n.code,{children:"withdraw_nonce"})," and accredited sequencer key."]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-rust",children:"use lb_core::mantle::{Note, ledger::Outputs};\nuse lb_zone_sdk::sequencer::WithdrawArg;\n\n// Describe what to withdraw: a single note to the recipient.\nlet withdraw = WithdrawArg {\n outputs: Outputs::new([Note::new(50, recipient_pk)]),\n};\n"})}),"\n"]}),"\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsxs)(n.p,{children:["Submit the inscription bundled with the withdraw by calling ",(0,s.jsx)(n.code,{children:"sequencer.handle().publish_atomic_withdraw(..)"}),"."]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-rust",children:"// Inside the drive task: submit the inscription bundled with the withdraw.\nlet (result, checkpoint) = sequencer.handle().publish_atomic_withdraw(\n inscription_payload, // the Zone block this withdraw goes with\n vec![withdraw],\n)?;\n"})}),"\n",(0,s.jsx)(n.p,{children:"Because the inscription and the withdrawal share one transaction, they become adopted, orphaned, or finalized as a unit, so the Zone block recording the withdrawal and the on-chain debit cannot drift apart."}),"\n",(0,s.jsx)(n.admonition,{type:"info"}),"\n"]}),"\n"]}),"\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.code,{children:"publish_atomic_withdraw"})," returns the ",(0,s.jsx)(n.code,{children:"PublishResult"})," synchronously. For a single-signature bundle, ",(0,s.jsx)(n.code,{children:"PublishResult.tx"})," is a ",(0,s.jsx)(n.code,{children:"PendingTx::AtomicWithdraw(AtomicWithdrawInfo)"})," carrying the inscription and the bundled withdraw operations.\n:::"]}),"\n",(0,s.jsxs)(n.ol,{children:["\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsxs)(n.p,{children:["Check the finalized transactions in ",(0,s.jsx)(n.code,{children:"Event::BlocksProcessed.finalized"})," against the pending transaction's ",(0,s.jsx)(n.code,{children:"AtomicWithdrawInfo.tx_hash"}),". Because it is a bundle, both the inscription and the withdrawal will appear in the same ",(0,s.jsx)(n.code,{children:"tx.ops"})," once the withdrawal is finalized."]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-rust",children:'use lb_zone_sdk::sequencer::{Event, FinalizedOp};\n\nif let Event::BlocksProcessed { finalized, .. } = event {\n for tx in finalized {\n for op in tx.ops {\n match op {\n FinalizedOp::Inscription(info) => {\n // The Zone block carried with the withdrawal.\n println!("Inscribed {:?} in tx {:?}", info.this_msg, info.tx_hash);\n\n // Check if info.tx_hash matches the pending tx_hash\n }\n FinalizedOp::Withdraw(withdrawal) => {\n // The on-chain debit.\n println!("Withdrawn {:?} in tx {:?}", withdrawal.op, withdrawal.tx_hash);\n }\n FinalizedOp::Deposit(_) => {}\n }\n }\n }\n}\n'})}),"\n"]}),"\n"]}),"\n",(0,s.jsx)(n.h3,{id:"option-2-submit-a-multi-sequencer-withdrawal",children:"Option 2: Submit a multi-sequencer withdrawal"}),"\n",(0,s.jsxs)(n.p,{children:["When ",(0,s.jsx)(n.code,{children:"withdraw_threshold > 1"}),", no single sequencer can authorize a withdrawal alone. The proposing sequencer builds the ",(0,s.jsx)(n.code,{children:"ChannelWithdrawOp"})," directly, because it must commit to a specific ",(0,s.jsx)(n.code,{children:"withdraw_nonce"})," before sharing the unsigned transaction with the rest of the committee."]}),"\n",(0,s.jsxs)(n.ol,{children:["\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsxs)(n.p,{children:["Read the current ",(0,s.jsx)(n.code,{children:"withdraw_nonce"})," and this sequencer's accredited-key index from the channel view."]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-rust",children:'use lb_core::mantle::{\n Op, SignedMantleTx,\n ops::{OpProof, channel::withdraw::ChannelWithdrawOp},\n};\nuse lb_core::proofs::channel_multi_sig_proof::{ChannelMultiSigProof, IndexedSignature};\n\n// 1. Read current nonce + this sequencer\'s accredited-key index from\n// the channel view.\nlet view = sequencer.subscribe_channel_view().borrow().clone();\nlet withdraw_nonce = view\n .channel\n .as_ref()\n .ok_or("channel state not yet available")?\n .withdrawal_nonce;\nlet own_key_index = view.own_key_index.ok_or("not an accredited key")?;\n'})}),"\n"]}),"\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsxs)(n.p,{children:["Build the unsigned transaction and obtain this sequencer's own signature by calling ",(0,s.jsx)(n.code,{children:"handle.prepare_tx(ops, inscription)"}),"."]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-rust",children:"// 2. Build the unsigned tx and get this sequencer's own signature back.\nlet withdraw = ChannelWithdrawOp {\n channel_id,\n outputs,\n withdraw_nonce,\n};\nlet (tx, msg_id, own_sig) = sequencer.handle().prepare_tx(\n [Op::ChannelWithdraw(withdraw)].into(),\n inscription_payload,\n)?;\n"})}),"\n"]}),"\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsxs)(n.p,{children:["Share the unsigned transaction with the other accredited signers and collect their ",(0,s.jsx)(n.code,{children:"IndexedSignature"}),"s. Each signer calls ",(0,s.jsx)(n.code,{children:"handle.sign_tx(&tx)"})," on the transaction the first sequencer proposes."]}),"\n",(0,s.jsx)(n.admonition,{type:"info"}),"\n"]}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"Defining the committee transport - how proposals and signatures are exchanged - is outside the Zone SDK's scope.\n:::"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-rust",children:"// 3. Hand `tx` to the other accredited signers and collect their\n// `IndexedSignature`s. Transport is application-defined.\nlet signatures: Vec<IndexedSignature> = collect_signatures_from_committee(\n &tx,\n IndexedSignature::new(own_key_index, own_sig.clone()),\n).await?;\n"})}),"\n",(0,s.jsxs)(n.ol,{children:["\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsxs)(n.p,{children:["Assemble the threshold proof and submit it once you have gathered ",(0,s.jsx)(n.code,{children:"ChannelState.withdraw_threshold"})," signatures, by calling ",(0,s.jsx)(n.code,{children:"handle.submit_signed_tx(signed_tx, msg_id)"}),"."]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-rust",children:"// 4. Assemble the threshold proof and submit.\nlet withdraw_proof = ChannelMultiSigProof::new(signatures)?;\nlet signed_tx = SignedMantleTx::new(\n tx,\n vec![\n OpProof::ChannelMultiSigProof(withdraw_proof),\n OpProof::Ed25519Sig(own_sig),\n ],\n)?;\nlet (result, checkpoint) = sequencer\n .handle()\n .submit_signed_tx(signed_tx, msg_id)?;\n"})}),"\n",(0,s.jsxs)(n.p,{children:["Keep the result and use the returned ",(0,s.jsx)(n.code,{children:"tx_hash"})," to identify the bundle. Unlike the single-sig flow, the SDK treats the caller-built transaction as opaque, so ",(0,s.jsx)(n.code,{children:"PublishResult.tx"})," is ",(0,s.jsx)(n.code,{children:"PendingTx::Inscription(InscriptionInfo)"})," regardless of the underlying ops."]}),"\n"]}),"\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsxs)(n.p,{children:["Match the finalized transaction by ",(0,s.jsx)(n.code,{children:"tx_hash"})," once it appears in ",(0,s.jsx)(n.code,{children:"Event::BlocksProcessed.finalized"}),", the same way as in the single-signature flow in Option 1."]}),"\n"]}),"\n"]}),"\n",(0,s.jsx)(n.h2,{id:"step-4-recover-from-a-reorg",children:"Step 4: Recover from a reorg"}),"\n",(0,s.jsxs)(n.p,{children:["A reorg can orphan the parent inscription of a withdrawal submitted via ",(0,s.jsx)(n.code,{children:"publish_atomic_withdraw"}),", which invalidates the original signed transaction."]}),"\n",(0,s.jsxs)(n.ol,{children:["\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsxs)(n.p,{children:["Watch for orphaned transactions in the ",(0,s.jsx)(n.code,{children:"channel_update"})," field of ",(0,s.jsx)(n.code,{children:"Event::BlocksProcessed"}),". The abandoned transaction appears in ",(0,s.jsx)(n.code,{children:"channel_update.orphaned"}),"."]}),"\n"]}),"\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsxs)(n.p,{children:["Reconstruct the original ",(0,s.jsx)(n.code,{children:"WithdrawArg"}),"s from the orphaned bundle and re-call ",(0,s.jsx)(n.code,{children:"publish_atomic_withdraw"})," with the same inscription payload; the SDK refills the inscription parent and the ",(0,s.jsx)(n.code,{children:"withdraw_nonce"})," from the current on-chain state."]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-rust",children:"use lb_zone_sdk::sequencer::{Event, OrphanedTx, WithdrawArg};\n\nif let Event::BlocksProcessed { channel_update, .. } = event {\n for tx in channel_update.orphaned {\n if let OrphanedTx::AtomicWithdraw(info) = tx {\n\n // Rebuild the withdraw args from the orphaned bundle.\n let withdraws = info\n .withdraws\n .into_iter()\n .map(|w| WithdrawArg { outputs: w.op.outputs })\n .collect();\n\n // Republish with the same inscription payload; the SDK\n // refreshes the parent and withdraw_nonce automatically.\n let (result, checkpoint) = sequencer.handle().publish_atomic_withdraw(\n info.inscription.payload,\n withdraws,\n )?;\n\n // Keep `result` + `checkpoint` exactly as on the original publish.\n }\n }\n}\n"})}),"\n"]}),"\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsxs)(n.p,{children:["Keep the new ",(0,s.jsx)(n.code,{children:"result"})," and ",(0,s.jsx)(n.code,{children:"checkpoint"})," exactly as you did for the original publish to compare it to"]}),"\n"]}),"\n"]}),"\n",(0,s.jsx)(n.h2,{id:"frequently-asked-questions",children:"Frequently asked questions"}),"\n",(0,s.jsx)(n.h3,{id:"does-the-reorg-recovery-path-work-for-multi-sequencer-withdrawals",children:"Does the reorg recovery path work for multi-sequencer withdrawals?"}),"\n",(0,s.jsx)(n.p,{children:"No. The reorg-aware recovery path described in Step 5 is not supported for multi-sig withdrawals at the moment and is planned for a future release."})]})}function h(e={}){let{wrapper:n}={...(0,o.R)(),...e.components};return n?(0,s.jsx)(n,{...e,children:(0,s.jsx)(l,{...e})}):l(e)}},8453(e,n,i){i.d(n,{R:()=>a,x:()=>r});var t=i(6540);let s={},o=t.createContext(s);function a(e){let n=t.useContext(o);return t.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function r(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(s):e.components||s:a(e.components),t.createElement(o.Provider,{value:n},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/bcd62c17.0b2d25bb.js b/assets/js/bcd62c17.0b2d25bb.js new file mode 100644 index 00000000..2546f87c --- /dev/null +++ b/assets/js/bcd62c17.0b2d25bb.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunklogos_docs=self.webpackChunklogos_docs||[]).push([["266"],{2029(e,n,t){t.r(n),t.d(n,{metadata:()=>s,default:()=>h,frontMatter:()=>o,contentTitle:()=>l,toc:()=>c,assets:()=>r});var s=JSON.parse('{"id":"lez/get-started/quickstart-for-the-logos-execution-zone-wallet","title":"Quickstart for the Logos Execution Zone wallet","description":"Set up the wallet, connect to a sequencer, and run a minimal transfer flow.","source":"@site/docs/lez/get-started/quickstart-for-the-logos-execution-zone-wallet.md","sourceDirName":"lez/get-started","slug":"/lez/get-started/lez-quickstart","permalink":"/lez/get-started/lez-quickstart","draft":false,"unlisted":false,"editUrl":"https://github.com/logos-co/logos-docs/tree/main/docs/lez/get-started/quickstart-for-the-logos-execution-zone-wallet.md","tags":[],"version":"current","sidebarPosition":2,"frontMatter":{"title":"Quickstart for the Logos Execution Zone wallet","doc_type":"quickstart","product":"lez","topics":["wallet","sequencer","Logos Execution Zone","LEZ","Logos Execution Environment","LEE","zero-knowledge proofs","ZKPs"],"authors":["jorge-campo"],"owner":"logos","doc_version":2,"slug":"lez-quickstart","sidebar_position":2},"sidebar":"lezSidebar","previous":{"title":"Introduction to the Logos Execution Zone","permalink":"/lez/get-started/introduction-to-the-logos-execution-zone"},"next":{"title":"Connect the wallet CLI to the LEZ testnet","permalink":"/lez/get-started/connect-wallet-cli-to-lez-testnet"}}'),i=t(4848),a=t(8453);let o={title:"Quickstart for the Logos Execution Zone wallet",doc_type:"quickstart",product:"lez",topics:["wallet","sequencer","Logos Execution Zone","LEZ","Logos Execution Environment","LEE","zero-knowledge proofs","ZKPs"],authors:["jorge-campo"],owner:"logos",doc_version:2,slug:"lez-quickstart",sidebar_position:2},l="Quickstart for the Logos Execution Zone wallet",r={},c=[{value:"Set up the wallet, connect to a sequencer, and run a minimal transfer flow.",id:"set-up-the-wallet-connect-to-a-sequencer-and-run-a-minimal-transfer-flow",level:4},{value:"Before you start",id:"before-you-start",level:2},{value:"Step 1: Install the prerequisites",id:"step-1-install-the-prerequisites",level:2},{value:"Install system build dependencies",id:"install-system-build-dependencies",level:3},{value:"Install Rust and RISC Zero components",id:"install-rust-and-risc-zero-components",level:3},{value:"Set up the <code>wallet</code> binary prerequisites and build the wallet",id:"set-up-the-wallet-binary-prerequisites-and-build-the-wallet",level:3},{value:"Step 2: Start the LEZ sequencer in standalone mode",id:"step-2-start-the-lez-sequencer-in-standalone-mode",level:2},{value:"Step 3 (optional): Configure the wallet home directory",id:"step-3-optional-configure-the-wallet-home-directory",level:2},{value:"Step 4: Initialise the wallet local storage and verify connectivity",id:"step-4-initialise-the-wallet-local-storage-and-verify-connectivity",level:2},{value:"Step 5: Complete a minimal wallet flow",id:"step-5-complete-a-minimal-wallet-flow",level:2},{value:"Create and initialise the sender public account",id:"create-and-initialise-the-sender-public-account",level:3},{value:"Claim funds using the Pi\xf1ata faucet",id:"claim-funds-using-the-pi\xf1ata-faucet",level:3},{value:"Create and fund the recipient public account",id:"create-and-fund-the-recipient-public-account",level:3},{value:"Next steps",id:"next-steps",level:2}];function d(e){let n={a:"a",admonition:"admonition",code:"code",h1:"h1",h2:"h2",h3:"h3",h4:"h4",header:"header",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,a.R)(),...e.components};return(0,i.jsxs)(i.Fragment,{children:[(0,i.jsx)(n.header,{children:(0,i.jsx)(n.h1,{id:"quickstart-for-the-logos-execution-zone-wallet",children:"Quickstart for the Logos Execution Zone wallet"})}),"\n",(0,i.jsx)(n.h4,{id:"set-up-the-wallet-connect-to-a-sequencer-and-run-a-minimal-transfer-flow",children:"Set up the wallet, connect to a sequencer, and run a minimal transfer flow."}),"\n",(0,i.jsx)(n.admonition,{type:"warning",children:(0,i.jsx)(n.p,{children:"This page should be accurate for the specific version referenced in this doc, but it may not have been run end-to-end as written. Expect minor gaps (for example, missing environment details) and be prepared to troubleshoot. We are actively working to complete and verify this content."})}),"\n",(0,i.jsx)(n.admonition,{type:"info",children:(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Permissions"}),": No special permissions required."]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Product"}),": ",(0,i.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#logos-execution-zone",children:"Logos Execution Zone"})," wallet CLI."]}),"\n"]})}),"\n",(0,i.jsxs)(n.admonition,{type:"tip",children:[(0,i.jsxs)(n.p,{children:["A public ",(0,i.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#lez",children:"LEZ"})," testnet is available. This quickstart runs against a local sequencer you start yourself, but you can browse live testnet activity in the block explorer, and the testnet sequencer is reachable at its public RPC endpoint."]}),(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:["Explorer: ",(0,i.jsx)(n.a,{href:"https://explorer.testnet.lez.logos.co/",children:"https://explorer.testnet.lez.logos.co/"})]}),"\n",(0,i.jsxs)(n.li,{children:["Sequencer RPC: ",(0,i.jsx)(n.a,{href:"https://testnet.lez.logos.co/",children:"https://testnet.lez.logos.co/"})]}),"\n"]})]}),"\n",(0,i.jsx)(n.p,{children:"The Logos Execution Zone (LEZ, for short) is a programmable blockchain that records transactions, maintains public on-chain state, and exposes a sequencer endpoint that clients (like a wallet) can submit transactions to."}),"\n",(0,i.jsxs)(n.p,{children:["LEZ separates ",(0,i.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#account",children:"account"})," state into public (visible, on-chain) and private (hidden, off-chain). You choose which one you are using by creating a public or ",(0,i.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#private-account",children:"private account"})," and using it in transactions. This ability to maintain a public and private state is provided by the ",(0,i.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#logos-execution-environment",children:"Logos Execution Environment"})," (",(0,i.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#lee",children:"LEE"}),"), that defines what an account is, how transactions are structured, and how executions are validated when some data must remain private. You can think of LEZ as the blockchain you connect to and where transactions are recorded, and LEE as the execution model that powers it."]}),"\n",(0,i.jsx)(n.admonition,{type:"info",children:(0,i.jsxs)(n.p,{children:["This quickstart covers the public wallet flow only so you can get set up quickly. Privacy-preserving transfers require local proof generation and take longer to run. For the private workflow, see ",(0,i.jsx)(n.a,{href:"/lez/transfer-tokens/transfer-native-tokens-on-the-logos-execution-zone",children:"Transfer native tokens on the Logos Execution Zone"}),"."]})}),"\n",(0,i.jsx)(n.p,{children:"When a transaction touches the private state, the client runs the private part locally using your private keys and local client data, producing a zero-knowledge proof (ZKP). Validators verify the proof and accept the state update (for example, updating public balances), so the network stays correct even though the private data is never published."}),"\n",(0,i.jsx)(n.admonition,{type:"info",children:(0,i.jsx)(n.p,{children:"In the context of the Logos Execution Zone, a zero-knowledge proof (ZKP) is a cryptographic proof that lets a blockchain client, such as a wallet, prove a private transaction followed LEE\u2019s rules without revealing the private inputs (like balances). Using ZKPs, LEZ can safely accept the resulting state update and keep the public chain consistent with private execution, even though the network never sees the private values."})}),"\n",(0,i.jsx)(n.p,{children:"In this quickstart, you install the wallet tooling, connect to a local sequencer endpoint, and complete a minimal transfer flow with balance checks. In wallet terms, the wallet client is your control panel for the system: you install it, create and manage public or private accounts, sync private state, and send commands."}),"\n",(0,i.jsx)(n.h2,{id:"before-you-start",children:"Before you start"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsx)(n.li,{children:"This guide is intended for a developer audience with CLI-first workflow familiarity."}),"\n",(0,i.jsx)(n.li,{children:"You should have a basic knowledge of blockchain concepts like accounts, transactions, and balances."}),"\n",(0,i.jsx)(n.li,{children:"You will use the Rust toolchain to complete this tutorial, but you don't need prior Rust experience."}),"\n",(0,i.jsx)(n.li,{children:"Commands assume a bash-compatible shell."}),"\n"]}),"\n",(0,i.jsx)(n.h2,{id:"step-1-install-the-prerequisites",children:"Step 1: Install the prerequisites"}),"\n",(0,i.jsxs)(n.p,{children:["To run the LEZ wallet CLI, you first need to install system dependencies, the Rust toolchain, and the ",(0,i.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#logos-blockchain",children:"Logos Blockchain"})," circuits files."]}),"\n",(0,i.jsx)(n.h3,{id:"install-system-build-dependencies",children:"Install system build dependencies"}),"\n",(0,i.jsx)(n.p,{children:"Install the build prerequisites you need to compile the sequencer and wallet."}),"\n",(0,i.jsx)(n.admonition,{type:"tip",children:(0,i.jsx)(n.p,{children:"These prerequisites include a working C toolchain and linker on your machine. You may already have these installed if you have experience building software from source."})}),"\n",(0,i.jsx)(n.p,{children:"Choose the instructions for your operating system:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:"# Ubuntu / Debian\nsudo apt update\napt install git curl build-essential clang libclang-dev pkg-config libssl-dev\n"})}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:"# Fedora\nsudo dnf install git curl gcc glibc-devel clang clang-devel pkgconf-pkg-config openssl-devel llvm-libs\n"})}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:"# macOS\nxcode-select --install\nbrew install pkg-config openssl\n"})}),"\n",(0,i.jsx)(n.h3,{id:"install-rust-and-risc-zero-components",children:"Install Rust and RISC Zero components"}),"\n",(0,i.jsx)(n.admonition,{type:"info",children:(0,i.jsx)(n.p,{children:"Rust is the language used for wallet development, while RISC Zero is the proof toolchain used to generate the ZKPs."})}),"\n",(0,i.jsxs)(n.ol,{children:["\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsxs)(n.p,{children:["Install Rust with ",(0,i.jsx)(n.code,{children:"rustup"}),":"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:"# Install the official Rust compiler. -y installs non-interactively (required in CI/Docker, where there is no TTY for the rustup TUI).\ncurl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable\n"})}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsx)(n.p,{children:"Install the RISC Zero components:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:'# Source the env file under $HOME/.cargo and install the RISC Zero component\n. "$HOME/.cargo/env" \ncurl -L https://risczero.com/install | bash \n'})}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsxs)(n.p,{children:["Restart your shell to ensure the ",(0,i.jsx)(n.code,{children:"cargo"})," and ",(0,i.jsx)(n.code,{children:"rzup"})," commands are available."]}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsxs)(n.p,{children:["Add the RISC Zero components to your Rust toolchain with ",(0,i.jsx)(n.code,{children:"rzup"}),":"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:"rzup install\n"})}),"\n"]}),"\n"]}),"\n",(0,i.jsxs)(n.h3,{id:"set-up-the-wallet-binary-prerequisites-and-build-the-wallet",children:["Set up the ",(0,i.jsx)(n.code,{children:"wallet"})," binary prerequisites and build the wallet"]}),"\n",(0,i.jsxs)(n.p,{children:["The Logos Blockchain repository provides a script that downloads a circuits release required by the ",(0,i.jsx)(n.code,{children:"wallet"})," build."]}),"\n",(0,i.jsx)(n.admonition,{type:"tip",children:(0,i.jsxs)(n.p,{children:['"Circuits" are prebuilt files used for privacy-preserving execution (zero-knowledge proofs). Even though the quickstart flow uses public transactions, the current ',(0,i.jsx)(n.code,{children:"wallet"})," build still requires these files to be present locally."]})}),"\n",(0,i.jsxs)(n.ol,{children:["\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsx)(n.p,{children:"Create a workspace folder and clone the Logos Blockchain repository:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:"mkdir -p ~/logos/src\ncd ~/logos/src\ngit clone https://github.com/logos-blockchain/logos-blockchain.git\n"})}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsx)(n.p,{children:"Run the script to download the circuits release:"}),"\n",(0,i.jsx)(n.admonition,{type:"info"}),"\n"]}),"\n"]}),"\n",(0,i.jsxs)(n.p,{children:["This script downloads ",(0,i.jsx)(n.code,{children:"logos-blockchain-circuits-<version>-<platform>.tar.gz"})," and installs it under ",(0,i.jsx)(n.code,{children:"~/.logos-blockchain-circuits"})," by default.\n:::"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:"cd logos-blockchain\n./scripts/setup-logos-blockchain-circuits.sh\n"})}),"\n",(0,i.jsxs)(n.ol,{children:["\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsx)(n.p,{children:"From the same workspace folder, clone the Logos Execution Zone repository:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:"cd ~/logos/src\ngit clone https://github.com/logos-blockchain/logos-execution-zone.git\ncd logos-execution-zone\ngit checkout v0.2.0\n"})}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsx)(n.p,{children:"From the repository root, install the wallet CLI:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:"cargo install --path lez/wallet --force\n"})}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsxs)(n.p,{children:["Confirm that the ",(0,i.jsx)(n.code,{children:"wallet"})," command is available:"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:"wallet help\n"})}),"\n"]}),"\n"]}),"\n",(0,i.jsx)(n.h2,{id:"step-2-start-the-lez-sequencer-in-standalone-mode",children:"Step 2: Start the LEZ sequencer in standalone mode"}),"\n",(0,i.jsx)(n.p,{children:"Open a new terminal window and start the LEZ sequencer from the root of the Logos Execution Zone repository:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:"cd ~/logos/src/logos-execution-zone\nRUST_LOG=info cargo run --features standalone -p sequencer_service lez/sequencer/service/configs/debug/sequencer_config.json\n"})}),"\n",(0,i.jsx)(n.admonition,{type:"info",children:(0,i.jsx)(n.p,{children:"This quickstart uses standalone mode, which runs only the LEZ sequencer locally. The full local stack also runs a Logos Blockchain node and the indexer service for development and block exploration, but it adds extra components and is covered separately."})}),"\n",(0,i.jsxs)(n.p,{children:["You should see the sequencer starting up at ",(0,i.jsx)(n.code,{children:"localhost:3040"})," and logging information to the terminal:"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:'[2026-02-24T16:27:58Z INFO sequencer_runner] HTTP server started\n[2026-02-24T16:27:58Z INFO actix_server::server] Tokio runtime found; starting in existing Tokio runtime\n[2026-02-24T16:27:58Z INFO actix_server::server] starting service: "actix-web-service-0.0.0.0:3040", workers: 4, listening on: 0.0.0.0:3040\n[2026-02-24T16:27:58Z INFO sequencer_runner] Starting main sequencer loop\n[2026-02-24T16:27:58Z INFO sequencer_runner] Starting pending block retry loop\n[2026-02-24T16:27:58Z INFO sequencer_runner] Starting bedrock block listening loop\n[2026-02-24T16:27:58Z INFO sequencer_runner] Sequencer running. Monitoring concurrent tasks...\n'})}),"\n",(0,i.jsx)(n.h2,{id:"step-3-optional-configure-the-wallet-home-directory",children:"Step 3 (optional): Configure the wallet home directory"}),"\n",(0,i.jsxs)(n.p,{children:['The wallet reads its configuration from a "wallet home" directory. If the ',(0,i.jsx)(n.code,{children:"NSSA_WALLET_HOME_DIR"})," environment variable is not set, it falls back to ",(0,i.jsx)(n.code,{children:"~/.nssa/wallet"}),"."]}),"\n",(0,i.jsxs)(n.p,{children:["If you want the wallet to initialise in a different location, set the variable before continuing. For example, to set the wallet home to a ",(0,i.jsx)(n.code,{children:".wallet-home"})," folder in the current directory, run:"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:'export NSSA_WALLET_HOME_DIR="$PWD/.wallet-home"\n'})}),"\n",(0,i.jsx)(n.admonition,{type:"info",children:(0,i.jsxs)(n.p,{children:["The ",(0,i.jsx)(n.code,{children:"wallet help"})," output incorrectly states that ",(0,i.jsx)(n.code,{children:"NSSA_WALLET_HOME_DIR"}),' "must be set." In practice, the binary falls back to ',(0,i.jsx)(n.code,{children:"~/.nssa/wallet"})," when unset, as described above. The mismatch is in the help string."]})}),"\n",(0,i.jsx)(n.h2,{id:"step-4-initialise-the-wallet-local-storage-and-verify-connectivity",children:"Step 4: Initialise the wallet local storage and verify connectivity"}),"\n",(0,i.jsxs)(n.p,{children:["The wallet persistent storage is defined by the ",(0,i.jsx)(n.code,{children:"storage.json"})," file. When you run any ",(0,i.jsx)(n.code,{children:"wallet"})," subcommand, the wallet checks whether ",(0,i.jsx)(n.code,{children:"storage.json"})," exists in the wallet home directory. If it does not exist, it requires a password to initialise the wallet storage."]}),"\n",(0,i.jsx)(n.admonition,{type:"tip",children:(0,i.jsx)(n.p,{children:"Leave the sequencer running in the other terminal window while you initialise the wallet storage."})}),"\n",(0,i.jsxs)(n.p,{children:["Run a ",(0,i.jsx)(n.code,{children:"wallet"})," command to initialise the storage. Use the built-in health check:"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:"wallet check-health\n"})}),"\n",(0,i.jsxs)(n.p,{children:["If the wallet storage was not previously initialised, this command prints ",(0,i.jsx)(n.code,{children:"Persistent storage not found, need to execute setup"}),", and prompts you to create a password. You can choose any password you like, but make sure to remember it, as you will need it to access the wallet in the future."]}),"\n",(0,i.jsx)(n.admonition,{type:"warning",children:(0,i.jsx)(n.p,{children:"The wallet uses this password as a seed to deterministically generate your public and private key trees. The wallet stores the derived key material and local state in storage.json under the wallet home directory."})}),"\n",(0,i.jsx)(n.h2,{id:"step-5-complete-a-minimal-wallet-flow",children:"Step 5: Complete a minimal wallet flow"}),"\n",(0,i.jsx)(n.p,{children:"In this flow, you create and initialise an account, claim testnet funds, send a transfer, and confirm resulting balances."}),"\n",(0,i.jsxs)(n.p,{children:["In this task, wallet account and transfer commands interact with the authenticated-transfer ",(0,i.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#program",children:"program"}),", and sequencer processing determines the resulting account state. Public and private account paths share command patterns, while private paths can include local proof generation."]}),"\n",(0,i.jsx)(n.h3,{id:"create-and-initialise-the-sender-public-account",children:"Create and initialise the sender public account"}),"\n",(0,i.jsxs)(n.ol,{children:["\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsxs)(n.p,{children:["Create a sender ",(0,i.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#public-account",children:"public account"})," and record the ",(0,i.jsx)(n.code,{children:"account_id"})," value:"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:"cd ~/logos/src/logos-execution-zone\nwallet account new public\n"})}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsxs)(n.p,{children:["Using the sender public ",(0,i.jsx)(n.code,{children:"account_id"})," from the previous step, check the sender status:"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:"wallet account get --account-id <sender_public_account_id>\n"})}),"\n",(0,i.jsx)(n.p,{children:"Example:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:"wallet account get --account-id Public/14TYHiuzKiNR1ydETpr9mJMkjY6jf1hQFZ11d3X8Tc7N\n"})}),"\n",(0,i.jsxs)(n.p,{children:["You should see ",(0,i.jsx)(n.code,{children:"Account is Uninitialized"})," in the output. New accounts start uninitialised, so no program owns them yet. A program can claim an uninitialised account (for example, the authenticated-transfer program or the ",(0,i.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#token-program",children:"token program"}),"). After a program claims an account, only that program can modify the account state. LEZ makes one exception for account credits, where any program can credit native tokens to any account. For account debits, LEZ requires the owning program."]}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsx)(n.p,{children:"Initialise the sender account, then check the updated state:"}),"\n",(0,i.jsx)(n.admonition,{type:"info"}),"\n"]}),"\n"]}),"\n",(0,i.jsxs)(n.p,{children:["Running ",(0,i.jsx)(n.code,{children:"wallet auth-transfer init"})," initialises the sender account under the authenticated-transfer program, so the account can debit native tokens when you send transfers.\n:::"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:"wallet auth-transfer init --account-id <sender_public_account_id>\n"})}),"\n",(0,i.jsxs)(n.p,{children:["In the output, you should see ",(0,i.jsx)(n.code,{children:'status: "Transaction submitted"'}),", and the transaction hash. If you change to the terminal session where the sequencer is running, you can see a message similar to this: ",(0,i.jsx)(n.code,{children:"Validated transaction with hash <hash_id>, including it in block"}),"."]}),"\n",(0,i.jsxs)(n.ol,{children:["\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsx)(n.p,{children:"Check the account updated state:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:"wallet account get --account-id <sender_public_account_id>\n"})}),"\n",(0,i.jsxs)(n.p,{children:["In the output you should see ",(0,i.jsx)(n.code,{children:"Account owned by authenticated transfer program"}),", with ",(0,i.jsx)(n.code,{children:'"balance":0'}),"."]}),"\n"]}),"\n"]}),"\n",(0,i.jsx)(n.h3,{id:"claim-funds-using-the-pi\xf1ata-faucet",children:"Claim funds using the Pi\xf1ata faucet"}),"\n",(0,i.jsx)(n.p,{children:'"Pi\xf1ata" is the name of the LEZ-specific testnet faucet program that funds accounts with native tokens.'}),"\n",(0,i.jsxs)(n.ol,{children:["\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsx)(n.p,{children:"Fund the sender account via Pi\xf1ata:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:"# This may take a few seconds to complete\nwallet pinata claim --to <sender_public_account_id>\n"})}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsx)(n.p,{children:"Check the sender account balance:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:"wallet account get --account-id <sender_public_account_id>\n"})}),"\n",(0,i.jsxs)(n.p,{children:["In the output you should see ",(0,i.jsx)(n.code,{children:"Account owned by authenticated transfer program"}),", with a ",(0,i.jsx)(n.code,{children:'"balance":150'}),"."]}),"\n"]}),"\n"]}),"\n",(0,i.jsx)(n.h3,{id:"create-and-fund-the-recipient-public-account",children:"Create and fund the recipient public account"}),"\n",(0,i.jsxs)(n.ol,{children:["\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsxs)(n.p,{children:["Create a recipient public account and record the ",(0,i.jsx)(n.code,{children:"account_id"})," value. Complete this step in the same terminal session as the sender account commands to avoid exporting ",(0,i.jsx)(n.code,{children:"NSSA_WALLET_HOME_DIR"})," again."]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:"wallet account new public\n"})}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsx)(n.p,{children:"Send 37 tokens from sender to recipient:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:"wallet auth-transfer send \\\n --from <sender_public_account_id> \\\n --to <recipient_public_account_id> \\\n --amount 37\n"})}),"\n",(0,i.jsx)(n.p,{children:"Example:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:"wallet auth-transfer send \\\n --from Public/14TYHiuzKiNR1ydETpr9mJMkjY6jf1hQFZ11d3X8Tc7N \\\n --to Public/74zHyMW81mtfcd6VMaLnpnAna8k2V4AN2Ygyy9LcEAQQ \\\n --amount 37\n"})}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsx)(n.p,{children:"Check sender and recipient balances:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:"# Sender account\nwallet account get --account-id <sender_public_account_id>\n"})}),"\n"]}),"\n"]}),"\n",(0,i.jsxs)(n.p,{children:["This should show a ",(0,i.jsx)(n.code,{children:'"balance":113'})," (150 - 37 = 113)."]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:"# Recipient account\nwallet account get --account-id <recipient_public_account_id>\n"})}),"\n",(0,i.jsxs)(n.p,{children:["This should show a ",(0,i.jsx)(n.code,{children:'"balance":37'}),"."]}),"\n",(0,i.jsx)(n.h2,{id:"next-steps",children:"Next steps"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsx)(n.li,{children:(0,i.jsx)(n.a,{href:"/lez/transfer-tokens/transfer-native-tokens-on-the-logos-execution-zone",children:"Transfer native tokens on the Logos Execution Zone"})}),"\n",(0,i.jsx)(n.li,{children:(0,i.jsx)(n.a,{href:"/lez/transfer-tokens/create-and-transfer-custom-tokens-on-the-logos-execution-zone",children:"Create and transfer custom tokens on the Logos Execution Zone"})}),"\n"]})]})}function h(e={}){let{wrapper:n}={...(0,a.R)(),...e.components};return n?(0,i.jsx)(n,{...e,children:(0,i.jsx)(d,{...e})}):d(e)}},8453(e,n,t){t.d(n,{R:()=>o,x:()=>l});var s=t(6540);let i={},a=s.createContext(i);function o(e){let n=s.useContext(a);return s.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function l(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(i):e.components||i:o(e.components),s.createElement(a.Provider,{value:n},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/bcf9457e.2ae6fe79.js b/assets/js/bcf9457e.2ae6fe79.js new file mode 100644 index 00000000..06a3db33 --- /dev/null +++ b/assets/js/bcf9457e.2ae6fe79.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunklogos_docs=self.webpackChunklogos_docs||[]).push([["3557"],{470(e,o,n){n.r(o),n.d(o,{metadata:()=>t,default:()=>h,frontMatter:()=>l,contentTitle:()=>r,toc:()=>d,assets:()=>c});var t=JSON.parse('{"id":"get-started/glossary","title":"Glossary","description":"A list of some common terms and phrases specific to the Logos ecosystem.","source":"@site/docs/get-started/glossary.md","sourceDirName":"get-started","slug":"/get-started/glossary","permalink":"/get-started/glossary","draft":false,"unlisted":false,"editUrl":"https://github.com/logos-co/logos-docs/tree/main/docs/get-started/glossary.md","tags":[],"version":"current","sidebarPosition":3,"frontMatter":{"sidebar_position":3},"sidebar":"getStartedSidebar","previous":{"title":"Logos ecosystem repositories","permalink":"/get-started/logos-ecosystem-repositories"}}'),s=n(4848),i=n(8453);let l={sidebar_position:3},r="Glossary",c={},d=[{value:"A",id:"a",level:2},{value:"Account",id:"account",level:3},{value:"B",id:"b",level:2},{value:"Basecamp",id:"basecamp",level:3},{value:"Bedrock",id:"bedrock",level:3},{value:"Blend Network",id:"blend-network",level:3},{value:"Blend node",id:"blend-node",level:3},{value:"Blend Protocol",id:"blend-protocol",level:3},{value:"Bootstrapping",id:"bootstrapping",level:3},{value:"C",id:"c",level:2},{value:"Catalogue",id:"catalogue",level:3},{value:"Channel",id:"channel",level:3},{value:"CID",id:"cid",level:3},{value:"Codex",id:"codex",level:3},{value:"Content topic",id:"content-topic",level:3},{value:"Core module",id:"core-module",level:3},{value:"Core node",id:"core-node",level:3},{value:"Cover traffic",id:"cover-traffic",level:3},{value:"Cryptarchia",id:"cryptarchia",level:3},{value:"D",id:"d",level:2},{value:"Delivery module",id:"delivery-module",level:3},{value:"DNS discovery",id:"dns-discovery",level:3},{value:"E",id:"e",level:2},{value:"ENR",id:"enr",level:3},{value:"Epoch",id:"epoch",level:3},{value:"Ethereum Node Record",id:"ethereum-node-record",level:3},{value:"F",id:"f",level:2},{value:"Filter",id:"filter",level:3},{value:"G",id:"g",level:2},{value:"GMS",id:"gms",level:3},{value:"GossipSub",id:"gossipsub",level:3},{value:"Group Master Secret",id:"group-master-secret",level:3},{value:"I",id:"i",level:2},{value:"Inscription",id:"inscription",level:3},{value:"L",id:"l",level:2},{value:"LEE",id:"lee",level:3},{value:"LEZ",id:"lez",level:3},{value:"<code>lgpm</code>",id:"lgpm",level:3},{value:"<code>lgpd</code>",id:"lgpd",level:3},{value:"<code>.lgx</code>",id:"lgx",level:3},{value:"Light node",id:"light-node",level:3},{value:"Light push",id:"light-push",level:3},{value:"LIP",id:"lip",level:3},{value:"<code>lm</code>",id:"lm",level:3},{value:"Locator",id:"locator",level:3},{value:"Logos Blockchain",id:"logos-blockchain",level:3},{value:"Logos Chat",id:"logos-chat",level:3},{value:"Logos Delivery",id:"logos-delivery",level:3},{value:"Logos Execution Environment",id:"logos-execution-environment",level:3},{value:"Logos Execution Zone",id:"logos-execution-zone",level:3},{value:"Logos Messaging",id:"logos-messaging",level:3},{value:"Logos node",id:"logos-node",level:3},{value:"<code>logos-module-builder</code>",id:"logos-module-builder",level:3},{value:"<code>logos-module-viewer</code>",id:"logos-module-viewer",level:3},{value:"<code>logos-standalone-app</code>",id:"logos-standalone-app",level:3},{value:"Logos Storage",id:"logos-storage",level:3},{value:"<code>logoscore</code>",id:"logoscore",level:3},{value:"LSSA",id:"lssa",level:3},{value:"M",id:"m",level:2},{value:"Mantle",id:"mantle",level:3},{value:"Message",id:"message",level:3},{value:"Mix",id:"mix",level:3},{value:"Module",id:"module",level:3},{value:"Module repo",id:"module-repo",level:3},{value:"Mostly offline",id:"mostly-offline",level:3},{value:"N",id:"n",level:2},{value:"Nescience",id:"nescience",level:3},{value:"Nomos",id:"nomos",level:3},{value:"Node key",id:"node-key",level:3},{value:"Note",id:"note",level:3},{value:"NPK",id:"npk",level:3},{value:"NSSA",id:"nssa",level:3},{value:"NSK",id:"nsk",level:3},{value:"Nullifier keys",id:"nullifier-keys",level:3},{value:"Nullifier Public Key",id:"nullifier-public-key",level:3},{value:"Nullifier Secret Key",id:"nullifier-secret-key",level:3},{value:"O",id:"o",level:2},{value:"Out-of-band",id:"out-of-band",level:3},{value:"P",id:"p",level:2},{value:"PDA",id:"pda",level:3},{value:"Peer discovery",id:"peer-discovery",level:3},{value:"Peer exchange",id:"peer-exchange",level:3},{value:"Peer ID",id:"peer-id",level:3},{value:"PoL",id:"pol",level:3},{value:"PPoS",id:"ppos",level:3},{value:"Private account",id:"private-account",level:3},{value:"Private Proof of Stake",id:"private-proof-of-stake",level:3},{value:"Program",id:"program",level:3},{value:"Program Derived Address",id:"program-derived-address",level:3},{value:"Proof of Leadership",id:"proof-of-leadership",level:3},{value:"Public account",id:"public-account",level:3},{value:"R",id:"r",level:2},{value:"Relay",id:"relay",level:3},{value:"S",id:"s",level:2},{value:"SDP",id:"sdp",level:3},{value:"Sealing keys",id:"sealing-keys",level:3},{value:"Service Declaration Protocol",id:"service-declaration-protocol",level:3},{value:"Service Reward Distribution Protocol",id:"service-reward-distribution-protocol",level:3},{value:"Slot",id:"slot",level:3},{value:"SRDP",id:"srdp",level:3},{value:"Storage module",id:"storage-module",level:3},{value:"Store",id:"store",level:3},{value:"T",id:"t",level:2},{value:"Token definition account",id:"token-definition-account",level:3},{value:"Token holding account",id:"token-holding-account",level:3},{value:"Token program",id:"token-program",level:3},{value:"Transport",id:"transport",level:3},{value:"U",id:"u",level:2},{value:"UI Module",id:"ui-module",level:3},{value:"V",id:"v",level:2},{value:"Viewing keys",id:"viewing-keys",level:3},{value:"Viewing Public Key",id:"viewing-public-key",level:3},{value:"Viewing Secret Key",id:"viewing-secret-key",level:3},{value:"VPK",id:"vpk",level:3},{value:"VSK",id:"vsk",level:3},{value:"W",id:"w",level:2},{value:"Waku",id:"waku",level:3},{value:"Z",id:"z",level:2},{value:"Zone SDK",id:"zone-sdk",level:3},{value:"Zone",id:"zone",level:3}];function a(e){let o={a:"a",code:"code",h1:"h1",h2:"h2",h3:"h3",header:"header",hr:"hr",p:"p",strong:"strong",...(0,i.R)(),...e.components};return(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(o.header,{children:(0,s.jsx)(o.h1,{id:"glossary",children:"Glossary"})}),"\n",(0,s.jsx)(o.p,{children:"A list of some common terms and phrases specific to the Logos ecosystem."}),"\n",(0,s.jsx)(o.hr,{}),"\n",(0,s.jsx)(o.h2,{id:"a",children:"A"}),"\n",(0,s.jsx)(o.h3,{id:"account",children:"Account"}),"\n",(0,s.jsxs)(o.p,{children:["The basic organisational unit of the LEZ state, with all persistent data stored in accounts. Accounts can be either public or private, and can also represent LEZ programs. See ",(0,s.jsx)(o.a,{href:"https://docs.logos.co/lez/get-started/introduction-to-the-logos-execution-zone#accounts-model",children:"Introduction to the Logos Execution Zone"}),"."]}),"\n",(0,s.jsx)(o.hr,{}),"\n",(0,s.jsx)(o.h2,{id:"b",children:"B"}),"\n",(0,s.jsx)(o.h3,{id:"basecamp",children:"Basecamp"}),"\n",(0,s.jsx)(o.p,{children:"The desktop shell for Logos, which facilitates installing and running Logos modules and apps."}),"\n",(0,s.jsx)(o.h3,{id:"bedrock",children:"Bedrock"}),"\n",(0,s.jsxs)(o.p,{children:["The foundational layer of the Logos Blockchain, operating as a decentralised peer-to-peer network. See ",(0,s.jsx)(o.a,{href:"https://docs.logos.co/blockchain/concepts/about-bedrock",children:"About Bedrock"}),"."]}),"\n",(0,s.jsx)(o.h3,{id:"blend-network",children:"Blend Network"}),"\n",(0,s.jsxs)(o.p,{children:["A service that adds anonymity for block proposers on the Logos Blockchain. See ",(0,s.jsx)(o.a,{href:"https://docs.logos.co/blockchain/concepts/about-the-blend-network",children:"About the Blend Network"}),"."]}),"\n",(0,s.jsx)(o.h3,{id:"blend-node",children:"Blend node"}),"\n",(0,s.jsxs)(o.p,{children:["A Logos Blockchain node that chooses to participate in the Blend Network. Also known as a ",(0,s.jsx)(o.strong,{children:"core node"}),". See ",(0,s.jsx)(o.a,{href:"https://docs.logos.co/blockchain/concepts/about-the-blend-network",children:"About the Blend Network"}),"."]}),"\n",(0,s.jsx)(o.h3,{id:"blend-protocol",children:"Blend Protocol"}),"\n",(0,s.jsxs)(o.p,{children:["The protocol underlying the Blend Network, which provides anonymity for Logos Blockchain block proposers. See ",(0,s.jsx)(o.a,{href:"https://docs.logos.co/blockchain/concepts/about-the-blend-network",children:"About the Blend Network"}),"."]}),"\n",(0,s.jsx)(o.h3,{id:"bootstrapping",children:"Bootstrapping"}),"\n",(0,s.jsx)(o.p,{children:'The process a Logos Blockchain or Delivery node undergoes when first connected to the network. For a Logos Blockchain node, this involves "catching up" on the history of the blockchain.'}),"\n",(0,s.jsx)(o.hr,{}),"\n",(0,s.jsx)(o.h2,{id:"c",children:"C"}),"\n",(0,s.jsx)(o.h3,{id:"catalogue",children:"Catalogue"}),"\n",(0,s.jsxs)(o.p,{children:["A repository that includes a list of Logos modules. Installing a module repo on Basecamp allows you to install the latest version of all these modules together. Also known as a ",(0,s.jsx)(o.strong,{children:"module repo"}),"."]}),"\n",(0,s.jsx)(o.h3,{id:"channel",children:"Channel"}),"\n",(0,s.jsxs)(o.p,{children:["A lightweight virtual chain of ordered message logs that represent the state of a Zone on Bedrock. See ",(0,s.jsx)(o.a,{href:"https://docs.logos.co/blockchain/concepts/about-mantle",children:"About Mantle"})]}),"\n",(0,s.jsx)(o.h3,{id:"cid",children:"CID"}),"\n",(0,s.jsxs)(o.p,{children:[(0,s.jsx)(o.strong,{children:"Content Identifier"}),", used to identify files for the storage module."]}),"\n",(0,s.jsx)(o.h3,{id:"codex",children:"Codex"}),"\n",(0,s.jsx)(o.p,{children:"The old name for the Logos Storage component of the Logos technology stack."}),"\n",(0,s.jsx)(o.h3,{id:"content-topic",children:"Content topic"}),"\n",(0,s.jsxs)(o.p,{children:["A string attached to messages sent via Logos Delivery to enable protocol-level features like selective message processing and retrieval based on specific criteria. See ",(0,s.jsx)(o.a,{href:"https://docs.logos.co/messaging/concepts/about-content-topics",children:"About content topics"}),"."]}),"\n",(0,s.jsx)(o.h3,{id:"core-module",children:"Core module"}),"\n",(0,s.jsx)(o.p,{children:"A Logos module that does not include a user interface view and provides backend functionality."}),"\n",(0,s.jsx)(o.h3,{id:"core-node",children:"Core node"}),"\n",(0,s.jsxs)(o.p,{children:["A Logos Blockchain node that chooses to participate in the Blend Network. Also known as a ",(0,s.jsx)(o.strong,{children:"Blend node"}),". See ",(0,s.jsx)(o.a,{href:"https://docs.logos.co/blockchain/concepts/about-the-blend-network",children:"About the Blend Network"}),"."]}),"\n",(0,s.jsx)(o.h3,{id:"cover-traffic",children:"Cover traffic"}),"\n",(0,s.jsxs)(o.p,{children:["Dummy messages sent by Blend nodes to improve proposer anonymity. See ",(0,s.jsx)(o.a,{href:"https://docs.logos.co/blockchain/concepts/about-the-blend-network#cover-traffic",children:"Cover traffic"}),"."]}),"\n",(0,s.jsx)(o.h3,{id:"cryptarchia",children:"Cryptarchia"}),"\n",(0,s.jsxs)(o.p,{children:["The Private Proof of Stake consensus protocol used by the Logos Blockchain. See ",(0,s.jsx)(o.a,{href:"https://docs.logos.co/blockchain/concepts/about-cryptarchia",children:"About Cryptarchia"}),"."]}),"\n",(0,s.jsx)(o.hr,{}),"\n",(0,s.jsx)(o.h2,{id:"d",children:"D"}),"\n",(0,s.jsx)(o.h3,{id:"delivery-module",children:"Delivery module"}),"\n",(0,s.jsx)(o.p,{children:"A module that uses the Logos Delivery network to send messages and subscribe to content topics."}),"\n",(0,s.jsx)(o.h3,{id:"dns-discovery",children:"DNS discovery"}),"\n",(0,s.jsxs)(o.p,{children:["A peer discovery mechanism used in Logos Delivery that allows the retrieval of an Ethereum Node Record (ENR) tree from the TXT field of a domain name, enabling the storage of node connection details and promoting decentralisation. See ",(0,s.jsx)(o.a,{href:"/messaging/concepts/understand-peer-discovery/dns-discovery",children:"About DNS discovery"}),"."]}),"\n",(0,s.jsx)(o.hr,{}),"\n",(0,s.jsx)(o.h2,{id:"e",children:"E"}),"\n",(0,s.jsx)(o.h3,{id:"enr",children:"ENR"}),"\n",(0,s.jsxs)(o.p,{children:[(0,s.jsx)(o.strong,{children:"Ethereum Node Record"})," - a specification used in Logos Delivery to represent and identify nodes, facilitating discovery and communication within the network. Besides connection details, ENR also includes node configuration information like enabled protocol and shards."]}),"\n",(0,s.jsx)(o.h3,{id:"epoch",children:"Epoch"}),"\n",(0,s.jsxs)(o.p,{children:["A time unit used by the Logos Blockchain, about 7.5 days long. See ",(0,s.jsx)(o.a,{href:"https://docs.logos.co/blockchain/concepts/about-cryptarchia#time-units",children:"About Cryptarchia"}),"."]}),"\n",(0,s.jsx)(o.h3,{id:"ethereum-node-record",children:"Ethereum Node Record"}),"\n",(0,s.jsxs)(o.p,{children:["A specification used in Logos Delivery to represent and identify nodes, facilitating discovery and communication within the network. Also known as ",(0,s.jsx)(o.strong,{children:"ENR"}),". Besides connection details, ENR also includes node configuration information like enabled protocol and shards."]}),"\n",(0,s.jsx)(o.hr,{}),"\n",(0,s.jsx)(o.h2,{id:"f",children:"F"}),"\n",(0,s.jsx)(o.h3,{id:"filter",children:"Filter"}),"\n",(0,s.jsxs)(o.p,{children:["A protocol that enables Logos Delivery light nodes to selectively subscribe to specific messages transmitted by peers using content topics. It is designed to be a lightweight alternative for accessing the Relay network. See ",(0,s.jsx)(o.a,{href:"https://docs.logos.co/messaging/concepts/understand-logos-delivery-protocols#filter",children:"Understand Logos Delivery protocols"}),"."]}),"\n",(0,s.jsx)(o.hr,{}),"\n",(0,s.jsx)(o.h2,{id:"g",children:"G"}),"\n",(0,s.jsx)(o.h3,{id:"gms",children:"GMS"}),"\n",(0,s.jsxs)(o.p,{children:["A ",(0,s.jsx)(o.strong,{children:"Group Master Secret"})," - a 32-byte secret used on the LEZ that, when sealed with another party's public sealing key, allows them to create a local instance of a shared LEZ account. See ",(0,s.jsx)(o.a,{href:"https://docs.logos.co/lez/accounts/set-up-shared-private-lez-account",children:"Set up a shared private LEZ account"}),"."]}),"\n",(0,s.jsx)(o.h3,{id:"gossipsub",children:"GossipSub"}),"\n",(0,s.jsxs)(o.p,{children:["A protocol for efficient and scalable information dissemination in decentralised networks commonly used in blockchain systems. See ",(0,s.jsx)(o.a,{href:"https://docs.logos.co/messaging/concepts/about-network-domains#gossip-domain",children:"About network domains"}),"."]}),"\n",(0,s.jsx)(o.h3,{id:"group-master-secret",children:"Group Master Secret"}),"\n",(0,s.jsxs)(o.p,{children:["A 32-byte secret used on the LEZ that, when sealed with another party's public sealing key, allows them to create a local instance of a shared LEZ account. Also known as a ",(0,s.jsx)(o.strong,{children:"GMS"}),". See ",(0,s.jsx)(o.a,{href:"https://docs.logos.co/lez/accounts/set-up-shared-private-lez-account",children:"Set up a shared private LEZ account"}),"."]}),"\n",(0,s.jsx)(o.hr,{}),"\n",(0,s.jsx)(o.h2,{id:"i",children:"I"}),"\n",(0,s.jsx)(o.h3,{id:"inscription",children:"Inscription"}),"\n",(0,s.jsxs)(o.p,{children:["A type of Mantle operation that writes data to the Logos Blockchain. Inscriptions are often used to represent Zone state updates. See ",(0,s.jsx)(o.a,{href:"https://docs.logos.co/blockchain/concepts/about-mantle",children:"About Mantle"}),"."]}),"\n",(0,s.jsx)(o.hr,{}),"\n",(0,s.jsx)(o.h2,{id:"l",children:"L"}),"\n",(0,s.jsx)(o.h3,{id:"lee",children:"LEE"}),"\n",(0,s.jsxs)(o.p,{children:["The ",(0,s.jsx)(o.strong,{children:"Logos Execution Environment"})," - the virtual machine that runs on the Logos Execution Zone. See ",(0,s.jsx)(o.a,{href:"https://docs.logos.co/lez/get-started/introduction-to-the-logos-execution-zone",children:"Introduction to the Logos Execution Zone"}),"."]}),"\n",(0,s.jsx)(o.h3,{id:"lez",children:"LEZ"}),"\n",(0,s.jsxs)(o.p,{children:["The ",(0,s.jsx)(o.strong,{children:"Logos Execution Zone"})," - the primary execution layer for general-purpose applications on Logos, with built-in support for private execution. See ",(0,s.jsx)(o.a,{href:"https://docs.logos.co/lez/get-started/introduction-to-the-logos-execution-zone",children:"Introduction to the Logos Execution Zone"}),"."]}),"\n",(0,s.jsx)(o.h3,{id:"lgpm",children:(0,s.jsx)(o.code,{children:"lgpm"})}),"\n",(0,s.jsx)(o.p,{children:"The official package manager used for Logos modules."}),"\n",(0,s.jsx)(o.h3,{id:"lgpd",children:(0,s.jsx)(o.code,{children:"lgpd"})}),"\n",(0,s.jsx)(o.p,{children:"The official package downloader for Logos modules."}),"\n",(0,s.jsx)(o.h3,{id:"lgx",children:(0,s.jsx)(o.code,{children:".lgx"})}),"\n",(0,s.jsxs)(o.p,{children:["A module file type which allows it to be loaded with ",(0,s.jsx)(o.code,{children:"logoscore"})," and Basecamp."]}),"\n",(0,s.jsx)(o.h3,{id:"light-node",children:"Light node"}),"\n",(0,s.jsx)(o.p,{children:"A resource-limited device or client that leverages service nodes to access the Logos Delivery Relay network."}),"\n",(0,s.jsx)(o.h3,{id:"light-push",children:"Light push"}),"\n",(0,s.jsxs)(o.p,{children:["A protocol enabling Logos Delivery light nodes to send messages to the Relay network and receive acknowledgements confirming that a peer has received them. See ",(0,s.jsx)(o.a,{href:"https://docs.logos.co/messaging/concepts/understand-logos-delivery-protocols#light-push",children:"Understand Logos Delivery protocols"}),"."]}),"\n",(0,s.jsx)(o.h3,{id:"lip",children:"LIP"}),"\n",(0,s.jsxs)(o.p,{children:[(0,s.jsx)(o.strong,{children:"Logos Improvement Proposal"})," - a proposed community update to the Logos design, which may or may not be incorporated into the project."]}),"\n",(0,s.jsx)(o.h3,{id:"lm",children:(0,s.jsx)(o.code,{children:"lm"})}),"\n",(0,s.jsx)(o.p,{children:"A helper tool for Logos modules."}),"\n",(0,s.jsx)(o.h3,{id:"locator",children:"Locator"}),"\n",(0,s.jsxs)(o.p,{children:["An address assigned to a Logos Blockchain node participating in the Blend Network. See ",(0,s.jsx)(o.a,{href:"https://docs.logos.co/blockchain/concepts/about-the-blend-network#service-declaration-protocol",children:"About the Blend Network"}),"."]}),"\n",(0,s.jsx)(o.h3,{id:"logos-blockchain",children:"Logos Blockchain"}),"\n",(0,s.jsxs)(o.p,{children:["The foundational infrastructure layer of the Logos technology stack. See ",(0,s.jsx)(o.a,{href:"https://docs.logos.co/blockchain/get-started/introduction-to-the-logos-blockchain",children:"Introduction to the Logos Blockchain"}),"."]}),"\n",(0,s.jsx)(o.h3,{id:"logos-chat",children:"Logos Chat"}),"\n",(0,s.jsx)(o.p,{children:"A library for private, encrypted peer-to-peer chat using Logos Delivery. A component of Logos Delivery."}),"\n",(0,s.jsx)(o.h3,{id:"logos-delivery",children:"Logos Delivery"}),"\n",(0,s.jsx)(o.p,{children:"A peer-to-peer messaging network with DoS protection via Rate Limiting Nullifiers. A component of Logos Delivery."}),"\n",(0,s.jsx)(o.h3,{id:"logos-execution-environment",children:"Logos Execution Environment"}),"\n",(0,s.jsxs)(o.p,{children:["The virtual machine that runs on the Logos Execution Zone. Also known as the ",(0,s.jsx)(o.strong,{children:"LEE"}),". See ",(0,s.jsx)(o.a,{href:"https://docs.logos.co/lez/get-started/introduction-to-the-logos-execution-zone",children:"Introduction to the Logos Execution Zone"}),"."]}),"\n",(0,s.jsx)(o.h3,{id:"logos-execution-zone",children:"Logos Execution Zone"}),"\n",(0,s.jsxs)(o.p,{children:["The primary execution layer for general-purpose applications on Logos, with built-in support for private execution. Also known as the ",(0,s.jsx)(o.strong,{children:"LEZ"}),". See ",(0,s.jsx)(o.a,{href:"https://docs.logos.co/lez/get-started/introduction-to-the-logos-execution-zone",children:"Introduction to the Logos Execution Zone"}),"."]}),"\n",(0,s.jsx)(o.h3,{id:"logos-messaging",children:"Logos Messaging"}),"\n",(0,s.jsx)(o.p,{children:"The private messaging layer of the Logos technology stack."}),"\n",(0,s.jsx)(o.h3,{id:"logos-node",children:"Logos node"}),"\n",(0,s.jsxs)(o.p,{children:["A node running one or several Logos modules that connect to a network of other such nodes. See ",(0,s.jsx)(o.a,{href:"https://docs.logos.co/run-a-node/get-started/run-logos-node-blockchain-storage-delivery",children:"Run a Logos node with blockchain, storage, and delivery"}),"."]}),"\n",(0,s.jsx)(o.h3,{id:"logos-module-builder",children:(0,s.jsx)(o.code,{children:"logos-module-builder"})}),"\n",(0,s.jsx)(o.p,{children:"A build tool for Logos modules."}),"\n",(0,s.jsx)(o.h3,{id:"logos-module-viewer",children:(0,s.jsx)(o.code,{children:"logos-module-viewer"})}),"\n",(0,s.jsx)(o.p,{children:"A graphical tool for inspecting Logos modules."}),"\n",(0,s.jsx)(o.h3,{id:"logos-standalone-app",children:(0,s.jsx)(o.code,{children:"logos-standalone-app"})}),"\n",(0,s.jsx)(o.p,{children:"An app that displays UI views for Logos modules."}),"\n",(0,s.jsx)(o.h3,{id:"logos-storage",children:"Logos Storage"}),"\n",(0,s.jsx)(o.p,{children:"The privacy-preserving filesharing component of the Logos technology stack."}),"\n",(0,s.jsx)(o.h3,{id:"logoscore",children:(0,s.jsx)(o.code,{children:"logoscore"})}),"\n",(0,s.jsx)(o.p,{children:"A daemon that runs Logos modules from the command line."}),"\n",(0,s.jsx)(o.h3,{id:"lssa",children:"LSSA"}),"\n",(0,s.jsxs)(o.p,{children:["An old name for the Logos Execution Environment. See ",(0,s.jsx)(o.a,{href:"https://docs.logos.co/lez/get-started/introduction-to-the-logos-execution-zone",children:"Introduction to the Logos Execution Zone"}),"."]}),"\n",(0,s.jsx)(o.hr,{}),"\n",(0,s.jsx)(o.h2,{id:"m",children:"M"}),"\n",(0,s.jsx)(o.h3,{id:"mantle",children:"Mantle"}),"\n",(0,s.jsxs)(o.p,{children:["The execution layer of Bedrock, enabling participation in the Blend Network and facilitating interactions wth Zones. See ",(0,s.jsx)(o.a,{href:"https://docs.logos.co/blockchain/concepts/about-mantle",children:"About Mantle"}),"."]}),"\n",(0,s.jsx)(o.h3,{id:"message",children:"Message"}),"\n",(0,s.jsxs)(o.p,{children:['A virtual "block" of data that forms part of a Logos channel. See ',(0,s.jsx)(o.a,{href:"https://docs.logos.co/blockchain/concepts/about-mantle",children:"About Mantle"}),"."]}),"\n",(0,s.jsx)(o.h3,{id:"mix",children:"Mix"}),"\n",(0,s.jsx)(o.p,{children:"Traffic obfuscation used to improve privacy guarantees."}),"\n",(0,s.jsx)(o.h3,{id:"module",children:"Module"}),"\n",(0,s.jsx)(o.p,{children:"A self-contained, resuable software component that provides a specific capability. Multiple modules can be loaded and made to interact to form a complete application."}),"\n",(0,s.jsx)(o.h3,{id:"module-repo",children:"Module repo"}),"\n",(0,s.jsxs)(o.p,{children:["A repository that includes a list of Logos modules. Installing a module repo on Basecamp allows you to install the latest version of all these modules together. Also known as a ",(0,s.jsx)(o.strong,{children:"catalogue"}),"."]}),"\n",(0,s.jsx)(o.h3,{id:"mostly-offline",children:"Mostly offline"}),"\n",(0,s.jsx)(o.p,{children:"Logos Delivery clients who spend most of their time offline or disconnected from the internet and only occasionally to the network. Examples include browsers and mobile phones."}),"\n",(0,s.jsx)(o.hr,{}),"\n",(0,s.jsx)(o.h2,{id:"n",children:"N"}),"\n",(0,s.jsx)(o.h3,{id:"nescience",children:"Nescience"}),"\n",(0,s.jsxs)(o.p,{children:["An old name for the Logos Execution Environment. See ",(0,s.jsx)(o.a,{href:"https://docs.logos.co/lez/get-started/introduction-to-the-logos-execution-zone",children:"Introduction to the Logos Execution Zone"}),"."]}),"\n",(0,s.jsx)(o.h3,{id:"nomos",children:"Nomos"}),"\n",(0,s.jsxs)(o.p,{children:["An old name for the Logos Blockchain. See ",(0,s.jsx)(o.a,{href:"https://docs.logos.co/blockchain/get-started/introduction-to-the-logos-blockchain",children:"Introduction to the Logos Blockchain"}),"."]}),"\n",(0,s.jsx)(o.h3,{id:"node-key",children:"Node key"}),"\n",(0,s.jsx)(o.p,{children:"A Secp256k1 (64-char hex string) private key for generating the PeerID, listening addresses, and discovery addresses of a Logos Delivery node."}),"\n",(0,s.jsx)(o.h3,{id:"note",children:"Note"}),"\n",(0,s.jsxs)(o.p,{children:["A fungible UTXO token used on Mantle. See ",(0,s.jsx)(o.a,{href:"https://docs.logos.co/blockchain/concepts/about-mantle#mantle-ledger",children:"About Mantle"}),"."]}),"\n",(0,s.jsx)(o.h3,{id:"npk",children:"NPK"}),"\n",(0,s.jsxs)(o.p,{children:["A ",(0,s.jsx)(o.strong,{children:"Nullifier Public Key"})," - a public key used to verify token ownership on the LEZ. See ",(0,s.jsx)(o.a,{href:"https://docs.logos.co/lez/get-started/introduction-to-the-logos-execution-zone#accounts-model",children:"Introduction to the Logos Execution Zone"}),"."]}),"\n",(0,s.jsx)(o.h3,{id:"nssa",children:"NSSA"}),"\n",(0,s.jsxs)(o.p,{children:["An old name for the Logos Execution Environment. See ",(0,s.jsx)(o.a,{href:"https://docs.logos.co/lez/get-started/introduction-to-the-logos-execution-zone",children:"Introduction to the Logos Execution Zone"}),"."]}),"\n",(0,s.jsx)(o.h3,{id:"nsk",children:"NSK"}),"\n",(0,s.jsxs)(o.p,{children:["A ",(0,s.jsx)(o.strong,{children:"Nullifier Secret Key"})," - a secret key used to sign LEZ transactions. See ",(0,s.jsx)(o.a,{href:"https://docs.logos.co/lez/get-started/introduction-to-the-logos-execution-zone#accounts-model",children:"Introduction to the Logos Execution Zone"}),"."]}),"\n",(0,s.jsx)(o.h3,{id:"nullifier-keys",children:"Nullifier keys"}),"\n",(0,s.jsxs)(o.p,{children:["A key pair used to sign transactions and verify token ownership on the LEZ. See ",(0,s.jsx)(o.a,{href:"https://docs.logos.co/lez/get-started/introduction-to-the-logos-execution-zone#accounts-model",children:"Introduction to the Logos Execution Zone"}),"."]}),"\n",(0,s.jsx)(o.h3,{id:"nullifier-public-key",children:"Nullifier Public Key"}),"\n",(0,s.jsxs)(o.p,{children:["A public key used to verify token ownership on the LEZ, also known as an ",(0,s.jsx)(o.strong,{children:"NPK"}),". See ",(0,s.jsx)(o.a,{href:"https://docs.logos.co/lez/get-started/introduction-to-the-logos-execution-zone#accounts-model",children:"Introduction to the Logos Execution Zone"}),"."]}),"\n",(0,s.jsx)(o.h3,{id:"nullifier-secret-key",children:"Nullifier Secret Key"}),"\n",(0,s.jsxs)(o.p,{children:["A secret key used to sign LEZ transactions, also known as an ",(0,s.jsx)(o.strong,{children:"NSK"}),". See ",(0,s.jsx)(o.a,{href:"https://docs.logos.co/lez/get-started/introduction-to-the-logos-execution-zone#accounts-model",children:"Introduction to the Logos Execution Zone"}),"."]}),"\n",(0,s.jsx)(o.hr,{}),"\n",(0,s.jsx)(o.h2,{id:"o",children:"O"}),"\n",(0,s.jsx)(o.h3,{id:"out-of-band",children:"Out-of-band"}),"\n",(0,s.jsx)(o.p,{children:"Exchanging information through a separate, secure channel distinct from the main communication method to enhance security."}),"\n",(0,s.jsx)(o.hr,{}),"\n",(0,s.jsx)(o.h2,{id:"p",children:"P"}),"\n",(0,s.jsx)(o.h3,{id:"pda",children:"PDA"}),"\n",(0,s.jsxs)(o.p,{children:[(0,s.jsx)(o.strong,{children:"Program Derived Address"})," - the address assigned to an LEE program. See ",(0,s.jsx)(o.a,{href:"https://docs.logos.co/lez/get-started/introduction-to-the-logos-execution-zone",children:"Introduction to the Logos Execution Zone"}),"."]}),"\n",(0,s.jsx)(o.h3,{id:"peer-discovery",children:"Peer discovery"}),"\n",(0,s.jsxs)(o.p,{children:["When a Logos Delivery node locates and gets information about other peers in the network. See ",(0,s.jsx)(o.a,{href:"https://docs.logos.co/messaging/concepts/understand-peer-discovery",children:"Understand peer discovery"}),"."]}),"\n",(0,s.jsx)(o.h3,{id:"peer-exchange",children:"Peer exchange"}),"\n",(0,s.jsxs)(o.p,{children:["A peer discovery mechanism used in Logos Delivery that enables light nodes to request and receive peers from other nodes in the network, allowing them to bootstrap and expand their connections without depending on Discv5. See ",(0,s.jsx)(o.a,{href:"/messaging/concepts/understand-peer-discovery/peer-exchange",children:"About peer exchange"}),"."]}),"\n",(0,s.jsx)(o.h3,{id:"peer-id",children:"Peer ID"}),"\n",(0,s.jsx)(o.p,{children:"The unique identifier of a node in the Logos Delivery network generated from the cryptographic hash of the node's public key."}),"\n",(0,s.jsx)(o.h3,{id:"pol",children:"PoL"}),"\n",(0,s.jsxs)(o.p,{children:[(0,s.jsx)(o.strong,{children:"Proof of Leadership"})," - the ZK proof that a note's owner is eligible to propose a block. See ",(0,s.jsx)(o.a,{href:"https://docs.logos.co/blockchain/concepts/about-cryptarchia",children:"About Cryptarchia"}),"."]}),"\n",(0,s.jsx)(o.h3,{id:"ppos",children:"PPoS"}),"\n",(0,s.jsxs)(o.p,{children:[(0,s.jsx)(o.strong,{children:"Private Proof of Stake"})," - a Proof of Stake consensus mechanism that protects the identity of block proposers. See ",(0,s.jsx)(o.a,{href:"https://docs.logos.co/blockchain/concepts/about-cryptarchia",children:"About Cryptarchia"}),"."]}),"\n",(0,s.jsx)(o.h3,{id:"private-account",children:"Private account"}),"\n",(0,s.jsxs)(o.p,{children:["A type of LEZ account that keeps its transactions private. Account updates are proven via ZK proofs. See ",(0,s.jsx)(o.a,{href:"https://docs.logos.co/lez/get-started/introduction-to-the-logos-execution-zone#accounts-model",children:"Introduction to the Logos Execution Zone"}),"."]}),"\n",(0,s.jsx)(o.h3,{id:"private-proof-of-stake",children:"Private Proof of Stake"}),"\n",(0,s.jsxs)(o.p,{children:["A Proof of Stake consensus mechanism that protects the identity of block proposers, also known as ",(0,s.jsx)(o.strong,{children:"PPoS"}),". See ",(0,s.jsx)(o.a,{href:"https://docs.logos.co/blockchain/concepts/about-cryptarchia",children:"About Cryptarchia"}),"."]}),"\n",(0,s.jsx)(o.h3,{id:"program",children:"Program"}),"\n",(0,s.jsxs)(o.p,{children:["An executable smart contract on the LEZ, associated with an LEZ account. See ",(0,s.jsx)(o.a,{href:"https://docs.logos.co/lez/get-started/introduction-to-the-logos-execution-zone#accounts-model",children:"Introduction to the Logos Execution Zone"}),"."]}),"\n",(0,s.jsx)(o.h3,{id:"program-derived-address",children:"Program Derived Address"}),"\n",(0,s.jsxs)(o.p,{children:["The address assigned to an LEZ program, also known as a ",(0,s.jsx)(o.strong,{children:"PDE"}),". See ",(0,s.jsx)(o.a,{href:"https://docs.logos.co/lez/get-started/introduction-to-the-logos-execution-zone",children:"Introduction to the Logos Execution Zone"}),"."]}),"\n",(0,s.jsx)(o.h3,{id:"proof-of-leadership",children:"Proof of Leadership"}),"\n",(0,s.jsxs)(o.p,{children:["The ZK proof that a note's owner is eligible to propose a block. Also known as ",(0,s.jsx)(o.strong,{children:"PoL"}),". See ",(0,s.jsx)(o.a,{href:"https://docs.logos.co/blockchain/concepts/about-cryptarchia",children:"About Cryptarchia"}),"."]}),"\n",(0,s.jsx)(o.h3,{id:"public-account",children:"Public account"}),"\n",(0,s.jsxs)(o.p,{children:["A type of LEZ account whose state is publicly available. See ",(0,s.jsx)(o.a,{href:"https://docs.logos.co/lez/get-started/introduction-to-the-logos-execution-zone#accounts-model",children:"Introduction to the Logos Execution Zone"}),"."]}),"\n",(0,s.jsx)(o.hr,{}),"\n",(0,s.jsx)(o.h2,{id:"r",children:"R"}),"\n",(0,s.jsx)(o.h3,{id:"relay",children:"Relay"}),"\n",(0,s.jsxs)(o.p,{children:["A protocol that extends the GossipSub protocol to enable secure and censorship resistant message sending and receiving among peers while preserving privacy. It also scales the Logos Delivery network to accommodate many nodes efficiently. See ",(0,s.jsx)(o.a,{href:"https://docs.logos.co/messaging/concepts/understand-logos-delivery-protocols#relay",children:"Understand Logos Delivery protocols"}),"."]}),"\n",(0,s.jsx)(o.hr,{}),"\n",(0,s.jsx)(o.h2,{id:"s",children:"S"}),"\n",(0,s.jsx)(o.h3,{id:"sdp",children:"SDP"}),"\n",(0,s.jsxs)(o.p,{children:["The ",(0,s.jsx)(o.strong,{children:"Service Declaration Protocol"})," - used to keep track of Logos Blockchain nodes that opted in to participate in the Blend Network. See ",(0,s.jsx)(o.a,{href:"https://docs.logos.co/blockchain/concepts/about-the-blend-network#service-declaration-protocol",children:"About the Blend Network"}),"."]}),"\n",(0,s.jsx)(o.h3,{id:"sealing-keys",children:"Sealing keys"}),"\n",(0,s.jsxs)(o.p,{children:["A key pair used by users of a shared LEZ account, together with the Group Master Secret, to derive a local instance of a shared account. See ",(0,s.jsx)(o.a,{href:"https://docs.logos.co/lez/accounts/set-up-shared-private-lez-account",children:"Set up a shared private LEZ account"}),"."]}),"\n",(0,s.jsx)(o.h3,{id:"service-declaration-protocol",children:"Service Declaration Protocol"}),"\n",(0,s.jsxs)(o.p,{children:["Used to keep track of Logos Blockchain nodes that opted in to participate in the Blend Network. Also known as the ",(0,s.jsx)(o.strong,{children:"SDP"}),". See ",(0,s.jsx)(o.a,{href:"https://docs.logos.co/blockchain/concepts/about-the-blend-network#service-declaration-protocol",children:"About the Blend Network"}),"."]}),"\n",(0,s.jsx)(o.h3,{id:"service-reward-distribution-protocol",children:"Service Reward Distribution Protocol"}),"\n",(0,s.jsxs)(o.p,{children:["The ",(0,s.jsx)(o.strong,{children:"Service Reward Distribution Protocol"})," - enables reward distribution to Logos Blockchain nodes participating in the Blend Network. See ",(0,s.jsx)(o.a,{href:"https://docs.logos.co/blockchain/concepts/about-the-blend-network#service-reward-distribution-protocol",children:"About the Blend Network"}),"."]}),"\n",(0,s.jsx)(o.h3,{id:"slot",children:"Slot"}),"\n",(0,s.jsxs)(o.p,{children:["The basic time unit used by the Logos Blockchain, equivalent to 1 second. See ",(0,s.jsx)(o.a,{href:"https://docs.logos.co/blockchain/concepts/about-cryptarchia#time-units",children:"About Cryptarchia"}),"."]}),"\n",(0,s.jsx)(o.h3,{id:"srdp",children:"SRDP"}),"\n",(0,s.jsxs)(o.p,{children:["Enables reward distribution to Logos Blockchain nodes participating in the Blend Network. Also known as the ",(0,s.jsx)(o.strong,{children:"SRDP"}),". See ",(0,s.jsx)(o.a,{href:"https://docs.logos.co/blockchain/concepts/about-the-blend-network#service-reward-distribution-protocol",children:"About the Blend Network"}),"."]}),"\n",(0,s.jsx)(o.h3,{id:"storage-module",children:"Storage module"}),"\n",(0,s.jsx)(o.p,{children:"The Logos module that provides filesharing capabilities via Logos Storage."}),"\n",(0,s.jsx)(o.h3,{id:"store",children:"Store"}),"\n",(0,s.jsxs)(o.p,{children:["A protocol that enables the storage of relayed messages in the Logos Delivery network, allowing offline peers to retrieve missed messages upon reconnecting to the network. See ",(0,s.jsx)(o.a,{href:"https://docs.logos.co/messaging/concepts/understand-logos-delivery-protocols#store",children:"Understand Logos Delivery protocols"}),"."]}),"\n",(0,s.jsx)(o.hr,{}),"\n",(0,s.jsx)(o.h2,{id:"t",children:"T"}),"\n",(0,s.jsx)(o.h3,{id:"token-definition-account",children:"Token definition account"}),"\n",(0,s.jsxs)(o.p,{children:["An LEZ account that defines the properties and behaviour of a custom token. See ",(0,s.jsx)(o.a,{href:"https://docs.logos.co/lez/transfer-tokens/create-and-transfer-custom-tokens-on-the-logos-execution-zone",children:"Create and transfer custom tokens on the Logos Execution Zone"}),"."]}),"\n",(0,s.jsx)(o.h3,{id:"token-holding-account",children:"Token holding account"}),"\n",(0,s.jsxs)(o.p,{children:["An LEZ account that is able to hold a custom token. See ",(0,s.jsx)(o.a,{href:"https://docs.logos.co/lez/transfer-tokens/create-and-transfer-custom-tokens-on-the-logos-execution-zone",children:"Create and transfer custom tokens on the Logos Execution Zone"}),"."]}),"\n",(0,s.jsx)(o.h3,{id:"token-program",children:"Token program"}),"\n",(0,s.jsxs)(o.p,{children:["An LEZ program that facilitates the creation of custom LEZ tokens. See ",(0,s.jsx)(o.a,{href:"https://docs.logos.co/lez/transfer-tokens/create-and-transfer-custom-tokens-on-the-logos-execution-zone",children:"Create and transfer custom tokens on the Logos Execution Zone"}),"."]}),"\n",(0,s.jsx)(o.h3,{id:"transport",children:"Transport"}),"\n",(0,s.jsx)(o.p,{children:"A network mechanism that establishes connections between Logos Delivery peers and enables efficient transmission, routing, and delivery of data packets."}),"\n",(0,s.jsx)(o.hr,{}),"\n",(0,s.jsx)(o.h2,{id:"u",children:"U"}),"\n",(0,s.jsx)(o.h3,{id:"ui-module",children:"UI Module"}),"\n",(0,s.jsx)(o.p,{children:"A Logos module that includes a user interface view."}),"\n",(0,s.jsx)(o.hr,{}),"\n",(0,s.jsx)(o.h2,{id:"v",children:"V"}),"\n",(0,s.jsx)(o.h3,{id:"viewing-keys",children:"Viewing keys"}),"\n",(0,s.jsxs)(o.p,{children:["A key pair used to generate and verify ZK proofs on the LEZ. See ",(0,s.jsx)(o.a,{href:"https://docs.logos.co/lez/get-started/introduction-to-the-logos-execution-zone#accounts-model",children:"Introduction to the Logos Execution Zone"}),"."]}),"\n",(0,s.jsx)(o.h3,{id:"viewing-public-key",children:"Viewing Public Key"}),"\n",(0,s.jsxs)(o.p,{children:["A public key used to verify ZK proofs on the LEZ, also known as a ",(0,s.jsx)(o.strong,{children:"VPK"}),". See ",(0,s.jsx)(o.a,{href:"https://docs.logos.co/lez/get-started/introduction-to-the-logos-execution-zone#accounts-model",children:"Introduction to the Logos Execution Zone"}),"."]}),"\n",(0,s.jsx)(o.h3,{id:"viewing-secret-key",children:"Viewing Secret Key"}),"\n",(0,s.jsxs)(o.p,{children:["A secret key used to generate ZK proofs on the LEZ, also known as a ",(0,s.jsx)(o.strong,{children:"VSK"}),". See ",(0,s.jsx)(o.a,{href:"https://docs.logos.co/lez/get-started/introduction-to-the-logos-execution-zone#accounts-model",children:"Introduction to the Logos Execution Zone"}),"."]}),"\n",(0,s.jsx)(o.h3,{id:"vpk",children:"VPK"}),"\n",(0,s.jsxs)(o.p,{children:["A ",(0,s.jsx)(o.strong,{children:"Viewing Public Key"})," - a public key used to verify ZK proofs on the LEZ. See ",(0,s.jsx)(o.a,{href:"https://docs.logos.co/lez/get-started/introduction-to-the-logos-execution-zone#accounts-model",children:"Introduction to the Logos Execution Zone"}),"."]}),"\n",(0,s.jsx)(o.h3,{id:"vsk",children:"VSK"}),"\n",(0,s.jsxs)(o.p,{children:["A ",(0,s.jsx)(o.strong,{children:"Viewing Secret Key"})," - a secret key used to generate ZK proofs on the LEZ. See ",(0,s.jsx)(o.a,{href:"https://docs.logos.co/lez/get-started/introduction-to-the-logos-execution-zone#accounts-model",children:"Introduction to the Logos Execution Zone"}),"."]}),"\n",(0,s.jsx)(o.hr,{}),"\n",(0,s.jsx)(o.h2,{id:"w",children:"W"}),"\n",(0,s.jsx)(o.h3,{id:"waku",children:"Waku"}),"\n",(0,s.jsx)(o.p,{children:"An old name for the Logos Delivery component of the Logos technology stack."}),"\n",(0,s.jsx)(o.hr,{}),"\n",(0,s.jsx)(o.h2,{id:"z",children:"Z"}),"\n",(0,s.jsx)(o.h3,{id:"zone-sdk",children:"Zone SDK"}),"\n",(0,s.jsxs)(o.p,{children:["An SDK provided by Logos to facilitate the creation of custom Logos Zones. See ",(0,s.jsx)(o.a,{href:"https://docs.logos.co/blockchain/concepts/about-zones",children:"About Zones"}),"."]}),"\n",(0,s.jsx)(o.h3,{id:"zone",children:"Zone"}),"\n",(0,s.jsxs)(o.p,{children:["An L2 blockchain that defines its own state but uses the Logos Blockchain for consensus. See ",(0,s.jsx)(o.a,{href:"https://docs.logos.co/blockchain/concepts/about-zones",children:"About Zones"}),"."]})]})}function h(e={}){let{wrapper:o}={...(0,i.R)(),...e.components};return o?(0,s.jsx)(o,{...e,children:(0,s.jsx)(a,{...e})}):a(e)}},8453(e,o,n){n.d(o,{R:()=>l,x:()=>r});var t=n(6540);let s={},i=t.createContext(s);function l(e){let o=t.useContext(i);return t.useMemo(function(){return"function"==typeof e?e(o):{...o,...e}},[o,e])}function r(e){let o;return o=e.disableParentContext?"function"==typeof e.components?e.components(s):e.components||s:l(e.components),t.createElement(i.Provider,{value:o},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/bf77b29a.96acacdb.js b/assets/js/bf77b29a.96acacdb.js new file mode 100644 index 00000000..18f334a0 --- /dev/null +++ b/assets/js/bf77b29a.96acacdb.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunklogos_docs=self.webpackChunklogos_docs||[]).push([["6400"],{6812(e,n,o){o.r(n),o.d(n,{metadata:()=>c,default:()=>h,frontMatter:()=>t,contentTitle:()=>l,toc:()=>i,assets:()=>s});var c=JSON.parse('{"id":"blockchain/blend/join-the-blend-network-as-a-core-node","title":"Join the Blend network as a core node","description":"Connect your blockchain node to Blend to contribute to proposer privacy.","source":"@site/docs/blockchain/blend/join-the-blend-network-as-a-core-node.md","sourceDirName":"blockchain/blend","slug":"/blockchain/blend/join-the-blend-network-as-a-core-node","permalink":"/blockchain/blend/join-the-blend-network-as-a-core-node","draft":false,"unlisted":false,"editUrl":"https://github.com/logos-co/logos-docs/tree/main/docs/blockchain/blend/join-the-blend-network-as-a-core-node.md","tags":[],"version":"current","sidebarPosition":1,"frontMatter":{"title":"Join the Blend network as a core node","doc_type":"procedure","product":"blockchain","topics":"blend","steps_layout":"flat","authors":"ntn-x2, kashepavadan","owner":"logos","doc_version":1,"slug":"join-the-blend-network-as-a-core-node","sidebar_position":1},"sidebar":"blockchainSidebar","previous":{"title":"Operate a decentralised Zone with round-robin sequencer rotation","permalink":"/blockchain/zone-sdk/operate-decentralised-zone-with-round-robin-sequencer-rotation"},"next":{"title":"About Bedrock","permalink":"/blockchain/concepts/about-bedrock"}}'),r=o(4848),d=o(8453);let t={title:"Join the Blend network as a core node",doc_type:"procedure",product:"blockchain",topics:"blend",steps_layout:"flat",authors:"ntn-x2, kashepavadan",owner:"logos",doc_version:1,slug:"join-the-blend-network-as-a-core-node",sidebar_position:1},l="Join the Blend Network as a core node",s={},i=[{value:"Connect your blockchain node to Blend to contribute to proposer privacy.",id:"connect-your-blockchain-node-to-blend-to-contribute-to-proposer-privacy",level:4},{value:"What to expect",id:"what-to-expect",level:2},{value:"Register your node as a Blend core node",id:"register-your-node-as-a-blend-core-node",level:2}];function a(e){let n={a:"a",code:"code",h1:"h1",h2:"h2",h4:"h4",header:"header",li:"li",ol:"ol",p:"p",pre:"pre",ul:"ul",...(0,d.R)(),...e.components};return(0,r.jsxs)(r.Fragment,{children:[(0,r.jsx)(n.header,{children:(0,r.jsx)(n.h1,{id:"join-the-blend-network-as-a-core-node",children:"Join the Blend Network as a core node"})}),"\n",(0,r.jsx)(n.h4,{id:"connect-your-blockchain-node-to-blend-to-contribute-to-proposer-privacy",children:"Connect your blockchain node to Blend to contribute to proposer privacy."}),"\n",(0,r.jsxs)(n.p,{children:["Joining the ",(0,r.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#blend-network",children:"Blend Network"})," lets your blockchain node contribute to the privacy of ",(0,r.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#logos-blockchain",children:"Logos Blockchain"})," proposers and receive rewards for participating. This procedure applies to operators of a running Logos Blockchain node who want to register that node as a Blend ",(0,r.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#core-node",children:"core node"}),". Before you start, make sure your node's address is publicly reachable so other peers can connect to it."]}),"\n",(0,r.jsx)(n.p,{children:"You need:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.a,{href:"/blockchain/get-started/run-a-logos-blockchain-node-from-cli",children:"A running blockchain node"}),"."]}),"\n",(0,r.jsx)(n.li,{children:"A publicly reachable IP and port (or DNS) combination for the node."}),"\n"]}),"\n",(0,r.jsx)(n.h2,{id:"what-to-expect",children:"What to expect"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"Your blockchain node is registered as a Blend core node."}),"\n",(0,r.jsx)(n.li,{children:"Your node contributes to proposer privacy and becomes eligible for rewards."}),"\n",(0,r.jsx)(n.li,{children:"Your declaration is confirmed on-chain and becomes active after two epochs."}),"\n"]}),"\n",(0,r.jsx)(n.h2,{id:"register-your-node-as-a-blend-core-node",children:"Register your node as a Blend core node"}),"\n",(0,r.jsxs)(n.p,{children:["Complete these steps to fund the required keys, retrieve a locked ",(0,r.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#note",children:"note"}),", and submit your Blend join declaration."]}),"\n",(0,r.jsxs)(n.ol,{children:["\n",(0,r.jsxs)(n.li,{children:["\n",(0,r.jsxs)(n.p,{children:["Start the node and poll until the mode switches to ",(0,r.jsx)(n.code,{children:'"Online"'}),". This takes approximately one hour:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:'logoscore call blockchain_module get_cryptarchia_info | jq -r .result.value | jq .mode\n# > "Bootstrapping"\n\n# ... Wait ~1h, then re-run until you see:\n# > "Online"\n'})}),"\n"]}),"\n",(0,r.jsxs)(n.li,{children:["\n",(0,r.jsxs)(n.p,{children:["Open your ",(0,r.jsx)(n.code,{children:"keystore.yaml"})," and use the ",(0,r.jsx)(n.a,{href:"https://testnet.blockchain.logos.co/web/faucet/",children:"faucet"})," to send funds to both the ",(0,r.jsx)(n.code,{children:"BlendZk"})," and ",(0,r.jsx)(n.code,{children:"SdpFunding"})," public keys."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"# keystore.yaml\npublic_keys:\n ...\n BlendZk: 13cccf99f90fd78c2134891ce3c1afce0605753a7694b9d56678d63a8d471820\n ...\n SdpFunding: 91d381a87e05d46fc9bc95246273b6930290506f0589ad039444decd3c24940e\n ...\n"})}),"\n"]}),"\n",(0,r.jsxs)(n.li,{children:["\n",(0,r.jsxs)(n.p,{children:["Wait until both keys have received funds. Check each balance with ",(0,r.jsx)(n.code,{children:"wallet_get_notes"}),". You may need to repeat the faucet requests since only one drip is allowed per block:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:'logoscore call blockchain_module wallet_get_notes <ADDRESS> "" \\\n | jq -r .result.value | jq .notes\n# > [\n# > {\n# > "id": "<BLEND_ZK_NODE_ID>",\n# > "value": "1000"\n# > }\n# > ]\n'})}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:["Note the ",(0,r.jsx)(n.code,{children:"id"})," of a note held by the ",(0,r.jsx)(n.code,{children:"BlendZk"})," key \u2014 you need it in the next step."]}),"\n"]}),"\n"]}),"\n",(0,r.jsxs)(n.li,{children:["\n",(0,r.jsxs)(n.p,{children:["Join the Blend network by locking one of the notes held by your ",(0,r.jsx)(n.code,{children:"BlendZk"})," key:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:'curl -X POST http://<YOUR_NODE_IP>:8080/blend/join \\\n -H \'Content-Type: application/json\' \\\n -d \'{"locator":"/ip4/<YOUR_IP>/udp/<YOUR_BLEND_PORT>/quic-v1","locked_note_id":"<BLEND_ZK_NOTE_ID>"}\'\n\n# A successful call returns the declaration id:\n# > 2691821bd61394cc18939626de4e9231c699e8ddefd1ebf9e6c35b32229bdc65\n'})}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"<YOUR_IP>"}),": your external IP address."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"<YOUR_BLEND_PORT>"}),": the Blend port from ",(0,r.jsx)(n.code,{children:"blend.core.backend.listening_address"})," in ",(0,r.jsx)(n.code,{children:"user_config.yaml"}),". If you use port mapping, use the externally mapped port."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"<BLEND_ZK_NOTE_ID>"}),": the note ",(0,r.jsx)(n.code,{children:"id"})," from the ",(0,r.jsx)(n.code,{children:"BlendZk"})," balance check above."]}),"\n"]}),"\n"]}),"\n",(0,r.jsxs)(n.li,{children:["\n",(0,r.jsxs)(n.p,{children:["Confirm the declaration was accepted on-chain by polling ",(0,r.jsx)(n.code,{children:"/mantle/sdp/declarations"})," and looking for your entry:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:'curl http://<YOUR_NODE_IP>:8080/mantle/sdp/declarations | jq .\n# > [\n# > {\n# > "service_type": "BN",\n# > "provider_id": "35d60d973560b8344f83dc266a3fe89e35a3dcf9959c492d0a7a0b7a85c5d2ce",\n# > "locked_note_id": "<BLEND_ZK_NOTE_ID>",\n# > "locators": [\n# > "/ip4/<YOUR_IP>/udp/<YOUR_BLEND_PORT>/quic-v1"\n# > ],\n# > "zk_id": "13cccf99f90fd78c2134891ce3c1afce0605753a7694b9d56678d63a8d471820",\n# > "created": 1,\n# > "active": 3,\n# > "withdraw_at": null,\n# > "nonce": 0\n# > }\n# > ]\n'})}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:'service_type: "BN"'})," identifies this as a ",(0,r.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#blend-node",children:"Blend node"})," declaration."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"zk_id"})," is your ",(0,r.jsx)(n.code,{children:"BlendZk"})," public key; ",(0,r.jsx)(n.code,{children:"provider_id"})," is your ",(0,r.jsx)(n.code,{children:"BlendSigning"})," key."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"active == created + 2"}),": your node becomes active two epochs after the declaration is included in a block."]}),"\n",(0,r.jsx)(n.li,{children:"If your declaration is not yet listed, retry after your transaction is included in a block."}),"\n"]}),"\n"]}),"\n"]})]})}function h(e={}){let{wrapper:n}={...(0,d.R)(),...e.components};return n?(0,r.jsx)(n,{...e,children:(0,r.jsx)(a,{...e})}):a(e)}},8453(e,n,o){o.d(n,{R:()=>t,x:()=>l});var c=o(6540);let r={},d=c.createContext(r);function t(e){let n=c.useContext(d);return c.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function l(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(r):e.components||r:t(e.components),c.createElement(d.Provider,{value:n},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/bfa2415e.0b877223.js b/assets/js/bfa2415e.0b877223.js new file mode 100644 index 00000000..6ab78b5a --- /dev/null +++ b/assets/js/bfa2415e.0b877223.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunklogos_docs=self.webpackChunklogos_docs||[]).push([["3510"],{9710(e,n,s){s.r(n),s.d(n,{metadata:()=>t,default:()=>h,frontMatter:()=>d,contentTitle:()=>l,toc:()=>c,assets:()=>r});var t=JSON.parse('{"id":"messaging/chat-module/build-logos-module-that-uses-chat-module-api","title":"Build a Logos module that uses the Chat module API","description":"Get started with private 1:1 end-to-end encrypted messaging in your own Logos module.","source":"@site/docs/messaging/chat-module/build-logos-module-that-uses-chat-module-api.md","sourceDirName":"messaging/chat-module","slug":"/messaging/chat-module/build-logos-module-that-uses-chat-module-api","permalink":"/messaging/chat-module/build-logos-module-that-uses-chat-module-api","draft":false,"unlisted":false,"editUrl":"https://github.com/logos-co/logos-docs/tree/main/docs/messaging/chat-module/build-logos-module-that-uses-chat-module-api.md","tags":[],"version":"current","sidebarPosition":1,"frontMatter":{"title":"Build a Logos module that uses the Chat module API","doc_type":"procedure","product":"messaging","topics":"chat","steps_layout":"sectioned","authors":"kashepavadan, igor-sirotin","owner":"logos","doc_version":1,"slug":"build-logos-module-that-uses-chat-module-api","sidebar_position":1},"sidebar":"messagingSidebar","previous":{"title":"Learn the differences between Logos Delivery and libp2p","permalink":"/messaging/concepts/logos-delivery-and-libp2p"},"next":{"title":"Send anonymised messages over the mix network","permalink":"/messaging/chat-module/send-anonymised-messages-over-the-mix-network"}}'),o=s(4848),i=s(8453);let d={title:"Build a Logos module that uses the Chat module API",doc_type:"procedure",product:"messaging",topics:"chat",steps_layout:"sectioned",authors:"kashepavadan, igor-sirotin",owner:"logos",doc_version:1,slug:"build-logos-module-that-uses-chat-module-api",sidebar_position:1},l="Build a Logos module that uses the Chat module API",r={},c=[{value:"Get started with private 1:1 end-to-end encrypted messaging in your own Logos module.",id:"get-started-with-private-11-end-to-end-encrypted-messaging-in-your-own-logos-module",level:4},{value:"What to expect",id:"what-to-expect",level:2},{value:"Step 1: Scaffold a new Logos module",id:"step-1-scaffold-a-new-logos-module",level:2},{value:"Step 2: Declare <code>chat_module</code> as a dependency",id:"step-2-declare-chat_module-as-a-dependency",level:2},{value:"Step 3: Initialise <code>LogosModules</code> and subscribe to events",id:"step-3-initialise-logosmodules-and-subscribe-to-events",level:2},{value:"Step 4: Drive the chat lifecycle",id:"step-4-drive-the-chat-lifecycle",level:2},{value:"Step 5: Build and run",id:"step-5-build-and-run",level:2},{value:"Step 6: Verify a two-instance exchange",id:"step-6-verify-a-two-instance-exchange",level:2},{value:"Troubleshooting the Logos Chat module",id:"troubleshooting-the-logos-chat-module",level:2},{value:"Why does a method fail?",id:"why-does-a-method-fail",level:3},{value:"Why do peers not connect or messages not propagate?",id:"why-do-peers-not-connect-or-messages-not-propagate",level:3},{value:"Why does a read stall the UI?",id:"why-does-a-read-stall-the-ui",level:3},{value:"Why is a previously created conversation still there after a restart?",id:"why-is-a-previously-created-conversation-still-there-after-a-restart",level:3}];function a(e){let n={a:"a",admonition:"admonition",code:"code",em:"em",h1:"h1",h2:"h2",h3:"h3",h4:"h4",header:"header",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,i.R)(),...e.components};return(0,o.jsxs)(o.Fragment,{children:[(0,o.jsx)(n.header,{children:(0,o.jsx)(n.h1,{id:"build-a-logos-module-that-uses-the-chat-module-api",children:"Build a Logos module that uses the Chat module API"})}),"\n",(0,o.jsx)(n.h4,{id:"get-started-with-private-11-end-to-end-encrypted-messaging-in-your-own-logos-module",children:"Get started with private 1:1 end-to-end encrypted messaging in your own Logos module."}),"\n",(0,o.jsxs)(n.p,{children:["This procedure covers how to build a Logos ",(0,o.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#module",children:"module"})," that calls the ",(0,o.jsx)(n.a,{href:"https://github.com/logos-co/logos-chat-module",children:"logos-chat-module"})," API (tag ",(0,o.jsx)(n.code,{children:"v0.1.2"}),") to exchange introduction bundles, open private 1:1 conversations, and send and receive end-to-end encrypted messages on the Logos network. It is intended for application developers who want to integrate private messaging without taking direct dependencies on ",(0,o.jsx)(n.code,{children:"liblogoschat"})," or ",(0,o.jsx)(n.code,{children:"logos-delivery"}),"."]}),"\n",(0,o.jsx)(n.admonition,{type:"info",children:(0,o.jsxs)(n.p,{children:["Identity, conversations, and message history persist in the instance directory you pass to ",(0,o.jsx)(n.code,{children:"init()"})," (",(0,o.jsx)(n.code,{children:"identity.db"})," + ",(0,o.jsx)(n.code,{children:"history.json"}),"). Restarting an instance against the same directory restores its identity and conversations."]})}),"\n",(0,o.jsx)(n.p,{children:"Before you start, make sure you have the following:"}),"\n",(0,o.jsxs)(n.ul,{children:["\n",(0,o.jsxs)(n.li,{children:["\n",(0,o.jsx)(n.p,{children:"Linux or macOS"}),"\n"]}),"\n",(0,o.jsxs)(n.li,{children:["\n",(0,o.jsxs)(n.p,{children:[(0,o.jsx)(n.strong,{children:"Nix"})," with flakes enabled. Install from ",(0,o.jsx)(n.a,{href:"https://nixos.org/download.html",children:"nixos.org"}),", then enable flakes:"]}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{className:"language-bash",children:"mkdir -p ~/.config/nix\necho 'experimental-features = nix-command flakes' >> ~/.config/nix/nix.conf\n"})}),"\n"]}),"\n",(0,o.jsxs)(n.li,{children:["\n",(0,o.jsx)(n.p,{children:"Network access so that two instances can reach each other"}),"\n"]}),"\n",(0,o.jsxs)(n.li,{children:["\n",(0,o.jsxs)(n.p,{children:["An understanding of ",(0,o.jsx)(n.a,{href:"/core/build-modules/build-a-logos-cpp-ui-module",children:"Logos modules"})]}),"\n"]}),"\n"]}),"\n",(0,o.jsx)(n.h2,{id:"what-to-expect",children:"What to expect"}),"\n",(0,o.jsxs)(n.ul,{children:["\n",(0,o.jsx)(n.li,{children:"You can initialise a chat client, connect to the Logos network, and exchange end-to-end encrypted messages with another instance."}),"\n",(0,o.jsxs)(n.li,{children:["You can open a private 1:1 conversation by exchanging introduction bundles ",(0,o.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#out-of-band",children:"out of band"})," and calling ",(0,o.jsx)(n.code,{children:"create_conversation"})," from the initiating side."]}),"\n",(0,o.jsx)(n.li,{children:"You can integrate the full chat lifecycle \u2014 init, subscribe to events, create bundle, open conversation, send, shut down \u2014 into any Logos C++ module."}),"\n"]}),"\n",(0,o.jsx)(n.h2,{id:"step-1-scaffold-a-new-logos-module",children:"Step 1: Scaffold a new Logos module"}),"\n",(0,o.jsxs)(n.p,{children:["Scaffold a new module using ",(0,o.jsx)(n.a,{href:"https://github.com/logos-co/logos-module-builder",children:(0,o.jsx)(n.code,{children:"logos-module-builder"})}),". For a full walkthrough, see the ",(0,o.jsx)(n.a,{href:"/core/build-modules/build-a-logos-cpp-ui-module",children:"Build a Logos C++ UI module"})," tutorial."]}),"\n",(0,o.jsxs)(n.ol,{children:["\n",(0,o.jsxs)(n.li,{children:["\n",(0,o.jsx)(n.p,{children:"Create and enter the project directory:"}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{className:"language-bash",children:"mkdir your-module-name && cd your-module-name\n"})}),"\n"]}),"\n",(0,o.jsxs)(n.li,{children:["\n",(0,o.jsx)(n.p,{children:"Initialise from the template:"}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{className:"language-bash",children:"nix flake init -t github:logos-co/logos-module-builder/tutorial-v3#ui-qml-backend\n"})}),"\n"]}),"\n",(0,o.jsxs)(n.li,{children:["\n",(0,o.jsx)(n.p,{children:"Initialise a Git repository and stage all generated files:"}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{className:"language-bash",children:"git init && git add -A\n"})}),"\n"]}),"\n",(0,o.jsxs)(n.li,{children:["\n",(0,o.jsxs)(n.p,{children:["Keep the generated files \u2014 you build on them, you don't delete them. The template is a working ",(0,o.jsx)(n.code,{children:"ui_example"})," module. Its main files are:"]}),"\n",(0,o.jsxs)(n.table,{children:[(0,o.jsx)(n.thead,{children:(0,o.jsxs)(n.tr,{children:[(0,o.jsx)(n.th,{children:"File"}),(0,o.jsx)(n.th,{children:"What it is"}),(0,o.jsx)(n.th,{children:"Can you edit it?"})]})}),(0,o.jsxs)(n.tbody,{children:[(0,o.jsxs)(n.tr,{children:[(0,o.jsx)(n.td,{children:(0,o.jsx)(n.code,{children:"src/ui_example_plugin.{h,cpp}"})}),(0,o.jsx)(n.td,{children:"The C++ plugin"}),(0,o.jsx)(n.td,{children:"Yes \u2014 your chat code goes here (Steps 3\u20134)"})]}),(0,o.jsxs)(n.tr,{children:[(0,o.jsxs)(n.td,{children:[(0,o.jsx)(n.code,{children:"src/ui_example.rep"}),", ",(0,o.jsx)(n.code,{children:"src/ui_example_interface.h"})]}),(0,o.jsx)(n.td,{children:"The module's interface"}),(0,o.jsx)(n.td,{children:"No"})]}),(0,o.jsxs)(n.tr,{children:[(0,o.jsx)(n.td,{children:(0,o.jsx)(n.code,{children:"src/qml/Main.qml"})}),(0,o.jsx)(n.td,{children:"The example view"}),(0,o.jsx)(n.td,{children:"Later \u2014 replace with your own UI"})]}),(0,o.jsxs)(n.tr,{children:[(0,o.jsxs)(n.td,{children:[(0,o.jsx)(n.code,{children:"metadata.json"}),", ",(0,o.jsx)(n.code,{children:"CMakeLists.txt"})]}),(0,o.jsx)(n.td,{children:"Build config"}),(0,o.jsx)(n.td,{children:"Step 2 only"})]})]})]}),"\n",(0,o.jsxs)(n.p,{children:["You add your chat code in ",(0,o.jsx)(n.code,{children:"UiExamplePlugin::initLogos()"}),". Leave the example ",(0,o.jsx)(n.code,{children:"status"})," / ",(0,o.jsx)(n.code,{children:"add"})," UI as is for now."]}),"\n"]}),"\n"]}),"\n",(0,o.jsx)(n.admonition,{type:"info",children:(0,o.jsxs)(n.p,{children:["Keep the module named ",(0,o.jsx)(n.code,{children:"ui_example"}),". The name is referenced in ",(0,o.jsx)(n.code,{children:"metadata.json"}),", ",(0,o.jsx)(n.code,{children:"CMakeLists.txt"}),", the ",(0,o.jsx)(n.code,{children:"src/ui_example*"})," files, and ",(0,o.jsx)(n.code,{children:"Main.qml"})," (",(0,o.jsx)(n.code,{children:'logos.module("ui_example")'}),") \u2014 they must all match, or ",(0,o.jsx)(n.code,{children:"nix build"})," fails. To use a different name, change it in every one of these."]})}),"\n",(0,o.jsxs)(n.h2,{id:"step-2-declare-chat_module-as-a-dependency",children:["Step 2: Declare ",(0,o.jsx)(n.code,{children:"chat_module"})," as a dependency"]}),"\n",(0,o.jsxs)(n.p,{children:["Add ",(0,o.jsx)(n.code,{children:"chat_module"})," to both ",(0,o.jsx)(n.code,{children:"metadata.json"})," and ",(0,o.jsx)(n.code,{children:"flake.nix"}),", pinning to the released tag so your app stays stable as the module's API evolves."]}),"\n",(0,o.jsx)(n.admonition,{type:"info",children:(0,o.jsxs)(n.p,{children:["The flake input name (",(0,o.jsx)(n.code,{children:"chat_module"}),") must match the dependency name in ",(0,o.jsx)(n.code,{children:"metadata.json"}),". ",(0,o.jsx)(n.code,{children:"logos-module-builder"})," automatically generates the typed ",(0,o.jsx)(n.code,{children:"chat_module"})," wrapper at build time."]})}),"\n",(0,o.jsxs)(n.ol,{children:["\n",(0,o.jsxs)(n.li,{children:["\n",(0,o.jsxs)(n.p,{children:["In ",(0,o.jsx)(n.code,{children:"metadata.json"}),", add ",(0,o.jsx)(n.code,{children:"chat_module"})," and ",(0,o.jsx)(n.code,{children:"delivery_module"})," to the dependencies array and reuse ",(0,o.jsx)(n.code,{children:"chat_module"}),"'s bundled ",(0,o.jsx)(n.code,{children:"delivery_module"})," contract:"]}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{className:"language-json",children:'{\n "name": "ui_example",\n "dependencies": ["chat_module", "delivery_module"],\n "dependency_overrides": {\n "delivery_module": {\n "input": "chat_module",\n "file": "rust-lib/deps/delivery_module.lidl"\n }\n },\n ...\n}\n'})}),"\n"]}),"\n",(0,o.jsxs)(n.li,{children:["\n",(0,o.jsxs)(n.p,{children:["In ",(0,o.jsx)(n.code,{children:"flake.nix"}),", pin ",(0,o.jsx)(n.code,{children:"chat_module"})," and its ",(0,o.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#transport",children:"transport"})," dependency ",(0,o.jsx)(n.code,{children:"delivery_module"}),", then map the delivery input to the ",(0,o.jsx)(n.code,{children:"delivery_module"})," dependency so the builder can resolve its runtime:"]}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{className:"language-nix",children:'inputs = {\n logos-module-builder.url = "github:logos-co/logos-module-builder";\n # Pin chat_module to the released tag so its API can\'t shift under `nix flake update`.\n chat_module.url = "github:logos-co/logos-chat-module/v0.1.2";\n # chat_module reaches delivery over IPC, but the builder still needs delivery\'s\n # runtime build from a matching flake input. Pin the v0.1.3 tag: it carries the\n # zerokit/RLN nix-build fix that earlier delivery tags (\u2264 v0.1.2) lack.\n logos-delivery-module.url = "github:logos-co/logos-delivery-module/v0.1.3";\n};\n\noutputs = inputs@{ logos-module-builder, logos-delivery-module, ... }:\n logos-module-builder.lib.mkLogosQmlModule {\n src = ./.;\n configFile = ./metadata.json;\n # Map the delivery input to the `delivery_module` dependency name.\n flakeInputs = { delivery_module = logos-delivery-module; } // inputs;\n };\n'})}),"\n"]}),"\n"]}),"\n",(0,o.jsx)(n.admonition,{type:"info",children:(0,o.jsxs)(n.p,{children:["The ",(0,o.jsx)(n.code,{children:"dependency_overrides"})," entry above only points the builder at delivery's ",(0,o.jsx)(n.code,{children:".lidl"})," contract for code generation \u2014 it does ",(0,o.jsx)(n.strong,{children:"not"})," supply delivery's runtime build. The builder resolves each ",(0,o.jsx)(n.code,{children:"metadata.json"})," dependency's runtime from a matching flake input, so ",(0,o.jsx)(n.code,{children:"delivery_module"})," needs the ",(0,o.jsx)(n.code,{children:"logos-delivery-module"})," input, mapped in via ",(0,o.jsx)(n.code,{children:"flakeInputs"}),". Without it, ",(0,o.jsx)(n.code,{children:"nix build"})," cannot resolve ",(0,o.jsx)(n.code,{children:"delivery_module"}),". This mirrors how ",(0,o.jsx)(n.a,{href:"https://github.com/logos-co/logos-chat-ui/blob/v0.1.2/flake.nix",children:(0,o.jsx)(n.code,{children:"logos-chat-ui"})})," wires the two modules together."]})}),"\n",(0,o.jsxs)(n.h2,{id:"step-3-initialise-logosmodules-and-subscribe-to-events",children:["Step 3: Initialise ",(0,o.jsx)(n.code,{children:"LogosModules"})," and subscribe to events"]}),"\n",(0,o.jsxs)(n.p,{children:["In your module's ",(0,o.jsx)(n.code,{children:"initLogos()"})," function, construct ",(0,o.jsx)(n.code,{children:"LogosModules"})," with the provided ",(0,o.jsx)(n.code,{children:"LogosAPI*"})," and subscribe to all push events before calling ",(0,o.jsx)(n.code,{children:"init()"}),". Subscribing first ensures you do not miss early events or the first incoming messages."]}),"\n",(0,o.jsxs)(n.ol,{children:["\n",(0,o.jsxs)(n.li,{children:["\n",(0,o.jsxs)(n.p,{children:["Add the ",(0,o.jsx)(n.code,{children:"LogosModules"})," member, then construct it in ",(0,o.jsx)(n.code,{children:"initLogos()"}),"."]}),"\n",(0,o.jsxs)(n.p,{children:["In the header ",(0,o.jsx)(n.code,{children:"src/ui_example_plugin.h"})," \u2014 add the include and the member:"]}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{className:"language-cpp",children:'#include "logos_sdk.h" // generated umbrella \u2014 exposes LogosModules\n\n// inside the UiExamplePlugin class:\nLogosModules* m_logos = nullptr;\n'})}),"\n",(0,o.jsxs)(n.p,{children:["In ",(0,o.jsx)(n.code,{children:"src/ui_example_plugin.cpp"})," \u2014 construct it:"]}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{className:"language-cpp",children:"void UiExamplePlugin::initLogos(LogosAPI* api) {\n logosAPI = api; // keep the scaffold's two existing lines\n setBackend(this);\n m_logos = new LogosModules(api);\n // Use m_logos->chat_module to call the Logos Chat module.\n}\n"})}),"\n"]}),"\n",(0,o.jsxs)(n.li,{children:["\n",(0,o.jsxs)(n.p,{children:["Subscribe to the module's push events. Each handler receives the event's positional arguments in the order declared in ",(0,o.jsx)(n.code,{children:"chat_module.lidl"}),":"]}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{className:"language-cpp",children:'auto& chat = m_logos->chat_module;\n\n// A new message arrived in a conversation.\nchat.on("message_received", [](const QVariantList& a) {\n // a[0]: QString convo_id, a[1]: QString content, a[2]: qint64 timestamp_ms\n});\n// One of your own messages was recorded/sent.\nchat.on("message_sent", [](const QVariantList& a) {\n // a[0]: convo_id, a[1]: content, a[2]: timestamp_ms\n});\n// A conversation was created \u2014 incoming from a peer, or your own outgoing one.\nchat.on("conversation_created", [](const QVariantList& a) {\n // a[0]: QString convo_id, a[1]: bool is_outgoing, a[2]: QString peer_label\n});\nchat.on("conversation_updated", [](const QVariantList& a) { /* a[0]: convo_id */ });\nchat.on("conversation_deleted", [](const QVariantList& a) { /* a[0]: convo_id */ });\n// Delivery/connection state changed \u2014 drives your "connected" indicator.\nchat.on("delivery_state_changed", [](const QVariantList& a) {\n // a[0]: QString delivery_state ("initialising" | "online" | "stopped" | "error")\n // a[1]: QString detail\n});\n'})}),"\n",(0,o.jsxs)(n.p,{children:["See ",(0,o.jsx)(n.a,{href:"https://github.com/logos-co/logos-chat-module/blob/v0.1.2/rust-lib/chat_module.lidl",children:(0,o.jsx)(n.code,{children:"rust-lib/chat_module.lidl"})})," for the exact argument list of every method and event."]}),"\n"]}),"\n"]}),"\n",(0,o.jsx)(n.h2,{id:"step-4-drive-the-chat-lifecycle",children:"Step 4: Drive the chat lifecycle"}),"\n",(0,o.jsxs)(n.p,{children:["Status-bearing methods return their result ",(0,o.jsx)(n.strong,{children:"synchronously"})," as a ",(0,o.jsx)(n.code,{children:"LogosResult"}),"."]}),"\n",(0,o.jsxs)(n.ul,{children:["\n",(0,o.jsxs)(n.li,{children:[(0,o.jsx)(n.code,{children:"res.success"})," tells you whether the call succeeded;"]}),"\n",(0,o.jsxs)(n.li,{children:[(0,o.jsx)(n.code,{children:"res.getError<QString>()"})," carries the failure reason;"]}),"\n",(0,o.jsxs)(n.li,{children:[(0,o.jsx)(n.code,{children:"res.getValue<QString>()"})," carries the returned value (for example, the intro bundle)."]}),"\n"]}),"\n",(0,o.jsxs)(n.p,{children:["Ongoing activity \u2014 incoming messages, new conversations, delivery-state changes \u2014 arrives ",(0,o.jsx)(n.strong,{children:"asynchronously"})," through the push events you subscribed to in Step 3."]}),"\n",(0,o.jsx)(n.admonition,{type:"info",children:(0,o.jsxs)(n.p,{children:[(0,o.jsx)(n.code,{children:"init()"})," starts delivery asynchronously, so the client is not connected the moment ",(0,o.jsx)(n.code,{children:"init()"})," returns. Watch ",(0,o.jsx)(n.code,{children:"delivery_state_changed"})," for the ",(0,o.jsx)(n.code,{children:"online"})," state before creating conversations or sending messages."]})}),"\n",(0,o.jsxs)(n.p,{children:[(0,o.jsx)(n.code,{children:"init()"})," takes the instance directory, a delivery preset, and a TCP port:"]}),"\n",(0,o.jsxs)(n.table,{children:[(0,o.jsx)(n.thead,{children:(0,o.jsxs)(n.tr,{children:[(0,o.jsx)(n.th,{children:"Parameter"}),(0,o.jsx)(n.th,{children:"Type"}),(0,o.jsx)(n.th,{children:"Notes"})]})}),(0,o.jsxs)(n.tbody,{children:[(0,o.jsxs)(n.tr,{children:[(0,o.jsx)(n.td,{children:(0,o.jsx)(n.code,{children:"instance_path"})}),(0,o.jsx)(n.td,{children:"string"}),(0,o.jsx)(n.td,{children:"Directory for this instance's persistent state. Use a distinct directory per instance to run several side by side."})]}),(0,o.jsxs)(n.tr,{children:[(0,o.jsx)(n.td,{children:(0,o.jsx)(n.code,{children:"delivery_preset"})}),(0,o.jsx)(n.td,{children:"string"}),(0,o.jsxs)(n.td,{children:["Network preset for the delivery node. Use ",(0,o.jsx)(n.code,{children:"logos.test"})," to reach the Logos test network. Must match across all participants."]})]}),(0,o.jsxs)(n.tr,{children:[(0,o.jsx)(n.td,{children:(0,o.jsx)(n.code,{children:"tcp_port"})}),(0,o.jsx)(n.td,{children:"int"}),(0,o.jsxs)(n.td,{children:[(0,o.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#logos-delivery",children:"Logos Delivery"})," TCP port. ",(0,o.jsx)(n.code,{children:"0"})," picks a random port."]})]})]})]}),"\n",(0,o.jsxs)(n.ol,{children:["\n",(0,o.jsxs)(n.li,{children:["\n",(0,o.jsx)(n.p,{children:"Initialise the chat client:"}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{className:"language-cpp",children:'const QString dir = qEnvironmentVariable("CHAT_INSTANCE_DIR", "/tmp/chat-instance");\nconst LogosResult res = m_logos->chat_module.init(dir, "logos.test", 0);\nif (!res.success) {\n qWarning() << "init failed:" << res.getError<QString>();\n return;\n}\n// init succeeded; delivery connects asynchronously.\n// Wait for delivery_state_changed with state == "online" before creating conversations.\n'})}),"\n"]}),"\n",(0,o.jsxs)(n.li,{children:["\n",(0,o.jsx)(n.p,{children:"Read (or set) your identity:"}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{className:"language-cpp",children:'const QString myId = m_logos->chat_module.get_installation_name();\n// Optionally choose a name: m_logos->chat_module.set_installation_name("alice");\n'})}),"\n"]}),"\n",(0,o.jsxs)(n.li,{children:["\n",(0,o.jsx)(n.p,{children:"Create and share your introduction bundle:"}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{className:"language-cpp",children:"const LogosResult bundle = m_logos->chat_module.create_intro_bundle();\nif (bundle.success) {\n const QString myBundle = bundle.getValue<QString>();\n // share `myBundle` out of band (the recipient pastes it \u2014 see Step 6)\n}\n"})}),"\n",(0,o.jsx)(n.admonition,{type:"warning"}),"\n"]}),"\n"]}),"\n",(0,o.jsxs)(n.p,{children:["Each introduction bundle is ",(0,o.jsx)(n.strong,{children:"single-use"})," \u2014 it can open exactly one conversation. Generate a fresh bundle with ",(0,o.jsx)(n.code,{children:"create_intro_bundle()"})," for every new contact you want to be able to reach you.\n:::"]}),"\n",(0,o.jsxs)(n.ol,{start:"4",children:["\n",(0,o.jsxs)(n.li,{children:["\n",(0,o.jsx)(n.p,{children:"Open a private conversation as the initiator, or receive one as the recipient:"}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{className:"language-cpp",children:'// content is plain text \u2014 no encoding required.\nconst LogosResult res = m_logos->chat_module.create_conversation(peerBundle, "Hello!");\n// On success a conversation_created event (is_outgoing == true) fires with the new convo_id.\n'})}),"\n",(0,o.jsxs)(n.ul,{children:["\n",(0,o.jsxs)(n.li,{children:["The initiator calls ",(0,o.jsx)(n.code,{children:"create_conversation"})," with the peer's intro bundle and a plain-text opening message."]}),"\n",(0,o.jsxs)(n.li,{children:["The recipient does not call anything; a ",(0,o.jsx)(n.code,{children:"conversation_created"})," push event arrives automatically, followed by a ",(0,o.jsx)(n.code,{children:"message_received"})," event."]}),"\n"]}),"\n"]}),"\n",(0,o.jsxs)(n.li,{children:["\n",(0,o.jsx)(n.p,{children:"Send and receive messages:"}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{className:"language-cpp",children:'m_logos->chat_module.send_message(convoId, "How are you?");\n// On success a message_sent event fires locally; the peer receives a message_received event.\n'})}),"\n",(0,o.jsxs)(n.ul,{children:["\n",(0,o.jsx)(n.li,{children:"Message content is plain text in both directions \u2014 the module handles encoding and end-to-end encryption on the wire."}),"\n"]}),"\n"]}),"\n",(0,o.jsxs)(n.li,{children:["\n",(0,o.jsx)(n.p,{children:"Read history and conversation state at any time (synchronous reads):"}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{className:"language-cpp",children:"const QVariantList convos = m_logos->chat_module.list_conversations(); // [Conversation]\nconst QVariantList msgs = m_logos->chat_module.get_messages(convoId); // [Message]\nconst QVariantMap st = m_logos->chat_module.status().toMap(); // { convo_count, delivery_state, detail }\n"})}),"\n",(0,o.jsx)(n.admonition,{type:"warning"}),"\n"]}),"\n"]}),"\n",(0,o.jsxs)(n.p,{children:["Do not make a synchronous module read (",(0,o.jsx)(n.code,{children:"list_conversations"}),", ",(0,o.jsx)(n.code,{children:"get_messages"}),", ",(0,o.jsx)(n.code,{children:"status"}),") from ",(0,o.jsx)(n.em,{children:"inside"})," an event handler \u2014 it re-enters the IPC replica while its read notifier is disabled and stalls until the call times out. Defer the read to the next event-loop turn instead (see ",(0,o.jsx)(n.code,{children:"deferToEventLoop"})," in ",(0,o.jsx)(n.a,{href:"https://github.com/logos-co/logos-chat-ui/blob/v0.1.2/src/ChatBackend.cpp",children:(0,o.jsx)(n.code,{children:"logos-chat-ui"})}),").\n:::"]}),"\n",(0,o.jsxs)(n.ol,{start:"7",children:["\n",(0,o.jsxs)(n.li,{children:["\n",(0,o.jsx)(n.p,{children:"Shut down cleanly:"}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{className:"language-cpp",children:"m_logos->chat_module.shutdown(); // disconnects and tears the client down\n"})}),"\n"]}),"\n"]}),"\n",(0,o.jsx)(n.h2,{id:"step-5-build-and-run",children:"Step 5: Build and run"}),"\n",(0,o.jsxs)(n.ol,{children:["\n",(0,o.jsxs)(n.li,{children:["\n",(0,o.jsx)(n.p,{children:"Build the module:"}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{className:"language-sh",children:"nix build\n"})}),"\n"]}),"\n",(0,o.jsxs)(n.li,{children:["\n",(0,o.jsxs)(n.p,{children:["Preview the module using ",(0,o.jsx)(n.code,{children:"logos-standalone-app"})," (for ",(0,o.jsx)(n.code,{children:"ui_qml"})," modules):"]}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{className:"language-sh",children:"nix run # preview via logos-standalone-app (for ui_qml modules)\nnix build .#lgx # package as .lgx for installation into logos-basecamp\n"})}),"\n"]}),"\n"]}),"\n",(0,o.jsx)(n.h2,{id:"step-6-verify-a-two-instance-exchange",children:"Step 6: Verify a two-instance exchange"}),"\n",(0,o.jsxs)(n.p,{children:["A chat is only proven end to end when a message travels between two running instances. Start two copies of your module \u2014 each with its own ",(0,o.jsx)(n.code,{children:"instance_path"})," and ",(0,o.jsx)(n.code,{children:"tcp_port"})," \u2014 and confirm a message lands as a ",(0,o.jsx)(n.code,{children:"message_received"})," event."]}),"\n",(0,o.jsxs)(n.ol,{children:["\n",(0,o.jsxs)(n.li,{children:["Start both instances and wait until each reports ",(0,o.jsx)(n.code,{children:'delivery_state == "online"'})," via ",(0,o.jsx)(n.code,{children:"delivery_state_changed"}),"."]}),"\n",(0,o.jsxs)(n.li,{children:["In instance A, call ",(0,o.jsx)(n.code,{children:"create_intro_bundle()"})," and share the returned bundle out of band (copy it into instance B)."]}),"\n",(0,o.jsxs)(n.li,{children:["In instance B, call ",(0,o.jsx)(n.code,{children:'create_conversation(bundleFromA, "Hello from B")'}),". Instance A receives a ",(0,o.jsx)(n.code,{children:"conversation_created"})," event (",(0,o.jsx)(n.code,{children:"is_outgoing == false"}),") followed by a ",(0,o.jsx)(n.code,{children:"message_received"})," event carrying ",(0,o.jsx)(n.code,{children:'"Hello from B"'}),"."]}),"\n",(0,o.jsxs)(n.li,{children:["In instance A, reply with ",(0,o.jsx)(n.code,{children:'send_message(convoId, "Hi B")'}),". Instance B receives the matching ",(0,o.jsx)(n.code,{children:"message_received"})," event."]}),"\n"]}),"\n",(0,o.jsxs)(n.p,{children:["Seeing the ",(0,o.jsx)(n.code,{children:"message_received"})," events on both sides confirms the full round trip: identity, intro-bundle exchange, conversation setup, and end-to-end encrypted delivery."]}),"\n",(0,o.jsx)(n.h2,{id:"troubleshooting-the-logos-chat-module",children:"Troubleshooting the Logos Chat module"}),"\n",(0,o.jsx)(n.h3,{id:"why-does-a-method-fail",children:"Why does a method fail?"}),"\n",(0,o.jsxs)(n.p,{children:["The method returns a ",(0,o.jsx)(n.code,{children:"LogosResult"})," with ",(0,o.jsx)(n.code,{children:"success == false"})," and a reason in ",(0,o.jsx)(n.code,{children:"getError<QString>()"}),". The most common cause is calling a conversation or message method before ",(0,o.jsx)(n.code,{children:"init()"})," succeeded, or before delivery reached the ",(0,o.jsx)(n.code,{children:"online"})," state. Call ",(0,o.jsx)(n.code,{children:"init()"})," first, check ",(0,o.jsx)(n.code,{children:"res.success"}),", and wait for a ",(0,o.jsx)(n.code,{children:"delivery_state_changed"})," event with ",(0,o.jsx)(n.code,{children:'delivery_state == "online"'})," before creating conversations or sending messages."]}),"\n",(0,o.jsx)(n.h3,{id:"why-do-peers-not-connect-or-messages-not-propagate",children:"Why do peers not connect or messages not propagate?"}),"\n",(0,o.jsxs)(n.p,{children:["The ",(0,o.jsx)(n.code,{children:"delivery_preset"})," differs across instances, or delivery has not reached ",(0,o.jsx)(n.code,{children:"online"}),". All participants must use the same preset (for example ",(0,o.jsx)(n.code,{children:"logos.test"}),") to share a network, and each instance must report ",(0,o.jsx)(n.code,{children:"online"})," via ",(0,o.jsx)(n.code,{children:"delivery_state_changed"})," (or ",(0,o.jsx)(n.code,{children:"status()"}),") before it can exchange messages."]}),"\n",(0,o.jsx)(n.h3,{id:"why-does-a-read-stall-the-ui",children:"Why does a read stall the UI?"}),"\n",(0,o.jsxs)(n.p,{children:["You issued a synchronous module read (",(0,o.jsx)(n.code,{children:"list_conversations"}),", ",(0,o.jsx)(n.code,{children:"get_messages"}),", ",(0,o.jsx)(n.code,{children:"status"}),") from inside an event handler. That re-enters the IPC replica while its read notifier is disabled and blocks until the call times out. Defer such reads to the next event-loop turn."]}),"\n",(0,o.jsx)(n.h3,{id:"why-is-a-previously-created-conversation-still-there-after-a-restart",children:"Why is a previously created conversation still there after a restart?"}),"\n",(0,o.jsxs)(n.p,{children:["Chat state is ",(0,o.jsx)(n.strong,{children:"persistent"}),". Identity and history live in the instance directory passed to ",(0,o.jsx)(n.code,{children:"init()"})," (",(0,o.jsx)(n.code,{children:"identity.db"}),", ",(0,o.jsx)(n.code,{children:"history.json"}),"), so restarting against the same directory restores conversations and your identity. Point ",(0,o.jsx)(n.code,{children:"init()"})," at a fresh directory to start from a clean state."]})]})}function h(e={}){let{wrapper:n}={...(0,i.R)(),...e.components};return n?(0,o.jsx)(n,{...e,children:(0,o.jsx)(a,{...e})}):a(e)}},8453(e,n,s){s.d(n,{R:()=>d,x:()=>l});var t=s(6540);let o={},i=t.createContext(o);function d(e){let n=t.useContext(i);return t.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function l(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(o):e.components||o:d(e.components),t.createElement(i.Provider,{value:n},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/c1de2c67.e3f6face.js b/assets/js/c1de2c67.e3f6face.js new file mode 100644 index 00000000..a903f929 --- /dev/null +++ b/assets/js/c1de2c67.e3f6face.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunklogos_docs=self.webpackChunklogos_docs||[]).push([["2299"],{9469(e,t,n){n.r(t),n.d(t,{metadata:()=>s,default:()=>h,frontMatter:()=>o,contentTitle:()=>i,toc:()=>c,assets:()=>l});var s=JSON.parse('{"id":"lez/accounts/set-up-shared-private-lez-account","title":"Set up a shared private account","description":"Get started with group-owned private accounts where every member independently derives the same keys.","source":"@site/docs/lez/accounts/set-up-shared-private-lez-account.md","sourceDirName":"lez/accounts","slug":"/lez/accounts/set-up-shared-private-lez-account","permalink":"/lez/accounts/set-up-shared-private-lez-account","draft":false,"unlisted":false,"editUrl":"https://github.com/logos-co/logos-docs/tree/main/docs/lez/accounts/set-up-shared-private-lez-account.md","tags":[],"version":"current","sidebarPosition":1,"frontMatter":{"title":"Set up a shared private account","doc_type":"procedure","product":"lez","topics":"lez","steps_layout":"sectioned","authors":"moudyellaz, kashepavadan","owner":"logos","doc_version":1,"slug":"set-up-shared-private-lez-account","sidebar_position":1},"sidebar":"lezSidebar","previous":{"title":"Start and use an instance of the LEZ Explorer","permalink":"/lez/follow-chain/start-and-use-instance-of-lez-explorer"}}'),r=n(4848),a=n(8453);let o={title:"Set up a shared private account",doc_type:"procedure",product:"lez",topics:"lez",steps_layout:"sectioned",authors:"moudyellaz, kashepavadan",owner:"logos",doc_version:1,slug:"set-up-shared-private-lez-account",sidebar_position:1},i="Set up a shared private LEZ account",l={},c=[{value:"Get started with group-owned private accounts where every member independently derives the same keys.",id:"get-started-with-group-owned-private-accounts-where-every-member-independently-derives-the-same-keys",level:4},{value:"What to expect",id:"what-to-expect",level:2},{value:"Step 1: Generate a sealing key pair for each member",id:"step-1-generate-a-sealing-key-pair-for-each-member",level:2},{value:"Step 2: Create the group and the shared account",id:"step-2-create-the-group-and-the-shared-account",level:2},{value:"Step 3: Invite a new member by sealing the GMS",id:"step-3-invite-a-new-member-by-sealing-the-gms",level:2},{value:"Step 4: Join the group and derive the shared account",id:"step-4-join-the-group-and-derive-the-shared-account",level:2}];function d(e){let t={a:"a",code:"code",h1:"h1",h2:"h2",h4:"h4",header:"header",li:"li",ol:"ol",p:"p",pre:"pre",ul:"ul",...(0,a.R)(),...e.components};return(0,r.jsxs)(r.Fragment,{children:[(0,r.jsx)(t.header,{children:(0,r.jsx)(t.h1,{id:"set-up-a-shared-private-lez-account",children:"Set up a shared private LEZ account"})}),"\n",(0,r.jsx)(t.h4,{id:"get-started-with-group-owned-private-accounts-where-every-member-independently-derives-the-same-keys",children:"Get started with group-owned private accounts where every member independently derives the same keys."}),"\n",(0,r.jsxs)(t.p,{children:["This procedure covers how to create a shared ",(0,r.jsx)(t.a,{href:"https://docs.logos.co/get-started/glossary#private-account",children:"private account"})," (regular or ",(0,r.jsx)(t.a,{href:"https://docs.logos.co/get-started/glossary#pda",children:"PDA"}),") on the ",(0,r.jsx)(t.a,{href:"https://docs.logos.co/get-started/glossary#lez",children:"LEZ"})," that is jointly controlled by multiple members. From one 32-byte ",(0,r.jsx)(t.a,{href:"https://docs.logos.co/get-started/glossary#group-master-secret",children:"Group Master Secret"})," (",(0,r.jsx)(t.a,{href:"https://docs.logos.co/get-started/glossary#gms",children:"GMS"}),"), every member independently derives the same ",(0,r.jsx)(t.a,{href:"https://docs.logos.co/get-started/glossary#account",children:"account"})," keys (",(0,r.jsx)(t.a,{href:"https://docs.logos.co/get-started/glossary#nsk",children:"NSK"}),", ",(0,r.jsx)(t.a,{href:"https://docs.logos.co/get-started/glossary#vsk",children:"VSK"}),", ",(0,r.jsx)(t.a,{href:"https://docs.logos.co/get-started/glossary#npk",children:"NPK"}),", ",(0,r.jsx)(t.a,{href:"https://docs.logos.co/get-started/glossary#vpk",children:"VPK"}),"), so any member can view and spend the shared balance without an interactive key exchange at spend time. It is intended for developers on testnet v0.2 who need multi-party custody of a private balance or a private PDA."]}),"\n",(0,r.jsxs)(t.p,{children:["This feature is 1-of-n at the key layer: any GMS holder can derive every key and spend the account. Threshold gating must be implemented at the ",(0,r.jsx)(t.a,{href:"https://docs.logos.co/get-started/glossary#program",children:"program"})," layer. View-only membership is not supported \u2014 any GMS holder gets both viewing and spending capability."]}),"\n",(0,r.jsx)(t.p,{children:"Before you start, make sure you have the following:"}),"\n",(0,r.jsxs)(t.ul,{children:["\n",(0,r.jsx)(t.li,{children:"Linux or macOS \u2014 macOS requires full Xcode with the Metal toolchain for the Risc0 guest build, not just command-line tools"}),"\n",(0,r.jsxs)(t.li,{children:["Rust toolchain and ",(0,r.jsx)(t.code,{children:"cargo"})]}),"\n",(0,r.jsxs)(t.li,{children:[(0,r.jsx)(t.code,{children:"justfile"})," recipes from the ",(0,r.jsx)(t.a,{href:"https://github.com/logos-blockchain/logos-execution-zone",children:"logos-execution-zone"})," repository"]}),"\n",(0,r.jsxs)(t.li,{children:["A local clone of the ",(0,r.jsx)(t.a,{href:"https://github.com/logos-blockchain/logos-execution-zone",children:"LEZ repository"})," and ",(0,r.jsx)(t.a,{href:"/lez/get-started/connect-wallet-cli-to-lez-testnet",children:"wallet"})," set up and funded"]}),"\n"]}),"\n",(0,r.jsx)(t.h2,{id:"what-to-expect",children:"What to expect"}),"\n",(0,r.jsxs)(t.ul,{children:["\n",(0,r.jsx)(t.li,{children:"You can create a shared private account from a single GMS so that every invited member independently derives the same NPK, VPK, NSK, and VSK without an interactive key exchange at spend time."}),"\n",(0,r.jsx)(t.li,{children:"You can admit new members by sealing the GMS to their sealing public key and having them unseal it locally, with no shared secrets transmitted in the clear."}),"\n",(0,r.jsx)(t.li,{children:"You can create a group-owned PDA family where each PDA is distinguished by an identifier derived from the same group keys."}),"\n"]}),"\n",(0,r.jsx)(t.h2,{id:"step-1-generate-a-sealing-key-pair-for-each-member",children:"Step 1: Generate a sealing key pair for each member"}),"\n",(0,r.jsxs)(t.p,{children:["Each member who will join a group needs a one-time sealing key pair before the owner can invite them. The sealing key uses ML-KEM-768 and is kept separate from account ",(0,r.jsx)(t.a,{href:"https://docs.logos.co/get-started/glossary#viewing-keys",children:"viewing keys"}),"."]}),"\n",(0,r.jsxs)(t.ol,{children:["\n",(0,r.jsxs)(t.li,{children:["\n",(0,r.jsx)(t.p,{children:"Each joining member generates their sealing key pair:"}),"\n",(0,r.jsx)(t.pre,{children:(0,r.jsx)(t.code,{className:"language-sh",children:"wallet group new-sealing-key\n"})}),"\n",(0,r.jsxs)(t.ul,{children:["\n",(0,r.jsx)(t.li,{children:"This prints the sealing public key. Each member shares the public key with the group owner."}),"\n",(0,r.jsx)(t.li,{children:"Run this once per wallet. The secret key is stored locally and never shared."}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,r.jsx)(t.h2,{id:"step-2-create-the-group-and-the-shared-account",children:"Step 2: Create the group and the shared account"}),"\n",(0,r.jsx)(t.p,{children:"The group owner creates a local group with a fresh random GMS, then derives a shared account from it. Any account derived from the same group will have identical keys for every member who holds the GMS."}),"\n",(0,r.jsxs)(t.ol,{children:["\n",(0,r.jsxs)(t.li,{children:["\n",(0,r.jsx)(t.p,{children:"Create the group with a fresh random GMS:"}),"\n",(0,r.jsx)(t.pre,{children:(0,r.jsx)(t.code,{className:"language-sh",children:"wallet group new test-group\n"})}),"\n",(0,r.jsxs)(t.ul,{children:["\n",(0,r.jsxs)(t.li,{children:["The group is registered locally and visible in ",(0,r.jsx)(t.code,{children:"wallet group list"})," (alias ",(0,r.jsx)(t.code,{children:"ls"}),")."]}),"\n"]}),"\n"]}),"\n",(0,r.jsxs)(t.li,{children:["\n",(0,r.jsx)(t.p,{children:"Create a shared regular private account derived from the group:"}),"\n",(0,r.jsx)(t.pre,{children:(0,r.jsx)(t.code,{className:"language-sh",children:"wallet account new private-gms test-group --label shared-acc\n"})}),"\n",(0,r.jsxs)(t.ul,{children:["\n",(0,r.jsxs)(t.li,{children:["\n",(0,r.jsx)(t.p,{children:"For a group-owned PDA instead of a regular private account, pass the PDA flags:"}),"\n",(0,r.jsx)(t.pre,{children:(0,r.jsx)(t.code,{className:"language-sh",children:"wallet account new private-gms test-group --pda \\\n --seed <32-byte-hex> --program-id <program-id-hex> --identifier 0\n"})}),"\n",(0,r.jsxs)(t.p,{children:[(0,r.jsx)(t.code,{children:"--identifier"})," diversifies one PDA from another within the same ",(0,r.jsx)(t.code,{children:"(program_id, seed)"})," family; it defaults to a random value if omitted."]}),"\n"]}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,r.jsx)(t.h2,{id:"step-3-invite-a-new-member-by-sealing-the-gms",children:"Step 3: Invite a new member by sealing the GMS"}),"\n",(0,r.jsx)(t.p,{children:"The owner seals the GMS to each new member's sealing public key using ML-KEM-768. The sealed GMS is safe to transmit over any channel."}),"\n",(0,r.jsxs)(t.ol,{children:["\n",(0,r.jsxs)(t.li,{children:["\n",(0,r.jsx)(t.p,{children:"Seal the group's GMS for the new member using their sealing public key:"}),"\n",(0,r.jsx)(t.pre,{children:(0,r.jsx)(t.code,{className:"language-sh",children:"wallet group invite test-group --key <joining-member-sealing-pubkey-hex>\n"})}),"\n",(0,r.jsxs)(t.ul,{children:["\n",(0,r.jsx)(t.li,{children:"Share this hex string with the new member over any channel. Repeat this step for each additional member."}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,r.jsx)(t.h2,{id:"step-4-join-the-group-and-derive-the-shared-account",children:"Step 4: Join the group and derive the shared account"}),"\n",(0,r.jsx)(t.p,{children:"The new member unseals the GMS using their local sealing secret key and derives their instance of the shared account. The derived keys are identical to those held by every other member."}),"\n",(0,r.jsxs)(t.ol,{children:["\n",(0,r.jsxs)(t.li,{children:["\n",(0,r.jsx)(t.p,{children:"Unseal the GMS and store it under a local group name:"}),"\n",(0,r.jsx)(t.pre,{children:(0,r.jsx)(t.code,{className:"language-sh",children:"wallet group join my-copy --sealed <sealed-gms-hex-from-owner>\n"})}),"\n",(0,r.jsxs)(t.ul,{children:["\n",(0,r.jsxs)(t.li,{children:["The GMS is now stored locally under ",(0,r.jsx)(t.code,{children:"my-copy"}),". Both the owner and this member now hold the same GMS."]}),"\n"]}),"\n"]}),"\n",(0,r.jsxs)(t.li,{children:["\n",(0,r.jsx)(t.p,{children:"Derive the shared account from the joined group:"}),"\n",(0,r.jsx)(t.pre,{children:(0,r.jsx)(t.code,{className:"language-sh",children:"wallet account new private-gms my-copy\n"})}),"\n",(0,r.jsxs)(t.ul,{children:["\n",(0,r.jsx)(t.li,{children:"This produces the same NPK, VPK, NSK, and VSK as the owner's account derived in Step 2."}),"\n"]}),"\n"]}),"\n"]})]})}function h(e={}){let{wrapper:t}={...(0,a.R)(),...e.components};return t?(0,r.jsx)(t,{...e,children:(0,r.jsx)(d,{...e})}):d(e)}},8453(e,t,n){n.d(t,{R:()=>o,x:()=>i});var s=n(6540);let r={},a=s.createContext(r);function o(e){let t=s.useContext(a);return s.useMemo(function(){return"function"==typeof e?e(t):{...t,...e}},[t,e])}function i(e){let t;return t=e.disableParentContext?"function"==typeof e.components?e.components(r):e.components||r:o(e.components),s.createElement(a.Provider,{value:t},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/c634495e.3759b944.js b/assets/js/c634495e.3759b944.js new file mode 100644 index 00000000..316a80b1 --- /dev/null +++ b/assets/js/c634495e.3759b944.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunklogos_docs=self.webpackChunklogos_docs||[]).push([["6723"],{3954(e,n,s){s.r(n),s.d(n,{metadata:()=>o,default:()=>h,frontMatter:()=>a,contentTitle:()=>l,toc:()=>d,assets:()=>r});var o=JSON.parse('{"id":"blockchain/node-app/bridge-assets-from-logos-blockchain-to-zone-using-app","title":"Bridge assets from Logos Blockchain to a Zone using the Logos Blockchain app","description":"Get started locking wallet notes into a Logos Zone, including the LEZ, using the Logos Blockchain UI app.","source":"@site/docs/blockchain/node-app/bridge-assets-from-logos-blockchain-to-zone-using-app.md","sourceDirName":"blockchain/node-app","slug":"/blockchain/node-app/bridge-assets-from-logos-blockchain-to-zone-using-app","permalink":"/blockchain/node-app/bridge-assets-from-logos-blockchain-to-zone-using-app","draft":false,"unlisted":false,"editUrl":"https://github.com/logos-co/logos-docs/tree/main/docs/blockchain/node-app/bridge-assets-from-logos-blockchain-to-zone-using-app.md","tags":[],"version":"current","sidebarPosition":3,"frontMatter":{"title":"Bridge assets from Logos Blockchain to a Zone using the Logos Blockchain app","doc_type":"procedure","product":"blockchain","topics":["lez","bridging"],"steps_layout":"flat","authors":"danielsanchezq, kashepavadan","owner":"logos","doc_version":1,"slug":"bridge-assets-from-logos-blockchain-to-zone-using-app","sidebar_position":3},"sidebar":"blockchainSidebar","previous":{"title":"Move assets using the Logos Blockchain UI app","permalink":"/blockchain/node-app/move-assets-using-logos-blockchain-app"},"next":{"title":"Claim leader rewards in the Logos Blockchain UI app","permalink":"/blockchain/node-app/claim-leader-rewards-in-logos-blockchain-ui-app"}}'),t=s(4848),i=s(8453);let a={title:"Bridge assets from Logos Blockchain to a Zone using the Logos Blockchain app",doc_type:"procedure",product:"blockchain",topics:["lez","bridging"],steps_layout:"flat",authors:"danielsanchezq, kashepavadan",owner:"logos",doc_version:1,slug:"bridge-assets-from-logos-blockchain-to-zone-using-app",sidebar_position:3},l="Bridge assets from Logos Blockchain to a Zone using the Logos Blockchain app",r={},d=[{value:"Get started locking wallet notes into a Logos Zone, including the LEZ, using the Logos Blockchain UI app.",id:"get-started-locking-wallet-notes-into-a-logos-zone-including-the-lez-using-the-logos-blockchain-ui-app",level:4},{value:"What to expect",id:"what-to-expect",level:2},{value:"Launch the app and start the node",id:"launch-the-app-and-start-the-node",level:2},{value:"Submit a deposit",id:"submit-a-deposit",level:2},{value:"Read the deposit result",id:"read-the-deposit-result",level:2},{value:"Troubleshooting channel deposits",id:"troubleshooting-channel-deposits",level:2},{value:"Why is Channel Deposit unavailable in the Operations tab?",id:"why-is-channel-deposit-unavailable-in-the-operations-tab",level:3},{value:"Why do no notes appear after selecting an address?",id:"why-do-no-notes-appear-after-selecting-an-address",level:3},{value:"Why does Next stay disabled on Step 2?",id:"why-does-next-stay-disabled-on-step-2",level:3},{value:"Why does the wizard show "Deposit failed"?",id:"why-does-the-wizard-show-deposit-failed",level:3}];function c(e){let n={a:"a",admonition:"admonition",code:"code",h1:"h1",h2:"h2",h3:"h3",h4:"h4",header:"header",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,i.R)(),...e.components};return(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)(n.header,{children:(0,t.jsx)(n.h1,{id:"bridge-assets-from-logos-blockchain-to-a-zone-using-the-logos-blockchain-app",children:"Bridge assets from Logos Blockchain to a Zone using the Logos Blockchain app"})}),"\n",(0,t.jsx)(n.h4,{id:"get-started-locking-wallet-notes-into-a-logos-zone-including-the-lez-using-the-logos-blockchain-ui-app",children:"Get started locking wallet notes into a Logos Zone, including the LEZ, using the Logos Blockchain UI app."}),"\n",(0,t.jsxs)(n.p,{children:["This procedure covers how to lock one or more of your wallet ",(0,t.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#note",children:"notes"})," (UTXOs) into a Logos ",(0,t.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#zone",children:"Zone"})," (such as the ",(0,t.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#lez",children:"LEZ"}),") using the ",(0,t.jsx)(n.a,{href:"/blockchain/node-app/build-and-run-logos-blockchain-node-app-ui",children:"Logos Blockchain desktop app"}),", and receive the resulting transaction hash. It is intended for wallet users on testnet v0.2 who need to fund a ",(0,t.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#channel",children:"channel"})," for off-chain or on-chain channel operations without hand-assembling note IDs, keys, and fees on a CLI."]}),"\n",(0,t.jsx)(n.p,{children:"Before you start, make sure you have the following:"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:["\n",(0,t.jsxs)(n.p,{children:["The ",(0,t.jsx)(n.a,{href:"/blockchain/node-app/build-and-run-logos-blockchain-node-app-ui",children:"Logos Blockchain UI app"})," built and able to launch"]}),"\n"]}),"\n",(0,t.jsxs)(n.li,{children:["\n",(0,t.jsxs)(n.p,{children:["A ",(0,t.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#logos-blockchain",children:"Logos Blockchain"})," node user config selected or generated on the first screen, with an associated keystore (",(0,t.jsx)(n.code,{children:"user_config.yaml"}),", optional ",(0,t.jsx)(n.code,{children:"deployment-settings.yaml"}),", ",(0,t.jsx)(n.code,{children:"keystore.yaml"}),")"]}),"\n"]}),"\n",(0,t.jsxs)(n.li,{children:["\n",(0,t.jsxs)(n.p,{children:["At least one wallet address with a positive balance, for example one funded via the faucet at ",(0,t.jsx)(n.a,{href:"https://testnet.blockchain.logos.co/web/faucet/",children:(0,t.jsx)(n.code,{children:"testnet.blockchain.logos.co"})})]}),"\n"]}),"\n",(0,t.jsxs)(n.li,{children:["\n",(0,t.jsx)(n.p,{children:"The target Zone's channel ID you are depositing into. You can get the channel ID of the public LEZ instance by running:"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-bash",children:'curl https://testnet.lez.logos.co/ \\\n -H "Content-Type: application/json" \\\n -d "{ \\\n \\"jsonrpc\\": \\"2.0\\", \\\n \\"method\\": \\"getChannelId\\", \\\n \\"params\\": {}, \\\n \\"id\\": 1 \\\n }"\n'})}),"\n"]}),"\n",(0,t.jsxs)(n.li,{children:["\n",(0,t.jsx)(n.p,{children:"Public keys for change (receives leftover value) and funding (to pay for gas). This is typically your own wallet address"}),"\n"]}),"\n"]}),"\n",(0,t.jsx)(n.h2,{id:"what-to-expect",children:"What to expect"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsx)(n.li,{children:"You can select one or more wallet notes and lock their full value into a channel through a guided four-step wizard."}),"\n",(0,t.jsx)(n.li,{children:"You can review the exact deposit payload \u2014 channel ID, notes, keys, fee, and metadata \u2014 before submitting."}),"\n",(0,t.jsxs)(n.li,{children:["You can confirm the deposit succeeded by copying the returned transaction hash and finding it in a new block under opcode ",(0,t.jsx)(n.code,{children:"18"})," (Channel Deposit)."]}),"\n"]}),"\n",(0,t.jsx)(n.h2,{id:"launch-the-app-and-start-the-node",children:"Launch the app and start the node"}),"\n",(0,t.jsxs)(n.p,{children:["The ",(0,t.jsx)(n.strong,{children:"Operations"})," tab is disabled until the node is running. Notes and balances are unavailable until the node is also synced."]}),"\n",(0,t.jsxs)(n.ol,{children:["\n",(0,t.jsxs)(n.li,{children:["\n",(0,t.jsx)(n.p,{children:"Launch the app and, on the config screen, select or generate a user config and deployment config. The default deployment will connect to the testnet:"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-sh",children:"nix run\n"})}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsx)(n.li,{children:"This lands you on the main view."}),"\n"]}),"\n"]}),"\n",(0,t.jsxs)(n.li,{children:["\n",(0,t.jsxs)(n.p,{children:["In the ",(0,t.jsx)(n.strong,{children:"Node"})," tab, click ",(0,t.jsx)(n.strong,{children:"Start Node"}),"."]}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:["Status turns ",(0,t.jsx)(n.strong,{children:"Running"})," (green), the consensus card appears and progresses toward ",(0,t.jsx)(n.strong,{children:"Online"}),", and blocks begin streaming in the ",(0,t.jsx)(n.strong,{children:"Blocks"})," list."]}),"\n",(0,t.jsxs)(n.li,{children:["The ",(0,t.jsx)(n.strong,{children:"Operations"})," tab becomes enabled."]}),"\n"]}),"\n"]}),"\n",(0,t.jsxs)(n.li,{children:["\n",(0,t.jsxs)(n.p,{children:["Wait for the consensus card to reach ",(0,t.jsx)(n.strong,{children:"Online"})," before proceeding."]}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsx)(n.li,{children:"Notes and balances will not load until the node reaches this state."}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,t.jsx)(n.h2,{id:"submit-a-deposit",children:"Submit a deposit"}),"\n",(0,t.jsx)(n.p,{children:"The UI collects the deposit payload across three input steps, then submits it on confirmation."}),"\n",(0,t.jsxs)(n.ol,{children:["\n",(0,t.jsxs)(n.li,{children:["\n",(0,t.jsxs)(n.p,{children:["In the ",(0,t.jsx)(n.strong,{children:"Operations"})," tab, click ",(0,t.jsx)(n.strong,{children:"Channel Deposit"})," in the left sidebar."]}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsx)(n.li,{children:"The deposit wizard opens at Step 1."}),"\n"]}),"\n"]}),"\n",(0,t.jsxs)(n.li,{children:["\n",(0,t.jsxs)(n.p,{children:["On ",(0,t.jsx)(n.strong,{children:"Step 1 \u2013 Select notes"}),", choose your address from ",(0,t.jsx)(n.strong,{children:"Known address\u2026"}),", or paste a hex address and commit it with ",(0,t.jsx)(n.strong,{children:"Enter"}),"."]}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsx)(n.li,{children:"Notes load automatically once an address is selected. Selecting a different address reloads them."}),"\n"]}),"\n"]}),"\n",(0,t.jsxs)(n.li,{children:["\n",(0,t.jsxs)(n.p,{children:["Select one or more notes. ",(0,t.jsx)(n.strong,{children:"Next"})," enables once at least one note is selected; the full value of each selected note will be consumed."]}),"\n"]}),"\n",(0,t.jsxs)(n.li,{children:["\n",(0,t.jsxs)(n.p,{children:["On ",(0,t.jsx)(n.strong,{children:"Step 2 \u2013 Fields"}),", enter the deposit parameters:"]}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:["\n",(0,t.jsxs)(n.p,{children:[(0,t.jsx)(n.strong,{children:"Channel ID (hex)"})," \u2014 the target channel."]}),"\n"]}),"\n",(0,t.jsxs)(n.li,{children:["\n",(0,t.jsxs)(n.p,{children:[(0,t.jsx)(n.strong,{children:"Change public key"})," \u2014 prefilled from the selected wallet; receives leftover value."]}),"\n"]}),"\n",(0,t.jsxs)(n.li,{children:["\n",(0,t.jsxs)(n.p,{children:[(0,t.jsx)(n.strong,{children:"Funding public keys"})," \u2014 one per line, prefilled; for paying the gas fee."]}),"\n"]}),"\n",(0,t.jsxs)(n.li,{children:["\n",(0,t.jsxs)(n.p,{children:[(0,t.jsx)(n.strong,{children:"Max tx fee"})," \u2014 Maximum fee allowed to spend"]}),"\n"]}),"\n",(0,t.jsxs)(n.li,{children:["\n",(0,t.jsxs)(n.p,{children:[(0,t.jsx)(n.strong,{children:"Metadata (base58)"})," (Optional) \u2014 Usually specific to the Zone"]}),"\n"]}),"\n",(0,t.jsxs)(n.li,{children:["\n",(0,t.jsxs)(n.p,{children:[(0,t.jsx)(n.strong,{children:"tip hex"})," or ",(0,t.jsx)(n.strong,{children:"Use query tip"})," (Optional) \u2014 The chain tip the deposit is built against. Leave empty to use the node's current tip."]}),"\n"]}),"\n",(0,t.jsxs)(n.li,{children:["\n",(0,t.jsxs)(n.p,{children:["The ",(0,t.jsx)(n.strong,{children:"Next"})," button is enabled once the channel ID, change key, at least one funding key, and max fee are present, and any metadata entered is valid base58."]}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,t.jsxs)(n.li,{children:["\n",(0,t.jsxs)(n.p,{children:["On ",(0,t.jsx)(n.strong,{children:"Step 3 \u2013 Confirm"}),", review the exact payload \u2014 channel ID, notes and total amount, change and funding keys, max fee, metadata, and tip \u2014 then click ",(0,t.jsx)(n.strong,{children:"Confirm & deposit"}),"."]}),"\n",(0,t.jsx)(n.admonition,{type:"warning"}),"\n"]}),"\n"]}),"\n",(0,t.jsx)(n.p,{children:"Review the information carefully. Deposits are irreversible once included in a block.\n:::"}),"\n",(0,t.jsx)(n.h2,{id:"read-the-deposit-result",children:"Read the deposit result"}),"\n",(0,t.jsxs)(n.ol,{children:["\n",(0,t.jsxs)(n.li,{children:["\n",(0,t.jsxs)(n.p,{children:["On ",(0,t.jsx)(n.strong,{children:"Step 4 \u2013 Result"}),", wait for the submission status:"]}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:["On success, the wizard shows ",(0,t.jsx)(n.strong,{children:"Deposit submitted"})," with a copyable transaction hash."]}),"\n",(0,t.jsxs)(n.li,{children:["On failure, the wizard shows ",(0,t.jsx)(n.strong,{children:"Deposit failed"})," with the backend error message."]}),"\n"]}),"\n"]}),"\n",(0,t.jsxs)(n.li,{children:["\n",(0,t.jsxs)(n.p,{children:["Click ",(0,t.jsx)(n.strong,{children:"New deposit"})," to reset the wizard for another deposit."]}),"\n"]}),"\n",(0,t.jsxs)(n.li,{children:["\n",(0,t.jsxs)(n.p,{children:["Confirm the transaction on-chain by expanding the new block in the ",(0,t.jsx)(n.strong,{children:"Node"})," tab's ",(0,t.jsx)(n.strong,{children:"Blocks"})," list and checking that the transaction's opcode is ",(0,t.jsx)(n.code,{children:"18"})," (Channel Deposit)."]}),"\n"]}),"\n"]}),"\n",(0,t.jsx)(n.h2,{id:"troubleshooting-channel-deposits",children:"Troubleshooting channel deposits"}),"\n",(0,t.jsx)(n.h3,{id:"why-is-channel-deposit-unavailable-in-the-operations-tab",children:"Why is Channel Deposit unavailable in the Operations tab?"}),"\n",(0,t.jsxs)(n.p,{children:["The node is not in the ",(0,t.jsx)(n.strong,{children:"Running"})," state. Start it from the ",(0,t.jsx)(n.strong,{children:"Node"})," tab and wait for the status to turn green before opening the wizard."]}),"\n",(0,t.jsx)(n.h3,{id:"why-do-no-notes-appear-after-selecting-an-address",children:"Why do no notes appear after selecting an address?"}),"\n",(0,t.jsxs)(n.p,{children:["The node has not finished syncing, or the selected address has no spendable notes. Wait for the consensus card to reach ",(0,t.jsx)(n.strong,{children:"Online"})," before retrying, and confirm the address actually holds notes."]}),"\n",(0,t.jsx)(n.h3,{id:"why-does-next-stay-disabled-on-step-2",children:"Why does Next stay disabled on Step 2?"}),"\n",(0,t.jsxs)(n.p,{children:["A required field is missing \u2014 channel ID, change key, at least one funding key, or max fee \u2014 or the metadata entered is not valid base58. The metadata field shows ",(0,t.jsx)(n.strong,{children:"Invalid base58 input"})," when this is the cause."]}),"\n",(0,t.jsx)(n.h3,{id:"why-does-the-wizard-show-deposit-failed",children:'Why does the wizard show "Deposit failed"?'}),"\n",(0,t.jsx)(n.p,{children:"The backend rejected the transaction. Common causes are insufficient funds to cover the selected notes plus the max transaction fee, an invalid channel ID or key, or a rejected or expired tip. The exact backend error is shown in the result step."})]})}function h(e={}){let{wrapper:n}={...(0,i.R)(),...e.components};return n?(0,t.jsx)(n,{...e,children:(0,t.jsx)(c,{...e})}):c(e)}},8453(e,n,s){s.d(n,{R:()=>a,x:()=>l});var o=s(6540);let t={},i=o.createContext(t);function a(e){let n=o.useContext(i);return o.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function l(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(t):e.components||t:a(e.components),o.createElement(i.Provider,{value:n},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/c7d7a685.ed90628f.js b/assets/js/c7d7a685.ed90628f.js new file mode 100644 index 00000000..7745cdd1 --- /dev/null +++ b/assets/js/c7d7a685.ed90628f.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunklogos_docs=self.webpackChunklogos_docs||[]).push([["2615"],{3292(e,o,t){t.r(o),t.d(o,{metadata:()=>n,default:()=>h,frontMatter:()=>a,contentTitle:()=>r,toc:()=>l,assets:()=>c});var n=JSON.parse('{"id":"blockchain/get-started/introduction-to-the-logos-blockchain","title":"Introduction to the Logos Blockchain","description":"Learn how Logos provides a private, resilient foundation for decentralised applications.","source":"@site/docs/blockchain/get-started/introduction-to-the-logos-blockchain.md","sourceDirName":"blockchain/get-started","slug":"/blockchain/get-started/introduction-to-the-logos-blockchain","permalink":"/blockchain/get-started/introduction-to-the-logos-blockchain","draft":false,"unlisted":false,"editUrl":"https://github.com/logos-co/logos-docs/tree/main/docs/blockchain/get-started/introduction-to-the-logos-blockchain.md","tags":[],"version":"current","sidebarPosition":1,"frontMatter":{"title":"Introduction to the Logos Blockchain","doc_type":"concept","product":"blockchain","topics":"blockchain","authors":"kashepavadan","owner":"logos","doc_version":1,"slug":"introduction-to-the-logos-blockchain","sidebar_position":1},"sidebar":"blockchainSidebar","next":{"title":"Run a Logos Blockchain node from Basecamp","permalink":"/blockchain/get-started/run-a-logos-blockchain-node-from-basecamp"}}'),i=t(4848),s=t(8453);let a={title:"Introduction to the Logos Blockchain",doc_type:"concept",product:"blockchain",topics:"blockchain",authors:"kashepavadan",owner:"logos",doc_version:1,slug:"introduction-to-the-logos-blockchain",sidebar_position:1},r="Introduction to the Logos Blockchain",c={},l=[{value:"Learn how Logos provides a private, resilient foundation for decentralised applications.",id:"learn-how-logos-provides-a-private-resilient-foundation-for-decentralised-applications",level:4},{value:"The basics",id:"the-basics",level:2},{value:"Design principles",id:"design-principles",level:2},{value:"Architecture",id:"architecture",level:2},{value:"Bedrock",id:"bedrock",level:3},{value:"Blend Network",id:"blend-network",level:3},{value:"Zones",id:"zones",level:3}];function d(e){let o={a:"a",h1:"h1",h2:"h2",h3:"h3",h4:"h4",header:"header",li:"li",p:"p",ul:"ul",...(0,s.R)(),...e.components};return(0,i.jsxs)(i.Fragment,{children:[(0,i.jsx)(o.header,{children:(0,i.jsx)(o.h1,{id:"introduction-to-the-logos-blockchain",children:"Introduction to the Logos Blockchain"})}),"\n",(0,i.jsx)(o.h4,{id:"learn-how-logos-provides-a-private-resilient-foundation-for-decentralised-applications",children:"Learn how Logos provides a private, resilient foundation for decentralised applications."}),"\n",(0,i.jsxs)(o.p,{children:["The ",(0,i.jsx)(o.a,{href:"https://docs.logos.co/get-started/glossary#logos-blockchain",children:"Logos Blockchain"})," is the foundational infrastructure of the Logos technology stack. It hosts decentralised applications and social institutions that require high privacy and resilience, gives those applications a common context for interaction, and guarantees they operate correctly and without corruption. Nodes can participate with consumer hardware and any amount of stake."]}),"\n",(0,i.jsx)(o.h2,{id:"the-basics",children:"The basics"}),"\n",(0,i.jsxs)(o.ul,{children:["\n",(0,i.jsx)(o.li,{children:"The Logos Blockchain is designed to decentralised applications and institutions that require high levels of privacy and resilience."}),"\n",(0,i.jsx)(o.li,{children:"Nodes can join with typical consumer hardware and any amount of stake, which keeps participation barriers low."}),"\n",(0,i.jsxs)(o.li,{children:["Logos Blockchain consists of two layers: lightweight, permissionless blockchains called ",(0,i.jsx)(o.a,{href:"/blockchain/concepts/about-zones",children:"Zones"})," built on top of a Layer 1 foundation called ",(0,i.jsx)(o.a,{href:"/blockchain/concepts/about-bedrock",children:"Bedrock"}),". The ",(0,i.jsx)(o.a,{href:"/blockchain/concepts/about-the-blend-network",children:"Blend Network"})," service is used to improve the privacy of the network."]}),"\n"]}),"\n",(0,i.jsx)(o.h2,{id:"design-principles",children:"Design principles"}),"\n",(0,i.jsx)(o.p,{children:"The Logos Blockchain was designed around three principles: privacy, neutrality, and resilience."}),"\n",(0,i.jsx)(o.p,{children:"Privacy means the network protects information about all participants, regardless of their role. At the infrastructure level, nodes can propose blocks with a very low probability of being traced. Logos also gives developers tools to build applications with programmable privacy, so those applications can keep personal data hidden."}),"\n",(0,i.jsx)(o.p,{children:"Neutrality means activity is handled without compromising the public neutrality of nodes. Nodes can process transactions without having to make their inclusion public."}),"\n",(0,i.jsx)(o.p,{children:"Resilience means the network stays operational and protects privacy under difficult conditions, including a partitioned internet, heavy censorship, and hostile government action. Running a node is meant to be easy enough to do on a laptop, which attracts more nodes and reduces points of failure. A consensus protocol that prioritises liveness lets the network keep operating in the worst conditions."}),"\n",(0,i.jsx)(o.h2,{id:"architecture",children:"Architecture"}),"\n",(0,i.jsxs)(o.p,{children:["The Logos Blockchain is implemented as two blockchain layers. Application execution happens on lightweight, permissionless blockchains called ",(0,i.jsx)(o.a,{href:"/blockchain/concepts/about-zones",children:"Zones"}),", which are built on a Layer 1 foundation called ",(0,i.jsx)(o.a,{href:"/blockchain/concepts/about-bedrock",children:"Bedrock"}),". The ",(0,i.jsx)(o.a,{href:"/blockchain/concepts/about-the-blend-network",children:"Blend Network"})," service extends ",(0,i.jsx)(o.a,{href:"https://docs.logos.co/get-started/glossary#bedrock",children:"Bedrock"}),"'s privacy properties."]}),"\n",(0,i.jsx)(o.h3,{id:"bedrock",children:"Bedrock"}),"\n",(0,i.jsxs)(o.p,{children:["Bedrock is a large-scale validator network that serves as the foundational layer of the Logos Blockchain. It provides consensus, data availability, and lightweight verification to Zones. Its ",(0,i.jsx)(o.a,{href:"https://docs.logos.co/get-started/glossary#private-proof-of-stake",children:"Private Proof of Stake"})," (",(0,i.jsx)(o.a,{href:"https://docs.logos.co/get-started/glossary#ppos",children:"PPoS"}),") consensus protocol, ",(0,i.jsx)(o.a,{href:"/blockchain/concepts/about-cryptarchia",children:"Cryptarchia"}),", keeps block proposers private while staying scalable, resilient, and accessible. Bedrock also enables decentralised sequencing for Zones, token bridging, and inter-",(0,i.jsx)(o.a,{href:"https://docs.logos.co/get-started/glossary#zone",children:"Zone"})," messaging."]}),"\n",(0,i.jsxs)(o.p,{children:["Running a Bedrock validator node means leaving the ",(0,i.jsx)(o.a,{href:"https://docs.logos.co/get-started/glossary#logos-node",children:"Logos node"})," application running in the background, with a low-maintenance approach. This makes it straightforward to contribute to the security, consensus, and interoperability of the network."]}),"\n",(0,i.jsx)(o.h3,{id:"blend-network",children:"Blend Network"}),"\n",(0,i.jsxs)(o.p,{children:["The ",(0,i.jsx)(o.a,{href:"https://docs.logos.co/get-started/glossary#blend-network",children:"Blend Network"})," is an opt-in service that provides network-level privacy for consensus. It virtually eliminates the chance of linking a block proposer to the block they propose, so proposers cannot be targeted. Operators who join the Blend Network need more hardware than Bedrock validation alone requires."]}),"\n",(0,i.jsx)(o.h3,{id:"zones",children:"Zones"}),"\n",(0,i.jsxs)(o.p,{children:["Applications do not run directly on Bedrock. They run on Layer 2 blockchains called ",(0,i.jsx)(o.a,{href:"/blockchain/concepts/about-zones",children:"Zones"}),", which define their own state transitions and validity but rely on Bedrock for consensus guarantees. This modular design lets creators customise a chain and maximise properties such as performance."]}),"\n",(0,i.jsx)(o.p,{children:"Zones can run as fully independent sovereign rollups, but Bedrock also enables token bridging and decentralised sequencing. Zones can exchange asynchronous messages through Bedrock, which allows limited interoperability without giving up the advantages of the sovereign rollup model."}),"\n",(0,i.jsxs)(o.p,{children:["The primary example is the ",(0,i.jsx)(o.a,{href:"/lez/get-started/introduction-to-the-logos-execution-zone",children:"Logos Execution Zone"}),", the initial host for the messaging and storage applications of the Logos stack, with support for private token transfers and ",(0,i.jsx)(o.a,{href:"https://docs.logos.co/get-started/glossary#program",children:"program"})," execution."]})]})}function h(e={}){let{wrapper:o}={...(0,s.R)(),...e.components};return o?(0,i.jsx)(o,{...e,children:(0,i.jsx)(d,{...e})}):d(e)}},8453(e,o,t){t.d(o,{R:()=>a,x:()=>r});var n=t(6540);let i={},s=n.createContext(i);function a(e){let o=n.useContext(s);return n.useMemo(function(){return"function"==typeof e?e(o):{...o,...e}},[o,e])}function r(e){let o;return o=e.disableParentContext?"function"==typeof e.components?e.components(i):e.components||i:a(e.components),n.createElement(s.Provider,{value:o},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/c96c9452.c6df6040.js b/assets/js/c96c9452.c6df6040.js new file mode 100644 index 00000000..aed59110 --- /dev/null +++ b/assets/js/c96c9452.c6df6040.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunklogos_docs=self.webpackChunklogos_docs||[]).push([["6221"],{3417(e,n,o){o.r(n),o.d(n,{metadata:()=>s,default:()=>h,frontMatter:()=>r,contentTitle:()=>t,toc:()=>c,assets:()=>i});var s=JSON.parse('{"id":"messaging/delivery/run-logos-delivery-node","title":"Run a Logos delivery node","description":"Use the Logos Delivery module to run a Logos delivery node","source":"@site/docs/messaging/delivery/run-logos-delivery-node.md","sourceDirName":"messaging/delivery","slug":"/messaging/delivery/run-logos-delivery-node","permalink":"/messaging/delivery/run-logos-delivery-node","draft":false,"unlisted":false,"editUrl":"https://github.com/logos-co/logos-docs/tree/main/docs/messaging/delivery/run-logos-delivery-node.md","tags":[],"version":"current","sidebarPosition":2,"frontMatter":{"title":"Run a Logos delivery node","doc_type":"procedure","product":"messaging","topics":"messaging","steps_layout":"sectioned","authors":"NagyZoltanPeter, kashepavadan","owner":"logos","doc_version":1,"slug":"run-logos-delivery-node","sidebar_position":2},"sidebar":"messagingSidebar","previous":{"title":"Use the Logos Delivery module API from an app","permalink":"/messaging/delivery/use-logos-delivery-module-api-from-app"},"next":{"title":"Understand Logos Delivery protocols","permalink":"/messaging/concepts/protocols"}}'),l=o(4848),d=o(8453);let r={title:"Run a Logos delivery node",doc_type:"procedure",product:"messaging",topics:"messaging",steps_layout:"sectioned",authors:"NagyZoltanPeter, kashepavadan",owner:"logos",doc_version:1,slug:"run-logos-delivery-node",sidebar_position:2},t="Run a Logos delivery node",i={},c=[{value:"Use the Logos Delivery module to run a Logos delivery node",id:"use-the-logos-delivery-module-to-run-a-logos-delivery-node",level:4},{value:"What to expect",id:"what-to-expect",level:2},{value:"Step 1: Install and start the daemon",id:"step-1-install-and-start-the-daemon",level:2},{value:"Step 2: Load the module and boot the node",id:"step-2-load-the-module-and-boot-the-node",level:2},{value:"Step 3: Verify the node is running",id:"step-3-verify-the-node-is-running",level:2},{value:"Troubleshooting delivery node setup",id:"troubleshooting-delivery-node-setup",level:2},{value:"Why does the first <code>docker compose up</code> appear stuck?",id:"why-does-the-first-docker-compose-up-appear-stuck",level:3},{value:"Why does <code>logoscore call</code> return an error after <code>load-module</code>?",id:"why-does-logoscore-call-return-an-error-after-load-module",level:3}];function a(e){let n={a:"a",admonition:"admonition",code:"code",h1:"h1",h2:"h2",h3:"h3",h4:"h4",header:"header",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,d.R)(),...e.components};return(0,l.jsxs)(l.Fragment,{children:[(0,l.jsx)(n.header,{children:(0,l.jsx)(n.h1,{id:"run-a-logos-delivery-node",children:"Run a Logos delivery node"})}),"\n",(0,l.jsx)(n.h4,{id:"use-the-logos-delivery-module-to-run-a-logos-delivery-node",children:"Use the Logos Delivery module to run a Logos delivery node"}),"\n",(0,l.jsxs)(n.p,{children:["This procedure covers how to install and start a ",(0,l.jsx)(n.code,{children:"logos-delivery-module"})," node connected to the Logos testnet (v0.2). It is intended for node operators who want to run their own Delivery node and join the network. Three installation paths are available \u2014 Docker, prebuilt binaries, and Nix \u2014 so you can choose the one that fits your environment."]}),"\n",(0,l.jsx)(n.p,{children:"Choose one of the three installation paths based on your environment:"}),"\n",(0,l.jsxs)(n.table,{children:[(0,l.jsx)(n.thead,{children:(0,l.jsxs)(n.tr,{children:[(0,l.jsx)(n.th,{style:{textAlign:"left"},children:"Path"}),(0,l.jsx)(n.th,{style:{textAlign:"left"},children:"Method"}),(0,l.jsx)(n.th,{style:{textAlign:"left"},children:"Best for"})]})}),(0,l.jsxs)(n.tbody,{children:[(0,l.jsxs)(n.tr,{children:[(0,l.jsx)(n.td,{style:{textAlign:"left"},children:(0,l.jsx)(n.strong,{children:"A"})}),(0,l.jsx)(n.td,{style:{textAlign:"left"},children:"Docker with Compose"}),(0,l.jsx)(n.td,{style:{textAlign:"left"},children:"Quickest start; first build takes 30\u201345 min"})]}),(0,l.jsxs)(n.tr,{children:[(0,l.jsx)(n.td,{style:{textAlign:"left"},children:(0,l.jsx)(n.strong,{children:"B"})}),(0,l.jsx)(n.td,{style:{textAlign:"left"},children:"Prebuilt binaries"}),(0,l.jsx)(n.td,{style:{textAlign:"left"},children:"No build, no clone required"})]}),(0,l.jsxs)(n.tr,{children:[(0,l.jsx)(n.td,{style:{textAlign:"left"},children:(0,l.jsx)(n.strong,{children:"C"})}),(0,l.jsx)(n.td,{style:{textAlign:"left"},children:"Nix"}),(0,l.jsx)(n.td,{style:{textAlign:"left"},children:"From source; most reproducible"})]})]})]}),"\n",(0,l.jsx)(n.p,{children:"Before you start, make sure you have the following:"}),"\n",(0,l.jsxs)(n.ul,{children:["\n",(0,l.jsx)(n.li,{children:"Linux or macOS"}),"\n",(0,l.jsx)(n.li,{children:"Network access so both node instances can reach each other"}),"\n",(0,l.jsxs)(n.li,{children:[(0,l.jsx)(n.code,{children:"jq"})," installed (required for the verify step)"]}),"\n",(0,l.jsxs)(n.li,{children:["Per path: ",(0,l.jsx)(n.strong,{children:"A"})," \u2014 Docker with Compose; ",(0,l.jsx)(n.strong,{children:"B"})," \u2014 ",(0,l.jsx)(n.code,{children:"curl"})," and a shell; ",(0,l.jsx)(n.strong,{children:"C"})," \u2014 ",(0,l.jsx)(n.a,{href:"https://nixos.org/download",children:"Nix with flakes enabled"})]}),"\n"]}),"\n",(0,l.jsx)(n.h2,{id:"what-to-expect",children:"What to expect"}),"\n",(0,l.jsxs)(n.ul,{children:["\n",(0,l.jsxs)(n.li,{children:["You can start a ",(0,l.jsx)(n.code,{children:"delivery_module"})," node connected to the Logos Network."]}),"\n",(0,l.jsxs)(n.li,{children:["You can confirm the node is running and has a live network identity by querying its discv5 ",(0,l.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#enr",children:"ENR"}),"."]}),"\n",(0,l.jsxs)(n.li,{children:["You can configure the node for a different network preset by swapping the config file passed to ",(0,l.jsx)(n.code,{children:"createNode"}),"."]}),"\n"]}),"\n",(0,l.jsx)(n.h2,{id:"step-1-install-and-start-the-daemon",children:"Step 1: Install and start the daemon"}),"\n",(0,l.jsx)(n.p,{children:"Follow the instructions for your chosen path."}),"\n",(0,l.jsx)(n.p,{children:(0,l.jsx)(n.strong,{children:"Path A \u2014 Docker"})}),"\n",(0,l.jsxs)(n.ol,{children:["\n",(0,l.jsxs)(n.li,{children:["\n",(0,l.jsx)(n.p,{children:"Clone the repository and start the daemon container:"}),"\n",(0,l.jsx)(n.pre,{children:(0,l.jsx)(n.code,{className:"language-bash",children:"git clone https://github.com/logos-co/logos-delivery-module.git\ncd logos-delivery-module\ndocker compose up -d --build\n"})}),"\n",(0,l.jsx)(n.admonition,{type:"info"}),"\n"]}),"\n"]}),"\n",(0,l.jsx)(n.p,{children:"The first Docker build runs Nix and downloads release packages. It can take 30\u201345 minutes; subsequent starts are fast.\n:::"}),"\n",(0,l.jsx)(n.p,{children:(0,l.jsx)(n.strong,{children:"Path B \u2014 Prebuilt binaries"})}),"\n",(0,l.jsxs)(n.ol,{children:["\n",(0,l.jsxs)(n.li,{children:["\n",(0,l.jsxs)(n.p,{children:["Install ",(0,l.jsx)(n.code,{children:"logoscore"}),", ",(0,l.jsx)(n.code,{children:"lgpd"}),", and ",(0,l.jsx)(n.code,{children:"lgpm"})," into ",(0,l.jsx)(n.code,{children:"./bin"}),":"]}),"\n",(0,l.jsx)(n.pre,{children:(0,l.jsx)(n.code,{className:"language-bash",children:'curl -fsSL https://raw.githubusercontent.com/logos-co/logos-docs/main/resources/scripts/install-node-tools.sh | sh\nexport PATH="$PWD/bin:$PATH"\n'})}),"\n"]}),"\n",(0,l.jsxs)(n.li,{children:["\n",(0,l.jsxs)(n.p,{children:["Download and install the ",(0,l.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#delivery-module",children:"delivery module"}),":"]}),"\n",(0,l.jsx)(n.pre,{children:(0,l.jsx)(n.code,{className:"language-bash",children:"mkdir -p packages modules\nlgpd download delivery_module --output ./packages\nlgpm install --dir ./packages --modules-dir ./modules\n"})}),"\n"]}),"\n",(0,l.jsxs)(n.li,{children:["\n",(0,l.jsx)(n.p,{children:"Write the testnet config and start the daemon:"}),"\n",(0,l.jsx)(n.pre,{children:(0,l.jsx)(n.code,{className:"language-bash",children:'cat > logos-test.json <<EOF\n{\n "preset": "logos.test",\n "mode": "Core",\n "logLevel": "INFO",\n "tcpPort": 30303,\n "discv5UdpPort": 9000,\n "discv5Discovery": true,\n "nat": "extip:<public-ip>"\n}\nEOF\n\nlogoscore -D -m ./modules > logs.txt\n'})}),"\n"]}),"\n"]}),"\n",(0,l.jsx)(n.p,{children:(0,l.jsx)(n.strong,{children:"Path C \u2014 Nix"})}),"\n",(0,l.jsxs)(n.ol,{children:["\n",(0,l.jsxs)(n.li,{children:["\n",(0,l.jsxs)(n.p,{children:["Clone the repository and build the runtime, package manager, and ",(0,l.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#module",children:"module"}),":"]}),"\n",(0,l.jsx)(n.pre,{children:(0,l.jsx)(n.code,{className:"language-bash",children:"git clone https://github.com/logos-co/logos-delivery-module.git\ncd logos-delivery-module\n\nnix build 'github:logos-co/logos-logoscore-cli' --out-link ./logos\nnix build 'github:logos-co/logos-package-manager#cli' -o lgpm\nnix build '.#lgx' -o delivery-lgx\n"})}),"\n"]}),"\n",(0,l.jsxs)(n.li,{children:["\n",(0,l.jsx)(n.p,{children:"Seed the modules directory and install the module:"}),"\n",(0,l.jsx)(n.pre,{children:(0,l.jsx)(n.code,{className:"language-bash",children:"mkdir -p modules\ncp -RL ./logos/modules/. ./modules/\n./lgpm/bin/lgpm --modules-dir ./modules --allow-unsigned install --file delivery-lgx/*.lgx\n"})}),"\n"]}),"\n",(0,l.jsxs)(n.li,{children:["\n",(0,l.jsx)(n.p,{children:"Start the daemon:"}),"\n",(0,l.jsx)(n.pre,{children:(0,l.jsx)(n.code,{className:"language-bash",children:'export PATH="$PWD/logos/bin:$PATH"\nlogoscore -D -m ./modules > logs.txt\n'})}),"\n"]}),"\n"]}),"\n",(0,l.jsx)(n.h2,{id:"step-2-load-the-module-and-boot-the-node",children:"Step 2: Load the module and boot the node"}),"\n",(0,l.jsx)(n.p,{children:"Run these commands for your path."}),"\n",(0,l.jsxs)(n.ol,{children:["\n",(0,l.jsxs)(n.li,{children:["\n",(0,l.jsx)(n.p,{children:"Load the delivery module:"}),"\n",(0,l.jsx)(n.pre,{children:(0,l.jsx)(n.code,{className:"language-bash",children:"# Path A (Docker)\ndocker exec logos-node logoscore load-module delivery_module --json\n\n# Paths B and C\nlogoscore load-module delivery_module\n"})}),"\n"]}),"\n",(0,l.jsxs)(n.li,{children:["\n",(0,l.jsx)(n.p,{children:"Create the node with the testnet config:"}),"\n",(0,l.jsxs)(n.ul,{children:["\n",(0,l.jsxs)(n.li,{children:["\n",(0,l.jsxs)(n.p,{children:["Path A (config is mounted at ",(0,l.jsx)(n.code,{children:"/conf"})," in the container):"]}),"\n",(0,l.jsx)(n.pre,{children:(0,l.jsx)(n.code,{className:"language-bash",children:"docker exec logos-node logoscore call delivery_module createNode @/conf/logos-test.json --json\n"})}),"\n"]}),"\n",(0,l.jsxs)(n.li,{children:["\n",(0,l.jsx)(n.p,{children:"Path B:"}),"\n",(0,l.jsx)(n.pre,{children:(0,l.jsx)(n.code,{className:"language-bash",children:"logoscore call delivery_module createNode @logos-test.json\n"})}),"\n"]}),"\n",(0,l.jsxs)(n.li,{children:["\n",(0,l.jsx)(n.p,{children:"Path C:"}),"\n",(0,l.jsx)(n.pre,{children:(0,l.jsx)(n.code,{className:"language-bash",children:"logoscore call delivery_module createNode @conf/logos-test.json\n"})}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,l.jsxs)(n.li,{children:["\n",(0,l.jsx)(n.p,{children:"Start the node:"}),"\n",(0,l.jsx)(n.pre,{children:(0,l.jsx)(n.code,{className:"language-bash",children:"# Path A\ndocker exec logos-node logoscore call delivery_module start --json\n\n# Paths B and C\nlogoscore call delivery_module start\n"})}),"\n"]}),"\n"]}),"\n",(0,l.jsx)(n.h2,{id:"step-3-verify-the-node-is-running",children:"Step 3: Verify the node is running"}),"\n",(0,l.jsx)(n.p,{children:"Query the node's discv5 ENR to confirm it booted with a network identity and joined the Logos Testnet."}),"\n",(0,l.jsxs)(n.ol,{children:["\n",(0,l.jsxs)(n.li,{children:["\n",(0,l.jsx)(n.p,{children:"Verify the status of the node:"}),"\n",(0,l.jsx)(n.pre,{children:(0,l.jsx)(n.code,{className:"language-bash",children:"# Path A\ndocker exec logos-node logoscore status --json\n\n# Paths B and C\nlogoscore status\n"})}),"\n"]}),"\n",(0,l.jsxs)(n.li,{children:["\n",(0,l.jsxs)(n.p,{children:["Run the ",(0,l.jsx)(n.a,{href:"https://github.com/logos-co/logos-delivery-module/blob/master/docs/query-node.md",children:"health query"}),":"]}),"\n",(0,l.jsx)(n.pre,{children:(0,l.jsx)(n.code,{className:"language-bash",children:"# Path A\ndocker exec logos-node logoscore call delivery_module getNodeInfo MyENR --json | jq\n\n# Paths B and C\nlogoscore call delivery_module getNodeInfo MyENR --json | jq\n"})}),"\n",(0,l.jsx)(n.p,{children:"Expected output:"}),"\n",(0,l.jsx)(n.pre,{children:(0,l.jsx)(n.code,{className:"language-json",children:'{\n "method": "getNodeInfo",\n "module": "delivery_module",\n "result": {\n "error": null,\n "success": true,\n "value": "enr:-LW4QItc5tHj3rWoFaaQIUWvaBYijDf2TJKW83SNdyylJVAYVoUlBl1h5..."\n },\n "status": "ok"\n}\n'})}),"\n",(0,l.jsxs)(n.ul,{children:["\n",(0,l.jsxs)(n.li,{children:["The ",(0,l.jsx)(n.code,{children:'"value"'})," field is your node's live ENR and will differ from the example above."]}),"\n",(0,l.jsxs)(n.li,{children:[(0,l.jsx)(n.code,{children:'"success": true'})," and ",(0,l.jsx)(n.code,{children:'"status": "ok"'})," confirm the delivery node is running."]}),"\n"]}),"\n"]}),"\n",(0,l.jsxs)(n.li,{children:["\n",(0,l.jsx)(n.p,{children:"Stop the node when finished:"}),"\n",(0,l.jsxs)(n.ul,{children:["\n",(0,l.jsxs)(n.li,{children:["Path A: ",(0,l.jsx)(n.code,{children:"docker compose down"})]}),"\n",(0,l.jsxs)(n.li,{children:["Paths B and C: ",(0,l.jsx)(n.code,{children:"logoscore stop"})]}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,l.jsx)(n.h2,{id:"troubleshooting-delivery-node-setup",children:"Troubleshooting delivery node setup"}),"\n",(0,l.jsxs)(n.h3,{id:"why-does-the-first-docker-compose-up-appear-stuck",children:["Why does the first ",(0,l.jsx)(n.code,{children:"docker compose up"})," appear stuck?"]}),"\n",(0,l.jsx)(n.p,{children:"The first build runs Nix and downloads release packages, which takes 30\u201345 minutes on a typical connection. The process is not hung \u2014 let it finish. Subsequent starts use the cached layers and complete in seconds."}),"\n",(0,l.jsxs)(n.h3,{id:"why-does-logoscore-call-return-an-error-after-load-module",children:["Why does ",(0,l.jsx)(n.code,{children:"logoscore call"})," return an error after ",(0,l.jsx)(n.code,{children:"load-module"}),"?"]}),"\n",(0,l.jsxs)(n.p,{children:["The daemon may not have finished starting. Wait a few seconds after ",(0,l.jsx)(n.code,{children:"logoscore -D"})," returns and retry. For Path A, confirm the container is running with ",(0,l.jsx)(n.code,{children:"docker ps"})," before calling ",(0,l.jsx)(n.code,{children:"docker exec logos-node logoscore \u2026"}),"."]})]})}function h(e={}){let{wrapper:n}={...(0,d.R)(),...e.components};return n?(0,l.jsx)(n,{...e,children:(0,l.jsx)(a,{...e})}):a(e)}},8453(e,n,o){o.d(n,{R:()=>r,x:()=>t});var s=o(6540);let l={},d=s.createContext(l);function r(e){let n=s.useContext(d);return s.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function t(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(l):e.components||l:r(e.components),s.createElement(d.Provider,{value:n},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/d83083f1.bec85c22.js b/assets/js/d83083f1.bec85c22.js new file mode 100644 index 00000000..ed0f8c38 --- /dev/null +++ b/assets/js/d83083f1.bec85c22.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunklogos_docs=self.webpackChunklogos_docs||[]).push([["4719"],{105(e,s,n){n.r(s),n.d(s,{metadata:()=>o,default:()=>h,frontMatter:()=>i,contentTitle:()=>r,toc:()=>a,assets:()=>t});var o=JSON.parse('{"id":"messaging/delivery/use-logos-delivery-module-api-from-app","title":"Use the Logos Delivery module API from an app","description":"Get started integrating Logos messaging into a C++ module.","source":"@site/docs/messaging/delivery/use-logos-delivery-module-api-from-app.md","sourceDirName":"messaging/delivery","slug":"/messaging/delivery/use-logos-delivery-module-api-from-app","permalink":"/messaging/delivery/use-logos-delivery-module-api-from-app","draft":false,"unlisted":false,"editUrl":"https://github.com/logos-co/logos-docs/tree/main/docs/messaging/delivery/use-logos-delivery-module-api-from-app.md","tags":[],"version":"current","sidebarPosition":1,"frontMatter":{"title":"Use the Logos Delivery module API from an app","doc_type":"procedure","product":"messaging","topics":"delivery","steps_layout":"sectioned","authors":"igor-sirotin, kashepavadan","owner":"logos","doc_version":1,"slug":"use-logos-delivery-module-api-from-app","sidebar_position":1},"sidebar":"messagingSidebar","previous":{"title":"FAQ: Logos Messaging","permalink":"/messaging/get-started/faq"},"next":{"title":"Run a Logos delivery node","permalink":"/messaging/delivery/run-logos-delivery-node"}}'),l=n(4848),d=n(8453);let i={title:"Use the Logos Delivery module API from an app",doc_type:"procedure",product:"messaging",topics:"delivery",steps_layout:"sectioned",authors:"igor-sirotin, kashepavadan",owner:"logos",doc_version:1,slug:"use-logos-delivery-module-api-from-app",sidebar_position:1},r="Use the Logos Delivery module API from an app",t={},a=[{value:"Get started integrating Logos messaging into a C++ module.",id:"get-started-integrating-logos-messaging-into-a-c-module",level:4},{value:"What to expect",id:"what-to-expect",level:2},{value:"Step 1: Create a Logos module",id:"step-1-create-a-logos-module",level:2},{value:"Step 2: Declare <code>delivery_module</code> as a dependency",id:"step-2-declare-delivery_module-as-a-dependency",level:2},{value:"Step 3: Call the delivery module API",id:"step-3-call-the-delivery-module-api",level:2},{value:"Step 4: Build and run",id:"step-4-build-and-run",level:2},{value:"Troubleshooting the Logos Delivery module",id:"troubleshooting-the-logos-delivery-module",level:2},{value:"<code>createNode</code> returns an unsuccessful <code>LogosResult</code>?",id:"createnode-returns-an-unsuccessful-logosresult",level:3},{value:"<code>send()</code> returns an unsuccessful <code>LogosResult</code>?",id:"send-returns-an-unsuccessful-logosresult",level:3},{value:"<code>messageSent</code> never fires after a successful <code>send()</code>?",id:"messagesent-never-fires-after-a-successful-send",level:3},{value:"<code>messageReceived</code> never fires?",id:"messagereceived-never-fires",level:3},{value:"Two instances of the same app on one host fail to start?",id:"two-instances-of-the-same-app-on-one-host-fail-to-start",level:3},{value:"<code>createNode</code> succeeds but a second call without <code>stop()</code> causes undefined behaviour?",id:"createnode-succeeds-but-a-second-call-without-stop-causes-undefined-behaviour",level:3}];function c(e){let s={a:"a",admonition:"admonition",code:"code",h1:"h1",h2:"h2",h3:"h3",h4:"h4",header:"header",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,d.R)(),...e.components};return(0,l.jsxs)(l.Fragment,{children:[(0,l.jsx)(s.header,{children:(0,l.jsx)(s.h1,{id:"use-the-logos-delivery-module-api-from-an-app",children:"Use the Logos Delivery module API from an app"})}),"\n",(0,l.jsx)(s.h4,{id:"get-started-integrating-logos-messaging-into-a-c-module",children:"Get started integrating Logos messaging into a C++ module."}),"\n",(0,l.jsxs)(s.p,{children:["This procedure covers building a Logos ",(0,l.jsx)(s.a,{href:"https://docs.logos.co/get-started/glossary#module",children:"module"})," that calls the ",(0,l.jsx)(s.a,{href:"https://docs.logos.co/get-started/glossary#logos-delivery",children:"Logos Delivery"})," API to subscribe to content topics, send messages, and react to delivery events. It gives application developers a working pattern for integrating Logos messaging into their C++ modules. A complete, runnable reference implementation is available in ",(0,l.jsx)(s.a,{href:"https://github.com/logos-co/logos-delivery-demo/tree/v0.1.0",children:(0,l.jsx)(s.code,{children:"logos-delivery-demo"})})," (tag ",(0,l.jsx)(s.code,{children:"v0.1.0"}),")."]}),"\n",(0,l.jsxs)(s.p,{children:["The two repositories used in this tutorial are ",(0,l.jsx)(s.a,{href:"https://github.com/logos-co/logos-delivery-module",children:(0,l.jsx)(s.code,{children:"logos-delivery-module"})})," (pinned to ",(0,l.jsx)(s.a,{href:"https://github.com/logos-co/logos-delivery-module/tree/794c21cbe177bdea16d4907468eaf52d4282dda7",children:(0,l.jsx)(s.code,{children:"v0.1.3"})}),") and ",(0,l.jsx)(s.a,{href:"https://github.com/logos-messaging/logos-delivery",children:(0,l.jsx)(s.code,{children:"logos-delivery"})}),", which is a transitive dependency resolved and linked statically by Nix."]}),"\n",(0,l.jsxs)(s.p,{children:[(0,l.jsx)(s.strong,{children:"Before you start"}),", make sure you have the following:"]}),"\n",(0,l.jsxs)(s.ul,{children:["\n",(0,l.jsx)(s.li,{children:"macOS (aarch64 or x86_64) or Linux (aarch64 or x86_64)"}),"\n",(0,l.jsx)(s.li,{children:"~1 GB of RAM"}),"\n",(0,l.jsx)(s.li,{children:"Nix with flakes enabled"}),"\n"]}),"\n",(0,l.jsx)(s.h2,{id:"what-to-expect",children:"What to expect"}),"\n",(0,l.jsxs)(s.ul,{children:["\n",(0,l.jsxs)(s.li,{children:["You can subscribe to a ",(0,l.jsx)(s.a,{href:"https://docs.logos.co/get-started/glossary#content-topic",children:"content topic"})," and receive messages."]}),"\n",(0,l.jsxs)(s.li,{children:["You can send a message and confirm delivery by tracking ",(0,l.jsx)(s.code,{children:"messageSent"})," and ",(0,l.jsx)(s.code,{children:"messagePropagated"})," events."]}),"\n",(0,l.jsx)(s.li,{children:"You can integrate the full Logos Delivery lifecycle \u2014 create, start, subscribe, send, stop \u2014 into your C++ module."}),"\n"]}),"\n",(0,l.jsx)(s.h2,{id:"step-1-create-a-logos-module",children:"Step 1: Create a Logos module"}),"\n",(0,l.jsxs)(s.p,{children:["Scaffold a new module using ",(0,l.jsx)(s.a,{href:"https://github.com/logos-co/logos-module-builder",children:(0,l.jsx)(s.code,{children:"logos-module-builder"})}),". For a full walkthrough, see the ",(0,l.jsx)(s.a,{href:"/core/build-modules/build-a-logos-cpp-ui-module",children:"Build a Logos C++ UI module"})," tutorial."]}),"\n",(0,l.jsxs)(s.ol,{children:["\n",(0,l.jsxs)(s.li,{children:["\n",(0,l.jsx)(s.p,{children:"Create and enter the project directory:"}),"\n",(0,l.jsx)(s.pre,{children:(0,l.jsx)(s.code,{className:"language-bash",children:"mkdir your-module-name && cd your-module-name\n"})}),"\n"]}),"\n",(0,l.jsxs)(s.li,{children:["\n",(0,l.jsx)(s.p,{children:"Initialise from the template:"}),"\n",(0,l.jsx)(s.pre,{children:(0,l.jsx)(s.code,{className:"language-bash",children:"nix flake init -t github:logos-co/logos-module-builder/0.2.0#ui-qml-backend\n"})}),"\n"]}),"\n",(0,l.jsxs)(s.li,{children:["\n",(0,l.jsx)(s.p,{children:"Initialise a Git repository and stage all generated files:"}),"\n",(0,l.jsx)(s.pre,{children:(0,l.jsx)(s.code,{className:"language-bash",children:"git init && git add -A\n"})}),"\n"]}),"\n",(0,l.jsxs)(s.li,{children:["\n",(0,l.jsxs)(s.p,{children:["Remove the template's example sources. The scaffolded template includes ",(0,l.jsx)(s.code,{children:"ui_example"})," files with mismatched class names and IIDs; leaving them causes build errors or plugin-load failures at runtime:"]}),"\n",(0,l.jsx)(s.pre,{children:(0,l.jsx)(s.code,{className:"language-bash",children:"rm -f src/ui_example.rep src/ui_example_interface.h src/ui_example_plugin.h src/ui_example_plugin.cpp\n"})}),"\n"]}),"\n"]}),"\n",(0,l.jsxs)(s.h2,{id:"step-2-declare-delivery_module-as-a-dependency",children:["Step 2: Declare ",(0,l.jsx)(s.code,{children:"delivery_module"})," as a dependency"]}),"\n",(0,l.jsxs)(s.p,{children:["Add ",(0,l.jsx)(s.code,{children:"delivery_module"})," to both ",(0,l.jsx)(s.code,{children:"metadata.json"})," and ",(0,l.jsx)(s.code,{children:"flake.nix"}),", pinning to the released tag so your app stays stable as the module API evolves."]}),"\n",(0,l.jsx)(s.admonition,{type:"info",children:(0,l.jsxs)(s.p,{children:["The flake input name (",(0,l.jsx)(s.code,{children:"delivery_module"}),") must exactly match the dependency name in ",(0,l.jsx)(s.code,{children:"metadata.json"}),". ",(0,l.jsx)(s.code,{children:"logos-module-builder"})," uses this name to generate the typed wrapper at build time."]})}),"\n",(0,l.jsxs)(s.ol,{children:["\n",(0,l.jsxs)(s.li,{children:["\n",(0,l.jsxs)(s.p,{children:["In ",(0,l.jsx)(s.code,{children:"metadata.json"}),", add ",(0,l.jsx)(s.code,{children:"delivery_module"})," to the dependencies array:"]}),"\n",(0,l.jsx)(s.pre,{children:(0,l.jsx)(s.code,{className:"language-json",children:'{\n "name": "my_app",\n "dependencies": ["delivery_module"],\n ...\n}\n'})}),"\n"]}),"\n",(0,l.jsxs)(s.li,{children:["\n",(0,l.jsxs)(s.p,{children:["In ",(0,l.jsx)(s.code,{children:"flake.nix"}),", add a matching pinned input:"]}),"\n",(0,l.jsx)(s.pre,{children:(0,l.jsx)(s.code,{className:"language-nix",children:'inputs = {\n logos-module-builder.url = "github:logos-co/logos-module-builder";\n delivery_module.url = "github:logos-co/logos-delivery-module/v0.1.3";\n};\n'})}),"\n"]}),"\n"]}),"\n",(0,l.jsx)(s.h2,{id:"step-3-call-the-delivery-module-api",children:"Step 3: Call the delivery module API"}),"\n",(0,l.jsxs)(s.p,{children:["In your module's ",(0,l.jsx)(s.code,{children:"initLogos()"})," function, construct ",(0,l.jsx)(s.code,{children:"LogosModules"})," with the provided ",(0,l.jsx)(s.code,{children:"LogosAPI*"}),". The ",(0,l.jsx)(s.code,{children:"initLogos()"})," function can be found in the ",(0,l.jsx)(s.code,{children:".cpp"})," file generated by the scaffolding \u2014 in the demo, this is ",(0,l.jsx)(s.code,{children:"src/delivery_module_plugin.cpp"}),". ",(0,l.jsx)(s.code,{children:"LogosModules"})," is generated at build time by ",(0,l.jsx)(s.code,{children:"logos-module-builder"}),"; include it via the umbrella header and keep it as a member on the plugin. The full API reference is in ",(0,l.jsx)(s.a,{href:"https://github.com/logos-co/logos-delivery-module/blob/794c21cbe177bdea16d4907468eaf52d4282dda7/src/delivery_module_plugin.h",children:(0,l.jsx)(s.code,{children:"src/delivery_module_plugin.h"})})," and the module ",(0,l.jsx)(s.a,{href:"https://github.com/logos-co/logos-delivery-module/blob/794c21cbe177bdea16d4907468eaf52d4282dda7/README.md#module-interface",children:"README"}),"."]}),"\n",(0,l.jsx)(s.admonition,{type:"info",children:(0,l.jsxs)(s.p,{children:["Register event handlers before calling ",(0,l.jsx)(s.code,{children:"start()"})," so you don't miss the first ",(0,l.jsx)(s.code,{children:"connectionStateChanged"})," event."]})}),"\n",(0,l.jsxs)(s.ol,{children:["\n",(0,l.jsxs)(s.li,{children:["\n",(0,l.jsxs)(s.p,{children:["Initialise ",(0,l.jsx)(s.code,{children:"LogosModules"})," in ",(0,l.jsx)(s.code,{children:"initLogos()"}),":"]}),"\n",(0,l.jsx)(s.pre,{children:(0,l.jsx)(s.code,{className:"language-cpp",children:'#include "logos_sdk.h" // generated umbrella \u2014 exposes LogosModules\n\n// In your plugin class:\n// LogosModules* m_logos = nullptr;\n\nvoid MyPlugin::initLogos(LogosAPI* api) {\n m_logos = new LogosModules(api);\n // m_logos->delivery_module is now the typed wrapper for the Logos Delivery module.\n}\n'})}),"\n"]}),"\n",(0,l.jsxs)(s.li,{children:["\n",(0,l.jsx)(s.p,{children:"Register event handlers. All lifecycle calls are synchronous; events arrive off-thread via the Qt event loop:"}),"\n",(0,l.jsx)(s.pre,{children:(0,l.jsx)(s.code,{className:"language-cpp",children:'m_logos->delivery_module.on("connectionStateChanged", [](const QVariantList& data) {\n // data[0]: QString \u2014 connection status\n // data[1]: QString \u2014 timestamp (ns since epoch)\n});\n\nm_logos->delivery_module.on("messageReceived", [](const QVariantList& data) {\n // data[0]: QString \u2014 messageHash\n // data[1]: QString \u2014 contentTopic\n // data[2]: QByteArray \u2014 payload (raw bytes)\n // data[3]: QString \u2014 timestamp (ns since epoch)\n});\n\nm_logos->delivery_module.on("messageSent", [](const QVariantList& data) { /* requestId, hash, ts */ });\nm_logos->delivery_module.on("messagePropagated", [](const QVariantList& data) { /* requestId, hash, ts */ });\nm_logos->delivery_module.on("messageError", [](const QVariantList& data) { /* requestId, hash, error, ts */ });\n'})}),"\n"]}),"\n",(0,l.jsxs)(s.li,{children:["\n",(0,l.jsxs)(s.p,{children:["Initialise the node with ",(0,l.jsx)(s.code,{children:"createNode"}),", which returns a ",(0,l.jsx)(s.code,{children:"LogosResult"}),". Always check ",(0,l.jsx)(s.code,{children:"success"})," before continuing and surface ",(0,l.jsx)(s.code,{children:"getError()"})," on failure. For a complete list of node configuration keys, see the ",(0,l.jsx)(s.a,{href:"https://github.com/logos-co/logos-delivery-module/blob/794c21cbe177bdea16d4907468eaf52d4282dda7/README.md#module-interface",children:"Module Interface"})," section of the README."]}),"\n",(0,l.jsx)(s.pre,{children:(0,l.jsx)(s.code,{className:"language-cpp",children:'const QString cfg = R"({"logLevel":"INFO","mode":"Core","preset":"logos.test"})";\n\nLogosResult r = m_logos->delivery_module.createNode(cfg);\nif (!r.success) {\n qWarning() << "createNode failed:" << r.getError();\n return;\n}\n'})}),"\n"]}),"\n",(0,l.jsxs)(s.li,{children:["\n",(0,l.jsxs)(s.p,{children:["Connect to the network with ",(0,l.jsx)(s.code,{children:"start()"}),". The ",(0,l.jsx)(s.code,{children:"connectionStateChanged"})," event fires on the Qt event loop once the node connects to peers:"]}),"\n",(0,l.jsx)(s.pre,{children:(0,l.jsx)(s.code,{className:"language-cpp",children:'LogosResult r = m_logos->delivery_module.start();\nif (!r.success) {\n qWarning() << "start failed:" << r.getError();\n return;\n}\n'})}),"\n"]}),"\n",(0,l.jsxs)(s.li,{children:["\n",(0,l.jsxs)(s.p,{children:["Subscribe to a content topic using a ",(0,l.jsx)(s.a,{href:"https://lip.logos.co/messaging/informational/draft/23/topics.html",children:"LIP-23"})," content-topic string. Call ",(0,l.jsx)(s.code,{children:"subscribe()"})," before any messages are sent on that topic:"]}),"\n",(0,l.jsx)(s.pre,{children:(0,l.jsx)(s.code,{className:"language-cpp",children:'LogosResult r = m_logos->delivery_module.subscribe(contentTopic);\nif (!r.success) {\n qWarning() << "subscribe failed:" << r.getError();\n}\n'})}),"\n"]}),"\n",(0,l.jsxs)(s.li,{children:["\n",(0,l.jsxs)(s.p,{children:["Send a message. On success, ",(0,l.jsx)(s.code,{children:"getString()"})," returns the request ID; track it through ",(0,l.jsx)(s.code,{children:"messageSent"})," \u2192 ",(0,l.jsx)(s.code,{children:"messagePropagated"})," events (or ",(0,l.jsx)(s.code,{children:"messageError"}),"):"]}),"\n",(0,l.jsx)(s.pre,{children:(0,l.jsx)(s.code,{className:"language-cpp",children:'// payload is a QByteArray of raw bytes (same type messageReceived delivers in data[2]).\nLogosResult r = m_logos->delivery_module.send(contentTopic, payload);\nif (!r.success) {\n qWarning() << "send failed:" << r.getError();\n return;\n}\nconst QString requestId = r.getString();\n'})}),"\n"]}),"\n",(0,l.jsxs)(s.li,{children:["\n",(0,l.jsxs)(s.p,{children:["Shut down cleanly with ",(0,l.jsx)(s.code,{children:"stop()"}),". This tears down the underlying node and drops every active subscription and event listener:"]}),"\n",(0,l.jsx)(s.pre,{children:(0,l.jsx)(s.code,{className:"language-cpp",children:'LogosResult s = m_logos->delivery_module.stop();\nif (!s.success) qWarning() << "stop failed:" << s.getError();\n'})}),"\n"]}),"\n"]}),"\n",(0,l.jsx)(s.h2,{id:"step-4-build-and-run",children:"Step 4: Build and run"}),"\n",(0,l.jsxs)(s.ol,{children:["\n",(0,l.jsxs)(s.li,{children:["\n",(0,l.jsx)(s.p,{children:"Build the module:"}),"\n",(0,l.jsx)(s.pre,{children:(0,l.jsx)(s.code,{className:"language-sh",children:"nix build\n"})}),"\n"]}),"\n",(0,l.jsxs)(s.li,{children:["\n",(0,l.jsxs)(s.p,{children:["Preview the module using ",(0,l.jsx)(s.code,{children:"logos-standalone-app"})," (for ",(0,l.jsx)(s.code,{children:"ui_qml"})," modules):"]}),"\n",(0,l.jsx)(s.pre,{children:(0,l.jsx)(s.code,{className:"language-sh",children:"nix run\n"})}),"\n"]}),"\n",(0,l.jsxs)(s.li,{children:["\n",(0,l.jsxs)(s.p,{children:["Package as ",(0,l.jsx)(s.code,{children:".lgx"})," for installation into ",(0,l.jsx)(s.code,{children:"logos-basecamp"}),":"]}),"\n",(0,l.jsx)(s.pre,{children:(0,l.jsx)(s.code,{className:"language-sh",children:"nix build .#lgx\n"})}),"\n"]}),"\n"]}),"\n",(0,l.jsx)(s.h2,{id:"troubleshooting-the-logos-delivery-module",children:"Troubleshooting the Logos Delivery module"}),"\n",(0,l.jsxs)(s.h3,{id:"createnode-returns-an-unsuccessful-logosresult",children:[(0,l.jsx)(s.code,{children:"createNode"})," returns an unsuccessful ",(0,l.jsx)(s.code,{children:"LogosResult"}),"?"]}),"\n",(0,l.jsxs)(s.p,{children:["The JSON may be malformed, or an internal initialisation error occurred. Validate that the JSON is well-formed and that key names are camelCase matching ",(0,l.jsx)(s.code,{children:"WakuNodeConf"})," fields. Set ",(0,l.jsx)(s.code,{children:'"logLevel": "DEBUG"'})," for verbose output and inspect ",(0,l.jsx)(s.code,{children:"result.getError()"})," for details."]}),"\n",(0,l.jsxs)(s.h3,{id:"send-returns-an-unsuccessful-logosresult",children:[(0,l.jsx)(s.code,{children:"send()"})," returns an unsuccessful ",(0,l.jsx)(s.code,{children:"LogosResult"}),"?"]}),"\n",(0,l.jsxs)(s.p,{children:["The node was not started, or ",(0,l.jsx)(s.code,{children:"contentTopic"})," is empty or invalid. Call ",(0,l.jsx)(s.code,{children:"start()"})," first and verify it returned success. Confirm the content topic follows the ",(0,l.jsx)(s.a,{href:"https://docs.logos.co/get-started/glossary#lip",children:"LIP"}),"-23 format."]}),"\n",(0,l.jsxs)(s.h3,{id:"messagesent-never-fires-after-a-successful-send",children:[(0,l.jsx)(s.code,{children:"messageSent"})," never fires after a successful ",(0,l.jsx)(s.code,{children:"send()"}),"?"]}),"\n",(0,l.jsxs)(s.p,{children:["The node may not be connected to peers yet, or the network layer rejected the message (for example, an RLN proof failure). Wait for ",(0,l.jsx)(s.code,{children:"connectionStateChanged"})," before sending. If ",(0,l.jsx)(s.code,{children:"messageError"})," fires, inspect ",(0,l.jsx)(s.code,{children:"data[2]"})," for the error message."]}),"\n",(0,l.jsxs)(s.h3,{id:"messagereceived-never-fires",children:[(0,l.jsx)(s.code,{children:"messageReceived"})," never fires?"]}),"\n",(0,l.jsxs)(s.p,{children:[(0,l.jsx)(s.code,{children:"subscribe()"})," was not called before messages were sent, or the payload was sent on a different content topic. Call ",(0,l.jsx)(s.code,{children:"subscribe(topic)"})," before any messages are sent on that topic."]}),"\n",(0,l.jsx)(s.h3,{id:"two-instances-of-the-same-app-on-one-host-fail-to-start",children:"Two instances of the same app on one host fail to start?"}),"\n",(0,l.jsxs)(s.p,{children:["You pinned the same fixed port (for example, ",(0,l.jsx)(s.code,{children:"tcpPort"}),") for both instances. Omit the port keys from the ",(0,l.jsx)(s.code,{children:"createNode"})," config so each instance binds an OS-assigned ephemeral port \u2014 this is the default behaviour in ",(0,l.jsx)(s.code,{children:"v0.1.3"}),". Alternatively, assign distinct explicit port values per instance."]}),"\n",(0,l.jsxs)(s.h3,{id:"createnode-succeeds-but-a-second-call-without-stop-causes-undefined-behaviour",children:[(0,l.jsx)(s.code,{children:"createNode"})," succeeds but a second call without ",(0,l.jsx)(s.code,{children:"stop()"})," causes undefined behaviour?"]}),"\n",(0,l.jsxs)(s.p,{children:[(0,l.jsx)(s.code,{children:"createNode"})," must be called exactly once per context. Call ",(0,l.jsx)(s.code,{children:"stop()"})," and destroy the context before calling ",(0,l.jsx)(s.code,{children:"createNode"})," again."]})]})}function h(e={}){let{wrapper:s}={...(0,d.R)(),...e.components};return s?(0,l.jsx)(s,{...e,children:(0,l.jsx)(c,{...e})}):c(e)}},8453(e,s,n){n.d(s,{R:()=>i,x:()=>r});var o=n(6540);let l={},d=o.createContext(l);function i(e){let s=o.useContext(d);return o.useMemo(function(){return"function"==typeof e?e(s):{...s,...e}},[s,e])}function r(e){let s;return s=e.disableParentContext?"function"==typeof e.components?e.components(l):e.components||l:i(e.components),o.createElement(d.Provider,{value:s},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/dbf4bdd3.1fbc45a6.js b/assets/js/dbf4bdd3.1fbc45a6.js new file mode 100644 index 00000000..52d7e7db --- /dev/null +++ b/assets/js/dbf4bdd3.1fbc45a6.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunklogos_docs=self.webpackChunklogos_docs||[]).push([["4903"],{112(e,n,i){i.r(n),i.d(n,{metadata:()=>t,default:()=>u,frontMatter:()=>s,contentTitle:()=>r,toc:()=>d,assets:()=>l});var t=JSON.parse('{"id":"build-an-app/tutorials/create-and-use-an-amm-liquidity-pool-on-the-logos-execution-zone","title":"Create and use an AMM liquidity pool on the Logos Execution Zone","description":"This page is an early draft and may be incomplete or incorrect. Expect changes, missing prerequisites, and commands that might not work in your setup. We are actively working to complete and verify this content.","source":"@site/docs/build-an-app/tutorials/create-and-use-an-amm-liquidity-pool-on-the-logos-execution-zone.md","sourceDirName":"build-an-app/tutorials","slug":"/build-an-app/tutorials/create-and-use-an-amm-liquidity-pool-on-the-logos-execution-zone","permalink":"/build-an-app/tutorials/create-and-use-an-amm-liquidity-pool-on-the-logos-execution-zone","draft":false,"unlisted":false,"editUrl":"https://github.com/logos-co/logos-docs/tree/main/docs/build-an-app/tutorials/create-and-use-an-amm-liquidity-pool-on-the-logos-execution-zone.md","tags":[],"version":"current","frontMatter":{},"sidebar":"buildAnAppSidebar","previous":{"title":"Build an app","permalink":"/build-an-app"},"next":{"title":"Build a dApp with the Logos Zone SDK","permalink":"/build-an-app/tutorials/build-dapp-with-logos-zone-sdk"}}'),o=i(4848),a=i(8453);let s={},r="Create and use an AMM liquidity pool on the Logos Execution Zone",l={},d=[{value:"1. Creating a liquidity pool for a token pair",id:"1-creating-a-liquidity-pool-for-a-token-pair",level:2},{value:"a. Create an LP holding account",id:"a-create-an-lp-holding-account",level:3},{value:"b. Initialise the pool",id:"b-initialise-the-pool",level:3},{value:"2. Swapping",id:"2-swapping",level:2},{value:"3. Withdrawing liquidity from the pool",id:"3-withdrawing-liquidity-from-the-pool",level:2},{value:"4. Adding liquidity to the pool",id:"4-adding-liquidity-to-the-pool",level:2}];function c(e){let n={a:"a",admonition:"admonition",code:"code",h1:"h1",h2:"h2",h3:"h3",header:"header",li:"li",ol:"ol",p:"p",pre:"pre",...(0,a.R)(),...e.components};return(0,o.jsxs)(o.Fragment,{children:[(0,o.jsx)(n.header,{children:(0,o.jsx)(n.h1,{id:"create-and-use-an-amm-liquidity-pool-on-the-logos-execution-zone",children:"Create and use an AMM liquidity pool on the Logos Execution Zone"})}),"\n",(0,o.jsx)(n.admonition,{type:"warning",children:(0,o.jsx)(n.p,{children:"This page is an early draft and may be incomplete or incorrect. Expect changes, missing prerequisites, and commands that might not work in your setup. We are actively working to complete and verify this content."})}),"\n",(0,o.jsxs)(n.p,{children:["This tutorial covers the AMM ",(0,o.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#program",children:"program"})," in ",(0,o.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#lez",children:"LEZ"}),". The AMM manages liquidity pools and enables swaps between custom tokens. By the end, you will have practiced:"]}),"\n",(0,o.jsxs)(n.ol,{children:["\n",(0,o.jsx)(n.li,{children:"Creating a liquidity pool for a token pair."}),"\n",(0,o.jsx)(n.li,{children:"Swapping tokens."}),"\n",(0,o.jsx)(n.li,{children:"Withdrawing liquidity from the pool."}),"\n",(0,o.jsx)(n.li,{children:"Adding liquidity to the pool."}),"\n"]}),"\n",(0,o.jsx)(n.h2,{id:"1-creating-a-liquidity-pool-for-a-token-pair",children:"1. Creating a liquidity pool for a token pair"}),"\n",(0,o.jsx)(n.p,{children:"We start by creating a pool for the tokens created earlier. In return for providing liquidity, you receive liquidity provider (LP) tokens. LP tokens represent your share of the pool and are required to withdraw liquidity later."}),"\n",(0,o.jsx)(n.admonition,{type:"info",children:(0,o.jsx)(n.p,{children:"The AMM does not currently charge swap fees or distribute rewards to liquidity providers. LP tokens therefore represent only a proportional share of the pool reserves. Fee support will be added in future versions."})}),"\n",(0,o.jsx)(n.h3,{id:"a-create-an-lp-holding-account",children:"a. Create an LP holding account"}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{className:"language-bash",children:"wallet account new public\n\n# Output:\nGenerated new account with account_id Public/FHgLW9jW4HXMV6egLWbwpTqVAGiCHw2vkg71KYSuimVf\n"})}),"\n",(0,o.jsx)(n.h3,{id:"b-initialise-the-pool",children:"b. Initialise the pool"}),"\n",(0,o.jsxs)(n.p,{children:["Deposit tokens A and B and specify the ",(0,o.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#account",children:"account"})," that will receive LP tokens:"]}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{className:"language-bash",children:"wallet amm new \\\n --user-holding-a Public/9RRSMm3w99uCD2Jp2Mqqf6dfc8me2tkFRE9HeU2DFftw \\\n --user-holding-b Public/88f2zeTgiv9LUthQwPJbrmufb9SiDfmpCs47B7vw6Gd6 \\\n --user-holding-lp Public/FHgLW9jW4HXMV6egLWbwpTqVAGiCHw2vkg71KYSuimVf \\\n --balance-a 100 \\\n --balance-b 200\n"})}),"\n",(0,o.jsx)(n.admonition,{type:"warning",children:(0,o.jsxs)(n.p,{children:["The LP holding account is owned by the ",(0,o.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#token-program",children:"token program"}),", so LP tokens are managed using the same token infrastructure as regular tokens."]})}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{className:"language-bash",children:'wallet account get --account-id Public/FHgLW9jW4HXMV6egLWbwpTqVAGiCHw2vkg71KYSuimVf\n\n# Output:\nHolding account owned by token program\n{"account_type":"Token holding","definition_id":"7BeDS3e28MA5Err7gBswmR1fUKdHXqmUpTefNPu3pJ9i","balance":100}\n'})}),"\n",(0,o.jsx)(n.admonition,{type:"tip",children:(0,o.jsxs)(n.p,{children:["If you inspect the ",(0,o.jsx)(n.code,{children:"user-holding-a"})," and ",(0,o.jsx)(n.code,{children:"user-holding-b"})," accounts, you will see that 100 and 200 tokens were deducted. Those tokens now reside in the pool and are available for swaps by any user."]})}),"\n",(0,o.jsx)(n.h2,{id:"2-swapping",children:"2. Swapping"}),"\n",(0,o.jsxs)(n.p,{children:["Use ",(0,o.jsx)(n.code,{children:"wallet amm swap-exact-input"})," to perform a token swap:"]}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{className:"language-bash",children:"wallet amm swap-exact-input \\\n --user-holding-a Public/9RRSMm3w99uCD2Jp2Mqqf6dfc8me2tkFRE9HeU2DFftw \\\n --user-holding-b Public/88f2zeTgiv9LUthQwPJbrmufb9SiDfmpCs47B7vw6Gd6 \\\n # The amount of tokens to swap\n --amount-in 5 \\\n # The minimum number of tokens expected in return\n --min-amount-out 8 \\\n # The definition ID of the token being provided to the swap\n # In this case, we are swapping from TOKENA to TOKENB, and so this is the definition ID of TOKENA\n --token-definition 4X9kAcnCZ1Ukkbm3nywW9xfCNPK8XaMWCk3zfs1sP4J7\n"})}),"\n",(0,o.jsx)(n.p,{children:"Once executed, 5 tokens are deducted from the Token A holding account and the corresponding amount (computed by the pool\u2019s pricing function) is credited to the Token B holding account."}),"\n",(0,o.jsx)(n.h2,{id:"3-withdrawing-liquidity-from-the-pool",children:"3. Withdrawing liquidity from the pool"}),"\n",(0,o.jsx)(n.p,{children:"Liquidity providers can withdraw assets by redeeming (burning) LP tokens. The amount received is proportional to the share of LP tokens redeemed relative to the total LP supply."}),"\n",(0,o.jsxs)(n.p,{children:["Use ",(0,o.jsx)(n.code,{children:"wallet amm remove-liquidity"}),":"]}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{className:"language-bash",children:"wallet amm remove-liquidity \\\n --user-holding-a Public/9RRSMm3w99uCD2Jp2Mqqf6dfc8me2tkFRE9HeU2DFftw \\\n --user-holding-b Public/88f2zeTgiv9LUthQwPJbrmufb9SiDfmpCs47B7vw6Gd6 \\\n --user-holding-lp Public/FHgLW9jW4HXMV6egLWbwpTqVAGiCHw2vkg71KYSuimVf \\\n --balance-lp 20 \\\n --min-amount-a 1 \\\n --min-amount-b 1\n"})}),"\n",(0,o.jsx)(n.admonition,{type:"warning",children:(0,o.jsxs)(n.p,{children:["This burns ",(0,o.jsx)(n.code,{children:"balance-lp"})," LP tokens from the user\u2019s LP holding account. In return, the AMM transfers tokens A and B from the pool vaults to the user\u2019s holding accounts, based on current reserves.\nThe ",(0,o.jsx)(n.code,{children:"min-amount-a"})," and ",(0,o.jsx)(n.code,{children:"min-amount-b"})," parameters set the minimum acceptable outputs. If the computed amounts fall below either threshold, the instruction fails to protect against unfavourable pool changes."]})}),"\n",(0,o.jsx)(n.h2,{id:"4-adding-liquidity-to-the-pool",children:"4. Adding liquidity to the pool"}),"\n",(0,o.jsx)(n.p,{children:"To add liquidity, deposit tokens A and B in the ratio implied by current pool reserves. In return, the AMM mints new LP tokens that represent your proportional share."}),"\n",(0,o.jsxs)(n.p,{children:["Use ",(0,o.jsx)(n.code,{children:"wallet amm add-liquidity"}),":"]}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{className:"language-bash",children:"wallet amm add-liquidity \\\n --user-holding-a Public/9RRSMm3w99uCD2Jp2Mqqf6dfc8me2tkFRE9HeU2DFftw \\\n --user-holding-b Public/88f2zeTgiv9LUthQwPJbrmufb9SiDfmpCs47B7vw6Gd6 \\\n --user-holding-lp Public/FHgLW9jW4HXMV6egLWbwpTqVAGiCHw2vkg71KYSuimVf \\\n --min-amount-lp 1 \\\n --max-amount-a 10 \\\n --max-amount-b 10\n"})}),"\n",(0,o.jsx)(n.admonition,{type:"warning",children:(0,o.jsxs)(n.p,{children:[(0,o.jsx)(n.code,{children:"max-amount-a"})," and ",(0,o.jsx)(n.code,{children:"max-amount-b"})," cap how many tokens A and B can be taken from the user\u2019s accounts. The AMM computes the required amounts based on the pool\u2019s reserve ratio.\n",(0,o.jsx)(n.code,{children:"min-amount-lp"})," sets the minimum LP tokens to mint. If the computed LP amount falls below this threshold, the instruction fails."]})})]})}function u(e={}){let{wrapper:n}={...(0,a.R)(),...e.components};return n?(0,o.jsx)(n,{...e,children:(0,o.jsx)(c,{...e})}):c(e)}},8453(e,n,i){i.d(n,{R:()=>s,x:()=>r});var t=i(6540);let o={},a=t.createContext(o);function s(e){let n=t.useContext(a);return t.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function r(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(o):e.components||o:s(e.components),t.createElement(a.Provider,{value:n},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/e98c3ec0.e0da381f.js b/assets/js/e98c3ec0.e0da381f.js new file mode 100644 index 00000000..8b3a622b --- /dev/null +++ b/assets/js/e98c3ec0.e0da381f.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunklogos_docs=self.webpackChunklogos_docs||[]).push([["1836"],{5985(e,n,s){s.r(n),s.d(n,{metadata:()=>o,default:()=>h,frontMatter:()=>r,contentTitle:()=>t,toc:()=>c,assets:()=>i});var o=JSON.parse('{"id":"run-a-node/get-started/run-logos-node-blockchain-storage-delivery","title":"Run a Logos node with blockchain, storage, and delivery","description":"Get started running a full Logos node with all three core modules on testnet v0.2.","source":"@site/docs/run-a-node/get-started/run-logos-node-blockchain-storage-delivery.md","sourceDirName":"run-a-node/get-started","slug":"/run-a-node/get-started/run-logos-node-blockchain-storage-delivery","permalink":"/run-a-node/get-started/run-logos-node-blockchain-storage-delivery","draft":false,"unlisted":false,"editUrl":"https://github.com/logos-co/logos-docs/tree/main/docs/run-a-node/get-started/run-logos-node-blockchain-storage-delivery.md","tags":[],"version":"current","sidebarPosition":1,"frontMatter":{"title":"Run a Logos node with blockchain, storage, and delivery","doc_type":"procedure","product":"core","topics":["node","core"],"steps_layout":"sectioned","authors":null,"owner":"logos","doc_version":1,"slug":"run-logos-node-blockchain-storage-delivery","sidebar_position":1},"sidebar":"runANodeSidebar"}'),l=s(4848),d=s(8453);let r={title:"Run a Logos node with blockchain, storage, and delivery",doc_type:"procedure",product:"core",topics:["node","core"],steps_layout:"sectioned",authors:null,owner:"logos",doc_version:1,slug:"run-logos-node-blockchain-storage-delivery",sidebar_position:1},t="Run a Logos node with blockchain, storage, and delivery",i={},c=[{value:"Get started running a full Logos node with all three core modules on testnet v0.2.",id:"get-started-running-a-full-logos-node-with-all-three-core-modules-on-testnet-v02",level:4},{value:"What to expect",id:"what-to-expect",level:2},{value:"Step 1: Install runtime tools",id:"step-1-install-runtime-tools",level:2},{value:"Step 2: Prepare the host",id:"step-2-prepare-the-host",level:2},{value:"Step 3: Install modules",id:"step-3-install-modules",level:2},{value:"Step 4: Start Logos Core",id:"step-4-start-logos-core",level:2},{value:"Step 5: Configure and start the blockchain module",id:"step-5-configure-and-start-the-blockchain-module",level:2},{value:"Step 6: Configure and start the storage module",id:"step-6-configure-and-start-the-storage-module",level:2},{value:"Step 7: Configure and start the delivery module",id:"step-7-configure-and-start-the-delivery-module",level:2},{value:"Step 8: Verify the full node is healthy",id:"step-8-verify-the-full-node-is-healthy",level:2},{value:"Optional: Run the node unattended with systemd",id:"optional-run-the-node-unattended-with-systemd",level:3}];function a(e){let n={a:"a",admonition:"admonition",code:"code",h1:"h1",h2:"h2",h3:"h3",h4:"h4",header:"header",li:"li",ol:"ol",p:"p",pre:"pre",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,d.R)(),...e.components};return(0,l.jsxs)(l.Fragment,{children:[(0,l.jsx)(n.header,{children:(0,l.jsx)(n.h1,{id:"run-a-logos-node-with-blockchain-storage-and-delivery",children:"Run a Logos node with blockchain, storage, and delivery"})}),"\n",(0,l.jsx)(n.h4,{id:"get-started-running-a-full-logos-node-with-all-three-core-modules-on-testnet-v02",children:"Get started running a full Logos node with all three core modules on testnet v0.2."}),"\n",(0,l.jsxs)(n.p,{children:["This procedure covers installing and running a single ",(0,l.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#logos-node",children:"Logos node"})," with ",(0,l.jsx)(n.code,{children:"logoscore"})," hosting the ",(0,l.jsx)(n.code,{children:"blockchain_module"}),", ",(0,l.jsx)(n.code,{children:"storage_module"}),", and ",(0,l.jsx)(n.code,{children:"delivery_module"})," from one shared modules directory. It is intended for node operators who want to join the testnet and contribute to the Logos network. The steps assume a Linux host."]}),"\n",(0,l.jsx)(n.p,{children:"The default paths used throughout this procedure are:"}),"\n",(0,l.jsx)(n.pre,{children:(0,l.jsx)(n.code,{className:"language-text",children:"/usr/local/bin/logoscore\n/usr/local/bin/lgpd\n/usr/local/bin/lgpm\n/opt/logos-node/modules\n/opt/logos-node/packages\n/var/lib/logos-node\n"})}),"\n",(0,l.jsx)(n.p,{children:"Before you start, make sure you have the following:"}),"\n",(0,l.jsxs)(n.ul,{children:["\n",(0,l.jsx)(n.li,{children:"Linux host with a public IPv4 address"}),"\n",(0,l.jsxs)(n.li,{children:["Ports ",(0,l.jsx)(n.code,{children:"3000/udp"}),", ",(0,l.jsx)(n.code,{children:"8090/udp"}),", ",(0,l.jsx)(n.code,{children:"8091/tcp"}),", ",(0,l.jsx)(n.code,{children:"9000/udp"}),", and ",(0,l.jsx)(n.code,{children:"30303/tcp"})," open on the host firewall"]}),"\n",(0,l.jsxs)(n.li,{children:["Root or ",(0,l.jsx)(n.code,{children:"sudo"})," access to install tools and create system users"]}),"\n"]}),"\n",(0,l.jsx)(n.h2,{id:"what-to-expect",children:"What to expect"}),"\n",(0,l.jsxs)(n.ul,{children:["\n",(0,l.jsx)(n.li,{children:"You can run a full Logos node with all three modules active and publicly reachable on the testnet."}),"\n",(0,l.jsxs)(n.li,{children:["You can verify each ",(0,l.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#module",children:"module"})," is healthy by querying the daemon and checking live port bindings."]}),"\n",(0,l.jsxs)(n.li,{children:["You can configure the node for unattended operation using the systemd service pattern described in ",(0,l.jsx)(n.a,{href:"#optional-run-the-node-unattended-with-systemd",children:"here"}),"."]}),"\n"]}),"\n",(0,l.jsx)(n.h2,{id:"step-1-install-runtime-tools",children:"Step 1: Install runtime tools"}),"\n",(0,l.jsx)(n.p,{children:"Install the system dependencies and download the three Logos CLI tools."}),"\n",(0,l.jsxs)(n.admonition,{type:"info",children:[(0,l.jsx)(n.p,{children:"You can also install these tools by running:"}),(0,l.jsx)(n.pre,{children:(0,l.jsx)(n.code,{className:"language-bash",children:'curl -fsSL https://raw.githubusercontent.com/logos-co/logos-docs/main/resources/scripts/install-node-tools.sh | sh\nexport PATH="$PWD/bin:$PATH"\n'})})]}),"\n",(0,l.jsxs)(n.ol,{children:["\n",(0,l.jsxs)(n.li,{children:["\n",(0,l.jsxs)(n.p,{children:["Install ",(0,l.jsx)(n.code,{children:"curl"}),", ",(0,l.jsx)(n.code,{children:"jq"}),", ",(0,l.jsx)(n.code,{children:"wget"}),", and FUSE support for AppImage binaries:"]}),"\n",(0,l.jsx)(n.pre,{children:(0,l.jsx)(n.code,{className:"language-sh",children:"apt-get update\napt-get install -y curl jq wget fuse3\n"})}),"\n"]}),"\n",(0,l.jsxs)(n.li,{children:["\n",(0,l.jsxs)(n.p,{children:["Download the Linux release assets for ",(0,l.jsx)(n.code,{children:"logoscore"}),", ",(0,l.jsx)(n.code,{children:"lgpd"}),", and ",(0,l.jsx)(n.code,{children:"lgpm"}),":"]}),"\n",(0,l.jsxs)(n.table,{children:[(0,l.jsx)(n.thead,{children:(0,l.jsxs)(n.tr,{children:[(0,l.jsx)(n.th,{children:"Tool"}),(0,l.jsx)(n.th,{children:"Repository"})]})}),(0,l.jsxs)(n.tbody,{children:[(0,l.jsxs)(n.tr,{children:[(0,l.jsx)(n.td,{children:(0,l.jsx)(n.code,{children:"logoscore"})}),(0,l.jsx)(n.td,{children:(0,l.jsx)(n.code,{children:"https://github.com/logos-co/logos-logoscore-cli"})})]}),(0,l.jsxs)(n.tr,{children:[(0,l.jsx)(n.td,{children:(0,l.jsx)(n.code,{children:"lgpd"})}),(0,l.jsx)(n.td,{children:(0,l.jsx)(n.code,{children:"https://github.com/logos-co/logos-package-downloader"})})]}),(0,l.jsxs)(n.tr,{children:[(0,l.jsx)(n.td,{children:(0,l.jsx)(n.code,{children:"lgpm"})}),(0,l.jsx)(n.td,{children:(0,l.jsx)(n.code,{children:"https://github.com/logos-co/logos-package-manager"})})]})]})]}),"\n",(0,l.jsx)(n.p,{children:"For x86_64 Linux, these are the current download URLs as of 2026-06-25:"}),"\n",(0,l.jsx)(n.pre,{children:(0,l.jsx)(n.code,{className:"language-sh",children:"wget https://github.com/logos-co/logos-logoscore-cli/releases/download/v3/logoscore-x86_64-linux.AppImage\nwget https://github.com/logos-co/logos-package-downloader/releases/download/pre-release-99d70db-7/lgpd-x86_64-linux.tar.gz\nwget https://github.com/logos-co/logos-package-manager/releases/download/pre-release-05b2cf8-7/lgpm-x86_64-linux.tar.gz\n"})}),"\n"]}),"\n",(0,l.jsxs)(n.li,{children:["\n",(0,l.jsxs)(n.p,{children:["Install the tools under ",(0,l.jsx)(n.code,{children:"/usr/local/bin"}),":"]}),"\n",(0,l.jsx)(n.pre,{children:(0,l.jsx)(n.code,{className:"language-sh",children:"install -m755 logoscore-x86_64-linux.AppImage /usr/local/bin/logoscore\ntar -xzf lgpd-x86_64-linux.tar.gz\ninstall -m755 lgpd-x86_64.AppImage /usr/local/bin/lgpd\ntar -xzf lgpm-x86_64-linux.tar.gz\ninstall -m755 lgpm-x86_64.AppImage /usr/local/bin/lgpm\n"})}),"\n"]}),"\n",(0,l.jsxs)(n.li,{children:["\n",(0,l.jsx)(n.p,{children:"Verify all three tools are accessible:"}),"\n",(0,l.jsx)(n.pre,{children:(0,l.jsx)(n.code,{className:"language-sh",children:"logoscore --help\nlgpd --help\nlgpm --help\n"})}),"\n"]}),"\n"]}),"\n",(0,l.jsx)(n.h2,{id:"step-2-prepare-the-host",children:"Step 2: Prepare the host"}),"\n",(0,l.jsx)(n.p,{children:"Create the runtime user and the directory layout the node uses at runtime."}),"\n",(0,l.jsxs)(n.ol,{children:["\n",(0,l.jsxs)(n.li,{children:["\n",(0,l.jsxs)(n.p,{children:["Create the ",(0,l.jsx)(n.code,{children:"logos"})," system user and data directories:"]}),"\n",(0,l.jsx)(n.pre,{children:(0,l.jsx)(n.code,{className:"language-sh",children:"useradd --system --home /var/lib/logos-node --create-home --shell /usr/sbin/nologin logos\nmkdir -p /opt/logos-node/modules /opt/logos-node/packages\nmkdir -p /var/lib/logos-node/blockchain-module-testnet\nmkdir -p /var/lib/logos-node/storage-module\nmkdir -p /var/lib/logos-node/delivery-module\nchown -R logos:logos /var/lib/logos-node\n"})}),"\n"]}),"\n",(0,l.jsxs)(n.li,{children:["\n",(0,l.jsx)(n.p,{children:"Open these on the host firewall:"}),"\n",(0,l.jsx)(n.pre,{children:(0,l.jsx)(n.code,{className:"language-text",children:"3000/udp\n8090/udp\n8091/tcp\n9000/udp\n30303/tcp\n"})}),"\n"]}),"\n"]}),"\n",(0,l.jsx)(n.h2,{id:"step-3-install-modules",children:"Step 3: Install modules"}),"\n",(0,l.jsxs)(n.p,{children:["Download and install the three module packages from the configured module ",(0,l.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#catalogue",children:"catalogue"}),"."]}),"\n",(0,l.jsx)(n.admonition,{type:"info",children:(0,l.jsxs)(n.p,{children:[(0,l.jsx)(n.code,{children:"lgpd download"})," fetches the version published in the catalogue. It does not automatically pull the newest commit from module repositories. Ensure the intended versions are published in the catalogue before running these commands."]})}),"\n",(0,l.jsxs)(n.ol,{children:["\n",(0,l.jsxs)(n.li,{children:["\n",(0,l.jsx)(n.p,{children:"Download the module packages:"}),"\n",(0,l.jsx)(n.pre,{children:(0,l.jsx)(n.code,{className:"language-sh",children:"lgpd download blockchain_module --version 0.2.0 --output /opt/logos-node/packages\nlgpd download storage_module --output /opt/logos-node/packages\nlgpd download delivery_module --output /opt/logos-node/packages\n"})}),"\n"]}),"\n",(0,l.jsxs)(n.li,{children:["\n",(0,l.jsx)(n.p,{children:"Install all three packages into the shared modules directory:"}),"\n",(0,l.jsx)(n.pre,{children:(0,l.jsx)(n.code,{className:"language-sh",children:"lgpm --modules-dir /opt/logos-node/modules install --file /opt/logos-node/packages/blockchain_module-0.2.0.lgx\nlgpm --modules-dir /opt/logos-node/modules install --file /opt/logos-node/packages/storage_module-*.lgx\nlgpm --modules-dir /opt/logos-node/modules install --file /opt/logos-node/packages/delivery_module-*.lgx\n"})}),"\n"]}),"\n",(0,l.jsxs)(n.li,{children:["\n",(0,l.jsx)(n.p,{children:"Verify the installed versions:"}),"\n",(0,l.jsx)(n.pre,{children:(0,l.jsx)(n.code,{className:"language-sh",children:"jq -r '.name + \" \" + .version' /opt/logos-node/modules/*/manifest.json\n"})}),"\n"]}),"\n"]}),"\n",(0,l.jsx)(n.h2,{id:"step-4-start-logos-core",children:"Step 4: Start Logos Core"}),"\n",(0,l.jsxs)(n.p,{children:["Start the ",(0,l.jsx)(n.code,{children:"logoscore"})," daemon with the shared modules directory before loading any modules."]}),"\n",(0,l.jsxs)(n.ol,{children:["\n",(0,l.jsxs)(n.li,{children:["\n",(0,l.jsxs)(n.p,{children:["Start ",(0,l.jsx)(n.code,{children:"logoscore"})," in the foreground for a first manual run:"]}),"\n",(0,l.jsx)(n.pre,{children:(0,l.jsx)(n.code,{className:"language-sh",children:"cd /var/lib/logos-node\nlogoscore -m /opt/logos-node/modules\n"})}),"\n",(0,l.jsxs)(n.ul,{children:["\n",(0,l.jsx)(n.li,{children:"Keep this terminal open. Use a second terminal for all module commands."}),"\n",(0,l.jsxs)(n.li,{children:["For a detached run, pass ",(0,l.jsx)(n.code,{children:"-D"})," to start in daemon mode: ",(0,l.jsx)(n.code,{children:"logoscore -m /opt/logos-node/modules -D"})]}),"\n"]}),"\n"]}),"\n",(0,l.jsxs)(n.li,{children:["\n",(0,l.jsx)(n.p,{children:"Verify the daemon is running:"}),"\n",(0,l.jsx)(n.pre,{children:(0,l.jsx)(n.code,{className:"language-sh",children:"logoscore status\n"})}),"\n"]}),"\n"]}),"\n",(0,l.jsx)(n.h2,{id:"step-5-configure-and-start-the-blockchain-module",children:"Step 5: Configure and start the blockchain module"}),"\n",(0,l.jsx)(n.p,{children:"Load the blockchain module, generate the node config, and start the module."}),"\n",(0,l.jsx)(n.admonition,{type:"info",children:(0,l.jsxs)(n.p,{children:[(0,l.jsx)(n.code,{children:"user_config.yaml"})," contains node-local wallet and key-management configuration. Keep it private, restrict file permissions, and do not publish it. Generate a fresh file for each node."]})}),"\n",(0,l.jsxs)(n.ol,{children:["\n",(0,l.jsxs)(n.li,{children:["\n",(0,l.jsx)(n.p,{children:"Create the peer bootstrap file:"}),"\n",(0,l.jsx)(n.pre,{children:(0,l.jsx)(n.code,{className:"language-sh",children:'cd /var/lib/logos-node/blockchain-module-testnet\ncat > peers.json <<EOF\n{\n "initial_peers": [\n "/ip4/65.109.51.37/udp/3000/quic-v1/p2p/12D3KooWFrouXfmrR4nsLMtE7wu15DoMJ6VtoUtHinREZCvbWHar",\n "/ip4/65.109.51.37/udp/3001/quic-v1/p2p/12D3KooWJRGau8M1rjT7R5e4YYsgdFhsMX35nRDtMwCDjxQkXAHz",\n "/ip4/65.109.51.37/udp/3002/quic-v1/p2p/12D3KooWQXJavMDTRscjauFSgVAB1VLB6Rzpy2uY5SU9Tk7927tb",\n "/ip4/65.109.51.37/udp/50001/quic-v1/p2p/12D3KooWSQc7CcGtvWDPF1yCbBthFnQjprfCVHmfmNDUrSmqQsU1"\n ]\n}\nEOF\n'})}),"\n"]}),"\n",(0,l.jsxs)(n.li,{children:["\n",(0,l.jsxs)(n.p,{children:["Load the module and generate ",(0,l.jsx)(n.code,{children:"user_config.yaml"}),":"]}),"\n",(0,l.jsx)(n.pre,{children:(0,l.jsx)(n.code,{className:"language-sh",children:'logoscore load-module blockchain_module\ncd /var/lib/logos-node/blockchain-module-testnet\nlogoscore call blockchain_module generate_user_config "$(cat peers.json)"\nchmod 600 /var/lib/logos-node/user_config.yaml\n'})}),"\n",(0,l.jsxs)(n.ul,{children:["\n",(0,l.jsxs)(n.li,{children:[(0,l.jsx)(n.code,{children:"generate_user_config"})," writes ",(0,l.jsx)(n.code,{children:"user_config.yaml"})," to the ",(0,l.jsx)(n.code,{children:"logoscore"})," daemon working directory (",(0,l.jsx)(n.code,{children:"/var/lib/logos-node/user_config.yaml"})," with this guide's layout)."]}),"\n",(0,l.jsxs)(n.li,{children:["Important fields in ",(0,l.jsx)(n.code,{children:"user_config.yaml"})," include:"]}),"\n"]}),"\n",(0,l.jsxs)(n.table,{children:[(0,l.jsx)(n.thead,{children:(0,l.jsxs)(n.tr,{children:[(0,l.jsx)(n.th,{children:"Field"}),(0,l.jsx)(n.th,{children:"Purpose"}),(0,l.jsx)(n.th,{children:"Guidance"})]})}),(0,l.jsxs)(n.tbody,{children:[(0,l.jsxs)(n.tr,{children:[(0,l.jsx)(n.td,{children:(0,l.jsx)(n.code,{children:"network.initial_peers"})}),(0,l.jsx)(n.td,{children:"Bootstrap peers"}),(0,l.jsx)(n.td,{children:"Use the current network document"})]}),(0,l.jsxs)(n.tr,{children:[(0,l.jsx)(n.td,{children:(0,l.jsx)(n.code,{children:"network.port"})}),(0,l.jsx)(n.td,{children:"Public UDP P2P port"}),(0,l.jsxs)(n.td,{children:["Keep aligned with firewall/NAT, normally ",(0,l.jsx)(n.code,{children:"3000"})]})]}),(0,l.jsxs)(n.tr,{children:[(0,l.jsx)(n.td,{children:(0,l.jsx)(n.code,{children:"api.listen_address"})}),(0,l.jsx)(n.td,{children:"Local API bind"}),(0,l.jsxs)(n.td,{children:["Keep private, normally ",(0,l.jsx)(n.code,{children:"127.0.0.1:8080"})]})]}),(0,l.jsxs)(n.tr,{children:[(0,l.jsx)(n.td,{children:(0,l.jsx)(n.code,{children:"state.base_folder"})}),(0,l.jsx)(n.td,{children:"State directory"}),(0,l.jsx)(n.td,{children:"Use a persistent local path"})]}),(0,l.jsxs)(n.tr,{children:[(0,l.jsx)(n.td,{children:"logger filters"}),(0,l.jsx)(n.td,{children:"Log verbosity"}),(0,l.jsxs)(n.td,{children:["Use ",(0,l.jsx)(n.code,{children:"INFO"})," for unattended operation"]})]})]})]}),"\n"]}),"\n",(0,l.jsxs)(n.li,{children:["\n",(0,l.jsx)(n.p,{children:"Start the blockchain module:"}),"\n",(0,l.jsx)(n.pre,{children:(0,l.jsx)(n.code,{className:"language-sh",children:'logoscore call blockchain_module start /var/lib/logos-node/user_config.yaml ""\n'})}),"\n",(0,l.jsxs)(n.ul,{children:["\n",(0,l.jsxs)(n.li,{children:["The second argument is intentionally an empty string; the blockchain module no longer requires a downloaded ",(0,l.jsx)(n.code,{children:"deployment.yaml"})," file."]}),"\n"]}),"\n"]}),"\n",(0,l.jsxs)(n.li,{children:["\n",(0,l.jsx)(n.p,{children:"Verify the module is running:"}),"\n",(0,l.jsx)(n.pre,{children:(0,l.jsx)(n.code,{className:"language-sh",children:"logoscore call blockchain_module get_cryptarchia_info | jq -r .result.value | jq .\n"})}),"\n"]}),"\n"]}),"\n",(0,l.jsx)(n.h2,{id:"step-6-configure-and-start-the-storage-module",children:"Step 6: Configure and start the storage module"}),"\n",(0,l.jsxs)(n.p,{children:["Create the storage config and start the module. Replace ",(0,l.jsx)(n.code,{children:"<public-ip>"})," with the node's public IPv4 address before running these commands."]}),"\n",(0,l.jsxs)(n.ol,{children:["\n",(0,l.jsxs)(n.li,{children:["\n",(0,l.jsx)(n.p,{children:"Create the storage config:"}),"\n",(0,l.jsx)(n.pre,{children:(0,l.jsx)(n.code,{className:"language-sh",children:'cd /var/lib/logos-node/storage-module\nmkdir -p storage-data\ncat > config.json <<EOF\n{\n "data-dir": "./storage-data",\n "log-level": "INFO",\n "listen-ip": "0.0.0.0",\n "listen-port": 8091,\n "disc-port": 8090,\n "nat": "extip:<public-ip>",\n "network": "logos.test"\n}\nEOF\n'})}),"\n",(0,l.jsxs)(n.ul,{children:["\n",(0,l.jsxs)(n.li,{children:[(0,l.jsx)(n.code,{children:"config.json"})," includes the following fields:"]}),"\n"]}),"\n",(0,l.jsxs)(n.table,{children:[(0,l.jsx)(n.thead,{children:(0,l.jsxs)(n.tr,{children:[(0,l.jsx)(n.th,{children:"Field"}),(0,l.jsx)(n.th,{children:"Purpose"})]})}),(0,l.jsxs)(n.tbody,{children:[(0,l.jsxs)(n.tr,{children:[(0,l.jsx)(n.td,{children:(0,l.jsx)(n.code,{children:"data-dir"})}),(0,l.jsx)(n.td,{children:"Storage repository path"})]}),(0,l.jsxs)(n.tr,{children:[(0,l.jsx)(n.td,{children:(0,l.jsx)(n.code,{children:"log-level"})}),(0,l.jsx)(n.td,{children:"Log verbosity"})]}),(0,l.jsxs)(n.tr,{children:[(0,l.jsx)(n.td,{children:(0,l.jsx)(n.code,{children:"listen-ip"})}),(0,l.jsx)(n.td,{children:"Local TCP bind address"})]}),(0,l.jsxs)(n.tr,{children:[(0,l.jsx)(n.td,{children:(0,l.jsx)(n.code,{children:"listen-port"})}),(0,l.jsx)(n.td,{children:"Public TCP libp2p port"})]}),(0,l.jsxs)(n.tr,{children:[(0,l.jsx)(n.td,{children:(0,l.jsx)(n.code,{children:"disc-port"})}),(0,l.jsx)(n.td,{children:"Public UDP discovery port"})]}),(0,l.jsxs)(n.tr,{children:[(0,l.jsx)(n.td,{children:(0,l.jsx)(n.code,{children:"nat"})}),(0,l.jsx)(n.td,{children:"Public IP advertisement mode"})]}),(0,l.jsxs)(n.tr,{children:[(0,l.jsx)(n.td,{children:(0,l.jsx)(n.code,{children:"network"})}),(0,l.jsx)(n.td,{children:"Storage network preset"})]})]})]}),"\n",(0,l.jsxs)(n.ul,{children:["\n",(0,l.jsxs)(n.li,{children:["Use fixed ",(0,l.jsx)(n.code,{children:"listen-port"})," and ",(0,l.jsx)(n.code,{children:"disc-port"}),"; do not leave public nodes on random ports."]}),"\n",(0,l.jsxs)(n.li,{children:["The ",(0,l.jsx)(n.code,{children:"logos.test"})," preset provides the storage bootstrap settings."]}),"\n",(0,l.jsx)(n.li,{children:"Fields"}),"\n"]}),"\n",(0,l.jsx)(n.admonition,{type:"info"}),"\n"]}),"\n"]}),"\n",(0,l.jsxs)(n.p,{children:["To run storage with ",(0,l.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#mix",children:"mix"})," support, generate the config from the published mix bootstrap data:"]}),"\n",(0,l.jsx)(n.pre,{children:(0,l.jsx)(n.code,{className:"language-sh",children:'cd /var/lib/logos-node/storage-module\ncat > make-mix-storage-config.sh <<\'EOF\'\n#!/usr/bin/env bash\nset -e\n\ndata_dir=${1:-"./logos-storage-data"}\nudp_spr_json=$(curl -s https://logos-storage-network.fra1.digitaloceanspaces.com/v0.2/udp-sprs.json)\ntcp_spr_json=$(curl -s https://logos-storage-network.fra1.digitaloceanspaces.com/v0.2/tcp-sprs.json)\n\nwget https://logos-storage-network.fra1.digitaloceanspaces.com/v0.2/mix-pool.json\nmp_path=$(realpath "./mix-pool.json")\n\ncat <<JSON\n{\n "nat": "any",\n "log-level": "DEBUG",\n "mix-enabled": true,\n "listen-port": 8080,\n "disc-port": 8090,\n "bootstrap-node": $udp_spr_json,\n "dht-mix-proxy": $tcp_spr_json,\n "data-dir": "${data_dir}",\n "mix-pool": "${mp_path}"\n}\nJSON\nEOF\n\nchmod 755 make-mix-storage-config.sh\n./make-mix-storage-config.sh > config.json\n'})}),"\n",(0,l.jsx)(n.p,{children:":::"}),"\n",(0,l.jsxs)(n.ol,{children:["\n",(0,l.jsxs)(n.li,{children:["\n",(0,l.jsxs)(n.p,{children:["Load and start the ",(0,l.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#storage-module",children:"storage module"}),":"]}),"\n",(0,l.jsx)(n.pre,{children:(0,l.jsx)(n.code,{className:"language-sh",children:"cd /var/lib/logos-node/storage-module\nlogoscore load-module storage_module\nlogoscore call storage_module init @config.json\nlogoscore call storage_module start\n"})}),"\n",(0,l.jsxs)(n.ul,{children:["\n",(0,l.jsxs)(n.li,{children:["\n",(0,l.jsx)(n.p,{children:"If using the mix config, also enable private queries and verify with a test download:"}),"\n",(0,l.jsx)(n.pre,{children:(0,l.jsx)(n.code,{className:"language-sh",children:"logoscore call storage_module togglePrivateQueries true\nlogoscore call storage_module downloadToUrl zDvZRwzkzrrYB6sS1rRpRLt4gBhc1pWoyTSjkfszfmj1seaYYLCZ ./farewell-to-westphalia.pdf false 65536\n"})}),"\n"]}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,l.jsx)(n.h2,{id:"step-7-configure-and-start-the-delivery-module",children:"Step 7: Configure and start the delivery module"}),"\n",(0,l.jsxs)(n.p,{children:["Create the delivery config and start the module. Replace ",(0,l.jsx)(n.code,{children:"<public-ip>"})," with the node's public IPv4 address before running these commands."]}),"\n",(0,l.jsxs)(n.ol,{children:["\n",(0,l.jsxs)(n.li,{children:["\n",(0,l.jsx)(n.p,{children:"Create the delivery config:"}),"\n",(0,l.jsx)(n.pre,{children:(0,l.jsx)(n.code,{className:"language-sh",children:'cd /var/lib/logos-node/delivery-module\ncat > config.json <<EOF\n{\n "preset": "logos.test",\n "mode": "Core",\n "logLevel": "INFO",\n "tcpPort": 30303,\n "discv5UdpPort": 9000,\n "discv5Discovery": true,\n "nat": "extip:<public-ip>"\n}\nEOF\n'})}),"\n",(0,l.jsxs)(n.ul,{children:["\n",(0,l.jsxs)(n.li,{children:[(0,l.jsx)(n.code,{children:"config.json"})," includes the following fields:"]}),"\n"]}),"\n",(0,l.jsxs)(n.table,{children:[(0,l.jsx)(n.thead,{children:(0,l.jsxs)(n.tr,{children:[(0,l.jsx)(n.th,{children:"Field"}),(0,l.jsx)(n.th,{children:"Purpose"})]})}),(0,l.jsxs)(n.tbody,{children:[(0,l.jsxs)(n.tr,{children:[(0,l.jsx)(n.td,{children:(0,l.jsx)(n.code,{children:"preset"})}),(0,l.jsx)(n.td,{children:"Network preset"})]}),(0,l.jsxs)(n.tr,{children:[(0,l.jsx)(n.td,{children:(0,l.jsx)(n.code,{children:"mode"})}),(0,l.jsx)(n.td,{children:"Delivery node mode"})]}),(0,l.jsxs)(n.tr,{children:[(0,l.jsx)(n.td,{children:(0,l.jsx)(n.code,{children:"logLevel"})}),(0,l.jsx)(n.td,{children:"Log verbosity"})]}),(0,l.jsxs)(n.tr,{children:[(0,l.jsx)(n.td,{children:(0,l.jsx)(n.code,{children:"tcpPort"})}),(0,l.jsx)(n.td,{children:"Public TCP P2P port"})]}),(0,l.jsxs)(n.tr,{children:[(0,l.jsx)(n.td,{children:(0,l.jsx)(n.code,{children:"discv5UdpPort"})}),(0,l.jsx)(n.td,{children:"Public UDP discovery port"})]}),(0,l.jsxs)(n.tr,{children:[(0,l.jsx)(n.td,{children:(0,l.jsx)(n.code,{children:"discv5Discovery"})}),(0,l.jsx)(n.td,{children:"Enable discv5 discovery"})]}),(0,l.jsxs)(n.tr,{children:[(0,l.jsx)(n.td,{children:(0,l.jsx)(n.code,{children:"nat"})}),(0,l.jsx)(n.td,{children:"Public IP advertisement mode"})]})]})]}),"\n",(0,l.jsxs)(n.ul,{children:["\n",(0,l.jsxs)(n.li,{children:["Use fixed ",(0,l.jsx)(n.code,{children:"tcpPort"})," and ",(0,l.jsx)(n.code,{children:"discv5UdpPort"}),"; do not leave public nodes on random ports."]}),"\n",(0,l.jsxs)(n.li,{children:["The ",(0,l.jsx)(n.code,{children:"logos.test"})," preset provides the delivery network bootstrap settings."]}),"\n"]}),"\n"]}),"\n",(0,l.jsxs)(n.li,{children:["\n",(0,l.jsxs)(n.p,{children:["Load and start the ",(0,l.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#delivery-module",children:"delivery module"}),":"]}),"\n",(0,l.jsx)(n.pre,{children:(0,l.jsx)(n.code,{className:"language-sh",children:"cd /var/lib/logos-node/delivery-module\nlogoscore load-module delivery_module\nlogoscore call delivery_module createNode @config.json\nlogoscore call delivery_module start\n"})}),"\n"]}),"\n",(0,l.jsxs)(n.li,{children:["\n",(0,l.jsx)(n.p,{children:"Verify the delivery module is running:"}),"\n",(0,l.jsx)(n.pre,{children:(0,l.jsx)(n.code,{className:"language-sh",children:"logoscore call delivery_module getNodeInfo Version\nlogoscore call delivery_module getNodeInfo MyBoundPorts\n"})}),"\n"]}),"\n"]}),"\n",(0,l.jsx)(n.h2,{id:"step-8-verify-the-full-node-is-healthy",children:"Step 8: Verify the full node is healthy"}),"\n",(0,l.jsx)(n.p,{children:"Run health checks against the daemon and all three modules to confirm the node is fully operational."}),"\n",(0,l.jsxs)(n.ol,{children:["\n",(0,l.jsxs)(n.li,{children:["\n",(0,l.jsx)(n.p,{children:"Check the daemon and all loaded modules:"}),"\n",(0,l.jsx)(n.pre,{children:(0,l.jsx)(n.code,{className:"language-sh",children:"logoscore status --json\n"})}),"\n",(0,l.jsxs)(n.p,{children:["Expected modules in the output: ",(0,l.jsx)(n.code,{children:"storage_module"}),", ",(0,l.jsx)(n.code,{children:"blockchain_module"}),", ",(0,l.jsx)(n.code,{children:"delivery_module"}),", ",(0,l.jsx)(n.code,{children:"capability_module"}),"."]}),"\n"]}),"\n",(0,l.jsxs)(n.li,{children:["\n",(0,l.jsx)(n.p,{children:"Verify all ports are bound correctly:"}),"\n",(0,l.jsx)(n.pre,{children:(0,l.jsx)(n.code,{className:"language-sh",children:"ss -lntup | egrep '(:3000|:8090|:8091|:9000|:30303|:8080)'\n"})}),"\n",(0,l.jsx)(n.p,{children:"Expected bindings:"}),"\n",(0,l.jsx)(n.pre,{children:(0,l.jsx)(n.code,{className:"language-text",children:"0.0.0.0:3000/udp\n0.0.0.0:8090/udp\n0.0.0.0:8091/tcp\n0.0.0.0:9000/udp\n0.0.0.0:30303/tcp\n127.0.0.1:8080/tcp\n"})}),"\n"]}),"\n",(0,l.jsxs)(n.li,{children:["\n",(0,l.jsx)(n.p,{children:"Check the blockchain module sync state:"}),"\n",(0,l.jsx)(n.pre,{children:(0,l.jsx)(n.code,{className:"language-sh",children:"logoscore call blockchain_module get_cryptarchia_info | jq -r .result.value | jq .\n"})}),"\n"]}),"\n",(0,l.jsxs)(n.li,{children:["\n",(0,l.jsx)(n.p,{children:"Check the delivery module bound ports:"}),"\n",(0,l.jsx)(n.pre,{children:(0,l.jsx)(n.code,{className:"language-sh",children:"logoscore call delivery_module getNodeInfo MyBoundPorts\n"})}),"\n"]}),"\n"]}),"\n",(0,l.jsx)(n.h3,{id:"optional-run-the-node-unattended-with-systemd",children:"Optional: Run the node unattended with systemd"}),"\n",(0,l.jsxs)(n.p,{children:["Use a dedicated service for ",(0,l.jsx)(n.code,{children:"logoscore"})," and a separate bootstrap script for module startup. Do not start modules from ",(0,l.jsx)(n.code,{children:"ExecStartPost"})," in the ",(0,l.jsx)(n.code,{children:"logoscore"})," service \u2014 slow or failing module starts may cause systemd to kill the daemon."]}),"\n",(0,l.jsx)(n.p,{children:"Daemon service unit:"}),"\n",(0,l.jsx)(n.pre,{children:(0,l.jsx)(n.code,{className:"language-ini",children:"[Unit]\nDescription=Logos Node\nAfter=network-online.target\nWants=network-online.target\n\n[Service]\nUser=logos\nGroup=logos\nWorkingDirectory=/var/lib/logos-node\nEnvironment=HOME=/var/lib/logos-node\nExecStart=/usr/local/bin/logoscore -m /opt/logos-node/modules -D\nRestart=always\nRestartSec=10\nStandardOutput=journal\nStandardError=journal\n\n[Install]\nWantedBy=multi-user.target\n"})}),"\n",(0,l.jsxs)(n.p,{children:["The bootstrap script should wait for ",(0,l.jsx)(n.code,{children:"logoscore status"}),", load and start the storage module, load and start the blockchain module, and load and start the delivery module. It should tolerate already-loaded modules and slow module starts."]}),"\n",(0,l.jsx)(n.p,{children:"Recommended journald retention to cap disk usage:"}),"\n",(0,l.jsx)(n.pre,{children:(0,l.jsx)(n.code,{className:"language-ini",children:"[Journal]\nSystemMaxUse=200M\nSystemKeepFree=1G\nMaxRetentionSec=7day\nMaxFileSec=1day\n"})}),"\n",(0,l.jsxs)(n.p,{children:["Use the ",(0,l.jsx)(n.code,{children:"INFO"})," log level for unattended operation; use ",(0,l.jsx)(n.code,{children:"DEBUG"})," only for short troubleshooting windows."]})]})}function h(e={}){let{wrapper:n}={...(0,d.R)(),...e.components};return n?(0,l.jsx)(n,{...e,children:(0,l.jsx)(a,{...e})}):a(e)}},8453(e,n,s){s.d(n,{R:()=>r,x:()=>t});var o=s(6540);let l={},d=o.createContext(l);function r(e){let n=o.useContext(d);return o.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function t(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(l):e.components||l:r(e.components),o.createElement(d.Provider,{value:n},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/ea2b553e.89e8db87.js b/assets/js/ea2b553e.89e8db87.js new file mode 100644 index 00000000..e4ec1d79 --- /dev/null +++ b/assets/js/ea2b553e.89e8db87.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunklogos_docs=self.webpackChunklogos_docs||[]).push([["4164"],{4909(e,s,r){r.r(s),r.d(s,{metadata:()=>t,default:()=>p,frontMatter:()=>i,contentTitle:()=>d,toc:()=>c,assets:()=>a});var t=JSON.parse('{"id":"messaging/concepts/understand-peer-discovery/about-static-peers","title":"About static peers","description":"Understand how hard-coded bootstrap peers work and the trade-offs they carry.","source":"@site/docs/messaging/concepts/understand-peer-discovery/about-static-peers.md","sourceDirName":"messaging/concepts/understand-peer-discovery","slug":"/messaging/concepts/understand-peer-discovery/static-peers","permalink":"/messaging/concepts/understand-peer-discovery/static-peers","draft":false,"unlisted":false,"editUrl":"https://github.com/logos-co/logos-docs/tree/main/docs/messaging/concepts/understand-peer-discovery/about-static-peers.md","tags":[],"version":"current","sidebarPosition":2,"frontMatter":{"title":"About static peers","doc_type":"concept","product":"messaging","topics":"peer-discovery, static-peers, networking","authors":"LordGhostX, fryorcraken","owner":"logos","doc_version":1,"slug":"static-peers","sidebar_position":2},"sidebar":"messagingSidebar","previous":{"title":"Understand peer discovery","permalink":"/messaging/concepts/understand-peer-discovery/peer-discovery"},"next":{"title":"About DNS discovery","permalink":"/messaging/concepts/understand-peer-discovery/dns-discovery"}}'),o=r(4848),n=r(8453);let i={title:"About static peers",doc_type:"concept",product:"messaging",topics:"peer-discovery, static-peers, networking",authors:"LordGhostX, fryorcraken",owner:"logos",doc_version:1,slug:"static-peers",sidebar_position:2},d="About static peers",a={},c=[{value:"Understand how hard-coded bootstrap peers work and the trade-offs they carry.",id:"understand-how-hard-coded-bootstrap-peers-work-and-the-trade-offs-they-carry",level:4},{value:"How static peers work",id:"how-static-peers-work",level:2},{value:"Pros and cons",id:"pros-and-cons",level:2}];function l(e){let s={a:"a",code:"code",h1:"h1",h2:"h2",h4:"h4",header:"header",li:"li",p:"p",pre:"pre",ul:"ul",...(0,n.R)(),...e.components};return(0,o.jsxs)(o.Fragment,{children:[(0,o.jsx)(s.header,{children:(0,o.jsx)(s.h1,{id:"about-static-peers",children:"About static peers"})}),"\n",(0,o.jsx)(s.h4,{id:"understand-how-hard-coded-bootstrap-peers-work-and-the-trade-offs-they-carry",children:"Understand how hard-coded bootstrap peers work and the trade-offs they carry."}),"\n",(0,o.jsxs)(s.p,{children:[(0,o.jsx)(s.a,{href:"https://docs.logos.co/get-started/glossary#logos-delivery",children:"Logos Delivery"})," applications have the flexibility to embed bootstrap node addresses directly into their codebase. Developers can either use static peers operated by ",(0,o.jsx)(s.a,{href:"https://status.im/",children:"Status"})," or ",(0,o.jsx)(s.a,{href:"/messaging/delivery/run-logos-delivery-node",children:"run a node"}),"."]}),"\n",(0,o.jsx)(s.h2,{id:"how-static-peers-work",children:"How static peers work"}),"\n",(0,o.jsx)(s.pre,{children:(0,o.jsx)(s.code,{className:"language-mermaid",children:"sequenceDiagram\nAlice->>Bob: Connect to Bob (static peer details)\nAlice->>Carol: Connect to Carol (static peer details)\n"})}),"\n",(0,o.jsx)(s.p,{children:"Alice establishes connections with Bob and Carol using their node details, which are predefined (hard-coded) into Alice's node."}),"\n",(0,o.jsx)(s.h2,{id:"pros-and-cons",children:"Pros and cons"}),"\n",(0,o.jsx)(s.p,{children:"Pros:"}),"\n",(0,o.jsxs)(s.ul,{children:["\n",(0,o.jsx)(s.li,{children:"Low latency."}),"\n",(0,o.jsx)(s.li,{children:"Low resource requirements."}),"\n"]}),"\n",(0,o.jsx)(s.p,{children:"Cons:"}),"\n",(0,o.jsxs)(s.ul,{children:["\n",(0,o.jsx)(s.li,{children:"Vulnerable to censorship: Node IPs can be blocked or restricted."}),"\n",(0,o.jsx)(s.li,{children:"Limited scalability: The number of nodes is fixed and cannot easily be expanded."}),"\n",(0,o.jsx)(s.li,{children:"Maintenance challenges: Updating the node list requires modifying the code, which can be cumbersome and involves releasing and deploying."}),"\n"]})]})}function p(e={}){let{wrapper:s}={...(0,n.R)(),...e.components};return s?(0,o.jsx)(s,{...e,children:(0,o.jsx)(l,{...e})}):l(e)}},8453(e,s,r){r.d(s,{R:()=>i,x:()=>d});var t=r(6540);let o={},n=t.createContext(o);function i(e){let s=t.useContext(n);return t.useMemo(function(){return"function"==typeof e?e(s):{...s,...e}},[s,e])}function d(e){let s;return s=e.disableParentContext?"function"==typeof e.components?e.components(o):e.components||o:i(e.components),t.createElement(n.Provider,{value:s},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/ea6a119a.b14e472c.js b/assets/js/ea6a119a.b14e472c.js new file mode 100644 index 00000000..9cc27ff3 --- /dev/null +++ b/assets/js/ea6a119a.b14e472c.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunklogos_docs=self.webpackChunklogos_docs||[]).push([["8087"],{3805(e,n,t){t.r(n),t.d(n,{metadata:()=>s,default:()=>h,frontMatter:()=>a,contentTitle:()=>r,toc:()=>l,assets:()=>c});var s=JSON.parse('{"id":"blockchain/zone-sdk/inscribe-data-on-chain-using-zone-sdk","title":"Inscribe data on-chain using the Zone SDK","description":"Learn how to write plain text as on-chain inscriptions using a simple Logos Zone.","source":"@site/docs/blockchain/zone-sdk/inscribe-data-on-chain-using-zone-sdk.md","sourceDirName":"blockchain/zone-sdk","slug":"/blockchain/zone-sdk/inscribe-data-on-chain-using-zone-sdk","permalink":"/blockchain/zone-sdk/inscribe-data-on-chain-using-zone-sdk","draft":false,"unlisted":false,"editUrl":"https://github.com/logos-co/logos-docs/tree/main/docs/blockchain/zone-sdk/inscribe-data-on-chain-using-zone-sdk.md","tags":[],"version":"current","sidebarPosition":1,"frontMatter":{"title":"Inscribe data on-chain using the Zone SDK","doc_type":"procedure","product":"blockchain","topics":["channels","zones"],"steps_layout":"sectioned","authors":"kashepavadan","owner":"logos","doc_version":1,"slug":"inscribe-data-on-chain-using-zone-sdk","sidebar_position":1},"sidebar":"blockchainSidebar","previous":{"title":"Claim leader rewards in the Logos Blockchain UI app","permalink":"/blockchain/node-app/claim-leader-rewards-in-logos-blockchain-ui-app"},"next":{"title":"Bridge assets between Logos Blockchain and a Zone","permalink":"/blockchain/zone-sdk/bridge-assets-between-blockchain-and-zone"}}'),i=t(4848),o=t(8453);let a={title:"Inscribe data on-chain using the Zone SDK",doc_type:"procedure",product:"blockchain",topics:["channels","zones"],steps_layout:"sectioned",authors:"kashepavadan",owner:"logos",doc_version:1,slug:"inscribe-data-on-chain-using-zone-sdk",sidebar_position:1},r="Inscribe data on-chain using the Zone SDK",c={},l=[{value:"Learn how to write plain text as on-chain inscriptions using a simple Logos Zone.",id:"learn-how-to-write-plain-text-as-on-chain-inscriptions-using-a-simple-logos-zone",level:4},{value:"What to expect",id:"what-to-expect",level:2},{value:"Step 1: Clone the Logos Blockchain repository",id:"step-1-clone-the-logos-blockchain-repository",level:2},{value:"Step 2: Initialise the <code>ZoneSequencer</code> struct",id:"step-2-initialise-the-zonesequencer-struct",level:2},{value:"Step 3: Handle channel events",id:"step-3-handle-channel-events",level:2},{value:"Step 4: Publish data",id:"step-4-publish-data",level:2},{value:"Step 5: Build and run the sequencer",id:"step-5-build-and-run-the-sequencer",level:2}];function d(e){let n={a:"a",admonition:"admonition",code:"code",em:"em",h1:"h1",h2:"h2",h4:"h4",header:"header",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,o.R)(),...e.components};return(0,i.jsxs)(i.Fragment,{children:[(0,i.jsx)(n.header,{children:(0,i.jsx)(n.h1,{id:"inscribe-data-on-chain-using-the-zone-sdk",children:"Inscribe data on-chain using the Zone SDK"})}),"\n",(0,i.jsx)(n.h4,{id:"learn-how-to-write-plain-text-as-on-chain-inscriptions-using-a-simple-logos-zone",children:"Learn how to write plain text as on-chain inscriptions using a simple Logos Zone."}),"\n",(0,i.jsxs)(n.p,{children:["Applications built on Logos are implemented in execution environments known as ",(0,i.jsx)(n.a,{href:"/blockchain/concepts/about-zones",children:(0,i.jsx)(n.em,{children:"Zones"})}),", which post data ",(0,i.jsx)(n.em,{children:"inscriptions"})," on-chain via Logos channels. A ",(0,i.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#zone",children:"Zone"})," could host a versatile rollup with thousands of applications, such as the ",(0,i.jsx)(n.a,{href:"/lez/get-started/lez-quickstart",children:"Logos Execution Zone"}),", or it could be a simple, standalone Zone tracking the state of just one application."]}),"\n",(0,i.jsxs)(n.p,{children:["The ",(0,i.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#zone-sdk",children:(0,i.jsx)(n.strong,{children:"Zone SDK"})})," is a ready-to-use toolbox that handles basic interactions with a Logos Zone. This tutorial shows how to create a simple Logos Zone that writes plain text as on-chain inscriptions, based on the ",(0,i.jsx)(n.a,{href:"https://github.com/logos-blockchain/logos-blockchain/tree/master/deployment/tui-zone",children:"TUI Zone demo"}),"."]}),"\n",(0,i.jsx)(n.admonition,{type:"info",children:(0,i.jsxs)(n.p,{children:["You can try out a working version of the TUI Zone used in this tutorial directly from your ",(0,i.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#logos-blockchain",children:"Logos Blockchain"})," node. Just run ",(0,i.jsx)(n.code,{children:"./logos-blockchain-node inscribe"}),"."]})}),"\n",(0,i.jsx)(n.p,{children:"Before you begin, you will need:"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:["A running ",(0,i.jsx)(n.a,{href:"/blockchain/get-started/run-a-logos-blockchain-node-from-cli",children:"Logos node"})]}),"\n"]}),"\n",(0,i.jsx)(n.h2,{id:"what-to-expect",children:"What to expect"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:["You will initialise a ",(0,i.jsx)(n.code,{children:"ZoneSequencer"})," that connects to your ",(0,i.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#logos-node",children:"Logos node"})," and creates a new ",(0,i.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#channel",children:"channel"})," for posting inscriptions."]}),"\n",(0,i.jsx)(n.li,{children:"You will implement event handling so the sequencer tracks which of its inscriptions have finalised and keeps a checkpoint to resume from."}),"\n",(0,i.jsx)(n.li,{children:"You will publish plain-text messages as on-chain inscriptions and verify they are finalised in the channel."}),"\n"]}),"\n",(0,i.jsx)(n.h2,{id:"step-1-clone-the-logos-blockchain-repository",children:"Step 1: Clone the Logos Blockchain repository"}),"\n",(0,i.jsxs)(n.p,{children:["Before you begin, clone the ",(0,i.jsx)(n.a,{href:"https://github.com/logos-blockchain/logos-blockchain/tree/master",children:"logos-blockchain"})," repository. The ",(0,i.jsx)(n.code,{children:"tui-zone-tutorial"})," branch contains a skeleton implementation of the TUI Zone with space to fill in the relevant code. A more comprehensive implementation, which also supports decentralised sequencing, can be found in the ",(0,i.jsx)(n.code,{children:"master"})," branch."]}),"\n",(0,i.jsxs)(n.ol,{children:["\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsx)(n.p,{children:"Run the following in your desired path to clone the repository and switch to the tutorial branch:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:"git clone https://github.com/logos-blockchain/logos-blockchain.git\ncd logos-blockchain/deployment/tui-zone\n\n# To follow along with the tutorial\ngit checkout tui-zone-tutorial\n"})}),"\n"]}),"\n"]}),"\n",(0,i.jsxs)(n.h2,{id:"step-2-initialise-the-zonesequencer-struct",children:["Step 2: Initialise the ",(0,i.jsx)(n.code,{children:"ZoneSequencer"})," struct"]}),"\n",(0,i.jsxs)(n.p,{children:["Sequencer functionality is provided via the ",(0,i.jsx)(n.code,{children:"ZoneSequencer"})," struct from the Zone SDK, found in ",(0,i.jsx)(n.code,{children:"logos-blockchain/zone-sdk/src/sequencer/zone_sequencer.rs"}),". When initialising this struct, provide the following arguments:"]}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"channel_id: ChannelId"})," \u2014 the ID of the channel associated with the Zone."]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"signing_key: Ed25519Key"})," \u2014 a key authorised to post updates to the channel."]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"node: Node"})," \u2014 a Node struct referring to your Logos Blockchain node, together with the credentials to access it. Created by ",(0,i.jsx)(n.code,{children:"NodeHttpClient::new()"}),"."]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"checkpoint: Option<SequencerCheckpoint>"})," \u2014 [Optional] the checkpoint representing the most recently-pushed channel update."]}),"\n"]}),"\n",(0,i.jsxs)(n.p,{children:["Before posting to a new channel, the sequencer must first generate an Ed25519 public/private key pair. ",(0,i.jsx)(n.strong,{children:"This initial public key defines the channel ID, while the private key becomes the first authorised signing key."})," The channel is created when the sequencer posts a ",(0,i.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#message",children:"message"})," with this channel ID, unless it already exists. Initially, only the sequencer with the signing key can post messages to the channel. Additional keys can be authorised via the ",(0,i.jsx)(n.a,{href:"https://nomos-tech.notion.site/v1-2-Mantle-Specification-2ce261aa09df805ea358d80c2046cf95",children:"CHANNEL_CONFIG"})," ",(0,i.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#mantle",children:"Mantle"})," Operation."]}),"\n",(0,i.jsxs)(n.p,{children:["After the first channel message, further messages include a hash reference to the previous message in the channel. If a message is posted with an incorrect parent hash, it is rejected. ",(0,i.jsx)(n.strong,{children:"Providing a checkpoint lets the sequencer track the last message posted to its channel, allowing it to resume posting new updates after restarting."})," A checkpoint is not necessary for the session during which a sequencer creates a new channel."]}),"\n",(0,i.jsxs)(n.ol,{children:["\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsxs)(n.p,{children:["Add a function to create a new private signing key in ",(0,i.jsx)(n.code,{children:"src/lib.rs"}),":"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-rust",children:'// Load signing key from file or generate a new one if it doesn\'t exist\n//\n// path: The path to the signing key file\nfn load_or_create_signing_key(path: &Path) -> Ed25519Key {\n if path.exists() {\n let key_bytes = fs::read(path).expect("failed to read key file");\n assert!(\n key_bytes.len() == ED25519_SECRET_KEY_SIZE,\n "invalid key file: expected {} bytes, got {}",\n ED25519_SECRET_KEY_SIZE,\n key_bytes.len()\n );\n let key_array: [u8; ED25519_SECRET_KEY_SIZE] =\n key_bytes.try_into().expect("length already checked");\n Ed25519Key::from_bytes(&key_array)\n } else {\n let mut key_bytes = [0u8; ED25519_SECRET_KEY_SIZE];\n rand::RngCore::fill_bytes(&mut rand::thread_rng(), &mut key_bytes);\n fs::write(path, key_bytes).expect("failed to write key file");\n Ed25519Key::from_bytes(&key_bytes)\n }\n}\n'})}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsxs)(n.p,{children:["Fill in the ",(0,i.jsx)(n.code,{children:"run()"})," function in ",(0,i.jsx)(n.code,{children:"src/lib.rs"})," with the following code that creates a channel with an associated sequencer:"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-rust",children:'// Processing loop\n//\n// args: Setup info\npub async fn run(args: InscribeArgs) {\n\n // Get node URL\n let node_url: Url = args.node_url.parse().expect("invalid node URL");\n\n // Create new signing key or load existing one from path\n let signing_key = load_or_create_signing_key(Path::new(&args.key_path));\n\n // Derive channel ID\n let channel_id = ChannelId::from(signing_key.public_key().to_bytes());\n\n println!("TUI Zone Sequencer");\n println!(" Node: {node_url}");\n println!(" Key: {}", args.key_path);\n println!(" Channel ID: {}", hex::encode(channel_id.as_ref()));\n println!();\n\n // Create initial Zone state & load checkpoint (to be implemented later in the tutorial)\n let mut state = InMemoryZoneState::default();\n let checkpoint = state.load_checkpoint().cloned();\n\n // Connect to node\n let node = NodeHttpClient::new(CommonHttpClient::new(None), node_url);\n\n // Initialise ZoneSequencer\n let mut sequencer = ZoneSequencer::init(channel_id, signing_key, node, checkpoint);\n\n // Wait to start reading from stdin until ready\n let (ready_tx, ready_rx) = tokio::sync::oneshot::channel();\n let mut stdin_rx = spawn_stdin_reader(ready_rx);\n let mut ready_tx = Some(ready_tx);\n\n ...\n\n}\n'})}),"\n"]}),"\n"]}),"\n",(0,i.jsx)(n.h2,{id:"step-3-handle-channel-events",children:"Step 3: Handle channel events"}),"\n",(0,i.jsxs)(n.p,{children:["For a centralized Zone \u2014 a single sequencer that owns its channel \u2014 event handling is minimal. There are no competing sequencers, and a single sequencer never loses a ",(0,i.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#slot",children:"slot"}),", so the sequencer only needs to track which of its inscriptions have finalised and keep a checkpoint so it can resume after a restart. It learns about both through the Zone SDK's ",(0,i.jsx)(n.code,{children:"Event"})," stream."]}),"\n",(0,i.jsxs)(n.p,{children:["The status of the sequencer's backfill process, transactions sent by the sequencer, and any updates to the Zone state are communicated via the Zone SDK's ",(0,i.jsx)(n.code,{children:"Event"}),". These events are:"]}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"Ready"})," \u2014 the sequencer is caught up and ready to accept updates."]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"BlocksProcessed"})," \u2014 a new block was processed. Includes the latest ",(0,i.jsx)(n.code,{children:"checkpoint"})," and the list of ",(0,i.jsx)(n.code,{children:"finalized"})," transactions. It also carries a ",(0,i.jsx)(n.code,{children:"channel_update"})," (",(0,i.jsx)(n.code,{children:"adopted"})," / ",(0,i.jsx)(n.code,{children:"orphaned"})," inscriptions), but that is only relevant when several sequencers share a channel \u2014 ",(0,i.jsx)(n.strong,{children:"a centralized Zone ignores it"}),"."]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"MempoolPending"})," \u2014 transaction was accepted by node API and is waiting in mempool."]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"TurnNotification"})," \u2014 the sequencer's turn to write in a turn-based decentralised sequencing scenario (not applicable to our example)."]}),"\n"]}),"\n",(0,i.jsxs)(n.ol,{children:["\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsxs)(n.p,{children:["In ",(0,i.jsx)(n.code,{children:"src/state.rs"}),", add the following struct to track Zone state in memory and maintain the checkpoint:"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-rust",children:"// Keep track of Zone state in memory\n//\n// published: Inscriptions published by your sequencer, not yet finalised\n// finalized: All finalised inscriptions\n// checkpoint: Last message in Zone state \u2014 lets the sequencer resume after a restart\n#[derive(Default)]\npub struct InMemoryZoneState {\n published: Vec<Msg>,\n finalized: Vec<Msg>,\n checkpoint: Option<SequencerCheckpoint>,\n}\n\nimpl InMemoryZoneState {\n // Record a tx we just published locally, so the local view stays in\n // sync with what the SDK accepted. Called at the publish-call site.\n pub fn on_published(&mut self, info: &InscriptionInfo) {\n self.published\n .push(Msg::from_payload(info.this_msg, &info.payload));\n }\n\n // Move our finalised publishes out of `published` and into `finalized`.\n fn on_finalized(&mut self, inscriptions: &[InscriptionInfo]) {\n for info in inscriptions {\n if let Some(i) = self\n .published\n .iter()\n .position(|m| m.msg_id == info.this_msg)\n {\n self.published.remove(i);\n }\n if !self.finalized.iter().any(|m| m.msg_id == info.this_msg) {\n self.finalized\n .push(Msg::from_payload(info.this_msg, &info.payload));\n }\n }\n }\n\n pub fn published(&self) -> &[Msg] {\n &self.published\n }\n\n pub fn finalized(&self) -> &[Msg] {\n &self.finalized\n }\n\n pub fn save_checkpoint(&mut self, checkpoint: SequencerCheckpoint) {\n self.checkpoint = Some(checkpoint);\n }\n\n pub fn load_checkpoint(&self) -> Option<&SequencerCheckpoint> {\n self.checkpoint.as_ref()\n }\n}\n"})}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsxs)(n.p,{children:["Back in ",(0,i.jsx)(n.code,{children:"src/lib.rs"}),", add a function to handle when the sequencer has finished ",(0,i.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#bootstrapping",children:"bootstrapping"}),":"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-rust",children:'// Defines initial post-bootstrapping behaviour\n//\n// state: Zone state\n// ready_tx: The transmitter of the ready event to ready_rx\nfn handle_ready(\n state: &InMemoryZoneState,\n ready_tx: &mut Option<tokio::sync::oneshot::Sender<()>>,\n) {\n info!("Sequencer ready");\n if let Some(tx) = ready_tx.take() {\n let _ = tx.send(());\n }\n println!("Ready.");\n println!();\n println!("Type a message and press Enter to publish.");\n println!("Press Ctrl-D or type an empty line to exit.");\n println!();\n ui::render_state(state);\n ui::prompt();\n}\n'})}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsx)(n.p,{children:"Add a function to apply finalised messages to the Zone state:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-rust",children:"// Apply finalised messages from chain to Zone state\n//\n// items: Finalised transactions\n// state: Zone state\nfn apply_finalized(items: &[FinalizedTx], state: &mut InMemoryZoneState) {\n\n // TUI only cares about inscriptions for rendering; deposit / withdraw\n // ops have no inscription payload.\n let inscriptions: Vec<InscriptionInfo> = items\n .iter()\n .flat_map(|t| t.ops.iter())\n .filter_map(|op| match op {\n FinalizedOp::Inscription(i) => Some(i.clone()),\n FinalizedOp::Deposit(_) | FinalizedOp::Withdraw(_) => None,\n })\n .collect();\n state.on_finalized(&inscriptions);\n ui::render_state(state);\n ui::prompt();\n}\n"})}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsx)(n.p,{children:"Add a function to handle sequencer events and execute whichever of the above handlers is necessary for the current event:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-rust",children:"// Handle sequencer events\n//\n// event: current sequencer event\nfn handle_event(\n event: Event,\n state: &mut InMemoryZoneState,\n ready_tx: &mut Option<tokio::sync::oneshot::Sender<()>>,\n) {\n match event {\n Event::Ready => handle_ready(state, ready_tx),\n\n // Centralized Zone: ignore `channel_update` \u2014 a single sequencer\n // never loses a slot, so there is nothing to adopt or roll back.\n // Just apply finalised inscriptions and persist the checkpoint.\n Event::BlocksProcessed {\n checkpoint, finalized, ..\n } => {\n if !finalized.is_empty() {\n apply_finalized(&finalized, state);\n }\n state.save_checkpoint(checkpoint);\n }\n Event::MempoolPending(_) | Event::TurnNotification { .. } => {}\n }\n}\n"})}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsxs)(n.p,{children:["Add the processing loop to ",(0,i.jsx)(n.code,{children:"run()"})," to watch for events:"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-rust",children:'pub async fn run(args: InscribeArgs) {\n\n ...\n\n loop {\n tokio::select! {\n\n // Watch for events\n event = sequencer.next_event() => {\n handle_event(event, &mut state, &mut ready_tx);\n }\n\n ...\n\n // Ctrl+C exits the loop\n _ = tokio::signal::ctrl_c() => {\n println!();\n break;\n }\n }\n }\n\n println!("Goodbye!");\n}\n'})}),"\n"]}),"\n"]}),"\n",(0,i.jsx)(n.h2,{id:"step-4-publish-data",children:"Step 4: Publish data"}),"\n",(0,i.jsxs)(n.p,{children:["Once the ",(0,i.jsx)(n.code,{children:"ZoneSequencer"})," is set up, posting data to the channel is as easy as passing a vector of bytes to the sequencer handler's ",(0,i.jsx)(n.code,{children:"publish"})," function. Once the transaction is submitted to the Zone SDK, this function returns a struct consisting of the ",(0,i.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#inscription",children:"inscription"})," (message) ID and the current checkpoint."]}),"\n",(0,i.jsxs)(n.ol,{children:["\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsxs)(n.p,{children:["Add the following code to process user input text in the ",(0,i.jsx)(n.code,{children:"run"})," function in ",(0,i.jsx)(n.code,{children:"src/lib.rs"}),":"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-rust",children:'pub async fn run(args: InscribeArgs) {\n\n ...\n\n loop {\n tokio::select! {\n\n ...\n\n // Get input text from stdin\n input = stdin_rx.recv() => {\n\n // Handle unexpected input\n let Some(text) = input else {\n println!();\n break;\n };\n\n // Turn input into AppMessage wrapper. Includes unique ID for each transaction to avoid removing \'duplicate\' messages\n // from mempool\n let msg = AppMessage::new(text);\n debug!(tx_uuid = %msg.tx_uuid, text = %msg.text, "Publishing message");\n\n // publish() takes bytes anyway, so must convert\n let Ok(inscription) = Inscription::try_from(msg.to_bytes()) else {\n error!("Message is too large to fit in an inscription");\n continue;\n };\n\n // Publish data\n match sequencer.handle().publish(inscription) {\n\n // Get result and checkpoint, update Zone state\n Ok((result, checkpoint)) => {\n let info = result.tx.inscription();\n debug!(msg_id = %hex::encode(info.this_msg.as_ref()), "Published");\n state.on_published(info);\n state.save_checkpoint(checkpoint);\n ui::render_state(&state);\n eprintln!(" \\x1b[90mpending...\\x1b[0m");\n ui::prompt();\n }\n Err(lb_zone_sdk::sequencer::Error::Unavailable { reason }) => {\n warn!("publish rejected: {reason}");\n eprintln!(\n " \\x1b[33msequencer is still starting up, try again in a moment\\x1b[0m"\n );\n ui::prompt();\n }\n Err(e) => {\n error!("failed to publish: {e}");\n break;\n }\n }\n }\n\n ...\n\n }\n }\n\n ...\n\n}\n'})}),"\n"]}),"\n"]}),"\n",(0,i.jsx)(n.h2,{id:"step-5-build-and-run-the-sequencer",children:"Step 5: Build and run the sequencer"}),"\n",(0,i.jsxs)(n.ol,{children:["\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsxs)(n.p,{children:["With the code filled in, build and run the sequencer from the ",(0,i.jsx)(n.code,{children:"deployment/tui-zone"})," directory. The binary is named ",(0,i.jsx)(n.code,{children:"tui-sequencer"}),":"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:"cargo run --bin tui-sequencer -- --node-url <Your Node URL>\n"})}),"\n",(0,i.jsxs)(n.p,{children:["The sequencer connects to your node, derives the channel ID from your signing key, and prints ",(0,i.jsx)(n.code,{children:"Ready."})," once it has bootstrapped. Type a message and press Enter to publish it as an on-chain inscription."]}),"\n",(0,i.jsxs)(n.p,{children:["Each message first appears under ",(0,i.jsx)(n.strong,{children:"Published"})," (pending). Once your node finalises the block containing it, the message moves to ",(0,i.jsx)(n.strong,{children:"Finalized"})," \u2014 confirming the inscription is on-chain. Press Ctrl-D or enter an empty line to exit."]}),"\n"]}),"\n"]})]})}function h(e={}){let{wrapper:n}={...(0,o.R)(),...e.components};return n?(0,i.jsx)(n,{...e,children:(0,i.jsx)(d,{...e})}):d(e)}},8453(e,n,t){t.d(n,{R:()=>a,x:()=>r});var s=t(6540);let i={},o=s.createContext(i);function a(e){let n=s.useContext(o);return s.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function r(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(i):e.components||i:a(e.components),s.createElement(o.Provider,{value:n},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/eaab68dc.997c9db9.js b/assets/js/eaab68dc.997c9db9.js new file mode 100644 index 00000000..42d5b2f3 --- /dev/null +++ b/assets/js/eaab68dc.997c9db9.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunklogos_docs=self.webpackChunklogos_docs||[]).push([["8546"],{9300(e,n,t){t.r(n),t.d(n,{metadata:()=>i,default:()=>h,frontMatter:()=>r,contentTitle:()=>c,toc:()=>l,assets:()=>a});var i=JSON.parse('{"id":"blockchain/zone-sdk/operate-decentralised-zone-with-round-robin-sequencer-rotation","title":"Operate a decentralised Zone with round-robin sequencer rotation","description":"Get started with multi-sequencer channels using the Zone SDK and round-robin slot windows.","source":"@site/docs/blockchain/zone-sdk/operate-decentralised-zone-with-round-robin-sequencer-rotation.md","sourceDirName":"blockchain/zone-sdk","slug":"/blockchain/zone-sdk/operate-decentralised-zone-with-round-robin-sequencer-rotation","permalink":"/blockchain/zone-sdk/operate-decentralised-zone-with-round-robin-sequencer-rotation","draft":false,"unlisted":false,"editUrl":"https://github.com/logos-co/logos-docs/tree/main/docs/blockchain/zone-sdk/operate-decentralised-zone-with-round-robin-sequencer-rotation.md","tags":[],"version":"current","sidebarPosition":3,"frontMatter":{"title":"Operate a decentralised Zone with round-robin sequencer rotation","doc_type":"procedure","product":"blockchain","topics":"zone-sdk","steps_layout":"sectioned","authors":"pradovic, hansieodendaal, kashepavadan","owner":"logos","doc_version":1,"slug":"operate-decentralised-zone-with-round-robin-sequencer-rotation","sidebar_position":3},"sidebar":"blockchainSidebar","previous":{"title":"Bridge assets between Logos Blockchain and a Zone","permalink":"/blockchain/zone-sdk/bridge-assets-between-blockchain-and-zone"},"next":{"title":"Join the Blend network as a core node","permalink":"/blockchain/blend/join-the-blend-network-as-a-core-node"}}'),s=t(4848),o=t(8453);let r={title:"Operate a decentralised Zone with round-robin sequencer rotation",doc_type:"procedure",product:"blockchain",topics:"zone-sdk",steps_layout:"sectioned",authors:"pradovic, hansieodendaal, kashepavadan",owner:"logos",doc_version:1,slug:"operate-decentralised-zone-with-round-robin-sequencer-rotation",sidebar_position:3},c="Operate a decentralised Zone with round-robin sequencer rotation",a={},l=[{value:"Get started with multi-sequencer channels using the Zone SDK and round-robin slot windows.",id:"get-started-with-multi-sequencer-channels-using-the-zone-sdk-and-round-robin-slot-windows",level:4},{value:"What to expect",id:"what-to-expect",level:2},{value:"Step 1: Size the rotation parameters",id:"step-1-size-the-rotation-parameters",level:2},{value:"Step 2: Publish inscriptions and monitor turn state",id:"step-2-publish-inscriptions-and-monitor-turn-state",level:2},{value:"Step 3: Update the committee with a channel config op",id:"step-3-update-the-committee-with-a-channel-config-op",level:2}];function d(e){let n={a:"a",admonition:"admonition",code:"code",h1:"h1",h2:"h2",h4:"h4",header:"header",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,o.R)(),...e.components};return(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(n.header,{children:(0,s.jsx)(n.h1,{id:"operate-a-decentralised-zone-with-round-robin-sequencer-rotation",children:"Operate a decentralised Zone with round-robin sequencer rotation"})}),"\n",(0,s.jsx)(n.h4,{id:"get-started-with-multi-sequencer-channels-using-the-zone-sdk-and-round-robin-slot-windows",children:"Get started with multi-sequencer channels using the Zone SDK and round-robin slot windows."}),"\n",(0,s.jsxs)(n.p,{children:["This tutorial covers how to configure and operate a ",(0,s.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#logos-blockchain",children:"Logos Blockchain"})," ",(0,s.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#channel",children:"channel"})," with a committee of accredited sequencers that take turns publishing inscriptions to a Logos ",(0,s.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#zone",children:"Zone"}),". It is intended for zone developers using the ",(0,s.jsx)(n.a,{href:"https://github.com/logos-blockchain/logos-blockchain/tree/master/zone-sdk",children:"Zone SDK"})," who need to move from a single-sequencer setup to a decentralized one."]}),"\n",(0,s.jsxs)(n.p,{children:["The ",(0,s.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#zone-sdk",children:"Zone SDK"})," currently supports ",(0,s.jsx)(n.strong,{children:"round-robin"})," rotation only. Each sequencer publishes inscriptions for ",(0,s.jsx)(n.code,{children:"posting_timeframe"})," slots before the rotation advances to the next sequencer in the ",(0,s.jsx)(n.code,{children:"accredited_keys"})," list. Other scheduling schemes (such as First-Write-Wins) are not yet available."]}),"\n",(0,s.jsx)(n.p,{children:"Before you start, make sure you have the following:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:["An existing application using the ",(0,s.jsx)(n.a,{href:"/blockchain/zone-sdk/inscribe-data-on-chain-using-zone-sdk",children:"Zone SDK"})]}),"\n",(0,s.jsxs)(n.li,{children:["At least one ",(0,s.jsx)(n.code,{children:"Ed25519PublicKey"})," generated per committee member"]}),"\n"]}),"\n",(0,s.jsx)(n.h2,{id:"what-to-expect",children:"What to expect"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"You can publish inscriptions from any sequencer at any time; the SDK holds pending transactions and drains them automatically when the sequencer's turn arrives."}),"\n",(0,s.jsxs)(n.li,{children:["You can monitor each sequencer's turn status in real time using ",(0,s.jsx)(n.code,{children:"subscribe_turn_to_write"})," or ",(0,s.jsx)(n.code,{children:"subscribe_channel_view"}),"."]}),"\n",(0,s.jsxs)(n.li,{children:["You can update the accredited key list to add or remove sequencers by submitting a ",(0,s.jsx)(n.code,{children:"ChannelConfigOp"}),"."]}),"\n"]}),"\n",(0,s.jsx)(n.h2,{id:"step-1-size-the-rotation-parameters",children:"Step 1: Size the rotation parameters"}),"\n",(0,s.jsxs)(n.p,{children:["Choose values for ",(0,s.jsx)(n.code,{children:"posting_timeframe"})," and ",(0,s.jsx)(n.code,{children:"posting_timeout"})," before creating or reconfiguring the channel. These fields on ",(0,s.jsx)(n.a,{href:"https://nomos-tech.notion.site/1-5-0-Mantle-33d261aa09df8051b0d0cd4d5ddade85#e9c261aa09df839b8f1d81a357aaf616",children:(0,s.jsx)(n.code,{children:"ChannelState"})})," control the rotation cadence and inactive-sequencer skip behavior."]}),"\n",(0,s.jsx)(n.admonition,{type:"info",children:(0,s.jsxs)(n.p,{children:["The ",(0,s.jsx)(n.a,{href:"https://nomos-tech.notion.site/1-5-0-Mantle-33d261aa09df8051b0d0cd4d5ddade85#5d6261aa09df836f9638814e35f5fe81",children:"Mantle specification"})," is the source of truth for the exact rotation algorithm. The guidance below is a practical summary."]})}),"\n",(0,s.jsxs)(n.ol,{children:["\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsxs)(n.p,{children:["Set ",(0,s.jsx)(n.code,{children:"posting_timeframe"})," - the length of each sequencer's turn under normal circumstances - to at least the average slots-per-block of the Logos Blockchain."]}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"A turn shorter than one block lets multiple sequencers become authorized within the same block, defeating the purpose of rotation."}),"\n",(0,s.jsxs)(n.li,{children:["As a rule of thumb, use a multiple: with a ~20 slots/block average, ",(0,s.jsx)(n.code,{children:"posting_timeframe = 60"})," gives each sequencer roughly three blocks of ownership per turn."]}),"\n"]}),"\n"]}),"\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsxs)(n.p,{children:["Set ",(0,s.jsx)(n.code,{children:"posting_timeout"})," to a value greater than or equal to ",(0,s.jsx)(n.code,{children:"posting_timeframe"}),". ",(0,s.jsx)(n.code,{children:"posting_timeout"})," is the time we wait for an inactive sequencer to publish before continuing to the next sequencer - all subsequent inactive sequencers have a turn of ",(0,s.jsx)(n.code,{children:"posting_timeout"})," slots until one of them publishes. At that point, turns go back to being ",(0,s.jsx)(n.code,{children:"posting_timeframe"})," slots long."]}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:["Values smaller than ",(0,s.jsx)(n.code,{children:"posting_timeframe"})," make the timeout branch dominate cadence as soon as the authorized sequencer goes silent."]}),"\n"]}),"\n"]}),"\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsx)(n.p,{children:"Confirm the worked example matches your intended behavior before proceeding."}),"\n",(0,s.jsxs)(n.p,{children:["A channel with three accredited keys, ",(0,s.jsx)(n.code,{children:"posting_timeframe = 60"}),", and ",(0,s.jsx)(n.code,{children:"posting_timeout = 180"}),":"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{children:"slots 0\u201359 \u2192 index 0\nslots 60\u2013119 \u2192 index 1\nslots 120\u2013179 \u2192 index 2\nslots 180\u2013239 \u2192 index 0\n...\n"})}),"\n",(0,s.jsx)(n.admonition,{type:"info"}),"\n"]}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"The Zone SDK exposes the current rotation state to your sequencer in two forms:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:["The full ",(0,s.jsx)(n.code,{children:"SequencerChannelView"})," (",(0,s.jsx)(n.code,{children:"subscribe_channel_view"}),") \u2014 a ",(0,s.jsx)(n.code,{children:"tokio::sync::watch"})," receiver carrying ",(0,s.jsx)(n.code,{children:"authorized_key_index"}),", ",(0,s.jsx)(n.code,{children:"own_key_index"}),", ",(0,s.jsx)(n.code,{children:"our_turn_to_write"}),", the current ",(0,s.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#slot",children:"slot"}),", and the turn window's ",(0,s.jsx)(n.code,{children:"turn_to_write_slots"}),"."]}),"\n",(0,s.jsxs)(n.li,{children:["A focused ",(0,s.jsx)(n.code,{children:"TurnNotification"})," (",(0,s.jsx)(n.code,{children:"subscribe_turn_to_write"}),") plus ",(0,s.jsx)(n.code,{children:"Event::TurnNotification"})," \u2014 emitted only when the turn boundary actually changes."]}),"\n"]}),"\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.code,{children:"our_turn_to_write"})," is the boolean a sequencer reads to decide whether it is currently authorised.\n:::"]}),"\n",(0,s.jsx)(n.h2,{id:"step-2-publish-inscriptions-and-monitor-turn-state",children:"Step 2: Publish inscriptions and monitor turn state"}),"\n",(0,s.jsxs)(n.p,{children:["The Zone SDK queues inscriptions locally when it is not your sequencer's turn and posts them automatically when the turn arrives. You do not need to gate ",(0,s.jsx)(n.code,{children:"publish"})," calls on turn state yourself."]}),"\n",(0,s.jsxs)(n.ol,{children:["\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsxs)(n.p,{children:["Call ",(0,s.jsx)(n.code,{children:"publish"})," from any task at any time."]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-rust",children:"// From the drive task, once `Event::Ready` has fired.\nlet (result, checkpoint) = sequencer.handle().publish(zone_block)?;\n// `result.tx` is a `PendingTx::Inscription(...)`.\n// `checkpoint` is up to date with the new pending entry.\n// The post may not have hit the node yet if it is not our turn.\n"})}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"The SDK enqueues the transaction immediately if it is your turn and posts to the network."}),"\n",(0,s.jsxs)(n.li,{children:["The SDK enqueues the transaction with ",(0,s.jsx)(n.code,{children:"posted: false"})," if it is not your turn; it drains automatically when the turn arrives."]}),"\n",(0,s.jsxs)(n.li,{children:["A periodic ",(0,s.jsx)(n.code,{children:"resubmit_interval"})," (default 30 s) re-drains pending publishes if your turn is current, covering transient network failures."]}),"\n"]}),"\n"]}),"\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsx)(n.p,{children:"Subscribe to turn notifications to drive your UI or application logic. Choose one of the two surfaces based on your use case:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsxs)(n.p,{children:["Use ",(0,s.jsx)(n.code,{children:"TurnNotification"})," (edge-triggered) to wake the UI only when the turn boundary changes:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-rust",children:'use lb_zone_sdk::sequencer::Event;\n\nif let Event::TurnNotification { notification } = event {\n if notification.our_turn_to_write {\n println!(\n "Our turn until slot {:?}",\n notification.ends_at_slot,\n );\n } else {\n println!(\n "Waiting for our turn; current authorized window ends at {:?}",\n notification.ends_at_slot,\n );\n }\n}\n'})}),"\n"]}),"\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsxs)(n.p,{children:["Use ",(0,s.jsx)(n.code,{children:"subscribe_channel_view"})," (snapshot) to read the full rotation state on every block, for example to render a status panel:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-rust",children:'let view_rx = sequencer.subscribe_channel_view();\nlet view = view_rx.borrow().clone();\nif view.our_turn_to_write {\n // Authorized right now.\n} else if let (Some(own), Some(auth)) = (view.own_key_index, view.authorized_key_index) {\n println!(\n "Sequencer {} is publishing; we are sequencer {}.",\n auth, own,\n );\n}\n'})}),"\n",(0,s.jsxs)(n.p,{children:["The view also carries ",(0,s.jsx)(n.code,{children:"pending_publish_txs"})," and ",(0,s.jsx)(n.code,{children:"queued_messages"})," so the UI can show how many inscriptions are waiting for the next turn."]}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsxs)(n.p,{children:["Handle competing writes by listening for ",(0,s.jsx)(n.code,{children:"Event::BlocksProcessed"})," events and checking ",(0,s.jsx)(n.code,{children:"channel_update.orphaned"})," for orphaned transactions. Republish these transactions with the latest parent."]}),"\n"]}),"\n"]}),"\n",(0,s.jsx)(n.admonition,{type:"info",children:(0,s.jsxs)(n.p,{children:["Two sequencers can race on the same parent slot during rotation transitions or after a resync. The on-chain rule is: the first valid ",(0,s.jsx)(n.a,{href:"https://docs.logos.co/get-started/glossary#inscription",children:"inscription"})," wins; the loser's transaction is surfaced in ",(0,s.jsx)(n.code,{children:"channel_update.orphaned"}),"."]})}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-rust",children:"use lb_zone_sdk::sequencer::{Event, OrphanedTx};\n\nif let Event::BlocksProcessed { channel_update, .. } = event {\n for entry in channel_update.orphaned {\n if let OrphanedTx::Inscription(info) = entry {\n let (result, checkpoint) = sequencer.handle().publish(info.payload)?;\n // Persist `result` + `checkpoint` exactly as on the original publish.\n }\n }\n}\n"})}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:["See ",(0,s.jsx)(n.code,{children:"OrphanRepublishPolicy"})," in ",(0,s.jsx)(n.code,{children:"tests/src/cucumber/steps/manual_zone/support.rs"})," for the reference policy used in integration tests."]}),"\n",(0,s.jsx)(n.li,{children:"If the orphan was caused by an application-level conflict rather than a race, you can choose to drop the payload or deduplicate it against a higher-level transaction stream, or apply any other custom rule instead of republishing."}),"\n"]}),"\n",(0,s.jsx)(n.h2,{id:"step-3-update-the-committee-with-a-channel-config-op",children:"Step 3: Update the committee with a channel config op"}),"\n",(0,s.jsxs)(n.p,{children:["Submit a ",(0,s.jsx)(n.code,{children:"ChannelConfigOp"})," to add or remove sequencers from ",(0,s.jsx)(n.code,{children:"accredited_keys"}),". Because ",(0,s.jsx)(n.code,{children:"ChannelConfigOp"})," overwrites the entire key list, you should re-include any keys that should remain accredited after the update."]}),"\n",(0,s.jsxs)(n.ol,{children:["\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsxs)(n.p,{children:["Use ",(0,s.jsx)(n.code,{children:"channel_config"})," directly when ",(0,s.jsx)(n.code,{children:"configuration_threshold == 1"})," - that is, when you only need one sequencer's signature to change the configuration."]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-rust",children:"use lb_core::mantle::channel::{SlotTimeframe, SlotTimeout};\nuse lb_core::mantle::ops::channel::config::Keys;\n\nlet new_keys = Keys::from(vec![\n admin_pk,\n new_sequencer_b_pk,\n new_sequencer_c_pk,\n]);\n\nlet (result, checkpoint, signed_tx) = sequencer.handle().channel_config(\n new_keys,\n SlotTimeframe::from(60), // ~3 blocks per turn at 20 slots/block\n SlotTimeout::from(180), // skip after 3 turn windows of inactivity\n 1, // configuration_threshold (still single-admin)\n 1, // withdraw_threshold\n)?;\n"})}),"\n",(0,s.jsxs)(n.p,{children:["On finalization, ",(0,s.jsx)(n.code,{children:"refresh_channel_state"})," picks up the new config and the next ",(0,s.jsx)(n.code,{children:"BlocksProcessed"})," event recomputes ",(0,s.jsx)(n.code,{children:"our_turn_to_write"})," for every running sequencer."]}),"\n"]}),"\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsxs)(n.p,{children:["Use the ",(0,s.jsx)(n.code,{children:"prepare_tx"})," + ",(0,s.jsx)(n.code,{children:"submit_signed_tx"})," flow when ",(0,s.jsx)(n.code,{children:"configuration_threshold > 1"}),". In this case, collecting signatures from other sequencers is the responsibility of the application rather than the Zone SDK."]}),"\n"]}),"\n"]}),"\n",(0,s.jsx)(n.admonition,{type:"info",children:(0,s.jsx)(n.p,{children:"Multi-admin config changes are not yet exercised by integration tests; treat the flow below as the reference path until full SDK support lands."})}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:["The proposer calls ",(0,s.jsx)(n.code,{children:"prepare_tx"})," with a ",(0,s.jsx)(n.code,{children:"ChannelConfigOp"})," payload and distributes the unsigned transaction to the rest of the committee."]}),"\n",(0,s.jsxs)(n.li,{children:["Each co-signer calls ",(0,s.jsx)(n.code,{children:"sign_tx"})," and returns an ",(0,s.jsx)(n.code,{children:"IndexedSignature"}),"."]}),"\n",(0,s.jsxs)(n.li,{children:["The proposer assembles a ",(0,s.jsx)(n.code,{children:"ChannelMultiSigProof"})," from the collected signatures and submits via ",(0,s.jsx)(n.code,{children:"submit_signed_tx"}),"."]}),"\n"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-rust",children:"use lb_core::mantle::{\n Op, SignedMantleTx,\n ops::{OpProof, channel::config::ChannelConfigOp},\n};\nuse lb_core::proofs::channel_multi_sig_proof::{ChannelMultiSigProof, IndexedSignature};\n\n// 1. Read this sequencer's index from the channel view.\nlet view = sequencer.subscribe_channel_view().borrow().clone();\nlet own_key_index = view.own_key_index.ok_or(\"not an accredited key\")?;\n\n// 2. Build the unsigned config tx and get this sequencer's own signature.\nlet config = ChannelConfigOp {\n channel,\n keys: new_keys,\n posting_timeframe,\n posting_timeout,\n configuration_threshold,\n withdraw_threshold,\n};\nlet (tx, msg_id, own_sig) = sequencer.handle().prepare_tx(\n [Op::ChannelConfig(config)].into(),\n inscription_payload,\n)?;\n\n// 3. Distribute `tx` to the rest of the committee, collect their\n// `IndexedSignature`s. Transport is application-defined.\nlet signatures: Vec<IndexedSignature> = collect_signatures_from_committee(\n &tx,\n IndexedSignature::new(own_key_index, own_sig.clone()),\n).await?;\n\n// 4. Assemble the threshold proof and submit.\nlet config_proof = ChannelMultiSigProof::new(signatures)?;\nlet signed_tx = SignedMantleTx::new(\n tx,\n vec![\n OpProof::ChannelMultiSigProof(config_proof),\n OpProof::Ed25519Sig(own_sig),\n ],\n)?;\nlet (result, checkpoint) = sequencer\n .handle()\n .submit_signed_tx(signed_tx, msg_id)?;\n"})})]})}function h(e={}){let{wrapper:n}={...(0,o.R)(),...e.components};return n?(0,s.jsx)(n,{...e,children:(0,s.jsx)(d,{...e})}):d(e)}},8453(e,n,t){t.d(n,{R:()=>r,x:()=>c});var i=t(6540);let s={},o=i.createContext(s);function r(e){let n=i.useContext(o);return i.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function c(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(s):e.components||s:r(e.components),i.createElement(o.Provider,{value:n},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/f3eca8a6.cc619b8f.js b/assets/js/f3eca8a6.cc619b8f.js new file mode 100644 index 00000000..83a74c10 --- /dev/null +++ b/assets/js/f3eca8a6.cc619b8f.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunklogos_docs=self.webpackChunklogos_docs||[]).push([["2693"],{4758(e,s,t){t.r(s),t.d(s,{metadata:()=>n,default:()=>p,frontMatter:()=>i,contentTitle:()=>a,toc:()=>c,assets:()=>d});var n=JSON.parse('{"id":"messaging/concepts/about-transports","title":"About transports","description":"Understand how nodes move data between peers and which transports suit each environment.","source":"@site/docs/messaging/concepts/about-transports.md","sourceDirName":"messaging/concepts","slug":"/messaging/concepts/transports","permalink":"/messaging/concepts/transports","draft":false,"unlisted":false,"editUrl":"https://github.com/logos-co/logos-docs/tree/main/docs/messaging/concepts/about-transports.md","tags":[],"version":"current","sidebarPosition":4,"frontMatter":{"title":"About transports","doc_type":"concept","product":"messaging","topics":"transports, networking","authors":"LordGhostX, fryorcraken","owner":"logos","doc_version":1,"slug":"transports","sidebar_position":4},"sidebar":"messagingSidebar","previous":{"title":"About network domains","permalink":"/messaging/concepts/network-domains"},"next":{"title":"Understand peer discovery","permalink":"/messaging/concepts/understand-peer-discovery/peer-discovery"}}'),o=t(4848),r=t(8453);let i={title:"About transports",doc_type:"concept",product:"messaging",topics:"transports, networking",authors:"LordGhostX, fryorcraken",owner:"logos",doc_version:1,slug:"transports",sidebar_position:4},a="About transports",d={},c=[{value:"Understand how nodes move data between peers and which transports suit each environment.",id:"understand-how-nodes-move-data-between-peers-and-which-transports-suit-each-environment",level:4},{value:"The basics",id:"the-basics",level:2},{value:"Recommended transports",id:"recommended-transports",level:2}];function l(e){let s={a:"a",admonition:"admonition",h1:"h1",h2:"h2",h4:"h4",header:"header",li:"li",p:"p",strong:"strong",ul:"ul",...(0,r.R)(),...e.components};return(0,o.jsxs)(o.Fragment,{children:[(0,o.jsx)(s.header,{children:(0,o.jsx)(s.h1,{id:"about-transports",children:"About transports"})}),"\n",(0,o.jsx)(s.h4,{id:"understand-how-nodes-move-data-between-peers-and-which-transports-suit-each-environment",children:"Understand how nodes move data between peers and which transports suit each environment."}),"\n",(0,o.jsxs)(s.p,{children:[(0,o.jsx)(s.a,{href:"https://docs.logos.co/get-started/glossary#transport",children:"Transports"})," help move data packets across a network by establishing connections between peers. They define the rules and protocols to ensure efficient network transmission, routing, and data delivery. ",(0,o.jsx)(s.a,{href:"https://docs.logos.co/get-started/glossary#logos-delivery",children:"Logos Delivery"})," is a transport-agnostic framework that allows developers to choose and support multiple protocols according to their requirements."]}),"\n",(0,o.jsx)(s.h2,{id:"the-basics",children:"The basics"}),"\n",(0,o.jsxs)(s.ul,{children:["\n",(0,o.jsx)(s.li,{children:"Transports move data packets between peers and define how the network transmits, routes, and delivers data."}),"\n",(0,o.jsx)(s.li,{children:"Logos Delivery is transport-agnostic, so developers choose the protocols that suit their requirements."}),"\n",(0,o.jsxs)(s.li,{children:["The ",(0,o.jsx)(s.a,{href:"#recommended-transports",children:"recommended transports"})," vary by environment, from TCP for service nodes to secure WebSocket for browsers."]}),"\n"]}),"\n",(0,o.jsx)(s.h2,{id:"recommended-transports",children:"Recommended transports"}),"\n",(0,o.jsx)(s.p,{children:"For Logos Delivery nodes, the following transports are recommended:"}),"\n",(0,o.jsxs)(s.ul,{children:["\n",(0,o.jsxs)(s.li,{children:[(0,o.jsx)(s.strong,{children:"TCP"}),": By default, Logos Delivery nodes use TCP for communication. Service nodes should employ TCP for listening to and connecting with other nodes."]}),"\n",(0,o.jsxs)(s.li,{children:[(0,o.jsx)(s.strong,{children:"Secure WebSocket"}),": In browser environments, secure WebSocket is used. Service nodes are encouraged to set up SSL certificates to enable incoming connections from browsers and serve them securely."]}),"\n",(0,o.jsxs)(s.li,{children:["Other protocols like ",(0,o.jsx)(s.a,{href:"https://github.com/logos-messaging/logos-delivery-js/issues/20",children:"WebRTC"}),", ",(0,o.jsx)(s.a,{href:"https://github.com/logos-messaging/logos-delivery-js/issues/697",children:"WebTransport"}),", and QUIC have been researched and studied for potential integration."]}),"\n"]}),"\n",(0,o.jsx)(s.admonition,{type:"info",children:(0,o.jsx)(s.p,{children:"Logos Delivery ensures compatibility and improved communication capabilities by following these recommended transports."})})]})}function p(e={}){let{wrapper:s}={...(0,r.R)(),...e.components};return s?(0,o.jsx)(s,{...e,children:(0,o.jsx)(l,{...e})}):l(e)}},8453(e,s,t){t.d(s,{R:()=>i,x:()=>a});var n=t(6540);let o={},r=n.createContext(o);function i(e){let s=n.useContext(r);return n.useMemo(function(){return"function"==typeof e?e(s):{...s,...e}},[s,e])}function a(e){let s;return s=e.disableParentContext?"function"==typeof e.components?e.components(o):e.components||o:i(e.components),n.createElement(r.Provider,{value:s},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/f494eaf6.0d483337.js b/assets/js/f494eaf6.0d483337.js new file mode 100644 index 00000000..5eda0853 --- /dev/null +++ b/assets/js/f494eaf6.0d483337.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunklogos_docs=self.webpackChunklogos_docs||[]).push([["6153"],{6291(e,r,n){n.r(r),n.d(r,{metadata:()=>o,default:()=>u,frontMatter:()=>a,contentTitle:()=>l,toc:()=>s,assets:()=>d});var o=JSON.parse('{"id":"core/reference/lgx-package-format-and-bundling-reference","title":"LGX package format and bundling reference","description":"We\'re working on the content.","source":"@site/docs/core/reference/lgx-package-format-and-bundling-reference.md","sourceDirName":"core/reference","slug":"/core/reference/lgx-package-format-and-bundling-reference","permalink":"/core/reference/lgx-package-format-and-bundling-reference","draft":false,"unlisted":false,"editUrl":"https://github.com/logos-co/logos-docs/tree/main/docs/core/reference/lgx-package-format-and-bundling-reference.md","tags":[],"version":"current","frontMatter":{"title":"LGX package format and bundling reference","doc_type":"reference","product":"core","topics":"core","authors":null,"owner":"logos","doc_version":1,"slug":"lgx-package-format-and-bundling-reference"},"sidebar":"coreSidebar","previous":{"title":"Build a Logos Core module that uses the Service Discovery API","permalink":"/core/build-modules/build-logos-core-module-that-uses-service-discovery-api"},"next":{"title":"Logos CLI Reference","permalink":"/core/reference/logos-cli-reference"}}'),t=n(4848),c=n(8453);let a={title:"LGX package format and bundling reference",doc_type:"reference",product:"core",topics:"core",authors:null,owner:"logos",doc_version:1,slug:"lgx-package-format-and-bundling-reference"},l="LGX package format and bundling reference",d={},s=[];function i(e){let r={h1:"h1",header:"header",p:"p",...(0,c.R)(),...e.components};return(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)(r.header,{children:(0,t.jsx)(r.h1,{id:"lgx-package-format-and-bundling-reference",children:"LGX package format and bundling reference"})}),"\n",(0,t.jsx)(r.p,{children:"We're working on the content."})]})}function u(e={}){let{wrapper:r}={...(0,c.R)(),...e.components};return r?(0,t.jsx)(r,{...e,children:(0,t.jsx)(i,{...e})}):i(e)}},8453(e,r,n){n.d(r,{R:()=>a,x:()=>l});var o=n(6540);let t={},c=o.createContext(t);function a(e){let r=o.useContext(c);return o.useMemo(function(){return"function"==typeof e?e(r):{...r,...e}},[r,e])}function l(e){let r;return r=e.disableParentContext?"function"==typeof e.components?e.components(t):e.components||t:a(e.components),o.createElement(c.Provider,{value:r},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/f911e5e7.c42eeb8a.js b/assets/js/f911e5e7.c42eeb8a.js new file mode 100644 index 00000000..463d99ad --- /dev/null +++ b/assets/js/f911e5e7.c42eeb8a.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunklogos_docs=self.webpackChunklogos_docs||[]).push([["5397"],{5530(e){e.exports=JSON.parse('{"categoryGeneratedIndex":{"title":"Run an app","description":"Run modules inside Basecamp, through individual UI apps, or headlessly.","slug":"/run-an-app","permalink":"/run-an-app","sidebar":"runAnAppSidebar","navigation":{}}}')}}]); \ No newline at end of file diff --git a/assets/js/fdf0ae8c.87346358.js b/assets/js/fdf0ae8c.87346358.js new file mode 100644 index 00000000..d2aa9058 --- /dev/null +++ b/assets/js/fdf0ae8c.87346358.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunklogos_docs=self.webpackChunklogos_docs||[]).push([["2307"],{6649(e,l,n){n.r(l),n.d(l,{metadata:()=>o,default:()=>h,frontMatter:()=>i,contentTitle:()=>r,toc:()=>a,assets:()=>c});var o=JSON.parse('{"id":"core/build-modules/build-and-run-a-logos-core-module","title":"Build and run a Logos core module","description":"Scaffold, build, package, and test a core module on Logos.","source":"@site/docs/core/build-modules/build-and-run-a-logos-core-module.md","sourceDirName":"core/build-modules","slug":"/core/build-modules/build-and-run-a-logos-core-module","permalink":"/core/build-modules/build-and-run-a-logos-core-module","draft":false,"unlisted":false,"editUrl":"https://github.com/logos-co/logos-docs/tree/main/docs/core/build-modules/build-and-run-a-logos-core-module.md","tags":[],"version":"current","sidebarPosition":2,"frontMatter":{"title":"Build and run a Logos core module","doc_type":"procedure","product":"core","topics":"core","steps_layout":"sectioned","authors":"iurimatias, Khushboo-dev-cpp, cheny0","owner":"logos","doc_version":1,"slug":"build-and-run-a-logos-core-module","sidebar_position":2},"sidebar":"coreSidebar","previous":{"title":"Start a Logos module from the CLI","permalink":"/core/build-modules/start-a-logos-module-from-the-cli"},"next":{"title":"Wrap a C library as a Logos module","permalink":"/core/build-modules/wrap-a-c-library-as-a-logos-core-module"}}'),s=n(4848),d=n(8453);let i={title:"Build and run a Logos core module",doc_type:"procedure",product:"core",topics:"core",steps_layout:"sectioned",authors:"iurimatias, Khushboo-dev-cpp, cheny0",owner:"logos",doc_version:1,slug:"build-and-run-a-logos-core-module",sidebar_position:2},r="Build and run a Logos core module",c={},a=[{value:"Scaffold, build, package, and test a core module on Logos.",id:"scaffold-build-package-and-test-a-core-module-on-logos",level:4},{value:"What to expect",id:"what-to-expect",level:2},{value:"Step 1: Scaffold the module project",id:"step-1-scaffold-the-module-project",level:2},{value:"Step 2: Adapt the template for your module",id:"step-2-adapt-the-template-for-your-module",level:2},{value:"Step 3: Build the module",id:"step-3-build-the-module",level:2},{value:"Step 4: Inspect your module",id:"step-4-inspect-your-module",level:2},{value:"Inspect with the CLI tool",id:"inspect-with-the-cli-tool",level:3},{value:"Inspect with the graphical tool",id:"inspect-with-the-graphical-tool",level:3},{value:"Step 5: Package the module",id:"step-5-package-the-module",level:2},{value:"Use the Nix derivation",id:"use-the-nix-derivation",level:3},{value:"Use the <code>nix bundle</code> command",id:"use-the-nix-bundle-command",level:3},{value:"Step 6: Install the module",id:"step-6-install-the-module",level:2},{value:"Install a locally built <code>.lgx</code> package",id:"install-a-locally-built-lgx-package",level:3},{value:"Download and install a <code>.lgx</code> file from a registry",id:"download-and-install-a-lgx-file-from-a-registry",level:3},{value:"Step 7: Run the module",id:"step-7-run-the-module",level:2},{value:"Run with <code>logoscore</code>",id:"run-with-logoscore",level:3},{value:"Run with <code>logos-basecamp</code>",id:"run-with-logos-basecamp",level:3},{value:"Troubleshooting",id:"troubleshooting",level:2},{value:"Known constraints",id:"known-constraints",level:3},{value:"Nix reports an "experimental features" error",id:"nix-reports-an-experimental-features-error",level:3},{value:"Module not discovered by <code>logos-basecamp</code>",id:"module-not-discovered-by-logos-basecamp",level:3},{value:"Module not discovered by <code>logoscore</code>",id:"module-not-discovered-by-logoscore",level:3},{value:"<code>lgpm</code> fails to install a module",id:"lgpm-fails-to-install-a-module",level:3},{value:"LGX variant mismatch",id:"lgx-variant-mismatch",level:3},{value:"<code>nix build .#lib</code> does nothing or fails silently",id:"nix-build-lib-does-nothing-or-fails-silently",level:3},{value:"First build is slow",id:"first-build-is-slow",level:3}];function t(e){let l={a:"a",admonition:"admonition",code:"code",h1:"h1",h2:"h2",h3:"h3",h4:"h4",header:"header",li:"li",ol:"ol",p:"p",pre:"pre",ul:"ul",...(0,d.R)(),...e.components};return(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(l.header,{children:(0,s.jsx)(l.h1,{id:"build-and-run-a-logos-core-module",children:"Build and run a Logos core module"})}),"\n",(0,s.jsx)(l.h4,{id:"scaffold-build-package-and-test-a-core-module-on-logos",children:"Scaffold, build, package, and test a core module on Logos."}),"\n",(0,s.jsx)(l.p,{children:"Logos is a modular application framework built on Qt 6. Applications are composed of dynamically loaded modules (Qt plugins) that provide features and functionality."}),"\n",(0,s.jsxs)(l.p,{children:["Logos ",(0,s.jsx)(l.a,{href:"https://docs.logos.co/get-started/glossary#core-module",children:"core modules"})," are non-UI modules that provide backend functionality. Core modules run in isolated ",(0,s.jsx)(l.code,{children:"logos_host"})," processes and communicate via Qt Remote Objects."]}),"\n",(0,s.jsx)(l.admonition,{type:"info",children:(0,s.jsxs)(l.p,{children:["For other ",(0,s.jsx)(l.a,{href:"https://docs.logos.co/get-started/glossary#module",children:"module"})," types, check out ",(0,s.jsx)(l.a,{href:"/core/build-modules/wrap-a-c-library-as-a-logos-core-module",children:"Wrap a C Library as a Logos core module"})," and ",(0,s.jsx)(l.a,{href:"/core/build-modules/build-a-logos-cpp-ui-module",children:"Build a Logos C++ UI module"}),". These guides \u2014 along with the ",(0,s.jsx)(l.a,{href:"/core/reference/lgx-package-format-and-bundling-reference",children:"LGX package format and bundling reference"})," and the ",(0,s.jsx)(l.a,{href:"/core/reference/logos-cli-reference",children:"Logos CLI Reference"})," \u2014 are still being written; the linked pages are placeholders for now."]})}),"\n",(0,s.jsx)(l.p,{children:"Before you start, make sure you have the following:"}),"\n",(0,s.jsxs)(l.ul,{children:["\n",(0,s.jsxs)(l.li,{children:["\n",(0,s.jsx)(l.p,{children:"Linux (x86_64 or aarch64) or macOS (arm64 or x86_64)"}),"\n"]}),"\n",(0,s.jsxs)(l.li,{children:["\n",(0,s.jsx)(l.p,{children:"At least 10 GB of disk space"}),"\n"]}),"\n",(0,s.jsxs)(l.li,{children:["\n",(0,s.jsxs)(l.p,{children:[(0,s.jsx)(l.a,{href:"https://nixos.org/download.html",children:"Nix"})," with flakes enabled"]}),"\n"]}),"\n",(0,s.jsxs)(l.li,{children:["\n",(0,s.jsx)(l.p,{children:"Git"}),"\n"]}),"\n",(0,s.jsxs)(l.li,{children:["\n",(0,s.jsxs)(l.p,{children:[(0,s.jsx)(l.a,{href:"https://github.com/logos-co/logos-logoscore-cli/releases/tag/0.2.0",children:(0,s.jsx)(l.code,{children:"logoscore"})}),", and ",(0,s.jsx)(l.a,{href:"https://github.com/logos-co/logos-package-manager/releases/tag/0.2.0",children:(0,s.jsx)(l.code,{children:"lgpm"})})," installed. To install these tools, use the ",(0,s.jsx)(l.code,{children:"install-node-tools.sh"})," helper script:"]}),"\n",(0,s.jsx)(l.pre,{children:(0,s.jsx)(l.code,{className:"language-bash",children:'curl -fsSL https://raw.githubusercontent.com/logos-co/logos-docs/main/resources/scripts/install-node-tools.sh | sh\nexport PATH="$PWD/bin:$PATH"\n'})}),"\n"]}),"\n",(0,s.jsxs)(l.li,{children:["\n",(0,s.jsxs)(l.p,{children:["Basic familiarity with C++ (C++17), Qt 6 (",(0,s.jsx)(l.code,{children:"QObject"}),", ",(0,s.jsx)(l.code,{children:"Q_INVOKABLE"}),", signals/slots), CMake, and Nix concepts"]}),"\n"]}),"\n"]}),"\n",(0,s.jsx)(l.h2,{id:"what-to-expect",children:"What to expect"}),"\n",(0,s.jsxs)(l.ul,{children:["\n",(0,s.jsxs)(l.li,{children:["You can scaffold, configure, and build a Logos core module using the templates provided by ",(0,s.jsx)(l.code,{children:"logos-module-builder"}),"."]}),"\n",(0,s.jsxs)(l.li,{children:["You can inspect the compiled module's metadata and methods using the ",(0,s.jsx)(l.code,{children:"lm"})," CLI tool or the ",(0,s.jsx)(l.code,{children:"logos-module-viewer"})," graphical tool."]}),"\n",(0,s.jsxs)(l.li,{children:["You can package, install, and call your module's methods through ",(0,s.jsx)(l.code,{children:"logoscore"})," or load it into ",(0,s.jsx)(l.code,{children:"logos-basecamp"}),"."]}),"\n"]}),"\n",(0,s.jsx)(l.h2,{id:"step-1-scaffold-the-module-project",children:"Step 1: Scaffold the module project"}),"\n",(0,s.jsxs)(l.p,{children:["The ",(0,s.jsx)(l.code,{children:"logos-module-builder"})," provides four scaffolding templates for different module types. To create a Logos core module, choose the ",(0,s.jsx)(l.code,{children:"default"})," template that produces a minimal core module with a C++ backend and no UI."]}),"\n",(0,s.jsxs)(l.ol,{children:["\n",(0,s.jsxs)(l.li,{children:["\n",(0,s.jsxs)(l.p,{children:["Create a new directory using your module's name and initialise it from the module builder template. Replace ",(0,s.jsx)(l.code,{children:"<module-name>"})," with your module's name, for example, ",(0,s.jsx)(l.code,{children:"my_module"}),"."]}),"\n",(0,s.jsx)(l.pre,{children:(0,s.jsx)(l.code,{className:"language-bash",children:"mkdir <module-name> && cd <module-name>\nnix flake init -t github:logos-co/logos-module-builder/tutorial-v1\n"})}),"\n"]}),"\n",(0,s.jsxs)(l.li,{children:["\n",(0,s.jsx)(l.p,{children:"Review the project directory. The generated project structure looks like this:"}),"\n",(0,s.jsx)(l.pre,{children:(0,s.jsx)(l.code,{children:"<module-name>/\n\u251C\u2500\u2500 flake.nix\n\u251C\u2500\u2500 metadata.json\n\u251C\u2500\u2500 CMakeLists.txt\n\u2514\u2500\u2500 src/\n \u251C\u2500\u2500 minimal_interface.h\n \u251C\u2500\u2500 minimal_plugin.h\n \u2514\u2500\u2500 minimal_plugin.cpp\n"})}),"\n",(0,s.jsxs)(l.p,{children:["The template uses ",(0,s.jsx)(l.code,{children:"minimal"})," as a placeholder in the source filenames, class names, and identifiers. You replace these placeholders with your module's name in Step 2."]}),"\n",(0,s.jsx)(l.admonition,{type:"info"}),"\n"]}),"\n"]}),"\n",(0,s.jsxs)(l.p,{children:["The ",(0,s.jsx)(l.code,{children:"metadata.json"})," file is the single source of truth for your module. Read ",(0,s.jsx)(l.a,{href:"/core/reference/lgx-package-format-and-bundling-reference",children:"LGX package format and bundling reference"})," for more details.\n:::"]}),"\n",(0,s.jsx)(l.h2,{id:"step-2-adapt-the-template-for-your-module",children:"Step 2: Adapt the template for your module"}),"\n",(0,s.jsxs)(l.p,{children:["The template generates files with placeholder names like ",(0,s.jsx)(l.code,{children:"minimal"}),"/",(0,s.jsx)(l.code,{children:"Minimal"})," and ",(0,s.jsx)(l.code,{children:"doSomething"}),". Replace these in every generated file to match your module's name and methods."]}),"\n",(0,s.jsxs)(l.ol,{children:["\n",(0,s.jsxs)(l.li,{children:["\n",(0,s.jsxs)(l.p,{children:["Edit ",(0,s.jsx)(l.code,{children:"metadata.json"})," and set ",(0,s.jsx)(l.code,{children:"name"}),", ",(0,s.jsx)(l.code,{children:"version"}),", ",(0,s.jsx)(l.code,{children:"description"}),", and ",(0,s.jsx)(l.code,{children:"main"})," to match your module."]}),"\n",(0,s.jsxs)(l.ul,{children:["\n",(0,s.jsxs)(l.li,{children:[(0,s.jsx)(l.code,{children:"name"})," must be a valid C identifier; it is used in filenames, method calls, and module loading."]}),"\n",(0,s.jsxs)(l.li,{children:[(0,s.jsx)(l.code,{children:"main"})," must match the plugin filename without the extension (for example, ",(0,s.jsx)(l.code,{children:"my_module_plugin"})," resolves to ",(0,s.jsx)(l.code,{children:"my_module_plugin.so"})," or ",(0,s.jsx)(l.code,{children:".dylib"}),")."]}),"\n",(0,s.jsxs)(l.li,{children:["Leave the other fields (",(0,s.jsx)(l.code,{children:"type"}),", ",(0,s.jsx)(l.code,{children:"category"}),", ",(0,s.jsx)(l.code,{children:"dependencies"}),", and the ",(0,s.jsx)(l.code,{children:"nix"})," block with ",(0,s.jsx)(l.code,{children:"packages"}),", ",(0,s.jsx)(l.code,{children:"external_libraries"}),", ",(0,s.jsx)(l.code,{children:"cmake.find_packages"}),") in place. The bundler relies on the ",(0,s.jsx)(l.code,{children:"nix"})," block to build the LGX package."]}),"\n"]}),"\n"]}),"\n",(0,s.jsxs)(l.li,{children:["\n",(0,s.jsxs)(l.p,{children:["Edit ",(0,s.jsx)(l.code,{children:"CMakeLists.txt"})," and update the ",(0,s.jsx)(l.code,{children:"project()"})," name and the ",(0,s.jsx)(l.code,{children:"NAME"})," and ",(0,s.jsx)(l.code,{children:"SOURCES"})," values inside the ",(0,s.jsx)(l.code,{children:"logos_module()"})," call."]}),"\n",(0,s.jsxs)(l.ul,{children:["\n",(0,s.jsxs)(l.li,{children:["The ",(0,s.jsx)(l.code,{children:"CMakeLists.txt"})," uses the ",(0,s.jsx)(l.code,{children:"logos_module()"})," macro to handle Qt plugin setup."]}),"\n",(0,s.jsxs)(l.li,{children:[(0,s.jsx)(l.code,{children:"NAME"})," must match the ",(0,s.jsx)(l.code,{children:"name"})," field in ",(0,s.jsx)(l.code,{children:"metadata.json"}),". A mismatch causes the build to succeed but the install phase to fail."]}),"\n"]}),"\n"]}),"\n",(0,s.jsxs)(l.li,{children:["\n",(0,s.jsxs)(l.p,{children:["Edit ",(0,s.jsx)(l.code,{children:"flake.nix"})," and update the ",(0,s.jsx)(l.code,{children:"description"})," field."]}),"\n",(0,s.jsxs)(l.ul,{children:["\n",(0,s.jsxs)(l.li,{children:["The generated ",(0,s.jsx)(l.code,{children:"flake.nix"})," uses an unpinned ",(0,s.jsx)(l.code,{children:"logos-module-builder"})," URL. For reproducible builds, pin it to ",(0,s.jsx)(l.code,{children:"tutorial-v1"}),"."]}),"\n"]}),"\n"]}),"\n",(0,s.jsxs)(l.li,{children:["\n",(0,s.jsxs)(l.p,{children:["Rename the source files in ",(0,s.jsx)(l.code,{children:"src/"})," to match your module name."]}),"\n",(0,s.jsx)(l.pre,{children:(0,s.jsx)(l.code,{className:"language-bash",children:"mv src/minimal_interface.h src/<module-name>_interface.h\nmv src/minimal_plugin.h src/<module-name>_plugin.h\nmv src/minimal_plugin.cpp src/<module-name>_plugin.cpp\n"})}),"\n"]}),"\n",(0,s.jsxs)(l.li,{children:["\n",(0,s.jsxs)(l.p,{children:["Edit the interface header (",(0,s.jsx)(l.code,{children:"src/<module-name>_interface.h"}),") and replace the class name, include guard, and interface ID string."]}),"\n",(0,s.jsxs)(l.ul,{children:["\n",(0,s.jsxs)(l.li,{children:["Declare each method your module exposes as ",(0,s.jsx)(l.code,{children:"Q_INVOKABLE virtual"})," and pure-virtual."]}),"\n",(0,s.jsxs)(l.li,{children:["The interface ID (for example, ",(0,s.jsx)(l.code,{children:'"org.logos.MyModuleInterface"'}),") must be unique across all modules."]}),"\n"]}),"\n"]}),"\n",(0,s.jsxs)(l.li,{children:["\n",(0,s.jsxs)(l.p,{children:["Edit the plugin header (",(0,s.jsx)(l.code,{children:"src/<module-name>_plugin.h"}),") and replace the class name, include guard, and interface references."]}),"\n",(0,s.jsxs)(l.ul,{children:["\n",(0,s.jsxs)(l.li,{children:[(0,s.jsx)(l.code,{children:'Q_PLUGIN_METADATA(IID ... FILE "metadata.json")'})," embeds the metadata into the binary."]}),"\n",(0,s.jsxs)(l.li,{children:[(0,s.jsx)(l.code,{children:"Q_INTERFACES"})," must list both your interface and ",(0,s.jsx)(l.code,{children:"PluginInterface"}),"."]}),"\n",(0,s.jsxs)(l.li,{children:[(0,s.jsx)(l.code,{children:"name()"})," must return the same string as the ",(0,s.jsx)(l.code,{children:"name"})," field in ",(0,s.jsx)(l.code,{children:"metadata.json"}),"."]}),"\n",(0,s.jsxs)(l.li,{children:["Declare ",(0,s.jsx)(l.code,{children:"initLogos(LogosAPI* api)"})," as ",(0,s.jsx)(l.code,{children:"Q_INVOKABLE"})," but not ",(0,s.jsx)(l.code,{children:"override"}),"."]}),"\n"]}),"\n"]}),"\n",(0,s.jsxs)(l.li,{children:["\n",(0,s.jsxs)(l.p,{children:["Edit the plugin implementation (",(0,s.jsx)(l.code,{children:"src/<module-name>_plugin.cpp"}),") and replace the placeholder method bodies with your logic."]}),"\n",(0,s.jsxs)(l.ul,{children:["\n",(0,s.jsxs)(l.li,{children:["In ",(0,s.jsx)(l.code,{children:"initLogos"}),", assign the ",(0,s.jsx)(l.code,{children:"LogosAPI*"})," pointer to the global ",(0,s.jsx)(l.code,{children:"logosAPI"})," variable, not to a class member."]}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,s.jsx)(l.admonition,{type:"tip",children:(0,s.jsxs)(l.p,{children:["Run ",(0,s.jsx)(l.code,{children:'grep -ri "minimal" .'})," after editing to catch any remaining placeholder references (",(0,s.jsx)(l.code,{children:"minimal"}),", ",(0,s.jsx)(l.code,{children:"Minimal"}),", ",(0,s.jsx)(l.code,{children:"MINIMAL_*"}),", ",(0,s.jsx)(l.code,{children:"MinimalInterface_iid"}),") before building."]})}),"\n",(0,s.jsx)(l.h2,{id:"step-3-build-the-module",children:"Step 3: Build the module"}),"\n",(0,s.jsxs)(l.ol,{children:["\n",(0,s.jsxs)(l.li,{children:["\n",(0,s.jsx)(l.p,{children:"Initialise a Git repository."}),"\n",(0,s.jsx)(l.pre,{children:(0,s.jsx)(l.code,{className:"language-bash",children:"git init && git add -A\n"})}),"\n"]}),"\n",(0,s.jsxs)(l.li,{children:["\n",(0,s.jsx)(l.p,{children:"Build the full module output (library and generated SDK headers)."}),"\n",(0,s.jsx)(l.pre,{children:(0,s.jsx)(l.code,{className:"language-bash",children:"nix build\n"})}),"\n",(0,s.jsxs)(l.ul,{children:["\n",(0,s.jsxs)(l.li,{children:["Use ",(0,s.jsx)(l.code,{children:"nix build '.#lib'"})," to build only the plugin shared library."]}),"\n",(0,s.jsxs)(l.li,{children:["Use ",(0,s.jsx)(l.code,{children:"nix build '.#include'"})," to build only the generated SDK headers."]}),"\n"]}),"\n"]}),"\n",(0,s.jsxs)(l.li,{children:["\n",(0,s.jsx)(l.p,{children:"Verify the build output contains the plugin binary and generated headers:"}),"\n",(0,s.jsx)(l.pre,{children:(0,s.jsx)(l.code,{className:"language-text",children:"result/\n\u251C\u2500\u2500 lib/\n\u2502 \u2514\u2500\u2500 <module-name>_plugin.so # (or .dylib on macOS)\n\u2514\u2500\u2500 include/\n \u251C\u2500\u2500 <module-name>_api.h # Generated type-safe wrapper header\n \u2514\u2500\u2500 <module-name>_api.cpp # Generated wrapper implementation\n"})}),"\n"]}),"\n"]}),"\n",(0,s.jsx)(l.h2,{id:"step-4-inspect-your-module",children:"Step 4: Inspect your module"}),"\n",(0,s.jsxs)(l.p,{children:["You can inspect the compiled module binary to verify the embedded metadata and exposed methods using the ",(0,s.jsx)(l.code,{children:"lm"})," CLI tool or the ",(0,s.jsx)(l.code,{children:"logos-module-viewer"})," graphical tool."]}),"\n",(0,s.jsx)(l.h3,{id:"inspect-with-the-cli-tool",children:"Inspect with the CLI tool"}),"\n",(0,s.jsxs)(l.p,{children:["The ",(0,s.jsx)(l.code,{children:"lm"})," tool (from ",(0,s.jsx)(l.code,{children:"logos-module"}),") lets you inspect compiled module binaries without loading them into the full runtime. It reads metadata and enumerates methods via Qt's meta-object system."]}),"\n",(0,s.jsxs)(l.ol,{children:["\n",(0,s.jsxs)(l.li,{children:["\n",(0,s.jsxs)(l.p,{children:["Build the ",(0,s.jsx)(l.code,{children:"lm"})," tool from the ",(0,s.jsx)(l.code,{children:"logos-module"})," repository."]}),"\n",(0,s.jsx)(l.pre,{children:(0,s.jsx)(l.code,{className:"language-bash",children:"nix build 'github:logos-co/logos-module/tutorial-v1#lm' --out-link ./lm\n"})}),"\n"]}),"\n",(0,s.jsxs)(l.li,{children:["\n",(0,s.jsx)(l.p,{children:"View the module metadata and confirm the information is correct."}),"\n",(0,s.jsx)(l.pre,{children:(0,s.jsx)(l.code,{className:"language-bash",children:"./lm/bin/lm metadata result/lib/<module-name>_plugin.so\n"})}),"\n",(0,s.jsxs)(l.ul,{children:["\n",(0,s.jsxs)(l.li,{children:["Append ",(0,s.jsx)(l.code,{children:"--json"})," for JSON output."]}),"\n"]}),"\n",(0,s.jsx)(l.p,{children:"The JSON output looks like this:"}),"\n",(0,s.jsx)(l.pre,{children:(0,s.jsx)(l.code,{className:"language-json",children:'{\n "name": "my_module",\n "version": "1.0.0",\n "description": "My first Logos module",\n "author": "",\n "type": "core",\n "dependencies": []\n}\n'})}),"\n"]}),"\n",(0,s.jsxs)(l.li,{children:["\n",(0,s.jsx)(l.p,{children:"View the module methods."}),"\n",(0,s.jsx)(l.pre,{children:(0,s.jsx)(l.code,{className:"language-bash",children:"./lm/bin/lm methods result/lib/<module-name>_plugin.so\n"})}),"\n",(0,s.jsxs)(l.ul,{children:["\n",(0,s.jsxs)(l.li,{children:["Append ",(0,s.jsx)(l.code,{children:"--json"})," for JSON output."]}),"\n"]}),"\n",(0,s.jsx)(l.p,{children:"The JSON output looks like this:"}),"\n",(0,s.jsx)(l.pre,{children:(0,s.jsx)(l.code,{className:"language-json",children:'[\n {\n "name": "initLogos",\n "signature": "initLogos(LogosAPI*)",\n "returnType": "void",\n "isInvokable": true,\n "parameters": [\n { "name": "logosAPIInstance", "type": "LogosAPI*" }\n ]\n },\n {\n "name": "doSomething",\n "signature": "doSomething(QString)",\n "returnType": "QString",\n "isInvokable": true,\n "parameters": [\n { "name": "input", "type": "QString" }\n ]\n }\n]\n'})}),"\n",(0,s.jsxs)(l.p,{children:["The output also includes any Qt signals declared in the module (for example, ",(0,s.jsx)(l.code,{children:"eventResponse"}),") with ",(0,s.jsx)(l.code,{children:"isInvokable: false"}),"."]}),"\n"]}),"\n"]}),"\n",(0,s.jsx)(l.h3,{id:"inspect-with-the-graphical-tool",children:"Inspect with the graphical tool"}),"\n",(0,s.jsxs)(l.p,{children:["The ",(0,s.jsx)(l.code,{children:"logos-module-viewer"})," is a graphical tool for inspecting loaded modules. It displays the module's metadata and methods in a graphical interface and lets you call methods interactively."]}),"\n",(0,s.jsxs)(l.ol,{children:["\n",(0,s.jsxs)(l.li,{children:["\n",(0,s.jsx)(l.p,{children:"Build the viewer."}),"\n",(0,s.jsx)(l.pre,{children:(0,s.jsx)(l.code,{className:"language-bash",children:"nix build 'github:logos-co/logos-module-viewer/tutorial-v1#app' --out-link ./logos-viewer\n"})}),"\n"]}),"\n",(0,s.jsxs)(l.li,{children:["\n",(0,s.jsx)(l.p,{children:"Launch the viewer with the module binary."}),"\n",(0,s.jsx)(l.pre,{children:(0,s.jsx)(l.code,{className:"language-bash",children:"./logos-viewer/bin/logos-module-viewer -m ./result/lib/<module-name>_plugin.so\n"})}),"\n"]}),"\n"]}),"\n",(0,s.jsx)(l.h2,{id:"step-5-package-the-module",children:"Step 5: Package the module"}),"\n",(0,s.jsxs)(l.p,{children:["Before you can run your module with ",(0,s.jsx)(l.code,{children:"logoscore"})," or install it into ",(0,s.jsx)(l.code,{children:"logos-basecamp"}),", you need to package the build output into an ",(0,s.jsx)(l.code,{children:".lgx"})," package and install it into a ",(0,s.jsx)(l.code,{children:"modules/"})," directory. Check out the ",(0,s.jsx)(l.a,{href:"/core/reference/lgx-package-format-and-bundling-reference",children:"LGX package format and bundling reference"})," for more details on the format and bundling options."]}),"\n",(0,s.jsx)(l.admonition,{type:"info",children:(0,s.jsxs)(l.p,{children:["The ",(0,s.jsx)(l.code,{children:"manifest.json"})," is auto-generated from your module's ",(0,s.jsx)(l.code,{children:"metadata.json"})," by the bundler. It maps each variant to its main entry point."]})}),"\n",(0,s.jsxs)(l.p,{children:["There are two ways to create ",(0,s.jsx)(l.code,{children:".lgx"})," packages:"]}),"\n",(0,s.jsxs)(l.ul,{children:["\n",(0,s.jsxs)(l.li,{children:["Use the built-in Nix derivation that comes with ",(0,s.jsx)(l.code,{children:"logos-module-builder"})," (preferred)."]}),"\n",(0,s.jsxs)(l.li,{children:["Use the ",(0,s.jsx)(l.code,{children:"nix bundle"})," command directly."]}),"\n"]}),"\n",(0,s.jsx)(l.h3,{id:"use-the-nix-derivation",children:"Use the Nix derivation"}),"\n",(0,s.jsxs)(l.p,{children:["When your module uses ",(0,s.jsx)(l.code,{children:"logos-module-builder"}),", LGX package outputs are automatically available as part of your flake (the builder includes ",(0,s.jsx)(l.code,{children:"nix-bundle-lgx"})," internally)."]}),"\n",(0,s.jsxs)(l.ol,{children:["\n",(0,s.jsxs)(l.li,{children:["\n",(0,s.jsxs)(l.p,{children:["Bundle the module into an LGX package that uses ",(0,s.jsx)(l.code,{children:"/nix/store"})," references for local development."]}),"\n",(0,s.jsx)(l.pre,{children:(0,s.jsx)(l.code,{className:"language-bash",children:"nix build .#lgx\n"})}),"\n",(0,s.jsxs)(l.ul,{children:["\n",(0,s.jsxs)(l.li,{children:["Use ",(0,s.jsx)(l.code,{children:"#lgx-portable"})," for a self-contained, all dependencies bundled package: ",(0,s.jsx)(l.code,{children:"nix build .#lgx-portable"}),"."]}),"\n"]}),"\n"]}),"\n",(0,s.jsxs)(l.li,{children:["\n",(0,s.jsxs)(l.p,{children:["Check the ",(0,s.jsx)(l.code,{children:"result/"})," directory and confirm the ",(0,s.jsx)(l.code,{children:"logos-<module-name>-module-lib.lgx"})," file is present."]}),"\n",(0,s.jsx)(l.admonition,{type:"info"}),"\n"]}),"\n"]}),"\n",(0,s.jsxs)(l.p,{children:[(0,s.jsx)(l.code,{children:".#lgx"})," produces a single variant (for example, ",(0,s.jsx)(l.code,{children:"linux-amd64"}),") and ",(0,s.jsx)(l.code,{children:".#lgx-portable"})," produces a single portable variant. Neither produces the ",(0,s.jsx)(l.code,{children:"-dev"})," variant that ",(0,s.jsx)(l.code,{children:"logos-basecamp"})," dev builds expect. If you need the dev variant for use with ",(0,s.jsx)(l.code,{children:"logos-basecamp"}),", use the ",(0,s.jsx)(l.code,{children:"#dual"})," bundler described in the next section.\n:::"]}),"\n",(0,s.jsxs)(l.h3,{id:"use-the-nix-bundle-command",children:["Use the ",(0,s.jsx)(l.code,{children:"nix bundle"})," command"]}),"\n",(0,s.jsxs)(l.p,{children:["The ",(0,s.jsx)(l.code,{children:"nix bundle"})," command is useful if your module does not use ",(0,s.jsx)(l.code,{children:"logos-module-builder"}),", or if you need the ",(0,s.jsx)(l.code,{children:"dual"})," bundling mode (both ",(0,s.jsx)(l.code,{children:"dev"})," and ",(0,s.jsx)(l.code,{children:"portable"})," in a single ",(0,s.jsx)(l.code,{children:".lgx"})," file) which is only available via the ",(0,s.jsx)(l.code,{children:"nix bundle"})," command."]}),"\n",(0,s.jsxs)(l.ol,{children:["\n",(0,s.jsxs)(l.li,{children:["\n",(0,s.jsxs)(l.p,{children:["Bundle the module into an LGX package using the ",(0,s.jsx)(l.code,{children:"nix bundle"})," command."]}),"\n",(0,s.jsx)(l.pre,{children:(0,s.jsx)(l.code,{className:"language-bash",children:"nix bundle --bundler github:logos-co/nix-bundle-lgx/tutorial-v3 .#lib\n"})}),"\n",(0,s.jsxs)(l.ul,{children:["\n",(0,s.jsxs)(l.li,{children:["Use ",(0,s.jsx)(l.code,{children:"#portable"})," for a self-contained package with no ",(0,s.jsx)(l.code,{children:"/nix/store"})," references: ",(0,s.jsx)(l.code,{children:"nix bundle --bundler github:logos-co/nix-bundle-lgx/tutorial-v3#portable .#lib"}),"."]}),"\n",(0,s.jsxs)(l.li,{children:["Use ",(0,s.jsx)(l.code,{children:"#dual"})," to produce both ",(0,s.jsx)(l.code,{children:"-dev"})," and portable variants in a single ",(0,s.jsx)(l.code,{children:".lgx"})," file: ",(0,s.jsx)(l.code,{children:"nix bundle --bundler github:logos-co/nix-bundle-lgx/tutorial-v3#dual .#lib"}),". Use this mode when you need to install the module into a dev build of ",(0,s.jsx)(l.code,{children:"logos-basecamp"}),"."]}),"\n"]}),"\n"]}),"\n",(0,s.jsxs)(l.li,{children:["\n",(0,s.jsxs)(l.p,{children:["Check the current directory for the bundle output. ",(0,s.jsx)(l.code,{children:"nix bundle"})," creates a symlink directory in the current directory named ",(0,s.jsx)(l.code,{children:"./logos-<module-name>-module-lib-lgx-<version>/"}),", and the ",(0,s.jsx)(l.code,{children:".lgx"})," file is inside it at ",(0,s.jsx)(l.code,{children:"./logos-<module-name>-module-lib-lgx-<version>/logos-<module-name>-module-lib.lgx"}),"."]}),"\n"]}),"\n"]}),"\n",(0,s.jsx)(l.admonition,{type:"tip",children:(0,s.jsxs)(l.p,{children:["Check out ",(0,s.jsx)(l.a,{href:"/core/reference/lgx-package-format-and-bundling-reference",children:"LGX package format and bundling reference"})," for more details on the format and bundling options."]})}),"\n",(0,s.jsx)(l.h2,{id:"step-6-install-the-module",children:"Step 6: Install the module"}),"\n",(0,s.jsxs)(l.p,{children:["Before you can run your module with ",(0,s.jsx)(l.code,{children:"logoscore"})," or ",(0,s.jsx)(l.code,{children:"logos-basecamp"}),", install the LGX package into a ",(0,s.jsx)(l.code,{children:"modules/"})," directory that the runtime can load from."]}),"\n",(0,s.jsxs)(l.p,{children:["There are two ways to install ",(0,s.jsx)(l.code,{children:".lgx"})," packages:"]}),"\n",(0,s.jsxs)(l.ul,{children:["\n",(0,s.jsxs)(l.li,{children:["Install a locally built ",(0,s.jsx)(l.code,{children:".lgx"})," package"]}),"\n",(0,s.jsxs)(l.li,{children:["Download and install a ",(0,s.jsx)(l.code,{children:".lgx"})," file from a registry"]}),"\n"]}),"\n",(0,s.jsxs)(l.h3,{id:"install-a-locally-built-lgx-package",children:["Install a locally built ",(0,s.jsx)(l.code,{children:".lgx"})," package"]}),"\n",(0,s.jsxs)(l.ol,{children:["\n",(0,s.jsxs)(l.li,{children:["\n",(0,s.jsxs)(l.p,{children:["Create the ",(0,s.jsx)(l.code,{children:"modules/"})," directory and install the ",(0,s.jsx)(l.code,{children:".lgx"})," package."]}),"\n",(0,s.jsx)(l.pre,{children:(0,s.jsx)(l.code,{className:"language-bash",children:"lgpm --modules-dir ./modules install --file result/logos-<module-name>-module-lib.lgx\n"})}),"\n",(0,s.jsxs)(l.ul,{children:["\n",(0,s.jsxs)(l.li,{children:["Use ",(0,s.jsx)(l.code,{children:"--dir"})," instead of ",(0,s.jsx)(l.code,{children:"--file"})," to install all LGX packages in a directory at once: ",(0,s.jsx)(l.code,{children:"./package-manager/bin/lgpm --modules-dir ./modules install --dir ./packages/"})]}),"\n",(0,s.jsxs)(l.li,{children:["If you bundled with ",(0,s.jsx)(l.code,{children:"nix bundle"}),", the path is ",(0,s.jsx)(l.code,{children:"./logos-<module-name>-module-lib-lgx-<version>/logos-<module-name>-module-lib.lgx"})," instead of ",(0,s.jsx)(l.code,{children:"result/..."}),"."]}),"\n"]}),"\n"]}),"\n",(0,s.jsxs)(l.li,{children:["\n",(0,s.jsxs)(l.p,{children:["Verify the installed module directory. The directory contains ",(0,s.jsx)(l.code,{children:"manifest.json"}),", the plugin binary (",(0,s.jsx)(l.code,{children:".so"})," or ",(0,s.jsx)(l.code,{children:".dylib"}),"), and a ",(0,s.jsx)(l.code,{children:"variant"})," file."]}),"\n"]}),"\n"]}),"\n",(0,s.jsxs)(l.h3,{id:"download-and-install-a-lgx-file-from-a-registry",children:["Download and install a ",(0,s.jsx)(l.code,{children:".lgx"})," file from a registry"]}),"\n",(0,s.jsxs)(l.p,{children:["The Logos module ",(0,s.jsx)(l.a,{href:"https://docs.logos.co/get-started/glossary#catalogue",children:"catalogue"})," is hosted on GitHub Releases in the ",(0,s.jsx)(l.a,{href:"https://github.com/logos-co/logos-modules",children:"logos-modules"})," repository. Use ",(0,s.jsx)(l.code,{children:"lgpd"})," to search and download packages, then ",(0,s.jsx)(l.code,{children:"lgpm"})," to install them locally."]}),"\n",(0,s.jsx)(l.admonition,{type:"warning",children:(0,s.jsxs)(l.p,{children:["Registry packages currently ship portable variants only (for example, ",(0,s.jsx)(l.code,{children:"linux-amd64"}),", ",(0,s.jsx)(l.code,{children:"darwin-arm64"}),"). They cannot be installed into a dev build of ",(0,s.jsx)(l.code,{children:"logos-basecamp"}),", which expects ",(0,s.jsx)(l.code,{children:"-dev"})," variants. To use a registry module with a dev build, you must build the module from source and bundle it with ",(0,s.jsx)(l.code,{children:"#dual"}),". They install cleanly into ",(0,s.jsx)(l.code,{children:"logoscore"})," and into portable builds of ",(0,s.jsx)(l.code,{children:"logos-basecamp"}),"."]})}),"\n",(0,s.jsxs)(l.ol,{children:["\n",(0,s.jsxs)(l.li,{children:["\n",(0,s.jsxs)(l.p,{children:["Search the catalogue for the module you want to install. Replace ",(0,s.jsx)(l.code,{children:"<registry-name>"})," with the registry name of the module you want to find (for example, ",(0,s.jsx)(l.code,{children:"logos-chat-module"}),")."]}),"\n",(0,s.jsx)(l.pre,{children:(0,s.jsx)(l.code,{className:"language-bash",children:"lgpd search <registry-name>\n"})}),"\n",(0,s.jsx)(l.admonition,{type:"tip"}),"\n"]}),"\n"]}),"\n",(0,s.jsxs)(l.p,{children:["Use ",(0,s.jsx)(l.code,{children:"lgpd list"})," to browse all available packages.\n:::"]}),"\n",(0,s.jsxs)(l.ol,{children:["\n",(0,s.jsxs)(l.li,{children:["\n",(0,s.jsx)(l.p,{children:"Download the LGX package to a local directory."}),"\n",(0,s.jsx)(l.pre,{children:(0,s.jsx)(l.code,{className:"language-bash",children:"lgpd download <registry-name> -o ./packages/\n"})}),"\n",(0,s.jsxs)(l.ul,{children:["\n",(0,s.jsxs)(l.li,{children:["Use ",(0,s.jsx)(l.code,{children:"--release <tag>"})," to download from a specific release version. For example: ",(0,s.jsx)(l.code,{children:"./downloader/bin/lgpd --release v2.0.0 download <registry-name> -o ./packages/"})]}),"\n",(0,s.jsxs)(l.li,{children:["The downloaded file is named after the module's internal ",(0,s.jsx)(l.code,{children:"name"})," field, not the registry name. For example, ",(0,s.jsx)(l.code,{children:"lgpd download logos-chat-module"})," writes ",(0,s.jsx)(l.code,{children:"./packages/chat_module.lgx"}),"."]}),"\n"]}),"\n"]}),"\n",(0,s.jsxs)(l.li,{children:["\n",(0,s.jsxs)(l.p,{children:["Create the ",(0,s.jsx)(l.code,{children:"modules/"})," directory and install the downloaded package. Replace ",(0,s.jsx)(l.code,{children:"<downloaded-name>"})," with the actual filename written by ",(0,s.jsx)(l.code,{children:"lgpd"})," (for example, ",(0,s.jsx)(l.code,{children:"chat_module.lgx"}),")."]}),"\n",(0,s.jsx)(l.pre,{children:(0,s.jsx)(l.code,{className:"language-bash",children:"lgpm --modules-dir ./modules install --file ./packages/<downloaded-name>.lgx\n"})}),"\n",(0,s.jsxs)(l.ul,{children:["\n",(0,s.jsxs)(l.li,{children:["Use ",(0,s.jsx)(l.code,{children:"--ui-plugins-dir"})," instead of ",(0,s.jsx)(l.code,{children:"--modules-dir"})," when installing ",(0,s.jsx)(l.a,{href:"https://docs.logos.co/get-started/glossary#ui-module",children:"UI modules"}),"."]}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,s.jsx)(l.h2,{id:"step-7-run-the-module",children:"Step 7: Run the module"}),"\n",(0,s.jsxs)(l.p,{children:["There are two Logos runtimes, ",(0,s.jsx)(l.code,{children:"logoscore"})," and ",(0,s.jsx)(l.code,{children:"logos-basecamp"}),", that can load and run your module. However, to interact with your module directly through the ",(0,s.jsx)(l.code,{children:"logos-basecamp"})," interface, you need to ",(0,s.jsx)(l.a,{href:"/core/build-modules/build-a-logos-cpp-ui-module",children:"provide a UI module"}),"."]}),"\n",(0,s.jsxs)(l.h3,{id:"run-with-logoscore",children:["Run with ",(0,s.jsx)(l.code,{children:"logoscore"})]}),"\n",(0,s.jsxs)(l.p,{children:["The ",(0,s.jsx)(l.code,{children:"logoscore"})," CLI (from ",(0,s.jsx)(l.code,{children:"logos-liblogos"}),") is a headless runtime that can load modules and invoke their methods from the command line. It runs as a daemon that stays alive to host modules."]}),"\n",(0,s.jsxs)(l.ol,{children:["\n",(0,s.jsxs)(l.li,{children:["\n",(0,s.jsxs)(l.p,{children:["Start the ",(0,s.jsx)(l.code,{children:"logoscore"})," daemon with the ",(0,s.jsx)(l.code,{children:"modules/"})," directory."]}),"\n",(0,s.jsx)(l.pre,{children:(0,s.jsx)(l.code,{className:"language-bash",children:"logoscore -D -m ./modules\n"})}),"\n"]}),"\n",(0,s.jsxs)(l.li,{children:["\n",(0,s.jsxs)(l.p,{children:["From another terminal, load the module and call a method. Replace ",(0,s.jsx)(l.code,{children:"<method>"})," and ",(0,s.jsx)(l.code,{children:"<args>"})," with the method name and arguments you want to call."]}),"\n",(0,s.jsx)(l.pre,{children:(0,s.jsx)(l.code,{className:"language-bash",children:"logoscore load-module <module-name>\nlogoscore call <module-name> <method> <args>\n"})}),"\n"]}),"\n",(0,s.jsxs)(l.li,{children:["\n",(0,s.jsx)(l.p,{children:"Stop the daemon when finished."}),"\n",(0,s.jsx)(l.pre,{children:(0,s.jsx)(l.code,{className:"language-bash",children:"logoscore stop\n"})}),"\n"]}),"\n"]}),"\n",(0,s.jsx)(l.admonition,{type:"tip",children:(0,s.jsxs)(l.p,{children:["Check out ",(0,s.jsx)(l.a,{href:"/core/reference/logos-cli-reference",children:"Logos CLI Reference"})," for more details on available commands and options."]})}),"\n",(0,s.jsxs)(l.h3,{id:"run-with-logos-basecamp",children:["Run with ",(0,s.jsx)(l.code,{children:"logos-basecamp"})]}),"\n",(0,s.jsxs)(l.p,{children:["Logos Basecamp is a desktop shell that provides a graphical interface for managing and running modules. Core modules run as background services in ",(0,s.jsx)(l.code,{children:"logos-basecamp"}),". Other UI modules can call them through ",(0,s.jsx)(l.code,{children:"LogosAPI"})," or the ",(0,s.jsx)(l.code,{children:"logos.callModule()"})," bridge once they are installed."]}),"\n",(0,s.jsx)(l.admonition,{type:"warning",children:(0,s.jsxs)(l.p,{children:["The LGX variant type must match the basecamp build type. Dev builds of basecamp expect dev LGX variants (for example, ",(0,s.jsx)(l.code,{children:"darwin-arm64-dev"}),"), and portable builds expect portable variants (for example, ",(0,s.jsx)(l.code,{children:"darwin-arm64"}),"). Check out the ",(0,s.jsx)(l.a,{href:"/core/reference/lgx-package-format-and-bundling-reference",children:"LGX package format and bundling reference"})," for more details."]})}),"\n",(0,s.jsxs)(l.ol,{children:["\n",(0,s.jsxs)(l.li,{children:["\n",(0,s.jsxs)(l.p,{children:["Build the development version of ",(0,s.jsx)(l.code,{children:"logos-basecamp"}),"."]}),"\n",(0,s.jsx)(l.pre,{children:(0,s.jsx)(l.code,{className:"language-bash",children:"nix build 'github:logos-co/logos-basecamp/tutorial-v1#app' --out-link ./logos-basecamp\n"})}),"\n"]}),"\n",(0,s.jsxs)(l.li,{children:["\n",(0,s.jsxs)(l.p,{children:["Launch ",(0,s.jsx)(l.code,{children:"logos-basecamp"})," once to create its data directory and preinstall bundled modules, then close it."]}),"\n",(0,s.jsx)(l.pre,{children:(0,s.jsx)(l.code,{className:"language-bash",children:" ./logos-basecamp/bin/logos-basecamp\n"})}),"\n",(0,s.jsxs)(l.ul,{children:["\n",(0,s.jsxs)(l.li,{children:["Look for the directory containing ",(0,s.jsx)(l.code,{children:"modules/"})," and ",(0,s.jsx)(l.code,{children:"plugins/"})," subdirectories at ",(0,s.jsx)(l.code,{children:"~/Library/Application Support/Logos/LogosBasecamp/"})," (macOS) or ",(0,s.jsx)(l.code,{children:"~/.config/Logos/LogosBasecamp/"})," (Linux)."]}),"\n"]}),"\n"]}),"\n",(0,s.jsxs)(l.li,{children:["\n",(0,s.jsxs)(l.p,{children:["Set the ",(0,s.jsx)(l.code,{children:"BASECAMP_DIR"})," variable to your platform's path."]}),"\n",(0,s.jsx)(l.pre,{children:(0,s.jsx)(l.code,{className:"language-bash",children:'# macOS\nBASECAMP_DIR="$HOME/Library/Application Support/Logos/LogosBasecamp"\n\n# Linux\nBASECAMP_DIR="$HOME/.config/Logos/LogosBasecamp"\n'})}),"\n"]}),"\n",(0,s.jsxs)(l.li,{children:["\n",(0,s.jsxs)(l.p,{children:["Install the module's dev LGX package into basecamp's modules directory. The package must contain a ",(0,s.jsx)(l.code,{children:"-dev"})," variant for your platform; build it with ",(0,s.jsx)(l.code,{children:"nix bundle --bundler github:logos-co/nix-bundle-lgx/tutorial-v3#dual .#lib"})," as described in Step 5."]}),"\n",(0,s.jsx)(l.pre,{children:(0,s.jsx)(l.code,{className:"language-bash",children:'lgpm --modules-dir "$BASECAMP_DIR/modules" install --file ./logos-<module-name>-module-lib-lgx-<version>/logos-<module-name>-module-lib.lgx\n'})}),"\n"]}),"\n"]}),"\n",(0,s.jsx)(l.h2,{id:"troubleshooting",children:"Troubleshooting"}),"\n",(0,s.jsx)(l.h3,{id:"known-constraints",children:"Known constraints"}),"\n",(0,s.jsxs)(l.p,{children:["A single ",(0,s.jsx)(l.code,{children:"logoscore"})," instance supports only one instance of a module, so all dependent apps share that state. To run multiple independent instances, start separate ",(0,s.jsx)(l.code,{children:"logos-basecamp"})," instances with their own user directories, and each ",(0,s.jsx)(l.code,{children:"logos-basecamp"})," instance runs its own ",(0,s.jsx)(l.code,{children:"logoscore"})," and module instances in isolation."]}),"\n",(0,s.jsx)(l.h3,{id:"nix-reports-an-experimental-features-error",children:'Nix reports an "experimental features" error'}),"\n",(0,s.jsx)(l.p,{children:"If you see errors about experimental features, either pass the flag:"}),"\n",(0,s.jsx)(l.pre,{children:(0,s.jsx)(l.code,{className:"language-bash",children:'nix --extra-experimental-features "nix-command flakes" build\n'})}),"\n",(0,s.jsxs)(l.p,{children:["Or add the following to ",(0,s.jsx)(l.code,{children:"~/.config/nix/nix.conf"}),":"]}),"\n",(0,s.jsx)(l.pre,{children:(0,s.jsx)(l.code,{className:"language-conf",children:"experimental-features = nix-command flakes\n"})}),"\n",(0,s.jsxs)(l.h3,{id:"module-not-discovered-by-logos-basecamp",children:["Module not discovered by ",(0,s.jsx)(l.code,{children:"logos-basecamp"})]}),"\n",(0,s.jsxs)(l.p,{children:["Confirm the module is in a subdirectory of the ",(0,s.jsx)(l.code,{children:"modules/"})," directory (for example, ",(0,s.jsx)(l.code,{children:"modules/my_module/"}),") and that the subdirectory contains the module binary and ",(0,s.jsx)(l.code,{children:"manifest.json"}),". The ",(0,s.jsx)(l.code,{children:"name"})," field in ",(0,s.jsx)(l.code,{children:"manifest.json"})," must match the binary name (for example, ",(0,s.jsx)(l.code,{children:"my_module"})," for ",(0,s.jsx)(l.code,{children:"my_module_plugin.so"}),")."]}),"\n",(0,s.jsxs)(l.h3,{id:"module-not-discovered-by-logoscore",children:["Module not discovered by ",(0,s.jsx)(l.code,{children:"logoscore"})]}),"\n",(0,s.jsxs)(l.p,{children:["Confirm the module is in a subdirectory of the ",(0,s.jsx)(l.code,{children:"modules/"})," directory (for example, ",(0,s.jsx)(l.code,{children:"modules/my_module/"}),") and that the subdirectory contains a ",(0,s.jsx)(l.code,{children:"manifest.json"})," with a ",(0,s.jsx)(l.code,{children:"main"})," object matching your OS and architecture."]}),"\n",(0,s.jsxs)(l.h3,{id:"lgpm-fails-to-install-a-module",children:[(0,s.jsx)(l.code,{children:"lgpm"})," fails to install a module"]}),"\n",(0,s.jsxs)(l.p,{children:["Verify the target directory exists and is writable. If installing from a local ",(0,s.jsx)(l.code,{children:".lgx"})," file, confirm the file path is correct (the bundler writes ",(0,s.jsx)(l.code,{children:"logos-<module-name>-module-lib.lgx"}),", not ",(0,s.jsx)(l.code,{children:"<module-name>.lgx"}),"). If installing from the registry, check your internet connection \u2014 ",(0,s.jsx)(l.code,{children:"lgpd"})," fetches packages from GitHub Releases. To pin a specific release version, pass ",(0,s.jsx)(l.code,{children:"--release <tag>"})," to ",(0,s.jsx)(l.code,{children:"lgpd"})," (not ",(0,s.jsx)(l.code,{children:"lgpm"}),") when downloading."]}),"\n",(0,s.jsx)(l.h3,{id:"lgx-variant-mismatch",children:"LGX variant mismatch"}),"\n",(0,s.jsxs)(l.p,{children:["If ",(0,s.jsx)(l.code,{children:"lgpm install"})," fails with ",(0,s.jsx)(l.code,{children:"Package does not contain variant for platform: <platform>-dev"}),", the LGX file does not include a ",(0,s.jsx)(l.code,{children:"-dev"})," variant for your platform."]}),"\n",(0,s.jsxs)(l.ul,{children:["\n",(0,s.jsxs)(l.li,{children:[(0,s.jsx)(l.code,{children:"nix build .#lgx"})," produces a single variant (for example, ",(0,s.jsx)(l.code,{children:"linux-amd64"}),") suitable for ",(0,s.jsx)(l.code,{children:"logoscore"})," but not for a dev build of ",(0,s.jsx)(l.code,{children:"logos-basecamp"}),"."]}),"\n",(0,s.jsxs)(l.li,{children:[(0,s.jsx)(l.code,{children:"nix build .#lgx-portable"})," produces a single portable variant suitable for portable builds of ",(0,s.jsx)(l.code,{children:"logos-basecamp"}),"."]}),"\n",(0,s.jsxs)(l.li,{children:[(0,s.jsx)(l.code,{children:"nix bundle --bundler github:logos-co/nix-bundle-lgx/tutorial-v3#dual .#lib"})," produces both ",(0,s.jsx)(l.code,{children:"-dev"})," and portable variants in a single ",(0,s.jsx)(l.code,{children:".lgx"})," file, which works with dev and portable builds of ",(0,s.jsx)(l.code,{children:"logos-basecamp"}),"."]}),"\n"]}),"\n",(0,s.jsxs)(l.p,{children:["Registry packages downloaded with ",(0,s.jsx)(l.code,{children:"lgpd"})," currently ship portable variants only."]}),"\n",(0,s.jsx)(l.admonition,{type:"info",children:(0,s.jsxs)(l.p,{children:[(0,s.jsx)(l.code,{children:"lgpm"})," error messages report the platform as ",(0,s.jsx)(l.code,{children:"linux-x86_64"})," while LGX manifests label it ",(0,s.jsx)(l.code,{children:"linux-amd64"}),". These refer to the same architecture."]})}),"\n",(0,s.jsxs)(l.h3,{id:"nix-build-lib-does-nothing-or-fails-silently",children:[(0,s.jsx)(l.code,{children:"nix build .#lib"})," does nothing or fails silently"]}),"\n",(0,s.jsxs)(l.p,{children:["Some shells (notably zsh) treats ",(0,s.jsx)(l.code,{children:"#"})," as a comment character outside quotes. Quote the flake reference so the ",(0,s.jsx)(l.code,{children:"#"})," reaches nix intact."]}),"\n",(0,s.jsx)(l.pre,{children:(0,s.jsx)(l.code,{className:"language-bash",children:"nix build '.#lib'\n"})}),"\n",(0,s.jsx)(l.h3,{id:"first-build-is-slow",children:"First build is slow"}),"\n",(0,s.jsxs)(l.p,{children:["The first ",(0,s.jsx)(l.code,{children:"nix build"})," downloads Qt 6, the Logos C++ SDK, the code generator, and the rest of the build dependencies. This is a one-time cost, subsequent builds reuse the cache and typically complete in under 30 seconds."]})]})}function h(e={}){let{wrapper:l}={...(0,d.R)(),...e.components};return l?(0,s.jsx)(l,{...e,children:(0,s.jsx)(t,{...e})}):t(e)}},8453(e,l,n){n.d(l,{R:()=>i,x:()=>r});var o=n(6540);let s={},d=o.createContext(s);function i(e){let l=o.useContext(d);return o.useMemo(function(){return"function"==typeof e?e(l):{...l,...e}},[l,e])}function r(e){let l;return l=e.disableParentContext?"function"==typeof e.components?e.components(s):e.components||s:i(e.components),o.createElement(d.Provider,{value:l},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/main.78385118.js b/assets/js/main.78385118.js new file mode 100644 index 00000000..eb1e270b --- /dev/null +++ b/assets/js/main.78385118.js @@ -0,0 +1,38 @@ +(self.webpackChunklogos_docs=self.webpackChunklogos_docs||[]).push([["1889"],{7612(e,t,n){"use strict";n.r(t)},6464(e,t,n){"use strict";n.r(t)},6448(e,t,n){"use strict";n.r(t)},3328(e,t,n){"use strict";n.d(t,{AO:()=>p,yJ:()=>f,sC:()=>T,TM:()=>A,zR:()=>w});var r=n(8168);function a(e){return"/"===e.charAt(0)}function o(e,t){for(var n=t,r=n+1,a=e.length;r<a;n+=1,r+=1)e[n]=e[r];e.pop()}let i=function(e,t){void 0===t&&(t="");var n,r=e&&e.split("/")||[],i=t&&t.split("/")||[],l=e&&a(e),s=t&&a(t),c=l||s;if(e&&a(e)?i=r:r.length&&(i.pop(),i=i.concat(r)),!i.length)return"/";if(i.length){var u=i[i.length-1];n="."===u||".."===u||""===u}else n=!1;for(var d=0,p=i.length;p>=0;p--){var f=i[p];"."===f?o(i,p):".."===f?(o(i,p),d++):d&&(o(i,p),d--)}if(!c)for(;d--;)i.unshift("..");!c||""===i[0]||i[0]&&a(i[0])||i.unshift("");var m=i.join("/");return n&&"/"!==m.substr(-1)&&(m+="/"),m};var l=n(1561);function s(e){return"/"===e.charAt(0)?e:"/"+e}function c(e){return"/"===e.charAt(0)?e.substr(1):e}function u(e,t){return 0===e.toLowerCase().indexOf(t.toLowerCase())&&-1!=="/?#".indexOf(e.charAt(t.length))?e.substr(t.length):e}function d(e){return"/"===e.charAt(e.length-1)?e.slice(0,-1):e}function p(e){var t=e.pathname,n=e.search,r=e.hash,a=t||"/";return n&&"?"!==n&&(a+="?"===n.charAt(0)?n:"?"+n),r&&"#"!==r&&(a+="#"===r.charAt(0)?r:"#"+r),a}function f(e,t,n,a){var o,l,s,c,u,d;"string"==typeof e?(s="",c="",-1!==(u=(l=e||"/").indexOf("#"))&&(c=l.substr(u),l=l.substr(0,u)),-1!==(d=l.indexOf("?"))&&(s=l.substr(d),l=l.substr(0,d)),(o={pathname:l,search:"?"===s?"":s,hash:"#"===c?"":c}).state=t):(void 0===(o=(0,r.A)({},e)).pathname&&(o.pathname=""),o.search?"?"!==o.search.charAt(0)&&(o.search="?"+o.search):o.search="",o.hash?"#"!==o.hash.charAt(0)&&(o.hash="#"+o.hash):o.hash="",void 0!==t&&void 0===o.state&&(o.state=t));try{o.pathname=decodeURI(o.pathname)}catch(e){if(e instanceof URIError)throw URIError('Pathname "'+o.pathname+'" could not be decoded. This is likely caused by an invalid percent-encoding.');throw e}return n&&(o.key=n),a?o.pathname?"/"!==o.pathname.charAt(0)&&(o.pathname=i(o.pathname,a.pathname)):o.pathname=a.pathname:o.pathname||(o.pathname="/"),o}function m(){var e=null,t=[];return{setPrompt:function(t){return e=t,function(){e===t&&(e=null)}},confirmTransitionTo:function(t,n,r,a){if(null!=e){var o="function"==typeof e?e(t,n):e;"string"==typeof o?"function"==typeof r?r(o,a):a(!0):a(!1!==o)}else a(!0)},appendListener:function(e){var n=!0;function r(){n&&e.apply(void 0,arguments)}return t.push(r),function(){n=!1,t=t.filter(function(e){return e!==r})}},notifyListeners:function(){for(var e=arguments.length,n=Array(e),r=0;r<e;r++)n[r]=arguments[r];t.forEach(function(e){return e.apply(void 0,n)})}}}var g=!!("u">typeof window&&window.document&&window.document.createElement);function h(e,t){t(window.confirm(e))}var b="popstate",y="hashchange";function v(){try{return window.history.state||{}}catch(e){return{}}}function w(e){void 0===e&&(e={}),g||(0,l.A)(!1);var t,n=window.history,a=(-1===(t=window.navigator.userAgent).indexOf("Android 2.")&&-1===t.indexOf("Android 4.0")||-1===t.indexOf("Mobile Safari")||-1!==t.indexOf("Chrome")||-1!==t.indexOf("Windows Phone"))&&window.history&&"pushState"in window.history,o=-1!==window.navigator.userAgent.indexOf("Trident"),i=e,c=i.forceRefresh,w=void 0!==c&&c,k=i.getUserConfirmation,x=void 0===k?h:k,S=i.keyLength,E=void 0===S?6:S,_=e.basename?d(s(e.basename)):"";function A(e){var t=e||{},n=t.key,r=t.state,a=window.location,o=a.pathname+a.search+a.hash;return _&&(o=u(o,_)),f(o,r,n)}function C(){return Math.random().toString(36).substr(2,E)}var T=m();function j(e){(0,r.A)(B,e),B.length=n.length,T.notifyListeners(B.location,B.action)}function P(e){(void 0!==e.state||-1!==navigator.userAgent.indexOf("CriOS"))&&N(A(e.state))}function z(){N(A(v()))}var L=!1;function N(e){L?(L=!1,j()):T.confirmTransitionTo(e,"POP",x,function(t){var n,r,a,o,i;t?j({action:"POP",location:e}):(n=e,r=B.location,-1===(a=R.indexOf(r.key))&&(a=0),-1===(o=R.indexOf(n.key))&&(o=0),(i=a-o)&&(L=!0,F(i)))})}var O=A(v()),R=[O.key];function D(e){return _+p(e)}function F(e){n.go(e)}var I=0;function M(e){1===(I+=e)&&1===e?(window.addEventListener(b,P),o&&window.addEventListener(y,z)):0===I&&(window.removeEventListener(b,P),o&&window.removeEventListener(y,z))}var $=!1,B={length:n.length,action:"POP",location:O,createHref:D,push:function(e,t){var r="PUSH",o=f(e,t,C(),B.location);T.confirmTransitionTo(o,r,x,function(e){if(e){var t=D(o),i=o.key,l=o.state;if(a)if(n.pushState({key:i,state:l},null,t),w)window.location.href=t;else{var s=R.indexOf(B.location.key),c=R.slice(0,s+1);c.push(o.key),R=c,j({action:r,location:o})}else window.location.href=t}})},replace:function(e,t){var r="REPLACE",o=f(e,t,C(),B.location);T.confirmTransitionTo(o,r,x,function(e){if(e){var t=D(o),i=o.key,l=o.state;if(a)if(n.replaceState({key:i,state:l},null,t),w)window.location.replace(t);else{var s=R.indexOf(B.location.key);-1!==s&&(R[s]=o.key),j({action:r,location:o})}else window.location.replace(t)}})},go:F,goBack:function(){F(-1)},goForward:function(){F(1)},block:function(e){void 0===e&&(e=!1);var t=T.setPrompt(e);return $||(M(1),$=!0),function(){return $&&($=!1,M(-1)),t()}},listen:function(e){var t=T.appendListener(e);return M(1),function(){M(-1),t()}}};return B}var k="hashchange",x={hashbang:{encodePath:function(e){return"!"===e.charAt(0)?e:"!/"+c(e)},decodePath:function(e){return"!"===e.charAt(0)?e.substr(1):e}},noslash:{encodePath:c,decodePath:s},slash:{encodePath:s,decodePath:s}};function S(e){var t=e.indexOf("#");return -1===t?e:e.slice(0,t)}function E(){var e=window.location.href,t=e.indexOf("#");return -1===t?"":e.substring(t+1)}function _(e){window.location.replace(S(window.location.href)+"#"+e)}function A(e){void 0===e&&(e={}),g||(0,l.A)(!1);var t=window.history;window.navigator.userAgent.indexOf("Firefox");var n=e,a=n.getUserConfirmation,o=void 0===a?h:a,i=n.hashType,c=e.basename?d(s(e.basename)):"",b=x[void 0===i?"slash":i],y=b.encodePath,v=b.decodePath;function w(){var e=v(E());return c&&(e=u(e,c)),f(e)}var A=m();function C(e){(0,r.A)(M,e),M.length=t.length,A.notifyListeners(M.location,M.action)}var T=!1,j=null;function P(){var e=E(),t=y(e);if(e!==t)_(t);else{var n,r=w(),a=M.location;if(!T&&a.pathname===r.pathname&&a.search===r.search&&a.hash===r.hash||j===p(r))return;j=null,n=r,T?(T=!1,C()):A.confirmTransitionTo(n,"POP",o,function(e){var t,r,a,o,i;e?C({action:"POP",location:n}):(t=n,r=M.location,-1===(a=O.lastIndexOf(p(r)))&&(a=0),-1===(o=O.lastIndexOf(p(t)))&&(o=0),(i=a-o)&&(T=!0,R(i)))})}}var z=E(),L=y(z);z!==L&&_(L);var N=w(),O=[p(N)];function R(e){t.go(e)}var D=0;function F(e){1===(D+=e)&&1===e?window.addEventListener(k,P):0===D&&window.removeEventListener(k,P)}var I=!1,M={length:t.length,action:"POP",location:N,createHref:function(e){var t=document.querySelector("base"),n="";return t&&t.getAttribute("href")&&(n=S(window.location.href)),n+"#"+y(c+p(e))},push:function(e,t){var n="PUSH",r=f(e,void 0,void 0,M.location);A.confirmTransitionTo(r,n,o,function(e){if(e){var t=p(r),a=y(c+t);if(E()!==a){j=t,window.location.hash=a;var o=O.lastIndexOf(p(M.location)),i=O.slice(0,o+1);i.push(t),O=i,C({action:n,location:r})}else C()}})},replace:function(e,t){var n="REPLACE",r=f(e,void 0,void 0,M.location);A.confirmTransitionTo(r,n,o,function(e){if(e){var t=p(r),a=y(c+t);E()!==a&&(j=t,_(a));var o=O.indexOf(p(M.location));-1!==o&&(O[o]=t),C({action:n,location:r})}})},go:R,goBack:function(){R(-1)},goForward:function(){R(1)},block:function(e){void 0===e&&(e=!1);var t=A.setPrompt(e);return I||(F(1),I=!0),function(){return I&&(I=!1,F(-1)),t()}},listen:function(e){var t=A.appendListener(e);return F(1),function(){F(-1),t()}}};return M}function C(e,t,n){return Math.min(Math.max(e,t),n)}function T(e){void 0===e&&(e={});var t=e,n=t.getUserConfirmation,a=t.initialEntries,o=void 0===a?["/"]:a,i=t.initialIndex,l=t.keyLength,s=void 0===l?6:l,c=m();function u(e){(0,r.A)(y,e),y.length=y.entries.length,c.notifyListeners(y.location,y.action)}function d(){return Math.random().toString(36).substr(2,s)}var g=C(void 0===i?0:i,0,o.length-1),h=o.map(function(e){return"string"==typeof e?f(e,void 0,d()):f(e,void 0,e.key||d())});function b(e){var t=C(y.index+e,0,y.entries.length-1),r=y.entries[t];c.confirmTransitionTo(r,"POP",n,function(e){e?u({action:"POP",location:r,index:t}):u()})}var y={length:h.length,action:"POP",location:h[g],index:g,entries:h,createHref:p,push:function(e,t){var r="PUSH",a=f(e,t,d(),y.location);c.confirmTransitionTo(a,r,n,function(e){if(e){var t=y.index+1,n=y.entries.slice(0);n.length>t?n.splice(t,n.length-t,a):n.push(a),u({action:r,location:a,index:t,entries:n})}})},replace:function(e,t){var r="REPLACE",a=f(e,t,d(),y.location);c.confirmTransitionTo(a,r,n,function(e){e&&(y.entries[y.index]=a,u({action:r,location:a}))})},go:b,goBack:function(){b(-1)},goForward:function(){b(1)},canGo:function(e){var t=y.index+e;return t>=0&&t<y.entries.length},block:function(e){return void 0===e&&(e=!1),c.setPrompt(e)},listen:function(e){return c.appendListener(e)}};return y}},4146(e,t,n){"use strict";var r=n(4363),a={childContextTypes:!0,contextType:!0,contextTypes:!0,defaultProps:!0,displayName:!0,getDefaultProps:!0,getDerivedStateFromError:!0,getDerivedStateFromProps:!0,mixins:!0,propTypes:!0,type:!0},o={name:!0,length:!0,prototype:!0,caller:!0,callee:!0,arguments:!0,arity:!0},i={$$typeof:!0,compare:!0,defaultProps:!0,displayName:!0,propTypes:!0,type:!0},l={};function s(e){return r.isMemo(e)?i:l[e.$$typeof]||a}l[r.ForwardRef]={$$typeof:!0,render:!0,defaultProps:!0,displayName:!0,propTypes:!0},l[r.Memo]=i;var c=Object.defineProperty,u=Object.getOwnPropertyNames,d=Object.getOwnPropertySymbols,p=Object.getOwnPropertyDescriptor,f=Object.getPrototypeOf,m=Object.prototype;e.exports=function e(t,n,r){if("string"!=typeof n){if(m){var a=f(n);a&&a!==m&&e(t,a,r)}var i=u(n);d&&(i=i.concat(d(n)));for(var l=s(t),g=s(n),h=0;h<i.length;++h){var b=i[h];if(!o[b]&&!(r&&r[b])&&!(g&&g[b])&&!(l&&l[b])){var y=p(n,b);try{c(t,b,y)}catch(e){}}}}return t}},311(e){"use strict";e.exports=function(e,t,n,r,a,o,i,l){if(!e){var s;if(void 0===t)s=Error("Minified exception occurred; use the non-minified dev environment for the full error message and additional helpful warnings.");else{var c=[n,r,a,o,i,l],u=0;(s=Error(t.replace(/%s/g,function(){return c[u++]}))).name="Invariant Violation"}throw s.framesToPop=1,s}}},4634(e){e.exports=Array.isArray||function(e){return"[object Array]"==Object.prototype.toString.call(e)}},5947(e){var t;t=function(){var e,t,n,r={};r.version="0.2.0";var a=r.settings={minimum:.08,easing:"ease",positionUsing:"",speed:200,trickle:!0,trickleRate:.02,trickleSpeed:800,showSpinner:!0,barSelector:'[role="bar"]',spinnerSelector:'[role="spinner"]',parent:"body",template:'<div class="bar" role="bar"><div class="peg"></div></div><div class="spinner" role="spinner"><div class="spinner-icon"></div></div>'};function o(e,t,n){return e<t?t:e>n?n:e}r.configure=function(e){var t,n;for(t in e)void 0!==(n=e[t])&&e.hasOwnProperty(t)&&(a[t]=n);return this},r.status=null,r.set=function(e){var t=r.isStarted();r.status=1===(e=o(e,a.minimum,1))?null:e;var n=r.render(!t),s=n.querySelector(a.barSelector),c=a.speed,u=a.easing;return n.offsetWidth,i(function(t){var o,i,d,p;""===a.positionUsing&&(a.positionUsing=r.getPositioningCSS()),l(s,(o=e,i=c,d=u,(p="translate3d"===a.positionUsing?{transform:"translate3d("+(-1+o)*100+"%,0,0)"}:"translate"===a.positionUsing?{transform:"translate("+(-1+o)*100+"%,0)"}:{"margin-left":(-1+o)*100+"%"}).transition="all "+i+"ms "+d,p)),1===e?(l(n,{transition:"none",opacity:1}),n.offsetWidth,setTimeout(function(){l(n,{transition:"all "+c+"ms linear",opacity:0}),setTimeout(function(){r.remove(),t()},c)},c)):setTimeout(t,c)}),this},r.isStarted=function(){return"number"==typeof r.status},r.start=function(){r.status||r.set(0);var e=function(){setTimeout(function(){r.status&&(r.trickle(),e())},a.trickleSpeed)};return a.trickle&&e(),this},r.done=function(e){return e||r.status?r.inc(.3+.5*Math.random()).set(1):this},r.inc=function(e){var t=r.status;return t?("number"!=typeof e&&(e=(1-t)*o(Math.random()*t,.1,.95)),t=o(t+e,0,.994),r.set(t)):r.start()},r.trickle=function(){return r.inc(Math.random()*a.trickleRate)},e=0,t=0,r.promise=function(n){return n&&"resolved"!==n.state()&&(0===t&&r.start(),e++,t++,n.always(function(){0==--t?(e=0,r.done()):r.set((e-t)/e)})),this},r.render=function(e){if(r.isRendered())return document.getElementById("nprogress");c(document.documentElement,"nprogress-busy");var t=document.createElement("div");t.id="nprogress",t.innerHTML=a.template;var n,o=t.querySelector(a.barSelector),i=e?"-100":(-1+(r.status||0))*100,s=document.querySelector(a.parent);return l(o,{transition:"all 0 linear",transform:"translate3d("+i+"%,0,0)"}),!a.showSpinner&&(n=t.querySelector(a.spinnerSelector))&&p(n),s!=document.body&&c(s,"nprogress-custom-parent"),s.appendChild(t),t},r.remove=function(){u(document.documentElement,"nprogress-busy"),u(document.querySelector(a.parent),"nprogress-custom-parent");var e=document.getElementById("nprogress");e&&p(e)},r.isRendered=function(){return!!document.getElementById("nprogress")},r.getPositioningCSS=function(){var e=document.body.style,t="WebkitTransform"in e?"Webkit":"MozTransform"in e?"Moz":"msTransform"in e?"ms":"OTransform"in e?"O":"";return t+"Perspective"in e?"translate3d":t+"Transform"in e?"translate":"margin"};var i=(n=[],function(e){n.push(e),1==n.length&&function e(){var t=n.shift();t&&t(e)}()}),l=function(){var e=["Webkit","O","Moz","ms"],t={};function n(n,r,a){var o;r=t[o=(o=r).replace(/^-ms-/,"ms-").replace(/-([\da-z])/gi,function(e,t){return t.toUpperCase()})]||(t[o]=function(t){var n=document.body.style;if(t in n)return t;for(var r,a=e.length,o=t.charAt(0).toUpperCase()+t.slice(1);a--;)if((r=e[a]+o)in n)return r;return t}(o)),n.style[r]=a}return function(e,t){var r,a,o=arguments;if(2==o.length)for(r in t)void 0!==(a=t[r])&&t.hasOwnProperty(r)&&n(e,r,a);else n(e,o[1],o[2])}}();function s(e,t){return("string"==typeof e?e:d(e)).indexOf(" "+t+" ")>=0}function c(e,t){var n=d(e),r=n+t;s(n,t)||(e.className=r.substring(1))}function u(e,t){var n,r=d(e);s(e,t)&&(e.className=(n=r.replace(" "+t+" "," ")).substring(1,n.length-1))}function d(e){return(" "+(e.className||"")+" ").replace(/\s+/gi," ")}function p(e){e&&e.parentNode&&e.parentNode.removeChild(e)}return r},"function"==typeof define&&define.amd?define(t):e.exports=t()},5302(e,t,n){var r=n(4634);e.exports=function e(t,n,a){if(r(n)||(a=n||a,n=[]),a=a||{},t instanceof RegExp){var i,l,s=n,d=t.source.match(/\((?!\?)/g);if(d)for(var p=0;p<d.length;p++)s.push({name:p,prefix:null,delimiter:null,optional:!1,repeat:!1,partial:!1,asterisk:!1,pattern:null});return t.keys=s,t}if(r(t)){for(var f,m=n,g=a,h=[],b=0;b<t.length;b++)h.push(e(t[b],m,g).source);return(f=RegExp("(?:"+h.join("|")+")",c(g))).keys=m,f}return i=n,u(o(t,l=a),i,l)},e.exports.parse=o,e.exports.compile=function(e,t){return l(o(e,t),t)},e.exports.tokensToFunction=l,e.exports.tokensToRegExp=u;var a=RegExp("(\\\\.)|([\\/.])?(?:(?:\\:(\\w+)(?:\\(((?:\\\\.|[^\\\\()])+)\\))?|\\(((?:\\\\.|[^\\\\()])+)\\))([+*?])?|(\\*))","g");function o(e,t){for(var n,r=[],o=0,i=0,l="",c=t&&t.delimiter||"/";null!=(n=a.exec(e));){var u,d,p=n[0],f=n[1],m=n.index;if(l+=e.slice(i,m),i=m+p.length,f){l+=f[1];continue}var g=e[i],h=n[2],b=n[3],y=n[4],v=n[5],w=n[6],k=n[7];l&&(r.push(l),l="");var x=null!=h&&null!=g&&g!==h,S="+"===w||"*"===w,E="?"===w||"*"===w,_=h||c,A=y||v,C=h||("string"==typeof r[r.length-1]?r[r.length-1]:"");r.push({name:b||o++,prefix:h||"",delimiter:_,optional:E,repeat:S,partial:x,asterisk:!!k,pattern:A?A.replace(/([=!:$\/()])/g,"\\$1"):k?".*":(u=_,!(d=C)||d.indexOf(u)>-1?"[^"+s(u)+"]+?":s(d)+"|(?:(?!"+s(d)+")[^"+s(u)+"])+?")})}return i<e.length&&(l+=e.substr(i)),l&&r.push(l),r}function i(e){return encodeURI(e).replace(/[\/?#]/g,function(e){return"%"+e.charCodeAt(0).toString(16).toUpperCase()})}function l(e,t){for(var n=Array(e.length),a=0;a<e.length;a++)"object"==typeof e[a]&&(n[a]=RegExp("^(?:"+e[a].pattern+")$",c(t)));return function(t,a){for(var o="",l=t||{},s=(a||{}).pretty?i:encodeURIComponent,c=0;c<e.length;c++){var u,d=e[c];if("string"==typeof d){o+=d;continue}var p=l[d.name];if(null==p)if(d.optional){d.partial&&(o+=d.prefix);continue}else throw TypeError('Expected "'+d.name+'" to be defined');if(r(p)){if(!d.repeat)throw TypeError('Expected "'+d.name+'" to not repeat, but received `'+JSON.stringify(p)+"`");if(0===p.length)if(d.optional)continue;else throw TypeError('Expected "'+d.name+'" to not be empty');for(var f=0;f<p.length;f++){if(u=s(p[f]),!n[c].test(u))throw TypeError('Expected all "'+d.name+'" to match "'+d.pattern+'", but received `'+JSON.stringify(u)+"`");o+=(0===f?d.prefix:d.delimiter)+u}continue}if(u=d.asterisk?encodeURI(p).replace(/[?#]/g,function(e){return"%"+e.charCodeAt(0).toString(16).toUpperCase()}):s(p),!n[c].test(u))throw TypeError('Expected "'+d.name+'" to match "'+d.pattern+'", but received "'+u+'"');o+=d.prefix+u}return o}}function s(e){return e.replace(/([.+*?=^!:${}()[\]|\/\\])/g,"\\$1")}function c(e){return e&&e.sensitive?"":"i"}function u(e,t,n){r(t)||(n=t||n,t=[]);for(var a,o=(n=n||{}).strict,i=!1!==n.end,l="",u=0;u<e.length;u++){var d=e[u];if("string"==typeof d)l+=s(d);else{var p=s(d.prefix),f="(?:"+d.pattern+")";t.push(d),d.repeat&&(f+="(?:"+p+f+")*"),l+=f=d.optional?d.partial?p+"("+f+")?":"(?:"+p+"("+f+"))?":p+"("+f+")"}}var m=s(n.delimiter||"/"),g=l.slice(-m.length)===m;return o||(l=(g?l.slice(0,-m.length):l)+"(?:"+m+"(?=$))?"),i?l+="$":l+=o&&g?"":"(?="+m+"|$)",(a=RegExp("^"+l,c(n))).keys=t,a}},6969(e){e.exports&&(e.exports={core:{meta:{path:"components/prism-core.js",option:"mandatory"},core:"Core"},themes:{meta:{path:"themes/{id}.css",link:"index.html?theme={id}",exclusive:!0},prism:{title:"Default",option:"default"},"prism-dark":"Dark","prism-funky":"Funky","prism-okaidia":{title:"Okaidia",owner:"ocodia"},"prism-twilight":{title:"Twilight",owner:"remybach"},"prism-coy":{title:"Coy",owner:"tshedor"},"prism-solarizedlight":{title:"Solarized Light",owner:"hectormatos2011 "},"prism-tomorrow":{title:"Tomorrow Night",owner:"Rosey"}},languages:{meta:{path:"components/prism-{id}",noCSS:!0,examplesPath:"examples/prism-{id}",addCheckAll:!0},markup:{title:"Markup",alias:["html","xml","svg","mathml","ssml","atom","rss"],aliasTitles:{html:"HTML",xml:"XML",svg:"SVG",mathml:"MathML",ssml:"SSML",atom:"Atom",rss:"RSS"},option:"default"},css:{title:"CSS",option:"default",modify:"markup"},clike:{title:"C-like",option:"default"},javascript:{title:"JavaScript",require:"clike",modify:"markup",optional:"regex",alias:"js",option:"default"},abap:{title:"ABAP",owner:"dellagustin"},abnf:{title:"ABNF",owner:"RunDevelopment"},actionscript:{title:"ActionScript",require:"javascript",modify:"markup",owner:"Golmote"},ada:{title:"Ada",owner:"Lucretia"},agda:{title:"Agda",owner:"xy-ren"},al:{title:"AL",owner:"RunDevelopment"},antlr4:{title:"ANTLR4",alias:"g4",owner:"RunDevelopment"},apacheconf:{title:"Apache Configuration",owner:"GuiTeK"},apex:{title:"Apex",require:["clike","sql"],owner:"RunDevelopment"},apl:{title:"APL",owner:"ngn"},applescript:{title:"AppleScript",owner:"Golmote"},aql:{title:"AQL",owner:"RunDevelopment"},arduino:{title:"Arduino",require:"cpp",alias:"ino",owner:"dkern"},arff:{title:"ARFF",owner:"Golmote"},armasm:{title:"ARM Assembly",alias:"arm-asm",owner:"RunDevelopment"},arturo:{title:"Arturo",alias:"art",optional:["bash","css","javascript","markup","markdown","sql"],owner:"drkameleon"},asciidoc:{alias:"adoc",title:"AsciiDoc",owner:"Golmote"},aspnet:{title:"ASP.NET (C#)",require:["markup","csharp"],owner:"nauzilus"},asm6502:{title:"6502 Assembly",owner:"kzurawel"},asmatmel:{title:"Atmel AVR Assembly",owner:"cerkit"},autohotkey:{title:"AutoHotkey",owner:"aviaryan"},autoit:{title:"AutoIt",owner:"Golmote"},avisynth:{title:"AviSynth",alias:"avs",owner:"Zinfidel"},"avro-idl":{title:"Avro IDL",alias:"avdl",owner:"RunDevelopment"},awk:{title:"AWK",alias:"gawk",aliasTitles:{gawk:"GAWK"},owner:"RunDevelopment"},bash:{title:"Bash",alias:["sh","shell"],aliasTitles:{sh:"Shell",shell:"Shell"},owner:"zeitgeist87"},basic:{title:"BASIC",owner:"Golmote"},batch:{title:"Batch",owner:"Golmote"},bbcode:{title:"BBcode",alias:"shortcode",aliasTitles:{shortcode:"Shortcode"},owner:"RunDevelopment"},bbj:{title:"BBj",owner:"hyyan"},bicep:{title:"Bicep",owner:"johnnyreilly"},birb:{title:"Birb",require:"clike",owner:"Calamity210"},bison:{title:"Bison",require:"c",owner:"Golmote"},bnf:{title:"BNF",alias:"rbnf",aliasTitles:{rbnf:"RBNF"},owner:"RunDevelopment"},bqn:{title:"BQN",owner:"yewscion"},brainfuck:{title:"Brainfuck",owner:"Golmote"},brightscript:{title:"BrightScript",owner:"RunDevelopment"},bro:{title:"Bro",owner:"wayward710"},bsl:{title:"BSL (1C:Enterprise)",alias:"oscript",aliasTitles:{oscript:"OneScript"},owner:"Diversus23"},c:{title:"C",require:"clike",owner:"zeitgeist87"},csharp:{title:"C#",require:"clike",alias:["cs","dotnet"],owner:"mvalipour"},cpp:{title:"C++",require:"c",owner:"zeitgeist87"},cfscript:{title:"CFScript",require:"clike",alias:"cfc",owner:"mjclemente"},chaiscript:{title:"ChaiScript",require:["clike","cpp"],owner:"RunDevelopment"},cil:{title:"CIL",owner:"sbrl"},cilkc:{title:"Cilk/C",require:"c",alias:"cilk-c",owner:"OpenCilk"},cilkcpp:{title:"Cilk/C++",require:"cpp",alias:["cilk-cpp","cilk"],owner:"OpenCilk"},clojure:{title:"Clojure",owner:"troglotit"},cmake:{title:"CMake",owner:"mjrogozinski"},cobol:{title:"COBOL",owner:"RunDevelopment"},coffeescript:{title:"CoffeeScript",require:"javascript",alias:"coffee",owner:"R-osey"},concurnas:{title:"Concurnas",alias:"conc",owner:"jasontatton"},csp:{title:"Content-Security-Policy",owner:"ScottHelme"},cooklang:{title:"Cooklang",owner:"ahue"},coq:{title:"Coq",owner:"RunDevelopment"},crystal:{title:"Crystal",require:"ruby",owner:"MakeNowJust"},"css-extras":{title:"CSS Extras",require:"css",modify:"css",owner:"milesj"},csv:{title:"CSV",owner:"RunDevelopment"},cue:{title:"CUE",owner:"RunDevelopment"},cypher:{title:"Cypher",owner:"RunDevelopment"},d:{title:"D",require:"clike",owner:"Golmote"},dart:{title:"Dart",require:"clike",owner:"Golmote"},dataweave:{title:"DataWeave",owner:"machaval"},dax:{title:"DAX",owner:"peterbud"},dhall:{title:"Dhall",owner:"RunDevelopment"},diff:{title:"Diff",owner:"uranusjr"},django:{title:"Django/Jinja2",require:"markup-templating",alias:"jinja2",owner:"romanvm"},"dns-zone-file":{title:"DNS zone file",owner:"RunDevelopment",alias:"dns-zone"},docker:{title:"Docker",alias:"dockerfile",owner:"JustinBeckwith"},dot:{title:"DOT (Graphviz)",alias:"gv",optional:"markup",owner:"RunDevelopment"},ebnf:{title:"EBNF",owner:"RunDevelopment"},editorconfig:{title:"EditorConfig",owner:"osipxd"},eiffel:{title:"Eiffel",owner:"Conaclos"},ejs:{title:"EJS",require:["javascript","markup-templating"],owner:"RunDevelopment",alias:"eta",aliasTitles:{eta:"Eta"}},elixir:{title:"Elixir",owner:"Golmote"},elm:{title:"Elm",owner:"zwilias"},etlua:{title:"Embedded Lua templating",require:["lua","markup-templating"],owner:"RunDevelopment"},erb:{title:"ERB",require:["ruby","markup-templating"],owner:"Golmote"},erlang:{title:"Erlang",owner:"Golmote"},"excel-formula":{title:"Excel Formula",alias:["xlsx","xls"],owner:"RunDevelopment"},fsharp:{title:"F#",require:"clike",owner:"simonreynolds7"},factor:{title:"Factor",owner:"catb0t"},false:{title:"False",owner:"edukisto"},"firestore-security-rules":{title:"Firestore security rules",require:"clike",owner:"RunDevelopment"},flow:{title:"Flow",require:"javascript",owner:"Golmote"},fortran:{title:"Fortran",owner:"Golmote"},ftl:{title:"FreeMarker Template Language",require:"markup-templating",owner:"RunDevelopment"},gml:{title:"GameMaker Language",alias:"gamemakerlanguage",require:"clike",owner:"LiarOnce"},gap:{title:"GAP (CAS)",owner:"RunDevelopment"},gcode:{title:"G-code",owner:"RunDevelopment"},gdscript:{title:"GDScript",owner:"RunDevelopment"},gedcom:{title:"GEDCOM",owner:"Golmote"},gettext:{title:"gettext",alias:"po",owner:"RunDevelopment"},gherkin:{title:"Gherkin",owner:"hason"},git:{title:"Git",owner:"lgiraudel"},glsl:{title:"GLSL",require:"c",owner:"Golmote"},gn:{title:"GN",alias:"gni",owner:"RunDevelopment"},"linker-script":{title:"GNU Linker Script",alias:"ld",owner:"RunDevelopment"},go:{title:"Go",require:"clike",owner:"arnehormann"},"go-module":{title:"Go module",alias:"go-mod",owner:"RunDevelopment"},gradle:{title:"Gradle",require:"clike",owner:"zeabdelkhalek-badido18"},graphql:{title:"GraphQL",optional:"markdown",owner:"Golmote"},groovy:{title:"Groovy",require:"clike",owner:"robfletcher"},haml:{title:"Haml",require:"ruby",optional:["css","css-extras","coffeescript","erb","javascript","less","markdown","scss","textile"],owner:"Golmote"},handlebars:{title:"Handlebars",require:"markup-templating",alias:["hbs","mustache"],aliasTitles:{mustache:"Mustache"},owner:"Golmote"},haskell:{title:"Haskell",alias:"hs",owner:"bholst"},haxe:{title:"Haxe",require:"clike",optional:"regex",owner:"Golmote"},hcl:{title:"HCL",owner:"outsideris"},hlsl:{title:"HLSL",require:"c",owner:"RunDevelopment"},hoon:{title:"Hoon",owner:"matildepark"},http:{title:"HTTP",optional:["csp","css","hpkp","hsts","javascript","json","markup","uri"],owner:"danielgtaylor"},hpkp:{title:"HTTP Public-Key-Pins",owner:"ScottHelme"},hsts:{title:"HTTP Strict-Transport-Security",owner:"ScottHelme"},ichigojam:{title:"IchigoJam",owner:"BlueCocoa"},icon:{title:"Icon",owner:"Golmote"},"icu-message-format":{title:"ICU Message Format",owner:"RunDevelopment"},idris:{title:"Idris",alias:"idr",owner:"KeenS",require:"haskell"},ignore:{title:".ignore",owner:"osipxd",alias:["gitignore","hgignore","npmignore"],aliasTitles:{gitignore:".gitignore",hgignore:".hgignore",npmignore:".npmignore"}},inform7:{title:"Inform 7",owner:"Golmote"},ini:{title:"Ini",owner:"aviaryan"},io:{title:"Io",owner:"AlesTsurko"},j:{title:"J",owner:"Golmote"},java:{title:"Java",require:"clike",owner:"sherblot"},javadoc:{title:"JavaDoc",require:["markup","java","javadoclike"],modify:"java",optional:"scala",owner:"RunDevelopment"},javadoclike:{title:"JavaDoc-like",modify:["java","javascript","php"],owner:"RunDevelopment"},javastacktrace:{title:"Java stack trace",owner:"RunDevelopment"},jexl:{title:"Jexl",owner:"czosel"},jolie:{title:"Jolie",require:"clike",owner:"thesave"},jq:{title:"JQ",owner:"RunDevelopment"},jsdoc:{title:"JSDoc",require:["javascript","javadoclike","typescript"],modify:"javascript",optional:["actionscript","coffeescript"],owner:"RunDevelopment"},"js-extras":{title:"JS Extras",require:"javascript",modify:"javascript",optional:["actionscript","coffeescript","flow","n4js","typescript"],owner:"RunDevelopment"},json:{title:"JSON",alias:"webmanifest",aliasTitles:{webmanifest:"Web App Manifest"},owner:"CupOfTea696"},json5:{title:"JSON5",require:"json",owner:"RunDevelopment"},jsonp:{title:"JSONP",require:"json",owner:"RunDevelopment"},jsstacktrace:{title:"JS stack trace",owner:"sbrl"},"js-templates":{title:"JS Templates",require:"javascript",modify:"javascript",optional:["css","css-extras","graphql","markdown","markup","sql"],owner:"RunDevelopment"},julia:{title:"Julia",owner:"cdagnino"},keepalived:{title:"Keepalived Configure",owner:"dev-itsheng"},keyman:{title:"Keyman",owner:"mcdurdin"},kotlin:{title:"Kotlin",alias:["kt","kts"],aliasTitles:{kts:"Kotlin Script"},require:"clike",owner:"Golmote"},kumir:{title:"KuMir (\u041A\u0443\u041C\u0438\u0440)",alias:"kum",owner:"edukisto"},kusto:{title:"Kusto",owner:"RunDevelopment"},latex:{title:"LaTeX",alias:["tex","context"],aliasTitles:{tex:"TeX",context:"ConTeXt"},owner:"japborst"},latte:{title:"Latte",require:["clike","markup-templating","php"],owner:"nette"},less:{title:"Less",require:"css",optional:"css-extras",owner:"Golmote"},lilypond:{title:"LilyPond",require:"scheme",alias:"ly",owner:"RunDevelopment"},liquid:{title:"Liquid",require:"markup-templating",owner:"cinhtau"},lisp:{title:"Lisp",alias:["emacs","elisp","emacs-lisp"],owner:"JuanCaicedo"},livescript:{title:"LiveScript",owner:"Golmote"},llvm:{title:"LLVM IR",owner:"porglezomp"},log:{title:"Log file",optional:"javastacktrace",owner:"RunDevelopment"},lolcode:{title:"LOLCODE",owner:"Golmote"},lua:{title:"Lua",owner:"Golmote"},magma:{title:"Magma (CAS)",owner:"RunDevelopment"},makefile:{title:"Makefile",owner:"Golmote"},markdown:{title:"Markdown",require:"markup",optional:"yaml",alias:"md",owner:"Golmote"},"markup-templating":{title:"Markup templating",require:"markup",owner:"Golmote"},mata:{title:"Mata",owner:"RunDevelopment"},matlab:{title:"MATLAB",owner:"Golmote"},maxscript:{title:"MAXScript",owner:"RunDevelopment"},mel:{title:"MEL",owner:"Golmote"},mermaid:{title:"Mermaid",owner:"RunDevelopment"},metafont:{title:"METAFONT",owner:"LaeriExNihilo"},mizar:{title:"Mizar",owner:"Golmote"},mongodb:{title:"MongoDB",owner:"airs0urce",require:"javascript"},monkey:{title:"Monkey",owner:"Golmote"},moonscript:{title:"MoonScript",alias:"moon",owner:"RunDevelopment"},n1ql:{title:"N1QL",owner:"TMWilds"},n4js:{title:"N4JS",require:"javascript",optional:"jsdoc",alias:"n4jsd",owner:"bsmith-n4"},"nand2tetris-hdl":{title:"Nand To Tetris HDL",owner:"stephanmax"},naniscript:{title:"Naninovel Script",owner:"Elringus",alias:"nani"},nasm:{title:"NASM",owner:"rbmj"},neon:{title:"NEON",owner:"nette"},nevod:{title:"Nevod",owner:"nezaboodka"},nginx:{title:"nginx",owner:"volado"},nim:{title:"Nim",owner:"Golmote"},nix:{title:"Nix",owner:"Golmote"},nsis:{title:"NSIS",owner:"idleberg"},objectivec:{title:"Objective-C",require:"c",alias:"objc",owner:"uranusjr"},ocaml:{title:"OCaml",owner:"Golmote"},odin:{title:"Odin",owner:"edukisto"},opencl:{title:"OpenCL",require:"c",modify:["c","cpp"],owner:"Milania1"},openqasm:{title:"OpenQasm",alias:"qasm",owner:"RunDevelopment"},oz:{title:"Oz",owner:"Golmote"},parigp:{title:"PARI/GP",owner:"Golmote"},parser:{title:"Parser",require:"markup",owner:"Golmote"},pascal:{title:"Pascal",alias:"objectpascal",aliasTitles:{objectpascal:"Object Pascal"},owner:"Golmote"},pascaligo:{title:"Pascaligo",owner:"DefinitelyNotAGoat"},psl:{title:"PATROL Scripting Language",owner:"bertysentry"},pcaxis:{title:"PC-Axis",alias:"px",owner:"RunDevelopment"},peoplecode:{title:"PeopleCode",alias:"pcode",owner:"RunDevelopment"},perl:{title:"Perl",owner:"Golmote"},php:{title:"PHP",require:"markup-templating",owner:"milesj"},phpdoc:{title:"PHPDoc",require:["php","javadoclike"],modify:"php",owner:"RunDevelopment"},"php-extras":{title:"PHP Extras",require:"php",modify:"php",owner:"milesj"},"plant-uml":{title:"PlantUML",alias:"plantuml",owner:"RunDevelopment"},plsql:{title:"PL/SQL",require:"sql",owner:"Golmote"},powerquery:{title:"PowerQuery",alias:["pq","mscript"],owner:"peterbud"},powershell:{title:"PowerShell",owner:"nauzilus"},processing:{title:"Processing",require:"clike",owner:"Golmote"},prolog:{title:"Prolog",owner:"Golmote"},promql:{title:"PromQL",owner:"arendjr"},properties:{title:".properties",owner:"Golmote"},protobuf:{title:"Protocol Buffers",require:"clike",owner:"just-boris"},pug:{title:"Pug",require:["markup","javascript"],optional:["coffeescript","ejs","handlebars","less","livescript","markdown","scss","stylus","twig"],owner:"Golmote"},puppet:{title:"Puppet",owner:"Golmote"},pure:{title:"Pure",optional:["c","cpp","fortran"],owner:"Golmote"},purebasic:{title:"PureBasic",require:"clike",alias:"pbfasm",owner:"HeX0R101"},purescript:{title:"PureScript",require:"haskell",alias:"purs",owner:"sriharshachilakapati"},python:{title:"Python",alias:"py",owner:"multipetros"},qsharp:{title:"Q#",require:"clike",alias:"qs",owner:"fedonman"},q:{title:"Q (kdb+ database)",owner:"Golmote"},qml:{title:"QML",require:"javascript",owner:"RunDevelopment"},qore:{title:"Qore",require:"clike",owner:"temnroegg"},r:{title:"R",owner:"Golmote"},racket:{title:"Racket",require:"scheme",alias:"rkt",owner:"RunDevelopment"},cshtml:{title:"Razor C#",alias:"razor",require:["markup","csharp"],optional:["css","css-extras","javascript","js-extras"],owner:"RunDevelopment"},jsx:{title:"React JSX",require:["markup","javascript"],optional:["jsdoc","js-extras","js-templates"],owner:"vkbansal"},tsx:{title:"React TSX",require:["jsx","typescript"]},reason:{title:"Reason",require:"clike",owner:"Golmote"},regex:{title:"Regex",owner:"RunDevelopment"},rego:{title:"Rego",owner:"JordanSh"},renpy:{title:"Ren'py",alias:"rpy",owner:"HyuchiaDiego"},rescript:{title:"ReScript",alias:"res",owner:"vmarcosp"},rest:{title:"reST (reStructuredText)",owner:"Golmote"},rip:{title:"Rip",owner:"ravinggenius"},roboconf:{title:"Roboconf",owner:"Golmote"},robotframework:{title:"Robot Framework",alias:"robot",owner:"RunDevelopment"},ruby:{title:"Ruby",require:"clike",alias:"rb",owner:"samflores"},rust:{title:"Rust",owner:"Golmote"},sas:{title:"SAS",optional:["groovy","lua","sql"],owner:"Golmote"},sass:{title:"Sass (Sass)",require:"css",optional:"css-extras",owner:"Golmote"},scss:{title:"Sass (SCSS)",require:"css",optional:"css-extras",owner:"MoOx"},scala:{title:"Scala",require:"java",owner:"jozic"},scheme:{title:"Scheme",owner:"bacchus123"},"shell-session":{title:"Shell session",require:"bash",alias:["sh-session","shellsession"],owner:"RunDevelopment"},smali:{title:"Smali",owner:"RunDevelopment"},smalltalk:{title:"Smalltalk",owner:"Golmote"},smarty:{title:"Smarty",require:"markup-templating",optional:"php",owner:"Golmote"},sml:{title:"SML",alias:"smlnj",aliasTitles:{smlnj:"SML/NJ"},owner:"RunDevelopment"},solidity:{title:"Solidity (Ethereum)",alias:"sol",require:"clike",owner:"glachaud"},"solution-file":{title:"Solution file",alias:"sln",owner:"RunDevelopment"},soy:{title:"Soy (Closure Template)",require:"markup-templating",owner:"Golmote"},sparql:{title:"SPARQL",require:"turtle",owner:"Triply-Dev",alias:"rq"},"splunk-spl":{title:"Splunk SPL",owner:"RunDevelopment"},sqf:{title:"SQF: Status Quo Function (Arma 3)",require:"clike",owner:"RunDevelopment"},sql:{title:"SQL",owner:"multipetros"},squirrel:{title:"Squirrel",require:"clike",owner:"RunDevelopment"},stan:{title:"Stan",owner:"RunDevelopment"},stata:{title:"Stata Ado",require:["mata","java","python"],owner:"RunDevelopment"},iecst:{title:"Structured Text (IEC 61131-3)",owner:"serhioromano"},stylus:{title:"Stylus",owner:"vkbansal"},supercollider:{title:"SuperCollider",alias:"sclang",owner:"RunDevelopment"},swift:{title:"Swift",owner:"chrischares"},systemd:{title:"Systemd configuration file",owner:"RunDevelopment"},"t4-templating":{title:"T4 templating",owner:"RunDevelopment"},"t4-cs":{title:"T4 Text Templates (C#)",require:["t4-templating","csharp"],alias:"t4",owner:"RunDevelopment"},"t4-vb":{title:"T4 Text Templates (VB)",require:["t4-templating","vbnet"],owner:"RunDevelopment"},tap:{title:"TAP",owner:"isaacs",require:"yaml"},tcl:{title:"Tcl",owner:"PeterChaplin"},tt2:{title:"Template Toolkit 2",require:["clike","markup-templating"],owner:"gflohr"},textile:{title:"Textile",require:"markup",optional:"css",owner:"Golmote"},toml:{title:"TOML",owner:"RunDevelopment"},tremor:{title:"Tremor",alias:["trickle","troy"],owner:"darach",aliasTitles:{trickle:"trickle",troy:"troy"}},turtle:{title:"Turtle",alias:"trig",aliasTitles:{trig:"TriG"},owner:"jakubklimek"},twig:{title:"Twig",require:"markup-templating",owner:"brandonkelly"},typescript:{title:"TypeScript",require:"javascript",optional:"js-templates",alias:"ts",owner:"vkbansal"},typoscript:{title:"TypoScript",alias:"tsconfig",aliasTitles:{tsconfig:"TSConfig"},owner:"dkern"},unrealscript:{title:"UnrealScript",alias:["uscript","uc"],owner:"RunDevelopment"},uorazor:{title:"UO Razor Script",owner:"jaseowns"},uri:{title:"URI",alias:"url",aliasTitles:{url:"URL"},owner:"RunDevelopment"},v:{title:"V",require:"clike",owner:"taggon"},vala:{title:"Vala",require:"clike",optional:"regex",owner:"TemplarVolk"},vbnet:{title:"VB.Net",require:"basic",owner:"Bigsby"},velocity:{title:"Velocity",require:"markup",owner:"Golmote"},verilog:{title:"Verilog",owner:"a-rey"},vhdl:{title:"VHDL",owner:"a-rey"},vim:{title:"vim",owner:"westonganger"},"visual-basic":{title:"Visual Basic",alias:["vb","vba"],aliasTitles:{vba:"VBA"},owner:"Golmote"},warpscript:{title:"WarpScript",owner:"RunDevelopment"},wasm:{title:"WebAssembly",owner:"Golmote"},"web-idl":{title:"Web IDL",alias:"webidl",owner:"RunDevelopment"},wgsl:{title:"WGSL",owner:"Dr4gonthree"},wiki:{title:"Wiki markup",require:"markup",owner:"Golmote"},wolfram:{title:"Wolfram language",alias:["mathematica","nb","wl"],aliasTitles:{mathematica:"Mathematica",nb:"Mathematica Notebook"},owner:"msollami"},wren:{title:"Wren",owner:"clsource"},xeora:{title:"Xeora",require:"markup",alias:"xeoracube",aliasTitles:{xeoracube:"XeoraCube"},owner:"freakmaxi"},"xml-doc":{title:"XML doc (.net)",require:"markup",modify:["csharp","fsharp","vbnet"],owner:"RunDevelopment"},xojo:{title:"Xojo (REALbasic)",owner:"Golmote"},xquery:{title:"XQuery",require:"markup",owner:"Golmote"},yaml:{title:"YAML",alias:"yml",owner:"hason"},yang:{title:"YANG",owner:"RunDevelopment"},zig:{title:"Zig",owner:"RunDevelopment"}},plugins:{meta:{path:"plugins/{id}/prism-{id}",link:"plugins/{id}/"},"line-highlight":{title:"Line Highlight",description:"Highlights specific lines and/or line ranges."},"line-numbers":{title:"Line Numbers",description:"Line number at the beginning of code lines.",owner:"kuba-kubula"},"show-invisibles":{title:"Show Invisibles",description:"Show hidden characters such as tabs and line breaks.",optional:["autolinker","data-uri-highlight"]},autolinker:{title:"Autolinker",description:"Converts URLs and emails in code to clickable links. Parses Markdown links in comments."},wpd:{title:"WebPlatform Docs",description:'Makes tokens link to <a href="https://webplatform.github.io/docs/">WebPlatform.org documentation</a>. The links open in a new tab.'},"custom-class":{title:"Custom Class",description:"This plugin allows you to prefix Prism's default classes (<code>.comment</code> can become <code>.namespace--comment</code>) or replace them with your defined ones (like <code>.editor__comment</code>). You can even add new classes.",owner:"dvkndn",noCSS:!0},"file-highlight":{title:"File Highlight",description:"Fetch external files and highlight them with Prism. Used on the Prism website itself.",noCSS:!0},"show-language":{title:"Show Language",description:"Display the highlighted language in code blocks (inline code does not show the label).",owner:"nauzilus",noCSS:!0,require:"toolbar"},"jsonp-highlight":{title:"JSONP Highlight",description:"Fetch content with JSONP and highlight some interesting content (e.g. GitHub/Gists or Bitbucket API).",noCSS:!0,owner:"nauzilus"},"highlight-keywords":{title:"Highlight Keywords",description:"Adds special CSS classes for each keyword for fine-grained highlighting.",owner:"vkbansal",noCSS:!0},"remove-initial-line-feed":{title:"Remove initial line feed",description:"Removes the initial line feed in code blocks.",owner:"Golmote",noCSS:!0},"inline-color":{title:"Inline color",description:"Adds a small inline preview for colors in style sheets.",require:"css-extras",owner:"RunDevelopment"},previewers:{title:"Previewers",description:"Previewers for angles, colors, gradients, easing and time.",require:"css-extras",owner:"Golmote"},autoloader:{title:"Autoloader",description:"Automatically loads the needed languages to highlight the code blocks.",owner:"Golmote",noCSS:!0},"keep-markup":{title:"Keep Markup",description:"Prevents custom markup from being dropped out during highlighting.",owner:"Golmote",optional:"normalize-whitespace",noCSS:!0},"command-line":{title:"Command Line",description:"Display a command line with a prompt and, optionally, the output/response from the commands.",owner:"chriswells0"},"unescaped-markup":{title:"Unescaped Markup",description:"Write markup without having to escape anything."},"normalize-whitespace":{title:"Normalize Whitespace",description:"Supports multiple operations to normalize whitespace in code blocks.",owner:"zeitgeist87",optional:"unescaped-markup",noCSS:!0},"data-uri-highlight":{title:"Data-URI Highlight",description:"Highlights data-URI contents.",owner:"Golmote",noCSS:!0},toolbar:{title:"Toolbar",description:"Attach a toolbar for plugins to easily register buttons on the top of a code block.",owner:"mAAdhaTTah"},"copy-to-clipboard":{title:"Copy to Clipboard Button",description:"Add a button that copies the code block to the clipboard when clicked.",owner:"mAAdhaTTah",require:"toolbar",noCSS:!0},"download-button":{title:"Download Button",description:"A button in the toolbar of a code block adding a convenient way to download a code file.",owner:"Golmote",require:"toolbar",noCSS:!0},"match-braces":{title:"Match braces",description:"Highlights matching braces.",owner:"RunDevelopment"},"diff-highlight":{title:"Diff Highlight",description:"Highlights the code inside diff blocks.",owner:"RunDevelopment",require:"diff"},"filter-highlight-all":{title:"Filter highlightAll",description:"Filters the elements the <code>highlightAll</code> and <code>highlightAllUnder</code> methods actually highlight.",owner:"RunDevelopment",noCSS:!0},treeview:{title:"Treeview",description:"A language with special styles to highlight file system tree structures.",owner:"Golmote"}}})},8722(e,t,n){let r=n(6969),a=n(8380),o=new Set;function i(e){void 0===e?e=Object.keys(r.languages).filter(e=>"meta"!=e):Array.isArray(e)||(e=[e]),a(r,e,[...o,...Object.keys(Prism.languages)]).load(e=>{if(!(e in r.languages)){i.silent||console.warn("Language does not exist: "+e);return}let t="./prism-"+e;delete n.c[n(3157).resolve(t)],delete Prism.languages[e],n(3157)(t),o.add(e)})}i.silent=!1,e.exports=i},9700(){var e=Prism;function t(e,t){return"___"+e.toUpperCase()+t+"___"}Object.defineProperties(e.languages["markup-templating"]={},{buildPlaceholders:{value:function(n,r,a,o){if(n.language===r){var i=n.tokenStack=[];n.code=n.code.replace(a,function(e){if("function"==typeof o&&!o(e))return e;for(var a,l=i.length;-1!==n.code.indexOf(a=t(r,l));)++l;return i[l]=e,a}),n.grammar=e.languages.markup}}},tokenizePlaceholders:{value:function(n,r){if(n.language===r&&n.tokenStack){n.grammar=e.languages[r];var a=0,o=Object.keys(n.tokenStack);!function i(l){for(var s=0;s<l.length&&!(a>=o.length);s++){var c=l[s];if("string"==typeof c||c.content&&"string"==typeof c.content){var u=o[a],d=n.tokenStack[u],p="string"==typeof c?c:c.content,f=t(r,u),m=p.indexOf(f);if(m>-1){++a;var g=p.substring(0,m),h=new e.Token(r,e.tokenize(d,n.grammar),"language-"+r,d),b=p.substring(m+f.length),y=[];g&&y.push.apply(y,i([g])),y.push(h),b&&y.push.apply(y,i([b])),"string"==typeof c?l.splice.apply(l,[s,1].concat(y)):c.content=y}}else c.content&&i(c.content)}return l}(n.tokens)}}}})},3157(e,t,n){var r={"./":"8722"};function a(e){return n(o(e))}function o(e){if(!n.o(r,e)){var t=Error("Cannot find module '"+e+"'");throw t.code="MODULE_NOT_FOUND",t}return r[e]}a.keys=function(){return Object.keys(r)},a.resolve=o,e.exports=a,a.id=3157},8380(e){"use strict";e.exports=function(){var e=function(){};function t(e,t){Array.isArray(e)?e.forEach(t):null!=e&&t(e,0)}function n(e){for(var t={},n=0,r=e.length;n<r;n++)t[e[n]]=!0;return t}return function(r,a,o){var i,l,s,c,u=function(e){var t={};for(var n in e){var r=e[n];for(var a in r)if("meta"!=a){var o=r[a];t[a]="string"==typeof o?{title:o}:o}}return t}(r),d=function(e){if(e in u)return e;if(!i)for(var n in i={},u){var r=u[n];t(r&&r.alias,function(e){if(e in i)throw Error(e+" cannot be alias for both "+n+" and "+i[e]);if(e in u)throw Error(e+" cannot be alias of "+n+" because it is a component.");i[e]=n})}return i[e]||e};a=a.map(d),o=(o||[]).map(d);var p=n(a),f=n(o);a.forEach(function e(n){var r=u[n];t(r&&r.require,function(t){t in f||(p[t]=!0,e(t))})});for(var m=(l={},s=[],function(e){var n=l[e];return n||(!function e(n,r){if(!(n in l)){r.push(n);var a=r.indexOf(n);if(a<r.length-1)throw Error("Circular dependency: "+r.slice(a).join(" -> "));var o={},i=u[n];if(i){function s(t){if(!(t in u))throw Error(n+" depends on an unknown component "+t);if(!(t in o))for(var a in e(t,r),o[t]=!0,l[t])o[a]=!0}t(i.require,s),t(i.optional,s),t(i.modify,s)}l[n]=o,r.pop()}}(e,s),n=l[e]),n}),g=p;function(e){for(var t in e)return!0;return!1}(g);){for(var h in c={},g){var b=u[h];t(b&&b.modify,function(e){e in f&&(c[e]=!0)})}for(var y in f)if(!(y in p)){for(var v in m(y))if(v in p){c[y]=!0;break}}for(var w in g=c)p[w]=!0}var k={getIds:function(){var e=[];return k.load(function(t){e.push(t)}),e},load:function(t,n){return function(t,n,r,a){var o=a?a.series:void 0,i=a?a.parallel:e,l={},s={};for(var c in n)!function e(a){if(a in l)return l[a];s[a]=!0;var c,u=[];for(var d in t(a))d in n&&u.push(d);if(0===u.length)c=r(a);else{var p=i(u.map(function(t){var n=e(t);return delete s[t],n}));o?c=o(p,function(){return r(a)}):r(a)}return l[a]=c}(c);var u=[];for(var d in s)u.push(l[d]);return i(u)}(m,p,t,n)}};return k}}()},2694(e,t,n){"use strict";var r=n(6925);function a(){}function o(){}o.resetWarningCache=a,e.exports=function(){function e(e,t,n,a,o,i){if(i!==r){var l=Error("Calling PropTypes validators directly is not supported by the `prop-types` package. Use PropTypes.checkPropTypes() to call them. Read more at http://fb.me/use-check-prop-types");throw l.name="Invariant Violation",l}}function t(){return e}e.isRequired=e;var n={array:e,bigint:e,bool:e,func:e,number:e,object:e,string:e,symbol:e,any:e,arrayOf:t,element:e,elementType:e,instanceOf:t,node:e,objectOf:t,oneOf:t,oneOfType:t,shape:t,exact:t,checkPropTypes:o,resetWarningCache:a};return n.PropTypes=n,n}},5556(e,t,n){e.exports=n(2694)()},6925(e){"use strict";e.exports="SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED"},1247(e,t,n){"use strict";var r,a=n(9982),o=n(6540),i=n(961);function l(e){var t="https://react.dev/errors/"+e;if(1<arguments.length){t+="?args[]="+encodeURIComponent(arguments[1]);for(var n=2;n<arguments.length;n++)t+="&args[]="+encodeURIComponent(arguments[n])}return"Minified React error #"+e+"; visit "+t+" for the full message or use the non-minified dev environment for full errors and additional helpful warnings."}function s(e){return!(!e||1!==e.nodeType&&9!==e.nodeType&&11!==e.nodeType)}function c(e){var t=e,n=e;if(e.alternate)for(;t.return;)t=t.return;else{e=t;do 0!=(4098&(t=e).flags)&&(n=t.return),e=t.return;while(e)}return 3===t.tag?n:null}function u(e){if(13===e.tag){var t=e.memoizedState;if(null===t&&null!==(e=e.alternate)&&(t=e.memoizedState),null!==t)return t.dehydrated}return null}function d(e){if(31===e.tag){var t=e.memoizedState;if(null===t&&null!==(e=e.alternate)&&(t=e.memoizedState),null!==t)return t.dehydrated}return null}function p(e){if(c(e)!==e)throw Error(l(188))}var f=Object.assign,m=Symbol.for("react.element"),g=Symbol.for("react.transitional.element"),h=Symbol.for("react.portal"),b=Symbol.for("react.fragment"),y=Symbol.for("react.strict_mode"),v=Symbol.for("react.profiler"),w=Symbol.for("react.consumer"),k=Symbol.for("react.context"),x=Symbol.for("react.forward_ref"),S=Symbol.for("react.suspense"),E=Symbol.for("react.suspense_list"),_=Symbol.for("react.memo"),A=Symbol.for("react.lazy");Symbol.for("react.scope");var C=Symbol.for("react.activity");Symbol.for("react.legacy_hidden"),Symbol.for("react.tracing_marker");var T=Symbol.for("react.memo_cache_sentinel");Symbol.for("react.view_transition");var j=Symbol.iterator;function P(e){return null===e||"object"!=typeof e?null:"function"==typeof(e=j&&e[j]||e["@@iterator"])?e:null}var z=Symbol.for("react.client.reference"),L=Array.isArray,N=o.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE,O=i.__DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE,R={pending:!1,data:null,method:null,action:null},D=[],F=-1;function I(e){return{current:e}}function M(e){0>F||(e.current=D[F],D[F]=null,F--)}function $(e,t){D[++F]=e.current,e.current=t}var B=I(null),U=I(null),q=I(null),H=I(null);function G(e,t){switch($(q,t),$(U,e),$(B,null),t.nodeType){case 9:case 11:e=(e=t.documentElement)&&(e=e.namespaceURI)?cy(e):0;break;default:if(e=t.tagName,t=t.namespaceURI)e=cv(t=cy(t),e);else switch(e){case"svg":e=1;break;case"math":e=2;break;default:e=0}}M(B),$(B,e)}function V(){M(B),M(U),M(q)}function W(e){null!==e.memoizedState&&$(H,e);var t=B.current,n=cv(t,e.type);t!==n&&($(U,e),$(B,n))}function Q(e){U.current===e&&(M(B),M(U)),H.current===e&&(M(H),ut._currentValue=R)}function K(e){if(void 0===tF)try{throw Error()}catch(e){var t=e.stack.trim().match(/\n( *(at )?)/);tF=t&&t[1]||"",tI=-1<e.stack.indexOf("\n at")?" (<anonymous>)":-1<e.stack.indexOf("@")?"@unknown:0:0":""}return"\n"+tF+e+tI}var Y=!1;function Z(e,t){if(!e||Y)return"";Y=!0;var n=Error.prepareStackTrace;Error.prepareStackTrace=void 0;try{var r={DetermineComponentFrameRoot:function(){try{if(t){var n=function(){throw Error()};if(Object.defineProperty(n.prototype,"props",{set:function(){throw Error()}}),"object"==typeof Reflect&&Reflect.construct){try{Reflect.construct(n,[])}catch(e){var r=e}Reflect.construct(e,[],n)}else{try{n.call()}catch(e){r=e}e.call(n.prototype)}}else{try{throw Error()}catch(e){r=e}(n=e())&&"function"==typeof n.catch&&n.catch(function(){})}}catch(e){if(e&&r&&"string"==typeof e.stack)return[e.stack,r.stack]}return[null,null]}};r.DetermineComponentFrameRoot.displayName="DetermineComponentFrameRoot";var a=Object.getOwnPropertyDescriptor(r.DetermineComponentFrameRoot,"name");a&&a.configurable&&Object.defineProperty(r.DetermineComponentFrameRoot,"name",{value:"DetermineComponentFrameRoot"});var o=r.DetermineComponentFrameRoot(),i=o[0],l=o[1];if(i&&l){var s=i.split("\n"),c=l.split("\n");for(a=r=0;r<s.length&&!s[r].includes("DetermineComponentFrameRoot");)r++;for(;a<c.length&&!c[a].includes("DetermineComponentFrameRoot");)a++;if(r===s.length||a===c.length)for(r=s.length-1,a=c.length-1;1<=r&&0<=a&&s[r]!==c[a];)a--;for(;1<=r&&0<=a;r--,a--)if(s[r]!==c[a]){if(1!==r||1!==a)do if(r--,a--,0>a||s[r]!==c[a]){var u="\n"+s[r].replace(" at new "," at ");return e.displayName&&u.includes("<anonymous>")&&(u=u.replace("<anonymous>",e.displayName)),u}while(1<=r&&0<=a)break}}}finally{Y=!1,Error.prepareStackTrace=n}return(n=e?e.displayName||e.name:"")?K(n):""}function X(e){try{var t="",n=null;do t+=function(e,t){switch(e.tag){case 26:case 27:case 5:return K(e.type);case 16:return K("Lazy");case 13:return e.child!==t&&null!==t?K("Suspense Fallback"):K("Suspense");case 19:return K("SuspenseList");case 0:case 15:return Z(e.type,!1);case 11:return Z(e.type.render,!1);case 1:return Z(e.type,!0);case 31:return K("Activity");default:return""}}(e,n),n=e,e=e.return;while(e)return t}catch(e){return"\nError generating stack: "+e.message+"\n"+e.stack}}var J=Object.prototype.hasOwnProperty,ee=a.unstable_scheduleCallback,et=a.unstable_cancelCallback,en=a.unstable_shouldYield,er=a.unstable_requestPaint,ea=a.unstable_now,eo=a.unstable_getCurrentPriorityLevel,ei=a.unstable_ImmediatePriority,el=a.unstable_UserBlockingPriority,es=a.unstable_NormalPriority,ec=a.unstable_LowPriority,eu=a.unstable_IdlePriority,ed=a.log,ep=a.unstable_setDisableYieldValue,ef=null,em=null;function eg(e){if("function"==typeof ed&&ep(e),em&&"function"==typeof em.setStrictMode)try{em.setStrictMode(ef,e)}catch(e){}}var eh=Math.clz32?Math.clz32:function(e){return 0==(e>>>=0)?32:31-(eb(e)/ey|0)|0},eb=Math.log,ey=Math.LN2,ev=256,ew=262144,ek=4194304;function ex(e){var t=42&e;if(0!==t)return t;switch(e&-e){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:return 64;case 128:return 128;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:return 261888&e;case 262144:case 524288:case 1048576:case 2097152:return 3932160&e;case 4194304:case 8388608:case 0x1000000:case 0x2000000:return 0x3c00000&e;case 0x4000000:return 0x4000000;case 0x8000000:return 0x8000000;case 0x10000000:return 0x10000000;case 0x20000000:return 0x20000000;case 0x40000000:return 0;default:return e}}function eS(e,t,n){var r=e.pendingLanes;if(0===r)return 0;var a=0,o=e.suspendedLanes,i=e.pingedLanes;e=e.warmLanes;var l=0x7ffffff&r;return 0!==l?0!=(r=l&~o)?a=ex(r):0!=(i&=l)?a=ex(i):n||0!=(n=l&~e)&&(a=ex(n)):0!=(l=r&~o)?a=ex(l):0!==i?a=ex(i):n||0!=(n=r&~e)&&(a=ex(n)),0===a?0:0!==t&&t!==a&&0==(t&o)&&((o=a&-a)>=(n=t&-t)||32===o&&0!=(4194048&n))?t:a}function eE(e,t){return 0==(e.pendingLanes&~(e.suspendedLanes&~e.pingedLanes)&t)}function e_(){var e=ek;return 0==(0x3c00000&(ek<<=1))&&(ek=4194304),e}function eA(e){for(var t=[],n=0;31>n;n++)t.push(e);return t}function eC(e,t){e.pendingLanes|=t,0x10000000!==t&&(e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0)}function eT(e,t,n){e.pendingLanes|=t,e.suspendedLanes&=~t;var r=31-eh(t);e.entangledLanes|=t,e.entanglements[r]=0x40000000|e.entanglements[r]|261930&n}function ej(e,t){var n=e.entangledLanes|=t;for(e=e.entanglements;n;){var r=31-eh(n),a=1<<r;a&t|e[r]&t&&(e[r]|=t),n&=~a}}function eP(e,t){var n=t&-t;return 0!=((n=0!=(42&n)?1:ez(n))&(e.suspendedLanes|t))?0:n}function ez(e){switch(e){case 2:e=1;break;case 8:e=4;break;case 32:e=16;break;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:case 4194304:case 8388608:case 0x1000000:case 0x2000000:e=128;break;case 0x10000000:e=0x8000000;break;default:e=0}return e}function eL(e){return 2<(e&=-e)?8<e?0!=(0x7ffffff&e)?32:0x10000000:8:2}function eN(){var e=O.p;return 0!==e?e:void 0===(e=window.event)?32:uh(e.type)}function eO(e,t){var n=O.p;try{return O.p=e,t()}finally{O.p=n}}var eR=Math.random().toString(36).slice(2),eD="__reactFiber$"+eR,eF="__reactProps$"+eR,eI="__reactContainer$"+eR,eM="__reactEvents$"+eR,e$="__reactListeners$"+eR,eB="__reactHandles$"+eR,eU="__reactResources$"+eR,eq="__reactMarker$"+eR;function eH(e){delete e[eD],delete e[eF],delete e[eM],delete e[e$],delete e[eB]}function eG(e){var t=e[eD];if(t)return t;for(var n=e.parentNode;n;){if(t=n[eI]||n[eD]){if(n=t.alternate,null!==t.child||null!==n&&null!==n.child)for(e=cF(e);null!==e;){if(n=e[eD])return n;e=cF(e)}return t}n=(e=n).parentNode}return null}function eV(e){if(e=e[eD]||e[eI]){var t=e.tag;if(5===t||6===t||13===t||31===t||26===t||27===t||3===t)return e}return null}function eW(e){var t=e.tag;if(5===t||26===t||27===t||6===t)return e.stateNode;throw Error(l(33))}function eQ(e){var t=e[eU];return t||(t=e[eU]={hoistableStyles:new Map,hoistableScripts:new Map}),t}function eK(e){e[eq]=!0}var eY=new Set,eZ={};function eX(e,t){eJ(e,t),eJ(e+"Capture",t)}function eJ(e,t){for(eZ[e]=t,e=0;e<t.length;e++)eY.add(t[e])}var e0=RegExp("^[:A-Z_a-z\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF\\u200C-\\u200D\\u2070-\\u218F\\u2C00-\\u2FEF\\u3001-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFFD][:A-Z_a-z\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF\\u200C-\\u200D\\u2070-\\u218F\\u2C00-\\u2FEF\\u3001-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFFD\\-.0-9\\u00B7\\u0300-\\u036F\\u203F-\\u2040]*$"),e1={},e2={};function e4(e,t,n){if(J.call(e2,t)||!J.call(e1,t)&&(e0.test(t)?e2[t]=!0:(e1[t]=!0,!1)))if(null===n)e.removeAttribute(t);else{switch(typeof n){case"undefined":case"function":case"symbol":e.removeAttribute(t);return;case"boolean":var r=t.toLowerCase().slice(0,5);if("data-"!==r&&"aria-"!==r)return void e.removeAttribute(t)}e.setAttribute(t,""+n)}}function e3(e,t,n){if(null===n)e.removeAttribute(t);else{switch(typeof n){case"undefined":case"function":case"symbol":case"boolean":e.removeAttribute(t);return}e.setAttribute(t,""+n)}}function e8(e,t,n,r){if(null===r)e.removeAttribute(n);else{switch(typeof r){case"undefined":case"function":case"symbol":case"boolean":e.removeAttribute(n);return}e.setAttributeNS(t,n,""+r)}}function e6(e){switch(typeof e){case"bigint":case"boolean":case"number":case"string":case"undefined":case"object":return e;default:return""}}function e9(e){var t=e.type;return(e=e.nodeName)&&"input"===e.toLowerCase()&&("checkbox"===t||"radio"===t)}function e5(e){if(!e._valueTracker){var t=e9(e)?"checked":"value";e._valueTracker=function(e,t,n){var r=Object.getOwnPropertyDescriptor(e.constructor.prototype,t);if(!e.hasOwnProperty(t)&&void 0!==r&&"function"==typeof r.get&&"function"==typeof r.set){var a=r.get,o=r.set;return Object.defineProperty(e,t,{configurable:!0,get:function(){return a.call(this)},set:function(e){n=""+e,o.call(this,e)}}),Object.defineProperty(e,t,{enumerable:r.enumerable}),{getValue:function(){return n},setValue:function(e){n=""+e},stopTracking:function(){e._valueTracker=null,delete e[t]}}}}(e,t,""+e[t])}}function e7(e){if(!e)return!1;var t=e._valueTracker;if(!t)return!0;var n=t.getValue(),r="";return e&&(r=e9(e)?e.checked?"true":"false":e.value),(e=r)!==n&&(t.setValue(e),!0)}function te(e){if(void 0===(e=e||("u">typeof document?document:void 0)))return null;try{return e.activeElement||e.body}catch(t){return e.body}}var tt=/[\n"\\]/g;function tn(e){return e.replace(tt,function(e){return"\\"+e.charCodeAt(0).toString(16)+" "})}function tr(e,t,n,r,a,o,i,l){e.name="",null!=i&&"function"!=typeof i&&"symbol"!=typeof i&&"boolean"!=typeof i?e.type=i:e.removeAttribute("type"),null!=t?"number"===i?(0===t&&""===e.value||e.value!=t)&&(e.value=""+e6(t)):e.value!==""+e6(t)&&(e.value=""+e6(t)):"submit"!==i&&"reset"!==i||e.removeAttribute("value"),null!=t?to(e,i,e6(t)):null!=n?to(e,i,e6(n)):null!=r&&e.removeAttribute("value"),null==a&&null!=o&&(e.defaultChecked=!!o),null!=a&&(e.checked=a&&"function"!=typeof a&&"symbol"!=typeof a),null!=l&&"function"!=typeof l&&"symbol"!=typeof l&&"boolean"!=typeof l?e.name=""+e6(l):e.removeAttribute("name")}function ta(e,t,n,r,a,o,i,l){if(null!=o&&"function"!=typeof o&&"symbol"!=typeof o&&"boolean"!=typeof o&&(e.type=o),null!=t||null!=n){if(("submit"===o||"reset"===o)&&null==t)return void e5(e);n=null!=n?""+e6(n):"",t=null!=t?""+e6(t):n,l||t===e.value||(e.value=t),e.defaultValue=t}r="function"!=typeof(r=null!=r?r:a)&&"symbol"!=typeof r&&!!r,e.checked=l?e.checked:!!r,e.defaultChecked=!!r,null!=i&&"function"!=typeof i&&"symbol"!=typeof i&&"boolean"!=typeof i&&(e.name=i),e5(e)}function to(e,t,n){"number"===t&&te(e.ownerDocument)===e||e.defaultValue===""+n||(e.defaultValue=""+n)}function ti(e,t,n,r){if(e=e.options,t){t={};for(var a=0;a<n.length;a++)t["$"+n[a]]=!0;for(n=0;n<e.length;n++)a=t.hasOwnProperty("$"+e[n].value),e[n].selected!==a&&(e[n].selected=a),a&&r&&(e[n].defaultSelected=!0)}else{for(a=0,n=""+e6(n),t=null;a<e.length;a++){if(e[a].value===n){e[a].selected=!0,r&&(e[a].defaultSelected=!0);return}null!==t||e[a].disabled||(t=e[a])}null!==t&&(t.selected=!0)}}function tl(e,t,n){if(null!=t&&((t=""+e6(t))!==e.value&&(e.value=t),null==n)){e.defaultValue!==t&&(e.defaultValue=t);return}e.defaultValue=null!=n?""+e6(n):""}function ts(e,t,n,r){if(null==t){if(null!=r){if(null!=n)throw Error(l(92));if(L(r)){if(1<r.length)throw Error(l(93));r=r[0]}n=r}null==n&&(n=""),t=n}e.defaultValue=n=e6(t),(r=e.textContent)===n&&""!==r&&null!==r&&(e.value=r),e5(e)}function tc(e,t){if(t){var n=e.firstChild;if(n&&n===e.lastChild&&3===n.nodeType){n.nodeValue=t;return}}e.textContent=t}var tu=new Set("animationIterationCount aspectRatio borderImageOutset borderImageSlice borderImageWidth boxFlex boxFlexGroup boxOrdinalGroup columnCount columns flex flexGrow flexPositive flexShrink flexNegative flexOrder gridArea gridRow gridRowEnd gridRowSpan gridRowStart gridColumn gridColumnEnd gridColumnSpan gridColumnStart fontWeight lineClamp lineHeight opacity order orphans scale tabSize widows zIndex zoom fillOpacity floodOpacity stopOpacity strokeDasharray strokeDashoffset strokeMiterlimit strokeOpacity strokeWidth MozAnimationIterationCount MozBoxFlex MozBoxFlexGroup MozLineClamp msAnimationIterationCount msFlex msZoom msFlexGrow msFlexNegative msFlexOrder msFlexPositive msFlexShrink msGridColumn msGridColumnSpan msGridRow msGridRowSpan WebkitAnimationIterationCount WebkitBoxFlex WebKitBoxFlexGroup WebkitBoxOrdinalGroup WebkitColumnCount WebkitColumns WebkitFlex WebkitFlexGrow WebkitFlexPositive WebkitFlexShrink WebkitLineClamp".split(" "));function td(e,t,n){var r=0===t.indexOf("--");null==n||"boolean"==typeof n||""===n?r?e.setProperty(t,""):"float"===t?e.cssFloat="":e[t]="":r?e.setProperty(t,n):"number"!=typeof n||0===n||tu.has(t)?"float"===t?e.cssFloat=n:e[t]=(""+n).trim():e[t]=n+"px"}function tp(e,t,n){if(null!=t&&"object"!=typeof t)throw Error(l(62));if(e=e.style,null!=n){for(var r in n)!n.hasOwnProperty(r)||null!=t&&t.hasOwnProperty(r)||(0===r.indexOf("--")?e.setProperty(r,""):"float"===r?e.cssFloat="":e[r]="");for(var a in t)r=t[a],t.hasOwnProperty(a)&&n[a]!==r&&td(e,a,r)}else for(var o in t)t.hasOwnProperty(o)&&td(e,o,t[o])}function tf(e){if(-1===e.indexOf("-"))return!1;switch(e){case"annotation-xml":case"color-profile":case"font-face":case"font-face-src":case"font-face-uri":case"font-face-format":case"font-face-name":case"missing-glyph":return!1;default:return!0}}var tm=new Map([["acceptCharset","accept-charset"],["htmlFor","for"],["httpEquiv","http-equiv"],["crossOrigin","crossorigin"],["accentHeight","accent-height"],["alignmentBaseline","alignment-baseline"],["arabicForm","arabic-form"],["baselineShift","baseline-shift"],["capHeight","cap-height"],["clipPath","clip-path"],["clipRule","clip-rule"],["colorInterpolation","color-interpolation"],["colorInterpolationFilters","color-interpolation-filters"],["colorProfile","color-profile"],["colorRendering","color-rendering"],["dominantBaseline","dominant-baseline"],["enableBackground","enable-background"],["fillOpacity","fill-opacity"],["fillRule","fill-rule"],["floodColor","flood-color"],["floodOpacity","flood-opacity"],["fontFamily","font-family"],["fontSize","font-size"],["fontSizeAdjust","font-size-adjust"],["fontStretch","font-stretch"],["fontStyle","font-style"],["fontVariant","font-variant"],["fontWeight","font-weight"],["glyphName","glyph-name"],["glyphOrientationHorizontal","glyph-orientation-horizontal"],["glyphOrientationVertical","glyph-orientation-vertical"],["horizAdvX","horiz-adv-x"],["horizOriginX","horiz-origin-x"],["imageRendering","image-rendering"],["letterSpacing","letter-spacing"],["lightingColor","lighting-color"],["markerEnd","marker-end"],["markerMid","marker-mid"],["markerStart","marker-start"],["overlinePosition","overline-position"],["overlineThickness","overline-thickness"],["paintOrder","paint-order"],["panose-1","panose-1"],["pointerEvents","pointer-events"],["renderingIntent","rendering-intent"],["shapeRendering","shape-rendering"],["stopColor","stop-color"],["stopOpacity","stop-opacity"],["strikethroughPosition","strikethrough-position"],["strikethroughThickness","strikethrough-thickness"],["strokeDasharray","stroke-dasharray"],["strokeDashoffset","stroke-dashoffset"],["strokeLinecap","stroke-linecap"],["strokeLinejoin","stroke-linejoin"],["strokeMiterlimit","stroke-miterlimit"],["strokeOpacity","stroke-opacity"],["strokeWidth","stroke-width"],["textAnchor","text-anchor"],["textDecoration","text-decoration"],["textRendering","text-rendering"],["transformOrigin","transform-origin"],["underlinePosition","underline-position"],["underlineThickness","underline-thickness"],["unicodeBidi","unicode-bidi"],["unicodeRange","unicode-range"],["unitsPerEm","units-per-em"],["vAlphabetic","v-alphabetic"],["vHanging","v-hanging"],["vIdeographic","v-ideographic"],["vMathematical","v-mathematical"],["vectorEffect","vector-effect"],["vertAdvY","vert-adv-y"],["vertOriginX","vert-origin-x"],["vertOriginY","vert-origin-y"],["wordSpacing","word-spacing"],["writingMode","writing-mode"],["xmlnsXlink","xmlns:xlink"],["xHeight","x-height"]]),tg=/^[\u0000-\u001F ]*j[\r\n\t]*a[\r\n\t]*v[\r\n\t]*a[\r\n\t]*s[\r\n\t]*c[\r\n\t]*r[\r\n\t]*i[\r\n\t]*p[\r\n\t]*t[\r\n\t]*:/i;function th(e){return tg.test(""+e)?"javascript:throw new Error('React has blocked a javascript: URL as a security precaution.')":e}function tb(){}var ty=null;function tv(e){return(e=e.target||e.srcElement||window).correspondingUseElement&&(e=e.correspondingUseElement),3===e.nodeType?e.parentNode:e}var tw=null,tk=null;function tx(e){var t=eV(e);if(t&&(e=t.stateNode)){var n=e[eF]||null;switch(e=t.stateNode,t.type){case"input":if(tr(e,n.value,n.defaultValue,n.defaultValue,n.checked,n.defaultChecked,n.type,n.name),t=n.name,"radio"===n.type&&null!=t){for(n=e;n.parentNode;)n=n.parentNode;for(n=n.querySelectorAll('input[name="'+tn(""+t)+'"][type="radio"]'),t=0;t<n.length;t++){var r=n[t];if(r!==e&&r.form===e.form){var a=r[eF]||null;if(!a)throw Error(l(90));tr(r,a.value,a.defaultValue,a.defaultValue,a.checked,a.defaultChecked,a.type,a.name)}}for(t=0;t<n.length;t++)(r=n[t]).form===e.form&&e7(r)}break;case"textarea":tl(e,n.value,n.defaultValue);break;case"select":null!=(t=n.value)&&ti(e,!!n.multiple,t,!1)}}}var tS=!1;function tE(e,t,n){if(tS)return e(t,n);tS=!0;try{return e(t)}finally{if(tS=!1,(null!==tw||null!==tk)&&(sp(),tw&&(t=tw,e=tk,tk=tw=null,tx(t),e)))for(t=0;t<e.length;t++)tx(e[t])}}function t_(e,t){var n=e.stateNode;if(null===n)return null;var r=n[eF]||null;if(null===r)return null;switch(n=r[t],t){case"onClick":case"onClickCapture":case"onDoubleClick":case"onDoubleClickCapture":case"onMouseDown":case"onMouseDownCapture":case"onMouseMove":case"onMouseMoveCapture":case"onMouseUp":case"onMouseUpCapture":case"onMouseEnter":(r=!r.disabled)||(r="button"!==(e=e.type)&&"input"!==e&&"select"!==e&&"textarea"!==e),e=!r;break;default:e=!1}if(e)return null;if(n&&"function"!=typeof n)throw Error(l(231,t,typeof n));return n}var tA="u">typeof window&&void 0!==window.document&&void 0!==window.document.createElement,tC=!1;if(tA)try{var tT={};Object.defineProperty(tT,"passive",{get:function(){tC=!0}}),window.addEventListener("test",tT,tT),window.removeEventListener("test",tT,tT)}catch(e){tC=!1}var tj=null,tP=null,tz=null;function tL(){if(tz)return tz;var e,t,n=tP,r=n.length,a="value"in tj?tj.value:tj.textContent,o=a.length;for(e=0;e<r&&n[e]===a[e];e++);var i=r-e;for(t=1;t<=i&&n[r-t]===a[o-t];t++);return tz=a.slice(e,1<t?1-t:void 0)}function tN(e){var t=e.keyCode;return"charCode"in e?0===(e=e.charCode)&&13===t&&(e=13):e=t,10===e&&(e=13),32<=e||13===e?e:0}function tO(){return!0}function tR(){return!1}function tD(e){function t(t,n,r,a,o){for(var i in this._reactName=t,this._targetInst=r,this.type=n,this.nativeEvent=a,this.target=o,this.currentTarget=null,e)e.hasOwnProperty(i)&&(t=e[i],this[i]=t?t(a):a[i]);return this.isDefaultPrevented=(null!=a.defaultPrevented?a.defaultPrevented:!1===a.returnValue)?tO:tR,this.isPropagationStopped=tR,this}return f(t.prototype,{preventDefault:function(){this.defaultPrevented=!0;var e=this.nativeEvent;e&&(e.preventDefault?e.preventDefault():"unknown"!=typeof e.returnValue&&(e.returnValue=!1),this.isDefaultPrevented=tO)},stopPropagation:function(){var e=this.nativeEvent;e&&(e.stopPropagation?e.stopPropagation():"unknown"!=typeof e.cancelBubble&&(e.cancelBubble=!0),this.isPropagationStopped=tO)},persist:function(){},isPersistent:tO}),t}var tF,tI,tM,t$,tB,tU={eventPhase:0,bubbles:0,cancelable:0,timeStamp:function(e){return e.timeStamp||Date.now()},defaultPrevented:0,isTrusted:0},tq=tD(tU),tH=f({},tU,{view:0,detail:0}),tG=tD(tH),tV=f({},tH,{screenX:0,screenY:0,clientX:0,clientY:0,pageX:0,pageY:0,ctrlKey:0,shiftKey:0,altKey:0,metaKey:0,getModifierState:t4,button:0,buttons:0,relatedTarget:function(e){return void 0===e.relatedTarget?e.fromElement===e.srcElement?e.toElement:e.fromElement:e.relatedTarget},movementX:function(e){return"movementX"in e?e.movementX:(e!==tB&&(tB&&"mousemove"===e.type?(tM=e.screenX-tB.screenX,t$=e.screenY-tB.screenY):t$=tM=0,tB=e),tM)},movementY:function(e){return"movementY"in e?e.movementY:t$}}),tW=tD(tV),tQ=tD(f({},tV,{dataTransfer:0})),tK=tD(f({},tH,{relatedTarget:0})),tY=tD(f({},tU,{animationName:0,elapsedTime:0,pseudoElement:0})),tZ=tD(f({},tU,{clipboardData:function(e){return"clipboardData"in e?e.clipboardData:window.clipboardData}})),tX=tD(f({},tU,{data:0})),tJ={Esc:"Escape",Spacebar:" ",Left:"ArrowLeft",Up:"ArrowUp",Right:"ArrowRight",Down:"ArrowDown",Del:"Delete",Win:"OS",Menu:"ContextMenu",Apps:"ContextMenu",Scroll:"ScrollLock",MozPrintableKey:"Unidentified"},t0={8:"Backspace",9:"Tab",12:"Clear",13:"Enter",16:"Shift",17:"Control",18:"Alt",19:"Pause",20:"CapsLock",27:"Escape",32:" ",33:"PageUp",34:"PageDown",35:"End",36:"Home",37:"ArrowLeft",38:"ArrowUp",39:"ArrowRight",40:"ArrowDown",45:"Insert",46:"Delete",112:"F1",113:"F2",114:"F3",115:"F4",116:"F5",117:"F6",118:"F7",119:"F8",120:"F9",121:"F10",122:"F11",123:"F12",144:"NumLock",145:"ScrollLock",224:"Meta"},t1={Alt:"altKey",Control:"ctrlKey",Meta:"metaKey",Shift:"shiftKey"};function t2(e){var t=this.nativeEvent;return t.getModifierState?t.getModifierState(e):!!(e=t1[e])&&!!t[e]}function t4(){return t2}var t3=tD(f({},tH,{key:function(e){if(e.key){var t=tJ[e.key]||e.key;if("Unidentified"!==t)return t}return"keypress"===e.type?13===(e=tN(e))?"Enter":String.fromCharCode(e):"keydown"===e.type||"keyup"===e.type?t0[e.keyCode]||"Unidentified":""},code:0,location:0,ctrlKey:0,shiftKey:0,altKey:0,metaKey:0,repeat:0,locale:0,getModifierState:t4,charCode:function(e){return"keypress"===e.type?tN(e):0},keyCode:function(e){return"keydown"===e.type||"keyup"===e.type?e.keyCode:0},which:function(e){return"keypress"===e.type?tN(e):"keydown"===e.type||"keyup"===e.type?e.keyCode:0}})),t8=tD(f({},tV,{pointerId:0,width:0,height:0,pressure:0,tangentialPressure:0,tiltX:0,tiltY:0,twist:0,pointerType:0,isPrimary:0})),t6=tD(f({},tH,{touches:0,targetTouches:0,changedTouches:0,altKey:0,metaKey:0,ctrlKey:0,shiftKey:0,getModifierState:t4})),t9=tD(f({},tU,{propertyName:0,elapsedTime:0,pseudoElement:0})),t5=tD(f({},tV,{deltaX:function(e){return"deltaX"in e?e.deltaX:"wheelDeltaX"in e?-e.wheelDeltaX:0},deltaY:function(e){return"deltaY"in e?e.deltaY:"wheelDeltaY"in e?-e.wheelDeltaY:"wheelDelta"in e?-e.wheelDelta:0},deltaZ:0,deltaMode:0})),t7=tD(f({},tU,{newState:0,oldState:0})),ne=[9,13,27,32],nt=tA&&"CompositionEvent"in window,nn=null;tA&&"documentMode"in document&&(nn=document.documentMode);var nr=tA&&"TextEvent"in window&&!nn,na=tA&&(!nt||nn&&8<nn&&11>=nn),no=!1;function ni(e,t){switch(e){case"keyup":return -1!==ne.indexOf(t.keyCode);case"keydown":return 229!==t.keyCode;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function nl(e){return"object"==typeof(e=e.detail)&&"data"in e?e.data:null}var ns=!1,nc={color:!0,date:!0,datetime:!0,"datetime-local":!0,email:!0,month:!0,number:!0,password:!0,range:!0,search:!0,tel:!0,text:!0,time:!0,url:!0,week:!0};function nu(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return"input"===t?!!nc[e.type]:"textarea"===t}function nd(e,t,n,r){tw?tk?tk.push(r):tk=[r]:tw=r,0<(t=ca(t,"onChange")).length&&(n=new tq("onChange","change",null,n,r),e.push({event:n,listeners:t}))}var np=null,nf=null;function nm(e){s6(e,0)}function ng(e){if(e7(eW(e)))return e}function nh(e,t){if("change"===e)return t}var nb=!1;if(tA){if(tA){var ny="oninput"in document;if(!ny){var nv=document.createElement("div");nv.setAttribute("oninput","return;"),ny="function"==typeof nv.oninput}r=ny}else r=!1;nb=r&&(!document.documentMode||9<document.documentMode)}function nw(){np&&(np.detachEvent("onpropertychange",nk),nf=np=null)}function nk(e){if("value"===e.propertyName&&ng(nf)){var t=[];nd(t,nf,e,tv(e)),tE(nm,t)}}function nx(e,t,n){"focusin"===e?(nw(),np=t,nf=n,np.attachEvent("onpropertychange",nk)):"focusout"===e&&nw()}function nS(e){if("selectionchange"===e||"keyup"===e||"keydown"===e)return ng(nf)}function nE(e,t){if("click"===e)return ng(t)}function n_(e,t){if("input"===e||"change"===e)return ng(t)}var nA="function"==typeof Object.is?Object.is:function(e,t){return e===t&&(0!==e||1/e==1/t)||e!=e&&t!=t};function nC(e,t){if(nA(e,t))return!0;if("object"!=typeof e||null===e||"object"!=typeof t||null===t)return!1;var n=Object.keys(e),r=Object.keys(t);if(n.length!==r.length)return!1;for(r=0;r<n.length;r++){var a=n[r];if(!J.call(t,a)||!nA(e[a],t[a]))return!1}return!0}function nT(e){for(;e&&e.firstChild;)e=e.firstChild;return e}function nj(e,t){var n,r=nT(e);for(e=0;r;){if(3===r.nodeType){if(n=e+r.textContent.length,e<=t&&n>=t)return{node:r,offset:t-e};e=n}e:{for(;r;){if(r.nextSibling){r=r.nextSibling;break e}r=r.parentNode}r=void 0}r=nT(r)}}function nP(e){e=null!=e&&null!=e.ownerDocument&&null!=e.ownerDocument.defaultView?e.ownerDocument.defaultView:window;for(var t=te(e.document);t instanceof e.HTMLIFrameElement;){try{var n="string"==typeof t.contentWindow.location.href}catch(e){n=!1}if(n)e=t.contentWindow;else break;t=te(e.document)}return t}function nz(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return t&&("input"===t&&("text"===e.type||"search"===e.type||"tel"===e.type||"url"===e.type||"password"===e.type)||"textarea"===t||"true"===e.contentEditable)}var nL=tA&&"documentMode"in document&&11>=document.documentMode,nN=null,nO=null,nR=null,nD=!1;function nF(e,t,n){var r=n.window===n?n.document:9===n.nodeType?n:n.ownerDocument;nD||null==nN||nN!==te(r)||(r="selectionStart"in(r=nN)&&nz(r)?{start:r.selectionStart,end:r.selectionEnd}:{anchorNode:(r=(r.ownerDocument&&r.ownerDocument.defaultView||window).getSelection()).anchorNode,anchorOffset:r.anchorOffset,focusNode:r.focusNode,focusOffset:r.focusOffset},nR&&nC(nR,r)||(nR=r,0<(r=ca(nO,"onSelect")).length&&(t=new tq("onSelect","select",null,t,n),e.push({event:t,listeners:r}),t.target=nN)))}function nI(e,t){var n={};return n[e.toLowerCase()]=t.toLowerCase(),n["Webkit"+e]="webkit"+t,n["Moz"+e]="moz"+t,n}var nM={animationend:nI("Animation","AnimationEnd"),animationiteration:nI("Animation","AnimationIteration"),animationstart:nI("Animation","AnimationStart"),transitionrun:nI("Transition","TransitionRun"),transitionstart:nI("Transition","TransitionStart"),transitioncancel:nI("Transition","TransitionCancel"),transitionend:nI("Transition","TransitionEnd")},n$={},nB={};function nU(e){if(n$[e])return n$[e];if(!nM[e])return e;var t,n=nM[e];for(t in n)if(n.hasOwnProperty(t)&&t in nB)return n$[e]=n[t];return e}tA&&(nB=document.createElement("div").style,"AnimationEvent"in window||(delete nM.animationend.animation,delete nM.animationiteration.animation,delete nM.animationstart.animation),"TransitionEvent"in window||delete nM.transitionend.transition);var nq=nU("animationend"),nH=nU("animationiteration"),nG=nU("animationstart"),nV=nU("transitionrun"),nW=nU("transitionstart"),nQ=nU("transitioncancel"),nK=nU("transitionend"),nY=new Map,nZ="abort auxClick beforeToggle cancel canPlay canPlayThrough click close contextMenu copy cut drag dragEnd dragEnter dragExit dragLeave dragOver dragStart drop durationChange emptied encrypted ended error gotPointerCapture input invalid keyDown keyPress keyUp load loadedData loadedMetadata loadStart lostPointerCapture mouseDown mouseMove mouseOut mouseOver mouseUp paste pause play playing pointerCancel pointerDown pointerMove pointerOut pointerOver pointerUp progress rateChange reset resize seeked seeking stalled submit suspend timeUpdate touchCancel touchEnd touchStart volumeChange scroll toggle touchMove waiting wheel".split(" ");function nX(e,t){nY.set(e,t),eX(t,[e])}nZ.push("scrollEnd");var nJ="function"==typeof reportError?reportError:function(e){if("object"==typeof window&&"function"==typeof window.ErrorEvent){var t=new window.ErrorEvent("error",{bubbles:!0,cancelable:!0,message:"object"==typeof e&&null!==e&&"string"==typeof e.message?String(e.message):String(e),error:e});if(!window.dispatchEvent(t))return}else if("object"==typeof process&&"function"==typeof process.emit)return void process.emit("uncaughtException",e);console.error(e)},n0=[],n1=0,n2=0;function n4(){for(var e=n1,t=n2=n1=0;t<e;){var n=n0[t];n0[t++]=null;var r=n0[t];n0[t++]=null;var a=n0[t];n0[t++]=null;var o=n0[t];if(n0[t++]=null,null!==r&&null!==a){var i=r.pending;null===i?a.next=a:(a.next=i.next,i.next=a),r.pending=a}0!==o&&n9(n,a,o)}}function n3(e,t,n,r){n0[n1++]=e,n0[n1++]=t,n0[n1++]=n,n0[n1++]=r,n2|=r,e.lanes|=r,null!==(e=e.alternate)&&(e.lanes|=r)}function n8(e,t,n,r){return n3(e,t,n,r),n5(e)}function n6(e,t){return n3(e,null,null,t),n5(e)}function n9(e,t,n){e.lanes|=n;var r=e.alternate;null!==r&&(r.lanes|=n);for(var a=!1,o=e.return;null!==o;)o.childLanes|=n,null!==(r=o.alternate)&&(r.childLanes|=n),22===o.tag&&(null===(e=o.stateNode)||1&e._visibility||(a=!0)),e=o,o=o.return;return 3===e.tag?(o=e.stateNode,a&&null!==t&&(a=31-eh(n),null===(r=(e=o.hiddenUpdates)[a])?e[a]=[t]:r.push(t),t.lane=0x20000000|n),o):null}function n5(e){if(50<sa)throw sa=0,so=null,Error(l(185));for(var t=e.return;null!==t;)t=(e=t).return;return 3===e.tag?e.stateNode:null}var n7={};function re(e,t,n,r){this.tag=e,this.key=n,this.sibling=this.child=this.return=this.stateNode=this.type=this.elementType=null,this.index=0,this.refCleanup=this.ref=null,this.pendingProps=t,this.dependencies=this.memoizedState=this.updateQueue=this.memoizedProps=null,this.mode=r,this.subtreeFlags=this.flags=0,this.deletions=null,this.childLanes=this.lanes=0,this.alternate=null}function rt(e,t,n,r){return new re(e,t,n,r)}function rn(e){return!(!(e=e.prototype)||!e.isReactComponent)}function rr(e,t){var n=e.alternate;return null===n?((n=rt(e.tag,t,e.key,e.mode)).elementType=e.elementType,n.type=e.type,n.stateNode=e.stateNode,n.alternate=e,e.alternate=n):(n.pendingProps=t,n.type=e.type,n.flags=0,n.subtreeFlags=0,n.deletions=null),n.flags=0x3e00000&e.flags,n.childLanes=e.childLanes,n.lanes=e.lanes,n.child=e.child,n.memoizedProps=e.memoizedProps,n.memoizedState=e.memoizedState,n.updateQueue=e.updateQueue,t=e.dependencies,n.dependencies=null===t?null:{lanes:t.lanes,firstContext:t.firstContext},n.sibling=e.sibling,n.index=e.index,n.ref=e.ref,n.refCleanup=e.refCleanup,n}function ra(e,t){e.flags&=0x3e00002;var n=e.alternate;return null===n?(e.childLanes=0,e.lanes=t,e.child=null,e.subtreeFlags=0,e.memoizedProps=null,e.memoizedState=null,e.updateQueue=null,e.dependencies=null,e.stateNode=null):(e.childLanes=n.childLanes,e.lanes=n.lanes,e.child=n.child,e.subtreeFlags=0,e.deletions=null,e.memoizedProps=n.memoizedProps,e.memoizedState=n.memoizedState,e.updateQueue=n.updateQueue,e.type=n.type,e.dependencies=null===(t=n.dependencies)?null:{lanes:t.lanes,firstContext:t.firstContext}),e}function ro(e,t,n,r,a,o){var i=0;if(r=e,"function"==typeof e)rn(e)&&(i=1);else if("string"==typeof e)i=!function(e,t,n){if(1===n||null!=t.itemProp)return!1;switch(e){case"meta":case"title":return!0;case"style":if("string"!=typeof t.precedence||"string"!=typeof t.href||""===t.href)break;return!0;case"link":if("string"!=typeof t.rel||"string"!=typeof t.href||""===t.href||t.onLoad||t.onError)break;if("stylesheet"===t.rel)return e=t.disabled,"string"==typeof t.precedence&&null==e;return!0;case"script":if(t.async&&"function"!=typeof t.async&&"symbol"!=typeof t.async&&!t.onLoad&&!t.onError&&t.src&&"string"==typeof t.src)return!0}return!1}(e,n,B.current)?"html"===e||"head"===e||"body"===e?27:5:26;else e:switch(e){case C:return(e=rt(31,n,t,a)).elementType=C,e.lanes=o,e;case b:return ri(n.children,a,o,t);case y:i=8,a|=24;break;case v:return(e=rt(12,n,t,2|a)).elementType=v,e.lanes=o,e;case S:return(e=rt(13,n,t,a)).elementType=S,e.lanes=o,e;case E:return(e=rt(19,n,t,a)).elementType=E,e.lanes=o,e;default:if("object"==typeof e&&null!==e)switch(e.$$typeof){case k:i=10;break e;case w:i=9;break e;case x:i=11;break e;case _:i=14;break e;case A:i=16,r=null;break e}i=29,n=Error(l(130,null===e?"null":typeof e,"")),r=null}return(t=rt(i,n,t,a)).elementType=e,t.type=r,t.lanes=o,t}function ri(e,t,n,r){return(e=rt(7,e,r,t)).lanes=n,e}function rl(e,t,n){return(e=rt(6,e,null,t)).lanes=n,e}function rs(e){var t=rt(18,null,null,0);return t.stateNode=e,t}function rc(e,t,n){return(t=rt(4,null!==e.children?e.children:[],e.key,t)).lanes=n,t.stateNode={containerInfo:e.containerInfo,pendingChildren:null,implementation:e.implementation},t}var ru=new WeakMap;function rd(e,t){if("object"==typeof e&&null!==e){var n=ru.get(e);return void 0!==n?n:(t={value:e,source:t,stack:X(t)},ru.set(e,t),t)}return{value:e,source:t,stack:X(t)}}var rp=[],rf=0,rm=null,rg=0,rh=[],rb=0,ry=null,rv=1,rw="";function rk(e,t){rp[rf++]=rg,rp[rf++]=rm,rm=e,rg=t}function rx(e,t,n){rh[rb++]=rv,rh[rb++]=rw,rh[rb++]=ry,ry=e;var r=rv;e=rw;var a=32-eh(r)-1;r&=~(1<<a),n+=1;var o=32-eh(t)+a;if(30<o){var i=a-a%5;o=(r&(1<<i)-1).toString(32),r>>=i,a-=i,rv=1<<32-eh(t)+a|n<<a|r,rw=o+e}else rv=1<<o|n<<a|r,rw=e}function rS(e){null!==e.return&&(rk(e,1),rx(e,1,0))}function rE(e){for(;e===rm;)rm=rp[--rf],rp[rf]=null,rg=rp[--rf],rp[rf]=null;for(;e===ry;)ry=rh[--rb],rh[rb]=null,rw=rh[--rb],rh[rb]=null,rv=rh[--rb],rh[rb]=null}function r_(e,t){rh[rb++]=rv,rh[rb++]=rw,rh[rb++]=ry,rv=t.id,rw=t.overflow,ry=e}var rA=null,rC=null,rT=!1,rj=null,rP=!1,rz=Error(l(519));function rL(e){var t=Error(l(418,1<arguments.length&&void 0!==arguments[1]&&arguments[1]?"text":"HTML",""));throw rI(rd(t,e)),rz}function rN(e){var t=e.stateNode,n=e.type,r=e.memoizedProps;switch(t[eD]=e,t[eF]=r,n){case"dialog":s9("cancel",t),s9("close",t);break;case"iframe":case"object":case"embed":s9("load",t);break;case"video":case"audio":for(n=0;n<s3.length;n++)s9(s3[n],t);break;case"source":s9("error",t);break;case"img":case"image":case"link":s9("error",t),s9("load",t);break;case"details":s9("toggle",t);break;case"input":s9("invalid",t),ta(t,r.value,r.defaultValue,r.checked,r.defaultChecked,r.type,r.name,!0);break;case"select":s9("invalid",t);break;case"textarea":s9("invalid",t),ts(t,r.value,r.defaultValue,r.children)}"string"!=typeof(n=r.children)&&"number"!=typeof n&&"bigint"!=typeof n||t.textContent===""+n||!0===r.suppressHydrationWarning||cu(t.textContent,n)?(null!=r.popover&&(s9("beforetoggle",t),s9("toggle",t)),null!=r.onScroll&&s9("scroll",t),null!=r.onScrollEnd&&s9("scrollend",t),null!=r.onClick&&(t.onclick=tb),t=!0):t=!1,t||rL(e,!0)}function rO(e){for(rA=e.return;rA;)switch(rA.tag){case 5:case 31:case 13:rP=!1;return;case 27:case 3:rP=!0;return;default:rA=rA.return}}function rR(e){if(e!==rA)return!1;if(!rT)return rO(e),rT=!0,!1;var t,n=e.tag;if((t=3!==n&&27!==n)&&((t=5===n)&&(t="form"===(t=e.type)||"button"===t||cw(e.type,e.memoizedProps)),t=!t),t&&rC&&rL(e),rO(e),13===n){if(!(e=null!==(e=e.memoizedState)?e.dehydrated:null))throw Error(l(317));rC=cD(e)}else if(31===n){if(!(e=null!==(e=e.memoizedState)?e.dehydrated:null))throw Error(l(317));rC=cD(e)}else 27===n?(n=rC,cC(e.type)?(e=cR,cR=null,rC=e):rC=n):rC=rA?cO(e.stateNode.nextSibling):null;return!0}function rD(){rC=rA=null,rT=!1}function rF(){var e=rj;return null!==e&&(null===l0?l0=e:l0.push.apply(l0,e),rj=null),e}function rI(e){null===rj?rj=[e]:rj.push(e)}var rM=I(null),r$=null,rB=null;function rU(e,t,n){$(rM,t._currentValue),t._currentValue=n}function rq(e){e._currentValue=rM.current,M(rM)}function rH(e,t,n){for(;null!==e;){var r=e.alternate;if((e.childLanes&t)!==t?(e.childLanes|=t,null!==r&&(r.childLanes|=t)):null!==r&&(r.childLanes&t)!==t&&(r.childLanes|=t),e===n)break;e=e.return}}function rG(e,t,n,r){var a=e.child;for(null!==a&&(a.return=e);null!==a;){var o=a.dependencies;if(null!==o){var i=a.child;o=o.firstContext;e:for(;null!==o;){var s=o;o=a;for(var c=0;c<t.length;c++)if(s.context===t[c]){o.lanes|=n,null!==(s=o.alternate)&&(s.lanes|=n),rH(o.return,n,e),r||(i=null);break e}o=s.next}}else if(18===a.tag){if(null===(i=a.return))throw Error(l(341));i.lanes|=n,null!==(o=i.alternate)&&(o.lanes|=n),rH(i,n,e),i=null}else i=a.child;if(null!==i)i.return=a;else for(i=a;null!==i;){if(i===e){i=null;break}if(null!==(a=i.sibling)){a.return=i.return,i=a;break}i=i.return}a=i}}function rV(e,t,n,r){e=null;for(var a=t,o=!1;null!==a;){if(!o){if(0!=(524288&a.flags))o=!0;else if(0!=(262144&a.flags))break}if(10===a.tag){var i=a.alternate;if(null===i)throw Error(l(387));if(null!==(i=i.memoizedProps)){var s=a.type;nA(a.pendingProps.value,i.value)||(null!==e?e.push(s):e=[s])}}else if(a===H.current){if(null===(i=a.alternate))throw Error(l(387));i.memoizedState.memoizedState!==a.memoizedState.memoizedState&&(null!==e?e.push(ut):e=[ut])}a=a.return}null!==e&&rG(t,e,n,r),t.flags|=262144}function rW(e){for(e=e.firstContext;null!==e;){if(!nA(e.context._currentValue,e.memoizedValue))return!0;e=e.next}return!1}function rQ(e){r$=e,rB=null,null!==(e=e.dependencies)&&(e.firstContext=null)}function rK(e){return rZ(r$,e)}function rY(e,t){return null===r$&&rQ(e),rZ(e,t)}function rZ(e,t){var n=t._currentValue;if(t={context:t,memoizedValue:n,next:null},null===rB){if(null===e)throw Error(l(308));rB=t,e.dependencies={lanes:0,firstContext:t},e.flags|=524288}else rB=rB.next=t;return n}var rX="u">typeof AbortController?AbortController:function(){var e=[],t=this.signal={aborted:!1,addEventListener:function(t,n){e.push(n)}};this.abort=function(){t.aborted=!0,e.forEach(function(e){return e()})}},rJ=a.unstable_scheduleCallback,r0=a.unstable_NormalPriority,r1={$$typeof:k,Consumer:null,Provider:null,_currentValue:null,_currentValue2:null,_threadCount:0};function r2(){return{controller:new rX,data:new Map,refCount:0}}function r4(e){e.refCount--,0===e.refCount&&rJ(r0,function(){e.controller.abort()})}var r3=null,r8=0,r6=0,r9=null;function r5(){if(0==--r8&&null!==r3){null!==r9&&(r9.status="fulfilled");var e=r3;r3=null,r6=0,r9=null;for(var t=0;t<e.length;t++)(0,e[t])()}}var r7=N.S;N.S=function(e,t){l4=ea(),"object"==typeof t&&null!==t&&"function"==typeof t.then&&function(e){if(null===r3){var t=r3=[];r8=0,r6=sJ(),r9={status:"pending",value:void 0,then:function(e){t.push(e)}}}r8++,e.then(r5,r5)}(t),null!==r7&&r7(e,t)};var ae=I(null);function at(){var e=ae.current;return null!==e?e:lI.pooledCache}function an(e,t){null===t?$(ae,ae.current):$(ae,t.pool)}function ar(){var e=at();return null===e?null:{parent:r1._currentValue,pool:e}}var aa=Error(l(460)),ao=Error(l(474)),ai=Error(l(542)),al={then:function(){}};function as(e){return"fulfilled"===(e=e.status)||"rejected"===e}function ac(e,t,n){switch(void 0===(n=e[n])?e.push(t):n!==t&&(t.then(tb,tb),t=n),t.status){case"fulfilled":return t.value;case"rejected":throw af(e=t.reason),e;default:if("string"==typeof t.status)t.then(tb,tb);else{if(null!==(e=lI)&&100<e.shellSuspendCounter)throw Error(l(482));(e=t).status="pending",e.then(function(e){if("pending"===t.status){var n=t;n.status="fulfilled",n.value=e}},function(e){if("pending"===t.status){var n=t;n.status="rejected",n.reason=e}})}switch(t.status){case"fulfilled":return t.value;case"rejected":throw af(e=t.reason),e}throw ad=t,aa}}function au(e){try{return(0,e._init)(e._payload)}catch(e){if(null!==e&&"object"==typeof e&&"function"==typeof e.then)throw ad=e,aa;throw e}}var ad=null;function ap(){if(null===ad)throw Error(l(459));var e=ad;return ad=null,e}function af(e){if(e===aa||e===ai)throw Error(l(483))}var am=null,ag=0;function ah(e){var t=ag;return ag+=1,null===am&&(am=[]),ac(am,e,t)}function ab(e,t){e.ref=void 0!==(t=t.props.ref)?t:null}function ay(e,t){if(t.$$typeof===m)throw Error(l(525));throw Error(l(31,"[object Object]"===(e=Object.prototype.toString.call(t))?"object with keys {"+Object.keys(t).join(", ")+"}":e))}function av(e){function t(t,n){if(e){var r=t.deletions;null===r?(t.deletions=[n],t.flags|=16):r.push(n)}}function n(n,r){if(!e)return null;for(;null!==r;)t(n,r),r=r.sibling;return null}function r(e){for(var t=new Map;null!==e;)null!==e.key?t.set(e.key,e):t.set(e.index,e),e=e.sibling;return t}function a(e,t){return(e=rr(e,t)).index=0,e.sibling=null,e}function o(t,n,r){return(t.index=r,e)?null!==(r=t.alternate)?(r=r.index)<n?(t.flags|=0x4000002,n):r:(t.flags|=0x4000002,n):(t.flags|=1048576,n)}function i(t){return e&&null===t.alternate&&(t.flags|=0x4000002),t}function s(e,t,n,r){return null===t||6!==t.tag?(t=rl(n,e.mode,r)).return=e:(t=a(t,n)).return=e,t}function c(e,t,n,r){var o=n.type;return o===b?d(e,t,n.props.children,r,n.key):(null!==t&&(t.elementType===o||"object"==typeof o&&null!==o&&o.$$typeof===A&&au(o)===t.type)?ab(t=a(t,n.props),n):ab(t=ro(n.type,n.key,n.props,null,e.mode,r),n),t.return=e,t)}function u(e,t,n,r){return null===t||4!==t.tag||t.stateNode.containerInfo!==n.containerInfo||t.stateNode.implementation!==n.implementation?(t=rc(n,e.mode,r)).return=e:(t=a(t,n.children||[])).return=e,t}function d(e,t,n,r,o){return null===t||7!==t.tag?(t=ri(n,e.mode,r,o)).return=e:(t=a(t,n)).return=e,t}function p(e,t,n){if("string"==typeof t&&""!==t||"number"==typeof t||"bigint"==typeof t)return(t=rl(""+t,e.mode,n)).return=e,t;if("object"==typeof t&&null!==t){switch(t.$$typeof){case g:return ab(n=ro(t.type,t.key,t.props,null,e.mode,n),t),n.return=e,n;case h:return(t=rc(t,e.mode,n)).return=e,t;case A:return p(e,t=au(t),n)}if(L(t)||P(t))return(t=ri(t,e.mode,n,null)).return=e,t;if("function"==typeof t.then)return p(e,ah(t),n);if(t.$$typeof===k)return p(e,rY(e,t),n);ay(e,t)}return null}function f(e,t,n,r){var a=null!==t?t.key:null;if("string"==typeof n&&""!==n||"number"==typeof n||"bigint"==typeof n)return null!==a?null:s(e,t,""+n,r);if("object"==typeof n&&null!==n){switch(n.$$typeof){case g:return n.key===a?c(e,t,n,r):null;case h:return n.key===a?u(e,t,n,r):null;case A:return f(e,t,n=au(n),r)}if(L(n)||P(n))return null!==a?null:d(e,t,n,r,null);if("function"==typeof n.then)return f(e,t,ah(n),r);if(n.$$typeof===k)return f(e,t,rY(e,n),r);ay(e,n)}return null}function m(e,t,n,r,a){if("string"==typeof r&&""!==r||"number"==typeof r||"bigint"==typeof r)return s(t,e=e.get(n)||null,""+r,a);if("object"==typeof r&&null!==r){switch(r.$$typeof){case g:return c(t,e=e.get(null===r.key?n:r.key)||null,r,a);case h:return u(t,e=e.get(null===r.key?n:r.key)||null,r,a);case A:return m(e,t,n,r=au(r),a)}if(L(r)||P(r))return d(t,e=e.get(n)||null,r,a,null);if("function"==typeof r.then)return m(e,t,n,ah(r),a);if(r.$$typeof===k)return m(e,t,n,rY(t,r),a);ay(t,r)}return null}return function(s,c,u,d){try{ag=0;var y=function s(c,u,d,y){if("object"==typeof d&&null!==d&&d.type===b&&null===d.key&&(d=d.props.children),"object"==typeof d&&null!==d){switch(d.$$typeof){case g:e:{for(var v=d.key;null!==u;){if(u.key===v){if((v=d.type)===b){if(7===u.tag){n(c,u.sibling),(y=a(u,d.props.children)).return=c,c=y;break e}}else if(u.elementType===v||"object"==typeof v&&null!==v&&v.$$typeof===A&&au(v)===u.type){n(c,u.sibling),ab(y=a(u,d.props),d),y.return=c,c=y;break e}n(c,u);break}t(c,u),u=u.sibling}d.type===b?(y=ri(d.props.children,c.mode,y,d.key)).return=c:(ab(y=ro(d.type,d.key,d.props,null,c.mode,y),d),y.return=c),c=y}return i(c);case h:e:{for(v=d.key;null!==u;){if(u.key===v)if(4===u.tag&&u.stateNode.containerInfo===d.containerInfo&&u.stateNode.implementation===d.implementation){n(c,u.sibling),(y=a(u,d.children||[])).return=c,c=y;break e}else{n(c,u);break}t(c,u),u=u.sibling}(y=rc(d,c.mode,y)).return=c,c=y}return i(c);case A:return s(c,u,d=au(d),y)}if(L(d))return function(a,i,l,s){for(var c=null,u=null,d=i,g=i=0,h=null;null!==d&&g<l.length;g++){d.index>g?(h=d,d=null):h=d.sibling;var b=f(a,d,l[g],s);if(null===b){null===d&&(d=h);break}e&&d&&null===b.alternate&&t(a,d),i=o(b,i,g),null===u?c=b:u.sibling=b,u=b,d=h}if(g===l.length)return n(a,d),rT&&rk(a,g),c;if(null===d){for(;g<l.length;g++)null!==(d=p(a,l[g],s))&&(i=o(d,i,g),null===u?c=d:u.sibling=d,u=d);return rT&&rk(a,g),c}for(d=r(d);g<l.length;g++)null!==(h=m(d,a,g,l[g],s))&&(e&&null!==h.alternate&&d.delete(null===h.key?g:h.key),i=o(h,i,g),null===u?c=h:u.sibling=h,u=h);return e&&d.forEach(function(e){return t(a,e)}),rT&&rk(a,g),c}(c,u,d,y);if(P(d)){if("function"!=typeof(v=P(d)))throw Error(l(150));return function(a,i,s,c){if(null==s)throw Error(l(151));for(var u=null,d=null,g=i,h=i=0,b=null,y=s.next();null!==g&&!y.done;h++,y=s.next()){g.index>h?(b=g,g=null):b=g.sibling;var v=f(a,g,y.value,c);if(null===v){null===g&&(g=b);break}e&&g&&null===v.alternate&&t(a,g),i=o(v,i,h),null===d?u=v:d.sibling=v,d=v,g=b}if(y.done)return n(a,g),rT&&rk(a,h),u;if(null===g){for(;!y.done;h++,y=s.next())null!==(y=p(a,y.value,c))&&(i=o(y,i,h),null===d?u=y:d.sibling=y,d=y);return rT&&rk(a,h),u}for(g=r(g);!y.done;h++,y=s.next())null!==(y=m(g,a,h,y.value,c))&&(e&&null!==y.alternate&&g.delete(null===y.key?h:y.key),i=o(y,i,h),null===d?u=y:d.sibling=y,d=y);return e&&g.forEach(function(e){return t(a,e)}),rT&&rk(a,h),u}(c,u,d=v.call(d),y)}if("function"==typeof d.then)return s(c,u,ah(d),y);if(d.$$typeof===k)return s(c,u,rY(c,d),y);ay(c,d)}return"string"==typeof d&&""!==d||"number"==typeof d||"bigint"==typeof d?(d=""+d,null!==u&&6===u.tag?(n(c,u.sibling),(y=a(u,d)).return=c):(n(c,u),(y=rl(d,c.mode,y)).return=c),i(c=y)):n(c,u)}(s,c,u,d);return am=null,y}catch(e){if(e===aa||e===ai)throw e;var v=rt(29,e,null,s.mode);return v.lanes=d,v.return=s,v}finally{}}}var aw=av(!0),ak=av(!1),ax=!1;function aS(e){e.updateQueue={baseState:e.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,lanes:0,hiddenCallbacks:null},callbacks:null}}function aE(e,t){e=e.updateQueue,t.updateQueue===e&&(t.updateQueue={baseState:e.baseState,firstBaseUpdate:e.firstBaseUpdate,lastBaseUpdate:e.lastBaseUpdate,shared:e.shared,callbacks:null})}function a_(e){return{lane:e,tag:0,payload:null,callback:null,next:null}}function aA(e,t,n){var r=e.updateQueue;if(null===r)return null;if(r=r.shared,0!=(2&lF)){var a=r.pending;return null===a?t.next=t:(t.next=a.next,a.next=t),r.pending=t,t=n5(e),n9(e,null,n),t}return n3(e,r,t,n),n5(e)}function aC(e,t,n){if(null!==(t=t.updateQueue)&&(t=t.shared,0!=(4194048&n))){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,ej(e,n)}}function aT(e,t){var n=e.updateQueue,r=e.alternate;if(null!==r&&n===(r=r.updateQueue)){var a=null,o=null;if(null!==(n=n.firstBaseUpdate)){do{var i={lane:n.lane,tag:n.tag,payload:n.payload,callback:null,next:null};null===o?a=o=i:o=o.next=i,n=n.next}while(null!==n)null===o?a=o=t:o=o.next=t}else a=o=t;n={baseState:r.baseState,firstBaseUpdate:a,lastBaseUpdate:o,shared:r.shared,callbacks:r.callbacks},e.updateQueue=n;return}null===(e=n.lastBaseUpdate)?n.firstBaseUpdate=t:e.next=t,n.lastBaseUpdate=t}var aj=!1;function aP(){if(aj){var e=r9;if(null!==e)throw e}}function az(e,t,n,r){aj=!1;var a=e.updateQueue;ax=!1;var o=a.firstBaseUpdate,i=a.lastBaseUpdate,l=a.shared.pending;if(null!==l){a.shared.pending=null;var s=l,c=s.next;s.next=null,null===i?o=c:i.next=c,i=s;var u=e.alternate;null!==u&&(l=(u=u.updateQueue).lastBaseUpdate)!==i&&(null===l?u.firstBaseUpdate=c:l.next=c,u.lastBaseUpdate=s)}if(null!==o){var d=a.baseState;for(i=0,u=c=s=null,l=o;;){var p=-0x20000001&l.lane,m=p!==l.lane;if(m?(l$&p)===p:(r&p)===p){0!==p&&p===r6&&(aj=!0),null!==u&&(u=u.next={lane:0,tag:l.tag,payload:l.payload,callback:null,next:null});e:{var g=e,h=l;switch(p=t,h.tag){case 1:if("function"==typeof(g=h.payload)){d=g.call(n,d,p);break e}d=g;break e;case 3:g.flags=-65537&g.flags|128;case 0:if(null==(p="function"==typeof(g=h.payload)?g.call(n,d,p):g))break e;d=f({},d,p);break e;case 2:ax=!0}}null!==(p=l.callback)&&(e.flags|=64,m&&(e.flags|=8192),null===(m=a.callbacks)?a.callbacks=[p]:m.push(p))}else m={lane:p,tag:l.tag,payload:l.payload,callback:l.callback,next:null},null===u?(c=u=m,s=d):u=u.next=m,i|=p;if(null===(l=l.next))if(null===(l=a.shared.pending))break;else l=(m=l).next,m.next=null,a.lastBaseUpdate=m,a.shared.pending=null}null===u&&(s=d),a.baseState=s,a.firstBaseUpdate=c,a.lastBaseUpdate=u,null===o&&(a.shared.lanes=0),lQ|=i,e.lanes=i,e.memoizedState=d}}function aL(e,t){if("function"!=typeof e)throw Error(l(191,e));e.call(t)}function aN(e,t){var n=e.callbacks;if(null!==n)for(e.callbacks=null,e=0;e<n.length;e++)aL(n[e],t)}var aO=I(null),aR=I(0);function aD(e,t){$(aR,e=lV),$(aO,t),lV=e|t.baseLanes}function aF(){$(aR,lV),$(aO,aO.current)}function aI(){lV=aR.current,M(aO),M(aR)}var aM=I(null),a$=null;function aB(e){var t=e.alternate;$(aV,1&aV.current),$(aM,e),null===a$&&(null===t||null!==aO.current?a$=e:null!==t.memoizedState&&(a$=e))}function aU(e){$(aV,aV.current),$(aM,e),null===a$&&(a$=e)}function aq(e){22===e.tag?($(aV,aV.current),$(aM,e),null===a$&&(a$=e)):aH(e)}function aH(){$(aV,aV.current),$(aM,aM.current)}function aG(e){M(aM),a$===e&&(a$=null),M(aV)}var aV=I(0);function aW(e){for(var t=e;null!==t;){if(13===t.tag){var n=t.memoizedState;if(null!==n&&(null===(n=n.dehydrated)||cL(n)||cN(n)))return t}else if(19===t.tag&&("forwards"===t.memoizedProps.revealOrder||"backwards"===t.memoizedProps.revealOrder||"unstable_legacy-backwards"===t.memoizedProps.revealOrder||"together"===t.memoizedProps.revealOrder)){if(0!=(128&t.flags))return t}else if(null!==t.child){t.child.return=t,t=t.child;continue}if(t===e)break;for(;null===t.sibling;){if(null===t.return||t.return===e)return null;t=t.return}t.sibling.return=t.return,t=t.sibling}return null}var aQ=0,aK=null,aY=null,aZ=null,aX=!1,aJ=!1,a0=!1,a1=0,a2=0,a4=null,a3=0;function a8(){throw Error(l(321))}function a6(e,t){if(null===t)return!1;for(var n=0;n<t.length&&n<e.length;n++)if(!nA(e[n],t[n]))return!1;return!0}function a9(e,t,n,r,a,o){return aQ=o,aK=t,t.memoizedState=null,t.updateQueue=null,t.lanes=0,N.H=null===e||null===e.memoizedState?ia:io,a0=!1,o=n(r,a),a0=!1,aJ&&(o=a7(t,n,r,a)),a5(e),o}function a5(e){N.H=ir;var t=null!==aY&&null!==aY.next;if(aQ=0,aZ=aY=aK=null,aX=!1,a2=0,a4=null,t)throw Error(l(300));null===e||ix||null!==(e=e.dependencies)&&rW(e)&&(ix=!0)}function a7(e,t,n,r){aK=e;var a=0;do{if(aJ&&(a4=null),a2=0,aJ=!1,25<=a)throw Error(l(301));if(a+=1,aZ=aY=null,null!=e.updateQueue){var o=e.updateQueue;o.lastEffect=null,o.events=null,o.stores=null,null!=o.memoCache&&(o.memoCache.index=0)}N.H=ii,o=t(n,r)}while(aJ)return o}function oe(){var e=N.H,t=e.useState()[0];return t="function"==typeof t.then?ol(t):t,e=e.useState()[0],(null!==aY?aY.memoizedState:null)!==e&&(aK.flags|=1024),t}function ot(){var e=0!==a1;return a1=0,e}function on(e,t,n){t.updateQueue=e.updateQueue,t.flags&=-2053,e.lanes&=~n}function or(e){if(aX){for(e=e.memoizedState;null!==e;){var t=e.queue;null!==t&&(t.pending=null),e=e.next}aX=!1}aQ=0,aZ=aY=aK=null,aJ=!1,a2=a1=0,a4=null}function oa(){var e={memoizedState:null,baseState:null,baseQueue:null,queue:null,next:null};return null===aZ?aK.memoizedState=aZ=e:aZ=aZ.next=e,aZ}function oo(){if(null===aY){var e=aK.alternate;e=null!==e?e.memoizedState:null}else e=aY.next;var t=null===aZ?aK.memoizedState:aZ.next;if(null!==t)aZ=t,aY=e;else{if(null===e){if(null===aK.alternate)throw Error(l(467));throw Error(l(310))}e={memoizedState:(aY=e).memoizedState,baseState:aY.baseState,baseQueue:aY.baseQueue,queue:aY.queue,next:null},null===aZ?aK.memoizedState=aZ=e:aZ=aZ.next=e}return aZ}function oi(){return{lastEffect:null,events:null,stores:null,memoCache:null}}function ol(e){var t=a2;return a2+=1,null===a4&&(a4=[]),e=ac(a4,e,t),t=aK,null===(null===aZ?t.memoizedState:aZ.next)&&(N.H=null===(t=t.alternate)||null===t.memoizedState?ia:io),e}function os(e){if(null!==e&&"object"==typeof e){if("function"==typeof e.then)return ol(e);if(e.$$typeof===k)return rK(e)}throw Error(l(438,String(e)))}function oc(e){var t=null,n=aK.updateQueue;if(null!==n&&(t=n.memoCache),null==t){var r=aK.alternate;null!==r&&null!==(r=r.updateQueue)&&null!=(r=r.memoCache)&&(t={data:r.data.map(function(e){return e.slice()}),index:0})}if(null==t&&(t={data:[],index:0}),null===n&&(n=oi(),aK.updateQueue=n),n.memoCache=t,void 0===(n=t.data[t.index]))for(n=t.data[t.index]=Array(e),r=0;r<e;r++)n[r]=T;return t.index++,n}function ou(e,t){return"function"==typeof t?t(e):t}function od(e){return op(oo(),aY,e)}function op(e,t,n){var r=e.queue;if(null===r)throw Error(l(311));r.lastRenderedReducer=n;var a=e.baseQueue,o=r.pending;if(null!==o){if(null!==a){var i=a.next;a.next=o.next,o.next=i}t.baseQueue=a=o,r.pending=null}if(o=e.baseState,null===a)e.memoizedState=o;else{t=a.next;var s=i=null,c=null,u=t,d=!1;do{var p=-0x20000001&u.lane;if(p!==u.lane?(l$&p)===p:(aQ&p)===p){var f=u.revertLane;if(0===f)null!==c&&(c=c.next={lane:0,revertLane:0,gesture:null,action:u.action,hasEagerState:u.hasEagerState,eagerState:u.eagerState,next:null}),p===r6&&(d=!0);else if((aQ&f)===f){u=u.next,f===r6&&(d=!0);continue}else p={lane:0,revertLane:u.revertLane,gesture:null,action:u.action,hasEagerState:u.hasEagerState,eagerState:u.eagerState,next:null},null===c?(s=c=p,i=o):c=c.next=p,aK.lanes|=f,lQ|=f;p=u.action,a0&&n(o,p),o=u.hasEagerState?u.eagerState:n(o,p)}else f={lane:p,revertLane:u.revertLane,gesture:u.gesture,action:u.action,hasEagerState:u.hasEagerState,eagerState:u.eagerState,next:null},null===c?(s=c=f,i=o):c=c.next=f,aK.lanes|=p,lQ|=p;u=u.next}while(null!==u&&u!==t)if(null===c?i=o:c.next=s,!nA(o,e.memoizedState)&&(ix=!0,d&&null!==(n=r9)))throw n;e.memoizedState=o,e.baseState=i,e.baseQueue=c,r.lastRenderedState=o}return null===a&&(r.lanes=0),[e.memoizedState,r.dispatch]}function of(e){var t=oo(),n=t.queue;if(null===n)throw Error(l(311));n.lastRenderedReducer=e;var r=n.dispatch,a=n.pending,o=t.memoizedState;if(null!==a){n.pending=null;var i=a=a.next;do o=e(o,i.action),i=i.next;while(i!==a)nA(o,t.memoizedState)||(ix=!0),t.memoizedState=o,null===t.baseQueue&&(t.baseState=o),n.lastRenderedState=o}return[o,r]}function om(e,t,n){var r=aK,a=oo(),o=rT;if(o){if(void 0===n)throw Error(l(407));n=n()}else n=t();var i=!nA((aY||a).memoizedState,n);if(i&&(a.memoizedState=n,ix=!0),a=a.queue,oM(ob.bind(null,r,a,e),[e]),a.getSnapshot!==t||i||null!==aZ&&1&aZ.memoizedState.tag){if(r.flags|=2048,oO(9,{destroy:void 0},oh.bind(null,r,a,n,t),null),null===lI)throw Error(l(349));o||0!=(127&aQ)||og(r,t,n)}return n}function og(e,t,n){e.flags|=16384,e={getSnapshot:t,value:n},null===(t=aK.updateQueue)?(t=oi(),aK.updateQueue=t,t.stores=[e]):null===(n=t.stores)?t.stores=[e]:n.push(e)}function oh(e,t,n,r){t.value=n,t.getSnapshot=r,oy(t)&&ov(e)}function ob(e,t,n){return n(function(){oy(t)&&ov(e)})}function oy(e){var t=e.getSnapshot;e=e.value;try{var n=t();return!nA(e,n)}catch(e){return!0}}function ov(e){var t=n6(e,2);null!==t&&ss(t,e,2)}function ow(e){var t=oa();if("function"==typeof e){var n=e;if(e=n(),a0){eg(!0);try{n()}finally{eg(!1)}}}return t.memoizedState=t.baseState=e,t.queue={pending:null,lanes:0,dispatch:null,lastRenderedReducer:ou,lastRenderedState:e},t}function ok(e,t,n,r){return e.baseState=n,op(e,aY,"function"==typeof r?r:ou)}function ox(e,t,n,r,a){if(o7(e))throw Error(l(485));if(null!==(e=t.action)){var o={payload:a,action:e,next:null,isTransition:!0,status:"pending",value:null,reason:null,listeners:[],then:function(e){o.listeners.push(e)}};null!==N.T?n(!0):o.isTransition=!1,r(o),null===(n=t.pending)?(o.next=t.pending=o,oS(t,o)):(o.next=n.next,t.pending=n.next=o)}}function oS(e,t){var n=t.action,r=t.payload,a=e.state;if(t.isTransition){var o=N.T,i={};N.T=i;try{var l=n(a,r),s=N.S;null!==s&&s(i,l),oE(e,t,l)}catch(n){oA(e,t,n)}finally{null!==o&&null!==i.types&&(o.types=i.types),N.T=o}}else try{o=n(a,r),oE(e,t,o)}catch(n){oA(e,t,n)}}function oE(e,t,n){null!==n&&"object"==typeof n&&"function"==typeof n.then?n.then(function(n){o_(e,t,n)},function(n){return oA(e,t,n)}):o_(e,t,n)}function o_(e,t,n){t.status="fulfilled",t.value=n,oC(t),e.state=n,null!==(t=e.pending)&&((n=t.next)===t?e.pending=null:(n=n.next,t.next=n,oS(e,n)))}function oA(e,t,n){var r=e.pending;if(e.pending=null,null!==r){r=r.next;do t.status="rejected",t.reason=n,oC(t),t=t.next;while(t!==r)}e.action=null}function oC(e){e=e.listeners;for(var t=0;t<e.length;t++)(0,e[t])()}function oT(e,t){return t}function oj(e,t){if(rT){var n=lI.formState;if(null!==n){e:{var r=aK;if(rT){if(rC){t:{for(var a=rC,o=rP;8!==a.nodeType;)if(!o||null===(a=cO(a.nextSibling))){a=null;break t}a="F!"===(o=a.data)||"F"===o?a:null}if(a){rC=cO(a.nextSibling),r="F!"===a.data;break e}}rL(r)}r=!1}r&&(t=n[0])}}return(n=oa()).memoizedState=n.baseState=t,r={pending:null,lanes:0,dispatch:null,lastRenderedReducer:oT,lastRenderedState:t},n.queue=r,n=o6.bind(null,aK,r),r.dispatch=n,r=ow(!1),o=o5.bind(null,aK,!1,r.queue),r=oa(),a={state:t,dispatch:null,action:e,pending:null},r.queue=a,n=ox.bind(null,aK,a,o,n),a.dispatch=n,r.memoizedState=e,[t,n,!1]}function oP(e){return oz(oo(),aY,e)}function oz(e,t,n){if(t=op(e,t,oT)[0],e=od(ou)[0],"object"==typeof t&&null!==t&&"function"==typeof t.then)try{var r=ol(t)}catch(e){if(e===aa)throw ai;throw e}else r=t;var a=(t=oo()).queue,o=a.dispatch;return n!==t.memoizedState&&(aK.flags|=2048,oO(9,{destroy:void 0},oL.bind(null,a,n),null)),[r,o,e]}function oL(e,t){e.action=t}function oN(e){var t=oo(),n=aY;if(null!==n)return oz(t,n,e);oo(),t=t.memoizedState;var r=(n=oo()).queue.dispatch;return n.memoizedState=e,[t,r,!1]}function oO(e,t,n,r){return e={tag:e,create:n,deps:r,inst:t,next:null},null===(t=aK.updateQueue)&&(t=oi(),aK.updateQueue=t),null===(n=t.lastEffect)?t.lastEffect=e.next=e:(r=n.next,n.next=e,e.next=r,t.lastEffect=e),e}function oR(){return oo().memoizedState}function oD(e,t,n,r){var a=oa();aK.flags|=e,a.memoizedState=oO(1|t,{destroy:void 0},n,void 0===r?null:r)}function oF(e,t,n,r){var a=oo();r=void 0===r?null:r;var o=a.memoizedState.inst;null!==aY&&null!==r&&a6(r,aY.memoizedState.deps)?a.memoizedState=oO(t,o,n,r):(aK.flags|=e,a.memoizedState=oO(1|t,o,n,r))}function oI(e,t){oD(8390656,8,e,t)}function oM(e,t){oF(2048,8,e,t)}function o$(e){var t=oo().memoizedState,n={ref:t,nextImpl:e};aK.flags|=4;var r=aK.updateQueue;if(null===r)r=oi(),aK.updateQueue=r,r.events=[n];else{var a=r.events;null===a?r.events=[n]:a.push(n)}return function(){if(0!=(2&lF))throw Error(l(440));return t.impl.apply(void 0,arguments)}}function oB(e,t){return oF(4,2,e,t)}function oU(e,t){return oF(4,4,e,t)}function oq(e,t){if("function"==typeof t){var n=t(e=e());return function(){"function"==typeof n?n():t(null)}}if(null!=t)return t.current=e=e(),function(){t.current=null}}function oH(e,t,n){n=null!=n?n.concat([e]):null,oF(4,4,oq.bind(null,t,e),n)}function oG(){}function oV(e,t){var n=oo();t=void 0===t?null:t;var r=n.memoizedState;return null!==t&&a6(t,r[1])?r[0]:(n.memoizedState=[e,t],e)}function oW(e,t){var n=oo();t=void 0===t?null:t;var r=n.memoizedState;if(null!==t&&a6(t,r[1]))return r[0];if(r=e(),a0){eg(!0);try{e()}finally{eg(!1)}}return n.memoizedState=[r,t],r}function oQ(e,t,n){return void 0===n||0!=(0x40000000&aQ)&&0==(261930&l$)?e.memoizedState=t:(e.memoizedState=n,e=sl(),aK.lanes|=e,lQ|=e,n)}function oK(e,t,n,r){return nA(n,t)?n:null!==aO.current?(nA(e=oQ(e,n,r),t)||(ix=!0),e):0==(42&aQ)||0!=(0x40000000&aQ)&&0==(261930&l$)?(ix=!0,e.memoizedState=n):(e=sl(),aK.lanes|=e,lQ|=e,t)}function oY(e,t,n,r,a){var o=O.p;O.p=0!==o&&8>o?o:8;var i=N.T,l={};N.T=l,o5(e,!1,t,n);try{var s=a(),c=N.S;if(null!==c&&c(l,s),null!==s&&"object"==typeof s&&"function"==typeof s.then){var u,d,p=(u=[],d={status:"pending",value:null,reason:null,then:function(e){u.push(e)}},s.then(function(){d.status="fulfilled",d.value=r;for(var e=0;e<u.length;e++)(0,u[e])(r)},function(e){for(d.status="rejected",d.reason=e,e=0;e<u.length;e++)(0,u[e])(void 0)}),d);o9(e,t,p,si(e))}else o9(e,t,r,si(e))}catch(n){o9(e,t,{then:function(){},status:"rejected",reason:n},si())}finally{O.p=o,null!==i&&null!==l.types&&(i.types=l.types),N.T=i}}function oZ(){}function oX(e,t,n,r){if(5!==e.tag)throw Error(l(476));var a=oJ(e).queue;oY(e,a,t,R,null===n?oZ:function(){return o0(e),n(r)})}function oJ(e){var t=e.memoizedState;if(null!==t)return t;var n={};return(t={memoizedState:R,baseState:R,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:ou,lastRenderedState:R},next:null}).next={memoizedState:n,baseState:n,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:ou,lastRenderedState:n},next:null},e.memoizedState=t,null!==(e=e.alternate)&&(e.memoizedState=t),t}function o0(e){var t=oJ(e);null===t.next&&(t=e.alternate.memoizedState),o9(e,t.next.queue,{},si())}function o1(){return rK(ut)}function o2(){return oo().memoizedState}function o4(){return oo().memoizedState}function o3(e){for(var t=e.return;null!==t;){switch(t.tag){case 24:case 3:var n=si(),r=aA(t,e=a_(n),n);null!==r&&(ss(r,t,n),aC(r,t,n)),t={cache:r2()},e.payload=t;return}t=t.return}}function o8(e,t,n){var r=si();n={lane:r,revertLane:0,gesture:null,action:n,hasEagerState:!1,eagerState:null,next:null},o7(e)?ie(t,n):null!==(n=n8(e,t,n,r))&&(ss(n,e,r),it(n,t,r))}function o6(e,t,n){o9(e,t,n,si())}function o9(e,t,n,r){var a={lane:r,revertLane:0,gesture:null,action:n,hasEagerState:!1,eagerState:null,next:null};if(o7(e))ie(t,a);else{var o=e.alternate;if(0===e.lanes&&(null===o||0===o.lanes)&&null!==(o=t.lastRenderedReducer))try{var i=t.lastRenderedState,l=o(i,n);if(a.hasEagerState=!0,a.eagerState=l,nA(l,i))return n3(e,t,a,0),null===lI&&n4(),!1}catch(e){}finally{}if(null!==(n=n8(e,t,a,r)))return ss(n,e,r),it(n,t,r),!0}return!1}function o5(e,t,n,r){if(r={lane:2,revertLane:sJ(),gesture:null,action:r,hasEagerState:!1,eagerState:null,next:null},o7(e)){if(t)throw Error(l(479))}else null!==(t=n8(e,n,r,2))&&ss(t,e,2)}function o7(e){var t=e.alternate;return e===aK||null!==t&&t===aK}function ie(e,t){aJ=aX=!0;var n=e.pending;null===n?t.next=t:(t.next=n.next,n.next=t),e.pending=t}function it(e,t,n){if(0!=(4194048&n)){var r=t.lanes;r&=e.pendingLanes,t.lanes=n|=r,ej(e,n)}}var ir={readContext:rK,use:os,useCallback:a8,useContext:a8,useEffect:a8,useImperativeHandle:a8,useLayoutEffect:a8,useInsertionEffect:a8,useMemo:a8,useReducer:a8,useRef:a8,useState:a8,useDebugValue:a8,useDeferredValue:a8,useTransition:a8,useSyncExternalStore:a8,useId:a8,useHostTransitionStatus:a8,useFormState:a8,useActionState:a8,useOptimistic:a8,useMemoCache:a8,useCacheRefresh:a8};ir.useEffectEvent=a8;var ia={readContext:rK,use:os,useCallback:function(e,t){return oa().memoizedState=[e,void 0===t?null:t],e},useContext:rK,useEffect:oI,useImperativeHandle:function(e,t,n){n=null!=n?n.concat([e]):null,oD(4194308,4,oq.bind(null,t,e),n)},useLayoutEffect:function(e,t){return oD(4194308,4,e,t)},useInsertionEffect:function(e,t){oD(4,2,e,t)},useMemo:function(e,t){var n=oa();t=void 0===t?null:t;var r=e();if(a0){eg(!0);try{e()}finally{eg(!1)}}return n.memoizedState=[r,t],r},useReducer:function(e,t,n){var r=oa();if(void 0!==n){var a=n(t);if(a0){eg(!0);try{n(t)}finally{eg(!1)}}}else a=t;return r.memoizedState=r.baseState=a,r.queue=e={pending:null,lanes:0,dispatch:null,lastRenderedReducer:e,lastRenderedState:a},e=e.dispatch=o8.bind(null,aK,e),[r.memoizedState,e]},useRef:function(e){return oa().memoizedState={current:e}},useState:function(e){var t=(e=ow(e)).queue,n=o6.bind(null,aK,t);return t.dispatch=n,[e.memoizedState,n]},useDebugValue:oG,useDeferredValue:function(e,t){return oQ(oa(),e,t)},useTransition:function(){var e=ow(!1);return e=oY.bind(null,aK,e.queue,!0,!1),oa().memoizedState=e,[!1,e]},useSyncExternalStore:function(e,t,n){var r=aK,a=oa();if(rT){if(void 0===n)throw Error(l(407));n=n()}else{if(n=t(),null===lI)throw Error(l(349));0!=(127&l$)||og(r,t,n)}a.memoizedState=n;var o={value:n,getSnapshot:t};return a.queue=o,oI(ob.bind(null,r,o,e),[e]),r.flags|=2048,oO(9,{destroy:void 0},oh.bind(null,r,o,n,t),null),n},useId:function(){var e=oa(),t=lI.identifierPrefix;if(rT){var n=rw,r=rv;t="_"+t+"R_"+(n=(r&~(1<<32-eh(r)-1)).toString(32)+n),0<(n=a1++)&&(t+="H"+n.toString(32)),t+="_"}else t="_"+t+"r_"+(n=a3++).toString(32)+"_";return e.memoizedState=t},useHostTransitionStatus:o1,useFormState:oj,useActionState:oj,useOptimistic:function(e){var t=oa();t.memoizedState=t.baseState=e;var n={pending:null,lanes:0,dispatch:null,lastRenderedReducer:null,lastRenderedState:null};return t.queue=n,t=o5.bind(null,aK,!0,n),n.dispatch=t,[e,t]},useMemoCache:oc,useCacheRefresh:function(){return oa().memoizedState=o3.bind(null,aK)},useEffectEvent:function(e){var t=oa(),n={impl:e};return t.memoizedState=n,function(){if(0!=(2&lF))throw Error(l(440));return n.impl.apply(void 0,arguments)}}},io={readContext:rK,use:os,useCallback:oV,useContext:rK,useEffect:oM,useImperativeHandle:oH,useInsertionEffect:oB,useLayoutEffect:oU,useMemo:oW,useReducer:od,useRef:oR,useState:function(){return od(ou)},useDebugValue:oG,useDeferredValue:function(e,t){return oK(oo(),aY.memoizedState,e,t)},useTransition:function(){var e=od(ou)[0],t=oo().memoizedState;return["boolean"==typeof e?e:ol(e),t]},useSyncExternalStore:om,useId:o2,useHostTransitionStatus:o1,useFormState:oP,useActionState:oP,useOptimistic:function(e,t){return ok(oo(),aY,e,t)},useMemoCache:oc,useCacheRefresh:o4};io.useEffectEvent=o$;var ii={readContext:rK,use:os,useCallback:oV,useContext:rK,useEffect:oM,useImperativeHandle:oH,useInsertionEffect:oB,useLayoutEffect:oU,useMemo:oW,useReducer:of,useRef:oR,useState:function(){return of(ou)},useDebugValue:oG,useDeferredValue:function(e,t){var n=oo();return null===aY?oQ(n,e,t):oK(n,aY.memoizedState,e,t)},useTransition:function(){var e=of(ou)[0],t=oo().memoizedState;return["boolean"==typeof e?e:ol(e),t]},useSyncExternalStore:om,useId:o2,useHostTransitionStatus:o1,useFormState:oN,useActionState:oN,useOptimistic:function(e,t){var n=oo();return null!==aY?ok(n,aY,e,t):(n.baseState=e,[e,n.queue.dispatch])},useMemoCache:oc,useCacheRefresh:o4};function il(e,t,n,r){n=null==(n=n(r,t=e.memoizedState))?t:f({},t,n),e.memoizedState=n,0===e.lanes&&(e.updateQueue.baseState=n)}ii.useEffectEvent=o$;var is={enqueueSetState:function(e,t,n){e=e._reactInternals;var r=si(),a=a_(r);a.payload=t,null!=n&&(a.callback=n),null!==(t=aA(e,a,r))&&(ss(t,e,r),aC(t,e,r))},enqueueReplaceState:function(e,t,n){e=e._reactInternals;var r=si(),a=a_(r);a.tag=1,a.payload=t,null!=n&&(a.callback=n),null!==(t=aA(e,a,r))&&(ss(t,e,r),aC(t,e,r))},enqueueForceUpdate:function(e,t){e=e._reactInternals;var n=si(),r=a_(n);r.tag=2,null!=t&&(r.callback=t),null!==(t=aA(e,r,n))&&(ss(t,e,n),aC(t,e,n))}};function ic(e,t,n,r,a,o,i){return"function"==typeof(e=e.stateNode).shouldComponentUpdate?e.shouldComponentUpdate(r,o,i):!t.prototype||!t.prototype.isPureReactComponent||!nC(n,r)||!nC(a,o)}function iu(e,t,n,r){e=t.state,"function"==typeof t.componentWillReceiveProps&&t.componentWillReceiveProps(n,r),"function"==typeof t.UNSAFE_componentWillReceiveProps&&t.UNSAFE_componentWillReceiveProps(n,r),t.state!==e&&is.enqueueReplaceState(t,t.state,null)}function id(e,t){var n=t;if("ref"in t)for(var r in n={},t)"ref"!==r&&(n[r]=t[r]);if(e=e.defaultProps)for(var a in n===t&&(n=f({},n)),e)void 0===n[a]&&(n[a]=e[a]);return n}function ip(e){nJ(e)}function im(e){console.error(e)}function ig(e){nJ(e)}function ih(e,t){try{(0,e.onUncaughtError)(t.value,{componentStack:t.stack})}catch(e){setTimeout(function(){throw e})}}function ib(e,t,n){try{(0,e.onCaughtError)(n.value,{componentStack:n.stack,errorBoundary:1===t.tag?t.stateNode:null})}catch(e){setTimeout(function(){throw e})}}function iy(e,t,n){return(n=a_(n)).tag=3,n.payload={element:null},n.callback=function(){ih(e,t)},n}function iv(e){return(e=a_(e)).tag=3,e}function iw(e,t,n,r){var a=n.type.getDerivedStateFromError;if("function"==typeof a){var o=r.value;e.payload=function(){return a(o)},e.callback=function(){ib(t,n,r)}}var i=n.stateNode;null!==i&&"function"==typeof i.componentDidCatch&&(e.callback=function(){ib(t,n,r),"function"!=typeof a&&(null===l6?l6=new Set([this]):l6.add(this));var e=r.stack;this.componentDidCatch(r.value,{componentStack:null!==e?e:""})})}var ik=Error(l(461)),ix=!1;function iS(e,t,n,r){t.child=null===e?ak(t,null,n,r):aw(t,e.child,n,r)}function iE(e,t,n,r,a){n=n.render;var o=t.ref;if("ref"in r){var i={};for(var l in r)"ref"!==l&&(i[l]=r[l])}else i=r;return(rQ(t),r=a9(e,t,n,i,o,a),l=ot(),null===e||ix)?(rT&&l&&rS(t),t.flags|=1,iS(e,t,r,a),t.child):(on(e,t,a),iW(e,t,a))}function i_(e,t,n,r,a){if(null===e){var o=n.type;return"function"!=typeof o||rn(o)||void 0!==o.defaultProps||null!==n.compare?((e=ro(n.type,null,r,t,t.mode,a)).ref=t.ref,e.return=t,t.child=e):(t.tag=15,t.type=o,iA(e,t,o,r,a))}if(o=e.child,!iQ(e,a)){var i=o.memoizedProps;if((n=null!==(n=n.compare)?n:nC)(i,r)&&e.ref===t.ref)return iW(e,t,a)}return t.flags|=1,(e=rr(o,r)).ref=t.ref,e.return=t,t.child=e}function iA(e,t,n,r,a){if(null!==e){var o=e.memoizedProps;if(nC(o,r)&&e.ref===t.ref)if(ix=!1,t.pendingProps=r=o,!iQ(e,a))return t.lanes=e.lanes,iW(e,t,a);else 0!=(131072&e.flags)&&(ix=!0)}return iN(e,t,n,r,a)}function iC(e,t,n,r){var a=r.children,o=null!==e?e.memoizedState:null;if(null===e&&null===t.stateNode&&(t.stateNode={_visibility:1,_pendingMarkers:null,_retryCache:null,_transitions:null}),"hidden"===r.mode){if(0!=(128&t.flags)){if(o=null!==o?o.baseLanes|n:n,null!==e){for(a=0,r=t.child=e.child;null!==r;)a=a|r.lanes|r.childLanes,r=r.sibling;r=a&~o}else r=0,t.child=null;return ij(e,t,o,n,r)}if(0==(0x20000000&n))return r=t.lanes=0x20000000,ij(e,t,null!==o?o.baseLanes|n:n,n,r);t.memoizedState={baseLanes:0,cachePool:null},null!==e&&an(t,null!==o?o.cachePool:null),null!==o?aD(t,o):aF(),aq(t)}else null!==o?(an(t,o.cachePool),aD(t,o),aH(t),t.memoizedState=null):(null!==e&&an(t,null),aF(),aH(t));return iS(e,t,a,n),t.child}function iT(e,t){return null!==e&&22===e.tag||null!==t.stateNode||(t.stateNode={_visibility:1,_pendingMarkers:null,_retryCache:null,_transitions:null}),t.sibling}function ij(e,t,n,r,a){var o=at();return t.memoizedState={baseLanes:n,cachePool:o=null===o?null:{parent:r1._currentValue,pool:o}},null!==e&&an(t,null),aF(),aq(t),null!==e&&rV(e,t,r,!0),t.childLanes=a,null}function iP(e,t){return(t=iU({mode:t.mode,children:t.children},e.mode)).ref=e.ref,e.child=t,t.return=e,t}function iz(e,t,n){return aw(t,e.child,null,n),e=iP(t,t.pendingProps),e.flags|=2,aG(t),t.memoizedState=null,e}function iL(e,t){var n=t.ref;if(null===n)null!==e&&null!==e.ref&&(t.flags|=4194816);else{if("function"!=typeof n&&"object"!=typeof n)throw Error(l(284));(null===e||e.ref!==n)&&(t.flags|=4194816)}}function iN(e,t,n,r,a){return(rQ(t),n=a9(e,t,n,r,void 0,a),r=ot(),null===e||ix)?(rT&&r&&rS(t),t.flags|=1,iS(e,t,n,a),t.child):(on(e,t,a),iW(e,t,a))}function iO(e,t,n,r,a,o){return(rQ(t),t.updateQueue=null,n=a7(t,r,n,a),a5(e),r=ot(),null===e||ix)?(rT&&r&&rS(t),t.flags|=1,iS(e,t,n,o),t.child):(on(e,t,o),iW(e,t,o))}function iR(e,t,n,r,a){if(rQ(t),null===t.stateNode){var o=n7,i=n.contextType;"object"==typeof i&&null!==i&&(o=rK(i)),t.memoizedState=null!==(o=new n(r,o)).state&&void 0!==o.state?o.state:null,o.updater=is,t.stateNode=o,o._reactInternals=t,(o=t.stateNode).props=r,o.state=t.memoizedState,o.refs={},aS(t),i=n.contextType,o.context="object"==typeof i&&null!==i?rK(i):n7,o.state=t.memoizedState,"function"==typeof(i=n.getDerivedStateFromProps)&&(il(t,n,i,r),o.state=t.memoizedState),"function"==typeof n.getDerivedStateFromProps||"function"==typeof o.getSnapshotBeforeUpdate||"function"!=typeof o.UNSAFE_componentWillMount&&"function"!=typeof o.componentWillMount||(i=o.state,"function"==typeof o.componentWillMount&&o.componentWillMount(),"function"==typeof o.UNSAFE_componentWillMount&&o.UNSAFE_componentWillMount(),i!==o.state&&is.enqueueReplaceState(o,o.state,null),az(t,r,o,a),aP(),o.state=t.memoizedState),"function"==typeof o.componentDidMount&&(t.flags|=4194308),r=!0}else if(null===e){o=t.stateNode;var l=t.memoizedProps,s=id(n,l);o.props=s;var c=o.context,u=n.contextType;i=n7,"object"==typeof u&&null!==u&&(i=rK(u));var d=n.getDerivedStateFromProps;u="function"==typeof d||"function"==typeof o.getSnapshotBeforeUpdate,l=t.pendingProps!==l,u||"function"!=typeof o.UNSAFE_componentWillReceiveProps&&"function"!=typeof o.componentWillReceiveProps||(l||c!==i)&&iu(t,o,r,i),ax=!1;var p=t.memoizedState;o.state=p,az(t,r,o,a),aP(),c=t.memoizedState,l||p!==c||ax?("function"==typeof d&&(il(t,n,d,r),c=t.memoizedState),(s=ax||ic(t,n,s,r,p,c,i))?(u||"function"!=typeof o.UNSAFE_componentWillMount&&"function"!=typeof o.componentWillMount||("function"==typeof o.componentWillMount&&o.componentWillMount(),"function"==typeof o.UNSAFE_componentWillMount&&o.UNSAFE_componentWillMount()),"function"==typeof o.componentDidMount&&(t.flags|=4194308)):("function"==typeof o.componentDidMount&&(t.flags|=4194308),t.memoizedProps=r,t.memoizedState=c),o.props=r,o.state=c,o.context=i,r=s):("function"==typeof o.componentDidMount&&(t.flags|=4194308),r=!1)}else{o=t.stateNode,aE(e,t),u=id(n,i=t.memoizedProps),o.props=u,d=t.pendingProps,p=o.context,c=n.contextType,s=n7,"object"==typeof c&&null!==c&&(s=rK(c)),(c="function"==typeof(l=n.getDerivedStateFromProps)||"function"==typeof o.getSnapshotBeforeUpdate)||"function"!=typeof o.UNSAFE_componentWillReceiveProps&&"function"!=typeof o.componentWillReceiveProps||(i!==d||p!==s)&&iu(t,o,r,s),ax=!1,p=t.memoizedState,o.state=p,az(t,r,o,a),aP();var f=t.memoizedState;i!==d||p!==f||ax||null!==e&&null!==e.dependencies&&rW(e.dependencies)?("function"==typeof l&&(il(t,n,l,r),f=t.memoizedState),(u=ax||ic(t,n,u,r,p,f,s)||null!==e&&null!==e.dependencies&&rW(e.dependencies))?(c||"function"!=typeof o.UNSAFE_componentWillUpdate&&"function"!=typeof o.componentWillUpdate||("function"==typeof o.componentWillUpdate&&o.componentWillUpdate(r,f,s),"function"==typeof o.UNSAFE_componentWillUpdate&&o.UNSAFE_componentWillUpdate(r,f,s)),"function"==typeof o.componentDidUpdate&&(t.flags|=4),"function"==typeof o.getSnapshotBeforeUpdate&&(t.flags|=1024)):("function"!=typeof o.componentDidUpdate||i===e.memoizedProps&&p===e.memoizedState||(t.flags|=4),"function"!=typeof o.getSnapshotBeforeUpdate||i===e.memoizedProps&&p===e.memoizedState||(t.flags|=1024),t.memoizedProps=r,t.memoizedState=f),o.props=r,o.state=f,o.context=s,r=u):("function"!=typeof o.componentDidUpdate||i===e.memoizedProps&&p===e.memoizedState||(t.flags|=4),"function"!=typeof o.getSnapshotBeforeUpdate||i===e.memoizedProps&&p===e.memoizedState||(t.flags|=1024),r=!1)}return o=r,iL(e,t),r=0!=(128&t.flags),o||r?(o=t.stateNode,n=r&&"function"!=typeof n.getDerivedStateFromError?null:o.render(),t.flags|=1,null!==e&&r?(t.child=aw(t,e.child,null,a),t.child=aw(t,null,n,a)):iS(e,t,n,a),t.memoizedState=o.state,e=t.child):e=iW(e,t,a),e}function iD(e,t,n,r){return rD(),t.flags|=256,iS(e,t,n,r),t.child}var iF={dehydrated:null,treeContext:null,retryLane:0,hydrationErrors:null};function iI(e){return{baseLanes:e,cachePool:ar()}}function iM(e,t,n){return e=null!==e?e.childLanes&~n:0,t&&(e|=lZ),e}function i$(e,t,n){var r,a=t.pendingProps,o=!1,i=0!=(128&t.flags);if((r=i)||(r=(null===e||null!==e.memoizedState)&&0!=(2&aV.current)),r&&(o=!0,t.flags&=-129),r=0!=(32&t.flags),t.flags&=-33,null===e){if(rT){if(o?aB(t):aH(t),(e=rC)?null!==(e=null!==(e=cz(e,rP))&&"&"!==e.data?e:null)&&(t.memoizedState={dehydrated:e,treeContext:null!==ry?{id:rv,overflow:rw}:null,retryLane:0x20000000,hydrationErrors:null},(n=rs(e)).return=t,t.child=n,rA=t,rC=null):e=null,null===e)throw rL(t);return cN(e)?t.lanes=32:t.lanes=0x20000000,null}var s=a.children;return(a=a.fallback,o)?(aH(t),s=iU({mode:"hidden",children:s},o=t.mode),a=ri(a,o,n,null),s.return=t,a.return=t,s.sibling=a,t.child=s,(a=t.child).memoizedState=iI(n),a.childLanes=iM(e,r,n),t.memoizedState=iF,iT(null,a)):(aB(t),iB(t,s))}var c=e.memoizedState;if(null!==c&&null!==(s=c.dehydrated)){if(i)256&t.flags?(aB(t),t.flags&=-257,t=iq(e,t,n)):null!==t.memoizedState?(aH(t),t.child=e.child,t.flags|=128,t=null):(aH(t),s=a.fallback,o=t.mode,a=iU({mode:"visible",children:a.children},o),s=ri(s,o,n,null),s.flags|=2,a.return=t,s.return=t,a.sibling=s,t.child=a,aw(t,e.child,null,n),(a=t.child).memoizedState=iI(n),a.childLanes=iM(e,r,n),t.memoizedState=iF,t=iT(null,a));else if(aB(t),cN(s)){if(r=s.nextSibling&&s.nextSibling.dataset)var u=r.dgst;r=u,(a=Error(l(419))).stack="",a.digest=r,rI({value:a,source:null,stack:null}),t=iq(e,t,n)}else if(ix||rV(e,t,n,!1),r=0!=(n&e.childLanes),ix||r){if(null!==(r=lI)&&0!==(a=eP(r,n))&&a!==c.retryLane)throw c.retryLane=a,n6(e,a),ss(r,e,a),ik;cL(s)||sv(),t=iq(e,t,n)}else cL(s)?(t.flags|=192,t.child=e.child,t=null):(e=c.treeContext,rC=cO(s.nextSibling),rA=t,rT=!0,rj=null,rP=!1,null!==e&&r_(t,e),t=iB(t,a.children),t.flags|=4096);return t}return o?(aH(t),s=a.fallback,o=t.mode,u=(c=e.child).sibling,(a=rr(c,{mode:"hidden",children:a.children})).subtreeFlags=0x3e00000&c.subtreeFlags,null!==u?s=rr(u,s):(s=ri(s,o,n,null),s.flags|=2),s.return=t,a.return=t,a.sibling=s,t.child=a,iT(null,a),a=t.child,null===(s=e.child.memoizedState)?s=iI(n):(null!==(o=s.cachePool)?(c=r1._currentValue,o=o.parent!==c?{parent:c,pool:c}:o):o=ar(),s={baseLanes:s.baseLanes|n,cachePool:o}),a.memoizedState=s,a.childLanes=iM(e,r,n),t.memoizedState=iF,iT(e.child,a)):(aB(t),e=(n=e.child).sibling,(n=rr(n,{mode:"visible",children:a.children})).return=t,n.sibling=null,null!==e&&(null===(r=t.deletions)?(t.deletions=[e],t.flags|=16):r.push(e)),t.child=n,t.memoizedState=null,n)}function iB(e,t){return(t=iU({mode:"visible",children:t},e.mode)).return=e,e.child=t}function iU(e,t){return(e=rt(22,e,null,t)).lanes=0,e}function iq(e,t,n){return aw(t,e.child,null,n),e=iB(t,t.pendingProps.children),e.flags|=2,t.memoizedState=null,e}function iH(e,t,n){e.lanes|=t;var r=e.alternate;null!==r&&(r.lanes|=t),rH(e.return,t,n)}function iG(e,t,n,r,a,o){var i=e.memoizedState;null===i?e.memoizedState={isBackwards:t,rendering:null,renderingStartTime:0,last:r,tail:n,tailMode:a,treeForkCount:o}:(i.isBackwards=t,i.rendering=null,i.renderingStartTime=0,i.last=r,i.tail=n,i.tailMode=a,i.treeForkCount=o)}function iV(e,t,n){var r=t.pendingProps,a=r.revealOrder,o=r.tail;r=r.children;var i=aV.current,l=0!=(2&i);if(l?(i=1&i|2,t.flags|=128):i&=1,$(aV,i),iS(e,t,r,n),r=rT?rg:0,!l&&null!==e&&0!=(128&e.flags))e:for(e=t.child;null!==e;){if(13===e.tag)null!==e.memoizedState&&iH(e,n,t);else if(19===e.tag)iH(e,n,t);else if(null!==e.child){e.child.return=e,e=e.child;continue}if(e===t)break;for(;null===e.sibling;){if(null===e.return||e.return===t)break e;e=e.return}e.sibling.return=e.return,e=e.sibling}switch(a){case"forwards":for(a=null,n=t.child;null!==n;)null!==(e=n.alternate)&&null===aW(e)&&(a=n),n=n.sibling;null===(n=a)?(a=t.child,t.child=null):(a=n.sibling,n.sibling=null),iG(t,!1,a,n,o,r);break;case"backwards":case"unstable_legacy-backwards":for(n=null,a=t.child,t.child=null;null!==a;){if(null!==(e=a.alternate)&&null===aW(e)){t.child=a;break}e=a.sibling,a.sibling=n,n=a,a=e}iG(t,!0,n,null,o,r);break;case"together":iG(t,!1,null,null,void 0,r);break;default:t.memoizedState=null}return t.child}function iW(e,t,n){if(null!==e&&(t.dependencies=e.dependencies),lQ|=t.lanes,0==(n&t.childLanes)){if(null===e)return null;else if(rV(e,t,n,!1),0==(n&t.childLanes))return null}if(null!==e&&t.child!==e.child)throw Error(l(153));if(null!==t.child){for(n=rr(e=t.child,e.pendingProps),t.child=n,n.return=t;null!==e.sibling;)e=e.sibling,(n=n.sibling=rr(e,e.pendingProps)).return=t;n.sibling=null}return t.child}function iQ(e,t){return 0!=(e.lanes&t)||!!(null!==(e=e.dependencies)&&rW(e))}function iK(e,t,n){if(null!==e)if(e.memoizedProps!==t.pendingProps)ix=!0;else{if(!iQ(e,n)&&0==(128&t.flags))return ix=!1,function(e,t,n){switch(t.tag){case 3:G(t,t.stateNode.containerInfo),rU(t,r1,e.memoizedState.cache),rD();break;case 27:case 5:W(t);break;case 4:G(t,t.stateNode.containerInfo);break;case 10:rU(t,t.type,t.memoizedProps.value);break;case 31:if(null!==t.memoizedState)return t.flags|=128,aU(t),null;break;case 13:var r=t.memoizedState;if(null!==r){if(null!==r.dehydrated)return aB(t),t.flags|=128,null;if(0!=(n&t.child.childLanes))return i$(e,t,n);return aB(t),null!==(e=iW(e,t,n))?e.sibling:null}aB(t);break;case 19:var a=0!=(128&e.flags);if((r=0!=(n&t.childLanes))||(rV(e,t,n,!1),r=0!=(n&t.childLanes)),a){if(r)return iV(e,t,n);t.flags|=128}if(null!==(a=t.memoizedState)&&(a.rendering=null,a.tail=null,a.lastEffect=null),$(aV,aV.current),!r)return null;break;case 22:return t.lanes=0,iC(e,t,n,t.pendingProps);case 24:rU(t,r1,e.memoizedState.cache)}return iW(e,t,n)}(e,t,n);ix=0!=(131072&e.flags)}else ix=!1,rT&&0!=(1048576&t.flags)&&rx(t,rg,t.index);switch(t.lanes=0,t.tag){case 16:e:{var r=t.pendingProps;if(e=au(t.elementType),t.type=e,"function"==typeof e)rn(e)?(r=id(e,r),t.tag=1,t=iR(null,t,e,r,n)):(t.tag=0,t=iN(null,t,e,r,n));else{if(null!=e){var a=e.$$typeof;if(a===x){t.tag=11,t=iE(null,t,e,r,n);break e}if(a===_){t.tag=14,t=i_(null,t,e,r,n);break e}}throw Error(l(306,t=function e(t){if(null==t)return null;if("function"==typeof t)return t.$$typeof===z?null:t.displayName||t.name||null;if("string"==typeof t)return t;switch(t){case b:return"Fragment";case v:return"Profiler";case y:return"StrictMode";case S:return"Suspense";case E:return"SuspenseList";case C:return"Activity"}if("object"==typeof t)switch(t.$$typeof){case h:return"Portal";case k:return t.displayName||"Context";case w:return(t._context.displayName||"Context")+".Consumer";case x:var n=t.render;return(t=t.displayName)||(t=""!==(t=n.displayName||n.name||"")?"ForwardRef("+t+")":"ForwardRef"),t;case _:return null!==(n=t.displayName||null)?n:e(t.type)||"Memo";case A:n=t._payload,t=t._init;try{return e(t(n))}catch(e){}}return null}(e)||e,""))}}return t;case 0:return iN(e,t,t.type,t.pendingProps,n);case 1:return a=id(r=t.type,t.pendingProps),iR(e,t,r,a,n);case 3:e:{if(G(t,t.stateNode.containerInfo),null===e)throw Error(l(387));r=t.pendingProps;var o=t.memoizedState;a=o.element,aE(e,t),az(t,r,null,n);var i=t.memoizedState;if(rU(t,r1,r=i.cache),r!==o.cache&&rG(t,[r1],n,!0),aP(),r=i.element,o.isDehydrated)if(o={element:r,isDehydrated:!1,cache:i.cache},t.updateQueue.baseState=o,t.memoizedState=o,256&t.flags){t=iD(e,t,r,n);break e}else if(r!==a){rI(a=rd(Error(l(424)),t)),t=iD(e,t,r,n);break e}else for(rC=cO((e=9===(e=t.stateNode.containerInfo).nodeType?e.body:"HTML"===e.nodeName?e.ownerDocument.body:e).firstChild),rA=t,rT=!0,rj=null,rP=!0,n=ak(t,null,r,n),t.child=n;n;)n.flags=-3&n.flags|4096,n=n.sibling;else{if(rD(),r===a){t=iW(e,t,n);break e}iS(e,t,r,n)}t=t.child}return t;case 26:return iL(e,t),null===e?(n=cV(t.type,null,t.pendingProps,null))?t.memoizedState=n:rT||(n=t.type,e=t.pendingProps,(r=cb(q.current).createElement(n))[eD]=t,r[eF]=e,cf(r,n,e),eK(r),t.stateNode=r):t.memoizedState=cV(t.type,e.memoizedProps,t.pendingProps,e.memoizedState),null;case 27:return W(t),null===e&&rT&&(r=t.stateNode=cI(t.type,t.pendingProps,q.current),rA=t,rP=!0,a=rC,cC(t.type)?(cR=a,rC=cO(r.firstChild)):rC=a),iS(e,t,t.pendingProps.children,n),iL(e,t),null===e&&(t.flags|=4194304),t.child;case 5:return null===e&&rT&&((a=r=rC)&&(null!==(r=function(e,t,n,r){for(;1===e.nodeType;){if(e.nodeName.toLowerCase()!==t.toLowerCase()){if(!r&&("INPUT"!==e.nodeName||"hidden"!==e.type))break}else if(r){if(!e[eq])switch(t){case"meta":if(!e.hasAttribute("itemprop"))break;return e;case"link":if("stylesheet"===(a=e.getAttribute("rel"))&&e.hasAttribute("data-precedence")||a!==n.rel||e.getAttribute("href")!==(null==n.href||""===n.href?null:n.href)||e.getAttribute("crossorigin")!==(null==n.crossOrigin?null:n.crossOrigin)||e.getAttribute("title")!==(null==n.title?null:n.title))break;return e;case"style":if(e.hasAttribute("data-precedence"))break;return e;case"script":if(((a=e.getAttribute("src"))!==(null==n.src?null:n.src)||e.getAttribute("type")!==(null==n.type?null:n.type)||e.getAttribute("crossorigin")!==(null==n.crossOrigin?null:n.crossOrigin))&&a&&e.hasAttribute("async")&&!e.hasAttribute("itemprop"))break;return e;default:return e}}else{if("input"!==t||"hidden"!==e.type)return e;var a=null==n.name?null:""+n.name;if("hidden"===n.type&&e.getAttribute("name")===a)return e}if(null===(e=cO(e.nextSibling)))break}return null}(r,t.type,t.pendingProps,rP))?(t.stateNode=r,rA=t,rC=cO(r.firstChild),rP=!1,a=!0):a=!1),a||rL(t)),W(t),a=t.type,o=t.pendingProps,i=null!==e?e.memoizedProps:null,r=o.children,cw(a,o)?r=null:null!==i&&cw(a,i)&&(t.flags|=32),null!==t.memoizedState&&(ut._currentValue=a=a9(e,t,oe,null,null,n)),iL(e,t),iS(e,t,r,n),t.child;case 6:return null===e&&rT&&((e=n=rC)&&(null!==(n=function(e,t,n){if(""===t)return null;for(;3!==e.nodeType;)if((1!==e.nodeType||"INPUT"!==e.nodeName||"hidden"!==e.type)&&!n||null===(e=cO(e.nextSibling)))return null;return e}(n,t.pendingProps,rP))?(t.stateNode=n,rA=t,rC=null,e=!0):e=!1),e||rL(t)),null;case 13:return i$(e,t,n);case 4:return G(t,t.stateNode.containerInfo),r=t.pendingProps,null===e?t.child=aw(t,null,r,n):iS(e,t,r,n),t.child;case 11:return iE(e,t,t.type,t.pendingProps,n);case 7:return iS(e,t,t.pendingProps,n),t.child;case 8:case 12:return iS(e,t,t.pendingProps.children,n),t.child;case 10:return r=t.pendingProps,rU(t,t.type,r.value),iS(e,t,r.children,n),t.child;case 9:return a=t.type._context,r=t.pendingProps.children,rQ(t),r=r(a=rK(a)),t.flags|=1,iS(e,t,r,n),t.child;case 14:return i_(e,t,t.type,t.pendingProps,n);case 15:return iA(e,t,t.type,t.pendingProps,n);case 19:return iV(e,t,n);case 31:var s=e,c=t,u=n,d=c.pendingProps,p=0!=(128&c.flags);if(c.flags&=-129,null===s){if(rT){if("hidden"===d.mode)return s=iP(c,d),c.lanes=0x20000000,iT(null,s);if(aU(c),(s=rC)?null!==(s=null!==(s=cz(s,rP))&&"&"===s.data?s:null)&&(c.memoizedState={dehydrated:s,treeContext:null!==ry?{id:rv,overflow:rw}:null,retryLane:0x20000000,hydrationErrors:null},(u=rs(s)).return=c,c.child=u,rA=c,rC=null):s=null,null===s)throw rL(c);return c.lanes=0x20000000,null}return iP(c,d)}var f=s.memoizedState;if(null!==f){var m=f.dehydrated;if(aU(c),p)if(256&c.flags)c.flags&=-257,c=iz(s,c,u);else if(null!==c.memoizedState)c.child=s.child,c.flags|=128,c=null;else throw Error(l(558));else if(ix||rV(s,c,u,!1),p=0!=(u&s.childLanes),ix||p){if(null!==(d=lI)&&0!==(m=eP(d,u))&&m!==f.retryLane)throw f.retryLane=m,n6(s,m),ss(d,s,m),ik;sv(),c=iz(s,c,u)}else s=f.treeContext,rC=cO(m.nextSibling),rA=c,rT=!0,rj=null,rP=!1,null!==s&&r_(c,s),c=iP(c,d),c.flags|=4096;return c}return(s=rr(s.child,{mode:d.mode,children:d.children})).ref=c.ref,c.child=s,s.return=c,s;case 22:return iC(e,t,n,t.pendingProps);case 24:return rQ(t),r=rK(r1),null===e?(null===(a=at())&&(a=lI,o=r2(),a.pooledCache=o,o.refCount++,null!==o&&(a.pooledCacheLanes|=n),a=o),t.memoizedState={parent:r,cache:a},aS(t),rU(t,r1,a)):(0!=(e.lanes&n)&&(aE(e,t),az(t,null,null,n),aP()),a=e.memoizedState,o=t.memoizedState,a.parent!==r?(a={parent:r,cache:r},t.memoizedState=a,0===t.lanes&&(t.memoizedState=t.updateQueue.baseState=a),rU(t,r1,r)):(rU(t,r1,r=o.cache),r!==a.cache&&rG(t,[r1],n,!0))),iS(e,t,t.pendingProps.children,n),t.child;case 29:throw t.pendingProps}throw Error(l(156,t.tag))}function iY(e){e.flags|=4}function iZ(e,t,n,r,a){if((t=0!=(32&e.mode))&&(t=!1),t){if(e.flags|=0x1000000,(0x13ffff40&a)===a)if(e.stateNode.complete)e.flags|=8192;else if(sh())e.flags|=8192;else throw ad=al,ao}else e.flags&=-0x1000001}function iX(e,t){if("stylesheet"!==t.type||0!=(4&t.state.loading))e.flags&=-0x1000001;else if(e.flags|=0x1000000,!c8(t))if(sh())e.flags|=8192;else throw ad=al,ao}function iJ(e,t){null!==t&&(e.flags|=4),16384&e.flags&&(t=22!==e.tag?e_():0x20000000,e.lanes|=t,lX|=t)}function i0(e,t){if(!rT)switch(e.tailMode){case"hidden":t=e.tail;for(var n=null;null!==t;)null!==t.alternate&&(n=t),t=t.sibling;null===n?e.tail=null:n.sibling=null;break;case"collapsed":n=e.tail;for(var r=null;null!==n;)null!==n.alternate&&(r=n),n=n.sibling;null===r?t||null===e.tail?e.tail=null:e.tail.sibling=null:r.sibling=null}}function i1(e){var t=null!==e.alternate&&e.alternate.child===e.child,n=0,r=0;if(t)for(var a=e.child;null!==a;)n|=a.lanes|a.childLanes,r|=0x3e00000&a.subtreeFlags,r|=0x3e00000&a.flags,a.return=e,a=a.sibling;else for(a=e.child;null!==a;)n|=a.lanes|a.childLanes,r|=a.subtreeFlags,r|=a.flags,a.return=e,a=a.sibling;return e.subtreeFlags|=r,e.childLanes=n,t}function i2(e,t){switch(rE(t),t.tag){case 3:rq(r1),V();break;case 26:case 27:case 5:Q(t);break;case 4:V();break;case 31:null!==t.memoizedState&&aG(t);break;case 13:aG(t);break;case 19:M(aV);break;case 10:rq(t.type);break;case 22:case 23:aG(t),aI(),null!==e&&M(ae);break;case 24:rq(r1)}}function i4(e,t){try{var n=t.updateQueue,r=null!==n?n.lastEffect:null;if(null!==r){var a=r.next;n=a;do{if((n.tag&e)===e){r=void 0;var o=n.create;n.inst.destroy=r=o()}n=n.next}while(n!==a)}}catch(e){sO(t,t.return,e)}}function i3(e,t,n){try{var r=t.updateQueue,a=null!==r?r.lastEffect:null;if(null!==a){var o=a.next;r=o;do{if((r.tag&e)===e){var i=r.inst,l=i.destroy;if(void 0!==l){i.destroy=void 0,a=t;try{l()}catch(e){sO(a,n,e)}}}r=r.next}while(r!==o)}}catch(e){sO(t,t.return,e)}}function i8(e){var t=e.updateQueue;if(null!==t){var n=e.stateNode;try{aN(t,n)}catch(t){sO(e,e.return,t)}}}function i6(e,t,n){n.props=id(e.type,e.memoizedProps),n.state=e.memoizedState;try{n.componentWillUnmount()}catch(n){sO(e,t,n)}}function i9(e,t){try{var n=e.ref;if(null!==n){switch(e.tag){case 26:case 27:case 5:var r=e.stateNode;break;default:r=e.stateNode}"function"==typeof n?e.refCleanup=n(r):n.current=r}}catch(n){sO(e,t,n)}}function i5(e,t){var n=e.ref,r=e.refCleanup;if(null!==n)if("function"==typeof r)try{r()}catch(n){sO(e,t,n)}finally{e.refCleanup=null,null!=(e=e.alternate)&&(e.refCleanup=null)}else if("function"==typeof n)try{n(null)}catch(n){sO(e,t,n)}else n.current=null}function i7(e){var t=e.type,n=e.memoizedProps,r=e.stateNode;try{switch(t){case"button":case"input":case"select":case"textarea":n.autoFocus&&r.focus();break;case"img":n.src?r.src=n.src:n.srcSet&&(r.srcset=n.srcSet)}}catch(t){sO(e,e.return,t)}}function le(e,t,n){try{var r=e.stateNode;(function(e,t,n,r){switch(t){case"div":case"span":case"svg":case"path":case"a":case"g":case"p":case"li":break;case"input":var a=null,o=null,i=null,s=null,c=null,u=null,d=null;for(m in n){var p=n[m];if(n.hasOwnProperty(m)&&null!=p)switch(m){case"checked":case"value":break;case"defaultValue":c=p;default:r.hasOwnProperty(m)||cd(e,t,m,null,r,p)}}for(var f in r){var m=r[f];if(p=n[f],r.hasOwnProperty(f)&&(null!=m||null!=p))switch(f){case"type":o=m;break;case"name":a=m;break;case"checked":u=m;break;case"defaultChecked":d=m;break;case"value":i=m;break;case"defaultValue":s=m;break;case"children":case"dangerouslySetInnerHTML":if(null!=m)throw Error(l(137,t));break;default:m!==p&&cd(e,t,f,m,r,p)}}tr(e,i,s,c,u,d,o,a);return;case"select":for(o in m=i=s=f=null,n)if(c=n[o],n.hasOwnProperty(o)&&null!=c)switch(o){case"value":break;case"multiple":m=c;default:r.hasOwnProperty(o)||cd(e,t,o,null,r,c)}for(a in r)if(o=r[a],c=n[a],r.hasOwnProperty(a)&&(null!=o||null!=c))switch(a){case"value":f=o;break;case"defaultValue":s=o;break;case"multiple":i=o;default:o!==c&&cd(e,t,a,o,r,c)}t=s,n=i,r=m,null!=f?ti(e,!!n,f,!1):!!r!=!!n&&(null!=t?ti(e,!!n,t,!0):ti(e,!!n,n?[]:"",!1));return;case"textarea":for(s in m=f=null,n)if(a=n[s],n.hasOwnProperty(s)&&null!=a&&!r.hasOwnProperty(s))switch(s){case"value":case"children":break;default:cd(e,t,s,null,r,a)}for(i in r)if(a=r[i],o=n[i],r.hasOwnProperty(i)&&(null!=a||null!=o))switch(i){case"value":f=a;break;case"defaultValue":m=a;break;case"children":break;case"dangerouslySetInnerHTML":if(null!=a)throw Error(l(91));break;default:a!==o&&cd(e,t,i,a,r,o)}tl(e,f,m);return;case"option":for(var g in n)f=n[g],n.hasOwnProperty(g)&&null!=f&&!r.hasOwnProperty(g)&&("selected"===g?e.selected=!1:cd(e,t,g,null,r,f));for(c in r)f=r[c],m=n[c],r.hasOwnProperty(c)&&f!==m&&(null!=f||null!=m)&&("selected"===c?e.selected=f&&"function"!=typeof f&&"symbol"!=typeof f:cd(e,t,c,f,r,m));return;case"img":case"link":case"area":case"base":case"br":case"col":case"embed":case"hr":case"keygen":case"meta":case"param":case"source":case"track":case"wbr":case"menuitem":for(var h in n)f=n[h],n.hasOwnProperty(h)&&null!=f&&!r.hasOwnProperty(h)&&cd(e,t,h,null,r,f);for(u in r)if(f=r[u],m=n[u],r.hasOwnProperty(u)&&f!==m&&(null!=f||null!=m))switch(u){case"children":case"dangerouslySetInnerHTML":if(null!=f)throw Error(l(137,t));break;default:cd(e,t,u,f,r,m)}return;default:if(tf(t)){for(var b in n)f=n[b],n.hasOwnProperty(b)&&void 0!==f&&!r.hasOwnProperty(b)&&cp(e,t,b,void 0,r,f);for(d in r)f=r[d],m=n[d],r.hasOwnProperty(d)&&f!==m&&(void 0!==f||void 0!==m)&&cp(e,t,d,f,r,m);return}}for(var y in n)f=n[y],n.hasOwnProperty(y)&&null!=f&&!r.hasOwnProperty(y)&&cd(e,t,y,null,r,f);for(p in r)f=r[p],m=n[p],r.hasOwnProperty(p)&&f!==m&&(null!=f||null!=m)&&cd(e,t,p,f,r,m)})(r,e.type,n,t),r[eF]=t}catch(t){sO(e,e.return,t)}}function lt(e){return 5===e.tag||3===e.tag||26===e.tag||27===e.tag&&cC(e.type)||4===e.tag}function ln(e){e:for(;;){for(;null===e.sibling;){if(null===e.return||lt(e.return))return null;e=e.return}for(e.sibling.return=e.return,e=e.sibling;5!==e.tag&&6!==e.tag&&18!==e.tag;){if(27===e.tag&&cC(e.type)||2&e.flags||null===e.child||4===e.tag)continue e;e.child.return=e,e=e.child}if(!(2&e.flags))return e.stateNode}}function lr(e,t,n){var r=e.tag;if(5===r||6===r)e=e.stateNode,t?n.insertBefore(e,t):n.appendChild(e);else if(4!==r&&(27===r&&cC(e.type)&&(n=e.stateNode),null!==(e=e.child)))for(lr(e,t,n),e=e.sibling;null!==e;)lr(e,t,n),e=e.sibling}function la(e){var t=e.stateNode,n=e.memoizedProps;try{for(var r=e.type,a=t.attributes;a.length;)t.removeAttributeNode(a[0]);cf(t,r,n),t[eD]=e,t[eF]=n}catch(t){sO(e,e.return,t)}}var lo=!1,li=!1,ll=!1,ls="function"==typeof WeakSet?WeakSet:Set,lc=null;function lu(e,t,n){var r=n.flags;switch(n.tag){case 0:case 11:case 15:lx(e,n),4&r&&i4(5,n);break;case 1:if(lx(e,n),4&r)if(e=n.stateNode,null===t)try{e.componentDidMount()}catch(e){sO(n,n.return,e)}else{var a=id(n.type,t.memoizedProps);t=t.memoizedState;try{e.componentDidUpdate(a,t,e.__reactInternalSnapshotBeforeUpdate)}catch(e){sO(n,n.return,e)}}64&r&&i8(n),512&r&&i9(n,n.return);break;case 3:if(lx(e,n),64&r&&null!==(e=n.updateQueue)){if(t=null,null!==n.child)switch(n.child.tag){case 27:case 5:case 1:t=n.child.stateNode}try{aN(e,t)}catch(e){sO(n,n.return,e)}}break;case 27:null===t&&4&r&&la(n);case 26:case 5:lx(e,n),null===t&&4&r&&i7(n),512&r&&i9(n,n.return);break;case 12:default:lx(e,n);break;case 31:lx(e,n),4&r&&lg(e,n);break;case 13:lx(e,n),4&r&&lh(e,n),64&r&&null!==(e=n.memoizedState)&&null!==(e=e.dehydrated)&&function(e,t){var n=e.ownerDocument;if("$~"===e.data)e._reactRetry=t;else if("$?"!==e.data||"loading"!==n.readyState)t();else{var r=function(){t(),n.removeEventListener("DOMContentLoaded",r)};n.addEventListener("DOMContentLoaded",r),e._reactRetry=r}}(e,n=sI.bind(null,n));break;case 22:if(!(r=null!==n.memoizedState||lo)){t=null!==t&&null!==t.memoizedState||li,a=lo;var o=li;lo=r,(li=t)&&!o?function e(t,n,r){for(r=r&&0!=(8772&n.subtreeFlags),n=n.child;null!==n;){var a=n.alternate,o=t,i=n,l=i.flags;switch(i.tag){case 0:case 11:case 15:e(o,i,r),i4(4,i);break;case 1:if(e(o,i,r),"function"==typeof(o=(a=i).stateNode).componentDidMount)try{o.componentDidMount()}catch(e){sO(a,a.return,e)}if(null!==(o=(a=i).updateQueue)){var s=a.stateNode;try{var c=o.shared.hiddenCallbacks;if(null!==c)for(o.shared.hiddenCallbacks=null,o=0;o<c.length;o++)aL(c[o],s)}catch(e){sO(a,a.return,e)}}r&&64&l&&i8(i),i9(i,i.return);break;case 27:la(i);case 26:case 5:e(o,i,r),r&&null===a&&4&l&&i7(i),i9(i,i.return);break;case 12:default:e(o,i,r);break;case 31:e(o,i,r),r&&4&l&&lg(o,i);break;case 13:e(o,i,r),r&&4&l&&lh(o,i);break;case 22:null===i.memoizedState&&e(o,i,r),i9(i,i.return);case 30:}n=n.sibling}}(e,n,0!=(8772&n.subtreeFlags)):lx(e,n),lo=a,li=o}case 30:}}var ld=null,lp=!1;function lf(e,t,n){for(n=n.child;null!==n;)lm(e,t,n),n=n.sibling}function lm(e,t,n){if(em&&"function"==typeof em.onCommitFiberUnmount)try{em.onCommitFiberUnmount(ef,n)}catch(e){}switch(n.tag){case 26:li||i5(n,t),lf(e,t,n),n.memoizedState?n.memoizedState.count--:n.stateNode&&(n=n.stateNode).parentNode.removeChild(n);break;case 27:li||i5(n,t);var r=ld,a=lp;cC(n.type)&&(ld=n.stateNode,lp=!1),lf(e,t,n),cM(n.stateNode),ld=r,lp=a;break;case 5:li||i5(n,t);case 6:if(r=ld,a=lp,ld=null,lf(e,t,n),ld=r,lp=a,null!==ld)if(lp)try{(9===ld.nodeType?ld.body:"HTML"===ld.nodeName?ld.ownerDocument.body:ld).removeChild(n.stateNode)}catch(e){sO(n,t,e)}else try{ld.removeChild(n.stateNode)}catch(e){sO(n,t,e)}break;case 18:null!==ld&&(lp?(cT(9===(e=ld).nodeType?e.body:"HTML"===e.nodeName?e.ownerDocument.body:e,n.stateNode),uO(e)):cT(ld,n.stateNode));break;case 4:r=ld,a=lp,ld=n.stateNode.containerInfo,lp=!0,lf(e,t,n),ld=r,lp=a;break;case 0:case 11:case 14:case 15:i3(2,n,t),li||i3(4,n,t),lf(e,t,n);break;case 1:li||(i5(n,t),"function"==typeof(r=n.stateNode).componentWillUnmount&&i6(n,t,r)),lf(e,t,n);break;case 21:default:lf(e,t,n);break;case 22:li=(r=li)||null!==n.memoizedState,lf(e,t,n),li=r}}function lg(e,t){if(null===t.memoizedState&&null!==(e=t.alternate)&&null!==(e=e.memoizedState)){e=e.dehydrated;try{uO(e)}catch(e){sO(t,t.return,e)}}}function lh(e,t){if(null===t.memoizedState&&null!==(e=t.alternate)&&null!==(e=e.memoizedState)&&null!==(e=e.dehydrated))try{uO(e)}catch(e){sO(t,t.return,e)}}function lb(e,t){var n=function(e){switch(e.tag){case 31:case 13:case 19:var t=e.stateNode;return null===t&&(t=e.stateNode=new ls),t;case 22:return null===(t=(e=e.stateNode)._retryCache)&&(t=e._retryCache=new ls),t;default:throw Error(l(435,e.tag))}}(e);t.forEach(function(t){if(!n.has(t)){n.add(t);var r=sM.bind(null,e,t);t.then(r,r)}})}function ly(e,t){var n=t.deletions;if(null!==n)for(var r=0;r<n.length;r++){var a=n[r],o=e,i=t,s=i;e:for(;null!==s;){switch(s.tag){case 27:if(cC(s.type)){ld=s.stateNode,lp=!1;break e}break;case 5:ld=s.stateNode,lp=!1;break e;case 3:case 4:ld=s.stateNode.containerInfo,lp=!0;break e}s=s.return}if(null===ld)throw Error(l(160));lm(o,i,a),ld=null,lp=!1,null!==(o=a.alternate)&&(o.return=null),a.return=null}if(13886&t.subtreeFlags)for(t=t.child;null!==t;)lw(t,e),t=t.sibling}var lv=null;function lw(e,t){var n=e.alternate,r=e.flags;switch(e.tag){case 0:case 11:case 14:case 15:ly(t,e),lk(e),4&r&&(i3(3,e,e.return),i4(3,e),i3(5,e,e.return));break;case 1:ly(t,e),lk(e),512&r&&(li||null===n||i5(n,n.return)),64&r&&lo&&null!==(e=e.updateQueue)&&null!==(r=e.callbacks)&&(n=e.shared.hiddenCallbacks,e.shared.hiddenCallbacks=null===n?r:n.concat(r));break;case 26:var a=lv;if(ly(t,e),lk(e),512&r&&(li||null===n||i5(n,n.return)),4&r){var o=null!==n?n.memoizedState:null;if(r=e.memoizedState,null===n)if(null===r)if(null===e.stateNode){e:{r=e.type,n=e.memoizedProps,a=a.ownerDocument||a;t:switch(r){case"title":(!(o=a.getElementsByTagName("title")[0])||o[eq]||o[eD]||"http://www.w3.org/2000/svg"===o.namespaceURI||o.hasAttribute("itemprop"))&&(o=a.createElement(r),a.head.insertBefore(o,a.querySelector("head > title"))),cf(o,r,n),o[eD]=e,eK(o),r=o;break e;case"link":var i=c4("link","href",a).get(r+(n.href||""));if(i){for(var s=0;s<i.length;s++)if((o=i[s]).getAttribute("href")===(null==n.href||""===n.href?null:n.href)&&o.getAttribute("rel")===(null==n.rel?null:n.rel)&&o.getAttribute("title")===(null==n.title?null:n.title)&&o.getAttribute("crossorigin")===(null==n.crossOrigin?null:n.crossOrigin)){i.splice(s,1);break t}}cf(o=a.createElement(r),r,n),a.head.appendChild(o);break;case"meta":if(i=c4("meta","content",a).get(r+(n.content||""))){for(s=0;s<i.length;s++)if((o=i[s]).getAttribute("content")===(null==n.content?null:""+n.content)&&o.getAttribute("name")===(null==n.name?null:n.name)&&o.getAttribute("property")===(null==n.property?null:n.property)&&o.getAttribute("http-equiv")===(null==n.httpEquiv?null:n.httpEquiv)&&o.getAttribute("charset")===(null==n.charSet?null:n.charSet)){i.splice(s,1);break t}}cf(o=a.createElement(r),r,n),a.head.appendChild(o);break;default:throw Error(l(468,r))}o[eD]=e,eK(o),r=o}e.stateNode=r}else c3(a,e.type,e.stateNode);else e.stateNode=cX(a,r,e.memoizedProps);else o!==r?(null===o?null!==n.stateNode&&(n=n.stateNode).parentNode.removeChild(n):o.count--,null===r?c3(a,e.type,e.stateNode):cX(a,r,e.memoizedProps)):null===r&&null!==e.stateNode&&le(e,e.memoizedProps,n.memoizedProps)}break;case 27:ly(t,e),lk(e),512&r&&(li||null===n||i5(n,n.return)),null!==n&&4&r&&le(e,e.memoizedProps,n.memoizedProps);break;case 5:if(ly(t,e),lk(e),512&r&&(li||null===n||i5(n,n.return)),32&e.flags){a=e.stateNode;try{tc(a,"")}catch(t){sO(e,e.return,t)}}4&r&&null!=e.stateNode&&(a=e.memoizedProps,le(e,a,null!==n?n.memoizedProps:a)),1024&r&&(ll=!0);break;case 6:if(ly(t,e),lk(e),4&r){if(null===e.stateNode)throw Error(l(162));r=e.memoizedProps,n=e.stateNode;try{n.nodeValue=r}catch(t){sO(e,e.return,t)}}break;case 3:if(c2=null,a=lv,lv=cU(t.containerInfo),ly(t,e),lv=a,lk(e),4&r&&null!==n&&n.memoizedState.isDehydrated)try{uO(t.containerInfo)}catch(t){sO(e,e.return,t)}ll&&(ll=!1,function e(t){if(1024&t.subtreeFlags)for(t=t.child;null!==t;){var n=t;e(n),5===n.tag&&1024&n.flags&&n.stateNode.reset(),t=t.sibling}}(e));break;case 4:r=lv,lv=cU(e.stateNode.containerInfo),ly(t,e),lk(e),lv=r;break;case 12:default:ly(t,e),lk(e);break;case 31:case 19:ly(t,e),lk(e),4&r&&null!==(r=e.updateQueue)&&(e.updateQueue=null,lb(e,r));break;case 13:ly(t,e),lk(e),8192&e.child.flags&&null!==e.memoizedState!=(null!==n&&null!==n.memoizedState)&&(l2=ea()),4&r&&null!==(r=e.updateQueue)&&(e.updateQueue=null,lb(e,r));break;case 22:a=null!==e.memoizedState;var c=null!==n&&null!==n.memoizedState,u=lo,d=li;if(lo=u||a,li=d||c,ly(t,e),li=d,lo=u,lk(e),8192&r)e:for((t=e.stateNode)._visibility=a?-2&t._visibility:1|t._visibility,a&&(null===n||c||lo||li||function e(t){for(t=t.child;null!==t;){var n=t;switch(n.tag){case 0:case 11:case 14:case 15:i3(4,n,n.return),e(n);break;case 1:i5(n,n.return);var r=n.stateNode;"function"==typeof r.componentWillUnmount&&i6(n,n.return,r),e(n);break;case 27:cM(n.stateNode);case 26:case 5:i5(n,n.return),e(n);break;case 22:null===n.memoizedState&&e(n);break;default:e(n)}t=t.sibling}}(e)),n=null,t=e;;){if(5===t.tag||26===t.tag){if(null===n){c=n=t;try{if(o=c.stateNode,a)i=o.style,"function"==typeof i.setProperty?i.setProperty("display","none","important"):i.display="none";else{s=c.stateNode;var p=c.memoizedProps.style,f=null!=p&&p.hasOwnProperty("display")?p.display:null;s.style.display=null==f||"boolean"==typeof f?"":(""+f).trim()}}catch(e){sO(c,c.return,e)}}}else if(6===t.tag){if(null===n){c=t;try{c.stateNode.nodeValue=a?"":c.memoizedProps}catch(e){sO(c,c.return,e)}}}else if(18===t.tag){if(null===n){c=t;try{var m=c.stateNode;a?cj(m,!0):cj(c.stateNode,!1)}catch(e){sO(c,c.return,e)}}}else if((22!==t.tag&&23!==t.tag||null===t.memoizedState||t===e)&&null!==t.child){t.child.return=t,t=t.child;continue}if(t===e)break;for(;null===t.sibling;){if(null===t.return||t.return===e)break e;n===t&&(n=null),t=t.return}n===t&&(n=null),t.sibling.return=t.return,t=t.sibling}4&r&&null!==(r=e.updateQueue)&&null!==(n=r.retryQueue)&&(r.retryQueue=null,lb(e,n));case 30:case 21:}}function lk(e){var t=e.flags;if(2&t){try{for(var n,r=e.return;null!==r;){if(lt(r)){n=r;break}r=r.return}if(null==n)throw Error(l(160));switch(n.tag){case 27:var a=n.stateNode,o=ln(e);lr(e,o,a);break;case 5:var i=n.stateNode;32&n.flags&&(tc(i,""),n.flags&=-33);var s=ln(e);lr(e,s,i);break;case 3:case 4:var c=n.stateNode.containerInfo,u=ln(e);!function e(t,n,r){var a=t.tag;if(5===a||6===a)t=t.stateNode,n?(9===r.nodeType?r.body:"HTML"===r.nodeName?r.ownerDocument.body:r).insertBefore(t,n):((n=9===r.nodeType?r.body:"HTML"===r.nodeName?r.ownerDocument.body:r).appendChild(t),null!=(r=r._reactRootContainer)||null!==n.onclick||(n.onclick=tb));else if(4!==a&&(27===a&&cC(t.type)&&(r=t.stateNode,n=null),null!==(t=t.child)))for(e(t,n,r),t=t.sibling;null!==t;)e(t,n,r),t=t.sibling}(e,u,c);break;default:throw Error(l(161))}}catch(t){sO(e,e.return,t)}e.flags&=-3}4096&t&&(e.flags&=-4097)}function lx(e,t){if(8772&t.subtreeFlags)for(t=t.child;null!==t;)lu(e,t.alternate,t),t=t.sibling}function lS(e,t){var n=null;null!==e&&null!==e.memoizedState&&null!==e.memoizedState.cachePool&&(n=e.memoizedState.cachePool.pool),e=null,null!==t.memoizedState&&null!==t.memoizedState.cachePool&&(e=t.memoizedState.cachePool.pool),e!==n&&(null!=e&&e.refCount++,null!=n&&r4(n))}function lE(e,t){e=null,null!==t.alternate&&(e=t.alternate.memoizedState.cache),(t=t.memoizedState.cache)!==e&&(t.refCount++,null!=e&&r4(e))}function l_(e,t,n,r){if(10256&t.subtreeFlags)for(t=t.child;null!==t;)lA(e,t,n,r),t=t.sibling}function lA(e,t,n,r){var a=t.flags;switch(t.tag){case 0:case 11:case 15:l_(e,t,n,r),2048&a&&i4(9,t);break;case 1:case 31:case 13:default:l_(e,t,n,r);break;case 3:l_(e,t,n,r),2048&a&&(e=null,null!==t.alternate&&(e=t.alternate.memoizedState.cache),(t=t.memoizedState.cache)!==e&&(t.refCount++,null!=e&&r4(e)));break;case 12:if(2048&a){l_(e,t,n,r),e=t.stateNode;try{var o=t.memoizedProps,i=o.id,l=o.onPostCommit;"function"==typeof l&&l(i,null===t.alternate?"mount":"update",e.passiveEffectDuration,-0)}catch(e){sO(t,t.return,e)}}else l_(e,t,n,r);break;case 23:break;case 22:o=t.stateNode,i=t.alternate,null!==t.memoizedState?2&o._visibility?l_(e,t,n,r):lC(e,t):2&o._visibility?l_(e,t,n,r):(o._visibility|=2,function e(t,n,r,a,o){for(o=o&&0!=(10256&n.subtreeFlags),n=n.child;null!==n;){var i=n,l=i.flags;switch(i.tag){case 0:case 11:case 15:e(t,i,r,a,o),i4(8,i);break;case 23:break;case 22:var s=i.stateNode;null!==i.memoizedState?2&s._visibility?e(t,i,r,a,o):lC(t,i):(s._visibility|=2,e(t,i,r,a,o)),o&&2048&l&&lS(i.alternate,i);break;case 24:e(t,i,r,a,o),o&&2048&l&&lE(i.alternate,i);break;default:e(t,i,r,a,o)}n=n.sibling}}(e,t,n,r,0!=(10256&t.subtreeFlags))),2048&a&&lS(i,t);break;case 24:l_(e,t,n,r),2048&a&&lE(t.alternate,t)}}function lC(e,t){if(10256&t.subtreeFlags)for(t=t.child;null!==t;){var n=t,r=n.flags;switch(n.tag){case 22:lC(e,n),2048&r&&lS(n.alternate,n);break;case 24:lC(e,n),2048&r&&lE(n.alternate,n);break;default:lC(e,n)}t=t.sibling}}var lT=8192;function lj(e,t,n){if(e.subtreeFlags&lT)for(e=e.child;null!==e;)lP(e,t,n),e=e.sibling}function lP(e,t,n){switch(e.tag){case 26:lj(e,t,n),e.flags&lT&&null!==e.memoizedState&&function(e,t,n,r){if("stylesheet"===n.type&&("string"!=typeof r.media||!1!==matchMedia(r.media).matches)&&0==(4&n.state.loading)){if(null===n.instance){var a=cW(r.href),o=t.querySelector(cQ(a));if(o){null!==(t=o._p)&&"object"==typeof t&&"function"==typeof t.then&&(e.count++,e=c9.bind(e),t.then(e,e)),n.state.loading|=4,n.instance=o,eK(o);return}o=t.ownerDocument||t,r=cK(r),(a=c$.get(a))&&c0(r,a),eK(o=o.createElement("link"));var i=o;i._p=new Promise(function(e,t){i.onload=e,i.onerror=t}),cf(o,"link",r),n.instance=o}null===e.stylesheets&&(e.stylesheets=new Map),e.stylesheets.set(n,t),(t=n.state.preload)&&0==(3&n.state.loading)&&(e.count++,n=c9.bind(e),t.addEventListener("load",n),t.addEventListener("error",n))}}(n,lv,e.memoizedState,e.memoizedProps);break;case 5:default:lj(e,t,n);break;case 3:case 4:var r=lv;lv=cU(e.stateNode.containerInfo),lj(e,t,n),lv=r;break;case 22:null===e.memoizedState&&(null!==(r=e.alternate)&&null!==r.memoizedState?(r=lT,lT=0x1000000,lj(e,t,n),lT=r):lj(e,t,n))}}function lz(e){var t=e.alternate;if(null!==t&&null!==(e=t.child)){t.child=null;do t=e.sibling,e.sibling=null,e=t;while(null!==e)}}function lL(e){var t=e.deletions;if(0!=(16&e.flags)){if(null!==t)for(var n=0;n<t.length;n++){var r=t[n];lc=r,lO(r,e)}lz(e)}if(10256&e.subtreeFlags)for(e=e.child;null!==e;)lN(e),e=e.sibling}function lN(e){switch(e.tag){case 0:case 11:case 15:lL(e),2048&e.flags&&i3(9,e,e.return);break;case 3:case 12:default:lL(e);break;case 22:var t=e.stateNode;null!==e.memoizedState&&2&t._visibility&&(null===e.return||13!==e.return.tag)?(t._visibility&=-3,function e(t){var n=t.deletions;if(0!=(16&t.flags)){if(null!==n)for(var r=0;r<n.length;r++){var a=n[r];lc=a,lO(a,t)}lz(t)}for(t=t.child;null!==t;){switch((n=t).tag){case 0:case 11:case 15:i3(8,n,n.return),e(n);break;case 22:2&(r=n.stateNode)._visibility&&(r._visibility&=-3,e(n));break;default:e(n)}t=t.sibling}}(e)):lL(e)}}function lO(e,t){for(;null!==lc;){var n=lc;switch(n.tag){case 0:case 11:case 15:i3(8,n,t);break;case 23:case 22:if(null!==n.memoizedState&&null!==n.memoizedState.cachePool){var r=n.memoizedState.cachePool.pool;null!=r&&r.refCount++}break;case 24:r4(n.memoizedState.cache)}if(null!==(r=n.child))r.return=n,lc=r;else for(n=e;null!==lc;){var a=(r=lc).sibling,o=r.return;if(!function e(t){var n=t.alternate;null!==n&&(t.alternate=null,e(n)),t.child=null,t.deletions=null,t.sibling=null,5===t.tag&&null!==(n=t.stateNode)&&eH(n),t.stateNode=null,t.return=null,t.dependencies=null,t.memoizedProps=null,t.memoizedState=null,t.pendingProps=null,t.stateNode=null,t.updateQueue=null}(r),r===n){lc=null;break}if(null!==a){a.return=o,lc=a;break}lc=o}}}var lR={getCacheForType:function(e){var t=rK(r1),n=t.data.get(e);return void 0===n&&(n=e(),t.data.set(e,n)),n},cacheSignal:function(){return rK(r1).controller.signal}},lD="function"==typeof WeakMap?WeakMap:Map,lF=0,lI=null,lM=null,l$=0,lB=0,lU=null,lq=!1,lH=!1,lG=!1,lV=0,lW=0,lQ=0,lK=0,lY=0,lZ=0,lX=0,lJ=null,l0=null,l1=!1,l2=0,l4=0,l3=1/0,l8=null,l6=null,l9=0,l5=null,l7=null,se=0,st=0,sn=null,sr=null,sa=0,so=null;function si(){return 0!=(2&lF)&&0!==l$?l$&-l$:null!==N.T?sJ():eN()}function sl(){if(0===lZ)if(0==(0x20000000&l$)||rT){var e=ew;0==(3932160&(ew<<=1))&&(ew=262144),lZ=e}else lZ=0x20000000;return null!==(e=aM.current)&&(e.flags|=32),lZ}function ss(e,t,n){(e===lI&&(2===lB||9===lB)||null!==e.cancelPendingCommit)&&(sm(e,0),sd(e,l$,lZ,!1)),eC(e,n),(0==(2&lF)||e!==lI)&&(e===lI&&(0==(2&lF)&&(lK|=n),4===lW&&sd(e,l$,lZ,!1)),sV(e))}function sc(e,t,n){if(0!=(6&lF))throw Error(l(327));for(var r=!n&&0==(127&t)&&0==(t&e.expiredLanes)||eE(e,t),a=r?function(e,t){var n=lF;lF|=2;var r=sb(),a=sy();lI!==e||l$!==t?(l8=null,l3=ea()+500,sm(e,t)):lH=eE(e,t);e:for(;;)try{if(0!==lB&&null!==lM){t=lM;var o=lU;t:switch(lB){case 1:lB=0,lU=null,sS(e,t,o,1);break;case 2:case 9:if(as(o)){lB=0,lU=null,sx(t);break}t=function(){2!==lB&&9!==lB||lI!==e||(lB=7),sV(e)},o.then(t,t);break e;case 3:lB=7;break e;case 4:lB=5;break e;case 7:as(o)?(lB=0,lU=null,sx(t)):(lB=0,lU=null,sS(e,t,o,7));break;case 5:var i=null;switch(lM.tag){case 26:i=lM.memoizedState;case 5:case 27:var s=lM;if(i?c8(i):s.stateNode.complete){lB=0,lU=null;var c=s.sibling;if(null!==c)lM=c;else{var u=s.return;null!==u?(lM=u,sE(u)):lM=null}break t}}lB=0,lU=null,sS(e,t,o,5);break;case 6:lB=0,lU=null,sS(e,t,o,6);break;case 8:sf(),lW=6;break e;default:throw Error(l(462))}}for(;null!==lM&&!en();)sk(lM);break}catch(t){sg(e,t)}return(rB=r$=null,N.H=r,N.A=a,lF=n,null!==lM)?0:(lI=null,l$=0,n4(),lW)}(e,t):sw(e,t,!0),o=r;;){if(0===a)lH&&!r&&sd(e,t,0,!1);else{if(n=e.current.alternate,o&&!function(e){for(var t=e;;){var n=t.tag;if((0===n||11===n||15===n)&&16384&t.flags&&null!==(n=t.updateQueue)&&null!==(n=n.stores))for(var r=0;r<n.length;r++){var a=n[r],o=a.getSnapshot;a=a.value;try{if(!nA(o(),a))return!1}catch(e){return!1}}if(n=t.child,16384&t.subtreeFlags&&null!==n)n.return=t,t=n;else{if(t===e)break;for(;null===t.sibling;){if(null===t.return||t.return===e)return!0;t=t.return}t.sibling.return=t.return,t=t.sibling}}return!0}(n)){a=sw(e,t,!1),o=!1;continue}if(2===a){if(o=t,e.errorRecoveryDisabledLanes&o)var i=0;else i=0!=(i=-0x20000001&e.pendingLanes)?i:0x20000000&i?0x20000000:0;if(0!==i){t=i;e:{a=lJ;var s=e.current.memoizedState.isDehydrated;if(s&&(sm(e,i).flags|=256),2!==(i=sw(e,i,!1))){if(lG&&!s){e.errorRecoveryDisabledLanes|=o,lK|=o,a=4;break e}o=l0,l0=a,null!==o&&(null===l0?l0=o:l0.push.apply(l0,o))}a=i}if(o=!1,2!==a)continue}}if(1===a){sm(e,0),sd(e,t,0,!0);break}e:{switch(r=e,o=a){case 0:case 1:throw Error(l(345));case 4:if((4194048&t)!==t)break;case 6:sd(r,t,lZ,!lq);break e;case 2:l0=null;break;case 3:case 5:break;default:throw Error(l(329))}if((0x3c00000&t)===t&&10<(a=l2+300-ea())){if(sd(r,t,lZ,!lq),0!==eS(r,0,!0))break e;se=t,r.timeoutHandle=cx(su.bind(null,r,n,l0,l8,l1,t,lZ,lK,lX,lq,o,"Throttled",-0,0),a);break e}su(r,n,l0,l8,l1,t,lZ,lK,lX,lq,o,null,-0,0)}}break}sV(e)}function su(e,t,n,r,a,o,i,l,s,c,u,d,p,f){if(e.timeoutHandle=-1,8192&(d=t.subtreeFlags)||0x1002000==(0x1002000&d)){lP(t,o,d={stylesheets:null,count:0,imgCount:0,imgBytes:0,suspenseyImages:[],waitingForImages:!0,waitingForViewTransition:!1,unsuspend:tb});var m,g,h=(0x3c00000&o)===o?l2-ea():(4194048&o)===o?l4-ea():0;if(null!==(m=d,g=h,m.stylesheets&&0===m.count&&c7(m,m.stylesheets),h=0<m.count||0<m.imgCount?function(e){var t=setTimeout(function(){if(m.stylesheets&&c7(m,m.stylesheets),m.unsuspend){var e=m.unsuspend;m.unsuspend=null,e()}},6e4+g);0<m.imgBytes&&0===c6&&(c6=62500*function(){if("function"==typeof performance.getEntriesByType){for(var e=0,t=0,n=performance.getEntriesByType("resource"),r=0;r<n.length;r++){var a=n[r],o=a.transferSize,i=a.initiatorType,l=a.duration;if(o&&l&&cm(i)){for(i=0,l=a.responseEnd,r+=1;r<n.length;r++){var s=n[r],c=s.startTime;if(c>l)break;var u=s.transferSize,d=s.initiatorType;u&&cm(d)&&(i+=u*((s=s.responseEnd)<l?1:(l-c)/(s-c)))}if(--r,t+=8*(o+i)/(a.duration/1e3),10<++e)break}}if(0<e)return t/e/1e6}return navigator.connection&&"number"==typeof(e=navigator.connection.downlink)?e:5}());var n=setTimeout(function(){if(m.waitingForImages=!1,0===m.count&&(m.stylesheets&&c7(m,m.stylesheets),m.unsuspend)){var e=m.unsuspend;m.unsuspend=null,e()}},(m.imgBytes>c6?50:800)+g);return m.unsuspend=e,function(){m.unsuspend=null,clearTimeout(t),clearTimeout(n)}}:null)){se=o,e.cancelPendingCommit=h(sA.bind(null,e,t,o,n,r,a,i,l,s,u,d,null,p,f)),sd(e,o,i,!c);return}}sA(e,t,o,n,r,a,i,l,s)}function sd(e,t,n,r){t&=~lY,t&=~lK,e.suspendedLanes|=t,e.pingedLanes&=~t,r&&(e.warmLanes|=t),r=e.expirationTimes;for(var a=t;0<a;){var o=31-eh(a),i=1<<o;r[o]=-1,a&=~i}0!==n&&eT(e,n,t)}function sp(){return 0!=(6&lF)||(sW(0,!1),!1)}function sf(){if(null!==lM){if(0===lB)var e=lM.return;else e=lM,rB=r$=null,or(e),am=null,ag=0,e=lM;for(;null!==e;)i2(e.alternate,e),e=e.return;lM=null}}function sm(e,t){var n=e.timeoutHandle;-1!==n&&(e.timeoutHandle=-1,cS(n)),null!==(n=e.cancelPendingCommit)&&(e.cancelPendingCommit=null,n()),se=0,sf(),lI=e,lM=n=rr(e.current,null),l$=t,lB=0,lU=null,lq=!1,lH=eE(e,t),lG=!1,lX=lZ=lY=lK=lQ=lW=0,l0=lJ=null,l1=!1,0!=(8&t)&&(t|=32&t);var r=e.entangledLanes;if(0!==r)for(e=e.entanglements,r&=t;0<r;){var a=31-eh(r),o=1<<a;t|=e[a],r&=~o}return lV=t,n4(),n}function sg(e,t){aK=null,N.H=ir,t===aa||t===ai?(t=ap(),lB=3):t===ao?(t=ap(),lB=4):lB=t===ik?8:null!==t&&"object"==typeof t&&"function"==typeof t.then?6:1,lU=t,null===lM&&(lW=1,ih(e,rd(t,e.current)))}function sh(){var e=aM.current;return null===e||((4194048&l$)===l$?null===a$:((0x3c00000&l$)===l$||0!=(0x20000000&l$))&&e===a$)}function sb(){var e=N.H;return N.H=ir,null===e?ir:e}function sy(){var e=N.A;return N.A=lR,e}function sv(){lW=4,lq||(4194048&l$)!==l$&&null!==aM.current||(lH=!0),0==(0x7ffffff&lQ)&&0==(0x7ffffff&lK)||null===lI||sd(lI,l$,lZ,!1)}function sw(e,t,n){var r=lF;lF|=2;var a=sb(),o=sy();(lI!==e||l$!==t)&&(l8=null,sm(e,t)),t=!1;var i=lW;e:for(;;)try{if(0!==lB&&null!==lM){var l=lM,s=lU;switch(lB){case 8:sf(),i=6;break e;case 3:case 2:case 9:case 6:null===aM.current&&(t=!0);var c=lB;if(lB=0,lU=null,sS(e,l,s,c),n&&lH){i=0;break e}break;default:c=lB,lB=0,lU=null,sS(e,l,s,c)}}(function(){for(;null!==lM;)sk(lM)})(),i=lW;break}catch(t){sg(e,t)}return t&&e.shellSuspendCounter++,rB=r$=null,lF=r,N.H=a,N.A=o,null===lM&&(lI=null,l$=0,n4()),i}function sk(e){var t=iK(e.alternate,e,lV);e.memoizedProps=e.pendingProps,null===t?sE(e):lM=t}function sx(e){var t=e,n=t.alternate;switch(t.tag){case 15:case 0:t=iO(n,t,t.pendingProps,t.type,void 0,l$);break;case 11:t=iO(n,t,t.pendingProps,t.type.render,t.ref,l$);break;case 5:or(t);default:i2(n,t),t=iK(n,t=lM=ra(t,lV),lV)}e.memoizedProps=e.pendingProps,null===t?sE(e):lM=t}function sS(e,t,n,r){rB=r$=null,or(t),am=null,ag=0;var a=t.return;try{if(function(e,t,n,r,a){if(n.flags|=32768,null!==r&&"object"==typeof r&&"function"==typeof r.then){if(null!==(t=n.alternate)&&rV(t,n,a,!0),null!==(n=aM.current)){switch(n.tag){case 31:case 13:return null===a$?sv():null===n.alternate&&0===lW&&(lW=3),n.flags&=-257,n.flags|=65536,n.lanes=a,r===al?n.flags|=16384:(null===(t=n.updateQueue)?n.updateQueue=new Set([r]):t.add(r),sR(e,r,a)),!1;case 22:return n.flags|=65536,r===al?n.flags|=16384:(null===(t=n.updateQueue)?(t={transitions:null,markerInstances:null,retryQueue:new Set([r])},n.updateQueue=t):null===(n=t.retryQueue)?t.retryQueue=new Set([r]):n.add(r),sR(e,r,a)),!1}throw Error(l(435,n.tag))}return sR(e,r,a),sv(),!1}if(rT)return null!==(t=aM.current)?(0==(65536&t.flags)&&(t.flags|=256),t.flags|=65536,t.lanes=a,r!==rz&&rI(rd(e=Error(l(422),{cause:r}),n))):(r!==rz&&rI(rd(t=Error(l(423),{cause:r}),n)),e=e.current.alternate,e.flags|=65536,a&=-a,e.lanes|=a,r=rd(r,n),a=iy(e.stateNode,r,a),aT(e,a),4!==lW&&(lW=2)),!1;var o=Error(l(520),{cause:r});if(o=rd(o,n),null===lJ?lJ=[o]:lJ.push(o),4!==lW&&(lW=2),null===t)return!0;r=rd(r,n),n=t;do{switch(n.tag){case 3:return n.flags|=65536,e=a&-a,n.lanes|=e,e=iy(n.stateNode,r,e),aT(n,e),!1;case 1:if(t=n.type,o=n.stateNode,0==(128&n.flags)&&("function"==typeof t.getDerivedStateFromError||null!==o&&"function"==typeof o.componentDidCatch&&(null===l6||!l6.has(o))))return n.flags|=65536,a&=-a,n.lanes|=a,iw(a=iv(a),e,n,r),aT(n,a),!1}n=n.return}while(null!==n)return!1}(e,a,t,n,l$)){lW=1,ih(e,rd(n,e.current)),lM=null;return}}catch(t){if(null!==a)throw lM=a,t;lW=1,ih(e,rd(n,e.current)),lM=null;return}32768&t.flags?(rT||1===r?e=!0:lH||0!=(0x20000000&l$)?e=!1:(lq=e=!0,(2===r||9===r||3===r||6===r)&&null!==(r=aM.current)&&13===r.tag&&(r.flags|=16384)),s_(t,e)):sE(t)}function sE(e){var t=e;do{if(0!=(32768&t.flags))return void s_(t,lq);e=t.return;var n=function(e,t,n){var r=t.pendingProps;switch(rE(t),t.tag){case 16:case 15:case 0:case 11:case 7:case 8:case 12:case 9:case 14:case 1:return i1(t),null;case 3:return n=t.stateNode,r=null,null!==e&&(r=e.memoizedState.cache),t.memoizedState.cache!==r&&(t.flags|=2048),rq(r1),V(),n.pendingContext&&(n.context=n.pendingContext,n.pendingContext=null),(null===e||null===e.child)&&(rR(t)?iY(t):null===e||e.memoizedState.isDehydrated&&0==(256&t.flags)||(t.flags|=1024,rF())),i1(t),null;case 26:var a=t.type,o=t.memoizedState;return null===e?(iY(t),null!==o?(i1(t),iX(t,o)):(i1(t),iZ(t,a,null,r,n))):o?o!==e.memoizedState?(iY(t),i1(t),iX(t,o)):(i1(t),t.flags&=-0x1000001):((e=e.memoizedProps)!==r&&iY(t),i1(t),iZ(t,a,e,r,n)),null;case 27:if(Q(t),n=q.current,a=t.type,null!==e&&null!=t.stateNode)e.memoizedProps!==r&&iY(t);else{if(!r){if(null===t.stateNode)throw Error(l(166));return i1(t),null}e=B.current,rR(t)?rN(t,e):(t.stateNode=e=cI(a,r,n),iY(t))}return i1(t),null;case 5:if(Q(t),a=t.type,null!==e&&null!=t.stateNode)e.memoizedProps!==r&&iY(t);else{if(!r){if(null===t.stateNode)throw Error(l(166));return i1(t),null}if(o=B.current,rR(t))rN(t,o);else{var i=cb(q.current);switch(o){case 1:o=i.createElementNS("http://www.w3.org/2000/svg",a);break;case 2:o=i.createElementNS("http://www.w3.org/1998/Math/MathML",a);break;default:switch(a){case"svg":o=i.createElementNS("http://www.w3.org/2000/svg",a);break;case"math":o=i.createElementNS("http://www.w3.org/1998/Math/MathML",a);break;case"script":(o=i.createElement("div")).innerHTML="<script><\/script>",o=o.removeChild(o.firstChild);break;case"select":o="string"==typeof r.is?i.createElement("select",{is:r.is}):i.createElement("select"),r.multiple?o.multiple=!0:r.size&&(o.size=r.size);break;default:o="string"==typeof r.is?i.createElement(a,{is:r.is}):i.createElement(a)}}o[eD]=t,o[eF]=r;e:for(i=t.child;null!==i;){if(5===i.tag||6===i.tag)o.appendChild(i.stateNode);else if(4!==i.tag&&27!==i.tag&&null!==i.child){i.child.return=i,i=i.child;continue}if(i===t)break;for(;null===i.sibling;){if(null===i.return||i.return===t)break e;i=i.return}i.sibling.return=i.return,i=i.sibling}switch(t.stateNode=o,cf(o,a,r),a){case"button":case"input":case"select":case"textarea":r=!!r.autoFocus;break;case"img":r=!0;break;default:r=!1}r&&iY(t)}}return i1(t),iZ(t,t.type,null===e?null:e.memoizedProps,t.pendingProps,n),null;case 6:if(e&&null!=t.stateNode)e.memoizedProps!==r&&iY(t);else{if("string"!=typeof r&&null===t.stateNode)throw Error(l(166));if(e=q.current,rR(t)){if(e=t.stateNode,n=t.memoizedProps,r=null,null!==(a=rA))switch(a.tag){case 27:case 5:r=a.memoizedProps}e[eD]=t,(e=!!(e.nodeValue===n||null!==r&&!0===r.suppressHydrationWarning||cu(e.nodeValue,n)))||rL(t,!0)}else(e=cb(e).createTextNode(r))[eD]=t,t.stateNode=e}return i1(t),null;case 31:if(n=t.memoizedState,null===e||null!==e.memoizedState){if(r=rR(t),null!==n){if(null===e){if(!r)throw Error(l(318));if(!(e=null!==(e=t.memoizedState)?e.dehydrated:null))throw Error(l(557));e[eD]=t}else rD(),0==(128&t.flags)&&(t.memoizedState=null),t.flags|=4;i1(t),e=!1}else n=rF(),null!==e&&null!==e.memoizedState&&(e.memoizedState.hydrationErrors=n),e=!0;if(!e){if(256&t.flags)return aG(t),t;return aG(t),null}if(0!=(128&t.flags))throw Error(l(558))}return i1(t),null;case 13:if(r=t.memoizedState,null===e||null!==e.memoizedState&&null!==e.memoizedState.dehydrated){if(a=rR(t),null!==r&&null!==r.dehydrated){if(null===e){if(!a)throw Error(l(318));if(!(a=null!==(a=t.memoizedState)?a.dehydrated:null))throw Error(l(317));a[eD]=t}else rD(),0==(128&t.flags)&&(t.memoizedState=null),t.flags|=4;i1(t),a=!1}else a=rF(),null!==e&&null!==e.memoizedState&&(e.memoizedState.hydrationErrors=a),a=!0;if(!a){if(256&t.flags)return aG(t),t;return aG(t),null}}if(aG(t),0!=(128&t.flags))return t.lanes=n,t;return n=null!==r,e=null!==e&&null!==e.memoizedState,n&&(r=t.child,a=null,null!==r.alternate&&null!==r.alternate.memoizedState&&null!==r.alternate.memoizedState.cachePool&&(a=r.alternate.memoizedState.cachePool.pool),o=null,null!==r.memoizedState&&null!==r.memoizedState.cachePool&&(o=r.memoizedState.cachePool.pool),o!==a&&(r.flags|=2048)),n!==e&&n&&(t.child.flags|=8192),iJ(t,t.updateQueue),i1(t),null;case 4:return V(),null===e&&ce(t.stateNode.containerInfo),i1(t),null;case 10:return rq(t.type),i1(t),null;case 19:if(M(aV),null===(r=t.memoizedState))return i1(t),null;if(a=0!=(128&t.flags),null===(o=r.rendering))if(a)i0(r,!1);else{if(0!==lW||null!==e&&0!=(128&e.flags))for(e=t.child;null!==e;){if(null!==(o=aW(e))){for(t.flags|=128,i0(r,!1),t.updateQueue=e=o.updateQueue,iJ(t,e),t.subtreeFlags=0,e=n,n=t.child;null!==n;)ra(n,e),n=n.sibling;return $(aV,1&aV.current|2),rT&&rk(t,r.treeForkCount),t.child}e=e.sibling}null!==r.tail&&ea()>l3&&(t.flags|=128,a=!0,i0(r,!1),t.lanes=4194304)}else{if(!a)if(null!==(e=aW(o))){if(t.flags|=128,a=!0,t.updateQueue=e=e.updateQueue,iJ(t,e),i0(r,!0),null===r.tail&&"hidden"===r.tailMode&&!o.alternate&&!rT)return i1(t),null}else 2*ea()-r.renderingStartTime>l3&&0x20000000!==n&&(t.flags|=128,a=!0,i0(r,!1),t.lanes=4194304);r.isBackwards?(o.sibling=t.child,t.child=o):(null!==(e=r.last)?e.sibling=o:t.child=o,r.last=o)}if(null!==r.tail)return e=r.tail,r.rendering=e,r.tail=e.sibling,r.renderingStartTime=ea(),e.sibling=null,n=aV.current,$(aV,a?1&n|2:1&n),rT&&rk(t,r.treeForkCount),e;return i1(t),null;case 22:case 23:return aG(t),aI(),r=null!==t.memoizedState,null!==e?null!==e.memoizedState!==r&&(t.flags|=8192):r&&(t.flags|=8192),r?0!=(0x20000000&n)&&0==(128&t.flags)&&(i1(t),6&t.subtreeFlags&&(t.flags|=8192)):i1(t),null!==(n=t.updateQueue)&&iJ(t,n.retryQueue),n=null,null!==e&&null!==e.memoizedState&&null!==e.memoizedState.cachePool&&(n=e.memoizedState.cachePool.pool),r=null,null!==t.memoizedState&&null!==t.memoizedState.cachePool&&(r=t.memoizedState.cachePool.pool),r!==n&&(t.flags|=2048),null!==e&&M(ae),null;case 24:return n=null,null!==e&&(n=e.memoizedState.cache),t.memoizedState.cache!==n&&(t.flags|=2048),rq(r1),i1(t),null;case 25:case 30:return null}throw Error(l(156,t.tag))}(t.alternate,t,lV);if(null!==n){lM=n;return}if(null!==(t=t.sibling)){lM=t;return}lM=t=e}while(null!==t)0===lW&&(lW=5)}function s_(e,t){do{var n=function(e,t){switch(rE(t),t.tag){case 1:return 65536&(e=t.flags)?(t.flags=-65537&e|128,t):null;case 3:return rq(r1),V(),0!=(65536&(e=t.flags))&&0==(128&e)?(t.flags=-65537&e|128,t):null;case 26:case 27:case 5:return Q(t),null;case 31:if(null!==t.memoizedState){if(aG(t),null===t.alternate)throw Error(l(340));rD()}return 65536&(e=t.flags)?(t.flags=-65537&e|128,t):null;case 13:if(aG(t),null!==(e=t.memoizedState)&&null!==e.dehydrated){if(null===t.alternate)throw Error(l(340));rD()}return 65536&(e=t.flags)?(t.flags=-65537&e|128,t):null;case 19:return M(aV),null;case 4:return V(),null;case 10:return rq(t.type),null;case 22:case 23:return aG(t),aI(),null!==e&&M(ae),65536&(e=t.flags)?(t.flags=-65537&e|128,t):null;case 24:return rq(r1),null;default:return null}}(e.alternate,e);if(null!==n){n.flags&=32767,lM=n;return}if(null!==(n=e.return)&&(n.flags|=32768,n.subtreeFlags=0,n.deletions=null),!t&&null!==(e=e.sibling)){lM=e;return}lM=e=n}while(null!==e)lW=6,lM=null}function sA(e,t,n,r,a,o,i,s,c){e.cancelPendingCommit=null;do sz();while(0!==l9)if(0!=(6&lF))throw Error(l(327));if(null!==t){if(t===e.current)throw Error(l(177));if(!function(e,t,n,r,a,o){var i=e.pendingLanes;e.pendingLanes=n,e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0,e.expiredLanes&=n,e.entangledLanes&=n,e.errorRecoveryDisabledLanes&=n,e.shellSuspendCounter=0;var l=e.entanglements,s=e.expirationTimes,c=e.hiddenUpdates;for(n=i&~n;0<n;){var u=31-eh(n),d=1<<u;l[u]=0,s[u]=-1;var p=c[u];if(null!==p)for(c[u]=null,u=0;u<p.length;u++){var f=p[u];null!==f&&(f.lane&=-0x20000001)}n&=~d}0!==r&&eT(e,r,0),0!==o&&0===a&&0!==e.tag&&(e.suspendedLanes|=o&~(i&~t))}(e,n,o=t.lanes|t.childLanes|n2,i,s,c),e===lI&&(lM=lI=null,l$=0),l7=t,l5=e,se=n,st=o,sn=a,sr=r,0!=(10256&t.subtreeFlags)||0!=(10256&t.flags)?(e.callbackNode=null,e.callbackPriority=0,ee(es,function(){return sL(),null})):(e.callbackNode=null,e.callbackPriority=0),r=0!=(13878&t.flags),0!=(13878&t.subtreeFlags)||r){r=N.T,N.T=null,a=O.p,O.p=2,i=lF,lF|=4;try{!function(e,t){if(e=e.containerInfo,cg=uc,nz(e=nP(e))){if("selectionStart"in e)var n={start:e.selectionStart,end:e.selectionEnd};else e:{var r=(n=(n=e.ownerDocument)&&n.defaultView||window).getSelection&&n.getSelection();if(r&&0!==r.rangeCount){n=r.anchorNode;var a,o=r.anchorOffset,i=r.focusNode;r=r.focusOffset;try{n.nodeType,i.nodeType}catch(e){n=null;break e}var s=0,c=-1,u=-1,d=0,p=0,f=e,m=null;t:for(;;){for(;f!==n||0!==o&&3!==f.nodeType||(c=s+o),f!==i||0!==r&&3!==f.nodeType||(u=s+r),3===f.nodeType&&(s+=f.nodeValue.length),null!==(a=f.firstChild);)m=f,f=a;for(;;){if(f===e)break t;if(m===n&&++d===o&&(c=s),m===i&&++p===r&&(u=s),null!==(a=f.nextSibling))break;m=(f=m).parentNode}f=a}n=-1===c||-1===u?null:{start:c,end:u}}else n=null}n=n||{start:0,end:0}}else n=null;for(ch={focusedElem:e,selectionRange:n},uc=!1,lc=t;null!==lc;)if(e=(t=lc).child,0!=(1028&t.subtreeFlags)&&null!==e)e.return=t,lc=e;else for(;null!==lc;){switch(i=(t=lc).alternate,e=t.flags,t.tag){case 0:if(0!=(4&e)&&null!==(e=null!==(e=t.updateQueue)?e.events:null))for(n=0;n<e.length;n++)(o=e[n]).ref.impl=o.nextImpl;break;case 11:case 15:case 5:case 26:case 27:case 6:case 4:case 17:break;case 1:if(0!=(1024&e)&&null!==i){e=void 0,n=t,o=i.memoizedProps,i=i.memoizedState,r=n.stateNode;try{var g=id(n.type,o);e=r.getSnapshotBeforeUpdate(g,i),r.__reactInternalSnapshotBeforeUpdate=e}catch(e){sO(n,n.return,e)}}break;case 3:if(0!=(1024&e)){if(9===(n=(e=t.stateNode.containerInfo).nodeType))cP(e);else if(1===n)switch(e.nodeName){case"HEAD":case"HTML":case"BODY":cP(e);break;default:e.textContent=""}}break;default:if(0!=(1024&e))throw Error(l(163))}if(null!==(e=t.sibling)){e.return=t.return,lc=e;break}lc=t.return}}(e,t)}finally{lF=i,O.p=a,N.T=r}}l9=1,sC(),sT(),sj()}}function sC(){if(1===l9){l9=0;var e=l5,t=l7,n=0!=(13878&t.flags);if(0!=(13878&t.subtreeFlags)||n){n=N.T,N.T=null;var r=O.p;O.p=2;var a=lF;lF|=4;try{lw(t,e);var o=ch,i=nP(e.containerInfo),l=o.focusedElem,s=o.selectionRange;if(i!==l&&l&&l.ownerDocument&&function e(t,n){return!!t&&!!n&&(t===n||(!t||3!==t.nodeType)&&(n&&3===n.nodeType?e(t,n.parentNode):"contains"in t?t.contains(n):!!t.compareDocumentPosition&&!!(16&t.compareDocumentPosition(n))))}(l.ownerDocument.documentElement,l)){if(null!==s&&nz(l)){var c=s.start,u=s.end;if(void 0===u&&(u=c),"selectionStart"in l)l.selectionStart=c,l.selectionEnd=Math.min(u,l.value.length);else{var d=l.ownerDocument||document,p=d&&d.defaultView||window;if(p.getSelection){var f=p.getSelection(),m=l.textContent.length,g=Math.min(s.start,m),h=void 0===s.end?g:Math.min(s.end,m);!f.extend&&g>h&&(i=h,h=g,g=i);var b=nj(l,g),y=nj(l,h);if(b&&y&&(1!==f.rangeCount||f.anchorNode!==b.node||f.anchorOffset!==b.offset||f.focusNode!==y.node||f.focusOffset!==y.offset)){var v=d.createRange();v.setStart(b.node,b.offset),f.removeAllRanges(),g>h?(f.addRange(v),f.extend(y.node,y.offset)):(v.setEnd(y.node,y.offset),f.addRange(v))}}}}for(d=[],f=l;f=f.parentNode;)1===f.nodeType&&d.push({element:f,left:f.scrollLeft,top:f.scrollTop});for("function"==typeof l.focus&&l.focus(),l=0;l<d.length;l++){var w=d[l];w.element.scrollLeft=w.left,w.element.scrollTop=w.top}}uc=!!cg,ch=cg=null}finally{lF=a,O.p=r,N.T=n}}e.current=t,l9=2}}function sT(){if(2===l9){l9=0;var e=l5,t=l7,n=0!=(8772&t.flags);if(0!=(8772&t.subtreeFlags)||n){n=N.T,N.T=null;var r=O.p;O.p=2;var a=lF;lF|=4;try{lu(e,t.alternate,t)}finally{lF=a,O.p=r,N.T=n}}l9=3}}function sj(){if(4===l9||3===l9){l9=0,er();var e=l5,t=l7,n=se,r=sr;0!=(10256&t.subtreeFlags)||0!=(10256&t.flags)?l9=5:(l9=0,l7=l5=null,sP(e,e.pendingLanes));var a=e.pendingLanes;if(0===a&&(l6=null),eL(n),t=t.stateNode,em&&"function"==typeof em.onCommitFiberRoot)try{em.onCommitFiberRoot(ef,t,void 0,128==(128&t.current.flags))}catch(e){}if(null!==r){t=N.T,a=O.p,O.p=2,N.T=null;try{for(var o=e.onRecoverableError,i=0;i<r.length;i++){var l=r[i];o(l.value,{componentStack:l.stack})}}finally{N.T=t,O.p=a}}0!=(3&se)&&sz(),sV(e),a=e.pendingLanes,0!=(261930&n)&&0!=(42&a)?e===so?sa++:(sa=0,so=e):sa=0,sW(0,!1)}}function sP(e,t){0==(e.pooledCacheLanes&=t)&&null!=(t=e.pooledCache)&&(e.pooledCache=null,r4(t))}function sz(){return sC(),sT(),sj(),sL()}function sL(){if(5!==l9)return!1;var e=l5,t=st;st=0;var n=eL(se),r=N.T,a=O.p;try{O.p=32>n?32:n,N.T=null,n=sn,sn=null;var o=l5,i=se;if(l9=0,l7=l5=null,se=0,0!=(6&lF))throw Error(l(331));var s=lF;if(lF|=4,lN(o.current),lA(o,o.current,i,n),lF=s,sW(0,!1),em&&"function"==typeof em.onPostCommitFiberRoot)try{em.onPostCommitFiberRoot(ef,o)}catch(e){}return!0}finally{O.p=a,N.T=r,sP(e,t)}}function sN(e,t,n){t=rd(n,t),t=iy(e.stateNode,t,2),null!==(e=aA(e,t,2))&&(eC(e,2),sV(e))}function sO(e,t,n){if(3===e.tag)sN(e,e,n);else for(;null!==t;){if(3===t.tag){sN(t,e,n);break}if(1===t.tag){var r=t.stateNode;if("function"==typeof t.type.getDerivedStateFromError||"function"==typeof r.componentDidCatch&&(null===l6||!l6.has(r))){e=rd(n,e),null!==(r=aA(t,n=iv(2),2))&&(iw(n,r,t,e),eC(r,2),sV(r));break}}t=t.return}}function sR(e,t,n){var r=e.pingCache;if(null===r){r=e.pingCache=new lD;var a=new Set;r.set(t,a)}else void 0===(a=r.get(t))&&(a=new Set,r.set(t,a));a.has(n)||(lG=!0,a.add(n),e=sD.bind(null,e,t,n),t.then(e,e))}function sD(e,t,n){var r=e.pingCache;null!==r&&r.delete(t),e.pingedLanes|=e.suspendedLanes&n,e.warmLanes&=~n,lI===e&&(l$&n)===n&&(4===lW||3===lW&&(0x3c00000&l$)===l$&&300>ea()-l2?0==(2&lF)&&sm(e,0):lY|=n,lX===l$&&(lX=0)),sV(e)}function sF(e,t){0===t&&(t=e_()),null!==(e=n6(e,t))&&(eC(e,t),sV(e))}function sI(e){var t=e.memoizedState,n=0;null!==t&&(n=t.retryLane),sF(e,n)}function sM(e,t){var n=0;switch(e.tag){case 31:case 13:var r=e.stateNode,a=e.memoizedState;null!==a&&(n=a.retryLane);break;case 19:r=e.stateNode;break;case 22:r=e.stateNode._retryCache;break;default:throw Error(l(314))}null!==r&&r.delete(t),sF(e,n)}var s$=null,sB=null,sU=!1,sq=!1,sH=!1,sG=0;function sV(e){e!==sB&&null===e.next&&(null===sB?s$=sB=e:sB=sB.next=e),sq=!0,sU||(sU=!0,c_(function(){0!=(6&lF)?ee(ei,sQ):sK()}))}function sW(e,t){if(!sH&&sq){sH=!0;do for(var n=!1,r=s$;null!==r;){if(!t)if(0!==e){var a=r.pendingLanes;if(0===a)var o=0;else{var i=r.suspendedLanes,l=r.pingedLanes;o=0xc000095&(o=(1<<31-eh(42|e)+1)-1&(a&~(i&~l)))?0xc000095&o|1:o?2|o:0}0!==o&&(n=!0,sX(r,o))}else o=l$,0==(3&(o=eS(r,r===lI?o:0,null!==r.cancelPendingCommit||-1!==r.timeoutHandle)))||eE(r,o)||(n=!0,sX(r,o));r=r.next}while(n)sH=!1}}function sQ(){sK()}function sK(){sq=sU=!1;var e,t=0;0===sG||((e=window.event)&&"popstate"===e.type?e===ck||(ck=e,0):(ck=null,1))||(t=sG);for(var n=ea(),r=null,a=s$;null!==a;){var o=a.next,i=sY(a,n);0===i?(a.next=null,null===r?s$=o:r.next=o,null===o&&(sB=r)):(r=a,(0!==t||0!=(3&i))&&(sq=!0)),a=o}0!==l9&&5!==l9||sW(t,!1),0!==sG&&(sG=0)}function sY(e,t){for(var n=e.suspendedLanes,r=e.pingedLanes,a=e.expirationTimes,o=-0x3c00001&e.pendingLanes;0<o;){var i=31-eh(o),l=1<<i,s=a[i];-1===s?(0==(l&n)||0!=(l&r))&&(a[i]=function(e,t){switch(e){case 1:case 2:case 4:case 8:case 64:return t+250;case 16:case 32:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return t+5e3;default:return -1}}(l,t)):s<=t&&(e.expiredLanes|=l),o&=~l}if(t=lI,n=l$,n=eS(e,e===t?n:0,null!==e.cancelPendingCommit||-1!==e.timeoutHandle),r=e.callbackNode,0===n||e===t&&(2===lB||9===lB)||null!==e.cancelPendingCommit)return null!==r&&null!==r&&et(r),e.callbackNode=null,e.callbackPriority=0;if(0==(3&n)||eE(e,n)){if((t=n&-n)===e.callbackPriority)return t;switch(null!==r&&et(r),eL(n)){case 2:case 8:n=el;break;case 32:default:n=es;break;case 0x10000000:n=eu}return n=ee(n,r=sZ.bind(null,e)),e.callbackPriority=t,e.callbackNode=n,t}return null!==r&&null!==r&&et(r),e.callbackPriority=2,e.callbackNode=null,2}function sZ(e,t){if(0!==l9&&5!==l9)return e.callbackNode=null,e.callbackPriority=0,null;var n=e.callbackNode;if(sz()&&e.callbackNode!==n)return null;var r=l$;return 0===(r=eS(e,e===lI?r:0,null!==e.cancelPendingCommit||-1!==e.timeoutHandle))?null:(sc(e,r,t),sY(e,ea()),null!=e.callbackNode&&e.callbackNode===n?sZ.bind(null,e):null)}function sX(e,t){if(sz())return null;sc(e,t,!0)}function sJ(){if(0===sG){var e=r6;0===e&&(e=ev,0==(261888&(ev<<=1))&&(ev=256)),sG=e}return sG}function s0(e){return null==e||"symbol"==typeof e||"boolean"==typeof e?null:"function"==typeof e?e:th(""+e)}function s1(e,t){var n=t.ownerDocument.createElement("input");return n.name=t.name,n.value=t.value,e.id&&n.setAttribute("form",e.id),t.parentNode.insertBefore(n,t),e=new FormData(e),n.parentNode.removeChild(n),e}for(var s2=0;s2<nZ.length;s2++){var s4=nZ[s2];nX(s4.toLowerCase(),"on"+(s4[0].toUpperCase()+s4.slice(1)))}nX(nq,"onAnimationEnd"),nX(nH,"onAnimationIteration"),nX(nG,"onAnimationStart"),nX("dblclick","onDoubleClick"),nX("focusin","onFocus"),nX("focusout","onBlur"),nX(nV,"onTransitionRun"),nX(nW,"onTransitionStart"),nX(nQ,"onTransitionCancel"),nX(nK,"onTransitionEnd"),eJ("onMouseEnter",["mouseout","mouseover"]),eJ("onMouseLeave",["mouseout","mouseover"]),eJ("onPointerEnter",["pointerout","pointerover"]),eJ("onPointerLeave",["pointerout","pointerover"]),eX("onChange","change click focusin focusout input keydown keyup selectionchange".split(" ")),eX("onSelect","focusout contextmenu dragend focusin keydown keyup mousedown mouseup selectionchange".split(" ")),eX("onBeforeInput",["compositionend","keypress","textInput","paste"]),eX("onCompositionEnd","compositionend focusout keydown keypress keyup mousedown".split(" ")),eX("onCompositionStart","compositionstart focusout keydown keypress keyup mousedown".split(" ")),eX("onCompositionUpdate","compositionupdate focusout keydown keypress keyup mousedown".split(" "));var s3="abort canplay canplaythrough durationchange emptied encrypted ended error loadeddata loadedmetadata loadstart pause play playing progress ratechange resize seeked seeking stalled suspend timeupdate volumechange waiting".split(" "),s8=new Set("beforetoggle cancel close invalid load scroll scrollend toggle".split(" ").concat(s3));function s6(e,t){t=0!=(4&t);for(var n=0;n<e.length;n++){var r=e[n],a=r.event;r=r.listeners;e:{var o=void 0;if(t)for(var i=r.length-1;0<=i;i--){var l=r[i],s=l.instance,c=l.currentTarget;if(l=l.listener,s!==o&&a.isPropagationStopped())break e;o=l,a.currentTarget=c;try{o(a)}catch(e){nJ(e)}a.currentTarget=null,o=s}else for(i=0;i<r.length;i++){if(s=(l=r[i]).instance,c=l.currentTarget,l=l.listener,s!==o&&a.isPropagationStopped())break e;o=l,a.currentTarget=c;try{o(a)}catch(e){nJ(e)}a.currentTarget=null,o=s}}}}function s9(e,t){var n=t[eM];void 0===n&&(n=t[eM]=new Set);var r=e+"__bubble";n.has(r)||(ct(t,e,2,!1),n.add(r))}function s5(e,t,n){var r=0;t&&(r|=4),ct(n,e,r,t)}var s7="_reactListening"+Math.random().toString(36).slice(2);function ce(e){if(!e[s7]){e[s7]=!0,eY.forEach(function(t){"selectionchange"!==t&&(s8.has(t)||s5(t,!1,e),s5(t,!0,e))});var t=9===e.nodeType?e:e.ownerDocument;null===t||t[s7]||(t[s7]=!0,s5("selectionchange",!1,t))}}function ct(e,t,n,r){switch(uh(t)){case 2:var a=uu;break;case 8:a=ud;break;default:a=up}n=a.bind(null,t,n,e),a=void 0,tC&&("touchstart"===t||"touchmove"===t||"wheel"===t)&&(a=!0),r?void 0!==a?e.addEventListener(t,n,{capture:!0,passive:a}):e.addEventListener(t,n,!0):void 0!==a?e.addEventListener(t,n,{passive:a}):e.addEventListener(t,n,!1)}function cn(e,t,n,r,a){var o=r;if(0==(1&t)&&0==(2&t)&&null!==r)e:for(;;){if(null===r)return;var i=r.tag;if(3===i||4===i){var l=r.stateNode.containerInfo;if(l===a)break;if(4===i)for(i=r.return;null!==i;){var s=i.tag;if((3===s||4===s)&&i.stateNode.containerInfo===a)return;i=i.return}for(;null!==l;){if(null===(i=eG(l)))return;if(5===(s=i.tag)||6===s||26===s||27===s){r=o=i;continue e}l=l.parentNode}}r=r.return}tE(function(){var r=o,a=tv(n),i=[];e:{var l=nY.get(e);if(void 0!==l){var s=tq,u=e;switch(e){case"keypress":if(0===tN(n))break e;case"keydown":case"keyup":s=t3;break;case"focusin":u="focus",s=tK;break;case"focusout":u="blur",s=tK;break;case"beforeblur":case"afterblur":s=tK;break;case"click":if(2===n.button)break e;case"auxclick":case"dblclick":case"mousedown":case"mousemove":case"mouseup":case"mouseout":case"mouseover":case"contextmenu":s=tW;break;case"drag":case"dragend":case"dragenter":case"dragexit":case"dragleave":case"dragover":case"dragstart":case"drop":s=tQ;break;case"touchcancel":case"touchend":case"touchmove":case"touchstart":s=t6;break;case nq:case nH:case nG:s=tY;break;case nK:s=t9;break;case"scroll":case"scrollend":s=tG;break;case"wheel":s=t5;break;case"copy":case"cut":case"paste":s=tZ;break;case"gotpointercapture":case"lostpointercapture":case"pointercancel":case"pointerdown":case"pointermove":case"pointerout":case"pointerover":case"pointerup":s=t8;break;case"toggle":case"beforetoggle":s=t7}var d=0!=(4&t),p=!d&&("scroll"===e||"scrollend"===e),f=d?null!==l?l+"Capture":null:l;d=[];for(var m,g=r;null!==g;){var h=g;if(m=h.stateNode,5!==(h=h.tag)&&26!==h&&27!==h||null===m||null===f||null!=(h=t_(g,f))&&d.push(cr(g,h,m)),p)break;g=g.return}0<d.length&&(l=new s(l,u,null,n,a),i.push({event:l,listeners:d}))}}if(0==(7&t)){if((l="mouseover"===e||"pointerover"===e,s="mouseout"===e||"pointerout"===e,!(l&&n!==ty&&(u=n.relatedTarget||n.fromElement)&&(eG(u)||u[eI])))&&(s||l)&&(l=a.window===a?a:(l=a.ownerDocument)?l.defaultView||l.parentWindow:window,s?(u=n.relatedTarget||n.toElement,s=r,null!==(u=u?eG(u):null)&&(p=c(u),d=u.tag,u!==p||5!==d&&27!==d&&6!==d)&&(u=null)):(s=null,u=r),s!==u)){if(d=tW,h="onMouseLeave",f="onMouseEnter",g="mouse",("pointerout"===e||"pointerover"===e)&&(d=t8,h="onPointerLeave",f="onPointerEnter",g="pointer"),p=null==s?l:eW(s),m=null==u?l:eW(u),(l=new d(h,g+"leave",s,n,a)).target=p,l.relatedTarget=m,h=null,eG(a)===r&&((d=new d(f,g+"enter",u,n,a)).target=m,d.relatedTarget=p,h=d),p=h,s&&u)t:{for(d=co,f=s,g=u,m=0,h=f;h;h=d(h))m++;h=0;for(var b,y=g;y;y=d(y))h++;for(;0<m-h;)f=d(f),m--;for(;0<h-m;)g=d(g),h--;for(;m--;){if(f===g||null!==g&&f===g.alternate){d=f;break t}f=d(f),g=d(g)}d=null}else d=null;null!==s&&ci(i,l,s,d,!1),null!==u&&null!==p&&ci(i,p,u,d,!0)}e:{if("select"===(s=(l=r?eW(r):window).nodeName&&l.nodeName.toLowerCase())||"input"===s&&"file"===l.type)var v=nh;else if(nu(l))if(nb)v=n_;else{v=nS;var w=nx}else(s=l.nodeName)&&"input"===s.toLowerCase()&&("checkbox"===l.type||"radio"===l.type)?v=nE:r&&tf(r.elementType)&&(v=nh);if(v&&(v=v(e,r))){nd(i,v,n,a);break e}w&&w(e,l,r),"focusout"===e&&r&&"number"===l.type&&null!=r.memoizedProps.value&&to(l,"number",l.value)}switch(w=r?eW(r):window,e){case"focusin":(nu(w)||"true"===w.contentEditable)&&(nN=w,nO=r,nR=null);break;case"focusout":nR=nO=nN=null;break;case"mousedown":nD=!0;break;case"contextmenu":case"mouseup":case"dragend":nD=!1,nF(i,n,a);break;case"selectionchange":if(nL)break;case"keydown":case"keyup":nF(i,n,a)}if(nt)t:{switch(e){case"compositionstart":var k="onCompositionStart";break t;case"compositionend":k="onCompositionEnd";break t;case"compositionupdate":k="onCompositionUpdate";break t}k=void 0}else ns?ni(e,n)&&(k="onCompositionEnd"):"keydown"===e&&229===n.keyCode&&(k="onCompositionStart");k&&(na&&"ko"!==n.locale&&(ns||"onCompositionStart"!==k?"onCompositionEnd"===k&&ns&&(b=tL()):(tP="value"in(tj=a)?tj.value:tj.textContent,ns=!0)),0<(w=ca(r,k)).length&&(k=new tX(k,e,null,n,a),i.push({event:k,listeners:w}),b?k.data=b:null!==(b=nl(n))&&(k.data=b))),(b=nr?function(e,t){switch(e){case"compositionend":return nl(t);case"keypress":if(32!==t.which)return null;return no=!0," ";case"textInput":return" "===(e=t.data)&&no?null:e;default:return null}}(e,n):function(e,t){if(ns)return"compositionend"===e||!nt&&ni(e,t)?(e=tL(),tz=tP=tj=null,ns=!1,e):null;switch(e){case"paste":default:return null;case"keypress":if(!(t.ctrlKey||t.altKey||t.metaKey)||t.ctrlKey&&t.altKey){if(t.char&&1<t.char.length)return t.char;if(t.which)return String.fromCharCode(t.which)}return null;case"compositionend":return na&&"ko"!==t.locale?null:t.data}}(e,n))&&0<(k=ca(r,"onBeforeInput")).length&&(w=new tX("onBeforeInput","beforeinput",null,n,a),i.push({event:w,listeners:k}),w.data=b);var x=e;if("submit"===x&&r&&r.stateNode===a){var S=s0((a[eF]||null).action),E=n.submitter;E&&null!==(x=(x=E[eF]||null)?s0(x.formAction):E.getAttribute("formAction"))&&(S=x,E=null);var _=new tq("action","action",null,n,a);i.push({event:_,listeners:[{instance:null,listener:function(){if(n.defaultPrevented){if(0!==sG){var e=E?s1(a,E):new FormData(a);oX(r,{pending:!0,data:e,method:a.method,action:S},null,e)}}else"function"==typeof S&&(_.preventDefault(),oX(r,{pending:!0,data:e=E?s1(a,E):new FormData(a),method:a.method,action:S},S,e))},currentTarget:a}]})}}s6(i,t)})}function cr(e,t,n){return{instance:e,listener:t,currentTarget:n}}function ca(e,t){for(var n=t+"Capture",r=[];null!==e;){var a=e,o=a.stateNode;if(5!==(a=a.tag)&&26!==a&&27!==a||null===o||(null!=(a=t_(e,n))&&r.unshift(cr(e,a,o)),null!=(a=t_(e,t))&&r.push(cr(e,a,o))),3===e.tag)return r;e=e.return}return[]}function co(e){if(null===e)return null;do e=e.return;while(e&&5!==e.tag&&27!==e.tag)return e||null}function ci(e,t,n,r,a){for(var o=t._reactName,i=[];null!==n&&n!==r;){var l=n,s=l.alternate,c=l.stateNode;if(l=l.tag,null!==s&&s===r)break;5!==l&&26!==l&&27!==l||null===c||(s=c,a?null!=(c=t_(n,o))&&i.unshift(cr(n,c,s)):a||null!=(c=t_(n,o))&&i.push(cr(n,c,s))),n=n.return}0!==i.length&&e.push({event:t,listeners:i})}var cl=/\r\n?/g,cs=/\u0000|\uFFFD/g;function cc(e){return("string"==typeof e?e:""+e).replace(cl,"\n").replace(cs,"")}function cu(e,t){return t=cc(t),cc(e)===t}function cd(e,t,n,r,a,o){switch(n){case"children":"string"==typeof r?"body"===t||"textarea"===t&&""===r||tc(e,r):("number"==typeof r||"bigint"==typeof r)&&"body"!==t&&tc(e,""+r);break;case"className":e3(e,"class",r);break;case"tabIndex":e3(e,"tabindex",r);break;case"dir":case"role":case"viewBox":case"width":case"height":e3(e,n,r);break;case"style":tp(e,r,o);break;case"data":if("object"!==t){e3(e,"data",r);break}case"src":case"href":if(""===r&&("a"!==t||"href"!==n)||null==r||"function"==typeof r||"symbol"==typeof r||"boolean"==typeof r){e.removeAttribute(n);break}r=th(""+r),e.setAttribute(n,r);break;case"action":case"formAction":if("function"==typeof r){e.setAttribute(n,"javascript:throw new Error('A React form was unexpectedly submitted. If you called form.submit() manually, consider using form.requestSubmit() instead. If you\\'re trying to use event.stopPropagation() in a submit event handler, consider also calling event.preventDefault().')");break}if("function"==typeof o&&("formAction"===n?("input"!==t&&cd(e,t,"name",a.name,a,null),cd(e,t,"formEncType",a.formEncType,a,null),cd(e,t,"formMethod",a.formMethod,a,null),cd(e,t,"formTarget",a.formTarget,a,null)):(cd(e,t,"encType",a.encType,a,null),cd(e,t,"method",a.method,a,null),cd(e,t,"target",a.target,a,null))),null==r||"symbol"==typeof r||"boolean"==typeof r){e.removeAttribute(n);break}r=th(""+r),e.setAttribute(n,r);break;case"onClick":null!=r&&(e.onclick=tb);break;case"onScroll":null!=r&&s9("scroll",e);break;case"onScrollEnd":null!=r&&s9("scrollend",e);break;case"dangerouslySetInnerHTML":if(null!=r){if("object"!=typeof r||!("__html"in r))throw Error(l(61));if(null!=(n=r.__html)){if(null!=a.children)throw Error(l(60));e.innerHTML=n}}break;case"multiple":e.multiple=r&&"function"!=typeof r&&"symbol"!=typeof r;break;case"muted":e.muted=r&&"function"!=typeof r&&"symbol"!=typeof r;break;case"suppressContentEditableWarning":case"suppressHydrationWarning":case"defaultValue":case"defaultChecked":case"innerHTML":case"ref":case"autoFocus":case"innerText":case"textContent":break;case"xlinkHref":if(null==r||"function"==typeof r||"boolean"==typeof r||"symbol"==typeof r){e.removeAttribute("xlink:href");break}n=th(""+r),e.setAttributeNS("http://www.w3.org/1999/xlink","xlink:href",n);break;case"contentEditable":case"spellCheck":case"draggable":case"value":case"autoReverse":case"externalResourcesRequired":case"focusable":case"preserveAlpha":null!=r&&"function"!=typeof r&&"symbol"!=typeof r?e.setAttribute(n,""+r):e.removeAttribute(n);break;case"inert":case"allowFullScreen":case"async":case"autoPlay":case"controls":case"default":case"defer":case"disabled":case"disablePictureInPicture":case"disableRemotePlayback":case"formNoValidate":case"hidden":case"loop":case"noModule":case"noValidate":case"open":case"playsInline":case"readOnly":case"required":case"reversed":case"scoped":case"seamless":case"itemScope":r&&"function"!=typeof r&&"symbol"!=typeof r?e.setAttribute(n,""):e.removeAttribute(n);break;case"capture":case"download":!0===r?e.setAttribute(n,""):!1!==r&&null!=r&&"function"!=typeof r&&"symbol"!=typeof r?e.setAttribute(n,r):e.removeAttribute(n);break;case"cols":case"rows":case"size":case"span":null!=r&&"function"!=typeof r&&"symbol"!=typeof r&&!isNaN(r)&&1<=r?e.setAttribute(n,r):e.removeAttribute(n);break;case"rowSpan":case"start":null==r||"function"==typeof r||"symbol"==typeof r||isNaN(r)?e.removeAttribute(n):e.setAttribute(n,r);break;case"popover":s9("beforetoggle",e),s9("toggle",e),e4(e,"popover",r);break;case"xlinkActuate":e8(e,"http://www.w3.org/1999/xlink","xlink:actuate",r);break;case"xlinkArcrole":e8(e,"http://www.w3.org/1999/xlink","xlink:arcrole",r);break;case"xlinkRole":e8(e,"http://www.w3.org/1999/xlink","xlink:role",r);break;case"xlinkShow":e8(e,"http://www.w3.org/1999/xlink","xlink:show",r);break;case"xlinkTitle":e8(e,"http://www.w3.org/1999/xlink","xlink:title",r);break;case"xlinkType":e8(e,"http://www.w3.org/1999/xlink","xlink:type",r);break;case"xmlBase":e8(e,"http://www.w3.org/XML/1998/namespace","xml:base",r);break;case"xmlLang":e8(e,"http://www.w3.org/XML/1998/namespace","xml:lang",r);break;case"xmlSpace":e8(e,"http://www.w3.org/XML/1998/namespace","xml:space",r);break;case"is":e4(e,"is",r);break;default:2<n.length&&("o"===n[0]||"O"===n[0])&&("n"===n[1]||"N"===n[1])||e4(e,n=tm.get(n)||n,r)}}function cp(e,t,n,r,a,o){switch(n){case"style":tp(e,r,o);break;case"dangerouslySetInnerHTML":if(null!=r){if("object"!=typeof r||!("__html"in r))throw Error(l(61));if(null!=(n=r.__html)){if(null!=a.children)throw Error(l(60));e.innerHTML=n}}break;case"children":"string"==typeof r?tc(e,r):("number"==typeof r||"bigint"==typeof r)&&tc(e,""+r);break;case"onScroll":null!=r&&s9("scroll",e);break;case"onScrollEnd":null!=r&&s9("scrollend",e);break;case"onClick":null!=r&&(e.onclick=tb);break;case"suppressContentEditableWarning":case"suppressHydrationWarning":case"innerHTML":case"ref":case"innerText":case"textContent":break;default:if(!eZ.hasOwnProperty(n))e:{if("o"===n[0]&&"n"===n[1]&&(a=n.endsWith("Capture"),t=n.slice(2,a?n.length-7:void 0),"function"==typeof(o=null!=(o=e[eF]||null)?o[n]:null)&&e.removeEventListener(t,o,a),"function"==typeof r)){"function"!=typeof o&&null!==o&&(n in e?e[n]=null:e.hasAttribute(n)&&e.removeAttribute(n)),e.addEventListener(t,r,a);break e}n in e?e[n]=r:!0===r?e.setAttribute(n,""):e4(e,n,r)}}}function cf(e,t,n){switch(t){case"div":case"span":case"svg":case"path":case"a":case"g":case"p":case"li":break;case"img":s9("error",e),s9("load",e);var r,a=!1,o=!1;for(r in n)if(n.hasOwnProperty(r)){var i=n[r];if(null!=i)switch(r){case"src":a=!0;break;case"srcSet":o=!0;break;case"children":case"dangerouslySetInnerHTML":throw Error(l(137,t));default:cd(e,t,r,i,n,null)}}o&&cd(e,t,"srcSet",n.srcSet,n,null),a&&cd(e,t,"src",n.src,n,null);return;case"input":s9("invalid",e);var s=r=i=o=null,c=null,u=null;for(a in n)if(n.hasOwnProperty(a)){var d=n[a];if(null!=d)switch(a){case"name":o=d;break;case"type":i=d;break;case"checked":c=d;break;case"defaultChecked":u=d;break;case"value":r=d;break;case"defaultValue":s=d;break;case"children":case"dangerouslySetInnerHTML":if(null!=d)throw Error(l(137,t));break;default:cd(e,t,a,d,n,null)}}ta(e,r,s,c,u,i,o,!1);return;case"select":for(o in s9("invalid",e),a=i=r=null,n)if(n.hasOwnProperty(o)&&null!=(s=n[o]))switch(o){case"value":r=s;break;case"defaultValue":i=s;break;case"multiple":a=s;default:cd(e,t,o,s,n,null)}t=r,n=i,e.multiple=!!a,null!=t?ti(e,!!a,t,!1):null!=n&&ti(e,!!a,n,!0);return;case"textarea":for(i in s9("invalid",e),r=o=a=null,n)if(n.hasOwnProperty(i)&&null!=(s=n[i]))switch(i){case"value":a=s;break;case"defaultValue":o=s;break;case"children":r=s;break;case"dangerouslySetInnerHTML":if(null!=s)throw Error(l(91));break;default:cd(e,t,i,s,n,null)}ts(e,a,o,r);return;case"option":for(c in n)n.hasOwnProperty(c)&&null!=(a=n[c])&&("selected"===c?e.selected=a&&"function"!=typeof a&&"symbol"!=typeof a:cd(e,t,c,a,n,null));return;case"dialog":s9("beforetoggle",e),s9("toggle",e),s9("cancel",e),s9("close",e);break;case"iframe":case"object":s9("load",e);break;case"video":case"audio":for(a=0;a<s3.length;a++)s9(s3[a],e);break;case"image":s9("error",e),s9("load",e);break;case"details":s9("toggle",e);break;case"embed":case"source":case"link":s9("error",e),s9("load",e);case"area":case"base":case"br":case"col":case"hr":case"keygen":case"meta":case"param":case"track":case"wbr":case"menuitem":for(u in n)if(n.hasOwnProperty(u)&&null!=(a=n[u]))switch(u){case"children":case"dangerouslySetInnerHTML":throw Error(l(137,t));default:cd(e,t,u,a,n,null)}return;default:if(tf(t)){for(d in n)n.hasOwnProperty(d)&&void 0!==(a=n[d])&&cp(e,t,d,a,n,void 0);return}}for(s in n)n.hasOwnProperty(s)&&null!=(a=n[s])&&cd(e,t,s,a,n,null)}function cm(e){switch(e){case"css":case"script":case"font":case"img":case"image":case"input":case"link":return!0;default:return!1}}var cg=null,ch=null;function cb(e){return 9===e.nodeType?e:e.ownerDocument}function cy(e){switch(e){case"http://www.w3.org/2000/svg":return 1;case"http://www.w3.org/1998/Math/MathML":return 2;default:return 0}}function cv(e,t){if(0===e)switch(t){case"svg":return 1;case"math":return 2;default:return 0}return 1===e&&"foreignObject"===t?0:e}function cw(e,t){return"textarea"===e||"noscript"===e||"string"==typeof t.children||"number"==typeof t.children||"bigint"==typeof t.children||"object"==typeof t.dangerouslySetInnerHTML&&null!==t.dangerouslySetInnerHTML&&null!=t.dangerouslySetInnerHTML.__html}var ck=null,cx="function"==typeof setTimeout?setTimeout:void 0,cS="function"==typeof clearTimeout?clearTimeout:void 0,cE="function"==typeof Promise?Promise:void 0,c_="function"==typeof queueMicrotask?queueMicrotask:void 0!==cE?function(e){return cE.resolve(null).then(e).catch(cA)}:cx;function cA(e){setTimeout(function(){throw e})}function cC(e){return"head"===e}function cT(e,t){var n=t,r=0;do{var a=n.nextSibling;if(e.removeChild(n),a&&8===a.nodeType)if("/$"===(n=a.data)||"/&"===n){if(0===r){e.removeChild(a),uO(t);return}r--}else if("$"===n||"$?"===n||"$~"===n||"$!"===n||"&"===n)r++;else if("html"===n)cM(e.ownerDocument.documentElement);else if("head"===n){cM(n=e.ownerDocument.head);for(var o=n.firstChild;o;){var i=o.nextSibling,l=o.nodeName;o[eq]||"SCRIPT"===l||"STYLE"===l||"LINK"===l&&"stylesheet"===o.rel.toLowerCase()||n.removeChild(o),o=i}}else"body"===n&&cM(e.ownerDocument.body);n=a}while(n)uO(t)}function cj(e,t){var n=e;e=0;do{var r=n.nextSibling;if(1===n.nodeType?t?(n._stashedDisplay=n.style.display,n.style.display="none"):(n.style.display=n._stashedDisplay||"",""===n.getAttribute("style")&&n.removeAttribute("style")):3===n.nodeType&&(t?(n._stashedText=n.nodeValue,n.nodeValue=""):n.nodeValue=n._stashedText||""),r&&8===r.nodeType)if("/$"===(n=r.data))if(0===e)break;else e--;else"$"!==n&&"$?"!==n&&"$~"!==n&&"$!"!==n||e++;n=r}while(n)}function cP(e){var t=e.firstChild;for(t&&10===t.nodeType&&(t=t.nextSibling);t;){var n=t;switch(t=t.nextSibling,n.nodeName){case"HTML":case"HEAD":case"BODY":cP(n),eH(n);continue;case"SCRIPT":case"STYLE":continue;case"LINK":if("stylesheet"===n.rel.toLowerCase())continue}e.removeChild(n)}}function cz(e,t){for(;8!==e.nodeType;)if((1!==e.nodeType||"INPUT"!==e.nodeName||"hidden"!==e.type)&&!t||null===(e=cO(e.nextSibling)))return null;return e}function cL(e){return"$?"===e.data||"$~"===e.data}function cN(e){return"$!"===e.data||"$?"===e.data&&"loading"!==e.ownerDocument.readyState}function cO(e){for(;null!=e;e=e.nextSibling){var t=e.nodeType;if(1===t||3===t)break;if(8===t){if("$"===(t=e.data)||"$!"===t||"$?"===t||"$~"===t||"&"===t||"F!"===t||"F"===t)break;if("/$"===t||"/&"===t)return null}}return e}var cR=null;function cD(e){e=e.nextSibling;for(var t=0;e;){if(8===e.nodeType){var n=e.data;if("/$"===n||"/&"===n){if(0===t)return cO(e.nextSibling);t--}else"$"!==n&&"$!"!==n&&"$?"!==n&&"$~"!==n&&"&"!==n||t++}e=e.nextSibling}return null}function cF(e){e=e.previousSibling;for(var t=0;e;){if(8===e.nodeType){var n=e.data;if("$"===n||"$!"===n||"$?"===n||"$~"===n||"&"===n){if(0===t)return e;t--}else"/$"!==n&&"/&"!==n||t++}e=e.previousSibling}return null}function cI(e,t,n){switch(t=cb(n),e){case"html":if(!(e=t.documentElement))throw Error(l(452));return e;case"head":if(!(e=t.head))throw Error(l(453));return e;case"body":if(!(e=t.body))throw Error(l(454));return e;default:throw Error(l(451))}}function cM(e){for(var t=e.attributes;t.length;)e.removeAttributeNode(t[0]);eH(e)}var c$=new Map,cB=new Set;function cU(e){return"function"==typeof e.getRootNode?e.getRootNode():9===e.nodeType?e:e.ownerDocument}var cq=O.d;O.d={f:function(){var e=cq.f(),t=sp();return e||t},r:function(e){var t=eV(e);null!==t&&5===t.tag&&"form"===t.type?o0(t):cq.r(e)},D:function(e){cq.D(e),cG("dns-prefetch",e,null)},C:function(e,t){cq.C(e,t),cG("preconnect",e,t)},L:function(e,t,n){if(cq.L(e,t,n),cH&&e&&t){var r='link[rel="preload"][as="'+tn(t)+'"]';"image"===t&&n&&n.imageSrcSet?(r+='[imagesrcset="'+tn(n.imageSrcSet)+'"]',"string"==typeof n.imageSizes&&(r+='[imagesizes="'+tn(n.imageSizes)+'"]')):r+='[href="'+tn(e)+'"]';var a=r;switch(t){case"style":a=cW(e);break;case"script":a=cY(e)}c$.has(a)||(e=f({rel:"preload",href:"image"===t&&n&&n.imageSrcSet?void 0:e,as:t},n),c$.set(a,e),null!==cH.querySelector(r)||"style"===t&&cH.querySelector(cQ(a))||"script"===t&&cH.querySelector(cZ(a))||(cf(t=cH.createElement("link"),"link",e),eK(t),cH.head.appendChild(t)))}},m:function(e,t){if(cq.m(e,t),cH&&e){var n=t&&"string"==typeof t.as?t.as:"script",r='link[rel="modulepreload"][as="'+tn(n)+'"][href="'+tn(e)+'"]',a=r;switch(n){case"audioworklet":case"paintworklet":case"serviceworker":case"sharedworker":case"worker":case"script":a=cY(e)}if(!c$.has(a)&&(e=f({rel:"modulepreload",href:e},t),c$.set(a,e),null===cH.querySelector(r))){switch(n){case"audioworklet":case"paintworklet":case"serviceworker":case"sharedworker":case"worker":case"script":if(cH.querySelector(cZ(a)))return}cf(n=cH.createElement("link"),"link",e),eK(n),cH.head.appendChild(n)}}},X:function(e,t){if(cq.X(e,t),cH&&e){var n=eQ(cH).hoistableScripts,r=cY(e),a=n.get(r);a||((a=cH.querySelector(cZ(r)))||(e=f({src:e,async:!0},t),(t=c$.get(r))&&c1(e,t),eK(a=cH.createElement("script")),cf(a,"link",e),cH.head.appendChild(a)),a={type:"script",instance:a,count:1,state:null},n.set(r,a))}},S:function(e,t,n){if(cq.S(e,t,n),cH&&e){var r=eQ(cH).hoistableStyles,a=cW(e);t=t||"default";var o=r.get(a);if(!o){var i={loading:0,preload:null};if(o=cH.querySelector(cQ(a)))i.loading=5;else{e=f({rel:"stylesheet",href:e,"data-precedence":t},n),(n=c$.get(a))&&c0(e,n);var l=o=cH.createElement("link");eK(l),cf(l,"link",e),l._p=new Promise(function(e,t){l.onload=e,l.onerror=t}),l.addEventListener("load",function(){i.loading|=1}),l.addEventListener("error",function(){i.loading|=2}),i.loading|=4,cJ(o,t,cH)}o={type:"stylesheet",instance:o,count:1,state:i},r.set(a,o)}}},M:function(e,t){if(cq.M(e,t),cH&&e){var n=eQ(cH).hoistableScripts,r=cY(e),a=n.get(r);a||((a=cH.querySelector(cZ(r)))||(e=f({src:e,async:!0,type:"module"},t),(t=c$.get(r))&&c1(e,t),eK(a=cH.createElement("script")),cf(a,"link",e),cH.head.appendChild(a)),a={type:"script",instance:a,count:1,state:null},n.set(r,a))}}};var cH="u"<typeof document?null:document;function cG(e,t,n){if(cH&&"string"==typeof t&&t){var r=tn(t);r='link[rel="'+e+'"][href="'+r+'"]',"string"==typeof n&&(r+='[crossorigin="'+n+'"]'),cB.has(r)||(cB.add(r),e={rel:e,crossOrigin:n,href:t},null===cH.querySelector(r)&&(cf(t=cH.createElement("link"),"link",e),eK(t),cH.head.appendChild(t)))}}function cV(e,t,n,r){var a=(a=q.current)?cU(a):null;if(!a)throw Error(l(446));switch(e){case"meta":case"title":return null;case"style":return"string"==typeof n.precedence&&"string"==typeof n.href?(t=cW(n.href),(r=(n=eQ(a).hoistableStyles).get(t))||(r={type:"style",instance:null,count:0,state:null},n.set(t,r)),r):{type:"void",instance:null,count:0,state:null};case"link":if("stylesheet"===n.rel&&"string"==typeof n.href&&"string"==typeof n.precedence){e=cW(n.href);var o,i,s,c,u=eQ(a).hoistableStyles,d=u.get(e);if(d||(a=a.ownerDocument||a,d={type:"stylesheet",instance:null,count:0,state:{loading:0,preload:null}},u.set(e,d),(u=a.querySelector(cQ(e)))&&!u._p&&(d.instance=u,d.state.loading=5),c$.has(e)||(n={rel:"preload",as:"style",href:n.href,crossOrigin:n.crossOrigin,integrity:n.integrity,media:n.media,hrefLang:n.hrefLang,referrerPolicy:n.referrerPolicy},c$.set(e,n),u||(o=a,i=e,s=n,c=d.state,o.querySelector('link[rel="preload"][as="style"]['+i+"]")?c.loading=1:(c.preload=i=o.createElement("link"),i.addEventListener("load",function(){return c.loading|=1}),i.addEventListener("error",function(){return c.loading|=2}),cf(i,"link",s),eK(i),o.head.appendChild(i))))),t&&null===r)throw Error(l(528,""));return d}if(t&&null!==r)throw Error(l(529,""));return null;case"script":return t=n.async,"string"==typeof(n=n.src)&&t&&"function"!=typeof t&&"symbol"!=typeof t?(t=cY(n),(r=(n=eQ(a).hoistableScripts).get(t))||(r={type:"script",instance:null,count:0,state:null},n.set(t,r)),r):{type:"void",instance:null,count:0,state:null};default:throw Error(l(444,e))}}function cW(e){return'href="'+tn(e)+'"'}function cQ(e){return'link[rel="stylesheet"]['+e+"]"}function cK(e){return f({},e,{"data-precedence":e.precedence,precedence:null})}function cY(e){return'[src="'+tn(e)+'"]'}function cZ(e){return"script[async]"+e}function cX(e,t,n){if(t.count++,null===t.instance)switch(t.type){case"style":var r=e.querySelector('style[data-href~="'+tn(n.href)+'"]');if(r)return t.instance=r,eK(r),r;var a=f({},n,{"data-href":n.href,"data-precedence":n.precedence,href:null,precedence:null});return eK(r=(e.ownerDocument||e).createElement("style")),cf(r,"style",a),cJ(r,n.precedence,e),t.instance=r;case"stylesheet":a=cW(n.href);var o=e.querySelector(cQ(a));if(o)return t.state.loading|=4,t.instance=o,eK(o),o;r=cK(n),(a=c$.get(a))&&c0(r,a),eK(o=(e.ownerDocument||e).createElement("link"));var i=o;return i._p=new Promise(function(e,t){i.onload=e,i.onerror=t}),cf(o,"link",r),t.state.loading|=4,cJ(o,n.precedence,e),t.instance=o;case"script":if(o=cY(n.src),a=e.querySelector(cZ(o)))return t.instance=a,eK(a),a;return r=n,(a=c$.get(o))&&c1(r=f({},n),a),eK(a=(e=e.ownerDocument||e).createElement("script")),cf(a,"link",r),e.head.appendChild(a),t.instance=a;case"void":return null;default:throw Error(l(443,t.type))}return"stylesheet"===t.type&&0==(4&t.state.loading)&&(r=t.instance,t.state.loading|=4,cJ(r,n.precedence,e)),t.instance}function cJ(e,t,n){for(var r=n.querySelectorAll('link[rel="stylesheet"][data-precedence],style[data-precedence]'),a=r.length?r[r.length-1]:null,o=a,i=0;i<r.length;i++){var l=r[i];if(l.dataset.precedence===t)o=l;else if(o!==a)break}o?o.parentNode.insertBefore(e,o.nextSibling):(t=9===n.nodeType?n.head:n).insertBefore(e,t.firstChild)}function c0(e,t){null==e.crossOrigin&&(e.crossOrigin=t.crossOrigin),null==e.referrerPolicy&&(e.referrerPolicy=t.referrerPolicy),null==e.title&&(e.title=t.title)}function c1(e,t){null==e.crossOrigin&&(e.crossOrigin=t.crossOrigin),null==e.referrerPolicy&&(e.referrerPolicy=t.referrerPolicy),null==e.integrity&&(e.integrity=t.integrity)}var c2=null;function c4(e,t,n){if(null===c2){var r=new Map,a=c2=new Map;a.set(n,r)}else(r=(a=c2).get(n))||(r=new Map,a.set(n,r));if(r.has(e))return r;for(r.set(e,null),n=n.getElementsByTagName(e),a=0;a<n.length;a++){var o=n[a];if(!(o[eq]||o[eD]||"link"===e&&"stylesheet"===o.getAttribute("rel"))&&"http://www.w3.org/2000/svg"!==o.namespaceURI){var i=o.getAttribute(t)||"";i=e+i;var l=r.get(i);l?l.push(o):r.set(i,[o])}}return r}function c3(e,t,n){(e=e.ownerDocument||e).head.insertBefore(n,"title"===t?e.querySelector("head > title"):null)}function c8(e){return"stylesheet"!==e.type||0!=(3&e.state.loading)}var c6=0;function c9(){if(this.count--,0===this.count&&(0===this.imgCount||!this.waitingForImages)){if(this.stylesheets)c7(this,this.stylesheets);else if(this.unsuspend){var e=this.unsuspend;this.unsuspend=null,e()}}}var c5=null;function c7(e,t){e.stylesheets=null,null!==e.unsuspend&&(e.count++,c5=new Map,t.forEach(ue,e),c5=null,c9.call(e))}function ue(e,t){if(!(4&t.state.loading)){var n=c5.get(e);if(n)var r=n.get(null);else{n=new Map,c5.set(e,n);for(var a=e.querySelectorAll("link[data-precedence],style[data-precedence]"),o=0;o<a.length;o++){var i=a[o];("LINK"===i.nodeName||"not all"!==i.getAttribute("media"))&&(n.set(i.dataset.precedence,i),r=i)}r&&n.set(null,r)}i=(a=t.instance).getAttribute("data-precedence"),(o=n.get(i)||r)===r&&n.set(null,a),n.set(i,a),this.count++,r=c9.bind(this),a.addEventListener("load",r),a.addEventListener("error",r),o?o.parentNode.insertBefore(a,o.nextSibling):(e=9===e.nodeType?e.head:e).insertBefore(a,e.firstChild),t.state.loading|=4}}var ut={$$typeof:k,Provider:null,Consumer:null,_currentValue:R,_currentValue2:R,_threadCount:0};function un(e,t,n,r,a,o,i,l,s){this.tag=1,this.containerInfo=e,this.pingCache=this.current=this.pendingChildren=null,this.timeoutHandle=-1,this.callbackNode=this.next=this.pendingContext=this.context=this.cancelPendingCommit=null,this.callbackPriority=0,this.expirationTimes=eA(-1),this.entangledLanes=this.shellSuspendCounter=this.errorRecoveryDisabledLanes=this.expiredLanes=this.warmLanes=this.pingedLanes=this.suspendedLanes=this.pendingLanes=0,this.entanglements=eA(0),this.hiddenUpdates=eA(null),this.identifierPrefix=r,this.onUncaughtError=a,this.onCaughtError=o,this.onRecoverableError=i,this.pooledCache=null,this.pooledCacheLanes=0,this.formState=s,this.incompleteTransitions=new Map}function ur(e,t,n,r,a,o,i,l,s,c,u,d){return e=new un(e,t,n,i,s,c,u,d,l),t=1,!0===o&&(t|=24),o=rt(3,null,null,t),e.current=o,o.stateNode=e,t=r2(),t.refCount++,e.pooledCache=t,t.refCount++,o.memoizedState={element:r,isDehydrated:n,cache:t},aS(o),e}function ua(e,t,n,r,a,o){a=a?n7:n7,null===r.context?r.context=a:r.pendingContext=a,(r=a_(t)).payload={element:n},null!==(o=void 0===o?null:o)&&(r.callback=o),null!==(n=aA(e,r,t))&&(ss(n,e,t),aC(n,e,t))}function uo(e,t){if(null!==(e=e.memoizedState)&&null!==e.dehydrated){var n=e.retryLane;e.retryLane=0!==n&&n<t?n:t}}function ui(e,t){uo(e,t),(e=e.alternate)&&uo(e,t)}function ul(e){if(13===e.tag||31===e.tag){var t=n6(e,0x4000000);null!==t&&ss(t,e,0x4000000),ui(e,0x4000000)}}function us(e){if(13===e.tag||31===e.tag){var t=si(),n=n6(e,t=ez(t));null!==n&&ss(n,e,t),ui(e,t)}}var uc=!0;function uu(e,t,n,r){var a=N.T;N.T=null;var o=O.p;try{O.p=2,up(e,t,n,r)}finally{O.p=o,N.T=a}}function ud(e,t,n,r){var a=N.T;N.T=null;var o=O.p;try{O.p=8,up(e,t,n,r)}finally{O.p=o,N.T=a}}function up(e,t,n,r){if(uc){var a=uf(r);if(null===a)cn(e,t,r,um,n),u_(e,r);else if(function(e,t,n,r,a){switch(t){case"focusin":return uy=uA(uy,e,t,n,r,a),!0;case"dragenter":return uv=uA(uv,e,t,n,r,a),!0;case"mouseover":return uw=uA(uw,e,t,n,r,a),!0;case"pointerover":var o=a.pointerId;return uk.set(o,uA(uk.get(o)||null,e,t,n,r,a)),!0;case"gotpointercapture":return o=a.pointerId,ux.set(o,uA(ux.get(o)||null,e,t,n,r,a)),!0}return!1}(a,e,t,n,r))r.stopPropagation();else if(u_(e,r),4&t&&-1<uE.indexOf(e)){for(;null!==a;){var o=eV(a);if(null!==o)switch(o.tag){case 3:if((o=o.stateNode).current.memoizedState.isDehydrated){var i=ex(o.pendingLanes);if(0!==i){var l=o;for(l.pendingLanes|=2,l.entangledLanes|=2;i;){var s=1<<31-eh(i);l.entanglements[1]|=s,i&=~s}sV(o),0==(6&lF)&&(l3=ea()+500,sW(0,!1))}}break;case 31:case 13:null!==(l=n6(o,2))&&ss(l,o,2),sp(),ui(o,2)}if(null===(o=uf(r))&&cn(e,t,r,um,n),o===a)break;a=o}null!==a&&r.stopPropagation()}else cn(e,t,r,null,n)}}function uf(e){return ug(e=tv(e))}var um=null;function ug(e){if(um=null,null!==(e=eG(e))){var t=c(e);if(null===t)e=null;else{var n=t.tag;if(13===n){if(null!==(e=u(t)))return e;e=null}else if(31===n){if(null!==(e=d(t)))return e;e=null}else if(3===n){if(t.stateNode.current.memoizedState.isDehydrated)return 3===t.tag?t.stateNode.containerInfo:null;e=null}else t!==e&&(e=null)}}return um=e,null}function uh(e){switch(e){case"beforetoggle":case"cancel":case"click":case"close":case"contextmenu":case"copy":case"cut":case"auxclick":case"dblclick":case"dragend":case"dragstart":case"drop":case"focusin":case"focusout":case"input":case"invalid":case"keydown":case"keypress":case"keyup":case"mousedown":case"mouseup":case"paste":case"pause":case"play":case"pointercancel":case"pointerdown":case"pointerup":case"ratechange":case"reset":case"resize":case"seeked":case"submit":case"toggle":case"touchcancel":case"touchend":case"touchstart":case"volumechange":case"change":case"selectionchange":case"textInput":case"compositionstart":case"compositionend":case"compositionupdate":case"beforeblur":case"afterblur":case"beforeinput":case"blur":case"fullscreenchange":case"focus":case"hashchange":case"popstate":case"select":case"selectstart":return 2;case"drag":case"dragenter":case"dragexit":case"dragleave":case"dragover":case"mousemove":case"mouseout":case"mouseover":case"pointermove":case"pointerout":case"pointerover":case"scroll":case"touchmove":case"wheel":case"mouseenter":case"mouseleave":case"pointerenter":case"pointerleave":return 8;case"message":switch(eo()){case ei:return 2;case el:return 8;case es:case ec:return 32;case eu:return 0x10000000;default:return 32}default:return 32}}var ub=!1,uy=null,uv=null,uw=null,uk=new Map,ux=new Map,uS=[],uE="mousedown mouseup touchcancel touchend touchstart auxclick dblclick pointercancel pointerdown pointerup dragend dragstart drop compositionend compositionstart keydown keypress keyup input textInput copy cut paste click change contextmenu reset".split(" ");function u_(e,t){switch(e){case"focusin":case"focusout":uy=null;break;case"dragenter":case"dragleave":uv=null;break;case"mouseover":case"mouseout":uw=null;break;case"pointerover":case"pointerout":uk.delete(t.pointerId);break;case"gotpointercapture":case"lostpointercapture":ux.delete(t.pointerId)}}function uA(e,t,n,r,a,o){return null===e||e.nativeEvent!==o?(e={blockedOn:t,domEventName:n,eventSystemFlags:r,nativeEvent:o,targetContainers:[a]},null!==t&&null!==(t=eV(t))&&ul(t)):(e.eventSystemFlags|=r,t=e.targetContainers,null!==a&&-1===t.indexOf(a)&&t.push(a)),e}function uC(e){var t=eG(e.target);if(null!==t){var n=c(t);if(null!==n){if(13===(t=n.tag)){if(null!==(t=u(n))){e.blockedOn=t,eO(e.priority,function(){us(n)});return}}else if(31===t){if(null!==(t=d(n))){e.blockedOn=t,eO(e.priority,function(){us(n)});return}}else if(3===t&&n.stateNode.current.memoizedState.isDehydrated){e.blockedOn=3===n.tag?n.stateNode.containerInfo:null;return}}}e.blockedOn=null}function uT(e){if(null!==e.blockedOn)return!1;for(var t=e.targetContainers;0<t.length;){var n=uf(e.nativeEvent);if(null!==n)return null!==(t=eV(n))&&ul(t),e.blockedOn=n,!1;var r=new(n=e.nativeEvent).constructor(n.type,n);ty=r,n.target.dispatchEvent(r),ty=null,t.shift()}return!0}function uj(e,t,n){uT(e)&&n.delete(t)}function uP(){ub=!1,null!==uy&&uT(uy)&&(uy=null),null!==uv&&uT(uv)&&(uv=null),null!==uw&&uT(uw)&&(uw=null),uk.forEach(uj),ux.forEach(uj)}function uz(e,t){e.blockedOn===t&&(e.blockedOn=null,ub||(ub=!0,a.unstable_scheduleCallback(a.unstable_NormalPriority,uP)))}var uL=null;function uN(e){uL!==e&&(uL=e,a.unstable_scheduleCallback(a.unstable_NormalPriority,function(){uL===e&&(uL=null);for(var t=0;t<e.length;t+=3){var n=e[t],r=e[t+1],a=e[t+2];if("function"!=typeof r)if(null===ug(r||n))continue;else break;var o=eV(n);null!==o&&(e.splice(t,3),t-=3,oX(o,{pending:!0,data:a,method:n.method,action:r},r,a))}}))}function uO(e){function t(t){return uz(t,e)}null!==uy&&uz(uy,e),null!==uv&&uz(uv,e),null!==uw&&uz(uw,e),uk.forEach(t),ux.forEach(t);for(var n=0;n<uS.length;n++){var r=uS[n];r.blockedOn===e&&(r.blockedOn=null)}for(;0<uS.length&&null===(n=uS[0]).blockedOn;)uC(n),null===n.blockedOn&&uS.shift();if(null!=(n=(e.ownerDocument||e).$$reactFormReplay))for(r=0;r<n.length;r+=3){var a=n[r],o=n[r+1],i=a[eF]||null;if("function"==typeof o)i||uN(n);else if(i){var l=null;if(o&&o.hasAttribute("formAction")){if(a=o,i=o[eF]||null)l=i.formAction;else if(null!==ug(a))continue}else l=i.action;"function"==typeof l?n[r+1]=l:(n.splice(r,3),r-=3),uN(n)}}}function uR(){function e(e){e.canIntercept&&"react-transition"===e.info&&e.intercept({handler:function(){return new Promise(function(e){return a=e})},focusReset:"manual",scroll:"manual"})}function t(){null!==a&&(a(),a=null),r||setTimeout(n,20)}function n(){if(!r&&!navigation.transition){var e=navigation.currentEntry;e&&null!=e.url&&navigation.navigate(e.url,{state:e.getState(),info:"react-transition",history:"replace"})}}if("object"==typeof navigation){var r=!1,a=null;return navigation.addEventListener("navigate",e),navigation.addEventListener("navigatesuccess",t),navigation.addEventListener("navigateerror",t),setTimeout(n,100),function(){r=!0,navigation.removeEventListener("navigate",e),navigation.removeEventListener("navigatesuccess",t),navigation.removeEventListener("navigateerror",t),null!==a&&(a(),a=null)}}}function uD(e){this._internalRoot=e}function uF(e){this._internalRoot=e}uF.prototype.render=uD.prototype.render=function(e){var t=this._internalRoot;if(null===t)throw Error(l(409));ua(t.current,si(),e,t,null,null)},uF.prototype.unmount=uD.prototype.unmount=function(){var e=this._internalRoot;if(null!==e){this._internalRoot=null;var t=e.containerInfo;ua(e.current,2,null,e,null,null),sp(),t[eI]=null}},uF.prototype.unstable_scheduleHydration=function(e){if(e){var t=eN();e={blockedOn:null,target:e,priority:t};for(var n=0;n<uS.length&&0!==t&&t<uS[n].priority;n++);uS.splice(n,0,e),0===n&&uC(e)}};var uI=o.version;if("19.2.7"!==uI)throw Error(l(527,uI,"19.2.7"));if(O.findDOMNode=function(e){var t=e._reactInternals;if(void 0===t){if("function"==typeof e.render)throw Error(l(188));throw Error(l(268,e=Object.keys(e).join(",")))}return null===(e=null!==(e=function(e){var t=e.alternate;if(!t){if(null===(t=c(e)))throw Error(l(188));return t!==e?null:e}for(var n=e,r=t;;){var a=n.return;if(null===a)break;var o=a.alternate;if(null===o){if(null!==(r=a.return)){n=r;continue}break}if(a.child===o.child){for(o=a.child;o;){if(o===n)return p(a),e;if(o===r)return p(a),t;o=o.sibling}throw Error(l(188))}if(n.return!==r.return)n=a,r=o;else{for(var i=!1,s=a.child;s;){if(s===n){i=!0,n=a,r=o;break}if(s===r){i=!0,r=a,n=o;break}s=s.sibling}if(!i){for(s=o.child;s;){if(s===n){i=!0,n=o,r=a;break}if(s===r){i=!0,r=o,n=a;break}s=s.sibling}if(!i)throw Error(l(189))}}if(n.alternate!==r)throw Error(l(190))}if(3!==n.tag)throw Error(l(188));return n.stateNode.current===n?e:t}(t))?function e(t){var n=t.tag;if(5===n||26===n||27===n||6===n)return t;for(t=t.child;null!==t;){if(null!==(n=e(t)))return n;t=t.sibling}return null}(e):null)?null:e.stateNode},"u">typeof __REACT_DEVTOOLS_GLOBAL_HOOK__){var uM=__REACT_DEVTOOLS_GLOBAL_HOOK__;if(!uM.isDisabled&&uM.supportsFiber)try{ef=uM.inject({bundleType:0,version:"19.2.7",rendererPackageName:"react-dom",currentDispatcherRef:N,reconcilerVersion:"19.2.7"}),em=uM}catch(e){}}t.createRoot=function(e,t){if(!s(e))throw Error(l(299));var n=!1,r="",a=ip,o=im,i=ig;return null!=t&&(!0===t.unstable_strictMode&&(n=!0),void 0!==t.identifierPrefix&&(r=t.identifierPrefix),void 0!==t.onUncaughtError&&(a=t.onUncaughtError),void 0!==t.onCaughtError&&(o=t.onCaughtError),void 0!==t.onRecoverableError&&(i=t.onRecoverableError)),t=ur(e,1,!1,null,null,n,r,null,a,o,i,uR),e[eI]=t.current,ce(e),new uD(t)},t.hydrateRoot=function(e,t,n){if(!s(e))throw Error(l(299));var r=!1,a="",o=ip,i=im,c=ig,u=null;return null!=n&&(!0===n.unstable_strictMode&&(r=!0),void 0!==n.identifierPrefix&&(a=n.identifierPrefix),void 0!==n.onUncaughtError&&(o=n.onUncaughtError),void 0!==n.onCaughtError&&(i=n.onCaughtError),void 0!==n.onRecoverableError&&(c=n.onRecoverableError),void 0!==n.formState&&(u=n.formState)),(t=ur(e,1,!0,t,null!=n?n:null,r,a,u,o,i,c,uR)).context=n7,n=t.current,(a=a_(r=ez(r=si()))).callback=null,aA(n,a,r),n=r,t.current.lanes=n,eC(t,n),sV(t),e[eI]=t.current,ce(e),new uF(t)},t.version="19.2.7"},6221(e,t,n){"use strict";var r=n(6540);function a(e){var t="https://react.dev/errors/"+e;if(1<arguments.length){t+="?args[]="+encodeURIComponent(arguments[1]);for(var n=2;n<arguments.length;n++)t+="&args[]="+encodeURIComponent(arguments[n])}return"Minified React error #"+e+"; visit "+t+" for the full message or use the non-minified dev environment for full errors and additional helpful warnings."}function o(){}var i={d:{f:o,r:function(){throw Error(a(522))},D:o,C:o,L:o,m:o,X:o,S:o,M:o},p:0,findDOMNode:null},l=Symbol.for("react.portal"),s=r.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE;function c(e,t){return"font"===e?"":"string"==typeof t?"use-credentials"===t?t:"":void 0}t.__DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE=i,t.createPortal=function(e,t){var n=2<arguments.length&&void 0!==arguments[2]?arguments[2]:null;if(!t||1!==t.nodeType&&9!==t.nodeType&&11!==t.nodeType)throw Error(a(299));return function(e,t,n){var r=3<arguments.length&&void 0!==arguments[3]?arguments[3]:null;return{$$typeof:l,key:null==r?null:""+r,children:e,containerInfo:t,implementation:n}}(e,t,null,n)},t.flushSync=function(e){var t=s.T,n=i.p;try{if(s.T=null,i.p=2,e)return e()}finally{s.T=t,i.p=n,i.d.f()}},t.preconnect=function(e,t){"string"==typeof e&&(t=t?"string"==typeof(t=t.crossOrigin)?"use-credentials"===t?t:"":void 0:null,i.d.C(e,t))},t.prefetchDNS=function(e){"string"==typeof e&&i.d.D(e)},t.preinit=function(e,t){if("string"==typeof e&&t&&"string"==typeof t.as){var n=t.as,r=c(n,t.crossOrigin),a="string"==typeof t.integrity?t.integrity:void 0,o="string"==typeof t.fetchPriority?t.fetchPriority:void 0;"style"===n?i.d.S(e,"string"==typeof t.precedence?t.precedence:void 0,{crossOrigin:r,integrity:a,fetchPriority:o}):"script"===n&&i.d.X(e,{crossOrigin:r,integrity:a,fetchPriority:o,nonce:"string"==typeof t.nonce?t.nonce:void 0})}},t.preinitModule=function(e,t){if("string"==typeof e)if("object"==typeof t&&null!==t){if(null==t.as||"script"===t.as){var n=c(t.as,t.crossOrigin);i.d.M(e,{crossOrigin:n,integrity:"string"==typeof t.integrity?t.integrity:void 0,nonce:"string"==typeof t.nonce?t.nonce:void 0})}}else null==t&&i.d.M(e)},t.preload=function(e,t){if("string"==typeof e&&"object"==typeof t&&null!==t&&"string"==typeof t.as){var n=t.as,r=c(n,t.crossOrigin);i.d.L(e,n,{crossOrigin:r,integrity:"string"==typeof t.integrity?t.integrity:void 0,nonce:"string"==typeof t.nonce?t.nonce:void 0,type:"string"==typeof t.type?t.type:void 0,fetchPriority:"string"==typeof t.fetchPriority?t.fetchPriority:void 0,referrerPolicy:"string"==typeof t.referrerPolicy?t.referrerPolicy:void 0,imageSrcSet:"string"==typeof t.imageSrcSet?t.imageSrcSet:void 0,imageSizes:"string"==typeof t.imageSizes?t.imageSizes:void 0,media:"string"==typeof t.media?t.media:void 0})}},t.preloadModule=function(e,t){if("string"==typeof e)if(t){var n=c(t.as,t.crossOrigin);i.d.m(e,{as:"string"==typeof t.as&&"script"!==t.as?t.as:void 0,crossOrigin:n,integrity:"string"==typeof t.integrity?t.integrity:void 0})}else i.d.m(e)},t.requestFormReset=function(e){i.d.r(e)},t.unstable_batchedUpdates=function(e,t){return e(t)},t.useFormState=function(e,t,n){return s.H.useFormState(e,t,n)},t.useFormStatus=function(){return s.H.useHostTransitionStatus()},t.version="19.2.7"},5338(e,t,n){"use strict";!function e(){if("u">typeof __REACT_DEVTOOLS_GLOBAL_HOOK__&&"function"==typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE)try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(e)}catch(e){console.error(e)}}(),e.exports=n(1247)},961(e,t,n){"use strict";!function e(){if("u">typeof __REACT_DEVTOOLS_GLOBAL_HOOK__&&"function"==typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE)try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(e)}catch(e){console.error(e)}}(),e.exports=n(6221)},115(e){var t="u">typeof Element,n="function"==typeof Map,r="function"==typeof Set,a="function"==typeof ArrayBuffer&&!!ArrayBuffer.isView;e.exports=function(e,o){try{return function e(o,i){if(o===i)return!0;if(o&&i&&"object"==typeof o&&"object"==typeof i){var l,s,c,u;if(o.constructor!==i.constructor)return!1;if(Array.isArray(o)){if((l=o.length)!=i.length)return!1;for(s=l;0!=s--;)if(!e(o[s],i[s]))return!1;return!0}if(n&&o instanceof Map&&i instanceof Map){if(o.size!==i.size)return!1;for(u=o.entries();!(s=u.next()).done;)if(!i.has(s.value[0]))return!1;for(u=o.entries();!(s=u.next()).done;)if(!e(s.value[1],i.get(s.value[0])))return!1;return!0}if(r&&o instanceof Set&&i instanceof Set){if(o.size!==i.size)return!1;for(u=o.entries();!(s=u.next()).done;)if(!i.has(s.value[0]))return!1;return!0}if(a&&ArrayBuffer.isView(o)&&ArrayBuffer.isView(i)){if((l=o.length)!=i.length)return!1;for(s=l;0!=s--;)if(o[s]!==i[s])return!1;return!0}if(o.constructor===RegExp)return o.source===i.source&&o.flags===i.flags;if(o.valueOf!==Object.prototype.valueOf&&"function"==typeof o.valueOf&&"function"==typeof i.valueOf)return o.valueOf()===i.valueOf();if(o.toString!==Object.prototype.toString&&"function"==typeof o.toString&&"function"==typeof i.toString)return o.toString()===i.toString();if((l=(c=Object.keys(o)).length)!==Object.keys(i).length)return!1;for(s=l;0!=s--;)if(!Object.prototype.hasOwnProperty.call(i,c[s]))return!1;if(t&&o instanceof Element)return!1;for(s=l;0!=s--;)if(("_owner"!==c[s]&&"__v"!==c[s]&&"__o"!==c[s]||!o.$$typeof)&&!e(o[c[s]],i[c[s]]))return!1;return!0}return o!=o&&i!=i}(e,o)}catch(e){if((e.message||"").match(/stack|recursion/i))return console.warn("react-fast-compare cannot handle circular refs"),!1;throw e}}},545(e,t,n){"use strict";n.d(t,{mg:()=>Z,vd:()=>q});var r=n(6540),a=n(5556),o=n.n(a),i=n(115),l=n.n(i),s=n(311),c=n.n(s),u=n(2833),d=n.n(u);function p(){return(p=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e}).apply(this,arguments)}function f(e,t){e.prototype=Object.create(t.prototype),e.prototype.constructor=e,m(e,t)}function m(e,t){return(m=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e})(e,t)}function g(e,t){if(null==e)return{};var n,r,a={},o=Object.keys(e);for(r=0;r<o.length;r++)t.indexOf(n=o[r])>=0||(a[n]=e[n]);return a}var h={BASE:"base",BODY:"body",HEAD:"head",HTML:"html",LINK:"link",META:"meta",NOSCRIPT:"noscript",SCRIPT:"script",STYLE:"style",TITLE:"title",FRAGMENT:"Symbol(react.fragment)"},b={rel:["amphtml","canonical","alternate"]},y={type:["application/ld+json"]},v={charset:"",name:["robots","description"],property:["og:type","og:title","og:url","og:image","og:image:alt","og:description","twitter:url","twitter:title","twitter:description","twitter:image","twitter:image:alt","twitter:card","twitter:site"]},w=Object.keys(h).map(function(e){return h[e]}),k={accesskey:"accessKey",charset:"charSet",class:"className",contenteditable:"contentEditable",contextmenu:"contextMenu","http-equiv":"httpEquiv",itemprop:"itemProp",tabindex:"tabIndex"},x=Object.keys(k).reduce(function(e,t){return e[k[t]]=t,e},{}),S=function(e,t){for(var n=e.length-1;n>=0;n-=1){var r=e[n];if(Object.prototype.hasOwnProperty.call(r,t))return r[t]}return null},E=function(e){var t=S(e,h.TITLE),n=S(e,"titleTemplate");if(Array.isArray(t)&&(t=t.join("")),n&&t)return n.replace(/%s/g,function(){return t});var r=S(e,"defaultTitle");return t||r||void 0},_=function(e,t){return t.filter(function(t){return void 0!==t[e]}).map(function(t){return t[e]}).reduce(function(e,t){return p({},e,t)},{})},A=function(e,t,n){var r={};return n.filter(function(t){return!!Array.isArray(t[e])||(void 0!==t[e]&&console&&"function"==typeof console.warn&&console.warn("Helmet: "+e+' should be of type "Array". Instead found type "'+typeof t[e]+'"'),!1)}).map(function(t){return t[e]}).reverse().reduce(function(e,n){var a={};n.filter(function(e){for(var n,o=Object.keys(e),i=0;i<o.length;i+=1){var l=o[i],s=l.toLowerCase();-1===t.indexOf(s)||"rel"===n&&"canonical"===e[n].toLowerCase()||"rel"===s&&"stylesheet"===e[s].toLowerCase()||(n=s),-1===t.indexOf(l)||"innerHTML"!==l&&"cssText"!==l&&"itemprop"!==l||(n=l)}if(!n||!e[n])return!1;var c=e[n].toLowerCase();return r[n]||(r[n]={}),a[n]||(a[n]={}),!r[n][c]&&(a[n][c]=!0,!0)}).reverse().forEach(function(t){return e.push(t)});for(var o=Object.keys(a),i=0;i<o.length;i+=1){var l=o[i],s=p({},r[l],a[l]);r[l]=s}return e},[]).reverse()},C=function(e,t){if(Array.isArray(e)&&e.length){for(var n=0;n<e.length;n+=1)if(e[n][t])return!0}return!1},T=function(e){return Array.isArray(e)?e.join(""):e},j=function(e,t){return Array.isArray(e)?e.reduce(function(e,n){return!function(e,t){for(var n=Object.keys(e),r=0;r<n.length;r+=1)if(t[n[r]]&&t[n[r]].includes(e[n[r]]))return!0;return!1}(n,t)?e.default.push(n):e.priority.push(n),e},{priority:[],default:[]}):{default:e}},P=function(e,t){var n;return p({},e,((n={})[t]=void 0,n))},z=[h.NOSCRIPT,h.SCRIPT,h.STYLE],L=function(e,t){return void 0===t&&(t=!0),!1===t?String(e):String(e).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'")},N=function(e){return Object.keys(e).reduce(function(t,n){var r=void 0!==e[n]?n+'="'+e[n]+'"':""+n;return t?t+" "+r:r},"")},O=function(e,t){return void 0===t&&(t={}),Object.keys(e).reduce(function(t,n){return t[k[n]||n]=e[n],t},t)},R=function(e,t){return t.map(function(t,n){var a,o=((a={key:n})["data-rh"]=!0,a);return Object.keys(t).forEach(function(e){var n=k[e]||e;"innerHTML"===n||"cssText"===n?o.dangerouslySetInnerHTML={__html:t.innerHTML||t.cssText}:o[n]=t[e]}),r.createElement(e,o)})},D=function(e,t,n){switch(e){case h.TITLE:return{toComponent:function(){var e,n,a,o;return n=t.titleAttributes,(a={key:e=t.title})["data-rh"]=!0,o=O(n,a),[r.createElement(h.TITLE,o,e)]},toString:function(){var r,a,o,i;return r=t.title,a=t.titleAttributes,o=N(a),i=T(r),o?"<"+e+' data-rh="true" '+o+">"+L(i,n)+"</"+e+">":"<"+e+' data-rh="true">'+L(i,n)+"</"+e+">"}};case"bodyAttributes":case"htmlAttributes":return{toComponent:function(){return O(t)},toString:function(){return N(t)}};default:return{toComponent:function(){return R(e,t)},toString:function(){return t.reduce(function(t,r){var a=Object.keys(r).filter(function(e){return"innerHTML"!==e&&"cssText"!==e}).reduce(function(e,t){var a=void 0===r[t]?t:t+'="'+L(r[t],n)+'"';return e?e+" "+a:a},""),o=r.innerHTML||r.cssText||"",i=-1===z.indexOf(e);return t+"<"+e+' data-rh="true" '+a+(i?"/>":">"+o+"</"+e+">")},"")}}}},F=function(e){var t=e.baseTag,n=e.bodyAttributes,r=e.encode,a=e.htmlAttributes,o=e.noscriptTags,i=e.styleTags,l=e.title,s=e.titleAttributes,c=e.linkTags,u=e.metaTags,d=e.scriptTags,p={toComponent:function(){},toString:function(){return""}};if(e.prioritizeSeoTags){var f,m,g,w,k,x,S=(f=e.linkTags,m=e.scriptTags,g=e.encode,w=j(e.metaTags,v),k=j(f,b),x=j(m,y),{priorityMethods:{toComponent:function(){return[].concat(R(h.META,w.priority),R(h.LINK,k.priority),R(h.SCRIPT,x.priority))},toString:function(){return D(h.META,w.priority,g)+" "+D(h.LINK,k.priority,g)+" "+D(h.SCRIPT,x.priority,g)}},metaTags:w.default,linkTags:k.default,scriptTags:x.default});p=S.priorityMethods,c=S.linkTags,u=S.metaTags,d=S.scriptTags}return{priority:p,base:D(h.BASE,t,r),bodyAttributes:D("bodyAttributes",n,r),htmlAttributes:D("htmlAttributes",a,r),link:D(h.LINK,c,r),meta:D(h.META,u,r),noscript:D(h.NOSCRIPT,o,r),script:D(h.SCRIPT,d,r),style:D(h.STYLE,i,r),title:D(h.TITLE,{title:void 0===l?"":l,titleAttributes:s},r)}},I=[],M=function(e,t){var n=this;void 0===t&&(t="u">typeof document),this.instances=[],this.value={setHelmet:function(e){n.context.helmet=e},helmetInstances:{get:function(){return n.canUseDOM?I:n.instances},add:function(e){(n.canUseDOM?I:n.instances).push(e)},remove:function(e){var t=(n.canUseDOM?I:n.instances).indexOf(e);(n.canUseDOM?I:n.instances).splice(t,1)}}},this.context=e,this.canUseDOM=t,t||(e.helmet=F({baseTag:[],bodyAttributes:{},encodeSpecialCharacters:!0,htmlAttributes:{},linkTags:[],metaTags:[],noscriptTags:[],scriptTags:[],styleTags:[],title:"",titleAttributes:{}}))},$=r.createContext({}),B=o().shape({setHelmet:o().func,helmetInstances:o().shape({get:o().func,add:o().func,remove:o().func})}),U="u">typeof document,q=function(e){function t(n){var r;return(r=e.call(this,n)||this).helmetData=new M(r.props.context,t.canUseDOM),r}return f(t,e),t.prototype.render=function(){return r.createElement($.Provider,{value:this.helmetData.value},this.props.children)},t}(r.Component);q.canUseDOM=U,q.propTypes={context:o().shape({helmet:o().shape()}),children:o().node.isRequired},q.defaultProps={context:{}},q.displayName="HelmetProvider";var H=function(e,t){var n,r=document.head||document.querySelector(h.HEAD),a=r.querySelectorAll(e+"[data-rh]"),o=[].slice.call(a),i=[];return t&&t.length&&t.forEach(function(t){var r=document.createElement(e);for(var a in t)Object.prototype.hasOwnProperty.call(t,a)&&("innerHTML"===a?r.innerHTML=t.innerHTML:"cssText"===a?r.styleSheet?r.styleSheet.cssText=t.cssText:r.appendChild(document.createTextNode(t.cssText)):r.setAttribute(a,void 0===t[a]?"":t[a]));r.setAttribute("data-rh","true"),o.some(function(e,t){return n=t,r.isEqualNode(e)})?o.splice(n,1):i.push(r)}),o.forEach(function(e){return e.parentNode.removeChild(e)}),i.forEach(function(e){return r.appendChild(e)}),{oldTags:o,newTags:i}},G=function(e,t){var n=document.getElementsByTagName(e)[0];if(n){for(var r=n.getAttribute("data-rh"),a=r?r.split(","):[],o=[].concat(a),i=Object.keys(t),l=0;l<i.length;l+=1){var s=i[l],c=t[s]||"";n.getAttribute(s)!==c&&n.setAttribute(s,c),-1===a.indexOf(s)&&a.push(s);var u=o.indexOf(s);-1!==u&&o.splice(u,1)}for(var d=o.length-1;d>=0;d-=1)n.removeAttribute(o[d]);a.length===o.length?n.removeAttribute("data-rh"):n.getAttribute("data-rh")!==i.join(",")&&n.setAttribute("data-rh",i.join(","))}},V=function(e,t){var n=e.baseTag,r=e.htmlAttributes,a=e.linkTags,o=e.metaTags,i=e.noscriptTags,l=e.onChangeClientState,s=e.scriptTags,c=e.styleTags,u=e.title,d=e.titleAttributes;G(h.BODY,e.bodyAttributes),G(h.HTML,r),void 0!==u&&document.title!==u&&(document.title=T(u)),G(h.TITLE,d);var p={baseTag:H(h.BASE,n),linkTags:H(h.LINK,a),metaTags:H(h.META,o),noscriptTags:H(h.NOSCRIPT,i),scriptTags:H(h.SCRIPT,s),styleTags:H(h.STYLE,c)},f={},m={};Object.keys(p).forEach(function(e){var t=p[e],n=t.newTags,r=t.oldTags;n.length&&(f[e]=n),r.length&&(m[e]=p[e].oldTags)}),t&&t(),l(e,f,m)},W=null,Q=function(e){function t(){for(var t,n=arguments.length,r=Array(n),a=0;a<n;a++)r[a]=arguments[a];return(t=e.call.apply(e,[this].concat(r))||this).rendered=!1,t}f(t,e);var n=t.prototype;return n.shouldComponentUpdate=function(e){return!d()(e,this.props)},n.componentDidUpdate=function(){this.emitChange()},n.componentWillUnmount=function(){this.props.context.helmetInstances.remove(this),this.emitChange()},n.emitChange=function(){var e,t,n=this.props.context,r=n.setHelmet,a=null,o={baseTag:(e=["href"],(t=n.helmetInstances.get().map(function(e){var t=p({},e.props);return delete t.context,t})).filter(function(e){return void 0!==e[h.BASE]}).map(function(e){return e[h.BASE]}).reverse().reduce(function(t,n){if(!t.length)for(var r=Object.keys(n),a=0;a<r.length;a+=1){var o=r[a].toLowerCase();if(-1!==e.indexOf(o)&&n[o])return t.concat(n)}return t},[])),bodyAttributes:_("bodyAttributes",t),defer:S(t,"defer"),encode:S(t,"encodeSpecialCharacters"),htmlAttributes:_("htmlAttributes",t),linkTags:A(h.LINK,["rel","href"],t),metaTags:A(h.META,["name","charset","http-equiv","property","itemprop"],t),noscriptTags:A(h.NOSCRIPT,["innerHTML"],t),onChangeClientState:S(t,"onChangeClientState")||function(){},scriptTags:A(h.SCRIPT,["src","innerHTML"],t),styleTags:A(h.STYLE,["cssText"],t),title:E(t),titleAttributes:_("titleAttributes",t),prioritizeSeoTags:C(t,"prioritizeSeoTags")};q.canUseDOM?(W&&cancelAnimationFrame(W),o.defer?W=requestAnimationFrame(function(){V(o,function(){W=null})}):(V(o),W=null)):F&&(a=F(o)),r(a)},n.init=function(){this.rendered||(this.rendered=!0,this.props.context.helmetInstances.add(this),this.emitChange())},n.render=function(){return this.init(),null},t}(r.Component);Q.propTypes={context:B.isRequired},Q.displayName="HelmetDispatcher";var K=["children"],Y=["children"],Z=function(e){function t(){return e.apply(this,arguments)||this}f(t,e);var n=t.prototype;return n.shouldComponentUpdate=function(e){return!l()(P(this.props,"helmetData"),P(e,"helmetData"))},n.mapNestedChildrenToProps=function(e,t){if(!t)return null;switch(e.type){case h.SCRIPT:case h.NOSCRIPT:return{innerHTML:t};case h.STYLE:return{cssText:t};default:throw Error("<"+e.type+" /> elements are self-closing and can not contain children. Refer to our API for more information.")}},n.flattenArrayTypeChildren=function(e){var t,n=e.child,r=e.arrayTypeChildren;return p({},r,((t={})[n.type]=[].concat(r[n.type]||[],[p({},e.newChildProps,this.mapNestedChildrenToProps(n,e.nestedChildren))]),t))},n.mapObjectTypeChildren=function(e){var t,n,r=e.child,a=e.newProps,o=e.newChildProps,i=e.nestedChildren;switch(r.type){case h.TITLE:return p({},a,((t={})[r.type]=i,t.titleAttributes=p({},o),t));case h.BODY:return p({},a,{bodyAttributes:p({},o)});case h.HTML:return p({},a,{htmlAttributes:p({},o)});default:return p({},a,((n={})[r.type]=p({},o),n))}},n.mapArrayTypeChildrenToProps=function(e,t){var n=p({},t);return Object.keys(e).forEach(function(t){var r;n=p({},n,((r={})[t]=e[t],r))}),n},n.warnOnInvalidChildren=function(e,t){return c()(w.some(function(t){return e.type===t}),"function"==typeof e.type?"You may be attempting to nest <Helmet> components within each other, which is not allowed. Refer to our API for more information.":"Only elements types "+w.join(", ")+" are allowed. Helmet does not support rendering <"+e.type+"> elements. Refer to our API for more information."),c()(!t||"string"==typeof t||Array.isArray(t)&&!t.some(function(e){return"string"!=typeof e}),"Helmet expects a string as a child of <"+e.type+">. Did you forget to wrap your children in braces? ( <"+e.type+">{``}</"+e.type+"> ) Refer to our API for more information."),!0},n.mapChildrenToProps=function(e,t){var n=this,a={};return r.Children.forEach(e,function(e){if(e&&e.props){var r=e.props,o=r.children,i=g(r,K),l=Object.keys(i).reduce(function(e,t){return e[x[t]||t]=i[t],e},{}),s=e.type;switch("symbol"==typeof s?s=s.toString():n.warnOnInvalidChildren(e,o),s){case h.FRAGMENT:t=n.mapChildrenToProps(o,t);break;case h.LINK:case h.META:case h.NOSCRIPT:case h.SCRIPT:case h.STYLE:a=n.flattenArrayTypeChildren({child:e,arrayTypeChildren:a,newChildProps:l,nestedChildren:o});break;default:t=n.mapObjectTypeChildren({child:e,newProps:t,newChildProps:l,nestedChildren:o})}}}),this.mapArrayTypeChildrenToProps(a,t)},n.render=function(){var e=this.props,t=e.children,n=g(e,Y),a=p({},n),o=n.helmetData;return t&&(a=this.mapChildrenToProps(t,a)),!o||o instanceof M||(o=new M(o.context,o.instances)),o?r.createElement(Q,p({},a,{context:o.value,helmetData:void 0})):r.createElement($.Consumer,null,function(e){return r.createElement(Q,p({},a,{context:e}))})},t}(r.Component);Z.propTypes={base:o().object,bodyAttributes:o().object,children:o().oneOfType([o().arrayOf(o().node),o().node]),defaultTitle:o().string,defer:o().bool,encodeSpecialCharacters:o().bool,htmlAttributes:o().object,link:o().arrayOf(o().object),meta:o().arrayOf(o().object),noscript:o().arrayOf(o().object),onChangeClientState:o().func,script:o().arrayOf(o().object),style:o().arrayOf(o().object),title:o().string,titleAttributes:o().object,titleTemplate:o().string,prioritizeSeoTags:o().bool,helmetData:o().object},Z.defaultProps={defer:!0,encodeSpecialCharacters:!0,prioritizeSeoTags:!1},Z.displayName="Helmet"},2799(e,t){"use strict";var n="function"==typeof Symbol&&Symbol.for,r=n?Symbol.for("react.element"):60103,a=n?Symbol.for("react.portal"):60106,o=n?Symbol.for("react.fragment"):60107,i=n?Symbol.for("react.strict_mode"):60108,l=n?Symbol.for("react.profiler"):60114,s=n?Symbol.for("react.provider"):60109,c=n?Symbol.for("react.context"):60110,u=n?Symbol.for("react.async_mode"):60111,d=n?Symbol.for("react.concurrent_mode"):60111,p=n?Symbol.for("react.forward_ref"):60112,f=n?Symbol.for("react.suspense"):60113,m=n?Symbol.for("react.suspense_list"):60120,g=n?Symbol.for("react.memo"):60115,h=n?Symbol.for("react.lazy"):60116,b=n?Symbol.for("react.block"):60121,y=n?Symbol.for("react.fundamental"):60117,v=n?Symbol.for("react.responder"):60118,w=n?Symbol.for("react.scope"):60119;function k(e){if("object"==typeof e&&null!==e){var t=e.$$typeof;switch(t){case r:switch(e=e.type){case u:case d:case o:case l:case i:case f:return e;default:switch(e=e&&e.$$typeof){case c:case p:case h:case g:case s:return e;default:return t}}case a:return t}}}function x(e){return k(e)===d}t.AsyncMode=u,t.ConcurrentMode=d,t.ContextConsumer=c,t.ContextProvider=s,t.Element=r,t.ForwardRef=p,t.Fragment=o,t.Lazy=h,t.Memo=g,t.Portal=a,t.Profiler=l,t.StrictMode=i,t.Suspense=f,t.isAsyncMode=function(e){return x(e)||k(e)===u},t.isConcurrentMode=x,t.isContextConsumer=function(e){return k(e)===c},t.isContextProvider=function(e){return k(e)===s},t.isElement=function(e){return"object"==typeof e&&null!==e&&e.$$typeof===r},t.isForwardRef=function(e){return k(e)===p},t.isFragment=function(e){return k(e)===o},t.isLazy=function(e){return k(e)===h},t.isMemo=function(e){return k(e)===g},t.isPortal=function(e){return k(e)===a},t.isProfiler=function(e){return k(e)===l},t.isStrictMode=function(e){return k(e)===i},t.isSuspense=function(e){return k(e)===f},t.isValidElementType=function(e){return"string"==typeof e||"function"==typeof e||e===o||e===d||e===l||e===i||e===f||e===m||"object"==typeof e&&null!==e&&(e.$$typeof===h||e.$$typeof===g||e.$$typeof===s||e.$$typeof===c||e.$$typeof===p||e.$$typeof===y||e.$$typeof===v||e.$$typeof===w||e.$$typeof===b)},t.typeOf=k},4363(e,t,n){"use strict";e.exports=n(2799)},3259(e,t,n){"use strict";function r(e,t){e.prototype=Object.create(t.prototype),e.prototype.constructor=e,e.__proto__=t}function a(e){if(void 0===e)throw ReferenceError("this hasn't been initialised - super() hasn't been called");return e}function o(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function i(){return(i=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e}).apply(this,arguments)}var l=n(6540),s=[],c=[],u=l.createContext(null);function d(e){var t=e(),n={loading:!0,loaded:null,error:null};return n.promise=t.then(function(e){return n.loading=!1,n.loaded=e,e}).catch(function(e){throw n.loading=!1,n.error=e,e}),n}function p(e){var t={loading:!1,loaded:{},error:null},n=[];try{Object.keys(e).forEach(function(r){var a=d(e[r]);a.loading?t.loading=!0:(t.loaded[r]=a.loaded,t.error=a.error),n.push(a.promise),a.promise.then(function(e){t.loaded[r]=e}).catch(function(e){t.error=e})})}catch(e){t.error=e}return t.promise=Promise.all(n).then(function(e){return t.loading=!1,e}).catch(function(e){throw t.loading=!1,e}),t}function f(e,t){return l.createElement(e&&e.__esModule?e.default:e,t)}function m(e,t){if(!t.loading)throw Error("react-loadable requires a `loading` component");var d,p,m=i({loader:null,loading:null,delay:200,timeout:null,render:f,webpack:null,modules:null},t),g=null;function h(){return g||(g=e(m.loader)),g.promise}return s.push(h),"function"==typeof m.webpack&&c.push(function(){if((0,m.webpack)().every(function(e){return void 0!==e&&void 0!==n.m[e]}))return h()}),p=d=function(t){function n(n){var r;return o(a(a(r=t.call(this,n)||this)),"retry",function(){r.setState({error:null,loading:!0,timedOut:!1}),g=e(m.loader),r._loadModule()}),h(),r.state={error:g.error,pastDelay:!1,timedOut:!1,loading:g.loading,loaded:g.loaded},r}r(n,t),n.preload=function(){return h()};var i=n.prototype;return i.UNSAFE_componentWillMount=function(){this._loadModule()},i.componentDidMount=function(){this._mounted=!0},i._loadModule=function(){var e=this;if(this.context&&Array.isArray(m.modules)&&m.modules.forEach(function(t){e.context.report(t)}),g.loading){var t=function(t){e._mounted&&e.setState(t)};"number"==typeof m.delay&&(0===m.delay?this.setState({pastDelay:!0}):this._delay=setTimeout(function(){t({pastDelay:!0})},m.delay)),"number"==typeof m.timeout&&(this._timeout=setTimeout(function(){t({timedOut:!0})},m.timeout));var n=function(){t({error:g.error,loaded:g.loaded,loading:g.loading}),e._clearTimeouts()};g.promise.then(function(){return n(),null}).catch(function(e){return n(),null})}},i.componentWillUnmount=function(){this._mounted=!1,this._clearTimeouts()},i._clearTimeouts=function(){clearTimeout(this._delay),clearTimeout(this._timeout)},i.render=function(){return this.state.loading||this.state.error?l.createElement(m.loading,{isLoading:this.state.loading,pastDelay:this.state.pastDelay,timedOut:this.state.timedOut,error:this.state.error,retry:this.retry}):this.state.loaded?m.render(this.state.loaded,this.props):null},n}(l.Component),o(d,"contextType",u),p}function g(e){return m(d,e)}function h(e){for(var t=[];e.length;){var n=e.pop();t.push(n())}return Promise.all(t).then(function(){if(e.length)return h(e)})}g.Map=function(e){if("function"!=typeof e.render)throw Error("LoadableMap requires a `render(loaded, props)` function");return m(p,e)},g.Capture=function(e){function t(){return e.apply(this,arguments)||this}return r(t,e),t.prototype.render=function(){return l.createElement(u.Provider,{value:{report:this.props.report}},l.Children.only(this.props.children))},t}(l.Component),g.preloadAll=function(){return new Promise(function(e,t){h(s).then(e,t)})},g.preloadReady=function(){return new Promise(function(e,t){h(c).then(e,e)})},e.exports=g},2831(e,t,n){"use strict";n.d(t,{u:()=>function e(t,n,a){return void 0===a&&(a=[]),t.some(function(t){var o=t.path?(0,r.B6)(n,t):a.length?a[a.length-1].match:r.Ix.computeRootMatch(n);return o&&(a.push({route:t,match:o}),t.routes&&e(t.routes,n,a)),o}),a},v:()=>i});var r=n(6347),a=n(8168),o=n(6540);function i(e,t,n){return void 0===t&&(t={}),void 0===n&&(n={}),e?o.createElement(r.dO,n,e.map(function(e,n){return o.createElement(r.qh,{key:e.key||n,path:e.path,exact:e.exact,strict:e.strict,render:function(n){return e.render?e.render((0,a.A)({},n,{},t,{route:e})):o.createElement(e.component,(0,a.A)({},n,t,{route:e}))}})})):null}},4625(e,t,n){"use strict";n.d(t,{I9:()=>d,Kd:()=>u,N_:()=>b,k2:()=>w});var r=n(6347),a=n(594),o=n(6540),i=n(3328),l=n(8168),s=n(8587),c=n(1561),u=function(e){function t(){for(var t,n=arguments.length,r=Array(n),a=0;a<n;a++)r[a]=arguments[a];return(t=e.call.apply(e,[this].concat(r))||this).history=(0,i.zR)(t.props),t}return(0,a.A)(t,e),t.prototype.render=function(){return o.createElement(r.Ix,{history:this.history,children:this.props.children})},t}(o.Component),d=function(e){function t(){for(var t,n=arguments.length,r=Array(n),a=0;a<n;a++)r[a]=arguments[a];return(t=e.call.apply(e,[this].concat(r))||this).history=(0,i.TM)(t.props),t}return(0,a.A)(t,e),t.prototype.render=function(){return o.createElement(r.Ix,{history:this.history,children:this.props.children})},t}(o.Component),p=function(e,t){return"function"==typeof e?e(t):e},f=function(e,t){return"string"==typeof e?(0,i.yJ)(e,null,null,t):e},m=function(e){return e},g=o.forwardRef;void 0===g&&(g=m);var h=g(function(e,t){var n=e.innerRef,r=e.navigate,a=e.onClick,i=(0,s.A)(e,["innerRef","navigate","onClick"]),c=i.target,u=(0,l.A)({},i,{onClick:function(e){try{a&&a(e)}catch(t){throw e.preventDefault(),t}e.defaultPrevented||0!==e.button||c&&"_self"!==c||e.metaKey||e.altKey||e.ctrlKey||e.shiftKey||(e.preventDefault(),r())}});return m!==g?u.ref=t||n:u.ref=n,o.createElement("a",u)}),b=g(function(e,t){var n=e.component,a=void 0===n?h:n,u=e.replace,d=e.to,b=e.innerRef,y=(0,s.A)(e,["component","replace","to","innerRef"]);return o.createElement(r.XZ.Consumer,null,function(e){e||(0,c.A)(!1);var n=e.history,r=f(p(d,e.location),e.location),s=r?n.createHref(r):"",h=(0,l.A)({},y,{href:s,navigate:function(){var t=p(d,e.location),r=(0,i.AO)(e.location)===(0,i.AO)(f(t));(u||r?n.replace:n.push)(t)}});return m!==g?h.ref=t||b:h.innerRef=b,o.createElement(a,h)})}),y=function(e){return e},v=o.forwardRef;void 0===v&&(v=y);var w=v(function(e,t){var n=e["aria-current"],a=void 0===n?"page":n,i=e.activeClassName,u=void 0===i?"active":i,d=e.activeStyle,m=e.className,g=e.exact,h=e.isActive,w=e.location,k=e.sensitive,x=e.strict,S=e.style,E=e.to,_=e.innerRef,A=(0,s.A)(e,["aria-current","activeClassName","activeStyle","className","exact","isActive","location","sensitive","strict","style","to","innerRef"]);return o.createElement(r.XZ.Consumer,null,function(e){e||(0,c.A)(!1);var n=w||e.location,i=f(p(E,n),n),s=i.pathname,C=s&&s.replace(/([.+*?=^!:${}()[\]|/\\])/g,"\\$1"),T=C?(0,r.B6)(n.pathname,{path:C,exact:g,sensitive:k,strict:x}):null,j=!!(h?h(T,n):T),P="function"==typeof m?m(j):m,z="function"==typeof S?S(j):S;j&&(P=function(){for(var e=arguments.length,t=Array(e),n=0;n<e;n++)t[n]=arguments[n];return t.filter(function(e){return e}).join(" ")}(P,u),z=(0,l.A)({},z,d));var L=(0,l.A)({"aria-current":j&&a||null,className:P,style:z,to:i},A);return y!==v?L.ref=t||_:L.innerRef=_,o.createElement(b,L)})})},6347(e,t,n){"use strict";n.d(t,{B6:()=>v,Ix:()=>h,W6:()=>S,XZ:()=>g,dO:()=>k,qh:()=>w,zy:()=>E});var r=n(594),a=n(6540),o=n(5556),i=n.n(o),l=(n(3328),n(1561)),s=n(8168),c=n(5302),u=n.n(c);n(4363),n(8587),n(4146);var d="u">typeof globalThis?globalThis:"u">typeof window?window:void 0!==n.g?n.g:{},p=a.createContext||function(e,t){var n,o,l="__create-react-context-"+(d.__global_unique_id__=(d.__global_unique_id__||0)+1)+"__",s=function(e){function n(){for(var t,n,r,a=arguments.length,o=Array(a),i=0;i<a;i++)o[i]=arguments[i];return n=(t=e.call.apply(e,[this].concat(o))||this).props.value,r=[],t.emitter={on:function(e){r.push(e)},off:function(e){r=r.filter(function(t){return t!==e})},get:function(){return n},set:function(e,t){n=e,r.forEach(function(e){return e(n,t)})}},t}(0,r.A)(n,e);var a=n.prototype;return a.getChildContext=function(){var e;return(e={})[l]=this.emitter,e},a.componentWillReceiveProps=function(e){if(this.props.value!==e.value){var n,r=this.props.value,a=e.value;(r===a?0!==r||1/r==1/a:r!=r&&a!=a)?n=0:0!=(n=("function"==typeof t?t(r,a):0x3fffffff)|0)&&this.emitter.set(e.value,n)}},a.render=function(){return this.props.children},n}(a.Component);(n={})[l]=i().object.isRequired,s.childContextTypes=n;var c=function(t){function n(){for(var e,n=arguments.length,r=Array(n),a=0;a<n;a++)r[a]=arguments[a];return(e=t.call.apply(t,[this].concat(r))||this).observedBits=void 0,e.state={value:e.getValue()},e.onUpdate=function(t,n){(e.observedBits&n)!=0&&e.setState({value:e.getValue()})},e}(0,r.A)(n,t);var a=n.prototype;return a.componentWillReceiveProps=function(e){var t=e.observedBits;this.observedBits=null==t?0x3fffffff:t},a.componentDidMount=function(){this.context[l]&&this.context[l].on(this.onUpdate);var e=this.props.observedBits;this.observedBits=null==e?0x3fffffff:e},a.componentWillUnmount=function(){this.context[l]&&this.context[l].off(this.onUpdate)},a.getValue=function(){return this.context[l]?this.context[l].get():e},a.render=function(){var e;return(Array.isArray(e=this.props.children)?e[0]:e)(this.state.value)},n}(a.Component);return(o={})[l]=i().object,c.contextTypes=o,{Provider:s,Consumer:c}},f=function(e){var t=p();return t.displayName=e,t},m=f("Router-History"),g=f("Router"),h=function(e){function t(t){var n;return(n=e.call(this,t)||this).state={location:t.history.location},n._isMounted=!1,n._pendingLocation=null,t.staticContext||(n.unlisten=t.history.listen(function(e){n._pendingLocation=e})),n}(0,r.A)(t,e),t.computeRootMatch=function(e){return{path:"/",url:"/",params:{},isExact:"/"===e}};var n=t.prototype;return n.componentDidMount=function(){var e=this;this._isMounted=!0,this.unlisten&&this.unlisten(),this.props.staticContext||(this.unlisten=this.props.history.listen(function(t){e._isMounted&&e.setState({location:t})})),this._pendingLocation&&this.setState({location:this._pendingLocation})},n.componentWillUnmount=function(){this.unlisten&&(this.unlisten(),this._isMounted=!1,this._pendingLocation=null)},n.render=function(){return a.createElement(g.Provider,{value:{history:this.props.history,location:this.state.location,match:t.computeRootMatch(this.state.location.pathname),staticContext:this.props.staticContext}},a.createElement(m.Provider,{children:this.props.children||null,value:this.props.history}))},t}(a.Component);a.Component,a.Component;var b={},y=0;function v(e,t){void 0===t&&(t={}),("string"==typeof t||Array.isArray(t))&&(t={path:t});var n=t,r=n.path,a=n.exact,o=void 0!==a&&a,i=n.strict,l=void 0!==i&&i,s=n.sensitive,c=void 0!==s&&s;return[].concat(r).reduce(function(t,n){if(!n&&""!==n)return null;if(t)return t;var r=function(e,t){var n=""+t.end+t.strict+t.sensitive,r=b[n]||(b[n]={});if(r[e])return r[e];var a=[],o={regexp:u()(e,a,t),keys:a};return y<1e4&&(r[e]=o,y++),o}(n,{end:o,strict:l,sensitive:c}),a=r.regexp,i=r.keys,s=a.exec(e);if(!s)return null;var d=s[0],p=s.slice(1),f=e===d;return o&&!f?null:{path:n,url:"/"===n&&""===d?"/":d,isExact:f,params:i.reduce(function(e,t,n){return e[t.name]=p[n],e},{})}},null)}var w=function(e){function t(){return e.apply(this,arguments)||this}return(0,r.A)(t,e),t.prototype.render=function(){var e=this;return a.createElement(g.Consumer,null,function(t){t||(0,l.A)(!1);var n,r=e.props.location||t.location,o=e.props.computedMatch?e.props.computedMatch:e.props.path?v(r.pathname,e.props):t.match,i=(0,s.A)({},t,{location:r,match:o}),c=e.props,u=c.children,d=c.component,p=c.render;return Array.isArray(u)&&(n=u,0===a.Children.count(n))&&(u=null),a.createElement(g.Provider,{value:i},i.match?u?"function"==typeof u?u(i):u:d?a.createElement(d,i):p?p(i):null:"function"==typeof u?u(i):null)})},t}(a.Component);a.Component;var k=function(e){function t(){return e.apply(this,arguments)||this}return(0,r.A)(t,e),t.prototype.render=function(){var e=this;return a.createElement(g.Consumer,null,function(t){t||(0,l.A)(!1);var n,r,o=e.props.location||t.location;return a.Children.forEach(e.props.children,function(e){if(null==r&&a.isValidElement(e)){n=e;var i=e.props.path||e.props.from;r=i?v(o.pathname,(0,s.A)({},e.props,{path:i})):t.match}}),r?a.cloneElement(n,{location:o,computedMatch:r}):null})},t}(a.Component),x=a.useContext;function S(){return x(m)}function E(){return x(g).location}},9698(e,t){"use strict";var n=Symbol.for("react.transitional.element");function r(e,t,r){var a=null;if(void 0!==r&&(a=""+r),void 0!==t.key&&(a=""+t.key),"key"in t)for(var o in r={},t)"key"!==o&&(r[o]=t[o]);else r=t;return{$$typeof:n,type:e,key:a,ref:void 0!==(t=r.ref)?t:null,props:r}}t.Fragment=Symbol.for("react.fragment"),t.jsx=r,t.jsxs=r},9869(e,t){"use strict";var n=Symbol.for("react.transitional.element"),r=Symbol.for("react.portal"),a=Symbol.for("react.fragment"),o=Symbol.for("react.strict_mode"),i=Symbol.for("react.profiler"),l=Symbol.for("react.consumer"),s=Symbol.for("react.context"),c=Symbol.for("react.forward_ref"),u=Symbol.for("react.suspense"),d=Symbol.for("react.memo"),p=Symbol.for("react.lazy"),f=Symbol.for("react.activity"),m=Symbol.iterator,g={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},h=Object.assign,b={};function y(e,t,n){this.props=e,this.context=t,this.refs=b,this.updater=n||g}function v(){}function w(e,t,n){this.props=e,this.context=t,this.refs=b,this.updater=n||g}y.prototype.isReactComponent={},y.prototype.setState=function(e,t){if("object"!=typeof e&&"function"!=typeof e&&null!=e)throw Error("takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,e,t,"setState")},y.prototype.forceUpdate=function(e){this.updater.enqueueForceUpdate(this,e,"forceUpdate")},v.prototype=y.prototype;var k=w.prototype=new v;k.constructor=w,h(k,y.prototype),k.isPureReactComponent=!0;var x=Array.isArray;function S(){}var E={H:null,A:null,T:null,S:null},_=Object.prototype.hasOwnProperty;function A(e,t,r){var a=r.ref;return{$$typeof:n,type:e,key:t,ref:void 0!==a?a:null,props:r}}function C(e){return"object"==typeof e&&null!==e&&e.$$typeof===n}var T=/\/+/g;function j(e,t){var n,r;return"object"==typeof e&&null!==e&&null!=e.key?(n=""+e.key,r={"=":"=0",":":"=2"},"$"+n.replace(/[=:]/g,function(e){return r[e]})):t.toString(36)}function P(e,t,a){if(null==e)return e;var o=[],i=0;return!function e(t,a,o,i,l){var s,c,u,d=typeof t;("undefined"===d||"boolean"===d)&&(t=null);var f=!1;if(null===t)f=!0;else switch(d){case"bigint":case"string":case"number":f=!0;break;case"object":switch(t.$$typeof){case n:case r:f=!0;break;case p:return e((f=t._init)(t._payload),a,o,i,l)}}if(f)return l=l(t),f=""===i?"."+j(t,0):i,x(l)?(o="",null!=f&&(o=f.replace(T,"$&/")+"/"),e(l,a,o,"",function(e){return e})):null!=l&&(C(l)&&(s=l,c=o+(null==l.key||t&&t.key===l.key?"":(""+l.key).replace(T,"$&/")+"/")+f,l=A(s.type,c,s.props)),a.push(l)),1;f=0;var g=""===i?".":i+":";if(x(t))for(var h=0;h<t.length;h++)d=g+j(i=t[h],h),f+=e(i,a,o,d,l);else if("function"==typeof(h=null===(u=t)||"object"!=typeof u?null:"function"==typeof(u=m&&u[m]||u["@@iterator"])?u:null))for(t=h.call(t),h=0;!(i=t.next()).done;)d=g+j(i=i.value,h++),f+=e(i,a,o,d,l);else if("object"===d){if("function"==typeof t.then)return e(function(e){switch(e.status){case"fulfilled":return e.value;case"rejected":throw e.reason;default:switch("string"==typeof e.status?e.then(S,S):(e.status="pending",e.then(function(t){"pending"===e.status&&(e.status="fulfilled",e.value=t)},function(t){"pending"===e.status&&(e.status="rejected",e.reason=t)})),e.status){case"fulfilled":return e.value;case"rejected":throw e.reason}}throw e}(t),a,o,i,l);throw Error("Objects are not valid as a React child (found: "+("[object Object]"===(a=String(t))?"object with keys {"+Object.keys(t).join(", ")+"}":a)+"). If you meant to render a collection of children, use an array instead.")}return f}(e,o,"","",function(e){return t.call(a,e,i++)}),o}function z(e){if(-1===e._status){var t=e._result;(t=t()).then(function(t){(0===e._status||-1===e._status)&&(e._status=1,e._result=t)},function(t){(0===e._status||-1===e._status)&&(e._status=2,e._result=t)}),-1===e._status&&(e._status=0,e._result=t)}if(1===e._status)return e._result.default;throw e._result}var L="function"==typeof reportError?reportError:function(e){if("object"==typeof window&&"function"==typeof window.ErrorEvent){var t=new window.ErrorEvent("error",{bubbles:!0,cancelable:!0,message:"object"==typeof e&&null!==e&&"string"==typeof e.message?String(e.message):String(e),error:e});if(!window.dispatchEvent(t))return}else if("object"==typeof process&&"function"==typeof process.emit)return void process.emit("uncaughtException",e);console.error(e)};t.Activity=f,t.Children={map:P,forEach:function(e,t,n){P(e,function(){t.apply(this,arguments)},n)},count:function(e){var t=0;return P(e,function(){t++}),t},toArray:function(e){return P(e,function(e){return e})||[]},only:function(e){if(!C(e))throw Error("React.Children.only expected to receive a single React element child.");return e}},t.Component=y,t.Fragment=a,t.Profiler=i,t.PureComponent=w,t.StrictMode=o,t.Suspense=u,t.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE=E,t.__COMPILER_RUNTIME={__proto__:null,c:function(e){return E.H.useMemoCache(e)}},t.cache=function(e){return function(){return e.apply(null,arguments)}},t.cacheSignal=function(){return null},t.cloneElement=function(e,t,n){if(null==e)throw Error("The argument must be a React element, but you passed "+e+".");var r=h({},e.props),a=e.key;if(null!=t)for(o in void 0!==t.key&&(a=""+t.key),t)_.call(t,o)&&"key"!==o&&"__self"!==o&&"__source"!==o&&("ref"!==o||void 0!==t.ref)&&(r[o]=t[o]);var o=arguments.length-2;if(1===o)r.children=n;else if(1<o){for(var i=Array(o),l=0;l<o;l++)i[l]=arguments[l+2];r.children=i}return A(e.type,a,r)},t.createContext=function(e){return(e={$$typeof:s,_currentValue:e,_currentValue2:e,_threadCount:0,Provider:null,Consumer:null}).Provider=e,e.Consumer={$$typeof:l,_context:e},e},t.createElement=function(e,t,n){var r,a={},o=null;if(null!=t)for(r in void 0!==t.key&&(o=""+t.key),t)_.call(t,r)&&"key"!==r&&"__self"!==r&&"__source"!==r&&(a[r]=t[r]);var i=arguments.length-2;if(1===i)a.children=n;else if(1<i){for(var l=Array(i),s=0;s<i;s++)l[s]=arguments[s+2];a.children=l}if(e&&e.defaultProps)for(r in i=e.defaultProps)void 0===a[r]&&(a[r]=i[r]);return A(e,o,a)},t.createRef=function(){return{current:null}},t.forwardRef=function(e){return{$$typeof:c,render:e}},t.isValidElement=C,t.lazy=function(e){return{$$typeof:p,_payload:{_status:-1,_result:e},_init:z}},t.memo=function(e,t){return{$$typeof:d,type:e,compare:void 0===t?null:t}},t.startTransition=function(e){var t=E.T,n={};E.T=n;try{var r=e(),a=E.S;null!==a&&a(n,r),"object"==typeof r&&null!==r&&"function"==typeof r.then&&r.then(S,L)}catch(e){L(e)}finally{null!==t&&null!==n.types&&(t.types=n.types),E.T=t}},t.unstable_useCacheRefresh=function(){return E.H.useCacheRefresh()},t.use=function(e){return E.H.use(e)},t.useActionState=function(e,t,n){return E.H.useActionState(e,t,n)},t.useCallback=function(e,t){return E.H.useCallback(e,t)},t.useContext=function(e){return E.H.useContext(e)},t.useDebugValue=function(){},t.useDeferredValue=function(e,t){return E.H.useDeferredValue(e,t)},t.useEffect=function(e,t){return E.H.useEffect(e,t)},t.useEffectEvent=function(e){return E.H.useEffectEvent(e)},t.useId=function(){return E.H.useId()},t.useImperativeHandle=function(e,t,n){return E.H.useImperativeHandle(e,t,n)},t.useInsertionEffect=function(e,t){return E.H.useInsertionEffect(e,t)},t.useLayoutEffect=function(e,t){return E.H.useLayoutEffect(e,t)},t.useMemo=function(e,t){return E.H.useMemo(e,t)},t.useOptimistic=function(e,t){return E.H.useOptimistic(e,t)},t.useReducer=function(e,t,n){return E.H.useReducer(e,t,n)},t.useRef=function(e){return E.H.useRef(e)},t.useState=function(e){return E.H.useState(e)},t.useSyncExternalStore=function(e,t,n){return E.H.useSyncExternalStore(e,t,n)},t.useTransition=function(){return E.H.useTransition()},t.version="19.2.7"},6540(e,t,n){"use strict";e.exports=n(9869)},4848(e,t,n){"use strict";e.exports=n(9698)},4477(e,t){"use strict";function n(e,t){var n=e.length;for(e.push(t);0<n;){var r=n-1>>>1,a=e[r];if(0<o(a,t))e[r]=t,e[n]=a,n=r;else break}}function r(e){return 0===e.length?null:e[0]}function a(e){if(0===e.length)return null;var t=e[0],n=e.pop();if(n!==t){e[0]=n;for(var r=0,a=e.length,i=a>>>1;r<i;){var l=2*(r+1)-1,s=e[l],c=l+1,u=e[c];if(0>o(s,n))c<a&&0>o(u,s)?(e[r]=u,e[c]=n,r=c):(e[r]=s,e[l]=n,r=l);else if(c<a&&0>o(u,n))e[r]=u,e[c]=n,r=c;else break}}return t}function o(e,t){var n=e.sortIndex-t.sortIndex;return 0!==n?n:e.id-t.id}if(t.unstable_now=void 0,"object"==typeof performance&&"function"==typeof performance.now){var i,l=performance;t.unstable_now=function(){return l.now()}}else{var s=Date,c=s.now();t.unstable_now=function(){return s.now()-c}}var u=[],d=[],p=1,f=null,m=3,g=!1,h=!1,b=!1,y=!1,v="function"==typeof setTimeout?setTimeout:null,w="function"==typeof clearTimeout?clearTimeout:null,k="u">typeof setImmediate?setImmediate:null;function x(e){for(var t=r(d);null!==t;){if(null===t.callback)a(d);else if(t.startTime<=e)a(d),t.sortIndex=t.expirationTime,n(u,t);else break;t=r(d)}}function S(e){if(b=!1,x(e),!h)if(null!==r(u))h=!0,E||(E=!0,i());else{var t=r(d);null!==t&&L(S,t.startTime-e)}}var E=!1,_=-1,A=5,C=-1;function T(){return!!y||!(t.unstable_now()-C<A)}function j(){if(y=!1,E){var e=t.unstable_now();C=e;var n=!0;try{e:{h=!1,b&&(b=!1,w(_),_=-1),g=!0;var o=m;try{t:{for(x(e),f=r(u);null!==f&&!(f.expirationTime>e&&T());){var l=f.callback;if("function"==typeof l){f.callback=null,m=f.priorityLevel;var s=l(f.expirationTime<=e);if(e=t.unstable_now(),"function"==typeof s){f.callback=s,x(e),n=!0;break t}f===r(u)&&a(u),x(e)}else a(u);f=r(u)}if(null!==f)n=!0;else{var c=r(d);null!==c&&L(S,c.startTime-e),n=!1}}break e}finally{f=null,m=o,g=!1}}}finally{n?i():E=!1}}}if("function"==typeof k)i=function(){k(j)};else if("u">typeof MessageChannel){var P=new MessageChannel,z=P.port2;P.port1.onmessage=j,i=function(){z.postMessage(null)}}else i=function(){v(j,0)};function L(e,n){_=v(function(){e(t.unstable_now())},n)}t.unstable_IdlePriority=5,t.unstable_ImmediatePriority=1,t.unstable_LowPriority=4,t.unstable_NormalPriority=3,t.unstable_Profiling=null,t.unstable_UserBlockingPriority=2,t.unstable_cancelCallback=function(e){e.callback=null},t.unstable_forceFrameRate=function(e){0>e||125<e?console.error("forceFrameRate takes a positive int between 0 and 125, forcing frame rates higher than 125 fps is not supported"):A=0<e?Math.floor(1e3/e):5},t.unstable_getCurrentPriorityLevel=function(){return m},t.unstable_next=function(e){switch(m){case 1:case 2:case 3:var t=3;break;default:t=m}var n=m;m=t;try{return e()}finally{m=n}},t.unstable_requestPaint=function(){y=!0},t.unstable_runWithPriority=function(e,t){switch(e){case 1:case 2:case 3:case 4:case 5:break;default:e=3}var n=m;m=e;try{return t()}finally{m=n}},t.unstable_scheduleCallback=function(e,a,o){var l=t.unstable_now();switch(o="object"==typeof o&&null!==o&&"number"==typeof(o=o.delay)&&0<o?l+o:l,e){case 1:var s=-1;break;case 2:s=250;break;case 5:s=0x3fffffff;break;case 4:s=1e4;break;default:s=5e3}return s=o+s,e={id:p++,callback:a,priorityLevel:e,startTime:o,expirationTime:s,sortIndex:-1},o>l?(e.sortIndex=o,n(d,e),null===r(u)&&e===r(d)&&(b?(w(_),_=-1):b=!0,L(S,o-l))):(e.sortIndex=s,n(u,e),h||g||(h=!0,E||(E=!0,i()))),e},t.unstable_shouldYield=T,t.unstable_wrapCallback=function(e){var t=m;return function(){var n=m;m=t;try{return e.apply(this,arguments)}finally{m=n}}}},9982(e,t,n){"use strict";e.exports=n(4477)},2833(e){e.exports=function(e,t,n,r){var a=n?n.call(r,e,t):void 0;if(void 0!==a)return!!a;if(e===t)return!0;if("object"!=typeof e||!e||"object"!=typeof t||!t)return!1;var o=Object.keys(e),i=Object.keys(t);if(o.length!==i.length)return!1;for(var l=Object.prototype.hasOwnProperty.bind(t),s=0;s<o.length;s++){var c=o[s];if(!l(c))return!1;var u=e[c],d=t[c];if(!1===(a=n?n.call(r,u,d,c):void 0)||void 0===a&&u!==d)return!1}return!0}},8460(e,t,n){"use strict";n.d(t,{A:()=>p}),n(6540);var r=n(4848),a=n(3259),o=n.n(a),i=n(4054);let l={"00254a8b":[()=>n.e("1492").then(n.bind(n,6920)),"@site/docs/messaging/concepts/understand-peer-discovery/about-dns-discovery.md",6920],"04e28314":[()=>n.e("4595").then(n.bind(n,8054)),"@site/docs/lez/transfer-tokens/create-and-transfer-custom-tokens-on-the-logos-execution-zone.md",8054],"0ac18de3":[()=>n.e("5803").then(n.bind(n,6378)),"@site/docs/lez/get-started/initiate-native-token-transfers-on-lez-with-wallet-ui.md",6378],"0e1b61e3":[()=>n.e("1581").then(n.bind(n,6765)),"@site/docs/get-started/introduction-to-logos.md",6765],"102caa12":[()=>n.e("5539").then(n.bind(n,3078)),"@site/docs/peer-discovery/welcome.md",3078],"1132b708":[()=>n.e("1276").then(n.bind(n,6767)),"@site/docs/messaging/concepts/about-content-topics.md",6767],"14eb3368":[()=>Promise.all([n.e("4014"),n.e("7234")]).then(n.bind(n,4922)),"@theme/DocCategoryGeneratedIndexPage",4922],"15d6e2f3":[()=>n.e("9460").then(n.bind(n,6568)),"@site/docs/lez/transfer-tokens/transfer-native-lez-tokens-between-public-private-states.md",6568],0x11113f9:[()=>Promise.all([n.e("4014"),n.e("106")]).then(n.bind(n,2467)),"@theme/DocItem",2467],"19597d10":[()=>n.e("7022").then(n.bind(n,1116)),"@site/docs/blockchain/node-app/claim-leader-rewards-in-logos-blockchain-ui-app.md",1116],"1995774a":[()=>n.e("3109").then(n.bind(n,9737)),"@site/docs/blockchain/concepts/about-zones.md",9737],"1df93b7f":[()=>Promise.all([n.e("4014"),n.e("9452")]).then(n.bind(n,5741)),"@site/src/pages/index.tsx",5741],"22dd74f7":[()=>n.e("1924").then(n.t.bind(n,5226,19)),"@generated/docusaurus-plugin-content-docs/default/p/index-466.json",5226],"25c725b0":[()=>n.e("7868").then(n.bind(n,1156)),"@site/docs/contribute/welcome.md",1156],"2a349706":[()=>n.e("5980").then(n.bind(n,9278)),"@site/docs/lez/follow-chain/run-lez-indexer.md",9278],"2c805987":[()=>n.e("256").then(n.bind(n,3769)),"@site/docs/messaging/concepts/understand-logos-delivery-protocols.md",3769],"2d108750":[()=>n.e("8327").then(n.bind(n,8611)),"@site/docs/blockchain/node-app/build-and-run-logos-blockchain-node-app-ui.md",8611],"2d9cc0e4":[()=>n.e("3684").then(n.bind(n,5956)),"@site/docs/blockchain/concepts/about-mantle.md",5956],"33fcb833":[()=>n.e("3029").then(n.bind(n,2174)),"@site/docs/blockchain/concepts/about-cryptarchia.md",2174],"3e6f6f2a":[()=>n.e("2997").then(n.bind(n,9026)),"@site/docs/messaging/concepts/learn-the-differences-between-logos-delivery-and-libp2p.md",9026],"470027ea":[()=>n.e("7348").then(n.bind(n,8446)),"@site/docs/blockchain/get-started/run-a-logos-blockchain-node-from-cli.md",8446],"49b24bc7":[()=>n.e("3523").then(n.bind(n,238)),"@site/docs/messaging/concepts/understand-peer-discovery/about-discv5.md",238],"589ec14f":[()=>n.e("4745").then(n.bind(n,543)),"@site/docs/core/build-modules/build-logos-core-module-that-uses-service-discovery-api.md",543],"5a6d5130":[()=>n.e("2573").then(n.bind(n,4933)),"@site/docs/lez/follow-chain/start-and-use-instance-of-lez-explorer.md",4933],"5b9d1339":[()=>n.e("4462").then(n.bind(n,3080)),"@site/docs/build-an-app/tutorials/build-dapp-with-logos-zone-sdk.md",3080],"5e7d7155":[()=>n.e("6511").then(n.bind(n,1182)),"@site/docs/blockchain/concepts/about-bedrock.md",1182],"5e95c892":[()=>n.e("1668").then(n.bind(n,6569)),"@theme/DocsRoot",6569],"5e9f5e1a":[()=>Promise.resolve().then(n.bind(n,4784)),"@generated/docusaurus.config",4784],"638fbe8d":[()=>n.e("2256").then(n.t.bind(n,5244,19)),"@generated/docusaurus-plugin-content-docs/default/p/build-an-app-284.json",5244],"669488b6":[()=>n.e("7009").then(n.bind(n,2628)),"@site/docs/blockchain/get-started/run-a-logos-blockchain-node-from-basecamp.md",2628],"68fc9db9":[()=>n.e("8753").then(n.bind(n,6837)),"@site/docs/messaging/concepts/about-network-domains.md",6837],"6f4e7109":[()=>n.e("6524").then(n.bind(n,9106)),"@site/docs/blockchain/node-app/move-assets-using-logos-blockchain-app.md",9106],"6fc42ee4":[()=>n.e("15").then(n.bind(n,2896)),"@site/docs/messaging/chat-module/send-anonymised-messages-over-the-mix-network.md",2896],"7057f613":[()=>n.e("5479").then(n.bind(n,3376)),"@site/docs/messaging/get-started/send-1-1-messages-logos-chat.md",3376],"718f62c6":[()=>n.e("5367").then(n.bind(n,3965)),"@site/docs/messaging/concepts/understand-peer-discovery/about-peer-exchange.md",3965],"808de28f":[()=>n.e("5281").then(n.bind(n,44)),"@site/docs/messaging/concepts/understand-peer-discovery/understand-peer-discovery.md",44],"83f53bbb":[()=>n.e("593").then(n.bind(n,1483)),"@site/docs/core/build-modules/start-a-logos-module-from-the-cli.md",1483],"862b4f69":[()=>n.e("1701").then(n.bind(n,2337)),"@site/docs/get-started/logos-ecosystem-repositories.md",2337],"867de415":[()=>n.e("5286").then(n.bind(n,86)),"@site/docs/messaging/get-started/faq-logos-messaging.md",86],"8a80b850":[()=>n.e("5052").then(n.bind(n,3343)),"@site/docs/storage/get-started/run-logos-storage-node.md",3343],"8e906247":[()=>n.e("8847").then(n.bind(n,1310)),"@site/docs/basecamp/get-started/install-logos-basecamp.md",1310],"9003ff05":[()=>n.e("6111").then(n.bind(n,1569)),"@site/docs/mixnet/get-started/discover-nodes-and-send-messages-via-the-anoncomms-mixnet-demo-app.md",1569],"93168d98":[()=>n.e("9034").then(n.bind(n,6492)),"@site/docs/lez/get-started/introduction-to-the-logos-execution-zone.md",6492],0x5c4f887:[()=>n.e("9112").then(n.bind(n,1801)),"@site/docs/storage/get-started/set-up-and-use-logos-storage-ui.md",1801],"9e12ce96":[()=>n.e("8894").then(n.bind(n,1167)),"@site/docs/core/build-modules/build-a-logos-cpp-ui-module.md",1167],a1c727d1:[()=>n.e("7680").then(n.bind(n,814)),"@site/docs/core/build-modules/wrap-a-c-library-as-a-logos-core-module.md",814],a239bc72:[()=>n.e("3245").then(n.bind(n,4682)),"@site/docs/core/reference/logos-cli-reference.md",4682],a7456010:[()=>n.e("3056").then(n.t.bind(n,8552,19)),"@generated/docusaurus-plugin-content-pages/default/__plugin.json",8552],a7bd4aaa:[()=>n.e("5575").then(n.bind(n,8146)),"@theme/DocVersionRoot",8146],a8fa9c9c:[()=>n.e("6620").then(n.bind(n,3842)),"@site/docs/lez/get-started/connect-wallet-cli-to-lez-testnet.md",3842],a94703ab:[()=>Promise.all([n.e("4014"),n.e("6785")]).then(n.bind(n,1399)),"@theme/DocRoot",1399],aba21aa0:[()=>n.e("3747").then(n.t.bind(n,7093,19)),"@generated/docusaurus-plugin-content-docs/default/__plugin.json",7093],aba21bf3:[()=>n.e("3264").then(n.bind(n,771)),"@site/docs/core/build-modules/install-and-load-a-module-in-logos-basecamp.md",771],b00cc44c:[()=>n.e("8881").then(n.bind(n,744)),"@site/docs/blockchain/concepts/about-the-blend-network.md",744],b4479ab8:[()=>n.e("6663").then(n.bind(n,3064)),"@site/docs/lez/transfer-tokens/transfer-native-tokens-on-the-logos-execution-zone.md",3064],b9ebd1fd:[()=>n.e("3319").then(n.bind(n,5790)),"@site/docs/messaging/concepts/understand-logos-delivery-security-features.md",5790],ba594660:[()=>n.e("1951").then(n.bind(n,1042)),"@site/docs/messaging/get-started/introduction-to-logos-messaging.md",1042],bb3f60c2:[()=>n.e("4410").then(n.bind(n,3190)),"@site/docs/blockchain/zone-sdk/bridge-assets-between-blockchain-and-zone.md",3190],bcd62c17:[()=>n.e("266").then(n.bind(n,2029)),"@site/docs/lez/get-started/quickstart-for-the-logos-execution-zone-wallet.md",2029],bcf9457e:[()=>n.e("3557").then(n.bind(n,470)),"@site/docs/get-started/glossary.md",470],bf77b29a:[()=>n.e("6400").then(n.bind(n,6812)),"@site/docs/blockchain/blend/join-the-blend-network-as-a-core-node.md",6812],bfa2415e:[()=>n.e("3510").then(n.bind(n,9710)),"@site/docs/messaging/chat-module/build-logos-module-that-uses-chat-module-api.md",9710],c1de2c67:[()=>n.e("2299").then(n.bind(n,9469)),"@site/docs/lez/accounts/set-up-shared-private-lez-account.md",9469],c634495e:[()=>n.e("6723").then(n.bind(n,3954)),"@site/docs/blockchain/node-app/bridge-assets-from-logos-blockchain-to-zone-using-app.md",3954],c7d7a685:[()=>n.e("2615").then(n.bind(n,3292)),"@site/docs/blockchain/get-started/introduction-to-the-logos-blockchain.md",3292],c96c9452:[()=>n.e("6221").then(n.bind(n,3417)),"@site/docs/messaging/delivery/run-logos-delivery-node.md",3417],d83083f1:[()=>n.e("4719").then(n.bind(n,105)),"@site/docs/messaging/delivery/use-logos-delivery-module-api-from-app.md",105],dbf4bdd3:[()=>n.e("4903").then(n.bind(n,112)),"@site/docs/build-an-app/tutorials/create-and-use-an-amm-liquidity-pool-on-the-logos-execution-zone.md",112],e98c3ec0:[()=>n.e("1836").then(n.bind(n,5985)),"@site/docs/run-a-node/get-started/run-logos-node-blockchain-storage-delivery.md",5985],ea2b553e:[()=>n.e("4164").then(n.bind(n,4909)),"@site/docs/messaging/concepts/understand-peer-discovery/about-static-peers.md",4909],ea6a119a:[()=>n.e("8087").then(n.bind(n,3805)),"@site/docs/blockchain/zone-sdk/inscribe-data-on-chain-using-zone-sdk.md",3805],eaab68dc:[()=>n.e("8546").then(n.bind(n,9300)),"@site/docs/blockchain/zone-sdk/operate-decentralised-zone-with-round-robin-sequencer-rotation.md",9300],f3eca8a6:[()=>n.e("2693").then(n.bind(n,4758)),"@site/docs/messaging/concepts/about-transports.md",4758],f494eaf6:[()=>n.e("6153").then(n.bind(n,6291)),"@site/docs/core/reference/lgx-package-format-and-bundling-reference.md",6291],f911e5e7:[()=>n.e("5397").then(n.t.bind(n,5530,19)),"@generated/docusaurus-plugin-content-docs/default/p/run-an-app-140.json",5530],fdf0ae8c:[()=>n.e("2307").then(n.bind(n,6649)),"@site/docs/core/build-modules/build-and-run-a-logos-core-module.md",6649]};function s({error:e,retry:t,pastDelay:n}){return e?(0,r.jsxs)("div",{style:{textAlign:"center",color:"#fff",backgroundColor:"#fa383e",borderColor:"#fa383e",borderStyle:"solid",borderRadius:"0.25rem",borderWidth:"1px",boxSizing:"border-box",display:"block",padding:"1rem",flex:"0 0 50%",marginLeft:"25%",marginRight:"25%",marginTop:"5rem",maxWidth:"50%",width:"100%"},children:[(0,r.jsx)("p",{children:String(e)}),(0,r.jsx)("div",{children:(0,r.jsx)("button",{type:"button",onClick:t,children:"Retry"})})]}):n?(0,r.jsx)("div",{style:{display:"flex",justifyContent:"center",alignItems:"center",height:"100vh"},children:(0,r.jsx)("svg",{id:"loader",style:{width:128,height:110,position:"absolute",top:"calc(100vh - 64%)"},viewBox:"0 0 45 45",xmlns:"http://www.w3.org/2000/svg",stroke:"#61dafb",children:(0,r.jsxs)("g",{fill:"none",fillRule:"evenodd",transform:"translate(1 1)",strokeWidth:"2",children:[(0,r.jsxs)("circle",{cx:"22",cy:"22",r:"6",strokeOpacity:"0",children:[(0,r.jsx)("animate",{attributeName:"r",begin:"1.5s",dur:"3s",values:"6;22",calcMode:"linear",repeatCount:"indefinite"}),(0,r.jsx)("animate",{attributeName:"stroke-opacity",begin:"1.5s",dur:"3s",values:"1;0",calcMode:"linear",repeatCount:"indefinite"}),(0,r.jsx)("animate",{attributeName:"stroke-width",begin:"1.5s",dur:"3s",values:"2;0",calcMode:"linear",repeatCount:"indefinite"})]}),(0,r.jsxs)("circle",{cx:"22",cy:"22",r:"6",strokeOpacity:"0",children:[(0,r.jsx)("animate",{attributeName:"r",begin:"3s",dur:"3s",values:"6;22",calcMode:"linear",repeatCount:"indefinite"}),(0,r.jsx)("animate",{attributeName:"stroke-opacity",begin:"3s",dur:"3s",values:"1;0",calcMode:"linear",repeatCount:"indefinite"}),(0,r.jsx)("animate",{attributeName:"stroke-width",begin:"3s",dur:"3s",values:"2;0",calcMode:"linear",repeatCount:"indefinite"})]}),(0,r.jsx)("circle",{cx:"22",cy:"22",r:"8",children:(0,r.jsx)("animate",{attributeName:"r",begin:"0s",dur:"1.5s",values:"6;1;2;3;4;5;6",calcMode:"linear",repeatCount:"indefinite"})})]})})}):null}var c=n(7665),u=n(7574);function d(e,t){if("*"===e)return o()({loading:s,loader:()=>n.e("5062").then(n.bind(n,629)),modules:["@theme/NotFound"],webpack:()=>[629],render(e,t){let n=e.default;return(0,r.jsx)(u.W,{value:{plugin:{name:"native",id:"default"}},children:(0,r.jsx)(n,{...t})})}});let a=i[`${e}-${t}`],d={},p=[],f=[];return Object.entries((0,c.A)(a)).forEach(([e,t])=>{let n=l[t];n&&(d[e]=n[0],p.push(n[1]),f.push(n[2]))}),o().Map({loading:s,loader:d,modules:p,webpack:()=>f,render(t,n){let o=JSON.parse(JSON.stringify(a));Object.entries(t).forEach(([t,n])=>{let r=n.default;if(!r)throw Error(`The page component at ${e} doesn't have a default export. This makes it impossible to render anything. Consider default-exporting a React component.`);("object"==typeof r||"function"==typeof r)&&Object.keys(n).filter(e=>"default"!==e).forEach(e=>{r[e]=n[e]});let a=o,i=t.split(".");i.slice(0,-1).forEach(e=>{a=a[e]}),a[i[i.length-1]]=r});let i=o.__comp;delete o.__comp;let l=o.__context;delete o.__context;let s=o.__props;return delete o.__props,(0,r.jsx)(u.W,{value:l,children:(0,r.jsx)(i,{...o,...s,...n})})}})}let p=[{path:"/",component:d("/","e5f"),exact:!0},{path:"/",component:d("/","054"),routes:[{path:"/",component:d("/","0b9"),routes:[{path:"/",component:d("/","5ef"),routes:[{path:"/basecamp/get-started/install-logos-basecamp",component:d("/basecamp/get-started/install-logos-basecamp","97d"),exact:!0,sidebar:"basecampSidebar"},{path:"/blockchain/blend/join-the-blend-network-as-a-core-node",component:d("/blockchain/blend/join-the-blend-network-as-a-core-node","955"),exact:!0,sidebar:"blockchainSidebar"},{path:"/blockchain/concepts/about-bedrock",component:d("/blockchain/concepts/about-bedrock","272"),exact:!0,sidebar:"blockchainSidebar"},{path:"/blockchain/concepts/about-cryptarchia",component:d("/blockchain/concepts/about-cryptarchia","a25"),exact:!0,sidebar:"blockchainSidebar"},{path:"/blockchain/concepts/about-mantle",component:d("/blockchain/concepts/about-mantle","e7c"),exact:!0,sidebar:"blockchainSidebar"},{path:"/blockchain/concepts/about-the-blend-network",component:d("/blockchain/concepts/about-the-blend-network","589"),exact:!0,sidebar:"blockchainSidebar"},{path:"/blockchain/concepts/about-zones",component:d("/blockchain/concepts/about-zones","ebd"),exact:!0,sidebar:"blockchainSidebar"},{path:"/blockchain/get-started/introduction-to-the-logos-blockchain",component:d("/blockchain/get-started/introduction-to-the-logos-blockchain","536"),exact:!0,sidebar:"blockchainSidebar"},{path:"/blockchain/get-started/run-a-logos-blockchain-node-from-basecamp",component:d("/blockchain/get-started/run-a-logos-blockchain-node-from-basecamp","fc2"),exact:!0,sidebar:"blockchainSidebar"},{path:"/blockchain/get-started/run-a-logos-blockchain-node-from-cli",component:d("/blockchain/get-started/run-a-logos-blockchain-node-from-cli","811"),exact:!0,sidebar:"blockchainSidebar"},{path:"/blockchain/node-app/bridge-assets-from-logos-blockchain-to-zone-using-app",component:d("/blockchain/node-app/bridge-assets-from-logos-blockchain-to-zone-using-app","b99"),exact:!0,sidebar:"blockchainSidebar"},{path:"/blockchain/node-app/build-and-run-logos-blockchain-node-app-ui",component:d("/blockchain/node-app/build-and-run-logos-blockchain-node-app-ui","49b"),exact:!0,sidebar:"blockchainSidebar"},{path:"/blockchain/node-app/claim-leader-rewards-in-logos-blockchain-ui-app",component:d("/blockchain/node-app/claim-leader-rewards-in-logos-blockchain-ui-app","aa6"),exact:!0,sidebar:"blockchainSidebar"},{path:"/blockchain/node-app/move-assets-using-logos-blockchain-app",component:d("/blockchain/node-app/move-assets-using-logos-blockchain-app","c79"),exact:!0,sidebar:"blockchainSidebar"},{path:"/blockchain/zone-sdk/bridge-assets-between-blockchain-and-zone",component:d("/blockchain/zone-sdk/bridge-assets-between-blockchain-and-zone","b3a"),exact:!0,sidebar:"blockchainSidebar"},{path:"/blockchain/zone-sdk/inscribe-data-on-chain-using-zone-sdk",component:d("/blockchain/zone-sdk/inscribe-data-on-chain-using-zone-sdk","c47"),exact:!0,sidebar:"blockchainSidebar"},{path:"/blockchain/zone-sdk/operate-decentralised-zone-with-round-robin-sequencer-rotation",component:d("/blockchain/zone-sdk/operate-decentralised-zone-with-round-robin-sequencer-rotation","91b"),exact:!0,sidebar:"blockchainSidebar"},{path:"/build-an-app",component:d("/build-an-app","473"),exact:!0,sidebar:"buildAnAppSidebar"},{path:"/build-an-app/tutorials/build-dapp-with-logos-zone-sdk",component:d("/build-an-app/tutorials/build-dapp-with-logos-zone-sdk","5df"),exact:!0,sidebar:"buildAnAppSidebar"},{path:"/build-an-app/tutorials/create-and-use-an-amm-liquidity-pool-on-the-logos-execution-zone",component:d("/build-an-app/tutorials/create-and-use-an-amm-liquidity-pool-on-the-logos-execution-zone","45c"),exact:!0,sidebar:"buildAnAppSidebar"},{path:"/contribute/welcome",component:d("/contribute/welcome","9fe"),exact:!0,sidebar:"contributeSidebar"},{path:"/core/build-modules/build-a-logos-cpp-ui-module",component:d("/core/build-modules/build-a-logos-cpp-ui-module","715"),exact:!0,sidebar:"coreSidebar"},{path:"/core/build-modules/build-and-run-a-logos-core-module",component:d("/core/build-modules/build-and-run-a-logos-core-module","6b9"),exact:!0,sidebar:"coreSidebar"},{path:"/core/build-modules/build-logos-core-module-that-uses-service-discovery-api",component:d("/core/build-modules/build-logos-core-module-that-uses-service-discovery-api","468"),exact:!0,sidebar:"coreSidebar"},{path:"/core/build-modules/install-and-load-a-module-in-logos-basecamp",component:d("/core/build-modules/install-and-load-a-module-in-logos-basecamp","2dd"),exact:!0,sidebar:"coreSidebar"},{path:"/core/build-modules/start-a-logos-module-from-the-cli",component:d("/core/build-modules/start-a-logos-module-from-the-cli","528"),exact:!0,sidebar:"coreSidebar"},{path:"/core/build-modules/wrap-a-c-library-as-a-logos-core-module",component:d("/core/build-modules/wrap-a-c-library-as-a-logos-core-module","264"),exact:!0,sidebar:"coreSidebar"},{path:"/core/reference/lgx-package-format-and-bundling-reference",component:d("/core/reference/lgx-package-format-and-bundling-reference","dd1"),exact:!0,sidebar:"coreSidebar"},{path:"/core/reference/logos-cli-reference",component:d("/core/reference/logos-cli-reference","791"),exact:!0,sidebar:"coreSidebar"},{path:"/get-started/glossary",component:d("/get-started/glossary","349"),exact:!0,sidebar:"getStartedSidebar"},{path:"/get-started/logos-ecosystem-repositories",component:d("/get-started/logos-ecosystem-repositories","a73"),exact:!0,sidebar:"getStartedSidebar"},{path:"/get-started/what-is-logos",component:d("/get-started/what-is-logos","3bc"),exact:!0,sidebar:"getStartedSidebar"},{path:"/lez/accounts/set-up-shared-private-lez-account",component:d("/lez/accounts/set-up-shared-private-lez-account","9c1"),exact:!0,sidebar:"lezSidebar"},{path:"/lez/follow-chain/run-lez-indexer",component:d("/lez/follow-chain/run-lez-indexer","386"),exact:!0,sidebar:"lezSidebar"},{path:"/lez/follow-chain/start-and-use-instance-of-lez-explorer",component:d("/lez/follow-chain/start-and-use-instance-of-lez-explorer","3be"),exact:!0,sidebar:"lezSidebar"},{path:"/lez/get-started/connect-wallet-cli-to-lez-testnet",component:d("/lez/get-started/connect-wallet-cli-to-lez-testnet","663"),exact:!0,sidebar:"lezSidebar"},{path:"/lez/get-started/initiate-native-token-transfers-on-lez-with-wallet-ui",component:d("/lez/get-started/initiate-native-token-transfers-on-lez-with-wallet-ui","be2"),exact:!0,sidebar:"lezSidebar"},{path:"/lez/get-started/introduction-to-the-logos-execution-zone",component:d("/lez/get-started/introduction-to-the-logos-execution-zone","bf9"),exact:!0,sidebar:"lezSidebar"},{path:"/lez/get-started/lez-quickstart",component:d("/lez/get-started/lez-quickstart","f7e"),exact:!0,sidebar:"lezSidebar"},{path:"/lez/transfer-tokens/create-and-transfer-custom-tokens-on-the-logos-execution-zone",component:d("/lez/transfer-tokens/create-and-transfer-custom-tokens-on-the-logos-execution-zone","e58"),exact:!0,sidebar:"lezSidebar"},{path:"/lez/transfer-tokens/transfer-native-lez-tokens-between-public-private-states",component:d("/lez/transfer-tokens/transfer-native-lez-tokens-between-public-private-states","070"),exact:!0,sidebar:"lezSidebar"},{path:"/lez/transfer-tokens/transfer-native-tokens-on-the-logos-execution-zone",component:d("/lez/transfer-tokens/transfer-native-tokens-on-the-logos-execution-zone","559"),exact:!0,sidebar:"lezSidebar"},{path:"/messaging/chat-module/build-logos-module-that-uses-chat-module-api",component:d("/messaging/chat-module/build-logos-module-that-uses-chat-module-api","87e"),exact:!0,sidebar:"messagingSidebar"},{path:"/messaging/chat-module/send-anonymised-messages-over-the-mix-network",component:d("/messaging/chat-module/send-anonymised-messages-over-the-mix-network","85d"),exact:!0,sidebar:"messagingSidebar"},{path:"/messaging/concepts/content-topics",component:d("/messaging/concepts/content-topics","80c"),exact:!0,sidebar:"messagingSidebar"},{path:"/messaging/concepts/logos-delivery-and-libp2p",component:d("/messaging/concepts/logos-delivery-and-libp2p","fbc"),exact:!0,sidebar:"messagingSidebar"},{path:"/messaging/concepts/network-domains",component:d("/messaging/concepts/network-domains","522"),exact:!0,sidebar:"messagingSidebar"},{path:"/messaging/concepts/protocols",component:d("/messaging/concepts/protocols","3b8"),exact:!0,sidebar:"messagingSidebar"},{path:"/messaging/concepts/security-features",component:d("/messaging/concepts/security-features","375"),exact:!0,sidebar:"messagingSidebar"},{path:"/messaging/concepts/transports",component:d("/messaging/concepts/transports","c58"),exact:!0,sidebar:"messagingSidebar"},{path:"/messaging/concepts/understand-peer-discovery/discv5",component:d("/messaging/concepts/understand-peer-discovery/discv5","b8f"),exact:!0,sidebar:"messagingSidebar"},{path:"/messaging/concepts/understand-peer-discovery/dns-discovery",component:d("/messaging/concepts/understand-peer-discovery/dns-discovery","86e"),exact:!0,sidebar:"messagingSidebar"},{path:"/messaging/concepts/understand-peer-discovery/peer-discovery",component:d("/messaging/concepts/understand-peer-discovery/peer-discovery","a03"),exact:!0,sidebar:"messagingSidebar"},{path:"/messaging/concepts/understand-peer-discovery/peer-exchange",component:d("/messaging/concepts/understand-peer-discovery/peer-exchange","2cc"),exact:!0,sidebar:"messagingSidebar"},{path:"/messaging/concepts/understand-peer-discovery/static-peers",component:d("/messaging/concepts/understand-peer-discovery/static-peers","216"),exact:!0,sidebar:"messagingSidebar"},{path:"/messaging/delivery/run-logos-delivery-node",component:d("/messaging/delivery/run-logos-delivery-node","da8"),exact:!0,sidebar:"messagingSidebar"},{path:"/messaging/delivery/use-logos-delivery-module-api-from-app",component:d("/messaging/delivery/use-logos-delivery-module-api-from-app","53c"),exact:!0,sidebar:"messagingSidebar"},{path:"/messaging/get-started/faq",component:d("/messaging/get-started/faq","dca"),exact:!0,sidebar:"messagingSidebar"},{path:"/messaging/get-started/introduction-to-logos-messaging",component:d("/messaging/get-started/introduction-to-logos-messaging","29a"),exact:!0,sidebar:"messagingSidebar"},{path:"/messaging/get-started/send-1-1-messages-logos-chat",component:d("/messaging/get-started/send-1-1-messages-logos-chat","8e4"),exact:!0,sidebar:"messagingSidebar"},{path:"/mixnet/get-started/discover-nodes-and-send-messages-via-the-anoncomms-mixnet-demo-app",component:d("/mixnet/get-started/discover-nodes-and-send-messages-via-the-anoncomms-mixnet-demo-app","ec6"),exact:!0,sidebar:"mixnetSidebar"},{path:"/peer-discovery/welcome",component:d("/peer-discovery/welcome","0d5"),exact:!0,sidebar:"peerDiscoverySidebar"},{path:"/run-a-node/get-started/run-logos-node-blockchain-storage-delivery",component:d("/run-a-node/get-started/run-logos-node-blockchain-storage-delivery","b0d"),exact:!0,sidebar:"runANodeSidebar"},{path:"/run-an-app",component:d("/run-an-app","74a"),exact:!0,sidebar:"runAnAppSidebar"},{path:"/storage/get-started/run-logos-storage-node",component:d("/storage/get-started/run-logos-storage-node","190"),exact:!0,sidebar:"storageSidebar"},{path:"/storage/get-started/set-up-and-use-logos-storage-ui",component:d("/storage/get-started/set-up-and-use-logos-storage-ui","c77"),exact:!0,sidebar:"storageSidebar"}]}]}]},{path:"*",component:d("*")}]},1173(e,t,n){"use strict";n.d(t,{o:()=>o,x:()=>i});var r=n(4848),a=n(6540);let o=a.createContext(!1);function i({children:e}){let[t,n]=(0,a.useState)(!1);return(0,a.useEffect)(()=>{n(!0)},[]),(0,r.jsx)(o.Provider,{value:t,children:e})}},354(e,t,n){"use strict";var r=n(4848),a=n(6540),o=n(5338),i=n(545),l=n(4625),s=n(4784),c=n(1289);let u=[n(7612),n(6464),n(2489),n(3678),n(6448)];var d=n(8460),p=n(6347),f=n(2831);function m({children:e}){return(0,r.jsx)(r.Fragment,{children:e})}var g=n(3899);let h=e=>e.defaultFormatter(e);function b({children:e}){return(0,r.jsx)(g.AL,{formatter:h,children:e})}function y({children:e}){return(0,r.jsx)(b,{children:e})}var v=n(3572),w=n(898),k=n(6497),x=n(1022),S=n(4308),E=n(5243),_=n(5248),A=n(4175);function C(){let{i18n:{currentLocale:e,defaultLocale:t,localeConfigs:n}}=(0,w.A)(),a=(0,E.o)(),o=n[e].htmlLang;return(0,r.jsxs)(v.A,{children:[Object.entries(n).map(([e,{htmlLang:t}])=>(0,r.jsx)("link",{rel:"alternate",href:a.createUrl({locale:e,fullyQualified:!0}),hrefLang:t},e)),(0,r.jsx)("link",{rel:"alternate",href:a.createUrl({locale:t,fullyQualified:!0}),hrefLang:"x-default"}),(0,r.jsx)("meta",{property:"og:locale",content:o.replace("-","_")}),Object.values(n).filter(e=>o!==e.htmlLang).map(e=>(0,r.jsx)("meta",{property:"og:locale:alternate",content:e.htmlLang.replace("-","_")},`meta-og-${e.htmlLang}`))]})}function T({permalink:e}){let{siteConfig:{url:t}}=(0,w.A)(),n=function(){let{siteConfig:{url:e,baseUrl:t,trailingSlash:n}}=(0,w.A)(),{pathname:r}=(0,p.zy)();return e+(0,_.applyTrailingSlash)((0,k.Ay)(r),{trailingSlash:n,baseUrl:t})}(),a=e?`${t}${e}`:n;return(0,r.jsxs)(v.A,{children:[(0,r.jsx)("meta",{property:"og:url",content:a}),(0,r.jsx)("link",{rel:"canonical",href:a})]})}function j(){let{i18n:{currentLocale:e}}=(0,w.A)(),{metadata:t,image:n}=(0,x.p)();return(0,r.jsxs)(r.Fragment,{children:[(0,r.jsxs)(v.A,{children:[(0,r.jsx)("meta",{name:"twitter:card",content:"summary_large_image"}),(0,r.jsx)("body",{})]}),n&&(0,r.jsx)(S.be,{image:n}),(0,r.jsx)(T,{}),(0,r.jsx)(C,{}),(0,r.jsx)(A.A,{tag:"default",locale:e}),(0,r.jsx)(v.A,{children:t.map((e,t)=>(0,r.jsx)("meta",{...e},t))})]})}let P=new Map;var z=n(1173),L=n(3587),N=n(9989);function O(e,...t){let n=u.map(n=>{let r=n.default?.[e]??n[e];return r?.(...t)});return()=>n.forEach(e=>e?.())}let R=function({children:e,location:t,previousLocation:n}){return(0,N.A)(()=>{n!==t&&(!function({location:e,previousLocation:t}){if(!t)return;let n=e.pathname===t.pathname,r=e.hash===t.hash,a=e.search===t.search;if(n&&r&&!a)return;let{hash:o}=e;if(o){let e=decodeURIComponent(o.substring(1)),t=document.getElementById(e);t?.scrollIntoView()}else window.scrollTo(0,0)}({location:t,previousLocation:n}),O("onRouteDidUpdate",{previousLocation:n,location:t}))},[n,t]),e};function D(e){return Promise.all(Array.from(new Set([e,decodeURI(e)])).map(e=>(0,f.u)(d.A,e)).flat().map(e=>e.route.component.preload?.()))}class F extends a.Component{previousLocation;routeUpdateCleanupCb;constructor(e){super(e),this.previousLocation=null,this.routeUpdateCleanupCb=c.A.canUseDOM?O("onRouteUpdate",{previousLocation:null,location:this.props.location}):()=>{},this.state={nextRouteHasLoaded:!0}}shouldComponentUpdate(e,t){if(e.location===this.props.location)return t.nextRouteHasLoaded;let n=e.location;return this.previousLocation=this.props.location,this.setState({nextRouteHasLoaded:!1}),this.routeUpdateCleanupCb=O("onRouteUpdate",{previousLocation:this.previousLocation,location:n}),D(n.pathname).then(()=>{this.routeUpdateCleanupCb(),this.setState({nextRouteHasLoaded:!0})}).catch(e=>{console.warn(e),window.location.reload()}),!1}render(){let{children:e,location:t}=this.props;return(0,r.jsx)(R,{previousLocation:this.previousLocation,location:t,children:(0,r.jsx)(p.qh,{location:t,render:()=>e})})}}let I="__docusaurus-base-url-issue-banner-suggestion-container";function M(){let{siteConfig:{baseUrl:e}}=(0,w.A)();return(0,r.jsx)(r.Fragment,{children:!c.A.canUseDOM&&(0,r.jsx)(v.A,{children:(0,r.jsx)("script",{children:` +document.addEventListener('DOMContentLoaded', function maybeInsertBanner() { + var shouldInsert = typeof window['docusaurus'] === 'undefined'; + shouldInsert && insertBanner(); +}); + +function insertBanner() { + var bannerContainer = document.createElement('div'); + bannerContainer.id = '__docusaurus-base-url-issue-banner-container'; + var bannerHtml = ${JSON.stringify(` +<div id="__docusaurus-base-url-issue-banner" style="border: thick solid red; background-color: rgb(255, 230, 179); margin: 20px; padding: 20px; font-size: 20px;"> + <p style="font-weight: bold; font-size: 30px;">Your Docusaurus site did not load properly.</p> + <p>A very common reason is a wrong site <a href="https://docusaurus.io/docs/docusaurus.config.js/#baseUrl" style="font-weight: bold;">baseUrl configuration</a>.</p> + <p>Current configured baseUrl = <span style="font-weight: bold; color: red;">${e}</span> ${"/"===e?" (default value)":""}</p> + <p>We suggest trying baseUrl = <span id="${I}" style="font-weight: bold; color: green;"></span></p> +</div> +`).replace(/</g,"\\<")}; + bannerContainer.innerHTML = bannerHtml; + document.body.prepend(bannerContainer); + var suggestionContainer = document.getElementById('${I}'); + var actualHomePagePath = window.location.pathname; + var suggestedBaseUrl = actualHomePagePath.substr(-1) === '/' + ? actualHomePagePath + : actualHomePagePath + '/'; + suggestionContainer.textContent = suggestedBaseUrl; +} +`})})})}function $(){let{siteConfig:{baseUrl:e,baseUrlIssueBanner:t}}=(0,w.A)(),{pathname:n}=(0,p.zy)();return t&&n===e?(0,r.jsx)(M,{}):null}function B(){let{siteConfig:{favicon:e,title:t,noIndex:n},i18n:{currentLocale:a,localeConfigs:o}}=(0,w.A)(),i=(0,k.Ay)(e),{htmlLang:l,direction:s}=o[a];return(0,r.jsxs)(v.A,{children:[(0,r.jsx)("html",{lang:l,dir:s}),(0,r.jsx)("title",{children:t}),(0,r.jsx)("meta",{property:"og:title",content:t}),(0,r.jsx)("meta",{name:"viewport",content:"width=device-width, initial-scale=1.0"}),n&&(0,r.jsx)("meta",{name:"robots",content:"noindex, nofollow"}),e&&(0,r.jsx)("link",{rel:"icon",href:i})]})}var U=n(1701),q=n(9863);function H(){let e=(0,q.A)();return(0,r.jsx)(v.A,{children:(0,r.jsx)("html",{"data-has-hydrated":e})})}let G=(0,f.v)(d.A);function V(){let e=function(e){if(P.has(e.pathname))return{...e,pathname:P.get(e.pathname)};if((0,f.u)(d.A,e.pathname).some(({route:e})=>!0===e.exact))return P.set(e.pathname,e.pathname),e;let t=e.pathname.trim().replace(/(?:\/index)?\.html$/,"")||"/";return P.set(e.pathname,t),{...e,pathname:t}}((0,p.zy)());return(0,r.jsx)(F,{location:e,children:G})}var W=n(4054);let Q=!function(e){try{return document.createElement("link").relList.supports(e)}catch{return!1}}("prefetch")?function(e){return new Promise((t,n)=>{let r=new XMLHttpRequest;r.open("GET",e,!0),r.withCredentials=!0,r.onload=()=>{200===r.status?t():n()},r.send(null)})}:function(e){return new Promise((t,n)=>{if("u"<typeof document)return void n();let r=document.createElement("link");r.setAttribute("rel","prefetch"),r.setAttribute("href",e),r.onload=()=>t(),r.onerror=()=>n();let a=document.getElementsByTagName("head")[0]??document.getElementsByName("script")[0]?.parentNode;a?.appendChild(r)})};var K=n(7665);let Y=new Set,Z=new Set,X=()=>navigator.connection?.effectiveType.includes("2g")||navigator.connection?.saveData,J=Object.freeze({prefetch:e=>!(X()||Z.has(e)||Y.has(e))&&(Y.add(e),Promise.all((0,f.u)(d.A,e).flatMap(e=>{let t;return t=e.route.path,Object.entries(W).filter(([e])=>e.replace(/-[^-]+$/,"")===t).flatMap(([,e])=>Object.values((0,K.A)(e)))}).map(e=>{let t=n.gca(e);return t&&!t.includes("undefined")?Q(t).catch(()=>{}):Promise.resolve()}))),preload:e=>!(X()||Z.has(e))&&(Z.add(e),D(e))});if(c.A.canUseDOM){window.docusaurus=J;let e=document.getElementById("__docusaurus"),t=(0,r.jsx)(i.vd,{children:(0,r.jsx)(function({children:e}){return"hash"===s.default.future.experimental_router?(0,r.jsx)(l.I9,{children:e}):(0,r.jsx)(l.Kd,{children:e})},{children:(0,r.jsx)(function(){return(0,r.jsx)(U.A,{children:(0,r.jsx)(L.l,{children:(0,r.jsxs)(z.x,{children:[(0,r.jsx)(m,{children:(0,r.jsxs)(y,{children:[(0,r.jsx)(B,{}),(0,r.jsx)(j,{}),(0,r.jsx)($,{}),(0,r.jsx)(V,{})]})}),(0,r.jsx)(H,{})]})})})},{})})}),n=(e,t)=>{console.error("Docusaurus React Root onRecoverableError:",e,t)},c=()=>{window.docusaurusRoot?window.docusaurusRoot.render(t):window.docusaurusRoot=o.hydrateRoot(e,t,{onRecoverableError:n})};D(window.location.pathname).then(()=>{(0,a.startTransition)(c)})}},3587(e,t,n){"use strict";n.d(t,{l:()=>p,o:()=>d});var r=n(4848),a=n(6540),o=n(4784),i=JSON.parse('{"docusaurus-plugin-content-docs":{"default":{"path":"/","versions":[{"name":"current","label":"Next","isLast":true,"path":"/","mainDocId":"basecamp/get-started/install-logos-basecamp","docs":[{"id":"basecamp/get-started/install-logos-basecamp","path":"/basecamp/get-started/install-logos-basecamp","sidebar":"basecampSidebar"},{"id":"blockchain/blend/join-the-blend-network-as-a-core-node","path":"/blockchain/blend/join-the-blend-network-as-a-core-node","sidebar":"blockchainSidebar"},{"id":"blockchain/concepts/about-bedrock","path":"/blockchain/concepts/about-bedrock","sidebar":"blockchainSidebar"},{"id":"blockchain/concepts/about-cryptarchia","path":"/blockchain/concepts/about-cryptarchia","sidebar":"blockchainSidebar"},{"id":"blockchain/concepts/about-mantle","path":"/blockchain/concepts/about-mantle","sidebar":"blockchainSidebar"},{"id":"blockchain/concepts/about-the-blend-network","path":"/blockchain/concepts/about-the-blend-network","sidebar":"blockchainSidebar"},{"id":"blockchain/concepts/about-zones","path":"/blockchain/concepts/about-zones","sidebar":"blockchainSidebar"},{"id":"blockchain/get-started/introduction-to-the-logos-blockchain","path":"/blockchain/get-started/introduction-to-the-logos-blockchain","sidebar":"blockchainSidebar"},{"id":"blockchain/get-started/run-a-logos-blockchain-node-from-basecamp","path":"/blockchain/get-started/run-a-logos-blockchain-node-from-basecamp","sidebar":"blockchainSidebar"},{"id":"blockchain/get-started/run-a-logos-blockchain-node-from-cli","path":"/blockchain/get-started/run-a-logos-blockchain-node-from-cli","sidebar":"blockchainSidebar"},{"id":"blockchain/node-app/bridge-assets-from-logos-blockchain-to-zone-using-app","path":"/blockchain/node-app/bridge-assets-from-logos-blockchain-to-zone-using-app","sidebar":"blockchainSidebar"},{"id":"blockchain/node-app/build-and-run-logos-blockchain-node-app-ui","path":"/blockchain/node-app/build-and-run-logos-blockchain-node-app-ui","sidebar":"blockchainSidebar"},{"id":"blockchain/node-app/claim-leader-rewards-in-logos-blockchain-ui-app","path":"/blockchain/node-app/claim-leader-rewards-in-logos-blockchain-ui-app","sidebar":"blockchainSidebar"},{"id":"blockchain/node-app/move-assets-using-logos-blockchain-app","path":"/blockchain/node-app/move-assets-using-logos-blockchain-app","sidebar":"blockchainSidebar"},{"id":"blockchain/zone-sdk/bridge-assets-between-blockchain-and-zone","path":"/blockchain/zone-sdk/bridge-assets-between-blockchain-and-zone","sidebar":"blockchainSidebar"},{"id":"blockchain/zone-sdk/inscribe-data-on-chain-using-zone-sdk","path":"/blockchain/zone-sdk/inscribe-data-on-chain-using-zone-sdk","sidebar":"blockchainSidebar"},{"id":"blockchain/zone-sdk/operate-decentralised-zone-with-round-robin-sequencer-rotation","path":"/blockchain/zone-sdk/operate-decentralised-zone-with-round-robin-sequencer-rotation","sidebar":"blockchainSidebar"},{"id":"build-an-app/tutorials/build-dapp-with-logos-zone-sdk","path":"/build-an-app/tutorials/build-dapp-with-logos-zone-sdk","sidebar":"buildAnAppSidebar"},{"id":"build-an-app/tutorials/create-and-use-an-amm-liquidity-pool-on-the-logos-execution-zone","path":"/build-an-app/tutorials/create-and-use-an-amm-liquidity-pool-on-the-logos-execution-zone","sidebar":"buildAnAppSidebar"},{"id":"contribute/welcome","path":"/contribute/welcome","sidebar":"contributeSidebar"},{"id":"core/build-modules/build-a-logos-cpp-ui-module","path":"/core/build-modules/build-a-logos-cpp-ui-module","sidebar":"coreSidebar"},{"id":"core/build-modules/build-and-run-a-logos-core-module","path":"/core/build-modules/build-and-run-a-logos-core-module","sidebar":"coreSidebar"},{"id":"core/build-modules/build-logos-core-module-that-uses-service-discovery-api","path":"/core/build-modules/build-logos-core-module-that-uses-service-discovery-api","sidebar":"coreSidebar"},{"id":"core/build-modules/install-and-load-a-module-in-logos-basecamp","path":"/core/build-modules/install-and-load-a-module-in-logos-basecamp","sidebar":"coreSidebar"},{"id":"core/build-modules/start-a-logos-module-from-the-cli","path":"/core/build-modules/start-a-logos-module-from-the-cli","sidebar":"coreSidebar"},{"id":"core/build-modules/wrap-a-c-library-as-a-logos-core-module","path":"/core/build-modules/wrap-a-c-library-as-a-logos-core-module","sidebar":"coreSidebar"},{"id":"core/reference/lgx-package-format-and-bundling-reference","path":"/core/reference/lgx-package-format-and-bundling-reference","sidebar":"coreSidebar"},{"id":"core/reference/logos-cli-reference","path":"/core/reference/logos-cli-reference","sidebar":"coreSidebar"},{"id":"get-started/glossary","path":"/get-started/glossary","sidebar":"getStartedSidebar"},{"id":"get-started/introduction-to-logos","path":"/get-started/what-is-logos","sidebar":"getStartedSidebar"},{"id":"get-started/logos-ecosystem-repositories","path":"/get-started/logos-ecosystem-repositories","sidebar":"getStartedSidebar"},{"id":"lez/accounts/set-up-shared-private-lez-account","path":"/lez/accounts/set-up-shared-private-lez-account","sidebar":"lezSidebar"},{"id":"lez/follow-chain/run-lez-indexer","path":"/lez/follow-chain/run-lez-indexer","sidebar":"lezSidebar"},{"id":"lez/follow-chain/start-and-use-instance-of-lez-explorer","path":"/lez/follow-chain/start-and-use-instance-of-lez-explorer","sidebar":"lezSidebar"},{"id":"lez/get-started/connect-wallet-cli-to-lez-testnet","path":"/lez/get-started/connect-wallet-cli-to-lez-testnet","sidebar":"lezSidebar"},{"id":"lez/get-started/initiate-native-token-transfers-on-lez-with-wallet-ui","path":"/lez/get-started/initiate-native-token-transfers-on-lez-with-wallet-ui","sidebar":"lezSidebar"},{"id":"lez/get-started/introduction-to-the-logos-execution-zone","path":"/lez/get-started/introduction-to-the-logos-execution-zone","sidebar":"lezSidebar"},{"id":"lez/get-started/quickstart-for-the-logos-execution-zone-wallet","path":"/lez/get-started/lez-quickstart","sidebar":"lezSidebar"},{"id":"lez/transfer-tokens/create-and-transfer-custom-tokens-on-the-logos-execution-zone","path":"/lez/transfer-tokens/create-and-transfer-custom-tokens-on-the-logos-execution-zone","sidebar":"lezSidebar"},{"id":"lez/transfer-tokens/transfer-native-lez-tokens-between-public-private-states","path":"/lez/transfer-tokens/transfer-native-lez-tokens-between-public-private-states","sidebar":"lezSidebar"},{"id":"lez/transfer-tokens/transfer-native-tokens-on-the-logos-execution-zone","path":"/lez/transfer-tokens/transfer-native-tokens-on-the-logos-execution-zone","sidebar":"lezSidebar"},{"id":"messaging/chat-module/build-logos-module-that-uses-chat-module-api","path":"/messaging/chat-module/build-logos-module-that-uses-chat-module-api","sidebar":"messagingSidebar"},{"id":"messaging/chat-module/send-anonymised-messages-over-the-mix-network","path":"/messaging/chat-module/send-anonymised-messages-over-the-mix-network","sidebar":"messagingSidebar"},{"id":"messaging/concepts/about-content-topics","path":"/messaging/concepts/content-topics","sidebar":"messagingSidebar"},{"id":"messaging/concepts/about-network-domains","path":"/messaging/concepts/network-domains","sidebar":"messagingSidebar"},{"id":"messaging/concepts/about-transports","path":"/messaging/concepts/transports","sidebar":"messagingSidebar"},{"id":"messaging/concepts/learn-the-differences-between-logos-delivery-and-libp2p","path":"/messaging/concepts/logos-delivery-and-libp2p","sidebar":"messagingSidebar"},{"id":"messaging/concepts/understand-logos-delivery-protocols","path":"/messaging/concepts/protocols","sidebar":"messagingSidebar"},{"id":"messaging/concepts/understand-logos-delivery-security-features","path":"/messaging/concepts/security-features","sidebar":"messagingSidebar"},{"id":"messaging/concepts/understand-peer-discovery/about-discv5","path":"/messaging/concepts/understand-peer-discovery/discv5","sidebar":"messagingSidebar"},{"id":"messaging/concepts/understand-peer-discovery/about-dns-discovery","path":"/messaging/concepts/understand-peer-discovery/dns-discovery","sidebar":"messagingSidebar"},{"id":"messaging/concepts/understand-peer-discovery/about-peer-exchange","path":"/messaging/concepts/understand-peer-discovery/peer-exchange","sidebar":"messagingSidebar"},{"id":"messaging/concepts/understand-peer-discovery/about-static-peers","path":"/messaging/concepts/understand-peer-discovery/static-peers","sidebar":"messagingSidebar"},{"id":"messaging/concepts/understand-peer-discovery/understand-peer-discovery","path":"/messaging/concepts/understand-peer-discovery/peer-discovery","sidebar":"messagingSidebar"},{"id":"messaging/delivery/run-logos-delivery-node","path":"/messaging/delivery/run-logos-delivery-node","sidebar":"messagingSidebar"},{"id":"messaging/delivery/use-logos-delivery-module-api-from-app","path":"/messaging/delivery/use-logos-delivery-module-api-from-app","sidebar":"messagingSidebar"},{"id":"messaging/get-started/faq-logos-messaging","path":"/messaging/get-started/faq","sidebar":"messagingSidebar"},{"id":"messaging/get-started/introduction-to-logos-messaging","path":"/messaging/get-started/introduction-to-logos-messaging","sidebar":"messagingSidebar"},{"id":"messaging/get-started/send-1-1-messages-logos-chat","path":"/messaging/get-started/send-1-1-messages-logos-chat","sidebar":"messagingSidebar"},{"id":"mixnet/get-started/discover-nodes-and-send-messages-via-the-anoncomms-mixnet-demo-app","path":"/mixnet/get-started/discover-nodes-and-send-messages-via-the-anoncomms-mixnet-demo-app","sidebar":"mixnetSidebar"},{"id":"peer-discovery/welcome","path":"/peer-discovery/welcome","sidebar":"peerDiscoverySidebar"},{"id":"run-a-node/get-started/run-logos-node-blockchain-storage-delivery","path":"/run-a-node/get-started/run-logos-node-blockchain-storage-delivery","sidebar":"runANodeSidebar"},{"id":"storage/get-started/run-logos-storage-node","path":"/storage/get-started/run-logos-storage-node","sidebar":"storageSidebar"},{"id":"storage/get-started/set-up-and-use-logos-storage-ui","path":"/storage/get-started/set-up-and-use-logos-storage-ui","sidebar":"storageSidebar"},{"id":"/run-an-app","path":"/run-an-app","sidebar":"runAnAppSidebar"},{"id":"/build-an-app","path":"/build-an-app","sidebar":"buildAnAppSidebar"}],"draftIds":[],"sidebars":{"runAnAppSidebar":{"link":{"path":"/run-an-app","label":"Run an app"}},"runANodeSidebar":{"link":{"path":"/run-a-node/get-started/run-logos-node-blockchain-storage-delivery","label":"run-a-node/get-started/run-logos-node-blockchain-storage-delivery"}},"buildAnAppSidebar":{"link":{"path":"/build-an-app","label":"Build an app"}},"getStartedSidebar":{"link":{"path":"/get-started/what-is-logos","label":"get-started/introduction-to-logos"}},"basecampSidebar":{"link":{"path":"/basecamp/get-started/install-logos-basecamp","label":"basecamp/get-started/install-logos-basecamp"}},"blockchainSidebar":{"link":{"path":"/blockchain/get-started/introduction-to-the-logos-blockchain","label":"blockchain/get-started/introduction-to-the-logos-blockchain"}},"lezSidebar":{"link":{"path":"/lez/get-started/introduction-to-the-logos-execution-zone","label":"lez/get-started/introduction-to-the-logos-execution-zone"}},"coreSidebar":{"link":{"path":"/core/build-modules/start-a-logos-module-from-the-cli","label":"core/build-modules/start-a-logos-module-from-the-cli"}},"messagingSidebar":{"link":{"path":"/messaging/get-started/introduction-to-logos-messaging","label":"messaging/get-started/introduction-to-logos-messaging"}},"storageSidebar":{"link":{"path":"/storage/get-started/run-logos-storage-node","label":"storage/get-started/run-logos-storage-node"}},"mixnetSidebar":{"link":{"path":"/mixnet/get-started/discover-nodes-and-send-messages-via-the-anoncomms-mixnet-demo-app","label":"mixnet/get-started/discover-nodes-and-send-messages-via-the-anoncomms-mixnet-demo-app"}},"peerDiscoverySidebar":{"link":{"path":"/peer-discovery/welcome","label":"peer-discovery/welcome"}},"contributeSidebar":{"link":{"path":"/contribute/welcome","label":"contribute/welcome"}}}}],"breadcrumbs":true}}}'),l=JSON.parse('{"defaultLocale":"en","locales":["en"],"path":"i18n","currentLocale":"en","localeConfigs":{"en":{"label":"English","direction":"ltr","htmlLang":"en","calendar":"gregory","path":"en","translate":false,"url":"https://docs.logos.co","baseUrl":"/"}}}'),s=n(2654),c=JSON.parse('{"docusaurusVersion":"3.10.2","siteVersion":"0.0.0","pluginVersions":{"docusaurus-plugin-css-cascade-layers":{"type":"package","name":"@docusaurus/plugin-css-cascade-layers","version":"3.10.2"},"docusaurus-plugin-content-docs":{"type":"package","name":"@docusaurus/plugin-content-docs","version":"3.10.2"},"docusaurus-plugin-content-pages":{"type":"package","name":"@docusaurus/plugin-content-pages","version":"3.10.2"},"docusaurus-plugin-sitemap":{"type":"package","name":"@docusaurus/plugin-sitemap","version":"3.10.2"},"docusaurus-plugin-svgr":{"type":"package","name":"@docusaurus/plugin-svgr","version":"3.10.2"},"docusaurus-theme-classic":{"type":"package","name":"@docusaurus/theme-classic","version":"3.10.2"},"docusaurus-plugin-client-redirects":{"type":"package","name":"@docusaurus/plugin-client-redirects","version":"3.10.2"}}}');let u={siteConfig:o.default,siteMetadata:c,globalData:i,i18n:l,codeTranslations:s},d=a.createContext(u);function p({children:e}){return(0,r.jsx)(d.Provider,{value:u,children:e})}},1701(e,t,n){"use strict";n.d(t,{A:()=>m});var r=n(4848),a=n(6540),o=n(1289),i=n(3572),l=n(5248),s=n(1085),c=n(7574);function u({error:e,tryAgain:t}){return(0,r.jsxs)("div",{style:{display:"flex",flexDirection:"column",justifyContent:"center",alignItems:"flex-start",minHeight:"100vh",width:"100%",maxWidth:"80ch",fontSize:"20px",margin:"0 auto",padding:"1rem"},children:[(0,r.jsx)("h1",{style:{fontSize:"3rem"},children:"This page crashed"}),(0,r.jsx)("button",{type:"button",onClick:t,style:{margin:"1rem 0",fontSize:"2rem",cursor:"pointer",borderRadius:20,padding:"1rem"},children:"Try again"}),(0,r.jsx)(d,{error:e})]})}function d({error:e}){let t=(0,l.getErrorCausalChain)(e).map(e=>e.message).join("\n\nCause:\n");return(0,r.jsx)("p",{style:{whiteSpace:"pre-wrap"},children:t})}function p({children:e}){return(0,r.jsx)(c.W,{value:{plugin:{name:"docusaurus-core-error-boundary",id:"default"}},children:e})}function f({error:e,tryAgain:t}){return(0,r.jsx)(p,{children:(0,r.jsxs)(m,{fallback:()=>(0,r.jsx)(u,{error:e,tryAgain:t}),children:[(0,r.jsx)(i.A,{children:(0,r.jsx)("title",{children:"Page Error"})}),(0,r.jsx)(s.A,{children:(0,r.jsx)(u,{error:e,tryAgain:t})})]})})}class m extends a.Component{constructor(e){super(e),this.state={error:null}}componentDidCatch(e){o.A.canUseDOM&&this.setState({error:e})}render(){let{children:e}=this.props,{error:t}=this.state;return t?(this.props.fallback??(e=>(0,r.jsx)(f,{...e})))({error:t,tryAgain:()=>this.setState({error:null})}):e??null}}},1289(e,t,n){"use strict";n.d(t,{A:()=>a});let r="u">typeof window&&"document"in window&&"createElement"in window.document,a={canUseDOM:r,canUseEventListeners:r&&("addEventListener"in window||"attachEvent"in window),canUseIntersectionObserver:r&&"IntersectionObserver"in window,canUseViewport:r&&"screen"in window}},3572(e,t,n){"use strict";n.d(t,{A:()=>o});var r=n(4848);n(6540);var a=n(545);function o(e){return(0,r.jsx)(a.mg,{...e})}},5310(e,t,n){"use strict";n.d(t,{A:()=>p});var r=n(4848),a=n(6540),o=n(4625),i=n(5248),l=n(898),s=n(8310),c=n(1289),u=n(5916),d=n(6497);let p=a.forwardRef(function({isNavLink:e,to:t,href:n,activeClassName:p,isActive:f,"data-noBrokenLinkCheck":m,autoAddBaseUrl:g=!0,...h},b){var y;let{siteConfig:v}=(0,l.A)(),{trailingSlash:w,baseUrl:k}=v,x=v.future.experimental_router,{withBaseUrl:S}=(0,d.hH)(),E=(0,u.A)(),_=(0,a.useRef)(null);(0,a.useImperativeHandle)(b,()=>_.current);let A=t||n,C=(0,s.A)(A),T=A?.replace("pathname://",""),j=void 0!==T?(y=T,g&&y.startsWith("/")?S(y):y):void 0;"hash"===x&&j?.startsWith("./")&&(j=j?.slice(1)),j&&C&&(j=(0,i.applyTrailingSlash)(j,{trailingSlash:w,baseUrl:k}));let P=(0,a.useRef)(!1),z=e?o.k2:o.N_,L=c.A.canUseIntersectionObserver,N=(0,a.useRef)(void 0),O=()=>{P.current||null==j||(window.docusaurus.preload(j),P.current=!0)};(0,a.useEffect)(()=>(!L&&C&&c.A.canUseDOM&&null!=j&&window.docusaurus.prefetch(j),()=>{L&&N.current&&N.current.disconnect()}),[N,j,L,C]);let R=j?.startsWith("#")??!1,D=!h.target||"_self"===h.target,F=!j||!C||!D||R&&"hash"!==x;m||!R&&F||E.collectLink(j),h.id&&E.collectAnchor(h.id);let I={};return F?(0,r.jsx)("a",{ref:_,href:j,...A&&!C&&{target:"_blank",rel:"noopener noreferrer"},...h,...I}):(0,r.jsx)(z,{...h,onMouseEnter:O,onTouchStart:O,innerRef:e=>{_.current=e,L&&e&&C&&(N.current=new window.IntersectionObserver(t=>{t.forEach(t=>{e===t.target&&(t.isIntersecting||t.intersectionRatio>0)&&(N.current.unobserve(e),N.current.disconnect(),null!=j&&window.docusaurus.prefetch(j))})}),N.current.observe(e))},to:j,...e&&{isActive:f,activeClassName:p},...I})})},9034(e,t,n){"use strict";n.d(t,{A:()=>r});let r=()=>null},568(e,t,n){"use strict";n.d(t,{A:()=>c,T:()=>s});var r=n(4848),a=n(6540);function o(e,t){let n=e.split(/(\{\w+\})/).map((e,n)=>{if(n%2==1){let n=t?.[e.slice(1,-1)];if(void 0!==n)return n}return e});return n.some(e=>(0,a.isValidElement)(e))?n.map((e,t)=>(0,a.isValidElement)(e)?a.cloneElement(e,{key:t}):e).filter(e=>""!==e):n.join("")}var i=n(2654);function l({id:e,message:t}){if(void 0===e&&void 0===t)throw Error("Docusaurus translation declarations must have at least a translation id or a default translation message");return i[e??t]??t??e}function s({message:e,id:t},n){return o(l({message:e,id:t}),n)}function c({children:e,id:t,values:n}){if(e&&"string"!=typeof e)throw console.warn("Illegal <Translate> children",e),Error("The Docusaurus <Translate> component only accept simple string values");let a=l({message:e,id:t});return(0,r.jsx)(r.Fragment,{children:o(a,n)})}},8977(e,t,n){"use strict";n.d(t,{W:()=>r});let r="default"},8310(e,t,n){"use strict";function r(e){return/^(?:[A-Za-z][A-Za-z\d+.-]*:|\/\/)/.test(e)}function a(e){return void 0!==e&&!r(e)}n.d(t,{A:()=>a,z:()=>r})},6497(e,t,n){"use strict";n.d(t,{Ay:()=>l,hH:()=>i});var r=n(6540),a=n(898),o=n(8310);function i(){let{siteConfig:e}=(0,a.A)(),{baseUrl:t,url:n}=e,i=e.future.experimental_router;return{withBaseUrl:(0,r.useCallback)((e,r)=>(function({siteUrl:e,baseUrl:t,url:n,options:{forcePrependBaseUrl:r=!1,absolute:a=!1}={},router:i}){if(!n||n.startsWith("#")||(0,o.z)(n))return n;if("hash"===i)return n.startsWith("/")?`.${n}`:`./${n}`;if(r)return t+n.replace(/^\//,"");if(n===t.replace(/\/$/,""))return t;let l=n.startsWith(t)?n:t+n.replace(/^\//,"");return a?e+l:l})({siteUrl:n,baseUrl:t,url:e,options:r,router:i}),[n,t,i])}}function l(e,t={}){let{withBaseUrl:n}=i();return n(e,t)}},5916(e,t,n){"use strict";n.d(t,{A:()=>o}),n(4848);var r=n(6540);let a=r.createContext({collectAnchor:()=>{},collectLink:()=>{}});function o(){return(0,r.useContext)(a)}},898(e,t,n){"use strict";n.d(t,{A:()=>o});var r=n(6540),a=n(3587);function o(){return(0,r.useContext)(a.o)}},9863(e,t,n){"use strict";n.d(t,{A:()=>o});var r=n(6540),a=n(1173);function o(){return(0,r.useContext)(a.o)}},9989(e,t,n){"use strict";n.d(t,{A:()=>a});var r=n(6540);let a=n(1289).A.canUseDOM?r.useLayoutEffect:r.useEffect},8491(e,t,n){"use strict";n.d(t,{A:()=>o});var r=n(6540),a=n(7574);function o(){let e=r.useContext(a.o);if(!e)throw Error("Unexpected: no Docusaurus route context found");return e}},7665(e,t,n){"use strict";function r(e){let t={};return!function e(n,r){Object.entries(n).forEach(([n,a])=>{let o=r?`${r}.${n}`:n;"object"==typeof a&&a&&Object.keys(a).length>0?e(a,o):t[o]=a})}(e),t}n.d(t,{A:()=>r})},7574(e,t,n){"use strict";n.d(t,{W:()=>i,o:()=>o});var r=n(4848),a=n(6540);let o=a.createContext(null);function i({children:e,value:t}){let n=a.useContext(o),l=(0,a.useMemo)(()=>(function({parent:e,value:t}){if(!e){if(t){if(!("plugin"in t))throw Error("Unexpected: Docusaurus topmost route context has no `plugin` attribute")}else throw Error("Unexpected: no Docusaurus route context found");return t}let n={...e.data,...t?.data};return{plugin:e.plugin,data:n}})({parent:n,value:t}),[n,t]);return(0,r.jsx)(o.Provider,{value:l,children:e})}},2086(e,t,n){"use strict";n.d(t,{VQ:()=>f,g1:()=>m});var r=n(4848),a=n(6540),o=n(698),i=n(8977),l=n(1022),s=n(1367),c=n(2260);let u=e=>`docs-preferred-version-${e}`,d=a.createContext(null);function p({children:e}){let t=function(){let e=(0,o.Gy)(),t=(0,l.p)().docs.versionPersistence,n=(0,a.useMemo)(()=>Object.keys(e),[e]),[r,i]=(0,a.useState)(()=>Object.fromEntries(n.map(e=>[e,{preferredVersionName:null}])));return(0,a.useEffect)(()=>{i(function({pluginIds:e,versionPersistence:t,allDocsData:n}){return Object.fromEntries(e.map(e=>{var r,a;let o,i,l;return[e,(i=e,l=t,o=(0,s.Wf)(u(i),{persistence:l}).get(),n[e].versions.some(e=>e.name===o)?{preferredVersionName:o}:(r=e,a=t,(0,s.Wf)(u(r),{persistence:a}).del(),{preferredVersionName:null}))]}))}({allDocsData:e,versionPersistence:t,pluginIds:n}))},[e,t,n]),[r,(0,a.useMemo)(()=>({savePreferredVersion:function(e,n){var r,a,o;r=e,a=t,o=n,(0,s.Wf)(u(r),{persistence:a}).set(o),i(t=>({...t,[e]:{preferredVersionName:n}}))}}),[t])]}();return(0,r.jsx)(d.Provider,{value:t,children:e})}function f({children:e}){return(0,r.jsx)(p,{children:e})}function m(e=i.W){let t=(0,o.ht)(e),[n,r]=function(){let e=(0,a.useContext)(d);if(!e)throw new c.dV("DocsPreferredVersionContextProvider");return e}(),{preferredVersionName:l}=n[e];return{preferredVersion:t.versions.find(e=>e.name===l)??null,savePreferredVersionName:(0,a.useCallback)(t=>{r.savePreferredVersion(e,t)},[r,e])}}},2473(e,t,n){"use strict";n.d(t,{V:()=>s,t:()=>c});var r=n(4848),a=n(6540),o=n(2260);let i=Symbol("EmptyContext"),l=a.createContext(i);function s({children:e,name:t,items:n}){let o=(0,a.useMemo)(()=>t&&n?{name:t,items:n}:null,[t,n]);return(0,r.jsx)(l.Provider,{value:o,children:e})}function c(){let e=(0,a.useContext)(l);if(e===i)throw new o.dV("DocsSidebarProvider");return e}},6877(e,t,n){"use strict";n.d(t,{a4:()=>g,Y:()=>y,$S:()=>m,w8:()=>b,fW:()=>x,Nr:()=>f,B5:()=>E,Vd:()=>k,d1:()=>_,QB:()=>S,OF:()=>w,cC:()=>p});var r=n(6540),a=n(6347),o=n(2831),i=n(698),l=n(9209);function s(e){return Array.from(new Set(e))}var c=n(2086),u=n(921),d=n(2473);function p(e){let t=(0,u.r)();if(!e)return;let n=t.docs[e];if(!n)throw Error(`no version doc found by id=${e}`);return n}function f(e){if("link"===e.type&&!e.unlisted)return e.href;if("category"===e.type){if(e.href&&!e.linkUnlisted)return e.href;for(let t of e.items){let e=f(t);if(e)return e}return}}function m(){let{pathname:e}=(0,a.zy)(),t=(0,d.t)();if(!t)throw Error("Unexpected: cant find current sidebar in context");let n=v({sidebarItems:t.items,pathname:e,onlyCategories:!0}).slice(-1)[0];if(!n)throw Error(`${e} is not associated with a category. useCurrentSidebarCategory() should only be used on category index pages.`);return n}function g(){let{pathname:e}=(0,a.zy)(),t=(0,d.t)();if(!t)throw Error("Unexpected: cant find current sidebar in context");let n=v({sidebarItems:t.items,pathname:e,onlyCategories:!0}).slice(-1)[0];return n?.items??t.items}let h=(e,t)=>void 0!==e&&(0,l.ys)(e,t);function b(e,t){if("link"===e.type)return h(e.href,t);if("category"===e.type){let n;return h(e.href,t)||(n=e.items,n.some(e=>b(e,t)))}return!1}function y(e,t){return(0,r.useMemo)(()=>e.filter(e=>(function e(t,n){switch(t.type){case"category":return b(t,n)||void 0!==t.href&&!t.linkUnlisted||t.items.some(t=>e(t,n));case"link":return!t.unlisted||b(t,n);default:return!0}})(e,t)),[e,t])}function v({sidebarItems:e,pathname:t,onlyCategories:n=!1}){let r=[];return!function e(a){for(let o of a)if("category"===o.type){if((0,l.ys)(o.href,t)||e(o.items))return r.unshift(o),!0}else if("link"===o.type&&o.docId&&(0,l.ys)(o.href,t))return n||r.unshift(o),!0;return!1}(e),r}function w(){let e=(0,d.t)(),{pathname:t}=(0,a.zy)();return!1!==(0,i.vT)()?.pluginData.breadcrumbs&&e?v({sidebarItems:e.items,pathname:t}):null}function k(e){let{activeVersion:t}=(0,i.zK)(e),{preferredVersion:n}=(0,c.g1)(e),a=(0,i.r7)(e);return(0,r.useMemo)(()=>s([t,n,a].filter(Boolean)),[t,n,a])}function x(e,t){let n=k(t);return(0,r.useMemo)(()=>{let t=n.flatMap(e=>e.sidebars?Object.entries(e.sidebars):[]),r=t.find(t=>t[0]===e);if(!r)throw Error(`Can't find any sidebar with id "${e}" in version${n.length>1?"s":""} ${n.map(e=>e.name).join(", ")}". +Available sidebar ids are: +- ${t.map(e=>e[0]).join("\n- ")}`);return r[1]},[e,n])}function S(e,t){let n=k(t);return(0,r.useMemo)(()=>{let t=n.flatMap(e=>e.docs),r=t.find(t=>t.id===e);if(!r){if(n.flatMap(e=>e.draftIds).includes(e))return null;throw Error(`Couldn't find any doc with id "${e}" in version${n.length>1?"s":""} "${n.map(e=>e.name).join(", ")}". +Available doc ids are: +- ${s(t.map(e=>e.id)).join("\n- ")}`)}return r},[e,n])}function E({route:e}){let t=(0,a.zy)(),n=(0,u.r)(),r=e.routes,i=r.find(e=>(0,a.B6)(t.pathname,e));if(!i)return null;let l=i.sidebar,s=l?n.docsSidebars[l]:void 0;return{docElement:(0,o.v)(r),sidebarName:l,sidebarItems:s}}function _(e){return e.filter(e=>"category"!==e.type&&"link"!==e.type||!!f(e))}},921(e,t,n){"use strict";n.d(t,{n:()=>l,r:()=>s});var r=n(4848),a=n(6540),o=n(2260);let i=a.createContext(null);function l({children:e,version:t}){return(0,r.jsx)(i.Provider,{value:t,children:e})}function s(){let e=(0,a.useContext)(i);if(null===e)throw new o.dV("DocsVersionProvider");return e}},698(e,t,n){"use strict";n.d(t,{ht:()=>d,r7:()=>m,HW:()=>h,zK:()=>g,jh:()=>f,Gy:()=>u,vT:()=>p});var r=n(6347),a=n(898),o=n(8977);function i(e,t={}){let n=function(){let{globalData:e}=(0,a.A)();return e}()[e];if(!n&&t.failfast)throw Error(`Docusaurus plugin global data not found for "${e}" plugin.`);return n}let l=e=>e.versions.find(e=>e.isLast);function s(e,t){var n;let a,o=[...e.versions].sort((e,t)=>e.path===t.path?0:e.path.includes(t.path)?-1:+!!t.path.includes(e.path)).find(e=>!!(0,r.B6)(t,{path:e.path,exact:!1,strict:!1})),i=o?.docs.find(e=>!!(0,r.B6)(t,{path:e.path,exact:!0,strict:!1})),l=i?(n=i.id,a={},e.versions.forEach(e=>{e.docs.forEach(t=>{t.id===n&&(a[e.name]=t)})}),a):{};return{activeVersion:o,activeDoc:i,alternateDocVersions:l}}let c={},u=()=>i("docusaurus-plugin-content-docs")??c,d=e=>{try{return function(e,t=o.W,n={}){let r=i(e),a=r?.[t];if(!a&&n.failfast)throw Error(`Docusaurus plugin global data not found for "${e}" plugin with id "${t}".`);return a}("docusaurus-plugin-content-docs",e,{failfast:!0})}catch(t){throw Error(`You are using a feature of the Docusaurus docs plugin, but this plugin does not seem to be enabled${"Default"===e?"":` (pluginId=${e}`}`,{cause:t})}};function p(e={}){let t=u(),{pathname:n}=(0,r.zy)();return function(e,t,n={}){let a=Object.entries(e).sort((e,t)=>t[1].path.localeCompare(e[1].path)).find(([,e])=>!!(0,r.B6)(t,{path:e.path,exact:!1,strict:!1})),o=a?{pluginId:a[0],pluginData:a[1]}:void 0;if(!o&&n.failfast)throw Error(`Can't find active docs plugin for "${t}" pathname, while it was expected to be found. Maybe you tried to use a docs feature that can only be used on a docs-related page? Existing docs plugin paths are: ${Object.values(e).map(e=>e.path).join(", ")}`);return o}(t,n,e)}function f(e){return d(e).versions}function m(e){return l(d(e))}function g(e){let t=d(e),{pathname:n}=(0,r.zy)();return s(t,n)}function h(e){let t,n=d(e),{pathname:a}=(0,r.zy)();return t=l(n),{latestDocSuggestion:s(n,a).alternateDocVersions[t.name],latestVersionSuggestion:t}}},3678(e,t,n){"use strict";n.r(t),n.d(t,{default:()=>o});var r=n(5947),a=n.n(r);a().configure({showSpinner:!1});let o={onRouteUpdate({location:e,previousLocation:t}){if(t&&e.pathname!==t.pathname){let e=window.setTimeout(()=>{a().start()},200);return()=>window.clearTimeout(e)}},onRouteDidUpdate(){a().done()}}},2489(e,t,n){"use strict";var r=n(1765),a=n(4784);!function(e){let{themeConfig:{prism:t}}=a.default,{additionalLanguages:r}=t,o=globalThis.Prism;globalThis.Prism=e,r.forEach(e=>{"php"===e&&n(9700),n(3157)(`./prism-${e}`)}),delete globalThis.Prism,void 0!==o&&(globalThis.Prism=e)}(r.My)},2072(e,t,n){"use strict";n.d(t,{A:()=>c});var r=n(4848);n(6540);var a=n(4164),o=n(568),i=n(4727),l=n(5310),s=n(5916);function c({as:e,id:t,...n}){let u=(0,s.A)(),d=(0,i.v)(t);if("h1"===e||!t)return(0,r.jsx)(e,{...n,id:void 0});u.collectAnchor(t);let p=(0,o.T)({id:"theme.common.headingLinkTitle",message:"Direct link to {heading}",description:"Title for link to heading"},{heading:"string"==typeof n.children?n.children:t});return(0,r.jsxs)(e,{...n,className:(0,a.A)("anchor",d,n.className),id:t,children:[n.children,(0,r.jsx)(l.A,{className:"hash-link",to:`#${t}`,"aria-label":p,title:p,translate:"no",children:"\u200B"})]})}},7715(e,t,n){"use strict";n.d(t,{A:()=>o});var r=n(4848);n(6540);var a=n(568);function o({width:e=13.5,height:t=13.5}){return(0,r.jsx)("svg",{width:e,height:t,"aria-label":(0,a.T)({id:"theme.IconExternalLink.ariaLabel",message:"(opens in new tab)",description:"The ARIA label for the external link icon"}),className:"iconExternalLink_nPIU",children:(0,r.jsx)("use",{href:"#theme-svg-external-link"})})}},1085(e,t,n){"use strict";n.d(t,{A:()=>eJ});var r=n(4848),a=n(6540),o=n(4164),i=n(1701),l=n(4308),s=n(6347),c=n(568),u=n(8398);let d="__docusaurus_skipToContent_fallback";function p(e){e.setAttribute("tabindex","-1"),e.focus(),e.removeAttribute("tabindex")}let f=(0,c.T)({id:"theme.common.skipToMainContent",description:"The skip to content label used for accessibility, allowing to rapidly navigate to main content with keyboard tab/enter navigation",message:"Skip to main content"});function m(e){let t=e.children??f,{containerRef:n,onClick:o}=function(){let e=(0,a.useRef)(null),{action:t}=(0,s.W6)(),n=(0,a.useCallback)(e=>{e.preventDefault();let t=document.querySelector("main:first-of-type")??document.getElementById(d);t&&p(t)},[]);return(0,u.$)(({location:n})=>{e.current&&!n.hash&&"PUSH"===t&&p(e.current)}),{containerRef:e,onClick:n}}();return(0,r.jsx)("div",{ref:n,role:"region","aria-label":f,children:(0,r.jsx)("a",{...e,href:`#${d}`,onClick:o,children:t})})}var g=n(8287);function h(){return(0,r.jsx)(m,{className:"skipToContent_fXgn"})}var b=n(1022),y=n(3337);function v({width:e=21,height:t=21,color:n="currentColor",strokeWidth:a=1.2,className:o,...i}){return(0,r.jsx)("svg",{viewBox:"0 0 15 15",width:e,height:t,...i,children:(0,r.jsx)("g",{stroke:n,strokeWidth:a,children:(0,r.jsx)("path",{d:"M.75.75l13.5 13.5M14.25.75L.75 14.25"})})})}function w(e){return(0,r.jsx)("button",{type:"button","aria-label":(0,c.T)({id:"theme.AnnouncementBar.closeButtonAriaLabel",message:"Close",description:"The ARIA label for close button of announcement bar"}),...e,className:(0,o.A)("clean-btn close","closeButton_CVFx",e.className),children:(0,r.jsx)(v,{width:14,height:14,strokeWidth:3.1})})}function k(e){let{announcementBar:t}=(0,b.p)(),{content:n}=t;return(0,r.jsx)("div",{...e,className:(0,o.A)("content_knG7",e.className),dangerouslySetInnerHTML:{__html:n}})}function x(){let{announcementBar:e}=(0,b.p)(),{isActive:t,close:n}=(0,y.M)();if(!t)return null;let{backgroundColor:a,textColor:i,isCloseable:l}=e;return(0,r.jsxs)("div",{className:(0,o.A)(g.G.announcementBar.container,"announcementBar_mb4j"),style:{backgroundColor:a,color:i},role:"banner",children:[l&&(0,r.jsx)("div",{className:"announcementBarPlaceholder_vyr4"}),(0,r.jsx)(k,{className:"announcementBarContent_xLdY"}),l&&(0,r.jsx)(w,{onClick:n,className:"announcementBarClose_gvF7"})]})}var S=n(9965),E=n(8584),_=n(2260),A=n(7e3);let C=a.createContext(null);function T({children:e}){let t=function(){let e=(0,S.M)(),t=(0,A.YL)(),[n,r]=(0,a.useState)(!1),o=null!==t.component,i=(0,_.ZC)(o);return(0,a.useEffect)(()=>{o&&!i&&r(!0)},[o,i]),(0,a.useEffect)(()=>{o?e.shown||r(!0):r(!1)},[e.shown,o]),(0,a.useMemo)(()=>[n,r],[n])}();return(0,r.jsx)(C.Provider,{value:t,children:e})}function j(){let e=(0,a.useContext)(C);if(!e)throw new _.dV("NavbarSecondaryMenuDisplayProvider");let[t,n]=e,o=(0,a.useCallback)(()=>n(!1),[n]),i=(0,A.YL)();return(0,a.useMemo)(()=>({shown:t,hide:o,content:function(e){if(e.component){let t=e.component;return(0,r.jsx)(t,{...e.props})}}(i)}),[o,i,t])}function P({children:e,inert:t}){return(0,r.jsx)("div",{className:(0,o.A)(g.G.layout.navbar.mobileSidebar.panel,"navbar-sidebar__item menu"),...19>parseInt(a.version.split(".")[0],10)?{inert:t?"":void 0}:{inert:t},children:e})}function z({header:e,primaryMenu:t,secondaryMenu:n}){let{shown:a}=j();return(0,r.jsxs)("div",{className:(0,o.A)(g.G.layout.navbar.mobileSidebar.container,"navbar-sidebar"),children:[e,(0,r.jsxs)("div",{className:(0,o.A)("navbar-sidebar__items",{"navbar-sidebar__items--show-secondary":a}),children:[(0,r.jsx)(P,{inert:a,children:t}),(0,r.jsx)(P,{inert:!a,children:n})]})]})}var L=n(3941),N=n(9863);function O(e){return(0,r.jsx)("svg",{viewBox:"0 0 24 24",width:24,height:24,...e,children:(0,r.jsx)("path",{fill:"currentColor",d:"M12,9c1.65,0,3,1.35,3,3s-1.35,3-3,3s-3-1.35-3-3S10.35,9,12,9 M12,7c-2.76,0-5,2.24-5,5s2.24,5,5,5s5-2.24,5-5 S14.76,7,12,7L12,7z M2,13l2,0c0.55,0,1-0.45,1-1s-0.45-1-1-1l-2,0c-0.55,0-1,0.45-1,1S1.45,13,2,13z M20,13l2,0c0.55,0,1-0.45,1-1 s-0.45-1-1-1l-2,0c-0.55,0-1,0.45-1,1S19.45,13,20,13z M11,2v2c0,0.55,0.45,1,1,1s1-0.45,1-1V2c0-0.55-0.45-1-1-1S11,1.45,11,2z M11,20v2c0,0.55,0.45,1,1,1s1-0.45,1-1v-2c0-0.55-0.45-1-1-1C11.45,19,11,19.45,11,20z M5.99,4.58c-0.39-0.39-1.03-0.39-1.41,0 c-0.39,0.39-0.39,1.03,0,1.41l1.06,1.06c0.39,0.39,1.03,0.39,1.41,0s0.39-1.03,0-1.41L5.99,4.58z M18.36,16.95 c-0.39-0.39-1.03-0.39-1.41,0c-0.39,0.39-0.39,1.03,0,1.41l1.06,1.06c0.39,0.39,1.03,0.39,1.41,0c0.39-0.39,0.39-1.03,0-1.41 L18.36,16.95z M19.42,5.99c0.39-0.39,0.39-1.03,0-1.41c-0.39-0.39-1.03-0.39-1.41,0l-1.06,1.06c-0.39,0.39-0.39,1.03,0,1.41 s1.03,0.39,1.41,0L19.42,5.99z M7.05,18.36c0.39-0.39,0.39-1.03,0-1.41c-0.39-0.39-1.03-0.39-1.41,0l-1.06,1.06 c-0.39,0.39-0.39,1.03,0,1.41s1.03,0.39,1.41,0L7.05,18.36z"})})}function R(e){return(0,r.jsx)("svg",{viewBox:"0 0 24 24",width:24,height:24,...e,children:(0,r.jsx)("path",{fill:"currentColor",d:"M9.37,5.51C9.19,6.15,9.1,6.82,9.1,7.5c0,4.08,3.32,7.4,7.4,7.4c0.68,0,1.35-0.09,1.99-0.27C17.45,17.19,14.93,19,12,19 c-3.86,0-7-3.14-7-7C5,9.07,6.81,6.55,9.37,5.51z M12,3c-4.97,0-9,4.03-9,9s4.03,9,9,9s9-4.03,9-9c0-0.46-0.04-0.92-0.1-1.36 c-0.98,1.37-2.58,2.26-4.4,2.26c-2.98,0-5.4-2.42-5.4-5.4c0-1.81,0.89-3.42,2.26-4.4C12.92,3.04,12.46,3,12,3L12,3z"})})}function D(e){return(0,r.jsx)("svg",{viewBox:"0 0 24 24",width:24,height:24,...e,children:(0,r.jsx)("path",{fill:"currentColor",d:"m12 21c4.971 0 9-4.029 9-9s-4.029-9-9-9-9 4.029-9 9 4.029 9 9 9zm4.95-13.95c1.313 1.313 2.05 3.093 2.05 4.95s-0.738 3.637-2.05 4.95c-1.313 1.313-3.093 2.05-4.95 2.05v-14c1.857 0 3.637 0.737 4.95 2.05z"})})}let F="toggleIcon_g3eP";function I(e){switch(e){case null:return(0,c.T)({message:"system mode",id:"theme.colorToggle.ariaLabel.mode.system",description:"The name for the system color mode"});case"light":return(0,c.T)({message:"light mode",id:"theme.colorToggle.ariaLabel.mode.light",description:"The name for the light color mode"});case"dark":return(0,c.T)({message:"dark mode",id:"theme.colorToggle.ariaLabel.mode.dark",description:"The name for the dark color mode"});default:throw Error(`unexpected color mode ${e}`)}}function M(){return(0,r.jsxs)(r.Fragment,{children:[(0,r.jsx)(O,{"aria-hidden":!0,className:(0,o.A)(F,"lightToggleIcon_pyhR")}),(0,r.jsx)(R,{"aria-hidden":!0,className:(0,o.A)(F,"darkToggleIcon_wfgR")}),(0,r.jsx)(D,{"aria-hidden":!0,className:(0,o.A)(F,"systemToggleIcon_QzmC")})]})}let $=a.memo(function({className:e,buttonClassName:t,respectPrefersColorScheme:n,value:a,onChange:i}){let l=(0,N.A)();return(0,r.jsx)("div",{className:(0,o.A)("toggle_vylO",e),children:(0,r.jsx)("button",{className:(0,o.A)("clean-btn","toggleButton_gllP",!l&&"toggleButtonDisabled_aARS",t),type:"button",onClick:()=>i(function(e,t){if(!t)return"dark"===e?"light":"dark";switch(e){case null:return"light";case"light":return"dark";case"dark":return null;default:throw Error(`unexpected color mode ${e}`)}}(a,n)),disabled:!l,title:I(a),"aria-label":(0,c.T)({message:"Switch between dark and light mode (currently {mode})",id:"theme.colorToggle.ariaLabel",description:"The ARIA label for the color mode toggle"},{mode:I(a)}),children:(0,r.jsx)(M,{})})})});function B({className:e}){let t=(0,b.p)().navbar.style,{disableSwitch:n,respectPrefersColorScheme:a}=(0,b.p)().colorMode,{colorModeChoice:o,setColorMode:i}=(0,L.G)();return n?null:(0,r.jsx)($,{className:e,buttonClassName:"dark"===t?"darkNavbarColorModeToggle_X3D1":void 0,respectPrefersColorScheme:a,value:o,onChange:i})}var U=n(5153);function q(){return(0,r.jsx)(U.A,{className:"navbar__brand",imageClassName:"navbar__logo",titleClassName:"navbar__title text--truncate"})}function H(){let e=(0,S.M)();return(0,r.jsx)("button",{type:"button","aria-label":(0,c.T)({id:"theme.docs.sidebar.closeSidebarButtonAriaLabel",message:"Close navigation bar",description:"The ARIA label for close button of mobile sidebar"}),className:"clean-btn navbar-sidebar__close",onClick:()=>e.toggle(),children:(0,r.jsx)(v,{color:"var(--ifm-color-emphasis-600)"})})}function G(){return(0,r.jsxs)("div",{className:"navbar-sidebar__brand",children:[(0,r.jsx)(q,{}),(0,r.jsx)(B,{className:"margin-right--md"}),(0,r.jsx)(H,{})]})}var V=n(5310),W=n(6497),Q=n(8310);function K(e,t){return void 0!==e&&void 0!==t&&RegExp(e,"gi").test(t)}var Y=n(7715);function Z({activeBasePath:e,activeBaseRegex:t,to:n,href:a,label:o,html:i,isDropdownLink:l,prependBaseUrlToHref:s,...c}){let u=(0,W.Ay)(n),d=(0,W.Ay)(e),p=(0,W.Ay)(a,{forcePrependBaseUrl:!0}),f=o&&a&&!(0,Q.A)(a),m=i?{dangerouslySetInnerHTML:{__html:i}}:{children:(0,r.jsxs)(r.Fragment,{children:[o,f&&(0,r.jsx)(Y.A,{...l&&{width:12,height:12}})]})};return a?(0,r.jsx)(V.A,{href:s?p:a,...c,...m}):(0,r.jsx)(V.A,{to:u,isNavLink:!0,...(e||t)&&{isActive:(e,n)=>t?K(t,n.pathname):n.pathname.startsWith(d)},...c,...m})}function X({className:e,isDropdownItem:t,...n}){return(0,r.jsx)("li",{className:"menu__list-item",children:(0,r.jsx)(Z,{className:(0,o.A)("menu__link",e),...n})})}function J({className:e,isDropdownItem:t=!1,...n}){let a=(0,r.jsx)(Z,{className:(0,o.A)(t?"dropdown__link":"navbar__item navbar__link",e),isDropdownLink:t,...n});return t?(0,r.jsx)("li",{children:a}):a}function ee({mobile:e=!1,position:t,...n}){return(0,r.jsx)(e?X:J,{...n,activeClassName:n.activeClassName??(e?"menu__link--active":"navbar__link--active")})}var et=n(9510),en=n(9209),er=n(898);function ea({collapsed:e,onClick:t}){return(0,r.jsx)("button",{"aria-label":e?(0,c.T)({id:"theme.navbar.mobileDropdown.collapseButton.expandAriaLabel",message:"Expand the dropdown",description:"The ARIA label of the button to expand the mobile dropdown navbar item"}):(0,c.T)({id:"theme.navbar.mobileDropdown.collapseButton.collapseAriaLabel",message:"Collapse the dropdown",description:"The ARIA label of the button to collapse the mobile dropdown navbar item"}),"aria-expanded":!e,type:"button",className:"clean-btn menu__caret",onClick:t})}function eo({items:e,className:t,position:n,onClick:i,...l}){let c=function(){let{siteConfig:{baseUrl:e}}=(0,er.A)(),{pathname:t}=(0,s.zy)();return t.replace(e,"/")}(),u=(0,en.ys)(l.to,c),d=e.some(e=>!!((0,en.ys)(e.to,c)||K(e.activeBaseRegex,c)||e.activeBasePath&&c.startsWith(e.activeBasePath))),{collapsed:p,toggleCollapsed:f}=function({active:e}){let{collapsed:t,toggleCollapsed:n,setCollapsed:r}=(0,et.u)({initialState:()=>!e});return(0,a.useEffect)(()=>{e&&r(!1)},[e,r]),{collapsed:t,toggleCollapsed:n}}({active:u||d}),m=l.to?void 0:"#";return(0,r.jsxs)("li",{className:(0,o.A)("menu__list-item",{"menu__list-item--collapsed":p}),children:[(0,r.jsxs)("div",{className:(0,o.A)("menu__list-item-collapsible",{"menu__list-item-collapsible--active":u}),children:[(0,r.jsx)(Z,{role:"button",className:(0,o.A)("dropdownNavbarItemMobile_J0Sd","menu__link menu__link--sublist",t),href:m,...l,onClick:e=>{"#"===m&&e.preventDefault(),f()},children:l.children??l.label}),(0,r.jsx)(ea,{collapsed:p,onClick:e=>{e.preventDefault(),f()}})]}),(0,r.jsx)(et.N,{lazy:!0,as:"ul",className:"menu__list",collapsed:p,children:e.map((e,t)=>(0,a.createElement)(ey,{mobile:!0,isDropdownItem:!0,onClick:i,activeClassName:"menu__link--active",...e,key:t}))})]})}function ei({items:e,position:t,className:n,onClick:i,...l}){let s=(0,a.useRef)(null),[c,u]=(0,a.useState)(!1);return(0,a.useEffect)(()=>{let e=e=>{!s.current||s.current.contains(e.target)||u(!1)};return document.addEventListener("mousedown",e),document.addEventListener("touchstart",e),document.addEventListener("focusin",e),()=>{document.removeEventListener("mousedown",e),document.removeEventListener("touchstart",e),document.removeEventListener("focusin",e)}},[s]),(0,r.jsxs)("div",{ref:s,className:(0,o.A)("navbar__item","dropdown","dropdown--hoverable",{"dropdown--right":"right"===t,"dropdown--show":c}),children:[(0,r.jsx)(Z,{"aria-haspopup":"true","aria-expanded":c,role:"button",href:l.to?void 0:"#",className:(0,o.A)("navbar__link",n),...l,onClick:l.to?void 0:e=>e.preventDefault(),onKeyDown:e=>{("Enter"===e.key||" "===e.key)&&(e.preventDefault(),u(!c))},children:l.children??l.label}),(0,r.jsx)("ul",{className:"dropdown__menu",children:e.map((e,t)=>(0,a.createElement)(ey,{isDropdownItem:!0,activeClassName:"dropdown__link--active",...e,key:t}))})]})}function el({mobile:e=!1,...t}){return(0,r.jsx)(e?eo:ei,{...t})}var es=n(5243),ec=n(6629);function eu({width:e=20,height:t=20,...n}){return(0,r.jsx)("svg",{viewBox:"0 0 24 24",width:e,height:t,"aria-hidden":!0,...n,children:(0,r.jsx)("path",{fill:"currentColor",d:"M12.87 15.07l-2.54-2.51.03-.03c1.74-1.94 2.98-4.17 3.71-6.53H17V4h-7V2H8v2H1v1.99h11.17C11.5 7.92 10.44 9.75 9 11.35 8.07 10.32 7.3 9.19 6.69 8h-2c.73 1.63 1.73 3.17 2.98 4.56l-5.09 5.02L4 19l5-5 3.11 3.11.76-2.04zM18.5 10h-2L12 22h2l1.12-3h4.75L21 22h2l-4.5-12zm-2.62 7l1.62-4.33L19.12 17h-3.24z"})})}var ed=n(9034);function ep({children:e,className:t}){return(0,r.jsx)("div",{className:(0,o.A)(t,"navbarSearchContainer_Bca1"),children:e})}var ef=n(698),em=n(6877),eg=n(2086);function eh(e,t){return t.alternateDocVersions[e.name]??e.docs.find(t=>t.id===e.mainDocId)}let eb={default:ee,localeDropdown:function({mobile:e,dropdownItemsBefore:t,dropdownItemsAfter:n,queryString:a,...o}){let i=function(){let{siteConfig:e,i18n:{localeConfigs:t}}=(0,er.A)(),n=(0,es.o)(),r=(0,ec.Hl)(e=>e.location.search),a=(0,ec.Hl)(e=>e.location.hash),o=e=>{let n=t[e];if(!n)throw Error(`Docusaurus bug, no locale config found for locale=${e}`);return n};return{getURL:(t,i)=>{let l=(0,ec.jy)([r,i.queryString],"append");return`${o(t).url===e.url?`pathname://${n.createUrl({locale:t,fullyQualified:!1})}`:n.createUrl({locale:t,fullyQualified:!0})}${l}${a}`},getLabel:e=>o(e).label,getLang:e=>o(e).htmlLang}}(),{i18n:{currentLocale:l,locales:s}}=(0,er.A)(),u=[...t,...s.map(t=>({label:i.getLabel(t),lang:i.getLang(t),to:i.getURL(t,{queryString:a}),target:"_self",autoAddBaseUrl:!1,className:t===l?e?"menu__link--active":"dropdown__link--active":""})),...n],d=e?(0,c.T)({message:"Languages",id:"theme.navbar.mobileLanguageDropdown.label",description:"The label for the mobile language switcher dropdown"}):i.getLabel(l);return(0,r.jsx)(el,{...o,mobile:e,label:(0,r.jsxs)(r.Fragment,{children:[(0,r.jsx)(eu,{className:"iconLanguage_nlXk"}),d]}),items:u})},search:function({mobile:e,className:t}){return e?null:(0,r.jsx)(ep,{className:t,children:(0,r.jsx)(ed.A,{})})},dropdown:el,html:function({value:e,className:t,mobile:n=!1,isDropdownItem:a=!1}){return(0,r.jsx)(a?"li":"div",{className:(0,o.A)({navbar__item:!n&&!a,"menu__list-item":n},t),dangerouslySetInnerHTML:{__html:e}})},doc:function({docId:e,label:t,docsPluginId:n,...a}){let{activeDoc:o}=(0,ef.zK)(n),i=(0,em.QB)(e,n),l=o?.path===i?.path;return null===i||i.unlisted&&!l?null:(0,r.jsx)(ee,{exact:!0,...a,isActive:()=>l||!!o?.sidebar&&o.sidebar===i.sidebar,label:t??i.id,to:i.path})},docSidebar:function({sidebarId:e,label:t,docsPluginId:n,...a}){let{activeDoc:o}=(0,ef.zK)(n),i=(0,em.fW)(e,n).link;if(!i)throw Error(`DocSidebarNavbarItem: Sidebar with ID "${e}" doesn't have anything to be linked to.`);return(0,r.jsx)(ee,{exact:!0,...a,isActive:()=>o?.sidebar===e,label:t??i.label,to:i.path})},docsVersion:function({label:e,to:t,docsPluginId:n,...a}){let o=(0,em.Vd)(n)[0],i=e??o.label,l=t??o.docs.find(e=>e.id===o.mainDocId).path;return(0,r.jsx)(ee,{...a,label:i,to:l})},docsVersionDropdown:function({mobile:e,docsPluginId:t,dropdownActiveClassDisabled:n,dropdownItemsBefore:a,dropdownItemsAfter:o,versions:i,...l}){let s=(0,ec.Hl)(e=>e.location.search),u=(0,ec.Hl)(e=>e.location.hash),d=(0,ef.zK)(t),{savePreferredVersionName:p}=(0,eg.g1)(t),f=function({docsPluginId:e,configs:t}){var n=(0,ef.jh)(e);if(!t)return n.map(e=>({version:e,label:e.label}));{let e=new Map(n.map(e=>[e.name,e])),r=(t,r)=>{let a=e.get(t);if(!a)throw Error(`No docs version exist for name '${t}', please verify your 'docsVersionDropdown' navbar item versions config. +Available version names: +- ${n.map(e=>`${e.name}`).join("\n- ")}`);return{version:a,label:r?.label??a.label}};return Array.isArray(t)?t.map(e=>r(e,void 0)):Object.entries(t).map(([e,t])=>r(e,t))}}({docsPluginId:t,configs:i}),m=function({docsPluginId:e,versionItems:t}){return(0,em.Vd)(e).map(e=>t.find(t=>t.version===e)).filter(e=>void 0!==e)[0]??t[0]}({docsPluginId:t,versionItems:f}),g=[...a,...f.map(function({version:e,label:t}){let n=eh(e,d);return{label:t,to:`${n.path}${s}${u}`,isActive:()=>e===d.activeVersion,onClick:()=>p(e.name)}}),...o],h=e&&g.length>1?(0,c.T)({id:"theme.navbar.mobileVersionsDropdown.label",message:"Versions",description:"The label for the navbar versions dropdown on mobile view"}):m.label,b=e&&g.length>1?void 0:eh(m.version,d).path;return g.length<=1?(0,r.jsx)(ee,{...l,mobile:e,label:h,to:b,isActive:n?()=>!1:void 0}):(0,r.jsx)(el,{...l,mobile:e,label:h,to:b,items:g,isActive:n?()=>!1:void 0})}};function ey({type:e,...t}){let n=eb[!e||"default"===e?"items"in t?"dropdown":"default":e];if(!n)throw Error(`No NavbarItem component found for type "${e}".`);return(0,r.jsx)(n,{...t})}function ev(){let e=(0,S.M)(),t=(0,b.p)().navbar.items;return(0,r.jsx)("ul",{className:"menu__list",children:t.map((t,n)=>(0,a.createElement)(ey,{mobile:!0,...t,onClick:()=>e.toggle(),key:n}))})}function ew(e){return(0,r.jsx)("button",{...e,type:"button",className:"clean-btn navbar-sidebar__back",children:(0,r.jsx)(c.A,{id:"theme.navbar.mobileSidebarSecondaryMenu.backButtonLabel",description:"The label of the back button to return to main menu, inside the mobile navbar sidebar secondary menu (notably used to display the docs sidebar)",children:"\u2190 Back to main menu"})})}function ek(){let e=0===(0,b.p)().navbar.items.length,t=j();return(0,r.jsxs)(r.Fragment,{children:[!e&&(0,r.jsx)(ew,{onClick:()=>t.hide()}),t.content]})}function ex(){let e=(0,S.M)();return(!function(e=!0){(0,a.useEffect)(()=>(document.body.style.overflow=e?"hidden":"visible",()=>{document.body.style.overflow="visible"}),[e])}(e.shown),e.shouldRender)?(0,r.jsx)(z,{header:(0,r.jsx)(G,{}),primaryMenu:(0,r.jsx)(ev,{}),secondaryMenu:(0,r.jsx)(ek,{})}):null}function eS(e){return(0,r.jsx)("div",{role:"presentation",...e,className:(0,o.A)("navbar-sidebar__backdrop",e.className)})}function eE({children:e}){let{navbar:{hideOnScroll:t,style:n}}=(0,b.p)(),i=(0,S.M)(),{navbarRef:l,isNavbarVisible:s}=function(e){let[t,n]=(0,a.useState)(e),r=(0,a.useRef)(!1),o=(0,a.useRef)(0),i=(0,a.useCallback)(e=>{null!==e&&(o.current=e.getBoundingClientRect().height)},[]);return(0,E.Mq)(({scrollY:t},a)=>{if(!e)return;if(t<o.current)return void n(!0);if(r.current){r.current=!1;return}let i=a?.scrollY,l=document.documentElement.scrollHeight-o.current,s=window.innerHeight;i&&t>=i?n(!1):t+s<l&&n(!0)}),(0,u.$)(t=>{if(!e)return;let a=t.location.hash;if(a?document.getElementById(a.substring(1)):void 0){r.current=!0,n(!1);return}n(!0)}),{navbarRef:i,isNavbarVisible:t}}(t);return(0,r.jsxs)("nav",{ref:l,"aria-label":(0,c.T)({id:"theme.NavBar.navAriaLabel",message:"Main",description:"The ARIA label for the main navigation"}),className:(0,o.A)(g.G.layout.navbar.container,"navbar","navbar--fixed-top",t&&["navbarHideable_m1mJ",!s&&"navbarHidden_jGov"],{"navbar--dark":"dark"===n,"navbar--primary":"primary"===n,"navbar-sidebar--show":i.shown}),children:[e,(0,r.jsx)(eS,{onClick:i.toggle}),(0,r.jsx)(ex,{})]})}var e_=n(5248);function eA(e){return(0,r.jsx)("button",{type:"button",...e,children:(0,r.jsx)(c.A,{id:"theme.ErrorPageContent.tryAgain",description:"The label of the button to try again rendering when the React error boundary captures an error",children:"Try again"})})}function eC({error:e}){let t=(0,e_.getErrorCausalChain)(e).map(e=>e.message).join("\n\nCause:\n");return(0,r.jsx)("p",{className:"errorBoundaryError_a6uf",children:t})}class eT extends a.Component{componentDidCatch(e,t){throw this.props.onError(e,t)}render(){return this.props.children}}function ej({width:e=30,height:t=30,className:n,...a}){return(0,r.jsx)("svg",{className:n,width:e,height:t,viewBox:"0 0 30 30","aria-hidden":"true",...a,children:(0,r.jsx)("path",{stroke:"currentColor",strokeLinecap:"round",strokeMiterlimit:"10",strokeWidth:"2",d:"M4 7h22M4 15h22M4 23h22"})})}function eP(){let{toggle:e,shown:t}=(0,S.M)();return(0,r.jsx)("button",{onClick:e,"aria-label":(0,c.T)({id:"theme.docs.sidebar.toggleSidebarButtonAriaLabel",message:"Toggle navigation bar",description:"The ARIA label for hamburger menu button of mobile navigation"}),"aria-expanded":t,className:"navbar__toggle clean-btn",type:"button",children:(0,r.jsx)(ej,{})})}function ez({items:e}){return(0,r.jsx)(r.Fragment,{children:e.map((e,t)=>(0,r.jsx)(eT,{onError:t=>Error(`A theme navbar item failed to render. +Please double-check the following navbar item (themeConfig.navbar.items) of your Docusaurus config: +${JSON.stringify(e,null,2)}`,{cause:t}),children:(0,r.jsx)(ey,{...e})},t))})}function eL({left:e,right:t}){return(0,r.jsxs)("div",{className:"navbar__inner",children:[(0,r.jsx)("div",{className:(0,o.A)(g.G.layout.navbar.containerLeft,"navbar__items"),children:e}),(0,r.jsx)("div",{className:(0,o.A)(g.G.layout.navbar.containerRight,"navbar__items navbar__items--right"),children:t})]})}function eN(){let e=(0,S.M)(),t=(0,b.p)().navbar.items,[n,a]=function(e){function t(e){return(e.position??"right")==="left"}return[e.filter(t),e.filter(e=>!t(e))]}(t),o=t.find(e=>"search"===e.type);return(0,r.jsx)(eL,{left:(0,r.jsxs)(r.Fragment,{children:[!e.disabled&&(0,r.jsx)(eP,{}),(0,r.jsx)(q,{}),(0,r.jsx)(ez,{items:n})]}),right:(0,r.jsxs)(r.Fragment,{children:[(0,r.jsx)(ez,{items:a}),(0,r.jsx)(B,{className:"colorModeToggle_DEke"}),!o&&(0,r.jsx)(ep,{children:(0,r.jsx)(ed.A,{})})]})})}function eO(){return(0,r.jsx)(eE,{children:(0,r.jsx)(eN,{})})}function eR({item:e}){let{to:t,href:n,label:a,prependBaseUrlToHref:i,className:l,...s}=e,c=(0,W.Ay)(t),u=(0,W.Ay)(n,{forcePrependBaseUrl:!0});return(0,r.jsxs)(V.A,{className:(0,o.A)("footer__link-item",l),...n?{href:i?u:n}:{to:c},...s,children:[a,n&&!(0,Q.A)(n)&&(0,r.jsx)(Y.A,{})]})}function eD({item:e}){return e.html?(0,r.jsx)("li",{className:(0,o.A)("footer__item",e.className),dangerouslySetInnerHTML:{__html:e.html}}):(0,r.jsx)("li",{className:"footer__item",children:(0,r.jsx)(eR,{item:e})},e.href??e.to)}function eF({column:e}){return(0,r.jsxs)("div",{className:(0,o.A)(g.G.layout.footer.column,"col footer__col",e.className),children:[(0,r.jsx)("div",{className:"footer__title",children:e.title}),(0,r.jsx)("ul",{className:"footer__items clean-list",children:e.items.map((e,t)=>(0,r.jsx)(eD,{item:e},t))})]})}function eI({columns:e}){return(0,r.jsx)("div",{className:"row footer__links",children:e.map((e,t)=>(0,r.jsx)(eF,{column:e},t))})}function eM(){return(0,r.jsx)("span",{className:"footer__link-separator",children:"\xb7"})}function e$({item:e}){return e.html?(0,r.jsx)("span",{className:(0,o.A)("footer__link-item",e.className),dangerouslySetInnerHTML:{__html:e.html}}):(0,r.jsx)(eR,{item:e})}function eB({links:e}){return(0,r.jsx)("div",{className:"footer__links text--center",children:(0,r.jsx)("div",{className:"footer__links",children:e.map((t,n)=>(0,r.jsxs)(a.Fragment,{children:[(0,r.jsx)(e$,{item:t}),e.length!==n+1&&(0,r.jsx)(eM,{})]},n))})})}function eU({links:e}){return"title"in e[0]?(0,r.jsx)(eI,{columns:e}):(0,r.jsx)(eB,{links:e})}var eq=n(8586);function eH({logo:e}){let{withBaseUrl:t}=(0,W.hH)(),n={light:t(e.src),dark:t(e.srcDark??e.src)};return(0,r.jsx)(eq.A,{className:(0,o.A)("footer__logo",e.className),alt:e.alt,sources:n,width:e.width,height:e.height,style:e.style})}function eG({logo:e}){return e.href?(0,r.jsx)(V.A,{href:e.href,className:"footerLogoLink_BH7S",target:e.target,children:(0,r.jsx)(eH,{logo:e})}):(0,r.jsx)(eH,{logo:e})}function eV({copyright:e}){return(0,r.jsx)("div",{className:"footer__copyright",dangerouslySetInnerHTML:{__html:e}})}function eW({style:e,links:t,logo:n,copyright:a}){return(0,r.jsx)("footer",{className:(0,o.A)(g.G.layout.footer.container,"footer",{"footer--dark":"dark"===e}),children:(0,r.jsxs)("div",{className:"container container-fluid",children:[t,(n||a)&&(0,r.jsxs)("div",{className:"footer__bottom text--center",children:[n&&(0,r.jsx)("div",{className:"margin-bottom--sm",children:n}),a]})]})})}let eQ=a.memo(function(){let{footer:e}=(0,b.p)();if(!e)return null;let{copyright:t,links:n,logo:a,style:o}=e;return(0,r.jsx)(eW,{style:o,links:n&&n.length>0&&(0,r.jsx)(eU,{links:n}),logo:a&&(0,r.jsx)(eG,{logo:a}),copyright:t&&(0,r.jsx)(eV,{copyright:t})})}),eK=(0,_.fM)([L.a,y.o,E.Tv,eg.VQ,l.Jx,function({children:e}){return(0,r.jsx)(A.y_,{children:(0,r.jsx)(S.e,{children:(0,r.jsx)(T,{children:e})})})}]);function eY({children:e}){return(0,r.jsx)(eK,{children:e})}var eZ=n(2072);function eX({error:e,tryAgain:t}){return(0,r.jsx)("main",{className:"container margin-vert--xl",children:(0,r.jsx)("div",{className:"row",children:(0,r.jsxs)("div",{className:"col col--6 col--offset-3",children:[(0,r.jsx)(eZ.A,{as:"h1",className:"hero__title",children:(0,r.jsx)(c.A,{id:"theme.ErrorPageContent.title",description:"The title of the fallback page when the page crashed",children:"This page crashed."})}),(0,r.jsx)("div",{className:"margin-vert--lg",children:(0,r.jsx)(eA,{onClick:t,className:"button button--primary shadow--lw"})}),(0,r.jsx)("hr",{}),(0,r.jsx)("div",{className:"margin-vert--md",children:(0,r.jsx)(eC,{error:e})})]})})})}function eJ(e){let{children:t,noFooter:n,wrapperClassName:a,title:s,description:c}=e;return(0,r.jsxs)(eY,{children:[(0,r.jsx)(l.be,{title:s,description:c}),(0,r.jsx)(h,{}),(0,r.jsx)(x,{}),(0,r.jsx)(eO,{}),(0,r.jsx)("div",{id:d,className:(0,o.A)(g.G.layout.main.container,g.G.wrapper.main,"mainWrapper_z2l0",a),children:(0,r.jsx)(i.A,{fallback:e=>(0,r.jsx)(eX,{...e}),children:t})}),!n&&(0,r.jsx)(eQ,{})]})}},5153(e,t,n){"use strict";n.d(t,{A:()=>u});var r=n(4848);n(6540);var a=n(5310),o=n(6497),i=n(898),l=n(1022),s=n(8586);function c({logo:e,alt:t,imageClassName:n}){let a={light:(0,o.Ay)(e.src),dark:(0,o.Ay)(e.srcDark||e.src)},i=(0,r.jsx)(s.A,{className:e.className,sources:a,height:e.height,width:e.width,alt:t,style:e.style});return n?(0,r.jsx)("div",{className:n,children:i}):i}function u(e){let{siteConfig:{title:t}}=(0,i.A)(),{navbar:{title:n,logo:s}}=(0,l.p)(),{imageClassName:u,titleClassName:d,...p}=e,f=(0,o.Ay)(s?.href||"/"),m=s?.alt??(n?"":t);return(0,r.jsxs)(a.A,{to:f,...p,...s?.target&&{target:s.target},children:[s&&(0,r.jsx)(c,{logo:s,alt:m,imageClassName:u}),null!=n&&(0,r.jsx)("b",{className:d,children:n})]})}},4175(e,t,n){"use strict";n.d(t,{A:()=>o});var r=n(4848);n(6540);var a=n(3572);function o({locale:e,version:t,tag:n}){return(0,r.jsxs)(a.A,{children:[e&&(0,r.jsx)("meta",{name:"docusaurus_locale",content:e}),t&&(0,r.jsx)("meta",{name:"docusaurus_version",content:t}),n&&(0,r.jsx)("meta",{name:"docusaurus_tag",content:n}),e&&(0,r.jsx)("meta",{name:"docsearch:language",content:e}),t&&(0,r.jsx)("meta",{name:"docsearch:version",content:t}),n&&(0,r.jsx)("meta",{name:"docsearch:docusaurus_tag",content:n})]})}},8586(e,t,n){"use strict";n.d(t,{A:()=>u});var r=n(4848),a=n(6540),o=n(4164),i=n(9863),l=n(3941);let s={themedComponent:"themedComponent_mlkZ","themedComponent--light":"themedComponent--light_NVdE","themedComponent--dark":"themedComponent--dark_xIcU"};function c({className:e,children:t}){let n=(0,i.A)(),{colorMode:u}=(0,l.G)();return(0,r.jsx)(r.Fragment,{children:(n?"dark"===u?["dark"]:["light"]:["light","dark"]).map(n=>{let i=t({theme:n,className:(0,o.A)(e,s.themedComponent,s[`themedComponent--${n}`])});return(0,r.jsx)(a.Fragment,{children:i},n)})})}function u(e){let{sources:t,className:n,alt:a,...o}=e;return(0,r.jsx)(c,{className:n,children:({theme:e,className:n})=>(0,r.jsx)("img",{src:t[e],alt:a,className:n,...o})})}},9510(e,t,n){"use strict";n.d(t,{N:()=>f,u:()=>l});var r=n(4848),a=n(6540),o=n(9989),i=n(269);function l({initialState:e}){let[t,n]=(0,a.useState)(e??!1),r=(0,a.useCallback)(()=>{n(e=>!e)},[]);return{collapsed:t,setCollapsed:n,toggleCollapsed:r}}let s={display:"none",overflow:"hidden",height:"0px"},c={display:"block",overflow:"visible",height:"auto"};function u(e,t){let n=t?s:c;e.style.display=n.display,e.style.overflow=n.overflow,e.style.height=n.height}function d({as:e="div",collapsed:t,children:n,animation:o,onCollapseTransitionEnd:l,className:c}){let p=(0,a.useRef)(null);return!function({collapsibleRef:e,collapsed:t,animation:n}){let r=(0,a.useRef)(!1);(0,a.useEffect)(()=>{let a,o=e.current;function l(){let e,t,r,a=(e=o.scrollHeight,t=n?.duration??function(e){if((0,i.O)())return 1;let t=e/36;return Math.round((4+15*t**.25+t/5)*10)}(e),r=n?.easing??"ease-in-out",{transition:`height ${t}ms ${r}`,height:`${e}px`});o.style.transition=a.transition,o.style.height=a.height}if(!r.current){u(o,t),r.current=!0;return}return o.style.willChange="height",a=requestAnimationFrame(()=>{t?(l(),requestAnimationFrame(()=>{o.style.height=s.height,o.style.overflow=s.overflow})):(o.style.display="block",requestAnimationFrame(()=>{l()}))}),()=>cancelAnimationFrame(a)},[e,t,n])}({collapsibleRef:p,collapsed:t,animation:o}),(0,r.jsx)(e,{ref:p,onTransitionEnd:e=>{"height"===e.propertyName&&(u(p.current,t),l?.(t))},className:c,children:n})}function p({collapsed:e,...t}){let[n,i]=(0,a.useState)(!e),[l,s]=(0,a.useState)(e);return(0,o.A)(()=>{e||i(!0)},[e]),(0,o.A)(()=>{n&&s(e)},[n,e]),n?(0,r.jsx)(d,{...t,collapsed:l}):null}function f({lazy:e,...t}){return(0,r.jsx)(e?p:d,{...t})}},3337(e,t,n){"use strict";n.d(t,{M:()=>m,o:()=>f});var r=n(4848),a=n(6540),o=n(9863),i=n(1367),l=n(2260),s=n(1022);let c=(0,i.Wf)("docusaurus.announcement.dismiss"),u=(0,i.Wf)("docusaurus.announcement.id"),d=e=>c.set(String(e)),p=a.createContext(null);function f({children:e}){let t=function(){let{announcementBar:e}=(0,s.p)(),t=(0,o.A)(),[n,r]=(0,a.useState)(()=>!!t&&"true"===c.get());(0,a.useEffect)(()=>{r("true"===c.get())},[]);let i=(0,a.useCallback)(()=>{d(!0),r(!0)},[]);return(0,a.useEffect)(()=>{if(!e)return;let{id:t}=e,n=u.get();"annoucement-bar"===n&&(n="announcement-bar");let a=t!==n;u.set(t),a&&d(!1),(a||"true"!==c.get())&&r(!1)},[e]),(0,a.useMemo)(()=>({isActive:!!e&&!n,close:i}),[e,n,i])}();return(0,r.jsx)(p.Provider,{value:t,children:e})}function m(){let e=(0,a.useContext)(p);if(!e)throw new l.dV("AnnouncementBarProvider");return e}},3941(e,t,n){"use strict";n.d(t,{G:()=>w,a:()=>v});var r=n(4848),a=n(6540),o=n(9863),i=n(2260),l=n(1367),s=n(1022);function c(){return window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light"}let u=a.createContext(void 0),d=(0,l.Wf)("theme"),p="system",f=e=>"dark"===e?"dark":"light",m=e=>null===e||e===p?null:f(e),g=()=>f(document.documentElement.getAttribute("data-theme")),h=e=>{document.documentElement.setAttribute("data-theme",f(e))},b=()=>m(document.documentElement.getAttribute("data-theme-choice")),y=e=>{document.documentElement.setAttribute("data-theme-choice",m(e)??p)};function v({children:e}){let t=function(){let{colorMode:{defaultMode:e,disableSwitch:t,respectPrefersColorScheme:n}}=(0,s.p)(),{colorMode:r,setColorModeState:i,colorModeChoice:l,setColorModeChoiceState:u}=function(){let{colorMode:{defaultMode:e}}=(0,s.p)(),t=(0,o.A)(),[n,r]=(0,a.useState)(t?g():e),[i,l]=(0,a.useState)(t?b():null);return(0,a.useEffect)(()=>{r(g()),l(b())},[]),{colorMode:n,setColorModeState:r,colorModeChoice:i,setColorModeChoiceState:l}}();(0,a.useEffect)(()=>{t&&d.del()},[t]);let p=(0,a.useCallback)((t,r={})=>{let{persist:a=!0}=r;if(null===t){let t=n?c():e;h(t),i(t),y(null),u(null)}else h(t),y(t),i(t),u(t);a&&(null===t?d.del():d.set(f(t)))},[i,u,n,e]);return(0,a.useEffect)(()=>d.listen(e=>{p(m(e.newValue))}),[p]),(0,a.useEffect)(()=>{if(null===l&&n){var e,t;let n;return e=e=>{i(e),h(e)},t=()=>e(c()),(n=window.matchMedia("(prefers-color-scheme: dark)")).addEventListener("change",t),()=>n.removeEventListener("change",t)}},[n,l,i]),(0,a.useMemo)(()=>({colorMode:r,colorModeChoice:l,setColorMode:p,get isDarkTheme(){return"dark"===r},setLightTheme(){p("light")},setDarkTheme(){p("dark")}}),[r,l,p])}();return(0,r.jsx)(u.Provider,{value:t,children:e})}function w(){let e=(0,a.useContext)(u);if(null==e)throw new i.dV("ColorModeProvider","Please see https://docusaurus.io/docs/api/themes/configuration#use-color-mode.");return e}},9965(e,t,n){"use strict";n.d(t,{M:()=>f,e:()=>p});var r=n(4848),a=n(6540),o=n(7e3),i=n(3741),l=n(6629),s=n(1022),c=n(2260);let u=a.createContext(void 0);function d({handler:e}){return(0,l.$Z)(e),null}function p({children:e}){let t=function(){let e=function(){let e=(0,o.YL)(),{items:t}=(0,s.p)().navbar;return 0===t.length&&!e.component}(),t=(0,i.l)(),n=!e&&"mobile"===t,[r,l]=(0,a.useState)(!1),c=(0,a.useCallback)(()=>{l(e=>!e)},[]);return(0,a.useEffect)(()=>{"desktop"===t&&l(!1)},[t]),(0,a.useMemo)(()=>({disabled:e,shouldRender:n,toggle:c,shown:r}),[e,n,c,r])}();return(0,r.jsxs)(r.Fragment,{children:[t.shown&&(0,r.jsx)(d,{handler:()=>(t.toggle(),!1)}),(0,r.jsx)(u.Provider,{value:t,children:e})]})}function f(){let e=a.useContext(u);if(void 0===e)throw new c.dV("NavbarMobileSidebarProvider");return e}},7e3(e,t,n){"use strict";n.d(t,{GX:()=>c,YL:()=>s,y_:()=>l});var r=n(4848),a=n(6540),o=n(2260);let i=a.createContext(null);function l({children:e}){let t=(0,a.useState)({component:null,props:null});return(0,r.jsx)(i.Provider,{value:t,children:e})}function s(){let e=(0,a.useContext)(i);if(!e)throw new o.dV("NavbarSecondaryMenuContentProvider");return e[0]}function c({component:e,props:t}){let n=(0,a.useContext)(i);if(!n)throw new o.dV("NavbarSecondaryMenuContentProvider");let[,r]=n,l=(0,o.Be)(t);return(0,a.useEffect)(()=>{r({component:e,props:l})},[r,e,l]),(0,a.useEffect)(()=>()=>r({component:null,props:null}),[r]),null}},3741(e,t,n){"use strict";n.d(t,{l:()=>o});var r=n(6540),a=n(1289);function o({desktopBreakpoint:e=996}={}){let[t,n]=(0,r.useState)(()=>"ssr");return(0,r.useEffect)(()=>{function t(){n(function(e){if(!a.A.canUseDOM)throw Error("getWindowSize() should only be called after React hydration");return window.innerWidth>e?"desktop":"mobile"}(e))}return t(),window.addEventListener("resize",t),()=>{window.removeEventListener("resize",t)}},[e]),t}},8287(e,t,n){"use strict";n.d(t,{G:()=>r});let r={page:{blogListPage:"blog-list-page",blogPostPage:"blog-post-page",blogTagsListPage:"blog-tags-list-page",blogTagPostListPage:"blog-tags-post-list-page",blogAuthorsListPage:"blog-authors-list-page",blogAuthorsPostsPage:"blog-authors-posts-page",docsDocPage:"docs-doc-page",docsTagsListPage:"docs-tags-list-page",docsTagDocListPage:"docs-tags-doc-list-page",mdxPage:"mdx-page"},wrapper:{main:"main-wrapper",blogPages:"blog-wrapper",docsPages:"docs-wrapper",mdxPages:"mdx-wrapper"},common:{editThisPage:"theme-edit-this-page",lastUpdated:"theme-last-updated",backToTopButton:"theme-back-to-top-button",codeBlock:"theme-code-block",admonition:"theme-admonition",unlistedBanner:"theme-unlisted-banner",draftBanner:"theme-draft-banner",admonitionType:e=>`theme-admonition-${e}`},announcementBar:{container:"theme-announcement-bar"},tabs:{container:"theme-tabs-container"},layout:{navbar:{container:"theme-layout-navbar",containerLeft:"theme-layout-navbar-left",containerRight:"theme-layout-navbar-right",mobileSidebar:{container:"theme-layout-navbar-sidebar",panel:"theme-layout-navbar-sidebar-panel"}},main:{container:"theme-layout-main"},footer:{container:"theme-layout-footer",column:"theme-layout-footer-column"}},docs:{docVersionBanner:"theme-doc-version-banner",docVersionBadge:"theme-doc-version-badge",docBreadcrumbs:"theme-doc-breadcrumbs",docMarkdown:"theme-doc-markdown",docTocMobile:"theme-doc-toc-mobile",docTocDesktop:"theme-doc-toc-desktop",docFooter:"theme-doc-footer",docFooterTagsRow:"theme-doc-footer-tags-row",docFooterEditMetaRow:"theme-doc-footer-edit-meta-row",docSidebarContainer:"theme-doc-sidebar-container",docSidebarMenu:"theme-doc-sidebar-menu",docSidebarItemCategory:"theme-doc-sidebar-item-category",docSidebarItemLink:"theme-doc-sidebar-item-link",docSidebarItemCategoryLevel:e=>`theme-doc-sidebar-item-category-level-${e}`,docSidebarItemLinkLevel:e=>`theme-doc-sidebar-item-link-level-${e}`,docCard:{container:"theme-doc-card-container",heading:"theme-doc-card-heading",icon:"theme-doc-card-icon",title:"theme-doc-card-title",description:"theme-doc-card-description"}},blog:{blogFooterTagsRow:"theme-blog-footer-tags-row",blogFooterEditMetaRow:"theme-blog-footer-edit-meta-row"},pages:{pageFooterEditMetaRow:"theme-pages-footer-edit-meta-row"}}},269(e,t,n){"use strict";function r(){return window.matchMedia("(prefers-reduced-motion: reduce)").matches}n.d(t,{O:()=>r})},4727(e,t,n){"use strict";n.d(t,{v:()=>a});var r=n(1022);function a(e){let{navbar:{hideOnScroll:t}}=(0,r.p)();if(void 0!==e)return t?"anchorTargetHideOnScrollNavbar_vjPI":"anchorTargetStickyNavbar_Vzrq"}},6629(e,t,n){"use strict";n.d(t,{$Z:()=>i,Hl:()=>l,jy:()=>s});var r=n(6540),a=n(6347),o=n(2260);function i(e){let t,n;t=(0,a.W6)(),n=(0,o._q)((t,n)=>{if("POP"===n)return e(t,n)}),(0,r.useEffect)(()=>t.block((e,t)=>n(e,t)),[t,n])}function l(e){let t=(0,a.W6)();return(0,r.useSyncExternalStore)(t.listen,()=>e(t),()=>e({...t,location:{...t.location,search:"",hash:"",state:void 0}}))}function s(e,t){let n=(function(e,t){let n=new URLSearchParams;for(let r of e)for(let[e,a]of r.entries())"append"===t?n.append(e,a):n.set(e,a);return n})(e.map(e=>new URLSearchParams(e??"")),t).toString();return n?`?${n}`:n}},4308(e,t,n){"use strict";n.d(t,{Jx:()=>b,be:()=>m,e3:()=>h});var r=n(4848),a=n(6540),o=n(4164),i=n(3572),l=n(8491),s=n(6497),c=n(3899);function u({title:e}){let t=(0,c.s$)().format(e);return(0,r.jsxs)(i.A,{children:[(0,r.jsx)("title",{children:t}),(0,r.jsx)("meta",{property:"og:title",content:t})]})}function d({description:e}){return(0,r.jsxs)(i.A,{children:[(0,r.jsx)("meta",{name:"description",content:e}),(0,r.jsx)("meta",{property:"og:description",content:e})]})}function p({image:e}){let{withBaseUrl:t}=(0,s.hH)(),n=t(e,{absolute:!0});return(0,r.jsxs)(i.A,{children:[(0,r.jsx)("meta",{property:"og:image",content:n}),(0,r.jsx)("meta",{name:"twitter:image",content:n})]})}function f({keywords:e}){return(0,r.jsx)(i.A,{children:(0,r.jsx)("meta",{name:"keywords",content:Array.isArray(e)?e.join(","):e})})}function m({title:e,description:t,keywords:n,image:a,children:o}){return(0,r.jsxs)(r.Fragment,{children:[e&&(0,r.jsx)(u,{title:e}),t&&(0,r.jsx)(d,{description:t}),n&&(0,r.jsx)(f,{keywords:n}),a&&(0,r.jsx)(p,{image:a}),o&&(0,r.jsx)(i.A,{children:o})]})}let g=a.createContext(void 0);function h({className:e,children:t}){let n=a.useContext(g),l=(0,o.A)(n,e);return(0,r.jsxs)(g.Provider,{value:l,children:[(0,r.jsx)(i.A,{children:(0,r.jsx)("html",{className:l})}),t]})}function b({children:e}){var t;let n=(0,l.A)(),a=(t=n.plugin.name,`plugin-${t.replace(/docusaurus-(?:plugin|theme)-(?:content-)?/gi,"")}`),i=`plugin-id-${n.plugin.id}`;return(0,r.jsx)(h,{className:(0,o.A)(a,i),children:e})}},2260(e,t,n){"use strict";n.d(t,{Be:()=>c,ZC:()=>l,_q:()=>i,dV:()=>s,fM:()=>u});var r=n(4848),a=n(6540),o=n(9989);function i(e){let t=(0,a.useRef)(e);return(0,o.A)(()=>{t.current=e},[e]),(0,a.useCallback)((...e)=>t.current(...e),[])}function l(e){let t=(0,a.useRef)(void 0);return(0,o.A)(()=>{t.current=e}),t.current}class s extends Error{constructor(e,t){super(),this.name="ReactContextError",this.message=`Hook ${this.stack?.split("\n")[1]?.match(/at (?:\w+\.)?(?<name>\w+)/)?.groups.name??""} is called outside the <${e}>. ${t??""}`}}function c(e){let t=Object.entries(e);return t.sort((e,t)=>e[0].localeCompare(t[0])),(0,a.useMemo)(()=>e,t.flat())}function u(e){return({children:t})=>(0,r.jsx)(r.Fragment,{children:e.reduceRight((e,t)=>(0,r.jsx)(t,{children:e}),t)})}},9209(e,t,n){"use strict";n.d(t,{Dt:()=>l,ys:()=>i});var r=n(6540),a=n(8460),o=n(898);function i(e,t){let n=e=>(!e||e.endsWith("/")?e:`${e}/`)?.toLowerCase();return n(e)===n(t)}function l(){let{baseUrl:e}=(0,o.A)().siteConfig;return(0,r.useMemo)(()=>(function({baseUrl:e,routes:t}){function n(t){return t.path===e&&!0===t.exact}function r(t){return t.path===e&&!t.exact}return function e(t){if(0===t.length)return;let a=t.find(n);return a||e(t.filter(r).flatMap(e=>e.routes??[]))}(t)})({routes:a.A,baseUrl:e}),[e])}},8584(e,t,n){"use strict";n.d(t,{Mq:()=>d,Tv:()=>c,gk:()=>p});var r=n(4848),a=n(6540),o=n(1289),i=n(9863);n(9989);var l=n(2260);let s=a.createContext(void 0);function c({children:e}){let t,n=(t=(0,a.useRef)(!0),(0,a.useMemo)(()=>({scrollEventsEnabledRef:t,enableScrollEvents:()=>{t.current=!0},disableScrollEvents:()=>{t.current=!1}}),[]));return(0,r.jsx)(s.Provider,{value:n,children:e})}let u=()=>o.A.canUseDOM?{scrollX:window.pageXOffset,scrollY:window.pageYOffset}:null;function d(e,t=[]){let{scrollEventsEnabledRef:n}=function(){let e=(0,a.useContext)(s);if(null==e)throw new l.dV("ScrollControllerProvider");return e}(),r=(0,a.useRef)(u()),o=(0,l._q)(e);(0,a.useEffect)(()=>{let e=()=>{if(!n.current)return;let e=u();o(e,r.current),r.current=e},t={passive:!0};return e(),window.addEventListener("scroll",e,t),()=>window.removeEventListener("scroll",e,t)},[o,n,...t])}function p(){let e=(0,a.useRef)(null),t=(0,i.A)()&&"smooth"===getComputedStyle(document.documentElement).scrollBehavior;return{startScroll:n=>{let r,a;e.current=t?(window.scrollTo({top:n,behavior:"smooth"}),()=>{}):(r=null,a=document.documentElement.scrollTop>n,!function e(){let t=document.documentElement.scrollTop;(a&&t>n||!a&&t<n)&&(r=requestAnimationFrame(e),window.scrollTo(0,Math.floor((t-n)*.85)+n))}(),()=>r&&cancelAnimationFrame(r))},cancelScroll:()=>e.current?.()}}},1367(e,t,n){"use strict";n.d(t,{Wf:()=>s}),n(6540);var r=JSON.parse('{"N":"localStorage","M":"-597"}');let a=r.N;function o({key:e,oldValue:t,newValue:n,storage:r}){if(t===n)return;let a=document.createEvent("StorageEvent");a.initStorageEvent("storage",!1,!1,e,t,n,window.location.href,r),window.dispatchEvent(a)}let i=!1,l={get:()=>null,set:()=>{},del:()=>{},listen:()=>()=>{}};function s(e,t){let n=`${e}${r.M}`;if("u"<typeof window){function s(){throw Error(`Illegal storage API usage for storage key "${n}". +Docusaurus storage APIs are not supposed to be called on the server-rendering process. +Please only call storage APIs in effects and event handlers.`)}return{get:s,set:s,del:s,listen:s}}let c=function(e=a){if("u"<typeof window)throw Error("Browser storage is not available on Node.js/Docusaurus SSR process.");if("none"===e)return null;try{return window[e]}catch(e){var t;return t=e,i||(console.warn(`Docusaurus browser storage is not available. +Possible reasons: running Docusaurus in an iframe, in an incognito browser session, or using too strict browser privacy settings.`,t),i=!0),null}}(t?.persistence);return null===c?l:{get:()=>{try{return c.getItem(n)}catch(e){return console.error(`Docusaurus storage error, can't get key=${n}`,e),null}},set:e=>{try{let t=c.getItem(n);c.setItem(n,e),o({key:n,oldValue:t,newValue:e,storage:c})}catch(t){console.error(`Docusaurus storage error, can't set ${n}=${e}`,t)}},del:()=>{try{let e=c.getItem(n);c.removeItem(n),o({key:n,oldValue:e,newValue:null,storage:c})}catch(e){console.error(`Docusaurus storage error, can't delete key=${n}`,e)}},listen:e=>{try{let t=t=>{t.storageArea===c&&t.key===n&&e(t)};return window.addEventListener("storage",t),()=>window.removeEventListener("storage",t)}catch(e){return console.error(`Docusaurus storage error, can't listen for changes of key=${n}`,e),()=>{}}}}}},3899(e,t,n){"use strict";n.d(t,{AL:()=>u,s$:()=>d});var r=n(4848),a=n(6540),o=n(898),i=n(8491),l=n(2260);let s=({title:e,siteTitle:t,titleDelimiter:n})=>{let r=e?.trim();return r&&r!==t?`${r} ${n} ${t}`:t},c=(0,a.createContext)(null);function u({formatter:e,children:t}){return(0,r.jsx)(c.Provider,{value:e,children:t})}function d(){let e=function(){let e=(0,a.useContext)(c);if(null===e)throw new l.dV("TitleFormatterProvider");return e}(),{siteConfig:t}=(0,o.A)(),{title:n,titleDelimiter:r}=t,{plugin:u}=(0,i.A)();return{format:t=>e({title:t,siteTitle:n,titleDelimiter:r,plugin:u,defaultFormatter:s})}}},5243(e,t,n){"use strict";n.d(t,{o:()=>i});var r=n(898),a=n(6347),o=n(5248);function i(){let{siteConfig:{baseUrl:e,trailingSlash:t},i18n:{localeConfigs:n}}=(0,r.A)(),{pathname:i}=(0,a.zy)(),l=(0,o.applyTrailingSlash)(i,{trailingSlash:t,baseUrl:e}).replace(e,"");return{createUrl:function({locale:e,fullyQualified:t}){let r=function(e){let t=n[e];if(!t)throw Error(`Unexpected Docusaurus bug, no locale config found for locale=${e}`);return t}(e),a=`${t?r.url:""}`,o=r.baseUrl;return`${a}${o}${l}`}}}},8398(e,t,n){"use strict";n.d(t,{$:()=>i});var r=n(6540),a=n(6347),o=n(2260);function i(e){let t=(0,a.zy)(),n=(0,o.ZC)(t),i=(0,o._q)(e);(0,r.useEffect)(()=>{n&&t!==n&&i({location:t,previousLocation:n})},[i,t,n])}},1022(e,t,n){"use strict";n.d(t,{p:()=>a});var r=n(898);function a(){return(0,r.A)().siteConfig.themeConfig}},4207(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.addTrailingSlash=a,t.default=function(e,t){let{trailingSlash:n,baseUrl:r}=t;if(e.startsWith("#")||void 0===n)return e;let[i]=e.split(/[#?]/),l="/"===i||i===r?i:n?a(i):o(i);return e.replace(i,l)},t.addLeadingSlash=function(e){return(0,r.addPrefix)(e,"/")},t.removeTrailingSlash=o;let r=n(9262);function a(e){return e.endsWith("/")?e:`${e}/`}function o(e){return(0,r.removeSuffix)(e,"/")}},7541(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.getErrorCausalChain=function e(t){return t.cause?[t,...e(t.cause)]:[t]}},5248(e,t,n){"use strict";t.getErrorCausalChain=t.applyTrailingSlash=void 0;let r=n(1635);var a=n(4207);Object.defineProperty(t,"applyTrailingSlash",{enumerable:!0,get:function(){return r.__importDefault(a).default}}),n(9262);var o=n(7541);Object.defineProperty(t,"getErrorCausalChain",{enumerable:!0,get:function(){return o.getErrorCausalChain}})},9262(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.addPrefix=function(e,t){return e.startsWith(t)?e:`${t}${e}`},t.removeSuffix=function(e,t){return""===t?e:e.endsWith(t)?e.slice(0,-t.length):e},t.addSuffix=function(e,t){return e.endsWith(t)?e:`${e}${t}`},t.removePrefix=function(e,t){return e.startsWith(t)?e.slice(t.length):e}},4784(e,t,n){"use strict";n.r(t),n.d(t,{default:()=>r});let r={title:"Logos documentation",tagline:"Guides and references for the Logos stack.",favicon:"img/favicon.png",headTags:[{tagName:"link",attributes:{rel:"icon",href:"/img/favicon.png",media:"(prefers-color-scheme: light)"},customElement:!1},{tagName:"link",attributes:{rel:"icon",href:"/img/favicon-dark.png",media:"(prefers-color-scheme: dark)"},customElement:!1}],future:{v4:{removeLegacyPostBuildHeadAttribute:!0,useCssCascadeLayers:!0,siteStorageNamespacing:!0,fasterByDefault:!0,mdx1CompatDisabledByDefault:!0},experimental_vcs:{},experimental_router:"browser",faster:{swcJsLoader:!0,swcJsMinimizer:!0,swcHtmlMinimizer:!0,lightningCssMinimizer:!0,mdxCrossCompilerCache:!0,rspackBundler:!0,rspackPersistentCache:!0,ssgWorkerThreads:!0,gitEagerVcs:!0}},url:"https://docs.logos.co",baseUrl:"/",organizationName:"logos-co",projectName:"logos-docs",onBrokenLinks:"throw",markdown:{hooks:{onBrokenMarkdownLinks:"throw",onBrokenMarkdownImages:"throw"},format:"mdx",mermaid:!1,emoji:!0,mdx1Compat:{comments:!1,admonitions:!1,headingIds:!1},anchors:{maintainCase:!1}},i18n:{defaultLocale:"en",locales:["en"],path:"i18n",localeConfigs:{}},presets:[["classic",{docs:{path:"docs",routeBasePath:"/",sidebarPath:"./sidebars.ts",editUrl:"https://github.com/logos-co/logos-docs/tree/main/"},blog:!1,theme:{customCss:"./src/css/custom.css"}}]],plugins:[["@docusaurus/plugin-client-redirects",{redirects:[{from:"/run-an-app/basecamp/install-logos-basecamp",to:"/basecamp/get-started/install-logos-basecamp"},{from:"/run-an-app/basecamp/readme",to:"/basecamp/get-started/install-logos-basecamp"},{from:"/run-an-app/blockchain-ui-app/build-and-run-logos-blockchain-node-app-ui",to:"/blockchain/node-app/build-and-run-logos-blockchain-node-app-ui"},{from:"/run-an-app/blockchain-ui-app/move-assets-using-logos-blockchain-app",to:"/blockchain/node-app/move-assets-using-logos-blockchain-app"},{from:"/run-an-app/blockchain-ui-app/bridge-assets-from-logos-blockchain-to-zone-using-app",to:"/blockchain/node-app/bridge-assets-from-logos-blockchain-to-zone-using-app"},{from:"/run-an-app/blockchain-ui-app/claim-leader-rewards-in-logos-blockchain-ui-app",to:"/blockchain/node-app/claim-leader-rewards-in-logos-blockchain-ui-app"},{from:"/run-an-app/blockchain-ui-app/readme",to:"/blockchain/node-app/build-and-run-logos-blockchain-node-app-ui"},{from:"/run-an-app/lez-wallet-ui/initiate-native-token-transfers-on-lez-with-wallet-ui",to:"/lez/get-started/initiate-native-token-transfers-on-lez-with-wallet-ui"},{from:"/run-an-app/lez-wallet-ui/readme",to:"/lez/get-started/initiate-native-token-transfers-on-lez-with-wallet-ui"},{from:"/run-a-node/blockchain-node/run-a-logos-blockchain-node-from-cli",to:"/blockchain/get-started/run-a-logos-blockchain-node-from-cli"},{from:"/run-a-node/blockchain-node/readme",to:"/blockchain/get-started/run-a-logos-blockchain-node-from-cli"},{from:"/run-a-node/delivery-node/run-logos-delivery-node",to:"/messaging/delivery/run-logos-delivery-node"},{from:"/run-a-node/delivery-node/readme",to:"/messaging/delivery/run-logos-delivery-node"},{from:"/run-a-node/storage-node/run-logos-storage-node",to:"/storage/get-started/run-logos-storage-node"},{from:"/run-a-node/storage-node/readme",to:"/storage/get-started/run-logos-storage-node"},{from:"/build-an-app/build-modules/build-and-run-a-logos-core-module",to:"/core/build-modules/build-and-run-a-logos-core-module"},{from:"/build-an-app/build-modules/wrap-a-c-library-as-a-logos-core-module",to:"/core/build-modules/wrap-a-c-library-as-a-logos-core-module"},{from:"/build-an-app/build-modules/build-a-logos-cpp-ui-module",to:"/core/build-modules/build-a-logos-cpp-ui-module"},{from:"/build-an-app/build-modules/install-and-load-a-module-in-logos-basecamp",to:"/core/build-modules/install-and-load-a-module-in-logos-basecamp"},{from:"/build-an-app/build-modules/readme",to:"/core/build-modules/build-and-run-a-logos-core-module"}]}]],themeConfig:{image:"img/social-card.jpg",colorMode:{respectPrefersColorScheme:!0,defaultMode:"light",disableSwitch:!1},navbar:{logo:{alt:"Logos",src:"img/logo-light.png",srcDark:"img/logo-dark.png"},items:[{type:"docSidebar",sidebarId:"getStartedSidebar",label:"Get started",position:"left"},{type:"dropdown",label:"Your path",position:"left",items:[{type:"docSidebar",sidebarId:"runAnAppSidebar",label:"Run an app"},{type:"docSidebar",sidebarId:"runANodeSidebar",label:"Run a node"},{type:"docSidebar",sidebarId:"buildAnAppSidebar",label:"Build an app"},{type:"docSidebar",sidebarId:"contributeSidebar",label:"Contribute"}]},{type:"dropdown",label:"Explore",position:"left",items:[{type:"docSidebar",sidebarId:"basecampSidebar",label:"Basecamp"},{type:"docSidebar",sidebarId:"blockchainSidebar",label:"Blockchain"},{type:"docSidebar",sidebarId:"lezSidebar",label:"LEZ"},{type:"docSidebar",sidebarId:"coreSidebar",label:"Core"},{type:"docSidebar",sidebarId:"messagingSidebar",label:"Messaging"},{type:"docSidebar",sidebarId:"storageSidebar",label:"Storage"},{type:"docSidebar",sidebarId:"mixnetSidebar",label:"Mixnet"},{type:"docSidebar",sidebarId:"peerDiscoverySidebar",label:"Peer discovery"}]},{href:"https://github.com/logos-co/logos-docs",label:"GitHub",position:"right"}],hideOnScroll:!1},footer:{style:"dark",links:[{title:"Docs",items:[{label:"Get started",to:"/get-started/what-is-logos"}]},{title:"Community",items:[{label:"GitHub",href:"https://github.com/logos-co/logos-docs"}]}],copyright:"Copyright \xa9 2026 Logos. Built with Docusaurus."},prism:{theme:{plain:{color:"#393A34",backgroundColor:"#f6f8fa"},styles:[{types:["comment","prolog","doctype","cdata"],style:{color:"#999988",fontStyle:"italic"}},{types:["namespace"],style:{opacity:.7}},{types:["string","attr-value"],style:{color:"#e3116c"}},{types:["punctuation","operator"],style:{color:"#393A34"}},{types:["entity","url","symbol","number","boolean","variable","constant","property","regex","inserted"],style:{color:"#36acaa"}},{types:["atrule","keyword","attr-name","selector"],style:{color:"#00a4db"}},{types:["function","deleted","tag"],style:{color:"#d73a49"}},{types:["function-variable"],style:{color:"#6f42c1"}},{types:["tag","selector","keyword"],style:{color:"#00009f"}}]},darkTheme:{plain:{color:"#F8F8F2",backgroundColor:"#282A36"},styles:[{types:["prolog","constant","builtin"],style:{color:"rgb(189, 147, 249)"}},{types:["inserted","function"],style:{color:"rgb(80, 250, 123)"}},{types:["deleted"],style:{color:"rgb(255, 85, 85)"}},{types:["changed"],style:{color:"rgb(255, 184, 108)"}},{types:["punctuation","symbol"],style:{color:"rgb(248, 248, 242)"}},{types:["string","char","tag","selector"],style:{color:"rgb(255, 121, 198)"}},{types:["keyword","variable"],style:{color:"rgb(189, 147, 249)",fontStyle:"italic"}},{types:["comment"],style:{color:"rgb(98, 114, 164)"}},{types:["attr-name"],style:{color:"rgb(241, 250, 140)"}}]},additionalLanguages:[],magicComments:[{className:"theme-code-block-highlighted-line",line:"highlight-next-line",block:{start:"highlight-start",end:"highlight-end"}}]},docs:{versionPersistence:"localStorage",sidebar:{hideable:!1,autoCollapseCategories:!1}},blog:{sidebar:{groupByYear:!0}},metadata:[],tableOfContents:{minHeadingLevel:2,maxHeadingLevel:3}},baseUrlIssueBanner:!0,storage:{type:"localStorage",namespace:!0},onBrokenAnchors:"warn",onDuplicateRoutes:"warn",staticDirectories:["static"],customFields:{},themes:[],scripts:[],stylesheets:[],clientModules:[],titleDelimiter:"|",noIndex:!1}},8168(e,t,n){"use strict";function r(){return(r=Object.assign?Object.assign.bind():function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)({}).hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e}).apply(null,arguments)}n.d(t,{A:()=>r})},594(e,t,n){"use strict";function r(e,t){return(r=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(e,t){return e.__proto__=t,e})(e,t)}function a(e,t){e.prototype=Object.create(t.prototype),e.prototype.constructor=e,r(e,t)}n.d(t,{A:()=>a})},8587(e,t,n){"use strict";function r(e,t){if(null==e)return{};var n={};for(var r in e)if(({}).hasOwnProperty.call(e,r)){if(-1!==t.indexOf(r))continue;n[r]=e[r]}return n}n.d(t,{A:()=>r})},4164(e,t,n){"use strict";n.d(t,{A:()=>r});let r=function(){for(var e,t,n=0,r="",a=arguments.length;n<a;n++)(e=arguments[n])&&(t=function e(t){var n,r,a="";if("string"==typeof t||"number"==typeof t)a+=t;else if("object"==typeof t)if(Array.isArray(t)){var o=t.length;for(n=0;n<o;n++)t[n]&&(r=e(t[n]))&&(a&&(a+=" "),a+=r)}else for(r in t)t[r]&&(a&&(a+=" "),a+=r);return a}(e))&&(r&&(r+=" "),r+=t);return r}},1765(e,t,n){"use strict";let r,a,o,i,l;n.d(t,{My:()=>B,f4:()=>ey});var s,c,u,d,p,f,m,g,h,b,y,v,w,k,x,S,E,_,A=n(6540),C=n(4164),T=Object.create,j=Object.defineProperty,P=Object.defineProperties,z=Object.getOwnPropertyDescriptor,L=Object.getOwnPropertyDescriptors,N=Object.getOwnPropertyNames,O=Object.getOwnPropertySymbols,R=Object.getPrototypeOf,D=Object.prototype.hasOwnProperty,F=Object.prototype.propertyIsEnumerable,I=(e,t,n)=>t in e?j(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n,M=(e,t)=>{for(var n in t||(t={}))D.call(t,n)&&I(e,n,t[n]);if(O)for(var n of O(t))F.call(t,n)&&I(e,n,t[n]);return e},$=(e,t)=>{var n={};for(var r in e)D.call(e,r)&&0>t.indexOf(r)&&(n[r]=e[r]);if(null!=e&&O)for(var r of O(e))0>t.indexOf(r)&&F.call(e,r)&&(n[r]=e[r]);return n},B=(l=null!=(o=(r={"../../node_modules/.pnpm/prismjs@1.29.0_patch_hash=vrxx3pzkik6jpmgpayxfjunetu/node_modules/prismjs/prism.js"(e,t){var n=function(){var e=/(?:^|\s)lang(?:uage)?-([\w-]+)(?=\s|$)/i,t=0,n={},r={util:{encode:function e(t){return t instanceof a?new a(t.type,e(t.content),t.alias):Array.isArray(t)?t.map(e):t.replace(/&/g,"&").replace(/</g,"<").replace(/\u00a0/g," ")},type:function(e){return Object.prototype.toString.call(e).slice(8,-1)},objId:function(e){return e.__id||Object.defineProperty(e,"__id",{value:++t}),e.__id},clone:function e(t,n){var a,o;switch(n=n||{},r.util.type(t)){case"Object":if(n[o=r.util.objId(t)])return n[o];for(var i in a={},n[o]=a,t)t.hasOwnProperty(i)&&(a[i]=e(t[i],n));return a;case"Array":if(n[o=r.util.objId(t)])return n[o];return a=[],n[o]=a,t.forEach(function(t,r){a[r]=e(t,n)}),a;default:return t}},getLanguage:function(t){for(;t;){var n=e.exec(t.className);if(n)return n[1].toLowerCase();t=t.parentElement}return"none"},setLanguage:function(t,n){t.className=t.className.replace(RegExp(e,"gi"),""),t.classList.add("language-"+n)},isActive:function(e,t,n){for(var r="no-"+t;e;){var a=e.classList;if(a.contains(t))return!0;if(a.contains(r))return!1;e=e.parentElement}return!!n}},languages:{plain:n,plaintext:n,text:n,txt:n,extend:function(e,t){var n=r.util.clone(r.languages[e]);for(var a in t)n[a]=t[a];return n},insertBefore:function(e,t,n,a){var o=(a=a||r.languages)[e],i={};for(var l in o)if(o.hasOwnProperty(l)){if(l==t)for(var s in n)n.hasOwnProperty(s)&&(i[s]=n[s]);n.hasOwnProperty(l)||(i[l]=o[l])}var c=a[e];return a[e]=i,r.languages.DFS(r.languages,function(t,n){n===c&&t!=e&&(this[t]=i)}),i},DFS:function e(t,n,a,o){o=o||{};var i=r.util.objId;for(var l in t)if(t.hasOwnProperty(l)){n.call(t,l,t[l],a||l);var s=t[l],c=r.util.type(s);"Object"!==c||o[i(s)]?"Array"!==c||o[i(s)]||(o[i(s)]=!0,e(s,n,l,o)):(o[i(s)]=!0,e(s,n,null,o))}}},plugins:{},highlight:function(e,t,n){var o={code:e,grammar:t,language:n};if(r.hooks.run("before-tokenize",o),!o.grammar)throw Error('The language "'+o.language+'" has no grammar.');return o.tokens=r.tokenize(o.code,o.grammar),r.hooks.run("after-tokenize",o),a.stringify(r.util.encode(o.tokens),o.language)},tokenize:function(e,t){var n=t.rest;if(n){for(var s in n)t[s]=n[s];delete t.rest}var c=new i;return l(c,c.head,e),function e(t,n,i,s,c,u){for(var d in i)if(i.hasOwnProperty(d)&&i[d]){var p=i[d];p=Array.isArray(p)?p:[p];for(var f=0;f<p.length;++f){if(u&&u.cause==d+","+f)return;var m=p[f],g=m.inside,h=!!m.lookbehind,b=!!m.greedy,y=m.alias;if(b&&!m.pattern.global){var v=m.pattern.toString().match(/[imsuy]*$/)[0];m.pattern=RegExp(m.pattern.source,v+"g")}for(var w=m.pattern||m,k=s.next,x=c;k!==n.tail&&(!u||!(x>=u.reach));x+=k.value.length,k=k.next){var S,E=k.value;if(n.length>t.length)return;if(!(E instanceof a)){var _=1;if(b){if(!(S=o(w,x,t,h))||S.index>=t.length)break;var A=S.index,C=S.index+S[0].length,T=x;for(T+=k.value.length;A>=T;)T+=(k=k.next).value.length;if(T-=k.value.length,x=T,k.value instanceof a)continue;for(var j=k;j!==n.tail&&(T<C||"string"==typeof j.value);j=j.next)_++,T+=j.value.length;_--,E=t.slice(x,T),S.index-=x}else if(!(S=o(w,0,E,h)))continue;var A=S.index,P=S[0],z=E.slice(0,A),L=E.slice(A+P.length),N=x+E.length;u&&N>u.reach&&(u.reach=N);var O=k.prev;if(z&&(O=l(n,O,z),x+=z.length),function(e,t,n){for(var r=t.next,a=0;a<n&&r!==e.tail;a++)r=r.next;t.next=r,r.prev=t,e.length-=a}(n,O,_),k=l(n,O,new a(d,g?r.tokenize(P,g):P,y,P)),L&&l(n,k,L),_>1){var R={cause:d+","+f,reach:N};e(t,n,i,k.prev,x,R),u&&R.reach>u.reach&&(u.reach=R.reach)}}}}}}(e,c,t,c.head,0),function(e){for(var t=[],n=e.head.next;n!==e.tail;)t.push(n.value),n=n.next;return t}(c)},hooks:{all:{},add:function(e,t){var n=r.hooks.all;n[e]=n[e]||[],n[e].push(t)},run:function(e,t){var n=r.hooks.all[e];if(n&&n.length)for(var a,o=0;a=n[o++];)a(t)}},Token:a};function a(e,t,n,r){this.type=e,this.content=t,this.alias=n,this.length=0|(r||"").length}function o(e,t,n,r){e.lastIndex=t;var a=e.exec(n);if(a&&r&&a[1]){var o=a[1].length;a.index+=o,a[0]=a[0].slice(o)}return a}function i(){var e={value:null,prev:null,next:null},t={value:null,prev:e,next:null};e.next=t,this.head=e,this.tail=t,this.length=0}function l(e,t,n){var r=t.next,a={value:n,prev:t,next:r};return t.next=a,r.prev=a,e.length++,a}return a.stringify=function e(t,n){if("string"==typeof t)return t;if(Array.isArray(t)){var a="";return t.forEach(function(t){a+=e(t,n)}),a}var o={type:t.type,content:e(t.content,n),tag:"span",classes:["token",t.type],attributes:{},language:n},i=t.alias;i&&(Array.isArray(i)?Array.prototype.push.apply(o.classes,i):o.classes.push(i)),r.hooks.run("wrap",o);var l="";for(var s in o.attributes)l+=" "+s+'="'+(o.attributes[s]||"").replace(/"/g,""")+'"';return"<"+o.tag+' class="'+o.classes.join(" ")+'"'+l+">"+o.content+"</"+o.tag+">"},r}();t.exports=n,n.default=n}},function(){return a||(0,r[N(r)[0]])((a={exports:{}}).exports,a),a.exports})())?T(R(o)):{},((e,t,n,r)=>{if(t&&"object"==typeof t||"function"==typeof t)for(let a of N(t))D.call(e,a)||a===n||j(e,a,{get:()=>t[a],enumerable:!(r=z(t,a))||r.enumerable});return e})(!i&&o&&o.__esModule?l:j(l,"default",{value:o,enumerable:!0}),o));B.languages.markup={comment:{pattern:/<!--(?:(?!<!--)[\s\S])*?-->/,greedy:!0},prolog:{pattern:/<\?[\s\S]+?\?>/,greedy:!0},doctype:{pattern:/<!DOCTYPE(?:[^>"'[\]]|"[^"]*"|'[^']*')+(?:\[(?:[^<"'\]]|"[^"]*"|'[^']*'|<(?!!--)|<!--(?:[^-]|-(?!->))*-->)*\]\s*)?>/i,greedy:!0,inside:{"internal-subset":{pattern:/(^[^\[]*\[)[\s\S]+(?=\]>$)/,lookbehind:!0,greedy:!0,inside:null},string:{pattern:/"[^"]*"|'[^']*'/,greedy:!0},punctuation:/^<!|>$|[[\]]/,"doctype-tag":/^DOCTYPE/i,name:/[^\s<>'"]+/}},cdata:{pattern:/<!\[CDATA\[[\s\S]*?\]\]>/i,greedy:!0},tag:{pattern:/<\/?(?!\d)[^\s>\/=$<%]+(?:\s(?:\s*[^\s>\/=]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))|(?=[\s/>])))+)?\s*\/?>/,greedy:!0,inside:{tag:{pattern:/^<\/?[^\s>\/]+/,inside:{punctuation:/^<\/?/,namespace:/^[^\s>\/:]+:/}},"special-attr":[],"attr-value":{pattern:/=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+)/,inside:{punctuation:[{pattern:/^=/,alias:"attr-equals"},{pattern:/^(\s*)["']|["']$/,lookbehind:!0}]}},punctuation:/\/?>/,"attr-name":{pattern:/[^\s>\/]+/,inside:{namespace:/^[^\s>\/:]+:/}}}},entity:[{pattern:/&[\da-z]{1,8};/i,alias:"named-entity"},/&#x?[\da-f]{1,8};/i]},B.languages.markup.tag.inside["attr-value"].inside.entity=B.languages.markup.entity,B.languages.markup.doctype.inside["internal-subset"].inside=B.languages.markup,B.hooks.add("wrap",function(e){"entity"===e.type&&(e.attributes.title=e.content.replace(/&/,"&"))}),Object.defineProperty(B.languages.markup.tag,"addInlined",{value:function(e,t){var n={},n=(n["language-"+t]={pattern:/(^<!\[CDATA\[)[\s\S]+?(?=\]\]>$)/i,lookbehind:!0,inside:B.languages[t]},n.cdata=/^<!\[CDATA\[|\]\]>$/i,{"included-cdata":{pattern:/<!\[CDATA\[[\s\S]*?\]\]>/i,inside:n}}),t=(n["language-"+t]={pattern:/[\s\S]+/,inside:B.languages[t]},{});t[e]={pattern:RegExp(/(<__[^>]*>)(?:<!\[CDATA\[(?:[^\]]|\](?!\]>))*\]\]>|(?!<!\[CDATA\[)[\s\S])*?(?=<\/__>)/.source.replace(/__/g,function(){return e}),"i"),lookbehind:!0,greedy:!0,inside:n},B.languages.insertBefore("markup","cdata",t)}}),Object.defineProperty(B.languages.markup.tag,"addAttribute",{value:function(e,t){B.languages.markup.tag.inside["special-attr"].push({pattern:RegExp(/(^|["'\s])/.source+"(?:"+e+")"+/\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))/.source,"i"),lookbehind:!0,inside:{"attr-name":/^[^\s=]+/,"attr-value":{pattern:/=[\s\S]+/,inside:{value:{pattern:/(^=\s*(["']|(?!["'])))\S[\s\S]*(?=\2$)/,lookbehind:!0,alias:[t,"language-"+t],inside:B.languages[t]},punctuation:[{pattern:/^=/,alias:"attr-equals"},/"|'/]}}}})}}),B.languages.html=B.languages.markup,B.languages.mathml=B.languages.markup,B.languages.svg=B.languages.markup,B.languages.xml=B.languages.extend("markup",{}),B.languages.ssml=B.languages.xml,B.languages.atom=B.languages.xml,B.languages.rss=B.languages.xml,s={pattern:/\\[\\(){}[\]^$+*?|.]/,alias:"escape"},u=RegExp((u="(?:[^\\\\-]|"+(c=/\\(?:x[\da-fA-F]{2}|u[\da-fA-F]{4}|u\{[\da-fA-F]+\}|0[0-7]{0,2}|[123][0-7]{2}|c[a-zA-Z]|.)/).source+")")+"-"+u),d={pattern:/(<|')[^<>']+(?=[>']$)/,lookbehind:!0,alias:"variable"},B.languages.regex={"char-class":{pattern:/((?:^|[^\\])(?:\\\\)*)\[(?:[^\\\]]|\\[\s\S])*\]/,lookbehind:!0,inside:{"char-class-negation":{pattern:/(^\[)\^/,lookbehind:!0,alias:"operator"},"char-class-punctuation":{pattern:/^\[|\]$/,alias:"punctuation"},range:{pattern:u,inside:{escape:c,"range-punctuation":{pattern:/-/,alias:"operator"}}},"special-escape":s,"char-set":{pattern:/\\[wsd]|\\p\{[^{}]+\}/i,alias:"class-name"},escape:c}},"special-escape":s,"char-set":{pattern:/\.|\\[wsd]|\\p\{[^{}]+\}/i,alias:"class-name"},backreference:[{pattern:/\\(?![123][0-7]{2})[1-9]/,alias:"keyword"},{pattern:/\\k<[^<>']+>/,alias:"keyword",inside:{"group-name":d}}],anchor:{pattern:/[$^]|\\[ABbGZz]/,alias:"function"},escape:c,group:[{pattern:/\((?:\?(?:<[^<>']+>|'[^<>']+'|[>:]|<?[=!]|[idmnsuxU]+(?:-[idmnsuxU]+)?:?))?/,alias:"punctuation",inside:{"group-name":d}},{pattern:/\)/,alias:"punctuation"}],quantifier:{pattern:/(?:[+*?]|\{\d+(?:,\d*)?\})[?+]?/,alias:"number"},alternation:{pattern:/\|/,alias:"keyword"}},B.languages.clike={comment:[{pattern:/(^|[^\\])\/\*[\s\S]*?(?:\*\/|$)/,lookbehind:!0,greedy:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0,greedy:!0}],string:{pattern:/(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,greedy:!0},"class-name":{pattern:/(\b(?:class|extends|implements|instanceof|interface|new|trait)\s+|\bcatch\s+\()[\w.\\]+/i,lookbehind:!0,inside:{punctuation:/[.\\]/}},keyword:/\b(?:break|catch|continue|do|else|finally|for|function|if|in|instanceof|new|null|return|throw|try|while)\b/,boolean:/\b(?:false|true)\b/,function:/\b\w+(?=\()/,number:/\b0x[\da-f]+\b|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:e[+-]?\d+)?/i,operator:/[<>]=?|[!=]=?=?|--?|\+\+?|&&?|\|\|?|[?*/~^%]/,punctuation:/[{}[\];(),.:]/},B.languages.javascript=B.languages.extend("clike",{"class-name":[B.languages.clike["class-name"],{pattern:/(^|[^$\w\xA0-\uFFFF])(?!\s)[_$A-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\.(?:constructor|prototype))/,lookbehind:!0}],keyword:[{pattern:/((?:^|\})\s*)catch\b/,lookbehind:!0},{pattern:/(^|[^.]|\.\.\.\s*)\b(?:as|assert(?=\s*\{)|async(?=\s*(?:function\b|\(|[$\w\xA0-\uFFFF]|$))|await|break|case|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally(?=\s*(?:\{|$))|for|from(?=\s*(?:['"]|$))|function|(?:get|set)(?=\s*(?:[#\[$\w\xA0-\uFFFF]|$))|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)\b/,lookbehind:!0}],function:/#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*(?:\.\s*(?:apply|bind|call)\s*)?\()/,number:{pattern:RegExp(/(^|[^\w$])/.source+"(?:"+/NaN|Infinity/.source+"|"+/0[bB][01]+(?:_[01]+)*n?/.source+"|"+/0[oO][0-7]+(?:_[0-7]+)*n?/.source+"|"+/0[xX][\dA-Fa-f]+(?:_[\dA-Fa-f]+)*n?/.source+"|"+/\d+(?:_\d+)*n/.source+"|"+/(?:\d+(?:_\d+)*(?:\.(?:\d+(?:_\d+)*)?)?|\.\d+(?:_\d+)*)(?:[Ee][+-]?\d+(?:_\d+)*)?/.source+")"+/(?![\w$])/.source),lookbehind:!0},operator:/--|\+\+|\*\*=?|=>|&&=?|\|\|=?|[!=]==|<<=?|>>>?=?|[-+*/%&|^!=<>]=?|\.{3}|\?\?=?|\?\.?|[~:]/}),B.languages.javascript["class-name"][0].pattern=/(\b(?:class|extends|implements|instanceof|interface|new)\s+)[\w.\\]+/,B.languages.insertBefore("javascript","keyword",{regex:{pattern:RegExp(/((?:^|[^$\w\xA0-\uFFFF."'\])\s]|\b(?:return|yield))\s*)/.source+/\//.source+"(?:"+/(?:\[(?:[^\]\\\r\n]|\\.)*\]|\\.|[^/\\\[\r\n])+\/[dgimyus]{0,7}/.source+"|"+/(?:\[(?:[^[\]\\\r\n]|\\.|\[(?:[^[\]\\\r\n]|\\.|\[(?:[^[\]\\\r\n]|\\.)*\])*\])*\]|\\.|[^/\\\[\r\n])+\/[dgimyus]{0,7}v[dgimyus]{0,7}/.source+")"+/(?=(?:\s|\/\*(?:[^*]|\*(?!\/))*\*\/)*(?:$|[\r\n,.;:})\]]|\/\/))/.source),lookbehind:!0,greedy:!0,inside:{"regex-source":{pattern:/^(\/)[\s\S]+(?=\/[a-z]*$)/,lookbehind:!0,alias:"language-regex",inside:B.languages.regex},"regex-delimiter":/^\/|\/$/,"regex-flags":/^[a-z]+$/}},"function-variable":{pattern:/#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*[=:]\s*(?:async\s*)?(?:\bfunction\b|(?:\((?:[^()]|\([^()]*\))*\)|(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*)\s*=>))/,alias:"function"},parameter:[{pattern:/(function(?:\s+(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*)?\s*\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\))/,lookbehind:!0,inside:B.languages.javascript},{pattern:/(^|[^$\w\xA0-\uFFFF])(?!\s)[_$a-z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*=>)/i,lookbehind:!0,inside:B.languages.javascript},{pattern:/(\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\)\s*=>)/,lookbehind:!0,inside:B.languages.javascript},{pattern:/((?:\b|\s|^)(?!(?:as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)(?![$\w\xA0-\uFFFF]))(?:(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*\s*)\(\s*|\]\s*\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\)\s*\{)/,lookbehind:!0,inside:B.languages.javascript}],constant:/\b[A-Z](?:[A-Z_]|\dx?)*\b/}),B.languages.insertBefore("javascript","string",{hashbang:{pattern:/^#!.*/,greedy:!0,alias:"comment"},"template-string":{pattern:/`(?:\\[\s\S]|\$\{(?:[^{}]|\{(?:[^{}]|\{[^}]*\})*\})+\}|(?!\$\{)[^\\`])*`/,greedy:!0,inside:{"template-punctuation":{pattern:/^`|`$/,alias:"string"},interpolation:{pattern:/((?:^|[^\\])(?:\\{2})*)\$\{(?:[^{}]|\{(?:[^{}]|\{[^}]*\})*\})+\}/,lookbehind:!0,inside:{"interpolation-punctuation":{pattern:/^\$\{|\}$/,alias:"punctuation"},rest:B.languages.javascript}},string:/[\s\S]+/}},"string-property":{pattern:/((?:^|[,{])[ \t]*)(["'])(?:\\(?:\r\n|[\s\S])|(?!\2)[^\\\r\n])*\2(?=\s*:)/m,lookbehind:!0,greedy:!0,alias:"property"}}),B.languages.insertBefore("javascript","operator",{"literal-property":{pattern:/((?:^|[,{])[ \t]*)(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*:)/m,lookbehind:!0,alias:"property"}}),B.languages.markup&&(B.languages.markup.tag.addInlined("script","javascript"),B.languages.markup.tag.addAttribute(/on(?:abort|blur|change|click|composition(?:end|start|update)|dblclick|error|focus(?:in|out)?|key(?:down|up)|load|mouse(?:down|enter|leave|move|out|over|up)|reset|resize|scroll|select|slotchange|submit|unload|wheel)/.source,"javascript")),B.languages.js=B.languages.javascript,B.languages.actionscript=B.languages.extend("javascript",{keyword:/\b(?:as|break|case|catch|class|const|default|delete|do|dynamic|each|else|extends|final|finally|for|function|get|if|implements|import|in|include|instanceof|interface|internal|is|namespace|native|new|null|override|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|use|var|void|while|with)\b/,operator:/\+\+|--|(?:[+\-*\/%^]|&&?|\|\|?|<<?|>>?>?|[!=]=?)=?|[~?@]/}),B.languages.actionscript["class-name"].alias="function",delete B.languages.actionscript.parameter,delete B.languages.actionscript["literal-property"],B.languages.markup&&B.languages.insertBefore("actionscript","string",{xml:{pattern:/(^|[^.])<\/?\w+(?:\s+[^\s>\/=]+=("|')(?:\\[\s\S]|(?!\2)[^\\])*\2)*\s*\/?>/,lookbehind:!0,inside:B.languages.markup}}),p=/#(?!\{).+/,f={pattern:/#\{[^}]+\}/,alias:"variable"},B.languages.coffeescript=B.languages.extend("javascript",{comment:p,string:[{pattern:/'(?:\\[\s\S]|[^\\'])*'/,greedy:!0},{pattern:/"(?:\\[\s\S]|[^\\"])*"/,greedy:!0,inside:{interpolation:f}}],keyword:/\b(?:and|break|by|catch|class|continue|debugger|delete|do|each|else|extend|extends|false|finally|for|if|in|instanceof|is|isnt|let|loop|namespace|new|no|not|null|of|off|on|or|own|return|super|switch|then|this|throw|true|try|typeof|undefined|unless|until|when|while|window|with|yes|yield)\b/,"class-member":{pattern:/@(?!\d)\w+/,alias:"variable"}}),B.languages.insertBefore("coffeescript","comment",{"multiline-comment":{pattern:/###[\s\S]+?###/,alias:"comment"},"block-regex":{pattern:/\/{3}[\s\S]*?\/{3}/,alias:"regex",inside:{comment:p,interpolation:f}}}),B.languages.insertBefore("coffeescript","string",{"inline-javascript":{pattern:/`(?:\\[\s\S]|[^\\`])*`/,inside:{delimiter:{pattern:/^`|`$/,alias:"punctuation"},script:{pattern:/[\s\S]+/,alias:"language-javascript",inside:B.languages.javascript}}},"multiline-string":[{pattern:/'''[\s\S]*?'''/,greedy:!0,alias:"string"},{pattern:/"""[\s\S]*?"""/,greedy:!0,alias:"string",inside:{interpolation:f}}]}),B.languages.insertBefore("coffeescript","keyword",{property:/(?!\d)\w+(?=\s*:(?!:))/}),delete B.languages.coffeescript["template-string"],B.languages.coffee=B.languages.coffeescript,Object.defineProperty(m=B.languages.javadoclike={parameter:{pattern:/(^[\t ]*(?:\/{3}|\*|\/\*\*)\s*@(?:arg|arguments|param)\s+)\w+/m,lookbehind:!0},keyword:{pattern:/(^[\t ]*(?:\/{3}|\*|\/\*\*)\s*|\{)@[a-z][a-zA-Z-]+\b/m,lookbehind:!0},punctuation:/[{}]/},"addSupport",{value:function(e,t){(e="string"==typeof e?[e]:e).forEach(function(e){var n=function(e){e.inside||(e.inside={}),e.inside.rest=t},r="doc-comment";if(a=B.languages[e]){var a,o=a[r];if((o=o||(a=B.languages.insertBefore(e,"comment",{"doc-comment":{pattern:/(^|[^\\])\/\*\*[^/][\s\S]*?(?:\*\/|$)/,lookbehind:!0,alias:"comment"}}))[r])instanceof RegExp&&(o=a[r]={pattern:o}),Array.isArray(o))for(var i=0,l=o.length;i<l;i++)o[i]instanceof RegExp&&(o[i]={pattern:o[i]}),n(o[i]);else n(o)}})}}),m.addSupport(["java","javascript","php"],m),g=/(?:"(?:\\(?:\r\n|[\s\S])|[^"\\\r\n])*"|'(?:\\(?:\r\n|[\s\S])|[^'\\\r\n])*')/,B.languages.css={comment:/\/\*[\s\S]*?\*\//,atrule:{pattern:RegExp("@[\\w-](?:"+/[^;{\s"']|\s+(?!\s)/.source+"|"+g.source+")*?"+/(?:;|(?=\s*\{))/.source),inside:{rule:/^@[\w-]+/,"selector-function-argument":{pattern:/(\bselector\s*\(\s*(?![\s)]))(?:[^()\s]|\s+(?![\s)])|\((?:[^()]|\([^()]*\))*\))+(?=\s*\))/,lookbehind:!0,alias:"selector"},keyword:{pattern:/(^|[^\w-])(?:and|not|only|or)(?![\w-])/,lookbehind:!0}}},url:{pattern:RegExp("\\burl\\((?:"+g.source+"|"+/(?:[^\\\r\n()"']|\\[\s\S])*/.source+")\\)","i"),greedy:!0,inside:{function:/^url/i,punctuation:/^\(|\)$/,string:{pattern:RegExp("^"+g.source+"$"),alias:"url"}}},selector:{pattern:RegExp("(^|[{}\\s])[^{}\\s](?:[^{};\"'\\s]|\\s+(?![\\s{])|"+g.source+")*(?=\\s*\\{)"),lookbehind:!0},string:{pattern:g,greedy:!0},property:{pattern:/(^|[^-\w\xA0-\uFFFF])(?!\s)[-_a-z\xA0-\uFFFF](?:(?!\s)[-\w\xA0-\uFFFF])*(?=\s*:)/i,lookbehind:!0},important:/!important\b/i,function:{pattern:/(^|[^-a-z0-9])[-a-z0-9]+(?=\()/i,lookbehind:!0},punctuation:/[(){};:,]/},B.languages.css.atrule.inside.rest=B.languages.css,(g=B.languages.markup)&&(g.tag.addInlined("style","css"),g.tag.addAttribute("style","css")),h=/("|')(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,B.languages.css.selector={pattern:B.languages.css.selector.pattern,lookbehind:!0,inside:h={"pseudo-element":/:(?:after|before|first-letter|first-line|selection)|::[-\w]+/,"pseudo-class":/:[-\w]+/,class:/\.[-\w]+/,id:/#[-\w]+/,attribute:{pattern:RegExp("\\[(?:[^[\\]\"']|"+h.source+")*\\]"),greedy:!0,inside:{punctuation:/^\[|\]$/,"case-sensitivity":{pattern:/(\s)[si]$/i,lookbehind:!0,alias:"keyword"},namespace:{pattern:/^(\s*)(?:(?!\s)[-*\w\xA0-\uFFFF])*\|(?!=)/,lookbehind:!0,inside:{punctuation:/\|$/}},"attr-name":{pattern:/^(\s*)(?:(?!\s)[-\w\xA0-\uFFFF])+/,lookbehind:!0},"attr-value":[h,{pattern:/(=\s*)(?:(?!\s)[-\w\xA0-\uFFFF])+(?=\s*$)/,lookbehind:!0}],operator:/[|~*^$]?=/}},"n-th":[{pattern:/(\(\s*)[+-]?\d*[\dn](?:\s*[+-]\s*\d+)?(?=\s*\))/,lookbehind:!0,inside:{number:/[\dn]+/,operator:/[+-]/}},{pattern:/(\(\s*)(?:even|odd)(?=\s*\))/i,lookbehind:!0}],combinator:/>|\+|~|\|\|/,punctuation:/[(),]/}},B.languages.css.atrule.inside["selector-function-argument"].inside=h,B.languages.insertBefore("css","property",{variable:{pattern:/(^|[^-\w\xA0-\uFFFF])--(?!\s)[-_a-z\xA0-\uFFFF](?:(?!\s)[-\w\xA0-\uFFFF])*/i,lookbehind:!0}}),h={pattern:/(\b\d+)(?:%|[a-z]+(?![\w-]))/,lookbehind:!0},b={pattern:/(^|[^\w.-])-?(?:\d+(?:\.\d+)?|\.\d+)/,lookbehind:!0},B.languages.insertBefore("css","function",{operator:{pattern:/(\s)[+\-*\/](?=\s)/,lookbehind:!0},hexcode:{pattern:/\B#[\da-f]{3,8}\b/i,alias:"color"},color:[{pattern:/(^|[^\w-])(?:AliceBlue|AntiqueWhite|Aqua|Aquamarine|Azure|Beige|Bisque|Black|BlanchedAlmond|Blue|BlueViolet|Brown|BurlyWood|CadetBlue|Chartreuse|Chocolate|Coral|CornflowerBlue|Cornsilk|Crimson|Cyan|DarkBlue|DarkCyan|DarkGoldenRod|DarkGr[ae]y|DarkGreen|DarkKhaki|DarkMagenta|DarkOliveGreen|DarkOrange|DarkOrchid|DarkRed|DarkSalmon|DarkSeaGreen|DarkSlateBlue|DarkSlateGr[ae]y|DarkTurquoise|DarkViolet|DeepPink|DeepSkyBlue|DimGr[ae]y|DodgerBlue|FireBrick|FloralWhite|ForestGreen|Fuchsia|Gainsboro|GhostWhite|Gold|GoldenRod|Gr[ae]y|Green|GreenYellow|HoneyDew|HotPink|IndianRed|Indigo|Ivory|Khaki|Lavender|LavenderBlush|LawnGreen|LemonChiffon|LightBlue|LightCoral|LightCyan|LightGoldenRodYellow|LightGr[ae]y|LightGreen|LightPink|LightSalmon|LightSeaGreen|LightSkyBlue|LightSlateGr[ae]y|LightSteelBlue|LightYellow|Lime|LimeGreen|Linen|Magenta|Maroon|MediumAquaMarine|MediumBlue|MediumOrchid|MediumPurple|MediumSeaGreen|MediumSlateBlue|MediumSpringGreen|MediumTurquoise|MediumVioletRed|MidnightBlue|MintCream|MistyRose|Moccasin|NavajoWhite|Navy|OldLace|Olive|OliveDrab|Orange|OrangeRed|Orchid|PaleGoldenRod|PaleGreen|PaleTurquoise|PaleVioletRed|PapayaWhip|PeachPuff|Peru|Pink|Plum|PowderBlue|Purple|RebeccaPurple|Red|RosyBrown|RoyalBlue|SaddleBrown|Salmon|SandyBrown|SeaGreen|SeaShell|Sienna|Silver|SkyBlue|SlateBlue|SlateGr[ae]y|Snow|SpringGreen|SteelBlue|Tan|Teal|Thistle|Tomato|Transparent|Turquoise|Violet|Wheat|White|WhiteSmoke|Yellow|YellowGreen)(?![\w-])/i,lookbehind:!0},{pattern:/\b(?:hsl|rgb)\(\s*\d{1,3}\s*,\s*\d{1,3}%?\s*,\s*\d{1,3}%?\s*\)\B|\b(?:hsl|rgb)a\(\s*\d{1,3}\s*,\s*\d{1,3}%?\s*,\s*\d{1,3}%?\s*,\s*(?:0|0?\.\d+|1)\s*\)\B/i,inside:{unit:h,number:b,function:/[\w-]+(?=\()/,punctuation:/[(),]/}}],entity:/\\[\da-f]{1,8}/i,unit:h,number:b}),function(e){var t=/[*&][^\s[\]{},]+/,n=/!(?:<[\w\-%#;/?:@&=+$,.!~*'()[\]]+>|(?:[a-zA-Z\d-]*!)?[\w\-%#;/?:@&=+$.~*'()]+)?/,r="(?:"+n.source+"(?:[ ]+"+t.source+")?|"+t.source+"(?:[ ]+"+n.source+")?)",a=/(?:[^\s\x00-\x08\x0e-\x1f!"#%&'*,\-:>?@[\]`{|}\x7f-\x84\x86-\x9f\ud800-\udfff\ufffe\uffff]|[?:-]<PLAIN>)(?:[ \t]*(?:(?![#:])<PLAIN>|:<PLAIN>))*/.source.replace(/<PLAIN>/g,function(){return/[^\s\x00-\x08\x0e-\x1f,[\]{}\x7f-\x84\x86-\x9f\ud800-\udfff\ufffe\uffff]/.source}),o=/"(?:[^"\\\r\n]|\\.)*"|'(?:[^'\\\r\n]|\\.)*'/.source;function i(e,t){return t=(t||"").replace(/m/g,"")+"m",RegExp(/([:\-,[{]\s*(?:\s<<prop>>[ \t]+)?)(?:<<value>>)(?=[ \t]*(?:$|,|\]|\}|(?:[\r\n]\s*)?#))/.source.replace(/<<prop>>/g,function(){return r}).replace(/<<value>>/g,function(){return e}),t)}e.languages.yaml={scalar:{pattern:RegExp(/([\-:]\s*(?:\s<<prop>>[ \t]+)?[|>])[ \t]*(?:((?:\r?\n|\r)[ \t]+)\S[^\r\n]*(?:\2[^\r\n]+)*)/.source.replace(/<<prop>>/g,function(){return r})),lookbehind:!0,alias:"string"},comment:/#.*/,key:{pattern:RegExp(/((?:^|[:\-,[{\r\n?])[ \t]*(?:<<prop>>[ \t]+)?)<<key>>(?=\s*:\s)/.source.replace(/<<prop>>/g,function(){return r}).replace(/<<key>>/g,function(){return"(?:"+a+"|"+o+")"})),lookbehind:!0,greedy:!0,alias:"atrule"},directive:{pattern:/(^[ \t]*)%.+/m,lookbehind:!0,alias:"important"},datetime:{pattern:i(/\d{4}-\d\d?-\d\d?(?:[tT]|[ \t]+)\d\d?:\d{2}:\d{2}(?:\.\d*)?(?:[ \t]*(?:Z|[-+]\d\d?(?::\d{2})?))?|\d{4}-\d{2}-\d{2}|\d\d?:\d{2}(?::\d{2}(?:\.\d*)?)?/.source),lookbehind:!0,alias:"number"},boolean:{pattern:i(/false|true/.source,"i"),lookbehind:!0,alias:"important"},null:{pattern:i(/null|~/.source,"i"),lookbehind:!0,alias:"important"},string:{pattern:i(o),lookbehind:!0,greedy:!0},number:{pattern:i(/[+-]?(?:0x[\da-f]+|0o[0-7]+|(?:\d+(?:\.\d*)?|\.\d+)(?:e[+-]?\d+)?|\.inf|\.nan)/.source,"i"),lookbehind:!0},tag:n,important:t,punctuation:/---|[:[\]{}\-,|>?]|\.\.\./},e.languages.yml=e.languages.yaml}(B),function(e){var t=/(?:\\.|[^\\\n\r]|(?:\n|\r\n?)(?![\r\n]))/.source;function n(e){return e=e.replace(/<inner>/g,function(){return t}),RegExp(/((?:^|[^\\])(?:\\{2})*)/.source+"(?:"+e+")")}var r=/(?:\\.|``(?:[^`\r\n]|`(?!`))+``|`[^`\r\n]+`|[^\\|\r\n`])+/.source,a=/\|?__(?:\|__)+\|?(?:(?:\n|\r\n?)|(?![\s\S]))/.source.replace(/__/g,function(){return r}),o=/\|?[ \t]*:?-{3,}:?[ \t]*(?:\|[ \t]*:?-{3,}:?[ \t]*)+\|?(?:\n|\r\n?)/.source,i=(e.languages.markdown=e.languages.extend("markup",{}),e.languages.insertBefore("markdown","prolog",{"front-matter-block":{pattern:/(^(?:\s*[\r\n])?)---(?!.)[\s\S]*?[\r\n]---(?!.)/,lookbehind:!0,greedy:!0,inside:{punctuation:/^---|---$/,"front-matter":{pattern:/\S+(?:\s+\S+)*/,alias:["yaml","language-yaml"],inside:e.languages.yaml}}},blockquote:{pattern:/^>(?:[\t ]*>)*/m,alias:"punctuation"},table:{pattern:RegExp("^"+a+o+"(?:"+a+")*","m"),inside:{"table-data-rows":{pattern:RegExp("^("+a+o+")(?:"+a+")*$"),lookbehind:!0,inside:{"table-data":{pattern:RegExp(r),inside:e.languages.markdown},punctuation:/\|/}},"table-line":{pattern:RegExp("^("+a+")"+o+"$"),lookbehind:!0,inside:{punctuation:/\||:?-{3,}:?/}},"table-header-row":{pattern:RegExp("^"+a+"$"),inside:{"table-header":{pattern:RegExp(r),alias:"important",inside:e.languages.markdown},punctuation:/\|/}}}},code:[{pattern:/((?:^|\n)[ \t]*\n|(?:^|\r\n?)[ \t]*\r\n?)(?: {4}|\t).+(?:(?:\n|\r\n?)(?: {4}|\t).+)*/,lookbehind:!0,alias:"keyword"},{pattern:/^```[\s\S]*?^```$/m,greedy:!0,inside:{"code-block":{pattern:/^(```.*(?:\n|\r\n?))[\s\S]+?(?=(?:\n|\r\n?)^```$)/m,lookbehind:!0},"code-language":{pattern:/^(```).+/,lookbehind:!0},punctuation:/```/}}],title:[{pattern:/\S.*(?:\n|\r\n?)(?:==+|--+)(?=[ \t]*$)/m,alias:"important",inside:{punctuation:/==+$|--+$/}},{pattern:/(^\s*)#.+/m,lookbehind:!0,alias:"important",inside:{punctuation:/^#+|#+$/}}],hr:{pattern:/(^\s*)([*-])(?:[\t ]*\2){2,}(?=\s*$)/m,lookbehind:!0,alias:"punctuation"},list:{pattern:/(^\s*)(?:[*+-]|\d+\.)(?=[\t ].)/m,lookbehind:!0,alias:"punctuation"},"url-reference":{pattern:/!?\[[^\]]+\]:[\t ]+(?:\S+|<(?:\\.|[^>\\])+>)(?:[\t ]+(?:"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|\((?:\\.|[^)\\])*\)))?/,inside:{variable:{pattern:/^(!?\[)[^\]]+/,lookbehind:!0},string:/(?:"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|\((?:\\.|[^)\\])*\))$/,punctuation:/^[\[\]!:]|[<>]/},alias:"url"},bold:{pattern:n(/\b__(?:(?!_)<inner>|_(?:(?!_)<inner>)+_)+__\b|\*\*(?:(?!\*)<inner>|\*(?:(?!\*)<inner>)+\*)+\*\*/.source),lookbehind:!0,greedy:!0,inside:{content:{pattern:/(^..)[\s\S]+(?=..$)/,lookbehind:!0,inside:{}},punctuation:/\*\*|__/}},italic:{pattern:n(/\b_(?:(?!_)<inner>|__(?:(?!_)<inner>)+__)+_\b|\*(?:(?!\*)<inner>|\*\*(?:(?!\*)<inner>)+\*\*)+\*/.source),lookbehind:!0,greedy:!0,inside:{content:{pattern:/(^.)[\s\S]+(?=.$)/,lookbehind:!0,inside:{}},punctuation:/[*_]/}},strike:{pattern:n(/(~~?)(?:(?!~)<inner>)+\2/.source),lookbehind:!0,greedy:!0,inside:{content:{pattern:/(^~~?)[\s\S]+(?=\1$)/,lookbehind:!0,inside:{}},punctuation:/~~?/}},"code-snippet":{pattern:/(^|[^\\`])(?:``[^`\r\n]+(?:`[^`\r\n]+)*``(?!`)|`[^`\r\n]+`(?!`))/,lookbehind:!0,greedy:!0,alias:["code","keyword"]},url:{pattern:n(/!?\[(?:(?!\])<inner>)+\](?:\([^\s)]+(?:[\t ]+"(?:\\.|[^"\\])*")?\)|[ \t]?\[(?:(?!\])<inner>)+\])/.source),lookbehind:!0,greedy:!0,inside:{operator:/^!/,content:{pattern:/(^\[)[^\]]+(?=\])/,lookbehind:!0,inside:{}},variable:{pattern:/(^\][ \t]?\[)[^\]]+(?=\]$)/,lookbehind:!0},url:{pattern:/(^\]\()[^\s)]+/,lookbehind:!0},string:{pattern:/(^[ \t]+)"(?:\\.|[^"\\])*"(?=\)$)/,lookbehind:!0}}}}),["url","bold","italic","strike"].forEach(function(t){["url","bold","italic","strike","code-snippet"].forEach(function(n){t!==n&&(e.languages.markdown[t].inside.content.inside[n]=e.languages.markdown[n])})}),e.hooks.add("after-tokenize",function(e){"markdown"!==e.language&&"md"!==e.language||function e(t){if(t&&"string"!=typeof t)for(var n=0,r=t.length;n<r;n++){var a,o=t[n];"code"!==o.type?e(o.content):(a=o.content[1],o=o.content[3],a&&o&&"code-language"===a.type&&"code-block"===o.type&&"string"==typeof a.content&&(a=a.content.replace(/\b#/g,"sharp").replace(/\b\+\+/g,"pp"),a="language-"+(a=(/[a-z][\w-]*/i.exec(a)||[""])[0].toLowerCase()),o.alias?"string"==typeof o.alias?o.alias=[o.alias,a]:o.alias.push(a):o.alias=[a]))}}(e.tokens)}),e.hooks.add("wrap",function(t){if("code-block"===t.type){for(var n,r="",a=0,o=t.classes.length;a<o;a++){var c=t.classes[a],c=/language-(.+)/.exec(c);if(c){r=c[1];break}}var u,d=e.languages[r];d?t.content=e.highlight(t.content.replace(i,"").replace(/&(\w{1,8}|#x?[\da-f]{1,8});/gi,function(e,t){return"#"===(t=t.toLowerCase())[0]?s("x"===t[1]?parseInt(t.slice(2),16):Number(t.slice(1))):l[t]||e}),d,r):r&&"none"!==r&&e.plugins.autoloader&&(u="md-"+new Date().valueOf()+"-"+Math.floor(1e16*Math.random()),t.attributes.id=u,e.plugins.autoloader.loadLanguages(r,function(){var t=document.getElementById(u);t&&(t.innerHTML=e.highlight(t.textContent,e.languages[r],r))}))}}),RegExp(e.languages.markup.tag.pattern.source,"gi")),l={amp:"&",lt:"<",gt:">",quot:'"'},s=String.fromCodePoint||String.fromCharCode;e.languages.md=e.languages.markdown}(B),B.languages.graphql={comment:/#.*/,description:{pattern:/(?:"""(?:[^"]|(?!""")")*"""|"(?:\\.|[^\\"\r\n])*")(?=\s*[a-z_])/i,greedy:!0,alias:"string",inside:{"language-markdown":{pattern:/(^"(?:"")?)(?!\1)[\s\S]+(?=\1$)/,lookbehind:!0,inside:B.languages.markdown}}},string:{pattern:/"""(?:[^"]|(?!""")")*"""|"(?:\\.|[^\\"\r\n])*"/,greedy:!0},number:/(?:\B-|\b)\d+(?:\.\d+)?(?:e[+-]?\d+)?\b/i,boolean:/\b(?:false|true)\b/,variable:/\$[a-z_]\w*/i,directive:{pattern:/@[a-z_]\w*/i,alias:"function"},"attr-name":{pattern:/\b[a-z_]\w*(?=\s*(?:\((?:[^()"]|"(?:\\.|[^\\"\r\n])*")*\))?:)/i,greedy:!0},"atom-input":{pattern:/\b[A-Z]\w*Input\b/,alias:"class-name"},scalar:/\b(?:Boolean|Float|ID|Int|String)\b/,constant:/\b[A-Z][A-Z_\d]*\b/,"class-name":{pattern:/(\b(?:enum|implements|interface|on|scalar|type|union)\s+|&\s*|:\s*|\[)[A-Z_]\w*/,lookbehind:!0},fragment:{pattern:/(\bfragment\s+|\.{3}\s*(?!on\b))[a-zA-Z_]\w*/,lookbehind:!0,alias:"function"},"definition-mutation":{pattern:/(\bmutation\s+)[a-zA-Z_]\w*/,lookbehind:!0,alias:"function"},"definition-query":{pattern:/(\bquery\s+)[a-zA-Z_]\w*/,lookbehind:!0,alias:"function"},keyword:/\b(?:directive|enum|extend|fragment|implements|input|interface|mutation|on|query|repeatable|scalar|schema|subscription|type|union)\b/,operator:/[!=|&]|\.{3}/,"property-query":/\w+(?=\s*\()/,object:/\w+(?=\s*\{)/,punctuation:/[!(){}\[\]:=,]/,property:/\w+/},B.hooks.add("after-tokenize",function(e){if("graphql"===e.language)for(var t=e.tokens.filter(function(e){return"string"!=typeof e&&"comment"!==e.type&&"scalar"!==e.type}),n=0;n<t.length;){var r=t[n++];if("keyword"===r.type&&"mutation"===r.content){var a=[];if(u(["definition-mutation","punctuation"])&&"("===t[n+1].content){n+=2;var o=d(/^\($/,/^\)$/);if(-1===o)continue;for(;n<o;n++){var i=t[n+0];"variable"===i.type&&(p(i,"variable-input"),a.push(i.content))}n=o+1}if(u(["punctuation","property-query"])&&"{"===t[n+0].content&&(p(t[++n+0],"property-mutation"),0<a.length)){var l=d(/^\{$/,/^\}$/);if(-1!==l)for(var s=n;s<l;s++){var c=t[s];"variable"===c.type&&0<=a.indexOf(c.content)&&p(c,"variable-input")}}}}function u(e,r){r=r||0;for(var a=0;a<e.length;a++){var o=t[n+(a+r)];if(!o||o.type!==e[a])return}return 1}function d(e,r){for(var a=1,o=n;o<t.length;o++){var i=t[o],l=i.content;if("punctuation"===i.type&&"string"==typeof l){if(e.test(l))a++;else if(r.test(l)&&0==--a)return o}}return -1}function p(e,t){var n=e.alias;n?Array.isArray(n)||(e.alias=n=[n]):e.alias=n=[],n.push(t)}}),B.languages.sql={comment:{pattern:/(^|[^\\])(?:\/\*[\s\S]*?\*\/|(?:--|\/\/|#).*)/,lookbehind:!0},variable:[{pattern:/@(["'`])(?:\\[\s\S]|(?!\1)[^\\])+\1/,greedy:!0},/@[\w.$]+/],string:{pattern:/(^|[^@\\])("|')(?:\\[\s\S]|(?!\2)[^\\]|\2\2)*\2/,greedy:!0,lookbehind:!0},identifier:{pattern:/(^|[^@\\])`(?:\\[\s\S]|[^`\\]|``)*`/,greedy:!0,lookbehind:!0,inside:{punctuation:/^`|`$/}},function:/\b(?:AVG|COUNT|FIRST|FORMAT|LAST|LCASE|LEN|MAX|MID|MIN|MOD|NOW|ROUND|SUM|UCASE)(?=\s*\()/i,keyword:/\b(?:ACTION|ADD|AFTER|ALGORITHM|ALL|ALTER|ANALYZE|ANY|APPLY|AS|ASC|AUTHORIZATION|AUTO_INCREMENT|BACKUP|BDB|BEGIN|BERKELEYDB|BIGINT|BINARY|BIT|BLOB|BOOL|BOOLEAN|BREAK|BROWSE|BTREE|BULK|BY|CALL|CASCADED?|CASE|CHAIN|CHAR(?:ACTER|SET)?|CHECK(?:POINT)?|CLOSE|CLUSTERED|COALESCE|COLLATE|COLUMNS?|COMMENT|COMMIT(?:TED)?|COMPUTE|CONNECT|CONSISTENT|CONSTRAINT|CONTAINS(?:TABLE)?|CONTINUE|CONVERT|CREATE|CROSS|CURRENT(?:_DATE|_TIME|_TIMESTAMP|_USER)?|CURSOR|CYCLE|DATA(?:BASES?)?|DATE(?:TIME)?|DAY|DBCC|DEALLOCATE|DEC|DECIMAL|DECLARE|DEFAULT|DEFINER|DELAYED|DELETE|DELIMITERS?|DENY|DESC|DESCRIBE|DETERMINISTIC|DISABLE|DISCARD|DISK|DISTINCT|DISTINCTROW|DISTRIBUTED|DO|DOUBLE|DROP|DUMMY|DUMP(?:FILE)?|DUPLICATE|ELSE(?:IF)?|ENABLE|ENCLOSED|END|ENGINE|ENUM|ERRLVL|ERRORS|ESCAPED?|EXCEPT|EXEC(?:UTE)?|EXISTS|EXIT|EXPLAIN|EXTENDED|FETCH|FIELDS|FILE|FILLFACTOR|FIRST|FIXED|FLOAT|FOLLOWING|FOR(?: EACH ROW)?|FORCE|FOREIGN|FREETEXT(?:TABLE)?|FROM|FULL|FUNCTION|GEOMETRY(?:COLLECTION)?|GLOBAL|GOTO|GRANT|GROUP|HANDLER|HASH|HAVING|HOLDLOCK|HOUR|IDENTITY(?:COL|_INSERT)?|IF|IGNORE|IMPORT|INDEX|INFILE|INNER|INNODB|INOUT|INSERT|INT|INTEGER|INTERSECT|INTERVAL|INTO|INVOKER|ISOLATION|ITERATE|JOIN|KEYS?|KILL|LANGUAGE|LAST|LEAVE|LEFT|LEVEL|LIMIT|LINENO|LINES|LINESTRING|LOAD|LOCAL|LOCK|LONG(?:BLOB|TEXT)|LOOP|MATCH(?:ED)?|MEDIUM(?:BLOB|INT|TEXT)|MERGE|MIDDLEINT|MINUTE|MODE|MODIFIES|MODIFY|MONTH|MULTI(?:LINESTRING|POINT|POLYGON)|NATIONAL|NATURAL|NCHAR|NEXT|NO|NONCLUSTERED|NULLIF|NUMERIC|OFF?|OFFSETS?|ON|OPEN(?:DATASOURCE|QUERY|ROWSET)?|OPTIMIZE|OPTION(?:ALLY)?|ORDER|OUT(?:ER|FILE)?|OVER|PARTIAL|PARTITION|PERCENT|PIVOT|PLAN|POINT|POLYGON|PRECEDING|PRECISION|PREPARE|PREV|PRIMARY|PRINT|PRIVILEGES|PROC(?:EDURE)?|PUBLIC|PURGE|QUICK|RAISERROR|READS?|REAL|RECONFIGURE|REFERENCES|RELEASE|RENAME|REPEAT(?:ABLE)?|REPLACE|REPLICATION|REQUIRE|RESIGNAL|RESTORE|RESTRICT|RETURN(?:ING|S)?|REVOKE|RIGHT|ROLLBACK|ROUTINE|ROW(?:COUNT|GUIDCOL|S)?|RTREE|RULE|SAVE(?:POINT)?|SCHEMA|SECOND|SELECT|SERIAL(?:IZABLE)?|SESSION(?:_USER)?|SET(?:USER)?|SHARE|SHOW|SHUTDOWN|SIMPLE|SMALLINT|SNAPSHOT|SOME|SONAME|SQL|START(?:ING)?|STATISTICS|STATUS|STRIPED|SYSTEM_USER|TABLES?|TABLESPACE|TEMP(?:ORARY|TABLE)?|TERMINATED|TEXT(?:SIZE)?|THEN|TIME(?:STAMP)?|TINY(?:BLOB|INT|TEXT)|TOP?|TRAN(?:SACTIONS?)?|TRIGGER|TRUNCATE|TSEQUAL|TYPES?|UNBOUNDED|UNCOMMITTED|UNDEFINED|UNION|UNIQUE|UNLOCK|UNPIVOT|UNSIGNED|UPDATE(?:TEXT)?|USAGE|USE|USER|USING|VALUES?|VAR(?:BINARY|CHAR|CHARACTER|YING)|VIEW|WAITFOR|WARNINGS|WHEN|WHERE|WHILE|WITH(?: ROLLUP|IN)?|WORK|WRITE(?:TEXT)?|YEAR)\b/i,boolean:/\b(?:FALSE|NULL|TRUE)\b/i,number:/\b0x[\da-f]+\b|\b\d+(?:\.\d*)?|\B\.\d+\b/i,operator:/[-+*\/=%^~]|&&?|\|\|?|!=?|<(?:=>?|<|>)?|>[>=]?|\b(?:AND|BETWEEN|DIV|ILIKE|IN|IS|LIKE|NOT|OR|REGEXP|RLIKE|SOUNDS LIKE|XOR)\b/i,punctuation:/[;[\]()`,.]/},function(e){var t=e.languages.javascript["template-string"],n=t.pattern.source,r=t.inside.interpolation,a=r.inside["interpolation-punctuation"],o=r.pattern.source;function i(t,r){if(e.languages[t])return{pattern:RegExp("((?:"+r+")\\s*)"+n),lookbehind:!0,greedy:!0,inside:{"template-punctuation":{pattern:/^`|`$/,alias:"string"},"embedded-code":{pattern:/[\s\S]+/,alias:t}}}}function l(t,n,r){return t={code:t,grammar:n,language:r},e.hooks.run("before-tokenize",t),t.tokens=e.tokenize(t.code,t.grammar),e.hooks.run("after-tokenize",t),t.tokens}e.languages.javascript["template-string"]=[i("css",/\b(?:styled(?:\([^)]*\))?(?:\s*\.\s*\w+(?:\([^)]*\))*)*|css(?:\s*\.\s*(?:global|resolve))?|createGlobalStyle|keyframes)/.source),i("html",/\bhtml|\.\s*(?:inner|outer)HTML\s*\+?=/.source),i("svg",/\bsvg/.source),i("markdown",/\b(?:markdown|md)/.source),i("graphql",/\b(?:gql|graphql(?:\s*\.\s*experimental)?)/.source),i("sql",/\bsql/.source),t].filter(Boolean);var s={javascript:!0,js:!0,typescript:!0,ts:!0,jsx:!0,tsx:!0};e.hooks.add("after-tokenize",function(t){t.language in s&&function t(n){for(var i=0,s=n.length;i<s;i++){var c,u,d,p=n[i];"string"!=typeof p&&(Array.isArray(c=p.content)?"template-string"===p.type?(p=c[1],3===c.length&&"string"!=typeof p&&"embedded-code"===p.type&&(u=function e(t){return"string"==typeof t?t:Array.isArray(t)?t.map(e).join(""):e(t.content)}(p),p=Array.isArray(p=p.alias)?p[0]:p,d=e.languages[p])&&(c[1]=function(t,n,i){var s=e.tokenize(t,{interpolation:{pattern:RegExp(o),lookbehind:!0}}),c=0,u={},s=l(s.map(function(e){if("string"==typeof e)return e;for(var n,r,e=e.content;-1!==t.indexOf((r=c++,n="___"+i.toUpperCase()+"_"+r+"___")););return u[n]=e,n}).join(""),n,i),d=Object.keys(u);return c=0,function t(n){for(var o=0;o<n.length;o++){if(c>=d.length)return;var i,s,p,f,m,g,h,b=n[o];"string"==typeof b||"string"==typeof b.content?(i=d[c],-1!==(h=(g="string"==typeof b?b:b.content).indexOf(i))&&(++c,s=g.substring(0,h),m=u[i],p=void 0,(f={})["interpolation-punctuation"]=a,3===(f=e.tokenize(m,f)).length&&((p=[1,1]).push.apply(p,l(f[1],e.languages.javascript,"javascript")),f.splice.apply(f,p)),p=new e.Token("interpolation",f,r.alias,m),f=g.substring(h+i.length),m=[],s&&m.push(s),m.push(p),f&&(t(g=[f]),m.push.apply(m,g)),"string"==typeof b?(n.splice.apply(n,[o,1].concat(m)),o+=m.length-1):b.content=m)):Array.isArray(h=b.content)?t(h):t([h])}}(s),new e.Token(i,s,"language-"+i,t)}(u,d,p))):t(c):"string"!=typeof c&&t([c]))}}(t.tokens)})}(B),B.languages.typescript=B.languages.extend("javascript",{"class-name":{pattern:/(\b(?:class|extends|implements|instanceof|interface|new|type)\s+)(?!keyof\b)(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?:\s*<(?:[^<>]|<(?:[^<>]|<[^<>]*>)*>)*>)?/,lookbehind:!0,greedy:!0,inside:null},builtin:/\b(?:Array|Function|Promise|any|boolean|console|never|number|string|symbol|unknown)\b/}),B.languages.typescript.keyword.push(/\b(?:abstract|declare|is|keyof|readonly|require)\b/,/\b(?:asserts|infer|interface|module|namespace|type)\b(?=\s*(?:[{_$a-zA-Z\xA0-\uFFFF]|$))/,/\btype\b(?=\s*(?:[\{*]|$))/),delete B.languages.typescript.parameter,delete B.languages.typescript["literal-property"],y=B.languages.extend("typescript",{}),delete y["class-name"],B.languages.typescript["class-name"].inside=y,B.languages.insertBefore("typescript","function",{decorator:{pattern:/@[$\w\xA0-\uFFFF]+/,inside:{at:{pattern:/^@/,alias:"operator"},function:/^[\s\S]+/}},"generic-function":{pattern:/#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*\s*<(?:[^<>]|<(?:[^<>]|<[^<>]*>)*>)*>(?=\s*\()/,greedy:!0,inside:{function:/^#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*/,generic:{pattern:/<[\s\S]+/,alias:"class-name",inside:y}}}}),B.languages.ts=B.languages.typescript,v=B.languages.javascript,k="(@(?:arg|argument|param|property)\\s+(?:"+(w=/\{(?:[^{}]|\{(?:[^{}]|\{[^{}]*\})*\})+\}/.source)+"\\s+)?)",B.languages.jsdoc=B.languages.extend("javadoclike",{parameter:{pattern:RegExp(k+/(?:(?!\s)[$\w\xA0-\uFFFF.])+(?=\s|$)/.source),lookbehind:!0,inside:{punctuation:/\./}}}),B.languages.insertBefore("jsdoc","keyword",{"optional-parameter":{pattern:RegExp(k+/\[(?:(?!\s)[$\w\xA0-\uFFFF.])+(?:=[^[\]]+)?\](?=\s|$)/.source),lookbehind:!0,inside:{parameter:{pattern:/(^\[)[$\w\xA0-\uFFFF\.]+/,lookbehind:!0,inside:{punctuation:/\./}},code:{pattern:/(=)[\s\S]*(?=\]$)/,lookbehind:!0,inside:v,alias:"language-javascript"},punctuation:/[=[\]]/}},"class-name":[{pattern:RegExp(/(@(?:augments|class|extends|interface|memberof!?|template|this|typedef)\s+(?:<TYPE>\s+)?)[A-Z]\w*(?:\.[A-Z]\w*)*/.source.replace(/<TYPE>/g,function(){return w})),lookbehind:!0,inside:{punctuation:/\./}},{pattern:RegExp("(@[a-z]+\\s+)"+w),lookbehind:!0,inside:{string:v.string,number:v.number,boolean:v.boolean,keyword:B.languages.typescript.keyword,operator:/=>|\.\.\.|[&|?:*]/,punctuation:/[.,;=<>{}()[\]]/}}],example:{pattern:/(@example\s+(?!\s))(?:[^@\s]|\s+(?!\s))+?(?=\s*(?:\*\s*)?(?:@\w|\*\/))/,lookbehind:!0,inside:{code:{pattern:/^([\t ]*(?:\*\s*)?)\S.*$/m,lookbehind:!0,inside:v,alias:"language-javascript"}}}}),B.languages.javadoclike.addSupport("javascript",B.languages.jsdoc),B.languages.flow=B.languages.extend("javascript",{}),B.languages.insertBefore("flow","keyword",{type:[{pattern:/\b(?:[Bb]oolean|Function|[Nn]umber|[Ss]tring|[Ss]ymbol|any|mixed|null|void)\b/,alias:"class-name"}]}),B.languages.flow["function-variable"].pattern=/(?!\s)[_$a-z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*=\s*(?:function\b|(?:\([^()]*\)(?:\s*:\s*\w+)?|(?!\s)[_$a-z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*)\s*=>))/i,delete B.languages.flow.parameter,B.languages.insertBefore("flow","operator",{"flow-punctuation":{pattern:/\{\||\|\}/,alias:"punctuation"}}),Array.isArray(B.languages.flow.keyword)||(B.languages.flow.keyword=[B.languages.flow.keyword]),B.languages.flow.keyword.unshift({pattern:/(^|[^$]\b)(?:Class|declare|opaque|type)\b(?!\$)/,lookbehind:!0},{pattern:/(^|[^$]\B)\$(?:Diff|Enum|Exact|Keys|ObjMap|PropertyType|Record|Shape|Subtype|Supertype|await)\b(?!\$)/,lookbehind:!0}),B.languages.n4js=B.languages.extend("javascript",{keyword:/\b(?:Array|any|boolean|break|case|catch|class|const|constructor|continue|debugger|declare|default|delete|do|else|enum|export|extends|false|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|module|new|null|number|package|private|protected|public|return|set|static|string|super|switch|this|throw|true|try|typeof|var|void|while|with|yield)\b/}),B.languages.insertBefore("n4js","constant",{annotation:{pattern:/@+\w+/,alias:"operator"}}),B.languages.n4jsd=B.languages.n4js,function(e){function t(e,t){return RegExp(e.replace(/<ID>/g,function(){return/(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*/.source}),t)}e.languages.insertBefore("javascript","function-variable",{"method-variable":{pattern:RegExp("(\\.\\s*)"+e.languages.javascript["function-variable"].pattern.source),lookbehind:!0,alias:["function-variable","method","function","property-access"]}}),e.languages.insertBefore("javascript","function",{method:{pattern:RegExp("(\\.\\s*)"+e.languages.javascript.function.source),lookbehind:!0,alias:["function","property-access"]}}),e.languages.insertBefore("javascript","constant",{"known-class-name":[{pattern:/\b(?:(?:Float(?:32|64)|(?:Int|Uint)(?:8|16|32)|Uint8Clamped)?Array|ArrayBuffer|BigInt|Boolean|DataView|Date|Error|Function|Intl|JSON|(?:Weak)?(?:Map|Set)|Math|Number|Object|Promise|Proxy|Reflect|RegExp|String|Symbol|WebAssembly)\b/,alias:"class-name"},{pattern:/\b(?:[A-Z]\w*)Error\b/,alias:"class-name"}]}),e.languages.insertBefore("javascript","keyword",{imports:{pattern:t(/(\bimport\b\s*)(?:<ID>(?:\s*,\s*(?:\*\s*as\s+<ID>|\{[^{}]*\}))?|\*\s*as\s+<ID>|\{[^{}]*\})(?=\s*\bfrom\b)/.source),lookbehind:!0,inside:e.languages.javascript},exports:{pattern:t(/(\bexport\b\s*)(?:\*(?:\s*as\s+<ID>)?(?=\s*\bfrom\b)|\{[^{}]*\})/.source),lookbehind:!0,inside:e.languages.javascript}}),e.languages.javascript.keyword.unshift({pattern:/\b(?:as|default|export|from|import)\b/,alias:"module"},{pattern:/\b(?:await|break|catch|continue|do|else|finally|for|if|return|switch|throw|try|while|yield)\b/,alias:"control-flow"},{pattern:/\bnull\b/,alias:["null","nil"]},{pattern:/\bundefined\b/,alias:"nil"}),e.languages.insertBefore("javascript","operator",{spread:{pattern:/\.{3}/,alias:"operator"},arrow:{pattern:/=>/,alias:"operator"}}),e.languages.insertBefore("javascript","punctuation",{"property-access":{pattern:t(/(\.\s*)#?<ID>/.source),lookbehind:!0},"maybe-class-name":{pattern:/(^|[^$\w\xA0-\uFFFF])[A-Z][$\w\xA0-\uFFFF]+/,lookbehind:!0},dom:{pattern:/\b(?:document|(?:local|session)Storage|location|navigator|performance|window)\b/,alias:"variable"},console:{pattern:/\bconsole(?=\s*\.)/,alias:"class-name"}});for(var n=["function","function-variable","method","method-variable","property-access"],r=0;r<n.length;r++){var a=n[r],o=e.languages.javascript[a],a=(o="RegExp"===e.util.type(o)?e.languages.javascript[a]={pattern:o}:o).inside||{};(o.inside=a)["maybe-class-name"]=/^[A-Z][\s\S]*/}}(B),function(e){var t=e.util.clone(e.languages.javascript),n=/(?:\s|\/\/.*(?!.)|\/\*(?:[^*]|\*(?!\/))\*\/)/.source,r=/(?:\{(?:\{(?:\{[^{}]*\}|[^{}])*\}|[^{}])*\})/.source,a=/(?:\{<S>*\.{3}(?:[^{}]|<BRACES>)*\})/.source;function o(e,t){return RegExp(e=e.replace(/<S>/g,function(){return n}).replace(/<BRACES>/g,function(){return r}).replace(/<SPREAD>/g,function(){return a}),t)}a=o(a).source,e.languages.jsx=e.languages.extend("markup",t),e.languages.jsx.tag.pattern=o(/<\/?(?:[\w.:-]+(?:<S>+(?:[\w.:$-]+(?:=(?:"(?:\\[\s\S]|[^\\"])*"|'(?:\\[\s\S]|[^\\'])*'|[^\s{'"/>=]+|<BRACES>))?|<SPREAD>))*<S>*\/?)?>/.source),e.languages.jsx.tag.inside.tag.pattern=/^<\/?[^\s>\/]*/,e.languages.jsx.tag.inside["attr-value"].pattern=/=(?!\{)(?:"(?:\\[\s\S]|[^\\"])*"|'(?:\\[\s\S]|[^\\'])*'|[^\s'">]+)/,e.languages.jsx.tag.inside.tag.inside["class-name"]=/^[A-Z]\w*(?:\.[A-Z]\w*)*$/,e.languages.jsx.tag.inside.comment=t.comment,e.languages.insertBefore("inside","attr-name",{spread:{pattern:o(/<SPREAD>/.source),inside:e.languages.jsx}},e.languages.jsx.tag),e.languages.insertBefore("inside","special-attr",{script:{pattern:o(/=<BRACES>/.source),alias:"language-javascript",inside:{"script-punctuation":{pattern:/^=(?=\{)/,alias:"punctuation"},rest:e.languages.jsx}}},e.languages.jsx.tag);var i=function(e){return e?"string"==typeof e?e:"string"==typeof e.content?e.content:e.content.map(i).join(""):""};e.hooks.add("after-tokenize",function(t){"jsx"!==t.language&&"tsx"!==t.language||function t(n){for(var r=[],a=0;a<n.length;a++){var o=n[a],l=!1;"string"!=typeof o&&("tag"===o.type&&o.content[0]&&"tag"===o.content[0].type?"</"===o.content[0].content[0].content?0<r.length&&r[r.length-1].tagName===i(o.content[0].content[1])&&r.pop():"/>"!==o.content[o.content.length-1].content&&r.push({tagName:i(o.content[0].content[1]),openedBraces:0}):0<r.length&&"punctuation"===o.type&&"{"===o.content?r[r.length-1].openedBraces++:0<r.length&&0<r[r.length-1].openedBraces&&"punctuation"===o.type&&"}"===o.content?r[r.length-1].openedBraces--:l=!0),(l||"string"==typeof o)&&0<r.length&&0===r[r.length-1].openedBraces&&(l=i(o),a<n.length-1&&("string"==typeof n[a+1]||"plain-text"===n[a+1].type)&&(l+=i(n[a+1]),n.splice(a+1,1)),0<a&&("string"==typeof n[a-1]||"plain-text"===n[a-1].type)&&(l=i(n[a-1])+l,n.splice(a-1,1),a--),n[a]=new e.Token("plain-text",l,null,l)),o.content&&"string"!=typeof o.content&&t(o.content)}}(t.tokens)})}(B),x=B.util.clone(B.languages.typescript),(B.languages.tsx=B.languages.extend("jsx",x),delete B.languages.tsx.parameter,delete B.languages.tsx["literal-property"],x=B.languages.tsx.tag).pattern=RegExp(/(^|[^\w$]|(?=<\/))/.source+"(?:"+x.pattern.source+")",x.pattern.flags),x.lookbehind=!0,B.languages.swift={comment:{pattern:/(^|[^\\:])(?:\/\/.*|\/\*(?:[^/*]|\/(?!\*)|\*(?!\/)|\/\*(?:[^*]|\*(?!\/))*\*\/)*\*\/)/,lookbehind:!0,greedy:!0},"string-literal":[{pattern:RegExp(/(^|[^"#])/.source+"(?:"+/"(?:\\(?:\((?:[^()]|\([^()]*\))*\)|\r\n|[^(])|[^\\\r\n"])*"/.source+"|"+/"""(?:\\(?:\((?:[^()]|\([^()]*\))*\)|[^(])|[^\\"]|"(?!""))*"""/.source+")"+/(?!["#])/.source),lookbehind:!0,greedy:!0,inside:{interpolation:{pattern:/(\\\()(?:[^()]|\([^()]*\))*(?=\))/,lookbehind:!0,inside:null},"interpolation-punctuation":{pattern:/^\)|\\\($/,alias:"punctuation"},punctuation:/\\(?=[\r\n])/,string:/[\s\S]+/}},{pattern:RegExp(/(^|[^"#])(#+)/.source+"(?:"+/"(?:\\(?:#+\((?:[^()]|\([^()]*\))*\)|\r\n|[^#])|[^\\\r\n])*?"/.source+"|"+/"""(?:\\(?:#+\((?:[^()]|\([^()]*\))*\)|[^#])|[^\\])*?"""/.source+")\\2"),lookbehind:!0,greedy:!0,inside:{interpolation:{pattern:/(\\#+\()(?:[^()]|\([^()]*\))*(?=\))/,lookbehind:!0,inside:null},"interpolation-punctuation":{pattern:/^\)|\\#+\($/,alias:"punctuation"},string:/[\s\S]+/}}],directive:{pattern:RegExp(/#/.source+"(?:"+/(?:elseif|if)\b/.source+"(?:[ ]*"+/(?:![ \t]*)?(?:\b\w+\b(?:[ \t]*\((?:[^()]|\([^()]*\))*\))?|\((?:[^()]|\([^()]*\))*\))(?:[ \t]*(?:&&|\|\|))?/.source+")+|"+/(?:else|endif)\b/.source+")"),alias:"property",inside:{"directive-name":/^#\w+/,boolean:/\b(?:false|true)\b/,number:/\b\d+(?:\.\d+)*\b/,operator:/!|&&|\|\||[<>]=?/,punctuation:/[(),]/}},literal:{pattern:/#(?:colorLiteral|column|dsohandle|file(?:ID|Literal|Path)?|function|imageLiteral|line)\b/,alias:"constant"},"other-directive":{pattern:/#\w+\b/,alias:"property"},attribute:{pattern:/@\w+/,alias:"atrule"},"function-definition":{pattern:/(\bfunc\s+)\w+/,lookbehind:!0,alias:"function"},label:{pattern:/\b(break|continue)\s+\w+|\b[a-zA-Z_]\w*(?=\s*:\s*(?:for|repeat|while)\b)/,lookbehind:!0,alias:"important"},keyword:/\b(?:Any|Protocol|Self|Type|actor|as|assignment|associatedtype|associativity|async|await|break|case|catch|class|continue|convenience|default|defer|deinit|didSet|do|dynamic|else|enum|extension|fallthrough|fileprivate|final|for|func|get|guard|higherThan|if|import|in|indirect|infix|init|inout|internal|is|isolated|lazy|left|let|lowerThan|mutating|none|nonisolated|nonmutating|open|operator|optional|override|postfix|precedencegroup|prefix|private|protocol|public|repeat|required|rethrows|return|right|safe|self|set|some|static|struct|subscript|super|switch|throw|throws|try|typealias|unowned|unsafe|var|weak|where|while|willSet)\b/,boolean:/\b(?:false|true)\b/,nil:{pattern:/\bnil\b/,alias:"constant"},"short-argument":/\$\d+\b/,omit:{pattern:/\b_\b/,alias:"keyword"},number:/\b(?:[\d_]+(?:\.[\de_]+)?|0x[a-f0-9_]+(?:\.[a-f0-9p_]+)?|0b[01_]+|0o[0-7_]+)\b/i,"class-name":/\b[A-Z](?:[A-Z_\d]*[a-z]\w*)?\b/,function:/\b[a-z_]\w*(?=\s*\()/i,constant:/\b(?:[A-Z_]{2,}|k[A-Z][A-Za-z_]+)\b/,operator:/[-+*/%=!<>&|^~?]+|\.[.\-+*/%=!<>&|^~?]+/,punctuation:/[{}[\]();,.:\\]/},B.languages.swift["string-literal"].forEach(function(e){e.inside.interpolation.inside=B.languages.swift}),B.languages.kotlin=B.languages.extend("clike",{keyword:{pattern:/(^|[^.])\b(?:abstract|actual|annotation|as|break|by|catch|class|companion|const|constructor|continue|crossinline|data|do|dynamic|else|enum|expect|external|final|finally|for|fun|get|if|import|in|infix|init|inline|inner|interface|internal|is|lateinit|noinline|null|object|open|operator|out|override|package|private|protected|public|reified|return|sealed|set|super|suspend|tailrec|this|throw|to|try|typealias|val|var|vararg|when|where|while)\b/,lookbehind:!0},function:[{pattern:/(?:`[^\r\n`]+`|\b\w+)(?=\s*\()/,greedy:!0},{pattern:/(\.)(?:`[^\r\n`]+`|\w+)(?=\s*\{)/,lookbehind:!0,greedy:!0}],number:/\b(?:0[xX][\da-fA-F]+(?:_[\da-fA-F]+)*|0[bB][01]+(?:_[01]+)*|\d+(?:_\d+)*(?:\.\d+(?:_\d+)*)?(?:[eE][+-]?\d+(?:_\d+)*)?[fFL]?)\b/,operator:/\+[+=]?|-[-=>]?|==?=?|!(?:!|==?)?|[\/*%<>]=?|[?:]:?|\.\.|&&|\|\||\b(?:and|inv|or|shl|shr|ushr|xor)\b/}),delete B.languages.kotlin["class-name"],S={"interpolation-punctuation":{pattern:/^\$\{?|\}$/,alias:"punctuation"},expression:{pattern:/[\s\S]+/,inside:B.languages.kotlin}},B.languages.insertBefore("kotlin","string",{"string-literal":[{pattern:/"""(?:[^$]|\$(?:(?!\{)|\{[^{}]*\}))*?"""/,alias:"multiline",inside:{interpolation:{pattern:/\$(?:[a-z_]\w*|\{[^{}]*\})/i,inside:S},string:/[\s\S]+/}},{pattern:/"(?:[^"\\\r\n$]|\\.|\$(?:(?!\{)|\{[^{}]*\}))*"/,alias:"singleline",inside:{interpolation:{pattern:/((?:^|[^\\])(?:\\{2})*)\$(?:[a-z_]\w*|\{[^{}]*\})/i,lookbehind:!0,inside:S},string:/[\s\S]+/}}],char:{pattern:/'(?:[^'\\\r\n]|\\(?:.|u[a-fA-F0-9]{0,4}))'/,greedy:!0}}),delete B.languages.kotlin.string,B.languages.insertBefore("kotlin","keyword",{annotation:{pattern:/\B@(?:\w+:)?(?:[A-Z]\w*|\[[^\]]+\])/,alias:"builtin"}}),B.languages.insertBefore("kotlin","function",{label:{pattern:/\b\w+@|@\w+\b/,alias:"symbol"}}),B.languages.kt=B.languages.kotlin,B.languages.kts=B.languages.kotlin,B.languages.c=B.languages.extend("clike",{comment:{pattern:/\/\/(?:[^\r\n\\]|\\(?:\r\n?|\n|(?![\r\n])))*|\/\*[\s\S]*?(?:\*\/|$)/,greedy:!0},string:{pattern:/"(?:\\(?:\r\n|[\s\S])|[^"\\\r\n])*"/,greedy:!0},"class-name":{pattern:/(\b(?:enum|struct)\s+(?:__attribute__\s*\(\([\s\S]*?\)\)\s*)?)\w+|\b[a-z]\w*_t\b/,lookbehind:!0},keyword:/\b(?:_Alignas|_Alignof|_Atomic|_Bool|_Complex|_Generic|_Imaginary|_Noreturn|_Static_assert|_Thread_local|__attribute__|asm|auto|break|case|char|const|continue|default|do|double|else|enum|extern|float|for|goto|if|inline|int|long|register|return|short|signed|sizeof|static|struct|switch|typedef|typeof|union|unsigned|void|volatile|while)\b/,function:/\b[a-z_]\w*(?=\s*\()/i,number:/(?:\b0x(?:[\da-f]+(?:\.[\da-f]*)?|\.[\da-f]+)(?:p[+-]?\d+)?|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:e[+-]?\d+)?)[ful]{0,4}/i,operator:/>>=?|<<=?|->|([-+&|:])\1|[?:~]|[-+*/%&|^!=<>]=?/}),B.languages.insertBefore("c","string",{char:{pattern:/'(?:\\(?:\r\n|[\s\S])|[^'\\\r\n]){0,32}'/,greedy:!0}}),B.languages.insertBefore("c","string",{macro:{pattern:/(^[\t ]*)#\s*[a-z](?:[^\r\n\\/]|\/(?!\*)|\/\*(?:[^*]|\*(?!\/))*\*\/|\\(?:\r\n|[\s\S]))*/im,lookbehind:!0,greedy:!0,alias:"property",inside:{string:[{pattern:/^(#\s*include\s*)<[^>]+>/,lookbehind:!0},B.languages.c.string],char:B.languages.c.char,comment:B.languages.c.comment,"macro-name":[{pattern:/(^#\s*define\s+)\w+\b(?!\()/i,lookbehind:!0},{pattern:/(^#\s*define\s+)\w+\b(?=\()/i,lookbehind:!0,alias:"function"}],directive:{pattern:/^(#\s*)[a-z]+/,lookbehind:!0,alias:"keyword"},"directive-hash":/^#/,punctuation:/##|\\(?=[\r\n])/,expression:{pattern:/\S[\s\S]*/,inside:B.languages.c}}}}),B.languages.insertBefore("c","function",{constant:/\b(?:EOF|NULL|SEEK_CUR|SEEK_END|SEEK_SET|__DATE__|__FILE__|__LINE__|__TIMESTAMP__|__TIME__|__func__|stderr|stdin|stdout)\b/}),delete B.languages.c.boolean,B.languages.objectivec=B.languages.extend("c",{string:{pattern:/@?"(?:\\(?:\r\n|[\s\S])|[^"\\\r\n])*"/,greedy:!0},keyword:/\b(?:asm|auto|break|case|char|const|continue|default|do|double|else|enum|extern|float|for|goto|if|in|inline|int|long|register|return|self|short|signed|sizeof|static|struct|super|switch|typedef|typeof|union|unsigned|void|volatile|while)\b|(?:@interface|@end|@implementation|@protocol|@class|@public|@protected|@private|@property|@try|@catch|@finally|@throw|@synthesize|@dynamic|@selector)\b/,operator:/-[->]?|\+\+?|!=?|<<?=?|>>?=?|==?|&&?|\|\|?|[~^%?*\/@]/}),delete B.languages.objectivec["class-name"],B.languages.objc=B.languages.objectivec,B.languages.reason=B.languages.extend("clike",{string:{pattern:/"(?:\\(?:\r\n|[\s\S])|[^\\\r\n"])*"/,greedy:!0},"class-name":/\b[A-Z]\w*/,keyword:/\b(?:and|as|assert|begin|class|constraint|do|done|downto|else|end|exception|external|for|fun|function|functor|if|in|include|inherit|initializer|lazy|let|method|module|mutable|new|nonrec|object|of|open|or|private|rec|sig|struct|switch|then|to|try|type|val|virtual|when|while|with)\b/,operator:/\.{3}|:[:=]|\|>|->|=(?:==?|>)?|<=?|>=?|[|^?'#!~`]|[+\-*\/]\.?|\b(?:asr|land|lor|lsl|lsr|lxor|mod)\b/}),B.languages.insertBefore("reason","class-name",{char:{pattern:/'(?:\\x[\da-f]{2}|\\o[0-3][0-7][0-7]|\\\d{3}|\\.|[^'\\\r\n])'/,greedy:!0},constructor:/\b[A-Z]\w*\b(?!\s*\.)/,label:{pattern:/\b[a-z]\w*(?=::)/,alias:"symbol"}}),delete B.languages.reason.function,function(e){for(var t=/\/\*(?:[^*/]|\*(?!\/)|\/(?!\*)|<self>)*\*\//.source,n=0;n<2;n++)t=t.replace(/<self>/g,function(){return t});t=t.replace(/<self>/g,function(){return/[^\s\S]/.source}),e.languages.rust={comment:[{pattern:RegExp(/(^|[^\\])/.source+t),lookbehind:!0,greedy:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0,greedy:!0}],string:{pattern:/b?"(?:\\[\s\S]|[^\\"])*"|b?r(#*)"(?:[^"]|"(?!\1))*"\1/,greedy:!0},char:{pattern:/b?'(?:\\(?:x[0-7][\da-fA-F]|u\{(?:[\da-fA-F]_*){1,6}\}|.)|[^\\\r\n\t'])'/,greedy:!0},attribute:{pattern:/#!?\[(?:[^\[\]"]|"(?:\\[\s\S]|[^\\"])*")*\]/,greedy:!0,alias:"attr-name",inside:{string:null}},"closure-params":{pattern:/([=(,:]\s*|\bmove\s*)\|[^|]*\||\|[^|]*\|(?=\s*(?:\{|->))/,lookbehind:!0,greedy:!0,inside:{"closure-punctuation":{pattern:/^\||\|$/,alias:"punctuation"},rest:null}},"lifetime-annotation":{pattern:/'\w+/,alias:"symbol"},"fragment-specifier":{pattern:/(\$\w+:)[a-z]+/,lookbehind:!0,alias:"punctuation"},variable:/\$\w+/,"function-definition":{pattern:/(\bfn\s+)\w+/,lookbehind:!0,alias:"function"},"type-definition":{pattern:/(\b(?:enum|struct|trait|type|union)\s+)\w+/,lookbehind:!0,alias:"class-name"},"module-declaration":[{pattern:/(\b(?:crate|mod)\s+)[a-z][a-z_\d]*/,lookbehind:!0,alias:"namespace"},{pattern:/(\b(?:crate|self|super)\s*)::\s*[a-z][a-z_\d]*\b(?:\s*::(?:\s*[a-z][a-z_\d]*\s*::)*)?/,lookbehind:!0,alias:"namespace",inside:{punctuation:/::/}}],keyword:[/\b(?:Self|abstract|as|async|await|become|box|break|const|continue|crate|do|dyn|else|enum|extern|final|fn|for|if|impl|in|let|loop|macro|match|mod|move|mut|override|priv|pub|ref|return|self|static|struct|super|trait|try|type|typeof|union|unsafe|unsized|use|virtual|where|while|yield)\b/,/\b(?:bool|char|f(?:32|64)|[ui](?:8|16|32|64|128|size)|str)\b/],function:/\b[a-z_]\w*(?=\s*(?:::\s*<|\())/,macro:{pattern:/\b\w+!/,alias:"property"},constant:/\b[A-Z_][A-Z_\d]+\b/,"class-name":/\b[A-Z]\w*\b/,namespace:{pattern:/(?:\b[a-z][a-z_\d]*\s*::\s*)*\b[a-z][a-z_\d]*\s*::(?!\s*<)/,inside:{punctuation:/::/}},number:/\b(?:0x[\dA-Fa-f](?:_?[\dA-Fa-f])*|0o[0-7](?:_?[0-7])*|0b[01](?:_?[01])*|(?:(?:\d(?:_?\d)*)?\.)?\d(?:_?\d)*(?:[Ee][+-]?\d+)?)(?:_?(?:f32|f64|[iu](?:8|16|32|64|size)?))?\b/,boolean:/\b(?:false|true)\b/,punctuation:/->|\.\.=|\.{1,3}|::|[{}[\];(),:]/,operator:/[-+*\/%!^]=?|=[=>]?|&[&=]?|\|[|=]?|<<?=?|>>?=?|[@?]/},e.languages.rust["closure-params"].inside.rest=e.languages.rust,e.languages.rust.attribute.inside.string=e.languages.rust.string}(B),B.languages.go=B.languages.extend("clike",{string:{pattern:/(^|[^\\])"(?:\\.|[^"\\\r\n])*"|`[^`]*`/,lookbehind:!0,greedy:!0},keyword:/\b(?:break|case|chan|const|continue|default|defer|else|fallthrough|for|func|go(?:to)?|if|import|interface|map|package|range|return|select|struct|switch|type|var)\b/,boolean:/\b(?:_|false|iota|nil|true)\b/,number:[/\b0(?:b[01_]+|o[0-7_]+)i?\b/i,/\b0x(?:[a-f\d_]+(?:\.[a-f\d_]*)?|\.[a-f\d_]+)(?:p[+-]?\d+(?:_\d+)*)?i?(?!\w)/i,/(?:\b\d[\d_]*(?:\.[\d_]*)?|\B\.\d[\d_]*)(?:e[+-]?[\d_]+)?i?(?!\w)/i],operator:/[*\/%^!=]=?|\+[=+]?|-[=-]?|\|[=|]?|&(?:=|&|\^=?)?|>(?:>=?|=)?|<(?:<=?|=|-)?|:=|\.\.\./,builtin:/\b(?:append|bool|byte|cap|close|complex|complex(?:64|128)|copy|delete|error|float(?:32|64)|u?int(?:8|16|32|64)?|imag|len|make|new|panic|print(?:ln)?|real|recover|rune|string|uintptr)\b/}),B.languages.insertBefore("go","string",{char:{pattern:/'(?:\\.|[^'\\\r\n]){0,10}'/,greedy:!0}}),delete B.languages.go["class-name"],E=/\b(?:alignas|alignof|asm|auto|bool|break|case|catch|char|char16_t|char32_t|char8_t|class|co_await|co_return|co_yield|compl|concept|const|const_cast|consteval|constexpr|constinit|continue|decltype|default|delete|do|double|dynamic_cast|else|enum|explicit|export|extern|final|float|for|friend|goto|if|import|inline|int|int16_t|int32_t|int64_t|int8_t|long|module|mutable|namespace|new|noexcept|nullptr|operator|override|private|protected|public|register|reinterpret_cast|requires|return|short|signed|sizeof|static|static_assert|static_cast|struct|switch|template|this|thread_local|throw|try|typedef|typeid|typename|uint16_t|uint32_t|uint64_t|uint8_t|union|unsigned|using|virtual|void|volatile|wchar_t|while)\b/,_=/\b(?!<keyword>)\w+(?:\s*\.\s*\w+)*\b/.source.replace(/<keyword>/g,function(){return E.source}),B.languages.cpp=B.languages.extend("c",{"class-name":[{pattern:RegExp(/(\b(?:class|concept|enum|struct|typename)\s+)(?!<keyword>)\w+/.source.replace(/<keyword>/g,function(){return E.source})),lookbehind:!0},/\b[A-Z]\w*(?=\s*::\s*\w+\s*\()/,/\b[A-Z_]\w*(?=\s*::\s*~\w+\s*\()/i,/\b\w+(?=\s*<(?:[^<>]|<(?:[^<>]|<[^<>]*>)*>)*>\s*::\s*\w+\s*\()/],keyword:E,number:{pattern:/(?:\b0b[01']+|\b0x(?:[\da-f']+(?:\.[\da-f']*)?|\.[\da-f']+)(?:p[+-]?[\d']+)?|(?:\b[\d']+(?:\.[\d']*)?|\B\.[\d']+)(?:e[+-]?[\d']+)?)[ful]{0,4}/i,greedy:!0},operator:/>>=?|<<=?|->|--|\+\+|&&|\|\||[?:~]|<=>|[-+*/%&|^!=<>]=?|\b(?:and|and_eq|bitand|bitor|not|not_eq|or|or_eq|xor|xor_eq)\b/,boolean:/\b(?:false|true)\b/}),B.languages.insertBefore("cpp","string",{module:{pattern:RegExp(/(\b(?:import|module)\s+)/.source+"(?:"+/"(?:\\(?:\r\n|[\s\S])|[^"\\\r\n])*"|<[^<>\r\n]*>/.source+"|"+/<mod-name>(?:\s*:\s*<mod-name>)?|:\s*<mod-name>/.source.replace(/<mod-name>/g,function(){return _})+")"),lookbehind:!0,greedy:!0,inside:{string:/^[<"][\s\S]+/,operator:/:/,punctuation:/\./}},"raw-string":{pattern:/R"([^()\\ ]{0,16})\([\s\S]*?\)\1"/,alias:"string",greedy:!0}}),B.languages.insertBefore("cpp","keyword",{"generic-function":{pattern:/\b(?!operator\b)[a-z_]\w*\s*<(?:[^<>]|<[^<>]*>)*>(?=\s*\()/i,inside:{function:/^\w+/,generic:{pattern:/<[\s\S]+/,alias:"class-name",inside:B.languages.cpp}}}}),B.languages.insertBefore("cpp","operator",{"double-colon":{pattern:/::/,alias:"punctuation"}}),B.languages.insertBefore("cpp","class-name",{"base-clause":{pattern:/(\b(?:class|struct)\s+\w+\s*:\s*)[^;{}"'\s]+(?:\s+[^;{}"'\s]+)*(?=\s*[;{])/,lookbehind:!0,greedy:!0,inside:B.languages.extend("cpp",{})}}),B.languages.insertBefore("inside","double-colon",{"class-name":/\b[a-z_]\w*\b(?!\s*::)/i},B.languages.cpp["base-clause"]),B.languages.python={comment:{pattern:/(^|[^\\])#.*/,lookbehind:!0,greedy:!0},"string-interpolation":{pattern:/(?:f|fr|rf)(?:("""|''')[\s\S]*?\1|("|')(?:\\.|(?!\2)[^\\\r\n])*\2)/i,greedy:!0,inside:{interpolation:{pattern:/((?:^|[^{])(?:\{\{)*)\{(?!\{)(?:[^{}]|\{(?!\{)(?:[^{}]|\{(?!\{)(?:[^{}])+\})+\})+\}/,lookbehind:!0,inside:{"format-spec":{pattern:/(:)[^:(){}]+(?=\}$)/,lookbehind:!0},"conversion-option":{pattern:/![sra](?=[:}]$)/,alias:"punctuation"},rest:null}},string:/[\s\S]+/}},"triple-quoted-string":{pattern:/(?:[rub]|br|rb)?("""|''')[\s\S]*?\1/i,greedy:!0,alias:"string"},string:{pattern:/(?:[rub]|br|rb)?("|')(?:\\.|(?!\1)[^\\\r\n])*\1/i,greedy:!0},function:{pattern:/((?:^|\s)def[ \t]+)[a-zA-Z_]\w*(?=\s*\()/g,lookbehind:!0},"class-name":{pattern:/(\bclass\s+)\w+/i,lookbehind:!0},decorator:{pattern:/(^[\t ]*)@\w+(?:\.\w+)*/m,lookbehind:!0,alias:["annotation","punctuation"],inside:{punctuation:/\./}},keyword:/\b(?:_(?=\s*:)|and|as|assert|async|await|break|case|class|continue|def|del|elif|else|except|exec|finally|for|from|global|if|import|in|is|lambda|match|nonlocal|not|or|pass|print|raise|return|try|while|with|yield)\b/,builtin:/\b(?:__import__|abs|all|any|apply|ascii|basestring|bin|bool|buffer|bytearray|bytes|callable|chr|classmethod|cmp|coerce|compile|complex|delattr|dict|dir|divmod|enumerate|eval|execfile|file|filter|float|format|frozenset|getattr|globals|hasattr|hash|help|hex|id|input|int|intern|isinstance|issubclass|iter|len|list|locals|long|map|max|memoryview|min|next|object|oct|open|ord|pow|property|range|raw_input|reduce|reload|repr|reversed|round|set|setattr|slice|sorted|staticmethod|str|sum|super|tuple|type|unichr|unicode|vars|xrange|zip)\b/,boolean:/\b(?:False|None|True)\b/,number:/\b0(?:b(?:_?[01])+|o(?:_?[0-7])+|x(?:_?[a-f0-9])+)\b|(?:\b\d+(?:_\d+)*(?:\.(?:\d+(?:_\d+)*)?)?|\B\.\d+(?:_\d+)*)(?:e[+-]?\d+(?:_\d+)*)?j?(?!\w)/i,operator:/[-+%=]=?|!=|:=|\*\*?=?|\/\/?=?|<[<=>]?|>[=>]?|[&|^~]/,punctuation:/[{}[\];(),.:]/},B.languages.python["string-interpolation"].inside.interpolation.inside.rest=B.languages.python,B.languages.py=B.languages.python,B.languages.json={property:{pattern:/(^|[^\\])"(?:\\.|[^\\"\r\n])*"(?=\s*:)/,lookbehind:!0,greedy:!0},string:{pattern:/(^|[^\\])"(?:\\.|[^\\"\r\n])*"(?!\s*:)/,lookbehind:!0,greedy:!0},comment:{pattern:/\/\/.*|\/\*[\s\S]*?(?:\*\/|$)/,greedy:!0},number:/-?\b\d+(?:\.\d+)?(?:e[+-]?\d+)?\b/i,punctuation:/[{}[\],]/,operator:/:/,boolean:/\b(?:false|true)\b/,null:{pattern:/\bnull\b/,alias:"keyword"}},B.languages.webmanifest=B.languages.json;var U={},q={dracula:()=>G,duotoneDark:()=>V,duotoneLight:()=>W,github:()=>Q,gruvboxMaterialDark:()=>ed,gruvboxMaterialLight:()=>ep,jettwaveDark:()=>el,jettwaveLight:()=>es,nightOwl:()=>K,nightOwlLight:()=>Y,oceanicNext:()=>J,okaidia:()=>ee,oneDark:()=>ec,oneLight:()=>eu,palenight:()=>et,shadesOfPurple:()=>en,synthwave84:()=>er,ultramin:()=>ea,vsDark:()=>eo,vsLight:()=>ei};for(var H in q)j(U,H,{get:q[H],enumerable:!0});var G={plain:{color:"#F8F8F2",backgroundColor:"#282A36"},styles:[{types:["prolog","constant","builtin"],style:{color:"rgb(189, 147, 249)"}},{types:["inserted","function"],style:{color:"rgb(80, 250, 123)"}},{types:["deleted"],style:{color:"rgb(255, 85, 85)"}},{types:["changed"],style:{color:"rgb(255, 184, 108)"}},{types:["punctuation","symbol"],style:{color:"rgb(248, 248, 242)"}},{types:["string","char","tag","selector"],style:{color:"rgb(255, 121, 198)"}},{types:["keyword","variable"],style:{color:"rgb(189, 147, 249)",fontStyle:"italic"}},{types:["comment"],style:{color:"rgb(98, 114, 164)"}},{types:["attr-name"],style:{color:"rgb(241, 250, 140)"}}]},V={plain:{backgroundColor:"#2a2734",color:"#9a86fd"},styles:[{types:["comment","prolog","doctype","cdata","punctuation"],style:{color:"#6c6783"}},{types:["namespace"],style:{opacity:.7}},{types:["tag","operator","number"],style:{color:"#e09142"}},{types:["property","function"],style:{color:"#9a86fd"}},{types:["tag-id","selector","atrule-id"],style:{color:"#eeebff"}},{types:["attr-name"],style:{color:"#c4b9fe"}},{types:["boolean","string","entity","url","attr-value","keyword","control","directive","unit","statement","regex","atrule","placeholder","variable"],style:{color:"#ffcc99"}},{types:["deleted"],style:{textDecorationLine:"line-through"}},{types:["inserted"],style:{textDecorationLine:"underline"}},{types:["italic"],style:{fontStyle:"italic"}},{types:["important","bold"],style:{fontWeight:"bold"}},{types:["important"],style:{color:"#c4b9fe"}}]},W={plain:{backgroundColor:"#faf8f5",color:"#728fcb"},styles:[{types:["comment","prolog","doctype","cdata","punctuation"],style:{color:"#b6ad9a"}},{types:["namespace"],style:{opacity:.7}},{types:["tag","operator","number"],style:{color:"#063289"}},{types:["property","function"],style:{color:"#b29762"}},{types:["tag-id","selector","atrule-id"],style:{color:"#2d2006"}},{types:["attr-name"],style:{color:"#896724"}},{types:["boolean","string","entity","url","attr-value","keyword","control","directive","unit","statement","regex","atrule"],style:{color:"#728fcb"}},{types:["placeholder","variable"],style:{color:"#93abdc"}},{types:["deleted"],style:{textDecorationLine:"line-through"}},{types:["inserted"],style:{textDecorationLine:"underline"}},{types:["italic"],style:{fontStyle:"italic"}},{types:["important","bold"],style:{fontWeight:"bold"}},{types:["important"],style:{color:"#896724"}}]},Q={plain:{color:"#393A34",backgroundColor:"#f6f8fa"},styles:[{types:["comment","prolog","doctype","cdata"],style:{color:"#999988",fontStyle:"italic"}},{types:["namespace"],style:{opacity:.7}},{types:["string","attr-value"],style:{color:"#e3116c"}},{types:["punctuation","operator"],style:{color:"#393A34"}},{types:["entity","url","symbol","number","boolean","variable","constant","property","regex","inserted"],style:{color:"#36acaa"}},{types:["atrule","keyword","attr-name","selector"],style:{color:"#00a4db"}},{types:["function","deleted","tag"],style:{color:"#d73a49"}},{types:["function-variable"],style:{color:"#6f42c1"}},{types:["tag","selector","keyword"],style:{color:"#00009f"}}]},K={plain:{color:"#d6deeb",backgroundColor:"#011627"},styles:[{types:["changed"],style:{color:"rgb(162, 191, 252)",fontStyle:"italic"}},{types:["deleted"],style:{color:"rgba(239, 83, 80, 0.56)",fontStyle:"italic"}},{types:["inserted","attr-name"],style:{color:"rgb(173, 219, 103)",fontStyle:"italic"}},{types:["comment"],style:{color:"rgb(99, 119, 119)",fontStyle:"italic"}},{types:["string","url"],style:{color:"rgb(173, 219, 103)"}},{types:["variable"],style:{color:"rgb(214, 222, 235)"}},{types:["number"],style:{color:"rgb(247, 140, 108)"}},{types:["builtin","char","constant","function"],style:{color:"rgb(130, 170, 255)"}},{types:["punctuation"],style:{color:"rgb(199, 146, 234)"}},{types:["selector","doctype"],style:{color:"rgb(199, 146, 234)",fontStyle:"italic"}},{types:["class-name"],style:{color:"rgb(255, 203, 139)"}},{types:["tag","operator","keyword"],style:{color:"rgb(127, 219, 202)"}},{types:["boolean"],style:{color:"rgb(255, 88, 116)"}},{types:["property"],style:{color:"rgb(128, 203, 196)"}},{types:["namespace"],style:{color:"rgb(178, 204, 214)"}}]},Y={plain:{color:"#403f53",backgroundColor:"#FBFBFB"},styles:[{types:["changed"],style:{color:"rgb(162, 191, 252)",fontStyle:"italic"}},{types:["deleted"],style:{color:"rgba(239, 83, 80, 0.56)",fontStyle:"italic"}},{types:["inserted","attr-name"],style:{color:"rgb(72, 118, 214)",fontStyle:"italic"}},{types:["comment"],style:{color:"rgb(152, 159, 177)",fontStyle:"italic"}},{types:["string","builtin","char","constant","url"],style:{color:"rgb(72, 118, 214)"}},{types:["variable"],style:{color:"rgb(201, 103, 101)"}},{types:["number"],style:{color:"rgb(170, 9, 130)"}},{types:["punctuation"],style:{color:"rgb(153, 76, 195)"}},{types:["function","selector","doctype"],style:{color:"rgb(153, 76, 195)",fontStyle:"italic"}},{types:["class-name"],style:{color:"rgb(17, 17, 17)"}},{types:["tag"],style:{color:"rgb(153, 76, 195)"}},{types:["operator","property","keyword","namespace"],style:{color:"rgb(12, 150, 155)"}},{types:["boolean"],style:{color:"rgb(188, 84, 84)"}}]},Z="#c5a5c5",X="#8dc891",J={plain:{backgroundColor:"#282c34",color:"#ffffff"},styles:[{types:["attr-name"],style:{color:Z}},{types:["attr-value"],style:{color:X}},{types:["comment","block-comment","prolog","doctype","cdata","shebang"],style:{color:"#999999"}},{types:["property","number","function-name","constant","symbol","deleted"],style:{color:"#5a9bcf"}},{types:["boolean"],style:{color:"#ff8b50"}},{types:["tag"],style:{color:"#fc929e"}},{types:["string"],style:{color:X}},{types:["punctuation"],style:{color:X}},{types:["selector","char","builtin","inserted"],style:{color:"#D8DEE9"}},{types:["function"],style:{color:"#79b6f2"}},{types:["operator","entity","url","variable"],style:{color:"#d7deea"}},{types:["keyword"],style:{color:Z}},{types:["atrule","class-name"],style:{color:"#FAC863"}},{types:["important"],style:{fontWeight:"400"}},{types:["bold"],style:{fontWeight:"bold"}},{types:["italic"],style:{fontStyle:"italic"}},{types:["namespace"],style:{opacity:.7}}]},ee={plain:{color:"#f8f8f2",backgroundColor:"#272822"},styles:[{types:["changed"],style:{color:"rgb(162, 191, 252)",fontStyle:"italic"}},{types:["deleted"],style:{color:"#f92672",fontStyle:"italic"}},{types:["inserted"],style:{color:"rgb(173, 219, 103)",fontStyle:"italic"}},{types:["comment"],style:{color:"#8292a2",fontStyle:"italic"}},{types:["string","url"],style:{color:"#a6e22e"}},{types:["variable"],style:{color:"#f8f8f2"}},{types:["number"],style:{color:"#ae81ff"}},{types:["builtin","char","constant","function","class-name"],style:{color:"#e6db74"}},{types:["punctuation"],style:{color:"#f8f8f2"}},{types:["selector","doctype"],style:{color:"#a6e22e",fontStyle:"italic"}},{types:["tag","operator","keyword"],style:{color:"#66d9ef"}},{types:["boolean"],style:{color:"#ae81ff"}},{types:["namespace"],style:{color:"rgb(178, 204, 214)",opacity:.7}},{types:["tag","property"],style:{color:"#f92672"}},{types:["attr-name"],style:{color:"#a6e22e !important"}},{types:["doctype"],style:{color:"#8292a2"}},{types:["rule"],style:{color:"#e6db74"}}]},et={plain:{color:"#bfc7d5",backgroundColor:"#292d3e"},styles:[{types:["comment"],style:{color:"rgb(105, 112, 152)",fontStyle:"italic"}},{types:["string","inserted"],style:{color:"rgb(195, 232, 141)"}},{types:["number"],style:{color:"rgb(247, 140, 108)"}},{types:["builtin","char","constant","function"],style:{color:"rgb(130, 170, 255)"}},{types:["punctuation","selector"],style:{color:"rgb(199, 146, 234)"}},{types:["variable"],style:{color:"rgb(191, 199, 213)"}},{types:["class-name","attr-name"],style:{color:"rgb(255, 203, 107)"}},{types:["tag","deleted"],style:{color:"rgb(255, 85, 114)"}},{types:["operator"],style:{color:"rgb(137, 221, 255)"}},{types:["boolean"],style:{color:"rgb(255, 88, 116)"}},{types:["keyword"],style:{fontStyle:"italic"}},{types:["doctype"],style:{color:"rgb(199, 146, 234)",fontStyle:"italic"}},{types:["namespace"],style:{color:"rgb(178, 204, 214)"}},{types:["url"],style:{color:"rgb(221, 221, 221)"}}]},en={plain:{color:"#9EFEFF",backgroundColor:"#2D2A55"},styles:[{types:["changed"],style:{color:"rgb(255, 238, 128)"}},{types:["deleted"],style:{color:"rgba(239, 83, 80, 0.56)"}},{types:["inserted"],style:{color:"rgb(173, 219, 103)"}},{types:["comment"],style:{color:"rgb(179, 98, 255)",fontStyle:"italic"}},{types:["punctuation"],style:{color:"rgb(255, 255, 255)"}},{types:["constant"],style:{color:"rgb(255, 98, 140)"}},{types:["string","url"],style:{color:"rgb(165, 255, 144)"}},{types:["variable"],style:{color:"rgb(255, 238, 128)"}},{types:["number","boolean"],style:{color:"rgb(255, 98, 140)"}},{types:["attr-name"],style:{color:"rgb(255, 180, 84)"}},{types:["keyword","operator","property","namespace","tag","selector","doctype"],style:{color:"rgb(255, 157, 0)"}},{types:["builtin","char","constant","function","class-name"],style:{color:"rgb(250, 208, 0)"}}]},er={plain:{backgroundColor:"linear-gradient(to bottom, #2a2139 75%, #34294f)",backgroundImage:"#34294f",color:"#f92aad",textShadow:"0 0 2px #100c0f, 0 0 5px #dc078e33, 0 0 10px #fff3"},styles:[{types:["comment","block-comment","prolog","doctype","cdata"],style:{color:"#495495",fontStyle:"italic"}},{types:["punctuation"],style:{color:"#ccc"}},{types:["tag","attr-name","namespace","number","unit","hexcode","deleted"],style:{color:"#e2777a"}},{types:["property","selector"],style:{color:"#72f1b8",textShadow:"0 0 2px #100c0f, 0 0 10px #257c5575, 0 0 35px #21272475"}},{types:["function-name"],style:{color:"#6196cc"}},{types:["boolean","selector-id","function"],style:{color:"#fdfdfd",textShadow:"0 0 2px #001716, 0 0 3px #03edf975, 0 0 5px #03edf975, 0 0 8px #03edf975"}},{types:["class-name","maybe-class-name","builtin"],style:{color:"#fff5f6",textShadow:"0 0 2px #000, 0 0 10px #fc1f2c75, 0 0 5px #fc1f2c75, 0 0 25px #fc1f2c75"}},{types:["constant","symbol"],style:{color:"#f92aad",textShadow:"0 0 2px #100c0f, 0 0 5px #dc078e33, 0 0 10px #fff3"}},{types:["important","atrule","keyword","selector-class"],style:{color:"#f4eee4",textShadow:"0 0 2px #393a33, 0 0 8px #f39f0575, 0 0 2px #f39f0575"}},{types:["string","char","attr-value","regex","variable"],style:{color:"#f87c32"}},{types:["parameter"],style:{fontStyle:"italic"}},{types:["entity","url"],style:{color:"#67cdcc"}},{types:["operator"],style:{color:"ffffffee"}},{types:["important","bold"],style:{fontWeight:"bold"}},{types:["italic"],style:{fontStyle:"italic"}},{types:["entity"],style:{cursor:"help"}},{types:["inserted"],style:{color:"green"}}]},ea={plain:{color:"#282a2e",backgroundColor:"#ffffff"},styles:[{types:["comment"],style:{color:"rgb(197, 200, 198)"}},{types:["string","number","builtin","variable"],style:{color:"rgb(150, 152, 150)"}},{types:["class-name","function","tag","attr-name"],style:{color:"rgb(40, 42, 46)"}}]},eo={plain:{color:"#9CDCFE",backgroundColor:"#1E1E1E"},styles:[{types:["prolog"],style:{color:"rgb(0, 0, 128)"}},{types:["comment"],style:{color:"rgb(106, 153, 85)"}},{types:["builtin","changed","keyword","interpolation-punctuation"],style:{color:"rgb(86, 156, 214)"}},{types:["number","inserted"],style:{color:"rgb(181, 206, 168)"}},{types:["constant"],style:{color:"rgb(100, 102, 149)"}},{types:["attr-name","variable"],style:{color:"rgb(156, 220, 254)"}},{types:["deleted","string","attr-value","template-punctuation"],style:{color:"rgb(206, 145, 120)"}},{types:["selector"],style:{color:"rgb(215, 186, 125)"}},{types:["tag"],style:{color:"rgb(78, 201, 176)"}},{types:["tag"],languages:["markup"],style:{color:"rgb(86, 156, 214)"}},{types:["punctuation","operator"],style:{color:"rgb(212, 212, 212)"}},{types:["punctuation"],languages:["markup"],style:{color:"#808080"}},{types:["function"],style:{color:"rgb(220, 220, 170)"}},{types:["class-name"],style:{color:"rgb(78, 201, 176)"}},{types:["char"],style:{color:"rgb(209, 105, 105)"}}]},ei={plain:{color:"#000000",backgroundColor:"#ffffff"},styles:[{types:["comment"],style:{color:"rgb(0, 128, 0)"}},{types:["builtin"],style:{color:"rgb(0, 112, 193)"}},{types:["number","variable","inserted"],style:{color:"rgb(9, 134, 88)"}},{types:["operator"],style:{color:"rgb(0, 0, 0)"}},{types:["constant","char"],style:{color:"rgb(129, 31, 63)"}},{types:["tag"],style:{color:"rgb(128, 0, 0)"}},{types:["attr-name"],style:{color:"rgb(255, 0, 0)"}},{types:["deleted","string"],style:{color:"rgb(163, 21, 21)"}},{types:["changed","punctuation"],style:{color:"rgb(4, 81, 165)"}},{types:["function","keyword"],style:{color:"rgb(0, 0, 255)"}},{types:["class-name"],style:{color:"rgb(38, 127, 153)"}}]},el={plain:{color:"#f8fafc",backgroundColor:"#011627"},styles:[{types:["prolog"],style:{color:"#000080"}},{types:["comment"],style:{color:"#6A9955"}},{types:["builtin","changed","keyword","interpolation-punctuation"],style:{color:"#569CD6"}},{types:["number","inserted"],style:{color:"#B5CEA8"}},{types:["constant"],style:{color:"#f8fafc"}},{types:["attr-name","variable"],style:{color:"#9CDCFE"}},{types:["deleted","string","attr-value","template-punctuation"],style:{color:"#cbd5e1"}},{types:["selector"],style:{color:"#D7BA7D"}},{types:["tag"],style:{color:"#0ea5e9"}},{types:["tag"],languages:["markup"],style:{color:"#0ea5e9"}},{types:["punctuation","operator"],style:{color:"#D4D4D4"}},{types:["punctuation"],languages:["markup"],style:{color:"#808080"}},{types:["function"],style:{color:"#7dd3fc"}},{types:["class-name"],style:{color:"#0ea5e9"}},{types:["char"],style:{color:"#D16969"}}]},es={plain:{color:"#0f172a",backgroundColor:"#f1f5f9"},styles:[{types:["prolog"],style:{color:"#000080"}},{types:["comment"],style:{color:"#6A9955"}},{types:["builtin","changed","keyword","interpolation-punctuation"],style:{color:"#0c4a6e"}},{types:["number","inserted"],style:{color:"#B5CEA8"}},{types:["constant"],style:{color:"#0f172a"}},{types:["attr-name","variable"],style:{color:"#0c4a6e"}},{types:["deleted","string","attr-value","template-punctuation"],style:{color:"#64748b"}},{types:["selector"],style:{color:"#D7BA7D"}},{types:["tag"],style:{color:"#0ea5e9"}},{types:["tag"],languages:["markup"],style:{color:"#0ea5e9"}},{types:["punctuation","operator"],style:{color:"#475569"}},{types:["punctuation"],languages:["markup"],style:{color:"#808080"}},{types:["function"],style:{color:"#0e7490"}},{types:["class-name"],style:{color:"#0ea5e9"}},{types:["char"],style:{color:"#D16969"}}]},ec={plain:{backgroundColor:"hsl(220, 13%, 18%)",color:"hsl(220, 14%, 71%)",textShadow:"0 1px rgba(0, 0, 0, 0.3)"},styles:[{types:["comment","prolog","cdata"],style:{color:"hsl(220, 10%, 40%)"}},{types:["doctype","punctuation","entity"],style:{color:"hsl(220, 14%, 71%)"}},{types:["attr-name","class-name","maybe-class-name","boolean","constant","number","atrule"],style:{color:"hsl(29, 54%, 61%)"}},{types:["keyword"],style:{color:"hsl(286, 60%, 67%)"}},{types:["property","tag","symbol","deleted","important"],style:{color:"hsl(355, 65%, 65%)"}},{types:["selector","string","char","builtin","inserted","regex","attr-value"],style:{color:"hsl(95, 38%, 62%)"}},{types:["variable","operator","function"],style:{color:"hsl(207, 82%, 66%)"}},{types:["url"],style:{color:"hsl(187, 47%, 55%)"}},{types:["deleted"],style:{textDecorationLine:"line-through"}},{types:["inserted"],style:{textDecorationLine:"underline"}},{types:["italic"],style:{fontStyle:"italic"}},{types:["important","bold"],style:{fontWeight:"bold"}},{types:["important"],style:{color:"hsl(220, 14%, 71%)"}}]},eu={plain:{backgroundColor:"hsl(230, 1%, 98%)",color:"hsl(230, 8%, 24%)"},styles:[{types:["comment","prolog","cdata"],style:{color:"hsl(230, 4%, 64%)"}},{types:["doctype","punctuation","entity"],style:{color:"hsl(230, 8%, 24%)"}},{types:["attr-name","class-name","boolean","constant","number","atrule"],style:{color:"hsl(35, 99%, 36%)"}},{types:["keyword"],style:{color:"hsl(301, 63%, 40%)"}},{types:["property","tag","symbol","deleted","important"],style:{color:"hsl(5, 74%, 59%)"}},{types:["selector","string","char","builtin","inserted","regex","attr-value","punctuation"],style:{color:"hsl(119, 34%, 47%)"}},{types:["variable","operator","function"],style:{color:"hsl(221, 87%, 60%)"}},{types:["url"],style:{color:"hsl(198, 99%, 37%)"}},{types:["deleted"],style:{textDecorationLine:"line-through"}},{types:["inserted"],style:{textDecorationLine:"underline"}},{types:["italic"],style:{fontStyle:"italic"}},{types:["important","bold"],style:{fontWeight:"bold"}},{types:["important"],style:{color:"hsl(230, 8%, 24%)"}}]},ed={plain:{color:"#ebdbb2",backgroundColor:"#292828"},styles:[{types:["imports","class-name","maybe-class-name","constant","doctype","builtin","function"],style:{color:"#d8a657"}},{types:["property-access"],style:{color:"#7daea3"}},{types:["tag"],style:{color:"#e78a4e"}},{types:["attr-name","char","url","regex"],style:{color:"#a9b665"}},{types:["attr-value","string"],style:{color:"#89b482"}},{types:["comment","prolog","cdata","operator","inserted"],style:{color:"#a89984"}},{types:["delimiter","boolean","keyword","selector","important","atrule","property","variable","deleted"],style:{color:"#ea6962"}},{types:["entity","number","symbol"],style:{color:"#d3869b"}}]},ep={plain:{color:"#654735",backgroundColor:"#f9f5d7"},styles:[{types:["delimiter","boolean","keyword","selector","important","atrule","property","variable","deleted"],style:{color:"#af2528"}},{types:["imports","class-name","maybe-class-name","constant","doctype","builtin"],style:{color:"#b4730e"}},{types:["string","attr-value"],style:{color:"#477a5b"}},{types:["property-access"],style:{color:"#266b79"}},{types:["function","attr-name","char","url"],style:{color:"#72761e"}},{types:["tag"],style:{color:"#b94c07"}},{types:["comment","prolog","cdata","operator","inserted"],style:{color:"#a89984"}},{types:["entity","number","symbol"],style:{color:"#924f79"}}]},ef=/\r\n|\r|\n/,em=e=>{0===e.length?e.push({types:["plain"],content:"\n",empty:!0}):1===e.length&&""===e[0].content&&(e[0].content="\n",e[0].empty=!0)},eg=(e,t)=>{let n=e.length;return n>0&&e[n-1]===t?e:e.concat(t)},eh=e=>{let t=[[]],n=[e],r=[0],a=[e.length],o=0,i=0,l=[],s=[l];for(;i>-1;){for(;(o=r[i]++)<a[i];){let e,c=t[i],u=n[i][o];if("string"==typeof u?(c=i>0?c:["plain"],e=u):(c=eg(c,u.type),u.alias&&(c=eg(c,u.alias)),e=u.content),"string"!=typeof e){i++,t.push(c),n.push(e),r.push(0),a.push(e.length);continue}let d=e.split(ef),p=d.length;l.push({types:c,content:d[0]});for(let e=1;e<p;e++)em(l),s.push(l=[]),l.push({types:c,content:d[e]})}i--,t.pop(),n.pop(),r.pop(),a.pop()}return em(l),s},eb=({children:e,language:t,code:n,theme:r,prism:a})=>{let o,i=t.toLowerCase(),l=((e,t)=>{let{plain:n}=e,r=e.styles.reduce((e,n)=>{let{languages:r,style:a}=n;return r&&!r.includes(t)||n.types.forEach(t=>{let n=M(M({},e[t]),a);e[t]=n}),e},{});return r.root=n,r.plain=P(M({},n),L({backgroundColor:void 0})),r})(r,i),s=(0,A.useCallback)(e=>{var{className:t,style:n,line:r}=e;let a=P(M({},$(e,["className","style","line"])),L({className:(0,C.A)("token-line",t)}));return"object"==typeof l&&"plain"in l&&(a.style=l.plain),"object"==typeof n&&(a.style=M(M({},a.style||{}),n)),a},[l]),c=(o=(0,A.useCallback)(({types:e,empty:t})=>{if(null!=l)return 1===e.length&&"plain"===e[0]?null!=t?{display:"inline-block"}:void 0:1===e.length&&null!=t?l[e[0]]:Object.assign(null!=t?{display:"inline-block"}:{},...e.map(e=>l[e]))},[l]),(0,A.useCallback)(e=>{var{token:t,className:n,style:r}=e;let a=P(M({},$(e,["token","className","style"])),L({className:(0,C.A)("token",...t.types,n),children:t.content,style:o(t)}));return null!=r&&(a.style=M(M({},a.style||{}),r)),a},[o])),u=a.languages[i];return e({tokens:(({prism:e,code:t,grammar:n,language:r})=>(0,A.useMemo)(()=>{if(null==n)return eh([t]);let a={code:t,grammar:n,language:r,tokens:[]};return e.hooks.run("before-tokenize",a),a.tokens=e.tokenize(t,n),e.hooks.run("after-tokenize",a),eh(a.tokens)},[t,n,r,e]))({prism:a,language:i,code:n,grammar:u}),className:`prism-code language-${i}`,style:null!=l?l.root:{},getLineProps:s,getTokenProps:c})},ey=e=>(0,A.createElement)(eb,P(M({},e),L({prism:e.prism||B,theme:e.theme||eo,code:e.code,language:e.language})))},1561(e,t,n){"use strict";n.d(t,{A:()=>r});function r(e,t){if(!e)throw Error("Invariant failed")}},1635(e,t,n){"use strict";n.r(t),n.d(t,{__addDisposableResource:()=>R,__assign:()=>o,__asyncDelegator:()=>_,__asyncGenerator:()=>E,__asyncValues:()=>A,__await:()=>S,__awaiter:()=>m,__classPrivateFieldGet:()=>L,__classPrivateFieldIn:()=>O,__classPrivateFieldSet:()=>N,__createBinding:()=>h,__decorate:()=>l,__disposeResources:()=>F,__esDecorate:()=>c,__exportStar:()=>b,__extends:()=>a,__generator:()=>g,__importDefault:()=>z,__importStar:()=>P,__makeTemplateObject:()=>C,__metadata:()=>f,__param:()=>s,__propKey:()=>d,__read:()=>v,__rest:()=>i,__rewriteRelativeImportExtension:()=>I,__runInitializers:()=>u,__setFunctionName:()=>p,__spread:()=>w,__spreadArray:()=>x,__spreadArrays:()=>k,__values:()=>y,default:()=>M});var r=function(e,t){return(r=Object.setPrototypeOf||({__proto__:[]})instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n])})(e,t)};function a(e,t){if("function"!=typeof t&&null!==t)throw TypeError("Class extends value "+String(t)+" is not a constructor or null");function n(){this.constructor=e}r(e,t),e.prototype=null===t?Object.create(t):(n.prototype=t.prototype,new n)}var o=function(){return(o=Object.assign||function(e){for(var t,n=1,r=arguments.length;n<r;n++)for(var a in t=arguments[n])Object.prototype.hasOwnProperty.call(t,a)&&(e[a]=t[a]);return e}).apply(this,arguments)};function i(e,t){var n={};for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&0>t.indexOf(r)&&(n[r]=e[r]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols)for(var a=0,r=Object.getOwnPropertySymbols(e);a<r.length;a++)0>t.indexOf(r[a])&&Object.prototype.propertyIsEnumerable.call(e,r[a])&&(n[r[a]]=e[r[a]]);return n}function l(e,t,n,r){var a,o=arguments.length,i=o<3?t:null===r?r=Object.getOwnPropertyDescriptor(t,n):r;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)i=Reflect.decorate(e,t,n,r);else for(var l=e.length-1;l>=0;l--)(a=e[l])&&(i=(o<3?a(i):o>3?a(t,n,i):a(t,n))||i);return o>3&&i&&Object.defineProperty(t,n,i),i}function s(e,t){return function(n,r){t(n,r,e)}}function c(e,t,n,r,a,o){function i(e){if(void 0!==e&&"function"!=typeof e)throw TypeError("Function expected");return e}for(var l,s=r.kind,c="getter"===s?"get":"setter"===s?"set":"value",u=!t&&e?r.static?e:e.prototype:null,d=t||(u?Object.getOwnPropertyDescriptor(u,r.name):{}),p=!1,f=n.length-1;f>=0;f--){var m={};for(var g in r)m[g]="access"===g?{}:r[g];for(var g in r.access)m.access[g]=r.access[g];m.addInitializer=function(e){if(p)throw TypeError("Cannot add initializers after decoration has completed");o.push(i(e||null))};var h=(0,n[f])("accessor"===s?{get:d.get,set:d.set}:d[c],m);if("accessor"===s){if(void 0===h)continue;if(null===h||"object"!=typeof h)throw TypeError("Object expected");(l=i(h.get))&&(d.get=l),(l=i(h.set))&&(d.set=l),(l=i(h.init))&&a.unshift(l)}else(l=i(h))&&("field"===s?a.unshift(l):d[c]=l)}u&&Object.defineProperty(u,r.name,d),p=!0}function u(e,t,n){for(var r=arguments.length>2,a=0;a<t.length;a++)n=r?t[a].call(e,n):t[a].call(e);return r?n:void 0}function d(e){return"symbol"==typeof e?e:"".concat(e)}function p(e,t,n){return"symbol"==typeof t&&(t=t.description?"[".concat(t.description,"]"):""),Object.defineProperty(e,"name",{configurable:!0,value:n?"".concat(n," ",t):t})}function f(e,t){if("object"==typeof Reflect&&"function"==typeof Reflect.metadata)return Reflect.metadata(e,t)}function m(e,t,n,r){return new(n||(n=Promise))(function(a,o){function i(e){try{s(r.next(e))}catch(e){o(e)}}function l(e){try{s(r.throw(e))}catch(e){o(e)}}function s(e){var t;e.done?a(e.value):((t=e.value)instanceof n?t:new n(function(e){e(t)})).then(i,l)}s((r=r.apply(e,t||[])).next())})}function g(e,t){var n,r,a,o={label:0,sent:function(){if(1&a[0])throw a[1];return a[1]},trys:[],ops:[]},i=Object.create(("function"==typeof Iterator?Iterator:Object).prototype);return i.next=l(0),i.throw=l(1),i.return=l(2),"function"==typeof Symbol&&(i[Symbol.iterator]=function(){return this}),i;function l(l){return function(s){var c=[l,s];if(n)throw TypeError("Generator is already executing.");for(;i&&(i=0,c[0]&&(o=0)),o;)try{if(n=1,r&&(a=2&c[0]?r.return:c[0]?r.throw||((a=r.return)&&a.call(r),0):r.next)&&!(a=a.call(r,c[1])).done)return a;switch(r=0,a&&(c=[2&c[0],a.value]),c[0]){case 0:case 1:a=c;break;case 4:return o.label++,{value:c[1],done:!1};case 5:o.label++,r=c[1],c=[0];continue;case 7:c=o.ops.pop(),o.trys.pop();continue;default:if(!(a=(a=o.trys).length>0&&a[a.length-1])&&(6===c[0]||2===c[0])){o=0;continue}if(3===c[0]&&(!a||c[1]>a[0]&&c[1]<a[3])){o.label=c[1];break}if(6===c[0]&&o.label<a[1]){o.label=a[1],a=c;break}if(a&&o.label<a[2]){o.label=a[2],o.ops.push(c);break}a[2]&&o.ops.pop(),o.trys.pop();continue}c=t.call(e,o)}catch(e){c=[6,e],r=0}finally{n=a=0}if(5&c[0])throw c[1];return{value:c[0]?c[1]:void 0,done:!0}}}}var h=Object.create?function(e,t,n,r){void 0===r&&(r=n);var a=Object.getOwnPropertyDescriptor(t,n);(!a||("get"in a?!t.__esModule:a.writable||a.configurable))&&(a={enumerable:!0,get:function(){return t[n]}}),Object.defineProperty(e,r,a)}:function(e,t,n,r){void 0===r&&(r=n),e[r]=t[n]};function b(e,t){for(var n in e)"default"===n||Object.prototype.hasOwnProperty.call(t,n)||h(t,e,n)}function y(e){var t="function"==typeof Symbol&&Symbol.iterator,n=t&&e[t],r=0;if(n)return n.call(e);if(e&&"number"==typeof e.length)return{next:function(){return e&&r>=e.length&&(e=void 0),{value:e&&e[r++],done:!e}}};throw TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")}function v(e,t){var n="function"==typeof Symbol&&e[Symbol.iterator];if(!n)return e;var r,a,o=n.call(e),i=[];try{for(;(void 0===t||t-- >0)&&!(r=o.next()).done;)i.push(r.value)}catch(e){a={error:e}}finally{try{r&&!r.done&&(n=o.return)&&n.call(o)}finally{if(a)throw a.error}}return i}function w(){for(var e=[],t=0;t<arguments.length;t++)e=e.concat(v(arguments[t]));return e}function k(){for(var e=0,t=0,n=arguments.length;t<n;t++)e+=arguments[t].length;for(var r=Array(e),a=0,t=0;t<n;t++)for(var o=arguments[t],i=0,l=o.length;i<l;i++,a++)r[a]=o[i];return r}function x(e,t,n){if(n||2==arguments.length)for(var r,a=0,o=t.length;a<o;a++)!r&&a in t||(r||(r=Array.prototype.slice.call(t,0,a)),r[a]=t[a]);return e.concat(r||Array.prototype.slice.call(t))}function S(e){return this instanceof S?(this.v=e,this):new S(e)}function E(e,t,n){if(!Symbol.asyncIterator)throw TypeError("Symbol.asyncIterator is not defined.");var r,a=n.apply(e,t||[]),o=[];return r=Object.create(("function"==typeof AsyncIterator?AsyncIterator:Object).prototype),i("next"),i("throw"),i("return",function(e){return function(t){return Promise.resolve(t).then(e,c)}}),r[Symbol.asyncIterator]=function(){return this},r;function i(e,t){a[e]&&(r[e]=function(t){return new Promise(function(n,r){o.push([e,t,n,r])>1||l(e,t)})},t&&(r[e]=t(r[e])))}function l(e,t){try{var n;(n=a[e](t)).value instanceof S?Promise.resolve(n.value.v).then(s,c):u(o[0][2],n)}catch(e){u(o[0][3],e)}}function s(e){l("next",e)}function c(e){l("throw",e)}function u(e,t){e(t),o.shift(),o.length&&l(o[0][0],o[0][1])}}function _(e){var t,n;return t={},r("next"),r("throw",function(e){throw e}),r("return"),t[Symbol.iterator]=function(){return this},t;function r(r,a){t[r]=e[r]?function(t){return(n=!n)?{value:S(e[r](t)),done:!1}:a?a(t):t}:a}}function A(e){if(!Symbol.asyncIterator)throw TypeError("Symbol.asyncIterator is not defined.");var t,n=e[Symbol.asyncIterator];return n?n.call(e):(e=y(e),t={},r("next"),r("throw"),r("return"),t[Symbol.asyncIterator]=function(){return this},t);function r(n){t[n]=e[n]&&function(t){return new Promise(function(r,a){var o,i,l;o=r,i=a,l=(t=e[n](t)).done,Promise.resolve(t.value).then(function(e){o({value:e,done:l})},i)})}}}function C(e,t){return Object.defineProperty?Object.defineProperty(e,"raw",{value:t}):e.raw=t,e}var T=Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t},j=function(e){return(j=Object.getOwnPropertyNames||function(e){var t=[];for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&(t[t.length]=n);return t})(e)};function P(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var n=j(e),r=0;r<n.length;r++)"default"!==n[r]&&h(t,e,n[r]);return T(t,e),t}function z(e){return e&&e.__esModule?e:{default:e}}function L(e,t,n,r){if("a"===n&&!r)throw TypeError("Private accessor was defined without a getter");if("function"==typeof t?e!==t||!r:!t.has(e))throw TypeError("Cannot read private member from an object whose class did not declare it");return"m"===n?r:"a"===n?r.call(e):r?r.value:t.get(e)}function N(e,t,n,r,a){if("m"===r)throw TypeError("Private method is not writable");if("a"===r&&!a)throw TypeError("Private accessor was defined without a setter");if("function"==typeof t?e!==t||!a:!t.has(e))throw TypeError("Cannot write private member to an object whose class did not declare it");return"a"===r?a.call(e,n):a?a.value=n:t.set(e,n),n}function O(e,t){if(null===t||"object"!=typeof t&&"function"!=typeof t)throw TypeError("Cannot use 'in' operator on non-object");return"function"==typeof e?t===e:e.has(t)}function R(e,t,n){if(null!=t){var r,a;if("object"!=typeof t&&"function"!=typeof t)throw TypeError("Object expected.");if(n){if(!Symbol.asyncDispose)throw TypeError("Symbol.asyncDispose is not defined.");r=t[Symbol.asyncDispose]}if(void 0===r){if(!Symbol.dispose)throw TypeError("Symbol.dispose is not defined.");r=t[Symbol.dispose],n&&(a=r)}if("function"!=typeof r)throw TypeError("Object not disposable.");a&&(r=function(){try{a.call(this)}catch(e){return Promise.reject(e)}}),e.stack.push({value:t,dispose:r,async:n})}else n&&e.stack.push({async:!0});return t}var D="function"==typeof SuppressedError?SuppressedError:function(e,t,n){var r=Error(n);return r.name="SuppressedError",r.error=e,r.suppressed=t,r};function F(e){function t(t){e.error=e.hasError?new D(t,e.error,"An error was suppressed during disposal."):t,e.hasError=!0}var n,r=0;return function a(){for(;n=e.stack.pop();)try{if(!n.async&&1===r)return r=0,e.stack.push(n),Promise.resolve().then(a);if(n.dispose){var o=n.dispose.call(n.value);if(n.async)return r|=2,Promise.resolve(o).then(a,function(e){return t(e),a()})}else r|=1}catch(e){t(e)}if(1===r)return e.hasError?Promise.reject(e.error):Promise.resolve();if(e.hasError)throw e.error}()}function I(e,t){return"string"==typeof e&&/^\.\.?\//.test(e)?e.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i,function(e,n,r,a,o){return n?t?".jsx":".js":!r||a&&o?r+a+"."+o.toLowerCase()+"js":e}):e}let M={__extends:a,__assign:o,__rest:i,__decorate:l,__param:s,__esDecorate:c,__runInitializers:u,__propKey:d,__setFunctionName:p,__metadata:f,__awaiter:m,__generator:g,__createBinding:h,__exportStar:b,__values:y,__read:v,__spread:w,__spreadArrays:k,__spreadArray:x,__await:S,__asyncGenerator:E,__asyncDelegator:_,__asyncValues:A,__makeTemplateObject:C,__importStar:P,__importDefault:z,__classPrivateFieldGet:L,__classPrivateFieldSet:N,__classPrivateFieldIn:O,__addDisposableResource:R,__disposeResources:F,__rewriteRelativeImportExtension:I}},2654(e){"use strict";e.exports={}},4054(e){"use strict";e.exports=JSON.parse('{"/-e5f":{"__comp":"1df93b7f","__context":{"plugin":"a7456010"},"config":"5e9f5e1a"},"/-054":{"__comp":"5e95c892","__context":{"plugin":"aba21aa0"}},"/-0b9":{"__comp":"a7bd4aaa","__props":"22dd74f7"},"/-5ef":{"__comp":"a94703ab"},"/basecamp/get-started/install-logos-basecamp-97d":{"__comp":"17896441","content":"8e906247"},"/blockchain/blend/join-the-blend-network-as-a-core-node-955":{"__comp":"17896441","content":"bf77b29a"},"/blockchain/concepts/about-bedrock-272":{"__comp":"17896441","content":"5e7d7155"},"/blockchain/concepts/about-cryptarchia-a25":{"__comp":"17896441","content":"33fcb833"},"/blockchain/concepts/about-mantle-e7c":{"__comp":"17896441","content":"2d9cc0e4"},"/blockchain/concepts/about-the-blend-network-589":{"__comp":"17896441","content":"b00cc44c"},"/blockchain/concepts/about-zones-ebd":{"__comp":"17896441","content":"1995774a"},"/blockchain/get-started/introduction-to-the-logos-blockchain-536":{"__comp":"17896441","content":"c7d7a685"},"/blockchain/get-started/run-a-logos-blockchain-node-from-basecamp-fc2":{"__comp":"17896441","content":"669488b6"},"/blockchain/get-started/run-a-logos-blockchain-node-from-cli-811":{"__comp":"17896441","content":"470027ea"},"/blockchain/node-app/bridge-assets-from-logos-blockchain-to-zone-using-app-b99":{"__comp":"17896441","content":"c634495e"},"/blockchain/node-app/build-and-run-logos-blockchain-node-app-ui-49b":{"__comp":"17896441","content":"2d108750"},"/blockchain/node-app/claim-leader-rewards-in-logos-blockchain-ui-app-aa6":{"__comp":"17896441","content":"19597d10"},"/blockchain/node-app/move-assets-using-logos-blockchain-app-c79":{"__comp":"17896441","content":"6f4e7109"},"/blockchain/zone-sdk/bridge-assets-between-blockchain-and-zone-b3a":{"__comp":"17896441","content":"bb3f60c2"},"/blockchain/zone-sdk/inscribe-data-on-chain-using-zone-sdk-c47":{"__comp":"17896441","content":"ea6a119a"},"/blockchain/zone-sdk/operate-decentralised-zone-with-round-robin-sequencer-rotation-91b":{"__comp":"17896441","content":"eaab68dc"},"/build-an-app-473":{"__comp":"14eb3368","__props":"638fbe8d"},"/build-an-app/tutorials/build-dapp-with-logos-zone-sdk-5df":{"__comp":"17896441","content":"5b9d1339"},"/build-an-app/tutorials/create-and-use-an-amm-liquidity-pool-on-the-logos-execution-zone-45c":{"__comp":"17896441","content":"dbf4bdd3"},"/contribute/welcome-9fe":{"__comp":"17896441","content":"25c725b0"},"/core/build-modules/build-a-logos-cpp-ui-module-715":{"__comp":"17896441","content":"9e12ce96"},"/core/build-modules/build-and-run-a-logos-core-module-6b9":{"__comp":"17896441","content":"fdf0ae8c"},"/core/build-modules/build-logos-core-module-that-uses-service-discovery-api-468":{"__comp":"17896441","content":"589ec14f"},"/core/build-modules/install-and-load-a-module-in-logos-basecamp-2dd":{"__comp":"17896441","content":"aba21bf3"},"/core/build-modules/start-a-logos-module-from-the-cli-528":{"__comp":"17896441","content":"83f53bbb"},"/core/build-modules/wrap-a-c-library-as-a-logos-core-module-264":{"__comp":"17896441","content":"a1c727d1"},"/core/reference/lgx-package-format-and-bundling-reference-dd1":{"__comp":"17896441","content":"f494eaf6"},"/core/reference/logos-cli-reference-791":{"__comp":"17896441","content":"a239bc72"},"/get-started/glossary-349":{"__comp":"17896441","content":"bcf9457e"},"/get-started/logos-ecosystem-repositories-a73":{"__comp":"17896441","content":"862b4f69"},"/get-started/what-is-logos-3bc":{"__comp":"17896441","content":"0e1b61e3"},"/lez/accounts/set-up-shared-private-lez-account-9c1":{"__comp":"17896441","content":"c1de2c67"},"/lez/follow-chain/run-lez-indexer-386":{"__comp":"17896441","content":"2a349706"},"/lez/follow-chain/start-and-use-instance-of-lez-explorer-3be":{"__comp":"17896441","content":"5a6d5130"},"/lez/get-started/connect-wallet-cli-to-lez-testnet-663":{"__comp":"17896441","content":"a8fa9c9c"},"/lez/get-started/initiate-native-token-transfers-on-lez-with-wallet-ui-be2":{"__comp":"17896441","content":"0ac18de3"},"/lez/get-started/introduction-to-the-logos-execution-zone-bf9":{"__comp":"17896441","content":"93168d98"},"/lez/get-started/lez-quickstart-f7e":{"__comp":"17896441","content":"bcd62c17"},"/lez/transfer-tokens/create-and-transfer-custom-tokens-on-the-logos-execution-zone-e58":{"__comp":"17896441","content":"04e28314"},"/lez/transfer-tokens/transfer-native-lez-tokens-between-public-private-states-070":{"__comp":"17896441","content":"15d6e2f3"},"/lez/transfer-tokens/transfer-native-tokens-on-the-logos-execution-zone-559":{"__comp":"17896441","content":"b4479ab8"},"/messaging/chat-module/build-logos-module-that-uses-chat-module-api-87e":{"__comp":"17896441","content":"bfa2415e"},"/messaging/chat-module/send-anonymised-messages-over-the-mix-network-85d":{"__comp":"17896441","content":"6fc42ee4"},"/messaging/concepts/content-topics-80c":{"__comp":"17896441","content":"1132b708"},"/messaging/concepts/logos-delivery-and-libp2p-fbc":{"__comp":"17896441","content":"3e6f6f2a"},"/messaging/concepts/network-domains-522":{"__comp":"17896441","content":"68fc9db9"},"/messaging/concepts/protocols-3b8":{"__comp":"17896441","content":"2c805987"},"/messaging/concepts/security-features-375":{"__comp":"17896441","content":"b9ebd1fd"},"/messaging/concepts/transports-c58":{"__comp":"17896441","content":"f3eca8a6"},"/messaging/concepts/understand-peer-discovery/discv5-b8f":{"__comp":"17896441","content":"49b24bc7"},"/messaging/concepts/understand-peer-discovery/dns-discovery-86e":{"__comp":"17896441","content":"00254a8b"},"/messaging/concepts/understand-peer-discovery/peer-discovery-a03":{"__comp":"17896441","content":"808de28f"},"/messaging/concepts/understand-peer-discovery/peer-exchange-2cc":{"__comp":"17896441","content":"718f62c6"},"/messaging/concepts/understand-peer-discovery/static-peers-216":{"__comp":"17896441","content":"ea2b553e"},"/messaging/delivery/run-logos-delivery-node-da8":{"__comp":"17896441","content":"c96c9452"},"/messaging/delivery/use-logos-delivery-module-api-from-app-53c":{"__comp":"17896441","content":"d83083f1"},"/messaging/get-started/faq-dca":{"__comp":"17896441","content":"867de415"},"/messaging/get-started/introduction-to-logos-messaging-29a":{"__comp":"17896441","content":"ba594660"},"/messaging/get-started/send-1-1-messages-logos-chat-8e4":{"__comp":"17896441","content":"7057f613"},"/mixnet/get-started/discover-nodes-and-send-messages-via-the-anoncomms-mixnet-demo-app-ec6":{"__comp":"17896441","content":"9003ff05"},"/peer-discovery/welcome-0d5":{"__comp":"17896441","content":"102caa12"},"/run-a-node/get-started/run-logos-node-blockchain-storage-delivery-b0d":{"__comp":"17896441","content":"e98c3ec0"},"/run-an-app-74a":{"__comp":"14eb3368","__props":"f911e5e7"},"/storage/get-started/run-logos-storage-node-190":{"__comp":"17896441","content":"8a80b850"},"/storage/get-started/set-up-and-use-logos-storage-ui-c77":{"__comp":"17896441","content":"96794759"}}')}},function(e){e.O(0,["4014"],function(){return e(e.s=354)}),e.O()}]); \ No newline at end of file diff --git a/assets/js/runtime~main.ea6c7eaa.js b/assets/js/runtime~main.ea6c7eaa.js new file mode 100644 index 00000000..6e07e163 --- /dev/null +++ b/assets/js/runtime~main.ea6c7eaa.js @@ -0,0 +1 @@ +(()=>{"use strict";var e,a,c,f,b,d,t,r={},o={};function n(e){var a=o[e];if(void 0!==a)return a.exports;var c=o[e]={exports:{}};return r[e].call(c.exports,c,c.exports,n),c.exports}n.m=r,n.c=o,n.n=e=>{var a=e&&e.__esModule?()=>e.default:()=>e;return n.d(a,{a:a}),a},a=Object.getPrototypeOf?e=>Object.getPrototypeOf(e):e=>e.__proto__,n.t=function(c,f){if(1&f&&(c=this(c)),8&f||"object"==typeof c&&c&&(4&f&&c.__esModule||16&f&&"function"==typeof c.then))return c;var b=Object.create(null);n.r(b);var d={};e=e||[null,a({}),a([]),a(a)];for(var t=2&f&&c;("object"==typeof t||"function"==typeof t)&&!~e.indexOf(t);t=a(t))Object.getOwnPropertyNames(t).forEach(e=>{d[e]=()=>c[e]});return d.default=()=>c,n.d(b,d),b},n.d=(e,a)=>{for(var c in a)n.o(a,c)&&!n.o(e,c)&&Object.defineProperty(e,c,{enumerable:!0,get:a[c]})},n.f={},n.e=e=>Promise.all(Object.keys(n.f).reduce((a,c)=>(n.f[c](e,a),a),[])),n.u=e=>"assets/js/"+(({106:"17896441",1276:"1132b708",1492:"00254a8b",15:"6fc42ee4",1581:"0e1b61e3",1668:"5e95c892",1701:"862b4f69",1836:"e98c3ec0",1924:"22dd74f7",1951:"ba594660",2256:"638fbe8d",2299:"c1de2c67",2307:"fdf0ae8c",256:"2c805987",2573:"5a6d5130",2615:"c7d7a685",266:"bcd62c17",2693:"f3eca8a6",2997:"3e6f6f2a",3029:"33fcb833",3056:"a7456010",3109:"1995774a",3245:"a239bc72",3264:"aba21bf3",3319:"b9ebd1fd",3510:"bfa2415e",3523:"49b24bc7",3557:"bcf9457e",3684:"2d9cc0e4",3747:"aba21aa0",4164:"ea2b553e",4410:"bb3f60c2",4462:"5b9d1339",4595:"04e28314",4719:"d83083f1",4745:"589ec14f",4903:"dbf4bdd3",5052:"8a80b850",5281:"808de28f",5286:"867de415",5367:"718f62c6",5397:"f911e5e7",5479:"7057f613",5539:"102caa12",5575:"a7bd4aaa",5803:"0ac18de3",593:"83f53bbb",5980:"2a349706",6111:"9003ff05",6153:"f494eaf6",6221:"c96c9452",6400:"bf77b29a",6511:"5e7d7155",6524:"6f4e7109",6620:"a8fa9c9c",6663:"b4479ab8",6723:"c634495e",6785:"a94703ab",7009:"669488b6",7022:"19597d10",7234:"14eb3368",7348:"470027ea",7680:"a1c727d1",7868:"25c725b0",8087:"ea6a119a",8327:"2d108750",8546:"eaab68dc",8753:"68fc9db9",8847:"8e906247",8881:"b00cc44c",8894:"9e12ce96",9034:"93168d98",9112:"96794759",9452:"1df93b7f",9460:"15d6e2f3"})[e]||e)+"."+({106:"17fe2fbd",1276:"3d1fdaf9",1492:"8d3a3594",15:"91606ac4",1581:"d375f498",1668:"e6d38e0d",1701:"e0a93c92",1836:"e0da381f",1924:"4faa173f",1951:"19385d01",2256:"aa124cf3",2299:"e3f6face",2307:"87346358",256:"989509cd",2573:"4963629f",2615:"ed90628f",266:"0b2d25bb",2693:"cc619b8f",2997:"0be811ac",3029:"2e59f648",3056:"69111ac6",3109:"18209123",3245:"518f3e1b",3264:"402178b0",3319:"5787e371",3510:"0b877223",3523:"e87dd520",3557:"2ae6fe79",3684:"832d1eab",3693:"544af2eb",3747:"f6603ff7",4164:"89e8db87",4410:"0a49c94e",4462:"6131e2fb",4595:"91306938",4719:"bec85c22",4745:"cc8ba260",4903:"1fbc45a6",5052:"93bd8f32",5062:"a3990a67",5281:"6973f419",5286:"6d308577",5367:"d013b70a",5397:"c42eeb8a",5479:"4ac33364",5539:"af3dc56f",5575:"858d3a16",5803:"80b6624b",593:"acae4b54",5980:"2d5e5d68",6111:"060dddfe",6153:"0d483337",6221:"c6df6040",6400:"96acacdb",6511:"00f26f15",6524:"0ff12c24",6620:"ddd4e4eb",6663:"141d455e",6723:"3759b944",6785:"81210dcc",7009:"7f03cb79",7022:"cfc7ef79",7234:"d320f215",7348:"0278dbe3",7680:"dae89da2",7868:"d8e5280e",8087:"b14e472c",8327:"acbca250",8546:"997c9db9",8753:"c442194f",8847:"18607bd8",8881:"3939f4f0",8894:"eb9de1e2",9034:"ab00e330",9112:"b016c423",9452:"38dea343",9460:"57316c06"})[e]+".js",n.miniCssF=e=>""+e+".css",n.g=(()=>{if("object"==typeof globalThis)return globalThis;try{return this||Function("return this")()}catch(e){if("object"==typeof window)return window}})(),n.o=(e,a)=>Object.prototype.hasOwnProperty.call(e,a),c={},n.l=function(e,a,f,b){if(c[e])return void c[e].push(a);if(void 0!==f)for(var d,t,r=document.getElementsByTagName("script"),o=0;o<r.length;o++){var i=r[o];if(i.getAttribute("src")==e||i.getAttribute("data-rspack")=="logos-docs:"+f){d=i;break}}d||(t=!0,(d=document.createElement("script")).timeout=120,n.nc&&d.setAttribute("nonce",n.nc),d.setAttribute("data-rspack","logos-docs:"+f),d.src=e),c[e]=[a];var u=function(a,f){d.onerror=d.onload=null,clearTimeout(s);var b=c[e];if(delete c[e],d.parentNode&&d.parentNode.removeChild(d),b&&b.forEach(function(e){return e(f)}),a)return a(f)},s=setTimeout(u.bind(null,void 0,{type:"timeout",target:d}),12e4);d.onerror=u.bind(null,d.onerror),d.onload=u.bind(null,d.onload),t&&document.head.appendChild(d)},n.r=e=>{"u">typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},f=[],n.O=(e,a,c,b)=>{if(a){b=b||0;for(var d=f.length;d>0&&f[d-1][2]>b;d--)f[d]=f[d-1];f[d]=[a,c,b];return}for(var t=1/0,d=0;d<f.length;d++){for(var a=f[d][0],c=f[d][1],b=f[d][2],r=!0,o=0;o<a.length;o++)(!1&b||t>=b)&&Object.keys(n.O).every(e=>n.O[e](a[o]))?a.splice(o--,1):(r=!1,b<t&&(t=b));if(r){f.splice(d--,1);var i=c();void 0!==i&&(e=i)}}return e},n.p="/",n.rv=()=>"1.7.12",n.gca=function(e){return e=({0x11113f9:"106",0x5c4f887:"9112","6fc42ee4":"15","2c805987":"256",bcd62c17:"266","83f53bbb":"593","1132b708":"1276","00254a8b":"1492","0e1b61e3":"1581","5e95c892":"1668","862b4f69":"1701",e98c3ec0:"1836","22dd74f7":"1924",ba594660:"1951","638fbe8d":"2256",c1de2c67:"2299",fdf0ae8c:"2307","5a6d5130":"2573",c7d7a685:"2615",f3eca8a6:"2693","3e6f6f2a":"2997","33fcb833":"3029",a7456010:"3056","1995774a":"3109",a239bc72:"3245",aba21bf3:"3264",b9ebd1fd:"3319",bfa2415e:"3510","49b24bc7":"3523",bcf9457e:"3557","2d9cc0e4":"3684",aba21aa0:"3747",ea2b553e:"4164",bb3f60c2:"4410","5b9d1339":"4462","04e28314":"4595",d83083f1:"4719","589ec14f":"4745",dbf4bdd3:"4903","8a80b850":"5052","808de28f":"5281","867de415":"5286","718f62c6":"5367",f911e5e7:"5397","7057f613":"5479","102caa12":"5539",a7bd4aaa:"5575","0ac18de3":"5803","2a349706":"5980","9003ff05":"6111",f494eaf6:"6153",c96c9452:"6221",bf77b29a:"6400","5e7d7155":"6511","6f4e7109":"6524",a8fa9c9c:"6620",b4479ab8:"6663",c634495e:"6723",a94703ab:"6785","669488b6":"7009","19597d10":"7022","14eb3368":"7234","470027ea":"7348",a1c727d1:"7680","25c725b0":"7868",ea6a119a:"8087","2d108750":"8327",eaab68dc:"8546","68fc9db9":"8753","8e906247":"8847",b00cc44c:"8881","9e12ce96":"8894","93168d98":"9034","1df93b7f":"9452","15d6e2f3":"9460"})[e]||e,n.p+n.u(e)},b={4014:0,9783:0},n.f.j=function(e,a){var c=n.o(b,e)?b[e]:void 0;if(0!==c)if(c)a.push(c[2]);else if(/^(4014|9783)$/.test(e))b[e]=0;else{var f=new Promise((a,f)=>c=b[e]=[a,f]);a.push(c[2]=f);var d=n.p+n.u(e),t=Error();n.l(d,function(a){if(n.o(b,e)&&(0!==(c=b[e])&&(b[e]=void 0),c)){var f=a&&("load"===a.type?"missing":a.type),d=a&&a.target&&a.target.src;t.message="Loading chunk "+e+" failed.\n("+f+": "+d+")",t.name="ChunkLoadError",t.type=f,t.request=d,c[1](t)}},"chunk-"+e,e)}},n.O.j=e=>0===b[e],d=(e,a)=>{var c,f,d=a[0],t=a[1],r=a[2],o=0;if(d.some(e=>0!==b[e])){for(c in t)n.o(t,c)&&(n.m[c]=t[c]);if(r)var i=r(n)}for(e&&e(a);o<d.length;o++)f=d[o],n.o(b,f)&&b[f]&&b[f][0](),b[f]=0;return n.O(i)},(t=self.webpackChunklogos_docs=self.webpackChunklogos_docs||[]).forEach(d.bind(null,0)),t.push=d.bind(null,t.push.bind(t)),n.ruid="bundler=rspack@1.7.12"})(); \ No newline at end of file diff --git a/basecamp/get-started/install-logos-basecamp/index.html b/basecamp/get-started/install-logos-basecamp/index.html new file mode 100644 index 00000000..b023d0f8 --- /dev/null +++ b/basecamp/get-started/install-logos-basecamp/index.html @@ -0,0 +1,65 @@ +<!doctype html><html lang=en dir=ltr class="docs-wrapper plugin-docs plugin-id-default docs-version-current docs-doc-page docs-doc-id-basecamp/get-started/install-logos-basecamp" data-has-hydrated=false><head><meta charset=UTF-8><meta name=generator content="Docusaurus v3.10.2"><title data-rh=true>Install Logos Basecamp | Logos documentation + + +

Install Logos Basecamp

+

Get Logos Basecamp running on your desktop.

+

Logos Basecamp is the desktop shell for Logos. You can discover, install, and run Logos modules and apps using its graphical interface as an alternative to the command line.

+

You can install Logos Basecamp in two ways:

+
MethodWhen to use itTooling required
Prebuilt release (AppImage or DMG)End usersNone
Build from source with NixContributors, custom builds, unsupported platformsNix with flakes enabled
+
info

To enable flakes in nix, add experimental-features = nix-command flakes to /etc/nix/nix.conf.

+

Before you start, make sure you have the following:

+
    +
  • Internet access.
  • +
  • A supported OS: Linux x86_64 or aarch64 (tested on Ubuntu 22.04+), or macOS aarch64 (recent versions).
  • +
  • 4 GB RAM minimum (8 GB recommended) and ~2 GB free disk space.
  • +
  • For the source build only: Nix installed with flakes enabled.
  • +
+
info

Internet access is required to download the binary or clone the repository, but not to launch Logos Basecamp afterward. Logos Basecamp itself opens no inbound ports.

+

Install from a prebuilt release

+
    +
  1. +

    Go to the latest release page and download the artifact for your OS:

    +
      +
    • Linux: the AppImage files
    • +
    • macOS: the .dmg files
    • +
    +
  2. +
  3. +

    Depending on your OS, install and launch Basecamp as follows:

    +
  4. +
+
    +
  • +

    On macOS, drag the .dmg file into /Applications. Then launch Basecamp from /Applications.

    +
  • +
  • +

    On Linux, grant execute permission to the downloaded AppImage and launch it:

    +
    chmod +x LogosBasecamp-Desktop-*.AppImage
    ./LogosBasecamp-Desktop-x86_64.AppImage # or logos-basecamp-aarch64.AppImage
    +
  • +
+

Build and run Logos Basecamp from source

+
    +
  1. +

    Clone the repository and enter it:

    +
    git clone https://github.com/logos-co/logos-basecamp.git
    cd logos-basecamp
    +
  2. +
  3. +

    Build Basecamp with Nix flake:

    +
    nix build '.#bin-appimage'
    +
  4. +
  5. +

    Run the resulting binary:

    +
    ./result/logos-basecamp.AppImage
    +
  6. +
+

Troubleshooting Basecamp

+

Experimental Nix feature 'nix-command' when building the AppImage

+

The following error might appear while building the AppImage:

+
+

error: experimental Nix feature 'nix-command' is disabled; add '--extra-experimental-features nix-command' to enable it

+
+

in which case the command should be changed to enable flakes:

+
nix build --extra-experimental-features 'nix-command flakes' '.#bin-appimage'
+

I see an libEGL.so.1 / libOpenGL.so.0 missing error when trying to launch the AppImage on Linux?

+

Try running the following command:

+
sudo apt-get install -y fuse libegl1 libopengl0
\ No newline at end of file diff --git a/blockchain/blend/join-the-blend-network-as-a-core-node/index.html b/blockchain/blend/join-the-blend-network-as-a-core-node/index.html new file mode 100644 index 00000000..eb2dbc79 --- /dev/null +++ b/blockchain/blend/join-the-blend-network-as-a-core-node/index.html @@ -0,0 +1,55 @@ +Join the Blend network as a core node | Logos documentation + + +

Join the Blend Network as a core node

+

Connect your blockchain node to Blend to contribute to proposer privacy.

+

Joining the Blend Network lets your blockchain node contribute to the privacy of Logos Blockchain proposers and receive rewards for participating. This procedure applies to operators of a running Logos Blockchain node who want to register that node as a Blend core node. Before you start, make sure your node's address is publicly reachable so other peers can connect to it.

+

You need:

+ +

What to expect

+
    +
  • Your blockchain node is registered as a Blend core node.
  • +
  • Your node contributes to proposer privacy and becomes eligible for rewards.
  • +
  • Your declaration is confirmed on-chain and becomes active after two epochs.
  • +
+

Register your node as a Blend core node

+

Complete these steps to fund the required keys, retrieve a locked note, and submit your Blend join declaration.

+
    +
  1. +

    Start the node and poll until the mode switches to "Online". This takes approximately one hour:

    +
    logoscore call blockchain_module get_cryptarchia_info | jq -r .result.value | jq .mode
    # > "Bootstrapping"

    # ... Wait ~1h, then re-run until you see:
    # > "Online"
    +
  2. +
  3. +

    Open your keystore.yaml and use the faucet to send funds to both the BlendZk and SdpFunding public keys.

    +
    # keystore.yaml
    public_keys:
    ...
    BlendZk: 13cccf99f90fd78c2134891ce3c1afce0605753a7694b9d56678d63a8d471820
    ...
    SdpFunding: 91d381a87e05d46fc9bc95246273b6930290506f0589ad039444decd3c24940e
    ...
    +
  4. +
  5. +

    Wait until both keys have received funds. Check each balance with wallet_get_notes. You may need to repeat the faucet requests since only one drip is allowed per block:

    +
    logoscore call blockchain_module wallet_get_notes <ADDRESS> "" \
    | jq -r .result.value | jq .notes
    # > [
    # > {
    # > "id": "<BLEND_ZK_NODE_ID>",
    # > "value": "1000"
    # > }
    # > ]
    +
      +
    • Note the id of a note held by the BlendZk key — you need it in the next step.
    • +
    +
  6. +
  7. +

    Join the Blend network by locking one of the notes held by your BlendZk key:

    +
    curl -X POST http://<YOUR_NODE_IP>:8080/blend/join \
    -H 'Content-Type: application/json' \
    -d '{"locator":"/ip4/<YOUR_IP>/udp/<YOUR_BLEND_PORT>/quic-v1","locked_note_id":"<BLEND_ZK_NOTE_ID>"}'

    # A successful call returns the declaration id:
    # > 2691821bd61394cc18939626de4e9231c699e8ddefd1ebf9e6c35b32229bdc65
    +
      +
    • <YOUR_IP>: your external IP address.
    • +
    • <YOUR_BLEND_PORT>: the Blend port from blend.core.backend.listening_address in user_config.yaml. If you use port mapping, use the externally mapped port.
    • +
    • <BLEND_ZK_NOTE_ID>: the note id from the BlendZk balance check above.
    • +
    +
  8. +
  9. +

    Confirm the declaration was accepted on-chain by polling /mantle/sdp/declarations and looking for your entry:

    +
    curl http://<YOUR_NODE_IP>:8080/mantle/sdp/declarations | jq .
    # > [
    # > {
    # > "service_type": "BN",
    # > "provider_id": "35d60d973560b8344f83dc266a3fe89e35a3dcf9959c492d0a7a0b7a85c5d2ce",
    # > "locked_note_id": "<BLEND_ZK_NOTE_ID>",
    # > "locators": [
    # > "/ip4/<YOUR_IP>/udp/<YOUR_BLEND_PORT>/quic-v1"
    # > ],
    # > "zk_id": "13cccf99f90fd78c2134891ce3c1afce0605753a7694b9d56678d63a8d471820",
    # > "created": 1,
    # > "active": 3,
    # > "withdraw_at": null,
    # > "nonce": 0
    # > }
    # > ]
    +
      +
    • service_type: "BN" identifies this as a Blend node declaration.
    • +
    • zk_id is your BlendZk public key; provider_id is your BlendSigning key.
    • +
    • active == created + 2: your node becomes active two epochs after the declaration is included in a block.
    • +
    • If your declaration is not yet listed, retry after your transaction is included in a block.
    • +
    +
  10. +
\ No newline at end of file diff --git a/blockchain/concepts/about-bedrock/index.html b/blockchain/concepts/about-bedrock/index.html new file mode 100644 index 00000000..3a90a2c3 --- /dev/null +++ b/blockchain/concepts/about-bedrock/index.html @@ -0,0 +1,19 @@ +About Bedrock | Logos documentation + + +

About Bedrock

+

Understand how Bedrock provides the consensus and shared context that Zones build on.

+

The Bedrock layer is the basis on top of which the broader Logos Blockchain is built, operating as a large and decentralised peer-to-peer blockchain network. Bedrock functions as a solid foundation for Zones built on Logos, allowing Zone applications to leverage the Logos Blockchain's consensus guarantees. Bedrock also facilitates interoperability between Zones and Bedrock itself with support for asynchronous messaging and token bridging. By handling the logic allowing several parties to participate in sequencing a Zone, Bedrock contributes to the decentralisation of the Logos stack.

+

The basics

+
    +
  • Bedrock is the foundational layer of the Logos Blockchain, operating as a decentralised peer-to-peer network.
  • +
  • Bedrock provides consensus on transaction ordering to Zones, as well as a common context for Zone interaction.
  • +
  • Participation in Bedrock is credibly neutral and private, ensuring open access for all participants.
  • +
+

Bedrock components

+

Bedrock is organised into three key components. The most basic of these is the peer-to-peer network that allows Logos nodes to communicate with each other in a decentralised way.

+

Cryptarchia

+

On top of the peer-to-peer network sits Cryptarchia, the Logos Blockchain’s consensus protocol used to reach an agreement about the state of the blockchain. As a Private Proof of Stake (PPoS) protocol, Cryptarchia gives all participants a proportional chance to propose a block, while ensuring that blocks cannot be linked to their proposers both before and after the proposal.

+

Mantle

+

Mantle, a Bedrock component that serves as the operating system of the Logos Blockchain, provides a minimal shared execution environment for Zones that allows them to interact with Bedrock. This includes operations like writing data to the blockchain, as well as a restricted ledger of notes to support payments and staking.

+

Zones make use of Mantle operations when posting their updates to the Logos Blockchain, as well as to communicate asynchronously with other Zones. Mantle ensures that Zones state updates are correctly ordered via the use of Logos channels. Logos channels also allow for several sequencers to sequence a Zone, and enable token bridging between Zones and Bedrock.

\ No newline at end of file diff --git a/blockchain/concepts/about-cryptarchia/index.html b/blockchain/concepts/about-cryptarchia/index.html new file mode 100644 index 00000000..b7f9da75 --- /dev/null +++ b/blockchain/concepts/about-cryptarchia/index.html @@ -0,0 +1,35 @@ +About Cryptarchia | Logos documentation + + +

About Cryptarchia

+

Understand how Cryptarchia reaches consensus while keeping block proposers private.

+

Cryptarchia, the Logos Blockchain's consensus protocol, ensures that the entire Logos network reaches an agreement on the correct state of the blockchain. Cryptarchia uses a Private Proof of Stake (PPoS) consensus mechanism, ensuring that nodes cannot be linked to blocks they propose. PPoS also ensures that the proposer's relative stake cannot be deduced based on its activity. This separation reinforces the neutrality of the network, since nodes cannot be linked to any specific activity on the network. At the same time, Cryptarchia has no stake barrier for participating in consensus, fostering further decentralisation.

+

The basics

+
    +
  • Cryptarchia is a Private Proof of Stake (PPoS) consensus protocol, so nodes cannot be linked to the blocks they propose and their relative stake cannot be deduced from activity.
  • +
  • Cryptarchia maintains block production during failures, allowing the network to keep operating even if competing forks emerge temporarily.
  • +
  • Cryptarchia has low entry barriers, so validator nodes can run on a basic laptop with no minimum stake requirement.
  • +
+

Properties of Cryptarchia

+

Resilience via liveness

+

In designing a resilient consensus protocol to use for the Logos Blockchain, it was necessary to decide whether to prioritise safety or liveness in the event of a catastrophic failure. If safety is deemed a priority, the chain can never fork in order to ensure that the blockchain state is always agreed-upon, even if activity has to halt while the network recovers. Protocols that provide such safety guarantees generally rely on quorum-based consensus, which requires a permissioned set of participants with extensive communication and a low fault tolerance. These requirements introduce high barriers to entry that are antithetical to the Logos vision.

+

Prioritising liveness means that block production will continue during a failure, but competing forks will be created before the network settles on one honest chain. These protocols involve participants systematically making local choices about which fork to follow, with no need for a permissioned network or for extensive communication. Accordingly, Cryptarchia was designed to prioritise liveness during a failure.

+

Proposer privacy

+

Cryptarchia provides PPoS consensus that hides the identity of a block proposer both before and after a proposal. Hiding a proposer’s identity before the proposal is accomplished by using a secret leadership election. This mechanism selects consensus leaders without revealing the leader schedule ahead of time.

+

For full proposer privacy, a secret leadership election is not enough: once a leader proposes a block, it is trivial to link them to their proposed block without additional privacy measures in place. Cryptarchia is designed to work together with the Blend Network, which obfuscates the link between a proposer and their block. This property creates a much more powerful layer of privacy, resilience and neutrality.

+

Low barrier to entry

+

In order to achieve maximum decentralisation, Cryptarchia was designed to have low barriers to entry to encourage a greater circle of contributors. Participating as a Cryptarchia validator node is as simple as having the Logos node application run in the background on a laptop, with a “set it and forget it” approach to maintenance. This design makes it easy to contribute to the security and continued operation of the Logos Blockchain.

+

How Cryptarchia works

+

Time units

+

Following the Ouroboros model, Cryptarchia divides time into basic units called slots that are grouped into larger units called epochs. Each slot allows for the addition of at most one block to a given chain, while each new epoch refreshes the randomness and eligibility set used for the leadership election. Every Cryptarchia slot is 1 second long, and an epoch is about 7.5 days long. These time units are illustrated in the diagram below.

+

The time units used by the Logos Blockchain.

+

Fork choice rule

+

As mentioned earlier, Cryptarchia’s preference for liveness produces competing forks fairly often - even under honest behaviour. The way Logos nodes decide on which fork represents the correct, or canonical, blockchain depends on how long that node has been offline. The node will compare parallel chains it sees on the network to its own preferred chain, switching to the observed chain if it is selected by the fork choice rule.

+

When a Logos node is connected to the broader network and sees new honest blocks relatively quickly, it will use the online fork choice rule to select the honest chain. Under this rule, the node will select the chain with the most blocks - the “longest chain” - as long as it diverges from the node’s chain less than $k$ blocks ago. This $k$ parameter, known as the security parameter, describes how many blocks deep a transaction needs to be before it is considered immutable.

+

When a Logos node is joining the network for the first time, or after a prolonged absence, it must use the boostrapping fork choice rule. Like the online rule, the bootstrapping rule selects the longest chain when competing chains diverged from the node’s preferred chain less than $k$ blocks ago. However, forks diverging more than $k$ blocks ago are not dismissed. On the contrary, such a chain is selected if it has the most blocks (i.e. is the most dense) in the period immediately after the fork. The contrast between these two rules can be seen in the diagram below.

+

The Cryptarchia bootstrapping fork choice rule.

+

The bootstrapping rule ensures that honest parties can join or rejoin the protocol after prolonged absences without having to trust another node to select their canonical chain. During normal operations, such a costly fork choice rule is not needed, allowing active nodes to use the online rule without compromising on security.

+

Leadership election

+

Cryptarchia uses Logos notes (fungible assets) to select block proposers. Each note that has existed since the beginning of the previous epoch is eligible to win the Cryptarchia slot lottery. The owner of a winning note can then propose a block. Notes held by anyone can be used for consensus, with no minimum value requirement.

+

Each slot presents an opportunity for a block proposer to add a block to the chain, so long as they win the leadership election for that slot. The leadership election is run locally by each individual eligible note, without any public leadership schedule. Whether a particular note wins the leadership election for a given slot is determined by comparing a random “ticket” value to a threshold derived from the note’s relative stake. Due to the privacy properties of Cryptarchia, this relative stake relies on an estimate of the total participating stake derived from the block production rate.

+

If the ticket, generated by hashing the lottery randomness together with the note data, is less than the threshold value, that note is eligible to serve as a leader for that slot. The owner of a note that won an election can submit a block proposal, which will contain a zero-knowledge Proof of Leadership (PoL). Most slots will have no leader in order to allow parties to synchronise, while some will have one or even several leaders. A key deletion protocol used in the maintenance of the note’s secret key ensures that an adaptive adversary who corrupts an honest participant will not be able to generate proofs of leadership for past slots.

\ No newline at end of file diff --git a/blockchain/concepts/about-mantle/index.html b/blockchain/concepts/about-mantle/index.html new file mode 100644 index 00000000..6b59f6eb --- /dev/null +++ b/blockchain/concepts/about-mantle/index.html @@ -0,0 +1,32 @@ +About Mantle | Logos documentation + + +

About Mantle

+

Understand how Mantle lets nodes and Zones interact with Bedrock.

+

Mantle is a Bedrock component that serves as the operating system of Logos. It provides operations that allow nodes to participate in the Blend Network, as well as to enable Zones to interact with Bedrock. Mantle is also responsible for handling Logos notes, which are Bedrock-native fungible tokens.

+

The basics

+
    +
  • Mantle serves as Bedrock's execution layer, enabling nodes to participate in Bedrock Services and facilitating interactions between Zones and Bedrock.
  • +
  • Mantle maintains a limited ledger for fee payment, tracking fungible assets called notes with UTXO-based transactions.
  • +
  • Mantle supports channels, which enforce immediate transaction ordering for Zones and facilitate decentralised sequencing and token bridging.
  • +
+

Operations and transactions

+

Mantle operations are the way Logos nodes interact with Bedrock Services, and allow Zones to interact with Bedrock. Operations are submitted to Mantle via transactions, with each transaction potentially including several operations. The cost of executing operations is paid for by gas fees, denoted in notes. All Logos nodes re-execute Mantle operations independently to ensure that the ledger state is updated correctly.

+

Logos nodes use Mantle operations to indicate their participation in the Blend Network, with Mantle providing a locking mechanism used to incentivise correct behaviour by participants. The operations supported by Mantle include transferring notes, staking notes for participation in the Blend Network, paying rewards to compensate participants, and unstaking notes.

+

Inscription operations can be used to write arbitrary data to the Logos Blockchain, enabling a variety of ways to leverage the blockchain as a permanent, decentralised record. Crucially, Mantle can be used as a censorship resistant message delivery mechanism for Zones, allowing Zone sequencers to send information to each other and engage in coordinated action. This form of message passing facilitates cross-Zone interaction including arranging atomic transactions and state updates.

+

Mantle ledger

+

Mantle maintains a restricted ledger that keeps track of fungible assets known as notes, which are bound to their owners. Notes are primarily used to pay for Mantle operations, but can also be bridged to equivalent Zone tokens. Mantle notes are stored by Logos nodes in a dictionary mapping notes to their unique note identifiers.

+

Note transfers are effected by using transfer operations, which are based on the UTXO model. To transfer value, a sender spends their note and creates an equivalent new note belonging to the recipient. A spent note can never be spent again. These notes each have their own secret key for spending, and a corresponding public key for receiving new notes.

+

Logos channels

+

Logos channels, or channels for short, are lightweight virtual chains overlaid on top of the Logos Blockchain. The purpose of channels is to immediately enforce the correct ordering of transactions from Zones. These channels are implemented as permissioned, ordered logs of messages signed by a sequencer. These messages usually take the form of state updates from a particular Zone. Channels also provide several key features to Zones, which are described below.

+

Immediate ordering

+

Logos channels ensure that their transactions will eventually be included on-chain in the correct order, regardless of how the Logos Blockchain may fork or reorganise. This allows new transactions that depend on earlier ones to be submitted immediately, without waiting for finality. An example with two channels is shown below:

+

A diagram illustrating how two channels are included in the Logos Blockchain.

+

It is important to note that Logos channels can only be relied on if the sequencer is trusted to act honestly. In the absence of this assumption, users must wait for the true blockchain finality.

+

Decentralised sequencing

+

A channel may have one or several sequencers; in the latter case, authorised sequencers may take turns publishing messages according to a round-robin schedule, with unresponsive sequencers losing their turn via an automated timeout. Alternatively, sequencers can compete to update the Zone state via a "first write wins" model.

+

A given threshold of sequencers may also modify the sequencer list or change other channel properties by signing a message together, allowing an honest majority to remove a malicious sequencer. This form of decentralised Zone sequencing distributes the potential for MEV extraction across a set of parties rather than concentrating it as in the single-sequencer rollup design.

+

Token bridging

+

Logos channels also enable token bridging and inter-Zone token transfers for Zones via the use of token balances. A channel’s balance represents the total token value bridged to that Zone, which increases when an equivalent-value note is destroyed on Mantle as part of a deposit operation. Conversely, a new Mantle note is created when a Zone token balance is decreased as a result of a withdrawal operation. Logos channel balances can also be used to effect atomic token transfers between Zones.

+

Cross-Zone messaging

+

Since channel messages can include arbitrary data and are publicly visible, they can also be used for asynchronous or synchronous messaging. Used as a messaging platform, Logos channels can be used to coordinate action between sequencers of a single channel, for example to agree to sign a message modifying the sequencer list. They can also be used to arrange action across several Zones, such as atomic token transfers or other coordinated state transitions.

\ No newline at end of file diff --git a/blockchain/concepts/about-the-blend-network/index.html b/blockchain/concepts/about-the-blend-network/index.html new file mode 100644 index 00000000..015ca17d --- /dev/null +++ b/blockchain/concepts/about-the-blend-network/index.html @@ -0,0 +1,64 @@ +About the Blend Network | Logos documentation + + +

About the Blend Network

+ +

The Blend Network adds a layer of anonymity for block proposers on top of Cryptarchia, the Logos Private Proof of Stake consensus protocol. Cryptarchia uses a private leadership election as a first line of defence against deanonymisation, but preventing adversaries from learning about proposers through network monitoring needs stronger obfuscation. The Blend Protocol provides it: an anonymous broadcasting protocol that makes it hard to link a proposal to its proposer through network analysis. The Blend Network is the service made up of nodes that opt in to run the Blend Protocol, and those nodes are rewarded for participating.

+

The basics

+
    +
  • The Blend Network adds anonymity for block proposers on top of Cryptarchia's Private Proof of Stake consensus.
  • +
  • Proposals are wrapped in multiple layers of encryption and routed through random paths of nodes, each adding a random delay before forwarding.
  • +
  • Nodes must explicitly opt in through the Service Declaration Protocol and prove ownership of a note with a minimum stake.
  • +
+

Objectives

+

The main objective of the Logos Blend Network is to reduce the probability of linking a block proposal with its proposer, which also translates to increasing the difficulty of learning the proposer’s relative stake. At the same time, the Blend Protocol was designed to minimise bandwidth usage on the network compared to general-use mixnets, and to maximise decentralisation by involving all nodes in the obfuscation process. By doing so, the Blend Network increases the cost of attacking the network to deanonymise a block proposal without straining the network with high bandwidth usage.

+

The Blend Network supports the privacy of the Logos Blockchain as a whole, by providing additional anonymity to block proposers, protecting against adversaries with both a complete (global) and partial (local) view of the Logos Blockchain. The anonymity provided by the Blend Network also substantially improves the privacy guarantees of Cryptarchia by making it even harder to learn a proposer’s relative stake - which would allow an attacker to estimate the likelihood of that node winning the leadership election.

+

How the Blend Protocol works

+

The Blend Network makes it difficult to link a block proposer to their proposal by having the message travel between several nodes before being revealed. Blend nodes must maintain a minimum number of connections with other nodes, and cannot exceed a maximum frequency of messages they can send - putting an upper bound on bandwidth usage.

+

While dedicated participation in the Blend Network is reserved for declared Blend nodes, proposals can also be sent to it from regular Logos nodes. The following steps illustrate the process which a proposal message goes through before being broadcast and included in the chain.

+
    +
  1. +

    The message sender selects a random path of nodes along which it will relay its message to the receiver, covering the message in layers of encryption for every node on the path.

    +
  2. +
  3. +

    The sender sends the layered message to every Blend node with which it maintains a peer-to-peer connection. This process, known as dissemination, is shown below.

    +

    The Blend Network using dissemination to relay proposal messages. Peer-to-peer relaying of messages is not shown for simplicity.

    +
  4. +
  5. +

    When a Blend node receives a message, it checks that the message is unique and has not yet been seen, relaying it to its own peers. This ensures that every message is ultimately disseminated to the entire network.

    +
  6. +
  7. +

    A Blend node that receives a message will also attempt to decrypt the message. If it is able to decrypt the outer layer of a message it receives, it relays this decrypted message (after a randomised delay) to its peers for the next node in the path to receive and decrypt.

    +
  8. +
  9. +

    When the receiver finally receives its message, it decrypts this message and is able to retrieve the original payload. This payload is then broadcast to the Logos Blockchain as a block proposal.

    +
  10. +
+

This process of hiding messages under several layers of encryption and randomly delaying their propagation at each stage of dissemination is known as message blending. When a message is disseminated to all nodes, observers cannot determine which node was the intended receiver, even if they can identify the sender. Due to the layered encryption, observers also cannot determine which "hop" of the relay process the message is currently on. This decryption at each node in the path transforms messages, so the incoming and outgoing messages cannot be linked together based on their content.

+

Random delays are the other component of message blending. If messages are rare (as in a low bandwidth network like the Blend Network), then an observer may be able to link even encrypted messages if they are disseminated in close succession. To prevent this, every message is assigned a random delay by the node processing it, with each message being released in the order it is received once its delay period has passed. This ensures that incoming and outgoing messages cannot be linked together based on their timing.

+

Cover traffic

+

An important way that the Blend Network obscures network patterns is by producing indistinguishable messages within a "crowded" network. To increase this effect in an environment where proposals are relatively rare, the Blend Network also produces artificial cover messages. Cover messages do not contain any meaningful payload and are generated by Blend nodes to increase network noise and to blend in with data messages that contain real proposals.

+

Cover messages mimic the behaviour of data messages, in that they are disseminated and processed by Blend nodes in the same manner. Blend nodes repeatedly encrypt random payload data to generate a cover message, which is then relayed to the next node in its selected path via dissemination. At each step in the transmission process, intended nodes decrypt, randomly delay, and disseminate cover messages without any indication that they may not be genuine. In fact, encrypted data and cover messages are completely indistinguishable even to adversary-controlled Blend nodes (a type of local observer). The hiding effect provided by cover traffic is illustrated in the diagram below.

+

Disseminated Blend Network cover traffic (dotted lines) obscuring a data message (solid lines).

+

Disseminated Blend Network cover traffic (dotted lines) obscuring a data message (solid lines).

+

To avoid the network getting congested with too much message traffic, the Blend Network places a quota limiting the number of cover messages produced by each node, as well as the number of nodes in the path of each cover and data message. Adherence to this quota is verified via a zero knowledge proof.

+

Participating in the Blend Network

+

Participation in the Blend Network is more complex than running a Logos node. The protocol relies on an agreed-upon sets of active participants to ensure its correct operation, as Blend nodes must keep track of connections to other Blend nodes for message relaying. As a result, it is not possible to allow dynamic participation in the Blend Network as it exists for Bedrock. Participation in the Blend Network is therefore not incumbent upon a node unless it opts in.

+

Service Declaration Protocol

+

Logos nodes that choose to participate in the Blend Network explicitly declare their intent by using the Service Declaration Protocol (SDP). The goal of the SDP is to create a single repository of identifiers to determine which nodes have opted into the Blend Network at a given time.

+

The SDP provides a standardised mechanism for Logos nodes to declare their participation, demonstrate activity, and withdraw when desired. It operates around a schedule measured by consensus epochs. This protocol creates a single repository of identifiers used to establish secure communication between nodes and manage service participation.

+

The SDP consists of three basic steps, each of which represents a type of message sent by a participating node to Mantle:

+
    +
  • Declare: A node elects to participate in a given service.
  • +
  • Active: To continue participating, the node must regularly send an "active" message. Nodes that have not sent an active message for a prolonged period of time have their declarations withdrawn.
  • +
  • Withdraw: A node withdraws its declaration and stops providing a service.
  • +
+

To submit a service declaration, a node must prove that it owns a note with a service-dependent minimum stake value. This note is locked for the duration of the declaration, but remains eligible for consensus leadership. The stake requirement makes service declarations sufficiently expensive to avoid spamming or Sybil attacks. Nodes participating in services are assigned addresses (known as locators) based on the Multiaddr scheme, allowing them to communicate securely while engaging in a service.

+

Service Reward Distribution Protocol

+

The Service Reward Distribution Protocol (SRDP) enables deterministic, efficient, and verifiable reward distribution to nodes based on their participation in Bedrock Services. Like the SDP, it also operates around epochs. The SRDP process unfolds over three key phases, distributing rewards based on node activity from previous epochs. These phases are:

+
    +
  • Activity tracking: Nodes participating in services submit active messages to attest to their participation in the previous epoch.
  • +
  • Reward calculation: At the end of the epoch, the system calculates rewards for nodes that participated in the previous epoch.
  • +
  • Reward distribution: Starting immediately after the epoch when activity messages are submitted, rewards are gradually distributed to active service nodes. Each block includes one Mantle transaction that can distribute rewards to up to four nodes.
  • +
+

The selection of nodes receiving rewards in each block follows a deterministic, pseudo-random process based on the Cryptarchia epoch randomness from the start of the epoch. This approach ensures fairness and prevents manipulation of the distribution order.

\ No newline at end of file diff --git a/blockchain/concepts/about-zones/index.html b/blockchain/concepts/about-zones/index.html new file mode 100644 index 00000000..02dbbba3 --- /dev/null +++ b/blockchain/concepts/about-zones/index.html @@ -0,0 +1,41 @@ +About Zones | Logos documentation + + +

About Zones

+

Understand how Zones run custom, high-performance applications on the Logos Blockchain.

+

Zones are customisable, high-performance blockchains for applications built on Logos. A Zone defines its own state and execution environment, while relying on the Logos Blockchain for consensus and data availability. Zones can also use optional Bedrock features such as token bridging and cross-Zone messaging. A sequencer updates a Zone's state by submitting state updates to the blockchain. Zones suit applications that need speed and customisation more than they need interoperability.

+

The basics

+
    +
  • A Zone defines its own state and execution environment, while the Logos Blockchain provides consensus and data availability.
  • +
  • A sequencer, or a committee of sequencers, handles a Zone's transactions and submits state updates to the blockchain.
  • +
  • Zones can use tokens bridged from Bedrock and support cross-Zone transactions.
  • +
+

How Zones work

+

Zones let developers build performant applications with the freedom to define an execution environment that suits their needs, while the Logos Blockchain provides economic security and data availability that a Zone could not provide on its own. Bedrock does not interpret Zone data. Instead, it provides operations through Mantle that let Zone sequencers inscribe their state updates permanently to the blockchain.

+

Unlike Ethereum Layer 2s, Zones do not rely on a smart contract on the Layer 1 to ensure the correctness of their state. A Zone can ensure correctness in several ways, including publishing zero knowledge validity proofs for its validators to verify, providing a challenge window for fraud proofs, or requiring Zone nodes to re-execute the state transition function.

+

Additional features

+

The Zone model described above is very similar to that of conventional Sovereign Rollups. In fact, Zones can be built as pure sovereign rollups, with Bedrock only used for ordering and data availability guarantees. However, Bedrock also provides some optional functionality that somewhat increases the decentralisation and interoperability of Zones without significantly compromising the benefits of the sovereign rollup design.

+

Decentralised sequencing

+

While a Zone can be implemented with just one sequencer updating the Zone state, this design allows the sequencer to take full advantage of any MEV and represents a centralised point of failure. On Logos, it is also possible to distribute sequencing rights to a permissioned set of parties. These sequencers can compete to be the first to publish the next state update, or take turns sequencing the Zone according to a round-robin schedule. This latter model provides more Bedrock-level protections for Zone users, including an automated timeout for unresponsive sequencers. Decentralised sequencing also allows a given threshold of sequencers to update the sequencer list to remove a malicious sequencer. All these operations are handled by Logos channels.

+

Token bridging

+

Users can also take advantage of built-in token bridging to transfer value between Bedrock and Zones. A Zone’s associated channel maintains a token balance, which keeps track of the total token value stored in the Zone. This balance is updated as value is deposited to or withdrawn from a Zone, with notes of equivalent value destroyed or created on Bedrock as a result of these operations.

+

Note that it is the Zone’s responsibility to ensure that token transfers are executed correctly within its own state. Bedrock only ensures that the total token value withdrawn from a Zone does not exceed the value deposited.

+

Asynchronous messaging

+

Zone sequencers can use channels to submit messages with arbitrary data, allowing them to communicate on-chain with other sequencers. Sequencers can use these messages to coordinate actions with other sequencers of the same Zone, for example to agree to sign a message modifying the sequencer list. They can also be used to arrange action across several Zones, such as atomic token transfers or other coordinated state transitions. In the latter case, sequencers must interpret all new messages and determine whether the message is directed to them. The Logos Blockchain provides a suggested message standard to make it easier for sequencers to interpret messages from other Zones, but anybody can define their own standard if they so desire.

+

Use cases

+

Zones can implement almost anything, ranging from applications to virtual machines that are home to many different applications. They are best suited for applications that require high performance and do not need strong interoperability.

+

Logos Execution Zone

+

The Logos Execution Zone (LEZ) is the flagship Zone on the Logos Blockchain. It serves as the home for applications built using the complete Logos stack, including its messaging and storage modules. It also provides support for private accounts and program execution, with the state being verified via zero knowledge proofs. For more information about the Logos Execution Zone, see the page below:

+

Other uses

+

In addition to the LEZ, anybody can make a Zone on Logos. The Logos Team has already prepared a Zone SDK that makes it easy to build a Logos Zone. Some possibilities of Sovereign Zone applications include:

+
    +
  • Self-Sovereign Digital Nations/Communities: Logos Blockchain can power the core identity, communication, and governance layers for nascent digital nations. This would involve: +
      +
    • Self-Sovereign Identity (SSI) Solutions: Users can control their digital identities and share verifiable credentials privately and selectively.
    • +
    • Secure Communication Channels: The Logos ecosystem provides encrypted and censorship-resistant collaboration tools for citizens of network states.
    • +
    • Decentralised Justice Systems: Building frameworks for dispute resolution and legal agreements within a network state, potentially leveraging zero-knowledge proofs for privacy.
    • +
    +
  • +
  • Gaming Applications: Games often rely on rapid state changes to function correctly, making them ideal for implementation as Zone applications on Logos.
  • +
  • Small-scale Networked Applications: Networked applications can use the Logos Blockchain to store their state updates on-chain, with users recreating the state locally based on these updates. This makes the Logos Blockchain a low cost, easy alternative for self-hosting - especially for small-scale applications.
  • +
\ No newline at end of file diff --git a/blockchain/get-started/introduction-to-the-logos-blockchain/index.html b/blockchain/get-started/introduction-to-the-logos-blockchain/index.html new file mode 100644 index 00000000..60bdae80 --- /dev/null +++ b/blockchain/get-started/introduction-to-the-logos-blockchain/index.html @@ -0,0 +1,28 @@ +Introduction to the Logos Blockchain | Logos documentation + + +

Introduction to the Logos Blockchain

+

Learn how Logos provides a private, resilient foundation for decentralised applications.

+

The Logos Blockchain is the foundational infrastructure of the Logos technology stack. It hosts decentralised applications and social institutions that require high privacy and resilience, gives those applications a common context for interaction, and guarantees they operate correctly and without corruption. Nodes can participate with consumer hardware and any amount of stake.

+

The basics

+
    +
  • The Logos Blockchain is designed to decentralised applications and institutions that require high levels of privacy and resilience.
  • +
  • Nodes can join with typical consumer hardware and any amount of stake, which keeps participation barriers low.
  • +
  • Logos Blockchain consists of two layers: lightweight, permissionless blockchains called Zones built on top of a Layer 1 foundation called Bedrock. The Blend Network service is used to improve the privacy of the network.
  • +
+

Design principles

+

The Logos Blockchain was designed around three principles: privacy, neutrality, and resilience.

+

Privacy means the network protects information about all participants, regardless of their role. At the infrastructure level, nodes can propose blocks with a very low probability of being traced. Logos also gives developers tools to build applications with programmable privacy, so those applications can keep personal data hidden.

+

Neutrality means activity is handled without compromising the public neutrality of nodes. Nodes can process transactions without having to make their inclusion public.

+

Resilience means the network stays operational and protects privacy under difficult conditions, including a partitioned internet, heavy censorship, and hostile government action. Running a node is meant to be easy enough to do on a laptop, which attracts more nodes and reduces points of failure. A consensus protocol that prioritises liveness lets the network keep operating in the worst conditions.

+

Architecture

+

The Logos Blockchain is implemented as two blockchain layers. Application execution happens on lightweight, permissionless blockchains called Zones, which are built on a Layer 1 foundation called Bedrock. The Blend Network service extends Bedrock's privacy properties.

+

Bedrock

+

Bedrock is a large-scale validator network that serves as the foundational layer of the Logos Blockchain. It provides consensus, data availability, and lightweight verification to Zones. Its Private Proof of Stake (PPoS) consensus protocol, Cryptarchia, keeps block proposers private while staying scalable, resilient, and accessible. Bedrock also enables decentralised sequencing for Zones, token bridging, and inter-Zone messaging.

+

Running a Bedrock validator node means leaving the Logos node application running in the background, with a low-maintenance approach. This makes it straightforward to contribute to the security, consensus, and interoperability of the network.

+

Blend Network

+

The Blend Network is an opt-in service that provides network-level privacy for consensus. It virtually eliminates the chance of linking a block proposer to the block they propose, so proposers cannot be targeted. Operators who join the Blend Network need more hardware than Bedrock validation alone requires.

+

Zones

+

Applications do not run directly on Bedrock. They run on Layer 2 blockchains called Zones, which define their own state transitions and validity but rely on Bedrock for consensus guarantees. This modular design lets creators customise a chain and maximise properties such as performance.

+

Zones can run as fully independent sovereign rollups, but Bedrock also enables token bridging and decentralised sequencing. Zones can exchange asynchronous messages through Bedrock, which allows limited interoperability without giving up the advantages of the sovereign rollup model.

+

The primary example is the Logos Execution Zone, the initial host for the messaging and storage applications of the Logos stack, with support for private token transfers and program execution.

\ No newline at end of file diff --git a/blockchain/get-started/run-a-logos-blockchain-node-from-basecamp/index.html b/blockchain/get-started/run-a-logos-blockchain-node-from-basecamp/index.html new file mode 100644 index 00000000..87ae4aaa --- /dev/null +++ b/blockchain/get-started/run-a-logos-blockchain-node-from-basecamp/index.html @@ -0,0 +1,113 @@ +Run a Logos Blockchain node from Basecamp | Logos documentation + + +

Run a Logos Blockchain node from Basecamp

+

Install the blockchain modules and run a syncing testnet node from the Basecamp desktop app.

+

The Logos Blockchain is the blockchain module of the Logos technology stack. You can run a node from the CLI, from a standalone application, or — as described here — from Basecamp, the modular Logos desktop app, by installing the blockchain module and its UI.

+

This procedure covers Linux, Windows (via WSL), and macOS on Apple Silicon. It requires no terminal beyond a single prerequisite-install command on Linux.

+

Before you start

+

Make sure you have the following:

+
    +
  • A graphical desktop session on one of: Linux (tested on Ubuntu 24.04), Windows 11 with WSL2 (plus WSLg for the GUI), or an Apple Silicon Mac (M1 or later). Basecamp is a desktop application and needs a real display.
  • +
  • Around 8 GB of RAM and a few GB of free disk. The chain downloads on first sync and grows over time.
  • +
  • An always-on machine is recommended — a laptop that sleeps will fall behind and resync on wake.
  • +
+

Port forwarding is not required. A blockchain node participates outbound-only: it syncs, validates, and (once funded) proposes blocks. Forwarding only makes your node reachable so others can use it, which is out of scope here.

+

:::info Pin the versions +Install blockchain_module 0.2.0 and blockchain_ui 0.2.0. In the Package Manager, blockchain_module may pre-select 0.0.999, which is a devnet build and will not sync the public testnet. Always select 0.2.0 explicitly (Step 3). +:::

+

What to expect

+

By the end of this procedure:

+
    +
  • Basecamp will be running the blockchain_module and blockchain_ui modules, both at 0.2.0.
  • +
  • Your node will be connected to testnet peers and syncing the chain.
  • +
  • You will confirm sync by observing an advancing block height.
  • +
  • Optionally, you can fund your wallet from the faucet so your node proposes blocks.
  • +
+

Step 1: Install prerequisites

+

On Linux, install the libraries Basecamp's AppImage requires:

+
sudo apt-get install -y libfuse2t64 libegl1 libopengl0
+

On Ubuntu 22.04 and earlier, use libfuse2 instead of libfuse2t64.

+

On Windows, run every step inside a WSL2 Ubuntu shell, on Windows 11 (or Windows 10 21H2+ with WSLg for GUI support). Use the prebuilt AppImage rather than building from source. Default NAT networking is sufficient.

+

On macOS (Apple Silicon), no prerequisite command is needed. Intel Macs are not supported — there is no x86_64 build.

+

Step 2: Download and launch Basecamp

+

Download the Basecamp 0.2.1 release for your platform from the releases page. On Linux, use the x86_64 (or aarch64 on ARM) AppImage.

+

Make it executable and launch it:

+
chmod +x LogosBasecamp-Desktop-v0.2.1-*-x86_64.AppImage
./LogosBasecamp-Desktop-v0.2.1-*-x86_64.AppImage
+

Basecamp opens.

+

Step 3: Install the blockchain module

+
    +
  1. +

    In the sidebar, open Package Manager and find blockchain_module (category: Blockchain).

    +

    :::caution Use Package Manager, not Applications +Install blockchain modules from Package Manager. Open the version dropdown and select 0.2.0 before installing — the pre-selected version is 0.0.999, the devnet build. +:::

    +
  2. +
  3. +

    Select 0.2.0, then press Install (~142 MB). When it completes, the Details pane shows Installed version: 0.2.0. If it shows 0.0.999, select 0.2.0 again and reinstall — Basecamp will offer to upgrade, which is safe.

    +
  4. +
+

Step 4: Install the blockchain UI

+
    +
  1. +

    On the same screen, select blockchain_ui at version 0.2.0 (already the default) and press Install.

    +
  2. +
  3. +

    A dependency dialog appears. Choose "Install just 'blockchain_ui'" — installing with dependencies replaces your 0.2.0 core with the 0.0.999 devnet build.

    +
  4. +
+

Step 5: Configure the node

+

Open the blockchain module from the sidebar to reach the node view, then generate a config

+
    +
  1. +

    In the Initial peers field, add the testnet bootstrap peers (one per line). For example, for release 0.2.0:

    +
    /ip4/65.109.51.37/udp/3000/quic-v1/p2p/12D3KooWFrouXfmrR4nsLMtE7wu15DoMJ6VtoUtHinREZCvbWHar
    /ip4/65.109.51.37/udp/3001/quic-v1/p2p/12D3KooWJRGau8M1rjT7R5e4YYsgdFhsMX35nRDtMwCDjxQkXAHz
    /ip4/65.109.51.37/udp/3002/quic-v1/p2p/12D3KooWQXJavMDTRscjauFSgVAB1VLB6Rzpy2uY5SU9Tk7927tb
    /ip4/65.109.51.37/udp/50001/quic-v1/p2p/12D3KooWSQc7CcGtvWDPF1yCbBthFnQjprfCVHmfmNDUrSmqQsU1
    +
    info

    Make sure to use the current bootstrap peer addresses in the Logos Blockchain Node release notes for your selected release.

    +
  2. +
  3. +

    Leave the other fields at their defaults and select Generate config.

    +

    :::caution Peers are required +A config generated with an empty Initial peers field produces a node that starts and reports success but never syncs. Make sure the peers above are present before generating. +:::

    +
  4. +
+

Step 6: Start the node

+

On the node view, select Start Node. The status moves to Starting, then Bootstrapping, and the node begins connecting to peers.

+

Step 7: Verify that your node is syncing

+

Confirm sync by evidence rather than the status label. Query the node's HTTP API and check that the height advances across two polls:

+
curl -s http://127.0.0.1:8080/cryptarchia/info
+
    +
  • A syncing node returns a tip that is not genesis and a height/slot that increases between calls. The block list in the UI also begins filling within a minute or two.
  • +
+

:::note Bootstrapping can take time +If you were offline for a while, expect the node to sit in Bootstrapping while it catches up before it reports Online. A height that is far below the current slot during initial sync is normal. +:::

+

Fund your node and propose blocks

+

A synced node validates the chain but does not propose blocks until its wallet has a balance. For chain leadership, your wallet balance counts as stake automatically — there is no separate lock step.

+
    +
  1. In the node view, open Operations → Accounts and copy one of your wallet keys.
  2. +
  3. Go to the testnet faucet, paste the key, and select Request Funds (do this once your node is Online).
  4. +
  5. Once the funds arrive they auto-stake. Your node starts winning leader slots proportional to your balance and proposes blocks on its own.
  6. +
  7. Rewards appear under Operations → Leader Rewards, where Claim redeems them into your wallet.
  8. +
+

:::note Blend staking is separate +Participating in the Blend network as a Core node requires a separate locked note (staked funds) and does not affect block proposing. See Join the Blend network as a Core node. +:::

+

Keeping your node running

+

The node runs only while Basecamp is open, and it does not resume automatically after a restart — reopen Basecamp, open the blockchain module, and select Start Node again. For an unattended, always-on node, use the CLI setup with a service manager instead.

+

Troubleshooting

+

The node is Online but Consensus shows "Call failed", or Accounts won't load

+

This is a UI-side issue, not a node fault — the core is healthy but the UI cannot render the call. Recover least-destructively first:

+
    +
  1. Fully quit Basecamp and relaunch (Quit, not just close the window), then reopen the node. This clears most cases with no data loss.
  2. +
  3. Check the versions match — both blockchain_module and blockchain_ui should be 0.2.0. A version mismatch is a common cause.
  4. +
  5. If it persists, reset the chain state while keeping your keys: quit Basecamp, delete db/ and state/ inside module_data/blockchain_module/<id>/ (not the whole folder — that removes your wallet keystore), relaunch, and Start. The node re-syncs from genesis.
  6. +
+

If curl http://127.0.0.1:8080/cryptarchia/info shows an advancing height, the node is syncing and a restart (step 1) is enough — do not wipe a working node.

+

Height stays at 0

+

Your Initial peers are empty. Quit Basecamp, delete user_config.yaml, db/, and state/ inside module_data/blockchain_module/<id>/ (leave keystore.yaml), relaunch, and redo Step 5 with the peers present.

+

I need my wallet address but the Accounts panel is blank

+

Your keys are safe in the keystore regardless of the UI. Retrieve an address directly:

+
cd ~/.local/share/Logos/LogosBasecamp/module_data/blockchain_module/<id>/
grep -A20 public_keys keystore.yaml
+

Basecamp will not launch

+

On Linux, confirm the Step 1 prerequisites are installed. Clearing the QML cache can also help: rm -rf ~/.cache/Logos.

\ No newline at end of file diff --git a/blockchain/get-started/run-a-logos-blockchain-node-from-cli/index.html b/blockchain/get-started/run-a-logos-blockchain-node-from-cli/index.html new file mode 100644 index 00000000..ca3548d6 --- /dev/null +++ b/blockchain/get-started/run-a-logos-blockchain-node-from-cli/index.html @@ -0,0 +1,117 @@ +Run a Logos Blockchain node on the public testnet from the CLI | Logos documentation + + +

Run a Logos Blockchain node on the public testnet from the CLI

+

Start a node and verify runtime and consensus signals.

+

With this tutorial, you will install the Logos Blockchain node, connect to the public testnet, and verify that your node is running. The Logos Blockchain is the blockchain component of the Logos technology stack, providing a privacy-preserving and censorship-resistant framework for decentralised applications. This procedure is for node operators setting up a node for the first time.

+

There is currently no dynamic wallet key management. To add new keys you must manually edit user_config.yaml and restart the node. If the node is restarted while bootstrapping, it does not save sync progress and restarts from the beginning.

+

Before you start, ensure you have:

+
    +
  • Linux x86_64, macOS, or a Raspberry Pi 5 with Raspberry Pi OS installed
  • +
  • glibc version 2.39 or later (Linux only)
  • +
  • At least 64 GB of storage
  • +
+

What to expect

+
    +
  • You can install the node binary, generate a configuration, and join the public testnet.
  • +
  • You can verify that your node is syncing and connected to peers using the local API.
  • +
  • You can receive test tokens from the faucet and automatically participate in the consensus lottery once your stake matures.
  • +
+

Step 1: Install Logos core tools

+
    +
  1. +

    Use the install-node-tools.sh helper script to install logoscore, lgpd, and lgpm into ./bin:

    +
    curl -fsSL https://raw.githubusercontent.com/logos-co/logos-docs/main/resources/scripts/install-node-tools.sh | sh
    export PATH="$PWD/bin:$PATH"
    +
  2. +
+

Step 2: Load the Logos Blockchain module

+

Download the Logos Blockchain module with lgpd, then install it with lgpm before loading it with logoscore.

+
    +
  1. +

    Download the module:

    +
    lgpd download blockchain_module --version 0.2.0 --output ./
    # writes ./blockchain_module-0.2.0.lgx
    +
  2. +
  3. +

    Install the module:

    +
    lgpm --modules-dir ./modules install --file blockchain_module-0.2.0.lgx
    +
  4. +
  5. +

    Launch logoscore in daemon mode and load the Logos Blockchain module:

    +
    logoscore -m ./modules -D &
    logoscore load-module blockchain_module
    +
  6. +
+

Step 3: Configure and start the node

+

The generate_user_config subcommand generates a user configuration that includes per-node settings such as keys, ports, and peer addresses, along with fresh cryptographic keys and an auto-detected public IP.

+
info

Make sure to use the current bootstrap peer addresses in the Logos Blockchain Node release notes for your selected release.

+
    +
  1. +

    Generate your user_config.yaml by running generate_user_config with the bootstrap peer addresses. For example, for release 0.2.0:

    +
    logoscore call blockchain_module generate_user_config '{
    "initial_peers": [
    "/ip4/65.109.51.37/udp/3000/quic-v1/p2p/12D3KooWFrouXfmrR4nsLMtE7wu15DoMJ6VtoUtHinREZCvbWHar",
    "/ip4/65.109.51.37/udp/3001/quic-v1/p2p/12D3KooWJRGau8M1rjT7R5e4YYsgdFhsMX35nRDtMwCDjxQkXAHz",
    "/ip4/65.109.51.37/udp/3002/quic-v1/p2p/12D3KooWQXJavMDTRscjauFSgVAB1VLB6Rzpy2uY5SU9Tk7927tb",
    "/ip4/65.109.51.37/udp/50001/quic-v1/p2p/12D3KooWSQc7CcGtvWDPF1yCbBthFnQjprfCVHmfmNDUrSmqQsU1"
    ]
    }'
    +
      +
    • To change the API port, set api.backend.listen_address in user_config.yaml before starting. The default is 8080.
    • +
    +
    info

    To migrate an existing version 0.1.2 blockchain config to version 0.2.0, run the migrate_user_config_0_1_2 command of the blockchain module and stop the module before restarting it in the next step:

    logoscore call blockchain_module migrate_user_config_0_1_2 \
    user_config.yaml \
    old_user_config.yaml \
    my_keystore.yaml

    logoscore call blockchain_module stop
    +
  2. +
  3. +

    Start the node:

    +
    logoscore call blockchain_module start user_config.yaml ""
    +
  4. +
+

Step 4: Verify that your node is running and connected to peers

+

Wait for your node to finish syncing and reach Online mode before requesting tokens. Pipe the get_cryptarchia_info command through jq . to format JSON output.

+
    +
  1. +

    Check the consensus state:

    +
    logoscore call blockchain_module get_cryptarchia_info | jq -r .result.value | jq .

    # Alternatively, send a request directly to your node port
    curl -s http://localhost:8080/cryptarchia/info | jq .
    +

    Example response:

    +
    {
    "lib": "3d0c...4e6d",
    "lib_slot": 0,
    "tip": "f44d...e2f5",
    "slot": 70899,
    "height": 120,
    "mode": "Bootstrapping"
    }
    +
      +
    • mode starts as Bootstrapping while syncing and transitions to Online once caught up.
    • +
    • Confirm slot and height are increasing. height counts confirmed blocks; slot counts elapsed time intervals, with a new block expected roughly every 10 seconds.
    • +
    +
  2. +
  3. +

    Check peer connectivity:

    +
    curl -s http://localhost:8080/network/info | jq .
    +

    Example response:

    +
    {
    "listen_addresses": ["/ip4/127.0.0.1/udp/3001/quic-v1"],
    "peer_id": "12D3...fuS2",
    "n_peers": 16,
    "n_connections": 19,
    "n_pending_connections": 0
    }
    +
      +
    • Confirm n_peers is greater than 0.
    • +
    +
  4. +
  5. +

    After 30–60 seconds, run the get_cryptarchia_info command again and confirm slot and height have increased.

    +
  6. +
  7. +

    Wait until mode transitions to Online before continuing. Bootstrapping should take approximately 1 hour.

    +
  8. +
+

Step 5: Request tokens from the faucet

+

A faucet distributes free tokens on test networks so you can experiment without financial risk. Navigate to the public faucet site after your node reaches Online mode.

+
    +
  1. +

    Find the keys associated with your node:

    +
    grep -A3 known_keys user_config.yaml
    +

    Example output:

    +
    known_keys:
    57364103d3ff29c35d2073cba0526ef729b8e08490bddfc6b74128b6613fe923: ...
    de3233cec107e6589f83d4f3094caa65c633b5b33601211353779dc01972ca14: ...
    voucher_master_key_id: de3233cec107e6589f83d4f3094caa65c633b5b33601211353779dc01972ca14
    +
  2. +
  3. +

    Choose any key from known_keys, enter it in Destination Public Key (Hex) on the faucet site, and press Request Funds.

    +

    Image of the faucet UI after requesting funds with a public key

    +
  4. +
  5. +

    Wait 1 to 2 minutes, then check your balance. Replace <your-chosen-key> with the key you used:

    +
    curl -s http://localhost:8080/wallet/<your-chosen-key>/balance | jq .
    +

    Example response:

    +
    {
    "tip": "5d16d4bd3712dc5869fc624e59774552b4fb0c974a6efa516563b3778bac9258",
    "balance": 1000,
    "address": "57364103d3ff29c35d2073cba0526ef729b8e08490bddfc6b74128b6613fe923"
    }
    +
      +
    • Only one faucet transaction can be included per block. During high demand, your transaction may be dropped; retry the request and wait 1 to 2 minutes before checking again.
    • +
    +
  6. +
+
info

Your tokens become eligible for consensus after 3.5 hours. Confirm that your node is participating by checking that mode remains Online and height continues to increase.

Block proposal is probabilistic. Your node will not propose on every slot; participation depends on your stake relative to total active stake in the network.

+

Troubleshooting the Logos Blockchain node

+

The testnet explorer shows an error when I click on a transaction?

+

The testnet explorer does not support clicking on individual transactions. Searching by address is also not supported. Transaction hashes returned by the faucet may appear truncated and may not be immediately findable.

+

My wallet balance is not updating after requesting tokens?

+

Only one faucet transaction can be included per block. During high demand, your transaction may be dropped. Retry the request and wait 1 to 2 minutes before checking your balance again.

\ No newline at end of file diff --git a/blockchain/node-app/bridge-assets-from-logos-blockchain-to-zone-using-app/index.html b/blockchain/node-app/bridge-assets-from-logos-blockchain-to-zone-using-app/index.html new file mode 100644 index 00000000..df371f34 --- /dev/null +++ b/blockchain/node-app/bridge-assets-from-logos-blockchain-to-zone-using-app/index.html @@ -0,0 +1,131 @@ +Bridge assets from Logos Blockchain to a Zone using the Logos Blockchain app | Logos documentation + + +

Bridge assets from Logos Blockchain to a Zone using the Logos Blockchain app

+

Get started locking wallet notes into a Logos Zone, including the LEZ, using the Logos Blockchain UI app.

+

This procedure covers how to lock one or more of your wallet notes (UTXOs) into a Logos Zone (such as the LEZ) using the Logos Blockchain desktop app, and receive the resulting transaction hash. It is intended for wallet users on testnet v0.2 who need to fund a channel for off-chain or on-chain channel operations without hand-assembling note IDs, keys, and fees on a CLI.

+

Before you start, make sure you have the following:

+
    +
  • +

    The Logos Blockchain UI app built and able to launch

    +
  • +
  • +

    A Logos Blockchain node user config selected or generated on the first screen, with an associated keystore (user_config.yaml, optional deployment-settings.yaml, keystore.yaml)

    +
  • +
  • +

    At least one wallet address with a positive balance, for example one funded via the faucet at testnet.blockchain.logos.co

    +
  • +
  • +

    The target Zone's channel ID you are depositing into. You can get the channel ID of the public LEZ instance by running:

    +
    curl https://testnet.lez.logos.co/ \
    -H "Content-Type: application/json" \
    -d "{ \
    \"jsonrpc\": \"2.0\", \
    \"method\": \"getChannelId\", \
    \"params\": {}, \
    \"id\": 1 \
    }"
    +
  • +
  • +

    Public keys for change (receives leftover value) and funding (to pay for gas). This is typically your own wallet address

    +
  • +
+

What to expect

+
    +
  • You can select one or more wallet notes and lock their full value into a channel through a guided four-step wizard.
  • +
  • You can review the exact deposit payload — channel ID, notes, keys, fee, and metadata — before submitting.
  • +
  • You can confirm the deposit succeeded by copying the returned transaction hash and finding it in a new block under opcode 18 (Channel Deposit).
  • +
+

Launch the app and start the node

+

The Operations tab is disabled until the node is running. Notes and balances are unavailable until the node is also synced.

+
    +
  1. +

    Launch the app and, on the config screen, select or generate a user config and deployment config. The default deployment will connect to the testnet:

    +
    nix run
    +
      +
    • This lands you on the main view.
    • +
    +
  2. +
  3. +

    In the Node tab, click Start Node.

    +
      +
    • Status turns Running (green), the consensus card appears and progresses toward Online, and blocks begin streaming in the Blocks list.
    • +
    • The Operations tab becomes enabled.
    • +
    +
  4. +
  5. +

    Wait for the consensus card to reach Online before proceeding.

    +
      +
    • Notes and balances will not load until the node reaches this state.
    • +
    +
  6. +
+

Submit a deposit

+

The UI collects the deposit payload across three input steps, then submits it on confirmation.

+
    +
  1. +

    In the Operations tab, click Channel Deposit in the left sidebar.

    +
      +
    • The deposit wizard opens at Step 1.
    • +
    +
  2. +
  3. +

    On Step 1 – Select notes, choose your address from Known address…, or paste a hex address and commit it with Enter.

    +
      +
    • Notes load automatically once an address is selected. Selecting a different address reloads them.
    • +
    +
  4. +
  5. +

    Select one or more notes. Next enables once at least one note is selected; the full value of each selected note will be consumed.

    +
  6. +
  7. +

    On Step 2 – Fields, enter the deposit parameters:

    +
      +
    • +

      Channel ID (hex) — the target channel.

      +
    • +
    • +

      Change public key — prefilled from the selected wallet; receives leftover value.

      +
    • +
    • +

      Funding public keys — one per line, prefilled; for paying the gas fee.

      +
    • +
    • +

      Max tx fee — Maximum fee allowed to spend

      +
    • +
    • +

      Metadata (base58) (Optional) — Usually specific to the Zone

      +
    • +
    • +

      tip hex or Use query tip (Optional) — The chain tip the deposit is built against. Leave empty to use the node's current tip.

      +
    • +
    • +

      The Next button is enabled once the channel ID, change key, at least one funding key, and max fee are present, and any metadata entered is valid base58.

      +
    • +
    +
  8. +
  9. +

    On Step 3 – Confirm, review the exact payload — channel ID, notes and total amount, change and funding keys, max fee, metadata, and tip — then click Confirm & deposit.

    +
    warning
    +
  10. +
+

Review the information carefully. Deposits are irreversible once included in a block. +:::

+

Read the deposit result

+
    +
  1. +

    On Step 4 – Result, wait for the submission status:

    +
      +
    • On success, the wizard shows Deposit submitted with a copyable transaction hash.
    • +
    • On failure, the wizard shows Deposit failed with the backend error message.
    • +
    +
  2. +
  3. +

    Click New deposit to reset the wizard for another deposit.

    +
  4. +
  5. +

    Confirm the transaction on-chain by expanding the new block in the Node tab's Blocks list and checking that the transaction's opcode is 18 (Channel Deposit).

    +
  6. +
+

Troubleshooting channel deposits

+

Why is Channel Deposit unavailable in the Operations tab?

+

The node is not in the Running state. Start it from the Node tab and wait for the status to turn green before opening the wizard.

+

Why do no notes appear after selecting an address?

+

The node has not finished syncing, or the selected address has no spendable notes. Wait for the consensus card to reach Online before retrying, and confirm the address actually holds notes.

+

Why does Next stay disabled on Step 2?

+

A required field is missing — channel ID, change key, at least one funding key, or max fee — or the metadata entered is not valid base58. The metadata field shows Invalid base58 input when this is the cause.

+

Why does the wizard show "Deposit failed"?

+

The backend rejected the transaction. Common causes are insufficient funds to cover the selected notes plus the max transaction fee, an invalid channel ID or key, or a rejected or expired tip. The exact backend error is shown in the result step.

\ No newline at end of file diff --git a/blockchain/node-app/build-and-run-logos-blockchain-node-app-ui/index.html b/blockchain/node-app/build-and-run-logos-blockchain-node-app-ui/index.html new file mode 100644 index 00000000..6fc0f1d2 --- /dev/null +++ b/blockchain/node-app/build-and-run-logos-blockchain-node-app-ui/index.html @@ -0,0 +1,89 @@ +Build and run the Logos Blockchain UI app | Logos documentation + + +

Build and run the Logos Blockchain UI app

+

Run a node that participates in consensus via a standalone application.

+

The Logos Blockchain is the blockchain module of the Logos technology stack, providing a privacy-preserving and censorship-resistant framework for decentralised network states. You can run a Logos Blockchain node using the CLI or a standalone application.

+
info

For testnet v0.1, you must manually edit the config file and restart the node to add new keys.

+

Before you start, make sure you have the following:

+
    +
  • +

    A supported OS: Linux x86_64 or macOS

    +
  • +
  • +

    Nix with flakes enabled. Install from nixos.org, then enable flakes:

    +
    mkdir -p ~/.config/nix
    echo 'experimental-features = nix-command flakes' >> ~/.config/nix/nix.conf
    +
  • +
+

What to expect

+

By the end of this tutorial:

+
    +
  • You will have a Logos Blockchain node running and connected to peers.
  • +
  • Your wallet will hold testnet funds requested from the faucet.
  • +
  • Your node will be eligible to participate in the consensus lottery after the UTXO ages for approximately two hours.
  • +
+

Step 1: Run the Logos Blockchain app

+
    +
  1. +

    Clone the repository:

    +
    git clone https://github.com/logos-blockchain/logos-blockchain-ui.git
    cd logos-blockchain-ui
    +
  2. +
  3. +

    Build and run the standalone app:

    +
    nix run
    +
    info

    On a cold Nix cache, the first run compiles the blockchain UI from source (Qt/C++ and Rust dependencies). This can take 20–60 minutes. Subsequent runs are instant from cache.

    +
  4. +
+

Step 2: Generate and load a node config

+
    +
  1. +

    In the app, click Generate Config.

    +

    Initial screen

    +
  2. +
  3. +

    In the Logos Blockchain release notes, search for "testnet bootstrap peers" and copy the initial trusted peer set.

    +
  4. +
  5. +

    In the app, paste the peer set information in the Initial peers and click Generate Config on the bottom.

    +
  6. +
  7. +

    Click Load Config and select the generated config file.

    +
  8. +
  9. +

    Click Start Node. A green indicator shows the node is running, and the wallet appears with a balance of 0.

    +

    Running node

    +
  10. +
+

Step 3: Request testnet funds from the faucet

+
    +
  1. +

    From the wallet section of the UI, copy one of your keys.

    +
  2. +
  3. +

    Go to the testnet faucet, paste your key, and click Request Funds.

    +
    info

    The transaction can take up to a minute to confirm and appear in your wallet.

    +
  4. +
+

Step 4: Verify the node is healthy

+
    +
  1. +

    Check that the blockchain height is increasing:

    +
    curl localhost:8080/cryptarchia/info
    +

    Example response:

    +
    {"lib":"3d0c...4e6d","tip":"f44d...e2f5","slot":70899,"height":120,"mode":"Bootstrapping"}
    +

    Expect height to increase at an average rate of one block every 10 seconds. The timing is probabilistic, so some variance is normal.

    +
  2. +
  3. +

    Check that the node is connected to peers:

    +
    curl localhost:8080/network/info
    +

    Example response:

    +
    {"listen_addresses":["/ip4/127.0.0.1/udp/3001/quic-v1"],"peer_id":"12D3...fuS2","n_peers":16,"n_connections":19,"n_pending_connections":0}
    +

    Confirm that n_peers is greater than 0.

    +
  4. +
+

Once the node is funded and the UTXO has aged for approximately two hours, the node automatically participates in the consensus lottery and starts producing blocks.

+

Troubleshooting starting a node

+

The wallet balance does not update after a faucet request

+

Only one faucet transaction can be included per block, so transactions may be dropped during periods of high demand. Retry the request from the faucet.

+

nix build is taking a long time to build

+

This is expected behaviour. The build could take over an hour in some cases.

\ No newline at end of file diff --git a/blockchain/node-app/claim-leader-rewards-in-logos-blockchain-ui-app/index.html b/blockchain/node-app/claim-leader-rewards-in-logos-blockchain-ui-app/index.html new file mode 100644 index 00000000..74065457 --- /dev/null +++ b/blockchain/node-app/claim-leader-rewards-in-logos-blockchain-ui-app/index.html @@ -0,0 +1,69 @@ +Claim leader rewards in the Logos Blockchain UI app | Logos documentation + + +

Claim leader rewards in the Logos Blockchain UI app

+

Learn how to get rewarded for having your Logos Blockchain node propose blocks

+

This procedure covers how to claim rewards for participating in the consensus protocol of the Logos Blockchain. It is intended for node operators running their node via the Blockchain UI app.

+

Before you start, make sure you have the following:

+ +

What to expect

+
    +
  • You can view available reward vouchers in Leader Rewards and claim one with a single button press.
  • +
  • You can verify the claim by matching the transaction hash in the block list and confirming the reward account balance has increased.
  • +
+

Claim a reward voucher

+

Check the account balance before claiming, submit a claim, then confirm the transaction and balance update.

+
    +
  1. +

    In Operations, open Accounts and click Refresh to load the current wallet state.

    +

    Check accounts

    +
      +
    • Note the balance of the account that will receive the reward before claiming.
    • +
    +
  2. +
  3. +

    In Operations, open Leader Rewards.

    +

    Claimable vouchers

    +
      +
    • The panel shows the number of available claimable vouchers, the tip used for the query, and voucher details including cm and nf values.
    • +
    +
    info
    +
  4. +
+

If the voucher count shows 0, wait for more blocks and refresh the Leader Rewards panel before proceeding. +:::

+
    +
  1. +

    Click Claim.

    +

    Claim submitted

    +
      +
    • The UI displays a transaction hash. Note this hash to confirm inclusion in the next step.
    • +
    +
  2. +
  3. +

    Return to the Node tab and inspect the block list. Find a block that includes the claim transaction, expand it, and confirm the transaction hash matches the one shown after clicking Claim.

    +

    Claim transaction as seen in a block

    +
      +
    • On shared networks the block may contain other transactions too.
    • +
    +
  4. +
  5. +

    Return to Operations, then Accounts, and click Refresh.

    +

    Accounts after the claim

    +
      +
    • Confirm the reward account balance has increased. The balance updates after the claim transaction is processed.
    • +
    +
  6. +
+

Troubleshooting leader rewards

+

Why does the voucher count show 0?

+

The node has not yet produced enough blocks for vouchers to be available. Keep the node running and refresh Leader Rewards after more blocks appear.

+

Why does clicking Claim fail?

+

Either the node is not running or the voucher count is 0. Confirm the node status shows Running and that at least one voucher is listed before retrying.

+

Why is the transaction hash not showing in the block list yet?

+

Transaction inclusion is not always immediate. Wait for more blocks to appear in the block list and check again.

+

Why hasn't the account balance updated after claiming?

+

Refresh Accounts only after the claim transaction appears in a block. The balance reflects on-chain state and does not update until the transaction is included.

\ No newline at end of file diff --git a/blockchain/node-app/move-assets-using-logos-blockchain-app/index.html b/blockchain/node-app/move-assets-using-logos-blockchain-app/index.html new file mode 100644 index 00000000..91b4e67f --- /dev/null +++ b/blockchain/node-app/move-assets-using-logos-blockchain-app/index.html @@ -0,0 +1,75 @@ +Move assets using the Logos Blockchain UI app | Logos documentation + + +

Move assets using the Logos Blockchain UI app

+

Get started with token transfers between wallet accounts directly from the dashboard UI.

+

This procedure covers how to send funds from one of your wallet addresses to a recipient on the Logos Blockchain using the Logos Blockchain dashboard. It is intended for users who need to move tokens between accounts without using the CLI or crafting transactions manually.

+

Before you start, make sure you have the following:

+ +

What to expect

+
    +
  • You can select any funded wallet address as the sender and see its live balance in the From address dropdown.
  • +
  • You can submit a transfer and receive a transaction ID in the result row directly below the Send button.
  • +
  • Your sender balance updates after a refresh once the transfer is confirmed.
  • +
+

Start the node and open the transfer panel

+

The Operations tab is disabled until the node is running and synced. Start the node first, then navigate to the transfer panel.

+
    +
  1. +

    In the Node tab, start the node and wait until the status reads Running.

    +
      +
    • Stopping the node mid-session disables the Operations tab and returns the UI to the Node tab.
    • +
    +
  2. +
  3. +

    Click Operations in the top navigation, then click Transfer in the sidebar.

    +
      +
    • The Transfer funds panel appears.
    • +
    +
  4. +
  5. +

    Select a sender address in the From address dropdown.

    +
      +
    • The address's current balance is shown read-only next to the dropdown.
    • +
    • A balance that could not be fetched is displayed as ---.
    • +
    +
  6. +
  7. +

    Enter the recipient's public key in the To key (64 hex chars) field.

    +
  8. +
  9. +

    Enter the amount to send in the Amount field.

    +
  10. +
  11. +

    Click Send.

    +
    warning
    +
  12. +
+

Transfers are irreversible; double-check the 64-hex recipient key and the amount before pressing Send. +:::

+

Read the transfer result

+
    +
  1. +

    Check the result row directly below the Send button:

    +
      +
    • On success, the row displays the transaction ID or hash returned by the module. Click the copy button to copy it to the clipboard.
    • +
    • On failure, the row displays Error: <message> with the rejection reason from the module.
    • +
    +
  2. +
  3. +

    Confirm the sender's balance decreased by pressing Refresh in the Accounts panel.

    +
  4. +
+

Troubleshooting dashboard transfers

+

Why is the Operations tab disabled?

+

The Operations tab is gated on the node status being Running. Start the node from the Node tab and wait for the status to change before attempting a transfer.

+

Why does the result row show "Module not initialized"?

+

The backend client is null, which means the module did not initialise correctly at startup. Restart the app and try again.

+

Why does the result row show "Call failed"?

+

The remote call timed out or returned no reply. Confirm the node is still running and the network connection is healthy, then retry.

+

Why does the result row show "Error: …"?

+

The module rejected the transaction. Common causes are an insufficient balance, an invalid recipient key, or a malformed amount string. Verify the recipient key is exactly 64 hex characters and the amount is a valid number, then retry.

\ No newline at end of file diff --git a/blockchain/zone-sdk/bridge-assets-between-blockchain-and-zone/index.html b/blockchain/zone-sdk/bridge-assets-between-blockchain-and-zone/index.html new file mode 100644 index 00000000..4776ee6b --- /dev/null +++ b/blockchain/zone-sdk/bridge-assets-between-blockchain-and-zone/index.html @@ -0,0 +1,121 @@ +Bridge assets between Logos Blockchain and a Zone | Logos documentation + + +

Bridge assets between Logos Blockchain and a Zone

+

Move tokens between Logos Blockchain and a Zone using channels and the Zone SDK.

+

Logos Zones are customizable, high-performance blockchains for applications built on Logos. Logos Blockchain notes can be bridged from Bedrock to Zones, with the Zone's associated channel maintaining a token balance that keeps track of the total token value stored in the Zone.

+

This procedure covers creating a channel, depositing notes from the Blockchain into a Zone, and withdrawing notes from a Zone back to the Blockchain, using the Zone SDK's ZoneSequencer. It applies to Zone developers building sequencers and indexers; the Zone itself defines how the channel balance maps to its internal accounts, while the SDK only surfaces the on-chain events.

+

Bridging has two directions: deposit (Blockchain to Zone), where a user funds a channel and the Zone sequencer credits the user internally (ChannelDeposit), and withdraw (Zone to Blockchain), where the sequencer submits a signed ChannelWithdraw to debit the channel and mint fresh notes on-chain.

+
info

The Zone SDK currently supports the bundled withdrawal API only for single-sequencer Zones (ChannelState.withdraw_threshold == 1). Multi-sequencer Zones require building the threshold proof manually, as covered in a later step.

+

Before you start, make sure you have:

+
    +
  • A running Logos Blockchain node reachable over HTTP
  • +
  • The Zone SDK (lb_zone_sdk) and lb_core crates added to your project
  • +
  • Completed the Zone SDK inscription tutorial
  • +
+

What to expect

+
    +
  • You can create a channel and have it recognized on-chain without a separate deployment transaction.
  • +
  • You can observe finalized deposits on your channel as they are credited inside the zone.
  • +
  • You can submit single- or multi-signature withdrawals and confirm them once finalized, including after a reorg.
  • +
+

Step 1: Create a channel

+

A channel is created automatically the first time an operation references a previously unseen ChannelId.

+
    +
  1. +

    Generate a Ed25519Key for your sequencer. The private key is used to sign inscriptions and channel operations, while the public key defines the ChannelId. An example implementation is shown below:

    +
    // Generate Ed25519 key pair
    let mut key_bytes = [0u8; ED25519_SECRET_KEY_SIZE];
    rand::RngCore::fill_bytes(&mut rand::thread_rng(), &mut key_bytes);
    std::fs::write(&your_key_file_path, key_bytes).expect("failed to write key file");
    let signing_key = Ed25519Key::from_bytes(&key_bytes);

    // Derive channel ID
    let channel_id = ChannelId::from(signing_key.public_key().to_bytes());
    +

    The bridging-relevant fields on ChannelState in the Mantle specification:

    +
    FieldPurpose
    balanceOn-chain TokenValue held by the channel. Deposits add, withdrawals subtract. Defaults to 0.
    withdrawal_nonceIncrements by 1 on every successful withdraw. Provides replay protection.
    withdraw_thresholdMinimum number of accredited-key signatures needed to authorize a withdrawal. Defaults to 0.
    accredited_keysThe committee that may sign withdrawals.
    +
  2. +
  3. +

    Initialize a ZoneSequencer and publish the first inscription inside your event loop once Event::Ready has fired. The channel is created on-chain automatically, naming this sequencer's key as the sole accredited key.

    +
    use lb_zone_sdk::{
    CommonHttpClient, adapter::NodeHttpClient, sequencer::ZoneSequencer,
    };

    // Connect to the Logos Blockchain node.
    let node = NodeHttpClient::new(
    CommonHttpClient::new(None),
    "http://localhost:8080".parse()?,
    );

    // Initialize the sequencer for this channel
    let mut sequencer = ZoneSequencer::init(channel_id, signing_key, node, None);

    // Inside the event loop, once `Event::Ready` has fired:
    // Publishing the first inscription creates the channel just-in-time.
    let (result, checkpoint) = sequencer.handle().publish(genesis_zone_block)?;
    +

    publish returns synchronously after enqueueing the transaction into the sequencer's pending set; the post reaches the node the next time the event loop polls next_event. Persist the returned PublishResult and SequencerCheckpoint into your outbox.

    +
  4. +
  5. +

    (Optional) Reconfigure the channel by calling sequencer.handle().channel_config(..) with a ChannelConfig operation.

    +
  6. +
+

Step 2: Observe deposits from Bedrock

+

A deposit happens when a Bedrock user submits a transaction with a ChannelDeposit operation naming the target channel, the consumed inputs, and opaque metadata that the Zone interprets, such as a recipient address.

+
    +
  1. +

    Watch for finalized deposits inside your events loop. The Zone SDK surfaces every finalized deposit on your channel as a FinalizedOp::Deposit(DepositInfo) inside the finalized field of Event::BlocksProcessed.

    +
    use lb_zone_sdk::sequencer::{Event, FinalizedOp};

    if let Event::BlocksProcessed { finalized, .. } = event {
    // Iterate over every finalized transaction in this batch of blocks.
    for tx in finalized {
    for op in tx.ops {
    if let FinalizedOp::Deposit(deposit) = op {
    // Credit the user inside the Zone according to the Zone's internal rules.
    println!(
    "Deposit of {} with metadata {:?}",
    deposit.amount, deposit.metadata,
    );
    }
    }
    }
    }
    +

    Event::BlocksProcessed fires once per ingested block, whether live or backfilled, and only carries finalized items at or below LIB, so deposits surfaced here cannot be re-orged off the chain.

    +
  2. +
  3. +

    Credit the user inside the Zone according to the Zone's internal state transition function rules.

    +
  4. +
+

Step 3: Submit a withdrawal

+

The process of withdrawing funds is different for single-sequencer Zones and multiple-sequencer Zones with a withdraw threshold greater than one.

+

Option 1: Submit a single-sequencer withdrawal

+

A withdrawal is initiated inside the zone and lands on-chain as a signed ChannelWithdraw operation. This step applies only when ChannelState.withdraw_threshold == 1.

+
    +
  1. +

    Describe the withdrawal by building a WithdrawArg with the recipient Outputs. The SDK fills in the channel_id and reads the current withdraw_nonce and accredited sequencer key.

    +
    use lb_core::mantle::{Note, ledger::Outputs};
    use lb_zone_sdk::sequencer::WithdrawArg;

    // Describe what to withdraw: a single note to the recipient.
    let withdraw = WithdrawArg {
    outputs: Outputs::new([Note::new(50, recipient_pk)]),
    };
    +
  2. +
  3. +

    Submit the inscription bundled with the withdraw by calling sequencer.handle().publish_atomic_withdraw(..).

    +
    // Inside the drive task: submit the inscription bundled with the withdraw.
    let (result, checkpoint) = sequencer.handle().publish_atomic_withdraw(
    inscription_payload, // the Zone block this withdraw goes with
    vec![withdraw],
    )?;
    +

    Because the inscription and the withdrawal share one transaction, they become adopted, orphaned, or finalized as a unit, so the Zone block recording the withdrawal and the on-chain debit cannot drift apart.

    +
    info
    +
  4. +
+

publish_atomic_withdraw returns the PublishResult synchronously. For a single-signature bundle, PublishResult.tx is a PendingTx::AtomicWithdraw(AtomicWithdrawInfo) carrying the inscription and the bundled withdraw operations. +:::

+
    +
  1. +

    Check the finalized transactions in Event::BlocksProcessed.finalized against the pending transaction's AtomicWithdrawInfo.tx_hash. Because it is a bundle, both the inscription and the withdrawal will appear in the same tx.ops once the withdrawal is finalized.

    +
    use lb_zone_sdk::sequencer::{Event, FinalizedOp};

    if let Event::BlocksProcessed { finalized, .. } = event {
    for tx in finalized {
    for op in tx.ops {
    match op {
    FinalizedOp::Inscription(info) => {
    // The Zone block carried with the withdrawal.
    println!("Inscribed {:?} in tx {:?}", info.this_msg, info.tx_hash);

    // Check if info.tx_hash matches the pending tx_hash
    }
    FinalizedOp::Withdraw(withdrawal) => {
    // The on-chain debit.
    println!("Withdrawn {:?} in tx {:?}", withdrawal.op, withdrawal.tx_hash);
    }
    FinalizedOp::Deposit(_) => {}
    }
    }
    }
    }
    +
  2. +
+

Option 2: Submit a multi-sequencer withdrawal

+

When withdraw_threshold > 1, no single sequencer can authorize a withdrawal alone. The proposing sequencer builds the ChannelWithdrawOp directly, because it must commit to a specific withdraw_nonce before sharing the unsigned transaction with the rest of the committee.

+
    +
  1. +

    Read the current withdraw_nonce and this sequencer's accredited-key index from the channel view.

    +
    use lb_core::mantle::{
    Op, SignedMantleTx,
    ops::{OpProof, channel::withdraw::ChannelWithdrawOp},
    };
    use lb_core::proofs::channel_multi_sig_proof::{ChannelMultiSigProof, IndexedSignature};

    // 1. Read current nonce + this sequencer's accredited-key index from
    // the channel view.
    let view = sequencer.subscribe_channel_view().borrow().clone();
    let withdraw_nonce = view
    .channel
    .as_ref()
    .ok_or("channel state not yet available")?
    .withdrawal_nonce;
    let own_key_index = view.own_key_index.ok_or("not an accredited key")?;
    +
  2. +
  3. +

    Build the unsigned transaction and obtain this sequencer's own signature by calling handle.prepare_tx(ops, inscription).

    +
    // 2. Build the unsigned tx and get this sequencer's own signature back.
    let withdraw = ChannelWithdrawOp {
    channel_id,
    outputs,
    withdraw_nonce,
    };
    let (tx, msg_id, own_sig) = sequencer.handle().prepare_tx(
    [Op::ChannelWithdraw(withdraw)].into(),
    inscription_payload,
    )?;
    +
  4. +
  5. +

    Share the unsigned transaction with the other accredited signers and collect their IndexedSignatures. Each signer calls handle.sign_tx(&tx) on the transaction the first sequencer proposes.

    +
    info
    +
  6. +
+

Defining the committee transport - how proposals and signatures are exchanged - is outside the Zone SDK's scope. +:::

+
// 3. Hand `tx` to the other accredited signers and collect their
// `IndexedSignature`s. Transport is application-defined.
let signatures: Vec<IndexedSignature> = collect_signatures_from_committee(
&tx,
IndexedSignature::new(own_key_index, own_sig.clone()),
).await?;
+
    +
  1. +

    Assemble the threshold proof and submit it once you have gathered ChannelState.withdraw_threshold signatures, by calling handle.submit_signed_tx(signed_tx, msg_id).

    +
    // 4. Assemble the threshold proof and submit.
    let withdraw_proof = ChannelMultiSigProof::new(signatures)?;
    let signed_tx = SignedMantleTx::new(
    tx,
    vec![
    OpProof::ChannelMultiSigProof(withdraw_proof),
    OpProof::Ed25519Sig(own_sig),
    ],
    )?;
    let (result, checkpoint) = sequencer
    .handle()
    .submit_signed_tx(signed_tx, msg_id)?;
    +

    Keep the result and use the returned tx_hash to identify the bundle. Unlike the single-sig flow, the SDK treats the caller-built transaction as opaque, so PublishResult.tx is PendingTx::Inscription(InscriptionInfo) regardless of the underlying ops.

    +
  2. +
  3. +

    Match the finalized transaction by tx_hash once it appears in Event::BlocksProcessed.finalized, the same way as in the single-signature flow in Option 1.

    +
  4. +
+

Step 4: Recover from a reorg

+

A reorg can orphan the parent inscription of a withdrawal submitted via publish_atomic_withdraw, which invalidates the original signed transaction.

+
    +
  1. +

    Watch for orphaned transactions in the channel_update field of Event::BlocksProcessed. The abandoned transaction appears in channel_update.orphaned.

    +
  2. +
  3. +

    Reconstruct the original WithdrawArgs from the orphaned bundle and re-call publish_atomic_withdraw with the same inscription payload; the SDK refills the inscription parent and the withdraw_nonce from the current on-chain state.

    +
    use lb_zone_sdk::sequencer::{Event, OrphanedTx, WithdrawArg};

    if let Event::BlocksProcessed { channel_update, .. } = event {
    for tx in channel_update.orphaned {
    if let OrphanedTx::AtomicWithdraw(info) = tx {

    // Rebuild the withdraw args from the orphaned bundle.
    let withdraws = info
    .withdraws
    .into_iter()
    .map(|w| WithdrawArg { outputs: w.op.outputs })
    .collect();

    // Republish with the same inscription payload; the SDK
    // refreshes the parent and withdraw_nonce automatically.
    let (result, checkpoint) = sequencer.handle().publish_atomic_withdraw(
    info.inscription.payload,
    withdraws,
    )?;

    // Keep `result` + `checkpoint` exactly as on the original publish.
    }
    }
    }
    +
  4. +
  5. +

    Keep the new result and checkpoint exactly as you did for the original publish to compare it to

    +
  6. +
+

Frequently asked questions

+

Does the reorg recovery path work for multi-sequencer withdrawals?

+

No. The reorg-aware recovery path described in Step 5 is not supported for multi-sig withdrawals at the moment and is planned for a future release.

\ No newline at end of file diff --git a/blockchain/zone-sdk/inscribe-data-on-chain-using-zone-sdk/index.html b/blockchain/zone-sdk/inscribe-data-on-chain-using-zone-sdk/index.html new file mode 100644 index 00000000..14560478 --- /dev/null +++ b/blockchain/zone-sdk/inscribe-data-on-chain-using-zone-sdk/index.html @@ -0,0 +1,94 @@ +Inscribe data on-chain using the Zone SDK | Logos documentation + + +

Inscribe data on-chain using the Zone SDK

+

Learn how to write plain text as on-chain inscriptions using a simple Logos Zone.

+

Applications built on Logos are implemented in execution environments known as Zones, which post data inscriptions on-chain via Logos channels. A Zone could host a versatile rollup with thousands of applications, such as the Logos Execution Zone, or it could be a simple, standalone Zone tracking the state of just one application.

+

The Zone SDK is a ready-to-use toolbox that handles basic interactions with a Logos Zone. This tutorial shows how to create a simple Logos Zone that writes plain text as on-chain inscriptions, based on the TUI Zone demo.

+
info

You can try out a working version of the TUI Zone used in this tutorial directly from your Logos Blockchain node. Just run ./logos-blockchain-node inscribe.

+

Before you begin, you will need:

+ +

What to expect

+
    +
  • You will initialise a ZoneSequencer that connects to your Logos node and creates a new channel for posting inscriptions.
  • +
  • You will implement event handling so the sequencer tracks which of its inscriptions have finalised and keeps a checkpoint to resume from.
  • +
  • You will publish plain-text messages as on-chain inscriptions and verify they are finalised in the channel.
  • +
+

Step 1: Clone the Logos Blockchain repository

+

Before you begin, clone the logos-blockchain repository. The tui-zone-tutorial branch contains a skeleton implementation of the TUI Zone with space to fill in the relevant code. A more comprehensive implementation, which also supports decentralised sequencing, can be found in the master branch.

+
    +
  1. +

    Run the following in your desired path to clone the repository and switch to the tutorial branch:

    +
    git clone https://github.com/logos-blockchain/logos-blockchain.git
    cd logos-blockchain/deployment/tui-zone

    # To follow along with the tutorial
    git checkout tui-zone-tutorial
    +
  2. +
+

Step 2: Initialise the ZoneSequencer struct

+

Sequencer functionality is provided via the ZoneSequencer struct from the Zone SDK, found in logos-blockchain/zone-sdk/src/sequencer/zone_sequencer.rs. When initialising this struct, provide the following arguments:

+
    +
  • channel_id: ChannelId — the ID of the channel associated with the Zone.
  • +
  • signing_key: Ed25519Key — a key authorised to post updates to the channel.
  • +
  • node: Node — a Node struct referring to your Logos Blockchain node, together with the credentials to access it. Created by NodeHttpClient::new().
  • +
  • checkpoint: Option<SequencerCheckpoint> — [Optional] the checkpoint representing the most recently-pushed channel update.
  • +
+

Before posting to a new channel, the sequencer must first generate an Ed25519 public/private key pair. This initial public key defines the channel ID, while the private key becomes the first authorised signing key. The channel is created when the sequencer posts a message with this channel ID, unless it already exists. Initially, only the sequencer with the signing key can post messages to the channel. Additional keys can be authorised via the CHANNEL_CONFIG Mantle Operation.

+

After the first channel message, further messages include a hash reference to the previous message in the channel. If a message is posted with an incorrect parent hash, it is rejected. Providing a checkpoint lets the sequencer track the last message posted to its channel, allowing it to resume posting new updates after restarting. A checkpoint is not necessary for the session during which a sequencer creates a new channel.

+
    +
  1. +

    Add a function to create a new private signing key in src/lib.rs:

    +
    // Load signing key from file or generate a new one if it doesn't exist
    //
    // path: The path to the signing key file
    fn load_or_create_signing_key(path: &Path) -> Ed25519Key {
    if path.exists() {
    let key_bytes = fs::read(path).expect("failed to read key file");
    assert!(
    key_bytes.len() == ED25519_SECRET_KEY_SIZE,
    "invalid key file: expected {} bytes, got {}",
    ED25519_SECRET_KEY_SIZE,
    key_bytes.len()
    );
    let key_array: [u8; ED25519_SECRET_KEY_SIZE] =
    key_bytes.try_into().expect("length already checked");
    Ed25519Key::from_bytes(&key_array)
    } else {
    let mut key_bytes = [0u8; ED25519_SECRET_KEY_SIZE];
    rand::RngCore::fill_bytes(&mut rand::thread_rng(), &mut key_bytes);
    fs::write(path, key_bytes).expect("failed to write key file");
    Ed25519Key::from_bytes(&key_bytes)
    }
    }
    +
  2. +
  3. +

    Fill in the run() function in src/lib.rs with the following code that creates a channel with an associated sequencer:

    +
    // Processing loop
    //
    // args: Setup info
    pub async fn run(args: InscribeArgs) {

    // Get node URL
    let node_url: Url = args.node_url.parse().expect("invalid node URL");

    // Create new signing key or load existing one from path
    let signing_key = load_or_create_signing_key(Path::new(&args.key_path));

    // Derive channel ID
    let channel_id = ChannelId::from(signing_key.public_key().to_bytes());

    println!("TUI Zone Sequencer");
    println!(" Node: {node_url}");
    println!(" Key: {}", args.key_path);
    println!(" Channel ID: {}", hex::encode(channel_id.as_ref()));
    println!();

    // Create initial Zone state & load checkpoint (to be implemented later in the tutorial)
    let mut state = InMemoryZoneState::default();
    let checkpoint = state.load_checkpoint().cloned();

    // Connect to node
    let node = NodeHttpClient::new(CommonHttpClient::new(None), node_url);

    // Initialise ZoneSequencer
    let mut sequencer = ZoneSequencer::init(channel_id, signing_key, node, checkpoint);

    // Wait to start reading from stdin until ready
    let (ready_tx, ready_rx) = tokio::sync::oneshot::channel();
    let mut stdin_rx = spawn_stdin_reader(ready_rx);
    let mut ready_tx = Some(ready_tx);

    ...

    }
    +
  4. +
+

Step 3: Handle channel events

+

For a centralized Zone — a single sequencer that owns its channel — event handling is minimal. There are no competing sequencers, and a single sequencer never loses a slot, so the sequencer only needs to track which of its inscriptions have finalised and keep a checkpoint so it can resume after a restart. It learns about both through the Zone SDK's Event stream.

+

The status of the sequencer's backfill process, transactions sent by the sequencer, and any updates to the Zone state are communicated via the Zone SDK's Event. These events are:

+
    +
  • Ready — the sequencer is caught up and ready to accept updates.
  • +
  • BlocksProcessed — a new block was processed. Includes the latest checkpoint and the list of finalized transactions. It also carries a channel_update (adopted / orphaned inscriptions), but that is only relevant when several sequencers share a channel — a centralized Zone ignores it.
  • +
  • MempoolPending — transaction was accepted by node API and is waiting in mempool.
  • +
  • TurnNotification — the sequencer's turn to write in a turn-based decentralised sequencing scenario (not applicable to our example).
  • +
+
    +
  1. +

    In src/state.rs, add the following struct to track Zone state in memory and maintain the checkpoint:

    +
    // Keep track of Zone state in memory
    //
    // published: Inscriptions published by your sequencer, not yet finalised
    // finalized: All finalised inscriptions
    // checkpoint: Last message in Zone state — lets the sequencer resume after a restart
    #[derive(Default)]
    pub struct InMemoryZoneState {
    published: Vec<Msg>,
    finalized: Vec<Msg>,
    checkpoint: Option<SequencerCheckpoint>,
    }

    impl InMemoryZoneState {
    // Record a tx we just published locally, so the local view stays in
    // sync with what the SDK accepted. Called at the publish-call site.
    pub fn on_published(&mut self, info: &InscriptionInfo) {
    self.published
    .push(Msg::from_payload(info.this_msg, &info.payload));
    }

    // Move our finalised publishes out of `published` and into `finalized`.
    fn on_finalized(&mut self, inscriptions: &[InscriptionInfo]) {
    for info in inscriptions {
    if let Some(i) = self
    .published
    .iter()
    .position(|m| m.msg_id == info.this_msg)
    {
    self.published.remove(i);
    }
    if !self.finalized.iter().any(|m| m.msg_id == info.this_msg) {
    self.finalized
    .push(Msg::from_payload(info.this_msg, &info.payload));
    }
    }
    }

    pub fn published(&self) -> &[Msg] {
    &self.published
    }

    pub fn finalized(&self) -> &[Msg] {
    &self.finalized
    }

    pub fn save_checkpoint(&mut self, checkpoint: SequencerCheckpoint) {
    self.checkpoint = Some(checkpoint);
    }

    pub fn load_checkpoint(&self) -> Option<&SequencerCheckpoint> {
    self.checkpoint.as_ref()
    }
    }
    +
  2. +
  3. +

    Back in src/lib.rs, add a function to handle when the sequencer has finished bootstrapping:

    +
    // Defines initial post-bootstrapping behaviour
    //
    // state: Zone state
    // ready_tx: The transmitter of the ready event to ready_rx
    fn handle_ready(
    state: &InMemoryZoneState,
    ready_tx: &mut Option<tokio::sync::oneshot::Sender<()>>,
    ) {
    info!("Sequencer ready");
    if let Some(tx) = ready_tx.take() {
    let _ = tx.send(());
    }
    println!("Ready.");
    println!();
    println!("Type a message and press Enter to publish.");
    println!("Press Ctrl-D or type an empty line to exit.");
    println!();
    ui::render_state(state);
    ui::prompt();
    }
    +
  4. +
  5. +

    Add a function to apply finalised messages to the Zone state:

    +
    // Apply finalised messages from chain to Zone state
    //
    // items: Finalised transactions
    // state: Zone state
    fn apply_finalized(items: &[FinalizedTx], state: &mut InMemoryZoneState) {

    // TUI only cares about inscriptions for rendering; deposit / withdraw
    // ops have no inscription payload.
    let inscriptions: Vec<InscriptionInfo> = items
    .iter()
    .flat_map(|t| t.ops.iter())
    .filter_map(|op| match op {
    FinalizedOp::Inscription(i) => Some(i.clone()),
    FinalizedOp::Deposit(_) | FinalizedOp::Withdraw(_) => None,
    })
    .collect();
    state.on_finalized(&inscriptions);
    ui::render_state(state);
    ui::prompt();
    }
    +
  6. +
  7. +

    Add a function to handle sequencer events and execute whichever of the above handlers is necessary for the current event:

    +
    // Handle sequencer events
    //
    // event: current sequencer event
    fn handle_event(
    event: Event,
    state: &mut InMemoryZoneState,
    ready_tx: &mut Option<tokio::sync::oneshot::Sender<()>>,
    ) {
    match event {
    Event::Ready => handle_ready(state, ready_tx),

    // Centralized Zone: ignore `channel_update` — a single sequencer
    // never loses a slot, so there is nothing to adopt or roll back.
    // Just apply finalised inscriptions and persist the checkpoint.
    Event::BlocksProcessed {
    checkpoint, finalized, ..
    } => {
    if !finalized.is_empty() {
    apply_finalized(&finalized, state);
    }
    state.save_checkpoint(checkpoint);
    }
    Event::MempoolPending(_) | Event::TurnNotification { .. } => {}
    }
    }
    +
  8. +
  9. +

    Add the processing loop to run() to watch for events:

    +
    pub async fn run(args: InscribeArgs) {

    ...

    loop {
    tokio::select! {

    // Watch for events
    event = sequencer.next_event() => {
    handle_event(event, &mut state, &mut ready_tx);
    }

    ...

    // Ctrl+C exits the loop
    _ = tokio::signal::ctrl_c() => {
    println!();
    break;
    }
    }
    }

    println!("Goodbye!");
    }
    +
  10. +
+

Step 4: Publish data

+

Once the ZoneSequencer is set up, posting data to the channel is as easy as passing a vector of bytes to the sequencer handler's publish function. Once the transaction is submitted to the Zone SDK, this function returns a struct consisting of the inscription (message) ID and the current checkpoint.

+
    +
  1. +

    Add the following code to process user input text in the run function in src/lib.rs:

    +
    pub async fn run(args: InscribeArgs) {

    ...

    loop {
    tokio::select! {

    ...

    // Get input text from stdin
    input = stdin_rx.recv() => {

    // Handle unexpected input
    let Some(text) = input else {
    println!();
    break;
    };

    // Turn input into AppMessage wrapper. Includes unique ID for each transaction to avoid removing 'duplicate' messages
    // from mempool
    let msg = AppMessage::new(text);
    debug!(tx_uuid = %msg.tx_uuid, text = %msg.text, "Publishing message");

    // publish() takes bytes anyway, so must convert
    let Ok(inscription) = Inscription::try_from(msg.to_bytes()) else {
    error!("Message is too large to fit in an inscription");
    continue;
    };

    // Publish data
    match sequencer.handle().publish(inscription) {

    // Get result and checkpoint, update Zone state
    Ok((result, checkpoint)) => {
    let info = result.tx.inscription();
    debug!(msg_id = %hex::encode(info.this_msg.as_ref()), "Published");
    state.on_published(info);
    state.save_checkpoint(checkpoint);
    ui::render_state(&state);
    eprintln!(" \x1b[90mpending...\x1b[0m");
    ui::prompt();
    }
    Err(lb_zone_sdk::sequencer::Error::Unavailable { reason }) => {
    warn!("publish rejected: {reason}");
    eprintln!(
    " \x1b[33msequencer is still starting up, try again in a moment\x1b[0m"
    );
    ui::prompt();
    }
    Err(e) => {
    error!("failed to publish: {e}");
    break;
    }
    }
    }

    ...

    }
    }

    ...

    }
    +
  2. +
+

Step 5: Build and run the sequencer

+
    +
  1. +

    With the code filled in, build and run the sequencer from the deployment/tui-zone directory. The binary is named tui-sequencer:

    +
    cargo run --bin tui-sequencer -- --node-url <Your Node URL>
    +

    The sequencer connects to your node, derives the channel ID from your signing key, and prints Ready. once it has bootstrapped. Type a message and press Enter to publish it as an on-chain inscription.

    +

    Each message first appears under Published (pending). Once your node finalises the block containing it, the message moves to Finalized — confirming the inscription is on-chain. Press Ctrl-D or enter an empty line to exit.

    +
  2. +
\ No newline at end of file diff --git a/blockchain/zone-sdk/operate-decentralised-zone-with-round-robin-sequencer-rotation/index.html b/blockchain/zone-sdk/operate-decentralised-zone-with-round-robin-sequencer-rotation/index.html new file mode 100644 index 00000000..c0ab1de2 --- /dev/null +++ b/blockchain/zone-sdk/operate-decentralised-zone-with-round-robin-sequencer-rotation/index.html @@ -0,0 +1,104 @@ +Operate a decentralised Zone with round-robin sequencer rotation | Logos documentation + + +

Operate a decentralised Zone with round-robin sequencer rotation

+

Get started with multi-sequencer channels using the Zone SDK and round-robin slot windows.

+

This tutorial covers how to configure and operate a Logos Blockchain channel with a committee of accredited sequencers that take turns publishing inscriptions to a Logos Zone. It is intended for zone developers using the Zone SDK who need to move from a single-sequencer setup to a decentralized one.

+

The Zone SDK currently supports round-robin rotation only. Each sequencer publishes inscriptions for posting_timeframe slots before the rotation advances to the next sequencer in the accredited_keys list. Other scheduling schemes (such as First-Write-Wins) are not yet available.

+

Before you start, make sure you have the following:

+
    +
  • An existing application using the Zone SDK
  • +
  • At least one Ed25519PublicKey generated per committee member
  • +
+

What to expect

+
    +
  • You can publish inscriptions from any sequencer at any time; the SDK holds pending transactions and drains them automatically when the sequencer's turn arrives.
  • +
  • You can monitor each sequencer's turn status in real time using subscribe_turn_to_write or subscribe_channel_view.
  • +
  • You can update the accredited key list to add or remove sequencers by submitting a ChannelConfigOp.
  • +
+

Step 1: Size the rotation parameters

+

Choose values for posting_timeframe and posting_timeout before creating or reconfiguring the channel. These fields on ChannelState control the rotation cadence and inactive-sequencer skip behavior.

+
info

The Mantle specification is the source of truth for the exact rotation algorithm. The guidance below is a practical summary.

+
    +
  1. +

    Set posting_timeframe - the length of each sequencer's turn under normal circumstances - to at least the average slots-per-block of the Logos Blockchain.

    +
      +
    • A turn shorter than one block lets multiple sequencers become authorized within the same block, defeating the purpose of rotation.
    • +
    • As a rule of thumb, use a multiple: with a ~20 slots/block average, posting_timeframe = 60 gives each sequencer roughly three blocks of ownership per turn.
    • +
    +
  2. +
  3. +

    Set posting_timeout to a value greater than or equal to posting_timeframe. posting_timeout is the time we wait for an inactive sequencer to publish before continuing to the next sequencer - all subsequent inactive sequencers have a turn of posting_timeout slots until one of them publishes. At that point, turns go back to being posting_timeframe slots long.

    +
      +
    • Values smaller than posting_timeframe make the timeout branch dominate cadence as soon as the authorized sequencer goes silent.
    • +
    +
  4. +
  5. +

    Confirm the worked example matches your intended behavior before proceeding.

    +

    A channel with three accredited keys, posting_timeframe = 60, and posting_timeout = 180:

    +
    slots 0–59 → index 0
    slots 60–119 → index 1
    slots 120–179 → index 2
    slots 180–239 → index 0
    ...
    +
    info
    +
  6. +
+

The Zone SDK exposes the current rotation state to your sequencer in two forms:

+
    +
  • The full SequencerChannelView (subscribe_channel_view) — a tokio::sync::watch receiver carrying authorized_key_index, own_key_index, our_turn_to_write, the current slot, and the turn window's turn_to_write_slots.
  • +
  • A focused TurnNotification (subscribe_turn_to_write) plus Event::TurnNotification — emitted only when the turn boundary actually changes.
  • +
+

our_turn_to_write is the boolean a sequencer reads to decide whether it is currently authorised. +:::

+

Step 2: Publish inscriptions and monitor turn state

+

The Zone SDK queues inscriptions locally when it is not your sequencer's turn and posts them automatically when the turn arrives. You do not need to gate publish calls on turn state yourself.

+
    +
  1. +

    Call publish from any task at any time.

    +
    // From the drive task, once `Event::Ready` has fired.
    let (result, checkpoint) = sequencer.handle().publish(zone_block)?;
    // `result.tx` is a `PendingTx::Inscription(...)`.
    // `checkpoint` is up to date with the new pending entry.
    // The post may not have hit the node yet if it is not our turn.
    +
      +
    • The SDK enqueues the transaction immediately if it is your turn and posts to the network.
    • +
    • The SDK enqueues the transaction with posted: false if it is not your turn; it drains automatically when the turn arrives.
    • +
    • A periodic resubmit_interval (default 30 s) re-drains pending publishes if your turn is current, covering transient network failures.
    • +
    +
  2. +
  3. +

    Subscribe to turn notifications to drive your UI or application logic. Choose one of the two surfaces based on your use case:

    +
      +
    • +

      Use TurnNotification (edge-triggered) to wake the UI only when the turn boundary changes:

      +
      use lb_zone_sdk::sequencer::Event;

      if let Event::TurnNotification { notification } = event {
      if notification.our_turn_to_write {
      println!(
      "Our turn until slot {:?}",
      notification.ends_at_slot,
      );
      } else {
      println!(
      "Waiting for our turn; current authorized window ends at {:?}",
      notification.ends_at_slot,
      );
      }
      }
      +
    • +
    • +

      Use subscribe_channel_view (snapshot) to read the full rotation state on every block, for example to render a status panel:

      +
      let view_rx = sequencer.subscribe_channel_view();
      let view = view_rx.borrow().clone();
      if view.our_turn_to_write {
      // Authorized right now.
      } else if let (Some(own), Some(auth)) = (view.own_key_index, view.authorized_key_index) {
      println!(
      "Sequencer {} is publishing; we are sequencer {}.",
      auth, own,
      );
      }
      +

      The view also carries pending_publish_txs and queued_messages so the UI can show how many inscriptions are waiting for the next turn.

      +
    • +
    +
  4. +
  5. +

    Handle competing writes by listening for Event::BlocksProcessed events and checking channel_update.orphaned for orphaned transactions. Republish these transactions with the latest parent.

    +
  6. +
+
info

Two sequencers can race on the same parent slot during rotation transitions or after a resync. The on-chain rule is: the first valid inscription wins; the loser's transaction is surfaced in channel_update.orphaned.

+
use lb_zone_sdk::sequencer::{Event, OrphanedTx};

if let Event::BlocksProcessed { channel_update, .. } = event {
for entry in channel_update.orphaned {
if let OrphanedTx::Inscription(info) = entry {
let (result, checkpoint) = sequencer.handle().publish(info.payload)?;
// Persist `result` + `checkpoint` exactly as on the original publish.
}
}
}
+
    +
  • See OrphanRepublishPolicy in tests/src/cucumber/steps/manual_zone/support.rs for the reference policy used in integration tests.
  • +
  • If the orphan was caused by an application-level conflict rather than a race, you can choose to drop the payload or deduplicate it against a higher-level transaction stream, or apply any other custom rule instead of republishing.
  • +
+

Step 3: Update the committee with a channel config op

+

Submit a ChannelConfigOp to add or remove sequencers from accredited_keys. Because ChannelConfigOp overwrites the entire key list, you should re-include any keys that should remain accredited after the update.

+
    +
  1. +

    Use channel_config directly when configuration_threshold == 1 - that is, when you only need one sequencer's signature to change the configuration.

    +
    use lb_core::mantle::channel::{SlotTimeframe, SlotTimeout};
    use lb_core::mantle::ops::channel::config::Keys;

    let new_keys = Keys::from(vec![
    admin_pk,
    new_sequencer_b_pk,
    new_sequencer_c_pk,
    ]);

    let (result, checkpoint, signed_tx) = sequencer.handle().channel_config(
    new_keys,
    SlotTimeframe::from(60), // ~3 blocks per turn at 20 slots/block
    SlotTimeout::from(180), // skip after 3 turn windows of inactivity
    1, // configuration_threshold (still single-admin)
    1, // withdraw_threshold
    )?;
    +

    On finalization, refresh_channel_state picks up the new config and the next BlocksProcessed event recomputes our_turn_to_write for every running sequencer.

    +
  2. +
  3. +

    Use the prepare_tx + submit_signed_tx flow when configuration_threshold > 1. In this case, collecting signatures from other sequencers is the responsibility of the application rather than the Zone SDK.

    +
  4. +
+
info

Multi-admin config changes are not yet exercised by integration tests; treat the flow below as the reference path until full SDK support lands.

+
    +
  • The proposer calls prepare_tx with a ChannelConfigOp payload and distributes the unsigned transaction to the rest of the committee.
  • +
  • Each co-signer calls sign_tx and returns an IndexedSignature.
  • +
  • The proposer assembles a ChannelMultiSigProof from the collected signatures and submits via submit_signed_tx.
  • +
+
use lb_core::mantle::{
Op, SignedMantleTx,
ops::{OpProof, channel::config::ChannelConfigOp},
};
use lb_core::proofs::channel_multi_sig_proof::{ChannelMultiSigProof, IndexedSignature};

// 1. Read this sequencer's index from the channel view.
let view = sequencer.subscribe_channel_view().borrow().clone();
let own_key_index = view.own_key_index.ok_or("not an accredited key")?;

// 2. Build the unsigned config tx and get this sequencer's own signature.
let config = ChannelConfigOp {
channel,
keys: new_keys,
posting_timeframe,
posting_timeout,
configuration_threshold,
withdraw_threshold,
};
let (tx, msg_id, own_sig) = sequencer.handle().prepare_tx(
[Op::ChannelConfig(config)].into(),
inscription_payload,
)?;

// 3. Distribute `tx` to the rest of the committee, collect their
// `IndexedSignature`s. Transport is application-defined.
let signatures: Vec<IndexedSignature> = collect_signatures_from_committee(
&tx,
IndexedSignature::new(own_key_index, own_sig.clone()),
).await?;

// 4. Assemble the threshold proof and submit.
let config_proof = ChannelMultiSigProof::new(signatures)?;
let signed_tx = SignedMantleTx::new(
tx,
vec![
OpProof::ChannelMultiSigProof(config_proof),
OpProof::Ed25519Sig(own_sig),
],
)?;
let (result, checkpoint) = sequencer
.handle()
.submit_signed_tx(signed_tx, msg_id)?;
\ No newline at end of file diff --git a/build-an-app/build-modules/build-a-logos-cpp-ui-module/index.html b/build-an-app/build-modules/build-a-logos-cpp-ui-module/index.html new file mode 100644 index 00000000..aeeb652b --- /dev/null +++ b/build-an-app/build-modules/build-a-logos-cpp-ui-module/index.html @@ -0,0 +1,11 @@ + + + + + + + + + \ No newline at end of file diff --git a/build-an-app/build-modules/build-and-run-a-logos-core-module/index.html b/build-an-app/build-modules/build-and-run-a-logos-core-module/index.html new file mode 100644 index 00000000..6b48aad0 --- /dev/null +++ b/build-an-app/build-modules/build-and-run-a-logos-core-module/index.html @@ -0,0 +1,11 @@ + + + + + + + + + \ No newline at end of file diff --git a/build-an-app/build-modules/install-and-load-a-module-in-logos-basecamp/index.html b/build-an-app/build-modules/install-and-load-a-module-in-logos-basecamp/index.html new file mode 100644 index 00000000..f3c6cb82 --- /dev/null +++ b/build-an-app/build-modules/install-and-load-a-module-in-logos-basecamp/index.html @@ -0,0 +1,11 @@ + + + + + + + + + \ No newline at end of file diff --git a/build-an-app/build-modules/readme/index.html b/build-an-app/build-modules/readme/index.html new file mode 100644 index 00000000..6b48aad0 --- /dev/null +++ b/build-an-app/build-modules/readme/index.html @@ -0,0 +1,11 @@ + + + + + + + + + \ No newline at end of file diff --git a/build-an-app/build-modules/wrap-a-c-library-as-a-logos-core-module/index.html b/build-an-app/build-modules/wrap-a-c-library-as-a-logos-core-module/index.html new file mode 100644 index 00000000..a09659f2 --- /dev/null +++ b/build-an-app/build-modules/wrap-a-c-library-as-a-logos-core-module/index.html @@ -0,0 +1,11 @@ + + + + + + + + + \ No newline at end of file diff --git a/build-an-app/index.html b/build-an-app/index.html new file mode 100644 index 00000000..907dcd58 --- /dev/null +++ b/build-an-app/index.html @@ -0,0 +1,4 @@ +Build an app | Logos documentation + + + \ No newline at end of file diff --git a/build-an-app/tutorials/build-dapp-with-logos-zone-sdk/index.html b/build-an-app/tutorials/build-dapp-with-logos-zone-sdk/index.html new file mode 100644 index 00000000..91e6ba72 --- /dev/null +++ b/build-an-app/tutorials/build-dapp-with-logos-zone-sdk/index.html @@ -0,0 +1,149 @@ +Build a dApp with the Logos Zone SDK | Logos documentation + + +

Build a dApp with the Logos Zone SDK

+

Learn how to use the Zone SDK to implement a decentralised password manager application.

+

This tutorial covers building a Logos Zone from scratch using the Zone SDK. Zones are execution environments built on the Logos Blockchain that post data as on-chain inscriptions via Logos channels. A Zone can host a versatile rollup with thousands of applications, such as the Logos Execution Zone, or a simple standalone Zone tracking the state of a single application.

+

The Zone SDK provides a ready-to-use toolbox for basic interactions with a Logos Zone. Every Zone is operated by one or more sequencers, which collect transactions, batch them, and publish them as inscriptions on the Logos Blockchain. Indexers are nodes that follow a Zone's updates on-chain, re-executing them locally to maintain an up-to-date copy of the Zone state. The Zone SDK supports building both.

+

The tutorial uses a password manager Zone as the running example: a single sequencer posts SQL transactions on-chain, and one or more indexers replay those transactions to sync their local state. The password manager maintains state in a SQLite database, with updates taking the form of SQL transactions applied to that database. The Zone design maps onto those concepts as follows:

+
    +
  • The user interacts with the password manager on the main device (in the sequencer folder) to add or update passwords.
  • +
  • The main device operates as a sequencer, posting SQL transactions as inscriptions to its channel.
  • +
  • Secondary devices operate as indexers, following the channel and obtaining SQL transactions from the chain.
  • +
  • Secondary devices run a read-only version of the password manager (in the indexer folder), applying SQL transactions from the channel to update their local state.
  • +
+
info

Read Decentralise the Log, Not the Server for the motivation behind this design.

+

The implementation skeleton already has most of the password manager code written. This tutorial will focus on using the Zone SDK to write the sequencer and indexer functionality, contained primarily in the sequencer/src/sequencer.rs and indexer/src/indexer.rs files.

+

Before you start:

+ +

What to expect

+
    +
  • You will configure a ZoneSequencer that signs and publishes SQL transactions as on-chain inscriptions.
  • +
  • You will configure a ZoneIndexer that follows a channel and applies incoming inscriptions to a local database.
  • +
  • You will have a compilable password manager Zone demo that illustrates core Zone SDK patterns.
  • +
+

Set up the repository

+

Clone the Logos SQL Zone repository, which contains all dependencies and password manager code unrelated to Zone functionality.

+
    +
  1. +

    Clone the repository:

    +
    git clone https://github.com/logos-blockchain/logos-sql-zone.git
    +
  2. +
  3. +

    Switch to the tutorial branch and initialise submodules:

    +
    cd logos-sql-zone
    git checkout tutorial

    git submodule update --init --recursive
    +

    The complete demo is available in the master branch.

    +
  4. +
+

Sequencer

+

The sequencer is the node that accepts user transactions, batches them, and posts them as inscriptions to the Logos Blockchain. The steps in this section implement the sequencer using the Zone SDK, using the password manager Zone as the example.

+

Step 1: Initialise the ZoneSequencer struct

+

Your sequencer wraps the ZoneSequencer struct from the Zone SDK, found in logos-blockchain/zone-sdk/src/sequencer/zone_sequencer.rs. Initialising it requires these arguments:

+
    +
  • channel_id: ChannelId — the ID of the channel associated with the Zone.
  • +
  • signing_key: Ed25519Key — a key authorised to post updates to the channel.
  • +
  • node: Node — a Node struct referring to your Logos Blockchain node, created by NodeHttpClient::new().
  • +
  • checkpoint: Option<SequencerCheckpoint> — (optional) the checkpoint representing the most recently pushed channel update.
  • +
+

Before posting to a new channel, the sequencer must generate an Ed25519 public/private key pair. The initial public key defines the channel ID; the private key becomes the first authorised signing key. The channel is created when the sequencer posts a message with this channel ID, unless the channel already exists. Additional keys can be authorised via the CHANNEL_CONFIG Mantle Operation.

+

After the first channel message, each subsequent message includes a hash reference to the previous one. A message with an incorrect parent hash is rejected. Providing a checkpoint lets the sequencer resume posting after a restart. A checkpoint is not required during the session that creates a new channel.

+
    +
  1. +

    In sequencer/src/sequencer.rs, add the Sequencer struct definition:

    +
    // The sequencer that handles transactions using the Zone SDK
    //
    // sequencer: The ZoneSequencer instance used by our wrapper
    // client: Async handle for submitting requests to Zone SDK sequencer
    // state: A helper struct for keeping track of transaction state,
    // see more on this below.
    // queue_file: The path to a file that holds SQL transactions not yet posted to the channel
    // checkpoint_path: The path to the channel checkpoint file
    pub struct Sequencer {
    sequencer: ZoneSequencer<NodeHttpClient>,
    client: SequencerClient,
    state: InMemoryZoneState,
    queue_file: String,
    checkpoint_path: String,
    }
    +
  2. +
  3. +

    Add a function to load or generate a private signing key:

    +
    // Load signing key from file or generate a new one if it doesn't exist
    //
    // path: The path to the signing key file
    fn load_or_create_signing_key(path: &Path) -> Ed25519Key {
    if path.exists() {
    let key_bytes = fs::read(path).expect("failed to read key file");
    assert!(
    key_bytes.len() == ED25519_SECRET_KEY_SIZE,
    "invalid key file: expected {} bytes, got {}",
    ED25519_SECRET_KEY_SIZE,
    key_bytes.len()
    );
    let key_array: [u8; ED25519_SECRET_KEY_SIZE] =
    key_bytes.try_into().expect("length already checked");
    Ed25519Key::from_bytes(&key_array)
    } else {
    let mut key_bytes = [0u8; ED25519_SECRET_KEY_SIZE];
    rand::RngCore::fill_bytes(&mut rand::rng(), &mut key_bytes);
    fs::write(path, key_bytes).expect("failed to write key file");
    Ed25519Key::from_bytes(&key_bytes)
    }
    }
    +
  4. +
  5. +

    Add a function to restore from a saved checkpoint:

    +
    // Restore from saved checkpoint
    //
    // path: Path to checkpoint file
    fn load_checkpoint(path: &Path) -> Option<SequencerCheckpoint> {
    if !path.exists() {
    return None;
    }
    let data = fs::read(path).expect("failed to read checkpoint file");
    Some(serde_json::from_slice(&data).expect("failed to deserialize checkpoint"))
    }
    +
  6. +
  7. +

    Add the new function to the Sequencer struct implementation:

    +
    impl Sequencer {

    // Create a new Sequencer
    //
    // node_endpoint: Address of Logos Blockchain node
    // signing_key_path: Path to file containing signing key
    // node_auth_username: Username to access node
    // node_auth_password: Password to access node
    // queue_file: Path to file storing queued SQL statements
    // checkpoint_path: Path to file containing latest channel checkpoint
    // channel_path: Path to file with channel ID
    pub async fn new(
    node_endpoint: &str,
    signing_key_path: &str,
    node_auth_username: Option<String>,
    node_auth_password: Option<String>,
    queue_file: &str,
    checkpoint_path: &str,
    channel_path: &str,
    ) -> Result<Self> {
    let node_url = Url::parse(node_endpoint).map_err(|e| SequencerError::Url(e.to_string()))?;

    let basic_auth = node_auth_username
    .map(|username| BasicAuthCredentials::new(username, node_auth_password));

    // Create files from paths if they don't exist
    for path in [signing_key_path, checkpoint_path, channel_path] {
    if let Some(parent) = Path::new(path).parent() {
    fs::create_dir_all(parent)?;
    }
    }

    let checkpoint = load_checkpoint(Path::new(checkpoint_path));
    if checkpoint.is_some() {
    println!(" Restored checkpoint from {checkpoint_path}");
    }

    // Produce channel ID from signing key
    let signing_key = load_or_create_signing_key(Path::new(signing_key_path));
    let channel_id = ChannelId::from(signing_key.public_key().to_bytes());
    fs::write(channel_path, hex::encode(channel_id.as_ref()))
    .expect("failed to write channel id");

    // Initialise the ZoneSequencer
    let node = NodeHttpClient::new(CommonHttpClient::new(basic_auth), node_url);
    let sequencer = ZoneSequencer::init(channel_id, signing_key, node, checkpoint);
    let client = sequencer.client();

    Ok(Self {
    sequencer,
    client,
    state: InMemoryZoneState::default(),
    queue_file: queue_file.to_owned(),
    checkpoint_path: checkpoint_path.to_owned(),
    })
    }

    ...

    }
    +
  8. +
+

Step 2: Publish data to the channel

+

Once the ZoneSequencer is set up, posting data to the channel is as simple as passing a byte vector to the sequencer's publish function, which returns an inscription ID and the current checkpoint.

+

Whenever the password manager's database is updated, the SQL transactions are also written to a queue file. This functionality is already implemented in the sequencer/src/db.rs file. A processing loop continuously checks for new SQL transactions from the password database and submits them as plain text to the Zone.

+
    +
  1. +

    In sequencer/src/sequencer.rs, add a function to save checkpoints after each published update:

    +
    // Write latest checkpoint to file
    //
    // path: Path to checkpoint file
    // checkpoint: Checkpoint message
    fn save_checkpoint(path: &Path, checkpoint: &SequencerCheckpoint) {
    let data = serde_json::to_vec(checkpoint).expect("failed to serialize checkpoint");
    fs::write(path, data).expect("failed to write checkpoint file");
    }
    +
  2. +
  3. +

    Add a function to read pending SQL transactions from the queue file and clear it:

    +
    // Drain the queue file and return all pending queries
    //
    // queue_file: File for queued SQL statements
    fn queue_drain(queue_file: &str) -> Result<Vec<String>> {

    // Check if queue_file is empty
    let file = match OpenOptions::new().read(true).write(true).open(queue_file) {
    Ok(f) => f,
    Err(e) if e.kind() == io::ErrorKind::NotFound => return Ok(Vec::new()),
    Err(e) => return Err(SequencerError::Io(e)),
    };

    file.lock_exclusive()?;

    let reader = BufReader::new(&file);
    let mut queue_vec = Vec::new();
    for query in reader.lines() {
    queue_vec.push(query?);
    }

    // Clear queue file
    file.set_len(0)?;

    Ok(queue_vec)
    }
    +
  4. +
  5. +

    Add a function to publish the pending SQL transactions as a single inscription:

    +
    // Process all pending queries as a single inscription
    //
    // queue_file: File for queued SQL statements
    async fn process_pending_batch(queue_file: &str, client: &SequencerClient) -> Result<()> {

    // Read list of pending SQL statements
    let pending = queue_drain(queue_file)?;
    if pending.is_empty() {
    return Ok(());
    }

    let count = pending.len();
    debug!("Processing batch of {} queries", count);

    // Publish batch of statements
    let sql_bytes = pending.join("\n").into_bytes();
    let inscription = Inscription::try_from(sql_bytes)
    .map_err(|e| SequencerError::InscriptionTooLarge(e.to_string()))?;
    if let Err(e) = client.publish(inscription).await {
    error!("failed to publish batch: {e}");
    } else {
    info!("Submitted batch of {} statement(s)", count);
    }

    Ok(())
    }
    +
  6. +
  7. +

    Add the run function to the Sequencer struct implementation to poll the queue and publish its contents:

    +
    impl Sequencer {

    ...

    // Processing loop
    pub async fn run(self) {
    let Self { mut sequencer, client, mut state, queue_file, checkpoint_path } = self;

    // Loop to check queue and publish
    let batch_client = client;
    tokio::spawn(async move {
    // Wait until the sequencer completes cold-start backfill before publishing.
    let mut ready_rx = batch_client.subscribe_ready();
    drop(ready_rx.wait_for(|r| *r).await);

    let mut interval = tokio::time::interval(Duration::from_millis(100));
    loop {
    interval.tick().await;
    if let Err(e) = process_pending_batch(&queue_file, &batch_client).await {
    error!("Batch processing failed: {e}");
    }
    }
    });

    ...

    }
    }
    +
  8. +
+

Step 3: Handle channel events

+

The sequencer must track the Zone state on the blockchain so it can detect reorgs, finalized blocks, and updates from other sequencers. In our example, there is only one sequencer, and therefore the state only has to track which inscriptions have finalized. The Zone SDK communicates these via Event:

+
    +
  • Ready — the sequencer is caught up and ready to accept updates.
  • +
  • BlocksProcessed — a new block was processed. Includes the latest checkpoint and the list of finalized transactions. It also carries a channel_update (adopted / orphaned inscriptions), but that is only relevant when several sequencers share a channel — a centralized Zone ignores it.
  • +
  • MempoolPending — transaction was accepted by node API and is waiting in mempool.
  • +
  • TurnNotification — the sequencer's turn to write in a turn-based decentralised sequencing scenario (not applicable to our example).
  • +
+
    +
  1. +

    In common/src/state.rs, add the InMemoryZoneState struct to track inscription IDs:

    +
    // Keep track of Zone state in memory
    //
    // published: Inscriptions published by your sequencer but not yet finalised or orphaned
    // finalized: All finalised inscriptions
    #[derive(Default)]
    pub struct InMemoryZoneState {
    published: Vec<Msg>,
    finalized: Vec<Msg>,
    }

    impl ZoneState for InMemoryZoneState {
    fn on_published(&mut self, info: &InscriptionInfo) {
    self.published
    .push(Msg::from_payload(info.this_msg, &info.payload));
    }

    // Move our finalised publishes out of `published` and into `finalized`.
    fn on_finalized(&mut self, inscriptions: &[InscriptionInfo]) {
    for info in inscriptions {
    if let Some(i) = self
    .published
    .iter()
    .position(|m| m.msg_id == info.this_msg)
    {
    self.published.remove(i);
    }
    if !self.finalized.iter().any(|m| m.msg_id == info.this_msg) {
    self.finalized
    .push(Msg::from_payload(info.this_msg, &info.payload));
    }
    }
    }

    fn published(&self) -> &[Msg] {
    &self.published
    }

    fn finalized(&self) -> &[Msg] {
    &self.finalized
    }
    }
    +
  2. +
  3. +

    In sequencer/src/sequencer.rs, add a function to handle events:

    +
    // Handle channel events
    //
    // event: Channel event enum
    // state: Zone state struct from common/src/state.rs
    // checkpoint_path: Path to checkpoint file
    fn handle_event(
    event: Event,
    state: &mut InMemoryZoneState,
    checkpoint_path: &str,
    ) {
    match event {
    Event::Ready => {
    info!("Sequencer ready");
    }
    Event::BlocksProcessed { checkpoint, channel_update, finalized } => {

    // Add newly-finalized inscriptions to the finalized list
    let inscriptions: Vec<_> = finalized
    .into_iter()
    .flat_map(|tx| tx.ops.into_iter())
    .filter_map(|op| match op {
    FinalizedOp::Inscription(info) => Some(info),
    _ => None,
    })
    .collect();
    state.on_finalized(&inscriptions);
    save_checkpoint(Path::new(checkpoint_path), &checkpoint);
    }
    Event::MempoolPending(_) | Event::TurnNotification { .. } => {}
    }
    }
    +
  4. +
  5. +

    Add an event loop to the run function in the Sequencer struct:

    +
    impl Sequencer {

    ...

    // Processing loop
    pub async fn run(self) {

    ...

    loop {
    let event = sequencer.next_event().await;
    handle_event(event, &mut state, &checkpoint_path);
    }
    }
    }
    +
  6. +
+

Indexer

+

The indexer is the node that follows a Zone's channel on the Logos Blockchain and re-executes updates locally to maintain a current copy of the Zone state. The steps in this section implement the indexer using the Zone SDK, continuing the password manager example. The indexer must obtain the sequencer's channel ID via an out-of-band mechanism (in our case, the channel_path file) before it can start.

+

Step 1: Initialise the ZoneIndexer struct

+

Your indexer wraps the ZoneIndexer struct from the Zone SDK, found in logos-blockchain/zone-sdk/src/indexer.rs. Initialising it requires:

+
    +
  • channel_id: ChannelId — the ID of the channel associated with the Zone.
  • +
  • node: Node — a Node struct referring to your Logos Blockchain node, created by NodeHttpClient::new().
  • +
+
    +
  1. +

    In indexer/src/indexer.rs, add the Indexer struct definition:

    +
    // Indexer struct
    //
    // zone_indexer: SDK indexer
    // db_path: Path to password database file
    pub struct Indexer {
    zone_indexer: ZoneIndexer<NodeHttpClient>,
    db_path: String,
    }
    +
  2. +
  3. +

    Add a helper function to parse a channel ID from a hex string:

    +
    // Parse channel ID from string
    //
    // channel_id_str: channel ID
    fn parse_channel_id(channel_id_str: &str) -> Result<ChannelId> {

    // string to bytes
    let decoded = hex::decode(channel_id_str).map_err(|_| {
    Error::InvalidChannelId(format!(
    "INDEXER_CHANNEL_ID must be a valid hex string, got: '{channel_id_str}'"
    ))
    })?;

    // to 32 bytes
    let channel_bytes: [u8; 32] = decoded.try_into().map_err(|v: Vec<u8>| {
    Error::InvalidChannelId(format!(
    "INDEXER_CHANNEL_ID must be exactly 64 hex characters (32 bytes), got {} characters ({} bytes)",
    v.len() * 2,
    v.len()
    ))
    })?;

    Ok(ChannelId::from(channel_bytes))
    }
    +
  4. +
  5. +

    Add the new function to the Indexer struct implementation:

    +
    impl Indexer {

    // Create new indexer
    //
    // db_path: Path to local password db
    // node_endpoint: Address of Logos Blockchain node
    // channel_path: Path to file with channel ID
    // node_auth_username: Username to access node
    // node_auth_password: Password to access node
    pub fn new(
    db_path: &str,
    node_endpoint: &str,
    channel_path: &str,
    node_auth_username: Option<String>,
    node_auth_password: Option<String>,
    ) -> Result<Self> {
    let node_url = Url::parse(node_endpoint).map_err(|e| Error::Url(e.to_string()))?;

    let basic_auth = node_auth_username
    .map(|username| BasicAuthCredentials::new(username, node_auth_password));

    // Parse channel ID
    let channel_id_str = fs::read_to_string(channel_path).map_err(|e| {
    Error::InvalidChannelId(format!("Failed to read channel path '{channel_path}': {e}"))
    })?;
    let channel_id = parse_channel_id(channel_id_str.trim())?;

    info!("Channel ID: {}", hex::encode(channel_id.as_ref()));

    // New ZoneIndexer
    let node = NodeHttpClient::new(CommonHttpClient::new(basic_auth), node_url);
    let zone_indexer = ZoneIndexer::new(channel_id, node);

    Ok(Self { zone_indexer, db_path: db_path.to_owned() })
    }

    ...

    }
    +
  6. +
+

Step 2: Follow the channel

+

An indexer uses the follow function to check new blocks for channel updates. It returns a stream that can be iterated to obtain the latest messages. Once a block with a new channel message is finalised, the indexer applies the message to update its local state.

+
    +
  1. +

    Add the run function to the Indexer struct implementation:

    +
    impl Indexer {

    ...

    // Follow the Zone channel & apply updates
    pub async fn run(self) {

    // Open database at db_path
    let db = match DatabaseReadOnly::open(&self.db_path) {
    Ok(db) => db,
    Err(e) => {
    error!("Failed to open database: {e}");
    return;
    }
    };

    loop {

    // Follow channel & create stream
    info!("Connecting to zone block stream...");
    let stream = match self.zone_indexer.follow().await {
    Ok(s) => s,
    Err(e) => {
    error!("Failed to connect to block stream: {e}");
    tokio::time::sleep(std::time::Duration::from_secs(5)).await;
    continue;
    }
    };
    info!("Connected to zone block stream");

    // Get next message from stream
    futures::pin_mut!(stream);
    while let Some(zone_msg) = stream.next().await {

    // Ensure zone_block includes the inscriptions but not the deposit operations in the block
    let logos_blockchain_zone_sdk::ZoneMessage::Block(zone_block) = zone_msg else {
    continue;
    };

    let sql_text = match String::from_utf8(Vec::from(zone_block.data)) {
    Ok(s) => s,
    Err(e) => {
    error!("Zone block data is not valid UTF-8: {e}");
    continue;
    }
    };

    // Extract SQL statements from message
    let statements: Vec<&str> = sql_text
    .lines()
    .map(|l: &str| l.trim().trim_end_matches(';').trim())
    .filter(|s: &&str| !s.is_empty())
    .collect();

    if statements.is_empty() {
    continue;
    }

    info!("Applying {} SQL statement(s)", statements.len());

    // Apply statements to db
    for stmt in &statements {
    if let Err(e) = db.execute_batch(stmt) {
    error!("Failed to execute SQL '{}': {e}", stmt);
    }
    }
    info!("Applied {} statement(s)", statements.len());
    }

    error!("Zone block stream ended, reconnecting...");
    tokio::time::sleep(std::time::Duration::from_secs(5)).await;
    }
    }
    }
    +
  2. +
+

Frequently asked questions

+

What other Zones can I build with the SDK?

+

This tutorial covered a simple single-sequencer appchain, but the Zone SDK supports a much wider range. You could build traditional ZK or optimistic rollups, high-throughput appchains, or applications compartmentalised across several Zones. Logos Zones also support interoperability features such as bridging Layer 1 tokens into Zones, on-chain message passing between Zones, and complex cross-Zone transaction coordination.

+

Where do I find instructions for running the demo?

+

Instructions for building and interacting with the sequencer and indexer applications are in the README.md file in the repository root.

\ No newline at end of file diff --git a/build-an-app/tutorials/create-and-use-an-amm-liquidity-pool-on-the-logos-execution-zone/index.html b/build-an-app/tutorials/create-and-use-an-amm-liquidity-pool-on-the-logos-execution-zone/index.html new file mode 100644 index 00000000..df999f1a --- /dev/null +++ b/build-an-app/tutorials/create-and-use-an-amm-liquidity-pool-on-the-logos-execution-zone/index.html @@ -0,0 +1,39 @@ +Create and use an AMM liquidity pool on the Logos Execution Zone | Logos documentation + + +

Create and use an AMM liquidity pool on the Logos Execution Zone

+
warning

This page is an early draft and may be incomplete or incorrect. Expect changes, missing prerequisites, and commands that might not work in your setup. We are actively working to complete and verify this content.

+

This tutorial covers the AMM program in LEZ. The AMM manages liquidity pools and enables swaps between custom tokens. By the end, you will have practiced:

+
    +
  1. Creating a liquidity pool for a token pair.
  2. +
  3. Swapping tokens.
  4. +
  5. Withdrawing liquidity from the pool.
  6. +
  7. Adding liquidity to the pool.
  8. +
+

1. Creating a liquidity pool for a token pair

+

We start by creating a pool for the tokens created earlier. In return for providing liquidity, you receive liquidity provider (LP) tokens. LP tokens represent your share of the pool and are required to withdraw liquidity later.

+
info

The AMM does not currently charge swap fees or distribute rewards to liquidity providers. LP tokens therefore represent only a proportional share of the pool reserves. Fee support will be added in future versions.

+

a. Create an LP holding account

+
wallet account new public

# Output:
Generated new account with account_id Public/FHgLW9jW4HXMV6egLWbwpTqVAGiCHw2vkg71KYSuimVf
+

b. Initialise the pool

+

Deposit tokens A and B and specify the account that will receive LP tokens:

+
wallet amm new \
--user-holding-a Public/9RRSMm3w99uCD2Jp2Mqqf6dfc8me2tkFRE9HeU2DFftw \
--user-holding-b Public/88f2zeTgiv9LUthQwPJbrmufb9SiDfmpCs47B7vw6Gd6 \
--user-holding-lp Public/FHgLW9jW4HXMV6egLWbwpTqVAGiCHw2vkg71KYSuimVf \
--balance-a 100 \
--balance-b 200
+
warning

The LP holding account is owned by the token program, so LP tokens are managed using the same token infrastructure as regular tokens.

+
wallet account get --account-id Public/FHgLW9jW4HXMV6egLWbwpTqVAGiCHw2vkg71KYSuimVf

# Output:
Holding account owned by token program
{"account_type":"Token holding","definition_id":"7BeDS3e28MA5Err7gBswmR1fUKdHXqmUpTefNPu3pJ9i","balance":100}
+
tip

If you inspect the user-holding-a and user-holding-b accounts, you will see that 100 and 200 tokens were deducted. Those tokens now reside in the pool and are available for swaps by any user.

+

2. Swapping

+

Use wallet amm swap-exact-input to perform a token swap:

+
wallet amm swap-exact-input \
--user-holding-a Public/9RRSMm3w99uCD2Jp2Mqqf6dfc8me2tkFRE9HeU2DFftw \
--user-holding-b Public/88f2zeTgiv9LUthQwPJbrmufb9SiDfmpCs47B7vw6Gd6 \
# The amount of tokens to swap
--amount-in 5 \
# The minimum number of tokens expected in return
--min-amount-out 8 \
# The definition ID of the token being provided to the swap
# In this case, we are swapping from TOKENA to TOKENB, and so this is the definition ID of TOKENA
--token-definition 4X9kAcnCZ1Ukkbm3nywW9xfCNPK8XaMWCk3zfs1sP4J7
+

Once executed, 5 tokens are deducted from the Token A holding account and the corresponding amount (computed by the pool’s pricing function) is credited to the Token B holding account.

+

3. Withdrawing liquidity from the pool

+

Liquidity providers can withdraw assets by redeeming (burning) LP tokens. The amount received is proportional to the share of LP tokens redeemed relative to the total LP supply.

+

Use wallet amm remove-liquidity:

+
wallet amm remove-liquidity \
--user-holding-a Public/9RRSMm3w99uCD2Jp2Mqqf6dfc8me2tkFRE9HeU2DFftw \
--user-holding-b Public/88f2zeTgiv9LUthQwPJbrmufb9SiDfmpCs47B7vw6Gd6 \
--user-holding-lp Public/FHgLW9jW4HXMV6egLWbwpTqVAGiCHw2vkg71KYSuimVf \
--balance-lp 20 \
--min-amount-a 1 \
--min-amount-b 1
+
warning

This burns balance-lp LP tokens from the user’s LP holding account. In return, the AMM transfers tokens A and B from the pool vaults to the user’s holding accounts, based on current reserves. +The min-amount-a and min-amount-b parameters set the minimum acceptable outputs. If the computed amounts fall below either threshold, the instruction fails to protect against unfavourable pool changes.

+

4. Adding liquidity to the pool

+

To add liquidity, deposit tokens A and B in the ratio implied by current pool reserves. In return, the AMM mints new LP tokens that represent your proportional share.

+

Use wallet amm add-liquidity:

+
wallet amm add-liquidity \
--user-holding-a Public/9RRSMm3w99uCD2Jp2Mqqf6dfc8me2tkFRE9HeU2DFftw \
--user-holding-b Public/88f2zeTgiv9LUthQwPJbrmufb9SiDfmpCs47B7vw6Gd6 \
--user-holding-lp Public/FHgLW9jW4HXMV6egLWbwpTqVAGiCHw2vkg71KYSuimVf \
--min-amount-lp 1 \
--max-amount-a 10 \
--max-amount-b 10
+
warning

max-amount-a and max-amount-b cap how many tokens A and B can be taken from the user’s accounts. The AMM computes the required amounts based on the pool’s reserve ratio. +min-amount-lp sets the minimum LP tokens to mint. If the computed LP amount falls below this threshold, the instruction fails.

\ No newline at end of file diff --git a/build.json b/build.json new file mode 100644 index 00000000..5dd80926 --- /dev/null +++ b/build.json @@ -0,0 +1,15 @@ +{ + "timestamp": "2026-07-26T05:59:30Z", + "git": { + "commit": "5691a0666bfec54c1e2303a9d67149a2e1489a31", + "branch": "origin/develop", + "url": "git@github.com:logos-co/logos-docs.git" + }, + "build": { + "id": "1", + "number": "1", + "name": "website/dev-docs.logos.co", + "slave": "linux-01", + "url": "https://ci.infra.status.im/job/website/job/dev-docs.logos.co/1/" + } +} \ No newline at end of file diff --git a/contribute/welcome/index.html b/contribute/welcome/index.html new file mode 100644 index 00000000..7abe89ca --- /dev/null +++ b/contribute/welcome/index.html @@ -0,0 +1,5 @@ +Welcome | Logos documentation + + +

Welcome

+

Welcome to the contributor documentation! We're working on the content.

\ No newline at end of file diff --git a/core/build-modules/build-a-logos-cpp-ui-module/index.html b/core/build-modules/build-a-logos-cpp-ui-module/index.html new file mode 100644 index 00000000..53257719 --- /dev/null +++ b/core/build-modules/build-a-logos-cpp-ui-module/index.html @@ -0,0 +1,197 @@ +Build a Logos C++ UI module | Logos documentation + + +

Build a Logos C++ UI module

+

Get started building a ui_qml module with a C++ backend that runs in a separate process.

+

This guide covers building a module that pairs a QML user interface with a C++ backend plugin. The backend runs in a separate ui-host process while the QML view loads inside the host app (logos-basecamp or logos-standalone-app), so a backend crash cannot bring down the host. This guide is intended for developers who have completed Part 1 and want typed, process-isolated inter-module calls from their UI layer.

+

Before you start, make sure you have the following:

+
    +
  • Completed Part 1 — a working calc_module with the shared library built in logos-calc-module/lib/.
  • +
  • Nix with flakes enabled.
  • +
  • Basic familiarity with QML
  • +
+

What to expect

+
    +
  • You can scaffold, configure, and build a calc_ui_cpp module with a process-isolated C++ backend.
  • +
  • You can call calc_module methods from QML using a typed replica and logos.watch().
  • +
  • You will be able to build, run, and live-reload the module using nix run.
  • +
+

Step 1: Scaffold the project

+

Create a new directory and initialise it from the C++ backend UI template.

+
    +
  1. +

    Create and enter the project directory:

    +
    mkdir logos-calc-ui-cpp && cd logos-calc-ui-cpp
    +
  2. +
  3. +

    Initialise from the template:

    +
    nix flake init -t github:logos-co/logos-module-builder/0.2.0#ui-qml-backend
    +
  4. +
  5. +

    Initialise a Git repository and stage all generated files:

    +
    git init && git add -A
    +
  6. +
  7. +

    Remove the template's example sources. The scaffolded template includes ui_example files with mismatched class names and IIDs; leaving them causes build errors or plugin-load failures at runtime:

    +
    rm -f src/ui_example.rep src/ui_example_interface.h src/ui_example_plugin.h src/ui_example_plugin.cpp
    +
  8. +
+

Step 2: Configure the module metadata

+

Replace the template metadata.json with your plugin's details.

+
info

The calc_module.url input attribute name in flake.nix must match the dependency name declared here in "dependencies".

+
    +
  1. +

    Replace metadata.json with the following:

    +
    {
    "name": "calc_ui_cpp",
    "version": "1.0.0",
    "type": "ui_qml",
    "category": "tools",
    "description": "Calculator C++ UI — QML view with process-isolated backend for calc_module",
    "main": "calc_ui_cpp_plugin",
    "view": "qml/Main.qml",
    "icon": "icons/calc.png",
    "dependencies": ["calc_module"],

    "nix": {
    "packages": { "build": [], "runtime": [] },
    "external_libraries": [],
    "cmake": {
    "find_packages": [],
    "extra_sources": [],
    "extra_include_dirs": [],
    "extra_link_libraries": []
    }
    }
    }
    +

    Key fields:

    +
      +
    • "type": "ui_qml" — tells the builder this is a QML view module.
    • +
    • "main": "calc_ui_cpp_plugin" — the backend Qt plugin library name (without extension).
    • +
    • "view": "qml/Main.qml" — the QML entry point.
    • +
    • "dependencies": ["calc_module"]core modules the backend calls.
    • +
    +
  2. +
  3. +

    Create the icons directory and add a placeholder icon (displayed in the logos-basecamp sidebar when the module is loaded):

    +
    mkdir -p icons
    # Copy any PNG here - or generate a 64x64 placeholder:
    echo "iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAAmElEQVR4nO3QMREAIBDAsFeEN3ziCWRkoEP2XmedfX82OkBrgA7QGqADtAboAK0BOkBrgA7QGqADtAboAK0BOkBrgA7QGqADtAboAK0BOkBrgA7QGqADtAboAK0BOkBrgA7QGqADtAboAK0BOkBrgA7QGqADtAboAK0BOkBrgA7QGqADtAboAK0BOkBrgA7QGqADtAboAO0BN/SiO/PatoIAAAAASUVORK5CYII=" | base64 -d > icons/calc.png
    +
  4. +
+

Step 3: Define the remote interface

+

The .rep file is the single source of truth for the interface between the QML view and the C++ backend. repc compiles it at build time into typed source and replica headers.

+
    +
  1. +

    Create src/calc_ui_cpp.rep:

    +
    class CalcUiCpp
    {
    SLOT(int add(int a, int b))
    SLOT(int multiply(int a, int b))
    SLOT(int factorial(int n))
    SLOT(int fibonacci(int n))
    SLOT(QString libVersion())
    }
    +

    repc generates two headers from this file:

    +
      +
    • rep_calc_ui_cpp_source.hCalcUiCppSimpleSource with virtual slots the backend overrides.
    • +
    • rep_calc_ui_cpp_replica.hCalcUiCppReplica with typed methods the QML view calls.
    • +
    +
  2. +
+

Step 4: Write the interface header

+

Create src/calc_ui_cpp_interface.h:

+
#ifndef CALC_UI_CPP_INTERFACE_H
#define CALC_UI_CPP_INTERFACE_H

#include <QObject>
#include <QString>
#include "interface.h"

class CalcUiCppInterface : public PluginInterface
{
public:
virtual ~CalcUiCppInterface() = default;
};

#define CalcUiCppInterface_iid "org.logos.CalcUiCppInterface"
Q_DECLARE_INTERFACE(CalcUiCppInterface, CalcUiCppInterface_iid)

#endif // CALC_UI_CPP_INTERFACE_H
+

Step 5: Configure the CMake build

+

Create CMakeLists.txt:

+
cmake_minimum_required(VERSION 3.14)
project(CalcUiCppPlugin LANGUAGES CXX)

if(DEFINED ENV{LOGOS_MODULE_BUILDER_ROOT})
include($ENV{LOGOS_MODULE_BUILDER_ROOT}/cmake/LogosModule.cmake)
else()
message(FATAL_ERROR "LogosModule.cmake not found. Set LOGOS_MODULE_BUILDER_ROOT.")
endif()

logos_module(
NAME calc_ui_cpp
REP_FILE src/calc_ui_cpp.rep
SOURCES
src/calc_ui_cpp_interface.h
src/calc_ui_cpp_plugin.h
src/calc_ui_cpp_plugin.cpp
)
+

REP_FILE tells logos_module() to run repc to generate source/replica headers, generate CalcUiCppViewPluginBase, and build a separate calc_ui_cpp_replica_factory shared library.

+

Step 6: Write the C++ backend plugin

+

The backend plugin inherits three base classes:

+
    +
  • CalcUiCppSimpleSource — generated from .rep, provides the typed source for Qt Remote Objects.
  • +
  • CalcUiCppInterface — standard Logos plugin interface (name(), version()).
  • +
  • CalcUiCppViewPluginBase — generated, provides setBackend() and enableRemoting().
  • +
+
    +
  1. +

    Create src/calc_ui_cpp_plugin.h:

    +
    #ifndef CALC_UI_CPP_PLUGIN_H
    #define CALC_UI_CPP_PLUGIN_H

    #include <QString>
    #include <QVariantList>
    #include "calc_ui_cpp_interface.h"
    #include "LogosViewPluginBase.h"
    #include "rep_calc_ui_cpp_source.h"

    class LogosAPI;
    class LogosModules;

    class CalcUiCppPlugin : public CalcUiCppSimpleSource,
    public CalcUiCppInterface,
    public CalcUiCppViewPluginBase
    {
    Q_OBJECT
    Q_PLUGIN_METADATA(IID CalcUiCppInterface_iid FILE "metadata.json")
    Q_INTERFACES(CalcUiCppInterface)

    public:
    explicit CalcUiCppPlugin(QObject* parent = nullptr);
    ~CalcUiCppPlugin() override;

    QString name() const override { return "calc_ui_cpp"; }
    QString version() const override { return "1.0.0"; }

    Q_INVOKABLE void initLogos(LogosAPI* api);

    // Slots from calc_ui_cpp.rep — return values directly. The QML replica
    // receives QRemoteObjectPendingReply; use logos.watch() in QML to get the value.
    int add(int a, int b) override;
    int multiply(int a, int b) override;
    int factorial(int n) override;
    int fibonacci(int n) override;
    QString libVersion() override;

    signals:
    void eventResponse(const QString& eventName, const QVariantList& args);

    private:
    LogosAPI* m_logosAPI = nullptr;
    LogosModules* m_logos = nullptr;
    };

    #endif // CALC_UI_CPP_PLUGIN_H
    +
    info

    If the interface filename or IID symbol here doesn't match the names in src/calc_ui_cpp_interface.h, you will get build errors or plugin-load failures at runtime.

    +
  2. +
  3. +

    Create src/calc_ui_cpp_plugin.cpp:

    +
    #include "calc_ui_cpp_plugin.h"
    #include "logos_api.h"
    #include "logos_sdk.h"

    CalcUiCppPlugin::CalcUiCppPlugin(QObject* parent) : CalcUiCppSimpleSource(parent) {}
    CalcUiCppPlugin::~CalcUiCppPlugin() { delete m_logos; }

    void CalcUiCppPlugin::initLogos(LogosAPI* api)
    {
    if (m_logos) return;
    m_logosAPI = api;
    m_logos = new LogosModules(api);

    // Register this object as the Remote Objects source so the QML replica
    // can see its properties and call its slots.
    setBackend(this);
    }

    int CalcUiCppPlugin::add(int a, int b) { return m_logos->calc_module.add(a, b); }
    int CalcUiCppPlugin::multiply(int a, int b) { return m_logos->calc_module.multiply(a, b); }
    int CalcUiCppPlugin::factorial(int n) { return m_logos->calc_module.factorial(n); }
    int CalcUiCppPlugin::fibonacci(int n) { return m_logos->calc_module.fibonacci(n); }

    QString CalcUiCppPlugin::libVersion()
    {
    return m_logos->calc_module.libVersion();
    }
    +
  4. +
+

Step 7: Write the QML view

+
    +
  1. +

    Create src/qml/Main.qml:

    +
    import QtQuick
    import QtQuick.Controls
    import QtQuick.Layouts

    Item {
    id: root

    property string result: ""
    property string errorText: ""

    // Typed replica of the backend running in ui-host (generated from calc_ui_cpp.rep).
    readonly property var backend: logos.module("calc_ui_cpp")

    // The ui-host backend connects asynchronously, so the replica isn't
    // immediately usable. Track readiness reactively: isViewModuleReady()
    // is a Q_INVOKABLE (not a property), so we re-check it on the
    // onViewModuleReadyChanged signal and once at startup — never via a
    // plain property binding, which would not re-evaluate.
    property bool ready: false

    Connections {
    target: logos
    function onViewModuleReadyChanged(moduleName, isReady) {
    if (moduleName === "calc_ui_cpp")
    root.ready = isReady && root.backend !== null
    }
    }
    Component.onCompleted: {
    root.ready = root.backend !== null && logos.isViewModuleReady("calc_ui_cpp")
    }

    // logos.watch() delivers the result of a replica slot call via callbacks.
    function callCalc(method, args) {
    if (!root.ready) {
    root.errorText = "Backend not ready"
    return
    }
    root.errorText = ""
    root.result = "..."
    logos.watch(backend[method].apply(backend, args),
    function(value) { root.result = String(value) },
    function(error) { root.errorText = String(error) }
    )
    }

    ColumnLayout {
    anchors.fill: parent
    anchors.margins: 24
    spacing: 16

    Text {
    text: "Logos Calculator (C++ backend)"
    font.pixelSize: 20
    color: "#ffffff"
    Layout.alignment: Qt.AlignHCenter
    }

    // Reactive backend-connection indicator.
    Text {
    text: root.ready ? "Connected" : "Connecting to backend..."
    color: root.ready ? "#56d364" : "#f0883e"
    font.pixelSize: 12
    Layout.alignment: Qt.AlignHCenter
    }

    RowLayout {
    spacing: 12
    Layout.fillWidth: true

    TextField { id: inputA; placeholderText: "a"; Layout.preferredWidth: 80; validator: IntValidator {} }
    TextField { id: inputB; placeholderText: "b"; Layout.preferredWidth: 80; validator: IntValidator {} }

    Button {
    text: "Add"; enabled: root.ready
    onClicked: root.callCalc("add", [parseInt(inputA.text) || 0, parseInt(inputB.text) || 0])
    }
    Button {
    text: "Multiply"; enabled: root.ready
    onClicked: root.callCalc("multiply", [parseInt(inputA.text) || 0, parseInt(inputB.text) || 0])
    }
    }

    RowLayout {
    spacing: 12
    Layout.fillWidth: true

    TextField { id: inputN; placeholderText: "n"; Layout.preferredWidth: 80; validator: IntValidator { bottom: 0 } }

    Button { text: "Factorial"; enabled: root.ready; onClicked: root.callCalc("factorial", [parseInt(inputN.text) || 0]) }
    Button { text: "Fibonacci"; enabled: root.ready; onClicked: root.callCalc("fibonacci", [parseInt(inputN.text) || 0]) }
    Button { text: "libcalc version"; enabled: root.ready; onClicked: root.callCalc("libVersion", []) }
    }

    Rectangle {
    Layout.fillWidth: true
    height: 56
    color: root.errorText.length > 0 ? "#3d1a1a" : "#1a2d1a"
    radius: 8

    Text {
    anchors.centerIn: parent
    text: root.errorText.length > 0 ? root.errorText
    : (root.result.length > 0 ? root.result : "Enter values and press a button")
    color: root.errorText.length > 0 ? "#f85149" : "#56d364"
    font.pixelSize: 15
    }
    }

    Item { Layout.fillHeight: true }
    }
    }
    +

    Key patterns:

    +
      +
    • logos.module("calc_ui_cpp") — gets the typed replica, with auto-synced properties.
    • +
    • logos.watch(backend.add(1, 2), ...) — delivers a SLOT return value as a JS Promise.
    • +
    • The logos object is injected by the host at runtime — no QtRemoteObjects import is needed.
    • +
    +
  2. +
+

Step 7.5: Use the Logos Design System in your QML (Optional)

+

The QML view runs inside the logos-standalone-app host app, which already has logos-design-system on its import path. Use its themed components directly to automatically give your module a polished, consistent look.

+
    +
  1. +

    In src/qml/Main.qml, add the necessary imports and replace raw Button and TextField elements with design system equivalents:

    +
    import Logos.Theme
    import Logos.Controls
    import Logos.Icons // optional shared icon assets

    ...

    // Instead of Button:
    LogosButton {
    text: qsTr("Add")
    onClicked: root.callCalc("add", [parseInt(inputA.text) || 0,
    parseInt(inputB.text) || 0])
    }

    ...

    // Instead of TextField:
    LogosTextField {
    id: inputA
    placeholderText: qsTr("a")
    }

    ...

    // Use theme colors instead of hardcoded hex values:
    Rectangle {
    color: Theme.palette.backgroundSecondary
    radius: Theme.spacing.radiusSmall
    LogosText { text: qsTr("Result"); color: Theme.palette.text }
    }
    +
  2. +
  3. +

    Explore available components by running the design system storybook in the logos-design-system repo:

    +
    git clone https://github.com/logos-co/logos-design-system.git
    cd logos-design-system && nix run
    +

    The sidebar splits components into:

    +
      +
    • Controls — designed per Figma, production-ready (LogosButton, LogosBadge, LogosCheckbox, LogosComboBox, LogosIconButton, LogosPaginator, LogosSearchBar, LogosTabBar, LogosTable, LogosText, LogosTextField, LogosToolTip, …).
    • +
    • Controls (not designed) — placeholders with stable APIs but unstyled visuals (LogosDialog, LogosDrawer, LogosScrollView, LogosSpinner, LogosTextArea, LogosSwitch, …). You can ship with them; they'll get the polished look applied later without you having to change your QML.
    • +
    +

    Theme tokens (use these instead of hex literals or manual font sizes):

    +
      +
    • Theme.palette.-background, backgroundSecondary, surface, text, textSecondary, border, primary, success, warning, error, info, hover, pressed, …
    • +
    • Theme.spacing.*tiny, small, medium, large, xlarge, xxlarge, radiusSmall, radiusMedium, radiusLarge
    • +
    • Theme.typography.*pageTitleText (36), titleText (30), panelTitleText (24), subtitleText (16), primaryText (14), secondaryText (12); weightRegular / weightMedium / weightBold; publicSans
    • +
    • Logos.Icons.LogosIcons.*arrowLeft, arrowRight, refresh, install, trash, more, search, …
    • +
    +
  4. +
+

Step 8: Configure the Nix flake

+

The template already wires everything up. Update the description and point calc_module at your dependency.

+
    +
  1. +

    Replace flake.nix with the following:

    +
    {
    description = "Calculator C++ UI plugin for Logos - QML view with process-isolated backend for calc_module";

    inputs = {
    logos-module-builder.url = "github:logos-co/logos-module-builder/0.2.0";

    # Keep this placeholder as is - it gets locked to your real path in the next step.
    calc_module.url = "path:/path/to/your/calc_module";
    };

    outputs = inputs@{ logos-module-builder, calc_module, ... }:
    logos-module-builder.lib.mkLogosQmlModule {
    src = ./.;
    configFile = ./metadata.json;
    flakeInputs = inputs;
    };
    }
    +

    To point at a published repo instead of a local path, change the calc_module.url input to a github: URL, for example calc_module.url = "github:<your-org>/<your-calc-module>";.

    +
  2. +
+

Step 9: Build and run the module

+

Before building, confirm the calc_module shared library is present from Part 1.

+
    +
  1. +

    Confirm the shared library exists:

    +
    # Linux
    ls ../logos-calc-module/lib/libcalc.so

    # macOS
    ls ../logos-calc-module/lib/libcalc.dylib
    +

    If the file is missing, build it first:

    +
    cd ../logos-calc-module/lib
    gcc -shared -fPIC -o libcalc.so libcalc.c # Linux
    # gcc -shared -fPIC -o libcalc.dylib libcalc.c # macOS
    cd ../../logos-calc-ui-cpp
    +
  2. +
  3. +

    Stage all files, then lock calc_module to your local Part 1 checkout. The --override-input flag resolves ../logos-calc-module to an absolute path and records it in flake.lock:

    +
    git add -A
    nix flake update --override-input calc_module path:../logos-calc-module
    git add flake.lock
    +
  4. +
  5. +

    Build and run the app. After the lock is in place, no override flag is needed on subsequent commands:

    +
    nix run
    +
  6. +
  7. +

    Confirm the view loads with all controls visible, then click Add with values in the input fields to test it out:

    +

    Operation buttons visible

    +

    Result of 3 + 5 shows 8

    +
  8. +
+

Step 10: Update view with live reloading (Optional)

+

To enable live updates to the UI, set DEV_QML_PATH to the directory that contains your view entry's basename. This tutorial sets "view": "qml/Main.qml" in metadata.json, so the directory must contain Main.qml (here: src/qml/).

+
    +
  1. +

    Run with DEV_QML_PATH set so that QML is loaded from your source tree at runtime:

    +
    DEV_QML_PATH=$PWD/src/qml nix run .
    +
  2. +
+

Step 11: Add UI integration tests (Optional)

+

Add automated UI tests using the logos-qt-mcp test framework.

+
    +
  1. +

    Create tests/ui-tests.mjs:

    +
    import { resolve } from "node:path";

    // CI sets LOGOS_QT_MCP automatically; for interactive use: nix build .#test-framework -o result-mcp
    const root =
    process.env.LOGOS_QT_MCP ||
    new URL("../result-mcp", import.meta.url).pathname;
    const { test, run } = await import(
    resolve(root, "test-framework/framework.mjs")
    );

    test("calc_ui_cpp: loads and shows title", async (app) => {
    await app.waitFor(
    async () => {
    await app.expectTexts(["Logos Calculator (C++ backend)"]);
    },
    { timeout: 15000, interval: 500, description: "UI to load" },
    );
    });

    test("calc_ui_cpp: operation buttons visible", async (app) => {
    await app.expectTexts(["Add", "Multiply", "Factorial", "Fibonacci"]);
    });

    run();
    +
  2. +
  3. +

    Stage the test file and run the hermetic CI test:

    +
    git add tests/
    nix build .#integration-test -L
    +

    The integration-test output launches logos-standalone-app with QT_QPA_PLATFORM=offscreen (no display needed), connects to the QML inspector, and runs all .mjs files in tests/.

    +
  4. +
  5. +

    To run tests interactively against an already-running app, build the test framework and run the app and tests in separate terminals:

    +
    nix build .#test-framework -o result-mcp
    nix run . # terminal 1 — app with inspector on :3768
    node tests/ui-tests.mjs # terminal 2
    +
  6. +
+

Troubleshooting the C++ UI module build

+

The plugin fails to load at runtime with no clear error

+

Confirm the Q_PLUGIN_METADATA IID and Q_DECLARE_INTERFACE macro in calc_ui_cpp_interface.h both use CalcUiCppInterface_iid. A mismatch causes silent load failures.

+

Linker errors during nix build

+

Confirm ../logos-calc-module/lib/libcalc.so for Linux (or .dylib on macOS) exists and that flake.lock was updated with --override-input calc_module path:../logos-calc-module. A stale or placeholder lock file is the most common cause.

+

DEV_QML_PATH does not seem to take effect

+

Confirm the path points at the directory containing Main.qml directly — not a parent directory. The host looks for the basename from "view" in metadata.json inside the directory you provide.

\ No newline at end of file diff --git a/core/build-modules/build-and-run-a-logos-core-module/index.html b/core/build-modules/build-and-run-a-logos-core-module/index.html new file mode 100644 index 00000000..74d4c8d1 --- /dev/null +++ b/core/build-modules/build-and-run-a-logos-core-module/index.html @@ -0,0 +1,326 @@ +Build and run a Logos core module | Logos documentation + + +

Build and run a Logos core module

+

Scaffold, build, package, and test a core module on Logos.

+

Logos is a modular application framework built on Qt 6. Applications are composed of dynamically loaded modules (Qt plugins) that provide features and functionality.

+

Logos core modules are non-UI modules that provide backend functionality. Core modules run in isolated logos_host processes and communicate via Qt Remote Objects.

+
info

For other module types, check out Wrap a C Library as a Logos core module and Build a Logos C++ UI module. These guides — along with the LGX package format and bundling reference and the Logos CLI Reference — are still being written; the linked pages are placeholders for now.

+

Before you start, make sure you have the following:

+
    +
  • +

    Linux (x86_64 or aarch64) or macOS (arm64 or x86_64)

    +
  • +
  • +

    At least 10 GB of disk space

    +
  • +
  • +

    Nix with flakes enabled

    +
  • +
  • +

    Git

    +
  • +
  • +

    logoscore, and lgpm installed. To install these tools, use the install-node-tools.sh helper script:

    +
    curl -fsSL https://raw.githubusercontent.com/logos-co/logos-docs/main/resources/scripts/install-node-tools.sh | sh
    export PATH="$PWD/bin:$PATH"
    +
  • +
  • +

    Basic familiarity with C++ (C++17), Qt 6 (QObject, Q_INVOKABLE, signals/slots), CMake, and Nix concepts

    +
  • +
+

What to expect

+
    +
  • You can scaffold, configure, and build a Logos core module using the templates provided by logos-module-builder.
  • +
  • You can inspect the compiled module's metadata and methods using the lm CLI tool or the logos-module-viewer graphical tool.
  • +
  • You can package, install, and call your module's methods through logoscore or load it into logos-basecamp.
  • +
+

Step 1: Scaffold the module project

+

The logos-module-builder provides four scaffolding templates for different module types. To create a Logos core module, choose the default template that produces a minimal core module with a C++ backend and no UI.

+
    +
  1. +

    Create a new directory using your module's name and initialise it from the module builder template. Replace <module-name> with your module's name, for example, my_module.

    +
    mkdir <module-name> && cd <module-name>
    nix flake init -t github:logos-co/logos-module-builder/tutorial-v1
    +
  2. +
  3. +

    Review the project directory. The generated project structure looks like this:

    +
    <module-name>/
    ├── flake.nix
    ├── metadata.json
    ├── CMakeLists.txt
    └── src/
    ├── minimal_interface.h
    ├── minimal_plugin.h
    └── minimal_plugin.cpp
    +

    The template uses minimal as a placeholder in the source filenames, class names, and identifiers. You replace these placeholders with your module's name in Step 2.

    +
    info
    +
  4. +
+

The metadata.json file is the single source of truth for your module. Read LGX package format and bundling reference for more details. +:::

+

Step 2: Adapt the template for your module

+

The template generates files with placeholder names like minimal/Minimal and doSomething. Replace these in every generated file to match your module's name and methods.

+
    +
  1. +

    Edit metadata.json and set name, version, description, and main to match your module.

    +
      +
    • name must be a valid C identifier; it is used in filenames, method calls, and module loading.
    • +
    • main must match the plugin filename without the extension (for example, my_module_plugin resolves to my_module_plugin.so or .dylib).
    • +
    • Leave the other fields (type, category, dependencies, and the nix block with packages, external_libraries, cmake.find_packages) in place. The bundler relies on the nix block to build the LGX package.
    • +
    +
  2. +
  3. +

    Edit CMakeLists.txt and update the project() name and the NAME and SOURCES values inside the logos_module() call.

    +
      +
    • The CMakeLists.txt uses the logos_module() macro to handle Qt plugin setup.
    • +
    • NAME must match the name field in metadata.json. A mismatch causes the build to succeed but the install phase to fail.
    • +
    +
  4. +
  5. +

    Edit flake.nix and update the description field.

    +
      +
    • The generated flake.nix uses an unpinned logos-module-builder URL. For reproducible builds, pin it to tutorial-v1.
    • +
    +
  6. +
  7. +

    Rename the source files in src/ to match your module name.

    +
    mv src/minimal_interface.h src/<module-name>_interface.h
    mv src/minimal_plugin.h src/<module-name>_plugin.h
    mv src/minimal_plugin.cpp src/<module-name>_plugin.cpp
    +
  8. +
  9. +

    Edit the interface header (src/<module-name>_interface.h) and replace the class name, include guard, and interface ID string.

    +
      +
    • Declare each method your module exposes as Q_INVOKABLE virtual and pure-virtual.
    • +
    • The interface ID (for example, "org.logos.MyModuleInterface") must be unique across all modules.
    • +
    +
  10. +
  11. +

    Edit the plugin header (src/<module-name>_plugin.h) and replace the class name, include guard, and interface references.

    +
      +
    • Q_PLUGIN_METADATA(IID ... FILE "metadata.json") embeds the metadata into the binary.
    • +
    • Q_INTERFACES must list both your interface and PluginInterface.
    • +
    • name() must return the same string as the name field in metadata.json.
    • +
    • Declare initLogos(LogosAPI* api) as Q_INVOKABLE but not override.
    • +
    +
  12. +
  13. +

    Edit the plugin implementation (src/<module-name>_plugin.cpp) and replace the placeholder method bodies with your logic.

    +
      +
    • In initLogos, assign the LogosAPI* pointer to the global logosAPI variable, not to a class member.
    • +
    +
  14. +
+
tip

Run grep -ri "minimal" . after editing to catch any remaining placeholder references (minimal, Minimal, MINIMAL_*, MinimalInterface_iid) before building.

+

Step 3: Build the module

+
    +
  1. +

    Initialise a Git repository.

    +
    git init && git add -A
    +
  2. +
  3. +

    Build the full module output (library and generated SDK headers).

    +
    nix build
    +
      +
    • Use nix build '.#lib' to build only the plugin shared library.
    • +
    • Use nix build '.#include' to build only the generated SDK headers.
    • +
    +
  4. +
  5. +

    Verify the build output contains the plugin binary and generated headers:

    +
    result/
    ├── lib/
    │ └── <module-name>_plugin.so # (or .dylib on macOS)
    └── include/
    ├── <module-name>_api.h # Generated type-safe wrapper header
    └── <module-name>_api.cpp # Generated wrapper implementation
    +
  6. +
+

Step 4: Inspect your module

+

You can inspect the compiled module binary to verify the embedded metadata and exposed methods using the lm CLI tool or the logos-module-viewer graphical tool.

+

Inspect with the CLI tool

+

The lm tool (from logos-module) lets you inspect compiled module binaries without loading them into the full runtime. It reads metadata and enumerates methods via Qt's meta-object system.

+
    +
  1. +

    Build the lm tool from the logos-module repository.

    +
    nix build 'github:logos-co/logos-module/tutorial-v1#lm' --out-link ./lm
    +
  2. +
  3. +

    View the module metadata and confirm the information is correct.

    +
    ./lm/bin/lm metadata result/lib/<module-name>_plugin.so
    +
      +
    • Append --json for JSON output.
    • +
    +

    The JSON output looks like this:

    +
    {
    "name": "my_module",
    "version": "1.0.0",
    "description": "My first Logos module",
    "author": "",
    "type": "core",
    "dependencies": []
    }
    +
  4. +
  5. +

    View the module methods.

    +
    ./lm/bin/lm methods result/lib/<module-name>_plugin.so
    +
      +
    • Append --json for JSON output.
    • +
    +

    The JSON output looks like this:

    +
    [
    {
    "name": "initLogos",
    "signature": "initLogos(LogosAPI*)",
    "returnType": "void",
    "isInvokable": true,
    "parameters": [
    { "name": "logosAPIInstance", "type": "LogosAPI*" }
    ]
    },
    {
    "name": "doSomething",
    "signature": "doSomething(QString)",
    "returnType": "QString",
    "isInvokable": true,
    "parameters": [
    { "name": "input", "type": "QString" }
    ]
    }
    ]
    +

    The output also includes any Qt signals declared in the module (for example, eventResponse) with isInvokable: false.

    +
  6. +
+

Inspect with the graphical tool

+

The logos-module-viewer is a graphical tool for inspecting loaded modules. It displays the module's metadata and methods in a graphical interface and lets you call methods interactively.

+
    +
  1. +

    Build the viewer.

    +
    nix build 'github:logos-co/logos-module-viewer/tutorial-v1#app' --out-link ./logos-viewer
    +
  2. +
  3. +

    Launch the viewer with the module binary.

    +
    ./logos-viewer/bin/logos-module-viewer -m ./result/lib/<module-name>_plugin.so
    +
  4. +
+

Step 5: Package the module

+

Before you can run your module with logoscore or install it into logos-basecamp, you need to package the build output into an .lgx package and install it into a modules/ directory. Check out the LGX package format and bundling reference for more details on the format and bundling options.

+
info

The manifest.json is auto-generated from your module's metadata.json by the bundler. It maps each variant to its main entry point.

+

There are two ways to create .lgx packages:

+
    +
  • Use the built-in Nix derivation that comes with logos-module-builder (preferred).
  • +
  • Use the nix bundle command directly.
  • +
+

Use the Nix derivation

+

When your module uses logos-module-builder, LGX package outputs are automatically available as part of your flake (the builder includes nix-bundle-lgx internally).

+
    +
  1. +

    Bundle the module into an LGX package that uses /nix/store references for local development.

    +
    nix build .#lgx
    +
      +
    • Use #lgx-portable for a self-contained, all dependencies bundled package: nix build .#lgx-portable.
    • +
    +
  2. +
  3. +

    Check the result/ directory and confirm the logos-<module-name>-module-lib.lgx file is present.

    +
    info
    +
  4. +
+

.#lgx produces a single variant (for example, linux-amd64) and .#lgx-portable produces a single portable variant. Neither produces the -dev variant that logos-basecamp dev builds expect. If you need the dev variant for use with logos-basecamp, use the #dual bundler described in the next section. +:::

+

Use the nix bundle command

+

The nix bundle command is useful if your module does not use logos-module-builder, or if you need the dual bundling mode (both dev and portable in a single .lgx file) which is only available via the nix bundle command.

+
    +
  1. +

    Bundle the module into an LGX package using the nix bundle command.

    +
    nix bundle --bundler github:logos-co/nix-bundle-lgx/tutorial-v3 .#lib
    +
      +
    • Use #portable for a self-contained package with no /nix/store references: nix bundle --bundler github:logos-co/nix-bundle-lgx/tutorial-v3#portable .#lib.
    • +
    • Use #dual to produce both -dev and portable variants in a single .lgx file: nix bundle --bundler github:logos-co/nix-bundle-lgx/tutorial-v3#dual .#lib. Use this mode when you need to install the module into a dev build of logos-basecamp.
    • +
    +
  2. +
  3. +

    Check the current directory for the bundle output. nix bundle creates a symlink directory in the current directory named ./logos-<module-name>-module-lib-lgx-<version>/, and the .lgx file is inside it at ./logos-<module-name>-module-lib-lgx-<version>/logos-<module-name>-module-lib.lgx.

    +
  4. +
+
tip

Check out LGX package format and bundling reference for more details on the format and bundling options.

+

Step 6: Install the module

+

Before you can run your module with logoscore or logos-basecamp, install the LGX package into a modules/ directory that the runtime can load from.

+

There are two ways to install .lgx packages:

+
    +
  • Install a locally built .lgx package
  • +
  • Download and install a .lgx file from a registry
  • +
+

Install a locally built .lgx package

+
    +
  1. +

    Create the modules/ directory and install the .lgx package.

    +
    lgpm --modules-dir ./modules install --file result/logos-<module-name>-module-lib.lgx
    +
      +
    • Use --dir instead of --file to install all LGX packages in a directory at once: ./package-manager/bin/lgpm --modules-dir ./modules install --dir ./packages/
    • +
    • If you bundled with nix bundle, the path is ./logos-<module-name>-module-lib-lgx-<version>/logos-<module-name>-module-lib.lgx instead of result/....
    • +
    +
  2. +
  3. +

    Verify the installed module directory. The directory contains manifest.json, the plugin binary (.so or .dylib), and a variant file.

    +
  4. +
+

Download and install a .lgx file from a registry

+

The Logos module catalogue is hosted on GitHub Releases in the logos-modules repository. Use lgpd to search and download packages, then lgpm to install them locally.

+
warning

Registry packages currently ship portable variants only (for example, linux-amd64, darwin-arm64). They cannot be installed into a dev build of logos-basecamp, which expects -dev variants. To use a registry module with a dev build, you must build the module from source and bundle it with #dual. They install cleanly into logoscore and into portable builds of logos-basecamp.

+
    +
  1. +

    Search the catalogue for the module you want to install. Replace <registry-name> with the registry name of the module you want to find (for example, logos-chat-module).

    +
    lgpd search <registry-name>
    +
    tip
    +
  2. +
+

Use lgpd list to browse all available packages. +:::

+
    +
  1. +

    Download the LGX package to a local directory.

    +
    lgpd download <registry-name> -o ./packages/
    +
      +
    • Use --release <tag> to download from a specific release version. For example: ./downloader/bin/lgpd --release v2.0.0 download <registry-name> -o ./packages/
    • +
    • The downloaded file is named after the module's internal name field, not the registry name. For example, lgpd download logos-chat-module writes ./packages/chat_module.lgx.
    • +
    +
  2. +
  3. +

    Create the modules/ directory and install the downloaded package. Replace <downloaded-name> with the actual filename written by lgpd (for example, chat_module.lgx).

    +
    lgpm --modules-dir ./modules install --file ./packages/<downloaded-name>.lgx
    +
      +
    • Use --ui-plugins-dir instead of --modules-dir when installing UI modules.
    • +
    +
  4. +
+

Step 7: Run the module

+

There are two Logos runtimes, logoscore and logos-basecamp, that can load and run your module. However, to interact with your module directly through the logos-basecamp interface, you need to provide a UI module.

+

Run with logoscore

+

The logoscore CLI (from logos-liblogos) is a headless runtime that can load modules and invoke their methods from the command line. It runs as a daemon that stays alive to host modules.

+
    +
  1. +

    Start the logoscore daemon with the modules/ directory.

    +
    logoscore -D -m ./modules
    +
  2. +
  3. +

    From another terminal, load the module and call a method. Replace <method> and <args> with the method name and arguments you want to call.

    +
    logoscore load-module <module-name>
    logoscore call <module-name> <method> <args>
    +
  4. +
  5. +

    Stop the daemon when finished.

    +
    logoscore stop
    +
  6. +
+
tip

Check out Logos CLI Reference for more details on available commands and options.

+

Run with logos-basecamp

+

Logos Basecamp is a desktop shell that provides a graphical interface for managing and running modules. Core modules run as background services in logos-basecamp. Other UI modules can call them through LogosAPI or the logos.callModule() bridge once they are installed.

+
warning

The LGX variant type must match the basecamp build type. Dev builds of basecamp expect dev LGX variants (for example, darwin-arm64-dev), and portable builds expect portable variants (for example, darwin-arm64). Check out the LGX package format and bundling reference for more details.

+
    +
  1. +

    Build the development version of logos-basecamp.

    +
    nix build 'github:logos-co/logos-basecamp/tutorial-v1#app' --out-link ./logos-basecamp
    +
  2. +
  3. +

    Launch logos-basecamp once to create its data directory and preinstall bundled modules, then close it.

    +
    ./logos-basecamp/bin/logos-basecamp
    +
      +
    • Look for the directory containing modules/ and plugins/ subdirectories at ~/Library/Application Support/Logos/LogosBasecamp/ (macOS) or ~/.config/Logos/LogosBasecamp/ (Linux).
    • +
    +
  4. +
  5. +

    Set the BASECAMP_DIR variable to your platform's path.

    +
    # macOS
    BASECAMP_DIR="$HOME/Library/Application Support/Logos/LogosBasecamp"

    # Linux
    BASECAMP_DIR="$HOME/.config/Logos/LogosBasecamp"
    +
  6. +
  7. +

    Install the module's dev LGX package into basecamp's modules directory. The package must contain a -dev variant for your platform; build it with nix bundle --bundler github:logos-co/nix-bundle-lgx/tutorial-v3#dual .#lib as described in Step 5.

    +
    lgpm --modules-dir "$BASECAMP_DIR/modules" install --file ./logos-<module-name>-module-lib-lgx-<version>/logos-<module-name>-module-lib.lgx
    +
  8. +
+

Troubleshooting

+

Known constraints

+

A single logoscore instance supports only one instance of a module, so all dependent apps share that state. To run multiple independent instances, start separate logos-basecamp instances with their own user directories, and each logos-basecamp instance runs its own logoscore and module instances in isolation.

+

Nix reports an "experimental features" error

+

If you see errors about experimental features, either pass the flag:

+
nix --extra-experimental-features "nix-command flakes" build
+

Or add the following to ~/.config/nix/nix.conf:

+
experimental-features = nix-command flakes
+

Module not discovered by logos-basecamp

+

Confirm the module is in a subdirectory of the modules/ directory (for example, modules/my_module/) and that the subdirectory contains the module binary and manifest.json. The name field in manifest.json must match the binary name (for example, my_module for my_module_plugin.so).

+

Module not discovered by logoscore

+

Confirm the module is in a subdirectory of the modules/ directory (for example, modules/my_module/) and that the subdirectory contains a manifest.json with a main object matching your OS and architecture.

+

lgpm fails to install a module

+

Verify the target directory exists and is writable. If installing from a local .lgx file, confirm the file path is correct (the bundler writes logos-<module-name>-module-lib.lgx, not <module-name>.lgx). If installing from the registry, check your internet connection — lgpd fetches packages from GitHub Releases. To pin a specific release version, pass --release <tag> to lgpd (not lgpm) when downloading.

+

LGX variant mismatch

+

If lgpm install fails with Package does not contain variant for platform: <platform>-dev, the LGX file does not include a -dev variant for your platform.

+
    +
  • nix build .#lgx produces a single variant (for example, linux-amd64) suitable for logoscore but not for a dev build of logos-basecamp.
  • +
  • nix build .#lgx-portable produces a single portable variant suitable for portable builds of logos-basecamp.
  • +
  • nix bundle --bundler github:logos-co/nix-bundle-lgx/tutorial-v3#dual .#lib produces both -dev and portable variants in a single .lgx file, which works with dev and portable builds of logos-basecamp.
  • +
+

Registry packages downloaded with lgpd currently ship portable variants only.

+
info

lgpm error messages report the platform as linux-x86_64 while LGX manifests label it linux-amd64. These refer to the same architecture.

+

nix build .#lib does nothing or fails silently

+

Some shells (notably zsh) treats # as a comment character outside quotes. Quote the flake reference so the # reaches nix intact.

+
nix build '.#lib'
+

First build is slow

+

The first nix build downloads Qt 6, the Logos C++ SDK, the code generator, and the rest of the build dependencies. This is a one-time cost, subsequent builds reuse the cache and typically complete in under 30 seconds.

\ No newline at end of file diff --git a/core/build-modules/build-logos-core-module-that-uses-service-discovery-api/index.html b/core/build-modules/build-logos-core-module-that-uses-service-discovery-api/index.html new file mode 100644 index 00000000..8e2204f4 --- /dev/null +++ b/core/build-modules/build-logos-core-module-that-uses-service-discovery-api/index.html @@ -0,0 +1,177 @@ +Build a Logos Core module that uses the Service Discovery API | Logos documentation + + +

Build a Logos Core module that uses the Service Discovery API

+

Get started with typed, service-keyed peer lookups in a live Logos network.

+

Applications on the Logos network need a protocol-agnostic way to find peers offering specific services — mix nodes, relay nodes, storage providers — at runtime without hard-coding topology or peer lists. The Service Discovery API enables any Logos Core module to perform typed, service-keyed peer lookups that work from lightweight client nodes that do not participate in DHT routing, unblocking any app that needs to wire itself into a live Logos network service. This procedure covers how to write and run a Logos Core module that calls the libp2p_module Service Discovery API to advertise a named service to the network and discover other peers offering that same service.

+

Before you start, make sure you have the following:

+
    +
  • +

    Linux (Ubuntu 22.04+) or macOS 14+

    +
  • +
  • +

    Nix with flakes enabled. Install from nixos.org, then enable flakes:

    +
    mkdir -p ~/.config/nix
    echo 'experimental-features = nix-command flakes' >> ~/.config/nix/nix.conf
    +
  • +
  • +

    2 GB RAM (sufficient for a local two-module test)

    +
  • +
  • +

    logoscore installed. To install it, use the install-node-tools.sh helper script:

    +
    curl -fsSL https://raw.githubusercontent.com/logos-co/logos-docs/main/resources/scripts/install-node-tools.sh | sh
    export PATH="$PWD/bin:$PATH"
    +
  • +
+

What to expect

+
    +
  • You can advertise a named service from a Logos Core module and discover peers offering the same service via the Kad-DHT, without hard-coding peer addresses.
  • +
  • You can verify the full discovery flow locally across three logoscore daemon instances, each with its own config directory and listen port.
  • +
  • You have a reusable module scaffold with typed disco* wrappers that you can extend for production service types.
  • +
+

Step 1: Smoke-test the bundled example binary (Optional)

+

Build and run the self-contained two-node demo to confirm the module and its C bindings are working before writing your own module.

+
    +
  1. +

    Clone the repository and enter the project directory:

    +
    git clone https://github.com/logos-co/logos-libp2p-module
    cd logos-libp2p-module
    +
  2. +
  3. +

    Build the module:

    +
    nix build -L
    +
    info
    +
  4. +
+

The first-run Nix build can take 5–20 minutes to fetch dependencies; subsequent builds are cached. Estimated total time is 15–25 minutes. +:::

+
    +
  1. +

    Vendor the C-binding header and shared library that logos_module() expects in ./lib:

    +
    CBIND=$(find /nix/store -maxdepth 4 -name libp2p.h -path '*cbind*' | head -1 | xargs dirname)
    mkdir -p lib
    cp "$CBIND/libp2p.h" lib/
    find /nix/store -name libp2p.so -path '*cbind*' -exec cp {} lib/ \;
    +
  2. +
  3. +

    Build the example target from the root project and run it:

    +
    nix develop --command bash -c 'cmake -B build -S . && cmake --build build --target example_service_discovery -j'
    ./build/examples/example_service_discovery
    +

    Expected output:

    +
    Starting nodes...
    Advertiser: advertising demo-service
    Discoverer: registering interest in demo-service
    Discoverer: looking up demo-service
    Discoverer found 1 peer(s) advertising demo-service
    peer: 16Uiu2HAk... seq: 1359 addrs: 1
    Discoverer matched the advertiser: 16Uiu2HAk...
    Advertiser: random lookup
    Random lookup returned 2 peer(s)
    Advertiser: building a signed Extended Peer Record
    Signed XPR is 288 bytes
    Discoverer: unregistering interest in demo-service
    Advertiser: stopping advertising demo-service
    Done
    +
      +
    • Peer IDs are non-deterministic across runs.
    • +
    +
    info
    +
  4. +
+

The demo runs a bootstrap node plus an advertiser and a discoverer, so the discoverer finds the advertiser through the DHT — a successful run prints found 1 peer(s) and matched the advertiser. Exact peer counts, IDs, and the XPR byte size vary per run. +:::

+

Step 2: Scaffold the new Logos Core module

+

Run the scaffold tool from the parent directory to generate the module skeleton, then declare the libp2p_module dependency.

+
    +
  1. +

    From the parent directory, scaffold the module:

    +
    cd ..
    nix run github:logos-co/logos-dev-boost -- init my_service_module --type module
    cd logos-my-service-module
    +
      +
    • The scaffold tool prefixes the output directory with logos-, producing logos-my-service-module.
    • +
    +
  2. +
  3. +

    Replace the contents of metadata.json with the following to declare the libp2p_module dependency:

    +
    {
    "name": "my_service_module",
    "version": "1.0.0",
    "description": "Service discovery demo module",
    "type": "core",
    "interface": "universal",
    "main": "my_service_module_plugin",
    "dependencies": ["libp2p_module"]
    }
    +
  4. +
  5. +

    Replace the contents of src/my_service_module_impl.h with the following:

    +
    // my_service_module_impl.h — inherit LogosModuleContext:
    #pragma once
    #include <string>
    #include "logos_module_context.h"

    class MyServiceModuleImpl : public LogosModuleContext {
    public:
    std::string startDiscovery();
    std::string getPeerInfo();
    std::string advertise(const std::string& serviceId, const std::string& serviceData);
    std::string discover(const std::string& serviceId);
    std::string stopDiscovery();
    };
    +
  6. +
  7. +

    Replace the contents of src/my_service_module_impl.cpp with the following:

    +
    #include "my_service_module_impl.h"
    #include "logos_sdk.h" // generated; defines: struct LogosModules { Libp2pModule libp2p_module; };
    #include <thread>
    #include <chrono>

    std::string MyServiceModuleImpl::startDiscovery() {
    auto r = modules().libp2p_module.start();
    if (!r.success) return "start failed: " + r.error;
    auto r2 = modules().libp2p_module.discoStart();
    if (!r2.success) return "discoStart failed: " + r2.error;
    return "discovery started";
    }

    // Returns this node's own peer record (peerId + listen addrs). Use it on a
    // bootstrap node to obtain the peerId/addr that other instances bootstrap against.
    std::string MyServiceModuleImpl::getPeerInfo() {
    auto r = modules().libp2p_module.peerInfo();
    if (!r.success) return "peerInfo failed: " + r.error;
    return r.value.is_string() ? r.value.get<std::string>() : r.value.dump();
    }

    std::string MyServiceModuleImpl::advertise(const std::string& serviceId,
    const std::string& serviceData) {
    // discoStartAdvertising requires BOTH serviceId and serviceData.
    auto r = modules().libp2p_module.discoStartAdvertising(serviceId, serviceData);
    if (!r.success) return "advertise failed: " + r.error;
    return "advertising " + serviceId;
    }

    std::string MyServiceModuleImpl::discover(const std::string& serviceId) {
    auto r = modules().libp2p_module.discoRegisterInterest(serviceId);
    if (!r.success) return "registerInterest failed: " + r.error;
    std::this_thread::sleep_for(std::chrono::milliseconds(500));
    // "" serviceData = match any advertisement of this serviceId.
    auto r2 = modules().libp2p_module.discoLookup(serviceId, "");
    if (!r2.success) return "lookup failed: " + r2.error;
    return r2.value.is_string() ? r2.value.get<std::string>() : r2.value.dump();
    }

    std::string MyServiceModuleImpl::stopDiscovery() {
    auto r = modules().libp2p_module.discoStop();
    if (!r.success) return "discoStop failed: " + r.error;
    auto r2 = modules().libp2p_module.stop();
    if (!r2.success) return "stop failed: " + r2.error;
    return "discovery stopped";
    }
    +
  8. +
  9. +

    Add libp2p_module as a flake input in flake.nix:

    +
    inputs = {
    logos-module-builder.url = "github:logos-co/logos-module-builder";
    libp2p_module.url = "github:logos-co/logos-libp2p-module";
    };
    +
    info
    +
  10. +
+

For local development, overrideflake.nix at build time:

+
nix build --override-input libp2p_module path:../logos-libp2p-module
+

:::

+

Step 3: Build both modules

+

The .#install target runs lgpm internally and produces the directory structure logoscore requires. You only write metadata.json; the install target generates manifest.json, variant, and co-locates all .so files automatically.

+
    +
  1. +

    In logos-my-service-module, initialize a Git repository and run the install build:

    +
    git init && git add -A
    nix build .#install -L
    +

    This produces:

    +
    result/modules/my_service_module/
    ├── manifest.json
    ├── variant
    └── my_service_module_plugin.so
    +
  2. +
  3. +

    In logos-libp2p-module, run the install build:

    +
    cd ../logos-libp2p-module
    nix build .#install -L
    +

    This produces:

    +
    result/modules/libp2p_module/
    ├── manifest.json
    ├── variant
    ├── libp2p_module_plugin.so
    └── libp2p.so
    +
      +
    • libp2p.so is co-located automatically so the $ORIGIN RUNPATH resolves at runtime.
    • +
    +
  4. +
+

Step 4: Load the modules and verify the single-node flow

+
    +
  1. +

    Return to logos-my-service-module and pin a known, fixed listen address via the LIBP2P_MODULE_CONFIG environment variable:

    +
    cd ../logos-my-service-module
    export LIBP2P_MODULE_CONFIG='{"addrs":["/ip4/127.0.0.1/tcp/9000"]}'
    +
  2. +
  3. +

    Start the daemon in the background, pointing at both module directories:

    +
    logoscore -D \
    -m ../logos-libp2p-module/result/modules \
    -m ./result/modules &
    +
  4. +
  5. +

    Load both modules:

    +
    logoscore load-module libp2p_module
    logoscore load-module my_service_module
    +
  6. +
  7. +

    Drive the discovery lifecycle and verify each call returns immediately:

    +
    logoscore call my_service_module startDiscovery
    # → discovery started

    logoscore call my_service_module getPeerInfo
    # → {"peerId":"16Uiu2…","addrs":["/ip4/127.0.0.1/tcp/9000"]}

    logoscore call my_service_module advertise myservice/v1 version=1
    # → advertising myservice/v1

    logoscore call my_service_module discover myservice/v1
    # → [] (single node: no second advertiser)

    logoscore call my_service_module stopDiscovery
    # → discovery stopped
    +
      +
    • discover returning [] is expected with a single node. getPeerInfo prints this node's peerId and listen address, which you need in Step 5 to bootstrap other instances.
    • +
    +
  8. +
  9. +

    Shut down the daemon:

    +
    logoscore stop
    +
  10. +
+

Step 5: Run three-node local discovery

+

Run three logoscore daemon instances on one machine to see the Service Discovery API work: a bootstrap node, an advertiser, and a discoverer. The advertiser and discoverer are configured only with the bootstrap node as their bootstrap node — when the discoverer's lookup returns the advertiser's peer record, the advertisement provably travelled through the DHT, not over a direct A↔B link.

+

Each daemon needs its own --config-dir and LIBP2P_MODULE_CONFIG with a distinct listen port. Run each block in a separate terminal window.

+
    +
  1. +

    In Terminal 1, start the bootstrap node:

    +
    cd ../logos-my-service-module
    export LIBP2P_MODULE_CONFIG='{"addrs":["/ip4/127.0.0.1/tcp/9000"]}'
    logoscore -D --config-dir ~/.logoscore-bootstrap \
    -m ../logos-libp2p-module/result/modules -m ./result/modules &
    logoscore --config-dir ~/.logoscore-bootstrap load-module libp2p_module
    logoscore --config-dir ~/.logoscore-bootstrap load-module my_service_module
    logoscore --config-dir ~/.logoscore-bootstrap call my_service_module startDiscovery
    logoscore --config-dir ~/.logoscore-bootstrap call my_service_module getPeerInfo
    # → note the "peerId" value; the bootstrap node listens on /ip4/127.0.0.1/tcp/9000
    +
      +
    • Copy the bootstrap node's peerId from the getPeerInfo output. Substitute it for <BOOTSTRAP_PEER_ID> in the next two terminals.
    • +
    +
  2. +
  3. +

    In Terminal 2, start the advertiser:

    +
    cd ../logos-my-service-module
    export LIBP2P_MODULE_CONFIG='{"addrs":["/ip4/127.0.0.1/tcp/9001"],"bootstrapNodes":[{"peerId":"<BOOTSTRAP_PEER_ID>","addrs":["/ip4/127.0.0.1/tcp/9000"]}]}'
    logoscore -D --config-dir ~/.logoscore-advertiser \
    -m ../logos-libp2p-module/result/modules -m ./result/modules &
    logoscore --config-dir ~/.logoscore-advertiser load-module libp2p_module
    logoscore --config-dir ~/.logoscore-advertiser load-module my_service_module
    logoscore --config-dir ~/.logoscore-advertiser call my_service_module startDiscovery
    logoscore --config-dir ~/.logoscore-advertiser call my_service_module advertise myservice/v1 version=1
    +
  4. +
  5. +

    In Terminal 3, start the discoverer and look up the service:

    +
    cd ../logos-my-service-module
    export LIBP2P_MODULE_CONFIG='{"addrs":["/ip4/127.0.0.1/tcp/9002"],"bootstrapNodes":[{"peerId":"<BOOTSTRAP_PEER_ID>","addrs":["/ip4/127.0.0.1/tcp/9000"]}]}'
    logoscore -D --config-dir ~/.logoscore-discoverer \
    -m ../logos-libp2p-module/result/modules -m ./result/modules &
    logoscore --config-dir ~/.logoscore-discoverer load-module libp2p_module
    logoscore --config-dir ~/.logoscore-discoverer load-module my_service_module
    logoscore --config-dir ~/.logoscore-discoverer call my_service_module startDiscovery
    logoscore --config-dir ~/.logoscore-discoverer call my_service_module discover myservice/v1
    +

    Expected output:

    +
    {"method":"discover","module":"my_service_module",
    "result":"[{\"addrs\":[\"/ip4/127.0.0.1/tcp/9001\"],\"peerId\":\"<ADVERTISER_PEER_ID>\",\"seqNo\":1536,\"services\":[{\"data\":\"version=1\",\"id\":\"myservice/v1\"}]}]",
    "status":"ok"}
    +
      +
    • The returned peerId is the advertiser's, and addrs shows its listen port (9001), even though the discoverer only knew about the bootstrapping node. The services entry carries the serviceData (version=1) that A advertised.
    • +
    +
    info
    +
  6. +
+

A first discover returning [] means the advertisement has not yet propagated. Repeat the call after a few seconds. +:::

+
    +
  1. +

    Tear down all three daemons:

    +
    for D in bootstrap advertiser discoverer; do
    logoscore --config-dir ~/.logoscore-$D call my_service_module stopDiscovery
    logoscore --config-dir ~/.logoscore-$D stop
    done
    +
  2. +
+

Troubleshooting service discovery

+

Why does discover return [] even after waiting?

+

The Kad-DHT needs a few seconds to propagate the advertisement from the advertiser through the bootstrap node to the discoverer. Repeat logoscore --config-dir ~/.logoscore-discoverer call my_service_module discover myservice/v1 after 5–10 seconds. If it still returns empty, confirm that the advertiser's advertise call succeeded and that both the advertiser and the discoverer share the same <BOOTSTRAP_PEER_ID> for the bootstrap node.

+

Why does a logoscore call hang with a timeout error?

+

You may be using the one-shot -c "module.method()" or --quit-on-finish form instead of the logoscore call subcommand. The one-shot client does not await async lifecycle calls such as start(), which causes a spurious Timeout waiting for … before the node finishes starting up. Use logoscore call my_service_module <method> for all lifecycle and discovery calls.

\ No newline at end of file diff --git a/core/build-modules/install-and-load-a-module-in-logos-basecamp/index.html b/core/build-modules/install-and-load-a-module-in-logos-basecamp/index.html new file mode 100644 index 00000000..76c4ba2e --- /dev/null +++ b/core/build-modules/install-and-load-a-module-in-logos-basecamp/index.html @@ -0,0 +1,49 @@ +Install and load a module in Logos Basecamp | Logos documentation + + +

Install and load a module in Logos Basecamp

+

Access features and functionalities through modules in Logos Basecamp.

+

In Logos Basecamp, you can install and load modules that provide features like chat, storage, or wallets from the online catalogue or local .lgx files.

+

There are two types of modules in Logos Basecamp. Core modules are the headless background services that provide capabilities like messaging or storage, while UI modules are the visual front-ends users interact with.

+

Before you start, make sure you have:

+ +
info

The .lgx file's archive must contain a variant matching your platform. For example, linux-x86_64, linux-aarch64, darwin-x86_64, or darwin-arm64.

+

What to expect

+
    +
  • You can install a module from the online catalogue or from a local .lgx file.
  • +
  • You can load or unload a module from the Modules view.
  • +
  • You can read a loading module's status, CPU, and memory in the Modules view.
  • +
+

Step 1: Install the module package

+

When installing a module, Logos Basecamp extracts the variant for your platform into your user modules directory (core modules) or user plugins directory (UI modules).

+

Install from the online catalogue

+
    +
  1. In the sidebar, click Package Manager .
  2. +
  3. Browse and locate the module you want. You can click the module to view more details in the content area below.
  4. +
  5. Select the module to install and click Install.
  6. +
  7. At the bottom of the sidebar, click the Modules . The newly installed module appears under UI Modules or Core Modules depending on its type.
  8. +
+

Install from a local .lgx file

+
    +
  1. At the bottom of the sidebar, click Modules .
  2. +
  3. Click Install LGX Package.
  4. +
  5. Select the .lgx file and confirm.
  6. +
  7. The newly installed module appears under UI Modules or Core Modules depending on its type.
  8. +
+

Step 2: Load the module

+

Loading a module turns an installed module into a running service you can actually use. Each loaded Logos module runs in its own logos_host process, so memory usage increases with the number of loaded modules.

+
    +
  1. At the bottom of the sidebar, click Modules .
  2. +
  3. Find the module to load under UI Modules or Core Modules depending on its type.
  4. +
  5. Click Load next to the module.
  6. +
+
info

You can click Unload in the Modules view or close the tab of a module to unload it. Unloading stops the module's host process but not its dependencies, which may still be in use by other modules or UI Apps.

+

Troubleshooting

+

The installed module doesn't appear in the Modules view

+

The .lgx file probably does not contain a variant for your platform, or it was copied to a directory that Logos Basecamp doesn't scan. Confirm the archive includes a variant matching your platform (linux-x86_64, linux-aarch64, darwin-x86_64, or darwin-arm64), then reinstall using Install LGX Package in the Modules view rather than copying files manually so the package manager copies the files to the correct user modules directory.

+

A QML-based UI App cannot reach the network

+

By design, QML UI Apps run inside a sandboxed QML engine with a deny-all QNetworkAccessManager and a URL interceptor that whitelists only the app's own directory. To make network calls, route them through a Logos Module, which runs in its own unsandboxed process. Use logos.callModule("<module>", "<method>", [args]) from QML.

\ No newline at end of file diff --git a/core/build-modules/start-a-logos-module-from-the-cli/index.html b/core/build-modules/start-a-logos-module-from-the-cli/index.html new file mode 100644 index 00000000..d7cd2540 --- /dev/null +++ b/core/build-modules/start-a-logos-module-from-the-cli/index.html @@ -0,0 +1,92 @@ +Start a Logos module from the CLI | Logos documentation + + +

Start a Logos module from the CLI

+

Explore how to load and call a Logos module from the command line using logoscore.

+

This guide covers how to build and install a Logos module, start the logoscore daemon, and call module methods from the command line. It is intended for users who want to run an existing module, or developers who have already built a module binary and want to run it locally for testing or development. By the end you will have a running logoscore instance that loads accounts_module as an example module and returns results for mnemonic generation and relative strength.

+

Before you start, make sure you have the following:

+
    +
  • +

    Nix with flakes enabled. Install from nixos.org, then enable flakes:

    +
    mkdir -p ~/.config/nix
    echo 'experimental-features = nix-command flakes' >> ~/.config/nix/nix.conf
    +

    Verify: nix flake --help >/dev/null 2>&1 && echo "Flakes enabled"

    +
  • +
  • +

    Git

    +
  • +
  • +

    Linux or macOS

    +
  • +
  • +

    logoscore, lgpd, and lgpm installed. To install these tools, use the install-node-tools.sh helper script:

    +
    curl -fsSL https://raw.githubusercontent.com/logos-co/logos-docs/main/resources/scripts/install-node-tools.sh | sh
    export PATH="$PWD/bin:$PATH"
    +
  • +
+

What to expect

+
    +
  • You can load accounts_module into a running logoscore daemon and call its methods.
  • +
  • You can generate a BIP-39 mnemonic phrase and measure its entropy strength with no prior setup.
  • +
  • You have a working local modules directory that logoscore can scan for future modules.
  • +
+

Install the module

+

logoscore expects each module in its own subdirectory containing a manifest.json. The lgpm package manager handles this layout automatically when given an LGX package.

+
    +
  1. +

    Clone the logos-accounts-module repository and build the LGX package:

    +
    git clone https://github.com/logos-co/logos-accounts-module.git
    cd logos-accounts-module

    nix build '.#lgx'

    cd ..
    +
  2. +
  3. +

    Create the modules directory and copy the pre-loaded logos modules to it:

    +
    mkdir -p modules
    cp -RL ./logos/modules/. ./modules/
    +
  4. +
  5. +

    Install the LGX package into the modules directory:

    +
    lgpm --modules-dir ./modules install --file ./logos-accounts-module/result/*.lgx
    +

    After installation, the directory structure looks like this:

    +
    modules/accounts_module/
    ├── accounts_module_plugin.dylib # (or .so on Linux)
    ├── manifest.json # Auto-generated by lgx
    └── variant # Platform variant identifier
    +
  6. +
  7. +

    Confirm the module was installed correctly:

    +
    lgpm --modules-dir ./modules list
    +
  8. +
+

Call module methods

+

With the module installed, start the logoscore daemon, load the module, and call its methods.

+
    +
  1. +

    Start the daemon in the background, pointing it at the modules directory, and wait to initialise:

    +
    logoscore -D -m ./modules &
    sleep 3
    +
  2. +
  3. +

    Check the status of logoscore:

    +
    logoscore status
    +
  4. +
  5. +

    Load the module and confirm that it was loaded:

    +
    logoscore load-module accounts_module

    logoscore list-modules
    +
  6. +
  7. +

    Generate a random BIP-39 mnemonic phrase with 12 words:

    +
    logoscore call accounts_module createRandomMnemonic 12
    +
  8. +
  9. +

    Map a mnemonic word count to its entropy strength in bits — for example, 12 words is 128 bits.

    +
    logoscore call accounts_module lengthToEntropyStrength 12
    +
  10. +
  11. +

    Inspect all available methods in accounts_module with module_info:

    +
    logoscore module-info accounts_module
    +
  12. +
  13. +

    Stop the daemon:

    +
    logoscore stop
    sleep 2
    +

    For inline (legacy) mode and other logoscore options, see the Developer Guide — Running with logoscore.

    +
  14. +
+

Troubleshooting logoscore module startup

+

The modules directory is not found

+

Confirm the module subdirectory exists and contains a manifest.json file. Installing the package again with the correct --modules-dir path resolves this in most cases.

+

The platform key in manifest.json does not match

+

logoscore matches the main object in manifest.json against your OS and architecture (for example, linux-aarch64 or darwin-arm64). Rebuild the LGX package on the target platform and reinstall.

+

The daemon does not respond after sleep 3

+

Increase the sleep duration if your machine is slow to initialise, or check for port conflicts by inspecting the daemon's stderr output.

\ No newline at end of file diff --git a/core/build-modules/wrap-a-c-library-as-a-logos-core-module/index.html b/core/build-modules/wrap-a-c-library-as-a-logos-core-module/index.html new file mode 100644 index 00000000..c265875f --- /dev/null +++ b/core/build-modules/wrap-a-c-library-as-a-logos-core-module/index.html @@ -0,0 +1,249 @@ +Wrap a C library as a Logos module | Logos documentation + + +

Wrap a C library as a Logos module

+

Expose functions from a C shared library through a Logos core module.

+

This tutorial walks you through wrapping a C shared library (.so on Linux, .dylib on macOS) as a Logos module. By the end, you will have a calc_module that compiles, loads, and responds to method calls via logoscore. You write one plain C++ class — no Qt, no plugin boilerplate — and the build system generates the Qt plugin around it.

+

For an example used in production, refer to logos-lib2p2-module - a module that wraps the nim-libp2p library (compiled to a C shared library).

+

You need:

+
    +
  • +

    OS: Linux (x86_64 or aarch64) or macOS (x86_64 or aarch64). Tested on Ubuntu 22.04+ and recent macOS.

    +
  • +
  • +

    RAM: 4 GB minimum, 8 GB recommended.

    +
  • +
  • +

    Disk: ~2 GB free for the application + installed modules.

    +
  • +
  • +

    Nix with flakes enabled. Install from nixos.org, then enable flakes:

    +
    mkdir -p ~/.config/nix
    echo 'experimental-features = nix-command flakes' >> ~/.config/nix/nix.conf
    +

    Verify: nix flake --help >/dev/null 2>&1 && echo "Flakes enabled"

    +
  • +
  • +

    A C compiler (gcc or clang) for building the C library. Only needed if you are building the .so/.dylib yourself rather than using a pre-built library.

    +
  • +
  • +

    Basic familiarity with C and C++.

    +
  • +
+

What to expect

+
    +
  • You will write a calc_module that exposes arithmetic functions to Logos using the pure C++ (interface: universal) pattern.
  • +
  • You will build, inspect, and call the module with lm and logoscore, seeing your int64_t methods appear as Qt-typed signals.
  • +
  • You will unit-test the module directly against a link-time mock of the C library.
  • +
+

Step 1: Scaffold the module project

+

Before writing any C code, scaffold the Logos module project using the official template. This gives you the correct flake.nix, metadata.json, directory structure, and build configuration out of the box.

+
    +
  1. +

    Create the project directory and run the module builder template:

    +
    mkdir logos-calc-module && cd logos-calc-module

    # To wrap an external C library
    nix flake init -t github:logos-co/logos-module-builder/0.2.0#with-external-lib

    # Or for a plain module (no external library):
    # nix flake init -t github:logos-co/logos-module-builder/0.2.0
    +

    This generates skeleton files (flake.nix, metadata.json, CMakeLists.txt, and a src/ directory) pre-configured for the logos-module-builder. You then customise them for your specific library.

    +
    info
    +
  2. +
+

As the time of writing, nix flake init scaffolds a hand-written Qt plugin (*_interface.h + *_plugin.h + *_plugin.cpp). This tutorial uses the newer pure-C++ pattern instead: you write one plain *_impl.h / *_impl.cpp class with no Qt, set "interface": "universal" in metadata.json, and the build generates the Qt plugin wrapper for you. The steps below replace the template's src/ files entirely. The nix flake init command is still used to get the flake.nix / CMakeLists.txt skeleton and directory layout. +:::

+
    +
  1. +

    Remove the template's example sources. The with-external-lib template ships an example Qt plugin (external_lib_*). Delete those files — this tutorial supplies its own pure-C++ src/ files:

    +
    rm -f src/external_lib_interface.h src/external_lib_plugin.h src/external_lib_plugin.cpp
    +
  2. +
+

Step 2: Write the C library

+

Create the C library that your module will wrap. Place the header and implementation in the lib/ directory.

+
    +
  1. +

    Create the lib directory:

    +
    mkdir -p lib
    +
  2. +
  3. +

    Create lib/libcalc.h:

    +
    #ifndef LIBCALC_H
    #define LIBCALC_H

    #ifdef __cplusplus
    extern "C" {
    #endif

    /** Add two integers. */
    int calc_add(int a, int b);

    /** Multiply two integers. */
    int calc_multiply(int a, int b);

    /** Compute factorial of n (n must be >= 0). Returns -1 on error. */
    int calc_factorial(int n);

    /** Compute the nth Fibonacci number (n must be >= 0). Returns -1 on error. */
    int calc_fibonacci(int n);

    /** Return the library version string. Caller must NOT free. */
    const char* calc_version(void);

    #ifdef __cplusplus
    }
    #endif

    #endif /* LIBCALC_H */
    +

    The extern "C" block is essential — it prevents C++ name mangling so the Logos module can find the symbols.

    +
  4. +
  5. +

    Create lib/libcalc.c:

    +
    #include "libcalc.h"

    int calc_add(int a, int b)
    {
    return a + b;
    }

    int calc_multiply(int a, int b)
    {
    return a * b;
    }

    int calc_factorial(int n)
    {
    if (n < 0) return -1;
    if (n <= 1) return 1;
    int result = 1;
    for (int i = 2; i <= n; i++) {
    result *= i;
    }
    return result;
    }

    int calc_fibonacci(int n)
    {
    if (n < 0) return -1;
    if (n == 0) return 0;
    if (n == 1) return 1;
    int a = 0, b = 1;
    for (int i = 2; i <= n; i++) {
    int tmp = a + b;
    a = b;
    b = tmp;
    }
    return b;
    }

    const char* calc_version(void)
    {
    return "1.0.0";
    }
    +
  6. +
  7. +

    Build the shared library:

    +
    cd lib

    # Linux
    gcc -shared -fPIC -o libcalc.so libcalc.c

    # macOS
    # gcc -shared -fPIC -o libcalc.dylib libcalc.c

    cd ..
    +
  8. +
  9. +

    Verify the symbols are exported:

    +
    # Linux
    nm -D lib/libcalc.so | grep calc

    # macOS
    # nm -gU lib/libcalc.dylib | grep calc
    +

    Each symbol should be marked with T (text/code section). Addresses will vary:

    +
    0000000000001139 T calc_add (or _calc_add on macOS)
    0000000000001179 T calc_factorial
    00000000000011f5 T calc_fibonacci
    0000000000001159 T calc_multiply
    0000000000001299 T calc_version
    +
    info
    +
  10. +
+

If you are wrapping an existing library (for example, from a system package or a GitHub repo), you don't need to write the C code — just place the pre-built .so/.dylib and its header file in lib/. +:::

+

Step 3: Configure the Logos module

+

Write the files that turn your C library into a Logos module. With the pure-C++ (universal) pattern you only hand-write a single C++ class — metadata.json, CMakeLists.txt, and flake.nix tell the build system the rest, and logos-cpp-generator synthesises the Qt plugin wrapper.

+

After this step, your project will look like this:

+
logos-calc-module/
├── flake.nix # Nix build configuration (~10 lines)
├── metadata.json # Module metadata, build settings, and runtime config
├── CMakeLists.txt # CMake build file
├── lib/
│ ├── libcalc.h # C library header
│ └── libcalc.c # C library source (compiled by CMake)
└── src/
├── calc_module_impl.h # Plain C++ class
└── calc_module_impl.cpp # Implementation (wrapping logic)
+
    +
  1. Create metadata.json. Set name, description, main, add "interface": "universal", and declare your library under nix.external_libraries. This file is the single source of truth: it is embedded into the generated plugin binary, read by logos-module-builder to configure the Nix build, used by CMake to resolve and link external libraries, and used by nix-bundle-lgx to generate the LGX manifest.
  2. +
+

To fetch and build external libraries from source, add "build_command": "make shared" and "output_pattern": "build/<libname>" to "external_libraries".

+
{
"name": "calc_module",
"version": "1.0.0",
"type": "core",
"category": "general",
"description": "Calculator module wrapping libcalc C library",
"main": "calc_module_plugin",
"interface": "universal",
"dependencies": [],

"nix": {
"packages": {
"build": [],
"runtime": []
},
"external_libraries": [
{
"name": "calc",
"vendor_path": "lib"
}
],
"cmake": {
"find_packages": [],
"extra_sources": [],
"extra_include_dirs": ["lib"],
"extra_link_libraries": []
}
}
}
+

Key fields explained:

+
FieldWhat it does
nameModule name — must be a valid C identifier (used in filenames, method calls)
mainThe generated plugin's name, <name>_plugin. You don't write this file; the builder produces calc_module_plugin.so / .dylib
interface"universal" selects the pure-C++ pattern. The builder runs logos-cpp-generator --from-header over src/calc_module_impl.h and emits the Qt plugin, so you never touch Qt directly
nix.external_librariesDeclares C/C++ libraries vendored in the repo. Each entry has a name (the CMake target) and vendor_path (directory with the source/binary). The build compiles the library and links it into the plugin
nix.cmake.extra_include_dirsAdded to the include path so your C++ code can use #include "lib/libcalc.h"
+
    +
  1. +

    Create CMakeLists.txt. Set project() name, NAME, the SOURCES (your two implementation files), and EXTERNAL_LIBS. For a universal module you list only your plain C++ source files; the generated glue (generated_code/*.cpp) is picked up automatically by LogosModule.cmake.

    +
    cmake_minimum_required(VERSION 3.14)
    project(CalcModulePlugin LANGUAGES CXX)

    # Include the Logos Module CMake helper (provided by logos-module-builder)
    if(DEFINED ENV{LOGOS_MODULE_BUILDER_ROOT})
    include($ENV{LOGOS_MODULE_BUILDER_ROOT}/cmake/LogosModule.cmake)
    elseif(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/cmake/LogosModule.cmake")
    include(cmake/LogosModule.cmake)
    else()
    message(FATAL_ERROR "LogosModule.cmake not found")
    endif()

    # Define the module with its external library dependency.
    # Because metadata.json sets `interface: universal`, the builder runs
    # logos-cpp-generator over src/calc_module_impl.h before configuring,
    # and LogosModule.cmake compiles the generated glue automatically.
    logos_module(
    NAME calc_module
    SOURCES
    src/calc_module_impl.h
    src/calc_module_impl.cpp
    EXTERNAL_LIBS
    calc
    )
    +

    Keep these three fields in sync with metadata.json:

    +
      +
    • NAME — your module name (must match name in metadata.json, e.g., calc_module)
    • +
    • SOURCES — your implementation files (src/calc_module_impl.h, src/calc_module_impl.cpp)
    • +
    • EXTERNAL_LIBS — external libraries to link (must match nix.external_libraries[].name in metadata.json)
    • +
    +

    The if/elseif/else block is boilerplate — don't change it.

    +
  2. +
  3. +

    Create flake.nix and change description. External libraries can be added in inputs, allowing nix to fetch and build them from source.

    +
    {
    description = "Calculator module - wraps libcalc C library for Logos";

    inputs = {
    logos-module-builder.url = "github:logos-co/logos-module-builder/0.2.0";

    # Fetch the library source (non-flake)
    # libfoo-src = {
    # url = "github:example/libfoo";
    # flake = false;
    # };
    };

    outputs = inputs@{ logos-module-builder, ... }:
    logos-module-builder.lib.mkLogosModule {
    src = ./.;
    configFile = ./metadata.json;
    flakeInputs = inputs;
    };
    }
    +
    info
    +
  4. +
+

When adding module dependencies, the flake input attribute name must match the name field in that dependency's metadata.json. For example, if you depend on a module whose metadata.json has "name": "waku_module", your flake input must be waku_module.url = "github:logos-co/logos-waku-module". +:::

+
    +
  1. +

    Create src/calc_module_impl.h. This is the only interface you need to write. It allows every public method becomes callable by other modules and by logoscore. The code generator parses this header as text to derive the wire signatures, so keep it to the supported types (see the table below). Inheriting LogosModuleContext lets the class emit events and call other modules without touching the raw LogosAPI.

    +
    #pragma once

    #include <cstdint>
    #include <string>

    #include <logos_module_context.h> // LogosModuleContext base + `logos_events:`

    // Include the C library header (extern "C" already in the header).
    extern "C" {
    #include "lib/libcalc.h"
    }

    class CalcModuleImpl : public LogosModuleContext {
    public:
    CalcModuleImpl() = default;
    ~CalcModuleImpl() = default;

    // ── Public API — every method here is callable over IPC ──────────
    // The generator maps C++ types onto the wire automatically:
    // int64_t ↔ int std::string ↔ QString bool ↔ bool
    int64_t add(int64_t a, int64_t b);
    int64_t multiply(int64_t a, int64_t b);
    int64_t factorial(int64_t n);
    int64_t fibonacci(int64_t n);
    std::string libVersion();

    // Fire-and-forget: looks up the version, then emits it as an event
    // instead of returning it. Used by the QML tutorial (Part 2).
    void libVersionNotify();

    // ── Events ───────────────────────────────────────────────────────
    // Declared like Qt signals. The generator emits the body (in
    // calc_module_events.cpp) that routes the typed args to subscribers
    // via the host's `eventResponse` mechanism. QML subscribes with
    // logos.onModuleEvent("calc_module", "versionReady").
    logos_events:
    void versionReady(const std::string& version);
    };
    +

    Supported parameter and return types:

    +
    C++ typeQt
    voidvoid
    boolbool
    int64_tint
    uint64_tuint
    doubledouble
    std::stringQString
    std::vector<std::string>QStringList
    std::vector<uint8_t>QByteArray
    LogosMap / LogosListQVariantMap / QVariantList (from <logos_json.h>)
    StdLogosResultLogosResult (from <logos_result.h>) — { success, value, error }
    +
  2. +
  3. +

    Create src/calc_module_impl.cpp. Each method calls the corresponding C function and converts the result. No Qt types appear anywhere — you work in plain C++ and the generated glue handles the conversion.

    +
    #include "calc_module_impl.h"

    int64_t CalcModuleImpl::add(int64_t a, int64_t b)
    {
    return calc_add(static_cast<int>(a), static_cast<int>(b));
    }

    int64_t CalcModuleImpl::multiply(int64_t a, int64_t b)
    {
    return calc_multiply(static_cast<int>(a), static_cast<int>(b));
    }

    int64_t CalcModuleImpl::factorial(int64_t n)
    {
    return calc_factorial(static_cast<int>(n));
    }

    int64_t CalcModuleImpl::fibonacci(int64_t n)
    {
    return calc_fibonacci(static_cast<int>(n));
    }

    std::string CalcModuleImpl::libVersion()
    {
    return std::string(calc_version());
    }

    void CalcModuleImpl::libVersionNotify()
    {
    // Emit the event declared in `logos_events:`. When the module is
    // loaded by a host, this reaches every subscriber. When the class
    // is constructed outside a host (e.g. in unit tests), it is a
    // safe no-op.
    versionReady(std::string(calc_version()));
    }
    +

    The wrapping pattern is always the same: call the C function (converting int64_tint for libcalc's int API), convert the C result to a C++ type if needed (for example, const char*std::string), and return it.

    +
  4. +
+

Step 4: Build the module

+
    +
  1. +

    Initialise the Git repository. Nix flakes require a git repository. First create a .gitignore to exclude build artifacts:

    +
    # Nix build output
    result
    result-*

    # CMake build directory
    build/
    +

    Then initialise the repo and stage all files:

    +
    git init
    git add -A

    nix flake update

    git add flake.lock
    +
  2. +
  3. +

    Build just the plugin library (.so / .dylib):

    +
    nix build '.#lib'
    +
    info
    +
  4. +
+

The first build takes 5–15 minutes as Nix downloads Qt, the Logos SDK, and other dependencies. Subsequent builds are fast due to caching. +:::

+
    +
  1. +

    Build everything - both the library and generated SDK headers. For a universal module this is also where logos-cpp-generator --from-header runs over src/calc_module_impl.h to produce the Qt plugin glue under generated_code/ before CMake compiles it:

    +
    nix build
    +
  2. +
  3. +

    Inspect the output:

    +
    ls -la result/lib/
    +

    You should see two files (extensions depend on your platform):

    +
    # Linux
    calc_module_plugin.so # Your Logos module plugin
    libcalc.so # The C library (copied alongside)

    # macOS
    calc_module_plugin.dylib
    libcalc.dylib
    +

    Both library files are placed together so the plugin can find the C library at runtime via RPATH.

    +
  4. +
+

Step 5: Inspect the module

+

Use the lm CLI tool (from logos-module) to inspect the compiled module binary.

+
    +
  1. +

    Build the lm tool:

    +
    nix build 'github:logos-co/logos-module/0.2.0#lm' --out-link ./lm
    +
  2. +
  3. +

    View metadata:

    +
    # Linux
    ./lm/bin/lm metadata result/lib/calc_module_plugin.so

    # macOS
    ./lm/bin/lm metadata result/lib/calc_module_plugin.dylib
    +

    Expected output:

    +
    Plugin Metadata:
    ================
    Name: calc_module
    Version: 1.0.0
    Description: Calculator module wrapping libcalc C library
    Author:
    Type: core
    Dependencies: (none)
    +
  4. +
  5. +

    List methods:

    +
    # Linux
    ./lm/bin/lm methods result/lib/calc_module_plugin.so

    # macOS
    ./lm/bin/lm methods result/lib/calc_module_plugin.dylib

    # Add --json for scripting and CI

    # Linux
    ./lm/bin/lm methods result/lib/calc_module_plugin.so --json

    # macOS
    ./lm/bin/lm methods result/lib/calc_module_plugin.dylib --json
    +

    Expected output:

    +
    Plugin Methods:
    ===============

    int add(int a, int b)
    Signature: add(int,int)
    Invokable: yes

    ...
    +

    Expected output with --json:

    +
    [
    {
    "isInvokable": true,
    "name": "add",
    "parameters": [
    { "name": "a", "type": "int" },
    { "name": "b", "type": "int" }
    ],
    "returnType": "int",
    "signature": "add(int,int)"
    },
    ...
    ]
    +
  6. +
+

Step 6: Test with logoscore

+
    +
  1. +

    Build logoscore:

    +
    nix build 'github:logos-co/logos-logoscore-cli/0.2.0' --out-link ./logos
    +
  2. +
  3. +

    Set up the modules directory. logoscore expects modules in subdirectories, each with a manifest.json. Use the Nix derivation to create an LGX package and install it with the package manager:

    +
    nix build '.#lgx'
    nix build 'github:logos-co/logos-package-manager/0.2.0#cli' --out-link ./pm

    mkdir -p modules

    ./pm/bin/lgpm --modules-dir ./modules install --file result/*.lgx
    +

    This extracts the plugin, external libraries, and manifest into the correct directory structure:

    +
    modules/calc_module/
    ├── calc_module_plugin.dylib # (or .so on Linux)
    ├── libcalc.dylib # (or .so on Linux)
    ├── manifest.json # Auto-generated by lgx
    └── variant # Platform variant identifier
    +
  4. +
  5. +

    Start the daemon and call methods:

    +
    ./logos/bin/logoscore -D -m ./modules &
    sleep 3

    ./logos/bin/logoscore load-module calc_module

    ./logos/bin/logoscore call calc_module add 3 5
    ./logos/bin/logoscore call calc_module factorial 5
    ./logos/bin/logoscore call calc_module fibonacci 10
    ./logos/bin/logoscore call calc_module libVersion

    ./logos/bin/logoscore stop
    +
  6. +
+

Step 7: Unit-test the module

+

Because your module is a plain C++ class, you can unit-test it directly. The Logos Test Framework adds a tiny test runner (LOGOS_TEST and LOGOS_ASSERT_*) and link-time mocking of your C library, so each test can make functions return whatever you want and allows you to assert how your wrapper should behave.

+
    +
  1. +

    Enable tests in flake.nix. Add a tests block to the mkLogosModule call. mockCLibs lists the external libraries to replace with link-time mocks:

    +
    {
    description = "Calculator module - wraps libcalc C library for Logos";

    inputs = {
    logos-module-builder.url = "github:logos-co/logos-module-builder/0.2.0";
    };

    outputs = inputs@{ logos-module-builder, ... }:
    logos-module-builder.lib.mkLogosModule {
    src = ./.;
    configFile = ./metadata.json;
    flakeInputs = inputs;
    tests = {
    dir = ./tests;
    mockCLibs = [ "calc" ];
    };
    };
    }
    +
  2. +
  3. +

    Create tests/CMakeLists.txt. The test harness configures and builds tests/ as its own CMake project. It includes LogosTest (provided by the framework) and calls logos_test(), listing your impl source (MODULE_SOURCES), the test sources (TEST_SOURCES), and the C-library mock (MOCK_C_SOURCES):

    +
    cmake_minimum_required(VERSION 3.14)
    project(CalcModuleTests LANGUAGES CXX)

    include(LogosTest)

    logos_test(
    NAME calc_module_tests
    MODULE_SOURCES
    ../src/calc_module_impl.cpp
    mocks/calc_module_events_stub.cpp
    TEST_SOURCES
    main.cpp
    test_calc.cpp
    MOCK_C_SOURCES
    mocks/mock_libcalc.cpp
    )
    +

    logos_test() automatically puts the repo root and ../src on the include path, so #include "calc_module_impl.h" and #include "lib/libcalc.h" both resolve.

    +
  4. +
  5. +

    Create tests/mocks/calc_module_events_stub.cpp. In a normal build, logos-cpp-generator emits calc_module_events.cpp containing the body of every logos_events: method. The test harness runs the generator in a reduced mode that does not emit that file, so libVersionNotify() would fail to link. Provide a no-op stub:

    +
    // Stub bodies for the impl's `logos_events:` methods.
    // In the real build the codegen generates calc_module_events.cpp with
    // bodies that route through LogosModuleContext. The test build skips
    // that codegen, so we provide no-op stubs to satisfy the linker.
    #include "calc_module_impl.h"

    void CalcModuleImpl::versionReady(const std::string&) {}
    +

    If you add more events to logos_events:, add a matching no-op line here. A module with no events does not need this stub.

    +
  6. +
  7. +

    Create tests/main.cpp:

    +
    #include <logos_test.h>

    LOGOS_TEST_MAIN()
    +
  8. +
  9. +

    Create tests/mocks/mock_libcalc.cpp. When building tests, the real libcalc is not linked. Instead you provide functions with the same signatures backed by the framework's mock store:

    +
    // Link-time replacement for libcalc. Each function records the call
    // and returns whatever the active test configured via mockCFunction().
    #include <logos_clib_mock.h>

    extern "C" {
    #include "lib/libcalc.h"
    }

    extern "C" int calc_add(int a, int b) {
    LOGOS_CMOCK_RECORD("calc_add");
    return LOGOS_CMOCK_RETURN(int, "calc_add");
    }

    extern "C" int calc_multiply(int a, int b) {
    LOGOS_CMOCK_RECORD("calc_multiply");
    return LOGOS_CMOCK_RETURN(int, "calc_multiply");
    }

    extern "C" int calc_factorial(int n) {
    LOGOS_CMOCK_RECORD("calc_factorial");
    return LOGOS_CMOCK_RETURN(int, "calc_factorial");
    }

    extern "C" int calc_fibonacci(int n) {
    LOGOS_CMOCK_RECORD("calc_fibonacci");
    return LOGOS_CMOCK_RETURN(int, "calc_fibonacci");
    }

    extern "C" const char* calc_version(void) {
    LOGOS_CMOCK_RECORD("calc_version");
    return LOGOS_CMOCK_RETURN_STRING("calc_version");
    }
    +
  10. +
  11. +

    Create tests/test_calc.cpp. Each LOGOS_TEST constructs your impl directly, configures the C-function return values, calls a method, and asserts. LogosTestContext resets the mock store between tests:

    +
    #include <logos_test.h>
    #include "calc_module_impl.h"

    LOGOS_TEST(add_forwards_to_calc_add) {
    auto t = LogosTestContext("calc_module");
    t.mockCFunction("calc_add").returns(8);

    CalcModuleImpl calc;
    LOGOS_ASSERT_EQ(calc.add(3, 5), 8);
    LOGOS_ASSERT(t.cFunctionCalled("calc_add"));
    }

    LOGOS_TEST(multiply_forwards_to_calc_multiply) {
    auto t = LogosTestContext("calc_module");
    t.mockCFunction("calc_multiply").returns(42);

    CalcModuleImpl calc;
    LOGOS_ASSERT_EQ(calc.multiply(6, 7), 42);
    LOGOS_ASSERT(t.cFunctionCalled("calc_multiply"));
    }

    LOGOS_TEST(factorial_returns_mocked_value) {
    auto t = LogosTestContext("calc_module");
    t.mockCFunction("calc_factorial").returns(120);

    CalcModuleImpl calc;
    LOGOS_ASSERT_EQ(calc.factorial(5), 120);
    }

    LOGOS_TEST(libVersion_converts_cstring_to_string) {
    auto t = LogosTestContext("calc_module");
    t.mockCFunction("calc_version").returns("1.0.0");

    CalcModuleImpl calc;
    LOGOS_ASSERT_EQ(calc.libVersion(), std::string("1.0.0"));
    }
    +

    LOGOS_ASSERT_EQ, LOGOS_ASSERT, LOGOS_ASSERT_TRUE/FALSE, and LOGOS_ASSERT_NE/GT/GE/LT are all available from <logos_test.h>.

    +
  12. +
  13. +

    Track the new files (Nix only sees git-tracked files), then build and run:

    +
    git add tests/ flake.nix
    nix build '.#unit-tests' -L
    +

    The build compiles your impl against the mock library and the test sources, then runs every LOGOS_TEST. A passing run ends with a summary line; a failed assertion prints the file/line and fails the build.

    +
  14. +
+

Troubleshooting Logos module wrapping

+

A method doesn't appear in lm or can't be called

+

The generator only exposes public methods whose parameter and return types it recognises. Check that the method is in the public: section, that all types are supported (notably int64_t not int, std::string not char* or QString), and that each signature is on one line.

+

Build error: unknown type or generator can't parse a method

+

The --from-header parser reads *_impl.h as text. Pulling Qt types or unusual templates into a public method signature will confuse it. Keep Qt out of the impl header entirely and move helpers that need exotic types into the private: section or the .cpp.

+

"Cannot load library"

+

Ensure libcalc.so / libcalc.dylib is in the same directory as the plugin. The build system sets RPATH to $ORIGIN (Linux) or @loader_path (macOS) so the plugin looks for libraries in its own directory.

+

Events never reach subscribers

+

Check that the event is declared in a logos_events: section and that the class inherits LogosModuleContext. Events only fire when the module is loaded by a host; constructed standalone (for example in unit tests), emission is a safe no-op. The subscriber must use the exact event name string, for example logos.onModuleEvent("calc_module", "versionReady").

+

Plugin not discovered by logoscore

+

Verify that the module is in a subdirectory of the modules dir (for example modules/calc_module/), that the subdirectory contains a manifest.json with a valid main object, and that the platform key in main matches your OS/arch (for example linux-aarch64, darwin-arm64).

+

nix build .#lib does nothing or fails silently

+

Some shells (notably zsh) treat # as a comment character. Always put the flake reference in quotes, like so: nix build '.#lib'.

+

First build is slow

+

The first nix build downloads Qt 6, the Logos C++ SDK, the code generator, and other dependencies. This is a one-time cost — subsequent builds use the Nix cache and are fast (usually under 30 seconds).

+

Symbol not found errors

+

If you get "undefined symbol" errors for your C library functions, verify that the .so/.dylib is in lib/ before building, that the header has extern "C" guards, and that the symbols are exported: nm -D lib/libcalc.so | grep calc.

\ No newline at end of file diff --git a/core/reference/lgx-package-format-and-bundling-reference/index.html b/core/reference/lgx-package-format-and-bundling-reference/index.html new file mode 100644 index 00000000..c3c0b5ff --- /dev/null +++ b/core/reference/lgx-package-format-and-bundling-reference/index.html @@ -0,0 +1,5 @@ +LGX package format and bundling reference | Logos documentation + + + \ No newline at end of file diff --git a/core/reference/logos-cli-reference/index.html b/core/reference/logos-cli-reference/index.html new file mode 100644 index 00000000..641ce50d --- /dev/null +++ b/core/reference/logos-cli-reference/index.html @@ -0,0 +1,5 @@ +Logos CLI Reference | Logos documentation + + + \ No newline at end of file diff --git a/get-started/glossary/index.html b/get-started/glossary/index.html new file mode 100644 index 00000000..2b795ca5 --- /dev/null +++ b/get-started/glossary/index.html @@ -0,0 +1,245 @@ +Glossary | Logos documentation + + +

Glossary

+

A list of some common terms and phrases specific to the Logos ecosystem.

+
+

A

+

Account

+

The basic organisational unit of the LEZ state, with all persistent data stored in accounts. Accounts can be either public or private, and can also represent LEZ programs. See Introduction to the Logos Execution Zone.

+
+

B

+

Basecamp

+

The desktop shell for Logos, which facilitates installing and running Logos modules and apps.

+

Bedrock

+

The foundational layer of the Logos Blockchain, operating as a decentralised peer-to-peer network. See About Bedrock.

+

Blend Network

+

A service that adds anonymity for block proposers on the Logos Blockchain. See About the Blend Network.

+

Blend node

+

A Logos Blockchain node that chooses to participate in the Blend Network. Also known as a core node. See About the Blend Network.

+

Blend Protocol

+

The protocol underlying the Blend Network, which provides anonymity for Logos Blockchain block proposers. See About the Blend Network.

+

Bootstrapping

+

The process a Logos Blockchain or Delivery node undergoes when first connected to the network. For a Logos Blockchain node, this involves "catching up" on the history of the blockchain.

+
+

C

+

Catalogue

+

A repository that includes a list of Logos modules. Installing a module repo on Basecamp allows you to install the latest version of all these modules together. Also known as a module repo.

+

Channel

+

A lightweight virtual chain of ordered message logs that represent the state of a Zone on Bedrock. See About Mantle

+

CID

+

Content Identifier, used to identify files for the storage module.

+

Codex

+

The old name for the Logos Storage component of the Logos technology stack.

+

Content topic

+

A string attached to messages sent via Logos Delivery to enable protocol-level features like selective message processing and retrieval based on specific criteria. See About content topics.

+

Core module

+

A Logos module that does not include a user interface view and provides backend functionality.

+

Core node

+

A Logos Blockchain node that chooses to participate in the Blend Network. Also known as a Blend node. See About the Blend Network.

+

Cover traffic

+

Dummy messages sent by Blend nodes to improve proposer anonymity. See Cover traffic.

+

Cryptarchia

+

The Private Proof of Stake consensus protocol used by the Logos Blockchain. See About Cryptarchia.

+
+

D

+

Delivery module

+

A module that uses the Logos Delivery network to send messages and subscribe to content topics.

+

DNS discovery

+

A peer discovery mechanism used in Logos Delivery that allows the retrieval of an Ethereum Node Record (ENR) tree from the TXT field of a domain name, enabling the storage of node connection details and promoting decentralisation. See About DNS discovery.

+
+

E

+

ENR

+

Ethereum Node Record - a specification used in Logos Delivery to represent and identify nodes, facilitating discovery and communication within the network. Besides connection details, ENR also includes node configuration information like enabled protocol and shards.

+

Epoch

+

A time unit used by the Logos Blockchain, about 7.5 days long. See About Cryptarchia.

+

Ethereum Node Record

+

A specification used in Logos Delivery to represent and identify nodes, facilitating discovery and communication within the network. Also known as ENR. Besides connection details, ENR also includes node configuration information like enabled protocol and shards.

+
+

F

+

Filter

+

A protocol that enables Logos Delivery light nodes to selectively subscribe to specific messages transmitted by peers using content topics. It is designed to be a lightweight alternative for accessing the Relay network. See Understand Logos Delivery protocols.

+
+

G

+

GMS

+

A Group Master Secret - a 32-byte secret used on the LEZ that, when sealed with another party's public sealing key, allows them to create a local instance of a shared LEZ account. See Set up a shared private LEZ account.

+

GossipSub

+

A protocol for efficient and scalable information dissemination in decentralised networks commonly used in blockchain systems. See About network domains.

+

Group Master Secret

+

A 32-byte secret used on the LEZ that, when sealed with another party's public sealing key, allows them to create a local instance of a shared LEZ account. Also known as a GMS. See Set up a shared private LEZ account.

+
+

I

+

Inscription

+

A type of Mantle operation that writes data to the Logos Blockchain. Inscriptions are often used to represent Zone state updates. See About Mantle.

+
+

L

+

LEE

+

The Logos Execution Environment - the virtual machine that runs on the Logos Execution Zone. See Introduction to the Logos Execution Zone.

+

LEZ

+

The Logos Execution Zone - the primary execution layer for general-purpose applications on Logos, with built-in support for private execution. See Introduction to the Logos Execution Zone.

+

lgpm

+

The official package manager used for Logos modules.

+

lgpd

+

The official package downloader for Logos modules.

+

.lgx

+

A module file type which allows it to be loaded with logoscore and Basecamp.

+

Light node

+

A resource-limited device or client that leverages service nodes to access the Logos Delivery Relay network.

+

Light push

+

A protocol enabling Logos Delivery light nodes to send messages to the Relay network and receive acknowledgements confirming that a peer has received them. See Understand Logos Delivery protocols.

+

LIP

+

Logos Improvement Proposal - a proposed community update to the Logos design, which may or may not be incorporated into the project.

+

lm

+

A helper tool for Logos modules.

+

Locator

+

An address assigned to a Logos Blockchain node participating in the Blend Network. See About the Blend Network.

+

Logos Blockchain

+

The foundational infrastructure layer of the Logos technology stack. See Introduction to the Logos Blockchain.

+

Logos Chat

+

A library for private, encrypted peer-to-peer chat using Logos Delivery. A component of Logos Delivery.

+

Logos Delivery

+

A peer-to-peer messaging network with DoS protection via Rate Limiting Nullifiers. A component of Logos Delivery.

+

Logos Execution Environment

+

The virtual machine that runs on the Logos Execution Zone. Also known as the LEE. See Introduction to the Logos Execution Zone.

+

Logos Execution Zone

+

The primary execution layer for general-purpose applications on Logos, with built-in support for private execution. Also known as the LEZ. See Introduction to the Logos Execution Zone.

+

Logos Messaging

+

The private messaging layer of the Logos technology stack.

+

Logos node

+

A node running one or several Logos modules that connect to a network of other such nodes. See Run a Logos node with blockchain, storage, and delivery.

+

logos-module-builder

+

A build tool for Logos modules.

+

logos-module-viewer

+

A graphical tool for inspecting Logos modules.

+

logos-standalone-app

+

An app that displays UI views for Logos modules.

+

Logos Storage

+

The privacy-preserving filesharing component of the Logos technology stack.

+

logoscore

+

A daemon that runs Logos modules from the command line.

+

LSSA

+

An old name for the Logos Execution Environment. See Introduction to the Logos Execution Zone.

+
+

M

+

Mantle

+

The execution layer of Bedrock, enabling participation in the Blend Network and facilitating interactions wth Zones. See About Mantle.

+

Message

+

A virtual "block" of data that forms part of a Logos channel. See About Mantle.

+

Mix

+

Traffic obfuscation used to improve privacy guarantees.

+

Module

+

A self-contained, resuable software component that provides a specific capability. Multiple modules can be loaded and made to interact to form a complete application.

+

Module repo

+

A repository that includes a list of Logos modules. Installing a module repo on Basecamp allows you to install the latest version of all these modules together. Also known as a catalogue.

+

Mostly offline

+

Logos Delivery clients who spend most of their time offline or disconnected from the internet and only occasionally to the network. Examples include browsers and mobile phones.

+
+

N

+

Nescience

+

An old name for the Logos Execution Environment. See Introduction to the Logos Execution Zone.

+

Nomos

+

An old name for the Logos Blockchain. See Introduction to the Logos Blockchain.

+

Node key

+

A Secp256k1 (64-char hex string) private key for generating the PeerID, listening addresses, and discovery addresses of a Logos Delivery node.

+

Note

+

A fungible UTXO token used on Mantle. See About Mantle.

+

NPK

+

A Nullifier Public Key - a public key used to verify token ownership on the LEZ. See Introduction to the Logos Execution Zone.

+

NSSA

+

An old name for the Logos Execution Environment. See Introduction to the Logos Execution Zone.

+

NSK

+

A Nullifier Secret Key - a secret key used to sign LEZ transactions. See Introduction to the Logos Execution Zone.

+

Nullifier keys

+

A key pair used to sign transactions and verify token ownership on the LEZ. See Introduction to the Logos Execution Zone.

+

Nullifier Public Key

+

A public key used to verify token ownership on the LEZ, also known as an NPK. See Introduction to the Logos Execution Zone.

+

Nullifier Secret Key

+

A secret key used to sign LEZ transactions, also known as an NSK. See Introduction to the Logos Execution Zone.

+
+

O

+

Out-of-band

+

Exchanging information through a separate, secure channel distinct from the main communication method to enhance security.

+
+

P

+

PDA

+

Program Derived Address - the address assigned to an LEE program. See Introduction to the Logos Execution Zone.

+

Peer discovery

+

When a Logos Delivery node locates and gets information about other peers in the network. See Understand peer discovery.

+

Peer exchange

+

A peer discovery mechanism used in Logos Delivery that enables light nodes to request and receive peers from other nodes in the network, allowing them to bootstrap and expand their connections without depending on Discv5. See About peer exchange.

+

Peer ID

+

The unique identifier of a node in the Logos Delivery network generated from the cryptographic hash of the node's public key.

+

PoL

+

Proof of Leadership - the ZK proof that a note's owner is eligible to propose a block. See About Cryptarchia.

+

PPoS

+

Private Proof of Stake - a Proof of Stake consensus mechanism that protects the identity of block proposers. See About Cryptarchia.

+

Private account

+

A type of LEZ account that keeps its transactions private. Account updates are proven via ZK proofs. See Introduction to the Logos Execution Zone.

+

Private Proof of Stake

+

A Proof of Stake consensus mechanism that protects the identity of block proposers, also known as PPoS. See About Cryptarchia.

+

Program

+

An executable smart contract on the LEZ, associated with an LEZ account. See Introduction to the Logos Execution Zone.

+

Program Derived Address

+

The address assigned to an LEZ program, also known as a PDE. See Introduction to the Logos Execution Zone.

+

Proof of Leadership

+

The ZK proof that a note's owner is eligible to propose a block. Also known as PoL. See About Cryptarchia.

+

Public account

+

A type of LEZ account whose state is publicly available. See Introduction to the Logos Execution Zone.

+
+

R

+

Relay

+

A protocol that extends the GossipSub protocol to enable secure and censorship resistant message sending and receiving among peers while preserving privacy. It also scales the Logos Delivery network to accommodate many nodes efficiently. See Understand Logos Delivery protocols.

+
+

S

+

SDP

+

The Service Declaration Protocol - used to keep track of Logos Blockchain nodes that opted in to participate in the Blend Network. See About the Blend Network.

+

Sealing keys

+

A key pair used by users of a shared LEZ account, together with the Group Master Secret, to derive a local instance of a shared account. See Set up a shared private LEZ account.

+

Service Declaration Protocol

+

Used to keep track of Logos Blockchain nodes that opted in to participate in the Blend Network. Also known as the SDP. See About the Blend Network.

+

Service Reward Distribution Protocol

+

The Service Reward Distribution Protocol - enables reward distribution to Logos Blockchain nodes participating in the Blend Network. See About the Blend Network.

+

Slot

+

The basic time unit used by the Logos Blockchain, equivalent to 1 second. See About Cryptarchia.

+

SRDP

+

Enables reward distribution to Logos Blockchain nodes participating in the Blend Network. Also known as the SRDP. See About the Blend Network.

+

Storage module

+

The Logos module that provides filesharing capabilities via Logos Storage.

+

Store

+

A protocol that enables the storage of relayed messages in the Logos Delivery network, allowing offline peers to retrieve missed messages upon reconnecting to the network. See Understand Logos Delivery protocols.

+
+

T

+

Token definition account

+

An LEZ account that defines the properties and behaviour of a custom token. See Create and transfer custom tokens on the Logos Execution Zone.

+

Token holding account

+

An LEZ account that is able to hold a custom token. See Create and transfer custom tokens on the Logos Execution Zone.

+

Token program

+

An LEZ program that facilitates the creation of custom LEZ tokens. See Create and transfer custom tokens on the Logos Execution Zone.

+

Transport

+

A network mechanism that establishes connections between Logos Delivery peers and enables efficient transmission, routing, and delivery of data packets.

+
+

U

+

UI Module

+

A Logos module that includes a user interface view.

+
+

V

+

Viewing keys

+

A key pair used to generate and verify ZK proofs on the LEZ. See Introduction to the Logos Execution Zone.

+

Viewing Public Key

+

A public key used to verify ZK proofs on the LEZ, also known as a VPK. See Introduction to the Logos Execution Zone.

+

Viewing Secret Key

+

A secret key used to generate ZK proofs on the LEZ, also known as a VSK. See Introduction to the Logos Execution Zone.

+

VPK

+

A Viewing Public Key - a public key used to verify ZK proofs on the LEZ. See Introduction to the Logos Execution Zone.

+

VSK

+

A Viewing Secret Key - a secret key used to generate ZK proofs on the LEZ. See Introduction to the Logos Execution Zone.

+
+

W

+

Waku

+

An old name for the Logos Delivery component of the Logos technology stack.

+
+

Z

+

Zone SDK

+

An SDK provided by Logos to facilitate the creation of custom Logos Zones. See About Zones.

+

Zone

+

An L2 blockchain that defines its own state but uses the Logos Blockchain for consensus. See About Zones.

\ No newline at end of file diff --git a/get-started/logos-ecosystem-repositories/index.html b/get-started/logos-ecosystem-repositories/index.html new file mode 100644 index 00000000..cd4cd5ed --- /dev/null +++ b/get-started/logos-ecosystem-repositories/index.html @@ -0,0 +1,35 @@ +Logos ecosystem repositories | Logos documentation + + +

Logos ecosystem repositories

+

Review the key repositories across the logos organisations.

+

The Logos project is organised into several GitHub organisations, each focused on one part of the system. This page highlights the most important repositories for understanding the project and getting started with development. The selection is curated rather than exhaustive.

+

Orientation and contribution resources

+

These repositories explain what Logos is building and how to take part, with no code to run.

+
RepositoryDescription
logos-docsDocumentation, standards, and templates for Logos projects.
roadmapA Markdown-based view of the Logos roadmap.
ideasCommunity-sourced project ideas to build on top of Logos.
rfpGrants for building in the Logos ecosystem.
+

Logos Basecamp

+

Logos Basecamp is the desktop shell for Logos.

+
RepositoryDescription
logos-basecampThe Logos Basecamp repository.
+

Logos Core

+

Logos Core is the modular runtime that ties applications together.

+
RepositoryDescription
scaffoldCLI that bootstraps a runnable Logos application environment.
logos-liblogosThe core runtime library for the Logos modular application platform.
logos-logoscore-cliThe headless CLI runtime for the Logos modular application platform.
logos-logoscore-pyPython wrapper for the logoscore CLI.
logos-package-managerC++ library and CLI for local Logos package management.
logos-package-downloaderModule download tooling. No official description; purpose inferred.
logos-module-builderC++ library and CLI (lgpd) for fetching Logos module catalogs and downloading .lgx packages.
logos-app-builderShared Nix library for building Logos UI applications.
OverwatchSelf-contained services application framework.
+

Core modules and applications

+

Modules plug into Logos Core to provide specific capabilities, and user-interface repositories are the front-end applications built on those modules. Modules and applications live in both organisations.

+
RepositoryDescription
logos-chat-moduleA Logos Core module that exposes Logos Chat.
logos-chat-uiUI application for the Logos Chat SDK proof of concept.
logos-wallet-moduleEarly-stage wallet module built on go-wallet-sdk.
logos-accounts-moduleEarly-stage account-management module built on go-wallet-sdk.
logos-blockchain-moduleCore module wrapping logos-blockchain-c.
logos-blockchain-uiBlockchain UI application for Logos Core.
logos-execution-zone-moduleLogos Execution Zone module for Logos Core. No official description; purpose inferred.
logos-execution-zone-wallet-uiLogos Execution Zone wallet UI. No official description; purpose inferred.
logos-libp2p-moduleA Logos module that integrates libp2p networking capabilities.
+

Logos Blockchain

+

The Logos Blockchain is the foundational infrastructure of the Logos technology stack.

+
RepositoryDescription
logos-blockchainThe Logos blockchain repository.
logos-blockchain-block-explorer-templatePython block explorer and indexer template.
nimbosNim client for the Logos chain.
+

Logos Execution Zone

+

The Logos Execution Zone (LEZ) is the primary execution layer for applications built on the Logos stack.

+
RepositoryDescription
logos-execution-zoneThe LEZ repository.
lez-programsEssential Logos Execution Zone programs built by Logos.
lez-multisigMultisig built with the lez-framework program macro.
logos-lez-rlnLogos Execution Zone program for RLN registration.
logos-sql-zonePassword-manager demo built with the Zone SDK.
lez-fuzzingAdversarial testing and fuzzing infrastructure for LEZ.
+

Logos messaging

+

Logos Messaging is the project's decentralised communication system.

+
RepositoryDescription
logos-deliveryLogos messaging protocols implemented in Nim, formerly nwaku.
logos-delivery-jsLogos messaging protocols implemented in JavaScript and TypeScript, formerly Waku v2.
logos-delivery-python-bindingsPython wrapper around the node.
sds-go-bindingsGo bindings for the SDS reliability protocol. No official description; purpose inferred.
nim-sdsNim implementation of the end-to-end reliability protocol.
nim-segmentationMessage segmentation. No official description; purpose inferred.
logos-delivery-rlnv2-contractRLN-v2 smart contracts, written in Solidity.
specsProtocol specifications. No official description; purpose inferred.
logos-delivery-composeDocker-compose files to deploy a node.
logos-delivery-simulatorSimulates a network with multiple nodes, traffic, and users.
logos-delivery-interop-testsInteroperability tests. No official description; purpose inferred.
libchatSupporting library for Logos Chat, written in Rust.
awesome-logos-deliveryCommunity-curated list of resources.
+

Logos Storage

+

Logos Storage is the project's privacy-preserving, decentralised storage system.

+
RepositoryDescription
logos-storage-nimThe storage node and durability engine, formerly nim-codex.
logos-storage-nim-dhtA DHT based on Discv5 with libp2p provider-record support.
circom-compat-ffiFFI for circom-compat (ark-circom), used for zero-knowledge proving.
logos-storage-researchDurability-engine research.
logos-storage-nim-cs-dist-testsDistributed system tests for the node.
logos-storage-local-harnessA bash harness for running the node without containers.
transport-over-mixTransport-layer abstraction over Mix, with spec and reference implementation.
libp2p-storage-mix-transportExperiments with a storage transport over the libp2p Mix protocol.
mix-hidden-servicesHidden services over Mix.
lioness_blockcipherRust implementation of the LIONESS wide-block cipher.
logos-storage-docs-obsidianIndexed, searchable documentation as an Obsidian vault.
+

AnonComms

+

AnonComms focuses on building the core elements that allow privacy-preserving communication and coordination across the Logos stack. The work is at early stages, see the latest update in the roadmap.

+

Standards, governance, and verification

+

These repositories define how Logos documents, proposes, and verifies changes.

+
RepositoryDescription
logos-lipsLogos Improvement Proposals Index.
logos-doctestExecutable documentation that can be verified and rendered.
logos-tutorialTutorial materials
\ No newline at end of file diff --git a/get-started/what-is-logos/index.html b/get-started/what-is-logos/index.html new file mode 100644 index 00000000..6f3a00bb --- /dev/null +++ b/get-started/what-is-logos/index.html @@ -0,0 +1,42 @@ +Introduction to Logos | Logos documentation + + +

Introduction to Logos

+

Learn how the Logos technology stack is organised as a modular operating system for decentralised applications.

+

Logos is a modular, microkernel-based stack for decentralised infrastructure, built for privacy, composability, and resilience. Its structure can be compared to the Linux operating system, which consists of a kernel, a networking stack, a set of system services, and the applications that run on top of them. Different distributions share the same kernel but assemble different services and applications on top of it.

+

Logos follows the same structure. At its foundation sits the Logos runtime, a microkernel-style core that provides the primitives a decentralised application requires. Above the runtime sits a networking layer, which provides peer discovery, connection management, and privacy-preserving communication through a mixnet. Above that sit the modules - pluggable components that provide a specific capability, such as storage, messaging, or blockchain consensus. At the top of the stack sit the dApps that compose these modules into applications.

+

Logos ships with a default configuration consisting of the runtime plus storage, messaging, and blockchain modules. This configuration is not fixed: developers can assemble other distributions from a different selection of modules.

+

The basics

+
    +
  • Logos is organised into four layers: a runtime at the base, a privacy-preserving networking layer above it, a set of pluggable modules that provide specific capabilities, and decentralised applications on top that compose those modules.
  • +
  • Logos Basecamp is the default launcher for the stack. It runs the Logos runtime locally, loads a configured module profile, and provides a unified interface. The Logos Node runs the same runtime without a user interface, for validators, operators, or backend services.
  • +
  • Privacy is implemented at the infrastructure layer rather than the application layer. An application built on Logos inherits metadata protection and support for private state regardless of whether its developer has implemented anonymity measures directly.
  • +
+

Design principles

+

Logos was designed around modularity, structural privacy, and sovereignty.

+

Modularity determines how the system tolerates failure and change. Each layer and module can be developed, audited, and upgraded independently of the others. A defect in the storage module does not affect the messaging layer, and an upgrade to consensus does not require changes to the networking layer.

+

Structural privacy means that privacy guarantees are properties of the infrastructure rather than features that individual applications must implement. The mixnet obscures communication patterns at the networking layer, Cryptarchia and the Blend Network hide validator identities and stake amounts at the consensus layer, and the Logos Execution Zone supports private state at the execution layer.

+

Sovereignty refers to the operator's control over the infrastructure they run. A Logos node does not route messages through a third-party server, does not store files in a third-party data centre, and does not require permission from an intermediary to process a transaction. Because the stack is modular, an operator selects only the capabilities their use case requires.

+

Architecture

+

Logos is organised into distinct layers, each with a defined responsibility: the runtime, the networking layer, modules, and dApps.

+

The Logos architecture.

+

The runtime

+

The Logos runtime follows a microkernel design: it performs a minimal set of functions and delegates all other responsibilities to modules that run on top of it. This differs from a monolithic kernel such as Linux, where the networking stack and many other services run inside the kernel itself. The runtime's responsibilities are limited to loading and unloading modules, managing each module's lifecycle, and providing a mechanism for modules to locate and call one another.

+

The runtime is implemented in a C++ library called liblogos. Three supporting modules operate alongside it. The Process Manager Module runs each loaded module in its own operating system process. If a module fails, the Process Manager Module records the failure and restarts the module using an exponential backoff, so that a fault in one module does not affect the rest of the system. The Developer Module provides visibility into loaded modules, their registered objects, and their interactions, together with tracing, hot reload, and live debugging through a JSON-RPC interface. The Package Manager Module retrieves modules, currently from GitHub and in future from peer-to-peer storage, verifies them against cryptographic hashes, resolves dependencies, and applies incremental updates rather than re-downloading a module in full each time it changes.

+

This separation allows individual modules to be developed, tested, and updated independently of the runtime and of one another.

+

The networking layer

+

The networking layer sits above the runtime and is represented in the stack as discovery, peering, and mixnet. It determines how Logos nodes locate one another, establish connections, and exchange messages. Unlike a conventional networking stack, this layer is designed to prevent observers from determining who is communicating with whom, as a property of the network rather than of any individual application.

+

This layer comprises several components. Service Discovery allows a node to locate other nodes that offer a required capability without consulting a central directory. It adapts the DISC-NG protocol for use with libp2p's Kademlia-DHT: each node publishes a signed record describing its address and capabilities, and other nodes query for records that match the capabilities they require without participating in the full distributed hash table.

+

The libp2p mixnet is the privacy-preserving transport layer, based on the design principles used in anonymity networks such as Tor and Nym. Messages are routed through a sequence of intermediary relay nodes that shuffle and delay traffic before forwarding it, preventing an observer from correlating senders with receivers using timing or volume analysis. The protocol supports both one-way messages and request-response interactions, and includes cover traffic to maintain a sufficient set of plausible senders.

+

RLN and Zerokit add spam protection to the anonymous network. A Rate Limiting Nullifier allows each network participant to send messages up to a defined rate while remaining anonymous. If a participant exceeds that rate, the protocol reveals their identity, allowing them to be slashed or removed. This cryptography is implemented in Zerokit and is used both for denial-of-service protection on the mixnet and for rate limiting within Logos Delivery.

+

De-MLS extends private messaging from one-to-one conversations to group conversations. It is a decentralised implementation of Messaging Layer Security in which a group is managed by multiple stewards rather than a single administrator.

+

The networking layer is itself packaged as a module and loaded by the runtime in the same way as storage or messaging. Once loaded, it provides the private transport used by every other module to communicate across the network, independent of whether that traffic consists of file storage requests, chat messages, or transactions.

+

Modules

+

Modules are self-contained components that sit above the networking layer, each providing a defined capability. Logos includes three foundational modules, and the architecture supports the addition of further modules.

+

The Blockchain module provides decentralised compute and consensus. It is built on Cryptarchia, a private proof-of-stake consensus mechanism in which block proposers cannot be linked to their proposals, with additional privacy by the Blend Network. The Logos Execution Zone (LEZ), a Layer 2 execution environment, supports program deployment with both public and private account states.

+

The Messaging module provides private, censorship-resistant communication between parties and consists of two components. Logos Delivery provides the transport layer, implementing publish-subscribe messaging for reliable delivery. Logos Chat uses Delivery as its transport and provides encrypted one-to-one conversations, implemented with double-ratchet encryption, with support for group conversations under development using de-MLS.

+

The Storage module provides decentralised file storage and retrieval using content-addressed data. It exposes an API for storing a file and receiving a content identifier (CID) in return, and for retrieving a file given its CID.

+

User modules extend the architecture beyond the foundational set. Any developer can build a module that integrates with the same infrastructure. The runtime loads user modules, manages their lifecycle, and enables them to communicate with other modules, whether those modules are provided by Logos or by third parties.

+

Dapps

+

Dapps are the decentralised applications that compose the modules described above. A chat application could use the messaging and storage modules; a decentralised finance application could use the blockchain module and the LEZ; a file-sharing application could use the storage module.

+

The Logos Basecamp, is the default launcher for the stack. It starts the runtime, loads the configured module profile, and provides a unified interface. By default, it includes a set of applications for each foundational module: a wallet for managing tokens, a chat interface for encrypted messaging, a file-sharing tool, and an explorer for inspecting blockchain and LEZ activity. The Logos Node provides an alternative entry point, starting the same runtime without a user interface. Because a distribution is defined by its selected modules rather than by its launcher, developers can assemble distributions for purposes other than the default configuration.

\ No newline at end of file diff --git a/img/favicon-dark.png b/img/favicon-dark.png new file mode 100644 index 00000000..37e64583 Binary files /dev/null and b/img/favicon-dark.png differ diff --git a/img/favicon.png b/img/favicon.png new file mode 100644 index 00000000..49673258 Binary files /dev/null and b/img/favicon.png differ diff --git a/img/home/build-an-app.png b/img/home/build-an-app.png new file mode 100644 index 00000000..1965e756 Binary files /dev/null and b/img/home/build-an-app.png differ diff --git a/img/home/contribute.png b/img/home/contribute.png new file mode 100644 index 00000000..73bdd32e Binary files /dev/null and b/img/home/contribute.png differ diff --git a/img/home/run-a-node.png b/img/home/run-a-node.png new file mode 100644 index 00000000..1b0c9aee Binary files /dev/null and b/img/home/run-a-node.png differ diff --git a/img/home/run-an-app.png b/img/home/run-an-app.png new file mode 100644 index 00000000..31019c6c Binary files /dev/null and b/img/home/run-an-app.png differ diff --git a/img/home/understand-logos.webp b/img/home/understand-logos.webp new file mode 100644 index 00000000..dbdcbc46 Binary files /dev/null and b/img/home/understand-logos.webp differ diff --git a/img/logo-dark.png b/img/logo-dark.png new file mode 100644 index 00000000..b36cccc1 Binary files /dev/null and b/img/logo-dark.png differ diff --git a/img/logo-light.png b/img/logo-light.png new file mode 100644 index 00000000..9496273b Binary files /dev/null and b/img/logo-light.png differ diff --git a/img/social-card.jpg b/img/social-card.jpg new file mode 100644 index 00000000..ffcb4482 Binary files /dev/null and b/img/social-card.jpg differ diff --git a/index.html b/index.html new file mode 100644 index 00000000..0c066533 --- /dev/null +++ b/index.html @@ -0,0 +1,4 @@ +Logos documentation + + + \ No newline at end of file diff --git a/lez/accounts/set-up-shared-private-lez-account/index.html b/lez/accounts/set-up-shared-private-lez-account/index.html new file mode 100644 index 00000000..59746141 --- /dev/null +++ b/lez/accounts/set-up-shared-private-lez-account/index.html @@ -0,0 +1,83 @@ +Set up a shared private account | Logos documentation + + +

Set up a shared private LEZ account

+

Get started with group-owned private accounts where every member independently derives the same keys.

+

This procedure covers how to create a shared private account (regular or PDA) on the LEZ that is jointly controlled by multiple members. From one 32-byte Group Master Secret (GMS), every member independently derives the same account keys (NSK, VSK, NPK, VPK), so any member can view and spend the shared balance without an interactive key exchange at spend time. It is intended for developers on testnet v0.2 who need multi-party custody of a private balance or a private PDA.

+

This feature is 1-of-n at the key layer: any GMS holder can derive every key and spend the account. Threshold gating must be implemented at the program layer. View-only membership is not supported — any GMS holder gets both viewing and spending capability.

+

Before you start, make sure you have the following:

+
    +
  • Linux or macOS — macOS requires full Xcode with the Metal toolchain for the Risc0 guest build, not just command-line tools
  • +
  • Rust toolchain and cargo
  • +
  • justfile recipes from the logos-execution-zone repository
  • +
  • A local clone of the LEZ repository and wallet set up and funded
  • +
+

What to expect

+
    +
  • You can create a shared private account from a single GMS so that every invited member independently derives the same NPK, VPK, NSK, and VSK without an interactive key exchange at spend time.
  • +
  • You can admit new members by sealing the GMS to their sealing public key and having them unseal it locally, with no shared secrets transmitted in the clear.
  • +
  • You can create a group-owned PDA family where each PDA is distinguished by an identifier derived from the same group keys.
  • +
+

Step 1: Generate a sealing key pair for each member

+

Each member who will join a group needs a one-time sealing key pair before the owner can invite them. The sealing key uses ML-KEM-768 and is kept separate from account viewing keys.

+
    +
  1. +

    Each joining member generates their sealing key pair:

    +
    wallet group new-sealing-key
    +
      +
    • This prints the sealing public key. Each member shares the public key with the group owner.
    • +
    • Run this once per wallet. The secret key is stored locally and never shared.
    • +
    +
  2. +
+

Step 2: Create the group and the shared account

+

The group owner creates a local group with a fresh random GMS, then derives a shared account from it. Any account derived from the same group will have identical keys for every member who holds the GMS.

+
    +
  1. +

    Create the group with a fresh random GMS:

    +
    wallet group new test-group
    +
      +
    • The group is registered locally and visible in wallet group list (alias ls).
    • +
    +
  2. +
  3. +

    Create a shared regular private account derived from the group:

    +
    wallet account new private-gms test-group --label shared-acc
    +
      +
    • +

      For a group-owned PDA instead of a regular private account, pass the PDA flags:

      +
      wallet account new private-gms test-group --pda \
      --seed <32-byte-hex> --program-id <program-id-hex> --identifier 0
      +

      --identifier diversifies one PDA from another within the same (program_id, seed) family; it defaults to a random value if omitted.

      +
    • +
    +
  4. +
+

Step 3: Invite a new member by sealing the GMS

+

The owner seals the GMS to each new member's sealing public key using ML-KEM-768. The sealed GMS is safe to transmit over any channel.

+
    +
  1. +

    Seal the group's GMS for the new member using their sealing public key:

    +
    wallet group invite test-group --key <joining-member-sealing-pubkey-hex>
    +
      +
    • Share this hex string with the new member over any channel. Repeat this step for each additional member.
    • +
    +
  2. +
+

Step 4: Join the group and derive the shared account

+

The new member unseals the GMS using their local sealing secret key and derives their instance of the shared account. The derived keys are identical to those held by every other member.

+
    +
  1. +

    Unseal the GMS and store it under a local group name:

    +
    wallet group join my-copy --sealed <sealed-gms-hex-from-owner>
    +
      +
    • The GMS is now stored locally under my-copy. Both the owner and this member now hold the same GMS.
    • +
    +
  2. +
  3. +

    Derive the shared account from the joined group:

    +
    wallet account new private-gms my-copy
    +
      +
    • This produces the same NPK, VPK, NSK, and VSK as the owner's account derived in Step 2.
    • +
    +
  4. +
\ No newline at end of file diff --git a/lez/follow-chain/run-lez-indexer/index.html b/lez/follow-chain/run-lez-indexer/index.html new file mode 100644 index 00000000..e5a294cd --- /dev/null +++ b/lez/follow-chain/run-lez-indexer/index.html @@ -0,0 +1,84 @@ +Run an LEZ Indexer | Logos documentation + + +

Run an LEZ Indexer

+

Get started with the LEZ Indexer and query finalized LEZ state over HTTP RPC.

+

The LEZ Indexer is a service that reads the finalized LEZ state from the Logos Blockchain node and exposes it over an HTTP RPC interface. Any application can use this interface to query blocks, transactions, and account state. This procedure walks you through running an LEZ Indexer locally alongside its dependencies, verifying it is indexing blocks, and querying account state through the RPC.

+

Before you start, make sure you have the following:

+ +

What to expect

+
    +
  • You can query finalized LEZ blockchain state including blocks, transactions, and accounts via HTTP RPC on http://localhost:8779.
  • +
  • You have a running local stack with the Logos Blockchain node, LEZ Sequencer, and LEZ Indexer coordinated through just commands.
  • +
  • You can verify indexer health and confirm it is progressing by calling the getLastFinalizedBlockId and checkHealth RPC methods.
  • +
+

Start the LEZ stack and run the indexer

+

The indexer depends on the Logos Blockchain Node and benefits from an LEZ Sequencer running alongside it. For a local deployment, start each service in its own terminal.

+
    +
  1. +

    In the LEZ repository, run just clean to remove any leftover artifacts from previous runs.

    +
  2. +
  3. +

    Replace the default value of bedrock_config.addr in lez/indexer/service/configs/debug/indexer_config.json with your Logos Blockchain endpoint and replace the default channel_id there with the LEZ's channel ID. Get the channel ID from the public LEZ sequencer by running:

    +
    curl https://testnet.lez.logos.co/ \
    -H "Content-Type: application/json" \
    -d "{ \
    \"jsonrpc\": \"2.0\", \
    \"method\": \"getChannelId\", \
    \"params\": {}, \
    \"id\": 1 \
    }"
    +

    For a local deployment, run the following commands in two separate terminal windows:

    +
    # Run node, in terminal 1
    just run-bedrock
    +
    # Terminal 2
    just run-sequencer
    + +
  4. +
  5. +

    In a third terminal window, start the LEZ Indexer:

    +
    just run-indexer
    +

    The indexer serves JSON-RPC over HTTP (and WebSocket) on http://localhost:8779 by default. At startup, you will see:

    +
    [2026-06-18T14:21:59Z INFO indexer_service] Starting Indexer Service RPC server on 0.0.0.0:8779
    [2026-06-18T14:21:59Z INFO indexer_core] Starting indexer from beginning of channel
    +

    The indexer then listens for block finalizations.

    +
  6. +
+

Verify the indexer is progressing

+
    +
  1. +

    Call getLastFinalizedBlockId to confirm the indexer is receiving blocks:

    +
    curl -X POST http://localhost:8779 \
    -H "Content-Type: application/json" \
    -d '{"jsonrpc":"2.0","method":"getLastFinalizedBlockId","params":[],"id":1}'
    +

    You will receive a response similar to:

    +
    {"jsonrpc":"2.0","id":1,"result":15}
    +

    The result value should increase each time you repeat the call.

    +
  2. +
  3. +

    Run the health check to confirm the indexer can reconstruct the full state from the database:

    +
    curl -X POST http://localhost:8779 \
    -H "Content-Type: application/json" \
    -d '{"jsonrpc":"2.0","method":"checkHealth","params":[],"id":1}'
    +

    A healthy indexer returns:

    +
    {"jsonrpc":"2.0","id":1,"result":null}
    +
    info

    checkHealth reconstructs the full LEZ state from the database and can produce heavy disk usage. Run it sparingly.

    +
  4. +
+

Query account state using the wallet and RPC

+
    +
  1. +

    In a fourth terminal window, query an account state by calling getAccount with the account ID:

    +
    curl -X POST http://localhost:8779 \
    -H "Content-Type: application/json" \
    -d '{"jsonrpc":"2.0","method":"getAccount","params":{"account_id":"2RHZhw9h534Zr3eq2RGhQete2Hh667foECzXPmSkGni2"},"id":1}'
    +

    You will receive a response similar to:

    +
    {"jsonrpc":"2.0","id":1,"result":{"program_owner":"FeYNA4PVvs1SsSuECrmQNnJqtB1jA6CbVBBSVGos7qcu","balance":20000,"data":"","nonce":1}}
    +
  2. +
  3. +

    Run just run-wallet --help to see all available wallet commands.

    +
  4. +
+

Frequently asked questions

+

Why is there a delay between sending a transaction and seeing it in the indexer?

+

The delay is caused by Logos Blockchain's block finality time. The indexer only processes finalized blocks from the L1 node, so transactions sent through the sequencer are not visible until the corresponding block is finalized. This is expected behavior.

+

How do I change the indexer configuration?

+

The indexer config used by just run-indexer is located at lez/indexer/service/configs/debug/indexer_config.json (a configs/docker/indexer_config.json variant is used for the Docker setup). Two fields control connectivity:

+
    +
  • bedrock_config — the HTTP address of the Logos Blockchain node.
  • +
  • channel_id — the channel that LEZ Sequencer writes into. This must match the sequencer you are using.
  • +
\ No newline at end of file diff --git a/lez/follow-chain/start-and-use-instance-of-lez-explorer/index.html b/lez/follow-chain/start-and-use-instance-of-lez-explorer/index.html new file mode 100644 index 00000000..a43aff8c --- /dev/null +++ b/lez/follow-chain/start-and-use-instance-of-lez-explorer/index.html @@ -0,0 +1,56 @@ +Start and use an instance of the LEZ Explorer | Logos documentation + + +

Start and use an instance of the LEZ Explorer

+

Get a clear view of on-chain activity including blocks, transactions, and accounts.

+

The LEZ Explorer lets you inspect the state of the Logos Execution Zone in real time. This procedure walks you through starting all required services locally and using the Explorer UI to browse blocks, search transactions, and look up account balances. It is intended for developers working on testnet v0.2 who want to verify on-chain state or test wallet interactions.

+
info

If you don't want to run your own LEZ explorer instance, navigate to the public LEZ explorer.

+

Before you start, make sure you have the following:

+ +

What to expect

+
    +
  • You can browse all committed blocks and click through to inspect individual block details.
  • +
  • You can search for transactions and accounts and view their current state and balances.
  • +
+

Start the LEZ Explorer

+
    +
  1. +

    Navigate to your local clone of the LEZ repository.

    +
  2. +
  3. +

    In a new terminal window, start the LEZ Explorer:

    +
    just run-explorer
    +
    info

    By default, the LEZ Explorer connects to the LEZ Indexer at http://localhost:8779. Set the INDEXER_RPC_URL environment variable or pass --indexer-rpc-url to use a different address.

    +
  4. +
  5. +

    Open http://localhost:3000/ in your browser.

    +

    You will see a No blocks found message initially:

    +

    No blocks found screen

    +

    After approximately one minute, refresh the page. Blocks will appear:

    +

    Blocks visible after refresh

    +
  6. +
+

Browse blocks, transactions, and accounts

+
    +
  1. +

    Click any block in the list to view its details.

    +

    Block detail view

    +
  2. +
  3. +

    Use the search bar to look up a block, transaction, or account by ID.

    +

    Transaction search result

    +

    Account detail view

    +
  4. +
+

Troubleshooting LEZ Explorer

+

Why does the Explorer show "No blocks found" after startup?

+

Block production takes up to one minute after the stack starts. Refresh the page manually; live updates are not yet implemented.

+

Why does LEZ Explorer fail to load block data?

+

Confirm that the LEZ Indexer is running and reachable. By default, the Explorer connects to http://localhost:8779. Check that just run-indexer is active in its terminal and that no firewall rule is blocking port 8779.

\ No newline at end of file diff --git a/lez/get-started/connect-wallet-cli-to-lez-testnet/index.html b/lez/get-started/connect-wallet-cli-to-lez-testnet/index.html new file mode 100644 index 00000000..f65fc530 --- /dev/null +++ b/lez/get-started/connect-wallet-cli-to-lez-testnet/index.html @@ -0,0 +1,39 @@ +Connect the wallet CLI to the LEZ testnet | Logos documentation + + +

Connect the wallet CLI to the LEZ testnet

+

Try the wallet CLI against the live LEZ testnet instead of a local sequencer.

+

This procedure explains how to install the wallet CLI from the LEZ repository and point it at the LEZ testnet sequencer. It is intended for developers who have previously run the wallet against a local sequencer and want to test against testnet v0.2 instead.

+

What to expect

+
    +
  • You can run wallet commands against the live LEZ testnet sequencer.
  • +
  • Your wallet is configured to target https://testnet.lez.logos.co as the sequencer address.
  • +
+

Install the wallet and connect it to the testnet

+
    +
  1. +

    In the LEZ repository, check out the correct release tag:

    +
    git clone https://github.com/logos-blockchain/logos-execution-zone.git
    cd logos-execution-zone
    git checkout v0.2.0
    +
  2. +
  3. +

    Rename the existing wallet directory (if you have one) to avoid conflicts:

    +
    mv ~/.nssa/wallet ~/.nssa/wallet.old 2>/dev/null || true
    +
  4. +
  5. +

    Install the wallet CLI:

    +
    cargo install --path lez/wallet --force
    +
  6. +
  7. +

    Set the testnet sequencer address:

    +
    wallet config set sequencer_addr https://testnet.lez.logos.co
    +
  8. +
+

Verify the connection

+
    +
  1. +

    Run the health check command:

    +
    wallet check-health
    +

    A successful connection returns:

    +
    ✅All looks good!
    +
  2. +
\ No newline at end of file diff --git a/lez/get-started/initiate-native-token-transfers-on-lez-with-wallet-ui/index.html b/lez/get-started/initiate-native-token-transfers-on-lez-with-wallet-ui/index.html new file mode 100644 index 00000000..3cb289e5 --- /dev/null +++ b/lez/get-started/initiate-native-token-transfers-on-lez-with-wallet-ui/index.html @@ -0,0 +1,106 @@ +Initiate native token transfers on the LEZ with the wallet UI | Logos documentation + + +

Initiate native token transfers on the LEZ with the wallet UI

+

Use the wallet UI to set up accounts and try every type of native token transfer on the LEZ testnet.

+

The wallet UI is a simple entrypoint for getting started on the LEZ. This procedure walks you through running the wallet UI on your machine, syncing it with the public LEZ testnet, creating accounts, and executing all combinations of public, private, shielded, and deshielded native token transfers. The LEZ testnet runs the centralised LEZ sequencer, which processes the transactions the wallet UI submits, while the wallet UI itself manages your accounts locally and can execute transfers with any combination of private and public accounts.

+
info

Recovery from mnemonic and withdrawals to the L1 are not yet supported.

+

Before you begin, ensure you have:

+
    +
  • +

    Nix with flakes enabled. Install from nixos.org, then enable flakes:

    +
    mkdir -p ~/.config/nix
    echo 'experimental-features = nix-command flakes' >> ~/.config/nix/nix.conf
    +

    Verify: nix flake --help >/dev/null 2>&1 && echo "Flakes enabled"

    +
  • +
+

What to expect

+
    +
  • You can run the wallet UI locally and sync it with the live LEZ testnet.
  • +
  • You have created and funded accounts that you can use to test transfers.
  • +
  • You can complete public, private, shielded, and deshielded token transfers and verify finalisation on the L1.
  • +
+

Set up and sync the wallet UI

+

This task clones the wallet UI, starts it, and syncs it with the LEZ testnet.

+
    +
  1. +

    Clone the repository and start the wallet UI.

    +
    git clone https://github.com/logos-blockchain/logos-execution-zone-wallet-ui.git
    cd logos-execution-zone-wallet-ui
    nix run
    +
  2. +
+
info

On a cold Nix cache, the first run compiles the wallet UI from source (Qt/C++ and Rust dependencies). This can take 20–60 minutes. Subsequent runs are instant from cache.

+
    +
  1. +

    In the setup screen, enter paths for the config file and the storage file.

    +
      +
    • The wallet UI creates these files at the paths you enter if they don't already exist.
    • +
    • After the first run, the wallet UI remembers these paths and skips the setup screen on subsequent runs.
    • +
    +

    Wallet setup screen

    +
  2. +
  3. +

    Choose a password.

    +
  4. +
  5. +

    Click Create wallet.

    +
  6. +
  7. +

    The wallet UI ships with a set of predefined, funded public and private accounts that are shared between all wallet UI users on the testnet and act as a common faucet. Wait for the wallet UI to sync these private account values from the genesis block.

    +

    A progress bar shows the number of blocks processed, and this step can take a few minutes.

    +

    Sync progress screen

    +

    The list of accounts and their balances displays once the sync finishes.

    +

    Account list with balances

    +
  8. +
+

Create a new public account

+

Unlike the predefined accounts on the wallet, accounts you create yourself are controlled only by you and aren't shared.

+
    +
  1. +

    Click Create account.

    +
  2. +
  3. +

    Select Public.

    +

    Create account menu

    +
  4. +
  5. +

    Click Create.

    +

    The new public account will appear in your account list.

    +

    New public account in the list

    +
  6. +
+

Send a public transfer

+

This task moves tokens from a funded predefined account to the public account you created.

+
    +
  1. +

    In the From field, choose an account with a positive balance.

    +
  2. +
  3. +

    Next to your newly created account, click the copy button to copy its account ID.

    +
  4. +
  5. +

    In the recipient field, paste the account ID.

    +
  6. +
  7. +

    Click Send.

    +

    Public transfer screen

    +

    A transaction hash displays below the Send button. Use its copy button to copy the hash for the next task.

    +
  8. +
+

Verify L1 finalisation

+
    +
  1. +

    In your browser, navigate to https://explorer.testnet.lez.logos.co/transaction/<TX_HASH>, replacing <TX_HASH> with the transaction hash from the previous task.

    +

    The transaction details display once the transaction is finalised on the L1. Finalisation isn't immediate; it can take around 10 minutes to display.

    +

    Transaction details on the explorer

    +
  2. +
+

Try private, shielded, and deshielded transfers

+

Repeat the create-account and transfer tasks for the other account types and transfer flows.

+
    +
  1. Create one or more private accounts using the same Create account flow, selecting Private instead of Public.
  2. +
  3. Send a private transfer from a private sender to a private recipient.
  4. +
  5. Send a shielded transfer from a public sender to a private recipient.
  6. +
  7. Send a deshielded transfer from a private sender to a public recipient.
  8. +
+

Troubleshooting wallet UI setup

+

Why doesn't the setup screen show?

+

The setup screen is skipped if the config file at ~/.config/Logos/ExecutionZoneWalletUI.conf already lists paths for the storage and config files, typically from an earlier run of the wallet UI. Remove ~/.config/Logos/ExecutionZoneWalletUI.conf to see the setup screen again.

\ No newline at end of file diff --git a/lez/get-started/introduction-to-the-logos-execution-zone/index.html b/lez/get-started/introduction-to-the-logos-execution-zone/index.html new file mode 100644 index 00000000..a63a869c --- /dev/null +++ b/lez/get-started/introduction-to-the-logos-execution-zone/index.html @@ -0,0 +1,32 @@ +Introduction to the Logos Execution Zone | Logos documentation + + +

Introduction to the Logos Execution Zone

+

Understand how the Logos Execution Zone runs general-purpose applications with selective privacy.

+

The Logos Execution Zone (LEZ) is the primary execution layer for applications built on the Logos stack. It is implemented as a Zone on the Logos Blockchain and uses zero knowledge proofs to ensure the correctness of its operations. The LEZ runs a Risc0-based virtual machine, the Logos Execution Environment (LEE), which separates state into public and private components that LEE programs can use and modify seamlessly. This selective privacy lets developers write generic programs while the LEE guarantees privacy and correctness.

+

The basics

+
    +
  • The LEZ is the flagship Zone for general-purpose applications on Logos, with built-in support for private execution.
  • +
  • The LEZ separates state into public and private accounts. Public executions are validated by re-execution, and private executions are proven with zero knowledge proofs.
  • +
  • The LEZ supports applications with high transaction throughput, and inscribes its state updates on the Logos Blockchain through the LEZ channel.
  • +
+

Accounts Model

+

To achieve separation between public and private state while allowing for full programmability, the LEE uses a stateless program model. Under this model, all persistent data is stored in accounts, which can be either public or private. Public accounts are stored on-chain as a map between the account ID and its associated state (such as a token balance). A public account owner’s private key signs transactions and authorises executions, while the derived account ID is publicly visible on-chain.

+

Private accounts, by contrast, are stored locally on the account-holder’s node, which publishes commitments to the account state onto the chain whenever the state is updated. The latest commitment binds the current account state to the chain without revealing its data, while nullifiers for previous commitments ensure that old commitments are not used for program execution. Private accounts are created with two associated key pairs.

+
    +
  • Nullifier keys: The private nullifier key is used by the account owner to sign transactions and authorise executions. The public nullifier key is used as the account ID for verifying ownership.
  • +
  • Viewing keys: The private viewing key is used by the account owner to create ZK proofs. The public viewing key is used to verify proofs without revealing the account owner.
  • +
+
info

A program must obtain a private account’s nullifier public key, as well as its viewing public key, in order to modify a private account state. Therefore, you cannot transfer tokens to a private account without being provided this information by the owner.

+

When an LEE program executes, it modifies the accounts provided to it. These accounts could be either public or private, as an LEE program can be executed over both types of accounts. In either case, the program is executed using the same bytecode, the main difference being whether the user who runs a program will generate a zero knowledge proof. Executions modifying public accounts are executed transparently by LEZ validators like any standard RISC-V VM call. On the other hand, private executions involve the generation of zero knowledge proofs via Risc0, which are then verified by LEZ validators.

+

LEZ Node Architecture

+

The LEZ operates as a Zone, with updates to its state (both public and private) inscribed on the Logos Blockchain. The LEZ is sequenced in a decentralised manner by a permissioned set of sequencer nodes, who take turns posting the latest updates to the LEZ channel on Mantle. In addition to the sequencers, the LEZ maintains an open network of validators, which update the Zone state by transparently executing programs (for public executions) or by verifying zero knowledge proofs of correct state transitions (for private executions). Anybody can join the validator network and contribute to the correctness of the LEZ state, submitting the latest state to an LEZ sequencer to post on the blockchain. The LEZ also supports read-only indexer nodes that follow the LEZ channel for purposes such as block exploration and development.

+

Use Cases

+

The LEZ is a ready-made platform for privacy-preserving applications built using the Logos stack. Since the same LEE program can be executed over both public and private accounts, computationally costly private executions can be used only when necessary to speed up execution. Together with Solana-style parallel execution, this feature makes the LEZ uniquely suited to applications requiring high throughput as well as private application state.

+

As an example, the LEZ could be used to host private DeFi applications. With support for privacy built-in, the LEZ could be used to build protocols where transaction details, user balances, or trading strategies are kept confidential by default, only revealing necessary information to authorised parties. This is especially so when high volumes of transactions are expected by these applications. Examples may include:

+
    +
  • Private Exchanges: Trading platforms where order books or individual trades are obscured.
  • +
  • Confidential Lending/Borrowing Protocols: Financial services where loan details or collateral information are private.
  • +
  • Confidential Transfers: Securely sending and receiving digital assets with transaction details obscured using zero knowledge proofs, ensuring financial privacy for individuals and businesses.
  • +
+
tip

Check out the tutorial on how to create your own Automated Market Maker (AMM) trading pool program on the LEZ.

\ No newline at end of file diff --git a/lez/get-started/lez-quickstart/index.html b/lez/get-started/lez-quickstart/index.html new file mode 100644 index 00000000..172c4829 --- /dev/null +++ b/lez/get-started/lez-quickstart/index.html @@ -0,0 +1,173 @@ +Quickstart for the Logos Execution Zone wallet | Logos documentation + + +

Quickstart for the Logos Execution Zone wallet

+

Set up the wallet, connect to a sequencer, and run a minimal transfer flow.

+
warning

This page should be accurate for the specific version referenced in this doc, but it may not have been run end-to-end as written. Expect minor gaps (for example, missing environment details) and be prepared to troubleshoot. We are actively working to complete and verify this content.

+
info
+
tip

A public LEZ testnet is available. This quickstart runs against a local sequencer you start yourself, but you can browse live testnet activity in the block explorer, and the testnet sequencer is reachable at its public RPC endpoint.

+

The Logos Execution Zone (LEZ, for short) is a programmable blockchain that records transactions, maintains public on-chain state, and exposes a sequencer endpoint that clients (like a wallet) can submit transactions to.

+

LEZ separates account state into public (visible, on-chain) and private (hidden, off-chain). You choose which one you are using by creating a public or private account and using it in transactions. This ability to maintain a public and private state is provided by the Logos Execution Environment (LEE), that defines what an account is, how transactions are structured, and how executions are validated when some data must remain private. You can think of LEZ as the blockchain you connect to and where transactions are recorded, and LEE as the execution model that powers it.

+
info

This quickstart covers the public wallet flow only so you can get set up quickly. Privacy-preserving transfers require local proof generation and take longer to run. For the private workflow, see Transfer native tokens on the Logos Execution Zone.

+

When a transaction touches the private state, the client runs the private part locally using your private keys and local client data, producing a zero-knowledge proof (ZKP). Validators verify the proof and accept the state update (for example, updating public balances), so the network stays correct even though the private data is never published.

+
info

In the context of the Logos Execution Zone, a zero-knowledge proof (ZKP) is a cryptographic proof that lets a blockchain client, such as a wallet, prove a private transaction followed LEE’s rules without revealing the private inputs (like balances). Using ZKPs, LEZ can safely accept the resulting state update and keep the public chain consistent with private execution, even though the network never sees the private values.

+

In this quickstart, you install the wallet tooling, connect to a local sequencer endpoint, and complete a minimal transfer flow with balance checks. In wallet terms, the wallet client is your control panel for the system: you install it, create and manage public or private accounts, sync private state, and send commands.

+

Before you start

+
    +
  • This guide is intended for a developer audience with CLI-first workflow familiarity.
  • +
  • You should have a basic knowledge of blockchain concepts like accounts, transactions, and balances.
  • +
  • You will use the Rust toolchain to complete this tutorial, but you don't need prior Rust experience.
  • +
  • Commands assume a bash-compatible shell.
  • +
+

Step 1: Install the prerequisites

+

To run the LEZ wallet CLI, you first need to install system dependencies, the Rust toolchain, and the Logos Blockchain circuits files.

+

Install system build dependencies

+

Install the build prerequisites you need to compile the sequencer and wallet.

+
tip

These prerequisites include a working C toolchain and linker on your machine. You may already have these installed if you have experience building software from source.

+

Choose the instructions for your operating system:

+
# Ubuntu / Debian
sudo apt update
apt install git curl build-essential clang libclang-dev pkg-config libssl-dev
+
# Fedora
sudo dnf install git curl gcc glibc-devel clang clang-devel pkgconf-pkg-config openssl-devel llvm-libs
+
# macOS
xcode-select --install
brew install pkg-config openssl
+

Install Rust and RISC Zero components

+
info

Rust is the language used for wallet development, while RISC Zero is the proof toolchain used to generate the ZKPs.

+
    +
  1. +

    Install Rust with rustup:

    +
    # Install the official Rust compiler. -y installs non-interactively (required in CI/Docker, where there is no TTY for the rustup TUI).
    curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable
    +
  2. +
  3. +

    Install the RISC Zero components:

    +
    # Source the env file under $HOME/.cargo and install the RISC Zero component
    . "$HOME/.cargo/env"
    curl -L https://risczero.com/install | bash
    +
  4. +
  5. +

    Restart your shell to ensure the cargo and rzup commands are available.

    +
  6. +
  7. +

    Add the RISC Zero components to your Rust toolchain with rzup:

    +
    rzup install
    +
  8. +
+

Set up the wallet binary prerequisites and build the wallet

+

The Logos Blockchain repository provides a script that downloads a circuits release required by the wallet build.

+
tip

"Circuits" are prebuilt files used for privacy-preserving execution (zero-knowledge proofs). Even though the quickstart flow uses public transactions, the current wallet build still requires these files to be present locally.

+
    +
  1. +

    Create a workspace folder and clone the Logos Blockchain repository:

    +
    mkdir -p ~/logos/src
    cd ~/logos/src
    git clone https://github.com/logos-blockchain/logos-blockchain.git
    +
  2. +
  3. +

    Run the script to download the circuits release:

    +
    info
    +
  4. +
+

This script downloads logos-blockchain-circuits-<version>-<platform>.tar.gz and installs it under ~/.logos-blockchain-circuits by default. +:::

+
cd logos-blockchain
./scripts/setup-logos-blockchain-circuits.sh
+
    +
  1. +

    From the same workspace folder, clone the Logos Execution Zone repository:

    +
    cd ~/logos/src
    git clone https://github.com/logos-blockchain/logos-execution-zone.git
    cd logos-execution-zone
    git checkout v0.2.0
    +
  2. +
  3. +

    From the repository root, install the wallet CLI:

    +
    cargo install --path lez/wallet --force
    +
  4. +
  5. +

    Confirm that the wallet command is available:

    +
    wallet help
    +
  6. +
+

Step 2: Start the LEZ sequencer in standalone mode

+

Open a new terminal window and start the LEZ sequencer from the root of the Logos Execution Zone repository:

+
cd ~/logos/src/logos-execution-zone
RUST_LOG=info cargo run --features standalone -p sequencer_service lez/sequencer/service/configs/debug/sequencer_config.json
+
info

This quickstart uses standalone mode, which runs only the LEZ sequencer locally. The full local stack also runs a Logos Blockchain node and the indexer service for development and block exploration, but it adds extra components and is covered separately.

+

You should see the sequencer starting up at localhost:3040 and logging information to the terminal:

+
[2026-02-24T16:27:58Z INFO sequencer_runner] HTTP server started
[2026-02-24T16:27:58Z INFO actix_server::server] Tokio runtime found; starting in existing Tokio runtime
[2026-02-24T16:27:58Z INFO actix_server::server] starting service: "actix-web-service-0.0.0.0:3040", workers: 4, listening on: 0.0.0.0:3040
[2026-02-24T16:27:58Z INFO sequencer_runner] Starting main sequencer loop
[2026-02-24T16:27:58Z INFO sequencer_runner] Starting pending block retry loop
[2026-02-24T16:27:58Z INFO sequencer_runner] Starting bedrock block listening loop
[2026-02-24T16:27:58Z INFO sequencer_runner] Sequencer running. Monitoring concurrent tasks...
+

Step 3 (optional): Configure the wallet home directory

+

The wallet reads its configuration from a "wallet home" directory. If the NSSA_WALLET_HOME_DIR environment variable is not set, it falls back to ~/.nssa/wallet.

+

If you want the wallet to initialise in a different location, set the variable before continuing. For example, to set the wallet home to a .wallet-home folder in the current directory, run:

+
export NSSA_WALLET_HOME_DIR="$PWD/.wallet-home"
+
info

The wallet help output incorrectly states that NSSA_WALLET_HOME_DIR "must be set." In practice, the binary falls back to ~/.nssa/wallet when unset, as described above. The mismatch is in the help string.

+

Step 4: Initialise the wallet local storage and verify connectivity

+

The wallet persistent storage is defined by the storage.json file. When you run any wallet subcommand, the wallet checks whether storage.json exists in the wallet home directory. If it does not exist, it requires a password to initialise the wallet storage.

+
tip

Leave the sequencer running in the other terminal window while you initialise the wallet storage.

+

Run a wallet command to initialise the storage. Use the built-in health check:

+
wallet check-health
+

If the wallet storage was not previously initialised, this command prints Persistent storage not found, need to execute setup, and prompts you to create a password. You can choose any password you like, but make sure to remember it, as you will need it to access the wallet in the future.

+
warning

The wallet uses this password as a seed to deterministically generate your public and private key trees. The wallet stores the derived key material and local state in storage.json under the wallet home directory.

+

Step 5: Complete a minimal wallet flow

+

In this flow, you create and initialise an account, claim testnet funds, send a transfer, and confirm resulting balances.

+

In this task, wallet account and transfer commands interact with the authenticated-transfer program, and sequencer processing determines the resulting account state. Public and private account paths share command patterns, while private paths can include local proof generation.

+

Create and initialise the sender public account

+
    +
  1. +

    Create a sender public account and record the account_id value:

    +
    cd ~/logos/src/logos-execution-zone
    wallet account new public
    +
  2. +
  3. +

    Using the sender public account_id from the previous step, check the sender status:

    +
    wallet account get --account-id <sender_public_account_id>
    +

    Example:

    +
    wallet account get --account-id Public/14TYHiuzKiNR1ydETpr9mJMkjY6jf1hQFZ11d3X8Tc7N
    +

    You should see Account is Uninitialized in the output. New accounts start uninitialised, so no program owns them yet. A program can claim an uninitialised account (for example, the authenticated-transfer program or the token program). After a program claims an account, only that program can modify the account state. LEZ makes one exception for account credits, where any program can credit native tokens to any account. For account debits, LEZ requires the owning program.

    +
  4. +
  5. +

    Initialise the sender account, then check the updated state:

    +
    info
    +
  6. +
+

Running wallet auth-transfer init initialises the sender account under the authenticated-transfer program, so the account can debit native tokens when you send transfers. +:::

+
wallet auth-transfer init --account-id <sender_public_account_id>
+

In the output, you should see status: "Transaction submitted", and the transaction hash. If you change to the terminal session where the sequencer is running, you can see a message similar to this: Validated transaction with hash <hash_id>, including it in block.

+
    +
  1. +

    Check the account updated state:

    +
    wallet account get --account-id <sender_public_account_id>
    +

    In the output you should see Account owned by authenticated transfer program, with "balance":0.

    +
  2. +
+

Claim funds using the Piñata faucet

+

"Piñata" is the name of the LEZ-specific testnet faucet program that funds accounts with native tokens.

+
    +
  1. +

    Fund the sender account via Piñata:

    +
    # This may take a few seconds to complete
    wallet pinata claim --to <sender_public_account_id>
    +
  2. +
  3. +

    Check the sender account balance:

    +
    wallet account get --account-id <sender_public_account_id>
    +

    In the output you should see Account owned by authenticated transfer program, with a "balance":150.

    +
  4. +
+

Create and fund the recipient public account

+
    +
  1. +

    Create a recipient public account and record the account_id value. Complete this step in the same terminal session as the sender account commands to avoid exporting NSSA_WALLET_HOME_DIR again.

    +
    wallet account new public
    +
  2. +
  3. +

    Send 37 tokens from sender to recipient:

    +
    wallet auth-transfer send \
    --from <sender_public_account_id> \
    --to <recipient_public_account_id> \
    --amount 37
    +

    Example:

    +
    wallet auth-transfer send \
    --from Public/14TYHiuzKiNR1ydETpr9mJMkjY6jf1hQFZ11d3X8Tc7N \
    --to Public/74zHyMW81mtfcd6VMaLnpnAna8k2V4AN2Ygyy9LcEAQQ \
    --amount 37
    +
  4. +
  5. +

    Check sender and recipient balances:

    +
    # Sender account
    wallet account get --account-id <sender_public_account_id>
    +
  6. +
+

This should show a "balance":113 (150 - 37 = 113).

+
# Recipient account
wallet account get --account-id <recipient_public_account_id>
+

This should show a "balance":37.

+

Next steps

+
\ No newline at end of file diff --git a/lez/transfer-tokens/create-and-transfer-custom-tokens-on-the-logos-execution-zone/index.html b/lez/transfer-tokens/create-and-transfer-custom-tokens-on-the-logos-execution-zone/index.html new file mode 100644 index 00000000..21231237 --- /dev/null +++ b/lez/transfer-tokens/create-and-transfer-custom-tokens-on-the-logos-execution-zone/index.html @@ -0,0 +1,105 @@ +Create and transfer custom tokens on the Logos Execution Zone | Logos documentation + + +

Create and transfer custom tokens on the Logos Execution Zone

+

Use the wallet CLI to create custom tokens and transfer them between public and private accounts.

+
warning

This page is an early draft and may be incomplete or incorrect. Expect changes, missing prerequisites, and commands that might not work in your setup. We are actively working to complete and verify this content.

+
info
+

The Logos Execution Zone (LEZ) is a programmable blockchain that cleanly separates public and private state while keeping them fully interoperable. It's a component of the Logos project. You can use the wallet CLI to invoke LEZ's token program to create and transfer custom tokens between public and private accounts on LEZ.

+

The token program is a built-in LEZ program that provides standard token functionality, including defining new token assets and transferring balances. It uses a single shared program rather than requiring a separate contract deployment for each token. The token program is privacy-agnostic. You use the same instructions whether execution is public (on-chain) or privacy-preserving (off-chain with a zero-knowledge proof). The protocol decides the execution mode, and the token logic is unchanged.

+

Token program accounts fall into two types:

+
    +
  • Token definition account +
      +
    • Each token has exactly one token definition account.
    • +
    • It defines the token globally (the “token type”).
    • +
    • Its address is the token identifier (similar to a mint address).
    • +
    • The token program owns it.
    • +
    • It stores metadata including the token name and total supply.
    • +
    • It can be public or private.
    • +
    +
  • +
  • Token holding account +
      +
    • Each token can have multiple token holding accounts.
    • +
    • Any account that holds a balance of a token is a token holding account for that token.
    • +
    • It stores the token definition ID and the balance the account currently controls.
    • +
    • The token program owns it.
    • +
    • Only the token program can modify it. Modifications happen through token program executions authorised by the account’s keys.
    • +
    • It can be public or private.
    • +
    +
  • +
+
danger

Transfers are irreversible. Double-check all details before proceeding.

+

Before you begin, ensure that you have the following:

+ +

What to expect

+
    +
  • The token program can move balances between token holding accounts.
  • +
  • If the recipient account is uninitialised, the token program will automatically claim it. After initialisation, only the token program can modify the account.
  • +
  • You can transfer custom tokens to any uninitialised public or private account. Once the account is initialised, it can only receive tokens of the same definition ID. (A token holding account stores the balance for exactly one token definition ID.)
  • +
+
info

Currently, it's impossible to change the token name or total supply after you create the token.

+

Step 1: Create a token

+
    +
  1. +

    Create two new, uninitialised accounts: one token definition account and one token holding account to receive the total supply. Both accounts can be public or private, depending on your needs.

    + +
    wallet account new public
    +
      +
    • Private account:
    • +
    +
    wallet account new private
    +

    If you create a public account, the output is the account ID. If you create a private account, the output includes the account ID, nullifier public key (npk), and viewing public key (vpk).

    +
  2. +
+
info

Your account keys and data are stored in the local file $HOME/.nssa/wallet/storage.json.

+
    +
  1. +

    Use the wallet account ls command to confirm the accounts are created successfully. You should see a list showing all of your accounts.

    +
    wallet account ls
    +
  2. +
  3. +

    Create the token using the token definition account and token holding account you just created. Replace NAME with your token name, AMOUNT with the total supply, ACCOUNT-TYPE with the type of the account (public or private), and ACCOUNT-ID with the appropriate account IDs.

    +
    wallet token new \
    --name NAME \
    --total-supply AMOUNT \
    --definition-account-id ACCOUNT-TYPE/ACCOUNT-ID \
    --supply-account-id ACCOUNT-TYPE/ACCOUNT-ID
    +
  4. +
+

For example,

+
```sh
wallet token new \
--name TOKENA \
--total-supply 1337 \
--definition-account-id Public/4X9kAcnCZ1Ukkbm3nywW9xfCNPK8XaMWCk3zfs1sP4J7 \
--supply-account-id Public/9RRSMm3w99uCD2Jp2Mqqf6dfc8me2tkFRE9HeU2DFftw
```
+
    +
  1. +

    Use the wallet account get command to check the status of the token definition account and token holding account. You can see their account types and the token metadata.

    +
    wallet account get --account-id ACCOUNT-TYPE/ACCOUNT-ID
    +
  2. +
+

The output for the definition account and holding account looks like the following, respectively:

+
```text
Definition account owned by token program
{"Fungible":{"name":"TOKENA","total_supply":1337,"metadata_id":null}}
```

```text
Holding account owned by token program
{"Fungible":{"definition_id":"4X9kAcnCZ1Ukkbm3nywW9xfCNPK8XaMWCk3zfs1sP4J7","balance":1337}}
```
+
tip

When checking the status of a private account, the wallet account get command doesn't query the network. It works offline because private account data lives only in your wallet storage. Other users cannot read your private balances using this command and your private account ID.

+

Step 2: Transfer tokens

+

When transferring custom tokens using the wallet token send command, you specify the sender and recipient accounts with the account IDs. Both accounts can be public or private, but they must have the same token definition ID.

+
info

Transfers involving private accounts may take a few minutes because the wallet needs to generate a local proof.

+
    +
  1. +

    Use the wallet token send command to transfer custom tokens. Replace ACCOUNT-TYPE with the type of the account (public or private) and TOKEN-AMOUNT with the amount of tokens to transfer.

    +
    wallet token send \
    --from ACCOUNT-TYPE/SENDER-ACCOUNT-ID \
    --to ACCOUNT-TYPE/RECIPIENT-ACCOUNT-ID \
    --amount TOKEN-AMOUNT
    +
  2. +
+

For example, to transfer 1000 tokens from a private token holding account to a public recipient account, run:

+
```sh
wallet token send \
--from Private/HMRHZdPw4pbyPVZHNGrV6K5AA95wACFsHTRST84fr3CF \
--to Public/88f2zeTgiv9LUthQwPJbrmufb9SiDfmpCs47B7vw6Gd6 \
--amount 1000
```
+
    +
  1. +

    Use the wallet account get command to check the status of the sender and recipient accounts. You can see the updated token balances.

    +
    wallet account get --account-id ACCOUNT-TYPE/ACCOUNT-ID
    +
  2. +
+

The output looks like this:

+
```text
Holding account owned by token program
{"Fungible":{"definition_id":"4X9kAcnCZ1Ukkbm3nywW9xfCNPK8XaMWCk3zfs1sP4J7","balance":1000}}
```
+
tip

You can also use the wallet account ls -l command to check the balances of all your accounts at once.

\ No newline at end of file diff --git a/lez/transfer-tokens/transfer-native-lez-tokens-between-public-private-states/index.html b/lez/transfer-tokens/transfer-native-lez-tokens-between-public-private-states/index.html new file mode 100644 index 00000000..fe197035 --- /dev/null +++ b/lez/transfer-tokens/transfer-native-lez-tokens-between-public-private-states/index.html @@ -0,0 +1,74 @@ +Transfer native LEZ tokens between public and private states | Logos documentation + + +

Transfer native LEZ tokens between public and private states

+

Get started with private transfers to accounts you don't control, using a recipient-published keypair.

+

This procedure covers how to credit a private account — regular or a Program Derived Address (PDA) — that you do not control, using only the recipient's published keypair and the sender's chosen identifier. It is intended for wallet users on testnet v0.2 who need to make private payments without interactive setup or per-sender account registration. For example, a recipient can publish one keypair and receive from many independent senders, each into a separate account.

+

Before you start, make sure you have the following:

+
    +
  • Linux or macOS — macOS requires full Xcode with the Metal toolchain for the Risc0 guest build, not just command-line tools
  • +
  • Rust toolchain, cargo
  • +
  • A local clone of the LEZ repository and wallet set up and funded
  • +
+

What to expect

+
    +
  • You can credit a recipient's private account using only their published NPK and VPK, with no interactive setup required.
  • +
  • You can receive tokens into up to 2^128 distinct accounts from the same keypair by varying the identifier, so one published key serves many independent senders.
  • +
  • You can discover and spend incoming funds with wallet account sync-private after the sender's transaction is confirmed.
  • +
+

Step 1: Publish a reusable keypair as the recipient

+

The recipient generates a reusable keypair (NPK and VPK) and shares it with senders.

+
    +
  1. +

    Generate and publish a reusable keypair:

    +
    wallet account new private-accounts-key
    +
      +
    • This prints the chain_index, NPK, and VPK. No account is bound to the keypair yet.
    • +
    +
  2. +
  3. +

    Export the keys to share with senders:

    +
    wallet account show-keys --account-id <recipient-account-or-label>
    +
      +
    • This prints NPK (hex) then VPK (hex). Save the output to a file to use with --to-keys later.
    • +
    +
  4. +
+

Step 2: Send funds to the recipient's private account

+

The sender credits the recipient's account at a chosen identifier. The circuit initialises the account, emits its commitment and a deterministic nullifier, and encrypts the post-state to the recipient via ephemeral ECDH against the recipient VPK.

+
warning

Two senders who independently pick the same identifier for the same NPK target the same account. The second transfer fails at the commitment/nullifier layer. Use high-entropy identifiers to avoid collisions.

+
    +
  1. +

    Send funds to the recipient's private account using their published keys:

    +
    wallet auth-transfer send \
    --from <sender-account-or-label> \
    --to-npk <recipient-npk-hex> \
    --to-vpk <recipient-vpk-hex> \
    --to-identifier <u128> \
    --amount 100
    +
      +
    • Pass --to-keys <file> instead of --to-npk/--to-vpk if you saved the keys to a file in Step 1.
    • +
    +
  2. +
+

Step 3: Discover and spend the incoming funds as the recipient

+

The recipient scans for incoming transfers, decrypting the PrivateAccountKind header to reconstruct the account ID, then spends from the discovered account.

+
    +
  1. +

    Scan and decrypt incoming funds:

    +
    wallet account sync-private
    +
      +
    • The wallet decrypts the ciphertext header for each incoming transfer and reconstructs AccountId = SHA256(prefix || npk || identifier) for accounts where the VPK matches.
    • +
    • Each incoming transfer appears as a separate account (PrivateOwned or PrivatePdaOwned), distinguished by identifier.
    • +
    +
  2. +
  3. +

    Spend from the discovered account:

    +
    wallet auth-transfer send \
    --from <recipient-account> \
    --to-npk <next-npk> \
    --to-vpk <next-vpk> \
    --to-identifier <u128> \
    --amount 50
    +
      +
    • The recipient account must have been discovered by sync-private in the previous step before it can be used as a --from source.
    • +
    +
  4. +
+

Troubleshooting private foreign transfers

+

Why does the recipient not see the funds after sync-private?

+

The wallet reconstructs the account ID from the decrypted PrivateAccountKind header using the exact NPK, VPK, and identifier the sender used. Confirm that the sender used the NPK and VPK published by the recipient in Step 1 and the agreed-upon identifier, then re-run wallet account sync-private.

+

Why does the second transfer to the same (NPK, identifier) fail?

+

Two transfers to the same (npk, identifier) pair resolve to the same AccountId and collide at the commitment/nullifier layer (check_commitments_are_new / check_nullifiers_are_valid in lee/state_machine/src/state.rs). Each sender must use a unique identifier for a given recipient NPK.

+

Why does spending multiple incoming transfers become expensive?

+

There is no automatic balance consolidation. N transfers to the same keypair produce N separate accounts, and spending all of them requires N inputs, so proof and transaction cost grows linearly with N. Plan identifier allocation to minimize the number of accounts that need to be spent together.

\ No newline at end of file diff --git a/lez/transfer-tokens/transfer-native-tokens-on-the-logos-execution-zone/index.html b/lez/transfer-tokens/transfer-native-tokens-on-the-logos-execution-zone/index.html new file mode 100644 index 00000000..58c5f2bf --- /dev/null +++ b/lez/transfer-tokens/transfer-native-tokens-on-the-logos-execution-zone/index.html @@ -0,0 +1,151 @@ +Transfer native tokens on the Logos Execution Zone | Logos documentation + + +

Transfer native tokens on the Logos Execution Zone

+

Use the wallet CLI to send native tokens to public and private accounts.

+
warning

This page should be accurate for the specific version referenced in this doc, but it may not have been run end-to-end as written. Expect minor gaps (for example, missing environment details) and be prepared to troubleshoot. We are actively working to complete and verify this content.

+
info
+

The Logos Execution Zone (LEZ) is a programmable blockchain that cleanly separates public and private state while keeping them fully interoperable. It's a component of the Logos project. You can use the wallet CLI to invoke LEZ's authenticated-transfers program to transfer native tokens between public and private accounts.

+

On LEZ, public and private accounts differ in where their state lives and how transfers update that state.

+
    +
  • Public accounts +
      +
    • Live on-chain.
    • +
    • Identified by a 32-byte account ID derived from the public key
    • +
    • The private key signs transactions and authorises program executions.
    • +
    +
  • +
  • Transfers between public accounts +
      +
    • Validators execute the authenticated-transfers program transparently.
    • +
    • The public state (the on-chain map from public account IDs to their account states) is updated in place by debiting the sender's public account and crediting the recipient's public account.
    • +
    +
  • +
  • Private accounts +
      +
    • Structurally identical to public accounts, but their values are stored off-chain.
    • +
    • Use two keypairs: nullifier keys for privacy-preserving executions and viewing keys for encrypting and decrypting values.
    • +
    • The private account ID is derived from the nullifier public key.
    • +
    • Anyone can initialise private accounts, but once initialised they can only be modified by the owner's keys.
    • +
    +
  • +
  • Transfers involving any private account +
      +
    • The execution is privacy-preserving.
    • +
    • The transfer generates a zero-knowledge proof of correct execution locally, and submits the proof for validators to verify. Each update to the private state produces a new commitment (a 32-byte, hash-like binding to the actual values), and the previous commitment is marked as spent via a nullifier set so only the latest version can be used, while the actual private values remain local to the account owner.
    • +
    • Any public accounts involved are still updated visibly on-chain.
    • +
    +
  • +
+
danger

Transfers are irreversible. Double-check all details before proceeding.

+

Before you begin, ensure that you have the following:

+ +

What to expect

+
    +
  • The authenticated-transfers program manages native token transfers and enforces authenticated debits. When making transfers, you use the wallet CLI to interact with the program.
  • +
  • You can initialise accounts by sending tokens to them. The authenticated-transfers program claims any uninitialised account used in a transfer.
  • +
  • You can transfer native tokens to public accounts and verify balances on-chain.
  • +
  • Your private account balances are in your local wallet storage and rely on zero-knowledge proofs for privacy.
  • +
+

Step 1: Create and fund a sender account

+
    +
  1. +

    Create a public account or private account according to your needs.

    +
      +
    • Public account:
    • +
    +
    wallet account new public
    +
      +
    • Private account:
    • +
    +
    wallet account new private
    +

    If you create a public account, the output is the account ID. If you create a private account, the output includes the account ID, nullifier public key (npk), and viewing public key (vpk).

    +
  2. +
+
info

Your account keys and data are stored in the local file $HOME/.nssa/wallet/storage.json.

+
    +
  1. +

    Use the wallet account ls command to confirm the accounts are created successfully. You should see a list showing all of your accounts.

    +
    wallet account ls
    +
  2. +
  3. +

    Initialise the sender account. Replace ACCOUNT-TYPE with the type of the sender account (public or private) and ACCOUNT-ID with the account ID you want to initialise.

    +
    wallet auth-transfer init --account-id ACCOUNT-TYPE/ACCOUNT-ID
    +

    For example, to initialise the public account with ID Ev1JprP9BmhbFVQyBcbznU8bAXcwrzwRoPTetXdQPAWS, you run:

    +
    wallet auth-transfer init --account-id Public/Ev1JprP9BmhbFVQyBcbznU8bAXcwrzwRoPTetXdQPAWS
    +
  4. +
+
info

New accounts are created in an uninitialised state, which means no program on LEZ owns them yet. Any program can claim and own an uninitialised account. After initialisation, only the owning program can modify the account.

The only exception is native token credits: any program can credit native tokens to any account, but only the owning program can debit native tokens.

+
    +
  1. +

    Fund the sender account using the Testnet Piñata program. Your account receives 150 tokens every time you fund it.

    +
    wallet pinata claim --to ACCOUNT-TYPE/ACCOUNT-ID
    +
  2. +
  3. +

    Confirm your account balance after funding using the wallet account get command:

    +
    wallet account get --account-id ACCOUNT-TYPE/ACCOUNT-ID
    +

    The output looks like this:

    +
    Account owned by authenticated-transfer program
    {..."balance":150...}
    +
  4. +
+

Step 2: Transfer tokens

+

When transferring native tokens using the wallet auth-transfer send command, you specify the sender account with its account ID.

+

There are two ways to specify the recipient account depending on the account type:

+ +

With the recipient account ID, you can transfer native tokens across the following account types:

+
    +
  • Public → public (yours or someone else's)
  • +
  • Private → public (yours or someone else's)
  • +
  • Public → your private
  • +
  • Private → your private
  • +
+
info

Transfers involving private accounts may take a few minutes because the wallet needs to generate a local proof.

+

With the npk and vpk of the recipient account, you can transfer native tokens across the following account types:

+
    +
  • Public → uninitialised private (someone else's)
  • +
  • Private → uninitialised private (someone else's)
  • +
+
info

Currently, only uninitialised private accounts can be modified without authorisation. Sending funds to initialised private accounts is not possible because only the owner can modify them.

+

Method 1: Transfer tokens using the recipient account ID

+

Use the wallet auth-transfer send to transfer tokens. Replace ACCOUNT-TYPE with the type of the account (public or private) and TOKEN-AMOUNT with the amount of tokens to transfer.

+
```sh
wallet auth-transfer send \
--from ACCOUNT-TYPE/SENDER-ACCOUNT-ID \
--to ACCOUNT-TYPE/RECIPIENT-ACCOUNT-ID \
--amount TOKEN-AMOUNT
```
+

For example, to transfer 17 tokens from the public account with ID Ev1JprP9BmhbFVQyBcbznU8bAXcwrzwRoPTetXdQPAWS to the private account with ID HacPU3hakLYzWtSqUPw6TUr8fqoMieVWovsUR6sJf7cL, you run:

+
```sh
wallet auth-transfer send \
--from Public/Ev1JprP9BmhbFVQyBcbznU8bAXcwrzwRoPTetXdQPAWS \
--to Private/HacPU3hakLYzWtSqUPw6TUr8fqoMieVWovsUR6sJf7cL \
--amount 17
```
+

Method 2: Transfer tokens using the recipient account npk and vpk

+

When transferring someone else's private account, you need their account npk (nullifier public key)and vpk (viewing public key) instead of the account ID because LEZ uses the npk to confirm the ownership of the private account, and vpk to verify transactions without revealing the account owner. These keys do not expose sensitive information and allow others to verify transaction validity.

+
tip

Check your account npk and vpk using the wallet account get --account-id ACCOUNT-TYPE/ACCOUNT-ID command.

+
    +
  1. +

    Use the wallet auth-transfer send to transfer tokens to another user's private account. Replace ACCOUNT-TYPE with the type of the sender account (public or private) and TOKEN-AMOUNT with the amount of tokens to transfer.

    +
    wallet auth-transfer send \
    --from ACCOUNT-TYPE/SENDER-ACCOUNT-ID \
    --to-npk RECIPIENT-NPK \
    --to-vpk RECIPIENT-VPK \
    --amount TOKEN-AMOUNT
    +
  2. +
  3. +

    Once the transaction is accepted, run the following command to scan the chain for encrypted values in the transaction and update the local state accordingly.

    +
    wallet account sync-private
    +
  4. +
+

Step 3: Verify the transfer

+

Confirm the transfer by checking the balances of both accounts using the wallet account get command.

+
```sh
wallet account get --account-id ACCOUNT-TYPE/ACCOUNT-ID
```
+

For example, to check the balance of the private account with ID HacPU3hakLYzWtSqUPw6TUr8fqoMieVWovsUR6sJf7cL, you run:

+
```sh
wallet account get --account-id Private/HacPU3hakLYzWtSqUPw6TUr8fqoMieVWovsUR6sJf7cL
```
+

The output looks like this:

+
```text
Account owned by authenticated-transfer program
{..."balance":BALANCE-AMOUNT...}
```
+
tip

When checking the balance of a private account, the wallet account get command doesn't query the network. It works offline because private account data lives only in your wallet storage. Other users cannot read your private balances using this command and your private account ID.

+
tip

You can also use the wallet account ls -l command to check the balances of all your accounts at once.

+

Troubleshooting

+

Symptom

+

The wallet command panics and fails during proof generation with an assertion error when you try to send native tokens to an initialised private account.

+

The error message looks like this:

+
thread 'main' panicked at .../privacy_preserving_circuit.rs:394:13:
assertion `left == right` failed: Found new private account with non default values
left: Account { ... }
right: Account { ... }
...
called `Result::unwrap()` on an `Err` value: CircuitProvingError("Guest panicked: assertion `left == right` failed: Found new private account with non default values ...")
+

Explanation

+

The transfer fails because the privacy-preserving circuit expects the recipient private account to be uninitialised (default state), but an initialised private account already contains non-default values and can only be modified by its owner.

\ No newline at end of file diff --git a/messaging/chat-module/build-logos-module-that-uses-chat-module-api/index.html b/messaging/chat-module/build-logos-module-that-uses-chat-module-api/index.html new file mode 100644 index 00000000..74c1dda3 --- /dev/null +++ b/messaging/chat-module/build-logos-module-that-uses-chat-module-api/index.html @@ -0,0 +1,168 @@ +Build a Logos module that uses the Chat module API | Logos documentation + + +

Build a Logos module that uses the Chat module API

+

Get started with private 1:1 end-to-end encrypted messaging in your own Logos module.

+

This procedure covers how to build a Logos module that calls the logos-chat-module API (tag v0.1.2) to exchange introduction bundles, open private 1:1 conversations, and send and receive end-to-end encrypted messages on the Logos network. It is intended for application developers who want to integrate private messaging without taking direct dependencies on liblogoschat or logos-delivery.

+
info

Identity, conversations, and message history persist in the instance directory you pass to init() (identity.db + history.json). Restarting an instance against the same directory restores its identity and conversations.

+

Before you start, make sure you have the following:

+
    +
  • +

    Linux or macOS

    +
  • +
  • +

    Nix with flakes enabled. Install from nixos.org, then enable flakes:

    +
    mkdir -p ~/.config/nix
    echo 'experimental-features = nix-command flakes' >> ~/.config/nix/nix.conf
    +
  • +
  • +

    Network access so that two instances can reach each other

    +
  • +
  • +

    An understanding of Logos modules

    +
  • +
+

What to expect

+
    +
  • You can initialise a chat client, connect to the Logos network, and exchange end-to-end encrypted messages with another instance.
  • +
  • You can open a private 1:1 conversation by exchanging introduction bundles out of band and calling create_conversation from the initiating side.
  • +
  • You can integrate the full chat lifecycle — init, subscribe to events, create bundle, open conversation, send, shut down — into any Logos C++ module.
  • +
+

Step 1: Scaffold a new Logos module

+

Scaffold a new module using logos-module-builder. For a full walkthrough, see the Build a Logos C++ UI module tutorial.

+
    +
  1. +

    Create and enter the project directory:

    +
    mkdir your-module-name && cd your-module-name
    +
  2. +
  3. +

    Initialise from the template:

    +
    nix flake init -t github:logos-co/logos-module-builder/tutorial-v3#ui-qml-backend
    +
  4. +
  5. +

    Initialise a Git repository and stage all generated files:

    +
    git init && git add -A
    +
  6. +
  7. +

    Keep the generated files — you build on them, you don't delete them. The template is a working ui_example module. Its main files are:

    +
    FileWhat it isCan you edit it?
    src/ui_example_plugin.{h,cpp}The C++ pluginYes — your chat code goes here (Steps 3–4)
    src/ui_example.rep, src/ui_example_interface.hThe module's interfaceNo
    src/qml/Main.qmlThe example viewLater — replace with your own UI
    metadata.json, CMakeLists.txtBuild configStep 2 only
    +

    You add your chat code in UiExamplePlugin::initLogos(). Leave the example status / add UI as is for now.

    +
  8. +
+
info

Keep the module named ui_example. The name is referenced in metadata.json, CMakeLists.txt, the src/ui_example* files, and Main.qml (logos.module("ui_example")) — they must all match, or nix build fails. To use a different name, change it in every one of these.

+

Step 2: Declare chat_module as a dependency

+

Add chat_module to both metadata.json and flake.nix, pinning to the released tag so your app stays stable as the module's API evolves.

+
info

The flake input name (chat_module) must match the dependency name in metadata.json. logos-module-builder automatically generates the typed chat_module wrapper at build time.

+
    +
  1. +

    In metadata.json, add chat_module and delivery_module to the dependencies array and reuse chat_module's bundled delivery_module contract:

    +
    {
    "name": "ui_example",
    "dependencies": ["chat_module", "delivery_module"],
    "dependency_overrides": {
    "delivery_module": {
    "input": "chat_module",
    "file": "rust-lib/deps/delivery_module.lidl"
    }
    },
    ...
    }
    +
  2. +
  3. +

    In flake.nix, pin chat_module and its transport dependency delivery_module, then map the delivery input to the delivery_module dependency so the builder can resolve its runtime:

    +
    inputs = {
    logos-module-builder.url = "github:logos-co/logos-module-builder";
    # Pin chat_module to the released tag so its API can't shift under `nix flake update`.
    chat_module.url = "github:logos-co/logos-chat-module/v0.1.2";
    # chat_module reaches delivery over IPC, but the builder still needs delivery's
    # runtime build from a matching flake input. Pin the v0.1.3 tag: it carries the
    # zerokit/RLN nix-build fix that earlier delivery tags (≤ v0.1.2) lack.
    logos-delivery-module.url = "github:logos-co/logos-delivery-module/v0.1.3";
    };

    outputs = inputs@{ logos-module-builder, logos-delivery-module, ... }:
    logos-module-builder.lib.mkLogosQmlModule {
    src = ./.;
    configFile = ./metadata.json;
    # Map the delivery input to the `delivery_module` dependency name.
    flakeInputs = { delivery_module = logos-delivery-module; } // inputs;
    };
    +
  4. +
+
info

The dependency_overrides entry above only points the builder at delivery's .lidl contract for code generation — it does not supply delivery's runtime build. The builder resolves each metadata.json dependency's runtime from a matching flake input, so delivery_module needs the logos-delivery-module input, mapped in via flakeInputs. Without it, nix build cannot resolve delivery_module. This mirrors how logos-chat-ui wires the two modules together.

+

Step 3: Initialise LogosModules and subscribe to events

+

In your module's initLogos() function, construct LogosModules with the provided LogosAPI* and subscribe to all push events before calling init(). Subscribing first ensures you do not miss early events or the first incoming messages.

+
    +
  1. +

    Add the LogosModules member, then construct it in initLogos().

    +

    In the header src/ui_example_plugin.h — add the include and the member:

    +
    #include "logos_sdk.h" // generated umbrella — exposes LogosModules

    // inside the UiExamplePlugin class:
    LogosModules* m_logos = nullptr;
    +

    In src/ui_example_plugin.cpp — construct it:

    +
    void UiExamplePlugin::initLogos(LogosAPI* api) {
    logosAPI = api; // keep the scaffold's two existing lines
    setBackend(this);
    m_logos = new LogosModules(api);
    // Use m_logos->chat_module to call the Logos Chat module.
    }
    +
  2. +
  3. +

    Subscribe to the module's push events. Each handler receives the event's positional arguments in the order declared in chat_module.lidl:

    +
    auto& chat = m_logos->chat_module;

    // A new message arrived in a conversation.
    chat.on("message_received", [](const QVariantList& a) {
    // a[0]: QString convo_id, a[1]: QString content, a[2]: qint64 timestamp_ms
    });
    // One of your own messages was recorded/sent.
    chat.on("message_sent", [](const QVariantList& a) {
    // a[0]: convo_id, a[1]: content, a[2]: timestamp_ms
    });
    // A conversation was created — incoming from a peer, or your own outgoing one.
    chat.on("conversation_created", [](const QVariantList& a) {
    // a[0]: QString convo_id, a[1]: bool is_outgoing, a[2]: QString peer_label
    });
    chat.on("conversation_updated", [](const QVariantList& a) { /* a[0]: convo_id */ });
    chat.on("conversation_deleted", [](const QVariantList& a) { /* a[0]: convo_id */ });
    // Delivery/connection state changed — drives your "connected" indicator.
    chat.on("delivery_state_changed", [](const QVariantList& a) {
    // a[0]: QString delivery_state ("initialising" | "online" | "stopped" | "error")
    // a[1]: QString detail
    });
    +

    See rust-lib/chat_module.lidl for the exact argument list of every method and event.

    +
  4. +
+

Step 4: Drive the chat lifecycle

+

Status-bearing methods return their result synchronously as a LogosResult.

+
    +
  • res.success tells you whether the call succeeded;
  • +
  • res.getError<QString>() carries the failure reason;
  • +
  • res.getValue<QString>() carries the returned value (for example, the intro bundle).
  • +
+

Ongoing activity — incoming messages, new conversations, delivery-state changes — arrives asynchronously through the push events you subscribed to in Step 3.

+
info

init() starts delivery asynchronously, so the client is not connected the moment init() returns. Watch delivery_state_changed for the online state before creating conversations or sending messages.

+

init() takes the instance directory, a delivery preset, and a TCP port:

+
ParameterTypeNotes
instance_pathstringDirectory for this instance's persistent state. Use a distinct directory per instance to run several side by side.
delivery_presetstringNetwork preset for the delivery node. Use logos.test to reach the Logos test network. Must match across all participants.
tcp_portintLogos Delivery TCP port. 0 picks a random port.
+
    +
  1. +

    Initialise the chat client:

    +
    const QString dir = qEnvironmentVariable("CHAT_INSTANCE_DIR", "/tmp/chat-instance");
    const LogosResult res = m_logos->chat_module.init(dir, "logos.test", 0);
    if (!res.success) {
    qWarning() << "init failed:" << res.getError<QString>();
    return;
    }
    // init succeeded; delivery connects asynchronously.
    // Wait for delivery_state_changed with state == "online" before creating conversations.
    +
  2. +
  3. +

    Read (or set) your identity:

    +
    const QString myId = m_logos->chat_module.get_installation_name();
    // Optionally choose a name: m_logos->chat_module.set_installation_name("alice");
    +
  4. +
  5. +

    Create and share your introduction bundle:

    +
    const LogosResult bundle = m_logos->chat_module.create_intro_bundle();
    if (bundle.success) {
    const QString myBundle = bundle.getValue<QString>();
    // share `myBundle` out of band (the recipient pastes it — see Step 6)
    }
    +
    warning
    +
  6. +
+

Each introduction bundle is single-use — it can open exactly one conversation. Generate a fresh bundle with create_intro_bundle() for every new contact you want to be able to reach you. +:::

+
    +
  1. +

    Open a private conversation as the initiator, or receive one as the recipient:

    +
    // content is plain text — no encoding required.
    const LogosResult res = m_logos->chat_module.create_conversation(peerBundle, "Hello!");
    // On success a conversation_created event (is_outgoing == true) fires with the new convo_id.
    +
      +
    • The initiator calls create_conversation with the peer's intro bundle and a plain-text opening message.
    • +
    • The recipient does not call anything; a conversation_created push event arrives automatically, followed by a message_received event.
    • +
    +
  2. +
  3. +

    Send and receive messages:

    +
    m_logos->chat_module.send_message(convoId, "How are you?");
    // On success a message_sent event fires locally; the peer receives a message_received event.
    +
      +
    • Message content is plain text in both directions — the module handles encoding and end-to-end encryption on the wire.
    • +
    +
  4. +
  5. +

    Read history and conversation state at any time (synchronous reads):

    +
    const QVariantList convos = m_logos->chat_module.list_conversations(); // [Conversation]
    const QVariantList msgs = m_logos->chat_module.get_messages(convoId); // [Message]
    const QVariantMap st = m_logos->chat_module.status().toMap(); // { convo_count, delivery_state, detail }
    +
    warning
    +
  6. +
+

Do not make a synchronous module read (list_conversations, get_messages, status) from inside an event handler — it re-enters the IPC replica while its read notifier is disabled and stalls until the call times out. Defer the read to the next event-loop turn instead (see deferToEventLoop in logos-chat-ui). +:::

+
    +
  1. +

    Shut down cleanly:

    +
    m_logos->chat_module.shutdown(); // disconnects and tears the client down
    +
  2. +
+

Step 5: Build and run

+
    +
  1. +

    Build the module:

    +
    nix build
    +
  2. +
  3. +

    Preview the module using logos-standalone-app (for ui_qml modules):

    +
    nix run # preview via logos-standalone-app (for ui_qml modules)
    nix build .#lgx # package as .lgx for installation into logos-basecamp
    +
  4. +
+

Step 6: Verify a two-instance exchange

+

A chat is only proven end to end when a message travels between two running instances. Start two copies of your module — each with its own instance_path and tcp_port — and confirm a message lands as a message_received event.

+
    +
  1. Start both instances and wait until each reports delivery_state == "online" via delivery_state_changed.
  2. +
  3. In instance A, call create_intro_bundle() and share the returned bundle out of band (copy it into instance B).
  4. +
  5. In instance B, call create_conversation(bundleFromA, "Hello from B"). Instance A receives a conversation_created event (is_outgoing == false) followed by a message_received event carrying "Hello from B".
  6. +
  7. In instance A, reply with send_message(convoId, "Hi B"). Instance B receives the matching message_received event.
  8. +
+

Seeing the message_received events on both sides confirms the full round trip: identity, intro-bundle exchange, conversation setup, and end-to-end encrypted delivery.

+

Troubleshooting the Logos Chat module

+

Why does a method fail?

+

The method returns a LogosResult with success == false and a reason in getError<QString>(). The most common cause is calling a conversation or message method before init() succeeded, or before delivery reached the online state. Call init() first, check res.success, and wait for a delivery_state_changed event with delivery_state == "online" before creating conversations or sending messages.

+

Why do peers not connect or messages not propagate?

+

The delivery_preset differs across instances, or delivery has not reached online. All participants must use the same preset (for example logos.test) to share a network, and each instance must report online via delivery_state_changed (or status()) before it can exchange messages.

+

Why does a read stall the UI?

+

You issued a synchronous module read (list_conversations, get_messages, status) from inside an event handler. That re-enters the IPC replica while its read notifier is disabled and blocks until the call times out. Defer such reads to the next event-loop turn.

+

Why is a previously created conversation still there after a restart?

+

Chat state is persistent. Identity and history live in the instance directory passed to init() (identity.db, history.json), so restarting against the same directory restores conversations and your identity. Point init() at a fresh directory to start from a clean state.

\ No newline at end of file diff --git a/messaging/chat-module/send-anonymised-messages-over-the-mix-network/index.html b/messaging/chat-module/send-anonymised-messages-over-the-mix-network/index.html new file mode 100644 index 00000000..d023f829 --- /dev/null +++ b/messaging/chat-module/send-anonymised-messages-over-the-mix-network/index.html @@ -0,0 +1,94 @@ +Send anonymised messages over the mix network | Logos documentation + + +

Send anonymised messages over the mix network

+

Get started sending and receiving messages with sender anonymity using the Logos Chat UI.

+

This procedure covers how to run the chat_ui_mix desktop app, connect to the testnet-0.2 mix fleet, and exchange messages between two instances with sender unlinkability. No configuration is required — the app ships with shared testnet credentials and discovers the fleet automatically.

+

Before you start, make sure you have the following:

+
    +
  • macOS or Linux
  • +
  • Nix with flakes enabled — add experimental-features = nix-command flakes to ~/.config/nix/nix.conf (create it if needed)
  • +
  • Outbound TCP access to the fleet on port 30304
  • +
+

What to expect

+
    +
  • You can connect to the testnet-0.2 mix fleet and watch the status bar reach MIX 5/4, which confirms the mix node pool is ready for anonymous sends.
  • +
  • You can exchange intro bundles between two instances and send messages that are routed sender-unlinkably through the 3-hop mixnet.
  • +
  • You can confirm end-to-end delivery by matching the sender's Message sent via mix successfully log with the recipient's ChatBackend: New message: … log.
  • +
+

Start the first instance and connect to the mix fleet

+

The mix chat app can be found in the logos-chat-ui repository.

+
    +
  1. +

    Clone the repository and check out the feat/logos-testnetv02-mix branch:

    +
    git clone https://github.com/logos-co/logos-chat-ui.git
    cd logos-chat-ui
    git checkout feat/logos-testnetv02-mix
    +
  2. +
  3. +

    Start the app:

    +
    nix run --accept-flake-config
    +
    info
    +
  4. +
+

The first build pulls the full dependency chain through Nix and takes approximately 15–20 minutes. Subsequent launches use the cache and start in seconds. +:::

+
    +
  1. +

    In the startup popup, pick a demo user (1–10) and click Start.

    +
      +
    • The app loads the bundled testnet credentials and begins discovering the fleet mix nodes via libp2p Kademlia service discovery.
    • +
    +
  2. +
  3. +

    Wait for the mix pool to reach the minimum. Check the status bar and console for the following:

    +
      +
    • Status bar shows Connected, MIX: ENABLED, and USER: <n> immediately after launch. Console shows Kademlia mix discovery mounted and Connected to static peers.
    • +
    • Over the next 1–2 minutes the mix pool fills. Status bar moves from MIX 0/4 toward MIX 5/4 (pool size 5 / minimum 4). Console shows Waiting for mix node pool then Mix node pool ready poolSize=5.
    • +
    • The send box remains disabled until the pool reaches the minimum.
    • +
    +
  4. +
+

Start a second instance and exchange messages

+

Run the second instance in its own folder with a different demo user and port to avoid credential collisions and RLN rate-limit conflicts.

+
warning

Never run two instances in the same working directory. The app stages credential files (rln_membership.json, rln_tree.db, and others) into the current folder; two instances sharing the same folder race on identity. If a previous launch left orphaned processes behind, run pkill -9 -f logos_host_qt before starting a new instance.

+
    +
  1. +

    In a new terminal, create a separate working directory for the second instance and start it:

    +
    mkdir ../chat2 && cd ../chat2 # ../ steps out of the logos-chat-ui clone
    CHAT_DEMO_USER=2 CHAT_PORT=62002 nix run ../logos-chat-ui --accept-flake-config
    +
      +
    • CHAT_DEMO_USER pre-selects a different demo user (use any index other than the one chosen in the first instance).
    • +
    • CHAT_PORT sets the libp2p listen port to avoid a port conflict with the first instance (0 selects a random port).
    • +
    • Wait for this instance's status bar to also reach MIX 5/4 before sending.
    • +
    +
  2. +
  3. +

    Exchange intro bundles between the two instances:

    +
      +
    • In one instance, click Get Intro Bundle, then click Copy to Clipboard in the My Bundle dialog that opens.
    • +
    • In the other instance, click + new, paste the bundle and enter a first message, then click Create.
    • +
    +
  4. +
  5. +

    Send a message from one instance and confirm it arrives in the other:

    +
      +
    • Sender console: Sending via mix (lightpushPublish)Message sent via mix successfully
    • +
    • Recipient console: ChatBackend: New message: …
    • +
    • The message appears in the recipient's conversation within approximately 1–2 seconds.
    • +
    • A Mix lightpush: no SURB reply within 60s warning in the sender console is benign — delivery uses the forward path, which is independent of the SURB reply.
    • +
    +
  6. +
  7. +

    Confirm the final status bar state on both instances:

    +
      +
    • Status bar reads MIX 5/4 and Connected.
    • +
    +
  8. +
+

Troubleshooting mix chat

+

Why does the app fail to launch after a previous run?

+

A previous instance was killed while logos_host_qt child processes were still running. These orphaned processes block the next launch. Run pkill -9 -f logos_host_qt to clear them, then try again.

+

Why does the mix pool stay at MIX 0/4 or fill slowly?

+

The pool takes 1–2 minutes to fill on first launch. Wait and watch the status bar. If the pool does not reach MIX 5/4 after several minutes, the fleet may be temporarily degraded — the mix fleet is a shared testnet of 5 nodes and connectivity depends on all of them being reachable.

+

Why does the send succeed but the recipient never sees the message?

+

The app receives as a light client and must stay subscribed to a healthy fleet node. If fleet connectivity is degraded, the round-trip may not complete even though the send path works. Retry after confirming the pool is at MIX 5/4 on both instances. If the problem persists, the fleet may be temporarily unavailable.

+

Why do two instances on the same machine collide?

+

Either both instances share the same working directory, the same demo-user index, or both are listening on the same port. Run each instance in its own folder (chat2, and so on), use a different CHAT_DEMO_USER value for each, and set distinct CHAT_PORT values.

\ No newline at end of file diff --git a/messaging/concepts/content-topics/index.html b/messaging/concepts/content-topics/index.html new file mode 100644 index 00000000..d50dbdce --- /dev/null +++ b/messaging/concepts/content-topics/index.html @@ -0,0 +1,42 @@ +About content topics | Logos documentation + + +

About content topics

+

Understand how content topics filter and route messages, and how naming choices affect privacy.

+

Content Topics are metadata strings set by developers on outgoing messages to facilitate protocol-level features like selectively processing incoming messages (Relay or Filter) and retrieving historical messages (Store) that meet specific filtering criteria.

+
info

Check out the WAKU2-TOPICS specification to learn more.

+

The basics

+
    +
  • Content topics are developer-set metadata strings that let protocols filter, route, and retrieve messages.
  • +
  • The recommended naming format combines an application name, version, content topic name, and encoding.
  • +
  • Naming considerations matter for privacy, because some protocols disclose content topics to peers.
  • +
+

Naming format

+

Here is the recommended format for content topics:

+

/{application-name}/{version}/{content-topic-name}/{encoding}

+
    +
  • application-name: This is the unique name of your decentralised application (DApp) to prevent conflicts with other DApps.
  • +
  • version: Typically starting at 1, this field helps track breaking changes in your messages.
  • +
  • content-topic-name: The specific name of the content topic used for filtering.
  • +
  • encoding: The message encoding or serialisation format, with Protocol Buffers (proto) being the recommended choice.
  • +
+

For example, if your DApp is called SuperCrypto and it allows users to receive notifications and send private messages, you can consider using the following content topics:

+
    +
  • /supercrypto/1/notification/proto
  • +
  • /supercrypto/1/private-message/proto
  • +
+
tip

While you can choose any encoding format for your Content Topic, we highly recommend using Protocol Buffers (proto) because of its efficiency. Choosing a lightweight format ensures optimal performance of your DApp.

+

Naming considerations

+

When choosing a content topic, it is essential to consider the privacy implications. Here are several factors to keep in mind:

+

Protocols disclose content topics to peers

+

The Filter, Store, and Light Push protocols share content topics with peers, allowing them to link IP and content topic interests. The Relay protocol, using GossipSub, provides recipient anonymity, which can be compromised if the content topic exposes user information.

+

For example, instead of using Personally Identifiable Information (PII) in the content topic (e.g., a public key), you can create buckets (e.g., based on the first 4 bytes of the public key hash).

+
info

Logos Delivery is developing privacy-preserving features like Anonymous Filter Subscription for the Filter protocol and Anonymous Query for the Store protocol to hide content topics from potential adversaries.

+

Increasing k-anonymity preserves user anonymity

+

You can increase k-anonymity within the network by using a unified content topic across the entire application or targeting specific features like notifications or private messages, allowing multiple users to share it.

+

We recommend switching functionality using the Protocol Buffer (proto) message format. By doing so, applications can retain a high granularity and functionality while using a single content topic, preserving user privacy. For example:

+
message NotificationPayload {
...
}

message FeatureAbcPayload {
...
}

// By default, all fields in protobuf are optional so only field may be encoded at a time
message Payload {
NotificationPayload notification = 1;
FeatureAbcPayload feature_abc = 2;
}
+

Creating buckets help in distributing traffic

+

When an application uses a single content topic, all users using request/response protocols (Filter, Store) receive all its messages. For heavy traffic, developers can create buckets by hashing a unique identifier (e.g., recipient's ID, public key, or app domain topic) and adding its first byte to the content topic, like /my-app/0/a/proto.

+

This approach divides traffic into multiple topics, reducing the messages users have to download. Developers can add more first bytes to the content topic over time to improve efficiency and privacy based on messages and user needs.

+
info

The k value of k-anonymity equals the number of IDs for which the first character of the hash is "a". For example, using a single content topic in an application with 10,000 users results in k = 10,000. However, using the hash ID's first character, k reduces to 10,000 / 16 = 625.

\ No newline at end of file diff --git a/messaging/concepts/logos-delivery-and-libp2p/index.html b/messaging/concepts/logos-delivery-and-libp2p/index.html new file mode 100644 index 00000000..6c672e69 --- /dev/null +++ b/messaging/concepts/logos-delivery-and-libp2p/index.html @@ -0,0 +1,19 @@ +Learn the differences between Logos Delivery and libp2p | Logos documentation + + +

Learn the differences between Logos Delivery and libp2p

+

Understand what Logos Delivery adds on top of libp2p and where the two differ.

+

Since Logos Delivery is built on top of libp2p, they share a lot of concepts and terminologies between them. However, there are key differences between them that are worth noting.

+

The basics

+ +

Logos Delivery as a service network

+

Logos Delivery intends to incentivise mechanisms to run nodes, but it is not part of libp2p's scope. Additionally, users or developers do not have to deploy their infrastructure as a prerequisite to use Logos Delivery. It is a service network. However, you are encouraged to run a node to support and decentralise the network.

+

Logos Delivery as a turnkey solution

+

Logos Delivery includes various protocols covering the following domains: privacy preservation, censorship resistance, and platform agnosticism, allowing it to run on any platform or environment.

+

Logos Delivery provides out-of-the-box protocols to enable mostly offline/resource-limited devices, Store/Light Push/Filter caters to those use cases.

+

Economic spam protection

+

libp2p does not have strong spam protection guarantees, RLN Relay is a protocol being developed by the Logos Delivery team towards this goal.

\ No newline at end of file diff --git a/messaging/concepts/network-domains/index.html b/messaging/concepts/network-domains/index.html new file mode 100644 index 00000000..303fe9e3 --- /dev/null +++ b/messaging/concepts/network-domains/index.html @@ -0,0 +1,36 @@ +About network domains | Logos documentation + + +

About network domains

+

Understand the discovery, gossip, and request/response domains that structure how peers interact.

+

Logos Delivery is a unified and cohesive entity that offers a rich ecosystem with three distinct network interaction domains. These domains serve specialised purposes and contribute to the robust functionality of Logos Delivery, forming its foundation.

+

The basics

+ +

Discovery domain

+

Peer discovery in Logos Delivery facilitates locating other nodes within the network. As a modular protocol, Logos Delivery incorporates various discovery mechanisms, such as Discv5 and Peer Exchange. These mechanisms allow developers to choose the most suitable option(s) for their specific use cases and user environments, including mobile phones, desktop browsers, servers, and more.

+

Gossip domain

+

GossipSub derives its name from the practice within Pub/Sub networks where peers gossip about the messages they have encountered, thus establishing a message delivery network.

+

Logos Delivery employs gossiping through Relay to distribute messages across the network. Additionally, Logos Delivery introduces RLN Relay, an experimental mechanism that combines privacy preservation and economic spam protection.

+

Request/response domain

+

Logos Delivery provides a set of protocols to optimise its performance in resource-limited environments like low bandwidth or mostly offline scenarios for multiple purposes.

+
    +
  • Store enables the retrieval of historical messages.
  • +
  • Filter efficiently retrieves a subset of messages to conserve bandwidth.
  • +
  • Light Push facilitates message publication for nodes with limited bandwidth and short connection windows.
  • +
+

Overview of protocol interaction

+

Here is a diagram illustrating the interaction between different protocols.

+
sequenceDiagram
participant A as A relay
participant B as B relay(pubtopic1)
participant C as C relay(pubtopic1)
participant D as D relay(pubtopic1), store(pubtopic1), filter
participant E as E relay, store
participant F as F filter

A ->> A: msg1=WakuMessage(contentTopic1, data) (1)
F ->> D: FilterRequest(pubtopic1, contentTopic1) (2)
D ->> D: Subscribe F to filter (2)
A ->> B: Publish msg1 on pubtopic1 (3)
B ->> D: relay msg1 on pubtopic1 (3)
D ->> D: store: saves msg1 (4)
D ->> C: relay msg1 on pubtopic1 (4)
D ->> F: MessagePush(msg1) (5)
E ->> E: E comes online (6)
E ->> D: HistoryQuery(pubtopic1, contentTopic1) (6)
D ->> E: HistoryResponse(msg1, ...) (6)
+

The Pub/Sub topic pubtopic1 serves as a means of routing messages (the network employs a default Pub/Sub topic) and indicates that it is subscribed to messages on that topic for a relay. Node D serves as a Store and is responsible for storing messages.

+
    +
  1. Node A creates a WakuMessage msg1 with Content Topic contentTopic1.
  2. +
  3. Node F requests to get messages filtered by Pub/Sub topic pubtopic1 and Content Topic contentTopic1. Node D subscribes F to this filter and will forward messages that match that filter in the future.
  4. +
  5. Node A publishes msg1 on pubtopic1. The message is sent from Node A to Node B and then forwarded to Node D.
  6. +
  7. Node D, upon receiving msg1, stores the message for future retrieval by other nodes and forwards it to Node C.
  8. +
  9. Node D also pushes msg1 to Node F, informing it about the arrival of a new message.
  10. +
  11. At a later time, Node E comes online and requests messages matching pubtopic1 and contentTopic1 from Node D. Node D responds with msg1 and potentially other messages that match the query.
  12. +
\ No newline at end of file diff --git a/messaging/concepts/protocols/index.html b/messaging/concepts/protocols/index.html new file mode 100644 index 00000000..21a30a68 --- /dev/null +++ b/messaging/concepts/protocols/index.html @@ -0,0 +1,34 @@ +Understand Logos Delivery protocols | Logos documentation + + +

Understand Logos Delivery protocols

+

Understand the modular protocols that let applications balance anonymity, scalability, and latency.

+

Logos Delivery takes a modular approach, providing a range of protocols that enable applications to control the trade-offs involved in the Anonymity Trilemma. This flexibility empowers applications to make informed choices regarding the desired balance between anonymity, scalability, and latency.

+

The basics

+
    +
  • Logos Delivery is modular, so applications combine protocols to balance anonymity, scalability, and latency.
  • +
  • Relay provides the base publish/subscribe messaging, and RLN relay extends it with economic spam prevention.
  • +
  • Filter, Store, and Light push adapt access for nodes with limited bandwidth or intermittent connections.
  • +
+

Relay

+

Relay protocol employs a Pub/Sub architecture to facilitate the sending and receiving of messages among peers. It extends the libp2p GossipSub protocol to create a privacy-focused peer-to-peer messaging protocol that enables secure communication channels, encryption, and protection against censorship.

+

RLN Relay

+

RLN Relay protocol extends the Relay protocol by using Rate Limit Nullifiers (RLN) to provide efficient and economic spam-prevention. It enforces a rate limit on messages over time for all peers in the network, economically preventing spam, and imposes financial penalties and network removal for spammers. You can find more details in the RLN Relay blog post.

+

Filter

+

Filter protocol allows light nodes to selectively subscribe to specific messages relayed by other peers using content topics. It is designed to be a lightweight alternative for accessing the Relay network, particularly tailored for devices with limited bandwidth.

+
info

Filter protocol helps optimise bandwidth usage, but it has fewer privacy guarantees as it must disclose the content topic to its peers to retrieve messages.

+

Store

+

Store protocol is responsible for storing messages relayed in the network, making it possible to query and retrieve them later. This functionality benefits offline peers by enabling them to retrieve missed messages upon reconnection.

+
info

Using Relay and Filter protocols is recommended when a node is online, as Store does not guarantee data availability. The Store protocol is suitable for retrieving messages when connecting to the network, like when a DApp starts.

+

Light Push

+

Light Push is a Request/Response protocol for nodes with limited bandwidth and short connection windows. It allows a client to receive an acknowledgement when sending messages, indicating that at least one peer has received them. Subsequently, the remote peer forwards these messages to the Relay network.

+
info

While the Light Push protocol acknowledges the receipt by the remote peer, it does not guarantee network-wide propagation.

+

Waku message

+

Waku message specifies the structure and format of messages. It includes the following attributes:

+
    +
  • content_topic attribute for content-based filtering.
  • +
  • payload attribute containing the message data payload to be sent.
  • +
  • meta attribute for conveying additional details to various protocols for application-specific processing.
  • +
  • timestamp attribute signifying the time at which the message was generated by its sender.
  • +
  • ephemeral attribute specifying whether the network should not store the message.
  • +
\ No newline at end of file diff --git a/messaging/concepts/security-features/index.html b/messaging/concepts/security-features/index.html new file mode 100644 index 00000000..abec3d5d --- /dev/null +++ b/messaging/concepts/security-features/index.html @@ -0,0 +1,21 @@ +Understand Logos Delivery security features | Logos documentation + + +

Understand Logos Delivery security features

+

See the services that keep Logos Delivery communication private and abuse-resistant.

+

Logos Delivery's protocol layers offer different services and security considerations, shaping the overall security of Logos Delivery. We document the security models in the RFCs of the protocols, aiming to provide transparent and open-source references. This empowers Logos Delivery users to understand each protocol's security guarantees and limitations.

+

Pseudonymity

+

Logos Delivery ensures pseudonymity across its protocol layers, using libp2p PeerID as identifiers instead of disclosing true identities. However, it is important to note that pseudonymity does not provide complete anonymity. Actions performed under the same pseudonym (PeerID) can be linked, leading to the potential re-identification of the actual actor.

+

Anonymity/unlinkability

+

Anonymity means an adversary cannot connect an actor to their actions or data. To achieve anonymity, avoiding linking activities with actors or their Personally Identifiable Information (PII) is crucial. In Logos Delivery, the following anonymity features are provided:

+ +

Spam protection

+

The spam protection feature in Relay ensures that no adversary can flood the system with many messages, intentionally or not, regardless of the content's validity or usefulness. This protection is achieved through the scoring mechanism of GossipSub v1.1. Peers assign scores to their connections based on their behaviour and remove peers with low scores.

+

Ongoing research is being conducted, including developing Rate Limit Nullifiers (RLN), which can be explored further at: https://github.com/vacp2p/research/issues/148.

+

Data confidentiality, integrity, and authenticity

+

Confidentiality in Logos Delivery is ensured through data encryption, while integrity and authenticity are achieved through digital signatures. These security measures are available in Waku Message (version 1) and Noise protocols, which offer payload encryption and encrypted signatures.

+

Security considerations

+

In protocols like Store and Filter, where direct connections are required for the designated service, anonymity or unlinkability is not guaranteed. This is because nodes use their PeerID to identify each other during direct connections, making the service obtained in these protocols linkable to the beneficiary's PeerID, considered Personally Identifiable Information (PII). In Store, the queried node can link the querying node's PeerID to the topics being queried. Similarly, in Filter, a node can link the PeerID of a light node to its content filter. Check out the Security considerations section in the Waku2 RFC for more details.

\ No newline at end of file diff --git a/messaging/concepts/transports/index.html b/messaging/concepts/transports/index.html new file mode 100644 index 00000000..4208760d --- /dev/null +++ b/messaging/concepts/transports/index.html @@ -0,0 +1,20 @@ +About transports | Logos documentation + + +

About transports

+

Understand how nodes move data between peers and which transports suit each environment.

+

Transports help move data packets across a network by establishing connections between peers. They define the rules and protocols to ensure efficient network transmission, routing, and data delivery. Logos Delivery is a transport-agnostic framework that allows developers to choose and support multiple protocols according to their requirements.

+

The basics

+
    +
  • Transports move data packets between peers and define how the network transmits, routes, and delivers data.
  • +
  • Logos Delivery is transport-agnostic, so developers choose the protocols that suit their requirements.
  • +
  • The recommended transports vary by environment, from TCP for service nodes to secure WebSocket for browsers.
  • +
+ +

For Logos Delivery nodes, the following transports are recommended:

+
    +
  • TCP: By default, Logos Delivery nodes use TCP for communication. Service nodes should employ TCP for listening to and connecting with other nodes.
  • +
  • Secure WebSocket: In browser environments, secure WebSocket is used. Service nodes are encouraged to set up SSL certificates to enable incoming connections from browsers and serve them securely.
  • +
  • Other protocols like WebRTC, WebTransport, and QUIC have been researched and studied for potential integration.
  • +
+
info

Logos Delivery ensures compatibility and improved communication capabilities by following these recommended transports.

\ No newline at end of file diff --git a/messaging/concepts/understand-peer-discovery/discv5/index.html b/messaging/concepts/understand-peer-discovery/discv5/index.html new file mode 100644 index 00000000..0f45c192 --- /dev/null +++ b/messaging/concepts/understand-peer-discovery/discv5/index.html @@ -0,0 +1,31 @@ +About Discv5 | Logos documentation + + +

About Discv5

+

Understand how Discv5 uses a distributed hash table to find peers across the network.

+

Discv5 is a decentralised and efficient peer discovery mechanism for the Logos Delivery Network. It uses a Distributed Hash Table (DHT) for storing ENR records, providing resistance to censorship. Discv5 offers a global view of participating nodes, enabling random sampling for load distribution. It uses bootstrap nodes as an entry point to the network, providing randomised sets of nodes for mesh expansion. Have a look at the Discv5 specification to learn more.

+

How Discv5 works

+
sequenceDiagram
Alice->>DNS Server: (1) Execute DNS Discovery
DNS Server-->>Alice: (2) Bob's connection details (UDP + TCP)
activate Alice
Alice->>Bob: (3) Execute Discv5 over UDP
Bob-->>Alice: (4) ENRs (UDP + TCP): Carol, David, Erin
Alice->>Alice: (5) Decode ENRs
Alice->>Carol: (6) Execute Discv5 over UDP
Carol-->>Alice: (7) ENRs (UDP + TCP): Frank, Gwen, Harry
Alice->>Alice: (8) Decode ENRs
deactivate Alice
Alice->>Alice: (9) Select peers to dial
Alice->>David: (10) Libp2p dial (TCP)
Alice->>Frank: (10) Libp2p dial (TCP)
Alice->>Gwen: (10) Libp2p dial (TCP)
+
    +
  1. DNS Discovery protocol is executed.
  2. +
  3. Alice retrieves Bob's ENR (Ethereum Node Record) from DNS Server.
  4. +
  5. Alice executes the Discv5 protocol with Bob using UDP connection details from ENR.
  6. +
  7. Bob returns Carol's, David's and Erin's ENRs to Alice.
  8. +
  9. Alice decodes ENRs and extracts the details of Carol, David, and Erin's TCP and UDP connections.
  10. +
  11. Alice executes the Discv5 protocol with Carol using UDP connection details from ENR.
  12. +
  13. Carol returns Frank's, Gwen's and Harry's ENRs to Alice.
  14. +
  15. Alice decodes ENRs and extracts Frank's, Gwen's and Harry's TCP and UDP connection details.
  16. +
  17. Alice selects to dial David, Frank and Gwen.
  18. +
  19. Alice dials David, Frank and Gwen over libp2p using TCP connection details from ENRs.
  20. +
+

Pros and cons

+

Pros:

+
    +
  • Decentralised with random sampling from a global view.
  • +
  • Continuously researched and improved.
  • +
+

Cons:

+
    +
  • Requires lots of connections and involves frequent churn.
  • +
  • Relies on User Datagram Protocol (UDP), which is not supported in web browsers.
  • +
\ No newline at end of file diff --git a/messaging/concepts/understand-peer-discovery/dns-discovery/index.html b/messaging/concepts/understand-peer-discovery/dns-discovery/index.html new file mode 100644 index 00000000..bb6448d3 --- /dev/null +++ b/messaging/concepts/understand-peer-discovery/dns-discovery/index.html @@ -0,0 +1,29 @@ +About DNS discovery | Logos documentation + + +

About DNS discovery

+

Understand how a node retrieves peer connection details from an ENR tree published in DNS.

+

Built upon the foundation of EIP-1459: Node Discovery via DNS, DNS Discovery allows the retrieval of an ENR tree from the TXT field of a domain name. This approach enables the storage of essential node connection details, including IP, port, and multiaddr. This bootstrapping method allows anyone to register and publish a domain name for the network, promoting increased decentralisation.

+

How DNS discovery works

+
sequenceDiagram
Node->>DNS Server: (1) Lookup TXT example.com
DNS Server-->>Node: (2) enrtree-root:v1 e=U3...3Y ...
Node->>DNS Server: (3) Lookup TXT U3...3Y.example.com
DNS Server-->>Node: (4) enrtree-branch:DU...VQ,J3..HU,IC...WE
Node->> DNS Server: (5) Lookup TXT DU...VQ.example.com
DNS Server-->>Node: (6) enr:-M-4QLdAB-Kyz...Wt1Mg8
Node ->> Node: (7) Decode ENR: Peer's connection details
+
    +
  1. DNS lookup query to retrieve TXT data stored on example.com domain.
  2. +
  3. enrtree-root is returned, and the value of e is the enr-root, the root hash of the node subtree.
  4. +
  5. DNS lookup query to retrieve TXT data stored on <enr-root>.example.com domain.
  6. +
  7. enrtree-branch is returned; this tree contains hashes of node subtrees.
  8. +
  9. DNS lookup query to retrieve TXT data stored on DU...VQ.example.com domain, the first leaf of enrtree-branch.
  10. +
  11. enr record is returned.
  12. +
  13. Returned value is decoded, and peer connection details such as IP address and port are learned.
  14. +
+

Pros and cons

+

Pros:

+
    +
  • Low latency, low resource requirements.
  • +
  • Easy bootstrap list updates by modifying the domain name, eliminating the need for code changes.
  • +
  • Ability to reference a larger list of nodes by including other domain names in the code or ENR tree.
  • +
+

Cons:

+
    +
  • Vulnerable to censorship: Domain names can be blocked or restricted.
  • +
  • Limited scalability: The listed nodes are at risk of being overwhelmed by receiving all queries. Also, operators must provide their ENR to the domain owner for listing.
  • +
\ No newline at end of file diff --git a/messaging/concepts/understand-peer-discovery/peer-discovery/index.html b/messaging/concepts/understand-peer-discovery/peer-discovery/index.html new file mode 100644 index 00000000..d4fff063 --- /dev/null +++ b/messaging/concepts/understand-peer-discovery/peer-discovery/index.html @@ -0,0 +1,21 @@ +Understand peer discovery | Logos documentation + + +

Understand peer discovery

+

Learn how a node bootstraps and finds the peers it needs to operate.

+

When initialising a Logos Delivery node, it must connect with other peers to enable message sending, receiving, and retrieval. To achieve this, a discovery mechanism is employed to locate other peers in the network. This process is known as bootstrapping.

+

Why a node seeks more peers

+

Once a connection is established, the node must actively seek out additional peers to have:

+
    +
  • Sufficient peers in the Relay mesh: The goal is to have at least 6 peers in the mesh. This ensures a robust network where messages can be efficiently relayed.
  • +
  • Reserve peers for backup: It is essential to have a surplus of peers available as reserves. These reserves are backups when the current peers become overloaded or experience unexpected disconnections.
  • +
  • Peers with specific capabilities: The node seeks out peers with specific capabilities, such as Store, Light Push, or Filter. This allows for targeted interactions and enhanced functionality based on the desired capabilities.
  • +
+

Discovery mechanisms

+

Logos Delivery supports multiple peer discovery mechanisms, such as:

+
\ No newline at end of file diff --git a/messaging/concepts/understand-peer-discovery/peer-exchange/index.html b/messaging/concepts/understand-peer-discovery/peer-exchange/index.html new file mode 100644 index 00000000..6ac8f2ba --- /dev/null +++ b/messaging/concepts/understand-peer-discovery/peer-exchange/index.html @@ -0,0 +1,30 @@ +About peer exchange | Logos documentation + + +

About peer exchange

+

Understand how light nodes request peers from other nodes without relying on Discv5.

+

The primary objective of this protocol is to facilitate peer connectivity for resource-limited devices without relying on Discv5. The peer exchange protocol enables light nodes to request peers from other nodes within the network.

+
info

Peer Exchange enables requesting random peers from other network nodes without revealing information about their connectivity or neighbourhood.

+

How peer exchange works

+
sequenceDiagram
Alice->>DNS Server: (1) Execute DNS Discovery
DNS Server-->>Alice: (2) Bob's multiaddr (websocket)
Alice-->>Bob: (3) Dial
Alice->>Bob: (4) Peer Exchange Query
Bob-->>Alice: (5) Carol's ENR, David's ENR
Alice->>Alice: (6) Decode ENRs
Alice->>Carol: (7) Dial
Alice->>David: (7) Dial
Alice-->>Bob: (8) Disconnect
+
    +
  1. DNS Discovery protocol is executed.
  2. +
  3. Alice retrieves Bob's websocket multiaddr from DNS Server.
  4. +
  5. Alice dials Bob using libp2p protocols.
  6. +
  7. Alice executes a Peer Exchange query to Bob.
  8. +
  9. Bob returns Carol's and David's ENR to Alice.
  10. +
  11. Alice decodes ENRs and extracts Carol's and David's websocket multiaddrs.
  12. +
  13. Alice dials Carol and David.
  14. +
  15. Alice can now drop the connection with Bob (bootstrap node); Alice has 2 connections to the Logos Delivery Network.
  16. +
+

Pros and cons

+

Pros:

+
    +
  • Low resource requirements.
  • +
  • Decentralised with random sampling of nodes from a global view using Discv5.
  • +
+

Cons:

+
    +
  • Decreased anonymity.
  • +
  • Imposes additional load on responder nodes.
  • +
\ No newline at end of file diff --git a/messaging/concepts/understand-peer-discovery/static-peers/index.html b/messaging/concepts/understand-peer-discovery/static-peers/index.html new file mode 100644 index 00000000..9193859f --- /dev/null +++ b/messaging/concepts/understand-peer-discovery/static-peers/index.html @@ -0,0 +1,21 @@ +About static peers | Logos documentation + + +

About static peers

+

Understand how hard-coded bootstrap peers work and the trade-offs they carry.

+

Logos Delivery applications have the flexibility to embed bootstrap node addresses directly into their codebase. Developers can either use static peers operated by Status or run a node.

+

How static peers work

+
sequenceDiagram
Alice->>Bob: Connect to Bob (static peer details)
Alice->>Carol: Connect to Carol (static peer details)
+

Alice establishes connections with Bob and Carol using their node details, which are predefined (hard-coded) into Alice's node.

+

Pros and cons

+

Pros:

+
    +
  • Low latency.
  • +
  • Low resource requirements.
  • +
+

Cons:

+
    +
  • Vulnerable to censorship: Node IPs can be blocked or restricted.
  • +
  • Limited scalability: The number of nodes is fixed and cannot easily be expanded.
  • +
  • Maintenance challenges: Updating the node list requires modifying the code, which can be cumbersome and involves releasing and deploying.
  • +
\ No newline at end of file diff --git a/messaging/delivery/run-logos-delivery-node/index.html b/messaging/delivery/run-logos-delivery-node/index.html new file mode 100644 index 00000000..9d1b34ec --- /dev/null +++ b/messaging/delivery/run-logos-delivery-node/index.html @@ -0,0 +1,122 @@ +Run a Logos delivery node | Logos documentation + + +

Run a Logos delivery node

+

Use the Logos Delivery module to run a Logos delivery node

+

This procedure covers how to install and start a logos-delivery-module node connected to the Logos testnet (v0.2). It is intended for node operators who want to run their own Delivery node and join the network. Three installation paths are available — Docker, prebuilt binaries, and Nix — so you can choose the one that fits your environment.

+

Choose one of the three installation paths based on your environment:

+
PathMethodBest for
ADocker with ComposeQuickest start; first build takes 30–45 min
BPrebuilt binariesNo build, no clone required
CNixFrom source; most reproducible
+

Before you start, make sure you have the following:

+
    +
  • Linux or macOS
  • +
  • Network access so both node instances can reach each other
  • +
  • jq installed (required for the verify step)
  • +
  • Per path: A — Docker with Compose; Bcurl and a shell; CNix with flakes enabled
  • +
+

What to expect

+
    +
  • You can start a delivery_module node connected to the Logos Network.
  • +
  • You can confirm the node is running and has a live network identity by querying its discv5 ENR.
  • +
  • You can configure the node for a different network preset by swapping the config file passed to createNode.
  • +
+

Step 1: Install and start the daemon

+

Follow the instructions for your chosen path.

+

Path A — Docker

+
    +
  1. +

    Clone the repository and start the daemon container:

    +
    git clone https://github.com/logos-co/logos-delivery-module.git
    cd logos-delivery-module
    docker compose up -d --build
    +
    info
    +
  2. +
+

The first Docker build runs Nix and downloads release packages. It can take 30–45 minutes; subsequent starts are fast. +:::

+

Path B — Prebuilt binaries

+
    +
  1. +

    Install logoscore, lgpd, and lgpm into ./bin:

    +
    curl -fsSL https://raw.githubusercontent.com/logos-co/logos-docs/main/resources/scripts/install-node-tools.sh | sh
    export PATH="$PWD/bin:$PATH"
    +
  2. +
  3. +

    Download and install the delivery module:

    +
    mkdir -p packages modules
    lgpd download delivery_module --output ./packages
    lgpm install --dir ./packages --modules-dir ./modules
    +
  4. +
  5. +

    Write the testnet config and start the daemon:

    +
    cat > logos-test.json <<EOF
    {
    "preset": "logos.test",
    "mode": "Core",
    "logLevel": "INFO",
    "tcpPort": 30303,
    "discv5UdpPort": 9000,
    "discv5Discovery": true,
    "nat": "extip:<public-ip>"
    }
    EOF

    logoscore -D -m ./modules > logs.txt
    +
  6. +
+

Path C — Nix

+
    +
  1. +

    Clone the repository and build the runtime, package manager, and module:

    +
    git clone https://github.com/logos-co/logos-delivery-module.git
    cd logos-delivery-module

    nix build 'github:logos-co/logos-logoscore-cli' --out-link ./logos
    nix build 'github:logos-co/logos-package-manager#cli' -o lgpm
    nix build '.#lgx' -o delivery-lgx
    +
  2. +
  3. +

    Seed the modules directory and install the module:

    +
    mkdir -p modules
    cp -RL ./logos/modules/. ./modules/
    ./lgpm/bin/lgpm --modules-dir ./modules --allow-unsigned install --file delivery-lgx/*.lgx
    +
  4. +
  5. +

    Start the daemon:

    +
    export PATH="$PWD/logos/bin:$PATH"
    logoscore -D -m ./modules > logs.txt
    +
  6. +
+

Step 2: Load the module and boot the node

+

Run these commands for your path.

+
    +
  1. +

    Load the delivery module:

    +
    # Path A (Docker)
    docker exec logos-node logoscore load-module delivery_module --json

    # Paths B and C
    logoscore load-module delivery_module
    +
  2. +
  3. +

    Create the node with the testnet config:

    +
      +
    • +

      Path A (config is mounted at /conf in the container):

      +
      docker exec logos-node logoscore call delivery_module createNode @/conf/logos-test.json --json
      +
    • +
    • +

      Path B:

      +
      logoscore call delivery_module createNode @logos-test.json
      +
    • +
    • +

      Path C:

      +
      logoscore call delivery_module createNode @conf/logos-test.json
      +
    • +
    +
  4. +
  5. +

    Start the node:

    +
    # Path A
    docker exec logos-node logoscore call delivery_module start --json

    # Paths B and C
    logoscore call delivery_module start
    +
  6. +
+

Step 3: Verify the node is running

+

Query the node's discv5 ENR to confirm it booted with a network identity and joined the Logos Testnet.

+
    +
  1. +

    Verify the status of the node:

    +
    # Path A
    docker exec logos-node logoscore status --json

    # Paths B and C
    logoscore status
    +
  2. +
  3. +

    Run the health query:

    +
    # Path A
    docker exec logos-node logoscore call delivery_module getNodeInfo MyENR --json | jq

    # Paths B and C
    logoscore call delivery_module getNodeInfo MyENR --json | jq
    +

    Expected output:

    +
    {
    "method": "getNodeInfo",
    "module": "delivery_module",
    "result": {
    "error": null,
    "success": true,
    "value": "enr:-LW4QItc5tHj3rWoFaaQIUWvaBYijDf2TJKW83SNdyylJVAYVoUlBl1h5..."
    },
    "status": "ok"
    }
    +
      +
    • The "value" field is your node's live ENR and will differ from the example above.
    • +
    • "success": true and "status": "ok" confirm the delivery node is running.
    • +
    +
  4. +
  5. +

    Stop the node when finished:

    +
      +
    • Path A: docker compose down
    • +
    • Paths B and C: logoscore stop
    • +
    +
  6. +
+

Troubleshooting delivery node setup

+

Why does the first docker compose up appear stuck?

+

The first build runs Nix and downloads release packages, which takes 30–45 minutes on a typical connection. The process is not hung — let it finish. Subsequent starts use the cached layers and complete in seconds.

+

Why does logoscore call return an error after load-module?

+

The daemon may not have finished starting. Wait a few seconds after logoscore -D returns and retry. For Path A, confirm the container is running with docker ps before calling docker exec logos-node logoscore ….

\ No newline at end of file diff --git a/messaging/delivery/use-logos-delivery-module-api-from-app/index.html b/messaging/delivery/use-logos-delivery-module-api-from-app/index.html new file mode 100644 index 00000000..9a60a122 --- /dev/null +++ b/messaging/delivery/use-logos-delivery-module-api-from-app/index.html @@ -0,0 +1,113 @@ +Use the Logos Delivery module API from an app | Logos documentation + + +

Use the Logos Delivery module API from an app

+

Get started integrating Logos messaging into a C++ module.

+

This procedure covers building a Logos module that calls the Logos Delivery API to subscribe to content topics, send messages, and react to delivery events. It gives application developers a working pattern for integrating Logos messaging into their C++ modules. A complete, runnable reference implementation is available in logos-delivery-demo (tag v0.1.0).

+

The two repositories used in this tutorial are logos-delivery-module (pinned to v0.1.3) and logos-delivery, which is a transitive dependency resolved and linked statically by Nix.

+

Before you start, make sure you have the following:

+
    +
  • macOS (aarch64 or x86_64) or Linux (aarch64 or x86_64)
  • +
  • ~1 GB of RAM
  • +
  • Nix with flakes enabled
  • +
+

What to expect

+
    +
  • You can subscribe to a content topic and receive messages.
  • +
  • You can send a message and confirm delivery by tracking messageSent and messagePropagated events.
  • +
  • You can integrate the full Logos Delivery lifecycle — create, start, subscribe, send, stop — into your C++ module.
  • +
+

Step 1: Create a Logos module

+

Scaffold a new module using logos-module-builder. For a full walkthrough, see the Build a Logos C++ UI module tutorial.

+
    +
  1. +

    Create and enter the project directory:

    +
    mkdir your-module-name && cd your-module-name
    +
  2. +
  3. +

    Initialise from the template:

    +
    nix flake init -t github:logos-co/logos-module-builder/0.2.0#ui-qml-backend
    +
  4. +
  5. +

    Initialise a Git repository and stage all generated files:

    +
    git init && git add -A
    +
  6. +
  7. +

    Remove the template's example sources. The scaffolded template includes ui_example files with mismatched class names and IIDs; leaving them causes build errors or plugin-load failures at runtime:

    +
    rm -f src/ui_example.rep src/ui_example_interface.h src/ui_example_plugin.h src/ui_example_plugin.cpp
    +
  8. +
+

Step 2: Declare delivery_module as a dependency

+

Add delivery_module to both metadata.json and flake.nix, pinning to the released tag so your app stays stable as the module API evolves.

+
info

The flake input name (delivery_module) must exactly match the dependency name in metadata.json. logos-module-builder uses this name to generate the typed wrapper at build time.

+
    +
  1. +

    In metadata.json, add delivery_module to the dependencies array:

    +
    {
    "name": "my_app",
    "dependencies": ["delivery_module"],
    ...
    }
    +
  2. +
  3. +

    In flake.nix, add a matching pinned input:

    +
    inputs = {
    logos-module-builder.url = "github:logos-co/logos-module-builder";
    delivery_module.url = "github:logos-co/logos-delivery-module/v0.1.3";
    };
    +
  4. +
+

Step 3: Call the delivery module API

+

In your module's initLogos() function, construct LogosModules with the provided LogosAPI*. The initLogos() function can be found in the .cpp file generated by the scaffolding — in the demo, this is src/delivery_module_plugin.cpp. LogosModules is generated at build time by logos-module-builder; include it via the umbrella header and keep it as a member on the plugin. The full API reference is in src/delivery_module_plugin.h and the module README.

+
info

Register event handlers before calling start() so you don't miss the first connectionStateChanged event.

+
    +
  1. +

    Initialise LogosModules in initLogos():

    +
    #include "logos_sdk.h" // generated umbrella — exposes LogosModules

    // In your plugin class:
    // LogosModules* m_logos = nullptr;

    void MyPlugin::initLogos(LogosAPI* api) {
    m_logos = new LogosModules(api);
    // m_logos->delivery_module is now the typed wrapper for the Logos Delivery module.
    }
    +
  2. +
  3. +

    Register event handlers. All lifecycle calls are synchronous; events arrive off-thread via the Qt event loop:

    +
    m_logos->delivery_module.on("connectionStateChanged", [](const QVariantList& data) {
    // data[0]: QString — connection status
    // data[1]: QString — timestamp (ns since epoch)
    });

    m_logos->delivery_module.on("messageReceived", [](const QVariantList& data) {
    // data[0]: QString — messageHash
    // data[1]: QString — contentTopic
    // data[2]: QByteArray — payload (raw bytes)
    // data[3]: QString — timestamp (ns since epoch)
    });

    m_logos->delivery_module.on("messageSent", [](const QVariantList& data) { /* requestId, hash, ts */ });
    m_logos->delivery_module.on("messagePropagated", [](const QVariantList& data) { /* requestId, hash, ts */ });
    m_logos->delivery_module.on("messageError", [](const QVariantList& data) { /* requestId, hash, error, ts */ });
    +
  4. +
  5. +

    Initialise the node with createNode, which returns a LogosResult. Always check success before continuing and surface getError() on failure. For a complete list of node configuration keys, see the Module Interface section of the README.

    +
    const QString cfg = R"({"logLevel":"INFO","mode":"Core","preset":"logos.test"})";

    LogosResult r = m_logos->delivery_module.createNode(cfg);
    if (!r.success) {
    qWarning() << "createNode failed:" << r.getError();
    return;
    }
    +
  6. +
  7. +

    Connect to the network with start(). The connectionStateChanged event fires on the Qt event loop once the node connects to peers:

    +
    LogosResult r = m_logos->delivery_module.start();
    if (!r.success) {
    qWarning() << "start failed:" << r.getError();
    return;
    }
    +
  8. +
  9. +

    Subscribe to a content topic using a LIP-23 content-topic string. Call subscribe() before any messages are sent on that topic:

    +
    LogosResult r = m_logos->delivery_module.subscribe(contentTopic);
    if (!r.success) {
    qWarning() << "subscribe failed:" << r.getError();
    }
    +
  10. +
  11. +

    Send a message. On success, getString() returns the request ID; track it through messageSentmessagePropagated events (or messageError):

    +
    // payload is a QByteArray of raw bytes (same type messageReceived delivers in data[2]).
    LogosResult r = m_logos->delivery_module.send(contentTopic, payload);
    if (!r.success) {
    qWarning() << "send failed:" << r.getError();
    return;
    }
    const QString requestId = r.getString();
    +
  12. +
  13. +

    Shut down cleanly with stop(). This tears down the underlying node and drops every active subscription and event listener:

    +
    LogosResult s = m_logos->delivery_module.stop();
    if (!s.success) qWarning() << "stop failed:" << s.getError();
    +
  14. +
+

Step 4: Build and run

+
    +
  1. +

    Build the module:

    +
    nix build
    +
  2. +
  3. +

    Preview the module using logos-standalone-app (for ui_qml modules):

    +
    nix run
    +
  4. +
  5. +

    Package as .lgx for installation into logos-basecamp:

    +
    nix build .#lgx
    +
  6. +
+

Troubleshooting the Logos Delivery module

+

createNode returns an unsuccessful LogosResult?

+

The JSON may be malformed, or an internal initialisation error occurred. Validate that the JSON is well-formed and that key names are camelCase matching WakuNodeConf fields. Set "logLevel": "DEBUG" for verbose output and inspect result.getError() for details.

+

send() returns an unsuccessful LogosResult?

+

The node was not started, or contentTopic is empty or invalid. Call start() first and verify it returned success. Confirm the content topic follows the LIP-23 format.

+

messageSent never fires after a successful send()?

+

The node may not be connected to peers yet, or the network layer rejected the message (for example, an RLN proof failure). Wait for connectionStateChanged before sending. If messageError fires, inspect data[2] for the error message.

+

messageReceived never fires?

+

subscribe() was not called before messages were sent, or the payload was sent on a different content topic. Call subscribe(topic) before any messages are sent on that topic.

+

Two instances of the same app on one host fail to start?

+

You pinned the same fixed port (for example, tcpPort) for both instances. Omit the port keys from the createNode config so each instance binds an OS-assigned ephemeral port — this is the default behaviour in v0.1.3. Alternatively, assign distinct explicit port values per instance.

+

createNode succeeds but a second call without stop() causes undefined behaviour?

+

createNode must be called exactly once per context. Call stop() and destroy the context before calling createNode again.

\ No newline at end of file diff --git a/messaging/get-started/faq/index.html b/messaging/get-started/faq/index.html new file mode 100644 index 00000000..b0d1b265 --- /dev/null +++ b/messaging/get-started/faq/index.html @@ -0,0 +1,20 @@ +FAQ: Logos Messaging | Logos documentation + + +

FAQ: Logos Messaging

+

Review common questions about how Logos Messaging works and how to build on it.

+

What is Logos Messaging,Logos Delivery?

+

Logos Messaging is the messaging component of the Logos technology stack, composed of Logos Delivery and Logos Chat. Logos Delivery is a peer-to-peer messaging network with DoS protection via RLN (Rate Limiting Nullifiers). Any application can use it to send and receive messages over an open, censorship-resistant network.

+

Logos Chat is a protocol and library that provides 1:1 and group conversations with end-to-end encryption using de-MLS - everything an application needs to add private chat without rolling its own protocol.

+

Does messaging require a gas fee?

+

No, sending and receiving messages using Logos Messaging involves no gas fee.

+

What encryption does Logos Delivery use?

+

Logos Delivery uses libp2p noise encryption for node-to-node connections. However, no default encryption method is applied to the data sent over the network. This design choice enhances Logos Delivery's encryption flexibility, encouraging developers to use custom protocols or Logos Delivery message payload encryption methods freely.

+

Where does Logos Delivery store the messages?

+

Logos Delivery's Store protocol is designed to temporarily store messages within the network. However, Logos Delivery does not guarantee the message's availability and recommends using Logos Storage for long-term storage.

+

Can Logos Delivery only be used for wallet-to-wallet messaging?

+

No, Logos Delivery is flexible and imposes no specific rules on identifiers.

+

How does Logos Delivery differ from IPFS?

+

Logos Delivery focuses on short, ephemeral, real-time messages, while IPFS focuses on large, long-term data storage. Although there's an overlap between the two technologies, Logos Delivery does not currently support large data for privacy reasons.

+

What are Rate Limiting Nullifiers (RLN)?

+

Rate Limiting Nullifier is a zero-knowledge (ZK) protocol enabling spam protection in a decentralised network while preserving privacy. Each message must be accompanied by a ZK proof, which Relay nodes verify to ensure the publishers do not send more messages than they are allowed. The ZK proof does not leak any private information about message publishers - it only proves they are members of a set of users allowed to publish a certain number of messages per given time frame.

\ No newline at end of file diff --git a/messaging/get-started/introduction-to-logos-messaging/index.html b/messaging/get-started/introduction-to-logos-messaging/index.html new file mode 100644 index 00000000..e4ec0fc4 --- /dev/null +++ b/messaging/get-started/introduction-to-logos-messaging/index.html @@ -0,0 +1,35 @@ +Introduction to Logos Messaging | Logos documentation + + +

Introduction to Logos Messaging

+

Learn how Logos provides private, censorship-resistant communication for decentralised applications.

+

Logos Messaging is the messaging layer of the Logos technology stack. It lets applications send messages, relay data, and coordinate with one another without relying on centralised servers or exposing metadata to surveillance. It uses a peer-to-peer relay network where messages propagate across nodes with no central broker, supporting private one-to-one messaging, group messaging, and public broadcast channels.

+

The basics

+
    +
  • Logos Messaging comprises two modules: Delivery, a peer-to-peer transport layer with spam protection, and Chat, a library for private, end-to-end encrypted conversations built on top of Delivery.
  • +
  • Messages propagate across a peer-to-peer relay network with no central broker. Anyone can run a node and there is no gas fee for sending or receiving messages.
  • +
  • Delivery is modular. Applications combine a set of protocols — Relay, RLN Relay, Filter, Store, and Light Push — to balance anonymity, scalability, and latency for their own use case.
  • +
  • Logos Messaging is built on top of libp2p, but adds a service network that anyone can use without deploying their own infrastructure, along with economic spam protection that libp2p does not provide.
  • +
+

Architecture

+

Logos Messaging is implemented as two modules that sit on top of the Logos networking layer: Delivery, which handles peer-to-peer transport, and Chat, which builds private conversations on top of it.

+

Delivery

+

Delivery is the peer-to-peer messaging network at the base of the messaging stack, and the reference implementation of the Logos Messaging protocol. It provides publish-subscribe messaging over an open, censorship-resistant network with no central broker, and it protects the network from spam using RLN, which verifies a zero-knowledge proof rather than a publisher's identity.

+

Delivery's functionality is split across a set of protocols:

+
    +
  • Relay extends libp2p GossipSub to provide the base publish/subscribe messaging that peers use to send and receive messages.
  • +
  • RLN Relay extends Relay with rate limiting nullifiers for economic, privacy-preserving spam prevention.
  • +
  • Filter lets light nodes selectively subscribe to messages by content topic, trading some privacy for lower bandwidth use.
  • +
  • Store temporarily retains messages so offline peers can retrieve what they missed, though it does not guarantee availability — applications that need durable storage should pair Delivery with Logos Storage instead.
  • +
  • Light Push lets bandwidth-constrained nodes publish a message and receive an acknowledgement that at least one peer received it, without guaranteeing network-wide propagation.
  • +
+

These protocols interact across three network domains: the discovery domain, where nodes locate other peers; the gossip domain, where Relay and RLN Relay distribute messages; and the request/response domain, where Store, Filter, and Light Push serve resource-limited nodes directly.

+

Messages are routed using content topics, developer-set metadata strings that let protocols filter, route, and retrieve messages.. How narrowly or broadly an application scopes its content topics is a direct privacy trade-off: Filter, Store, and Light Push all disclose content topics to peers, so grouping many users under shared topics increases k-anonymity, while splitting traffic into topic "buckets" helps distribute load for high-traffic applications.

+

Delivery is transport-agnostic, so developers choose the protocol that fits their environment: TCP by default for service nodes, and secure WebSocket for browsers, with WebRTC, WebTransport, and QUIC under research for future support.

+

To join the network, a node bootstraps using one of several peer discovery mechanisms — including static peers, DNS discovery, Discv5, and Peer Exchange — and then keeps looking for peers to maintain a healthy Relay mesh, reserve backup connections, and find peers with specific capabilities such as Store or Filter.

+

Chat

+

Chat is a protocol and library that provides one-to-one and group conversations with end-to-end encryption, using de-MLS for group messaging. It uses Delivery as its transport, so applications get everything they need to add private chat without designing and securing their own messaging protocol from scratch.

+

Security features

+

Logos Messaging's security guarantees vary by protocol and are documented openly in the underlying RFCs. Relay provides pseudonymity and unlinkability between publishers, subscribers, and their activity, backed by GossipSub's peer-scoring mechanism and ongoing RLN research for spam protection. Data confidentiality, integrity, and authenticity are available through the Waku Message format and Noise protocols. Protocols that require a direct connection, such as Store and Filter, trade away some of that unlinkability, since a node can associate a peer's PeerID with the topics or filters it queries.

+

Logos Messaging and libp2p

+

Logos Messaging is built on top of libp2p and shares much of its terminology, but it goes further in two ways. It is a service network: developers can use it without deploying their own infrastructure, though anyone can choose to run a node to help decentralise the network. It is also a turnkey solution for privacy-preserving, censorship-resistant, platform-agnostic messaging, including out-of-the-box support for offline and resource-limited devices through Store, Filter, and Light Push, plus economic spam protection through RLN Relay, which libp2p does not provide on its own.

\ No newline at end of file diff --git a/messaging/get-started/send-1-1-messages-logos-chat/index.html b/messaging/get-started/send-1-1-messages-logos-chat/index.html new file mode 100644 index 00000000..b9e46a79 --- /dev/null +++ b/messaging/get-started/send-1-1-messages-logos-chat/index.html @@ -0,0 +1,99 @@ +Send 1:1 messages with the Logos Chat app | Logos documentation + + +

Send 1:1 messages with the Logos Chat app

+

Try out end-to-end encrypted private messaging over the Logos network.

+

This procedure shows how to use the Logos Chat app to exchange encrypted 1:1 messages between two running instances. The app is a QML and C++ UI built on top of the logos-chat-module, which wraps the Logos Chat SDK. It demonstrates the basic private-messaging capabilities of the Logos Chat Module: ephemeral identity, intro-bundle handshake, and encrypted messaging with no central server. Use this procedure to verify the setup works or to explore the messaging flow for development purposes.

+
info

Identity, conversations, and message history exist only while the app is running. Restarting an instance gives it a new identity and clears all conversations.

+

You need the following to complete this procedure:

+
    +
  • Linux or macOS
  • +
  • Network access so both instances can reach each other
  • +
  • Two running instances of the app — on two separate machines, or in two terminals on the same machine
  • +
+

What to expect

+
    +
  • You can run the Logos Chat app without building from source by installing it through Logos Basecamp.
  • +
  • You can exchange encrypted messages between two instances in real time after completing the intro-bundle handshake.
  • +
  • You can verify delivery by confirming each message appears on the receiving instance within a few seconds.
  • +
+

Step 1: Run the Logos Chat app

+

You need two running instances to complete this procedure. Each instance can use either of the options below independently.

+
info

When using Nix, all build dependencies — including Qt6, logos-chat-module, and liblogoschat — are fetched automatically.

+

Option A — Run in Logos Basecamp

+
    +
  1. +

    Download and install the latest release of Logos Basecamp from github.com/logos-co/logos-basecamp/releases.

    +
  2. +
  3. +

    In the left bar, select Package Manager.

    +

    Logos Basecamp screenshot

    +
  4. +
  5. +

    Select logos-chat-module and logos-chat-ui, then click Install.

    +

    Logos Basecamp package installation screenshot

    +
  6. +
  7. +

    Wait until a green Installed label appears next to both modules.

    +
  8. +
  9. +

    In the left bar, select chat to launch the Logos Chat app.

    +
  10. +
+

Option B — Build and run locally with Nix

+
    +
  1. +

    Clone the repository and check out the target release:

    +
    git clone https://github.com/logos-co/logos-chatsdk-ui
    cd logos-chatsdk-ui
    git checkout v0.1.0
    +
  2. +
  3. +

    Run the standalone app:

    +
    # Nix fetches all dependencies automatically
    nix run
    +
  4. +
+

Step 2: Exchange intro bundles

+

The app auto-initialises on launch and displays your identity ID in the bottom status bar. Perform the steps below on both instances — referred to here as A and B.

+

Logos Chat App UI screenshot

+

On instance A:

+
    +
  1. +

    Click Get Intro Bundle, then click Copy to Clipboard.

    +

    The bundle is a string starting with logos_chatintro….

    +
  2. +
  3. +

    Send the copied bundle to instance B through any out-of-band channel.

    +
  4. +
  5. +

    Close the My Bundle popup.

    +
  6. +
+

On instance B:

+
    +
  1. Click + new.
  2. +
  3. Paste A's intro bundle into the dialogue, then type an intro message (default: Hello!).
  4. +
  5. Confirm. A new conversation appears in B's conversation list.
  6. +
+

Back on instance A:

+
    +
  1. Confirm the new conversation appears automatically in A's conversation list, then select it.
  2. +
  3. Verify that B's intro message is visible in the chat panel.
  4. +
+

Step 3: Send and receive messages

+
    +
  1. +

    Select the shared conversation in either instance.

    +
  2. +
  3. +

    Type a message in the message input field, then press Enter or click >>.

    +

    Your messages appear right-aligned in the chat panel; the counterparty's messages appear left-aligned, each with a timestamp.

    +
  4. +
  5. +

    From instance A, send a message and observe instance B.

    +

    Expected result: the exact message text appears as an incoming (left-aligned) bubble in B's chat panel within a few seconds. A reply from B appears as an incoming bubble in A.

    +
  6. +
+

Troubleshooting Logos Chat

+

Messages never arrive and the left panel shows "Waiting for connection…"

+

Both instances need to reach a shared bootstrap peer to connect to the peer-to-peer network and discover each other. Ensure that both instances have a stable internet connection for peer discovery.

+

A previously open conversation has disappeared

+

Conversations are ephemeral and are not persisted between sessions. Re-exchange intro bundles between the two instances to open a new conversation.

\ No newline at end of file diff --git a/mixnet/get-started/discover-nodes-and-send-messages-via-the-anoncomms-mixnet-demo-app/index.html b/mixnet/get-started/discover-nodes-and-send-messages-via-the-anoncomms-mixnet-demo-app/index.html new file mode 100644 index 00000000..3577374c --- /dev/null +++ b/mixnet/get-started/discover-nodes-and-send-messages-via-the-anoncomms-mixnet-demo-app/index.html @@ -0,0 +1,173 @@ +Discover nodes and send messages via the AnonComms Mixnet demo app | Logos documentation + + +

Discover nodes and send messages via the AnonComms Mixnet demo app

+
warning

This page is an early draft and may be incomplete or incorrect. Expect changes, missing prerequisites, and commands that might not work in your setup. We are actively working to complete and verify this content.

+

A. Outcome + value (required)

+
    +
  • Outcome (end goal): User will send anonymous chat messages over the mixnet by discovering mix nodes through Logos capability discovery.
  • +
  • Why it matters: Proves two core testnet v0.1 primitives work end-to-end: capability-based node discovery and anonymous message routing via the mixnet.
  • +
+

B. Scope + ownership

+
    +
  • +

    Journey name: Use the Mix Push Message App to send messages over the libp2p mixnet

    +
  • +
  • +

    Owner (GitHub + Discord): GitHub, Discord: prem_chaitanya

    +
  • +
  • +

    Applies to:

    + +
  • +
  • +

    Runtime target: Logos testnet v0.1.

    +
  • +
+

C. Runnable happy path

+
    +
  • +

    Prereqs:

    +
      +
    • Operating Systems: Linux or macOS
    • +
    • Build Tools: +
        +
      • CMake (3.16 or later)
      • +
      • Ninja build system
      • +
      • pkg-config
      • +
      • nix
      • +
      +
    • +
    +
  • +
  • +

    Commands:

    +
      +
    1. +

      Clone the chat-ui repo

      +
      git clone https://github.com/logos-co/logos-chat-ui.git
      +
    2. +
    3. +

      Change into the repo directory and check out the demo branch

      +
      cd logos-chat-ui
      git checkout logos-testnet-demo
      +
    4. +
    5. +

      Run the standalone application. You should not see any errors in the console.

      +
      nix run '.'
      +

      If you don't have flakes enabled globally, add experimental flags:

      +
      nix run '.' --extra-experimental-features 'nix-command flakes'
      +
    6. +
    +
  • +
  • +

    Expected outputs:

    +
      +
    1. +

      Once the app is up and running, the user should see the UI with the following:

      +
        +
      • Status shown as Ready
      • +
      • LP Peer count increasing over time before stabilising
      • +
      • Mix peer count increasing over time before stabilising
      • +
      • Warning message Waiting for network peers... disappears once 3 mix nodes are discovered.
      • +
      +
    2. +
    3. +

      After sending a message from the app (which is possible once the warning message disappears), the app should display the same message in the Messages section. +Note that it takes some time for this to happen due to latency introduced by mix nodes.

      +
    4. +
    5. +

      After sending a message, there are two methods to check if the message was delivered successfully:

      +
        +
      • +

        The message appears in the Messages section in the UI.

        +
      • +
      • +

        The following logs appear in the console indicating success:

        +
        [LOGOS_HOST "waku_module" ]: "WakuModulePlugin: Lightpush publish initiated successfully for topic: \"\""
        ChatBackend: Sent message to channel "baixa-chiado"
        +
      • +
      +
    6. +
    +
  • +
+

D. Configuration

+
    +
  • Required env vars / flags / config keys: Not Applicable
  • +
  • Example config snippet: No config, everything should work out of the box.
  • +
  • Default ports/endpoints: +
      +
    • The user's machine should be able to connect to external fleet nodes using TCP port 60000 for discovery, sending, and receiving messages.
    • +
    • Connectivity and message reception will improve if the user's machine is reachable externally (even if behind a NAT).
    • +
    +
  • +
+

E. Hardware requirements (required for node/client journeys)

+
    +
  • Target devices: Nothing specific for this app. Any general restrictions placed by Logos modules should be considered.
  • +
  • Minimum requirements: +
      +
    • CPU — 1 core
    • +
    • RAM — 2 GB
    • +
    • Storage (type + free space) — at least 20 GB of free space for building everything
    • +
    • Network bandwidth — No specific requirements
    • +
    +
  • +
  • Recommended requirements: Same as minimum requirements
  • +
  • Storage profile: No specific storage required.
  • +
+

F. Verify + troubleshoot

+
    +
  • Verification step: Run the app and confirm a sent message appears in the Messages section.
  • +
  • Known failures (top 3-5): +
      +
    1. +

      Orphan processes when killing the app via CLI or GUI

      +
        +
      • +

        Symptom — Sometimes the child processes are orphaned when the app is killed. Check using the command below:

        +
        ps -eaf | grep -i logos_host
        +
      • +
      • +

        Cause (if known) — Issue in liblogos

        +
      • +
      • +

        Fix/workaround — Kill orphaned processes:

        +
        pkill -f logos_host
        +
      • +
      +
    2. +
    3. +

      Sent messages may not appear in the received messages section if the underlying connectivity of Waku-relay is not healthy.

      +
    4. +
    +
  • +
+

G. Limits for v0.1

+
    +
  • Known issues: +
      +
    1. It may take 30 seconds to 1 minute to discover mix nodes. Until then, no messages can be sent using the app.
    2. +
    3. If the app is killed, some child processes are orphaned and continue running in the background. They must be killed before restarting the app, otherwise it may not work properly. Note that this may also kill any other logos_host processes running.
    4. +
    +
  • +
+

H. References

+ +
+

Time and Security (optional)

+
    +
  • Estimated time-to-complete: 15-20 minutes
  • +
  • Security / safety notes: None
  • +
\ No newline at end of file diff --git a/peer-discovery/welcome/index.html b/peer-discovery/welcome/index.html new file mode 100644 index 00000000..44ef49fe --- /dev/null +++ b/peer-discovery/welcome/index.html @@ -0,0 +1,5 @@ +Welcome | Logos documentation + + +

Welcome

+

Welcome to the Logos peer discovery documentation! We're working on the content.

\ No newline at end of file diff --git a/run-a-node/blockchain-node/readme/index.html b/run-a-node/blockchain-node/readme/index.html new file mode 100644 index 00000000..aeccbda7 --- /dev/null +++ b/run-a-node/blockchain-node/readme/index.html @@ -0,0 +1,11 @@ + + + + + + + + + \ No newline at end of file diff --git a/run-a-node/blockchain-node/run-a-logos-blockchain-node-from-cli/index.html b/run-a-node/blockchain-node/run-a-logos-blockchain-node-from-cli/index.html new file mode 100644 index 00000000..aeccbda7 --- /dev/null +++ b/run-a-node/blockchain-node/run-a-logos-blockchain-node-from-cli/index.html @@ -0,0 +1,11 @@ + + + + + + + + + \ No newline at end of file diff --git a/run-a-node/delivery-node/readme/index.html b/run-a-node/delivery-node/readme/index.html new file mode 100644 index 00000000..6782a87f --- /dev/null +++ b/run-a-node/delivery-node/readme/index.html @@ -0,0 +1,11 @@ + + + + + + + + + \ No newline at end of file diff --git a/run-a-node/delivery-node/run-logos-delivery-node/index.html b/run-a-node/delivery-node/run-logos-delivery-node/index.html new file mode 100644 index 00000000..6782a87f --- /dev/null +++ b/run-a-node/delivery-node/run-logos-delivery-node/index.html @@ -0,0 +1,11 @@ + + + + + + + + + \ No newline at end of file diff --git a/run-a-node/get-started/run-logos-node-blockchain-storage-delivery/index.html b/run-a-node/get-started/run-logos-node-blockchain-storage-delivery/index.html new file mode 100644 index 00000000..0d7d05b6 --- /dev/null +++ b/run-a-node/get-started/run-logos-node-blockchain-storage-delivery/index.html @@ -0,0 +1,205 @@ +Run a Logos node with blockchain, storage, and delivery | Logos documentation + + +

Run a Logos node with blockchain, storage, and delivery

+

Get started running a full Logos node with all three core modules on testnet v0.2.

+

This procedure covers installing and running a single Logos node with logoscore hosting the blockchain_module, storage_module, and delivery_module from one shared modules directory. It is intended for node operators who want to join the testnet and contribute to the Logos network. The steps assume a Linux host.

+

The default paths used throughout this procedure are:

+
/usr/local/bin/logoscore
/usr/local/bin/lgpd
/usr/local/bin/lgpm
/opt/logos-node/modules
/opt/logos-node/packages
/var/lib/logos-node
+

Before you start, make sure you have the following:

+
    +
  • Linux host with a public IPv4 address
  • +
  • Ports 3000/udp, 8090/udp, 8091/tcp, 9000/udp, and 30303/tcp open on the host firewall
  • +
  • Root or sudo access to install tools and create system users
  • +
+

What to expect

+
    +
  • You can run a full Logos node with all three modules active and publicly reachable on the testnet.
  • +
  • You can verify each module is healthy by querying the daemon and checking live port bindings.
  • +
  • You can configure the node for unattended operation using the systemd service pattern described in here.
  • +
+

Step 1: Install runtime tools

+

Install the system dependencies and download the three Logos CLI tools.

+
info

You can also install these tools by running:

curl -fsSL https://raw.githubusercontent.com/logos-co/logos-docs/main/resources/scripts/install-node-tools.sh | sh
export PATH="$PWD/bin:$PATH"
+
    +
  1. +

    Install curl, jq, wget, and FUSE support for AppImage binaries:

    +
    apt-get update
    apt-get install -y curl jq wget fuse3
    +
  2. +
  3. +

    Download the Linux release assets for logoscore, lgpd, and lgpm:

    +
    ToolRepository
    logoscorehttps://github.com/logos-co/logos-logoscore-cli
    lgpdhttps://github.com/logos-co/logos-package-downloader
    lgpmhttps://github.com/logos-co/logos-package-manager
    +

    For x86_64 Linux, these are the current download URLs as of 2026-06-25:

    +
    wget https://github.com/logos-co/logos-logoscore-cli/releases/download/v3/logoscore-x86_64-linux.AppImage
    wget https://github.com/logos-co/logos-package-downloader/releases/download/pre-release-99d70db-7/lgpd-x86_64-linux.tar.gz
    wget https://github.com/logos-co/logos-package-manager/releases/download/pre-release-05b2cf8-7/lgpm-x86_64-linux.tar.gz
    +
  4. +
  5. +

    Install the tools under /usr/local/bin:

    +
    install -m755 logoscore-x86_64-linux.AppImage /usr/local/bin/logoscore
    tar -xzf lgpd-x86_64-linux.tar.gz
    install -m755 lgpd-x86_64.AppImage /usr/local/bin/lgpd
    tar -xzf lgpm-x86_64-linux.tar.gz
    install -m755 lgpm-x86_64.AppImage /usr/local/bin/lgpm
    +
  6. +
  7. +

    Verify all three tools are accessible:

    +
    logoscore --help
    lgpd --help
    lgpm --help
    +
  8. +
+

Step 2: Prepare the host

+

Create the runtime user and the directory layout the node uses at runtime.

+
    +
  1. +

    Create the logos system user and data directories:

    +
    useradd --system --home /var/lib/logos-node --create-home --shell /usr/sbin/nologin logos
    mkdir -p /opt/logos-node/modules /opt/logos-node/packages
    mkdir -p /var/lib/logos-node/blockchain-module-testnet
    mkdir -p /var/lib/logos-node/storage-module
    mkdir -p /var/lib/logos-node/delivery-module
    chown -R logos:logos /var/lib/logos-node
    +
  2. +
  3. +

    Open these on the host firewall:

    +
    3000/udp
    8090/udp
    8091/tcp
    9000/udp
    30303/tcp
    +
  4. +
+

Step 3: Install modules

+

Download and install the three module packages from the configured module catalogue.

+
info

lgpd download fetches the version published in the catalogue. It does not automatically pull the newest commit from module repositories. Ensure the intended versions are published in the catalogue before running these commands.

+
    +
  1. +

    Download the module packages:

    +
    lgpd download blockchain_module --version 0.2.0 --output /opt/logos-node/packages
    lgpd download storage_module --output /opt/logos-node/packages
    lgpd download delivery_module --output /opt/logos-node/packages
    +
  2. +
  3. +

    Install all three packages into the shared modules directory:

    +
    lgpm --modules-dir /opt/logos-node/modules install --file /opt/logos-node/packages/blockchain_module-0.2.0.lgx
    lgpm --modules-dir /opt/logos-node/modules install --file /opt/logos-node/packages/storage_module-*.lgx
    lgpm --modules-dir /opt/logos-node/modules install --file /opt/logos-node/packages/delivery_module-*.lgx
    +
  4. +
  5. +

    Verify the installed versions:

    +
    jq -r '.name + " " + .version' /opt/logos-node/modules/*/manifest.json
    +
  6. +
+

Step 4: Start Logos Core

+

Start the logoscore daemon with the shared modules directory before loading any modules.

+
    +
  1. +

    Start logoscore in the foreground for a first manual run:

    +
    cd /var/lib/logos-node
    logoscore -m /opt/logos-node/modules
    +
      +
    • Keep this terminal open. Use a second terminal for all module commands.
    • +
    • For a detached run, pass -D to start in daemon mode: logoscore -m /opt/logos-node/modules -D
    • +
    +
  2. +
  3. +

    Verify the daemon is running:

    +
    logoscore status
    +
  4. +
+

Step 5: Configure and start the blockchain module

+

Load the blockchain module, generate the node config, and start the module.

+
info

user_config.yaml contains node-local wallet and key-management configuration. Keep it private, restrict file permissions, and do not publish it. Generate a fresh file for each node.

+
    +
  1. +

    Create the peer bootstrap file:

    +
    cd /var/lib/logos-node/blockchain-module-testnet
    cat > peers.json <<EOF
    {
    "initial_peers": [
    "/ip4/65.109.51.37/udp/3000/quic-v1/p2p/12D3KooWFrouXfmrR4nsLMtE7wu15DoMJ6VtoUtHinREZCvbWHar",
    "/ip4/65.109.51.37/udp/3001/quic-v1/p2p/12D3KooWJRGau8M1rjT7R5e4YYsgdFhsMX35nRDtMwCDjxQkXAHz",
    "/ip4/65.109.51.37/udp/3002/quic-v1/p2p/12D3KooWQXJavMDTRscjauFSgVAB1VLB6Rzpy2uY5SU9Tk7927tb",
    "/ip4/65.109.51.37/udp/50001/quic-v1/p2p/12D3KooWSQc7CcGtvWDPF1yCbBthFnQjprfCVHmfmNDUrSmqQsU1"
    ]
    }
    EOF
    +
  2. +
  3. +

    Load the module and generate user_config.yaml:

    +
    logoscore load-module blockchain_module
    cd /var/lib/logos-node/blockchain-module-testnet
    logoscore call blockchain_module generate_user_config "$(cat peers.json)"
    chmod 600 /var/lib/logos-node/user_config.yaml
    +
      +
    • generate_user_config writes user_config.yaml to the logoscore daemon working directory (/var/lib/logos-node/user_config.yaml with this guide's layout).
    • +
    • Important fields in user_config.yaml include:
    • +
    +
    FieldPurposeGuidance
    network.initial_peersBootstrap peersUse the current network document
    network.portPublic UDP P2P portKeep aligned with firewall/NAT, normally 3000
    api.listen_addressLocal API bindKeep private, normally 127.0.0.1:8080
    state.base_folderState directoryUse a persistent local path
    logger filtersLog verbosityUse INFO for unattended operation
    +
  4. +
  5. +

    Start the blockchain module:

    +
    logoscore call blockchain_module start /var/lib/logos-node/user_config.yaml ""
    +
      +
    • The second argument is intentionally an empty string; the blockchain module no longer requires a downloaded deployment.yaml file.
    • +
    +
  6. +
  7. +

    Verify the module is running:

    +
    logoscore call blockchain_module get_cryptarchia_info | jq -r .result.value | jq .
    +
  8. +
+

Step 6: Configure and start the storage module

+

Create the storage config and start the module. Replace <public-ip> with the node's public IPv4 address before running these commands.

+
    +
  1. +

    Create the storage config:

    +
    cd /var/lib/logos-node/storage-module
    mkdir -p storage-data
    cat > config.json <<EOF
    {
    "data-dir": "./storage-data",
    "log-level": "INFO",
    "listen-ip": "0.0.0.0",
    "listen-port": 8091,
    "disc-port": 8090,
    "nat": "extip:<public-ip>",
    "network": "logos.test"
    }
    EOF
    +
      +
    • config.json includes the following fields:
    • +
    +
    FieldPurpose
    data-dirStorage repository path
    log-levelLog verbosity
    listen-ipLocal TCP bind address
    listen-portPublic TCP libp2p port
    disc-portPublic UDP discovery port
    natPublic IP advertisement mode
    networkStorage network preset
    +
      +
    • Use fixed listen-port and disc-port; do not leave public nodes on random ports.
    • +
    • The logos.test preset provides the storage bootstrap settings.
    • +
    • Fields
    • +
    +
    info
    +
  2. +
+

To run storage with mix support, generate the config from the published mix bootstrap data:

+
cd /var/lib/logos-node/storage-module
cat > make-mix-storage-config.sh <<'EOF'
#!/usr/bin/env bash
set -e

data_dir=${1:-"./logos-storage-data"}
udp_spr_json=$(curl -s https://logos-storage-network.fra1.digitaloceanspaces.com/v0.2/udp-sprs.json)
tcp_spr_json=$(curl -s https://logos-storage-network.fra1.digitaloceanspaces.com/v0.2/tcp-sprs.json)

wget https://logos-storage-network.fra1.digitaloceanspaces.com/v0.2/mix-pool.json
mp_path=$(realpath "./mix-pool.json")

cat <<JSON
{
"nat": "any",
"log-level": "DEBUG",
"mix-enabled": true,
"listen-port": 8080,
"disc-port": 8090,
"bootstrap-node": $udp_spr_json,
"dht-mix-proxy": $tcp_spr_json,
"data-dir": "${data_dir}",
"mix-pool": "${mp_path}"
}
JSON
EOF

chmod 755 make-mix-storage-config.sh
./make-mix-storage-config.sh > config.json
+

:::

+
    +
  1. +

    Load and start the storage module:

    +
    cd /var/lib/logos-node/storage-module
    logoscore load-module storage_module
    logoscore call storage_module init @config.json
    logoscore call storage_module start
    +
      +
    • +

      If using the mix config, also enable private queries and verify with a test download:

      +
      logoscore call storage_module togglePrivateQueries true
      logoscore call storage_module downloadToUrl zDvZRwzkzrrYB6sS1rRpRLt4gBhc1pWoyTSjkfszfmj1seaYYLCZ ./farewell-to-westphalia.pdf false 65536
      +
    • +
    +
  2. +
+

Step 7: Configure and start the delivery module

+

Create the delivery config and start the module. Replace <public-ip> with the node's public IPv4 address before running these commands.

+
    +
  1. +

    Create the delivery config:

    +
    cd /var/lib/logos-node/delivery-module
    cat > config.json <<EOF
    {
    "preset": "logos.test",
    "mode": "Core",
    "logLevel": "INFO",
    "tcpPort": 30303,
    "discv5UdpPort": 9000,
    "discv5Discovery": true,
    "nat": "extip:<public-ip>"
    }
    EOF
    +
      +
    • config.json includes the following fields:
    • +
    +
    FieldPurpose
    presetNetwork preset
    modeDelivery node mode
    logLevelLog verbosity
    tcpPortPublic TCP P2P port
    discv5UdpPortPublic UDP discovery port
    discv5DiscoveryEnable discv5 discovery
    natPublic IP advertisement mode
    +
      +
    • Use fixed tcpPort and discv5UdpPort; do not leave public nodes on random ports.
    • +
    • The logos.test preset provides the delivery network bootstrap settings.
    • +
    +
  2. +
  3. +

    Load and start the delivery module:

    +
    cd /var/lib/logos-node/delivery-module
    logoscore load-module delivery_module
    logoscore call delivery_module createNode @config.json
    logoscore call delivery_module start
    +
  4. +
  5. +

    Verify the delivery module is running:

    +
    logoscore call delivery_module getNodeInfo Version
    logoscore call delivery_module getNodeInfo MyBoundPorts
    +
  6. +
+

Step 8: Verify the full node is healthy

+

Run health checks against the daemon and all three modules to confirm the node is fully operational.

+
    +
  1. +

    Check the daemon and all loaded modules:

    +
    logoscore status --json
    +

    Expected modules in the output: storage_module, blockchain_module, delivery_module, capability_module.

    +
  2. +
  3. +

    Verify all ports are bound correctly:

    +
    ss -lntup | egrep '(:3000|:8090|:8091|:9000|:30303|:8080)'
    +

    Expected bindings:

    +
    0.0.0.0:3000/udp
    0.0.0.0:8090/udp
    0.0.0.0:8091/tcp
    0.0.0.0:9000/udp
    0.0.0.0:30303/tcp
    127.0.0.1:8080/tcp
    +
  4. +
  5. +

    Check the blockchain module sync state:

    +
    logoscore call blockchain_module get_cryptarchia_info | jq -r .result.value | jq .
    +
  6. +
  7. +

    Check the delivery module bound ports:

    +
    logoscore call delivery_module getNodeInfo MyBoundPorts
    +
  8. +
+

Optional: Run the node unattended with systemd

+

Use a dedicated service for logoscore and a separate bootstrap script for module startup. Do not start modules from ExecStartPost in the logoscore service — slow or failing module starts may cause systemd to kill the daemon.

+

Daemon service unit:

+
[Unit]
Description=Logos Node
After=network-online.target
Wants=network-online.target

[Service]
User=logos
Group=logos
WorkingDirectory=/var/lib/logos-node
Environment=HOME=/var/lib/logos-node
ExecStart=/usr/local/bin/logoscore -m /opt/logos-node/modules -D
Restart=always
RestartSec=10
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target
+

The bootstrap script should wait for logoscore status, load and start the storage module, load and start the blockchain module, and load and start the delivery module. It should tolerate already-loaded modules and slow module starts.

+

Recommended journald retention to cap disk usage:

+
[Journal]
SystemMaxUse=200M
SystemKeepFree=1G
MaxRetentionSec=7day
MaxFileSec=1day
+

Use the INFO log level for unattended operation; use DEBUG only for short troubleshooting windows.

\ No newline at end of file diff --git a/run-a-node/storage-node/readme/index.html b/run-a-node/storage-node/readme/index.html new file mode 100644 index 00000000..bba20eb9 --- /dev/null +++ b/run-a-node/storage-node/readme/index.html @@ -0,0 +1,11 @@ + + + + + + + + + \ No newline at end of file diff --git a/run-a-node/storage-node/run-logos-storage-node/index.html b/run-a-node/storage-node/run-logos-storage-node/index.html new file mode 100644 index 00000000..bba20eb9 --- /dev/null +++ b/run-a-node/storage-node/run-logos-storage-node/index.html @@ -0,0 +1,11 @@ + + + + + + + + + \ No newline at end of file diff --git a/run-an-app/basecamp/install-logos-basecamp/index.html b/run-an-app/basecamp/install-logos-basecamp/index.html new file mode 100644 index 00000000..bc1ca6bd --- /dev/null +++ b/run-an-app/basecamp/install-logos-basecamp/index.html @@ -0,0 +1,11 @@ + + + + + + + + + \ No newline at end of file diff --git a/run-an-app/basecamp/readme/index.html b/run-an-app/basecamp/readme/index.html new file mode 100644 index 00000000..bc1ca6bd --- /dev/null +++ b/run-an-app/basecamp/readme/index.html @@ -0,0 +1,11 @@ + + + + + + + + + \ No newline at end of file diff --git a/run-an-app/blockchain-ui-app/bridge-assets-from-logos-blockchain-to-zone-using-app/index.html b/run-an-app/blockchain-ui-app/bridge-assets-from-logos-blockchain-to-zone-using-app/index.html new file mode 100644 index 00000000..2f484af7 --- /dev/null +++ b/run-an-app/blockchain-ui-app/bridge-assets-from-logos-blockchain-to-zone-using-app/index.html @@ -0,0 +1,11 @@ + + + + + + + + + \ No newline at end of file diff --git a/run-an-app/blockchain-ui-app/build-and-run-logos-blockchain-node-app-ui/index.html b/run-an-app/blockchain-ui-app/build-and-run-logos-blockchain-node-app-ui/index.html new file mode 100644 index 00000000..8d6ca6a9 --- /dev/null +++ b/run-an-app/blockchain-ui-app/build-and-run-logos-blockchain-node-app-ui/index.html @@ -0,0 +1,11 @@ + + + + + + + + + \ No newline at end of file diff --git a/run-an-app/blockchain-ui-app/claim-leader-rewards-in-logos-blockchain-ui-app/index.html b/run-an-app/blockchain-ui-app/claim-leader-rewards-in-logos-blockchain-ui-app/index.html new file mode 100644 index 00000000..ce3853f8 --- /dev/null +++ b/run-an-app/blockchain-ui-app/claim-leader-rewards-in-logos-blockchain-ui-app/index.html @@ -0,0 +1,11 @@ + + + + + + + + + \ No newline at end of file diff --git a/run-an-app/blockchain-ui-app/move-assets-using-logos-blockchain-app/index.html b/run-an-app/blockchain-ui-app/move-assets-using-logos-blockchain-app/index.html new file mode 100644 index 00000000..2e8038df --- /dev/null +++ b/run-an-app/blockchain-ui-app/move-assets-using-logos-blockchain-app/index.html @@ -0,0 +1,11 @@ + + + + + + + + + \ No newline at end of file diff --git a/run-an-app/blockchain-ui-app/readme/index.html b/run-an-app/blockchain-ui-app/readme/index.html new file mode 100644 index 00000000..8d6ca6a9 --- /dev/null +++ b/run-an-app/blockchain-ui-app/readme/index.html @@ -0,0 +1,11 @@ + + + + + + + + + \ No newline at end of file diff --git a/run-an-app/index.html b/run-an-app/index.html new file mode 100644 index 00000000..9ffe5582 --- /dev/null +++ b/run-an-app/index.html @@ -0,0 +1,4 @@ +Run an app | Logos documentation + + + \ No newline at end of file diff --git a/run-an-app/lez-wallet-ui/initiate-native-token-transfers-on-lez-with-wallet-ui/index.html b/run-an-app/lez-wallet-ui/initiate-native-token-transfers-on-lez-with-wallet-ui/index.html new file mode 100644 index 00000000..ca227aee --- /dev/null +++ b/run-an-app/lez-wallet-ui/initiate-native-token-transfers-on-lez-with-wallet-ui/index.html @@ -0,0 +1,11 @@ + + + + + + + + + \ No newline at end of file diff --git a/run-an-app/lez-wallet-ui/readme/index.html b/run-an-app/lez-wallet-ui/readme/index.html new file mode 100644 index 00000000..ca227aee --- /dev/null +++ b/run-an-app/lez-wallet-ui/readme/index.html @@ -0,0 +1,11 @@ + + + + + + + + + \ No newline at end of file diff --git a/sitemap.xml b/sitemap.xml new file mode 100644 index 00000000..add814b5 --- /dev/null +++ b/sitemap.xml @@ -0,0 +1 @@ +https://docs.logos.co/weekly0.5https://docs.logos.co/basecamp/get-started/install-logos-basecampweekly0.5https://docs.logos.co/blockchain/blend/join-the-blend-network-as-a-core-nodeweekly0.5https://docs.logos.co/blockchain/concepts/about-bedrockweekly0.5https://docs.logos.co/blockchain/concepts/about-cryptarchiaweekly0.5https://docs.logos.co/blockchain/concepts/about-mantleweekly0.5https://docs.logos.co/blockchain/concepts/about-the-blend-networkweekly0.5https://docs.logos.co/blockchain/concepts/about-zonesweekly0.5https://docs.logos.co/blockchain/get-started/introduction-to-the-logos-blockchainweekly0.5https://docs.logos.co/blockchain/get-started/run-a-logos-blockchain-node-from-basecampweekly0.5https://docs.logos.co/blockchain/get-started/run-a-logos-blockchain-node-from-cliweekly0.5https://docs.logos.co/blockchain/node-app/bridge-assets-from-logos-blockchain-to-zone-using-appweekly0.5https://docs.logos.co/blockchain/node-app/build-and-run-logos-blockchain-node-app-uiweekly0.5https://docs.logos.co/blockchain/node-app/claim-leader-rewards-in-logos-blockchain-ui-appweekly0.5https://docs.logos.co/blockchain/node-app/move-assets-using-logos-blockchain-appweekly0.5https://docs.logos.co/blockchain/zone-sdk/bridge-assets-between-blockchain-and-zoneweekly0.5https://docs.logos.co/blockchain/zone-sdk/inscribe-data-on-chain-using-zone-sdkweekly0.5https://docs.logos.co/blockchain/zone-sdk/operate-decentralised-zone-with-round-robin-sequencer-rotationweekly0.5https://docs.logos.co/build-an-appweekly0.5https://docs.logos.co/build-an-app/tutorials/build-dapp-with-logos-zone-sdkweekly0.5https://docs.logos.co/build-an-app/tutorials/create-and-use-an-amm-liquidity-pool-on-the-logos-execution-zoneweekly0.5https://docs.logos.co/contribute/welcomeweekly0.5https://docs.logos.co/core/build-modules/build-a-logos-cpp-ui-moduleweekly0.5https://docs.logos.co/core/build-modules/build-and-run-a-logos-core-moduleweekly0.5https://docs.logos.co/core/build-modules/build-logos-core-module-that-uses-service-discovery-apiweekly0.5https://docs.logos.co/core/build-modules/install-and-load-a-module-in-logos-basecampweekly0.5https://docs.logos.co/core/build-modules/start-a-logos-module-from-the-cliweekly0.5https://docs.logos.co/core/build-modules/wrap-a-c-library-as-a-logos-core-moduleweekly0.5https://docs.logos.co/core/reference/lgx-package-format-and-bundling-referenceweekly0.5https://docs.logos.co/core/reference/logos-cli-referenceweekly0.5https://docs.logos.co/get-started/glossaryweekly0.5https://docs.logos.co/get-started/logos-ecosystem-repositoriesweekly0.5https://docs.logos.co/get-started/what-is-logosweekly0.5https://docs.logos.co/lez/accounts/set-up-shared-private-lez-accountweekly0.5https://docs.logos.co/lez/follow-chain/run-lez-indexerweekly0.5https://docs.logos.co/lez/follow-chain/start-and-use-instance-of-lez-explorerweekly0.5https://docs.logos.co/lez/get-started/connect-wallet-cli-to-lez-testnetweekly0.5https://docs.logos.co/lez/get-started/initiate-native-token-transfers-on-lez-with-wallet-uiweekly0.5https://docs.logos.co/lez/get-started/introduction-to-the-logos-execution-zoneweekly0.5https://docs.logos.co/lez/get-started/lez-quickstartweekly0.5https://docs.logos.co/lez/transfer-tokens/create-and-transfer-custom-tokens-on-the-logos-execution-zoneweekly0.5https://docs.logos.co/lez/transfer-tokens/transfer-native-lez-tokens-between-public-private-statesweekly0.5https://docs.logos.co/lez/transfer-tokens/transfer-native-tokens-on-the-logos-execution-zoneweekly0.5https://docs.logos.co/messaging/chat-module/build-logos-module-that-uses-chat-module-apiweekly0.5https://docs.logos.co/messaging/chat-module/send-anonymised-messages-over-the-mix-networkweekly0.5https://docs.logos.co/messaging/concepts/content-topicsweekly0.5https://docs.logos.co/messaging/concepts/logos-delivery-and-libp2pweekly0.5https://docs.logos.co/messaging/concepts/network-domainsweekly0.5https://docs.logos.co/messaging/concepts/protocolsweekly0.5https://docs.logos.co/messaging/concepts/security-featuresweekly0.5https://docs.logos.co/messaging/concepts/transportsweekly0.5https://docs.logos.co/messaging/concepts/understand-peer-discovery/discv5weekly0.5https://docs.logos.co/messaging/concepts/understand-peer-discovery/dns-discoveryweekly0.5https://docs.logos.co/messaging/concepts/understand-peer-discovery/peer-discoveryweekly0.5https://docs.logos.co/messaging/concepts/understand-peer-discovery/peer-exchangeweekly0.5https://docs.logos.co/messaging/concepts/understand-peer-discovery/static-peersweekly0.5https://docs.logos.co/messaging/delivery/run-logos-delivery-nodeweekly0.5https://docs.logos.co/messaging/delivery/use-logos-delivery-module-api-from-appweekly0.5https://docs.logos.co/messaging/get-started/faqweekly0.5https://docs.logos.co/messaging/get-started/introduction-to-logos-messagingweekly0.5https://docs.logos.co/messaging/get-started/send-1-1-messages-logos-chatweekly0.5https://docs.logos.co/mixnet/get-started/discover-nodes-and-send-messages-via-the-anoncomms-mixnet-demo-appweekly0.5https://docs.logos.co/peer-discovery/welcomeweekly0.5https://docs.logos.co/run-a-node/get-started/run-logos-node-blockchain-storage-deliveryweekly0.5https://docs.logos.co/run-an-appweekly0.5https://docs.logos.co/storage/get-started/run-logos-storage-nodeweekly0.5https://docs.logos.co/storage/get-started/set-up-and-use-logos-storage-uiweekly0.5 \ No newline at end of file diff --git a/storage/get-started/run-logos-storage-node/index.html b/storage/get-started/run-logos-storage-node/index.html new file mode 100644 index 00000000..6c791d85 --- /dev/null +++ b/storage/get-started/run-logos-storage-node/index.html @@ -0,0 +1,126 @@ +Run a Logos storage node | Logos documentation + + +

Run a Logos storage node

+

Get started running a Logos storage node and uploading your first file to the Logos network.

+

This procedure covers how to build and run the Logos Storage Module, connect it to the testnet bootstrap nodes, publish a file, and verify that the file can be downloaded. It is intended for node operators on testnet v0.2 who want to contribute storage capacity to the Logos network.

+

Before you start, make sure you have the following:

+
    +
  • +

    Linux (tested on Ubuntu 22.04)

    +
  • +
  • +

    Nix with flakes enabled. Install from nixos.org, then enable flakes:

    +
    mkdir -p ~/.config/nix
    echo 'experimental-features = nix-command flakes' >> ~/.config/nix/nix.conf
    +
  • +
  • +

    logoscore, and lgpm installed. To install these tools, use the install-node-tools.sh helper script:

    +
    curl -fsSL https://raw.githubusercontent.com/logos-co/logos-docs/main/resources/scripts/install-node-tools.sh | sh
    export PATH="$PWD/bin:$PATH"
    +
  • +
  • +

    jq on your PATH — used to pull the uploaded CID out of the manifests JSON. Verify: jq --version

    +
  • +
+

What to expect

+
    +
  • You can connect a Logos Storage node to the testnet and have it listed among the bootstrap peers.
  • +
  • You can publish a file to the network and retrieve a content address to share with other nodes.
  • +
  • You can download the file back from the network and confirm it lands on disk.
  • +
+

Build and install the storage module

+
    +
  1. +

    Build the module package with Nix:

    +
    nix build 'github:logos-co/logos-storage-module/v2.0.1#lgx' -o storage-lgx
    +
      +
    • This produces a logos-storage_module-module-lib.lgx package in ./storage-lgx/.
    • +
    +
    info

    The initial Nix build takes 15–20 minutes on first run. Subsequent builds use the Nix cache and complete in seconds.

    +
  2. +
  3. +

    Install the package into a local modules directory using lgpm:

    +
    lgpm --modules-dir ./modules install --file storage-lgx/*.lgx
    +
  4. +
+

Start the daemon and load the storage module

+

Run logoscore with the modules directory, then load and initialise the storage module against the testnet config.

+
    +
  1. +

    Start the logoscore daemon in background mode:

    +
    logoscore -D -m ./modules
    +
  2. +
  3. +

    Verify the daemon is running:

    +
    logoscore status
    +
  4. +
  5. +

    Create the storage config:

    +
    mkdir -p storage-data
    cat > config.json <<EOF
    {
    "data-dir": "./storage-data",
    "log-level": "INFO",
    "listen-ip": "0.0.0.0",
    "listen-port": 8091,
    "disc-port": 8090,
    "nat": "extip:<public-ip>",
    "network": "logos.test"
    }
    EOF
    +
      +
    • config.json includes the following fields:
    • +
    +
    FieldPurpose
    data-dirStorage repository path
    log-levelLog verbosity
    listen-ipLocal TCP bind address
    listen-portPublic TCP libp2p port
    disc-portPublic UDP discovery port
    natPublic IP advertisement mode
    networkStorage network preset
    +
      +
    • Use fixed listen-port and disc-port; do not leave public nodes on random ports.
    • +
    • The logos.test preset provides the storage bootstrap settings.
    • +
    • Fields
    • +
    +
    info

    To run storage with mix support, generate the config from the published mix bootstrap data:

    cat > make-mix-storage-config.sh <<'EOF'
    #!/usr/bin/env bash
    set -e

    data_dir=${1:-"./logos-storage-data"}
    udp_spr_json=$(curl -s https://logos-storage-network.fra1.digitaloceanspaces.com/v0.2/udp-sprs.json)
    tcp_spr_json=$(curl -s https://logos-storage-network.fra1.digitaloceanspaces.com/v0.2/tcp-sprs.json)

    wget https://logos-storage-network.fra1.digitaloceanspaces.com/v0.2/mix-pool.json
    mp_path=$(realpath "./mix-pool.json")

    cat <<JSON
    {
    "nat": "any",
    "log-level": "DEBUG",
    "mix-enabled": true,
    "listen-port": 8080,
    "disc-port": 8090,
    "bootstrap-node": $udp_spr_json,
    "dht-mix-proxy": $tcp_spr_json,
    "data-dir": "${data_dir}",
    "mix-pool": "${mp_path}"
    }
    JSON
    EOF

    chmod 755 make-mix-storage-config.sh
    ./make-mix-storage-config.sh > config.json
    +
  6. +
  7. +

    Load the storage module, initialise it with the testnet configuration, and start it:

    +
    logoscore load-module storage_module
    logoscore call storage_module init @config.json
    logoscore call storage_module start
    +
      +
    • +

      If using the mix config, also enable private queries and verify with a test download:

      +
      logoscore call storage_module togglePrivateQueries true
      logoscore call storage_module downloadToUrl zDvZRwzkzrrYB6sS1rRpRLt4gBhc1pWoyTSjkfszfmj1seaYYLCZ ./farewell-to-westphalia.pdf false 65536
      +
    • +
    +
  8. +
+

Publish and download a file

+

Once the node is running and connected to the testnet, publish a file and verify the round-trip.

+
    +
  1. +

    Upload a file to the network with uploadUrl:

    +
    logoscore call storage_module uploadUrl <file-path-or-url> <chunk-size-in-bytes>
    +
    info

    The default chunk size is 65536.

    +
  2. +
  3. +

    After a second, extract the content ID (CID) from the first manifests entry:

    +
    sleep 1

    logoscore call storage_module manifests \
    | jq -er '.result.value[0].cid' > cid.txt
    +
  4. +
  5. +

    Download the file back from the network with downloadToUrl. You need the CID for this step.

    +
    logoscore call storage_module downloadToUrl "$(cat cid.txt)" <destination-path> false <chunk-size-in-bytes>
    +
      +
    • downloadToUrl takes a local flag which reads only from locally cached data if set to true.
    • +
    +
  6. +
  7. +

    Confirm the downloaded file is present at <output-path> and matches the original.

    +
  8. +
+

Remove content and destroy the storage node

+

To clear your local storage and destroy the storage node, follow the steps below.

+
    +
  1. +

    Remove content from local storage by its CID:

    +
    logoscore call storage_module remove "$(cat cid.txt)"
    +
  2. +
  3. +

    Confirm content is gone:

    +
    sleep 1

    logoscore call storage_module exists "$(cat cid.txt)"
    # should return false
    +
  4. +
  5. +

    Stop the storage node:

    +
    logoscore call storage_module stop
    +
  6. +
  7. +

    Wait a bit and destroy the entire storage context:

    +
    sleep 2

    logoscore call storage_module destroy
    +
  8. +
+

Troubleshooting Logos Storage

+

Why does downloadToUrl time out when downloading from a different machine?

+

NAT is blocking the peer connection. When running multiple nodes across machines, the external IP and port mapping must be configured in config.json. See the NAT section of the Logos Storage Module documentation for guidance.

\ No newline at end of file diff --git a/storage/get-started/set-up-and-use-logos-storage-ui/index.html b/storage/get-started/set-up-and-use-logos-storage-ui/index.html new file mode 100644 index 00000000..00ff23b6 --- /dev/null +++ b/storage/get-started/set-up-and-use-logos-storage-ui/index.html @@ -0,0 +1,124 @@ +Set up and use the Logos Storage UI | Logos documentation + + +

Set up and use the Logos Storage UI

+

Get started sharing and downloading files on the Logos Storage network

+

The Logos Storage UI is a file-sharing application built on top of the Logos Storage Module. This guide covers building the application with Nix, configuring your node's network connectivity, and using the UI to share, download, and delete files. It is intended for node operators running the application on Linux or macOS.

+

Before you start, have the following ready:

+
    +
  • +

    Nix with flakes enabled. Install from nixos.org, then enable flakes:

    +
    mkdir -p ~/.config/nix
    echo 'experimental-features = nix-command flakes' >> ~/.config/nix/nix.conf
    +

    Verify: nix flake --help >/dev/null 2>&1 && echo "Flakes enabled"

    +
  • +
  • +

    Git with submodules support

    +
  • +
  • +

    A router where you can configure port forwarding or that supports UPnP/NAT-PMP

    +
  • +
+

What to expect

+
    +
  • You can build and run a standalone Logos Storage UI application using a single nix build command.
  • +
  • You can configure your node's network connectivity to make it reachable from the internet.
  • +
  • You can share files with other nodes and download files shared by others using a Content Identifier (CID).
  • +
+

Step 1: Build the application

+

The application is built using Nix flakes. The output includes the storage UI plugin and supporting binaries.

+
info

If Nix flakes are not enabled globally, add experimental-features = nix-command flakes to ~/.config/nix/nix.conf.

+
    +
  1. +

    Clone the logos-storage-ui repository and enter the project directory:

    +
    git clone --recurse-submodules https://github.com/logos-co/logos-storage-ui.git
    cd logos-storage-ui
    +
  2. +
  3. +

    Run the build command:

    +
    nix build
    +
  4. +
  5. +

    Confirm the build succeeded by checking the result/ directory for the following outputs on macOS (.dylib files are replaced with .so files on Linux):

    +
    result/
    └── lib/
    ├── storage_ui_plugin.dylib # Qt plugin (loaded by the app)
    └── storage_ui_replica_factory.dylib
    +
  6. +
  7. +

    Enter development shell (Optional)

    +
    # Enter development shell with all dependencies
    nix develop
    +
  8. +
  9. +

    Launch the application:

    +
    nix run
    +
      +
    • +

      To override a dependency with a local version, use --override-input. For example:

      +
      nix run --override-input storage_module/logos-storage git+file:///somewhere/logos-storage-nim?submodules=1
      +
    • +
    +
  10. +
+

Step 2: Configure network connectivity

+

Your node must be reachable from the internet. Choose the setup option that matches your network environment.

+
ScenarioSetup optionDetails
Behind NAT with UPnP or NAT-PMPGuided followed by UPnPLogos Storage configures the network automatically.
Behind NAT, manual port forwarding availableGuided followed by Port ForwardingRequires forwarding one TCP port (chosen during onboarding) and UDP 8090.
Custom or complex networkAdvancedDisplays a prepopulated configuration JSON you can edit manually. See the API reference.
+

The active configuration is saved to ${HOME}/.logos_storage/config.json. Change this file and restart the Storage Module to apply the changes.

+

The default settings are saved to ~/.config/Logos/LogosStorage.conf on Linux, ~/Library/Preferences/Logos.LogosStorage.plist on macOS, and HKCU\Software\Logos\LogosStorage (Registry) on Windows.

+
    +
  1. +

    Launch the application and select the setup option that matches your network environment from the table above and click Continue.

    +
  2. +
  3. +

    Wait for the connectivity checker to confirm your node is reachable. A message reading "your node is up and reachable" confirms success.

    +
      +
    • If the node is unreachable, see Troubleshooting Logos Storage below.
    • +
    • You can choose to continue without connectivity, but you will only be able to download files from other nodes.
    • +
    +
  4. +
+

Step 3: Share a file

+
    +
  1. +

    In the UI, locate and click the upload panel. A file selector opens.

    +
  2. +
  3. +

    Select the file you want to share and click Open. The file is uploaded to your node and sharing begins automatically.

    +
  4. +
  5. +

    Copy the Content Identifier (CID) displayed in the upload panel — for example, zDvZRwzm49ZJLzxheYtydzx6AcNVSrf69LriUWjPr1SNLVnaXfj2. Share this string with others so they can download the file.

    +
  6. +
+

Step 4: Download a file

+
    +
  1. +

    Paste the file's CID into the Fetch manifest panel and click Fetch. The file's metadata downloads from the network and an entry appears in the Manifests list at the bottom of the UI.

    +
  2. +
  3. +

    Next to the manifest entry, click the Download icon. A file selector will open.

    +
  4. +
  5. +

    Choose a save location and click Open. The download progress widget will show progress in real-time.

    +
  6. +
+

Step 5: Delete a file

+

To stop sharing a file, click the trash bin icon next to the manifest entry for that file. This removes the file from your node and stops sharing it.

+

Troubleshooting Logos Storage

+

Logos Storage requires two open ports to function:

+
    +
  • UDP (8090 by default) — used for discovery and DHT operations.
  • +
  • TCP (8500 by default) — used for data transfer and peer connections.
  • +
+

Node has no peers

+

Symptom: The node starts successfully but never connects to any peer.

+

Cause: Discovery is unavailable, typically because another process is using UDP port 8090.

+

Fix: Ensure no other process is using port 8090, or change the default port in the advanced configuration JSON.

+

UPnP is not working

+

Symptom: You selected the UPnP option during setup but the node remains unreachable.

+

Cause: Many routers have UPnP disabled by default.

+

Fix: Enable UPnP on your router, or switch to the manual Port Forwarding configuration.

+

Manual port forwarding is configured but node is unreachable

+

Symptom: Both UDP and TCP ports are configured but the node is still unreachable.

+

Cause: The ports are not open on your router.

+

Fix: Enable port forwarding for UDP 8090 and your chosen TCP port on your router.

+

Build fails with HTTP 500 on BoringSSL fetch

+

Symptom: The build fails with the following error:

+
error: Failed to fetch git repository https://boringssl.googlesource.com/boringssl : error: RPC failed; HTTP 500 curl 22 The requested URL returned error: 500
fatal: unable to write request to remote: Broken pipe
+

Cause: Git's HTTP request size limits are too low for large repositories.

+

Fix: Increase the limits and retry:

+
git config --global http.postBuffer 524288000
git config --global http.maxRequestBuffer 100M
\ No newline at end of file