diff --git a/website/src/components/InteractiveDiagrams/Timeboost/CentralizedAuction/modal-centralized-auction-step-1.mdx b/arbitrum-docs/how-arbitrum-works/timeboost/diagrams-modals/_partial-centralized-auction-step-1.mdx similarity index 100% rename from website/src/components/InteractiveDiagrams/Timeboost/CentralizedAuction/modal-centralized-auction-step-1.mdx rename to arbitrum-docs/how-arbitrum-works/timeboost/diagrams-modals/_partial-centralized-auction-step-1.mdx diff --git a/website/src/components/InteractiveDiagrams/Timeboost/CentralizedAuction/modal-centralized-auction-step-2.mdx b/arbitrum-docs/how-arbitrum-works/timeboost/diagrams-modals/_partial-centralized-auction-step-2.mdx similarity index 99% rename from website/src/components/InteractiveDiagrams/Timeboost/CentralizedAuction/modal-centralized-auction-step-2.mdx rename to arbitrum-docs/how-arbitrum-works/timeboost/diagrams-modals/_partial-centralized-auction-step-2.mdx index 1474ba741d..783aa527dd 100644 --- a/website/src/components/InteractiveDiagrams/Timeboost/CentralizedAuction/modal-centralized-auction-step-2.mdx +++ b/arbitrum-docs/how-arbitrum-works/timeboost/diagrams-modals/_partial-centralized-auction-step-2.mdx @@ -15,4 +15,5 @@ Users submit bids through the auctioneer's RPC API for the upcoming round. signature: "0x..." } ``` + [Read comprehensive explanation](https://docs.arbitrum.io/run-arbitrum-node/how-to-use-timeboost#step-2-submit-bids) diff --git a/website/src/components/InteractiveDiagrams/Timeboost/CentralizedAuction/modal-centralized-auction-step-3.mdx b/arbitrum-docs/how-arbitrum-works/timeboost/diagrams-modals/_partial-centralized-auction-step-3.mdx similarity index 100% rename from website/src/components/InteractiveDiagrams/Timeboost/CentralizedAuction/modal-centralized-auction-step-3.mdx rename to arbitrum-docs/how-arbitrum-works/timeboost/diagrams-modals/_partial-centralized-auction-step-3.mdx diff --git a/website/src/components/InteractiveDiagrams/Timeboost/CentralizedAuction/modal-centralized-auction-step-4.mdx b/arbitrum-docs/how-arbitrum-works/timeboost/diagrams-modals/_partial-centralized-auction-step-4.mdx similarity index 100% rename from website/src/components/InteractiveDiagrams/Timeboost/CentralizedAuction/modal-centralized-auction-step-4.mdx rename to arbitrum-docs/how-arbitrum-works/timeboost/diagrams-modals/_partial-centralized-auction-step-4.mdx diff --git a/arbitrum-docs/how-arbitrum-works/timeboost/gentle-introduction.mdx b/arbitrum-docs/how-arbitrum-works/timeboost/gentle-introduction.mdx index 33a96dbe5d..fece56ea95 100644 --- a/arbitrum-docs/how-arbitrum-works/timeboost/gentle-introduction.mdx +++ b/arbitrum-docs/how-arbitrum-works/timeboost/gentle-introduction.mdx @@ -8,7 +8,8 @@ user_story: As a current or prospective Arbitrum user, I want to understand how content_type: gentle-introduction --- -import { FlowChart } from '@site/src/components/InteractiveDiagrams/Timeboost/CentralizedAuction'; +import { FlowChart } from '@site/src/components/InteractiveDiagrams/PictureWithClickableNumbers'; +import { ModalWithPlugin } from '@site/src/components/InteractiveDiagrams/PictureWithClickableNumbers/ModalWithPlugin'; import ImageWithCaption from '@site/src/components/ImageCaptions/'; import ImageZoom from '@site/src/components/ImageZoom'; @@ -19,7 +20,7 @@ Timeboost is the culmination of over a year of [research and development](https: ### In a nutshell - The current "First-Come, First-Serve (FCFS)" ordering policy has many benefits, including a great UX and protection from harmful types of MEV. However, nearly all MEV on the chain is extracted by searchers who invest wastefully in hardware and spamming to win latency races (which negatively strains the network and leads to congestion). Timeboost is a new transaction ordering policy that preserves many of the great benefits of FCFS while unlocking a path for chain owners to capture some of the available MEV on their network and introducing an auction to reduce latency, racing, and, ultimately, spam. -- Timeboost introduces a few new components to an Arbitrum chain’s infrastructure: a sealed-bid second-price auction and a new "express lane" at an Arbitrum chain’s sequencer. Valid transactions submitted to the express lane will be sequenced immediately with no delay, while all other transactions submitted to the chain’s sequencer will experience a nominal delay (default: 200ms). The auction winner is granted the sole right to control the express lane for pre-defined, temporary intervals. The default block time for Arbitrum chains will continue to be industry-leading at 250ms, even with Timeboost enabled. What will change with Timeboost is that some transactions not in the express lane will be delayed to the next block. +- Timeboost introduces a few new components to an Arbitrum chain's infrastructure: a sealed-bid second-price auction and a new "express lane" at an Arbitrum chain's sequencer. Valid transactions submitted to the express lane will be sequenced immediately with no delay, while all other transactions submitted to the chain's sequencer will experience a nominal delay (default: 200ms). The auction winner is granted the sole right to control the express lane for pre-defined, temporary intervals. The default block time for Arbitrum chains will continue to be industry-leading at 250ms, even with Timeboost enabled. What will change with Timeboost is that some transactions not in the express lane will be delayed to the next block. - Timeboost is an optional feature for Arbitrum chains aimed at two types of groups of entities: (1) chain owners and their ecosystems and (2) sophisticated onchain actors and searchers. Chain owners can use Timeboost to capture additional revenue from the MEV their chain generates already, and sophisticated onchain actors and searchers will spend their resources on buying rights for the express lane (instead of spending those resources on winning latency races, which otherwise leads to spam and congestion on the network). - Timeboost will work with both centralized and [decentralized sequencer setups](https://medium.com/@espressosys/espresso-systems-and-offchain-labs-publish-decentralized-timeboost-specification-b29ff20c5db8). The specification for a centralized sequencer is public ([here](https://github.com/OffchainLabs/timeboost-design)) and the [proposal before the Arbitrum DAO](https://forum.arbitrum.foundation/t/constitutional-aip-proposal-to-adopt-timeboost-a-new-transaction-ordering-policy/25167/1) allows us to deliver Timeboost to market sooner, rather than waiting until the design with a decentralized sequencer set up and its implementation are complete. @@ -45,43 +46,61 @@ Timeboost retains most FCFS benefits while addressing FCFS limitations. #### Timeboost may help reduce spam and congestion on a network -- By introducing the ability to “purchase a time advantage” through the Timeboost auction, it is expected that rational, profit-seeking actors will spend on auctions _instead of_ investing in hardware or infrastructure to win latency races. This diversion of resources by these actors is expected to reduce FCFS MEV-driven spam on the network. +- By introducing the ability to "purchase a time advantage" through the Timeboost auction, it is expected that rational, profit-seeking actors will spend on auctions _instead of_ investing in hardware or infrastructure to win latency races. This diversion of resources by these actors is expected to reduce FCFS MEV-driven spam on the network. ## What is Timeboost, and how does it work? Timeboost is a _transaction ordering policy_. It's a set of rules that the sequencer of an Arbitrum chain is trusted to follow when ordering transactions submitted by users. In the near future, multiple sequencers will be able to enforce those rules with decentralized Timeboost. -For Arbitrum chains, the sequencer’s sole job is to take arriving, valid transactions from users, place them into an order dictated by the transaction ordering policy, and then publish the final sequence to a real-time feed and in compressed batches to the chain’s data availability layer. The current transaction ordering policy is FCFS, and Timeboost is a modified FCFS ordering policy. +For Arbitrum chains, the sequencer's sole job is to take arriving, valid transactions from users, place them into an order dictated by the transaction ordering policy, and then publish the final sequence to a real-time feed and in compressed batches to the chain's data availability layer. The current transaction ordering policy is FCFS, and Timeboost is a modified FCFS ordering policy. Timeboost is implemented using three separate components that work together: -- **A special “express lane”** which allows valid transactions to be sequenced as soon as the sequencer receives them for a given round. +- **A special "express lane"** which allows valid transactions to be sequenced as soon as the sequencer receives them for a given round. - **An offchain auction** to determine the controller of the express lane for a given round. This auction is managed by an autonomous auctioneer. - **An auction contract** deployed on the target chain to serve as the canonical source of truth for the auction results and handling of auction proceeds. -To start, the default duration of a round is 60 seconds. Transactions not in the express lane will be subject to a default 200-millisecond artificial delay to their arrival timestamp before their transaction is sequenced, which means that some non-express lane transactions may get delayed to the next block. It’s important to note that the default Arbitrum block time will remain at 250 milliseconds (which can be adjusted to 100 milliseconds if desired). Let’s dive into how each of these components works. +To start, the default duration of a round is 60 seconds. Transactions not in the express lane will be subject to a default 200-millisecond artificial delay to their arrival timestamp before their transaction is sequenced, which means that some non-express lane transactions may get delayed to the next block. It's important to note that the default Arbitrum block time will remain at 250 milliseconds (which can be adjusted to 100 milliseconds if desired). Let's dive into how each of these components works. ### The express lane - -The express lane is implemented using a special endpoint on the sequencer, formally titled `timeboost_sendExpressLaneTransaction`. This endpoint is special because transactions submitted to it will be sequenced immediately by the sequencer, hence the name, express lane. The sequencer will only accept valid transaction payloads to this endpoint if they are correctly signed by the current round’s express lane controller. Other transactions can still be submitted to the sequencer as normal, but these will be considered non-express lane transactions and will, therefore, have their arrival timestamp delayed by 200 milliseconds. It is important to note that transactions from both the express and non-express lanes are eventually sequenced into a single, ordered stream of transactions for node operators to produce an assertion and later post the data to a data availability layer. The express lane controller does _not_: +The express lane is implemented using a special endpoint on the sequencer, formally titled `timeboost_sendExpressLaneTransaction`. This endpoint is special because transactions submitted to it will be sequenced immediately by the sequencer, hence the name, express lane. The sequencer will only accept valid transaction payloads to this endpoint if they are correctly signed by the current round's express lane controller. Other transactions can still be submitted to the sequencer as normal, but these will be considered non-express lane transactions and will, therefore, have their arrival timestamp delayed by 200 milliseconds. It is important to note that transactions from both the express and non-express lanes are eventually sequenced into a single, ordered stream of transactions for node operators to produce an assertion and later post the data to a data availability layer. The express lane controller does _not_: - Have the right to re-order transactions. -- Have a guarantee that their transactions will always be first at the “top-of-the-block.” +- Have a guarantee that their transactions will always be first at the "top-of-the-block." - Guarantee a profit at all. The value of the express lane will be the sum of how much MEV the express lane controller predicts they can extract during the upcoming round (i.e., MEV opportunity estimates made before the auction closes) _plus_ the amount of MEV extracted by the express lane controller while they are in control (that they otherwise did not predict). Understanding how the value of the express lane is determined can be useful for chain owners when adjusting to the artificial delay and the time before the auction closes. ### The Timeboost auction -Control of the express lane in each round (default: 60 seconds) is determined by a per-round auction, which is a sealed-bid, second-price auction. This auction is held to determine the express lane controller for the next round. In other words, the express lane controller was determined at any point in time in the previous auction round. Bids for the auction can be made with any `ERC-20` token, in any amount, and be collected by any address - at the full discretion of the chain owner. - - +{/* Replace the PictureWithDynamicModals with FlowChart and ModalWithPlugin components */} + + + + + + The auction for a round has a closing time that is `auctionClosingSeconds` (default: 15) seconds before the beginning of the round. This means that, in the default parameters, parties have 45 seconds to submit bids before the auction will no longer accept bids. In the 15 seconds between when bids are no longer accepted and when the new round begins, the autonomous auctioneer will verify all bids, determine the winner, and make a call to the on-chain auction contract to formally resolve the auction. @@ -97,7 +116,7 @@ Below are a few of the default Timeboost parameters mentioned earlier. All these | Parameter name | Description | Recommended default value | | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------ | -| `roundDurationSeconds` | Duration of time that the sequencer will honor the express lane privileges for transactions signed by the current round’s express lane controller. | 60 seconds | +| `roundDurationSeconds` | Duration of time that the sequencer will honor the express lane privileges for transactions signed by the current round's express lane controller. | 60 seconds | | `auctionClosingSeconds` | Time before the start of the next round. The autonomous auctioneer will not accept bids during this time interval. | 15 seconds | | `beneficiary` | Address where proceeds from the Timeboost auction are sent to when `flushBeneficiaryBalance()` gets called on the auction contract. | An address controlled by the chain's owner | | `_biddingToken` | Address of the token used to make bids in the Timeboost auction. It can be any `ERC-20` token (assuming the token address chosen does not have fee-on-transfer, rebasing, transfer hooks, or otherwise non-standard `ERC-20` logic). | `WETH` | @@ -107,9 +126,9 @@ Below are a few of the default Timeboost parameters mentioned earlier. All these ## Who is Timeboost for, and how do I use it? -Timeboost is an optional addition to an Arbitrum chain’s infrastructure, meaning that enabling Timeboost is at the discretion of the chain owner and that an Arbitrum chain can fully function normally without Timeboost. +Timeboost is an optional addition to an Arbitrum chain's infrastructure, meaning that enabling Timeboost is at the discretion of the chain owner and that an Arbitrum chain can fully function normally without Timeboost. -When enabled, Timeboost is meant to serve different groups of parties with varying degrees of impact and benefits. Let’s go through them below: +When enabled, Timeboost is meant to serve different groups of parties with varying degrees of impact and benefits. Let's go through them below: #### For regular users: @@ -121,17 +140,17 @@ Timeboost represents a unique way to accrue value to their token and generate re #### For searchers/arbitrageurs: -Timeboost adds a unique twist to your existing or prospective MEV strategies that may become more profitable than before. For instance, purchasing the time advantage offered by Timeboost’s auction may end up costing _less_ than the costs to invest in hardware and win latency races. Another example is the potential new business model of reselling express lane rights to other parties in time slots or on a granular, per-transaction basis. +Timeboost adds a unique twist to your existing or prospective MEV strategies that may become more profitable than before. For instance, purchasing the time advantage offered by Timeboost's auction may end up costing _less_ than the costs to invest in hardware and win latency races. Another example is the potential new business model of reselling express lane rights to other parties in time slots or on a granular, per-transaction basis. ### Special note on Timeboost for chain owners As with many new features and upgrades to Arbitrum Nitro, Timeboost is an optional feature that chain owners may choose to deploy and customize however they see fit. Deploying and enabling/disabling Timeboost on a live Arbitrum chain will not halt or impact the chain but will instead influence the chain's transaction ordering policy. An Arbitrum chain will, by default, fall back to FCFS if Timeboost is deployed but disabled or if there is no express lane controller for a given round. -It is recommended that Arbitrum teams holistically assess the applicability and use cases of Timeboost for their chain before deploying and enabling Timeboost. This is because some Arbitrum chains may not have that much MEV (e.g., arbitrage) to begin with. Furthermore, we recommend that Arbitrum chains start with the default parameters recommended by Offchain Labs and closely monitor the results and impacts on your chain’s ecosystem over time before considering adjusting any of the parameters. +It is recommended that Arbitrum teams holistically assess the applicability and use cases of Timeboost for their chain before deploying and enabling Timeboost. This is because some Arbitrum chains may not have that much MEV (e.g., arbitrage) to begin with. Furthermore, we recommend that Arbitrum chains start with the default parameters recommended by Offchain Labs and closely monitor the results and impacts on your chain's ecosystem over time before considering adjusting any of the parameters. ### Wen mainnet? -Timeboost’s auction contract has completed a third-party audit and is rapidly approaching production readiness. Specifically for Arbitrum One and Arbitrum Nova, a [temperature check vote on Snapshot](https://snapshot.box/#/s:arbitrumfoundation.eth/proposal/0xffe9bb38228fdaf3d121140856fd2d51c2ca7f8e0d1021c07e791cebb541129a) has already passed and the AIP will move towards an onchain Tally vote next hopefully before the end of Q1 2025. +Timeboost's auction contract has completed a third-party audit and is rapidly approaching production readiness. Specifically for Arbitrum One and Arbitrum Nova, a [temperature check vote on Snapshot](https://snapshot.box/#/s:arbitrumfoundation.eth/proposal/0xffe9bb38228fdaf3d121140856fd2d51c2ca7f8e0d1021c07e791cebb541129a) has already passed and the AIP will move towards an onchain Tally vote next hopefully before the end of Q1 2025. In the meantime, please read the following resources to learn more about Timeboost: diff --git a/website/docusaurus.config.js b/website/docusaurus.config.js index cc2fa2bc63..a705796340 100644 --- a/website/docusaurus.config.js +++ b/website/docusaurus.config.js @@ -4,8 +4,9 @@ const markdownPreprocessor = require('./src/scripts/markdown-preprocessor'); const sdkSidebarGenerator = require('./src/scripts/sdk-sidebar-generator'); const sdkCodebasePath = '../arbitrum-sdk'; -import remarkMath from 'remark-math'; -import rehypeKatex from 'rehype-katex'; +const path = require('path'); +const remarkMath = require('remark-math'); +const rehypeKatex = require('rehype-katex'); /** @type {import('@docusaurus/types').Config} */ const config = { @@ -70,6 +71,51 @@ const config = { ], ], plugins: [ + // Modals-as-pages plugin + [ + require.resolve('./src/components/InteractiveDiagrams/modals-as-pages-plugin'), + { + contentDir: [ + // Primary content directory for interactive diagrams + 'src/components/InteractiveDiagrams/content', + + // Keep the old path for backward compatibility during transition + '../arbitrum-docs/how-arbitrum-works/timeboost/diagrams-modals', + ], + filePattern: '**/*.mdx', + debug: true, + // Custom content ID generator to support both new and legacy file naming + contentIdGenerator: (filePath) => { + // Legacy pattern: _partial-{diagramId}-step-{stepNumber}.mdx + const legacyMatch = path.basename(filePath).match(/^_partial-([^-]+)-step-(\d+)\.mdx$/); + if (legacyMatch) { + return { + contextId: 'diagrams', + groupId: legacyMatch[1], + contentId: `step-${legacyMatch[2]}`, + }; + } + + // New pattern: diagrams/{groupId}/step-{stepNumber}.mdx + const parts = filePath.split('/'); + + if (parts.length < 3) { + return { + contextId: 'default', + groupId: path.dirname(filePath).replace(/\//g, '-'), + contentId: path.basename(filePath, '.mdx'), + }; + } + + return { + contextId: parts[0], + groupId: parts[1], + contentId: path.basename(parts[2], '.mdx'), + }; + }, + }, + ], + [ 'docusaurus-plugin-typedoc', { @@ -189,156 +235,107 @@ const config = { // there may be a way to show the dropdown only on pages that have been translated, but that's out of scope for the initial version // { // type: 'localeDropdown', - // position: 'right', - // } + // }, ], }, footer: { style: 'dark', links: [ - {}, { + title: 'Ecosystem', items: [ { - label: 'Arbitrum.io', - to: 'https://arbitrum.io/', + label: 'Arbitrum One', + href: 'https://arbitrum.io', }, { - label: 'Arbitrum Rollup', - to: 'https://arbitrum.io/rollup', + label: 'Arbitrum Nova', + href: 'https://nova.arbitrum.io/', }, { - label: 'Arbitrum AnyTrust', - to: 'https://arbitrum.io/anytrust', + label: 'Arbitrum Portal', + href: 'https://portal.arbitrum.io', }, { - label: 'Arbitrum Orbit', - to: 'https://arbitrum.io/orbit', + label: 'Arbitrum Bridge', + href: 'https://bridge.arbitrum.io', }, { - label: 'Arbitrum Stylus', - to: 'https://arbitrum.io/stylus', + label: 'Arbitrum Governance', + href: 'https://arbitrum.foundation', }, { - label: 'Arbitrum Foundation', - to: 'https://arbitrum.foundation/', + label: 'Arbitrum Token Lists', + href: 'https://tokenlists.org/token-list?url=https://tokenlist.arbitrum.io/ArbTokenLists/arbed_arb_whitelist_era.json', }, { - label: 'Nitro whitepaper', - to: 'https://github.com/OffchainLabs/nitro/blob/master/docs/Nitro-whitepaper.pdf', + label: 'Stylus Live', + href: 'https://live.stylus-sdk.xyz/playground', }, ], }, { + title: 'Resources', items: [ { - label: 'Network status', - to: 'https://status.arbitrum.io/', - }, - { - label: 'Portal', - to: 'https://portal.arbitrum.io/', + label: 'Arbitrum One Explorer', + href: 'https://arbiscan.io', }, { - label: 'Bridge', - to: 'https://bridge.arbitrum.io/', + label: 'Arbitrum Nova Explorer', + href: 'https://nova.arbiscan.io', }, { - label: 'Governance docs', - to: 'https://docs.arbitrum.foundation/', + label: 'Arbitrum Sepolia Explorer', + href: 'https://sepolia.arbiscan.io/', }, { - label: 'Careers', - to: 'https://offchainlabs.com/careers/', + label: 'Stylus By Example', + href: 'https://example.arbitrum.io/', }, { - label: 'Support', - to: 'https://support.arbitrum.io/', - }, - { - label: 'Bug Bounties', - to: 'https://immunefi.com/bounty/arbitrum/', + label: 'Analytics', + href: 'https://dune.com/arbitrum/arbitrum-protocol-metrics', }, ], }, { + title: 'Community', items: [ { label: 'Discord', - to: 'https://discord.gg/ZpZuw7p', - }, - { - label: 'Twitter', - to: 'https://twitter.com/OffchainLabs', + href: 'https://discord.gg/arbitrum', }, { - label: 'Youtube', - to: 'https://www.youtube.com/@Arbitrum', + label: 'Offchain Labs Twitter', + href: 'https://twitter.com/OffchainLabs', }, { - label: 'Medium Blog', - to: 'https://medium.com/offchainlabs', + label: 'Arbitrum Twitter', + href: 'https://twitter.com/arbitrum', }, { - label: 'Research forum', - to: 'https://research.arbitrum.io/', + label: 'Forum', + href: 'https://forum.arbitrum.foundation/', }, { - label: 'Privacy Policy', - to: 'https://arbitrum.io/privacy', - }, - { - label: 'Terms of Service', - to: 'https://arbitrum.io/tos', + label: 'Research Papers', + href: 'https://github.com/OffchainLabs/arbitrum/blob/master/docs/Arbitrum_and_Fraud_Proofs.pdf', }, ], }, ], - copyright: `© ${new Date().getFullYear()} Offchain Labs`, + copyright: `Copyright © ${new Date().getFullYear()} Offchain Labs, Inc.`, }, - prism: { - additionalLanguages: ['solidity', 'rust', 'bash', 'toml'], + sidebar: { + hideable: true, }, - liveCodeBlock: { - /** - * The position of the live playground, above or under the editor - * Possible values: "top" | "bottom" - */ - playgroundPosition: 'top', - }, - mermaid: { - options: { - securityLevel: 'loose', - flowchart: { - curve: 'basis', - }, - }, - }, - docs: { - sidebar: { - autoCollapseCategories: true, - }, + colorMode: { + defaultMode: 'light', + disableSwitch: false, + respectPrefersColorScheme: true, }, }), }; -// HACK -// this was originally included above -// it broke local builds on Windows, not sure why yet. Works fine on Mac -// `generate_sdk_docs` runs fine, no difference in outputs between environments, so it's not easy to debug - low pri -const isRunningLocally = process.env.NODE_ENV === 'development'; -const isRunningOnWindows = process.platform === 'win32'; -if (isRunningLocally && isRunningOnWindows) { - config.plugins = config.plugins.filter((plugin) => { - if (Array.isArray(plugin) && plugin[0] === '@docusaurus/plugin-content-docs') { - return false; // remove the offending plugin config - } - return true; // keep everything else - }); -} else { - // another hack for another strange windows-specific issue, reproduceable through clean clone of repo - config.themeConfig.prism.theme = require('prism-react-renderer/themes/github'); - config.themeConfig.prism.darkTheme = require('prism-react-renderer/themes/palenight'); -} - module.exports = config; diff --git a/website/src/components/InteractiveDiagrams/DiagramContentLoader.js b/website/src/components/InteractiveDiagrams/DiagramContentLoader.js new file mode 100644 index 0000000000..e50981ae2b --- /dev/null +++ b/website/src/components/InteractiveDiagrams/DiagramContentLoader.js @@ -0,0 +1,8 @@ +/** + * Compatibility file that re-exports the DiagramContentLoader from the modals-as-pages-plugin. + * This file exists solely to prevent import errors with existing code. + */ + +import { DiagramContentLoader } from './modals-as-pages-plugin/compatibility'; + +export default DiagramContentLoader; diff --git a/website/src/components/InteractiveDiagrams/DiagramContentMap.js b/website/src/components/InteractiveDiagrams/DiagramContentMap.js new file mode 100644 index 0000000000..024aef0ba7 --- /dev/null +++ b/website/src/components/InteractiveDiagrams/DiagramContentMap.js @@ -0,0 +1,8 @@ +/** + * Compatibility file that re-exports the DiagramContentMap from the modals-as-pages-plugin. + * This file exists solely to prevent import errors with existing code. + */ + +import { DiagramContentMap } from './modals-as-pages-plugin/compatibility'; + +export default DiagramContentMap; diff --git a/website/src/components/InteractiveDiagrams/PictureWithClickableNumbers/Button.tsx b/website/src/components/InteractiveDiagrams/PictureWithClickableNumbers/Button.tsx new file mode 100644 index 0000000000..53959dc40b --- /dev/null +++ b/website/src/components/InteractiveDiagrams/PictureWithClickableNumbers/Button.tsx @@ -0,0 +1,203 @@ +import * as React from 'react'; +import { useSpring, animated as a } from '@react-spring/web'; +import { useColorMode } from '@docusaurus/theme-common'; +import '@site/src/css/custom.css'; +import { numberPaths } from './constants'; +import { NumberComponentProps } from './types'; + +/** + * Button component for the PictureWithClickableNumbers diagram. + * + * @remarks + * This component combines number display and interactive button functionality. + * It renders a numbered element that can be either static or dynamic, with hover effects + * for interactivity. Static buttons are just the number shape without a circle or interactivity. + * Dynamic buttons have a circle background, animations, and can be clicked. + * + * @param props - The component props + * @returns An SVG element representing an interactive numbered button in the diagram + */ +export const Button: React.FC = ({ + number, + type = 'static', + animated, + interactive, + coordinates, + id = 'default', + onHover, +}) => { + /** + * Get the current color mode (light/dark) from Docusaurus. + */ + const { isDarkTheme } = useColorMode(); + + /** + * State to track whether the component is currently being hovered over. + */ + const [isHovered, setIsHovered] = React.useState(false); + + /** + * Handle hover state changes + */ + const handleHoverChange = (hoverState: boolean) => { + setIsHovered(hoverState); + if (onHover) { + onHover(hoverState); + } + }; + + /** + * Determine if the number should be animated based on props + * Animation only applies to dynamic numbers + */ + const shouldAnimate = type === 'dynamic' && (animated !== undefined ? animated : true); + + /** + * Determine if the number should be interactive based on props + * Interactivity only applies to dynamic numbers + */ + const shouldBeInteractive = + type === 'dynamic' && (interactive !== undefined ? interactive : true); + + /** + * Animation configuration for the numbered circles. + * Only applied to dynamic numbers. + * Animation cycles between two colors while maintaining 100% opacity. + */ + const animationProps = !shouldAnimate + ? { opacity: 1 } + : useSpring({ + from: { opacity: 1, fill: '#ff7f2a' }, + to: [ + { opacity: 1, fill: '#ff7f2a' }, + { opacity: 1, fill: '#3578e5' }, + { opacity: 1, fill: '#ff7f2a' }, + ], + config: { tension: 20000, friction: 10 }, + loop: true, + reset: true, + immediate: false, + }); + + /** + * Get the coordinates and path data for the current number. + */ + const coords = coordinates[number]; + const pathData = numberPaths[number]; + + if (!coords || !pathData) { + return null; + } + + // Hard-coded circle radius value + const CIRCLE_RADIUS = 20.95; + + /** + * Calculate the offset for proper positioning of the number path within the circle. + */ + const offsetX = coords.circle.x - coords.path.x + (coords.offset?.x || 0); + const offsetY = coords.circle.y - coords.path.y + (coords.offset?.y || 0); + + /** + * Determine the appropriate CSS classes based on the current theme. + */ + const circleClassName = isDarkTheme ? 'cls-5' : 'cls-5-light'; + const pathClassName = isDarkTheme ? 'cls-10' : 'cls-10-light'; + + // For static numbers, we only render the number path without a circle or interactivity + if (type === 'static') { + return ( + + + + ); + } + + // For dynamic numbers, we render the full interactive component with integrated button functionality + return ( + + {/* Invisible touch/hover area with button behavior */} + {shouldBeInteractive && ( + handleHoverChange(true)} + onMouseLeave={() => handleHoverChange(false)} + /> + )} + + {/* Visual button effect when hovered */} + {shouldBeInteractive && isHovered && ( + + )} + + {/* The circle background for the number */} + {shouldAnimate ? ( + + ) : ( + + )} + + {/* The number path (SVG shape of the number) */} + + + ); +}; + +export default Button; diff --git a/website/src/components/InteractiveDiagrams/Timeboost/CentralizedAuction/ButtonComponent.jsx b/website/src/components/InteractiveDiagrams/PictureWithClickableNumbers/ButtonComponent.tsx similarity index 53% rename from website/src/components/InteractiveDiagrams/Timeboost/CentralizedAuction/ButtonComponent.jsx rename to website/src/components/InteractiveDiagrams/PictureWithClickableNumbers/ButtonComponent.tsx index 891c43c553..9dcb1374b0 100644 --- a/website/src/components/InteractiveDiagrams/Timeboost/CentralizedAuction/ButtonComponent.jsx +++ b/website/src/components/InteractiveDiagrams/PictureWithClickableNumbers/ButtonComponent.tsx @@ -1,7 +1,25 @@ import * as React from 'react'; +import { ButtonComponentProps } from './types'; -const ButtonComponent = ({ x, y, width, height }) => { - const [isHovered, setIsHovered] = React.useState(false); +/** + * Interactive button component for the PictureWithClickableNumbers diagram. + * + * @remarks + * This component renders an interactive SVG button that responds to hover events. + * It's used within the NumberComponent to make certain numbers clickable. + * + * @param props - The component props + * @param props.x - The x-coordinate position of the button + * @param props.y - The y-coordinate position of the button + * @param props.width - The width of the button + * @param props.height - The height of the button + * @returns An SVG element representing an interactive button + */ +const ButtonComponent: React.FC = ({ x, y, width, height }) => { + /** + * State to track whether the button is currently being hovered over. + */ + const [isHovered, setIsHovered] = React.useState(false); return ( { cx="256" cy="256" r="144" + className="interactive-button-circle" style={{ - fill: '#ff7f2a', filter: isHovered ? 'brightness(1.2)' : 'none', - transition: 'filter 0.2s', - animation: 'blink 2s ease-in-out infinite', }} /> diff --git a/website/src/components/InteractiveDiagrams/PictureWithClickableNumbers/DefaultBackground.tsx b/website/src/components/InteractiveDiagrams/PictureWithClickableNumbers/DefaultBackground.tsx new file mode 100644 index 0000000000..81f661e608 --- /dev/null +++ b/website/src/components/InteractiveDiagrams/PictureWithClickableNumbers/DefaultBackground.tsx @@ -0,0 +1,188 @@ +import * as React from 'react'; + +/** + * Default background SVG for the PictureWithClickableNumbers component. + * + * @remarks + * This component contains the default SVG background elements used when no custom + * SVG file path is provided. Extracted from the main component for better organization. + * + * @param props - Props for the SVG component, including viewBox and style + * @returns An SVG element containing the background elements + */ +export const DefaultBackground: React.FC<{ + viewBox?: string; + className?: string; + style?: React.CSSProperties; +}> = ({ viewBox = '0 0 1600 900', className, style }) => { + return ( + <> + + + + + + + + + + + + + + + {/* Network lines */} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {/* Main diagram elements */} + + {/* User icon */} + + + User + + + {/* Auction contract */} + + + Auction Contract + + + {/* Sequencer */} + + + Sequencer + + + {/* Express Lane */} + + + Express Lane + + + {/* Reward Distributor */} + + + Reward Distributor + + + + + + + + ); +}; + +export default DefaultBackground; diff --git a/website/src/components/InteractiveDiagrams/PictureWithClickableNumbers/FlowChartWithDynamicModals.tsx b/website/src/components/InteractiveDiagrams/PictureWithClickableNumbers/FlowChartWithDynamicModals.tsx new file mode 100644 index 0000000000..117eca8533 --- /dev/null +++ b/website/src/components/InteractiveDiagrams/PictureWithClickableNumbers/FlowChartWithDynamicModals.tsx @@ -0,0 +1,198 @@ +import * as React from 'react'; +import { Modal } from './Modal'; +import Button from './Button'; +import { PictureWithClickableNumbersProps } from './types'; +import { coordinates as defaultCoordinates } from './constants'; +import DefaultBackground from './DefaultBackground'; + +/** + * Enhanced FlowChart component that supports dynamic modal content loading + * for the PictureWithClickableNumbers diagram. + */ +export const FlowChartWithDynamicModals: React.FC< + PictureWithClickableNumbersProps & { diagramId?: string } +> = (props) => { + const { + id = 'default', + backgroundImagePath, + svgFilePath, // For backward compatibility + isSvgBackground = true, + viewBox = '0 0 1600 900', + backgroundElements, + numbers = [1, 2, 3, 4, 5], + style, + className, + customCoordinates, + diagramId = 'centralized-auction', // Default diagram ID + ...svgProps + } = props; + + // For backward compatibility: use svgFilePath if backgroundImagePath is not provided + const finalBackgroundPath = backgroundImagePath || svgFilePath; + + // If a custom SVG file path is provided, load it + const [svgContent, setSvgContent] = React.useState(null); + const svgRef = React.useRef(null); + + React.useEffect(() => { + if (finalBackgroundPath && isSvgBackground) { + fetch(finalBackgroundPath) + .then((response) => response.text()) + .then((data) => { + setSvgContent(data); + }) + .catch((error) => { + console.error('Error loading SVG file:', error); + }); + } + }, [finalBackgroundPath, isSvgBackground]); + + // Use custom coordinates if provided, otherwise use default + const finalCoordinates = customCoordinates + ? { ...defaultCoordinates, ...customCoordinates } + : defaultCoordinates; + + // Case 1: Static image background (non-SVG) + if (finalBackgroundPath && !isSvgBackground) { + return ( +
+ Background + + {numbers.map((number) => { + const isDynamic = props.dynamicButtons?.includes(number); + const isAnimated = props.animatedButtons?.includes(number); + return ( + + + + ); + })} + {backgroundElements} + +
+ ); + } + + // Case 2: Custom SVG background + if (finalBackgroundPath && isSvgBackground) { + return ( +
+ {svgContent ? ( +
+ ) : ( +
Loading SVG...
+ )} + + {numbers.map((number) => { + const isDynamic = props.dynamicButtons?.includes(number); + const isAnimated = props.animatedButtons?.includes(number); + return ( + + + + ); + })} + {backgroundElements} + +
+ ); + } + + // Case 3: Default SVG background (built-in) + return ( + + {/* Render the default background */} + + + {/* Render the numbered elements */} + {numbers.map((number) => { + const isDynamic = props.dynamicButtons?.includes(number); + const isAnimated = props.animatedButtons?.includes(number); + return ( + + + + ); + })} + {backgroundElements} + + ); +}; diff --git a/website/src/components/InteractiveDiagrams/Timeboost/CentralizedAuction/Modal.tsx b/website/src/components/InteractiveDiagrams/PictureWithClickableNumbers/Modal.tsx similarity index 58% rename from website/src/components/InteractiveDiagrams/Timeboost/CentralizedAuction/Modal.tsx rename to website/src/components/InteractiveDiagrams/PictureWithClickableNumbers/Modal.tsx index c48a11b2a7..bcd0d5c627 100644 --- a/website/src/components/InteractiveDiagrams/Timeboost/CentralizedAuction/Modal.tsx +++ b/website/src/components/InteractiveDiagrams/PictureWithClickableNumbers/Modal.tsx @@ -1,27 +1,49 @@ -import React, { useState, useEffect } from 'react'; -import { useTransition, animated } from '@react-spring/web'; +import React, { useState } from 'react'; +import { useTransition } from '@react-spring/web'; import * as Dialog from '@radix-ui/react-dialog'; -import step1Content from './modal-centralized-auction-step-1.mdx'; -import step2Content from './modal-centralized-auction-step-2.mdx'; -import step3Content from './modal-centralized-auction-step-3.mdx'; -import step4Content from './modal-centralized-auction-step-4.mdx'; -import step5Content from './modal-centralized-auction-step-5.mdx'; import { createPortal } from 'react-dom'; -import { NumberComponent } from './NumberComponent'; +import Button from './Button'; import { MDXProvider } from '@mdx-js/react'; -import type { MDXComponents } from '@mdx-js/react/lib'; import { PrismLight as SyntaxHighlighter } from 'react-syntax-highlighter'; import { oneDark, oneLight } from 'react-syntax-highlighter/dist/cjs/styles/prism'; import javascript from 'react-syntax-highlighter/dist/cjs/languages/prism/javascript'; import solidity from 'react-syntax-highlighter/dist/cjs/languages/prism/solidity'; import { useColorMode } from '@docusaurus/theme-common'; +import { SyntaxHighlighterProps } from './types'; +import { coordinates as defaultCoordinates } from './constants'; +import DiagramContentMap from '../DiagramContentMap'; -// Define the CodeBlock interface +/** + * Interface for code block data structure. + */ interface CodeBlock { language: string; code: string; } +/** + * A wrapper component for SyntaxHighlighter with proper TypeScript typing. + * + * @param props - The component props + * @param props.language - The programming language for syntax highlighting + * @param props.style - The style theme to apply + * @param props.customStyle - Additional custom styles to apply + * @param props.children - The code content to highlight + * @returns A syntax-highlighted code block + */ +const StyledSyntaxHighlighter: React.FC = ({ + language, + style, + customStyle, + children, +}) => { + return ( + + {children} + + ); +}; + SyntaxHighlighter.registerLanguage('javascript', javascript); SyntaxHighlighter.registerLanguage('solidity', solidity); @@ -34,7 +56,7 @@ const components = { code: ({ children, className }) => { const language = className?.replace('language-', '') || 'text'; return ( - - {children} - + {String(children)} + ); }, }; -export function Modal({ number }: { number: number }) { +/** + * Modal component for the PictureWithClickableNumbers diagram. + * + * @remarks + * This component renders an interactive modal that displays step-specific content + * when a numbered button is clicked. It uses React Spring for animations and + * Radix UI for the dialog functionality. + * + * @param props - The component props + * @param props.number - The step number (1-5) that determines which content to display + * @param props.customContent - Optional custom content component to override default step content + * @returns An SVG element with a clickable number that opens a modal with step-specific content + */ +export function Modal({ + number, + customContent, + coordinates, + children, + id = 'default', + diagramId = 'centralized-auction', +}: { + number: 1 | 2 | 3 | 4 | 5; + customContent?: React.ComponentType; + coordinates?: typeof defaultCoordinates; + children?: React.ReactNode; + id?: string; + diagramId?: string; +}) { + /** + * State to track whether the modal is currently open. + */ const [isOpen, setIsOpen] = useState(false); + + // Create a unique modal ID for this instance + const modalId = `modal-${id}-${number}`; + + /** + * Get the current color mode (light/dark) from Docusaurus. + */ const { isDarkTheme } = useColorMode(); - const stepContent = { - 1: step1Content, - 2: step2Content, - 3: step3Content, - 4: step4Content, - 5: step5Content, - }; - const StepContent = stepContent[number]; + /** + * Set the content component to use based on props + */ + const StepContent = + customContent || (() => ); + + /** + * Animation configuration for the modal content. + */ const transitions = useTransition(isOpen, { from: { opacity: 0, transform: 'scale(0.95)' }, enter: { opacity: 1, transform: 'scale(1)' }, @@ -69,6 +129,9 @@ export function Modal({ number }: { number: number }) { config: { tension: 300, friction: 20 }, }); + /** + * Animation configuration for the modal overlay. + */ const overlayTransitions = useTransition(isOpen, { from: { opacity: 0 }, enter: { opacity: 1 }, @@ -76,9 +139,16 @@ export function Modal({ number }: { number: number }) { config: { duration: 200 }, }); + /** + * Renders a code block with syntax highlighting. + * + * @param block - The code block to render + * @param index - The index of the code block + * @returns A styled code block with syntax highlighting + */ const renderCodeBlock = (block: CodeBlock, index: number) => (
- {block.code.trim()} - +
); @@ -102,11 +172,11 @@ export function Modal({ number }: { number: number }) { pointerEvents: 'all', }} > - + {children ||
+ } + className="modal-content-container" + /> + ); + } + + // Fallback to error message + return ( +
+

No content source specified

+

Please provide either customContent or set usePlugin to true.

+
+ ); + }; + + return ( + <> + setIsOpen(true)} + style={{ + cursor: 'pointer', + pointerEvents: 'all', + }} + > + {children ||