diff --git a/README.md b/README.md index a813aa8..0d3af1c 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Interactive Ethereum protocol explorer — hands on. Explore, visualize and understand Ethereum protocol changes (EIPs) by interacting with real library code running directly in the browser. -> **Note:** This project is under active development. Contributions and feedback are very welcome! +> **Status: Beta** — The project is fully functional, but the APIs of reusable components (E-Components, UI components) have not fully stabilized yet. Contributions are very welcome — expect some manual back-and-forth during review until the component interfaces settle. ## Quick Start diff --git a/cypress/e2e/eip7594.cy.ts b/cypress/e2e/eip7594.cy.ts deleted file mode 100644 index ce38f7d..0000000 --- a/cypress/e2e/eip7594.cy.ts +++ /dev/null @@ -1,36 +0,0 @@ -describe('EIP-7594/PeerDAS Tests', () => { - it('initialization', () => { - cy.visit('/eip-7594-peerdas-data-availability-sampling') - - // Basic component display - cy.contains('h1', 'Feel Your Protocol') - cy.contains('h3', 'Peer Data Availability Sampling') - - // Values from inital example - cy.get('#eip-7594-c textarea', { timeout: 10000 }).should( - 'contain.value', - '0a0001fbfc0084bd8494e56454b36510b0adc8aaa1b60', - ) - - // Select different example - cy.get('#eip-7594-c .e-select').click() - cy.get('#eip-7594-c [role="option"]').eq(1).click() - cy.get('#eip-7594-c textarea', { timeout: 10000 }).should( - 'contain.value', - '00000000000000000000000000000000000000000', - ) - - cy.get('.run-button').click() - let text = - '0xa522f1be9ec4a02fcb6998b10306e94331311ac29bcaaae357d8d7fbc087a04b5b66dd7fa84cbebabcc45202b8fee57f' - cy.get('#eip-7594-c .4844-7594-box') - .find('table tr:first td:nth-child(2)') - .should('contain.text', text, { timeout: 25000 }) - text = - '0x8bd1e6e38b2b54735c6f0102022510cf2abc2e4c6c5a437cba9831662c9f112e61e2a6ced8ce63b3de18cb9cc99ae21e' - cy.get('#eip-7594-c .4844-box').find('p').should('contain.text', text, { timeout: 25000 }) - text = - '0x8d90ed38068f3561132d9264db8c8dfb5237af24a6e28c3d4e72d3ad8d51d97be5733ddc382c9718822cb29ccc26364e' - cy.get('#eip-7594-c .7594-box').find('p:first').should('contain.text', text, { timeout: 25000 }) - }) -}) diff --git a/cypress/e2e/eip7883_Precompile_R.cy.ts b/cypress/e2e/eip7883_Precompile_R.cy.ts deleted file mode 100644 index 018616e..0000000 --- a/cypress/e2e/eip7883_Precompile_R.cy.ts +++ /dev/null @@ -1,107 +0,0 @@ -/** - * EIP-7823 is the representative EIP for the precompile component tests. - */ -describe('EIP-7823/Precompile Component Tests', () => { - let bytesLengthsExpected = - '0000000000000000000000000000000000000000000000000000000000000001' + - '0000000000000000000000000000000000000000000000000000000000000001' + - '0000000000000000000000000000000000000000000000000000000000000001' - const bytesValuesExpected = '030302' - let bytesExpected = bytesLengthsExpected + bytesValuesExpected - - it('initialization', () => { - cy.visit('/eip-7883-modexp-gas-cost-increase') - - cy.contains('h1', 'Feel Your Protocol') - cy.contains('h3', 'ModExp') - - cy.get('#eip-7883-c textarea', { timeout: 10000 }).should('have.value', bytesExpected) - cy.get('#eip-7883-c input').eq(0).should('have.value', '03') - cy.get('#eip-7883-c input').eq(1).should('have.value', '03') - cy.get('#eip-7883-c input').eq(2).should('have.value', '02') - - cy.get('.pre-hardfork').find('p').eq(0).should('include.text', '200 Gas') - cy.get('.post-hardfork').find('p').eq(0).should('include.text', '500 Gas') - }) - - it('values -> data', () => { - cy.visit('/eip-7883-modexp-gas-cost-increase') - - // Simple initial case - bytesExpected = bytesLengthsExpected + '040402' - cy.get('#eip-7883-c').within(() => { - cy.get('input').eq(0).clear() - cy.get('input').eq(1).clear() - cy.get('input').eq(2).clear() - cy.get('input').eq(0).type('04') - cy.get('input').eq(1).type('04') - cy.get('input').eq(2).type('02') - }) - cy.get('textarea').should('have.value', bytesExpected) - - // Slightly modified values case - bytesLengthsExpected = - '0000000000000000000000000000000000000000000000000000000000000002' + - '0000000000000000000000000000000000000000000000000000000000000002' + - '0000000000000000000000000000000000000000000000000000000000000002' - bytesExpected = bytesLengthsExpected + '040404040202' - cy.get('#eip-7883-c').within(() => { - cy.get('input').eq(0).type('04') - cy.get('input').eq(1).type('04') - cy.get('input').eq(2).type('02') - }) - cy.get('textarea').should('have.value', bytesExpected) - - cy.get('#eip-7883-c').within(() => { - // Gas changing example - cy.get('input').eq(0).type('040404040404040404040404040404040404040404') - cy.get('input').eq(1).type('040404040404040404040404040404040404040404') - cy.get('input').eq(2).type('02') - - cy.get('.pre-hardfork').find('p').eq(0).should('include.text', '534 Gas') - cy.get('.post-hardfork').find('p').eq(0).should('include.text', '2848 Gas') - }) - }) - - it('data -> values, URL sharing, EIP button', () => { - cy.visit('/eip-7883-modexp-gas-cost-increase') - - cy.get('#eip-7883-c').within(() => { - cy.window().then((win) => { - cy.stub(win, 'open').callsFake((url) => { - win.location.href = url // Redirect within the same tab - }) - }) - // data -> values - cy.get('textarea').clear() - cy.get('textarea').type(bytesExpected) // '040404040202' - cy.get('input').eq(0).should('have.value', '0404') - cy.get('input').eq(1).should('have.value', '0404') - cy.get('input').eq(2).should('have.value', '0202') - - // URL sharing - cy.get('.share-url-button').click() - cy.url().should('include', 'b=0404') - cy.url().should('include', 'e=0404') - cy.url().should('include', 'm=0202') - - cy.get('textarea').should('have.value', bytesExpected) - cy.get('input').eq(0).should('have.value', '0404') - cy.get('input').eq(1).should('have.value', '0404') - cy.get('input').eq(2).should('have.value', '0202') - - // examples - cy.get('.e-select').click() - cy.contains('[role="option"]', 'Simple').click() - cy.get('input').eq(0).should('have.value', '03') - cy.get('input').eq(1).should('have.value', '03') - cy.get('input').eq(2).should('have.value', '02') - - // EIP button - cy.get('.visit-exploration-button').invoke('removeAttr', 'target').click() - cy.origin('https://eips.ethereum.org', () => { - cy.url().should('eq', 'https://eips.ethereum.org/EIPS/eip-7883') - }) - }) - }) -}) diff --git a/cypress/e2e/eip7951.cy.ts b/cypress/e2e/eip7951.cy.ts deleted file mode 100644 index 342baf7..0000000 --- a/cypress/e2e/eip7951.cy.ts +++ /dev/null @@ -1,31 +0,0 @@ -describe('EIP-7951/secp256r1 Precompile Support', () => { - it('initialization', () => { - cy.visit('/eip-7951-secp256r1-precompile') - - // Basic component display - cy.contains('h1', 'Feel Your Protocol') - cy.contains('h3', 'secp256r1 Precompile Support') - - // Values from inital example - cy.get('#eip-7951-c textarea', { timeout: 10000 }).should( - 'contain.value', - '4dfb1eae8ed41e188b8a44a1109d982d01fc24bb85a933', - ) - const val = '3b91fedfb22f40063245c621036a040c159f02ae02e6d450ff9b53235e9232c4' - cy.get('#eip-7951-c input').eq(2).should('have.value', val) - - cy.get('.post-hardfork').find('p').eq(0).should('include.text', '6900 Gas') - - // Select different example - cy.get('#eip-7951-c .e-select').click() - cy.contains('[role="option"]', 'Invalid (Wycheproof), r value too large').click() - cy.get('#eip-7951-c textarea', { timeout: 10000 }).should( - 'contain.value', - '532eaabd9574880dbf76b9b8cc00832c20a6ec113d682299550d7a6e0f345e25', - ) - cy.get('#eip-7951-c input') - .eq(2) - .should('have.value', 'ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc63254e') - cy.get('.post-hardfork').find('p').eq(0).should('include.text', '6900 Gas') - }) -}) diff --git a/cypress/e2e/explorations.cy.ts b/cypress/e2e/explorations.cy.ts new file mode 100644 index 0000000..7d5a6a3 --- /dev/null +++ b/cypress/e2e/explorations.cy.ts @@ -0,0 +1,61 @@ +describe('EIP-7883 ModExp', () => { + it('loads and displays exploration content', () => { + cy.visit('/eip-7883-modexp-gas-cost-increase') + cy.contains('h1', 'Feel Your Protocol') + cy.contains('h3', 'ModExp') + cy.get('#eip-7883-c', { timeout: 10000 }).should('exist') + }) + + it('loads default example with inputs', () => { + cy.visit('/eip-7883-modexp-gas-cost-increase') + cy.get('#eip-7883-c textarea', { timeout: 10000 }).should('not.have.value', '') + cy.get('#eip-7883-c input').should('have.length.gte', 3) + }) + + it('example selector shows available options', () => { + cy.visit('/eip-7883-modexp-gas-cost-increase') + cy.get('#eip-7883-c .e-select', { timeout: 10000 }).click() + cy.get('[role="option"]').should('have.length.gte', 2) + }) +}) + +describe('EIP-7594 PeerDAS', () => { + it('loads and displays exploration content', () => { + cy.visit('/eip-7594-peerdas-data-availability-sampling') + cy.contains('h1', 'Feel Your Protocol') + cy.contains('h3', 'Peer Data Availability Sampling') + cy.get('#eip-7594-c', { timeout: 10000 }).should('exist') + }) + + it('loads default example with blob data', () => { + cy.visit('/eip-7594-peerdas-data-availability-sampling') + cy.get('#eip-7594-c textarea', { timeout: 10000 }).should('not.have.value', '') + }) + + it('example selector shows available options', () => { + cy.visit('/eip-7594-peerdas-data-availability-sampling') + cy.get('#eip-7594-c .e-select', { timeout: 10000 }).click() + cy.get('[role="option"]').should('have.length.gte', 2) + }) +}) + +describe('EIP-7951 secp256r1', () => { + it('loads and displays exploration content', () => { + cy.visit('/eip-7951-secp256r1-precompile') + cy.contains('h1', 'Feel Your Protocol') + cy.contains('h3', 'secp256r1 Precompile Support') + cy.get('#eip-7951-c', { timeout: 10000 }).should('exist') + }) + + it('loads default example with inputs', () => { + cy.visit('/eip-7951-secp256r1-precompile') + cy.get('#eip-7951-c textarea', { timeout: 10000 }).should('not.have.value', '') + cy.get('#eip-7951-c input').should('have.length.gte', 5) + }) + + it('example selector shows available options', () => { + cy.visit('/eip-7951-secp256r1-precompile') + cy.get('#eip-7951-c .e-select', { timeout: 10000 }).click() + cy.get('[role="option"]').should('have.length.gte', 2) + }) +}) diff --git a/cypress/e2e/sites.cy.ts b/cypress/e2e/sites.cy.ts index 35d3e6b..c757117 100644 --- a/cypress/e2e/sites.cy.ts +++ b/cypress/e2e/sites.cy.ts @@ -1,14 +1,14 @@ describe('Home', () => { it('loads and displays topics and explorations', () => { cy.visit('/') - cy.contains('h2', 'Fusaka').should('be.visible') + cy.contains('h2', 'Scaling').should('be.visible') cy.get('.exploration-c').should('have.length.gte', 1) }) it('topic link navigates to topic page', () => { cy.visit('/') - cy.contains('h2', 'Fusaka').closest('a').click() - cy.url().should('include', '/fusaka') + cy.contains('h2', 'Scaling').closest('a').click() + cy.url().should('include', '/scaling') }) it('exploration card navigates to exploration page', () => { @@ -18,19 +18,17 @@ describe('Home', () => { }) }) -describe('Topic (Fusaka)', () => { - it('loads all exploration widgets', () => { - cy.visit('/fusaka') - cy.get('#eip-7883-c', { timeout: 10000 }).should('exist') +describe('Topic (Scaling)', () => { + it('loads exploration widgets', () => { + cy.visit('/scaling') cy.get('#eip-7594-c', { timeout: 10000 }).should('exist') - cy.get('#eip-7951-c', { timeout: 10000 }).should('exist') }) }) describe('Imprint', () => { it('loads and shows key sections', () => { cy.visit('/imprint') - cy.contains('h3', 'CONTACT') + cy.contains('h3', 'ABOUT') cy.contains('h3', 'ACKNOWLEDGEMENTS') cy.contains('h3', 'DATA') }) diff --git a/dist/docs/404.html b/dist/docs/404.html index 5083a72..5f5ec4e 100644 --- a/dist/docs/404.html +++ b/dist/docs/404.html @@ -9,14 +9,14 @@ - +
- + \ No newline at end of file diff --git a/dist/docs/contributing/adding-an-exploration.html b/dist/docs/contributing/adding-an-exploration.html index d8459c0..c753e0d 100644 --- a/dist/docs/contributing/adding-an-exploration.html +++ b/dist/docs/contributing/adding-an-exploration.html @@ -9,27 +9,34 @@ - + - + - + -
Skip to content

Adding an Exploration

Each exploration lives in its own folder under src/explorations/ with a few files. This guide walks you through adding a new one.

Quick Overview

An exploration folder looks like this:

src/explorations/eip-XXXX/
-├── info.ts         # Metadata (required)
-├── MyC.vue         # Interactive widget (required)
-├── examples.ts     # Example presets (recommended)
-└── data/           # Optional data files

Step 1: Create the Folder

Create a new folder in src/explorations/ named after your exploration's ID. Use lowercase, hyphen-separated names:

bash
mkdir src/explorations/eip-XXXX

The ID can be eip-XXXX, erc-XXXX, or any descriptive identifier for research topics.

Step 2: Create info.ts

This file defines all the metadata for your exploration:

typescript
import type { Exploration } from '@/explorations/REGISTRY'
+    
Skip to content

Adding an Exploration

Each exploration lives in its own folder under src/explorations/ with a few files. This guide walks you through adding a new one.

Quick Overview

An exploration folder looks like this:

src/explorations/eip-XXXX/
+├── info.ts          # Metadata (required)
+├── MyC.vue          # Interactive widget (required)
+├── examples.ts      # Example presets (recommended)
+├── tests.spec.ts    # Unit tests (required)
+├── config.ts        # Precompile config (for precompile explorations)
+└── data/            # Optional data files

Step 1: Create the Folder

Create a new folder in src/explorations/ named after your exploration's ID. Use lowercase, hyphen-separated names:

bash
mkdir src/explorations/eip-XXXX

The ID can be eip-XXXX, erc-XXXX, or any descriptive identifier for research topics.

Step 2: Create info.ts

This file defines all the metadata for your exploration:

typescript
import type { Exploration } from '@/explorations/REGISTRY'
+import { Tag } from '@/explorations/TAGS'
 
 export const INFO: Exploration = {
   id: 'eip-XXXX',
   path: '/eip-XXXX-short-description',
   title: 'Human-Readable Title',
   infoURL: 'https://eips.ethereum.org/EIPS/eip-XXXX',
-  topic: 'fusaka',
+  topic: 'scaling',
+  timeline: 'fusaka',
+  tags: [Tag.EVM, Tag.GasCosts],
+  creatorName: 'YourName',
+  creatorURL: 'https://x.com/YourHandle',
   introText:
     '<b>What does this change?</b> ' +
     'A brief introduction to the protocol change.',
@@ -38,7 +45,7 @@
   poweredBy: [
     { name: 'EthereumJS', href: 'https://github.com/ethereumjs/ethereumjs-monorepo' },
   ],
-}

Field Reference

FieldRequiredDescription
idYesUnique identifier, matches the folder name
pathYesURL path for the exploration page
titleYesDisplay title
infoURLYesLink to the specification or reference material
topicYesTopic ID this exploration belongs to (e.g. 'fusaka')
imageNoImported image for topic overview display
introTextNoHTML-formatted introduction paragraph
usageTextNoHTML-formatted usage instructions
poweredByYesArray of { name, href } for library credits

Step 3: Create examples.ts

Define example presets that users can select from a dropdown:

typescript
import type { Examples } from '@/components/lib/general'
+}

Field Reference

FieldRequiredDescription
idYesUnique identifier, matches the folder name
pathYesURL path for the exploration page
titleYesDisplay title
infoURLYesLink to the specification or reference material
topicYesTopic ID this exploration belongs to. Must be one of the fixed set: scaling, privacy, ux, security, robustness, interoperability. Topics are static and not added via contributions — see Architecture for the full list.
timelineYesTimeline ID for this exploration (e.g. fusaka, glamsterdam, ready, research, ideas). See Architecture for details.
tagsYesArray of Tag enum values (max 3–4). Tags are broader technical concepts that must be reusable across explorations. New tags can be proposed — see Architecture for rules and the current list.
imageNoImported image for topic overview display
introTextNoHTML-formatted introduction paragraph
usageTextNoHTML-formatted usage instructions
creatorNameNoDisplay name of the exploration's creator
creatorURLNoURL to the creator's profile (X/Twitter, GitHub, etc.)
poweredByYesArray of { name, href } for library credits

Step 3: Create examples.ts

Define example presets that users can select from a dropdown:

typescript
import type { Examples } from '@/explorations/REGISTRY'
 
 export const examples: Examples = {
   basic: {
@@ -53,9 +60,9 @@
 import { ref } from 'vue'
 
 import { PP_BOX_LAYOUT } from '@/components/lib/layout'
-import ExamplesC from '@/components/ui/ExamplesC.vue'
-import HexDataInputC from '@/components/ui/HexDataInputC.vue'
-import PPBoxC from '@/components/ui/PPBoxC.vue'
+import ExamplesUIC from '@/eComponents/ui/ExamplesUIC.vue'
+import HexDataInputUIC from '@/eComponents/ui/HexDataInputUIC.vue'
+import ResultBoxUIC from '@/eComponents/ui/resultBox/ResultBoxUIC.vue'
 import ExplorationC from '@/explorations/ExplorationC.vue'
 import PoweredByC from '@/explorations/PoweredByC.vue'
 import { TOPICS } from '@/explorations/TOPICS'
@@ -88,47 +95,118 @@
   <ExplorationC explorationId="eip-XXXX" :exploration="exploration" :topic="topic">
     <template #content>
       <div>
-        <ExamplesC v-model="example" :examples="examples" :change="selectExample" />
-        <HexDataInputC v-model="data" rows="6" :formChange="onDataInputFormChange" />
+        <ExamplesUIC v-model="example" :examples="examples" :change="selectExample" />
+        <HexDataInputUIC v-model="data" rows="6" :formChange="onDataInputFormChange" />
         <!-- Your result display here -->
-        <PoweredByC :poweredBy="exploration.poweredBy" :topic="topic" />
+        <PoweredByC :poweredBy="exploration.poweredBy" :creatorName="exploration.creatorName" :creatorURL="exploration.creatorURL" />
       </div>
     </template>
   </ExplorationC>
-</template>

The ExplorationC wrapper renders the title, info link, intro text, and usage text from your info.ts. You provide the interactive content via the #content slot.

Option B: Precompile Interface E-Component

If your exploration is about a precompile, you can use the Precompile Interface E-Component and get a full-featured widget in ~30 lines:

vue
<script setup lang="ts">
-import { Hardfork } from '@ethereumjs/common'
-
-import PrecompileInterfaceEC from '@/eComponents/precompileInterfaceEC/PrecompileInterfaceEC.vue'
+</template>

The ExplorationC wrapper renders the title, info link, intro text, and usage text from your info.ts. You provide the interactive content via the #content slot.

Option B: Precompile Interface E-Component

If your exploration is about a precompile, you can use the Precompile Interface E-Component. It handles all input management while you provide the execution logic and result display:

First, define your precompile config in a separate config.ts file (this keeps the config testable):

typescript
// config.ts
 import type { PrecompileConfig } from '@/eComponents/precompileInterfaceEC/types'
 
-import { examples } from './examples'
-import { INFO as exploration } from './info'
-
-const config: PrecompileConfig = {
+export const config: PrecompileConfig = {
   explorationId: 'eip-XXXX',
-  precompileAddress: '0a',
-  preHardfork: Hardfork.Prague,
-  postHardfork: Hardfork.Osaka,
   defaultExample: 'basic',
   values: [
     { title: 'Input A', urlParam: 'a', expectedLen: 32n },
     { title: 'Input B', urlParam: 'b', expectedLen: 32n },
   ],
-}
+}

Then in MyC.vue:

vue
<script setup lang="ts">
+import { Hardfork } from '@ethereumjs/common'
+
+import PrecompileInterfaceEC from '@/eComponents/precompileInterfaceEC/PrecompileInterfaceEC.vue'
+import PrecompileInterfaceResultEC from '@/eComponents/precompileInterfaceEC/PrecompileInterfaceResultEC.vue'
+import { useStandardPrecompileRun } from '@/eComponents/precompileInterfaceEC/run'
+
+import { config } from './config'
+import { examples } from './examples'
+import { INFO as exploration } from './info'
+
+const { run, execResultPre, execResultPost } = useStandardPrecompileRun(
+  Hardfork.Prague, Hardfork.Osaka, '0a',
+)
 </script>
 
 <template>
-  <PrecompileInterfaceEC :config="config" :examples="examples" :exploration="exploration" />
-</template>

See Using E-Components for the full API reference.

Step 5: Register in the Registry

Add one import line to src/explorations/REGISTRY.ts:

typescript
import { INFO as eipXXXX } from './eip-XXXX/info'
+  <PrecompileInterfaceEC
+    :config="config" :examples="examples" :exploration="exploration" :run="run"
+  >
+    <template #result>
+      <div class="e-grid-double">
+        <PrecompileInterfaceResultEC v-model="execResultPre" title="Pre-Osaka" :left="true" />
+        <PrecompileInterfaceResultEC v-model="execResultPost" title="Post-Osaka" :left="false" />
+      </div>
+    </template>
+  </PrecompileInterfaceEC>
+</template>

The useStandardPrecompileRun helper covers the common EthereumJS pre/post hardfork comparison. For custom execution (different library, custom precompile, etc.), provide your own run function and #result slot — see Available E-Components for the full API reference.

Step 5: Register in the Registry

Add one import line to src/explorations/REGISTRY.ts:

typescript
import { INFO as eipXXXX } from './eip-XXXX/info'
 
 export const EXPLORATIONS: Explorations = {
   // ... existing explorations
   [eipXXXX.id]: eipXXXX,
-}

The router reads from EXPLORATIONS and automatically creates the route — no manual route configuration needed.

Step 6: Install Dependencies

If your widget needs additional libraries, install them:

bash
npm install some-library

Import libraries only in your MyC.vue — never in shared code. This keeps each exploration's dependencies isolated via Vite's code splitting.

If you need a custom library fork (e.g. with experimental features), see Library Forks.

Step 7: Verify

bash
npm run dev          # check your exploration locally
+}

The router reads from EXPLORATIONS and automatically creates the route — no manual route configuration needed.

Step 6: Install Dependencies

If your widget needs additional libraries, install them:

bash
npm install some-library

Import libraries only in your MyC.vue — never in shared code. This keeps each exploration's dependencies isolated via Vite's code splitting.

If you need a custom library fork (e.g. with experimental features), see Library Forks.

Step 7: Add Tests

Each exploration should have a tests.spec.ts file in its folder. Tests verify that your exploration's metadata, examples, and config are correct.

What to Test

All explorations should test:

  • info.ts — correct id, path, topic, and poweredBy
  • examples.ts — each example has the right number of values, valid hex data, and a non-empty title

Precompile explorations should additionally test:

  • config.tsdefaultExample exists in examples, value field count and URL params match expectations
  • assembleData/parseData — if defined, verify they produce correct output and are inverse operations

Example: Custom Exploration Test

typescript
import { describe, expect, it } from 'vitest'
+
+import { examples } from '../examples'
+import { INFO } from '../info'
+
+describe('EIP-XXXX Exploration', () => {
+  describe('info', () => {
+    it('has correct metadata', () => {
+      expect(INFO.id).toBe('eip-XXXX')
+      expect(INFO.path).toContain('eip-XXXX')
+      expect(INFO.topic).toBe('scaling')
+      expect(INFO.poweredBy.length).toBeGreaterThan(0)
+    })
+  })
+
+  describe('examples', () => {
+    it('each example has valid hex data', () => {
+      const hexRegex = /^[0-9a-f]+$/i
+      for (const [key, ex] of Object.entries(examples)) {
+        for (const val of ex.values) {
+          expect(val, `Value in "${key}" should be valid hex`).toMatch(hexRegex)
+        }
+      }
+    })
+  })
+})

Example: Precompile Exploration Test

typescript
import { describe, expect, it } from 'vitest'
+
+import { config } from '../config'
+import { examples } from '../examples'
+import { INFO } from '../info'
+
+describe('EIP-XXXX Exploration', () => {
+  describe('info', () => {
+    it('has correct metadata', () => {
+      expect(INFO.id).toBe('eip-XXXX')
+      expect(INFO.topic).toBe('scaling')
+    })
+  })
+
+  describe('config', () => {
+    it('references a valid default example', () => {
+      expect(examples[config.defaultExample]).toBeDefined()
+    })
+
+    it('has correct number of value fields', () => {
+      expect(config.values).toHaveLength(2)
+    })
+  })
+
+  describe('examples', () => {
+    it('each example has the right number of values', () => {
+      const editableCount = config.values.filter((v) => v.urlParam).length
+      for (const [key, ex] of Object.entries(examples)) {
+        expect(ex.values, `Example "${key}"`).toHaveLength(editableCount)
+      }
+    })
+  })
+})

Running Tests

bash
npx vitest run                              # run all unit tests
+npx vitest run src/explorations/eip-XXXX    # run tests for one exploration

Step 8: Verify

bash
npm run dev          # check your exploration locally
 npm run lf           # format + lint
 npm run type-check   # TypeScript check
-npm run build        # verify production build

Checklist

  • [ ] Created src/explorations/<id>/info.ts with metadata
  • [ ] Created src/explorations/<id>/MyC.vue with interactive widget
  • [ ] Created src/explorations/<id>/examples.ts with example presets
  • [ ] Added import and entry in src/explorations/REGISTRY.ts
  • [ ] Installed library dependencies (if needed)
  • [ ] Linting and type checking pass
  • [ ] Production build succeeds

This project and its documentation are under active development.

- +npm run build # verify production build

Checklist

  • [ ] Created src/explorations/<id>/info.ts with metadata
  • [ ] Created src/explorations/<id>/MyC.vue with interactive widget
  • [ ] Created src/explorations/<id>/examples.ts with example presets
  • [ ] Created src/explorations/<id>/tests.spec.ts with unit tests
  • [ ] Added import and entry in src/explorations/REGISTRY.ts
  • [ ] Installed library dependencies (if needed)
  • [ ] All unit tests pass
  • [ ] Linting and type checking pass
  • [ ] Production build succeeds

This project and its documentation are under active development.

+ \ No newline at end of file diff --git a/dist/docs/contributing/how-to-contribute.html b/dist/docs/contributing/how-to-contribute.html index 33e3c9c..b8f1cf0 100644 --- a/dist/docs/contributing/how-to-contribute.html +++ b/dist/docs/contributing/how-to-contribute.html @@ -9,22 +9,22 @@ - + - + - + -
Skip to content

How to Contribute

Contributions are what make Feel Your Protocol useful. Whether you are adding a brand-new exploration, polishing an existing one, or improving the shared components — every contribution helps the Ethereum community understand protocol changes better.

Ways to Contribute

Add a New Exploration

This is the most impactful contribution. Each exploration is a self-contained folder with metadata and an interactive widget. The Adding an Exploration guide walks you through it step by step.

Improve an Existing Exploration

  • Better examples and presets
  • UI/UX improvements
  • Bug fixes
  • More informative intro and usage texts

Build or Improve E-Components

E-Components are reusable Ethereum-specific components (e.g. a precompile interface). If you spot a pattern shared across explorations, it might be a candidate for a new E-Component.

Improve Documentation

Fix typos, add guides, clarify explanations. Documentation lives in the docs/ folder as standard markdown files. Preview locally with:

bash
npm run docs:dev

Report Issues

Found a bug or have a suggestion? Open an issue on GitHub.

Development Workflow

1. Setup

bash
git clone https://github.com/feelyourprotocol/website.git
+    
Skip to content

How to Contribute

Contributions are what make Feel Your Protocol useful. Whether you are adding a brand-new exploration, polishing an existing one, or improving the shared components — every contribution helps the Ethereum community understand protocol changes better.

Ways to Contribute

Add a New Exploration

This is the most impactful contribution. Each exploration is a self-contained folder with metadata and an interactive widget. The Adding an Exploration guide walks you through it step by step.

Improve an Existing Exploration

  • Better examples and presets
  • UI/UX improvements
  • Bug fixes
  • More informative intro and usage texts

Build or Improve E-Components

E-Components are reusable Ethereum-specific components (e.g. a precompile interface). If you spot a pattern shared across explorations, it might be a candidate for a new E-Component.

Improve Documentation

Fix typos, add guides, clarify explanations. Documentation lives in the docs/ folder as standard markdown files. Preview locally with:

bash
npm run docs:dev

Report Issues

Found a bug or have a suggestion? Open an issue on GitHub.

Development Workflow

1. Setup

bash
git clone https://github.com/feelyourprotocol/website.git
 cd website
 npm install

2. Develop

bash
npm run dev          # start dev server

3. Verify

Before submitting a PR, run all quality checks:

bash
npm run lf           # format + lint (auto-fix)
 npm run type-check   # TypeScript type checking
 npx vitest run       # unit tests (single run)
-npm run test:e2e     # E2E tests

4. Submit

  • Fork the repository and create a feature branch
  • Make your changes
  • Ensure all checks pass
  • Submit a pull request with a clear description of what you changed and why

What Goes Where

What you are working onWhere it lives
A new explorationsrc/explorations/<id>/
Exploration metadatasrc/explorations/<id>/info.ts
Interactive widgetsrc/explorations/<id>/MyC.vue
Example presetssrc/explorations/<id>/examples.ts
Exploration registrysrc/explorations/REGISTRY.ts
E-Componentssrc/eComponents/<name>EC/
Shared UI componentssrc/components/ui/
Shared utilitiessrc/components/lib/
Unit testssrc/views/__tests__/ (or co-located __tests__/)
E2E testscypress/e2e/
Documentationdocs/

Further Reading

This project and its documentation are under active development.

- +npm run test:e2e # E2E tests

4. Submit

  • Fork the repository and create a feature branch
  • Make your changes
  • Ensure all checks pass
  • Submit a pull request with a clear description of what you changed and why

What Goes Where

What you are working onWhere it lives
A new explorationsrc/explorations/<id>/
Exploration metadatasrc/explorations/<id>/info.ts
Interactive widgetsrc/explorations/<id>/MyC.vue
Example presetssrc/explorations/<id>/examples.ts
Exploration registrysrc/explorations/REGISTRY.ts
E-Componentssrc/eComponents/<name>EC/
Shared UI componentssrc/eComponents/ui/
Unit testssrc/views/__tests__/ (or co-located __tests__/)
E2E testscypress/e2e/
Documentationdocs/

Further Reading

This project and its documentation are under active development.

+ \ No newline at end of file diff --git a/dist/docs/guide/architecture.html b/dist/docs/guide/architecture.html index 5fb4013..39a5312 100644 --- a/dist/docs/guide/architecture.html +++ b/dist/docs/guide/architecture.html @@ -9,29 +9,30 @@ - + - + - + -
Skip to content

Architecture

Overview

Feel Your Protocol is a Vue 3 application built with Vite. The core idea is simple: each Ethereum protocol change gets its own interactive widget that runs real library code in the browser.

Tech Stack

Content Model

Content is organized around two concepts:

Explorations

The core content unit. Each exploration represents an interactive widget for a protocol change — EIPs, ERCs, or research topics. Explorations live in src/explorations/, one folder per exploration:

src/explorations/eip-7883/
-├── info.ts         # Metadata: id, title, path, topic, introText, poweredBy, …
+    
Skip to content

Architecture

Overview

Feel Your Protocol is a Vue 3 application built with Vite. The core idea is simple: each Ethereum protocol change gets its own interactive widget that runs real library code in the browser.

Tech Stack

Content Model

Content is organized around three taxonomies:

Explorations

The core content unit. Each exploration represents an interactive widget for a protocol change — EIPs, ERCs, or research topics. Explorations live in src/explorations/, one folder per exploration:

src/explorations/eip-7883/
+├── info.ts         # Metadata: id, title, path, topic, timeline, tags, poweredBy, …
 ├── MyC.vue         # The interactive widget
-└── examples.ts     # Example presets for the widget

Each info.ts exports a const INFO object typed as Exploration. The REGISTRY.ts imports all INFO constants and assembles them into the EXPLORATIONS dictionary. The router reads from this dictionary to automatically create routes — no manual route registration needed.

Topics

Topics group explorations by theme (e.g. "Fusaka" for an upcoming hardfork). Each exploration belongs to exactly one topic via the topic field in its info.ts. Topics are defined in src/explorations/TOPICS.ts.

E-Components

E-Components are reusable Ethereum-specific components that encapsulate common patterns across explorations. They live in src/eComponents/ and follow a naming convention: folder and component names are post-fixed with EC.

The first E-Component is precompileInterfaceEC, which provides a complete precompile exploration interface — input parsing, hardfork comparison, result display — as a single component backed by a composable:

src/eComponents/precompileInterfaceEC/
+└── examples.ts     # Example presets for the widget

Each info.ts exports a const INFO object typed as Exploration. The REGISTRY.ts imports all INFO constants and assembles them into the EXPLORATIONS dictionary. The router reads from this dictionary to automatically create routes — no manual route registration needed.

Topics

Topics are the high-level strategic pillars that group explorations by theme. Each exploration belongs to exactly one topic via the topic field in its info.ts. Topics are defined in src/explorations/TOPICS.ts.

Topics are a static, curated set — they are not meant to be added as part of regular contributions. The current topics are:

IDTitleDescription
scalingScalingData availability, throughput, and L2 enablement
privacyPrivacyZK-proofs, homomorphic encryption, private mempools
uxUXAccount abstraction, wallet infrastructure, signature schemes
securitySecurityValidator incentives, cryptographic agility, MEV mitigations
robustnessRobustnessGas cost accuracy, EVM semantics hardening, spec clarity
interoperabilityInteroperabilityCross-chain standards, bridge infrastructure, signature scheme support

When adding an exploration, pick the topic that best reflects the primary concern of the protocol change.

Timeline

Timeline is a taxonomy that combines two dimensions: how settled an idea is (from early mention to finalized spec) and where it sits relative to mainnet Ethereum (specific hardfork vs. general readiness stage). Each exploration belongs to exactly one timeline entry via the timeline field in its info.ts. Timeline entries are defined in src/explorations/TIMELINE.ts.

Hardfork entries are named after their Ethereum community event city of origin (e.g. "Fusaka" from Osaka, "Glamsterdam" from Amsterdam). The non-hardfork categories (Ready, Research, Ideas) are static. New hardfork entries can be added as Ethereum's upgrade schedule evolves.

Tags

Tags enrich navigation by adding broader Ethereum technical concepts, protocol-relevant areas, or general technology topics from the blockchain space. Each exploration can have up to 3–4 tags. Tags are defined as a TypeScript enum in src/explorations/TAGS.ts.

Tags grow with contributions — unlike topics and timeline, new tags can be proposed when adding an exploration. They must follow these rules:

RuleExample
Must be reusable beyond a single explorationEVM ✅ — EIP-7883
Short form preferredEVM ✅ — Ethereum Virtual Machine
No redundancy with existing tagsGas Costs exists → don't add Gas
When in doubt, choose the more generic conceptGas Costs ✅ — Gas Increases

Format: Enum keys use CamelCase (GasCosts), all-caps for abbreviations (EVM). Members must be sorted alphabetically (enforced by lint).

E-Components

E-Components are reusable Ethereum-specific components that encapsulate common patterns across explorations. They live in src/eComponents/ and follow a naming convention: folder and component names are post-fixed with EC.

The first E-Component is precompileInterfaceEC, which provides a complete precompile exploration interface — input parsing, hardfork comparison, result display — as a single component backed by a composable:

src/eComponents/precompileInterfaceEC/
 ├── PrecompileInterfaceEC.vue      # Full-featured precompile exploration template
 ├── PrecompileInterfaceResultEC.vue # Result display (pre/post hardfork comparison)
 ├── PrecompileValueInputEC.vue     # Value input with byte length validation
 ├── usePrecompileState.ts          # Composable: all state and logic
 ├── types.ts                       # PrecompileConfig and PrecompileValueDef interfaces
-└── run.ts                         # EVM precompile execution utility

Using the Precompile Interface E-Component, a precompile exploration widget can be as short as 30 lines — just a config object and a single component tag. See Using E-Components for details.

UI Components

Generic UI components live in src/components/ui/ and are used across explorations:

ComponentPurpose
ExamplesCExample selector dropdown
HexDataInputCHex data input textarea with validation
PPBoxCResult display box with title
PPBoxErrorTextError message display
PPBoxInfoTextInformational message display
ActionButtonCAction button with tooltip
ButtonCIcon button with tooltip
HeadlineButtonCHeadline with action button
TooltipCGeneric tooltip wrapper

Shared utilities in src/components/lib/:

ModulePurpose
byteFormUtils.tsHex/byte conversion and validation
general.tsShared types (e.g. Examples)
layout.tsTailwind CSS class constants for consistent layouts

Key Design Decisions

Folder-per-Exploration

Each exploration is fully self-contained in its own folder. This means:

  • Contributors can focus on a single folder
  • Adding a new exploration requires creating a folder and adding one import to REGISTRY.ts
  • Each exploration's dependencies are isolated

Dynamic Views

There are no static per-exploration or per-topic view files. Instead:

  • ExplorationView.vue dynamically loads the correct MyC.vue using import.meta.glob() and defineAsyncComponent() based on the route name
  • TopicView.vue dynamically lists all explorations belonging to a topic
  • HomeView.vue dynamically renders all topics defined in TOPICS.ts

Route-Level Code Splitting

Each exploration is a separate chunk that is loaded on demand. Users only download the libraries needed for the page they visit. This is achieved via import.meta.glob() for lazy loading:

typescript
const componentModules = import.meta.glob('../explorations/*/MyC.vue')
+└── run.ts                         # EVM precompile execution utility

Using the Precompile Interface E-Component, a precompile exploration widget can be as short as 30 lines — just a config object and a single component tag. See Using E-Components for details.

UI Components

Generic UI components live in src/eComponents/ui/ alongside the E-Components they serve. These are reusable building blocks available for any exploration or E-Component:

ComponentPurpose
ExamplesUICExample selector dropdown
HexDataInputUICHex data input textarea
ResultBoxUICResult display box with title, info text, and error text
ActionButtonUICAsync action button with loading state and tooltip
ButtonUICIcon button with tooltip
TooltipUICCSS tooltip wrapper

Import them from @/eComponents/ui/:

typescript
import ResultBoxUIC from '@/eComponents/ui/resultBox/ResultBoxUIC.vue'
+import ExamplesUIC from '@/eComponents/ui/ExamplesUIC.vue'

Key Design Decisions

Folder-per-Exploration

Each exploration is fully self-contained in its own folder. This means:

  • Contributors can focus on a single folder
  • Adding a new exploration requires creating a folder and adding one import to REGISTRY.ts
  • Each exploration's dependencies are isolated

Dynamic Views

There are no static per-exploration or per-topic view files. Instead:

  • ExplorationView.vue dynamically loads the correct MyC.vue using import.meta.glob() and defineAsyncComponent() based on the route name
  • TopicView.vue dynamically lists all explorations belonging to a topic (or all explorations on the /all route), with URL query parameter filtering by timeline and tag
  • HomeView.vue dynamically renders all active topics, a tag cloud, and a timeline navigation

Route-Level Code Splitting

Each exploration is a separate chunk that is loaded on demand. Users only download the libraries needed for the page they visit. This is achieved via import.meta.glob() for lazy loading:

typescript
const componentModules = import.meta.glob('../explorations/*/MyC.vue')
 const ExplorationComponent = defineAsyncComponent(
   componentModules[`../explorations/${explorationId}/MyC.vue`]
 )

Testing Strategy

The project uses a hybrid testing approach:

  • Unit tests (Vitest + Vue Test Utils) for component rendering, content verification, and UI logic — fast and focused
  • E2E tests (Cypress) as lean smoke tests for critical navigation flows and page-level integration

Unit tests live alongside their components in __tests__/ folders. E2E tests are consolidated in cypress/e2e/.

This project and its documentation are under active development.

- + \ No newline at end of file diff --git a/dist/docs/guide/getting-started.html b/dist/docs/guide/getting-started.html index 40a88f8..e7c8272 100644 --- a/dist/docs/guide/getting-started.html +++ b/dist/docs/guide/getting-started.html @@ -9,16 +9,16 @@ - + - + - + -
Skip to content

Getting Started

What is Feel Your Protocol?

Feel Your Protocol is an interactive website that lets you explore Ethereum protocol changes hands on. Instead of just reading specifications, you can interact with real Ethereum library code running directly in the browser.

Each protocol change — called an Exploration — gets its own page with a dedicated interactive widget. Explorations cover EIPs, ERCs, and other research topics. They are grouped into Topics (e.g. "Fusaka" for the upcoming hardfork), making it easy to discover related protocol changes.

Prerequisites

  • Node.js v20.19+ or v22.12+
  • npm (comes with Node.js)

Setup

Clone the repository and install dependencies:

bash
git clone https://github.com/feelyourprotocol/website.git
+    
Skip to content

Getting Started

What is Feel Your Protocol?

Feel Your Protocol is an interactive website that lets you explore Ethereum protocol changes hands on. Instead of just reading specifications, you can interact with real Ethereum library code running directly in the browser.

Each protocol change — called an Exploration — gets its own page with a dedicated interactive widget. Explorations cover EIPs, ERCs, and protocol research. They are organized through three taxonomies: Topics (static strategic pillars like "Scaling" or "UX"), Timeline (maturity and hardfork placement like "Fusaka" or "Research"), and Tags (reusable technical concepts like "EVM" or "Precompiles") — making it easy to discover and navigate related protocol changes.

Prerequisites

  • Node.js v20.19+ or v22.12+
  • npm (comes with Node.js)

Setup

Clone the repository and install dependencies:

bash
git clone https://github.com/feelyourprotocol/website.git
 cd website
 npm install

Development

Start the website dev server:

bash
npm run dev

Start the docs dev server:

bash
npm run docs:dev

Quality Checks

bash
npm run lf           # format + lint (auto-fix)
 npm run lf:ci        # lint + format check (CI mode, no auto-fix)
@@ -29,17 +29,21 @@
 ├── src/
 │   ├── explorations/              # Explorations (the core content)
 │   │   ├── REGISTRY.ts            # Exploration registry and types
-│   │   ├── TOPICS.ts              # Topic definitions and types
+│   │   ├── TOPICS.ts              # Topic definitions and colors
+│   │   ├── TIMELINE.ts            # Timeline taxonomy (hardforks + maturity stages)
+│   │   ├── TAGS.ts                # Tag enum (technical concepts)
 │   │   ├── ExplorationC.vue       # Shared exploration wrapper component
-│   │   ├── PoweredByC.vue         # Shared "powered by" component
+│   │   ├── PoweredByC.vue         # Shared creator + "powered by" component
+│   │   ├── NoExplorationsC.vue    # Empty state component
 │   │   ├── eip-7594/              # One folder per exploration
-│   │   │   ├── info.ts            #   Metadata (title, description, links, …)
+│   │   │   ├── info.ts            #   Metadata (title, topic, timeline, tags, …)
 │   │   │   ├── MyC.vue            #   Interactive widget component
 │   │   │   ├── examples.ts        #   Example presets
 │   │   │   └── data/              #   Optional data files
 │   │   ├── eip-7883/
 │   │   └── eip-7951/
 │   ├── eComponents/               # Reusable Ethereum-specific components (E-Components)
+│   │   ├── ui/                    # Generic UI components (UIC)
 │   │   └── precompileInterfaceEC/ # Precompile interface E-Component
 │   │       ├── PrecompileInterfaceEC.vue
 │   │       ├── PrecompileInterfaceResultEC.vue
@@ -47,19 +51,20 @@
 │   │       ├── usePrecompileState.ts
 │   │       ├── types.ts
 │   │       └── run.ts
-│   ├── components/                # Shared UI components and utilities
-│   │   ├── ui/                    # Generic UI components
-│   │   └── lib/                   # Shared logic and utilities
+│   ├── libs/                      # Shared computation logic
+│   │   └── tagCloud.ts            # Tag cloud weight computation
 │   ├── views/                     # Route views
 │   │   ├── HomeView.vue
-│   │   ├── TopicView.vue
+│   │   ├── TopicView.vue          # Topic + /all view with filtering
 │   │   ├── ExplorationView.vue
+│   │   ├── TimelineNaviView.vue   # Timeline navigation component
+│   │   ├── TagCloudView.vue       # Tag cloud navigation component
 │   │   └── __tests__/             # Unit tests
 │   └── router/                    # Vue Router config
 ├── docs/                          # Documentation (VitePress)
 ├── cypress/                       # E2E tests
 └── .github/workflows/             # CI workflows

This project and its documentation are under active development.

- + \ No newline at end of file diff --git a/dist/docs/hashmap.json b/dist/docs/hashmap.json index 3a30be5..301df05 100644 --- a/dist/docs/hashmap.json +++ b/dist/docs/hashmap.json @@ -1 +1 @@ -{"contributing_adding-an-exploration.md":"Ahsvo_Ol","contributing_available-e-components.md":"CJ3WaJq0","contributing_code-conventions.md":"q8SsjSd8","contributing_e-components.md":"EgChjAzs","contributing_how-to-contribute.md":"XKvLYLkk","contributing_library-forks.md":"DYk1JQhp","contributing_styling.md":"BxYm3jg7","guide_architecture.md":"BSBE4DKA","guide_getting-started.md":"B-wiDt-S","index.md":"DpQvSWpN"} +{"contributing_adding-an-exploration.md":"iBUUBPTj","contributing_ai-assisted-development.md":"lydwZLlM","contributing_available-e-components.md":"B4Q_z4Hi","contributing_code-conventions.md":"C86y1S4H","contributing_e-components.md":"PhGEgitZ","contributing_how-to-contribute.md":"DneE9-kZ","contributing_library-forks.md":"DYk1JQhp","contributing_styling.md":"BKPSSvmJ","contributing_ui-components.md":"vHxMK_fH","guide_architecture.md":"B6eczQC1","guide_getting-started.md":"BjX7SsDe","index.md":"COXeFy5O"} diff --git a/dist/docs/index.html b/dist/docs/index.html index 4ac9ff6..dc29eb4 100644 --- a/dist/docs/index.html +++ b/dist/docs/index.html @@ -9,17 +9,17 @@ - + - + - + -
Skip to content

Feel Your ProtocolInteractive Ethereum Protocol Explorations

Explore, visualize and understand Ethereum protocol changes — hands on.

Want to contribute?

The fastest way to get started is to add a new exploration. Each one lives in a single folder with just two files — metadata and your interactive widget.

This project and its documentation are under active development.

- +
Skip to content

Feel Your ProtocolInteractive Ethereum Protocol Explorations

Explore, visualize and understand Ethereum protocol changes — hands on.

Beta — Contributions Welcome!

This project is in beta. Everything works, but the APIs of reusable components (E-Components, shared UI) are still evolving. That means contributions may require some manual coordination during review until the interfaces fully stabilize — we are happy to guide you through! The fastest way to get started is to add a new exploration.

This project and its documentation are under active development.

+ \ No newline at end of file diff --git a/dist/website/index.html b/dist/website/index.html index 1ba5325..8bddfbd 100644 --- a/dist/website/index.html +++ b/dist/website/index.html @@ -5,8 +5,8 @@ Feel Your Protocol - - + +
diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index d77dd90..872b29f 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -22,6 +22,7 @@ export default defineConfig({ text: 'Contributing', items: [ { text: 'How to Contribute', link: '/contributing/how-to-contribute' }, + { text: 'AI-Assisted Development', link: '/contributing/ai-assisted-development' }, { text: 'Adding an Exploration', link: '/contributing/adding-an-exploration' }, { text: 'UI Components', link: '/contributing/ui-components' }, { text: 'E-Components', link: '/contributing/e-components' }, diff --git a/docs/contributing/adding-an-exploration.md b/docs/contributing/adding-an-exploration.md index 7177b4f..cc6aeaa 100644 --- a/docs/contributing/adding-an-exploration.md +++ b/docs/contributing/adding-an-exploration.md @@ -8,10 +8,12 @@ An exploration folder looks like this: ``` src/explorations/eip-XXXX/ -├── info.ts # Metadata (required) -├── MyC.vue # Interactive widget (required) -├── examples.ts # Example presets (recommended) -└── data/ # Optional data files +├── info.ts # Metadata (required) +├── MyC.vue # Interactive widget (required) +├── examples.ts # Example presets (recommended) +├── tests.spec.ts # Unit tests (required) +├── config.ts # Precompile config (for precompile explorations) +└── data/ # Optional data files ``` ## Step 1: Create the Folder @@ -30,13 +32,18 @@ This file defines all the metadata for your exploration: ```typescript import type { Exploration } from '@/explorations/REGISTRY' +import { Tag } from '@/explorations/TAGS' export const INFO: Exploration = { id: 'eip-XXXX', path: '/eip-XXXX-short-description', title: 'Human-Readable Title', infoURL: 'https://eips.ethereum.org/EIPS/eip-XXXX', - topic: 'fusaka', + topic: 'scaling', + timeline: 'fusaka', + tags: [Tag.EVM, Tag.GasCosts], + creatorName: 'YourName', + creatorURL: 'https://x.com/YourHandle', introText: 'What does this change? ' + 'A brief introduction to the protocol change.', @@ -56,10 +63,14 @@ export const INFO: Exploration = { | `path` | Yes | URL path for the exploration page | | `title` | Yes | Display title | | `infoURL` | Yes | Link to the specification or reference material | -| `topic` | Yes | Topic ID this exploration belongs to (e.g. `'fusaka'`) | +| `topic` | Yes | Topic ID this exploration belongs to. Must be one of the fixed set: `scaling`, `privacy`, `ux`, `security`, `robustness`, `interoperability`. Topics are static and not added via contributions — see [Architecture](/guide/architecture#topics) for the full list. | +| `timeline` | Yes | Timeline ID for this exploration (e.g. `fusaka`, `glamsterdam`, `ready`, `research`, `ideas`). See [Architecture](/guide/architecture) for details. | +| `tags` | Yes | Array of `Tag` enum values (max 3–4). Tags are broader technical concepts that must be reusable across explorations. New tags can be proposed — see [Architecture](/guide/architecture#tags) for rules and the current list. | | `image` | No | Imported image for topic overview display | | `introText` | No | HTML-formatted introduction paragraph | | `usageText` | No | HTML-formatted usage instructions | +| `creatorName` | No | Display name of the exploration's creator | +| `creatorURL` | No | URL to the creator's profile (X/Twitter, GitHub, etc.) | | `poweredBy` | Yes | Array of `{ name, href }` for library credits | ## Step 3: Create `examples.ts` @@ -67,7 +78,7 @@ export const INFO: Exploration = { Define example presets that users can select from a dropdown: ```typescript -import type { Examples } from '@/components/lib/general' +import type { Examples } from '@/explorations/REGISTRY' export const examples: Examples = { basic: { @@ -134,7 +145,7 @@ await init() - +
@@ -145,37 +156,58 @@ The `ExplorationC` wrapper renders the title, info link, intro text, and usage t ### Option B: Precompile Interface E-Component -If your exploration is about a precompile, you can use the Precompile Interface E-Component and get a full-featured widget in ~30 lines: +If your exploration is about a precompile, you can use the Precompile Interface E-Component. It handles all input management while you provide the execution logic and result display: -```vue - ``` -See [Using E-Components](/contributing/e-components) for the full API reference. +The `useStandardPrecompileRun` helper covers the common EthereumJS pre/post hardfork comparison. For custom execution (different library, custom precompile, etc.), provide your own `run` function and `#result` slot — see [Available E-Components](/contributing/available-e-components) for the full API reference. ## Step 5: Register in the Registry @@ -204,7 +236,99 @@ Import libraries only in your `MyC.vue` — never in shared code. This keeps eac If you need a custom library fork (e.g. with experimental features), see [Library Forks](/contributing/library-forks). -## Step 7: Verify +## Step 7: Add Tests + +Each exploration should have a `tests.spec.ts` file in its folder. Tests verify that your exploration's metadata, examples, and config are correct. + +### What to Test + +**All explorations** should test: + +- `info.ts` — correct `id`, `path`, `topic`, and `poweredBy` +- `examples.ts` — each example has the right number of values, valid hex data, and a non-empty title + +**Precompile explorations** should additionally test: + +- `config.ts` — `defaultExample` exists in examples, value field count and URL params match expectations +- `assembleData`/`parseData` — if defined, verify they produce correct output and are inverse operations + +### Example: Custom Exploration Test + +```typescript +import { describe, expect, it } from 'vitest' + +import { examples } from '../examples' +import { INFO } from '../info' + +describe('EIP-XXXX Exploration', () => { + describe('info', () => { + it('has correct metadata', () => { + expect(INFO.id).toBe('eip-XXXX') + expect(INFO.path).toContain('eip-XXXX') + expect(INFO.topic).toBe('scaling') + expect(INFO.poweredBy.length).toBeGreaterThan(0) + }) + }) + + describe('examples', () => { + it('each example has valid hex data', () => { + const hexRegex = /^[0-9a-f]+$/i + for (const [key, ex] of Object.entries(examples)) { + for (const val of ex.values) { + expect(val, `Value in "${key}" should be valid hex`).toMatch(hexRegex) + } + } + }) + }) +}) +``` + +### Example: Precompile Exploration Test + +```typescript +import { describe, expect, it } from 'vitest' + +import { config } from '../config' +import { examples } from '../examples' +import { INFO } from '../info' + +describe('EIP-XXXX Exploration', () => { + describe('info', () => { + it('has correct metadata', () => { + expect(INFO.id).toBe('eip-XXXX') + expect(INFO.topic).toBe('scaling') + }) + }) + + describe('config', () => { + it('references a valid default example', () => { + expect(examples[config.defaultExample]).toBeDefined() + }) + + it('has correct number of value fields', () => { + expect(config.values).toHaveLength(2) + }) + }) + + describe('examples', () => { + it('each example has the right number of values', () => { + const editableCount = config.values.filter((v) => v.urlParam).length + for (const [key, ex] of Object.entries(examples)) { + expect(ex.values, `Example "${key}"`).toHaveLength(editableCount) + } + }) + }) +}) +``` + +### Running Tests + +```bash +npx vitest run # run all unit tests +npx vitest run src/explorations/eip-XXXX # run tests for one exploration +``` + +## Step 8: Verify ```bash npm run dev # check your exploration locally @@ -218,7 +342,9 @@ npm run build # verify production build - [ ] Created `src/explorations//info.ts` with metadata - [ ] Created `src/explorations//MyC.vue` with interactive widget - [ ] Created `src/explorations//examples.ts` with example presets +- [ ] Created `src/explorations//tests.spec.ts` with unit tests - [ ] Added import and entry in `src/explorations/REGISTRY.ts` - [ ] Installed library dependencies (if needed) +- [ ] All unit tests pass - [ ] Linting and type checking pass - [ ] Production build succeeds diff --git a/docs/contributing/ai-assisted-development.md b/docs/contributing/ai-assisted-development.md new file mode 100644 index 0000000..0899fda --- /dev/null +++ b/docs/contributing/ai-assisted-development.md @@ -0,0 +1,126 @@ +# AI-Assisted Development + +Most explorations on Feel Your Protocol are — and are expected to be — built with AI coding assistants. This is a feature, not a caveat: the project's folder-per-exploration structure, typed interfaces, and reusable components are designed to be AI-friendly. This page collects practical guidance on how to get the best results. + +## Recommended Workflow + +### 1. Clone and Set Up Locally + +AI agents work best when they can read, run, and iterate on actual code. Start with a local checkout: + +```bash +git clone https://github.com/feelyourprotocol/website.git +cd website +npm install +npm run dev +``` + +### 2. Point the AI at the Docs + +Before writing any code, instruct your agent to read the project documentation. The docs are compact and self-contained — an agent can absorb them in one pass: + +> *Read all files under `docs/` to understand the project structure, contribution guidelines, and component APIs. Then follow the step-by-step guide in "Adding an Exploration" to create a new exploration for EIP-XXXX.* + +Key pages the agent should internalize: + +| Page | Why | +|------|-----| +| [Architecture](/guide/architecture) | Content model, taxonomies (topics, timeline, tags), design decisions | +| [Adding an Exploration](/contributing/adding-an-exploration) | Step-by-step creation guide with field reference | +| [Available E-Components](/contributing/available-e-components) | Reusable components that can save 80% of the work for precompile explorations | +| [Styling & Design](/contributing/styling) | CSS variables, design system classes — avoid hardcoded colors | +| [Code Conventions](/contributing/code-conventions) | Import order, naming, linting rules | + +### 3. Pick the Right Starting Point + +Not all explorations require the same effort. Help the agent choose: + +- **Precompile exploration?** → Use the Precompile Interface E-Component. The agent only needs to define a config, examples, and a result slot. This can be done in under 50 lines of widget code. +- **Custom widget?** → The agent builds from scratch using shared UI components. More freedom, more code, but the `ExplorationC` wrapper still handles the chrome (title, intro, links). + +### 4. Iterate with the Dev Server Running + +Have the agent run `npm run dev` and keep it running. After each change, check the browser. AI agents are good at generating plausible code but may miss visual issues — spacing, overflow, color contrast — that are obvious on screen. + +### 5. Run Quality Checks Before Submitting + +Instruct the agent to run all checks and fix any issues: + +```bash +npm run lf # format + lint (auto-fix) +npm run type-check # TypeScript type checking +npx vitest run # unit tests +``` + +## Prompting Tips + +### Be Specific About the EIP + +Give the agent the EIP number, a link to the spec, and a one-sentence summary of what the exploration should let users do. The more concrete the goal, the better the output: + +> *Create an exploration for EIP-7883 (ModExp gas cost increase). The widget should let users enter ModExp inputs (base, exponent, modulus) and compare gas costs before and after the change.* + +### Reference Existing Explorations + +Existing explorations are the best examples. Point the agent at a similar one: + +> *Look at `src/explorations/eip-7951/` as a reference — this new exploration follows the same precompile interface pattern.* + +### Let the Agent Read Real Source Files + +Documentation describes the intended patterns, but the source files show the actual implementation. If the agent is unsure about something, tell it to read the relevant source: + +> *Read `src/eComponents/precompileInterfaceEC/types.ts` to understand the PrecompileConfig interface.* + +### Ask for Incremental Steps + +Large prompts that ask for everything at once tend to produce lower-quality results. Break the work into stages: + +1. "Create `info.ts` with the metadata" +2. "Create `examples.ts` with three example presets" +3. "Create the widget in `MyC.vue`" +4. "Register in `REGISTRY.ts` and verify it builds" +5. "Add unit tests" + +## Common Pitfalls + +### Hardcoded Colors + +AI agents love to write `text-blue-600` or `bg-green-100`. The project uses a topic-aware color system via CSS variables — hardcoded colors will look wrong when the exploration's topic changes. Remind the agent: + +> *Use `e-text`, `e-result-box`, and the other `e-*` CSS classes instead of hardcoding Tailwind color utilities. Read the Styling & Design docs.* + +### Stale Patterns + +If your agent's training data is older than the project, it may generate patterns that don't match current conventions (wrong import paths, outdated component APIs). The fix is always the same: tell it to read the actual source files rather than guessing. + +### Inventing Components + +Agents sometimes fabricate component names or props that don't exist. If the output references a component you haven't seen before, have the agent verify it exists: + +> *Search the codebase for `ComponentName` before using it. If it doesn't exist, use the documented alternatives.* + +### Skipping the Registry + +The agent may create all the exploration files but forget to add the import to `REGISTRY.ts`. The exploration won't appear until it's registered. The [Adding an Exploration](/contributing/adding-an-exploration#step-5-register-in-the-registry) guide covers this step. + +### Wrong Taxonomy Values + +Agents may guess topic, timeline, or tag values. Remind them that topics and timeline entries are a fixed set — the agent should read `TOPICS.ts` and `TIMELINE.ts` to see the valid IDs. Tags come from the `Tag` enum in `TAGS.ts` and new ones can be proposed, but must follow the [tag rules](/guide/architecture#tags). + +## Cursor / IDE-Specific Tips + +If you are using [Cursor](https://cursor.com/) or a similar AI-native IDE: + +- **Add the docs as context.** Use `@docs/` to reference the documentation folder so the agent has the full contribution guidelines in scope. +- **Use the existing `.cursorrules` or project rules** if available — they encode project-specific conventions the agent should follow. +- **Run terminal commands through the agent.** Let the agent run `npm run lf`, `npm run type-check`, and `npx vitest run` directly so it can read and fix errors in the same session. + +## What to Review as a Human + +Even with a capable AI agent, a human review pass matters. Focus on: + +- **Does the widget actually help understand the protocol change?** The agent can build a functional UI, but the pedagogical value — what makes an exploration genuinely useful — comes from human judgment about what to highlight and how to frame it. +- **Are the intro and usage texts accurate?** AI-generated descriptions of EIPs can be subtly wrong. Cross-check against the actual specification. +- **Are the examples meaningful?** Good examples demonstrate edge cases and realistic inputs, not just `0x00` and `0xff`. +- **Does it look right?** Open the dev server and visually inspect. Check spacing, overflow on different screen sizes, and color consistency. diff --git a/docs/contributing/available-e-components.md b/docs/contributing/available-e-components.md index fe57124..6ab727d 100644 --- a/docs/contributing/available-e-components.md +++ b/docs/contributing/available-e-components.md @@ -4,47 +4,49 @@ This page lists all E-Components that are ready to use in your explorations. For ## Precompile Interface (`precompileInterfaceEC`) -A complete interface for exploring EVM precompiles. It handles: +An interface for exploring EVM precompiles. It handles input management while leaving execution and result display to the exploration: - Example selection and URL sharing - Hex data input with parsing and validation - Individual value inputs with byte length tracking -- Side-by-side pre/post hardfork comparison (running the precompile on two different EVM versions) -- Result display with gas cost and output data +- **Execution and result display** are provided by the exploration via the `run` prop and `#result` slot **Files:** ``` src/eComponents/precompileInterfaceEC/ ├── PrecompileInterfaceEC.vue # Main component -├── PrecompileInterfaceResultEC.vue # Result display (pre/post comparison) +├── PrecompileInterfaceResultEC.vue # Result display (reusable, e.g. pre/post comparison) ├── PrecompileValueInputEC.vue # Value input with byte length validation -├── usePrecompileState.ts # Composable: all state and logic +├── usePrecompileState.ts # Composable: input state and sync logic ├── types.ts # PrecompileConfig and PrecompileValueDef -└── run.ts # EVM precompile execution utility +└── run.ts # EVM precompile execution utility + useStandardPrecompileRun ``` **Used by:** [EIP-7951](https://github.com/feelyourprotocol/website/blob/main/src/explorations/eip-7951/MyC.vue) (secp256r1), [EIP-7883](https://github.com/feelyourprotocol/website/blob/main/src/explorations/eip-7883/MyC.vue) (ModExp gas cost) ### Basic Usage -A precompile exploration needs just a config object and a single component tag: +A precompile exploration provides a config for input layout, a `run` function for execution, and a `#result` slot for visualization. For the standard EthereumJS pre/post hardfork comparison, use the `useStandardPrecompileRun` helper: ```vue ``` +### Component Props + +| Prop | Required | Description | +|------|----------|-------------| +| `config` | Yes | Input configuration (see `PrecompileConfig` below) | +| `examples` | Yes | Example presets from `examples.ts` | +| `exploration` | Yes | Exploration metadata from `info.ts` | +| `run` | Yes | Execution function, called with the assembled hex data (without `0x`) on every valid input change | + ### `PrecompileConfig` Reference ```typescript interface PrecompileConfig { explorationId: string - precompileAddress: string - preHardfork: Hardfork - postHardfork: Hardfork defaultExample: string showBigInt?: boolean values: PrecompileValueDef[] @@ -77,9 +94,6 @@ interface PrecompileConfig { | Field | Required | Description | |-------|----------|-------------| | `explorationId` | Yes | Matches the exploration's `id` in `info.ts` | -| `precompileAddress` | Yes | Hex address of the precompile (e.g. `'05'` for ModExp) | -| `preHardfork` | Yes | Hardfork for the "before" comparison | -| `postHardfork` | Yes | Hardfork for the "after" comparison | | `defaultExample` | Yes | Key from `examples.ts` to load on initialization | | `showBigInt` | No | Show BigInt representations for values (default: per-value setting) | | `values` | Yes | Array of value definitions (see below) | @@ -137,3 +151,62 @@ const config: PrecompileConfig = { }, } ``` + +### Execution: `run` Prop and `#result` Slot + +The E-Component separates input management from execution. The exploration provides: + +1. A **`run` function** — called automatically with the assembled hex data on every valid input change +2. A **`#result` slot** — renders the execution results however the exploration needs + +#### Standard: `useStandardPrecompileRun` + +For the common pre/post hardfork comparison using the EthereumJS EVM, use the provided helper: + +```typescript +import { useStandardPrecompileRun } from '@/eComponents/precompileInterfaceEC/run' + +const { run, execResultPre, execResultPost } = useStandardPrecompileRun( + Hardfork.Prague, Hardfork.Osaka, '05', +) +``` + +This returns a `run` function ready to pass as a prop and two reactive refs for the results. Use `PrecompileInterfaceResultEC` in the `#result` slot for the standard gas + hex display. + +#### Custom Execution + +For explorations that need a different execution mechanism (custom precompile, different library, etc.), define your own `run` function and result state: + +```vue + + + +``` + +The `#result` slot template lives in the exploration's scope, so it naturally accesses your own refs and computed properties. diff --git a/docs/contributing/code-conventions.md b/docs/contributing/code-conventions.md index 4b168f7..e9ac177 100644 --- a/docs/contributing/code-conventions.md +++ b/docs/contributing/code-conventions.md @@ -112,6 +112,7 @@ The ESLint config (`eslint.config.ts`) includes: - Import sorting (`eslint-plugin-simple-import-sort`) - Vitest rules for test files - Cypress rules for E2E files +- Custom `sorted-enum-members` rule for `TAGS.ts` (enforces alphabetical enum ordering) ## Testing diff --git a/docs/contributing/how-to-contribute.md b/docs/contributing/how-to-contribute.md index c60e5ff..0bcc2dc 100644 --- a/docs/contributing/how-to-contribute.md +++ b/docs/contributing/how-to-contribute.md @@ -82,6 +82,7 @@ npm run test:e2e # E2E tests ## Further Reading +- [AI-Assisted Development](/contributing/ai-assisted-development) — best practices for building explorations with AI agents - [Adding an Exploration](/contributing/adding-an-exploration) — step-by-step guide - [Using E-Components](/contributing/e-components) — reusable Ethereum-specific components - [Code Conventions](/contributing/code-conventions) — imports, naming, linting, testing diff --git a/docs/contributing/styling.md b/docs/contributing/styling.md index 3a5fb13..45d74ef 100644 --- a/docs/contributing/styling.md +++ b/docs/contributing/styling.md @@ -4,10 +4,10 @@ The project uses [Tailwind CSS v4](https://tailwindcss.com/) for styling with a ## How Topic Colors Work -Every exploration belongs to a **topic** (e.g. "Fusaka"), and each topic has a color (blue, yellow, green, red). The `ExplorationC` wrapper component sets CSS custom properties on its root element based on the topic color. All child components — UI components, E-Components, and your widget — automatically inherit these colors. +Every exploration belongs to a **topic** (e.g. "Scaling", "UX"), and each topic has a color (green, purple, blue, red, yellow). The `ExplorationC` wrapper component sets CSS custom properties on its root element based on the topic color. All child components — UI components, E-Components, and your widget — automatically inherit these colors. ``` -Topic (e.g. Fusaka = blue) +Topic (e.g. Scaling = green) → ExplorationC sets --e-* CSS variables → All children inherit colors automatically ``` @@ -20,13 +20,14 @@ These variables are set by `ExplorationC` and available to all child elements: | Variable | Purpose | Blue topic example | |----------|---------|-------------------| -| `--e-text` | Primary text color | `blue-900` | -| `--e-bg` | Exploration wrapper background | `blue-200` | -| `--e-bg-light` | Input backgrounds | `blue-50` | +| `--e-text` | Primary text color | `blue-800` | +| `--e-bg` | Exploration wrapper background | `blue-100` | +| `--e-bg-light` | Input backgrounds | `white` | | `--e-bg-medium` | Button backgrounds | `blue-100` | | `--e-bg-dark` | Result box backgrounds | `blue-900` | -| `--e-border` | Light borders | `blue-200` | -| `--e-border-dark` | Dark borders | `blue-900` | +| `--e-border` | Borders on coloured backgrounds | `blue-300` | +| `--e-border-dark` | Dark/emphasis borders | `blue-800` | +| `--e-accent` | Focus rings, interactive accents | `blue-600` | If you need the topic color for custom elements, use the `.e-text` utility class or reference the variables directly: @@ -162,7 +163,7 @@ While the design system handles the common cases, you can always add custom Tail
- +
diff --git a/docs/guide/architecture.md b/docs/guide/architecture.md index eea5731..0598d4b 100644 --- a/docs/guide/architecture.md +++ b/docs/guide/architecture.md @@ -17,7 +17,7 @@ Feel Your Protocol is a Vue 3 application built with Vite. The core idea is simp ## Content Model -Content is organized around two concepts: +Content is organized around three taxonomies: ### Explorations @@ -25,7 +25,7 @@ The core content unit. Each exploration represents an interactive widget for a p ``` src/explorations/eip-7883/ -├── info.ts # Metadata: id, title, path, topic, introText, poweredBy, … +├── info.ts # Metadata: id, title, path, topic, timeline, tags, poweredBy, … ├── MyC.vue # The interactive widget └── examples.ts # Example presets for the widget ``` @@ -34,7 +34,41 @@ Each `info.ts` exports a `const INFO` object typed as `Exploration`. The `REGIST ### Topics -Topics group explorations by theme (e.g. "Fusaka" for an upcoming hardfork). Each exploration belongs to exactly one topic via the `topic` field in its `info.ts`. Topics are defined in `src/explorations/TOPICS.ts`. +Topics are the high-level strategic pillars that group explorations by theme. Each exploration belongs to exactly one topic via the `topic` field in its `info.ts`. Topics are defined in `src/explorations/TOPICS.ts`. + +**Topics are a static, curated set — they are not meant to be added as part of regular contributions.** The current topics are: + +| ID | Title | Description | +|----|-------|-------------| +| `scaling` | Scaling | Data availability, throughput, and L2 enablement | +| `privacy` | Privacy | ZK-proofs, homomorphic encryption, private mempools | +| `ux` | UX | Account abstraction, wallet infrastructure, signature schemes | +| `security` | Security | Validator incentives, cryptographic agility, MEV mitigations | +| `robustness` | Robustness | Gas cost accuracy, EVM semantics hardening, spec clarity | +| `interoperability` | Interoperability | Cross-chain standards, bridge infrastructure, signature scheme support | + +When adding an exploration, pick the topic that best reflects the primary concern of the protocol change. + +### Timeline + +Timeline is a taxonomy that combines two dimensions: how settled an idea is (from early mention to finalized spec) and where it sits relative to mainnet Ethereum (specific hardfork vs. general readiness stage). Each exploration belongs to exactly one timeline entry via the `timeline` field in its `info.ts`. Timeline entries are defined in `src/explorations/TIMELINE.ts`. + +Hardfork entries are named after their Ethereum community event city of origin (e.g. "Fusaka" from Osaka, "Glamsterdam" from Amsterdam). The non-hardfork categories (Ready, Research, Ideas) are static. **New hardfork entries can be added as Ethereum's upgrade schedule evolves.** + +### Tags + +Tags enrich navigation by adding broader Ethereum technical concepts, protocol-relevant areas, or general technology topics from the blockchain space. Each exploration can have up to 3–4 tags. Tags are defined as a TypeScript enum in `src/explorations/TAGS.ts`. + +**Tags grow with contributions** — unlike topics and timeline, new tags can be proposed when adding an exploration. They must follow these rules: + +| Rule | Example | +|------|---------| +| Must be reusable beyond a single exploration | `EVM` ✅ — `EIP-7883` ❌ | +| Short form preferred | `EVM` ✅ — `Ethereum Virtual Machine` ❌ | +| No redundancy with existing tags | `Gas Costs` exists → don't add `Gas` | +| When in doubt, choose the more generic concept | `Gas Costs` ✅ — `Gas Increases` ❌ | + +**Format:** Enum keys use CamelCase (`GasCosts`), all-caps for abbreviations (`EVM`). Members must be sorted alphabetically (enforced by lint). ## E-Components @@ -89,8 +123,8 @@ Each exploration is fully self-contained in its own folder. This means: There are no static per-exploration or per-topic view files. Instead: - **`ExplorationView.vue`** dynamically loads the correct `MyC.vue` using `import.meta.glob()` and `defineAsyncComponent()` based on the route name -- **`TopicView.vue`** dynamically lists all explorations belonging to a topic -- **`HomeView.vue`** dynamically renders all topics defined in `TOPICS.ts` +- **`TopicView.vue`** dynamically lists all explorations belonging to a topic (or all explorations on the `/all` route), with URL query parameter filtering by `timeline` and `tag` +- **`HomeView.vue`** dynamically renders all active topics, a tag cloud, and a timeline navigation ### Route-Level Code Splitting diff --git a/docs/guide/getting-started.md b/docs/guide/getting-started.md index ac28304..b3caf0e 100644 --- a/docs/guide/getting-started.md +++ b/docs/guide/getting-started.md @@ -4,7 +4,7 @@ Feel Your Protocol is an interactive website that lets you explore Ethereum protocol changes hands on. Instead of just reading specifications, you can interact with real Ethereum library code running directly in the browser. -Each protocol change — called an **Exploration** — gets its own page with a dedicated interactive widget. Explorations cover EIPs, ERCs, and other research topics. They are grouped into **Topics** (e.g. "Fusaka" for the upcoming hardfork), making it easy to discover related protocol changes. +Each protocol change — called an **Exploration** — gets its own page with a dedicated interactive widget. Explorations cover EIPs, ERCs, and protocol research. They are organized through three taxonomies: **Topics** (static strategic pillars like "Scaling" or "UX"), **Timeline** (maturity and hardfork placement like "Fusaka" or "Research"), and **Tags** (reusable technical concepts like "EVM" or "Precompiles") — making it easy to discover and navigate related protocol changes. ## Prerequisites @@ -65,17 +65,21 @@ website/ ├── src/ │ ├── explorations/ # Explorations (the core content) │ │ ├── REGISTRY.ts # Exploration registry and types -│ │ ├── TOPICS.ts # Topic definitions and types +│ │ ├── TOPICS.ts # Topic definitions and colors +│ │ ├── TIMELINE.ts # Timeline taxonomy (hardforks + maturity stages) +│ │ ├── TAGS.ts # Tag enum (technical concepts) │ │ ├── ExplorationC.vue # Shared exploration wrapper component -│ │ ├── PoweredByC.vue # Shared "powered by" component +│ │ ├── PoweredByC.vue # Shared creator + "powered by" component +│ │ ├── NoExplorationsC.vue # Empty state component │ │ ├── eip-7594/ # One folder per exploration -│ │ │ ├── info.ts # Metadata (title, description, links, …) +│ │ │ ├── info.ts # Metadata (title, topic, timeline, tags, …) │ │ │ ├── MyC.vue # Interactive widget component │ │ │ ├── examples.ts # Example presets │ │ │ └── data/ # Optional data files │ │ ├── eip-7883/ │ │ └── eip-7951/ │ ├── eComponents/ # Reusable Ethereum-specific components (E-Components) +│ │ ├── ui/ # Generic UI components (UIC) │ │ └── precompileInterfaceEC/ # Precompile interface E-Component │ │ ├── PrecompileInterfaceEC.vue │ │ ├── PrecompileInterfaceResultEC.vue @@ -83,13 +87,14 @@ website/ │ │ ├── usePrecompileState.ts │ │ ├── types.ts │ │ └── run.ts -│ ├── components/ # Shared UI components and utilities -│ │ ├── ui/ # Generic UI components -│ │ └── lib/ # Shared logic and utilities +│ ├── libs/ # Shared computation logic +│ │ └── tagCloud.ts # Tag cloud weight computation │ ├── views/ # Route views │ │ ├── HomeView.vue -│ │ ├── TopicView.vue +│ │ ├── TopicView.vue # Topic + /all view with filtering │ │ ├── ExplorationView.vue +│ │ ├── TimelineNaviView.vue # Timeline navigation component +│ │ ├── TagCloudView.vue # Tag cloud navigation component │ │ └── __tests__/ # Unit tests │ └── router/ # Vue Router config ├── docs/ # Documentation (VitePress) diff --git a/docs/index.md b/docs/index.md index 80b025d..5322483 100644 --- a/docs/index.md +++ b/docs/index.md @@ -27,6 +27,6 @@ features: details: Designed for community contributions. A modular folder-per-exploration structure and shared components make it straightforward to add new explorations. --- -::: tip Want to contribute? -The fastest way to get started is to [add a new exploration](/contributing/adding-an-exploration). Each one lives in a single folder with just two files — metadata and your interactive widget. +::: warning Beta — Contributions Welcome! +This project is in **beta**. Everything works, but the APIs of reusable components (E-Components, shared UI) are still evolving. That means contributions may require some manual coordination during review until the interfaces fully stabilize — we are happy to guide you through! The fastest way to get started is to [add a new exploration](/contributing/adding-an-exploration). ::: diff --git a/eslint.config.ts b/eslint.config.ts index 895caf8..1c13086 100644 --- a/eslint.config.ts +++ b/eslint.config.ts @@ -42,6 +42,41 @@ export default defineConfigWithVueTs( }, }, + { + name: 'app/sorted-tags-enum', + files: ['src/explorations/TAGS.ts'], + plugins: { + custom: { + rules: { + 'sorted-enum-members': { + meta: { type: 'suggestion', messages: { unsorted: 'Enum members must be sorted alphabetically. "{{current}}" should come after "{{previous}}", not before.' } }, + create(context: { report: (descriptor: Record) => void }) { + return { + TSEnumDeclaration(node: { members: Array<{ id: { type: string; name?: string; value?: string } }> }) { + const names = node.members.map((m) => + m.id.type === 'Identifier' ? m.id.name! : String(m.id.value), + ) + for (let i = 1; i < names.length; i++) { + if (names[i].localeCompare(names[i - 1]) < 0) { + context.report({ + node: node.members[i], + messageId: 'unsorted', + data: { current: names[i], previous: names[i - 1] }, + }) + } + } + }, + } + }, + }, + }, + }, + }, + rules: { + 'custom/sorted-enum-members': 'error', + }, + }, + { ...pluginVitest.configs.recommended, files: ['src/**/__tests__/*'], diff --git a/src/App.vue b/src/App.vue index db78485..a1ae8c6 100644 --- a/src/App.vue +++ b/src/App.vue @@ -110,7 +110,7 @@ watch(

- +

diff --git a/src/eComponents/precompileInterfaceEC/PrecompileInterfaceEC.vue b/src/eComponents/precompileInterfaceEC/PrecompileInterfaceEC.vue index 6511333..774439e 100644 --- a/src/eComponents/precompileInterfaceEC/PrecompileInterfaceEC.vue +++ b/src/eComponents/precompileInterfaceEC/PrecompileInterfaceEC.vue @@ -1,4 +1,6 @@ - @@ -62,11 +67,12 @@ await init() :bigIntVal="bigIntVals[val.index]" /> -
- - -
- + +

diff --git a/src/eComponents/precompileInterfaceEC/__tests__/run.spec.ts b/src/eComponents/precompileInterfaceEC/__tests__/run.spec.ts new file mode 100644 index 0000000..5c3da9a --- /dev/null +++ b/src/eComponents/precompileInterfaceEC/__tests__/run.spec.ts @@ -0,0 +1,56 @@ +import { describe, expect, it } from 'vitest' +import { Hardfork } from '@ethereumjs/common' +import type { PrefixedHexString } from '@ethereumjs/util' + +import { runPrecompile, useStandardPrecompileRun } from '../run' + +const MODEXP_SIMPLE = ('0x' + + '0000000000000000000000000000000000000000000000000000000000000001' + + '0000000000000000000000000000000000000000000000000000000000000001' + + '0000000000000000000000000000000000000000000000000000000000000001' + + '030302') as PrefixedHexString + +describe('runPrecompile', () => { + it('returns pre and post results for ModExp (address 05)', async () => { + const result = await runPrecompile(MODEXP_SIMPLE, Hardfork.Prague, Hardfork.Osaka, '05') + expect(result.pre).toBeDefined() + expect(result.post).toBeDefined() + expect(result.post.executionGasUsed).toBeTypeOf('bigint') + }) + + it('returns correct gas values for simple ModExp (EIP-7883)', async () => { + const result = await runPrecompile(MODEXP_SIMPLE, Hardfork.Prague, Hardfork.Osaka, '05') + expect(result.pre!.executionGasUsed).toBe(200n) + expect(result.post.executionGasUsed).toBe(500n) + }) + + it('returns gas for secp256r1 precompile (address 100)', async () => { + const data = ('0x' + + '4dfb1eae8ed41e188b8a44a1109d982d01fc24bb85a933e6283e8838e46942fd' + + 'eb3dc5ce2902f162745057efb7a3308eba992c0d843623603516845ffccd3f10' + + '3b91fedfb22f40063245c621036a040c159f02ae02e6d450ff9b53235e9232c4' + + 'bfa6d0a419b5bc625939cccb8db65a16f7c30c697928660e9da53eda031e80fa' + + 'db5998a893f9b8971a3892aecd132c0eca1bc9622e542f428d8129222f26bdc5') as PrefixedHexString + const result = await runPrecompile(data, Hardfork.Prague, Hardfork.Osaka, '100') + expect(result.post).toBeDefined() + expect(result.post.executionGasUsed).toBeTypeOf('bigint') + }) +}) + +describe('useStandardPrecompileRun', () => { + it('returns a run function', () => { + const { run } = useStandardPrecompileRun(Hardfork.Prague, Hardfork.Osaka, '05') + expect(run).toBeTypeOf('function') + }) + + it('run returns pre/post results directly', async () => { + const { run } = useStandardPrecompileRun(Hardfork.Prague, Hardfork.Osaka, '05') + + const result = await run(MODEXP_SIMPLE) + + expect(result.pre).toBeDefined() + expect(result.post).toBeDefined() + expect(result.pre!.executionGasUsed).toBe(200n) + expect(result.post.executionGasUsed).toBe(500n) + }) +}) diff --git a/src/eComponents/precompileInterfaceEC/__tests__/usePrecompileState.spec.ts b/src/eComponents/precompileInterfaceEC/__tests__/usePrecompileState.spec.ts new file mode 100644 index 0000000..df57ec4 --- /dev/null +++ b/src/eComponents/precompileInterfaceEC/__tests__/usePrecompileState.spec.ts @@ -0,0 +1,95 @@ +import { describe, expect, it, vi } from 'vitest' + +vi.mock('vue-router', () => ({ + useRoute: () => ({ query: {} }), + useRouter: () => ({ resolve: vi.fn(() => ({ href: '' })) }), +})) + +import type { Examples } from '@/explorations/REGISTRY' + +import type { PrecompileConfig } from '../types' +import { usePrecompileState } from '../usePrecompileState' + +const config: PrecompileConfig = { + explorationId: 'test', + defaultExample: 'ex1', + values: [{ title: 'A', urlParam: 'a', expectedLen: 4n }], +} + +const examples: Examples = { + ex1: { title: 'Example 1', values: ['deadbeef'] }, +} + +describe('usePrecompileState', () => { + it('calls run with assembled data on init', async () => { + const run = vi.fn().mockResolvedValue(undefined) + const state = usePrecompileState(config, examples, run) + + await state.init() + + expect(run).toHaveBeenCalledWith('0xdeadbeef') + }) + + it('calls run on data input change', async () => { + const run = vi.fn().mockResolvedValue(undefined) + const state = usePrecompileState(config, examples, run) + await state.init() + run.mockClear() + + state.data.value = 'cafebabe' + await state.onDataInputFormChange() + + expect(run).toHaveBeenCalledWith('0xcafebabe') + }) + + it('calls run on value input change', async () => { + const run = vi.fn().mockResolvedValue(undefined) + const state = usePrecompileState(config, examples, run) + await state.init() + run.mockClear() + + state.hexVals.value[0] = 'aabbccdd' + await state.onValueInputFormChange() + + expect(run).toHaveBeenCalledWith('0xaabbccdd') + }) + + it('clears example on manual data input', async () => { + const run = vi.fn().mockResolvedValue(undefined) + const state = usePrecompileState(config, examples, run) + await state.init() + expect(state.example.value).toBe('ex1') + + state.data.value = 'cafebabe' + await state.onDataInputFormChange() + + expect(state.example.value).toBe('') + }) + + it('exposes result ref populated by run return value', async () => { + const mockResult = { gas: 42n } + const run = vi.fn().mockResolvedValue(mockResult) + const state = usePrecompileState(config, examples, run) + + expect(state.result.value).toBeUndefined() + + await state.init() + + expect(state.result.value).toEqual(mockResult) + }) + + it('exposes input state without legacy execution refs', () => { + const run = vi.fn().mockResolvedValue(undefined) + const state = usePrecompileState(config, examples, run) + + expect(state.data).toBeDefined() + expect(state.hexVals).toBeDefined() + expect(state.bigIntVals).toBeDefined() + expect(state.byteLengths).toBeDefined() + expect(state.example).toBeDefined() + expect(state.inputValues).toBeDefined() + expect(state.result).toBeDefined() + expect(state).not.toHaveProperty('execResultPre') + expect(state).not.toHaveProperty('execResultPost') + }) +}) diff --git a/src/eComponents/precompileInterfaceEC/__tests__/utils.spec.ts b/src/eComponents/precompileInterfaceEC/__tests__/utils.spec.ts new file mode 100644 index 0000000..fc40ad9 --- /dev/null +++ b/src/eComponents/precompileInterfaceEC/__tests__/utils.spec.ts @@ -0,0 +1,185 @@ +import { describe, expect, it } from 'vitest' +import { ref } from 'vue' + +import { + countUpwardsHexStr, + dataToValueInput, + isValidByteInputForm, + padHex, + toHex, + valueToDataInput, +} from '../utils' + +describe('isValidByteInputForm', () => { + it('accepts valid hex without prefix', () => { + expect(isValidByteInputForm('deadbeef')).toEqual([]) + }) + + it('accepts empty string', () => { + expect(isValidByteInputForm('')).toEqual([]) + }) + + it('rejects 0x prefix', () => { + const errors = isValidByteInputForm('0xdeadbeef') + expect(errors.some((e) => e.includes('0x'))).toBe(true) + }) + + it('rejects invalid hex characters', () => { + const errors = isValidByteInputForm('zzzz') + expect(errors.some((e) => e.toLowerCase().includes('invalid'))).toBe(true) + }) + + it('validates expected byte length (too short)', () => { + const errors = isValidByteInputForm('dead', 4n) + expect(errors.some((e) => e.includes('4 bytes expected'))).toBe(true) + }) + + it('validates expected byte length (too long)', () => { + const errors = isValidByteInputForm('deadbeefcafe', 2n) + expect(errors.some((e) => e.includes('2 bytes expected'))).toBe(true) + }) + + it('accepts correct expected length', () => { + expect(isValidByteInputForm('deadbeef', 4n)).toEqual([]) + }) + + it('returns multiple errors when applicable', () => { + const errors = isValidByteInputForm('0xzz', 4n) + expect(errors.length).toBeGreaterThanOrEqual(2) + }) +}) + +describe('toHex', () => { + it('converts bigint to padded hex string', () => { + expect(toHex(255n, 4)).toBe('00ff') + }) + + it('converts zero', () => { + expect(toHex(0n, 4)).toBe('0000') + }) + + it('pads to requested length', () => { + expect(toHex(1n, 64)).toBe('0'.repeat(63) + '1') + }) + + it('does not truncate if value exceeds length', () => { + expect(toHex(256n, 2)).toBe('100') + }) +}) + +describe('padHex', () => { + it('pads odd-length hex strings', () => { + expect(padHex('abc')).toBe('0abc') + }) + + it('leaves even-length hex strings unchanged', () => { + expect(padHex('abcd')).toBe('abcd') + }) + + it('pads single character', () => { + expect(padHex('f')).toBe('0f') + }) + + it('leaves empty string unchanged', () => { + expect(padHex('')).toBe('') + }) +}) + +describe('countUpwardsHexStr', () => { + it('generates counting hex bytes', () => { + expect(countUpwardsHexStr(3)).toBe('010203') + }) + + it('returns empty string for zero', () => { + expect(countUpwardsHexStr(0)).toBe('') + }) + + it('generates single byte', () => { + expect(countUpwardsHexStr(1)).toBe('01') + }) + + it('pads single-digit numbers', () => { + expect(countUpwardsHexStr(9)).toBe('010203040506070809') + }) +}) + +describe('dataToValueInput', () => { + it('splits data into value fields by byte lengths', () => { + const data = ref('aabbccdd') + const hexVals = ref(['', '']) + const bigIntVals = ref<(bigint | undefined)[]>([undefined, undefined]) + const byteLengths = ref([2n, 2n]) + + dataToValueInput(data, hexVals, bigIntVals, byteLengths) + + expect(hexVals.value).toEqual(['aabb', 'ccdd']) + }) + + it('updates bigIntVals when defined', () => { + const data = ref('00ff') + const hexVals = ref(['']) + const bigIntVals = ref<(bigint | undefined)[]>([0n]) + const byteLengths = ref([2n]) + + dataToValueInput(data, hexVals, bigIntVals, byteLengths) + + expect(bigIntVals.value[0]).toBe(255n) + }) + + it('leaves bigIntVals undefined when not tracked', () => { + const data = ref('aabb') + const hexVals = ref(['']) + const bigIntVals = ref<(bigint | undefined)[]>([undefined]) + const byteLengths = ref([2n]) + + dataToValueInput(data, hexVals, bigIntVals, byteLengths) + + expect(bigIntVals.value[0]).toBeUndefined() + }) + + it('handles multiple fields with different lengths', () => { + const data = ref('aabbccddee') + const hexVals = ref(['', '', '']) + const bigIntVals = ref<(bigint | undefined)[]>([undefined, undefined, undefined]) + const byteLengths = ref([1n, 2n, 2n]) + + dataToValueInput(data, hexVals, bigIntVals, byteLengths) + + expect(hexVals.value).toEqual(['aa', 'bbcc', 'ddee']) + }) +}) + +describe('valueToDataInput', () => { + it('computes byte lengths from hex values', () => { + const hexVals = ref(['aabb', 'ccdd']) + const bigIntVals = ref<(bigint | undefined)[]>([undefined, undefined]) + const lengthsMask = ref<(bigint | undefined)[]>([undefined, undefined]) + const byteLengths = ref([0n, 0n]) + + valueToDataInput(hexVals, bigIntVals, lengthsMask, byteLengths) + + expect(byteLengths.value).toEqual([2n, 2n]) + }) + + it('uses length mask when defined', () => { + const hexVals = ref(['aabb']) + const bigIntVals = ref<(bigint | undefined)[]>([undefined]) + const lengthsMask = ref<(bigint | undefined)[]>([32n]) + const byteLengths = ref([0n]) + + valueToDataInput(hexVals, bigIntVals, lengthsMask, byteLengths) + + expect(byteLengths.value[0]).toBe(32n) + }) + + it('updates bigIntVals when defined', () => { + const hexVals = ref(['00ff']) + const bigIntVals = ref<(bigint | undefined)[]>([0n]) + const lengthsMask = ref<(bigint | undefined)[]>([undefined]) + const byteLengths = ref([0n]) + + valueToDataInput(hexVals, bigIntVals, lengthsMask, byteLengths) + + expect(bigIntVals.value[0]).toBe(255n) + }) +}) diff --git a/src/eComponents/precompileInterfaceEC/run.ts b/src/eComponents/precompileInterfaceEC/run.ts index dd95ccd..27a1c59 100644 --- a/src/eComponents/precompileInterfaceEC/run.ts +++ b/src/eComponents/precompileInterfaceEC/run.ts @@ -1,17 +1,27 @@ -import type { Ref } from 'vue' import { Common, type Hardfork, Mainnet } from '@ethereumjs/common' -import { createEVM, type ExecResult, getActivePrecompiles } from '@ethereumjs/evm' -import { hexToBytes } from '@ethereumjs/util' +import { + createEVM, + type ExecResult, + getActivePrecompiles, + type PrecompileInput, +} from '@ethereumjs/evm' +import { createAddressFromString, hexToBytes, type PrefixedHexString } from '@ethereumjs/util' + +type PrecompileFunc = (input: PrecompileInput) => Promise | ExecResult + +export interface StandardRunResult { + pre?: ExecResult + post: ExecResult +} export async function runPrecompile( - data: string, + data: PrefixedHexString, preHF: Hardfork, postHF: Hardfork, precompile: string, - execResultPre: Ref, - execResultPost: Ref, -) { +): Promise { const gasLimit = BigInt(5000000) + const dataBytes = hexToBytes(data) const commonPre = new Common({ chain: Mainnet, hardfork: preHF }) const evmPre = await createEVM({ common: commonPre }) @@ -21,23 +31,69 @@ export async function runPrecompile( const evmPost = await createEVM({ common: commonPost }) const precompilePost = getActivePrecompiles(commonPost).get(precompile.padStart(40, '0'))! - // Pre-HF run + let pre: ExecResult | undefined if (precompilePre) { - const callDataPre = { - data: hexToBytes(`0x${data}`), - gasLimit, - common: commonPre, - _EVM: evmPre, - } - execResultPre.value = await precompilePre(callDataPre) + pre = await precompilePre({ data: dataBytes, gasLimit, common: commonPre, _EVM: evmPre }) } - // Post-HF run - const callDataPost = { - data: hexToBytes(`0x${data}`), + const post = await precompilePost({ + data: dataBytes, gasLimit, common: commonPost, _EVM: evmPost, + }) + + return { pre, post } +} + +/** + * Creates a `run` function for the standard pre/post hardfork comparison pattern. + * The returned function is compatible with the `PrecompileInterfaceEC` `run` prop + * and returns the result directly (captured automatically by the composable). + */ +export function useStandardPrecompileRun( + preHF: Hardfork, + postHF: Hardfork, + precompileAddress: string, +) { + async function run(data: PrefixedHexString): Promise { + return runPrecompile(data, preHF, postHF, precompileAddress) } - execResultPost.value = await precompilePost(callDataPost) + + return { run } +} + +/** + * Runs a custom precompile function against the EVM. + * Handles all EVM setup boilerplate (Common, EVM instance, call data assembly). + * + * @param data - `0x`-prefixed hex input data + * @param precompileFn - The precompile implementation function + * @param address - `0x`-prefixed hex address to register the precompile at + * @param hardfork - Hardfork context (defaults to Prague) + * @returns The raw `ExecResult` from the precompile execution + */ +export async function runCustomPrecompile( + data: PrefixedHexString, + precompileFn: PrecompileFunc, + address: string, + hardfork: Hardfork = 'prague' as Hardfork, +): Promise { + const common = new Common({ chain: Mainnet, hardfork }) + const addr = createAddressFromString(address) + const evm = await createEVM({ + common, + customPrecompiles: [{ address: addr, function: precompileFn }], + }) + + const fn = getActivePrecompiles(common, [{ address: addr, function: precompileFn }]).get( + address.slice(2).padStart(40, '0').toLowerCase(), + )! + + return fn({ + data: hexToBytes(data), + gasLimit: BigInt(5000000), + common, + _EVM: evm, + }) } diff --git a/src/eComponents/precompileInterfaceEC/types.ts b/src/eComponents/precompileInterfaceEC/types.ts index d8fc29a..de3a0d6 100644 --- a/src/eComponents/precompileInterfaceEC/types.ts +++ b/src/eComponents/precompileInterfaceEC/types.ts @@ -1,5 +1,3 @@ -import type { Hardfork } from '@ethereumjs/common' - export interface PrecompileValueDef { title: string urlParam?: string @@ -11,9 +9,6 @@ export interface PrecompileValueDef { export interface PrecompileConfig { explorationId: string - precompileAddress: string - preHardfork: Hardfork - postHardfork: Hardfork defaultExample: string showBigInt?: boolean values: PrecompileValueDef[] diff --git a/src/eComponents/precompileInterfaceEC/usePrecompileState.ts b/src/eComponents/precompileInterfaceEC/usePrecompileState.ts index 9af1096..3a0e155 100644 --- a/src/eComponents/precompileInterfaceEC/usePrecompileState.ts +++ b/src/eComponents/precompileInterfaceEC/usePrecompileState.ts @@ -1,10 +1,9 @@ -import { computed, ref } from 'vue' +import { computed, ref, type ShallowRef, shallowRef } from 'vue' import { useRoute, useRouter } from 'vue-router' -import type { ExecResult } from '@ethereumjs/evm' +import type { PrefixedHexString } from '@ethereumjs/util' import type { Examples } from '@/explorations/REGISTRY' -import { runPrecompile } from './run' import type { PrecompileConfig } from './types' import { dataToValueInput, isValidByteInputForm, valueToDataInput } from './utils' @@ -18,22 +17,16 @@ function createState(config: PrecompileConfig) { lengthsMask: ref<(bigint | undefined)[]>(config.values.map((v) => v.expectedLen)), byteLengths: ref(config.values.map(() => 0n)), example: ref(''), - execResultPre: ref(), - execResultPost: ref(), } } -export function usePrecompileState(config: PrecompileConfig, examples: Examples) { - const { - data, - hexVals, - bigIntVals, - lengthsMask, - byteLengths, - example, - execResultPre, - execResultPost, - } = createState(config) +export function usePrecompileState( + config: PrecompileConfig, + examples: Examples, + run: (data: PrefixedHexString) => Promise, +) { + const { data, hexVals, bigIntVals, lengthsMask, byteLengths, example } = createState(config) + const result: ShallowRef = shallowRef() const router = useRouter() const route = useRoute() @@ -46,24 +39,13 @@ export function usePrecompileState(config: PrecompileConfig, examples: Examples) // --- Data conversion --- - async function run() { - await runPrecompile( - data.value, - config.preHardfork, - config.postHardfork, - config.precompileAddress, - execResultPre, - execResultPost, - ) - } - async function data2Values() { if (isValidByteInputForm(data.value).length > 0) return if (config.parseData) { config.parseData(data.value, byteLengths.value) } dataToValueInput(data, hexVals, bigIntVals, byteLengths) - await run() + result.value = await run(`0x${data.value}`) } async function values2Data() { @@ -76,7 +58,7 @@ export function usePrecompileState(config: PrecompileConfig, examples: Examples) data.value = config.assembleData ? config.assembleData(hexVals.value, byteLengths.value) : hexVals.value.join('') - await run() + result.value = await run(`0x${data.value}`) } // --- User interaction --- @@ -136,9 +118,8 @@ export function usePrecompileState(config: PrecompileConfig, examples: Examples) hexVals, bigIntVals, byteLengths, - execResultPre, - execResultPost, inputValues, + result, selectExample, shareURL, onDataInputFormChange, diff --git a/src/eComponents/ui/ButtonUIC.vue b/src/eComponents/ui/ButtonUIC.vue index ac65f98..42dd52e 100644 --- a/src/eComponents/ui/ButtonUIC.vue +++ b/src/eComponents/ui/ButtonUIC.vue @@ -1,10 +1,12 @@