diff --git a/packages/backend/src/content/tutorials/escapehatch.md b/packages/backend/src/content/tutorials/escapehatch.md new file mode 100644 index 000000000..054d88e21 --- /dev/null +++ b/packages/backend/src/content/tutorials/escapehatch.md @@ -0,0 +1,21 @@ +# Escape Hatch Explained + +For an introduction to StarkEx Explorer, see this guide: + +- [Introduction to StarkEx Explorer](/tutorials/introduction) + +Escape Hatch is a functionality available as the last resort for users to withdraw their funds from the StarkEx system to Ethereum. + +When the StarkEx system is operating normally but a user is unable to use the regular interface (e.g., an exchange's web page), they should initiate a Forced Action request, as described in a separate guide ([All about Forced Actions](/tutorials/forcedactions)), to attempt the withdrawal of their funds. If Forced Action is not honored within a predefined amount of time (configured by StarkEx, usually 7 or 14 days), either due to inaction or because the operator is not functioning, the StarkEx Explorer will detect this state and display an option in the website header to "Request Freeze" of the StarkEx system. + +## Exchange Freeze + +Freezing the StarkEx system can be initiated by any user if at least one Forced Action has not been honored by the StarkEx Operator within the predefined time. By clicking on the "Request Freeze" button and confirming the Metamask transaction, the entire StarkEx system will enter a frozen state, in which no off-chain (L2) operations can be performed (e.g., no trades), and users will only be able to request the withdrawal of their full balances. + +The StarkEx Explorer's interface will change accordingly, with the webpage header displaying an appropriate message on each page, and operations available to users will be altered as well. + +## Performing an Escape + +After visiting their User Page, users will find an "Escape" button next to their non-perpetual tokens in the Assets panel (the values of perpetual positions are automatically included in the collateral escape). Upon clicking this button, if a user's Ethereum address has not been registered on Ethereum (which implies that Ethereum contracts do not recognize which StarkKey belongs to that Ethereum address), they will need to register it via an Ethereum transaction. Next, the user will be presented with a description of the escape process and prompted to click on the "Initiate Escape" button. This action will prompt a Metamask transaction, which may incur significant cost due to the amount of data, specifically a Merkle Proof, which proves to the Ethereum smart contract that the user indeed owns the claimed assets as of the last state update. + +Once the transaction has been confirmed on Ethereum, the User Page will display a "Pending Escapes" section. By clicking on the "Finalize Escape" button next to an asset, the user will be prompted to send a transaction via Metamask, which will complete the transfer of funds to the user's Ethereum wallet. diff --git a/packages/backend/src/content/tutorials/forcedactions.md b/packages/backend/src/content/tutorials/forcedactions.md new file mode 100644 index 000000000..dab1cab9a --- /dev/null +++ b/packages/backend/src/content/tutorials/forcedactions.md @@ -0,0 +1,54 @@ +# All about Forced Actions + +For an introduction to the StarkEx Explorer, see this guide: + +- [Introduction to StarkEx Explorer](/tutorials/introduction) + +Forced Actions are special operations initiated via the Ethereum blockchain. They are emergency measures that **should not be used under normal conditions** due to their high cost and slow execution time. They are useful when: + +- the StarkEx system (e.g., an exchange) is operating normally, +- but the user cannot access the system's native interface (e.g., the exchange's website), for example, due to censorship. + +In such scenarios, forced actions provide an emergency exit route for users to withdraw their assets to Ethereum. + +If the StarkEx system is not operating normally (e.g., the operator's servers have been shut down completely), users will need to use the Escape Hatch, which is an expensive last-resort solution. + +### Forced Withdrawal + +Forced Withdrawals are used to withdraw user assets, except for "perpetual assets". If any perpetual positions are open, they must be closed via a "Forced Trade" before attempting a Forced Withdrawal. + +To initiate a Forced Withdrawal, users should: + +1. Open their User Page. +2. Click on the "Withdraw" button next to their token (collateral) entry in the "Assets" table. +3. Optional: If the user's Ethereum address has not been registered on Ethereum (meaning that Ethereum contracts do not recognize which StarkKey belongs to that Ethereum address), they will be prompted to do so via an Ethereum transaction. +4. Enter the requested amount to withdraw. StarkEx in Spot trading mode will only allow the withdrawal of the full balance. +5. Click on the "Prepare for withdrawal" button and send the requested transaction to Ethereum. + +The created Forced Withdrawal request will be visible on the User Page in the "Forced transactions" panel. + +After initiating a Forced Withdrawal, StarkEx operators have a predefined amount of time (usually 7 or 14 days) to process the requested withdrawal. When they do, the User Page will display a "Withdrawable assets" section with a "Withdraw now" button to trigger the final transfer of funds to the user's Ethereum account. + +If the StarkEx Operator does not honor the user's Forced Withdrawal request within the given time, the user will be able to trigger an Exchange Freeze and engage the Escape Hatch functionality, described in a separate guide ([Escape Hatch explained](/tutorials/escapehatch)). + +It is important to note that users can manually initiate Forced Withdrawals with incorrect amounts. Such requests will be processed by the StarkEx system, but due to their invalid data, they will not trigger the final withdrawal. + +### Forced Trades + +Forced Trades are required to close open perpetual positions. + +To initiate a Forced Trade, users should: + +1. Open their User Page. +2. Click on the "Close" button next to their perpetual asset entry in the "Assets" table. If the user's position is "long", this would trigger a "Sell" trade, or a "Buy" if the position is "short". +3. Optional: If the user's Ethereum address has not been registered on Ethereum (meaning that Ethereum contracts do not recognize which StarkKey belongs to that Ethereum address), they will be prompted to do so via an Ethereum transaction. +4. Enter the desired trade data (perpetual amount and price). +5. Click on the "Create buy/sell offer" button, which will require the user to sign with their MetaMask wallet. + +Such a trade will be visible on the StarkEx Explorer Home Page and on the user's User Page in the "Offers" panel. + +Offers are internal to the StarkEx Explorer and are not visible on Ethereum or within the StarkEx system. + +An offer needs to be accepted by a counterpart via interaction with the offer on the Explorer. When a user "Accepts" the offer, the original creator can either cancel or approve it by sending a Forced Withdrawal transaction to Ethereum. + +Just like with the Forced Withdrawal process, the StarkEx Operator has limited time to honor this trade. If they do, the position will disappear from the user's User Page, and the collateral asset balance will be updated accordingly. Otherwise, the user will be able to use the Escape Hatch functionality. diff --git a/packages/backend/src/content/tutorials/introduction.md b/packages/backend/src/content/tutorials/introduction.md new file mode 100644 index 000000000..ff2b890d4 --- /dev/null +++ b/packages/backend/src/content/tutorials/introduction.md @@ -0,0 +1,40 @@ +# Introduction to StarkEx Explorer + +StarkEx Explorer is an open-source tool with a Web interface that allows users to independently download, verify, and browse data published to Ethereum by StarkEx systems. Additionally, it provides an interface to perform so-called "Forced Actions" and trigger "Escape Hatch" functionalities, which are the main guarantees of self-custody of funds. + +## What is StarkEx + +StarkEx is an Ethereum Layer 2 (L2) system targeting DeFi and trading applications. While most operations (like trading and creating orders) are created and executed off-chain for speed and low cost, users' balances are periodically hashed (in the form of a "Merkle Root") and published to Ethereum. If StarkEx operates in a "rollup mode," changes to users' balances are also published to Ethereum. In "validium" mode, that data is published to a set of trusted operators, known as the Data Availability Committee (DAC). Although L2 transactions are not published to Ethereum, their validity is ensured via validity proofs (specifically STARK Zero-Knowledge proofs) which are published to Ethereum. This architecture allows users, in case of emergency (such as censorship or exchange shutdown), to independently and trustlessly withdraw their assets to Ethereum using one of two mechanisms: + +- Forced Actions - operations that must be included by StarkEx operators within a limited amount of time +- Escape Hatch - direct interaction with Ethereum contracts if Forced Actions are not honored by StarkEx operators + +This guide will explore how the StarkEx Explorer can be used to browse data published by StarkEx systems. + +To learn how it helps users ensure that their funds are safe and trades are valid, see: + +- [Accessing User Page](/tutorials/userpage) + +To learn how to perform Forced Actions and trigger Escape Hatch operations, see the following guides: + +- [All about Forced Actions](/tutorials/forcedactions) +- [Escape Hatch Explained](/tutorials/escapehatch) + +## Understanding Data Available on StarkEx Explorer + +A notable feature of the StarkEx system is that users are identified by their StarkKey, not their Ethereum address, although they are related. Specifically, forced actions and the escape hatch require a user's StarkKey to be explicitly mapped to their Ethereum address. This can happen automatically or via user action at any time, depending on the decision made by the StarkEx operator. + +### State Updates + +Off-chain (L2) operations performed on the StarkEx system update the system state (which includes users' balances). For instance, when a user trades with another user, their balances change accordingly. After some time (typically a few hours, but this depends on the StarkEx Operator), a "State Update" is published to Ethereum (and to the DAC in validium mode). It includes the _changes_ that were made to users' balances since the last published state update. + +Users can browse all published state updates in the "State Updates" table. Clicking on any given state update displays its details, which includes the Balance Changes for each StarkKey. It's important to remember that these are not actual transactions but the total change that occurred since the previous state update. Specifically, a user could have performed multiple operations in the time between two state updates, but only the final difference between the two will be presented in the Balance Changes table. + +### User Page + +Clicking on a specific Balance Change entry or searching for an existing StarkKey or previously mapped Ethereum address presents the "User Page," which displays: + +- Assets - the balances of the user's assets **during the last state update**. This means that the most recent balance changes that occurred after the last state update will not be reflected on the Explorer. This is intentional, because only balances published to Ethereum will be considered if an emergency Escape Hatch functionality needs to be triggered. +- Balance Changes - updates to users' balances in past state updates. It's important to remember that these are not single operations performed by the user, but their cumulative effect between subsequent state updates. + +Forced Transactions and Offers are described in a separate guide ([All about Forced Actions](/tutorials/forcedactions)). The Transactions (L2) panel is an optional feature available via custom integration with the StarkEx operator, but the validity of that data cannot be verified. diff --git a/packages/backend/src/content/tutorials/userpage.md b/packages/backend/src/content/tutorials/userpage.md new file mode 100644 index 000000000..f64f95dca --- /dev/null +++ b/packages/backend/src/content/tutorials/userpage.md @@ -0,0 +1,15 @@ +# Accessing User Page + +For an introduction to StarkEx Explorer, see this guide: + +- [Introduction to StarkEx Explorer](/tutorials/introduction) + +Accessing the user page is possible in multiple ways: + +- If the user knows their StarkKey, they can simply search for it using the search box. +- If the user knows their Ethereum address, they can also search for it, but only if that address was previously **registered as an owner of a StarkKey**. On some exchanges, this process happens automatically, but due to its high cost, it is currently discouraged. +- Connecting via MetaMask to calculate their StarkKey using their Ethereum account. + +## Connecting via MetaMask + +When a user clicks on the "Connect wallet" button and approves the connection in their MetaMask wallet, the Explorer checks if the user's Ethereum address is already "registered," i.e., mapped to a StarkKey. If it's not, the user will be presented with an option to "Recover StarkKey." This operation is free of cost. By clicking on the "Recover" button, the user will be presented with a "Signature request" by the MetaMask interface. It is important to read the message that is supposed to be signed—it should be a sign-on request to an exchange. By "clicking" on the "Sign" button, the user's StarkKey will be calculated based on their Ethereum account (using the private key signature), and the user will be redirected to their User Page. diff --git a/packages/backend/src/core/TutorialService.ts b/packages/backend/src/core/TutorialService.ts index 7e4548c7a..2a4136350 100644 --- a/packages/backend/src/core/TutorialService.ts +++ b/packages/backend/src/core/TutorialService.ts @@ -1,22 +1,25 @@ import { HomeTutorialEntry } from '@explorer/frontend' -import fs from 'fs' export class TutorialService { getTutorials(): HomeTutorialEntry[] { - try { - const files = fs.readdirSync('src/content/tutorials') - return files.map((filename) => this.toTutorialEntry(filename)) - } catch { - return [] - } - } - - private toTutorialEntry(filename: string): HomeTutorialEntry { - const filenameWithoutExt = filename.replace('.md', '') - return { - title: filenameWithoutExt.replaceAll('-', ' '), - imageUrl: `/images/${filenameWithoutExt}.jpg`, - slug: filenameWithoutExt.toLowerCase(), - } + const tutorials: HomeTutorialEntry[] = [ + { + title: 'Introduction to StarkEx Explorer', + slug: 'introduction', + }, + { + title: 'Accessing User Page', + slug: 'userpage', + }, + { + title: 'All about Forced Actions', + slug: 'forcedactions', + }, + { + title: 'Escape Hatch explained', + slug: 'escapehatch', + }, + ] + return tutorials } } diff --git a/packages/backend/src/utils/markdown/getHtmlFromMarkdown.ts b/packages/backend/src/utils/markdown/getHtmlFromMarkdown.ts index 3bec0cee1..fa379128e 100644 --- a/packages/backend/src/utils/markdown/getHtmlFromMarkdown.ts +++ b/packages/backend/src/utils/markdown/getHtmlFromMarkdown.ts @@ -12,7 +12,6 @@ export function getHtmlFromMarkdown(filePath: string) { $('a').each(function () { const $el = $(this) $el.attr('rel', 'noopener noreferrer') - $el.attr('target', '_blank') }) $('h1, h2, h3, h4, h5, h6').each(function () { const $el = $(this) diff --git a/packages/frontend/src/preview/data/tutorial.ts b/packages/frontend/src/preview/data/tutorial.ts index 8dbe72676..c4b0e5202 100644 --- a/packages/frontend/src/preview/data/tutorial.ts +++ b/packages/frontend/src/preview/data/tutorial.ts @@ -137,23 +137,19 @@ line 3 of code export const tutorials: HomeTutorialEntry[] = [ { - title: 'Learn how to use StarkEx Explorer efficiently', - imageUrl: '/images/tutorial.jpg', - slug: 'learn-how-to-use-starkex-explorer-efficiently', + title: 'Introduction to StarkEx Explorer', + slug: 'introduction', }, { - title: 'All about forced transactions', - imageUrl: '/images/tutorial.jpg', - slug: 'all-about-forced-transactions', + title: 'Accessing User Page', + slug: 'userpage', }, { - title: 'Stark key registration', - imageUrl: '/images/tutorial.jpg', - slug: 'stark-key-registration', + title: 'All about Forced Actions', + slug: 'forcedactions', }, { - title: 'Escape hatches explained', - imageUrl: '/images/tutorial.jpg', - slug: 'escape-hatch-explained', + title: 'Escape Hatch explained', + slug: 'escapehatch', }, ] diff --git a/packages/frontend/src/static/images/starkex-logo.png b/packages/frontend/src/static/images/starkex-logo.png new file mode 100644 index 000000000..7c5e59ae0 Binary files /dev/null and b/packages/frontend/src/static/images/starkex-logo.png differ diff --git a/packages/frontend/src/view/pages/home/components/HomeSpotlightArticle.tsx b/packages/frontend/src/view/pages/home/components/HomeSpotlightArticle.tsx index 82fd0d20d..3547f2be4 100644 --- a/packages/frontend/src/view/pages/home/components/HomeSpotlightArticle.tsx +++ b/packages/frontend/src/view/pages/home/components/HomeSpotlightArticle.tsx @@ -25,10 +25,7 @@ export function HomeSpotlightArticle(props: HomeSpotlightArticleProps) { Read now - + ) } diff --git a/packages/frontend/src/view/pages/home/components/HomeTutorials.tsx b/packages/frontend/src/view/pages/home/components/HomeTutorials.tsx index 90a1fe176..e293141fb 100644 --- a/packages/frontend/src/view/pages/home/components/HomeTutorials.tsx +++ b/packages/frontend/src/view/pages/home/components/HomeTutorials.tsx @@ -8,7 +8,6 @@ import { SectionHeading } from '../../../components/SectionHeading' export interface HomeTutorialEntry { title: string - imageUrl: string slug: string } interface HomeTutorialsProps { @@ -37,11 +36,7 @@ export function HomeTutorials(props: HomeTutorialsProps) { href={`/tutorials/${tutorial.slug}`} className="group flex w-full items-center gap-4" > - +
{tutorial.title} diff --git a/packages/frontend/src/view/pages/tutorial/TutorialsPage.tsx b/packages/frontend/src/view/pages/tutorial/TutorialsPage.tsx index a31a5b75a..625a4f60c 100644 --- a/packages/frontend/src/view/pages/tutorial/TutorialsPage.tsx +++ b/packages/frontend/src/view/pages/tutorial/TutorialsPage.tsx @@ -30,9 +30,8 @@ export function TutorialsPage(props: TutorialsPageProps) { {props.tutorials.map((tutorial, i) => (