Commit 6dc1fed0 authored by mergify[bot]'s avatar mergify[bot] Committed by GitHub

Merge branch 'develop' into willc/atst-docs

parents 68bbe48b a1e6eeed
---
'@eth-optimism/common-ts': patch
---
Fix BaseServiceV2 configuration for caseCase options
# The OP Stack Docs
[![Discord](https://img.shields.io/discord/667044843901681675.svg?color=768AD4&label=discord&logo=https%3A%2F%2Fdiscordapp.com%2Fassets%2F8c9701b98ad4372b58f13fd9f65f966e.svg)](https://discord-gateway.optimism.io)
[![Twitter Follow](https://img.shields.io/twitter/follow/optimismPBC.svg?label=optimismPBC&style=social)](https://twitter.com/optimismPBC)
The OP Stack is an open, collectively maintained development stack for blockchain ecosystems.
This repository contains the source code for the [OP Stack Docs](https://stack.optimism.io).
## Development
### Serving docs locally
```sh
yarn dev
```
Then navigate to [http://localhost:8080](http://localhost:8080).
If that link doesn't work, double check the output of `yarn dev`.
You might already be serving something on port 8080 and the site may be on another port (e.g., 8081).
### Building docs for production
```sh
yarn build
```
You probably don't need to run this command, but now you know.
### Editing docs
Edit the markdown directly in [src/docs](./src/docs).
### Adding new docs
Add your markdown files to [src/docs](./src/docs).
You will also have to update [src/.vuepress/config.js](./src/.vuepress/config.js) if you want these docs to show up in the sidebar.
### Updating the theme
We currently use an ejected version of [vuepress-theme-hope](https://vuepress-theme-hope.github.io/).
Since the version we use was ejected from the original theme, you'll see a bunch of compiled JavaScript files instead of the original TypeScript files.
There's not much we can do about that right now, so you'll just need to make do and edit the raw JS if you need to make theme adjustments.
We're planning to move away from VuePress relatively soon anyway so we won't be fixing this.
{
"name": "@eth-optimism/op-stack-docs",
"version": "0.0.2",
"description": "The OP Stack Docs",
"main": "index.js",
"scripts": {
"dev": "vuepress dev src",
"build": "vuepress build src"
},
"license": "MIT",
"devDependencies": {
"@vuepress/plugin-medium-zoom": "^1.8.2",
"@vuepress/plugin-pwa": "^1.9.7",
"vuepress": "^1.8.2",
"vuepress-plugin-plausible-analytics": "^0.2.1",
"vuepress-theme-hope": "^1.22.0"
},
"dependencies": {
"check-md": "^1.0.2",
"dayjs": "^1.11.7"
}
}
const { description } = require('../../package')
const path = require('path')
module.exports = {
title: 'OP Stack Docs',
description: description,
head: [
['link', { rel: 'manifest', href: '/manifest.json' }],
['meta', { name: 'theme-color', content: '#3eaf7c' }],
['meta', { name: 'apple-mobile-web-app-capable', content: 'yes' }],
['meta', { name: 'apple-mobile-web-app-status-bar-style', content: 'black' }],
['meta', { property: 'og:image', content: 'https://stack.optimism.io/assets/logos/twitter-logo.png' }],
['meta', { name: 'twitter:image', content: 'https://stack.optimism.io/assets/logos/twitter-logo.png' }],
['meta', { name: 'twitter:title', content: 'OP Stack Docs' }],
['meta', { property: 'og:title', content: 'OP Stack Docs' }],
['meta', { name: 'twitter:card', content: 'summary' } ],
['link', { rel: "icon", type: "image/png", sizes: "32x32", href: "/assets/logos/favicon.png"}],
],
theme: path.resolve(__dirname, './theme'),
themeConfig: {
"twitter:card": "summary",
contributor: false,
hostname: 'https://stack.optimism.io',
logo: '/assets/logos/logo.png',
docsDir: 'src',
docsRepo: 'https://github.com/ethereum-optimism/opstack-docs',
docsBranch: 'main',
lastUpdated: false,
darkmode: 'disable',
themeColor: false,
blog: false,
iconPrefix: 'far fa-',
pageInfo: false,
pwa: {
cacheHTML: false,
},
activeHash: {
offset: -200,
},
algolia: {
appId: '7Q6XITDI0Z',
apiKey: '9d55a31a04b210cd26f97deabd161705',
indexName: 'optimism'
},
nav: [
{
text: 'Home',
link: 'https://www.optimism.io/'
},
{
text: 'OP Stack Docs',
link: '/'
},
{
text: 'Optimism Docs',
link: 'https://community.optimism.io/'
},
{
text: 'Governance',
link: 'https://community.optimism.io/docs/governance/'
},
{
text: 'Community',
items: [
{
icon: 'discord',
iconPrefix: 'fab fa-',
iconClass: 'color-discord',
text: 'Discord',
link: 'https://discord.optimism.io',
},
{
icon: 'github',
iconPrefix: 'fab fa-',
iconClass: 'color-github',
text: 'GitHub',
link: 'https://github.com/ethereum-optimism/optimism',
},
{
icon: 'twitter',
iconPrefix: 'fab fa-',
iconClass: 'color-twitter',
text: 'Twitter',
link: 'https://twitter.com/optimismFND',
},
{
icon: 'twitch',
iconPrefix: 'fab fa-',
iconClass: 'color-twitch',
text: 'Twitch',
link: 'https://www.twitch.tv/optimismpbc'
},
{
icon: 'medium',
iconPrefix: 'fab fa-',
iconClass: 'color-medium',
text: 'Blog',
link: 'https://optimismpbc.medium.com/'
},
{
icon: 'computer-classic',
iconClass: 'color-ecosystem',
text: 'Ecosystem',
link: 'https://www.optimism.io/apps/all',
},
{
icon: 'globe',
iconClass: 'color-optimism',
text: 'optimism.io',
link: 'https://www.optimism.io/',
}
]
}
],
searchPlaceholder: 'Search the docs',
sidebar: [
{
title: "OP Stack",
collapsable: false,
children: [
'/',
[
'/docs/understand/design-principles.md',
'Design Principles'
],
'/docs/understand/landscape.md',
'/docs/understand/explainer.md'
]
},
{
title: "Releases",
collapsable: false,
children: [
'/docs/releases/',
{
title: "Bedrock",
collapsable: true,
children: [
'/docs/releases/bedrock/',
'/docs/releases/bedrock/explainer.md',
'/docs/releases/bedrock/differences.md'
]
}
]
},
{
title: "Building OP Stack Rollups",
collapsable: false,
children: [
'/docs/build/getting-started.md',
'/docs/build/conf.md',
'/docs/build/explorer.md',
{
title: "OP Stack Hacks",
collapsable: true,
children: [
'/docs/build/hacks.md',
'/docs/build/featured.md',
'/docs/build/data-avail.md',
'/docs/build/derivation.md',
'/docs/build/execution.md',
'/docs/build/settlement.md',
{
title: "Sample Hacks",
children: [
"/docs/build/tutorials/add-attr.md",
"/docs/build/tutorials/new-precomp.md",
]
} // End of tutorials
],
}, // End of OP Stack hacks
],
}, // End of Building OP Stack Rollups
{
title: "Contributing",
collapsable: false,
children: [
'/docs/contribute.md',
]
},
{
title: "Security",
collapsable: false,
children: [
'/docs/security/faq.md',
'/docs/security/policy.md',
]
},
], // end of sidebar
plugins: [
"@vuepress/pwa",
[
'@vuepress/plugin-medium-zoom',
{
selector: ':not(a) > img'
}
],
"plausible-analytics"
]
}
}
// module.exports.themeConfig.sidebar["/docs/useful-tools/"] = module.exports.themeConfig.sidebar["/docs/developers/"]
export default ({ router }) => {
router.addRoutes([
{ path: '/docs/', redirect: '/' },
])
}
{
"name": "OP Docs",
"short_name": "OP Docs",
"description": "The official OP Docs",
"icons": [
{
"src": "/assets/logos/icon-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/assets/logos/icon-256x256.png",
"sizes": "256x256",
"type": "image/png"
},
{
"src": "/assets/logos/icon-384x384.png",
"sizes": "384x384",
"type": "image/png"
},
{
"src": "/assets/logos/icon-512x512.png",
"sizes": "512x512",
"type": "image/png"
},
{
"src": "/assets/logos/icon-1020x1020.png",
"sizes": "any",
"type": "image/png"
}
],
"start_url": "/index.html",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#ff0420"
}
@import url('https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;600&display=swap');
@import url('https://fonts.googleapis.com/css2?family=Rubik:ital,wght@0,400;0,600;0,700;1,600;1,700&display=swap');
@import 'https://pro.fontawesome.com/releases/v5.15.4/css/all.css';
main, body, html {
font-family: 'Open Sans', sans-serif;
}
p {
font-size: 16px;
line-height: 24px;
}
aside.sidebar {
background-color: #F1F4F9;
border-right: none;
}
p.sidebar-heading {
color: #323A43 !important;
font-family: 'Open Sans', sans-serif;
font-weight: 600;
font-size: 14px !important;
line-height: 24px !important;
min-height: 36px;
margin-left: 32px;
padding: 8px 16px !important;
width: calc(100% - 64px) !important;
border-radius: 8px;
}
a.sidebar-link {
font-family: 'Open Sans', sans-serif;
font-size: 14px !important;
line-height: 24px !important;
min-height: 36px;
margin-left: 32px;
padding: 8px 16px !important;
width: calc(100% - 64px) !important;
border-radius: 8px;
}
section.sidebar-group a.sidebar-link {
margin-left: 44px;
width: calc(100% - 64px) !important;
}
.sidebar-links:not(.sidebar-group-items) > li > a.sidebar-link {
font-weight: 600 !important;
color: #323A43 !important;
}
.sidebar-links:not(.sidebar-group-items) > li > a.sidebar-link.active {
border-left-color: #F1F4F9 !important;
background-color: #FFDBDF !important;
color: #FF0420 !important;
}
a.sidebar-link.active {
border-left-color: #F1F4F9 !important;
background-color: #FFDBDF !important;
color: #FF0420 !important;
}
h1 {
font-size: 50px;
}
h2 {
font-size: 40px;
}
h3 {
font-size: 28px;
}
h4 {
font-size: 20px;
}
h1 {
font-family: 'Rubik', sans-serif;
font-weight: 700;
font-style: italic;
border-bottom: none;
color: #202327 !important;
}
h2, h3, h4 {
font-family: 'Rubik', sans-serif;
border-bottom: none;
font-weight: 400;
}
#search-form {
@media (min-width $MQNormal) {
margin-left: 2rem;
}
}
.search-box {
@media (min-width $MQNormal) {
order: 1;
margin-right: 0;
margin-left: 1rem;
.suggestions {
left: auto !important;
right: 0 !important;
}
}
input {
border-color: #CBD5E0 !important;
border-radius: 100px !important;
background-color: #FFFFFF !important;
}
}
header.navbar {
border-bottom: none;
box-shadow: 0px 6px 8px -6px rgba(20, 23, 26, 0.06), 0px 8px 16px -6px rgba(20, 23, 26, 0.04);
--bgcolor-blur: hsla(0,0%,100%,0.9);
}
span.site-name {
display: none !important;
}
a.nav-link,
div.nav-item span.title {
font-weight: 600;
}
a.nav-link:not(.router-link-active),
div.nav-item span.title {
color: #68778D;
}
.theme-default-content:not(.custom) > p {
text-align: inherit !important;
}
.sidebar {
box-shadow: none !important;
}
div.hero-info {
color: white;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
align-items: center;
}
div.hero-info .description {
display: none;
}
div.hero-info #main-title {
color: white !important;
}
header.hero > img {
border-radius: 16px;
max-height: none !important;
}
header.hero {
margin-top: 20px;
position: relative;
justify-content: space-between !important;
}
.home .features {
border-top: none !important;
justify-content: space-between !important;
margin: 0 !important;
padding-top: 0.5rem !important;
}
.home .features h2 {
font-family: 'Open Sans', sans-serif;
font-style: normal;
font-size: 16px !important;
font-weight: 600 !important;
color: #202327 !important;
}
.home .features p {
font-family: 'Open Sans', sans-serif;
font-size: 14px !important;
color: #68778D !important;
}
.home .features .icon-container {
height: 44px;
width: 44px;
background-color: #FFF0F1;
border-radius: 8px;
display: flex;
justify-content: center;
align-items: center;
color: #FF0420;
}
.home .features .feature {
background-color: #FFFFFF !important;
box-shadow: 0px 6px 8px -6px rgba(20, 23, 26, 0.12), 0px 8px 16px -6px rgba(20, 23, 26, 0.08);
border-radius: 16px !important;
margin: 0 !important;
margin-bottom: 2rem !important;
padding: 1.5rem !important;
}
.features-header {
margin-top: 30px;
margin-bottom: 0px;
}
div.theme-container:not(.has-sidebar) {
background-color: #F1F4F9;
}
.anchor-header {
color: #202327;
font-weight: 600;
font-size: 14px;
font-height: 20px;
margin-bottom: 10px;
}
.anchor-support {
margin-top: 20px;
}
.anchor-support-links i {
width: 20px;
text-align: center;
margin-right: 5px;
}
.anchor-support-links a {
color: #68778D;
}
.anchor-support-links a div {
height: 30px;
font-size: 14px;
}
.anchor-support-links a:hover {
color: #FF0420;
}
#anchor {
width: 15rem !important;
}
#anchor .anchor {
line-height: 27.2px !important;
}
#anchor .anchor div {
color: #68778D !important;
}
#anchor .anchor.active div {
font-weight: 600 !important;
}
.theme-default-content code {
top: 1px;
line-height: 22.4px !important;
vertical-align: middle !important;
}
.nav-dropdown svg.icon.outbound {
display: none;
}
.nav-dropdown .dropdown-item i {
width: 20px;
}
.color-discord {
color: #5865F2;
}
.color-github {
color: #121212;
}
.color-twitter {
color: #1DA1F2;
}
.color-twitch {
color: #6441A5;
}
.color-medium {
color: #000000;
}
.color-optimism {
color: #FF0420;
}
.color-ecosystem {
color: #ea94db;
}
$textColor = #000000
$accentColor = #f01a37
$backgroundColor = #272934
$lightBackgroundColor = #f3f3f3
$sidebarWidth = 320px
import Vue from "vue";
export default Vue.extend({
name: "AlgoliaSearchDropdown",
props: {
options: { type: Object, required: true },
},
data: () => ({
placeholder: "",
}),
watch: {
$lang(newValue) {
this.update(this.options, newValue);
},
options(newValue) {
this.update(newValue, this.$lang);
},
},
mounted() {
this.initialize(this.options, this.$lang);
this.placeholder =
this.$site.themeConfig.searchPlaceholder || "";
},
methods: {
initialize(userOptions, lang) {
void Promise.all([
import(
/* webpackChunkName: "docsearch" */ "docsearch.js/dist/cdn/docsearch.min.js"),
import(
/* webpackChunkName: "docsearch" */ "docsearch.js/dist/cdn/docsearch.min.css"),
]).then(([docsearch]) => {
// eslint-disable-next-line
docsearch.default(Object.assign(Object.assign({}, userOptions), { inputSelector: "#algolia-search-input",
// #697 Make docsearch work well at i18n mode.
algoliaOptions: {
facetFilters: [`lang:${lang}`].concat(
// eslint-disable-next-line
userOptions.facetFilters || []),
}, handleSelected: (_input, _event, suggestion) => {
const { pathname, hash } = new URL(suggestion.url);
const routepath = pathname.replace(this.$site.base, "/");
if (this.$router.getRoutes().some((route) => route.path === routepath))
void this.$router.push(`${routepath}${decodeURIComponent(hash)}`);
else
window.open(suggestion.url);
} }));
});
},
update(options, lang) {
this.$el.innerHTML =
'<input id="algolia-search-input" class="search-query">';
this.initialize(options, lang);
},
},
});
//# sourceMappingURL=Dropdown.js.map
\ No newline at end of file
<template>
<form
id="search-form"
class="algolia-search-wrapper search-box"
role="search"
>
<label class="sr-only" for="algolia-search-input">Algolia search</label>
<input
id="algolia-search-input"
class="search-query"
:placeholder="placeholder"
/>
</form>
</template>
<script src="./Dropdown" />
<style lang="stylus">
.algolia-search-wrapper
& > span
vertical-align middle
.algolia-autocomplete
line-height normal
.ds-dropdown-menu
min-width 515px !important
margin 6px 0 0
padding 4px
border 1px solid var(--light-grey)
border-radius 4px
background var(--bgcolor)
font-size 16px
text-align left
@media (max-width $MQMobile)
min-width calc(100vw - 4rem) !important
max-width calc(100vw - 4rem) !important
&:before
border-color var(--light-grey)
[class*=ds-dataset-]
padding 0
border none
background var(--bgcolor)
.ds-suggestions
margin-top 0
.ds-suggestion
border-bottom 1px solid var(--border-color)
.algolia-docsearch-suggestion--highlight
color var(--accent-color)
.algolia-docsearch-suggestion
padding 0
border-color var(--border-color)
background var(--bgcolor)
color var(--text-color)
.algolia-docsearch-suggestion--category-header
padding 5px 10px
margin-top 0
background var(--accent-color)
color var(--white)
font-weight 600
.algolia-docsearch-suggestion--highlight
background rgba(255, 255, 255, 0.6)
.algolia-docsearch-suggestion--wrapper
padding 0
@media (max-width $MQMobile)
padding 5px 7px 5px 5px !important
.algolia-docsearch-suggestion--title
margin-bottom 0
color var(--text-color)
font-weight 600
.algolia-docsearch-suggestion--subcategory-column
vertical-align top
padding 5px 7px 5px 5px
border-color var(--border-color)
background var(--bgcolor)
color var(--text-color)
@media (min-width $MQMobile)
display table-cell
float none
width 150px
min-width 150px
@media (max-width $MQMobile)
padding 0 !important
background white !important
&:after
display none
.algolia-docsearch-suggestion--subcategory-column-text
color #555
&:after
@media (max-width $MQMobile)
display inline-block
vertical-align middle
content ' > '
width 5px
margin -3px 3px 0
font-size 10px
line-height 14.4px
.algolia-docsearch-suggestion--content
@media (min-width $MQMobile)
display table-cell
float none
vertical-align top
width 100%
.algolia-docsearch-footer
border-color var(--border-color)
.ds-cursor .algolia-docsearch-suggestion--content
background var(--grey3)
color var(--text-color)
</style>
import { createElement } from "preact";
import Vue from "vue";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: docsearch type issue
import docsearch from "@docsearch/js";
export default Vue.extend({
name: "AlgoliaSearchFull",
props: {
options: { type: Object, required: true },
},
watch: {
$lang(newValue) {
this.update(this.options, newValue);
},
options(newValue) {
this.update(newValue, this.$lang);
},
},
mounted() {
this.initialize(this.options, this.$lang);
},
methods: {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
initialize(userOptions, _lang) {
// eslint-disable-next-line
docsearch(Object.assign(Object.assign({ container: "#docsearch", placeholder: this.$site.themeConfig.searchPlaceholder || "" }, userOptions), { searchParameters: userOptions.searchParameters || {},
// transform full url to route path
transformItems: (items) => items.map((item) => (Object.assign(Object.assign({}, item), {
// the `item.url` is full url with protocol and hostname
// so we have to transform it to vue-router path
url: this.resolveRoutePathFromUrl(item.url) }))),
// render the hit component with custom `onClick` handler
hitComponent: ({ hit, children }) => createElement("a", {
href: hit.url,
onClick: (event) => {
// We rely on the native link scrolling when user is
// already on the right anchor because Vue Router doesn’t
// support duplicated history entries.
if (this.$route.fullPath === hit.url)
return;
const fullPath = `${window.location.origin}${hit.url}`;
const { pathname: hitPathname } = new URL(fullPath);
// If the hits goes to another page, we prevent the native link behavior
// to leverage the Vue Router loading feature.
if (this.$route.path !== hitPathname)
event.preventDefault();
if (this.$router
.getRoutes()
.some((route) => route.path.replace(/index\.html$/, "") === hitPathname))
void this.$router.push(hit.url);
else
window.open(fullPath);
},
}, children), navigator: {
navigate: ({ itemUrl }) => {
const fullPath = `${window.location.origin}${itemUrl}`;
const { pathname: hitPathname } = new URL(fullPath);
// Vue Router doesn’t handle same-page navigation so we use
// the native browser location API for anchor navigation.
if (this.$route.path === hitPathname)
window.location.assign(fullPath);
else if (this.$router
.getRoutes()
.some((route) => route.path === hitPathname))
void this.$router.push(itemUrl);
else
window.open(fullPath);
},
navigateNewTab({ itemUrl }) {
window.open(itemUrl);
},
navigateNewWindow({ itemUrl }) {
window.open(itemUrl);
},
} }));
},
resolveRoutePathFromUrl(absoluteUrl) {
const { pathname, hash } = new URL(absoluteUrl);
return `${pathname.replace(this.$site.base, "/")}${hash}`;
},
update(options, lang) {
this.$el.innerHTML = '<div id="docsearch"></div>';
this.initialize(options, lang);
},
},
});
//# sourceMappingURL=Full.js.map
\ No newline at end of file
<template>
<div id="docsearch" />
</template>
<script src="./Full" />
<style lang="stylus">
@keyframes fade-in
0%
opacity 0
to
opacity 1
body
--docsearch-spacing 12px
--docsearch-icon-stroke-width 1.4
--docsearch-muted-color #969faf
--docsearch-container-background rgba(101, 108, 133, 0.8)
--docsearch-modal-width 560px
--docsearch-modal-height 600px
--docsearch-modal-shadow inset 1px 1px 0 0 hsla(0, 0%, 100%, 0.5), 0 3px 8px 0 #555a64
--docsearch-searchbox-height 56px
--docsearch-searchbox-background #ebedf0
--docsearch-searchbox-focus-background #efeef4
--docsearch-searchbox-shadow inset 0 0 0 2px var(--accent-color)
--docsearch-hit-height 56px
--docsearch-hit-color var(--dark-grey)
--docsearch-hit-shadow 0 1px 3px 0 #d4d9e1
--docsearch-key-gradient linear-gradient(-225deg, #d5dbe4, #f8f8f8)
--docsearch-key-shadow inset 0 -2px 0 0 #cdcde6, inset 0 0 1px 1px #fff, 0 1px 2px 1px rgba(30, 35, 90, 0.4)
--docsearch-footer-height 44px
--docsearch-footer-shadow 0 -1px 0 0 #e0e3e8, 0 -3px 6px 0 rgba(69, 98, 155, 0.12)
@media (max-width $MQMobile)
--docsearch-searchbox-height 44px
--docsearch-spacing 8px
--docsearch-footer-height 40px
body.theme-dark
--docsearch-container-background rgba(9, 10, 17, 0.8)
--docsearch-modal-shadow inset 1px 1px 0 0 #2c2e40, 0 3px 8px 0 #000309
--docsearch-searchbox-background #090a11
--docsearch-searchbox-focus-background lighten($darkBgColor, 10%)
--docsearch-hit-shadow none
--docsearch-key-gradient linear-gradient(-26.5deg, #565862, #31353b)
--docsearch-key-shadow inset 0 -2px 0 0 #282d55, inset 0 0 1px 1px #51577d, 0 2px 2px 0 rgba(3, 4, 9, 0.3)
--docsearch-footer-shadow inset 0 1px 0 0 rgba(73, 76, 106, 0.5), 0 -4px 8px 0 rgba(0, 0, 0, 0.2)
--docsearch-muted-color #7f8497
.DocSearch-Button
display inline-flex
justify-content space-between
align-items center
height 36px
margin 0 1rem 0 0.25rem
padding 0.5rem
background var(--docsearch-searchbox-background)
border-radius 40px
color var(--docsearch-muted-color)
font-weight 500
user-select none
outline none
@media (max-width $MQMobile)
margin-right 0
&:active, &:focus, &:hover
background var(--docsearch-searchbox-focus-background)
box-shadow var(--docsearch-searchbox-shadow)
color var(--dark-grey)
&:hover .DocSearch-Search-Icon
@media (max-width $MQMobile)
color var(--accent-color)
.DocSearch-Button-Container
.navbar &
align-items center
display flex
.DocSearch-Search-Icon
width 1rem
height 1rem
margin 0.1rem
color #aaa
stroke-width 3
position relative
.DocSearch-Search-Icon
stroke-width 1.6
.DocSearch-Button-Placeholder
padding 0 12px 0 6px
font-size 1rem
.DocSearch-Button-Keys
.navbar &
display flex
.DocSearch-Button-Key
position relative
top -1px
width 20px
height 18px
margin-right 0.4em
padding-bottom 2px
border-radius 3px
background var(--docsearch-key-gradient)
box-shadow var(--docsearch-key-shadow)
color var(--docsearch-muted-color)
display flex
justify-content center
align-items center
.navbar &
display flex
.navbar
@media (max-width $MQNarrow)
.DocSearch-Button-Key, .DocSearch-Button-KeySeparator, .DocSearch-Button-Placeholder
display none
.DocSearch--active
overflow hidden !important
.DocSearch-Container
position fixed
top 0
left 0
z-index 200
box-sizing border-box
width 100vw
height 100vh
background-color var(--docsearch-container-background)
*
box-sizing border-box
a
text-decoration none
.DocSearch-Link
margin 0
padding 0
border 0
background none
color var(--accent-color)
font inherit
appearance none
cursor pointer
.DocSearch-Modal
position relative
max-width var(--docsearch-modal-width)
margin 60px auto auto
border-radius 6px
background var(--bgcolor)
box-shadow var(--docsearch-modal-shadow)
flex-direction column
@media (max-width $MQNarrow)
width 100%
max-width 100%
height 100vh
margin 0
border-radius 0
box-shadow none
.DocSearch-SearchBar
display flex
padding var(--docsearch-spacing) var(--docsearch-spacing) 0
.DocSearch-Form
align-items center
background var(--docsearch-searchbox-focus-background)
border-radius 8px
display flex
height var(--docsearch-searchbox-height)
padding 0 var(--docsearch-spacing)
position relative
width 100%
.DocSearch-Input
width 80%
height 100%
padding 0 0 0 8px
border 0
background transparent
color var(--text-color)
font inherit
font-size 1.2rem
appearance none
outline none
flex 1
&::placeholder
color var(--docsearch-muted-color)
opacity 1
&::-webkit-search-cancel-button, &::-webkit-search-decoration, &::-webkit-search-results-button, &::-webkit-search-results-decoration
display none
@media (max-width $MQMobile)
font-size 1rem
.DocSearch-LoadingIndicator, .DocSearch-MagnifierLabel, .DocSearch-Reset
margin 0
padding 0
.DocSearch-MagnifierLabel, .DocSearch-Reset
align-items center
color var(--accent-color)
display flex
justify-content center
.DocSearch-Container--Stalled .DocSearch-MagnifierLabel, .DocSearch-LoadingIndicator
display none
.DocSearch-Container--Stalled .DocSearch-LoadingIndicator
align-items center
color var(--accent-color)
display flex
justify-content center
.DocSearch-Reset
animation fade-in 0.1s ease-in forwards
appearance none
background none
border 0
border-radius 50%
color var(--docsearch-icon-color)
cursor pointer
padding 2px
right 0
stroke-width var(--docsearch-icon-stroke-width)
@media screen and (prefers-reduced-motion reduce)
animation none
appearance none
background none
border 0
border-radius 50%
color var(--docsearch-icon-color)
cursor pointer
right 0
stroke-width var(--docsearch-icon-stroke-width)
&[hidden]
display none
&:focus
outline none
&:hover
color var(--accent-color)
.DocSearch-LoadingIndicator svg, .DocSearch-MagnifierLabel svg
height 24px
width 24px
.DocSearch-Cancel
display none
margin-left var(--docsearch-spacing)
padding 0
border 0
background none
color var(--accent-color)
font-family Arial, Helvetica, sans-serif
font-size 1em
font-weight 500
white-space nowrap
user-select none
appearance none
cursor pointer
flex none
outline none
overflow hidden
@media (max-width $MQNarrow)
display inline-block
.DocSearch-Dropdown
max-height calc(var(--docsearch-modal-height) - var(--docsearch-searchbox-height) - var(--docsearch-spacing) - var(--docsearch-footer-height))
min-height var(--docsearch-spacing)
overflow-y auto
overflow-y overlay
padding 0 var(--docsearch-spacing)
@media (max-width $MQNarrow)
height 100%
max-height unset
& ul
list-style none
margin 0
padding 0
.DocSearch-Label
font-size 0.75em
line-height 1.6em
color var(--docsearch-muted-color)
.DocSearch-Help
color var(--docsearch-muted-color)
font-size 0.9em
margin 0
user-select none
.DocSearch-Title
font-size 1.2em
.DocSearch-Logo
a
display flex
svg
margin-left 8px
color var(--accent-color)
.DocSearch-Hits
&:last-of-type
margin-bottom 24px
mark
background none
color var(--accent-color)
.DocSearch-HitsFooter
margin-bottom var(--docsearch-spacing)
padding var(--docsearch-spacing)
color var(--docsearch-muted-color)
font-size 0.85em
display flex
justify-content center
a
border-bottom 1px solid
color inherit
.DocSearch-Hit
position relative
padding-bottom 4px
border-radius 4px
display flex
a
width 100%
padding-left var(--docsearch-spacing)
border-radius 4px
background var(--bgcolor)
box-shadow var(--docsearch-hit-shadow)
display block
&[aria-selected='true'] a
background-color var(--accent-color)
&[aria-selected='true'] mark
text-decoration underline
.DocSearch-Hit--deleting
opacity 0
transition all 0.25s linear
@media screen and (prefers-reduced-motion reduce)
transition none
.DocSearch-Hit--favoriting
transform scale(0)
transform-origin top center
transition all 0.25s linear
transition-delay 0.25s
@media screen and (prefers-reduced-motion reduce)
transition none
.DocSearch-Hit-source
position sticky
top 0
z-index 10
margin 0 -4px
padding 8px 4px 0
background var(--bgcolor-light)
color var(--accent-color)
font-size 0.85em
font-weight 600
line-height 32px
.DocSearch-Hit-Tree
width 24px
height var(--docsearch-hit-height)
color var(--docsearch-muted-color)
opacity 0.5
stroke-width var(--docsearch-icon-stroke-width)
@media (max-width $MQNarrow)
display none
.DocSearch-Hit-Container
height var(--docsearch-hit-height)
padding 0 var(--docsearch-spacing) 0 0
color var(--docsearch-hit-color)
display flex
flex-direction row
align-items center
.DocSearch-Hit-icon
width 20px
height 20px
color var(--docsearch-muted-color)
stroke-width var(--docsearch-icon-stroke-width)
.DocSearch-Hit-action
width 22px
height 22px
color var(--docsearch-muted-color)
stroke-width var(--docsearch-icon-stroke-width)
display flex
align-items center
svg
display block
width 18px
height 18px
& + &
margin-left 6px
.DocSearch-Hit-action-button
padding 2px
border 0
border-radius 50%
background none
color inherit
cursor pointer
appearance none
&:focus, &:hover
background rgba(0, 0, 0, 0.2)
transition background-color 0.1s ease-in
@media screen and (prefers-reduced-motion reduce)
background rgba(0, 0, 0, 0.2)
transition none
path
fill #fff
svg.DocSearch-Hit-Select-Icon
display none
.DocSearch-Hit[aria-selected='true'] .DocSearch-Hit-Select-Icon
display block
.DocSearch-Hit-content-wrapper
width 80%
position relative
margin 0 8px
font-weight 500
line-height 1.2em
text-overflow ellipsis
white-space nowrap
display flex
flex 1 1 auto
flex-direction column
justify-content center
overflow-x hidden
.DocSearch-Hit-title
font-size 0.9em
.DocSearch-Hit-path
color var(--docsearch-muted-color)
font-size 0.75em
.DocSearch-Hit[aria-selected='true']
.DocSearch-Hit-action, .DocSearch-Hit-icon, .DocSearch-Hit-path, .DocSearch-Hit-text, .DocSearch-Hit-title, .DocSearch-Hit-Tree, mark
color var(--bgcolor) !important
.DocSearch-ErrorScreen, .DocSearch-NoResults, .DocSearch-StartScreen
width 80%
margin 0 auto
padding 36px 0
font-size 0.9em
text-align center
.DocSearch-Screen-Icon
padding-bottom 12px
color var(--docsearch-muted-color)
.DocSearch-NoResults-Prefill-List
display inline-block
padding-bottom 24px
text-align left
ul
display inline-block
padding 8px 0 0
li
list-style-position inside
list-style-type '» '
.DocSearch-Prefill
appearance none
background none
border 0
border-radius 1em
color var(--accent-color)
cursor pointer
display inline-block
font-size 1em
font-weight 700
padding 0
&:focus, &:hover
outline none
text-decoration underline
.DocSearch-Footer
position relative
z-index 300
width 100%
height var(--docsearch-footer-height)
padding 0 var(--docsearch-spacing)
border-radius 0 0 8px 8px
box-shadow var(--docsearch-footer-shadow)
background var(--bgcolor)
display flex
flex-direction row-reverse
flex-shrink 0
align-items center
justify-content space-between
user-select none
@media (max-width $MQNarrow)
position absolute
bottom 0
border-radius 0
.DocSearch-Commands
margin 0
padding 0
color var(--docsearch-muted-color)
display flex
list-style none
@media (max-width $MQNarrow)
display none
li
display flex
align-items center
&:not(:last-of-type)
margin-right 0.8em
.DocSearch-Commands-Key
width 20px
height 18px
margin-right 0.4em
padding-bottom 2px
border-radius 2px
background var(--docsearch-key-gradient)
box-shadow var(--docsearch-key-shadow)
display flex
justify-content center
align-items center
</style>
import Vue from "vue";
import { isActive } from "@theme/utils/path";
const renderLink = (h, { text, link, level }) => h("RouterLink", {
props: {
to: link,
activeClass: "",
exactActiveClass: "",
},
class: {
"anchor-link": true,
[level ? `heading${level}` : ""]: level,
},
}, [h("div", {}, [text])]);
const renderChildren = (h, { children, route }) => h("ul", { class: "anchor-list" }, children.map((child) => {
const active = isActive(route, `${route.path}#${child.slug}`);
return h("li", { class: { anchor: true, active } }, [
renderLink(h, {
text: child.title,
link: `${route.path}#${child.slug}`,
level: child.level,
}),
]);
}));
export default Vue.extend({
name: "Anchor",
functional: true,
props: {
items: {
type: Array,
default: () => [],
},
},
render(h, { props, parent: { $page, $route } }) {
return h("div", { attrs: { class: "anchor-place-holder" } }, [
h("aside", { attrs: { id: "anchor" } }, [
($page.headers && $page.headers.length)
? h("div", { class: "anchor-header" }, [
"On this page"
])
: null,
h("div", { class: "anchor-wrapper" }, [
props.items.length
? renderChildren(h, {
children: props.items,
route: $route,
})
: $page.headers
? renderChildren(h, {
children: $page.headers,
route: $route,
})
: null,
]),
($page.headers && $page.headers.length)
? h("div", [
h("div", { class: "anchor-header anchor-support" }, [
"Support"
]),
h("div", { class: "anchor-support-links" }, [
h("a", { attrs: { href: "https://discord.optimism.io", target: "_blank" } }, [
h("div", [
h("i", { attrs: { class: "fab fa-discord" } }),
" Discord community "
])
]),
h("a", { attrs: { href: "https://forms.monday.com/forms/055862bfb7f4091be3db2567288296f8?r=use1", target: "_blank" } }, [
h("div", [
h("i", { attrs: { class: "far fa-comment-dots" } }),
" Join the Superchain "
])
]),
h("a", { attrs: { href: "https://github.com/ethereum-optimism/optimism/issues", target: "_blank" } }, [
h("div", [
h("i", { attrs: { class: "fab fa-github" } }),
" Make an issue on GitHub"
])
]),
h("a", { attrs: { href: "https://github.com/ethereum-optimism/optimism/contribute", target: "_blank" } }, [
h("div", [
h("i", { attrs: { class: "far fa-hands-helping" } }),
" Contribute to Optimism"
])
]),
])
])
: null
]),
]);
},
});
//# sourceMappingURL=Anchor.js.map
<script src="./Anchor" />
<style lang="stylus">
$headings = 2 3 4 5 6
.anchor-place-holder
position sticky
top: ($navbarHeight + 2rem)
max-width $contentWidth
margin 0 auto
padding 0 2.5rem
z-index 99
@media (max-width $MQNarrow)
padding 0 1.5rem
& + .theme-default-content:not(.custom)
padding-top 0
#anchor
display none
position absolute
left calc(100% + 0.5rem)
min-width 10rem
max-width 15rem
max-height 85vh
overflow-y scroll
@media (min-width $MQWide)
.has-anchor &
display block
&::-webkit-scrollbar-track-piece
background transparent
&::-webkit-scrollbar
width 3px
&::-webkit-scrollbar-thumb:vertical
background #ddd
.theme-dark &
background #333
.anchor-wrapper
position relative
padding-left 8px
&::before
content ' '
position absolute
top 0
left 0px
bottom 0
width 2px
background var(--border-color)
z-index -1
> .anchor-list
margin 0
.anchor-list
padding-left 0
.anchor
position relative
box-sizing border-box
padding 1px 8px
list-style none
line-height 1.5
&::before
content ' '
position absolute
z-index 2
top 0
bottom 0
left -8px
width 2px
background transparent
&:hover
.anchor-link
color var(--accent-color)
&.active
.anchor-link
color var(--accent-color)
&::before
background var(--accent-color)
.anchor-link
display inline-block
vertical-align middle
position relative
max-width 100%
color var(--light-grey)
> div
text-overflow ellipsis
white-space nowrap
overflow hidden
for $heading in $headings
&.heading{$heading}
padding-left ($heading * 8 - 16) px
font-size: (16 - $heading)px
</style>
import Vue from "vue";
export default Vue.extend({
name: "Clipboard",
props: {
html: { type: String, default: "" },
lang: { type: String, default: "en-US" },
},
data: () => ({
location: "",
}),
computed: {
copyright() {
const { author } = this.$themeConfig;
const content = {
"zh-CN": `${this.html}\n-----\n${author ? `著作权归${author}所有。\n` : ""}链接: ${this.location}`,
"en-US": `${this.html}\n-----\n${author ? `Copyright by ${author}.\n` : ""}Link: ${this.location}`,
"vi-VN": `${this.html}\n-----\n${author ? `bản quyền bởi ${author}.\n` : ""}Liên kết: ${this.location}`,
};
return content[this.lang];
},
},
created() {
if (typeof window !== "undefined")
this.location = window.location.toString();
},
});
//# sourceMappingURL=Clipboard.js.map
\ No newline at end of file
<template>
<!-- eslint-disable-next-line vue/no-v-html -->
<div v-html="copyright" />
</template>
<script src="./Clipboard" />
import Vue from "vue";
import { getSidebarItems } from "@theme/utils/sidebar";
import Navbar from "@theme/components/Navbar/Navbar.vue";
import Sidebar from "@theme/components/Sidebar/Sidebar.vue";
import throttle from "lodash.throttle";
export default Vue.extend({
name: "Common",
components: {
Navbar,
Sidebar,
},
props: {
navbar: { type: Boolean, default: true },
sidebar: { type: Boolean, default: true },
},
data: () => ({
isSidebarOpen: false,
hideNavbar: false,
touchStart: {
clientX: 0,
clientY: 0,
},
}),
computed: {
enableNavbar() {
if (this.navbar === false)
return false;
const { frontmatter } = this.$page;
if (frontmatter.navbar === false || this.$themeConfig.navbar === false)
return false;
return Boolean(this.$title ||
this.$themeConfig.logo ||
this.$themeConfig.repo ||
this.$themeConfig.nav ||
this.$themeLocaleConfig.nav);
},
enableSidebar() {
if (this.sidebar === false)
return false;
return (!this.$frontmatter.home &&
this.$frontmatter.sidebar !== false &&
this.sidebarItems.length !== 0);
},
sidebarItems() {
if (this.sidebar === false)
return [];
return getSidebarItems(this.$page, this.$site, this.$localePath);
},
pageClasses() {
const userPageClass = this.$page.frontmatter.pageClass;
return [
{
"has-navbar": this.enableNavbar,
"has-sidebar": this.enableSidebar,
"has-anchor": this.enableAnchor,
"hide-navbar": this.hideNavbar,
"sidebar-open": this.isSidebarOpen,
},
userPageClass,
];
},
headers() {
return this.getHeader(this.sidebarItems);
},
enableAnchor() {
return (this.$frontmatter.anchorDisplay ||
(this.$themeConfig.anchorDisplay !== false &&
this.$frontmatter.anchorDisplay !== false));
},
},
mounted() {
let lastDistance = 0;
this.$router.afterEach(() => {
this.isSidebarOpen = false;
});
window.addEventListener("scroll", throttle(() => {
const distance = this.getScrollTop();
// scroll down
if (lastDistance < distance && distance > 58) {
if (!this.isSidebarOpen)
this.hideNavbar = true;
// scroll up
}
else
this.hideNavbar = false;
lastDistance = distance;
}, 300));
},
methods: {
/** Get scroll distance */
getScrollTop() {
return (window.pageYOffset ||
document.documentElement.scrollTop ||
document.body.scrollTop ||
0);
},
toggleSidebar(to) {
this.isSidebarOpen = typeof to === "boolean" ? to : !this.isSidebarOpen;
this.$emit("toggle-sidebar", this.isSidebarOpen);
},
// Side swipe
onTouchStart(event) {
this.touchStart = {
clientX: event.changedTouches[0].clientX,
clientY: event.changedTouches[0].clientY,
};
},
onTouchEnd(event) {
const dx = event.changedTouches[0].clientX - this.touchStart.clientX;
const dy = event.changedTouches[0].clientY - this.touchStart.clientY;
if (Math.abs(dx) > Math.abs(dy) && Math.abs(dx) > 40)
if (dx > 0 && this.touchStart.clientX <= 80)
this.toggleSidebar(true);
else
this.toggleSidebar(false);
},
getHeader(items) {
for (let i = 0; i < items.length; i++) {
const item = items[i];
if (item.type === "group") {
const matching = this.getHeader(item.children);
if (matching.length !== 0)
return matching;
}
else if (item.type === "page" &&
item.headers &&
item.path === this.$route.path)
return item.headers;
}
return [];
},
},
});
//# sourceMappingURL=Common.js.map
<template>
<div
class="theme-container"
:class="pageClasses"
@touchstart="onTouchStart"
@touchend="onTouchEnd"
>
<!-- Content -->
<template>
<Navbar v-if="enableNavbar" @toggle-sidebar="toggleSidebar">
<template #start>
<slot name="navbar-start" />
</template>
<template #center>
<slot name="navbar-center" />
</template>
<template #end>
<slot name="navbar-end" />
</template>
</Navbar>
<div class="sidebar-mask" @click="toggleSidebar(false)" />
<Sidebar :items="sidebarItems" @toggle-sidebar="toggleSidebar">
<template #top>
<slot name="sidebar-top" />
</template>
<template #center>
<slot name="sidebar-center" />
</template>
<template #bottom>
<slot name="sidebar-bottom" />
</template>
</Sidebar>
<slot :sidebar-items="sidebarItems" :headers="headers" />
</template>
</div>
</template>
<script src="./Common" />
<style lang="stylus">
.theme-container
min-height 100vh
.sidebar-mask
position fixed
z-index 9
top 0
left 0
width 100vw
height 100vh
display none
.theme-container.sidebar-open &
display block
</style>
import Vue from "vue";
import MyTransition from "@theme/components/MyTransition.vue";
import NavLink from "@theme/components/Navbar/NavLink.vue";
import { navigate } from "@theme/utils/navigate";
export default Vue.extend({
name: "Home",
components: { MyTransition, NavLink },
computed: {
actionLinks() {
const { action } = this.$frontmatter;
if (Array.isArray(action))
return action;
return [action];
},
},
methods: {
navigate(link) {
navigate(link, this.$router, this.$route);
},
},
});
//# sourceMappingURL=Home.js.map
\ No newline at end of file
<template>
<main
:aria-labelledby="$frontmatter.heroText !== null ? 'main-title' : null"
class="home"
>
<header class="hero">
<MyTransition>
<img
v-if="$frontmatter.heroImage"
key="light"
:class="{ light: Boolean($frontmatter.darkHeroImage) }"
:src="$withBase($frontmatter.heroImage)"
:alt="$frontmatter.heroAlt || 'HomeLogo'"
/>
</MyTransition>
<MyTransition>
<img
v-if="$frontmatter.darkHeroImage"
key="dark"
class="dark"
:src="$withBase($frontmatter.darkHeroImage)"
:alt="$frontmatter.heroAlt || 'HomeLogo'"
/>
</MyTransition>
<div class="hero-info">
<MyTransition :delay="0.04">
<h1
v-if="$frontmatter.heroText !== false"
id="main-title"
v-text="$frontmatter.heroText || $title || 'Hello'"
/>
</MyTransition>
<MyTransition :delay="0.08">
<p
class="description"
v-text="
$frontmatter.tagline ||
$description ||
'Welcome to your VuePress site'
"
/>
</MyTransition>
<MyTransition :delay="0.12">
<p v-if="$frontmatter.action" class="action">
<NavLink
v-for="action in actionLinks"
:key="action.text"
:item="action"
class="action-button"
:class="action.type || ''"
/>
</p>
</MyTransition>
</div>
</header>
<MyTransition :delay="0.16">
<div>
<h2 class="features-header">Resources</h2>
<div
v-if="$frontmatter.features && $frontmatter.features.length"
class="features"
>
<template v-for="(feature, index) in $frontmatter.features">
<div
v-if="feature.link"
:key="index"
class="feature link"
:class="`feature${index % 9}`"
tabindex="0"
role="navigation"
@click="navigate(feature.link)"
>
<div class="icon-container">
<i :class="`far fa-${feature.icon}`"></i>
</div>
<h2>{{ feature.title }}</h2>
<p>{{ feature.details }}</p>
</div>
<div
v-else
:key="index"
class="feature"
:class="`feature${index % 9}`"
>
<h2>{{ feature.title }}</h2>
<p>{{ feature.details }}</p>
</div>
</template>
</div>
</div>
</MyTransition>
<MyTransition :delay="0.24">
<Content class="theme-default-content custom" />
</MyTransition>
</main>
</template>
<script src="./Home" />
<style lang="stylus">
.home
display block
max-width $homePageWidth
min-height 100vh - $navbarHeight
padding $navbarHeight 2rem 0
margin 0px auto
overflow-x hidden
@media (max-width $MQNarrow)
min-height 100vh - $navbarMobileHeight
padding-top $navbarMobileHeight
@media (max-width $MQMobileNarrow)
padding-left 1.5rem
padding-right 1.5rem
.hero
text-align center
@media (min-width $MQNarrow)
display flex
justify-content space-evenly
align-items center
text-align left
img
display block
max-width 100%
max-height 320px
margin 0
@media (max-width $MQNarrow)
max-height 280px
margin 3rem auto 1.5rem
@media (max-width $MQMobile)
max-height 240px
margin 2rem auto 1.2rem
@media (max-width $MQMobileNarrow)
max-height 210px
margin 1.5rem auto 1rem
.theme-light &
&.light
display block
&.dark
display none
.theme-dark &
&.light
display none
&.dark
display block
h1
font-size 3rem
@media (max-width $MQMobile)
font-size 2.5rem
@media (max-width $MQMobileNarrow)
font-size 2rem
h1, .description, .action
margin 1.8rem auto
@media (max-width $MQMobile)
margin 1.5rem auto
@media (max-width $MQMobileNarrow)
margin 1.2rem auto
.description
max-width 35rem
color var(--text-color-l40)
font-size 1.6rem
line-height 1.3
@media (max-width $MQMobile)
font-size 1.4rem
@media (max-width $MQMobileNarrow)
font-size 1.2rem
.action-button
display inline-block
margin 0.6rem 0.8rem
padding 1rem 1.5rem
border 2px solid var(--accent-color)
border-radius 2rem
color var(--accent-color)
font-size 1.2rem
transition background 0.1s ease
overflow hidden
@media (max-width $MQMobile)
padding 0.8rem 1.2rem
font-size 1.1rem
@media (max-width $MQMobileNarrow)
font-size 1rem
&:hover
color var(--white)
background-color var(--accent-color)
&.primary
color var(--white)
background-color var(--accent-color)
&:hover
border-color var(--accent-color-l10)
background-color var(--accent-color-l10)
.theme-dark &
&:hover
border-color var(--accent-color-d10)
background-color var(--accent-color-d10)
.features
display flex
flex-wrap wrap
justify-content center
align-items stretch
align-content stretch
margin 0 -2rem
padding 1.2rem 0
border-top 1px solid var(--border-color)
@media (max-width $MQMobileNarrow)
margin 0 -1.5rem
.feature
display flex
flex-direction column
justify-content center
flex-basis calc(33% - 4rem)
margin 0.5rem
padding 0 1.5rem
border-radius 0.5rem
transition transform 0.3s, box-shadow 0.3s
overflow hidden
@media (max-width $MQNarrow)
flex-basis calc(50% - 4rem)
@media (max-width $MQMobile)
font-size 0.95rem
@media (max-width $MQMobileNarrow)
flex-basis calc(100%)
font-size 0.9rem
margin 0.5rem 0
border-radius 0
&.link
cursor pointer
&:hover
box-shadow 0 2px 12px 0 var(--card-shadow-color)
h2
margin-bottom 0.25rem
border-bottom none
color var(--text-color-l10)
font-size 1.25rem
font-weight 500
@media (max-width $MQMobileNarrow)
font-size 1.2rem
p
margin-top 0
color var(--text-color-l25)
{$contentClass}
padding-bottom 1.5rem
@require '~@mr-hope/vuepress-shared/styles/colors.styl'
for $color, $index in $colors
.home .features .feature{$index}
&, .theme-light &
background lighten($color, 90%)
.theme-dark &
background darken($color, 75%)
</style>
import Vue from "vue";
export default Vue.extend({
name: "MyTransition",
props: {
delay: { type: Number, default: 0 },
duration: { type: Number, default: 0.25 },
disable: { type: Boolean, default: false },
},
methods: {
setStyle(items) {
if (this.disable) {
return;
}
items.style.transition = `transform ${this.duration}s ease-in-out ${this.delay}s, opacity ${this.duration}s ease-in-out ${this.delay}s`;
items.style.transform = "translateY(-20px)";
items.style.opacity = "0";
},
unsetStyle(items) {
if (this.disable) {
return;
}
items.style.transform = "translateY(0)";
items.style.opacity = "1";
},
},
});
//# sourceMappingURL=MyTransition.js.map
\ No newline at end of file
<template>
<transition
name="drop"
appear
@appear="setStyle"
@after-appear="unsetStyle"
@enter="setStyle"
@after-enter="unsetStyle"
@before-leave="setStyle"
>
<slot />
</transition>
</template>
<script src="./MyTransition" />
<style lang="stylus">
.drop-enter, .drop-leave-to
opacity 0
transform translateY(-20px)
</style>
import Vue from "vue";
import NavLink from "@theme/components/Navbar/NavLink.vue";
export default Vue.extend({
name: "DropdownLink",
components: { NavLink },
props: {
item: { type: Object, required: true },
},
data: () => ({
open: false,
}),
computed: {
dropdownAriaLabel() {
return this.item.ariaLabel || this.item.text;
},
iconPrefix() {
const { iconPrefix } = this.$themeConfig;
return iconPrefix === "" ? "" : iconPrefix || "icon-";
},
},
watch: {
$route() {
this.open = false;
},
},
methods: {
setOpen(value) {
this.open = value;
},
handleDropdown(event) {
const isTriggerByTab = event.detail === 0;
if (isTriggerByTab)
this.setOpen(!this.open);
},
isLastItemOfArray(item, array) {
if (Array.isArray(array))
return item === array[array.length - 1];
return false;
},
},
});
//# sourceMappingURL=DropdownLink.js.map
\ No newline at end of file
<template>
<div class="dropdown-wrapper" :class="{ open }">
<button
class="dropdown-title"
type="button"
:aria-label="dropdownAriaLabel"
@click="handleDropdown"
>
<slot name="title">
<span class="title">
<i v-if="item.icon" :class="`iconfont ${iconPrefix}${item.icon}`" />
{{ item.text }}
</span>
</slot>
<span class="arrow" />
</button>
<ul class="nav-dropdown">
<li
v-for="(child, index) in item.items"
:key="child.link || index"
class="dropdown-item"
>
<template v-if="child.type === 'links'">
<h4 class="dropdown-subtitle">
<NavLink
v-if="child.link"
:item="child"
@focusout="
isLastItemOfArray(child, item.children) &&
child.children.length === 0 &&
setOpen(false)
"
/>
<span v-else>{{ child.text }}</span>
</h4>
<ul class="dropdown-subitem-wrapper">
<li
v-for="grandchild in child.items"
:key="grandchild.link"
class="dropdown-subitem"
>
<NavLink
:item="grandchild"
@focusout="
isLastItemOfArray(grandchild, child.items) &&
isLastItemOfArray(child, item.items) &&
setOpen(false)
"
/>
</li>
</ul>
</template>
<NavLink
v-else
:item="child"
@focusout="isLastItemOfArray(child, item.items) && setOpen(false)"
/>
</li>
</ul>
</div>
</template>
<script src="./DropdownLink" />
<style lang="stylus">
@require '~@mr-hope/vuepress-shared/styles/arrow'
@require '~@mr-hope/vuepress-shared/styles/reset'
.dropdown-wrapper
height 1.8rem
cursor pointer
&:not(:hover)
.arrow
transform rotate(-180deg)
&:hover, &.open
.nav-dropdown
z-index 2
transform scale(1)
visibility visible
opacity 1
.dropdown-title
button()
cursor inherit
padding inherit
color var(--dark-grey)
font-family inherit
font-size 0.9rem
font-weight 500
line-height 1.4rem
&::after
border-left 5px solid var(--accent-color)
&:hover
border-color transparent
.arrow
arrow()
font-size 1.2em
.nav-dropdown
box-sizing border-box
position absolute
top 100%
right 0
max-height 100vh - $navbarHeight
margin 0
padding 0.6rem 0
border 1px solid var(--grey14)
border-radius 0.25rem
background var(--bgcolor)
box-shadow 2px 2px 10px var(--card-shadow-color)
text-align left
white-space nowrap
overflow-y auto
transform scale(0.8)
opacity 0
visibility hidden
transition all 0.18s ease-out
.dropdown-item
color inherit
line-height 1.7rem
h4
margin 0
padding 0.75rem 1rem 0.25rem 0.75rem
border-top 1px solid var(--grey14)
color var(--dark-grey)
font-size 0.9rem
.nav-link
padding 0
&:before
display none
&:first-child h4
padding-top 0
border-top 0
.nav-link
display block
position relative
margin-bottom 0
padding 0 1.5rem 0 1.25rem
border-bottom none
color var(--dark-grey)
font-weight 400
line-height 1.7rem
&:hover
color var(--accent-color)
&.active
color var(--accent-color)
&::before
content ''
position absolute
top calc(50% - 3px)
left 9px
width 0
height 0
border-top 3px solid transparent
border-left 5px solid var(--accent-color)
border-bottom 3px solid transparent
.dropdown-subitem-wrapper
padding 0
list-style none
.dropdown-subitem
font-size 0.9em
</style>
import Vue from "vue";
import DropdownLink from "@theme/components/Navbar/DropdownLink.vue";
import I18nIcon from "@theme/icons/I18nIcon.vue";
import NavLink from "@theme/components/Navbar/NavLink.vue";
import { getNavLinkItem } from "@theme/utils/navbar";
export default Vue.extend({
name: "LanguageDropdown",
components: { NavLink, DropdownLink },
computed: {
dropdown() {
const { locales } = this.$site;
if (locales && Object.keys(locales).length > 1) {
const currentLink = this.$page.path;
const { routes } = this.$router.options;
const themeLocales = this.$themeConfig.locales || {};
const languageDropdown = {
text: this.$themeLocaleConfig.selectText || "Languages",
ariaLabel: this.$themeLocaleConfig.ariaLabel || "Select language",
items: Object.keys(locales).map((path) => {
const locale = locales[path];
const text = (themeLocales[path] && themeLocales[path].label) ||
locale.lang ||
"Unknown Language";
let link;
// Stay on the current page
if (locale.lang === this.$lang)
link = currentLink;
else {
// Try to stay on the same page
link = currentLink.replace(this.$localeConfig.path, path);
// Fallback to homepage
if (!(routes || []).some((route) => route.path === link))
link = path;
}
return { text, link };
}),
};
return getNavLinkItem(languageDropdown);
}
return false;
},
},
render(h) {
return this.dropdown
? h("div", { class: "nav-links" }, [
h("div", { class: "nav-item" }, [
h(DropdownLink, { props: { item: this.dropdown } }, [
h(I18nIcon, {
slot: "title",
style: {
width: "1rem",
height: "1rem",
verticalAlign: "middle",
marginLeft: "1rem",
},
}),
]),
]),
])
: null;
},
});
//# sourceMappingURL=LanguageDropdown.js.map
\ No newline at end of file
import Vue from "vue";
import { ensureExt, isExternal, isMailto, isTel } from "@theme/utils/path";
export default Vue.extend({
name: "NavLink",
props: {
item: { type: Object, required: true },
},
computed: {
link() {
return ensureExt(this.item.link);
},
iconPrefix() {
const { iconPrefix } = this.$themeConfig;
return iconPrefix === "" ? "" : iconPrefix || "icon-";
},
active() {
// link is home path
if ((this.$site.locales &&
Object.keys(this.$site.locales).some((rootLink) => rootLink === this.link)) ||
this.link === "/")
// exact match
return this.$route.path === this.link;
// inclusive match
return this.$route.path.startsWith(this.link);
},
isNonHttpURI() {
return isMailto(this.link) || isTel(this.link);
},
isBlankTarget() {
return this.target === "_blank";
},
isInternal() {
return !isExternal(this.link) && !this.isBlankTarget;
},
target() {
if (this.isNonHttpURI)
return null;
if (this.item.target)
return this.item.target;
return isExternal(this.link) ? "_blank" : "";
},
rel() {
if (this.isNonHttpURI)
return null;
if (this.item.rel === false)
return null;
if (this.item.rel)
return this.item.rel;
return this.isBlankTarget ? "noopener noreferrer" : null;
},
},
methods: {
focusoutAction() {
// eslint-disable-next-line vue/require-explicit-emits
this.$emit("focusout");
},
},
});
//# sourceMappingURL=NavLink.js.map
\ No newline at end of file
<template>
<!-- eslint-disable vue/no-deprecated-v-on-native-modifier -->
<RouterLink
v-if="isInternal"
class="nav-link"
:class="{ active }"
:to="link"
@focusout.native="focusoutAction"
>
<i v-if="item.icon" :class="`iconfont ${item.iconPrefix || iconPrefix}${item.icon} ${item.iconClass}`" />
{{ item.text }}
</RouterLink>
<a
v-else
class="nav-link external"
:href="link"
:target="target"
:rel="rel"
@focusout="focusoutAction"
>
<i v-if="item.icon" :class="`iconfont ${item.iconPrefix || iconPrefix}${item.icon} ${item.iconClass}`" />
{{ item.text }}
<OutboundLink v-if="isBlankTarget" />
</a>
</template>
<script src="./NavLink" />
<style lang="stylus">
.nav-link
line-height 1.4rem
.navbar &
color var(--dark-grey)
&.active
color var(--accent-color)
.sidebar &
color var(--text-color)
&:hover, &.active
color var(--accent-color)
</style>
import Vue from "vue";
import DropdownLink from "@theme/components/Navbar/DropdownLink.vue";
import NavLink from "@theme/components/Navbar/NavLink.vue";
import { getNavLinkItem } from "@theme/utils/navbar";
export default Vue.extend({
name: "NavLinks",
components: {
DropdownLink,
NavLink,
},
computed: {
navLinks() {
const navbar = this.$themeLocaleConfig.nav || this.$themeConfig.nav || [];
return navbar.map((link) => getNavLinkItem(link));
},
},
});
//# sourceMappingURL=NavLinks.js.map
\ No newline at end of file
<template>
<nav class="nav-links">
<!-- user links -->
<div v-for="item in navLinks" :key="item.link" class="nav-item">
<DropdownLink v-if="item.type === 'links'" :item="item" />
<NavLink v-else :item="item" />
</div>
</nav>
</template>
<script src="./NavLinks" />
<style lang="stylus">
.nav-links
display inline-block
.nav-item
position relative
display inline-block
line-height 2rem
margin-left 1.5rem
&:first-child
margin-left 0
> .nav-link
color var(--dark-grey)
&::after
position absolute
content ' '
left 50%
right 50%
bottom 0px
height 2px
background var(--accent-color-l10)
border-radius 1px
visibility hidden
transition left 0.2s ease-in-out, right 0.2s ease-in-out
&.active
color var(--accent-color)
&:hover, &.active
&::after
left 0
right 0
visibility visible
</style>
import Vue from "vue";
import AlgoliaSearchBox from "@AlgoliaSearchBox";
import LanguageDropdown from "@theme/components/Navbar/LanguageDropdown";
import NavLinks from "@theme/components/Navbar/NavLinks.vue";
import RepoLink from "@theme/components/Navbar/RepoLink.vue";
import SearchBox from "@SearchBox";
import SidebarButton from "@theme/components/Navbar/SidebarButton.vue";
import ThemeColor from "@ThemeColor";
let handler;
const css = (el, property) => {
// NOTE: Known bug, will return 'auto' if style value is 'auto'
const window = el.ownerDocument.defaultView;
// `null` means not to return pseudo styles
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return window.getComputedStyle(el, null)[property];
};
export default Vue.extend({
name: "Navbar",
components: {
AlgoliaSearchBox,
LanguageDropdown,
NavLinks,
RepoLink,
SearchBox,
SidebarButton,
ThemeColor,
},
data: () => ({
linksWrapMaxWidth: 0,
isMobile: false,
}),
computed: {
siteBrandTitle() {
return this.$site.title;
},
canHideSiteBrandTitle() {
return (Boolean(this.siteBrandTitle) &&
this.$themeConfig.hideSiteTitleonMobile !== false);
},
siteBrandLogo() {
const { logo } = this.$themeConfig;
return logo ? this.$withBase(logo) : "";
},
siteBrandDarkLogo() {
const { darkLogo } = this.$themeConfig;
return darkLogo ? this.$withBase(darkLogo) : "";
},
algoliaConfig() {
return (this.$themeLocaleConfig.algolia || this.$themeConfig.algolia || false);
},
isAlgoliaSearch() {
return Boolean(this.algoliaConfig &&
this.algoliaConfig.apiKey &&
this.algoliaConfig.indexName);
},
canHide() {
const autoHide = this.$themeConfig.navAutoHide;
return autoHide !== "none" && (autoHide === "always" || this.isMobile);
},
},
mounted() {
// Refer to config.styl
const MOBILE_DESKTOP_BREAKPOINT = 719;
const NAVBAR_HORIZONTAL_PADDING = parseInt(css(this.$el, "paddingLeft")) +
parseInt(css(this.$el, "paddingRight"));
handler = () => {
if (document.documentElement.clientWidth < MOBILE_DESKTOP_BREAKPOINT) {
this.isMobile = true;
this.linksWrapMaxWidth = 0;
}
else {
this.isMobile = false;
this.linksWrapMaxWidth =
this.$el.offsetWidth -
NAVBAR_HORIZONTAL_PADDING -
((this.$refs.siteInfo &&
this.$refs.siteInfo.$el &&
this.$refs.siteInfo.$el.offsetWidth) ||
0);
}
};
handler();
window.addEventListener("resize", handler);
window.addEventListener("orientationchange", handler);
},
// eslint-disable-next-line vue/no-deprecated-destroyed-lifecycle
beforeDestroy() {
window.removeEventListener("resize", handler);
window.removeEventListener("orientationchange", handler);
},
});
//# sourceMappingURL=Navbar.js.map
\ No newline at end of file
<template>
<header class="navbar" :class="{ 'can-hide': canHide }">
<slot name="start" />
<SidebarButton @toggle-sidebar="$emit('toggle-sidebar')" />
<RouterLink ref="siteInfo" :to="$localePath" class="home-link">
<img
v-if="siteBrandLogo"
class="logo"
:class="{ light: Boolean(siteBrandDarkLogo) }"
:src="siteBrandLogo"
:alt="siteBrandTitle"
/>
<img
v-if="siteBrandDarkLogo"
class="logo dark"
:src="siteBrandDarkLogo"
:alt="siteBrandTitle"
/>
<span
v-if="siteBrandTitle"
class="site-name"
:class="{ 'can-hide': canHideSiteBrandTitle }"
>{{ siteBrandTitle }}</span
>
</RouterLink>
<slot name="center" />
<div
:style="
linksWrapMaxWidth ? { 'max-width': `${linksWrapMaxWidth}px` } : {}
"
class="links"
>
<ThemeColor />
<AlgoliaSearchBox v-if="isAlgoliaSearch" :options="algoliaConfig" />
<SearchBox
v-else-if="
$themeConfig.search !== false && $page.frontmatter.search !== false
"
/>
<NavLinks class="can-hide" />
<LanguageDropdown />
<RepoLink class="can-hide" />
<slot name="end" />
</div>
</header>
</template>
<script src="./Navbar" />
<style lang="stylus">
.navbar
position fixed
z-index 200
top 0
left 0
right 0
height $navbarHeight
padding $navbarVerticalPadding $navbarHorizontalPadding
background var(--bgcolor-blur)
box-sizing border-box
box-shadow 0 2px 8px var(--card-shadow-color)
backdrop-filter saturate(200%) blur(20px)
line-height: $navbarHeight - $navbarVerticalPadding * 2
transition transform 0.3s ease-in-out
@media (max-width $MQMedium)
height $navbarMobileHeight
padding $navbarMobileVerticalPadding $navbarMobileHorizontalPadding
padding-left: $navbarMobileHorizontalPadding + 2.4rem
line-height: $navbarMobileHeight - $navbarMobileVerticalPadding * 2
.hide-navbar &.can-hide
transform translateY(-100%)
a, span, img
display inline-block
.logo
min-width: 200px
height: $navbarHeight - $navbarVerticalPadding * 2
margin-right 0.8rem
vertical-align top
@media (max-width $MQMedium)
min-width: $navbarMobileHeight - $navbarMobileVerticalPadding * 2
height: $navbarMobileHeight - $navbarMobileVerticalPadding * 2
.theme-light &
&.light
display inline-block
&.dark
display none
.theme-dark &
&.light
display none
&.dark
display inline-block
.can-hide
@media (max-width $MQMedium)
display none
.site-name
font-size 1.5rem
color var(--text-color)
position relative
@media (max-width $MQMedium)
width calc(100vw - 9.4rem)
overflow hidden
white-space nowrap
text-overflow ellipsis
.links
position absolute
top $navbarVerticalPadding
right $navbarHorizontalPadding
display flex
box-sizing border-box
padding-left 1.5rem
font-size 0.9rem
white-space nowrap
@media (max-width $MQMedium)
padding-left 0
top $navbarMobileVerticalPadding
right $navbarMobileHorizontalPadding
</style>
import Vue from "vue";
export default Vue.extend({
name: "RepoLink",
computed: {
repoLink() {
const { repo } = this.$themeConfig;
if (repo)
return /^https?:/u.test(repo) ? repo : `https://github.com/${repo}`;
return "";
},
repoLabel() {
if (!this.repoLink)
return "";
if (this.$themeConfig.repoLabel)
return this.$themeConfig.repoLabel;
const [repoHost] = /^https?:\/\/[^/]+/u.exec(this.repoLink) || [""];
const platforms = ["GitHub", "GitLab", "Bitbucket"];
for (let index = 0; index < platforms.length; index++) {
const platform = platforms[index];
if (new RegExp(platform, "iu").test(repoHost))
return platform;
}
return "Source";
},
},
});
//# sourceMappingURL=RepoLink.js.map
\ No newline at end of file
<template>
<!-- repo link -->
<a
v-if="repoLink && $themeConfig.repoDisplay !== false"
class="repo-link"
rel="noopener noreferrer"
:href="repoLink"
target="_blank"
>
{{ repoLabel }}
<OutboundLink />
</a>
</template>
<script src="./RepoLink" />
<style lang="stylus">
.repo-link
.navbar &
color var(--dark-grey)
margin-left 1rem
.sidebar-nav-links &
display block
padding 0.5rem 0 0.5rem 1.5rem
font-size 1.1em
line-height 1.25rem
</style>
<template>
<button
class="sidebar-button"
title="Sidebar Button"
@click="$emit('toggle-sidebar')"
>
<span class="icon" />
</button>
</template>
<style lang="stylus">
@require '~@mr-hope/vuepress-shared/styles/reset'
.sidebar-button
button()
display none
box-sizing content-box
position absolute
top calc(50% - 1.075rem)
left 1rem
width 1.25rem
height 1.25rem
padding 0.45rem
font unset
vertical-align middle
transition transform 0.2s ease-in-out
&::before
content ' '
margin-top 0.125em
&::after
content ' '
margin-bottom 0.125em
.icon
margin 0.2em 0
&::before, &::after, .icon
display block
width 100%
height 0.2em
transition transform 0.2s ease-in-out
border-radius 0.05em
background var(--text-color)
.sidebar-open .sidebar-button
&::before
transform translateY(0.4em) rotate(135deg)
.icon
transform scale(0)
&::after
transform translateY(-0.4em) rotate(-135deg)
@media (max-width $MQMedium)
.sidebar-button
display block
</style>
import Vue from "vue";
import Anchor from "@theme/components/Anchor.vue";
import Comment from "@Comment";
import MyTransition from "@theme/components/MyTransition.vue";
import PageInfo from "@mr-hope/vuepress-plugin-comment/lib/client/PageInfo.vue";
import PageMeta from "@theme/components/PageMeta.vue";
import PageNav from "@theme/components/PageNav.vue";
export default Vue.extend({
name: "Page",
components: {
Anchor,
Comment,
MyTransition,
PageInfo,
PageMeta,
PageNav,
},
props: {
sidebarItems: {
type: Array,
default: () => [],
},
headers: {
type: Array,
default: () => [],
},
},
});
//# sourceMappingURL=Page.js.map
<template>
<main class="page">
<BreadCrumb :key="$route.path" />
<slot name="top" />
<PageInfo :key="$route.path" />
<template>
<MyTransition :delay="0.12" :disable="true">
<Anchor :key="$route.path" />
</MyTransition>
<slot name="content-top" />
<MyTransition :delay="0.08" :disable="true">
<Content :key="$route.path" class="theme-default-content" />
</MyTransition>
<slot name="content-bottom" />
<MyTransition :delay="0.12" :disable="true">
<PageMeta :key="$route.path" />
</MyTransition>
<MyTransition :delay="0.14" :disable="true">
<PageNav :key="$route.path" v-bind="{ sidebarItems }" />
</MyTransition>
<MyTransition :delay="0.16" :disable="true">
<Comment :key="$route.path" />
</MyTransition>
</template>
<slot name="bottom" />
<!-- Google tag (gtag.js) -->
<!-- put here because the plugin didn't work -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-22HX148PZF">
</script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-9KLVB8X0ME');
</script>
</main>
</template>
<script src="./Page" />
<style lang="stylus">
.page
display block
box-sizing border-box
min-height 100vh
padding-left $sidebarWidth
padding-bottom 2rem
background var(--bgcolor)
@media (max-width $MQMobile)
min-height 100vh
// narrow desktop / iPad
@media (max-width $MQNarrow)
padding-left $mobileSidebarWidth
// wide mobile
@media (max-width $MQMobile)
padding-left 0
@media (min-width $MQMobile)
.theme-container:not(.has-sidebar) &
padding-left 0
@media (min-width $MQWide)
.has-anchor &:not(.blog)
padding-right 16rem
</style>
import Vue from "vue";
import EditIcon from "@theme/icons/EditIcon.vue";
import { endingSlashRE, outboundRE } from "@theme/utils/path";
export default Vue.extend({
name: "PageMeta",
components: { EditIcon },
computed: {
i18n() {
return (this.$themeLocaleConfig.meta || {
contributor: "Contributors",
editLink: "Edit this page",
updateTime: "Last Updated",
});
},
contributors() {
return this.$page.frontmatter.contributor === false ||
(this.$themeConfig.contributor === false &&
!this.$page.frontmatter.contributor)
? []
: this.$page.contributors || [];
},
contributorsText() {
return this.i18n.contributor;
},
updateTime() {
return this.$page.frontmatter.contributor === false ||
(this.$themeConfig.updateTime === false &&
!this.$page.frontmatter.updateTime)
? ""
: this.$page.updateTime || "";
},
updateTimeText() {
return this.i18n.updateTime;
},
editLink() {
const showEditLink = this.$page.frontmatter.editLink ||
(this.$themeConfig.editLinks !== false &&
this.$page.frontmatter.editLink !== false);
const { repo, docsRepo } = this.$site.themeConfig;
if (showEditLink && (repo || docsRepo) && this.$page.relativePath)
return this.createEditLink();
return false;
},
editLinkText() {
return this.i18n.editLink;
},
},
methods: {
createEditLink() {
const { repo = "", docsRepo = repo, docsDir = "", docsBranch = "main", } = this.$themeConfig;
const bitbucket = /bitbucket.org/u;
if (bitbucket.test(docsRepo))
return `${docsRepo.replace(endingSlashRE, "")}/src/${docsBranch}/${docsDir ? `${docsDir.replace(endingSlashRE, "")}/` : ""}${this.$page.relativePath}?mode=edit&spa=0&at=${docsBranch}&fileviewer=file-view-default`;
const gitlab = /gitlab.com/u;
if (gitlab.test(docsRepo))
return `${docsRepo.replace(endingSlashRE, "")}/-/edit/${docsBranch}/${docsDir ? `${docsDir.replace(endingSlashRE, "")}/` : ""}${this.$page.relativePath}`;
const base = outboundRE.test(docsRepo)
? docsRepo
: `https://github.com/${docsRepo}`;
return `${base.replace(endingSlashRE, "")}/edit/${docsBranch}/${docsDir ? `${docsDir.replace(endingSlashRE, "")}/` : ""}${this.$page.relativePath}`;
},
},
});
//# sourceMappingURL=PageMeta.js.map
\ No newline at end of file
<template>
<footer class="page-meta">
<div class="footer-box">
<div class="footer-box-area">
<span class="footer-section-header">About this page</span>
<ul>
<li>Updated: {{ updateTime.slice(0, updateTime.length - 6) }}</li>
</ul>
</div>
<div class="footer-box-area">
<span class="footer-section-header">Contribute</span>
<ul>
<li>
<a :href="editLink" target="_blank" rel="noopener noreferrer">
<i class="far fa-pencil"></i> Edit this page
</a>
</li>
<li>
<a href="https://github.com/ethereum-optimism/optimism/contribute" target="_blank" rel="noopener noreferrer">
<i class="far fa-hands-helping"></i> Contribute to Optimism
</a>
</li>
</ul>
</div>
<div class="footer-box-area">
<span class="footer-section-header">Still need help?</span>
<ul>
<li>
<a href="https://discord.optimism.io" target="_blank" rel="noopener noreferrer">
<i class="fab fa-discord"></i> Discord community
</a>
</li>
</ul>
</div>
</div>
</footer>
</template>
<script src="./PageMeta" />
<style lang="stylus">
@require '~@mr-hope/vuepress-shared/styles/wrapper'
.page-meta
@extend $wrapper
padding-top 12px
padding-bottom 12px
font-family Arial, Helvetica, sans-serif
overflow auto
.meta-item
.label
font-weight 500
color var(--text-color-l25)
.info
font-weight 400
color var(--dark-grey)
.edit-link
display inline-block
font-size 14px
.icon
position relative
bottom -0.125em
width 1em
height 1em
color var(--accent-color)
@media (max-width $MQMobile)
margin-bottom 8px
a
color var(--accent-color-l10)
.update-time
float right
font-size 14px
@media (max-width $MQMobile)
float none
font-size 13px
text-align left
.contributors
font-size 14px
text-align right
@media (max-width $MQMobile)
font-size 13px
text-align left
.footer-box
display flex
flex-direction row
justify-content space-between
background-color #F1F4F9
padding 32px
padding-left: 40px;
padding-right: 40px;
border-radius 16px
@media (max-width $MQNarrow)
flex-direction column
.footer-box-area
@media (max-width $MQNarrow)
margin-bottom 32px
span.footer-section-header
font-family 'Open Sans', sans-serif
font-weight 600
font-size 14px
line-height 20px
ul
list-style-type none
padding-left 0
font-size 14px
line-height 20px
margin-top 10px
margin-bottom 0px
color #68778D
li
margin-top 15px
margin-bottom 5px
a
color #68778D
font-family 'Open Sans', sans-serif
&:hover
color #FF0420
i
font-size 14px
width 20px
margin-right 3px
text-align center
</style>
import Vue from "vue";
import NextIcon from "@theme/icons/NextIcon.vue";
import PrevIcon from "@theme/icons/PrevIcon.vue";
import { resolvePath } from "@theme/utils/path";
import { resolvePageforSidebar } from "@theme/utils/sidebar";
const getSidebarItems = (items, result) => {
for (const item of items)
if (item.type === "group")
getSidebarItems((item.children || []), result);
else
result.push(item);
};
const find = (page, items, offset) => {
const result = [];
getSidebarItems(items, result);
for (let i = 0; i < result.length; i++) {
const cur = result[i];
if (cur.type === "page" && cur.path === decodeURIComponent(page.path))
return result[i + offset];
}
return false;
};
const resolvePageLink = (linkType, { themeConfig, page, route, site, sidebarItems }) => {
const themeLinkConfig =
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
themeConfig[`${linkType}Links`];
const pageLinkConfig = page.frontmatter[linkType];
if (themeLinkConfig === false || pageLinkConfig === false)
return false;
if (typeof pageLinkConfig === "string")
return resolvePageforSidebar(site.pages, resolvePath(pageLinkConfig, route.path));
return find(page, sidebarItems, linkType === "prev" ? -1 : 1);
};
export default Vue.extend({
name: "PageNav",
components: { NextIcon, PrevIcon },
props: {
sidebarItems: {
type: Array,
default: () => [],
},
},
computed: {
prev() {
return resolvePageLink("prev", {
sidebarItems: this.sidebarItems,
themeConfig: this.$themeConfig,
page: this.$page,
route: this.$route,
site: this.$site,
});
},
next() {
return resolvePageLink("next", {
sidebarItems: this.sidebarItems,
themeConfig: this.$themeConfig,
page: this.$page,
route: this.$route,
site: this.$site,
});
},
},
});
//# sourceMappingURL=PageNav.js.map
\ No newline at end of file
<template>
<div v-if="prev || next" class="page-nav">
<p class="inner">
<span v-if="prev" class="prev">
<a
v-if="prev.type === 'external'"
class="prev"
:href="prev.path"
target="_blank"
rel="noopener noreferrer"
>
<PrevIcon />
{{ prev.title || prev.path }}
<OutboundLink />
</a>
<RouterLink v-else class="prev" :to="prev.path">
<PrevIcon />
{{ prev.title || prev.path }}
</RouterLink>
</span>
<span v-if="next" class="next">
<a
v-if="next.type === 'external'"
:href="next.path"
target="_blank"
rel="noopener noreferrer"
>
{{ next.title || next.path }}
<OutboundLink />
<NextIcon />
</a>
<RouterLink v-else :to="next.path">
{{ next.title || next.path }}
<NextIcon />
</RouterLink>
</span>
</p>
</div>
</template>
<script src="./PageNav" />
<style lang="stylus">
@require '~@mr-hope/vuepress-shared/styles/wrapper'
.page-nav
@extend $wrapper
padding-top 12px
padding-bottom 0
font-family Arial, Helvetica, sans-serif
.inner
min-height 32px
margin-top 0
border-top 1px solid var(--border-color)
padding-top 16px
overflow auto // clear float
.prev .icon, .next .icon
position relative
top 0.125em
width 1em
height 1em
color var(--accent-color)
.next
float right
</style>
import Vue from "vue";
export default Vue.extend({
name: "DropdownTransition",
methods: {
setHeight(items) {
// explicitly set height so that it can be transitioned
items.style.height = `${items.scrollHeight}px`;
},
unsetHeight(items) {
items.style.height = "";
},
},
});
//# sourceMappingURL=DropdownTransition.js.map
\ No newline at end of file
<template>
<transition
name="dropdown"
@enter="setHeight"
@after-enter="unsetHeight"
@before-leave="setHeight"
>
<slot />
</transition>
</template>
<script src="./DropdownTransition" />
<style lang="stylus">
.dropdown-enter, .dropdown-leave-to
height 0 !important
</style>
import Vue from "vue";
import SidebarNavLinks from "@theme/components/Sidebar/SidebarNavLinks.vue";
import SidebarLinks from "@theme/components/Sidebar/SidebarLinks.vue";
export default Vue.extend({
name: "Sidebar",
components: {
SidebarLinks,
SidebarNavLinks,
},
props: {
items: { type: Array, required: true },
},
computed: {
blogConfig() {
return this.$themeConfig.blog || {};
},
sidebarDisplay() {
return this.blogConfig.sidebarDisplay || "none";
},
},
});
//# sourceMappingURL=Sidebar.js.map
<template>
<aside class="sidebar">
<slot name="top" />
<SidebarNavLinks />
<slot name="center" />
<SidebarLinks :depth="0" :items="items" />
<slot name="bottom" />
</aside>
</template>
<script src="./Sidebar" />
<style lang="stylus">
.sidebar
position fixed
z-index 150
top $navbarHeight
left 0
bottom 0
box-sizing border-box
width $sidebarWidth
margin 0
background var(--bgcolor-blur)
box-shadow 2px 0 4px var(--card-shadow-color)
backdrop-filter saturate(200%) blur(20px)
font-size 16px
overflow-y auto
@media (max-width $MQMedium)
top $navbarMobileHeight
.theme-container.hide-navbar &
top 0
.theme-container:not(.has-navbar) &
top 0
a
display inline-block
color var(--text-color)
.blogger-info.mobile
display none
.blogger-info.mobile + hr
display none
& > .sidebar-links
padding 1.5rem 0
& > li
& > a.sidebar-link
font-size 1.1em
line-height 1.7
&:not(:first-child)
margin-top 0.75rem
// narrow desktop / iPad
@media (max-width $MQNarrow)
width $mobileSidebarWidth
font-size 15px
@media (min-width $MQMedium)
.theme-container:not(.has-sidebar) &
display none
// wide mobile
@media (max-width $MQMedium)
transform translateX(-100%)
transition transform 0.2s ease
box-shadow none
.theme-container.sidebar-open &
transform translateX(0)
box-shadow 2px 0 8px var(--card-shadow-color)
.theme-container:not(.has-navbar) &
top 0
.blogger-info.mobile
display block
.blogger-info.mobile + hr
display block
margin-top 16px
& > .sidebar-links
padding 1rem 0
</style>
import Vue from "vue";
import DropdownTransition from "@theme/components/Sidebar/DropdownTransition.vue";
import NavLink from "@theme/components/Navbar/NavLink.vue";
export default Vue.extend({
name: "SidebarDropdownLink",
components: { NavLink, DropdownTransition },
props: {
item: { type: Object, required: true },
},
data: () => ({
open: false,
}),
computed: {
dropdownAriaLabel() {
return this.item.ariaLabel || this.item.text;
},
iconPrefix() {
const { iconPrefix } = this.$themeConfig;
return iconPrefix === "" ? "" : iconPrefix || "icon-";
},
},
watch: {
$route() {
this.open = false;
},
},
methods: {
setOpen(value) {
this.open = value;
},
isLastItemOfArray(item, array) {
if (Array.isArray(array))
return item === array[array.length - 1];
return false;
},
},
});
//# sourceMappingURL=SidebarDropdownLink.js.map
\ No newline at end of file
<template>
<div class="mobile-dropdown-wrapper" :class="{ open }">
<button
class="dropdown-title"
type="button"
:aria-label="dropdownAriaLabel"
@click="setOpen(!open)"
>
<span class="title">
<i v-if="item.icon" :class="`iconfont ${iconPrefix}${item.icon}`" />
{{ item.text }}
</span>
<span class="arrow" :class="open ? 'down' : 'right'" />
</button>
<DropdownTransition>
<ul v-show="open" class="nav-dropdown">
<li
v-for="(child, index) in item.items"
:key="child.link || index"
class="dropdown-item"
>
<h4 v-if="child.type === 'links'" class="dropdown-subtitle">
<NavLink
v-if="child.link"
:item="child"
@focusout="
isLastItemOfArray(child, item.children) &&
child.children.length === 0 &&
setOpen(false)
"
/>
<span v-else>{{ child.text }}</span>
</h4>
<ul v-if="child.type === 'links'" class="dropdown-subitem-wrapper">
<li
v-for="grandchild in child.items"
:key="grandchild.link"
class="dropdown-subitem"
>
<NavLink
:item="grandchild"
@focusout="
isLastItemOfArray(grandchild, child.items) &&
isLastItemOfArray(child, item.items) &&
setOpen(false)
"
/>
</li>
</ul>
<NavLink
v-else
:item="child"
@focusout="isLastItemOfArray(child, item.items) && setOpen(false)"
/>
</li>
</ul>
</DropdownTransition>
</div>
</template>
<script src="./SidebarDropdownLink" />
<style lang="stylus">
@require '~@mr-hope/vuepress-shared/styles/arrow'
@require '~@mr-hope/vuepress-shared/styles/reset'
.mobile-dropdown-wrapper
cursor pointer
.dropdown-title
button()
cursor inherit
padding inherit
color var(--text-color)
font-family inherit
font-size inherit
line-height 1.4rem
&:hover
color var(--accent-color)
.arrow
arrow()
.nav-dropdown
margin-top 0.25rem
transition height 0.1s ease-out
overflow hidden
.dropdown-item
color inherit
line-height 1.7rem
h4
margin 0
padding-left 1.25rem
font-size 15px
line-height 1.7
.nav-link
padding 0
&:before
display none
.nav-link
display block
position relative
margin-bottom 0
padding 0 1.5rem 0 1.25rem
border-bottom none
font-weight 400
line-height 1.7rem
&:hover
color var(--accent-color)
&.active
color var(--accent-color)
&::before
content ''
position absolute
top calc(50% - 3px)
left 9px
width 0
height 0
border-top 3px solid transparent
border-left 5px solid var(--accent-color)
border-bottom 3px solid transparent
& > .nav-link
font-size 15px
line-height 2rem
.dropdown-subitem-wrapper
padding 0
list-style none
.dropdown-subitem
font-size 0.9em
padding-left 0.5rem
</style>
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
import Vue from "vue";
import DropdownTransition from "@theme/components/Sidebar/DropdownTransition.vue";
import { isActive } from "@theme/utils/path";
export default Vue.extend({
name: "SidebarGroup",
components: { DropdownTransition },
props: {
item: {
type: Object,
required: true,
},
open: { type: Boolean },
depth: { type: Number, required: true },
},
beforeCreate() {
// eslint-disable-next-line
this.$options.components.SidebarLinks =
// eslint-disable-next-line @typescript-eslint/no-var-requires
require("@theme/components/Sidebar/SidebarLinks.vue").default;
},
methods: {
getIcon(icon) {
const { iconPrefix } = this.$themeConfig;
return this.$themeConfig.sidebarIcon !== false && icon
? `${iconPrefix === "" ? "" : iconPrefix || "icon-"}${icon}`
: "";
},
isActive,
},
});
//# sourceMappingURL=SidebarGroup.js.map
\ No newline at end of file
<template>
<section
:class="[
{
collapsable: item.collapsable,
'is-sub-group': depth !== 0,
},
`depth-${depth}`,
]"
class="sidebar-group"
>
<RouterLink
v-if="item.path"
:class="{
open,
active: isActive($route, item.path),
}"
class="sidebar-heading clickable"
:to="item.path"
@click="$emit('toggle')"
>
<i v-if="item.icon" :class="`iconfont ${getIcon(item.icon)}`" />
<span class="title">{{ item.title }}</span>
<span
v-if="item.collapsable"
:class="open ? 'down' : 'right'"
class="arrow"
/>
</RouterLink>
<p
v-else
:class="{ clickable: item.collapsable, open }"
class="sidebar-heading"
@click="$emit('toggle')"
>
<i v-if="item.icon" :class="`iconfont ${getIcon(item.icon)}`" />
<span class="title">{{ item.title }}</span>
<span
v-if="item.collapsable"
:class="open ? 'down' : 'right'"
class="arrow"
/>
</p>
<DropdownTransition>
<SidebarLinks
v-if="open || !item.collapsable"
class="sidebar-group-items"
:depth="depth + 1"
:items="item.children"
/>
</DropdownTransition>
</section>
</template>
<script src="./SidebarGroup" />
<style lang="stylus">
@require '~@mr-hope/vuepress-shared/styles/arrow'
.sidebar-group
.sidebar-group
padding-left 0.5em
&:not(.collapsable)
.sidebar-heading:not(.clickable)
color inherit
cursor auto
// refine styles of nested sidebar groups
&.is-sub-group
padding-left 0
& > .sidebar-heading
padding-left 1.75rem
font-weight normal
line-height 1.5
&:not(.clickable)
opacity 0.8
& > .sidebar-group-items
padding-left 1rem
& > li > .sidebar-link
border-left none
font-size 0.95em
&.depth-2
& > .sidebar-heading
border-left none
.sidebar-heading
display flex
box-sizing border-box
width 100%
margin 0
padding 0.35rem 1rem 0.35rem 1.25rem
border-left 0.25rem solid transparent
border-top-right-radius 0.25rem
border-bottom-right-radius 0.25rem
color var(--text-color)
font-size 1.1em
cursor pointer
transition color 0.15s ease
user-select none
&.open
color inherit
&.clickable
&:hover
background-color var(--bgcolor-light)
&.active
border-left-color var(--accent-color)
color var(--accent-color)
font-weight 500
.iconfont
margin-right 0.25em
.title
flex 1
.arrow
arrow()
font-size 1.5em
float right
.sidebar-group-items
font-size 0.95em
transition height 0.1s ease-out
overflow hidden
</style>
import Vue from "vue";
import { hashRE, isActive } from "@theme/utils/path";
import { groupSidebarHeaders } from "@theme/utils/sidebar";
const renderIcon = (h, icon) => icon
? h("i", {
class: ["iconfont", icon],
})
: null;
const renderLink = (h, { icon = "", text, link, level, active }) => h("RouterLink", {
props: {
to: link,
activeClass: "",
exactActiveClass: "",
},
class: {
active,
"sidebar-link": true,
[level ? `heading${level}` : ""]: level && level !== 2,
},
}, [renderIcon(h, icon), text]);
const renderExternalLink = (h, { path, title = path }) => h("a", {
attrs: {
href: path,
target: "_blank",
rel: "noopener noreferrer",
},
class: { "sidebar-link": true },
}, [title, h("OutboundLink")]);
const renderChildren = (h, { children, path, route, maxDepth, depth = 1 }) => {
if (!children || depth > maxDepth)
return null;
return h("ul", { class: "sidebar-sub-headers" }, children.map((child) => {
const active = isActive(route, `${path}#${child.slug}`);
return h("li", { class: "sidebar-sub-header" }, [
renderLink(h, {
text: child.title,
link: `${path}#${child.slug}`,
level: child.level,
active,
}),
renderChildren(h, {
children: child.children || false,
path,
route,
maxDepth,
depth: depth + 1,
}),
]);
}));
};
export default Vue.extend({
name: "SidebarLink",
functional: true,
props: {
item: {
type: Object,
required: true,
},
},
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
render(h, { parent: { $page, $route, $themeConfig, $themeLocaleConfig }, props }) {
const { item } = props;
// if the item can not be resolved
if (item.type === "error")
return null;
// external link
if (item.type === "external")
return renderExternalLink(h, item);
/*
* Use custom active class matching logic
* Due to edge case of paths ending with / + hash
*/
const selfActive = isActive($route, item.path);
/** whether the item is active */
const active =
// if the item is a heading, then one of the children needs to be active
item.type === "header"
? selfActive ||
(item.children || []).some((child) => isActive($route, `${item.basePath}#${child.slug}`))
: selfActive;
const pageMaxDepth = $page.frontmatter.sidebarDepth;
const localesMaxDepth = $themeLocaleConfig.sidebarDepth;
const themeMaxDepth = $themeConfig.sidebarDepth;
const maxDepth = typeof pageMaxDepth === "number"
? pageMaxDepth
: typeof localesMaxDepth === "number"
? localesMaxDepth
: typeof themeMaxDepth === "number"
? themeMaxDepth
: 2;
// the item is a heading
if (item.type === "header")
return [
renderLink(h, {
text: item.title || item.path,
link: item.path,
level: item.level,
active,
}),
renderChildren(h, {
children: item.children || false,
path: item.basePath,
route: $route,
maxDepth,
}),
];
const displayAllHeaders = $themeLocaleConfig.displayAllHeaders ||
$themeConfig.displayAllHeaders;
const link = renderLink(h, {
icon: $themeConfig.sidebarIcon !== false && item.frontmatter.icon
? `${$themeConfig.iconPrefix === ""
? ""
: $themeConfig.iconPrefix || "icon-"}${item.frontmatter.icon}`
: "",
text: item.title || item.path,
link: item.path,
active,
});
if ((active || displayAllHeaders) &&
item.headers &&
!hashRE.test(item.path)) {
const children = groupSidebarHeaders(item.headers);
return [
link,
renderChildren(h, {
children,
path: item.path,
route: $route,
maxDepth,
}),
];
}
return link;
},
});
//# sourceMappingURL=SidebarLink.js.map
\ No newline at end of file
<script src="./SidebarLink" />
<style lang="stylus">
$headings = 3 4 5 6
.sidebar-links
ul
padding 0
margin 0
list-style-type none
&.sidebar-sub-headers
padding-left 1rem
font-size 0.95em
.has-anchor &
@media (min-width $MQWide)
display none
a.sidebar-link
display inline-block
box-sizing border-box
width 100%
padding 0.35rem 1rem 0.35rem 1.25rem
border-left 0.2rem solid transparent
color var(--text-color)
font-size 1em
line-height 1.5
font-weight 400
for $heading in $headings
&.heading{$heading}
padding-left ($heading - 0.5) * 0.5rem !important
.iconfont
margin-right 0.25em
&:hover
background-color var(--bgcolor-light)
&.active
border-left-color var(--accent-color)
background-color var(--accent-color-a15)
color var(--accent-color)
font-weight 500
.iconfont
color var(--accent-color)
.sidebar-group &
padding-left 1.75rem
.sidebar-sub-headers &
padding-top 0.25rem
padding-bottom 0.25rem
border-left none
&.active
background-color transparent
font-weight 500
</style>
import Vue from "vue";
import SidebarGroup from "@theme/components/Sidebar/SidebarGroup.vue";
import SidebarLink from "@theme/components/Sidebar/SidebarLink.vue";
import { isActive } from "@theme/utils/path";
const descendantIsActive = (route, item) => {
if (item.type === "group")
return item.children.some((child) => {
if (child.type === "group")
return descendantIsActive(route, child);
return child.type === "page" && isActive(route, child.path);
});
return false;
};
const resolveOpenGroupIndex = (route, items) => {
for (let i = 0; i < items.length; i++)
if (descendantIsActive(route, items[i]))
return i;
return -1;
};
export default Vue.extend({
name: "SidebarLinks",
components: { SidebarGroup, SidebarLink },
props: {
items: {
type: Array,
required: true,
},
depth: { type: Number, required: true },
},
data: () => ({
openGroupIndex: 0,
}),
watch: {
$route() {
this.refreshIndex();
},
},
created() {
this.refreshIndex();
},
methods: {
refreshIndex() {
const index = resolveOpenGroupIndex(this.$route, this.items);
if (index > -1)
this.openGroupIndex = index;
},
toggleGroup(index) {
this.openGroupIndex = index === this.openGroupIndex ? -1 : index;
},
isActive(page) {
return isActive(this.$route, page.regularPath);
},
},
});
//# sourceMappingURL=SidebarLinks.js.map
\ No newline at end of file
<template>
<ul v-if="items.length" class="sidebar-links">
<li v-for="(item, index) in items" :key="index">
<SidebarGroup
v-if="item.type === 'group'"
:item="item"
:open="index === openGroupIndex"
:depth="depth"
@toggle="toggleGroup(index)"
/>
<SidebarLink v-else :item="item" />
</li>
</ul>
</template>
<script src="./SidebarLinks" />
import Vue from "vue";
import RepoLink from "@theme/components/Navbar/RepoLink.vue";
import SidebarDropdownLink from "@theme/components/Sidebar/SidebarDropdownLink.vue";
import NavLink from "@theme/components/Navbar/NavLink.vue";
import { getNavLinkItem } from "@theme/utils/navbar";
export default Vue.extend({
name: "SidebarNavLinks",
components: {
RepoLink,
SidebarDropdownLink,
NavLink,
},
computed: {
navLinks() {
const navbar = this.$themeLocaleConfig.nav || this.$themeConfig.nav || [];
return navbar.map((link) => getNavLinkItem(link));
},
},
});
//# sourceMappingURL=SidebarNavLinks.js.map
\ No newline at end of file
<template>
<nav class="sidebar-nav-links">
<!-- user links -->
<div v-for="item in navLinks" :key="item.link" class="nav-item">
<SidebarDropdownLink v-if="item.type === 'links'" :item="item" />
<NavLink v-else :item="item" />
</div>
<RepoLink />
</nav>
</template>
<script src="./SidebarNavLinks" />
<style lang="stylus">
.sidebar-nav-links
display none
padding 0.5rem 0 0.75rem 0
border-bottom 1px solid var(--border-color)
@media (max-width $MQMedium)
display block
ul
padding 0
margin 0.25rem 0 0
list-style-type none
.nav-item
position relative
display block
padding 0.5rem 0 0.5rem 1.5rem
font-size 1.1em
line-height 1.25rem
&:first-child
margin-left 0
</style>
import Vue from "vue";
import AutoIcon from "@theme/icons/AutoIcon.vue";
import DarkIcon from "@theme/icons/DarkIcon.vue";
import LightIcon from "@theme/icons/LightIcon.vue";
import { changeClass } from "@theme/utils/dom";
export default Vue.extend({
name: "DarkmodeSwitch",
components: { AutoIcon, DarkIcon, LightIcon },
data: () => ({
darkmode: "auto",
}),
computed: {
darkmodeConfig() {
return this.$themeConfig.darkmode || "auto-switch";
},
},
mounted() {
this.darkmode =
localStorage.getItem("darkmode") ||
"auto";
if (this.darkmodeConfig === "auto-switch")
if (this.darkmode === "auto")
this.setDarkmode("auto");
else
this.setDarkmode(this.darkmode);
else if (this.darkmodeConfig === "auto")
this.setDarkmode("auto");
else if (this.darkmodeConfig === "switch")
this.setDarkmode(this.darkmode);
// disabled
else
this.setDarkmode("off");
},
methods: {
setDarkmode(status) {
if (status === "on")
this.toggleDarkmode(true);
else if (status === "off")
this.toggleDarkmode(false);
else {
const isDarkMode = window.matchMedia("(prefers-color-scheme: dark)").matches;
const isLightMode = window.matchMedia("(prefers-color-scheme: light)").matches;
window
.matchMedia("(prefers-color-scheme: dark)")
.addEventListener("change", (event) => {
if (event.matches)
this.toggleDarkmode(true);
});
window
.matchMedia("(prefers-color-scheme: light)")
.addEventListener("change", (event) => {
if (event.matches)
this.toggleDarkmode(false);
});
if (isDarkMode)
this.toggleDarkmode(true);
else if (isLightMode)
this.toggleDarkmode(false);
else {
const timeHour = new Date().getHours();
this.toggleDarkmode(timeHour < 6 || timeHour >= 18);
}
}
this.darkmode = status;
localStorage.setItem("darkmode", status);
},
toggleDarkmode(isDarkmode) {
const classes = document.body.classList;
if (isDarkmode)
changeClass(classes, ["theme-dark"], ["theme-light"]);
else
changeClass(classes, ["theme-light"], ["theme-dark"]);
},
},
});
//# sourceMappingURL=DarkmodeSwitch.js.map
\ No newline at end of file
<template>
<div class="darkmode-switch">
<template v-if="darkmodeConfig === 'auto-switch'">
<div
class="item day"
:class="{ active: darkmode === 'off' }"
@click="setDarkmode('off')"
>
<LightIcon />
</div>
<div
class="item auto"
:class="{ active: darkmode === 'auto' }"
@click="setDarkmode('auto')"
>
<AutoIcon />
</div>
<div
class="item night"
:class="{ active: darkmode === 'on' }"
@click="setDarkmode('on')"
>
<DarkIcon />
</div>
</template>
<div v-else-if="darkmodeConfig === 'switch'" class="switch">
<input
id="switch"
class="switch-input"
type="checkbox"
:checked="darkmode !== 'on'"
@click="setDarkmode(darkmode === 'on' ? 'off' : 'on')"
/>
<label class="label" for="switch">
<span class="label-content" />
</label>
</div>
</div>
</template>
<script src="./DarkmodeSwitch" />
<style lang="stylus">
@keyframes starry_star
50%
background rgba(255, 255, 255, 0.1)
box-shadow #fff 7.5px -0.75px 0 0, #fff 3px 2.5px 0 -0.25px, rgba(255, 255, 255, 0.1) 9.5px 4.5px 0 0.25px, #fff 8px 8.5px 0 0, rgba(255, 255, 255, 0.1) 5px 6px 0 -0.375px, #fff 1.25px 9.5px 0 0.25px
@keyframes bounceIn
0%
opacity 0
transform scale(0.3)
50%
opacity 100
transform scale(1.1)
55%
transform scale(1.1)
75%
transform scale(0.9)
100%
opacity 100
transform scale(1)
.darkmode-switch
display flex
height 22px
&:hover
cursor pointer
.item
padding 2px
border 1px solid var(--accent-color)
border-left none
line-height 1
&:first-child
border-left 1px solid var(--accent-color)
&.day
border-radius 4px 0 0 4px
&.night
border-radius 0 4px 4px 0
.icon
width 16px
height 16px
color var(--accent-color)
&.active
background var(--accent-color)
&:hover
cursor default
.icon
color var(--white)
.switch
display block
text-align center
user-select none
.label
display block
position relative
width 31.25px
height 17.5px
margin 0 auto
border-radius 17.5px
border 1px solid #1c1c1c
background #3c4145
font-size 1.4em
transition all 250ms ease-in
&:hover
cursor pointer
&:before
content ''
display block
position absolute
top 0.5px
left 1px
width 14px
height 14px
border 1.25px solid #e3e3c7
border-radius 50%
background #fff
transition all 250ms ease-in
&:after
content ''
display block
position absolute
top 62%
left 9.75px
z-index 10
width 2.8px
height 2.8px
opacity 0
background #fff
border-radius 50%
box-shadow #fff 0 0, #fff 0.75px 0, #fff 1.5px 0, #fff 2.25px 0, #fff 2.75px 0, #fff 3.5px 0, #fff 4px 0, #fff 5.25px -0.25px 0 0.25px, #fff 4px -1.75px 0 -0.5px, #fff 1.75px -1.75px 0 0.25px, #d3d3d3 0 0 0 1px, #d3d3d3 1.5px 0 0 1px, #d3d3d3 2.75px 0 0 1px, #d3d3d3 4px 0 0 1px, #d3d3d3 5.25px -0.25px 0 1.25px, #d3d3d3 4px -1.75px 0 0.25px, #d3d3d3 1.75px -1.75px 0 1.25px
transition opacity 100ms ease-in
.label-content
display block
position absolute
top 2.25px
left 52.5%
z-index 20
width 1px
height 1px
border-radius 50%
background #fff
box-shadow rgba(255, 255, 255, 0.1) 7.5px -0.75px 0 0, rgba(255, 255, 255, 0.1) 3px 2.5px 0 -0.25px, #fff 9.5px 4.5px 0 0.25px, rgba(255, 255, 255, 0.1) 8px 8.5px 0 0, #fff 5px 6px 0 0.375px, rgba(255, 255, 255, 0.1) 1.25px 9.5px 0 0.25px
animation starry_star 5s ease-in-out infinite
transition all 250ms ease-in
&:before
content ''
display block
position absolute
top -0.5px
left -6.25px
width 4.5px
height 4.5px
background #fff
border-radius 50%
border 1.25px solid #e3e3c7
box-shadow #e3e3c7 -7px 0 0 -0.75px, #e3e3c7 -2px 6px 0 -0.5px
transform-origin -1.5px 130%
transition all 250ms ease-in
.switch-input
display none
transition all 250ms ease-in
&:checked + .label
background #9ee3fb
border 1px solid #86c3d7
&:before
left 13.75px
background #ffdf6d
border 1.25px solid #e1c348
&:after
opacity 100
animation bounceIn 0.6s ease-in-out 0.1s
animation-fill-mode backwards
& > .label-content
opacity 0
box-shadow rgba(255, 255, 255, 0.1) 7.5px -0.75px 0 -1px, rgba(255, 255, 255, 0.1) 3px 2.5px 0 -1.25px, #fff 9.5px 4.5px 0 -0.75px, rgba(255, 255, 255, 0.1) 8px 8.5px 0 -1px, #fff 5px 6px 0 -1.375px, rgba(255, 255, 255, 0.1) 1.25px 9.5px 0 -0.75px
animation none
&:before
left 6.25px
transform rotate(70deg)
</style>
import Vue from "vue";
import ClickOutside from "@theme/utils/click-outside";
import ThemeOptions from "@theme/components/Theme/ThemeOptions.vue";
export default Vue.extend({
name: "ThemeColor",
directives: { "click-outside": ClickOutside },
components: { ThemeOptions },
data: () => ({
showMenu: false,
}),
methods: {
clickOutside() {
this.showMenu = false;
},
},
});
//# sourceMappingURL=ThemeColor.js.map
\ No newline at end of file
<template>
<button
v-click-outside="clickOutside"
class="color-button"
:class="{ select: showMenu }"
tabindex="-1"
aria-hidden="true"
@click="showMenu = !showMenu"
>
<svg
class="skin-icon"
viewBox="0 0 1024 1024"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M224 800c0 9.6 3.2 44.8 6.4 54.4 6.4 48-48 76.8-48 76.8s80 41.6 147.2 0 134.4-134.4
38.4-195.2c-22.4-12.8-41.6-19.2-57.6-19.2C259.2 716.8 227.2 761.6 224 800zM560 675.2l-32
51.2c-51.2 51.2-83.2 32-83.2 32 25.6 67.2 0 112-12.8 128 25.6 6.4 51.2 9.6 80 9.6 54.4 0
102.4-9.6 150.4-32l0 0c3.2 0 3.2-3.2 3.2-3.2 22.4-16 12.8-35.2
6.4-44.8-9.6-12.8-12.8-25.6-12.8-41.6 0-54.4 60.8-99.2 137.6-99.2 6.4 0 12.8 0 22.4
0 12.8 0 38.4 9.6 48-25.6 0-3.2 0-3.2 3.2-6.4 0-3.2 3.2-6.4 3.2-6.4 6.4-16 6.4-16 6.4-19.2
9.6-35.2 16-73.6 16-115.2 0-105.6-41.6-198.4-108.8-268.8C704 396.8 560 675.2 560 675.2zM224
419.2c0-28.8 22.4-51.2 51.2-51.2 28.8 0 51.2 22.4 51.2 51.2 0 28.8-22.4 51.2-51.2 51.2C246.4
470.4 224 448 224 419.2zM320 284.8c0-22.4 19.2-41.6 41.6-41.6 22.4 0 41.6 19.2 41.6 41.6 0
22.4-19.2 41.6-41.6 41.6C339.2 326.4 320 307.2 320 284.8zM457.6 208c0-12.8 12.8-25.6 25.6-25.6
12.8 0 25.6 12.8 25.6 25.6 0 12.8-12.8 25.6-25.6 25.6C470.4 233.6 457.6 220.8 457.6 208zM128
505.6C128 592 153.6 672 201.6 736c28.8-60.8 112-60.8 124.8-60.8-16-51.2 16-99.2
16-99.2l316.8-422.4c-48-19.2-99.2-32-150.4-32C297.6 118.4 128 291.2 128 505.6zM764.8
86.4c-22.4 19.2-390.4 518.4-390.4 518.4-22.4 28.8-12.8 76.8 22.4 99.2l9.6 6.4c35.2 22.4
80 12.8 99.2-25.6 0 0 6.4-12.8 9.6-19.2 54.4-105.6 275.2-524.8 288-553.6
6.4-19.2-3.2-32-19.2-32C777.6 76.8 771.2 80 764.8 86.4z"
/>
</svg>
<transition mode="out-in" name="menu-transition">
<div v-show="showMenu" class="color-picker-menu">
<ThemeOptions />
</div>
</transition>
</button>
</template>
<script src="./ThemeColor" />
<style lang="stylus">
@require '~@mr-hope/vuepress-shared/styles/reset'
.color-button
button()
position relative
width 2.25rem
height 2.25rem
margin 0 0.25rem
padding 0.5rem
outline none
color #aaa
flex-shrink 0
&:hover, &.select
color var(--accent-color)
&.select:hover
color #aaa
.skin-icon
width 100%
height 100%
fill currentcolor
.color-picker-menu
position absolute
top: $navbarHeight - $navbarVerticalPadding
left 50%
min-width 100px
margin 0
padding 0.5em 0.75em
background var(--bgcolor)
box-shadow 2px 2px 10px var(--card-shadow-color)
color var(--dark-grey)
border-radius 0.25em
transform translateX(-50%)
z-index 250
@media (max-width $MQMobile)
top: $navbarMobileHeight - $navbarMobileVerticalPadding
transform translateX(-80%)
&::before
content ''
position absolute
top -7px
left 50%
border-style solid
border-color transparent transparent var(--bgcolor)
border-width 0 7px 7px
transform translateX(-50%)
@media (max-width $MQMobile)
left 80%
&.menu-transition-enter-active, &.menu-transition-leave-active
transition all 0.25s ease-in-out
&.menu-transition-enter, &.menu-transition-leave-to
top 30px
opacity 0
ul
list-style-type none
margin 0
padding 0
@media (max-width $MQMobile)
.color-picker
.color-picker-menu
left calc(50% - 35px)
&::before
left calc(50% + 35px)
</style>
import Vue from "vue";
import { getDefaultLocale } from "@mr-hope/vuepress-shared";
import DarkmodeSwitch from "@theme/components/Theme/DarkmodeSwitch.vue";
const defaultColorPicker = {
red: "#e74c3c",
blue: "#3498db",
green: "#3eaf7c",
orange: "#f39c12",
purple: "#8e44ad",
};
export default Vue.extend({
name: "ThemeOptions",
components: { DarkmodeSwitch },
data: () => ({
themeColor: {},
isDarkmode: false,
}),
computed: {
text() {
return (this.$themeLocaleConfig.themeColor || getDefaultLocale().themeColor);
},
themeColorEnabled() {
return this.$themeConfig.themeColor !== false;
},
switchEnabled() {
return (this.$themeConfig.darkmode !== "disable" &&
this.$themeConfig.darkmode !== "auto");
},
},
mounted() {
const theme = localStorage.getItem("theme");
this.themeColor = {
list: this.$themeConfig.themeColor
? Object.keys(this.$themeConfig.themeColor)
: Object.keys(defaultColorPicker),
picker: this.$themeConfig.themeColor || defaultColorPicker,
};
if (theme)
this.setTheme(theme);
},
methods: {
setTheme(theme) {
const classes = document.body.classList;
const themes = this.themeColor.list.map((colorTheme) => `theme-${colorTheme}`);
if (!theme) {
localStorage.removeItem("theme");
classes.remove(...themes);
return;
}
classes.remove(...themes.filter((themeclass) => themeclass !== `theme-${theme}`));
classes.add(`theme-${theme}`);
localStorage.setItem("theme", theme);
},
},
});
//# sourceMappingURL=ThemeOptions.js.map
\ No newline at end of file
<template>
<div class="theme-options">
<ul v-if="themeColorEnabled" class="themecolor-select">
<label for="themecolor-select" v-text="`${text.themeColor}:`" />
<li>
<span class="default-theme" @click="setTheme()" />
</li>
<li v-for="color in themeColor.list" :key="color">
<span
:style="{ background: themeColor.picker[color] }"
@click="setTheme(color)"
/>
</li>
</ul>
<div v-if="switchEnabled" class="darkmode-toggle">
<label class="desc" for="darkmode-toggle" v-text="`${text.themeMode}:`" />
<DarkmodeSwitch />
<ScreenFull />
</div>
</div>
</template>
<script src="./ThemeOptions" />
<style lang="stylus">
.theme-options
font-size 14px
.themecolor-select
display flex
justify-content space-around
label
padding-right 8px
li
&:first-child
margin-right 8px
span
vertical-align middle
width 15px
height 15px
margin 0 2px
border-radius 2px
&.default-theme
background $accentColor // must be fixed to the original accent-color
.darkmode-toggle
display flex
align-items center
margin-top 8px
.desc
padding-right 8px
line-height 1.5
.full-screen, .cancel-full-screen
margin-left 0.5em
</style>
<template>
<svg
class="icon auto-icon"
viewBox="0 0 1024 1024"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M460.864 539.072H564.8L510.592 376l-49.728 163.072zM872 362.368V149.504H659.648L510.528 0l-149.12 149.504H149.12v212.928L0 511.872l149.12 149.504v212.928h212.352l149.12 149.504 149.12-149.504h212.352V661.376l149.12-149.504L872 362.368zM614.464 693.12l-31.616-90.624H438.272l-31.616 90.624h-85.888l144.576-407.68h90.368l144.576 407.68h-85.824zm0 0"
fill="currentColor"
/>
</svg>
</template>
<template>
<svg
class="icon dark-icon"
viewBox="0 0 1024 1024"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M935.539 630.402c-11.43-11.432-28.674-14.739-43.531-8.354-46.734 20.103-96.363 30.297-147.508 30.297-99.59 0-193.221-38.784-263.64-109.203-108.637-108.637-139.61-270.022-78.908-411.148a39.497 39.497 0 0 0-51.886-51.887c-52.637 22.64-100.017 54.81-140.826 95.616-85.346 85.346-132.346 198.821-132.346 319.52 0 120.7 47.001 234.172 132.347 319.519S408.063 947.11 528.76 947.11c120.7 0 234.172-47.003 319.52-132.351 40.809-40.81 72.978-88.19 95.616-140.826a39.497 39.497 0 0 0-8.356-43.532z"
fill="currentColor"
/>
</svg>
</template>
<template>
<svg
class="icon edit-icon"
viewBox="0 0 1024 1024"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M117.953 696.992 64.306 959.696l265.931-49.336 450.204-452.505-212.284-213.376-450.204 452.513zm496.384-296.326L219.039 797.993l-46.108-46.34L568.233 354.33l46.104 46.335zm345.357-122.99-114.45 115.04-212.288-213.377 114.45-115.035 212.288 213.371zm0 0"
fill="currentColor"
/>
</svg>
</template>
<template>
<svg
class="icon i18n-icon"
viewBox="0 0 1024 1024"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M639.981 344.075c14.805 44.45 34.542 79.023 69.084 113.596 29.603-29.634 49.34-69.146 64.145-113.596H639.981zM314.33 591.024h128.29l-64.145-172.865-64.145 172.865z"
fill="currentColor"
/>
<path
d="M807.746 116.882H215.643c-54.274 0-98.681 44.45-98.681 98.78v592.677c0 54.329 44.407 98.78 98.68 98.78h592.104c54.273 0 98.681-44.451 98.681-98.78V215.66c0-54.329-39.475-98.78-98.68-98.78zM565.971 754.01c-9.866 9.878-19.738 9.878-29.603 9.878-4.94 0-14.805 0-19.738-4.939-4.939-4.939-9.872 0-9.872-4.939s-4.932-9.878-9.865-19.756c-4.94-9.878-4.94-14.817-9.872-24.695L467.29 655.23H294.592l-19.737 54.33c-9.866 19.755-14.805 34.572-19.738 44.45-4.939 9.878-14.804 9.878-29.603 9.878-9.871 0-19.737-4.939-29.609-9.878-9.865-9.878-14.798-14.817-14.798-24.695 0-4.939 0-9.878 4.933-19.756 4.939-9.878 4.939-14.817 9.865-24.695l108.553-276.583c4.939-9.878 4.939-19.756 9.872-29.633 4.932-9.878 9.865-19.756 14.798-24.695 4.939-4.94 9.872-14.817 19.737-19.756 9.872-4.94 19.738-4.94 29.61-4.94 9.865 0 19.73 0 29.603 4.94 9.865 4.939 14.804 9.878 19.737 19.756 4.933 4.939 9.866 14.817 14.798 24.695 4.94 9.877 9.872 19.755 14.805 34.572l108.553 271.644c9.865 19.756 14.804 34.573 14.804 44.451-4.939 4.94-9.872 14.817-14.804 24.695zm271.378-192.62c-54.273-19.756-93.748-44.451-128.29-74.085-34.536 34.573-78.943 59.268-133.223 74.085l-14.798-24.695c54.273-14.817 98.68-34.573 133.223-69.146-34.542-34.573-64.145-79.024-74.017-128.413h-49.34V319.38h133.228c-9.877-14.817-19.743-34.573-29.609-49.39l14.799-4.94c9.871 14.818 24.676 34.574 34.542 54.33h123.35v24.695h-49.34c-14.798 49.39-39.468 93.84-69.077 123.474 34.541 29.634 74.01 54.329 128.29 69.146l-19.738 24.695z"
fill="currentColor"
/>
</svg>
</template>
<template>
<svg
class="icon light-icon"
viewBox="0 0 1024 1024"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M512 256a42.667 42.667 0 0 0 42.667-42.667V128a42.667 42.667 0 0 0-85.334 0v85.333A42.667 42.667 0 0 0 512 256zm384 213.333h-85.333a42.667 42.667 0 0 0 0 85.334H896a42.667 42.667 0 0 0 0-85.334zM256 512a42.667 42.667 0 0 0-42.667-42.667H128a42.667 42.667 0 0 0 0 85.334h85.333A42.667 42.667 0 0 0 256 512zm9.387-298.667a42.667 42.667 0 0 0-59.307 62.72l61.44 59.307a42.667 42.667 0 0 0 31.147 11.947 42.667 42.667 0 0 0 30.72-13.227 42.667 42.667 0 0 0 0-60.16zm459.946 133.974a42.667 42.667 0 0 0 29.44-11.947l61.44-59.307a42.667 42.667 0 0 0-57.6-62.72l-61.44 60.587a42.667 42.667 0 0 0 0 60.16 42.667 42.667 0 0 0 28.16 13.227zM512 768a42.667 42.667 0 0 0-42.667 42.667V896a42.667 42.667 0 0 0 85.334 0v-85.333A42.667 42.667 0 0 0 512 768zm244.48-79.36a42.667 42.667 0 0 0-59.307 61.44l61.44 60.587a42.667 42.667 0 0 0 29.44 11.946 42.667 42.667 0 0 0 30.72-12.8 42.667 42.667 0 0 0 0-60.586zm-488.96 0-61.44 59.307a42.667 42.667 0 0 0 0 60.586 42.667 42.667 0 0 0 30.72 12.8 42.667 42.667 0 0 0 28.587-10.666l61.44-59.307a42.667 42.667 0 0 0-59.307-61.44zM512 341.333A170.667 170.667 0 1 0 682.667 512 170.667 170.667 0 0 0 512 341.333z"
fill="currentColor"
/>
</svg>
</template>
<template>
<svg
class="icon next-icon"
viewBox="0 0 1024 1024"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M906.772 512c0 4.772-2.211 9.267-5.99 12.175L524.257 813.66a15.37 15.37 0 0 1-18.616.092 15.368 15.368 0 0 1-5.038-17.91l75.714-191.672h-443.73c-8.488 0-15.36-6.881-15.36-15.36v-153.6c0-8.489 6.872-15.36 15.36-15.36h443.73l-75.714-191.682a15.358 15.358 0 0 1 5.048-17.91c5.51-4.158 13.128-4.137 18.606.092l376.525 289.485a15.323 15.323 0 0 1 5.99 12.165z"
fill="currentColor"
/>
</svg>
</template>
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
class="page-404-icon"
viewBox="0 0 178 130"
>
<defs>
<linearGradient
id="b"
x1=".5"
x2=".5"
y2="1"
gradientUnits="objectBoundingBox"
>
<stop offset="0" stop-color="#e9e9e9" />
<stop offset="1" stop-color="#fff" stop-opacity="0" />
</linearGradient>
<linearGradient
id="c"
x1=".5"
x2=".5"
y2="1"
gradientUnits="objectBoundingBox"
>
<stop offset="0" stop-color="#dcdcdc" />
<stop offset="1" stop-color="#fff" stop-opacity="0" />
</linearGradient>
<linearGradient
id="d"
x1=".5"
x2=".5"
y2="1"
gradientUnits="objectBoundingBox"
>
<stop offset="0" stop-color="#f1f1f1" />
<stop offset="1" stop-color="#fff" stop-opacity="0" />
</linearGradient>
<linearGradient
id="e"
x1=".5"
x2=".5"
y2="1"
gradientUnits="objectBoundingBox"
>
<stop offset="0" stop-color="#dedede" />
<stop offset="1" stop-color="#fff" stop-opacity="0" />
</linearGradient>
<linearGradient
id="f"
x1=".5"
x2=".5"
y2="1"
gradientUnits="objectBoundingBox"
>
<stop offset="0" stop-color="#e8e8e8" />
<stop offset="1" stop-color="#fff" stop-opacity="0" />
</linearGradient>
<linearGradient
id="g"
x1=".213"
y1="1.265"
x2=".846"
gradientUnits="objectBoundingBox"
>
<stop offset="0" stop-color="#fff" />
<stop offset="1" stop-color="#f5f5f5" />
</linearGradient>
<linearGradient
id="h"
x1=".5"
x2=".5"
y2="1"
gradientUnits="objectBoundingBox"
>
<stop offset="0" stop-color="#c5c5c5" />
<stop offset="1" stop-color="#fff" stop-opacity="0" />
</linearGradient>
<linearGradient
id="i"
x1=".5"
x2=".5"
y2="1"
gradientUnits="objectBoundingBox"
>
<stop offset="0" stop-color="#acacac" />
<stop offset="1" stop-color="#f2f2f2" stop-opacity=".388" />
</linearGradient>
<clipPath id="a">
<path transform="translate(744 1111)" fill="none" d="M0 0h178v130H0z" />
</clipPath>
</defs>
<g transform="translate(-744 -1111)" clip-path="url(#a)">
<g>
<path
d="M0 10.795 36.6 0v93.779L0 104.574z"
transform="translate(772.466 1122.142)"
fill="url(#b)"
/>
<path
d="M-8.492 10.642-26.361-.469v93.78l17.868 11.111z"
transform="translate(780.958 1122.293)"
fill="url(#c)"
/>
<path
d="M-8.5 5.55 28.106-5.3 10.228-16.437l-36.6 10.845z"
transform="translate(780.963 1127.438)"
fill="url(#d)"
/>
<path
d="M0 10.539 35.741 0v91.56L0 102.1z"
transform="translate(870.158 1123.617)"
fill="url(#d)"
/>
<path
d="M-8.913 10.38-26.361-.469v91.562l17.448 10.848z"
transform="translate(879.071 1123.775)"
fill="url(#e)"
/>
<path
d="m-8.918 5.032 35.741-10.59L9.366-16.437-26.375-5.848z"
transform="translate(879.076 1129.175)"
fill="url(#d)"
/>
<path
d="M0 9.137 30.839 0v79.381L0 88.519z"
transform="translate(799.678 1151.579)"
fill="url(#f)"
/>
<path
d="m-11.306 8.936-15.054-9.4v79.377l15.054 9.4z"
transform="translate(810.985 1151.78)"
fill="url(#c)"
/>
<path
d="M-11.313 2.087 19.526-7.05 4.464-16.437-26.375-7.3z"
transform="translate(810.991 1158.63)"
fill="url(#g)"
/>
<path
d="M178 53H0a51.361 51.361 0 0 1 10.453-20.952 74.532 74.532 0 0 1 19.742-16.811A103.3 103.3 0 0 1 57.089 4.058a127.515 127.515 0 0 1 63.823 0 103.3 103.3 0 0 1 26.894 11.179 74.532 74.532 0 0 1 19.741 16.811A51.363 51.363 0 0 1 178 53z"
transform="translate(744 1187.549)"
fill="url(#h)"
/>
<path d="m814.529 1199.586-1.272 1.212h2.3l1.2-1.212z" fill="#cbcbcb" />
<path
d="m816.725 1194.909-1.272 1.212h2.3l1.263-1.212z"
fill="#cbcbcb"
/>
<path d="m863.284 1199.585-1.272 1.212h2.3l1.2-1.212z" fill="#cbcbcb" />
<path d="m865.519 1194.9-1.272 1.212h2.3l1.263-1.212z" fill="#cbcbcb" />
<path
d="m799.527 1191.21 10.182-21.97h4.381l-9.931 21.719h14.876v3.941h-19.508zm13.081-9.493h4.152v17.859h-4.152zm20.728 18.151q-4.256 0-6.457-2.274a8.74 8.74 0 0 1-2.2-6.343v-13.791a8.708 8.708 0 0 1 2.21-6.353q2.212-2.264 6.447-2.264 4.256 0 6.457 2.253a8.726 8.726 0 0 1 2.2 6.363v13.792a8.708 8.708 0 0 1-2.21 6.349q-2.211 2.268-6.447 2.268zm0-4.048a4.29 4.29 0 0 0 3.328-1.178 4.862 4.862 0 0 0 1.074-3.39v-13.792a4.893 4.893 0 0 0-1.064-3.39 4.285 4.285 0 0 0-3.338-1.179 4.285 4.285 0 0 0-3.338 1.179 4.893 4.893 0 0 0-1.064 3.39v13.791a4.862 4.862 0 0 0 1.075 3.391 4.29 4.29 0 0 0 3.327 1.178zm14.928-4.61 10.181-21.97h4.381l-9.931 21.719h14.876v3.941h-19.507zm13.081-9.493h4.152v17.859h-4.152z"
fill="#c6c6c6"
/>
<path
d="m798.306 1192.431 10.182-21.97h4.381l-9.931 21.719h14.876v3.941h-19.508zm13.081-9.493h4.152v17.859h-4.152zm20.728 18.151q-4.256 0-6.457-2.274a8.74 8.74 0 0 1-2.2-6.343v-13.791a8.708 8.708 0 0 1 2.21-6.353q2.212-2.264 6.447-2.264 4.256 0 6.457 2.253a8.726 8.726 0 0 1 2.2 6.363v13.792a8.708 8.708 0 0 1-2.21 6.349q-2.211 2.268-6.447 2.268zm0-4.048a4.29 4.29 0 0 0 3.328-1.178 4.862 4.862 0 0 0 1.074-3.39v-13.792a4.893 4.893 0 0 0-1.064-3.39 4.285 4.285 0 0 0-3.338-1.179 4.285 4.285 0 0 0-3.338 1.179 4.893 4.893 0 0 0-1.064 3.39v13.791a4.862 4.862 0 0 0 1.075 3.391 4.29 4.29 0 0 0 3.327 1.178zm14.928-4.61 10.181-21.97h4.381l-9.931 21.719h14.876v3.941h-19.507zm13.081-9.493h4.152v17.859h-4.152z"
fill="#b2b2b2"
/>
<path
d="m-27.694-19.435 10.182 14.517h4.381l-9.931-14.352h14.876v-2.606h-19.508zm13.081 6.273h4.152v-11.8h-4.152zM6.115-25.156q-4.256 0-6.457 1.5a4.8 4.8 0 0 0-2.2 4.191v9.113a4.784 4.784 0 0 0 2.212 4.2 11.511 11.511 0 0 0 6.447 1.5q4.256 0 6.457-1.489a4.786 4.786 0 0 0 2.2-4.2v-9.113a4.784 4.784 0 0 0-2.212-4.2 11.511 11.511 0 0 0-6.447-1.502zm0 2.675a5.705 5.705 0 0 1 3.328.779 2.6 2.6 0 0 1 1.074 2.24v9.113a2.607 2.607 0 0 1-1.064 2.24 5.7 5.7 0 0 1-3.338.779 5.7 5.7 0 0 1-3.338-.779 2.607 2.607 0 0 1-1.064-2.24v-9.113A2.6 2.6 0 0 1 2.788-21.7a5.705 5.705 0 0 1 3.327-.782zm14.927 3.047L31.224-4.918h4.381l-9.931-14.351H40.55v-2.606H21.043zm13.081 6.273h4.152v-11.8h-4.151z"
transform="translate(826 1226.245)"
opacity=".32"
fill="url(#i)"
/>
<g fill="#e6e6e6">
<path d="m858.428 1169.23-1.2 1.259h4.388l1.178-1.259z" />
<path
d="m802.944 1192.187 1.288-1.375h7.143v1.375zm8.415-9.25 1.273-1.234h4.15l-1.235 1.234zm-2.855-12.469 1.198-1.259h4.367l-1.178 1.259z"
/>
<path d="m861.362 1181.678-1.27 1.3h4.188l1.236-1.3z" />
<path d="m865.519 1190.9-1.27 1.3h2.3l1.162-1.3z" />
<path d="m852.838 1190.791-1.207 1.508h8.447v-1.508z" />
</g>
</g>
</g>
</svg>
</template>
<style lang="stylus">
.page-404-icon
.theme-dark &
filter invert(70%)
</style>
<template>
<svg
class="icon prev-icon"
viewBox="0 0 1024 1024"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M906.783 588.79c-.02 8.499-6.882 15.36-15.38 15.37l-443.7-.01 75.704 191.682c2.52 6.42.482 13.763-5.038 17.91-5.52 4.168-13.138 4.147-18.616-.092L123.228 524.175a15.362 15.362 0 0 1-6-12.165c0-4.782 2.222-9.277 6-12.185L499.753 210.35a15.388 15.388 0 0 1 9.38-3.195c3.236 0 6.502 1.034 9.236 3.103 5.52 4.147 7.578 11.49 5.038 17.91L447.683 419.84l443.72-.01c8.498.01 15.36 6.881 15.36 15.36l.02 153.6z"
fill="currentColor"
/>
</svg>
</template>
"use strict";
const alias_1 = require("./node/alias");
const config_1 = require("./node/config");
const plugins_1 = require("./node/plugins");
// Theme API.
const themeAPI = (themeConfig, ctx) => ({
alias: (0, alias_1.getAlias)(themeConfig, ctx),
plugins: (0, plugins_1.getPluginConfig)(themeConfig),
additionalPages: [],
});
themeAPI.config = config_1.config;
// helper functions
themeAPI.themeConfig = (themeConfig) => themeConfig;
themeAPI.navbarConfig = (navbarConfig) => navbarConfig;
themeAPI.sidebarConfig = (sidebarConfig) => sidebarConfig;
module.exports = themeAPI;
//# sourceMappingURL=index.js.map
import Vue from "vue";
import Common from "@theme/components/Common.vue";
import Page404Icon from "@theme/icons/Page404Icon.vue";
import { getDefaultLocale } from "@mr-hope/vuepress-shared";
export default Vue.extend({
name: "NotFound",
components: {
Common,
Page404Icon,
},
computed: {
i18n() {
return this.$themeLocaleConfig.error404 || getDefaultLocale().error404;
},
msg() {
return this.i18n.hint[Math.floor(Math.random() * this.i18n.hint.length)];
},
},
methods: {
back() {
window.history.go(-1);
},
},
});
//# sourceMappingURL=404.js.map
\ No newline at end of file
<template>
<Common :sidebar="false">
<main class="page not-found">
<Page404Icon />
<blockquote v-text="msg" />
<button class="action-button" @click="back">{{ i18n.back }}</button>
<RouterLink class="action-button" to="/">{{ i18n.home }}</RouterLink>
</main>
</Common>
</template>
<script src="./404" />
<style lang="stylus">
.page.not-found
display block
max-width $homePageWidth
margin 0px auto
padding ($navbarHeight + 1rem) 2rem 2rem 2rem !important
text-align center
.page-404-icon
width 50%
margin 0 auto
.action-button
display inline-block
box-sizing border-box
margin 0 0.25rem
padding 0.5rem 1rem
border-width 0
border-bottom 1px solid var(--accent-color-d10)
border-radius 0.25rem
background var(--accent-color)
color var(--white)
font-size 1rem
outline none
transition background 0.1s ease
&:hover
cursor pointer
background var(--accent-color-l10)
</style>
import Vue from "vue";
import ContentBottom from "@ContentBottom";
import ContentTop from "@ContentTop";
import NavbarStart from "@NavbarStart";
import NavbarCenter from "@NavbarCenter";
import NavbarEnd from "@NavbarEnd";
import PageBottom from "@PageBottom";
import PageTop from "@PageTop";
import SidebarBottom from "@SidebarBottom";
import SidebarCenter from "@SidebarCenter";
import SidebarTop from "@SidebarTop";
import Common from "@theme/components/Common.vue";
import Home from "@theme/components/Home.vue";
import Page from "@theme/components/Page.vue";
export default Vue.extend({
name: "Layout",
components: {
Common,
ContentBottom,
ContentTop,
Home,
NavbarCenter,
NavbarEnd,
NavbarStart,
Page,
PageBottom,
PageTop,
SidebarBottom,
SidebarCenter,
SidebarTop,
},
});
//# sourceMappingURL=Layout.js.map
<template>
<Common :sidebar="true">
<template #navbar-start>
<slot name="navbar-start">
<NavbarStart />
<Content slot-key="navbar-start" />
</slot>
</template>
<template #navbar-center>
<slot name="navbar-center">
<NavbarCenter />
<Content slot-key="navbar-center" />
</slot>
</template>
<template #navbar-end>
<slot name="navbar-end">
<NavbarEnd />
<Content slot-key="navbar-end" />
</slot>
</template>
<template #sidebar-top>
<slot name="sidebar-top">
<SidebarTop />
<Content slot-key="sidebar-top" />
</slot>
</template>
<template #sidebar-center>
<slot name="sidebar-center">
<SidebarCenter />
<Content slot-key="sidebar-center" />
</slot>
</template>
<template #sidebar-bottom>
<slot name="sidebar-bottom">
<SidebarBottom />
<Content slot-key="sidebar-bottom" />
</slot>
</template>
<template #default="slotProps">
<Home v-if="$frontmatter.home" />
<Page
v-else
:headers="slotProps.headers"
:sidebar-items="slotProps.sidebarItems"
>
<template #top>
<slot name="page-top">
<PageTop />
<Content slot-key="page-top" />
</slot>
</template>
<template #content-top>
<slot name="content-top">
<ContentTop />
<Content slot-key="content-top" />
</slot>
</template>
<template #content-bottom>
<slot name="content-bottom">
<ContentBottom />
<Content slot-key="content-bottom" />
</slot>
</template>
<template #bottom>
<slot name="page-bottom">
<PageBottom />
<Content slot-key="page-bottom" />
</slot>
</template>
</Page>
</template>
</Common>
</template>
<script src="./Layout" />
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getAlias = void 0;
const path_1 = require("path");
const getAlias = (themeConfig, ctx) => {
const { siteConfig } = ctx;
// Resolve algolia
const isAlgoliaSearch = Boolean(themeConfig.algolia) ||
Object.keys((siteConfig.locales && themeConfig.locales) || {}).some((base) => themeConfig.locales[base].algolia);
const commentEnabled = themeConfig.comment &&
themeConfig.comment.type &&
themeConfig.comment.type !== "disable";
const themeColorEnabled = !(themeConfig.themeColor === false && themeConfig.darkmode === "disable");
const { custom = {} } = themeConfig;
const noopModule = "@mr-hope/vuepress-shared/lib/esm/noopModule";
return {
"@AlgoliaSearchBox": isAlgoliaSearch
? themeConfig.algoliaType === "full"
? (0, path_1.resolve)(__dirname, "../components/AlgoliaSearch/Full.vue")
: (0, path_1.resolve)(__dirname, "../components/AlgoliaSearch/Dropdown.vue")
: noopModule,
"@ContentTop": custom.contentTop
? (0, path_1.resolve)(ctx.sourceDir, ".vuepress", custom.contentTop)
: noopModule,
"@ContentBottom": custom.contentBottom
? (0, path_1.resolve)(ctx.sourceDir, ".vuepress", custom.contentBottom)
: noopModule,
"@PageTop": custom.pageTop
? (0, path_1.resolve)(ctx.sourceDir, ".vuepress", custom.pageTop)
: noopModule,
"@PageBottom": custom.pageBottom
? (0, path_1.resolve)(ctx.sourceDir, ".vuepress", custom.pageBottom)
: noopModule,
"@Comment": commentEnabled
? "@mr-hope/vuepress-plugin-comment/lib/client/Comment.vue"
: noopModule,
"@NavbarStart": custom.navbarStart
? (0, path_1.resolve)(ctx.sourceDir, ".vuepress", custom.navbarStart)
: noopModule,
"@NavbarCenter": custom.navbarCenter
? (0, path_1.resolve)(ctx.sourceDir, ".vuepress", custom.navbarCenter)
: noopModule,
"@NavbarEnd": custom.navbarEnd
? (0, path_1.resolve)(ctx.sourceDir, ".vuepress", custom.navbarEnd)
: noopModule,
"@ThemeColor": themeColorEnabled
? (0, path_1.resolve)(__dirname, "../components/Theme/ThemeColor.vue")
: noopModule,
"@SidebarTop": custom.sidebarTop
? (0, path_1.resolve)(ctx.sourceDir, ".vuepress", custom.sidebarTop)
: noopModule,
"@SidebarCenter": custom.sidebarCenter
? (0, path_1.resolve)(ctx.sourceDir, ".vuepress", custom.sidebarCenter)
: noopModule,
"@SidebarBottom": custom.sidebarBottom
? (0, path_1.resolve)(ctx.sourceDir, ".vuepress", custom.sidebarBottom)
: noopModule,
};
};
exports.getAlias = getAlias;
//# sourceMappingURL=alias.js.map
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.chunkRenamePlugin = void 0;
const chunkRenamePlugin = ({ pageChunkName = ({ title = "", key }) => {
const chunkTitle = (title || "").replace(/[.&*?#\\/:"<>| ]/gu, "");
return chunkTitle ? `page-${chunkTitle}` : `page-${key.slice(1)}`;
}, layoutChunkName = (layout) => `layout-${layout.componentName}`, }, context) => {
// override internal plugins
const plugins = [];
if (pageChunkName) {
plugins.push({
name: "@vuepress/internal-page-components",
extendPageData(page) {
page._chunkName = pageChunkName(page);
},
clientDynamicModules() {
const content = `export default {\n${context.pages
.filter(({ _filePath }) => _filePath)
.map((page) => {
const key = JSON.stringify(page.key);
const filePath = JSON.stringify(page._filePath);
const comment = page._chunkName
? `/* webpackChunkName: ${JSON.stringify(page._chunkName)} */`
: "";
return ` ${key}: () => import(${comment}${filePath})`;
})
.join(",\n")} \n}`;
return {
dirname: "internal",
name: "page-components.js",
content,
};
},
});
}
if (layoutChunkName) {
const { layoutComponentMap } = context.themeAPI;
for (const key in layoutComponentMap) {
const component = layoutComponentMap[key];
component._chunkName = layoutChunkName(component);
}
plugins.push({
name: "@vuepress/internal-layout-components",
clientDynamicModules() {
const { layoutComponentMap } = context.themeAPI;
const content = `export default {\n${Object.keys(layoutComponentMap)
.map((name) => {
const component = layoutComponentMap[name];
const key = JSON.stringify(name);
const filePath = JSON.stringify(component.path);
const comment = component._chunkName
? `/* webpackChunkName: ${JSON.stringify(component._chunkName)} */`
: "";
return ` ${key}: () => import(${comment}${filePath})`;
})
.join(",\n")} \n}`;
return {
dirname: "internal",
name: "layout-components.js",
content,
};
},
});
}
return {
name: "chunk-rename",
plugins,
};
};
exports.chunkRenamePlugin = chunkRenamePlugin;
//# sourceMappingURL=chunk-rename.js.map
\ No newline at end of file
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.cleanUrlPlugin = void 0;
const cleanUrlPlugin = ({ normalSuffix = "", indexSuffix = "/", notFoundPath = "/404.html", }) => ({
name: "clean-url",
extendPageData(page) {
const { regularPath, frontmatter = {} } = page;
if (!frontmatter.permalink) {
if (regularPath === "/404.html")
// path for 404 page
page.path = notFoundPath;
else if (regularPath.endsWith(".html"))
// normal path
// e.g. foo/bar.md -> foo/bar.html
page.path = `${regularPath.slice(0, -5)}${normalSuffix}`;
else if (regularPath.endsWith("/"))
// index path
// e.g. foo/index.md -> foo/
page.path = `${regularPath.slice(0, -1)}${indexSuffix}`;
}
},
});
exports.cleanUrlPlugin = cleanUrlPlugin;
//# sourceMappingURL=clean-url.js.map
\ No newline at end of file
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.config = void 0;
const vuepress_shared_1 = require("@mr-hope/vuepress-shared");
const locales_1 = require("./locales");
const themeConfig_1 = require("./themeConfig");
const defaultConfig = {
base: process.env.VuePress_BASE || "/",
temp: "./node_modules/.temp",
theme: "hope",
themeConfig: { locales: {} },
evergreen: true,
};
const getRootLang = (config) => {
var _a, _b;
// infer from siteLocale
const siteLocales = config.locales;
if ((siteLocales === null || siteLocales === void 0 ? void 0 : siteLocales["/"]) && (0, vuepress_shared_1.checkLang)((_a = siteLocales["/"]) === null || _a === void 0 ? void 0 : _a.lang))
return siteLocales["/"].lang;
// infer from themeLocale
const themeLocales = config.locales;
if ((themeLocales === null || themeLocales === void 0 ? void 0 : themeLocales["/"]) && (0, vuepress_shared_1.checkLang)((_b = themeLocales["/"]) === null || _b === void 0 ? void 0 : _b.lang))
return themeLocales["/"].lang;
(0, vuepress_shared_1.showLangError)("root");
return "en-US";
};
const config = (config) => {
// merge default config
(0, vuepress_shared_1.deepAssignReverse)(defaultConfig, config);
const resolvedConfig = config;
const rootLang = getRootLang(resolvedConfig);
(0, themeConfig_1.resolveThemeConfig)(resolvedConfig.themeConfig, rootLang);
(0, locales_1.resolveLocales)(resolvedConfig, rootLang);
return resolvedConfig;
};
exports.config = config;
//# sourceMappingURL=config.js.map
\ No newline at end of file
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.resolveLocales = void 0;
const vuepress_shared_1 = require("@mr-hope/vuepress-shared");
const resolveLocales = (config, rootLang) => {
// ensure locales config
if (!config.locales)
config.locales = {};
const { locales } = config;
// set locate for base
locales["/"] = Object.assign({ lang: rootLang }, (locales["/"] || {}));
// handle other languages
Object.keys(config.themeConfig.locales).forEach((path) => {
if (path === "/")
return;
locales[path] = Object.assign({ lang: (0, vuepress_shared_1.path2Lang)(path) }, (locales[path] || {}));
});
};
exports.resolveLocales = resolveLocales;
//# sourceMappingURL=locales.js.map
\ No newline at end of file
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getPluginConfig = void 0;
const path_1 = require("path");
const clean_url_1 = require("./clean-url");
const chunk_rename_1 = require("./chunk-rename");
const getPluginConfig = (themeConfig) => {
// set author for comment plugin
if (themeConfig.comment && themeConfig.author)
themeConfig.comment.author = themeConfig.author;
return [
["@mr-hope/comment", themeConfig.comment || true],
["@mr-hope/components"],
["@mr-hope/feed", themeConfig.feed],
["@mr-hope/git", themeConfig.git],
["@mr-hope/pwa", themeConfig.pwa],
["@mr-hope/seo", themeConfig.seo],
["@mr-hope/sitemap", themeConfig.sitemap],
[
"@mr-hope/smooth-scroll",
themeConfig.smoothScroll === false
? false
: typeof themeConfig.smoothScroll === "number"
? { delay: themeConfig.smoothScroll }
: themeConfig.smoothScroll || { delay: 500 },
],
["@vuepress/last-updated", false],
"@vuepress/nprogress",
[
"@vuepress/search",
{
searchMaxSuggestions: themeConfig.searchMaxSuggestions || 10,
},
],
["active-hash", themeConfig.activeHash],
["add-this", typeof themeConfig.addThis === "string"],
[
"copyright",
typeof themeConfig.copyright === "object"
? Object.assign({ minLength: 100, disable: themeConfig.copyright.status === "local", clipboardComponent: (0, path_1.resolve)(__dirname, "../components/Clipboard.vue") }, themeConfig.copyright) : false,
],
["md-enhance", themeConfig.mdEnhance || {}],
["@mr-hope/copy-code", themeConfig.copyCode],
["photo-swipe", themeConfig.photoSwipe],
[
"typescript",
themeConfig.typescript
? {
tsLoaderOptions: typeof themeConfig.typescript === "object"
? themeConfig.typescript
: {},
}
: false,
],
[
clean_url_1.cleanUrlPlugin,
themeConfig.cleanUrl === false
? false
: themeConfig.cleanUrl || { normalSuffix: "/" },
],
[
chunk_rename_1.chunkRenamePlugin,
themeConfig.chunkRename === false ? false : themeConfig.chunkRename,
],
];
};
exports.getPluginConfig = getPluginConfig;
//# sourceMappingURL=plugins.js.map
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.resolveThemeConfig = void 0;
const vuepress_shared_1 = require("@mr-hope/vuepress-shared");
const setThemeLocales = (themeConfig, rootLang) => {
const rootLangPath = (0, vuepress_shared_1.lang2Path)(rootLang);
// set locate for base
themeConfig.locales["/"] = Object.assign(Object.assign(Object.assign({}, (0, vuepress_shared_1.getLocale)(rootLang)), (themeConfig.locales[rootLangPath] || {})), (themeConfig.locales["/"] || {}));
// handle other languages
Object.keys(themeConfig.locales).forEach((path) => {
if (path === "/")
return;
const lang = (0, vuepress_shared_1.path2Lang)(path);
themeConfig.locales[path] = Object.assign(Object.assign({}, (0, vuepress_shared_1.getLocale)(lang)), themeConfig.locales[path]);
});
};
const resolveThemeConfig = (themeConfig, rootLang) => {
setThemeLocales(themeConfig, rootLang);
};
exports.resolveThemeConfig = resolveThemeConfig;
//# sourceMappingURL=themeConfig.js.map
{$contentClass}
code
color lighten($textColor, 20%)
padding 0.25rem 0.5rem
margin 0
font-size 0.85em
background-color rgba(27,31,35,0.05)
border-radius 3px
.token
&.deleted
color #EC5975
&.inserted
color $accentColor
{$contentClass}
pre, pre[class*="language-"]
line-height 1.4
padding 1.25rem 1.5rem
margin 0.85rem 0
background-color $codeBgColor
border-radius 6px
overflow auto
code
color #fff
padding 0
background-color transparent
border-radius 0
div[class*="language-"]
position relative
background-color $codeBgColor
border-radius 6px
.highlight-lines
user-select none
padding-top 1.3rem
position absolute
top 0
left 0
width 100%
line-height 1.4
.highlighted
background-color rgba(0, 0, 0, 66%)
pre, pre[class*="language-"]
background transparent
position relative
z-index 1
&::before
position absolute
z-index 3
top 0.8em
right 1em
font-size 0.75rem
color rgba(255, 255, 255, 0.4)
&:not(.line-numbers-mode)
.line-numbers-wrapper
display none
&.line-numbers-mode
.highlight-lines .highlighted
position relative
&:before
content ' '
position absolute
z-index 3
left 0
top 0
display block
width $lineNumbersWrapperWidth
height 100%
background-color rgba(0, 0, 0, 66%)
pre
padding-left $lineNumbersWrapperWidth + 1 rem
vertical-align middle
.line-numbers-wrapper
position absolute
top 0
width $lineNumbersWrapperWidth
text-align center
color rgba(255, 255, 255, 0.3)
padding 1.25rem 0
line-height 1.4
br
user-select none
.line-number
position relative
z-index 4
user-select none
font-size 0.85em
&::after
content ''
position absolute
z-index 2
top 0
left 0
width $lineNumbersWrapperWidth
height 100%
border-radius 6px 0 0 6px
border-right 1px solid rgba(0, 0, 0, 66%)
background-color $codeBgColor
for lang in $codeLang
div{'[class~="language-' + lang + '"]'}
&:before
content ('' + lang)
div[class~="language-javascript"]
&:before
content "js"
div[class~="language-typescript"]
&:before
content "ts"
div[class~="language-markup"]
&:before
content "html"
div[class~="language-markdown"]
&:before
content "md"
div[class~="language-json"]:before
content "json"
div[class~="language-ruby"]:before
content "rb"
div[class~="language-python"]:before
content "py"
div[class~="language-bash"]:before
content "sh"
div[class~="language-php"]:before
content "php"
@import '~prismjs/themes/prism-tomorrow.css'
@require './normalize'
@require './prefer-color-scheme-config'
@require './theme'
@require './plugins/index'
@require './theme-color'
@require '~balloon-css/balloon.min.css'
@require '~@mr-hope/vuepress-shared/styles/wrapper'
html, body
padding 0
margin 0
background var(--bgcolor)
body
font-family Georgia Pro, Georgia, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Fira Sans', 'Droid Sans', 'Helvetica Neue', STHeiti, 'Microsoft YaHei', SimSun, sans-serif
-webkit-font-smoothing antialiased
-moz-osx-font-smoothing grayscale
font-display optional
font-size 16px
color var(--text-color)
min-height 100vh
-webkit-tap-highlight-color transparent
{$contentClass}:not(.custom)
@extend $wrapper
> *:first-child
margin-top $navbarHeight
a:hover
text-decoration underline
// unknow container
p.demo
padding 1rem 1.5rem
border 1px solid #ddd
border-radius 4px
img
max-width 100%
{$contentClass}.custom
padding 0
margin 0
img
max-width 100%
a
font-weight 500
color var(--accent-color)
text-decoration none
overflow-wrap break-word
p a code
font-weight 400
color var(--accent-color)
kbd
background #eee
border solid 0.15rem #ddd
border-bottom solid 0.25rem #ddd
border-radius 0.15rem
padding 0 0.15em
blockquote
font-size 1rem
color var(--light-grey)
border-left 0.2rem solid #dfe2e5
margin 1rem 0
padding 0.25rem 0 0.25rem 1rem
& > p
margin 0
ul, ol
padding-left 1.2em
strong
font-weight 600
h1, h2, h3, h4, h5, h6
font-weight 500
line-height 1.25
{$contentClass}:not(.custom) > &
margin-top 0.5rem - $navbarHeight
padding-top: ($navbarHeight + 1rem)
margin-bottom 0.5rem
&:first-child
margin-top -3rem
margin-bottom 1rem
+ p, + pre, + .custom-block
margin-top 2rem
&:hover .header-anchor
opacity 1
p
{$contentClass}:not(.custom) > &, {$contentClass}:not(.custom) > ul &, {$contentClass}:not(.custom) > ol &
text-align justify
word-break break-word
hyphens auto
overflow-wrap break-word
@media (max-width $MQMobileNarrow)
text-align left
h1
font-size 2rem
h2
font-size 1.65rem
padding-bottom 0.3rem
border-bottom 1px solid var(--border-color)
h3
font-size 1.35rem
a.header-anchor
font-size 0.85em
float left
margin-left -0.87em
padding-right 0.23em
margin-top 0.125em
opacity 0
transition opacity 0.2s
&:hover
text-decoration none
code, kbd, .line-number
font-family source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace
p, ul, ol
line-height 1.7
hr
border 0
border-top 1px solid var(--border-color)
table
border-collapse collapse
margin 1rem 0
display block
overflow-x auto
tr
border-top 1px solid #dfe2e5
&:nth-child(2n)
background #f6f8fa
th, td
border 1px solid var(--grey14)
padding 0.6em 1em
.theme-dark
tr:nth-child(2n)
background #252322
th, td
border 1px solid var(--grey12)
/* basic color */
$accentColor ?= #3eaf7c
$textColor ?= #242424
$darkTextColor ?= #9e9e9e
$bgColor ?= #fff
$darkBgColor ?= #1e1e1e
$bgColorLight ?= #f8f8f8
$darkBgColorLight ?= #272727
$bgColorBlur ?= rgba(255, 255, 255, 0.9)
$darkBgColorBlur ?= rgba(30, 30, 30, 0.9)
$borderColor ?= #eaecef
$darkBorderColor ?= #302d28
$codeBgColor ?= #282c34
$darkCodeBgColor ?= #282c34
$arrowBgColor ?= #ccc
$darkArrowBgColor ?= #333
/* colors provided by theme */
$boxShadowColor ?= #f0f1f2
$darkBoxShadowColor ?= #0f0e0d
$cardShadowColor ?= rgba(0, 0, 0, 0.15)
$darkCardShadowColor ?= rgba(0, 0, 0, 0.3)
/* * theme-color */
$colorPicker ?= {
red: #e74c3c,
blue: #3498db,
green: #3eaf7c,
orange: #f39c12,
purple: #8e44ad
}
/* badge color */
$badgeTipColor ?= #42b983
$badgeWarningColor ?= darken(#ffe564, 35%)
$badgeErrorColor ?= #DA5961
/* layout */
$navbarHeight ?= 3.6rem
$navbarMobileHeight ?= 3.25rem
$navbarHorizontalPadding ?= 1.5rem
$navbarMobileHorizontalPadding ?= 1rem
$navbarVerticalPadding ?= 0.7rem
$navbarMobileVerticalPadding ?= 0.5rem
$sidebarWidth ?= 18rem
$mobileSidebarWidth ?= $sidebarWidth
$contentWidth ?= 820px
$homePageWidth ?= 960px
/* responsive breakpoints */
$MQWide ?= 1440px // wide screen
$MQMedium ?= 1350px // medium screen
$MQNormal ?= 1280px // desktop
$MQNarrow ?= 959px // narrow desktop / iPad
$MQMobile ?= 719px // wide mobile
$MQMobileNarrow ?= 419px // narrow mobile
/* code block */
$lineNumbersWrapperWidth ?= 2.5rem
$codeLang ?= js ts html md vue css sass scss less stylus go java c sh yaml py docker dockerfile makefile
/* content class */
$contentClass ?= '.theme-default-content'
@require './nprogress'
@require './search'
for $themeColorName, $themeColor in $colorPicker
.theme-{$themeColorName}
#nprogress
.bar
background $themeColor
.peg
box-shadow 0 0 10px $themeColor, 0 0 5px $themeColor
.spinner-icon
border-color $themeColor
.navbar
.search-box
height calc(2rem + 4px)
margin-left 0.25rem
input
margin-top 1px
margin-bottom 1px
border-color transparent
border-radius 0.25em
@media (max-width $MQMobile)
left 0
background-color transparent
@media (min-width $MQNarrow)
background-color #efeef4
&:focus
width 15rem
&:focus
background-color var(--bgcolor)
border-color var(--accent-color)
.theme-dark &
color var(--text-color)
background-color transparent
@media (min-width $MQNarrow)
background-color lighten($darkBgColor, 10%) !important
border-color var(--border-color)
&:focus
background-color lighten($darkBgColor, 10%) !important
.theme-dark &
.suggestion
a
color darken($darkTextColor, 35%)
&.focused
background #0c0b0a
a
color var(--accent-color)
border-color var(--accent-color)
.suggestions
border-color var(--border-color)
background var(--white)
#docsearch button
background-color transparent
border-color transparent
border-radius 0.25em
.mobile &
left 0
background-color transparent
@media (min-width $MQNarrow)
background-color #efeef4
&:focus
background-color var(--bgcolor)
border-color var(--accent-color)
.theme-dark &
color var(--text-color)
@media (min-width $MQNarrow)
background-color lighten($darkBgColor, 10%) !important
border-color var(--border-color)
&:focus
background-color lighten($darkBgColor, 10%) !important
body
--text-color $textColor
--bgcolor $bgColor
--bgcolor-light $bgColorLight
--border-color $borderColor
--code-bgcolor $codeBgColor
--arrow-bgcolor $arrowBgColor
--box-shadow-color $boxShadowColor
--card-shadow-color $cardShadowColor
--text-color-l10 lighten($textColor, 10%)
--text-color-l20 lighten($textColor, 20%)
--text-color-l25 lighten($textColor, 25%)
--text-color-l40 lighten($textColor, 40%)
--black #000
--dark-grey #666
--light-grey #999
--white #fff
--grey3 #333
--grey12 #bbb
--grey14 #eee
body.theme-light
--text-color $textColor
--bgcolor $bgColor
--bgcolor-light $bgColorLight
--bgcolor-blur $bgColorBlur
--border-color $borderColor
--code-bgcolor $codeBgColor
--arrow-bgcolor $arrowBgColor
--box-shadow-color $boxShadowColor
--card-shadow-color $cardShadowColor
--text-color-l10 lighten($textColor, 10%)
--text-color-l20 lighten($textColor, 20%)
--text-color-l25 lighten($textColor, 25%)
--text-color-l40 lighten($textColor, 40%)
--black #000
--dark-grey #666
--light-grey #999
--white #fff
--grey3 #333
--grey12 #bbb
--grey14 #eee
body.theme-dark
--text-color $darkTextColor
--bgcolor $darkBgColor
--bgcolor-light $darkBgColorLight
--bgcolor-blur $darkBgColorBlur
--border-color $darkBorderColor
--code-bgcolor $darkCodeBgColor
--arrow-bgcolor $darkArrowBgColor
--box-shadow-color $darkBoxShadowColor
--card-shadow-color $darkCardShadowColor
--text-color-l10 lighten($darkTextColor, 10%)
--text-color-l20 lighten($darkTextColor, 20%)
--text-color-l25 lighten($darkTextColor, 25%)
--text-color-l40 lighten($darkTextColor, 40%)
--black #fff
--dark-grey #999
--light-grey #666
--white #000
--grey3 #ccc
--grey12 #333
--grey14 #111
body
--accent-color $accentColor
--accent-color-l10 lighten($accentColor, 10%)
--accent-color-d10 darken($accentColor, 10%)
--accent-color-a15 alpha($accentColor, 15%)
theme-color($themeColor, $colorName)
body.theme-{$colorName}
--accent-color $themeColor
--accent-color-l10 lighten($themeColor, 10%)
--accent-color-d10 darken($themeColor, 10%)
--accent-color-a15 alpha($themeColor, 15%)
for key, value in $colorPicker
theme-color(value, key)
@require './code/index'
.theme-container:not(.has-navbar)
{$contentClass}:not(.custom) > h1, h2, h3, h4, h5, h6
margin-top 1.5rem
padding-top 0
// narrow mobile
@media (max-width $MQMobileNarrow)
h1
font-size 1.7rem
h2
font-size 1.5rem
h3
font-size 1.3rem
{$contentClass}
div[class*='language-']
margin 0.85rem -1.5rem
border-radius 0
.iconfont
font-weight normal
[aria-label][data-balloon-pos]
cursor help
import * as dayjs from "dayjs";
export const getDate = (date) => {
const time = dayjs(date instanceof Date || typeof date === "number" ? date : date.trim());
if (time.isValid()) {
const year = time.year();
const month = time.month() + 1;
const date = time.date();
const hour = time.hour();
const minute = time.minute();
const second = time.second();
const millisecond = time.millisecond();
if ((hour === 8 || hour === 0) &&
minute === 0 &&
second === 0 &&
millisecond === 0)
return [year, month, date, undefined, undefined, undefined];
return [year, month, date, hour, minute, second];
}
const pattern = /(?:(\d+)[/-](\d+)[/-](\d+))?\s*(?:(\d+):(\d+)(?::(\d+))?)?/u;
const [, year, month, day, hour, minute, second] = pattern.exec(date.trim()) || [];
const getNumber = (a) => typeof a === "undefined" ? undefined : Number(a);
const getYear = (yearNumber) => yearNumber && yearNumber < 100 ? yearNumber + 2000 : yearNumber;
const getSecond = (secondNumber) => hour && minute && !second ? 0 : secondNumber;
return [
getYear(getNumber(year)),
getNumber(month),
getNumber(day),
getNumber(hour),
getNumber(minute),
getSecond(getNumber(second)),
];
};
export const compareDate = (dataA, dataB) => {
if (!dataA)
return 1;
if (!dataB)
return -1;
const compare = (a, b) => {
if (a.length === 0)
return 0;
if (typeof b[0] === "undefined")
return typeof a[0] === "undefined" || a[0] === 0 ? 0 : -1;
if (typeof a[0] === "undefined")
return b[0] === 0 ? 0 : 1;
if (b[0] - a[0] === 0) {
a.shift();
b.shift();
return compare(a, b);
}
return b[0] - a[0];
};
return compare(getDate(dataA), getDate(dataB));
};
export const filterArticle = (pages, filterFunc) => pages.filter((page) => {
const { frontmatter: { article, blogpage, home }, title, } = page;
return (typeof title !== "undefined" &&
blogpage !== true &&
home !== true &&
article !== false &&
(!filterFunc || filterFunc(page)));
});
export const sortArticle = (pages, compareKey) => pages.slice(0).sort((prev, next) => {
if (compareKey) {
const prevKey = prev.frontmatter[compareKey];
const nextKey = next.frontmatter[compareKey];
if (prevKey && nextKey && prevKey !== nextKey)
return Number(nextKey) - Number(prevKey);
if (prevKey && !nextKey)
return -1;
if (!prevKey && nextKey)
return 1;
}
const prevTime = prev.frontmatter.time || prev.frontmatter.date || prev.createTimeStamp;
const nextTime = next.frontmatter.time || next.frontmatter.date || next.createTimeStamp;
return compareDate(prevTime, nextTime);
});
export const generatePagination = (pages, perPage = 10) => {
const result = [];
let index = 0;
while (index < pages.length) {
const paginationPage = [];
for (let i = 0; i < perPage; i++)
if (index < pages.length) {
paginationPage.push(pages[index]);
index += 1;
}
result.push(paginationPage);
}
return result;
};
//# sourceMappingURL=article.js.map
\ No newline at end of file
const validate = (binding) => {
if (typeof binding.value !== "function") {
console.warn("[Vue-click-outside:] provided expression", binding.expression, "is not a function.");
return false;
}
return true;
};
const isPopup = (popupItem, elements) => {
if (!popupItem || !elements)
return false;
for (let i = 0, len = elements.length; i < len; i++)
try {
if (popupItem.contains(elements[i]))
return true;
if (elements[i].contains(popupItem))
return false;
}
catch (err) {
return false;
}
return false;
};
const isServer = (vNode) => typeof vNode.componentInstance !== "undefined" &&
vNode.componentInstance.$isServer;
export const bind = (el, binding, vNode) => {
if (!validate(binding))
return;
// Define Handler and cache it on the element
const handler = (event) => {
if (!vNode.context)
return;
// Some components may have related popup item, on which we shall prevent the click outside event handler.
// eslint-disable-next-line
const elements =
// eslint-disable-next-line
event.path ||
(event.composedPath ? event.composedPath() : []);
if (elements && elements.length > 0)
elements.unshift(event.target);
if (el.contains(event.target) ||
// eslint-disable-next-line
isPopup(vNode.context.popupItem, elements))
return;
if (el.$vueClickOutside)
el.$vueClickOutside.callback(event);
};
// Add Event Listeners
el.$vueClickOutside = {
handler,
callback: binding.value,
};
const clickHandler = "ontouchstart" in document.documentElement ? "touchstart" : "click";
if (!isServer(vNode))
document.addEventListener(clickHandler, handler);
};
export const update = (el, binding) => {
if (validate(binding) && el.$vueClickOutside)
el.$vueClickOutside.callback = binding.value;
};
export const unbind = (el, _binding, vNode) => {
// Remove Event Listeners
const clickHandler = "ontouchstart" in document.documentElement ? "touchstart" : "click";
if (!isServer(vNode) && el.$vueClickOutside)
document.removeEventListener(clickHandler, el.$vueClickOutside.handler);
delete el.$vueClickOutside;
};
export default {
bind,
update,
unbind,
};
//# sourceMappingURL=click-outside.js.map
\ No newline at end of file
export default class Color {
constructor(type, red, green, blue, alpha = 1) {
this.type = type;
this.red = red;
this.green = green;
this.blue = blue;
this.alpha = alpha;
}
static fromHex(color) {
const parseHex = (colorString) => parseInt(colorString, 16);
const parseAlpha = (colorString, total) => Math.round((parseHex(colorString) * 100) / total) / 100;
if (color.length === 4)
return new Color("hex", parseHex(color[1]) * 17, parseHex(color[2]) * 17, parseHex(color[3]) * 17);
if (color.length === 5)
return new Color("hex", parseHex(color[1]) * 17, parseHex(color[2]) * 17, parseHex(color[3]) * 17, parseAlpha(color[4], 15));
if (color.length === 7)
return new Color("hex", parseHex(color.substring(1, 3)), parseHex(color.substring(3, 5)), parseHex(color.substring(5, 7)));
return new Color("hex", parseHex(color.substring(1, 3)), parseHex(color.substring(3, 5)), parseHex(color.substring(5, 7)), parseAlpha(color.substring(7, 9), 255));
}
// From RGB or RGBA
static fromRGB(color) {
// eslint-disable-next-line @typescript-eslint/naming-convention
const RGBAPattern = /rgba\((.+)?,(.+)?,(.+)?,(.+)?\)/u;
// eslint-disable-next-line @typescript-eslint/naming-convention
const RGBPattern = /rgb\((.+)?,(.+)?,(.+)?\)/u;
const fromRGB = (colorString) => colorString.includes("%")
? (Number(colorString.trim().substring(0, colorString.trim().length - 1)) /
100) *
256 -
1
: Number(colorString.trim());
const rgbaResult = RGBAPattern.exec(color);
if (rgbaResult)
return new Color("rgb", fromRGB(rgbaResult[1]), fromRGB(rgbaResult[2]), fromRGB(rgbaResult[3]), Number(rgbaResult[4] || 1));
const rgbResult = RGBPattern.exec(color);
if (rgbResult)
return new Color("rgb", fromRGB(rgbResult[1]), fromRGB(rgbResult[2]), fromRGB(rgbResult[3]));
throw new Error(`Can not handle color: ${color}`);
}
static getColor(colorString) {
if (colorString.startsWith("#"))
return this.fromHex(colorString);
return this.fromRGB(colorString);
}
toString() {
if (this.type === "hex" && this.alpha === 1) {
const toHex = (color) => color < 10
? color.toString()
: color === 10
? "a"
: color === 11
? "b"
: color === 12
? "c"
: color === 13
? "d"
: color === 14
? "e"
: "f";
if (this.red % 17 === 0 && this.green % 17 === 0 && this.blue % 17 === 0)
return `#${toHex(this.red / 17)}${toHex(this.green / 17)}${toHex(this.blue / 17)}`;
const getHex = (color) => toHex((color - (color % 16)) / 16) + toHex(color % 16);
return `#${getHex(this.red)}${getHex(this.green)}${getHex(this.blue)}`;
}
return this.alpha === 1
? `rgb(${this.red},${this.green},${this.blue})`
: `rgba(${this.red},${this.green},${this.blue},${this.alpha})`;
}
adjust(item, amount) {
const result = Math.round(this[item] * amount);
if (item === "alpha")
this.alpha = result < 0 ? 0 : result > 1 ? 1 : result;
else
this[item] = result < 0 ? 0 : result > 255 ? 255 : result;
}
darken(amount) {
this.adjust("red", 1 - amount);
this.adjust("green", 1 - amount);
this.adjust("blue", 1 - amount);
return this;
}
lighten(amount) {
this.adjust("red", 1 + amount);
this.adjust("green", 1 + amount);
this.adjust("blue", 1 + amount);
return this;
}
}
//# sourceMappingURL=color.js.map
\ No newline at end of file
/**
* Change DOM classes
*
* @param domClass DOM classlist
* @param insert class to insert
* @param remove class to remove
*/
export const changeClass = (domClass, insert, remove) => {
const oldClasses = [];
domClass.remove(...remove);
domClass.forEach((classname) => {
oldClasses.push(classname);
});
domClass.value = "";
domClass.add(...insert, ...oldClasses);
};
//# sourceMappingURL=dom.js.map
\ No newline at end of file
/** Group lower level headings under h2 children */
export const groupHeaders = (headers) => {
const headerscopy = headers.map((header) => (Object.assign({}, header)));
let lastH2;
// group other headings under h2 headings
headerscopy.forEach((header) => {
if (header.level === 2)
lastH2 = header;
else if (lastH2) {
if (!lastH2.children)
lastH2.children = [];
lastH2.children.push(header);
}
});
// filter only h2 headings
return headerscopy.filter((header) => header.level === 2);
};
//# sourceMappingURL=groupHeader.js.map
\ No newline at end of file
export const getNavLinkItem = (navbarLink, beforeprefix = "") => {
var _a;
const prefix = beforeprefix + (navbarLink.prefix || "");
const navbarItem = Object.assign({}, navbarLink);
if (prefix) {
if (navbarItem.link !== undefined)
navbarItem.link = prefix + navbarItem.link;
delete navbarItem.prefix;
}
if ((_a = navbarItem.items) === null || _a === void 0 ? void 0 : _a.length)
Object.assign(navbarItem, {
type: "links",
items: navbarItem.items.map((item) => getNavLinkItem(item, prefix)),
});
else
navbarItem.type = "link";
return navbarItem;
};
//# sourceMappingURL=navbar.js.map
\ No newline at end of file
/**
* @param url navigate link
* @param router router
* @param route current route
*/
export const navigate = (url, router, route) => {
if (url)
if (url.startsWith("/")) {
// Inner absolute path
if (route.path !== url)
void router.push(url);
}
else if (url.startsWith("http://") ||
url.startsWith("https://") ||
url.startsWith("mailto:")) {
// Outter url
if (window)
window.open(url);
}
else {
// Inner relative path
const base = route.path.slice(0, route.path.lastIndexOf("/"));
void router.push(`${base}/${encodeURI(url)}`);
}
};
//# sourceMappingURL=navigate.js.map
\ No newline at end of file
export const hashRE = /#.*$/u;
export const extRE = /\.(md|html)$/u;
export const endingSlashRE = /\/$/u;
export const outboundRE = /^[a-z]+:/iu;
/** Remove hash and ext in a link */
export const normalize = (path) => decodeURI(path).replace(hashRE, "").replace(extRE, "");
export const getHash = (path) => {
const match = hashRE.exec(path);
if (match)
return match[0];
return "";
};
/** Judge whether a path is external */
export const isExternal = (path) => outboundRE.test(path);
/** Judge whether a path is `mailto:` link */
export const isMailto = (path) => path.startsWith("mailto:");
/** Judge whether a path is `tel:` link */
export const isTel = (path) => path.startsWith("tel:");
export const ensureExt = (path) => {
// do not resolve external links
if (isExternal(path))
return path;
const hashMatch = hashRE.exec(path);
const hash = hashMatch ? hashMatch[0] : "";
const normalized = normalize(path);
// do not resolve links ending with `/`
if (normalized.endsWith("/"))
return path;
// add `.html` ext
return `${normalized}.html${hash}`;
};
export const ensureEndingSlash = (path) => /(\.html|\/)$/u.test(path) ? path : `${path}/`;
/** Judge whether a route match a link */
export const isActive = (route, path) => {
const routeHash = decodeURIComponent(route.hash);
const linkHash = getHash(path);
// compare the hash only if the link has a hash
if (linkHash && routeHash !== linkHash)
return false;
const routePath = normalize(route.path);
const pagePath = normalize(path);
return routePath === pagePath;
};
/**
* @param path links being resolved
* @param base deploy base
* @param append whether append directly
*/
export const resolvePath = (path, base, append) => {
// do not resolve external links
if (isExternal(path))
return path;
const firstChar = path.charAt(0);
// do not resolve absolute links
if (firstChar === "/")
return path;
// if link is hash or query string, add with base
if (firstChar === "?" || firstChar === "#")
return `${base}${path}`;
// base links stack
const stack = base.split("/");
/*
* remove trailing segment if:
* - not appending
* - appending to trailing slash (last segment is empty)
*/
if (!append || !stack[stack.length - 1])
stack.pop();
// resolve relative path
const segments = path.replace(/^\//u, "").split("/");
for (let i = 0; i < segments.length; i++) {
const segment = segments[i];
if (segment === "..")
stack.pop();
else if (segment !== ".")
stack.push(segment);
}
// ensure leading slash
if (stack[0] !== "")
stack.unshift("");
return stack.join("/");
};
//# sourceMappingURL=path.js.map
\ No newline at end of file
import { groupHeaders } from "./groupHeader";
import { ensureEndingSlash, ensureExt, isExternal, normalize, resolvePath, } from "./path";
export const groupSidebarHeaders = groupHeaders;
const resolveSidebarHeaders = (page) => {
const headers = page.headers ? groupSidebarHeaders(page.headers) : [];
return [
{
type: "group",
collapsable: false,
title: page.title,
icon: page.frontmatter.icon,
path: "",
children: headers.map((header) => (Object.assign(Object.assign({}, header), { type: "header", basePath: page.path, path: `${page.path}#${header.slug}`, children: header.children }))),
},
];
};
const findMatchingSidebarConfig = (regularPath, config) => {
// return directly as array-type config is the moest simple config
if (Array.isArray(config))
return {
base: "/",
config,
};
// find matching config
for (const base in config)
if (ensureEndingSlash(regularPath).startsWith(encodeURI(base)))
return {
base,
config: config[base],
};
console.warn(`${regularPath} do not have valid sidebar config`);
return false;
};
/** sidebarConfig merged with pageObject */
export const resolvePageforSidebar = (pages, path) => {
// if it is external link
if (isExternal(path))
return {
type: "external",
path,
};
const realPath = normalize(path);
// find matches in all pages
for (const page of pages)
if (normalize(page.regularPath) === realPath)
// return sidebarConfig merged with pageObject
return Object.assign(Object.assign({}, page), { type: "page", path: ensureExt(page.path) });
console.error(`Sidebar: "${realPath}" has no matching page`);
return { type: "error", path: realPath };
};
const resolve = (prefix, path, base) => resolvePath(`${prefix}${path}`, base);
/**
* @param sidebarConfigItem config item being resolved
* @param pages pages Object
* @param base sidebar base
*/
const resolveSidebarItem = (sidebarConfigItem, pages, base, prefix = "") => {
// resolve and return directly
if (typeof sidebarConfigItem === "string")
return resolvePageforSidebar(pages, resolve(prefix, sidebarConfigItem, base));
// custom title with format `['path', 'customTitle']`
if (Array.isArray(sidebarConfigItem))
return Object.assign(resolvePageforSidebar(pages, resolve(prefix, sidebarConfigItem[0], base)), { title: sidebarConfigItem[1] });
const children = sidebarConfigItem.children || [];
// item do not have children
if (children.length === 0 && sidebarConfigItem.path)
// cover title
return Object.assign(resolvePageforSidebar(pages, resolve(prefix, sidebarConfigItem.path, base)), { title: sidebarConfigItem.title });
// resolve children recursively then return
return Object.assign(Object.assign({}, sidebarConfigItem), { type: "group", path: sidebarConfigItem.path
? resolve(prefix, sidebarConfigItem.path, base)
: "", children: children.map((child) => resolveSidebarItem(child, pages, base, `${prefix}${sidebarConfigItem.prefix || ""}`)), collapsable: sidebarConfigItem.collapsable !== false });
};
export const getSidebarItems = (page, site, localePath) => {
const { themeConfig, pages } = site;
const localeConfig = localePath && themeConfig.locales
? themeConfig.locales[localePath] || themeConfig
: themeConfig;
const sidebarConfig = localeConfig.sidebar || themeConfig.sidebar;
// auto generate sidebar through headings
if (page.frontmatter.sidebar === "auto" || sidebarConfig === "auto")
return resolveSidebarHeaders(page);
// sidebar is disabled
if (!sidebarConfig)
return [];
const result = findMatchingSidebarConfig(page.regularPath, sidebarConfig);
return result
? result.config.map((item) => resolveSidebarItem(item, pages, result.base))
: [];
};
//# sourceMappingURL=sidebar.js.map
\ No newline at end of file
---
title: Welcome to the OP Stack
lang: en-US
---
**The OP Stack is the standardized, shared, and open-source development stack that powers Optimism, maintained by the Optimism Collective.**
::: tip Staying up to date
[Stay up to date on the Superchain and the OP Stack by subscribing to the Optimism newsletter](https://optimism.us6.list-manage.com/subscribe/post?u=9727fa8bec4011400e57cafcb&id=ca91042234&f_id=002a19e3f0).
:::
The OP Stack consists of the many different software components managed and maintained by the Optimism Collective that, together, form the backbone of Optimism.
The OP Stack is built as a public good for the Ethereum and Optimism ecosystems.
## The OP Stack powers Optimism
The OP Stack is the set of software that powers Optimism — currently in the form of the software behind Optimism Mainnet and eventually in the form of the Optimism Superchain and its governance.
With the advent of the Superchain concept, it has become increasingly important for Optimism to easily support the secure creation of new chains that can interoperate within the proposed Superchain ecosystem.
As a result, the OP Stack is primarily focused around the creation of a shared, high-quality, and fully open-source system for creating new L2 blockchains.
By coordinating on shared standards, the Optimism Collective can avoid rebuilding the same software in silos repeatedly.
Although the OP Stack today significantly simplifies the process of creating L2 blockchains, it’s important to note that this does not fundamentally define what the OP Stack **is**.
The OP Stack is *all* of the software that powers Optimism.
As Optimism evolves, so will the OP Stack.
**The OP Stack can be thought of as software components that either help define a specific layer of the Optimism ecosystem or fill a role as a module within an existing layer.**
Although the current heart of the OP Stack is infrastructure for running L2 blockchains, the OP Stack theoretically extends to layers on top of the underlying blockchain including tools like block explorers, message passing mechanisms, governance systems, and more.
Layers are generally more tightly defined towards the bottom of the stack (like the Data Availability Layer) but become more loosely defined towards the top of the stack (like the Governance Layer).
## The OP Stack today
Optimism Bedrock is the current iteration of the OP Stack.
The Bedrock release provides the tools for launching a production-quality Optimistic Rollup blockchain.
At this point in time, the APIs for the different layers of the OP Stack are still tightly coupled to this Rollup configuration of the stack.
If you'd like to learn more about the current state of the OP Stack, check out [the page describing the Bedrock release](/docs/releases/bedrock/README.md).
The OP Stack of today was built to support [the Optimism Superchain](./docs/understand/explainer.md), a proposed network of L2s that share security, communication layers, and a common development stack (the OP Stack itself).
The Bedrock release of the OP Stack makes it easy to spin up an L2 that will be compatible with the Superchain when it launches.
If you'd like to launch a Superchain-ready L2, check out our guide for running a chain based on the Bedrock release of the OP Stack.
It is possible to modify components of the OP Stack to build novel L2 systems.
If you're interested in experimenting with the OP Stack, check out [the OP Stack Hacks section of this site](/docs/build/hacks.md).
Please note that, as of the Bedrock release, the OP Stack is *not* designed to support these modifications and you will very much be *hacking* on the codebase.
As a result, **you should, for the moment, expect limited (if any) developer support for OP Stack Hacks.**
OP Stack Hacks will likely make your chain incompatible with the Optimism Superchain.
Have fun, but at your own risk and **stick to the Bedrock release if you're looking to join the Superchain!**
## The OP Stack tomorrow
The OP Stack is an evolving concept.
As Optimism grows, so will the OP Stack.
Today, the Bedrock Release of the OP Stack simplifies the process of deploying new L2 Rollups.
As work on the stack continues, it should become easier to plug in and configure different modules.
As the Superchain (link) begins to take shape, the OP Stack can evolve alongside it, to include the message-passing infrastructure that allows different chains to interoperate seamlessly.
At the end of the day, the OP Stack becomes what Optimism needs.
## Dive Deeper into the OP Stack
Ready to dive into the world of the OP Stack?
- If you’re interested in learning more about the current release of the OP Stack, check out the Bedrock Release page.
- If you’re interested in understanding the OP Stack in more depth, start with the [Design Principles](/docs/understand/design-principles.md) and [Landscape Overview](/docs/understand/landscape.md).
- If you're excited to join the Superchain, launch your first Superchain-ready L2 with our [Getting Started guide](/docs/build/getting-started.md) or dive directly into the OP Stack codebase to learn more.
The OP Stack is the next frontier for Ethereum. You’re already here, so what are you waiting for?
---
title: OP Stack Docs
lang: en-US
---
[Click here](..)
---
title: Building with the OP Stack
lang: en-US
---
The OP Stack is the decentralized development stack that powers Optimism. The current release of the OP Stack (named “Bedrock”) was designed to facilitate the creation of L2 blockchains that can interoperate with the proposed [Optimism Superchain](https://app.optimism.io/superchain/). The Superchain is a proposed network of rollups that share a security model, communication protocols, and a common development stack (the OP Stack itself). **The best way to get started building with the OP Stack today is to launch your own Bedrock-based Rollup by following our guide to [Running a Bedrock Rollup](./getting-started.md)**. Bedrock Rollups will be compatible with the Superchain.
You can also experiment with the OP Stack by making modifications to its various components. The OP Stack, as of the Bedrock release, is not explicitly designed to handle any significant modifications out of the box. By modifying the components of the OP Stack, you will likely no longer be compatible with the Superchain. Please also note that there is limited developer support available for modifications to the OP Stack. Have fun, but modify the stack at your own risk!
---
title: Configuration
lang: en-US
---
The OP Stack is a flexible platform with various configuration values that you can tweak to fit your specific needs. If you’re looking to fine-tune your deployment, look no further.
::: warning 🚧 Work in Progress
OP Stack configuration is an active work in progress and will likely evolve significantly as time goes on. If something isn’t working about your configuration, check back with this page to see if anything has changed.
:::
## New Blockchain Configuration
New OP Stack blockchains are currently configured with a JSON file inside the Optimism repository. The file is `<optimism repository>/packages/contracts-bedrock/deploy-config/<chain name>.json`. For example, [this is the configuration file for the tutorial blockchain](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/deploy-config/getting-started.json).
### Admin accounts
| Key | Type | Description | Default / Recommended value |
| --- | --- | --- | --- |
| `finalSystemOwner` | L1 Address | Address that will own all ownable contracts on L1 once the deployment is finished, including the `ProxyAdmin` contract. | It is recommended to have a single admin account to retain a common security model. |
| `controller` | L1 Address | Address that will own the `SystemDictator` contract and can therefore control the flow of the deployment or upgrade. | It is recommended to have a single admin account to retain a common security model. |
| `proxyAdminOwner` | L2 Address | Address that will own the `ProxyAdmin` contract on L2. The L2 `ProxyAdmin` contract owns all of the `Proxy` contracts for every predeployed contract in the range `0x42...0000` to `0x42..2048`. This makes predeployed contracts easily upgradeable. | It is recommended to have a single admin account to retain a common security model. |
### Fee recipients
| Key | Type | Description | Default value |
| --- | --- | --- | --- |
| `baseFeeVaultRecipient` | L1 Address | L1 address that the base fees from all transactions on the L2 can be withdrawn to. | It is recommended to have a single admin account to retain a common security model. |
| `l1FeeVaultRecipient` | L1 Address | L1 address that the L1 data fees from all transactions on the L2 can be withdrawn to. | It is recommended to have a single admin account to retain a common security model. |
| `sequencerFeeVaultRecipient` | L1 Address | L1 address that the tip fees from all transactions on the L2 can be withdrawn to. | It is recommended to have a single admin account to retain a common security model. |
### Misc.
| Key | Type | Description | Default value |
| --- | --- | --- | --- |
| `numDeployConfirmations` | Number of blocks | Number of confirmations to wait when deploying smart contracts to L1. | 1 |
| `l1StartingBlockTag` | Block hash | Block tag for the L1 block where the L2 chain will begin syncing from. Generally recommended to use a finalized block to avoid issues with reorgs. | |
| `l1ChainID` | Number | Chain ID of the L1 chain. | 1 for L1 Ethereum mainnet, <br> 5 for the Goerli test network. <br> [See here for other blockchains](https://chainlist.org/?testnets=true). |
| `l2ChainID` | Number | Chain ID of the L2 chain. | 42069 |
### Blocks
These fields apply to L2 blocks: Their timing, when do they need to be written to L1, and how they get written.
| Key | Type | Description | Default value |
| --- | --- | --- | --- |
| `l2BlockTime` | Number of seconds | Number of seconds between each L2 block. | 2 |
| `maxSequencerDrift` | Number of seconds | How far the L2 timestamp can differ from the actual L1 timestamp | 600 (10 minutes) |
| `sequencerWindowSize` | Number of blocks | Maximum number of L1 blocks that a Sequencer can wait to incorporate the information in a specific L1 block. For example, if the window is `10` then the information in L1 block `n` must be incorporated by L1 block `n+10`. | 3600 (12 hours) |
| `channelTimeout` | Number of blocks | Maximum number of L1 blocks that a transaction channel frame can be considered valid. A transaction channel frame is a chunk of a compressed batch of transactions. After the timeout, the frame is dropped. | 300 (1 hour) |
| `p2pSequencerAddress` | L1 Address | Address of the key that the Sequencer uses to sign blocks on the p2p network. | Sequencer, an address for which you own the private key |
| `batchInboxAddress` | L1 Address | Address that Sequencer transaction batches are sent to on L1. | 0xff00…0042069 |
| `batchSenderAddress` | L1 Address | Address of the account that nodes will filter for when searching for Sequencer transaction batches being sent to the `batchInboxAddress`. Can be updated later via the `SystemConfig` contract on L1. | Batcher, an address for which you own the private key |
### Proposal fields
These fields apply to output root proposals.
| Key | Type | Description | Default value |
| --- | --- | --- | --- |
| `l2OutputOracleStartingBlockNumber` | Number | Block number of the first OP Stack block. Typically this should be zero, but this may be non-zero for networks that have been upgraded from a legacy system (like Optimism Mainnet). Will be removed with the addition of permissionless proposals. | 0 |
| `l2OutputOracleStartingTimestamp` | Number | Timestamp of the first OP Stack block. This MUST be the timestamp corresponding to the block defined by the `l1StartingBlockTag`. Will be removed with the addition of permissionless proposals. | |
| `l2OutputOracleSubmissionInterval` | Number of blocks | Number of blocks between proposals to the `L2OutputOracle`. Will be removed with the addition of permissionless proposals. | 120 (24 minutes) |
| `finalizationPeriodSeconds` | Number of seconds | Number of seconds that a proposal must be available to challenge before it is considered finalized by the `OptimismPortal` contract. | We recommend 12 on test networks, seven days on production ones |
| `l2OutputOracleProposer` | L1 Address | Address that is allowed to submit output proposals to the `L2OutputOracle` contract. Will be removed when we have permissionless proposals. | |
| `l2OutputOracleChallenger` | L1 Address | Address that is allowed to challenge output proposals submitted to the `L2OutputOracle`. Will be removed when we have permissionless challenges. | It is recommended to have a single admin account to retain a common security model. |
### L1 data fee
These fields apply to the cost of the [L1 data fee](https://community.optimism.io/docs/developers/build/transaction-fees/#the-l1-data-fee) for L2 transactions.
| Key | Type | Description | Default value |
| --- | --- | --- | --- |
| `gasPriceOracleOverhead` | Number | Fixed L1 gas overhead per transaction. Default value will likely be adjusted with more information from the Optimism Goerli deployment. | 2100 |
| `gasPriceOracleScalar` | Number | Dynamic L1 gas overhead per transaction, given in 6 decimals. Default value of 1000000 implies a dynamic gas overhead of exactly 1x (no overhead). | 1000000 |
### EIP 1559 gas algorithm
These fields apply to [the EIP 1559 algorithm](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1559.md) used for the [L2 execution costs](https://community.optimism.io/docs/developers/build/transaction-fees/#the-l2-execution-fee) of transactions on the blockchain.
| Key | Type | Description | Default value | Value on L1 Ethereum |
| --- | --- | --- | --- | --- |
| `eip1559Denominator` | Number | Denominator used for the [EIP1559 gas pricing mechanism on L2](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1559.md). A larger denominator decreases the amount by which the base fee can change in a single block. | 50 | 8 |
| `eip1559Elasticity` | Number | Elasticity for the [EIP1559 gas pricing mechanism on L2](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1559.md). A larger elasticity increases the maximum allowable gas limit per block. | 10 | 2 |
| `l2GenesisBlockGasLimit` | String | Initial block gas limit, represented as a hex string. Default is 25m, implying a 2.5m target when combined with a 10x elasticity. | 0x17D7840 | |
| `l2GenesisBlockBaseFeePerGas` | String | Initial base fee, used to avoid an unstable EIP1559 calculation out of the gate. Initial value is 1 gwei. | 0x3b9aca00 | |
### Governance token
The governance token is a side-effect of use of the OP Stack in the Optimism Mainnet network. It may not be included by default in future releases.
| Key | Type | Description | Default value |
| --- | --- | --- | --- |
| `governanceTokenOwner` | L2 Address | Address that will own the token contract deployed by default to every OP Stack based chain. | |
| `governanceTokenSymbol` | String | Symbol for the token deployed by default to each OP Stack chain. | OP |
| `governanceTokenName` | String | Name for the token deployed by default to each OP Stack chain. | Optimism |
\ No newline at end of file
---
title: Data Availability Hacks
lang: en-US
---
::: warning 🚧 OP Stack Hacks are explicitly things that you can do with the OP Stack that are *not* currently intended for production use
OP Stack Hacks are not for the faint of heart. You will not be able to receive significant developer support for OP Stack Hacks — be prepared to get your hands dirty and to work without support.
:::
## Overview
The Data Availability Layer is responsible for the *ordering* and *storage* of the raw input data that forms the backbone of an OP Stack based chain (transactions, state roots, calls from other blockchains, etc.). You can conceptually think of this as an array of inputs — the ordering of this array should remain stable and the contents of this array should remain available. Unstable ordering of inputs will lead to reorgs of the OP Stack chain, while unavailable inputs will cause the OP Stack chain to halt entirely.
## Default
The default Data Availability Layer module for an OP Stack chain is the Ethereum DA module. When using the Ethereum DA module, all raw input data is expected to be found on Ethereum. Any data that is accessible on Ethereum can be queried when using this module, including calldata, events, and other block data.
## Security
OP Stack based chains are functions of the raw input data found on the Data Availability Layer module(s) used. If a required piece of data is not available, nodes will not be able to properly sync the chain. This also means that these nodes will not be able to dispute any invalid state proposals made to a Settlement Layer module. An OP Stack based chain cannot be safer than the Data Availability module.
You should be careful to understand the security properties of any Data Availability module(s) that you use. The standard Ethereum DA module generally provides the best security guarantees at the cost of higher transaction fees. Alternative DA modules may be appropriate depending on your particular use-case and risk tolerance.
## Modding
### Alternative EVM DA
A simple modification is to use an EVM-based blockchain other than Ethereum as the Data Availability Layer. Doing so simply requires using an L1 RPC other than Ethereum.
### EVM-Ordered Alternative DA
A more involved modification to the Data Availability Layer is an "EVM-Ordered" Alternative DA module. This involves using an EVM-based chain to maintain the *ordering* of transaction data while using a different data storage system to host the underlying data. Generally, ordering is maintained by publishing hashes of the data to the EVM-based chain while publishing the preimages to those hashes to the alternative data source.
An EVM-Ordered Alternative DA module significantly reduces costs by only publishing hashes and not full input data to the EVM chain. Using an EVM chain for ordering also reduces the number of changes that must be made to the standard Rollup configuration to achieve this result.
An example of an EVM-Ordered Alternative DA module can be found within [this modification to the OP Stack](https://github.com/celestiaorg/optimism/pull/3) that uses the Celestia blockchain as a third-party data availability provider.
### Non-EVM DA
A non-EVM DA module uses a chain not based on the EVM to manage both the ordering and storage of raw input data. Such a modification would require relatively significant modifications to the [derivation portion](https://github.com/ethereum-optimism/optimism/tree/develop/op-node/rollup/derive) of the `op-node`. No such fully-independent DA modules have been developed yet — be the first!
### Multiple DA
It is possible to use multiple Data Availability Layer modules at the same time. For instance, one could source data from two EVM-based chains simultaneously in order to form a bridge between the two chains. When using multiple Data Availability Layer modules, it is imperative to establish a global ordering between the two chains. One option for establishing this ordering is to use the timestamps of blocks from each chain.
Like a non-EVM DA module, a system with multiple Data Availability modules would need to make significant modifications to the [derivation portion](https://github.com/ethereum-optimism/optimism/tree/develop/op-node/rollup/derive) of the `op-node`. No such projects have been constructed yet.
\ No newline at end of file
---
title: Derivation Hacks
lang: en-US
---
::: warning 🚧 OP Stack Hacks are explicitly things that you can do with the OP Stack that are *not* currently intended for production use
OP Stack Hacks are not for the faint of heart. You will not be able to receive significant developer support for OP Stack Hacks — be prepared to get your hands dirty and to work without support.
:::
## Overview
The Derivation layer is responsible for parsing the raw inputs from the Data Availability layer and converting them into [Engine API](https://github.com/ethereum/execution-apis/tree/main/src/engine) payloads to be sent to the Execution layer. The Derivation Layer is generally tightly coupled to the Data Availability layer because it must understand both the APIs for the Data Availability layer module(s) of choice and the format of the raw data published to the chosen module(s).
## Default
The default Derivation layer module is the Rollup module. This module derives transactions from three sources: Sequencer transactions, user deposits, and L1 blocks. The Rollup module also enforces certain ordering properties that, for example, guarantee that user deposits are always included in the L2 chain within a certain configurable amount of time.
## Security
Modifying the Derivation layer can have unintended consequences. For example, removing or extending the time window in which user deposits must be included can allow a Sequencer to censor the L2 chain. Because of the flexibility of the Derivation layer, the exact impact of any change is likely to be unique to the specifics of the change. The negative impacts of any modifications should be carefully considered on a case-by-case basis.
## Modding
### EVM Event-Triggered Transactions
The default Rollup configuration of the OP Stack includes “deposited” transactions that are triggered whenever a specific event is emitted by the `OptimismPortal` contract on L1. Using the same principle, an OP Stack chain can derive transactions from events emitted by *any* contract on an EVM-based DA. Refer to [attributes.go](https://github.com/ethereum-optimism/optimism/blob/e468b66efedc5f47f4e04dc1acc803d4db2ce383/op-node/rollup/derive/attributes.go#L70) to understand how deposited transactions are derived and how custom transactions can be created.
### EVM Block-Triggered Transactions
Like with events, transactions on an OP Stack chain can be triggered whenever a new block is published on an EVM-based DA. The default Rollup configuration of the OP Stack already includes a block-triggered transaction in the form of [the “L1 info” transaction](https://github.com/ethereum-optimism/optimism/blob/e468b66efedc5f47f4e04dc1acc803d4db2ce383/op-node/rollup/derive/attributes.go#L103) that relays information like the latest block hash, timestamp, and base fee into L2. The Getting Started guide demonstrates the addition of a new block-triggered transaction in the form of a new transaction that reports the amount of gas burned via the base fee on L1.
### And much, much more…
The Derivation layer is one of the most flexible layers of the stack. Transactions can be generated from all sorts of raw input data and can be triggered from all sorts of conditions. You can derive transactions from any piece of data that can be found in the Data Availability layer modules!
[Tutorial: Adding attributes to the derivation function](./tutorials/add-attr.md).
\ No newline at end of file
---
title: Execution Hacks
lang: en-US
---
::: warning 🚧 OP Stack Hacks are explicitly things that you can do with the OP Stack that are *not* currently intended for production use
OP Stack Hacks are not for the faint of heart. You will not be able to receive significant developer support for OP Stack Hacks — be prepared to get your hands dirty and to work without support.
:::
## Overview
The Execution Layer is responsible for defining the format of state and the state transition function on L2. It is expected to trigger the state transition function when it receives a payload via the [Engine API](https://github.com/ethereum/execution-apis/tree/main/src/engine). Although the default Execution Layer module is the EVM, you can replace the EVM with any alternative VM as long as it sits behind the Engine API.
## Default
The default Execution Layer module is the Rollup EVM module. The Rollup EVM module utilizes a very lightly modified EVM that adds support for transactions that are triggered by smart contracts on L1 and introduces an L1 data fee to each transaction that accounts for the cost of publishing user transactions to L1. You can find the full set of differences between the standard EVM and the Rollup EVM [on this page](https://op-geth.optimism.io/).
## Security
As with modifications to the Derivation Layer, modifications to the Execution Layer can have unintended consequences. For instance, modifications to the EVM may break existing tooling or may open the door to denial of service attacks. Consider the impact of each modification carefully on a case-by-case basis.
## Modding
### EVM Tweaks
The default Execution Layer module is the EVM. It’s possible to modify the EVM in many different ways like adding new precompiles or inserting predeployed smart contracts into the genesis state. Precompiles can help make common smart contract operations cheaper and can therefore further reduce the cost of execution for your specific use-case. These modifications should be made directly to [the execution client](https://github.com/ethereum-optimism/op-geth).
It’s also possible to create alternative execution client implementations to improve the security properties of your chain. Note that if you modify the EVM, you must apply the same modifications to every execution client that you would like to support.
### Alternative VMs
The OP Stack allows you to replace the EVM with *any* state transition function, as long as the transition can be triggered via the Engine API. This has, for example, been used to implement an OP Stack chain that runs a GameBoy emulator rather than the EVM.
[Tutorial: Adding a precompile](./tutorials/new-precomp.md).
\ No newline at end of file
---
title: Explorer and Indexer
lang: en-US
---
The next step is to be able to see what is actually happening in your blockchain.
One easy way to do this is to use [Blockscout](https://www.blockscout.com/).
## Prerequisites
### Archive mode
Blockscout expects to interact with an Ethereum execution client in [archive mode](https://www.alchemy.com/overviews/archive-nodes#archive-nodes).
To create such a node, follow the [directions to add a node](./getting-started.md#adding-nodes), but in the command you use to start `op-geth` replace:
```sh
--gcmode=full \
```
with
```sh
--gcmode=archive \
```
### Docker
The easiest way to run Blockscout is to use Docker.
Download and install [Docker engine](https://docs.docker.com/engine/install/#server).
## Installation and configuration
1. Clone the Blockscout repository.
```sh
cd ~
git clone https://github.com/blockscout/blockscout.git
cd blockscout/docker-compose
```
1. Depending on the version of Docker you have, there may be an issue with the environment path.
Run this command to fix it:
```sh
ln -s `pwd`/envs ..
```
1. If `op-geth` in archive mode runs on a different computer or a port that isn't 8545, edit `docker-compose-no-build-geth.yml` to set `ETHEREUM_JSONRPC_HTTP_URL` to the correct URL.
1. Start Blockscout
```sh
docker compose -f docker-compose-no-build-geth.yml up
```
## Usage
After the docker containers start, browse to http:// < *computer running Blockscout* > :4000 to view the user interface.
You can also use the [API](https://docs.blockscout.com/for-users/api)
---
title: Featured Hacks
lang: en-US
---
::: warning 🚧 OP Stack Hacks are explicitly things that you can do with the OP Stack that are *not* currently intended for production use
OP Stack Hacks are not for the faint of heart. You will not be able to receive significant developer support for OP Stack Hacks — be prepared to get your hands dirty and to work without support.
:::
## Overview
Featured Hacks is a compilation of some of the cool stuff people are building on top of the OP Stack!
## OPCraft
### Author
[Lattice](https://lattice.xyz/)
### Description
OPCraft was an OP Stack chain that ran a modified EVM as the backend for a fully onchain 3D voxel game built with [MUD](https://mud.dev/).
### OP Stack Configuration
- Data Availability: Ethereum DA (Goerli)
- Sequencer: Single Sequencer
- Derivation: Standard Rollup
- Execution: Modified Rollup EVM
### Links
- [Announcing OPCraft: an Autonomous World built on the OP Stack](https://dev.optimism.io/opcraft-autonomous-world/)
- [OPCraft Explorer](https://opcraft.mud.dev/)
- [OPCraft on GitHub](https://github.com/latticexyz/opcraft)
- [MUD](https://mud.dev/)
## Ticking Optimism
### Author
[@therealbytes](https://twitter.com/therealbytes)
### Description
Ticking Optimism is a proof-of-concept implementation of an OP Stack chain that calls a `tick` function every block. By using the OP Stack, Ticking Optimism avoids the need for off-chain infrastructure to execute a function on a regular basis. Ticking Conway is a system that uses Ticking Optimism to build [Conway’s Game of Life](https://conwaylife.com/) onchain.
### OP Stack Configuration
- Data Availability: Ethereum DA (any)
- Sequencer: Single Sequencer
- Derivation: Standard Rollup with custom `tick` function
- Execution: Rollup EVM
### Links
- [Ticking Optimism on GitHub](https://github.com/therealbytes/ticking-optimism)
- [Ticking Conway on GitHub](https://github.com/therealbytes/ticking-conway)
\ No newline at end of file
---
title: Getting Started
lang: en-US
---
## Overview
Hello! This Getting Started guide is meant to help you kick off your OP Stack journey by taking you through the process of spinning up your very own OP Stack chain on the Ethereum Goerli testnet. You can use this chain to perform tests and prepare for the superchain, or you can modify it to adapt it to your own needs (which may make it incompatible with the superchain in the future).
## Know before you go
Before we kick off, note that this is a relatively long tutorial! You should prepare to set aside an hour or two to get everything running. Here’s an itemized list of what we’re about to do:
1. Install dependencies
2. Build the source code
3. Generate and fund accounts and private keys
4. Configure your network
5. Deploy the L1 contracts
6. Initialize op-geth
7. Run op-geth
8. Run op-node
9. Get some Goerli ETH on your L2
10. Send some test transactions
11. Celebrate!
## Prerequisites
You’ll need the following software installed to follow this tutorial:
- [Git](https://git-scm.com/)
- [Go](https://go.dev/)
- [Node](https://nodejs.org/en/)
- [Yarn](https://classic.yarnpkg.com/lang/en/docs/install/)
- [Foundry](https://github.com/foundry-rs/foundry#installation)
- [Make](https://linux.die.net/man/1/make)
This tutorial was checked on:
| Software | Version | Installation command(s) |
| -------- | ---------- | - |
| Ubuntu | 20.04 LTS | |
| git | OS default | |
| make | 4.2.1-1.2 | `sudo apt install -y make`
| Go | 1.20 | `sudo apt update` <br> `wget https://go.dev/dl/go1.20.linux-amd64.tar.gz` <br> `tar xvzf go1.20.linux-amd64.tar.gz` <br> `sudo cp go/bin/go /usr/bin/go` <br> `sudo mv go /usr/lib` <br> `echo export GOROOT=/usr/lib/go >> ~/.bashrc`
| Node | 16.19.0 | `curl -fsSL https://deb.nodesource.com/setup_16.x | sudo -E bash -` <br> `sudo apt-get install -y nodejs`
| yarn | 1.22.19 | `sudo npm install -g yarn`
| Foundry | 0.2.0 | `curl -L https://foundry.paradigm.xyz | bash` <br> `sudo bash` <br> `foundryup`
## Build the Source Code
We’re going to be spinning up an EVM Rollup from the OP Stack source code. You could use docker images, but this way we keep the option to modify component behavior if you need to do so. The OP Stack source code is split between two repositories, the [Optimism Monorepo](https://github.com/ethereum-optimism/optimism) and the [`op-geth`](https://github.com/ethereum-optimism/op-geth) repository.
### Build the Optimism Monorepo
1. Clone the [Optimism Monorepo](https://github.com/ethereum-optimism/optimism).
```bash
cd ~
git clone https://github.com/ethereum-optimism/optimism.git
```
1. Enter the Optimism Monorepo.
```bash
cd optimism
```
1. Install required modules. This is a slow process, while it is running you can already start building `op-geth`, as shown below.
```bash
yarn install
```
1. Build the various packages inside of the Optimism Monorepo.
```bash
make build
```
### Build op-geth
1. Clone [`op-geth`](https://github.com/ethereum-optimism/op-geth):
```bash
cd ~
git clone https://github.com/ethereum-optimism/op-geth.git
```
1. Enter `op-geth`:
```bash
cd op-geth
```
1. Build `op-geth`:
```bash
make geth
```
## Get access to a Goerli node
Since we’re deploying our OP Stack chain to Goerli, you’ll need to have access to a Goerli L1 node. You can either use a node provider like [Alchemy](https://www.alchemy.com/) (easier) or [run your own Goerli node](https://notes.ethereum.org/@launchpad/goerli) (harder).
## Generate some keys
You’ll need four accounts and their private keys when setting up the chain:
- The `Admin` account which has the ability to upgrade contracts.
- The `Batcher` account which publishes Sequencer transaction data to L1.
- The `Proposer` account which publishes L2 transaction results to L1.
- The `Sequencer` account which signs blocks on the p2p network.
You can generate all of these keys with the `rekey` tool in the `contracts-bedrock` package.
1. Enter the Optimism Monorepo:
```bash
cd optimism
```
1. Move into the `contracts-bedrock` package:
```bash
cd packages/contracts-bedrock
```
1. Run the `rekey` command:
```bash
npx hardhat rekey
```
You should get an output like the following:
```
Mnemonic: barely tongue excite actor edge huge lion employ gauge despair this learn
Admin: 0x301c314ca0eedf88a5f7a44680d9dccceb8fcbea
Private Key: ef06ba0291b6e2fa336fd9c06de9c2f18f72ed17cd4fcbda7b376f10592b43d8
Proposer: 0x54355b7d195fcdea96696a522c444c185afaf1a8
Private Key: 8bf67a8cd20087472db00fd869a0ffd7574a4481fb2a07a5f5c6bfb46dcb09ca
Batcher: 0x9a686086e3c74ddd5b59b710b26a73407d9c7e97
Private Key: 1533b607f668cce9553cafbfdfe9529eb31d67f1958d4b16fbdf857a8c50dd56
Sequencer: 0x0324a4c8c1955cb8364e8f07558238b3d2aa5f55
Private Key: fba31658f320bb8ce1ce39fab3c7c2acea6b4dd69cc8483fd85388a461d8426b
```
Save these accounts and their respective private keys somewhere, you’ll need them later. Fund the `Admin` address with a small amount of Goerli ETH as we’ll use that account to deploy our smart contracts. You’ll also need to fund the `Proposer` and `Batcher` address — note that the `Batcher` burns through the most ETH because it publishes transaction data to L1.
Recommended funding amounts are as follows:
- `Admin` — 0.2 ETH
- `Proposer` — 0.5 ETH
- `Batcher` — 1.0 ETH
::: danger Not for production deployments
The `rekey` tool is *not* designed for production deployments. If you are deploying an OP Stack based chain into production, you should likely be using a combination of hardware security modules and hardware wallets.
:::
## Configure your network
Once you’ve built both repositories, you’ll need head back to the Optimism Monorepo to set up the configuration for your chain. Currently, chain configuration lives inside of the [`contracts-bedrock`](https://github.com/ethereum-optimism/optimism/tree/develop/packages/contracts-bedrock) package.
1. Enter the Optimism Monorepo:
```bash
cd ~/optimism
```
1. Move into the `contracts-bedrock` package:
```bash
cd packages/contracts-bedrock
```
1. Before we can create our configuration file, we’ll need to pick an L1 block to serve as the starting point for our Rollup. It’s best to use a finalized L1 block as our starting block. You can use the `cast` command provided by Foundry to grab all of the necessary information (replace `<RPC>` with the URL for your L1 Goerli node):
```bash
cast block finalized --rpc-url <RPC> | grep -E "(timestamp|hash|number)"
```
You’ll get back something that looks like the following:
```
hash 0x784d8e7f0e90969e375c7d12dac7a3df6879450d41b4cb04d4f8f209ff0c4cd9
number 8482289
timestamp 1676253324
```
1. Fill out the remainder of the pre-populated config file found at [`deploy-config/getting-started.json`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/deploy-config/getting-started.json). Use the default values in the config file and make following modifications:
- Replace `"ADMIN"` with the address of the Admin account you generated earlier.
- Replace `"PROPOSER"` with the address of the Proposer account you generated earlier.
- Replace `"BATCHER"` with the address of the Batcher account you generated earlier.
- Replace `"SEQUENCER"` with the address of the Sequencer account you generated earlier.
- Replace `"BLOCKHASH"` with the blockhash you got from the `cast` command.
- Replace `"TIMESTAMP"` with the timestamp you got from the `cast` command. Note that although all the other fields are strings, this field is a number! Don’t include the quotation marks.
## Deploy the L1 contracts
Once you’ve configured your network, it’s time to deploy the L1 smart contracts necessary for the functionality of the chain.
1. Inside of `contracts-bedrock`, copy `.env.example` to `.env`.
```sh
cp .env.example .env
```
1. Fill out the two environment variables inside of that file:
- `L1_RPC` — URL for your L1 node.
- `PRIVATE_KEY_DEPLOYER` — Private key of the `Admin` account.
1. Once you’re ready, deploy the L1 smart contracts:
```bash
npx hardhat deploy --network getting-started
```
Contract deployment can take up to 15 minutes. Please wait for all smart contracts to be fully deployed before continuing to the next step.
## Generate the L2 config files
We’ve set up the L1 side of things, but now we need to set up the L2 side of things. We do this by generating three important files, a `genesis.json` file, a `rollup.json` configuration file, and a `jwt.txt` [JSON Web Token](https://jwt.io/introduction) that allows the `op-node` and `op-geth` to communicate securely.
1. Head over to the `op-node` package:
```bash
cd ~/optimism/op-node
```
1. Run the following command, and make sure to replace `<RPC>` with your L1 RPC URL:
```bash
go run cmd/main.go genesis l2 \
--deploy-config ../packages/contracts-bedrock/deploy-config/getting-started.json \
--deployment-dir ../packages/contracts-bedrock/deployments/getting-started/ \
--outfile.l2 genesis.json \
--outfile.rollup rollup.json \
--l1-rpc <RPC>
```
You should then see the `genesis.json` and `rollup.json` files inside the `op-node` package.
1. Next, generate the `jwt.txt` file with the following command:
```bash
openssl rand -hex 32 > jwt.txt
```
1. Finally, we’ll need to copy the `genesis.json` file and `jwt.txt` file into `op-geth` so we can use it to initialize and run `op-geth` in just a minute:
```bash
cp genesis.json ~/op-geth
cp jwt.txt ~/op-geth
```
## Initialize op-geth
We’re almost ready to run our chain! Now we just need to run a few commands to initialize `op-geth`. We’re going to be running a Sequencer node, so we’ll need to import the `Sequencer` private key that we generated earlier. This private key is what our Sequencer will use to sign new blocks.
1. Head over to the `op-geth` repository:
```bash
cd ~/op-geth
```
1. Create a data directory folder:
```bash
mkdir datadir
```
1. Put a password file into the data directory folder:
```bash
echo "pwd" > datadir/password
```
1. Put the `Sequencer` private key into the data directory folder (don’t include a “0x” prefix):
```bash
echo "<SEQUENCER KEY HERE>" > datadir/block-signer-key
```
1. Import the key into `op-geth`:
```bash
./build/bin/geth account import --datadir=datadir --password=datadir/password datadir/block-signer-key
```
1. Next we need to initialize `op-geth` with the genesis file we generated and copied earlier:
```bash
build/bin/geth init --datadir=datadir genesis.json
```
Everything is now initialized and ready to go!
## Run op-geth
Whew! We made it. It’s time to run `op-geth` and get our system started.
Run `op-geth` with the following command. Make sure to replace `<SEQUENCER>` with the address of the `Sequencer` account you generated earlier.
```bash
./build/bin/geth \
--datadir ./datadir \
--http \
--http.corsdomain="*" \
--http.vhosts="*" \
--http.addr=0.0.0.0 \
--http.api=web3,debug,eth,txpool,net,engine \
--ws \
--ws.addr=0.0.0.0 \
--ws.port=8546 \
--ws.origins="*" \
--ws.api=debug,eth,txpool,net,engine \
--syncmode=full \
--gcmode=full \
--nodiscover \
--maxpeers=0 \
--networkid=42069 \
--authrpc.vhosts="*" \
--authrpc.addr=0.0.0.0 \
--authrpc.port=8551 \
--authrpc.jwtsecret=./jwt.txt \
--rollup.disabletxpoolgossip=true \
--password=./datadir/password \
--allow-insecure-unlock \
--mine \
--miner.etherbase=<SEQUENCER> \
--unlock=<SEQUENCER>
```
And `op-geth` should be running! You should see some output, but you won’t see any blocks being created yet because `op-geth` is driven by the `op-node`. We’ll need to get that running next.
### Reinitializing op-geth
There are several situations are indicate database corruption and require you to reset the `op-geth` component:
- When `op-node` errors out when first started and exits.
- When `op-node` emits this error:
```
stage 0 failed resetting: temp: failed to find the L2 Heads to start from: failed to fetch L2 block by hash 0x0000000000000000000000000000000000000000000000000000000000000000
```
This is the reinitialization procedure:
1. Stop the `op-geth` process.
1. Delete the geth data.
```bash
cd ~/op-geth
rm -rf datadir/geth
```
1. Rerun init.
```bash
build/bin/geth init --datadir=datadir genesis.json
```
1. Start `op-geth`
1. Start `op-node`
## Run op-node
Once we’ve got `op-geth` running we’ll need to run `op-node`. Like Ethereum, the OP Stack has a consensus client (the `op-node`) and an execution client (`op-geth`). The consensus client drives the execution client over the Engine API.
Head over to the `op-node` package and start the `op-node` using the following command. Replace `<SEQUENCERKEY>` with the private key for the `Sequencer` account, replace `<RPC>` with the URL for your L1 node, and replace `<RPCKIND>` with the kind of RPC you’re connected to. Although the `l1.rpckind` argument is optional, setting it will help the `op-node` optimize requests and reduce the overall load on your endpoint. Available options for the `l1.rpckind` argument are `"alchemy"`, `"quicknode"`, `"quicknode"`, `"parity"`, `"nethermind"`, `"debug_geth"`, `"erigon"`, `"basic"`, and `"any"`.
```bash
./bin/op-node \
--l2=http://localhost:8551 \
--l2.jwt-secret=./jwt.txt \
--sequencer.enabled \
--sequencer.l1-confs=3 \
--verifier.l1-confs=3 \
--rollup.config=./rollup.json \
--rpc.addr=0.0.0.0 \
--rpc.port=8547 \
--p2p.listen.ip=0.0.0.0 \
--p2p.listen.tcp=9003 \
--p2p.listen.udp=9003 \
--rpc.enable-admin \
--p2p.sequencer.key=<SEQUENCERKEY> \
--l1=<RPC> \
--l1.rpckind=<RPCKIND>
```
Once you run this command, you should start seeing the `op-node` begin to process all of the L1 information after the starting block number that you picked earlier. Once the `op-node` has enough information, it’ll begin sending Engine API payloads to `op-geth`. At that point, you’ll start to see blocks being created inside of `op-geth`. We’re live!
## Run op-batcher
The final component necessary to put all the pieces together is the `op-batcher`. The `op-batcher` takes transactions from the Sequencer and publishes those transactions to L1. Once transactions are on L1, they’re officially part of the Rollup. Without the `op-batcher`, transactions sent to the Sequencer would never make it to L1 and wouldn’t become part of the canonical chain. The `op-batcher` is critical!
1. Head over to the `op-batcher` package inside the Optimism Monorepo:
```bash
cd ~/optimism/op-batcher
```
1. And run the `op-batcher` using the following command. Replace `<RPC>` with your L1 node URL and replace `<BATCHERKEY>` with the private key for the `Batcher` account that you created and funded earlier. It’s best to give the `Batcher` at least 1 Goerli ETH to ensure that it can continue operating without running out of ETH for gas.
```bash
./bin/op-batcher \
--l2-eth-rpc=http://localhost:8545 \
--rollup-rpc=http://localhost:8547 \
--poll-interval=1s \
--sub-safety-margin=6 \
--num-confirmations=1 \
--safe-abort-nonce-too-low-count=3 \
--resubmission-timeout=30s \
--rpc.addr=0.0.0.0 \
--rpc.port=8548 \
--target-l1-tx-size-bytes=2048 \
--l1-eth-rpc=<RPC> \
--private-key=<BATCHERKEY>
```
## Get some ETH on your Rollup
Once you’ve connected your wallet, you’ll probably notice that you don’t have any ETH on your Rollup. You’ll need some ETH to pay for gas on your Rollup. The easiest way to deposit Goerli ETH into your chain is to send funds directly to the `OptimismPortalProxy` contract. You can find the address of the `OptimismPortalProxy` contract for your chain by looking inside the `deployments` folder in the `contracts-bedrock` package.
1. First, head over to the `contracts-bedrock` package:
```bash
cd ~/optimism/packages/contracts-bedrock
```
1. Grab the address of the `OptimismPortalProxy` contract:
```bash
cat deployments/getting-started/OptimismPortalProxy.json | grep \"address\":
```
You should see a result like the following (**your address will be different**):
```
"address": "0x264B5fde6B37fb6f1C92AaC17BA144cf9e3DcFE9",
"address": "0x264B5fde6B37fb6f1C92AaC17BA144cf9e3DcFE9",
```
1. Grab the `OptimismPortalProxy` address and, using the wallet that you want to have ETH on your Rollup, send that address a small amount of ETH on Goerli (0.1 or less is fine). It may take up to 5 minutes for that ETH to appear in your wallet on L2.
## Use your Rollup
Congratulations, you made it! You now have a complete OP Stack based EVM Rollup.
To see your rollup in action, you can use the [Optimism Mainnet Getting Started tutorial](https://github.com/ethereum-optimism/optimism-tutorial/blob/main/getting-started). Follow these steps:
1. Clone the tutorials repository.
```bash
cd ~
git clone https://github.com/ethereum-optimism/optimism-tutorial.git
```
1. Change to the Foundry directory of the Getting Started tutorial.
```bash
cd optimism-tutorial/getting-started/foundry
```
1. Put your mnemonic (for the address where you have ETH, the one that sent ETH to `OptimismPortalProxy` on Goerli) in a file `mnem.delme`.
1. Provide the URL to your blockchain:
```bash
export ETH_RPC_URL=http://localhost:8545
```
1. Compile and deploy the `Greeter` contract:
```bash
forge create --mnemonic-path ./mnem.delme Greeter --constructor-args "hello" \
| tee deployment
```
1. Set the greeter to the deployed to address:
```bash
export GREETER=`cat deployment | awk '/Deployed to:/ {print $3}'`
echo $GREETER
```
1. See and modify the greeting
```bash
cast call $GREETER "greet()" | cast --to-ascii
cast send --mnemonic-path mnem.delme $GREETER "setGreeting(string)" "New greeting"
cast call $GREETER "greet()" | cast --to-ascii
```
To use any other development stack, see the getting started tutorial, just replace the Greeter address with the address of your rollup, and the Optimism Goerli URL with `http://localhost:8545`.
## Rollup operations
### Stopping your Rollup
To stop `op-geth` you should use Ctrl-C.
If `op-geth` aborts (for example, because the computer it is running on crashes), you will get these errors on `op-node`:
```
WARN [02-16|21:22:02.868] Derivation process temporary error attempts=14 err="stage 0 failed resetting: temp: failed to find the L2 Heads to start from: failed to fetch L2 block by hash 0x0000000000000000000000000000000000000000000000000000000000000000: failed to determine block-hash of hash 0x0000000000000000000000000000000000000000000000000000000000000000, could not get payload: not found"
```
In that case, you need to remove `datadir`, reinitialize it:
```bash
cd ~/op-geth
rm -rf datadir
mkdir datadir
echo "pwd" > datadir/password
echo "<SEQUENCER KEY HERE>" > datadir/block-signer-key
./build/bin/geth account import --datadir=./datadir --password=./datadir/password ./datadir/block-signer-key
./build/bin/geth init --datadir=./datadir ./genesis.json
```
### Starting your Rollup
To restart the blockchain, use the same order of components you did when you initialized it.
1. `op-geth`
2. `op-node`
3. `op-batcher`
## Adding nodes
To add nodes to the rollup, you need to initialize `op-node` and `op-geth`, similar to what you did for the first node:
1. Configure the OS and prerequisites as you did for the first node.
1. Build the Optimism monorepo and `op-geth` as you did for the first node.
1. Copy from the first node these files:
```bash
~/op-geth/genesis.json
~/optimism/op-node/rollup.json
```
1. Create a new `jwt.txt` file as a shared secret:
```bash
cd ~/op-geth
openssl rand -hex 32 > jwt.txt
cp jwt.txt ~/optimism/op-node
```
1. Initialize the new op-geth:
```bash
cd ~/op-geth
./build/bin/geth init --datadir=./datadir ./genesis.json
```
1. Start `op-geth` (using the same command line you used on the initial node)
1. Start `op-node` (using the same command line you used on the initial node)
1. Wait while the node synchronizes
## What’s next?
You can use this rollup the same way you’d use any other test blockchain. Once the superchain is available, this blockchain should be able to join the test version. Alternatively, you could [modify the blockchain in various ways](./hacks.md). **Please note that OP Stack Hacks are unofficial and are not explicitly supported by the OP Stack.** You will not be able to receive significant developer support for any modifications you make to the OP Stack.
\ No newline at end of file
---
title: Introduction to OP Stack Hacks
lang: en-US
---
Welcome to OP Stack Hacks, the **highly experimental** region of the OP Stack docs. OP Stack Hacks are an unofficial guide for messing around with the OP Stack. Here you’ll find information about ways that the OP Stack can be modified in interesting ways.
OP Stack Hacks create blockchains that aren’t exactly OP Stack, and may be insecure. Hacked OP Stack chains can break key invariants that are required to interoperate with [the Optimism Superchain](../understand/explainer.md). **Developers of chains that wish to interoperate with [the Optimism Superchain](../understand/explainer.md) should *not* include any hacks**. When in doubt, stick with the official components within [the current release of the OP Stack](../releases/README.md#current-release).
::: warning 🚧 OP Stack Hacks are explicitly things that you can do with the OP Stack that are *not* currently intended for production use
OP Stack Hacks are not for the faint of heart. You will not be able to receive significant developer support for OP Stack Hacks — be prepared to get your hands dirty and to work without support.
:::
---
title: Settlement Hacks
lang: en-US
---
::: warning 🚧 OP Stack Hacks are explicitly things that you can do with the OP Stack that are *not* currently intended for production use
OP Stack Hacks are not for the faint of heart. You will not be able to receive significant developer support for OP Stack Hacks — be prepared to get your hands dirty and to work without support.
:::
# Overview
The Settlement Layer includes modules that are used by third-party chains to establish a *view* of the state of your OP Stack chain. This view can then be used by applications on those chains to make decisions based on the state of your OP Stack chain. Third-party chains can be any other blockchain, including other OP Stack chains. One common Settlement Layer mechanism is a withdrawal system that allows users to send assets from your OP Stack chain to the third-party chain. Modifications to this layer typically involve introducing new modules or tweaking the security model of existing modules.
## Default
The default Settlement Layer module is currently the Attestation Proof Optimistic Settlement module. This module allows a third-party chain to become aware of the state of an OP Stack chain through an Optimistic protocol where challenges can be executed alongside a threshold of attestations from a pre-defined set of addresses over a state that differs from the proposed state. With a Cannon fault proof shipped to production, this default module can be replaced with a module that allows anyone to challenge proposals by playing the Cannon dispute game.
## Security
Modifications to the Settlement Layer can strongly impact the security of common mechanisms like user withdrawals. A decreased withdrawal delay can, for instance, open the door to gas spam attacks that make challenges exceedingly expensive. It is generally not recommended to modify the Settlement Layer unless you know what you’re doing.
## Modding
### Tweaked parameters
One simple modification to the Settlement Layer is to tweak the parameters of the default Optimistic asset withdrawal mechanism. For example, the withdrawal period can be reduced if a smaller withdrawal period would be sufficient to secure your system.
### Custom proofs
Settlement Layer modules use a proof system to verify the correctness of the state of your OP Stack chain as proposed on the third-party chain. In general, these proofs are either Optimistic proofs that require a withdrawal delay or Validity proofs that use a mathematical proof system to assert the validity of the proposal. The current Attestation Proof Optimistic Settlement module could be replaced with a fault proof system.
### Multiple modules
There is no requirement that a system only have one Settlement Layer module. It is possible to use one or more Settlement Layer modules on one or more third-party chains. A system that aims to bridge assets between two chains will likely need to use one Data Availability Layer module and one Settlement Layer module per chain.
\ No newline at end of file
---
title: Adding Attributes to the Derivation Function
lang: en-US
---
::: warning 🚧 OP Stack Hacks are explicitly things that you can do with the OP Stack that are *not* currently intended for production use
OP Stack Hacks are not for the faint of heart. You will not be able to receive significant developer support for OP Stack Hacks — be prepared to get your hands dirty and to work without support.
:::
## Overview
In this guide, we’ll modify the Bedrock Rollup. Although there are many ways to modify the OP Stack, we’re going to spend this tutorial modifying the Derivation function. Specifically, we’re going to update the Derivation function to track the amount of ETH being burned on L1! Who’s gonna tell [ultrasound.money](http://ultrasound.money) that they should replace their backend with an OP Stack chain?
## Getting the idea
Let’s quickly recap what we’re about to do. The `op-node` is responsible for generating the Engine API payloads that trigger `op-geth` to produce blocks and transactions. The `op-node` already generates a “system transaction” for every L1 block that relays information about the current L1 state to the L2 chain. We’re going to modify the `op-node` to add a new system transaction that reports the total burn amount (the base fee multiplied by the gas used) in each block.
Although it might sound like a lot, the whole process only involves deploying a single smart contract, adding one new file to `op-node`, and modifying one existing file inside `op-node`. It’ll be painless. Let’s go!
## Deploy the burn contract
We’re going to use a smart contract on our Rollup to store the reports that the `op-node` makes about the L1 burn. Here’s the code for our smart contract:
```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/**
* @title L1Burn
* @notice L1Burn keeps track of the total amount of ETH burned on L1.
*/
contract L1Burn {
/**
* @notice Total amount of ETH burned on L1.
*/
uint256 public total;
/**
* @notice Mapping of blocks numbers to total burn.
*/
mapping (uint64 => uint256) public reports;
/**
* @notice Allows the system address to submit a report.
*
* @param _blocknum L1 block number the report corresponds to.
* @param _burn Amount of ETH burned in the block.
*/
function report(uint64 _blocknum, uint64 _burn) external {
require(
msg.sender == 0xDeaDDEaDDeAdDeAdDEAdDEaddeAddEAdDEAd0001,
"L1Burn: reports can only be made from system address"
);
total += _burn;
reports[_blocknum] = total;
}
/**
* @notice Tallies up the total burn since a given block number.
*
* @param _blocknum L1 block number to tally from.
*
* @return Total amount of ETH burned since the given block number;
*/
function tally(uint64 _blocknum) external view returns (uint256) {
return total - reports[_blocknum];
}
}
```
Deploy this smart contract to your L2 (using any tool you find convenient). Make a note of the address that the contract is deployed to because you’ll need it in a minute. Simple!
## Add the burn transaction
Now we need to add logic to the `op-node` to automatically submit a burn report whenever an L1 block is produced. Since this transaction is very similar to the system transaction that reports other L1 block info (found in [l1_block_info.go](https://github.com/ethereum-optimism/optimism/blob/c9cd1215b76111888e25ee27a49a0bc0c4eeb0f8/op-node/rollup/derive/l1_block_info.go)), we’ll use that transaction as a jumping-off point.
1. Navigate to the `op-node` package:
```bash
cd ~/optimism/op-node
```
1. Inside of the folder `rollup/derive`, create a new file called `l1_burn_info.go`:
```bash
touch rollup/derive/l1_burn_info.go
```
1. Paste the following into `l1_burn_info.go`, and make sure to replace `YOUR_BURN_CONTRACT_HERE` with the address of the `L1Burn` contract you just deployed.
```go
package derive
import (
"bytes"
"encoding/binary"
"fmt"
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum-optimism/optimism/op-node/eth"
)
const (
L1BurnFuncSignature = "report(uint64,uint64)"
L1BurnArguments = 2
L1BurnLen = 4 + 32*L1BurnArguments
)
var (
L1BurnFuncBytes4 = crypto.Keccak256([]byte(L1BurnFuncSignature))[:4]
L1BurnAddress = common.HexToAddress("YOUR_BURN_CONTRACT_HERE")
)
type L1BurnInfo struct {
Number uint64
Burn uint64
}
func (info *L1BurnInfo) MarshalBinary() ([]byte, error) {
data := make([]byte, L1BurnLen)
offset := 0
copy(data[offset:4], L1BurnFuncBytes4)
offset += 4
binary.BigEndian.PutUint64(data[offset+24:offset+32], info.Number)
offset += 32
binary.BigEndian.PutUint64(data[offset+24:offset+32], info.Burn)
return data, nil
}
func (info *L1BurnInfo) UnmarshalBinary(data []byte) error {
if len(data) != L1InfoLen {
return fmt.Errorf("data is unexpected length: %d", len(data))
}
var padding [24]byte
offset := 4
info.Number = binary.BigEndian.Uint64(data[offset+24 : offset+32])
if !bytes.Equal(data[offset:offset+24], padding[:]) {
return fmt.Errorf("l1 burn tx number exceeds uint64 bounds: %x", data[offset:offset+32])
}
offset += 32
info.Burn = binary.BigEndian.Uint64(data[offset+24 : offset+32])
if !bytes.Equal(data[offset:offset+24], padding[:]) {
return fmt.Errorf("l1 burn tx burn exceeds uint64 bounds: %x", data[offset:offset+32])
}
return nil
}
func L1BurnDepositTxData(data []byte) (L1BurnInfo, error) {
var info L1BurnInfo
err := info.UnmarshalBinary(data)
return info, err
}
func L1BurnDeposit(seqNumber uint64, block eth.BlockInfo, sysCfg eth.SystemConfig) (*types.DepositTx, error) {
infoDat := L1BurnInfo{
Number: block.NumberU64(),
Burn: block.BaseFee().Uint64() * block.GasUsed(),
}
data, err := infoDat.MarshalBinary()
if err != nil {
return nil, err
}
source := L1InfoDepositSource{
L1BlockHash: block.Hash(),
SeqNumber: seqNumber,
}
return &types.DepositTx{
SourceHash: source.SourceHash(),
From: L1InfoDepositerAddress,
To: &L1BurnAddress,
Mint: nil,
Value: big.NewInt(0),
Gas: 150_000_000,
IsSystemTransaction: true,
Data: data,
}, nil
}
func L1BurnDepositBytes(seqNumber uint64, l1Info eth.BlockInfo, sysCfg eth.SystemConfig) ([]byte, error) {
dep, err := L1BurnDeposit(seqNumber, l1Info, sysCfg)
if err != nil {
return nil, fmt.Errorf("failed to create L1 burn tx: %w", err)
}
l1Tx := types.NewTx(dep)
opaqueL1Tx, err := l1Tx.MarshalBinary()
if err != nil {
return nil, fmt.Errorf("failed to encode L1 burn tx: %w", err)
}
return opaqueL1Tx, nil
}
```
Feel free to take a look at this file if you’re interested. It’s relatively simple, mainly just defining a new transaction type and describing how the transaction should be encoded.
## Insert the burn transactions
Finally, we’ll need to update `~/optimism/op-node/rollup/derive/attributes.go` to insert the new burn transaction into every block. You’ll need to make the following changes:
1. Find these lines:
```go
l1InfoTx, err := L1InfoDepositBytes(seqNumber, l1Info, sysConfig)
if err != nil {
return nil, NewCriticalError(fmt.Errorf("failed to create l1InfoTx: %w", err))
}
```
1. After those lines, add this code fragment:
```go
l1BurnTx, err := L1BurnDepositBytes(seqNumber, l1Info, sysConfig)
if err != nil {
return nil, NewCriticalError(fmt.Errorf("failed to create l1InfoTx: %w", err))
}
```
1. Immediately following, change these lines:
```go
txs := make([]hexutil.Bytes, 0, 1+len(depositTxs))
txs = append(txs, l1InfoTx)
```
to
```go
txs := make([]hexutil.Bytes, 0, 2+len(depositTxs))
txs = append(txs, l1InfoTx)
txs = append(txs, l1BurnTx)
```
All we’re doing here is creating a new burn transaction after every `l1InfoTx` and inserting it into every block.
## Rebuild your op-node
Before we can see this change take effect, you’ll need to rebuild your `op-node`:
```bash
cd ~/optimism/op-node
make op-node
```
Now start your `op-node` if it isn’t running or restart your `op-node` if it’s already running. You should see the change immediately — new blocks will contain two system transactions instead of just one!
## Checking the result
Query the `total` function of your contract, you should also start to see the total slowly increasing. Play around with the `tally` function to grab the amount of gas burned since a given L2 block. You could use this to implement a version of [ultrasound.money](http://ultrasound.money) that keeps track of things with an OP Stack as a backend. We did it reddit!
One way to get the total is to run these commands:
```bash
export ETH_RPC_URL=http://localhost:8545
cast call <YOUR_BURN_CONTRACT_HERE> "total()" | cast --from-wei
```
## Conclusion
With just a few tiny changes to the `op-node`, you were just able to implement a change to the OP Stack that allows you to keep track of the L1 ETH burn on L2. With a live Cannon fault proof system, you should not only be able to track the L1 burn on L2, you should be able to *prove* the burn to contracts back on L1. You could build a trustless prediction market on the amount of ETH burned. That’s crazy!
The OP Stack is an extremely powerful platform that allows you to perform a large amount of computation trustlessly. It’s a superpower for smart contracts. Tracking the L1 burn is just one of the many, many wild things you can do with the OP Stack. If you’re looking for inspiration or you want to see what others are building on the OP Stack, check out our OP Stack Hacks page. Maybe you’ll find a project you want to work on, or maybe you’ll get the inspiration you need to build the next killer smart contract.
\ No newline at end of file
---
title: Adding a Precompile
lang: en-US
---
::: warning 🚧 OP Stack Hacks are explicitly things that you can do with the OP Stack that are *not* currently intended for production use
OP Stack Hacks are not for the faint of heart. You will not be able to receive significant developer support for OP Stack Hacks — be prepared to get your hands dirty and to work without support.
:::
One possible use of OP Stack is to run an EVM with a new precompile for operations to speed up calculations that are not currently supported. In this case we’ll make a simple precompile that returns a constant value if it’s called with four or less bytes, or an error if it is called with more than that.
To create a new precompile, the file to modify is [`op-geth/core/vm/contracts.go`](https://github.com/ethereum-optimism/op-geth/blob/optimism-history/core/vm/contracts.go).
1. Add to `PrecompiledContractsBerlin` on line 82 (or a later fork, if the list of precompiles changes again) a structure named after your new precompile, with an address that is unlikely to ever clash with a standard precompile (0x100, for example):
```go
common.BytesToAddress([]byte{1,0}): &retConstant{},
```
1. Add the lines for the precompile.
```go
type retConstant struct{}
func (c *retConstant) RequiredGas(input []byte) uint64 {
return uint64(1024)
}
var (
errConstInvalidInputLength = errors.New("invalid input length")
)
func (c *retConstant) Run(input []byte) ([]byte, error) {
// Only allow input up to four bytes (function signature)
if len(input) > 4 {
return nil, errConstInvalidInputLength
}
output := make([]byte, 6)
for i := 0; i < 6; i++ {
output[i] = byte(64+i)
}
return output, nil
}
```
1. Stop `op-geth` and recompile:
```bash
cd ~/op-geth
make geth
```
1. Restart `op-geth`.
1. Run these command to see the result of calling the precompile successfully, and the result of an error:
```bash
cast call 0x0000000000000000000000000000000000000100 "whatever()"
cast call 0x0000000000000000000000000000000000000100 "whatever(string)" "fail"
```
## How does it work?
This is the precompile interface definition:
```go
type PrecompiledContract interface {
RequiredGas(input []byte) uint64 // RequiredPrice calculates the contract gas use
Run(input []byte) ([]byte, error) // Run runs the precompiled contract
}
```
It means that for every precompile we need two functions:
- `RequiredGas` which returns the gas cost for the call. This function takes an array of bytes as input, and returns a single value, the gas cost.
- `Run` which runs the actual precompile. This function also takes an array of bytes, but it returns two values: the call’s output (a byte array) and an error.
For every fork that changes the precompiles we have a [`map`](https://www.w3schools.com/go/go_maps.php)from addresses to the `PrecompiledContract` definitions:
```go
// PrecompiledContractsBerlin contains the default set of pre-compiled Ethereum
// contracts used in the Berlin release.
var PrecompiledContractsBerlin = map[common.Address]PrecompiledContract{
common.BytesToAddress([]byte{1}): &ecrecover{},
.
.
.
common.BytesToAddress([]byte{9}): &blake2F{},
common.BytesToAddress([]byte{1,0}): &retConstant{},
}
```
The key of the map is an address. We create those from bytes using `common.BytesToAddress([]byte{<bytes to convert to address go here>})`. In this case we have two bytes, `0x01` and `0x00`. Together we get the address `0x0…0100`.
The syntax for a precompiled contract interface is `&<variable name>{}`.
The next step is to define the precompiled contract itself.
```go
type retConstant struct{}
```
First we create a structure for the precompile.
```go
func (c *retConstant) RequiredGas(input []byte) uint64 {
return uint64(1024)
}
```
Then we define a function as part of that structure. Here we just require a constant amount of gas, but of course the calculation can be a lot more sophisticated.
```go
var (
errConstInvalidInputLength = errors.New("invalid input length")
)
```
Next we define a variable for the error.
```go
func (c *retConstant) Run(input []byte) ([]byte, error) {
```
This is the function that actually executes the precompile.
```go
// Only allow input up to four bytes (function signature)
if len(input) > 4 {
return nil, errConstInvalidInputLength
}
```
Return an error if warranted. The reason this precompile allows up to four bytes of input is that any standard call (for example, using `cast`) is going to have at least four bytes for the function signature.
`return a, b` is the way we return two values from a function in Go. The normal output is `nil`, nothing, because we return an error.
```go
output := make([]byte, 6)
for i := 0; i < 6; i++ {
output[i] = byte(64+i)
}
return output, nil
}
```
Finally, we create the output buffer, fill it, and then return it.
## Conclusion
An OP Stack chain with additional precompiles can be useful, for example, to further reduce the computational effort required for cryptographic operations by moving them from interpreted EVM code to compiled Go code.
\ No newline at end of file
---
title: Contribute to OP Stack
lang: en-US
---
The OP Stack is a collaborative, decentralized development stack that only gets more powerful as more people contribute. Code for the OP Stack should follow the stack’s [design principles](./understand/design-principles.md), which means it should be entirely open source and accessible for people to hack on, contribute to, and extend. The Optimism Collective wins when it works together. ♥️✨
Whether you’re a budding protocol developer, dapp developer, bounty hunter, documentation editor, content creator, or anything in between, the OP Stack always has something for you to contribute to. Every contribution makes a difference — no contribution is too small.
If you’re looking to find a way to contribute, check out one of the following contributor pathways below. Come make your first contribution today!
## Component contributions
The OP Stack is a decentralized development stack. Anyone can contribute components that can be considered part of the OP Stack as long as as those components fit [the stack’s design principles and goals](./understand/design-principles.md). To start contributing components to the stack, check out some of [these useful ideas](https://github.com/ethereum-optimism/optimism-project-ideas) and get to building! And don’t forget that projects can also receive funding from the Collective via RetroPGF.
If you’d like to contribute to existing OP Stack code, rather than creating new components, check out [the current release of the OP Stack](./releases/README.md#current-release). Any contributions to existing OP Stack components are highly appreciated. If you’re looking for a good way to make your first contribution, check out the [Good First Issues](https://github.com/ethereum-optimism/optimism/contribute) on the Optimism Monorepo.
## Bounty hunting
The OP Stack needs YOU (yes you!) to help review the codebase for bugs and vulnerabilities. If you’re interested in bounty hunting, check out our Security Policy, Vulnerability Reporting, and Bug Bounties page.
## Documentation help
Spot a typo in these docs? See something that could be a little clearer? Head over to the Optimism Monorepo where the OP Stack docs are hosted and make a pull request. No contribution is too small!
## Community contributions
If you’re looking for other ways to get involved, here are a few options:
- Grab an idea from the [project ideas list](https://github.com/ethereum-optimism/optimism-project-ideas) to and building
- Suggest a new idea for the [project ideas list](https://github.com/ethereum-optimism/optimism-project-ideas)
- Improve the [Optimism Community Hub](https://community.optimism.io/) [documentation](https://github.com/ethereum-optimism/community-hub) or [tutorials](https://github.com/ethereum-optimism/optimism-tutorial)
- Become an Optimism Ambassador, Support Nerd, and more in the [Optimism Discord](https://discord-gateway.optimism.io/)
\ No newline at end of file
---
title: Release History
lang: en-US
---
The OP Stack codebase is a decentralized development stack maintained by the Optimism Collective and built to power Optimism.
The OP Stack is constantly evolving as new layers and modules are developed. The OP Stack codebase is also not a product (in the traditional sense) but rather a collection of software components that power the Optimism ecosystem.
**A “Release” of the OP Stack codebase is a particular set of software components that are production-ready and which fit the stack’s design principles and goals.**
Only the software components included within the Current Release of the OP Stack codebase are considered in the scope of the OP Stack. Any usage of the OP Stack outside of the official, intended capabilities of the Current Release are considered [OP Stack Hacks](../build/hacks.md) — unofficial modifications that are useful for experimentation but could have unforeseen results, such as security vulnerabilities, and are likely cause your chain to no longer be interoperable with [the Optimism Superchain](https://app.optimism.io/superchain/). **Developer support for OP Stack Hacks is limited — when in doubt, stick to the capabilities of the Current Release!**
## Current Release
[OP Stack codebase V1: Bedrock](./bedrock/)
## Past Releases
N/A
\ No newline at end of file
---
title: OP Stack codebase V1 - Bedrock
lang: en-US
---
## Overview
The first release of the OP Stack codebase is called **Bedrock**.
The Bedrock release primarily consists of the core software required to run L2 blockchains and was originally designed to power an upgrade to the Optimism Mainnet network.
## Resources
### Rollup Protocol
Learn about the basics of the Rollup protocol used by Bedrock on the [Rollup Protocol](https://community.optimism.io/docs/protocol/2-rollup-protocol/) page.
### Bedrock Explainer
Learn all about the Bedrock release of the OP Stack by reading the [Bedrock Explainer](./explainer.md).
### Specifications
Dive deep into the specifications for the Bedrock release in the [specs folder of the Optimism Monorepo](https://github.com/ethereum-optimism/optimism/blob/develop/specs/README.md).
## Components
- [`op-node`](https://github.com/ethereum-optimism/optimism/tree/develop/op-node)
- [`op-geth`](https://github.com/ethereum-optimism/op-geth)
- [`op-batcher`](https://github.com/ethereum-optimism/optimism/tree/develop/op-batcher)
- [`op-proposer`](https://github.com/ethereum-optimism/optimism/tree/develop/op-proposer)
- [`contracts-bedrock`](https://github.com/ethereum-optimism/optimism/tree/develop/packages/contracts-bedrock)
- [`fault-detector`](https://github.com/ethereum-optimism/optimism/tree/develop/packages/fault-detector)
- [`sdk`](https://github.com/ethereum-optimism/optimism/tree/develop/packages/sdk)
- [`chain-mon`](https://github.com/ethereum-optimism/optimism/tree/develop/packages/chain-mon)
\ No newline at end of file
---
title: Differences between Bedrock and L1 Ethereum
lang: en-US
---
It's important to note that there are various minor discrepancies between the behavior of Optimism and Ethereum.
You should be aware of these descrepancies when building apps on top of Optimism or the OP Stack codebase.
## Opcode Differences
| Opcode | Solidity equivalent | Behavior |
| - | - | - |
| `COINBASE` | `block.coinbase` | Undefined |
| `DIFFICULTY` | `block.difficulty` | Random value. As this value is set by the sequencer, it is not as reliably random as the L1 equivalent. |
| `NUMBER` | `block.number` | L2 block number
| `TIMESTAMP` | `block.timestamp` | Timestamp of the L2 block
| `ORIGIN` | `tx.origin` | If the transaction is an L1 ⇒ L2 transaction, then `tx.origin` is set to the [aliased address](#address-aliasing) of the address that triggered the L1 ⇒ L2 transaction. Otherwise, this opcode behaves normally. |
| `CALLER` | `msg.sender` | If the transaction is an L1 ⇒ L2 transaction, and this is the initial call (rather than an internal transaction from one contract to another), the same [address aliasing](#address-aliasing) behavior applies.
::: tip `tx.origin == msg.sender`
On L1 Ethereum `tx.origin` is equal to `msg.sender` only when the smart contract was called directly from an externally owned account (EOA).
However, on Optimism `tx.origin` is the origin *on Optimism*.
It could be an EOA.
However, in the case of messages from L1, it is possible for a message from a smart contract on L1 to appear on L2 with `tx.origin == msg.origin`.
This is unlikely to make a significant difference, because an L1 smart contract cannot directly manipulate the L2 state.
However, there could be edge cases we did not think about where this matters.
:::
### Accessing L1 information
If you need the equivalent information from the latest L1 block, you can get it from [the `L1Block` contract](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/contracts/L2/L1Block.sol).
This contract is a predeploy at address [`0x4200000000000000000000000000000000000015`](https://goerli-optimism.etherscan.io/address/0x4200000000000000000000000000000000000015).
You can use [the getter functions](https://docs.soliditylang.org/en/v0.8.12/contracts.html#getter-functions) to get these parameters:
- `number`: The latest L1 block number known to L2
- `timestamp`: The timestamp of the latest L1 block
- `basefee`: The base fee of the latest L1 block
- `hash`: The hash of the latest L1 block
- `sequenceNumber`: The number of the L2 block within the epoch (the epoch changes when there is a new L1 block)
### Address Aliasing
<details>
Because of the behavior of the `CREATE` opcode, it is possible for a user to create a contract on L1 and on L2 that share the same address but have different bytecode.
This can break trust assumptions, because one contract may be trusted and another be untrusted (see below).
To prevent this problem the behavior of the `ORIGIN` and `CALLER` opcodes (`tx.origin` and `msg.sender`) differs slightly between L1 and L2.
The value of `tx.origin` is determined as follows:
| Call source | `tx.origin` |
| ---------------------------------- | ------------------------------------------ |
| L2 user (Externally Owned Account) | The user's address (same as in Ethereum) |
| L1 user (Externally Owned Account) | The user's address (same as in Ethereum) |
| L1 contract (using `CanonicalTransactionChain.enqueue`) | `L1_contract_address + 0x1111000000000000000000000000000000001111` |
The value of `msg.sender` at the top-level (the very first contract being called) is always equal to `tx.origin`.
Therefore, if the value of `tx.origin` is affected by the rules defined above, the top-level value of `msg.sender` will also be impacted.
Note that in general, [`tx.origin` should *not* be used for authorization](https://docs.soliditylang.org/en/latest/security-considerations.html#tx-origin).
However, that is a separate issue from address aliasing because address aliasing also affects `msg.sender`.
#### Why is address aliasing an issue?
The problem with two identical source addresses (the L1 contract and the L2 contract) is that we extend trust based on the address.
It is possible that we will want to trust one of the contracts, but not the other.
1. Helena Hacker forks [Uniswap](https://uniswap.org/) to create her own exchange (on L2), called Hackswap.
**Note:** There are actually multiple contracts in Uniswap, so this explanation is a bit simplified.
[See here if you want additional details](https://ethereum.org/en/developers/tutorials/uniswap-v2-annotated-code/).
1. Helena Hacker provides Hackswap with liquidity that appears to allow for profitable arbitrage opportunities.
For example, she can make it so that you can spend 1 [DAI](https://www.coindesk.com/price/dai/)to buy 1.1 [USDT](https://www.coindesk.com/price/tether/).
Both of those coins are supposed to be worth exactly $1.
1. Nimrod Naive knows that if something looks too good to be true it probably is.
However, he checks the Hackswap contract's bytecode and verifies it is 100% identical to Uniswap.
He decides this means the contract can be trusted to behave exactly as Uniswap does.
1. Nimrod approves an allowance of 1000 DAI for the Hackswap contract.
Nimrod expects to call the swap function on Hackswap and receive back nearly 1100 USDT.
1. Before Nimrod's swap transaction is sent to the blockchain, Helena Hacker sends a transaction from an L1 contract with the same address as Hackswap.
This transaction transfers 1000 DAI from Nimrod's address to Helena Hacker's address.
If this transaction were to come from the same address as Hackswap on L2, it would be able to transfer the 1000 DAI because of the allowance Nimrod *had* to give Hackswap in the previous step to swap tokens.
Nimrod, despite his naivete, is protected because Optimism modified the transaction's `tx.origin` (which is also the initial `msg.sender`).
That transaction comes from a *different* address, one that does not have the allowance.
**Note:** It is simple to create two different contracts on the same address in different chains.
But it is nearly impossible to create two that are different by a specified amount, so Helena Hacker can't do that.
</details>
## Blocks
There are several differences in the way blocks are produced between L1 Ethereum and Optimism Bedrock.
| Parameter | L1 Ethereum | Optimism Bedrock |
| - | - | - |
| Time between blocks | 12 seconds(1) | 2 seconds |
| Block target size | 15,000,000 gas | to be determined |
| Block maximum size | 30,000,000 gas | to be determined |
(1) This is the ideal.
If any blocks are missed it could be an integer multiple such as 24 seconds, 36 seconds, etc.
**Note:** The L1 Ethereum parameter values are taken from [ethereum.org](https://ethereum.org/en/developers/docs/blocks/#block-time). The Optimism Bedrock values are taken from [the Optimism specs](https://github.com/ethereum-optimism/optimism/blob/develop/specs/guaranteed-gas-market.md#limiting-guaranteed-gas).
## Network specifications
### JSON-RPC differences
OP Stack codebase uses the same [JSON-RPC API](https://eth.wiki/json-rpc/API) as Ethereum.
Some additional OP Stack specific methods have been introduced.
See the full list of [custom JSON-RPC methods](https://community.optimism.io/docs/developers/build/json-rpc/) for more information.
### Pre-EIP-155 support
[Pre-EIP-155](https://eips.ethereum.org/EIPS/eip-155) transactions do not have a chain ID, which means a transaction on one Ethereum blockchain can be replayed on others.
This is a security risk, so pre-EIP-155 transactions are not supported on OP Stack by default.
## Transaction costs
[By default, transaction costs on OP Stack chains](https://community.optimism.io/docs/developers/build/transaction-fees/) include an [L2 execution fee](https://community.optimism.io/docs/developers/build/transaction-fees#the-l2-execution-fee) and an [L1 data fee](https://community.optimism.io/docs/developers/build/transaction-fees#the-l1-data-fee).
---
title: Bedrock Explainer
lang: en-US
meta:
- name: og:image
content: https://dev.optimism.io/content/images/size/w2000/2022/12/bedrock-BLUE.jpg
---
![Bedrock](https://dev.optimism.io/content/images/size/w2000/2022/12/bedrock-BLUE.jpg)
::: tip Staying up to date
[Stay up to date on the Superchain and the OP Stack by subscribing to the Optimism newsletter](https://optimism.us6.list-manage.com/subscribe/post?u=9727fa8bec4011400e57cafcb&id=ca91042234&f_id=002a19e3f0).
:::
Bedrock is the name of the first ever official release of the OP Stack codebase, which is a set of free and open-source modular components that work together to power Optimism.
- To understand what is in the Bedrock release, keep reading.
- To develop on Optimism Mainnet, which will upgrade its infrastructure to the Bedrock release, read the docs.
- To contribute to the OP Stack, see the contribution guidelines on the ethereum-optimism monorepo.
## Summary of Improvements
Bedrock improves on its predecessor by reducing transaction fees using optimized batch [compression](#optimized-data-compression) and Ethereum as a data availability layer; shortening delays of including L1 transactions in rollups by handling L1 re-orgs more gracefully; enabling modular proof systems through code re-use; and improving node performance by removing technical debt.
### Lower fees
In addition, Bedrock implements an optimized data [compression](#optimized-data-compression) strategy to minimize data costs. We are currently benchmarking the impact of this change, but we expect it to reduce fees significantly.
Bedrock also removes all gas costs associated with EVM execution when submitting data to L1. This reduces fees by an additional 10% over the previous version of the protocol.
### Shorter deposit times
Bedrock introduces support for L1 re-orgs in the node software, which significantly reduces the amount of time users need to wait for deposits. Earlier versions of the protocol could take up to 10 minutes to confirm deposits. With Bedrock, we expect deposits to confirm within 3 minutes.
### Improved proof modularity
Bedrock abstracts the proof system from the OP Stack so that a rollup may use either a fault proof or validity proof (e.g., a zk-SNARK) to prove correct execution of inputs on the rollup. This abstraction enables systems like [Cannon](https://github.com/ethereum-optimism/cannon) to be used to prove faults in the system.
### Improved node performance
The node software performance has been significantly improved by enabling execution of several transactions in a single rollup "block" as opposed to the prior "one transaction per block" model in the previous version. This allows the cost of merkle trie updates to be amortized across multiple transactions. At current transaction volume, this reduces state growth by approximately 15GB/year.
Node performance is further improved by removing technical debt from the previous version of the protocol. This includes removing the need for a separate "data transport layer" node to index L1, and updating the node software to efficiently query for transaction data from L1.
### Improved Ethereum equivalence
Bedrock was designed from the ground up to be as close to Ethereum as possible. Multiple deviations from Ethereum in the previous version of the protocol have been removed, including:
1. The one-transaction-per-block model.
2. Custom opcodes to get L1 block information.
3. Separate L1/L2 fee fields in the JSON-RPC API.
4. A custom ERC20 representation of ETH balances.
Bedrock also adds support for EIP-1559, chain re-orgs, and other Ethereum features present on L1.
## Design Principles
Bedrock was built to be modular & upgradeable, to reuse existing code from Ethereum, and to be as close to 100% Ethereum-equivalent as possible.
### Modularity
Bedrock makes it easy to swap out different components in the OP Stack codebase and add new capabilities by using well-defined interfaces and versioning schemes. This allows for a flexible architecture that can adapt to future developments in the Ethereum ecosystem.
Examples:
- Separation of [rollup node](#rollup-node) and execution client
- Modular fault proof design
### Code re-use
Bedrock uses existing Ethereum architecture and infrastructure as much as possible. This approach enables the OP Stack to inherit security and "lindy" benefits from the battle-tested codebases used in production on Ethereum Mainnet. You'll find examples of this throughout the design including:
Examples:
- [Minimally modified execution clients](https://op-geth.optimism.io/)
- EVM contracts instead of precompiled client code
### Ethereum equivalence
Bedrock is designed to have maximum compatibility with the existing Ethereum developer experience. A few exceptions exist due to fundamental differences between an L1 and a rollup: an altered fee model, faster block time (2s vs 12s), and a special transaction type for including L1 [deposit](#deposits) transactions.
Examples:
- Fault proof designed to prove faults of minimally modified Ethereum [execution client](#execution-client)
- Code re-use of Ethereum [execution client](#execution-client) for use by nodes in the L2 network and sequencers
## Protocol
Rollups are derived from a data availability source (generally an L1 blockchain like Ethereum). In their most common configuration, rollup protocols derive a **"canonical L2 chain"** from two primary sources of information:
1. Transaction data posted by Sequencers to the L1 and;
2. [Deposit](#deposits) transactions posted by accounts and contracts to a bridge contract on L1.
The following are the fundamental components of the protocol:
* Deposits are _writes_ to the canonical L2 chain by directly interacting with smart contracts on the L1.
* Withdrawals are _writes_ to the canonical L2 chain that implicitly trigger interactions with contracts and accounts on the L1.
* Batches are _writes_ of data corresponding to batches on the rollup.
* Block derivation is how _reads_ of data on the L1 are interpreted to understand the canonical L2 chain.
* Proof systems define _finality_ of posted output roots on the L1 such that they may be _executed_ upon (e.g., to execute withdrawals).
### Deposits
A **deposit** is a transaction on L1 that is to be included in the rollup. [Deposits](#deposits) are _guaranteed_ by definition to be included in the [canonical L2 chain](#protocol) as a means of preventing censorship or control of the L2.
#### Arbitrary message passing from L1
A **deposited transaction** is the transaction on the rollup that is made as a part of a [deposit](#deposits). With Bedrock, [deposits](#deposits) are fully generalized Ethereum transactions. For example, an account or contract on Ethereum can “deposit” a contract creation.
Bedrock defines a **deposit contract** that is available on the L1: it is a smart contract that L1 accounts and contracts can interact with to write to the L2. [Deposited transactions](#arbitrary-message-passing-from-l1) on the L2 are derived from the values in the event(s) emitted by this [deposit](#deposits) contract, which include expected parameters such as from, to, and data.
For full details, see the [deposit contract](https://github.com/ethereum-optimism/optimism/blob/develop/specs/deposits.md#deposit-contract) section of the protocol specifications.
#### Purchasing guaranteed L2 gas on L1
Bedrock also specifies a gas burn mechanism and a fee market for [deposits](#deposits). The gas that [deposited transactions](#arbitrary-message-passing-from-l1) spend on an L2 is bought on L1 via a gas burn. This gas is purchased on a fee market and there is a hard cap on the amount of gas provided to all [deposits](#deposits) in a single L1 block. This mechanism is used to prevent denial of service attacks that could occur by writing transactions to L2 from L1 that are extremely gas-intensive on L2, but cheap on L1.
The gas provided to [deposited transactions](#arbitrary-message-passing-from-l1) is sometimes called "guaranteed gas." Guaranteed gas is unique in that it is paid for by burning gas on L1 and is therefore not refundable.The total amount of L1 gas that must be burned per unit of guaranteed L2 gas requested depends on the price of L2 gas reported by a EIP-1559 style fee mechanism. Furthermore, users receive a dynamic gas stipend based on the amount of L1 gas spent to compute updates to the fee mechanism.
For a deeper explanation, read the [deposits section](https://github.com/ethereum-optimism/optimism/blob/develop/specs/deposits.md#deposits) of the protocol specifications.
### Withdrawals
**Withdrawals** are cross-domain transactions that are initiated on L2 and finalized by a transaction executed on L1. Notably, withdrawals may be used by an L2 account to call an L1 contract, or to transfer ETH from an L2 account to an L1 account.
Withdrawals are initiated on L2 via a call to the **Message Passer** predeploy contract, which records the important properties of the message in its storage. Withdrawals are finalized on L1 via a call to the [OptimismPortal](https://github.com/ethereum-optimism/optimism/blob/develop/specs/withdrawals.md#the-optimism-portal-contract) contract, which proves the inclusion of this withdrawal message. In this way, withdrawals are different from [deposits](#deposits). Instead of relying on [block derivation](#block-derivation), withdrawal transactions must use smart contracts on L1 for finalization.
#### Two-step withdrawals
Withdrawal proof validation bugs have been the root cause of many of the biggest bridge hacks of the last few years. The Bedrock release introduces an additional step in the withdrawals’ process of prior versions meant to provide an extra layer of defense against these types of bugs. In the two-step withdrawal process, a Merkle proof corresponding to the withdrawal must be submitted 7 days before the withdrawal can be finalized.. This new safety mechanism gives monitoring tools a full 7 days to find and detect invalid withdrawal proofs . If the [withdrawal](#withdrawals) proof is found to be invalid, a contract fix can be deployed before funds are lost. This dramatically reduces the risk of a bridge compromise.
For full details, see the [withdrawals](https://github.com/ethereum-optimism/optimism/blob/develop/specs/withdrawals.md) section of the protocol specification.
### Batches
In Bedrock, a wire format is defined for messaging between the L1 and L2 (i.e., for L2 deriving blocks from L1 and for L2 to write transactions to the L1). This wire format is designed to minimize costs and software complexity for writing to the L1.
#### Optimized data compression
To optimize data compression, lists of L2 transactions called **sequencer batches** are organized into groups of objects called **channels**, each of which have a maximum size that is defined in a [configurable parameter](https://github.com/ethereum-optimism/optimism/blob/develop/specs/derivation.md#channel-format) that will initially be set to ~9.5Mb. These [channels](#optimized-data-compression) are expected to be compressed using a compression function and submitted to the L1.
#### Parallelized batch submission
To parallelize messages from the sequencers that are submitting [compressed](#optimized-data-compression) [channel](#optimized-data-compression) data to the L1, [channels](#optimized-data-compression) are further broken down into **channel frames**, which are chunks of [compressed](#optimized-data-compression) [channel](#optimized-data-compression) data that can fit inside of a single L1 transaction. Given [channel frames](#parallelized-batch-submission) are mutually independent and the ordering is known, the Ethereum transactions sent by the sequencer to the L1 can be sent in parallel which minimizes sequencer software complexity and allows for filling up all available space for data on the L1.
#### Minimized usage of Ethereum gas
Bedrock removes all execution gas used by the L1 system from submitting [channel](#optimized-data-compression) data to the L1 in transactions called **batcher transactions**. All validation logic that was previously happening on smart contracts on the L1 is moved into the [block derivation](#block-derivation) logic. Instead, [batcher transactions](#minimized-usage-of-ethereum-gas) are sent to a single EOA on Ethereum referred to as the **batch inbox address**.
Batches are still subject to validity checks (i.e. they have to be encoded correctly), and so are individual transactions within the batch (e.g. signatures have to be valid). Invalid [batches](#optimized-data-compression) and invalid individual transactions within an otherwise valid batch are considered to be discarded and irrelevant to the system.
> Note: Ethereum will soon upgrade to include [EIP-4844](https://eip4844.com/), which introduces a separate fee market for writing data and an increased cap of the amount of data the Ethereum protocol is willing to store. This change is expected to further decrease the costs associated with posting data to an L1.
For a deeper explanation, read [the wire format specifications](https://github.com/ethereum-optimism/optimism/blob/develop/specs/derivation.md#overview).
### Block Derivation
In Bedrock, the protocol is designed to guarantee that the timing of [deposits](#deposits) on the L1 is respected with regards to the [block derivation](#block-derivation) of the [canonical L2 chain](#protocol). Doing so is a _pure function_ of data written to the L1 by sequencers, [deposits](#deposits), and L1 block attributes. To accomplish this, the protocol defines strategies for guaranteeing inclusion of deposits, handling L1 and L2 timestamps, and processing sequencing windows in a pipeline to ensure correct ordering.
#### Guaranteed inclusion of deposits
A goal of the [block derivation](#block-derivation) protocol is to define it such that there must be an L2 block every "L2 block time" number of seconds, and that the timestamp of L2 blocks stays in sync with the timestamps of L1 (i.e., to ensure [deposits](#deposits) are included in a logical temporal order).
In Bedrock, the concept of a **sequencing epoch** is introduced: it is a range of L2 blocks derived from a range of L1 blocks. Each [epoch](#guaranteed-inclusion-of-deposits) is identified by an **epoch number**, which is equal to the block number of the first L1 block in the sequencing window. Epochs can vary in size, subject to some constraints.
The batch derivation pipeline treats the timestamps of the L1 blocks associated with [epoch number](#guaranteed-inclusion-of-deposits) as the anchor point for determining the order of transactions on the L2. The protocol guarantees that the first L2 block of an [epoch](#guaranteed-inclusion-of-deposits) never falls behind the timestamp of the L1 block matching the [epoch](#guaranteed-inclusion-of-deposits). The first blocks of an epoch _must_ contain deposits on L1 in order to guarantee that deposits will be processed.
Note that the target configuration for the block time on L2 in the Bedrock release is 2 seconds.
#### Handling L1 and L2 timestamps
Bedrock attempts to address the problem of reconciling the timestamps on L2 with timestamps on L1 present in [deposited transactions](#arbitrary-message-passing-from-l1). It does this by allowing a short window of time for sequencing to liberally apply timestamps on L2 transactions between [epochs](#guaranteed-inclusion-of-deposits).
A **sequencing window** is a sequence of L1 blocks from which an [epoch](#guaranteed-inclusion-of-deposits) can be derived. A [sequencing window](#handling-l1-and-l2-timestamps) whose first L1 block has the number `N` contains [batcher transactions](#minimized-usage-of-ethereum-gas) for [epoch](#guaranteed-inclusion-of-deposits) `N`.
The [sequencing window](#handling-l1-and-l2-timestamps) contains blocks `[N, N + SWS)` where `SWS` is the **sequencer window size**: a fixed rollup-level configuration parameter. This parameter must be at least 2. Increasing it provides more opportunity for sequencers to order L2 transactions with respect to [deposits](#deposits), and lowering it introduces stricter windows of time for sequencers to submit batcher transactions. It is a tradeoff between creating MEV opportunity and increasing software complexity.
A protocol constant called **max sequencer drift** governs the maximum timestamp a block can have within its epoch. Having this drift allows the sequencer to maintain liveness in case of temporary problems connecting to L1. Each L2 block’s timestamp fits within the following range:
```
l1_timestamp <= l2_block.timestamp <= max(l1_timestamp + max_sequencer_drift, l1_timestamp + l2_block_time)
```
#### Block derivation pipeline
The [canonical L2 chain](#protocol) can be processed from scratch by starting with the L2 genesis state, setting the L2 chain inception as the first epoch, and then processing all sequencing windows in order to determine the correct ordering of [sequencer batches](#optimized-data-compression) and [deposits](#deposits) according to the following simplified pipeline:
| **Stage** | **Notes** |
| --- | --- |
| Read from L1 | Epochs are defined by L1 blocks. Contained within an L2 block is data pertaining to [batcher transactions](#minimized-usage-of-ethereum-gas) or [deposits](#deposits) which must be included in the [canonical L2 chain](#protocol) |
| Buffer and decode into [channels](#optimized-data-compression) | The data from L1 blocks contains unordered [channel frames](#parallelized-batch-submission), which must all be collected before reconstructing them into channels. |
| Decompress [channels](#optimized-data-compression) into [batches](#optimized-data-compression) | Since [channels](#optimized-data-compression) are [compressed](#optimized-data-compression) to minimize data fee costs on the L1, they must be decompressed. |
| Queue [batches](#optimized-data-compression) into sequential order | With the latest information from L1, [batches](#optimized-data-compression) can be validated and processed sequentially. There are some nuances to what the correct ordering is in relation to [epochs](#guaranteed-inclusion-of-deposits) and timestamps from L2, see the full specification [here](https://github.com/ethereum-optimism/optimism/blob/develop/specs/derivation.md#batch-queue). |
| Interpret as L2 blocks | At this point, the correct ordering of [batches](#optimized-data-compression) can be determined.<br><br>Following this, the [execution client](#execution-client) can interpret them into L2 blocks. For implementation details pertaining to [execution clients](#execution-client), see the [engine queue](https://github.com/ethereum-optimism/optimism/blob/develop/specs/derivation.md#engine-queue) section of the protocol specifications. |
### Fault Proofs
After a sequencer processes one or more L2 blocks, the outputs computed from executing transactions in those blocks will need to be written with L1 for trustless execution of L2-to-L1 messaging, such as [withdrawals](#withdrawals).
In Bedrock, outputs are hashed in a tree-structured form which minimizes the cost of proving any piece of data captured by the outputs. Proposers periodically submit **output roots** that are Merkle roots of the entire [canonical L2 chain](#protocol) to the L1.
Future upgrades of the OP Stack codebase should include a specification for a variation of a fault proof with bonding included to create incentives for proposers to propose correct output roots.
For full details, read the [L2 Output Root Proposals section](https://github.com/ethereum-optimism/optimism/blob/develop/specs/proposals.md#l2-output-root-proposals-specification) of the protocol specifications.
## Implementation
With Bedrock, the OP Stack codebase leans heavily into the technical separation of concerns specified by Ethereum by mirroring the separation between the Ethereum execution layer and consensus layer. Bedrock introduces separation of execution client and rollup node in this same way.
### Execution Client
An **execution client** is the system that sequencers and other kinds of node operators run to determine the state of the [canonical L2 chain](#protocol). It also performs other functions such as processing inbound transactions and communicating them peer-to-peer, and handling the state of the system to process queries against it.
With Bedrock, the OP Stack is designed to reuse [Ethereum’s own execution client specifications](https://github.com/ethereum/execution-specs) and its many implementations. In this release, Bedrock has demonstrated an extremely limited modification of go-ethereum, the most popular Ethereum client written in Go, to a [diff of less than 2000 lines of code](https://op-geth.optimism.io/).
There are two fundamental reasons for having any diff at all: handling deposited transactions, and charging transaction fees.
#### Handling deposited transactions
To represent [deposited transactions](#arbitrary-message-passing-from-l1) in the rollup, there is an additional transaction type introduced. The [execution client](#execution-client) implements this [new transaction type](https://github.com/ethereum-optimism/optimism/blob/develop/specs/%5Bdeposits%5D(#deposits).md%23the-deposited-transaction-type) according to the [EIP-2718 typed transactions](https://eips.ethereum.org/EIPS/eip-2718) standard.
#### Charging transaction fees
Rollups also fundamentally have two kinds of fees associated with transactions:
**Sequencer fees**
The cost of operating a sequencer is computed using the same gas table as Ethereum and with the same [EIP-1559](https://eips.ethereum.org/EIPS/eip-1559) algorithm. These fees go to the protocol for operating sequencers and fluctuate based on the congestion of the network.
**Data availability fees**
Data availability costs are associated with writing [batcher transactions](#minimized-usage-of-ethereum-gas) to the L1. These fees are intended to cover the cost that sequencers need to pay to submit [batcher transactions](#minimized-usage-of-ethereum-gas) to the L1.
In Bedrock, the data availability portion of the fee is determined based on information in a system contract on the rollup called a [GasPriceOracle](https://github.com/ethereum-optimism/optimism/blob/develop/specs/predeploys.md#gaspriceoracle). This contract is updated during [block derivation](#block-derivation) from the gas pricing information retrieved from the L1 block attributes that get inserted at the beginning of every [epoch](#guaranteed-inclusion-of-deposits).
Bedrock specifies that both of these fees are added up into a single `gasPrice` field when using the JSON-RPC.
### Rollup Node
Unlike Ethereum, Bedrock does not have proof-of-stake consensus. Instead, the consensus of the [canonical L2 chain](#protocol) is defined by [block derivation](#block-derivation). An [execution client](#execution-client) of the OP Stack communicates to a new component that implements [block derivation](#block-derivation) called a **rollup node**. This node communicates to the [execution client](#execution-client) using the exact same [Engine API](https://github.com/ethereum/execution-apis/tree/main/src/engine) that Ethereum uses.
The [rollup node](#rollup-node) is a stateless component responsible for deriving the state of the system by reading data and [deposits](#deposits) on the L1. In Bedrock, a [rollup node](#rollup-node) can either be used to sequence incoming transactions from users or other [rollup nodes](#rollup-node) or to verify confirmed transactions posted on the L1 by singularly relying on the L1.
The multiple uses of a rollup node are outlined below.
#### Verifying the canonical L2 chain
The simplest mode of running a [rollup node](#rollup-node) is to only follow the [canonical L2 chain](#protocol). In this mode, the [rollup node](#rollup-node) has no peers and is strictly used to read data from the L1 and to interpret it according to [block derivation](#block-derivation) protocol rules.
One purpose of this kind of node is to verify that any output roots shared by other nodes or posted on the L1 are correct according to protocol definition. Additionally, proposers intending to submit output roots to the L1 themselves can generate the output roots they need using the [optimism_outputAtBlock](https://github.com/ethereum-optimism/optimism/blob/develop/specs/rollup-node.md#l2-output-rpc-method) of the node which returns a 32-byte hash corresponding to the L2 output root.
For this purpose, nodes should only need to follow the finalized head. The term ["finalized"](https://ethereum.org/en/developers/docs/consensus-mechanisms/pos/#finality) refers to the Ethereum proof-of-stake consensus (i.e. canonical and practically irreversible) — the finalized L2 head is the head of the [canonical L2 chain](#protocol) that is derived only from finalized L1 blocks.
#### Participating in the L2 network
The most common way to use a [rollup node](#rollup-node) is to participate in a network of other [rollup nodes](#rollup-node) tracking the progression and state of an L2. In this mode, a [rollup node](#rollup-node) is both reading the data and [deposits](#deposits) it observes from the L1 and interpreting it as blocks and accepting inbound transactions from users and peers in a network of other [rollup nodes](#rollup-node).
Nodes participating in the network may make use of the safe and unsafe heads of the L2 they're syncing.
- The **safe L2 head** represents the rollup that can be constructed where every block up to and including the head can be fully derived from the reference L1 chain, before L1 has necessarily finalized (i.e., a re-org may occur on L1 still).
- The **unsafe L2 head** includes [unsafe blocks](https://github.com/ethereum-optimism/optimism/blob/develop/specs/glossary.md#unsafe-l2-block) that have not yet been derived from L1. These blocks either come from operating the [rollup node](#rollup-node) as a sequencer or from [unsafe sync](https://github.com/ethereum-optimism/optimism/blob/develop/specs/glossary.md#unsafe-sync) with the sequencer. This is also known as the "latest" head. The safe L2 head is always chosen over the unsafe L2 head in cases of disagreements. When disagreements occur, the unsafe portion of the chain will reorg.
For most purposes, nodes in the L2 network will refer to the unsafe L2 head for end-user applications.
#### Sequencing transactions
The third way to use a [rollup node](#rollup-node) is to sequence transactions. In this mode, a [rollup node](#rollup-node) will _create_ new blocks on top of the unsafe L2 head. Currently, there is only one sequencer per OP Stack network.
The sequencer is also responsible for posting batches to L1 for other nodes in the network to sync from.
### Batcher
The role of a sequencer is to produce [batches](#batches). To do this, a sequencer can run [rollup nodes](#rollup-node) and have separate processes which perform [batching](#batches) by reading from a trusted [rollup node](#rollup-node) they run. This warrants an additional component of the OP Stack called a [batcher](https://github.com/ethereum-optimism/optimism/blob/develop/specs/glossary.md#batcher) that reads transaction data from a [rollup node](#rollup-node) and interprets it into [batcher transactions](#minimized-usage-of-ethereum-gas) to be written to the L1. The batcher component is responsible for reading the unsafe L2 head of a [rollup node](#rollup-node) run by a sequencer, creating batcher transactions, and writing them to the L1.
### Standard Bridge Contracts
Bedrock also includes a pair of bridge contracts used for the most common kinds of [deposits](#deposits) called the [standard bridges](https://github.com/ethereum-optimism/optimism/blob/develop/specs/bridges.md#standard-bridges). These contracts wrap the [deposit](#deposits) and [withdrawal](#withdrawals) contracts to provide simple interfaces for [depositing](#deposits) and [withdrawing](#withdrawals) ETH and ERC-20 tokens.
These bridges are designed to involve a native token on one side of the bridge, and a wrapped token on the other side that can manage minting and burning. Bridging a native token involves locking the native token in a contract and then minting an equivalent amount of mintable token on the other side of the bridge.
For full details, see the [standard bridge](https://github.com/ethereum-optimism/optimism/blob/develop/specs/bridges.md#standard-bridges) section of the protocol specifications.
### Cannon
Although fault proof construction and verification is implemented in the [Cannon](https://github.com/ethereum-optimism/cannon) project, the fault proof game specification and integration of an output root challenger into the rollup node are part of later specification milestones.
## Further Reading
### Protocol Specification
The protocol specification defines the technical details of the OP Stack codebase. It is the most up-to-date source of truth for the inner workings of the protocol. The protocol specification is located in the ethereum-optimism [monorepo](https://github.com/ethereum-optimism/optimism/blob/develop/specs/README.md).
### Bedrock Differences
For a deep dive into the differences between Bedrock and previous versions of the protocol, see the [How is Bedrock Different?](https://community.optimism.io/docs/developers/bedrock/differences/) page.
---
title: Security FAQs
lang: en-US
---
::: warning 🚧 Work in Progress
The OP Stack is a work in progress. Constantly pushing to improve the overall security and decentralization of the OP Stack is a top priority.
:::
## Security in the decentralized context
The OP Stack is a decentralized development stack that powers Optimism. Components of the OP Stack may be maintained by various different teams within the Optimism Collective. It is generally easier to talk about the security model of specific chains built on the OP Stack rather than the security model of the stack itself. **The OP Stack security baseline is to create safe defaults while still giving developers the flexibility to make modifications and extend the stack.**
## FAQ
### Is every OP Stack chain safe?
The security model of an OP Stack based blockchain depends on the modules used for its components. Because of the flexibility provided by OP Stack, it is always possible to set up an insecure blockchain using OP Stack components. **The goal of the OP Stack is to provide safe defaults.**
Please also keep in mind that just like any other system, **the OP Stack may contain unknown bugs** that could lead to the loss of some or all of the assets held within an OP Stack based system. [Many components of the OP Stack codebase have been audited](https://github.com/ethereum-optimism/optimism/tree/develop/technical-documents/security-reviews) but **audits are not a stamp of approval** and **a completed audit does not mean that the audited codebase is free of bugs.** It’s important to understand that using the OP Stack inherently exposes you to the risk of bugs within the OP Stack codebase.
### Is the OP Stack safe to modify?
As with anything, modify the OP Stack at your own risk. There is no guarantee that modifications to the stack will be safe. If you aren’t entirely sure about what you’re doing, stick with the safer defaults that the OP Stack provides. At the moment, the OP Stack is not particularly amenable to modifications and **you should not expect any technical support for modifications that fall outside of the standard Rollup configuration of the stack**.
### Can I use fault proofs?
**Not yet.** The OP Stack does not currently have a fault proof system. **Note that fault proofs do not meaningfully improve the security of a system if that system can be upgraded within the 7 day challenge window (”fast upgrade keys”)**. A system with fast upgrade keys is fully dependent on the upgrade keys for security.
Fault proofs are a key milestone and top priority for the OP Stack. In the meantime, the OP Stack can be shipped with several other excellent security options for systems that want to improve security before fault proofs are available in production.
### How can I help make the OP Stack more secure?
One of the easiest ways to help secure the OP Stack is to look for bugs and vulnerabilities. [Optimism Mainnet, a user of the OP Stack, has one of the biggest bug bounties (ever)](https://immunefi.com/bounty/optimism/). You can earn up to $2,000,042 by finding critical bugs in the Optimism Mainnet codebase (and by extension the OP Stack).
Don’t forget that the OP Stack is a decentralized development stack. Anyone can start to contribute to the OP Stack by building software that follows [the stack’s design principles](../understand/design-principles.md). You can always help make the OP Stack more secure by building components, like alternative client or proof implementations, that users of the OP Stack can take advantage of.
### Where do I report bugs?
[View the Security Policy for details about reporting vulnerabilities and available bug bounty programs](./policy.md)
\ No newline at end of file
README.md
\ No newline at end of file
---
title: Security Policy, Vulnerability Reporting, and Bug Bounties
lang: en-US
---
## Reporting in the decentralized context
It's important to remember that the OP Stack is a decentralized software development stack built by the Optimism Collective. Different components of the OP Stack may be maintained by different teams that have different reporting processes. **This page describes general best practices for reporting bugs and provides specific reporting guidelines for the OP Stack code contained within the [ethereum-optimism](https://github.com/ethereum-optimism) GitHub organization**.
## Reporting bugs and vulnerabilities
::: danger 🚫 How NOT to disclose a vulnerability
Do *not* disclose vulnerabilities publicly or by executing them against a production network. If you do, will you not only be putting users at risk, but you will forfeit your right to a reward. Always follow the appropriate reporting pathways as described below.
- Do *not* disclose the vulnerability publicly, for example by filing a public ticket.
- Do *not* test the vulnerability on a publicly available network, either the testnet or the mainnet.
:::
### OP Stack bounty programs
The security of OP Stack smart contracts and blockchain infrastructure is paramount. Below are the various OP Stack-related bug bounty programs, as well as how to reach out if your bug is not covered by an existing bounty.
#### Optimism Mainnet bounty program
Optimism Mainnet is covered by a comprehensive [bug bounty program on Immunefi](https://immunefi.com/bounty/optimism/), which has already resulted in one of the [largest bounty payouts ever](https://medium.com/ethereum-optimism/disclosure-fixing-a-critical-bug-in-optimisms-geth-fork-a836ebdf7c94). In the listing you can find all the information relating to assets in scope, reporting, and the payout process. Because Optimism Mainnet is currently the primary user of the OP Stack, bugs in OP Stack software can generally be reported via the Optimism Mainnet bounty program.
#### Unscoped bugs
If you think you have found a significant bug or vulnerabilities in OP Stack smart contracts, infrastructure, etc., even if that component is not covered by an existing bug bounty, please report it to via the [Optimism Mainnet Immunefi program](https://immunefi.com/bounty/optimism/). The impact of any and all reported issues will be considered and the program has previously rewarded security researchers for bugs not within its stated scope.
### Other vulnerabilities
For vulnerabilities in any websites, email servers, or other non-critical infrastructure within the OP Stack, please email [OP Labs](https://www.oplabs.co/) at [security@oplabs.co](mailto:security@oplabs.co) and include detailed instructions for confirming and reproducing the vulnerability.
## Vulnerability disclosure
Each OP Stack component maintainer may determine its own process for vulnerability disclosure. However, the following describes a recommended process for disclosure that is currently in use by [OP Labs](https://www.oplabs.co/).
In the event that an OP Stack component maintainer learns of a critical security vulnerability, the maintainer reserves the right to silently fix it without immediately publicly disclosing the existence of nature of the vulnerability.
In such a scenario, the disclosure process used by [OP Labs](https://www.oplabs.co/) is as follows:
1. Silently fix the vulnerability and include the fix in release X.
1. After 4-8 weeks, disclose that release X contained a security fix.
1. After an additional 4-8 weeks, publish details of the vulnerability, along with credit to the reporter (with express permission from the reporter).
Alongside this policy, maintainers also reserve the right to:
- Bypass this policy and publish details on a shorter timeline.
- Directly notify a subset of downstream users prior to making a public announcement.
This policy is based the [Geth](https://geth.ethereum.org/) team’s [silent patch policy](https://geth.ethereum.org/docs/vulnerabilities/vulnerabilities#why-silent-patches).
\ No newline at end of file
---
title: Design Principles for USEful Software
lang: en-US
---
::: tip The OP Stack is USEful software
The OP Stack is a set of software components for building L2 blockchain ecosystems, built by the Optimism Collective to power Optimism.
Components to be added to the OP Stack should be built according to three key design principles:
- **U**tility
- **S**implicity
- **E**xtensibility.
Software that follows these principles is **USE**ful software for the Optimism Collective!
:::
## Utility
For something to be part of the OP Stack, it should help power the Optimism Collective.
This condition helps guide the type of software that can be included in the stack.
For instance, a powerful open-source block explorer that makes it easier for users to inspect [the Superchain](https://app.optimism.io/superchain/) would be a great addition to the OP Stack.
Although utility is important for inclusion in the OP Stack, you shouldn’t be afraid to experiment.
Do something crazy.
Build something that’s never been built before, even if it doesn’t have any clear utility. Make a blockchain for Emojis, or whatever. Have fun!
## Simplicity
Complex code does not scale.
Code that makes it into the OP Stack should be simple.
Simplicity reduces engineering overhead, which in turn means the Collective can spend its time working on new features instead of re-creating existing ones.
The OP Stack prefers to use existing battle-tested code and infrastructure where possible.
The most visible example of this philosophy in practice is the choice to use Geth as the OP Stack’s default execution engine.
When dealing with critical infrastructure, simplicity is also security and maintainability.
Every line of code written is an opportunity to introduce bugs and vulnerabilities.
A simple protocol means there's less code to write and, as a result, less surface area for potential mistakes.
A clean and minimal codebase is also more accessible to external contributors and auditors.
All of this serves to maximize the security and correctness of the OP Stack.
## Extensibility
Good OP Stack code is inherently open, collaborative, and extensible.
Collaboration allows us to break out of siloed development.
Collaboration allows us spend more time building on top of one another's work and less time rebuilding the same components over and over again.
Collaboration is how we win, *together*.
Extensible code should be designed with the mindset that others will want to build with and on top of that code.
In practice, this means that the code should be open source (under a permissive license), expose clean APIs, and generally be modular such that another developer can relatively easily extend the functionality of the code.
Extensibility is a key design principle that unlocks the superpower of collaboration within the Optimism Collective ecosystem.
## Contributing to the OP Stack
The OP Stack is a decentralized software stack that anyone can contribute to.
If you're interested in contributing to the OP Stack, check out [the Contributing section of these docs](../contribute.md).
Of course, software that has impact for the Optimism Collective can receive [Retroactive Public Goods Funding](https://app.optimism.io/retropgf).
Build for the OP Stack — get rewarded for writing great open source software. What's not to love?
---
title: Superchain Explainer
lang: en-US
image: /assets/logos/twitter-superchain.png
meta:
- name: twitter:image
content: https://stack.optimism.io/assets/logos/twitter-superchain.png
- property: og:image
content: https://stack.optimism.io/assets/logos/twitter-superchain.png
- name: twitter:title
content: Superchain Explainer
- property: og:title
content: Superchain Explainer
- name: twitter:card
content: summary_large_image
---
::: tip Staying up to date
[Stay up to date on the Superchain and the OP Stack by subscribing to the Optimism newsletter](https://optimism.us6.list-manage.com/subscribe/post?u=9727fa8bec4011400e57cafcb&id=ca91042234&f_id=002a19e3f0).
:::
The next major scalability improvement to the OP Stack after [Bedrock](../releases/bedrock/) is to introduce the concept of *a Superchain*: a network of chains that share bridging, decentralized governance, upgrades, a communication layer and more—all built on the OP Stack.
The launch of the Superchain would merge Optimism Mainnet and other chains into a single unified network of OP Chains (i.e., chains within the Superchain), and mark a major step towards bringing scalable and decentralized compute to the world. The goal of this document is to describe the scalability vision, the Superchain concept, and some changes to the OP Stack required to make this vision a reality.
This is the detailed explanation. [Click here for a less technical introduction](https://app.optimism.io/superchain/).
::: tip Note
Today, the Superchain is a concept and in-flight project, not a concrete reality. This documentation represents our best current guess as to what the Superchain’s components, features, and roadmap will be. Ultimately, its actualization will depend on (and change alongside) contributions from across the entire Optimism Collective. We cannot wait to see where it goes.
:::
## The Scalability Vision
### Blockchain tech today is insufficient for the decentralized web
The unfortunate truth is that the blockchain ecosystem has not realized the potential of creating the decentralized web, a re-architected internet where trusted entities are replaced by permissionless protocols. This is largely due to the fact that a majority of web applications are unable to be run onchain due to scalability constraints inherent to the current state of blockchain technology—a problem which has plagued the industry since its inception.
In fact, in a display of remarkable foresight, the very first response to the Bitcoin whitepaper was:
> **We very, very much need such a system, but the way I understand your proposal, it does not seem to scale to the required size.**
More than a decade later this has not changed.
### The value of scalable decentralized compute is immense…
Imagine a world where we solved the blockchain scalability problem. Imagine if transacting onchain would be as cheap as interacting with centralized backends. In this world, what would be possible?
- Developers wouldn’t need to worry about the backend infrastructure their app exists on because the chain guarantees correct execution, uptime, and [horizontal scalability](https://en.wikipedia.org/wiki/Scalability#Horizontal_(scale_out)_and_vertical_scaling_(scale_up)) of their app.
- Due to the shared smart contract execution environment, composability would be supercharged far beyond the capabilities of traditional REST APIs.
- With standardized gas markets, developers are not required to front all infrastructure costs for their users. Paying for a viral application would no longer be a barrier to entry for app devs, and more monetization strategies would be unlocked.
The combination of these features would make it possible to program highly scalable web applications without having to touch the traditional backend software stack. Removing the need to worry about backends is a value proposition which extends beyond decentralization enthusiasts into regular application developers who just want to ship a product. With scalability, blockchains can go from a niche interest to becoming a core component of every developer’s toolkit.
Additionally, in this world where most applications go onchain more data becomes cryptographically verifiable. This cryptographic verifiability enables users to build reputations which transfer across all of their applications. The reputation can then be used for voting, loans, and collateral—facilitating trust on the internet. Plus there is no risk of losing access because users retain ownership of their data, applications, and reputation.
There is no doubt that the promise of blockchains could change the internet as we know it.
### …and the decentralized web can still be realized
This hypothetical isn’t a dream, it’s a tangible vision for the future which has motivated many—including Optimism—to dedicate their lives to its pursuit. Due to these collective contributions, every year we learn more about the blockchain technology stack and get closer to realizing the vision.
With the support of the industry, we think a clear picture for how to architect a truly scalable blockchain is beginning to come into view. We call it the “Superchain”. This document lays out the core technical principles underlying the Superchain architecture, as well as a set of tangible projects which, when complete, we believe will finally realize the blockchain scalability vision. It will be a multi-year (if not decade) journey. However, if we know roughly where we’re going we’ll get there a little faster.
## Foundational Superchain Concepts
### Horizontal scalability requires multiple chains…
Horizontal blockchain scalability fundamentally requires multiple chains. This is because the hardware requirements to sync a chain increase linearly with the amount of compute the chain performs. Therefore, to achieve horizontal scalability we must run chains in parallel.
::: details Chain
A state [transition system](https://en.wikipedia.org/wiki/Transition_system)—consisting of an initial state, a state transition function, and a list of inputs (transactions)—which is cryptographically committed to and can be independently replicated with commodity computer hardware and internet connection.
:::
### …but traditional multi-chain architectures are insufficient
Traditional approaches to ‘multi-chain’ architectures suffer from two fundamental problems:
1. Each chain introduces a new security model, resulting in compounding systemic risk as new chains are introduced into the ecosystem. (related [link](https://twitter.com/VitalikButerin/status/1479501366192132099?s=20))
2. New chains are costly to spin up because they require new validator sets & block producers.
These issues come from a lack of a single shared blockchain (an “L1” chain) which serves as a shared source of truth for all of the chains (”L2” chains) within the multi-chain system. By using the shared source of truth it is possible to: a) enforce standard security models across all chains; and b) remove the requirement that chain deployments require a new set of validators because each L2 chain uses L1 consensus.
### Not multi-chain, not mono-chain… Superchain
By using L2 chains to comprise the multi-chain ecosystem, it becomes possible to begin to treat chains as commodities—interchangeable compute resources. This commodification of chains enables developers to build cross-chain applications without introducing systemic risk and without incurring large overhead as new chains are deployed for their application. The concept of a chain itself can become abstracted, and at this point it will become possible to treat this network of interoperable chains as a single unit: the Superchain.
::: details Superchain
A decentralized blockchain platform which consists of many chains that share security and a technology stack (OP Stack). The interoperability and standardization enables individual chains to be treated identically by tools and wallets.
:::
## Superchain Overview
### The Superchain at a glance
The Superchain is a network of L2 chains, known as OP Chains, which share security, a communication layer, and an open source technology stack. However, unlike multi-chain designs, these chains are standardized and intended to be used as interchangeable resources. This enables developers to build applications which target the Superchain as a whole, and abstract away the underlying chains the apps are running on.
::: details OP Chain
An individual chain within the Optimism Superchain. All chains, regardless of their specific properties are considered OP Chains if they are officially governed by the Optimism Collective, and therefore part of the Superchain.
:::
![Superchain Explainer Diagram.png](../../assets/docs/understand/superchain-diag.png)
### Properties of the Superchain
In order for Optimism to upgraded to a Superchain, it must have the following properties:
| Property | Purpose |
| - | - |
| Shared L1 blockchain | Provides a total ordering of transactions across all OP Chains.
| Shared bridge for all OP Chains | Enables OP Chains to have standardized security properties.
| Cheap OP Chain deployment | Enables deploying and transacting on OP Chains without the high fees of transacting on L1.
| Configuration options for OP Chains | Enables OP Chains to configure their data availability provider, sequencer address, etc.
| Secure transactions and cross-chain messages | Enables users to safely migrate assets between OP Chains.
Once Optimism has satisfied these properties it may be considered a Superchain.
## Upgrading Optimism to Become a Superchain
We believe the following changes (after the Bedrock release) are required to create a initial Superchain that makes it possible to deploy and upgrade many chains with the same bridge:
### Upgrade the Bedrock bridge to be a chain factory
Bedrock introduced the [SystemConfig contract](https://github.com/ethereum-optimism/optimism/blob/74a63c94d881442b4edd4df6492513e0113eb064/packages/contracts-bedrock/contracts/L1/SystemConfig.sol) which began to define the L2 chain directly with L1 smart contracts. This can be extended to put *all information* defining the L2 chain, onchain. Including generating a unique chain ID, key configuration values such as block gas limit, etc.
Once the chain data is entirely onchain, we can create a factory which deploys the configuration and all other required contracts for each chain. This can be extended further by making the contract addresses deterministic with CREATE2, meaning that given a chain config it is possible to determine all bridge addresses associated with that chain. This also enables chains to be interacted with without having to deploy their bridge contracts, making (counterfactual) chain deployment virtually free, and allowing chains to inherit standard security properties.
### Derive OP Chain data using the chain factory
[Bedrock introduced L2 chain derivation from an L1 chain](../releases/bedrock/explainer/#block-derivation), where all chain data can be synced based on L1 blocks. With the L1 chain factory extending this to put all configuration onchain, it should become possible for Optimism nodes to sync *any* OP Chain deterministically given a single L1 address plus a connection to L1.
::: tip 📌
When the OP Chain is synced, the chain state is locally computed. This means determining the state of the OP Chain is fully permissionless & secure. No proof system is required for chain derivation because all invalid transactions are simply ignored by the local computation process performed by the node. A proof system is, however, still required to enable Superchain withdrawals.
:::
### Permissionless proof system to enable withdrawals
In Bedrock, there is a permissioned entity (the proposer) who is required for users to submit withdrawals. Additionally, proposers must submit proposals to L1 at a set interval. This introduces linear overhead as the number of chains in the Superchain increases, and even introduces an upper bound on the number of chains due to the limited L1 resources.
In order to address these issues, we can introduce two features:
1. Withdrawal claims (a.k.a. Permissionless proposals) — allow anyone to submit a withdrawal (aka a proposal), not just a designated proposer. This removes the permissioned entity from the system, enabling users to submit their own withdrawal messages.
2. Remove proposal submission interval — enable withdrawal claims to be made *only* when a user needs to withdraw. This removes the overhead incurred when deploying a new OP Chain.
::: details Withdrawal claims
A claim about the state of one chain made on another chain. For instance, I can claim that in OP Mainnet I have burned my tokens with the intent to withdraw those tokens back to L1.
:::
We can enable these two features first by introducing a permissionless proof system to the Optimism bridge contracts. With the modular proof design introduced in Bedrock, proofs may come in the form of fault proofs or validity proofs (e.g. zero knowledge proofs). However, until validity proofs are productionized, we assume withdrawals will use a fault proof system.
In the envisioned fault proof system, anyone can submit a withdrawal claim, and these withdrawal claims can be submitted at any time. Submitting withdrawal claims can be permissionless when claims come with bonds attached to them, as these bonds act as collateral if the claim is proven to be invalid. If a challenger successfully challenges the claim, the bond is paid out to the challenger for their participation in securing the system, thereby preventing spam even within this permissionless system. Additionally, there is no need to submit them at a regular interval because the fault proof game can efficiently prove the entire history of the chain since genesis.
The fault proof implementation may initially rely on a trusted set of chain attestors to be the final arbiter of disputes. Challengers must request attestations from a large number of chain attestors and combine these attestations into a single transaction called an attestation proof. The attestation proof is then used to challenge invalid claims.
The attestation-based fault proof should be designed to prefer safety over liveness. That means that if these chain attestors are malicious they cannot alone break the safety of withdrawals. The worst failure they can cause is preventing withdrawals from being processed until the next upgrade—a liveness failure.
In the future, the attestation proof will be incrementally phased out and replaced with trust-minimized proofs such as the [Cannon proof system](https://github.com/ethereum-optimism/cannon).
### Configurable sequencer per OP Chain
Bedrock introduced the ability to set the sequencer address in the SystemConfig contract. As we introduce multiple chains with their own SystemConfig contracts, we can enable the sequencer address to be configured by the OP Chain deployer. We call this configurable sequencer design modular sequencing. This enables OP Chains to be sequenced by different entities while retaining the standard [Superchain bridge] security model—a critical step towards sequencer decentralization.
::: details Modular sequencing
The ability to configure the sequencer address during OP Chain deployment. This value can be configured by the OP Chain deployer.
:::
::: details Superchain bridge
The L1 bridge contracts which govern all OP Chains in the Superchain. This bridge can be upgraded by the Optimism Collective.
:::
Within the Superchain bridge security model, chain safety (i.e. validity) as well as chain liveness (i.e. censorship resistance) is guaranteed. Safety is guaranteed by the proof system, and liveness is guaranteed by the ability to submit [transactions directly to L1](../releases/bedrock/explainer/#deposits). The combination of safety and liveness means that if an OP Chain sequencer were to misbehave, users can always submit transactions to L1 that migrates their usage to a new OP Chain with a correctly functioning sequencer.
Modular sequencing also enables permissionless experimentation with different sequencing models. Developers can envision implementing sequencing protocols such as: round robin sequencing, sequencer consensus protocols, PGA ordering, or FIFO ordering. We can expect that over time user friendly sequencing standards will emerge from the competition between competing sequencing protocols.
### One shared upgrade path for all OP Chains
To ship the initial Superchain with high confidence in security and decentralization, a decentralized security council should be introduced to govern upgrades. The security council should be able to update the set of chain attestors, initiate contract upgrades with a delay, and hit an emergency bridge pause button which also cancels pending upgrades.
The ability to pause the bridge in case of emergency means that in the worst case, where the requisite threshold of the security council participants had their private keys leaked, the result would be that withdrawals are indefinitely paused and bridge upgrades would be perpetually canceled. In other words, the L1 funds would be frozen. This follows the design principle of safety over liveness—the principle that one should always prevent the loss of funds (i.e. enforce safety) even if it means the funds get locked (i.e. sacrifice liveness).
#### Unfreezing the bridge via L1 soft fork
In order to address the frozen funds, there is a potential final recovery mechanism we call the “L1 Soft Fork Upgrade Recovery” mechanism. This mechanism enables L1 to initiate a bridge upgrade with a soft fork, bypassing all other permissions within the Superchain bridge contracts. The mechanism is as follows:
*Anyone* may propose an upgrade by submitting a transaction to a special bridge contract, along with a very large bond. This begins a two week challenge period. During this challenge period, *anyone* may submit a challenge which immediately *cancels* the upgrade and claims the bond. Under normal circumstances, it is impossible that an upgrade would go uncancelled for the required two weeks due to the large incentive provided for anyone to cancel the upgrade. However, if the upgrade is accompanied by a modification to Ethereum L1 validator software (the L1 soft fork), which ignores blocks that contain the cancellation transaction then it may succeed.
While a successful upgrade of this type would represent a soft fork of Ethereum L1, it would not incur long term technical debt to the Ethereum codebase because the soft fork logic can be removed once the upgrade has completed.
We expect this escape hatch will never be used, but its very existence should deter malicious behavior.
### The combination of these features results in a system satisfying the core Superchain properties
We believe these upgrades can provide a shared bridge for all OP Chains, cheap OP Chain deployment, important configuration options for the OP Chains, as well as secure transactions and cross-chain messages. Because the Bedrock release already provides the property of a shared L1 blockchain, after these changes we will have achieved all of the core properties required for the Superchain.
## Extending the Superchain—enhancements to realize the vision
We expect that, if successful, the post-Bedrock Superchain release will mark a major milestone in the scalability and decentralization of Optimism. However, there will still be significant pain points which must be addressed before the full scalable blockchain vision has been realized. Anticipated pain points include:
1. Withdrawal claims rely on a trusted set of chain attestors.
2. Cross-Chain transactions are slow because they require waiting a challenge period.
3. Cross-Chain transactions are asynchronous, breaking the ability to perform atomic cross-chain transactions (like flash loans).
4. Posting transactions to the Superchain is not-scalable because the transaction data must be submitted to L1 which has limited capacity.
5. There are no easy frameworks for building scalable dApps which utilize many OP Chains.
6. There is no easy wallet for managing assets and dApps across many OP Chains.
If each one of these pain points were addressed, it could be possible to build decentralized alternatives to even the most complex web2 applications.
The following is an overview of potential future enhancements, which when combined, addresses each one of these pain points.
### Multi-Proof Security
#### Pain Point:
1) Withdrawal claims rely on a trusted set of chain attestors.
#### Proposed Solution:
It is possible to replace the trusted set of chain attestors by introducing permissionless proofs—such as Cannon—where dispute resolution is entirely onchain. However, the challenge with entirely onchain proofs is there is no fallback mechanism if they were to break. To ensure that they will never fail, it is possible to introduce a multi-proof system which provides safety through redundancy. For more information on the multi-proof design click [here](https://medium.com/ethereum-optimism/our-pragmatic-path-to-decentralization-cb5805ca43c1).
### Low Latency L2 to L2 Message Passing
#### Pain Point:
2) Cross-Chain transactions are slow because they require waiting a challenge period.
#### Proposed Solution:
Fault proofs introduce a UX burden because they require waiting a challenge period in order to safely finalize. This means that, depending on your challenge period length, users need to wait a long time before their assets are migrated from one OP Chain to the next.
On the other hand, validity proofs do not have this problem. Validity proofs don’t have a challenge period and therefore provide instant withdrawals from one OP Chain to the next. This is extremely important if users are expected to migrate between chains frequently, even during normal dApp execution. However, validity proofs are commonly implemented using zero-knowledge proofs (ZKPs), which are expensive and bug-prone. It will likely take years to truly productionize ZKPs enough such that they can be the primary cross-chain communication protocol.
However, while ZKPs are being productionized, it is possible to achieve low latency L2 to L2 message passing using the OP Stack’s modular proof system. With modular proofs it is possible to use two proof systems for the same chain. This opens up the possibility to provide low latency bridging which trades off security while *also* providing high security high latency bridging.
This heterogeneous bridging system means that developers can build their applications using one of many bridge types, such as:
1. High security, high latency fault proof (standard high security bridge)
2. Low security, low latency fault proof (a short challenge period to achieve low latency)
3. Low security, low latency validity proof (using trusted chain attestors in place of a ZKP)
4. High security, low latency validity proof (once ZKPs are ready)
Mixing multiple proof systems enables developers to provide low latency bridging for low value assets and high latency for high value assets. It is even possible to turn a low security asset which was instantly bridged into a high security asset by proving the asset’s validity using a high security high latency bridge. This building block enables developers to make interesting security tradeoffs such as using a high threshold attestation proof with a high security high latency fault proof fallback.
### Synchronous Cross-Chain Transactions
#### Pain Point:
3) Cross-Chain transactions are asynchronous, breaking the ability to perform atomic cross-chain transactions (like flash loans).
#### Proposed Solution:
Traditional cross-chain messaging is done asynchronously, which means that cross-chain transactions are *not* atomic. For example, if a user would would like to execute a cross-chain arbitrage transaction—buying token A on chain A, and selling token B on chain B—there is no guarantee that their transaction executes in its entirety. The user might end up buying token A without having sold token B.
It is possible to introduce synchronous cross-chain messaging and enable atomic cross-chain interactions by using a shared sequencing protocol on both OP Chains. In our example, the sequencers on chain A and chain B would each receive the arbitrage transaction, come to consensus on when they will include it, and then atomically include each transaction in the linked block. Fees would only be paid if the transaction was indeed included on each chain, meaning the sequencers take the synchronization risk as opposed to the user in our initial example. These shared sequencing protocols can be implemented permissionlessly on top of the modular sequencing layer of the post-Bedrock Superchain.
With the combination of low latency L2 to L2 message passing as well as shared sequencing, it is possible to perform complex transactions such as cross-chain flash loans. It is even possible to go further and create an EVM abstraction where individual smart contracts (or even individual storage slots) exist on different chains.
### Alt-Data Availability Layer — Plasma Protocol
#### Pain Point:
4) Posting transactions to the Superchain is not-scalable because the transaction data must be submitted to L1 which has limited capacity.
#### Proposed Solution:
Today L1 data availability (DA) does not scale nearly enough to be able to support internet-level scale. However, it is possible to extend the amount of data availability accessible to OP Chains by using a Plasma protocol which enables alternative DA providers to supplement the more limited L1 DA.
A generic Plasma protocol is able to scale beyond what is possible on L1 because only the users who are interested in the transaction data will download the Plasma data, whereas on L1 every Ethereum node downloads all of the transaction data on L1. This means that Plasma data is extremely cheap. However, where Plasma has a worse security model than L1—it is possible for Plasma chain data to temporarily become unavailable, meaning users must withdraw from the chain. Note, this security model still guarantees safety of the Plasma chains, just not liveness.
::: details Plasma chain
A chain where transaction data is committed to on L1 but not supplied to L1 directly, with a data availability challenge fallback.
:::
**Plasma protocol overview:**
- Data Availability (DA) Providers receive transaction data from users.
- DA Providers then hash the transaction data and submit the hash to the Plasma Contract.
- Once the hash has been submitted, the DA Provider sends a proof to the user which proves inclusion of their transaction data in the hash. If the DA Provider is misbehaving, they will withhold the proof, not sending it to the user.
- If the DA Provider does not send the proof to the user, the user may submit a DA challenge. This forces the DA Provider to post the transaction data onchain. If the DA Provider does not submit the proof onchain, the hash is deleted. This ensures the user can always (after the challenge period) sync the Plasma chain.
- DA challenge periods may be extended in case of heavy L1 congestion.
- The user may also submit an L1 transaction to withdraw from the Plasma chain in order to switch their DA Provider.
- Settlement of Plasma chains use a near identical fault proof system to Rollup chains with the only difference being that additional data is derived from the chain using the hashes that are finalized in the Plasma Contract.
Because of the ability for hashes to reduce arbitrary size data into a constant size commitment, and the ability to parallelize transaction data hashing, it is possible to achieve near-perfect horizontal scalability of data commitments using Plasma DA. This means that it is possible to put massively scalable applications such as games or social media on Plasma chains.
### Multi-Chain dApp Frameworks
#### Pain Points:
5) There are no easy frameworks for building scalable dApps which utilize many OP Chains.
6) There is no easy wallet for managing assets and dApps across many OP Chains.
#### Proposed Solution (Sketch):
This is not a core protocol change, but instead tooling which can be built on top of the core Superchain protocols. The suggestions here are intended to give rough intuitions for how to build tools which improve the experience of deploying to the Superchain.
These are some tools which could make developing on the Superchain a better experience:
1. Content-addressable smart contracts — this enables contracts to have the same address on all chains. This way developers can write smart contracts which are counterfactually deployed to all OP Chains at the same address. If a user on an OP Chain would like to use the smart contract that is not yet available on their chain, they can independently deploy the code.
2. Cross-chain contract state management standards — creating standards for how smart contract state can migrate from one chain to the next enables developers to shard their applications on many chains. Additionally, this logic can be used in wallets to display user state as if it is all on the same chain. For instance, if a user has tokens split across many chains, the wallet can use the cross-chain state management logic to know that it should display the user balance as a sum of all of their token balances across all chains.
::: tip 📌
For the Ethereum scalability nerds: the state growth problem can be addressed in these frameworks by making it easy to migrate user state from bloated chains into fresh chains. Old bloated chains can be maintained with a low gas limit or deprecated entirely.
:::
3. Superchain RPC endpoint — creating a single RPC endpoint where users can send their Superchain transactions regardless of which OP Chain they are intended to enables users to avoid constantly switching their network.
With robust multi-chain dApp frameworks, it may become as easy to deploy cross-chain dApps as it is to deploy dApps which target a single chain.
## Get Involved
We believe scaling blockchains will radically decentralize the internet and make it easy to create horizontally scalable, secure, and decentralized web applications. We think the Superchain release of the OP Stack could mark a major step towards realizing this vision. However, after the release it will still take an enormous amount of work to realize the scalability vision.
However, with great challenge comes great opportunity! The work needed to arrive at the initial Superchain release of the OP stack, as well as the resulting ecosystem should be exciting greenfields of opportunities for developers who want to contribute. There will be an enormous amount of low hanging fruit contributions unlocked. We can’t pick it alone! The only way we can hope to achieve it is through open source contributions from folks like you! And with [retroactive public goods funding](https://medium.com/ethereum-optimism/retroactive-public-goods-funding-33c9b7d00f0c) your open source contributions may be rewarded too!
Exciting times ahead.
Stay Optimistic 🔴✨
## Glossary
- **Attestation-Based Fault Proof**: A fault proof where challenges can be successfully made by supplying an attestation proof which disagrees with the original withdrawal claim.
- **Attestation-Based Validity Proof**: A validity proof which can be verified by supplying an attestation proof which agrees with the withdrawal claim.
- **Attestation Proof**: A proof which consists of some number of signatures from a pre-agreed upon set of chain attestors.
- **Cannon Fault Proof**: A fault proof where challenges are evaluated using an onchain game which is guaranteed to result in a truthful outcome, given economic rationality assumptions.
- **Chain**: A state [transition system](https://en.wikipedia.org/wiki/Transition_system)—consisting of an initial state, a state transition function, and a list of inputs (transactions)—which is cryptographically committed to and can be independently replicated with commodity computer hardware and internet connection.
- **Chain Proof**: Difficult to forge evidence of the validity of a particular withdrawal claim. Proofs are commonly used to enable chains to communicate with each other.
- **Challenge Period**: The window of time in which a challenge can be made to disprove a fault proof.
- **Fault Proof**: A proof which relies on the absence of counter-evidence to prove correctness.
- **Modular Proof**: The ability to use multiple proof systems for the same OP Chain. For instance, it should be possible to prove an OP Chain using a fault proof or a validity proof.
- **Modular Sequencing**: The ability to configure the sequencer address during OP Chain deployment. This value can be configured by the OP Chain deployer.
- **OP Chain**: An individual chain within the Optimism Superchain. All chains, regardless of their specific properties are considered OP Chains if they are officially governed by the Optimism Collective, and therefore part of the Superchain.
- **Plasma Chain**: A chain where transaction data is committed to on L1 but not supplied to L1 directly, with a data availability challenge fallback.
- **Rollup Chain**: A chain where all transaction data is submitted to L1.
- **Sequencer**: The specific entity or smart contract which has priority when submitting transactions to an OP Chain.
- **Superchain**: A decentralized blockchain platform which consists of many chains that share security and a technology stack (OP Stack). The interoperability and standardization enables individual chains to be treated identically by tools and wallets.
- **Superchain Bridge**: The L1 bridge contracts which govern all OP Chains in the Superchain. This bridge can be upgraded by the Optimism Collective.
- **Validity Proof**: A proof of a withdrawal claim which can be immediately validated, without a challenge period.
- **Withdrawal Claim**: A claim about the state of one chain made on another chain. For instance, I can claim that in OP Mainnet I have burned my tokens with the intent to withdraw those tokens back to L1.
- **Zero Knowledge Proof**: A validity proof which relies on cryptographic properties and low error margins.
---
title: The OP Stack Landscape
lang: en-US
---
**The OP Stack is a common development stack for building L2 blockchain ecosystems, built by the Optimism Collective to power Optimism.**
The OP Stack is best thought of as a collection of software components maintained by the Optimism Collective that either help to define new layers of the stack or fit in as modules within the stack.
Because the OP Stack is a work in progress, the landscape of the different layers and modules is still evolving.
This page sketches out the different conceptual layers of the stack as they exist today and introduces some of the modules that fit into those layers.
This doesn't include all of the modules or layers that may exist in the future, but gives a good overview of the landscape of the OP Stack today.
If you’re interested in learning more about the latest *production* release of the OP Stack, the components of the stack that are highly tested and ready for real-world action, check out the page about the [Bedrock Release](../releases/bedrock.md).
::: warning
Please note that not all of the modules described on this page already exist in a production state — these are explicitly marked as either “**in development**” or “**proposed**
:::
## Existing Landscape
![The OP Stack layers](../../assets/docs/understand/landscape.png)
## Layers
### Data Availability
The Data Availability Layer defines where the raw inputs to an OP Stack based chain are published. An OP Stack chain can use one or more Data Availability module to source its input data. Because an OP Stack chain is derived from the Data Availability Layer, the Data Availability module(s) used have a significant impact on the security model of a system. For example, if a certain piece of data can no longer be retrieved from the Data Availability Layer, it may not be possible to sync the chain.
#### Ethereum DA
Ethereum DA is currently the most widely used Data Availability module for the OP Stack. When using the Ethereum DA module, source data can be derived from any piece of information accessible on the Ethereum blockchain. This includes Ethereum calldata, events, and 4844 data blobs.
- [Specifications](https://github.com/ethereum-optimism/optimism/blob/develop/specs/derivation.md#batch-submission-wire-format)
- [Source code](https://github.com/ethereum-optimism/optimism/tree/develop/op-batcher)
### Sequencing
The Sequencing Layer determines how user transactions on an OP Stack chain are collected and published to the Data Availability Layer module(s) in use. In the default Rollup configuration of the OP Stack, Sequencing is typically handled by a single dedicated Sequencer. Rules defined in the Derivation Layer generally restrict the Sequencer’s ability to withhold transactions for more than a specific period of time. In the proposed future, Sequencing will be modular such that chains can easily select and change the mechanism that controls their current Sequencer.
#### Single Sequencer
The default Sequencer module for the OP Stack is the Single Sequencer module in which a dedicated actor is given the ability to act as the Sequencer. The Single Sequencer module allows a governance mechanism to determine who may act as the Sequencer at any given time.
#### Multiple Sequencer (proposed)
A simple modification to the Single Sequencer module is the Multiple Sequencer module in which the Sequencer at any given time is selected from a pre-defined set of possible actors. Individual OP Stack based chains would be able to determine the exact mechanism that defines the set of possible Sequencers and the mechanism that selects a Sequencer from the set.
### Derivation
The Derivation Layer defines how the raw data in the Data Availability Layer is processed to form the processed inputs that are sent to the Execution Layer via the standard [Ethereum Engine API](https://github.com/ethereum/execution-apis/blob/94164851c1630ff0a9c31d8d7d3d4fb886e196c0/src/engine/README.md). The Derivation Layer may also use the current system state, as defined by the Execution Layer, to inform the parsing of raw input data. The Derivation Layer can be modified to derive Engine API inputs from many different data sources. The Derivation Layer is typically tied closely to the Data Availability Layer because it must understand how to parse any raw input data.
#### Rollup
The Rollup module derives Engine API inputs from Ethereum block data, Sequencer transaction batches, Deposited transaction events, and more.
- [Specifications](https://github.com/ethereum-optimism/optimism/blob/develop/specs/derivation.md#l2-chain-derivation-pipeline)
- [Source code](https://github.com/ethereum-optimism/optimism/tree/develop/op-node)
#### Indexer (proposed)
The Indexer module is a proposed Derivation Layer module that would derive Engine API inputs when transactions are sent to, events are emitted by, or storage is modified in specific smart contracts on a Data Availability Layer module like Ethereum DA.
### Execution
The Execution Layer defines the structure of state within an OP Stack system and defines the state transition function that mutates this state. State transitions are triggered when inputs are received from the Derivation Layer via the Engine API. The Execution Layer abstraction opens up the door to EVM modifications or different underlying VMs entirely.
#### EVM
The EVM is an Execution Layer module that uses the same state representation and state transition function as the Ethereum Virtual Machine. The EVM module in the Ethereum Rollup configuration of the OP Stack is a [lightly modified](https://op-geth.optimism.io/) version of the EVM that adds support for L2 transactions initiated on Ethereum and adds an extra L1 Data Fee to each transaction to account for the cost of publishing transactions to Ethereum.
- [Specifications](https://github.com/ethereum-optimism/optimism/blob/develop/specs/exec-engine.md) (where it differs from [geth](https://geth.ethereum.org/))
- [Source code](https://github.com/ethereum-optimism/op-geth)
### Settlement Layer
The Settlement Layer is a mechanism on external blockchains that establish a **view** of the state of an OP Stack chain on those external chains (including other OP Stack chains). For each OP Stack chain, there may be one or more Settlement mechanisms on one or more external chains. Settlement Layer mechanisms are **read-only** and allow parties external to the blockchain to make decisions based on the state of an OP Stack chain.
The term “Settlement Layer” has its origins in the fact that Settlement Layer mechanisms are often used to handle withdrawals of assets out of a blockchain. This sort of withdrawal system first involves proving the state of the target blockchain to some third-party chain and then processing a withdrawal based on that state. However, the Settlement Layer is not strictly (or even predominantly) financial and, at its core, simply allows a third-party chain to become aware of the state of the target chain.
Once a transaction is published and finalized on the corresponding Data Availability layer, the transaction is also finalized on the OP Stack chain. Short of breaking the underlying Data Availability layer, it can no longer be modified or removed. It may not be accepted by the Settlement Layer yet because the Settlement Layer needs to be able to verify transaction *results*, but the transaction itself is already immutable.
#### Attestation-based Fault Proof
An Attestation-based Fault Proof mechanism uses an optimistic protocol to establish a view of an OP Stack chain. In optimistic settlement mechanisms generally, **Proposer** entities can propose what they believe to be the current valid state of the OP Stack chain. If these proposals are not invalidated within a certain period of time (the “challenge period”), then the proposals are assumed by the mechanism to be correct. In the Attestation Proof mechanism in particular, a proposal can be invalidated if some threshold of pre-defined parties provide attestations to a valid state that is different than the state in the proposal. This places a trust assumption on the honesty of at least a threshold number of the pre-defined participants.
- [Specifications](https://github.com/ethereum-optimism/optimism/blob/develop/specs/withdrawals.md) (called [withdrawal transactions](https://community.optimism.io/docs/developers/bridge/messaging/#))
- [Source code](https://github.com/ethereum-optimism/optimism/tree/develop/packages/contracts-bedrock/contracts)
#### Fault Proof Optimistic Settlement (proposed)
A Fault Proof Optimistic Settlement mechanism is mostly identical to the Attestation-based Fault Proof mechanism used today but it replaces the MultiSig challenger with a permissionless fault proving process. A correctly constructed fault proof should be able to invalidate any incorrect proposals during the allocated challenge period. This places a trust assumption on the correctness of the fault proof construction. At this time, work on the development of a Fault Proof mechanism is well underway.
#### Validity Proof Settlement (proposed)
A Validity Proof Settlement mechanism uses a mathematical proof to attest to the correctness of a proposed view. A proposed state will not be accepted without a valid proof. This places a trust assumption on the correctness of the validity proof construction.
### Governance
The Governance Layer refers to the general set of tools and processes used to manage system configuration, upgrades, and design decisions. This is a relatively abstract layer that can contain a wide range of mechanisms on a target OP Stack chain and on third-party chains that impact many of the other layers of the OP Stack.
#### MultiSig Contracts
MultiSig Contracts are smart contracts that carry out actions when they receive a threshold of signatures from some pre-defined set of participants. These are often used to manage upgrades of components of an OP Stack based system. Currently, this is the mechanism used to manage upgrades of the bridge contracts on Optimism Mainnet. The security of a MultiSig Contract system depends on many different factors, including the number of participants, the threshold, and the safety procedures of each individual participant.
#### Governance Tokens
Governance Tokens are widely used to decentralize decision making. Although the exact functionality of a Governance Token varies on a case-by-case basis, the most common mechanisms allow token holders to vote on some subset of decisions that a project must make. Voting can either be carried out directly or via delegation.
\ No newline at end of file
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -54,6 +54,16 @@ func (c ChannelConfig) InputThreshold() uint64 {
return uint64(float64(c.TargetNumFrames) * float64(c.TargetFrameSize) / c.ApproxComprRatio)
}
type frameID struct {
chID derive.ChannelID
frameNumber uint16
}
type frameData struct {
data []byte
id frameID
}
// channelBuilder uses a ChannelOut to create a channel with output frame
// size approximation.
type channelBuilder struct {
......@@ -76,7 +86,7 @@ type channelBuilder struct {
// list of blocks in the channel. Saved in case the channel must be rebuilt
blocks []*types.Block
// frames data queue, to be send as txs
frames []taggedData
frames []frameData
}
func newChannelBuilder(cfg ChannelConfig) (*channelBuilder, error) {
......@@ -319,7 +329,7 @@ func (c *channelBuilder) outputFrame() error {
c.setFullErr(ErrMaxFrameIndex)
}
frame := taggedData{
frame := frameData{
id: txID{chID: c.co.ID(), frameNumber: fn},
data: buf.Bytes(),
}
......@@ -343,23 +353,23 @@ func (c *channelBuilder) NumFrames() int {
// NextFrame returns the next available frame.
// HasFrame must be called prior to check if there's a next frame available.
// Panics if called when there's no next frame.
func (c *channelBuilder) NextFrame() (txID, []byte) {
func (c *channelBuilder) NextFrame() frameData {
if len(c.frames) == 0 {
panic("no next frame")
}
f := c.frames[0]
c.frames = c.frames[1:]
return f.id, f.data
return f
}
// PushFrame adds the frame back to the internal frames queue. Panics if not of
// the same channel.
func (c *channelBuilder) PushFrame(id txID, frame []byte) {
if id.chID != c.ID() {
func (c *channelBuilder) PushFrame(frame frameData) {
if frame.id.chID != c.ID() {
panic("wrong channel")
}
c.frames = append(c.frames, taggedData{id: id, data: frame})
c.frames = append(c.frames, frame)
}
var (
......
......@@ -7,7 +7,6 @@ import (
"math"
"github.com/ethereum-optimism/optimism/op-node/eth"
"github.com/ethereum-optimism/optimism/op-node/rollup/derive"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
......@@ -15,29 +14,6 @@ import (
var ErrReorg = errors.New("block does not extend existing chain")
// txID is an opaque identifier for a transaction.
// It's internal fields should not be inspected after creation & are subject to change.
// This ID must be trivially comparable & work as a map key.
type txID struct {
chID derive.ChannelID
frameNumber uint16
}
func (id txID) String() string {
return fmt.Sprintf("%s:%d", id.chID.String(), id.frameNumber)
}
// TerminalString implements log.TerminalStringer, formatting a string for console
// output during logging.
func (id txID) TerminalString() string {
return fmt.Sprintf("%s:%d", id.chID.TerminalString(), id.frameNumber)
}
type taggedData struct {
data []byte
id txID
}
// channelManager stores a contiguous set of blocks & turns them into channels.
// Upon receiving tx confirmation (or a tx failure), it does channel error handling.
//
......@@ -59,7 +35,7 @@ type channelManager struct {
// pending channel builder
pendingChannel *channelBuilder
// Set of unconfirmed txID -> frame data. For tx resubmission
pendingTransactions map[txID][]byte
pendingTransactions map[txID]txData
// Set of confirmed txID -> inclusion block. For determining if the channel is timed out
confirmedTransactions map[txID]eth.BlockID
}
......@@ -68,7 +44,7 @@ func NewChannelManager(log log.Logger, cfg ChannelConfig) *channelManager {
return &channelManager{
log: log,
cfg: cfg,
pendingTransactions: make(map[txID][]byte),
pendingTransactions: make(map[txID]txData),
confirmedTransactions: make(map[txID]eth.BlockID),
}
}
......@@ -87,7 +63,10 @@ func (s *channelManager) Clear() {
func (s *channelManager) TxFailed(id txID) {
if data, ok := s.pendingTransactions[id]; ok {
s.log.Trace("marked transaction as failed", "id", id)
s.pendingChannel.PushFrame(id, data[1:]) // strip the version byte
// Note: when the batcher is changed to send multiple frames per tx,
// this needs to be changed to iterate over all frames of the tx data
// and re-queue them.
s.pendingChannel.PushFrame(data.Frame())
delete(s.pendingTransactions, id)
} else {
s.log.Warn("unknown transaction marked as failed", "id", id)
......@@ -128,7 +107,7 @@ func (s *channelManager) TxConfirmed(id txID, inclusionBlock eth.BlockID) {
// TODO: Create separate "pending" state
func (s *channelManager) clearPendingChannel() {
s.pendingChannel = nil
s.pendingTransactions = make(map[txID][]byte)
s.pendingTransactions = make(map[txID]txData)
s.confirmedTransactions = make(map[txID]eth.BlockID)
}
......@@ -166,21 +145,19 @@ func (s *channelManager) pendingChannelIsFullySubmitted() bool {
}
// nextTxData pops off s.datas & handles updating the internal state
func (s *channelManager) nextTxData() ([]byte, txID, error) {
func (s *channelManager) nextTxData() (txData, error) {
if s.pendingChannel == nil || !s.pendingChannel.HasFrame() {
s.log.Trace("no next tx data")
return nil, txID{}, io.EOF // TODO: not enough data error instead
return txData{}, io.EOF // TODO: not enough data error instead
}
id, data := s.pendingChannel.NextFrame()
// prepend version byte for first frame of transaction
// TODO: more memory efficient solution; shouldn't be responsibility of
// channelBuilder though.
data = append([]byte{0}, data...)
frame := s.pendingChannel.NextFrame()
txdata := txData{frame}
id := txdata.ID()
s.log.Trace("returning next tx data", "id", id)
s.pendingTransactions[id] = data
return data, id, nil
s.pendingTransactions[id] = txdata
return txdata, nil
}
// TxData returns the next tx data that should be submitted to L1.
......@@ -188,7 +165,7 @@ func (s *channelManager) nextTxData() ([]byte, txID, error) {
// It currently only uses one frame per transaction. If the pending channel is
// full, it only returns the remaining frames of this channel until it got
// successfully fully sent to L1. It returns io.EOF if there's no pending frame.
func (s *channelManager) TxData(l1Head eth.BlockID) ([]byte, txID, error) {
func (s *channelManager) TxData(l1Head eth.BlockID) (txData, error) {
dataPending := s.pendingChannel != nil && s.pendingChannel.HasFrame()
s.log.Debug("Requested tx data", "l1Head", l1Head, "data_pending", dataPending, "blocks_pending", len(s.blocks))
......@@ -201,15 +178,15 @@ func (s *channelManager) TxData(l1Head eth.BlockID) ([]byte, txID, error) {
// If we have no saved blocks, we will not be able to create valid frames
if len(s.blocks) == 0 {
return nil, txID{}, io.EOF
return txData{}, io.EOF
}
if err := s.ensurePendingChannel(l1Head); err != nil {
return nil, txID{}, err
return txData{}, err
}
if err := s.processBlocks(); err != nil {
return nil, txID{}, err
return txData{}, err
}
// Register current L1 head only after all pending blocks have been
......@@ -218,7 +195,7 @@ func (s *channelManager) TxData(l1Head eth.BlockID) ([]byte, txID, error) {
s.registerL1Block(l1Head)
if err := s.pendingChannel.OutputFrames(); err != nil {
return nil, txID{}, fmt.Errorf("creating frames with channel builder: %w", err)
return txData{}, fmt.Errorf("creating frames with channel builder: %w", err)
}
return s.nextTxData()
......
......@@ -3,11 +3,14 @@ package batcher_test
import (
"io"
"math/big"
"math/rand"
"testing"
"time"
"github.com/ethereum-optimism/optimism/op-batcher/batcher"
"github.com/ethereum-optimism/optimism/op-node/eth"
"github.com/ethereum-optimism/optimism/op-node/rollup/derive"
derivetest "github.com/ethereum-optimism/optimism/op-node/rollup/derive/test"
"github.com/ethereum-optimism/optimism/op-node/testlog"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
......@@ -54,15 +57,15 @@ func TestChannelManagerReturnsErrReorgWhenDrained(t *testing.T) {
log := testlog.Logger(t, log.LvlCrit)
m := batcher.NewChannelManager(log, batcher.ChannelConfig{
TargetFrameSize: 0,
MaxFrameSize: 100,
MaxFrameSize: 120_000,
ApproxComprRatio: 1.0,
})
lBlock := types.NewBlock(&types.Header{
l1Block := types.NewBlock(&types.Header{
BaseFee: big.NewInt(10),
Difficulty: common.Big0,
Number: big.NewInt(100),
}, nil, nil, nil, trie.NewStackTrie(nil))
l1InfoTx, err := derive.L1InfoDeposit(0, lBlock, eth.SystemConfig{}, false)
l1InfoTx, err := derive.L1InfoDeposit(0, l1Block, eth.SystemConfig{}, false)
require.NoError(t, err)
txs := []*types.Transaction{types.NewTx(l1InfoTx)}
......@@ -77,10 +80,50 @@ func TestChannelManagerReturnsErrReorgWhenDrained(t *testing.T) {
err = m.AddL2Block(a)
require.NoError(t, err)
_, _, err = m.TxData(eth.BlockID{})
_, err = m.TxData(eth.BlockID{})
require.NoError(t, err)
_, _, err = m.TxData(eth.BlockID{})
_, err = m.TxData(eth.BlockID{})
require.ErrorIs(t, err, io.EOF)
err = m.AddL2Block(x)
require.ErrorIs(t, err, batcher.ErrReorg)
}
func TestChannelManager_TxResend(t *testing.T) {
require := require.New(t)
rng := rand.New(rand.NewSource(time.Now().UnixNano()))
log := testlog.Logger(t, log.LvlError)
m := batcher.NewChannelManager(log, batcher.ChannelConfig{
TargetFrameSize: 0,
MaxFrameSize: 120_000,
ApproxComprRatio: 1.0,
})
a, _ := derivetest.RandomL2Block(rng, 4)
err := m.AddL2Block(a)
require.NoError(err)
txdata0, err := m.TxData(eth.BlockID{})
require.NoError(err)
txdata0bytes := txdata0.Bytes()
data0 := make([]byte, len(txdata0bytes))
// make sure we have a clone for later comparison
copy(data0, txdata0bytes)
// ensure channel is drained
_, err = m.TxData(eth.BlockID{})
require.ErrorIs(err, io.EOF)
// requeue frame
m.TxFailed(txdata0.ID())
txdata1, err := m.TxData(eth.BlockID{})
require.NoError(err)
data1 := txdata1.Bytes()
require.Equal(data1, data0)
fs, err := derive.ParseFrames(data1)
require.NoError(err)
require.Len(fs, 1)
}
......@@ -280,7 +280,7 @@ func (l *BatchSubmitter) loop() {
}
// Collect next transaction data
data, id, err := l.state.TxData(l1tip.ID())
txdata, err := l.state.TxData(l1tip.ID())
if err == io.EOF {
l.log.Trace("no transaction data available")
break // local for loop
......@@ -289,10 +289,10 @@ func (l *BatchSubmitter) loop() {
break
}
// Record TX Status
if receipt, err := l.txMgr.SendTransaction(l.ctx, data); err != nil {
l.recordFailedTx(id, err)
if receipt, err := l.txMgr.SendTransaction(l.ctx, txdata.Bytes()); err != nil {
l.recordFailedTx(txdata.ID(), err)
} else {
l.recordConfirmedTx(id, receipt)
l.recordConfirmedTx(txdata.ID(), receipt)
}
// hack to exit this loop. Proper fix is to do request another send tx or parallel tx sending
......
package batcher
import (
"fmt"
"github.com/ethereum-optimism/optimism/op-node/rollup/derive"
)
// txData represents the data for a single transaction.
//
// Note: The batcher currently sends exactly one frame per transaction. This
// might change in the future to allow for multiple frames from possibly
// different channels.
type txData struct {
frame frameData
}
// ID returns the id for this transaction data. It can be used as a map key.
func (td *txData) ID() txID {
return td.frame.id
}
// Bytes returns the transaction data. It's a version byte (0) followed by the
// concatenated frames for this transaction.
func (td *txData) Bytes() []byte {
return append([]byte{derive.DerivationVersion0}, td.frame.data...)
}
// Frame returns the single frame of this tx data.
//
// Note: when the batcher is changed to possibly send multiple frames per tx,
// this should be changed to a func Frames() []frameData.
func (td *txData) Frame() frameData {
return td.frame
}
// txID is an opaque identifier for a transaction.
// It's internal fields should not be inspected after creation & are subject to change.
// This ID must be trivially comparable & work as a map key.
//
// Note: transactions currently only hold a single frame, so it can be
// identified by the frame. This needs to be changed once the batcher is changed
// to send multiple frames per tx.
type txID = frameID
func (id txID) String() string {
return fmt.Sprintf("%s:%d", id.chID.String(), id.frameNumber)
}
// TerminalString implements log.TerminalStringer, formatting a string for console
// output during logging.
func (id txID) TerminalString() string {
return fmt.Sprintf("%s:%d", id.chID.TerminalString(), id.frameNumber)
}
package test
import (
"math/rand"
"github.com/ethereum-optimism/optimism/op-node/eth"
"github.com/ethereum-optimism/optimism/op-node/rollup/derive"
"github.com/ethereum-optimism/optimism/op-node/testutils"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/trie"
)
// RandomL2Block returns a random block whose first transaction is a random
// L1 Info Deposit transaction.
func RandomL2Block(rng *rand.Rand, txCount int) (*types.Block, []*types.Receipt) {
l1Block := types.NewBlock(testutils.RandomHeader(rng),
nil, nil, nil, trie.NewStackTrie(nil))
l1InfoTx, err := derive.L1InfoDeposit(0, l1Block, eth.SystemConfig{}, testutils.RandomBool(rng))
if err != nil {
panic("L1InfoDeposit: " + err.Error())
}
return testutils.RandomBlockPrependTxs(rng, txCount, types.NewTx(l1InfoTx))
}
......@@ -206,13 +206,21 @@ func RandomHeader(rng *rand.Rand) *types.Header {
}
func RandomBlock(rng *rand.Rand, txCount uint64) (*types.Block, []*types.Receipt) {
return RandomBlockPrependTxs(rng, int(txCount))
}
// RandomBlockPrependTxs returns a random block with txCount randomly generated
// transactions and additionally the transactions ptxs prepended. So the total
// number of transactions is len(ptxs) + txCount.
func RandomBlockPrependTxs(rng *rand.Rand, txCount int, ptxs ...*types.Transaction) (*types.Block, []*types.Receipt) {
header := RandomHeader(rng)
signer := types.NewLondonSigner(big.NewInt(rng.Int63n(1000)))
txs := make([]*types.Transaction, 0, txCount)
for i := uint64(0); i < txCount; i++ {
txs := make([]*types.Transaction, 0, txCount+len(ptxs))
txs = append(txs, ptxs...)
for i := 0; i < txCount; i++ {
txs = append(txs, RandomTx(rng, header.BaseFee, signer))
}
receipts := make([]*types.Receipt, 0, txCount)
receipts := make([]*types.Receipt, 0, len(txs))
cumulativeGasUsed := uint64(0)
for i, tx := range txs {
r := RandomReceipt(rng, signer, tx, uint64(i), cumulativeGasUsed)
......
......@@ -211,8 +211,15 @@ export abstract class BaseServiceV2<
// Since BCFG turns everything into lower case, we're required to turn all of the input option
// names into lower case for the validation step. We'll turn the names back into their original
// names when we're done.
const lowerCaseOptions = Object.entries(params.options).reduce(
(acc, [key, val]) => {
acc[key.toLowerCase()] = val
return acc
},
{}
)
const cleaned = cleanEnv<TOptions>(
{ ...config.env, ...config.args, ...(params.options || {}) },
{ ...config.env, ...config.args, ...(lowerCaseOptions || {}) },
Object.entries(params.optionsSpec || {}).reduce((acc, [key, val]) => {
acc[key.toLowerCase()] = val.validator({
desc: val.desc,
......
import { validators } from '../dist'
import { BaseServiceV2 } from '../src'
type ServiceOptions = {
camelCase: string
}
class Service extends BaseServiceV2<ServiceOptions, {}, {}> {
constructor(options?: Partial<ServiceOptions>) {
super({
name: 'test-service',
version: '0.0',
options,
optionsSpec: {
camelCase: { validator: validators.str, desc: 'test' },
},
metricsSpec: {},
})
}
protected async main() {
/* eslint-disable @typescript-eslint/no-empty-function */
}
}
describe('BaseServiceV2', () => {
it('base service ctor does not throw on camel case options', async () => {
new Service({ camelCase: 'test' })
})
})
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment