Code Monkey home page Code Monkey logo

evm-diff's Introduction

EVM Diff

Diff EVM-compatible chains in a friendly format.

Overview

There are lots of EVM-compatible chains, and they can differ in various, subtle ways. Developers write contracts intended to be deployed on multiple chains and therefore need to be aware of these differences.

Currently finding this information can be tedious because:

  • It requires checking each chain's documentation.
  • May require digging through node implementations.
  • Chains compare themselves to Ethereum, but not to each other.

As L2s aim to scale horizontally with more chains (such as the Optimism Superchain and new Arbitrum chains), developers will want to compare those chains against both the "base" optimism/arbitrum chains, and Ethereum mainnet.

Sites like op-geth are excellent for comparing the actual code, but for smart contract and application developers, this is too low-level to be easily digestible.

EVM Diff aims to solve these problems, by allowing you to diff the execution-level specifications of EVM-compatible chains in an easy-to-read format.

Status

This project is in the early stages of development, and should not yet be relied on.

The initial goal is to have a full diff between mainnet, Optimism, and Arbitrum, and then expand to other chains from there. Starting with these chains seems ideal because they have good documentation and they are the most popular L2s (by TVL).

See the open issues for current needs, and feel free to create new issues for bugs, feature requests, or other ideas.

If you want to contribute, please see CONTRIBUTING.md. Some issues have bounties attached, which will be paid out on Optimism in OP tokens.1 Please be sure to read CONTRIBUTING.md to understand how the bounty process works.

Footnotes

  1. Thanks to Optimism RPGF, as these bounties were made possible by an RPGF grant I received for Multicall3.

evm-diff's People

Contributors

cairoeth avatar dependabot[bot] avatar fvictorio avatar leovct avatar mds1 avatar tucksondev avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

evm-diff's Issues

feat(meta): structure and content of execution-level differences

This is a tracking issue, and will link out to specific issues below

Overview

The overall scope of the diff should be "things a smart contract developer or app developer needs to care about". For the most part, this means the diff is primarily an execution layer diff, as contract/app devs don't usually need to worry about consensus layer details. There are exceptions here, e.g. it's easier to manipulate TWAPs in PoS compared to PoW because an attacker knows when they'll be the block proposer for consecutive blocks. Things like this should be included.

Additionally, data should be focused on the current hard fork for a given chain. Including historical data when possible is great, but for now the plan it to keep it simple and focus on doing one thing—diff current EVM specs—really well.

Data Sections

The current plan for sections is below.

In terms of implementation, always start by implementing Ethereum mainnet's spec first, and other chains' specs can import/modify that. Alternatively, another chain may import a non-mainnet chain as it's template. However, mainnet is always the chain that should be independently defined.

Adding data for any of these sections should include references. Lean towards including too many vs. too few, so things are easy to validate.

  • Metadata: Chain name, ID, native token name, symbol, and decimals, block explorers, etc. This is pulled from the metadata in @wagmi/chains.
  • Opcodes: This is ready to be implemented and is tracked in #6
  • Precompiles and Predeploys: This is partially implemented and needs to be finished. It's being tracked in #3
  • Transaction Types: #15
  • Account Types: #28
  • Data structures and computation: #29
  • Languages and Compilers: #30
  • Gas: #31
  • JSON-RPC: #32
  • Nodes: #33
  • Mempools: #34
  • Block production: #35
  • Contracts: #36
  • EIPs: #37
  • EVM version: #27

References

feat: finish populating existing precompiles and predeploys (bounty: 75 OP 🔴)

This issue is eligible for a 75 OP bounty. Read CONTRIBUTING.md to learn how to qualify.

This is basically a version of https://www.evm.codes/precompiled for each chain, with the addition of predeploys. This has been started but is not left complete. The remaining tasks are:

  1. Search for TODO in any precompiles.ts files to reveal the uncompleted sections, and complete them.
  2. All existing references fields should be checked for accuracy
  3. Additional references should be added for more information when possible.

chore: clarify phrasing

Right now text in the UI will be phrased as

  • "Supported only in Shanghai hard fork."
  • "Supported only in Canyon hard fork"
  • "Supported only in ArbOS 11 hard fork"

However, those are the last, most recent forks on each chain. This should be clarified to say "Supported since {hard fork name}" when the hard fork is the most recent one for that network

feat: node diff

Most chain differences are presented in a user friendly way on evmdiff. There are cases where it's useful to see the actual diffs in the node, in order to see how these changes are implemented or understand edge cases.

Optimism has a very nice interface for exploring these differences: https://op-geth.optimism.io/

It's generate by this tool by protolambda: https://github.com/protolambda/forkdiff

Assuming all these chains have a geth fork as an implementation, it would be nice to be able to view these diffs for all pairs of chains (or, at the very least, each chain vs ETH mainnet).

Per @mds1, this would require tracking each chain's node repo in addition to the chain ID, but I believe it's a worthwhile feature.

feat(data): Mempools / Nonstandard RPCs

This issue is eligible for a bounty paid out in OP tokens. Read CONTRIBUTING.md to learn how to qualify.

By "nonstandard RPCs" I just mean RPC URLs that don't route your transaction to the standard public mempool. This data section includes:

  • Private mempools, e.g. flashbots
  • Alternative mempools, e.g. ERC-4337

feat: Add Humanode network

Would be great to add the Humanode network here.

We are planning to contribute a PR in a bit.

A mirroring issue on our side: humanode-network/humanode#738

Humanode is a network based on direct Sybil-resistance via biometrics at the consensus level (and we have recently shipped our EVM support).

feat(data): JSON-RPC methods

This issue is eligible for a bounty paid out in ETH on OP Mainnet. Read CONTRIBUTING.md to learn how to qualify.

  • List of all standard execution-related methods and their interfaces, along with chain-specific methods. For mainnet we can start with https://ethereum.org/en/developers/docs/apis/json-rpc/.
  • Consensus layer RPCs can be ignored for now since the site focus is execution level

This is dependent on #33 as some nodes have non-standard RPC methods that others nodes (even for the same chain) don't implement, so #33 should be completed first that

feat: dynamic social card images / OG tags (bounty: 100 OP 🔴)

This issue is eligible for a 100 OP bounty. Read CONTRIBUTING.md to learn how to qualify.

When sharing a link to compare two chains, the social card that's rendered should be dynamic, and show a summary of the comparison. Something like "X total differences between Mainnet and Optimism" as the header, with something like "Precompiles: Y differences" for each section.

This should be doable with https://vercel.com/blog/introducing-vercel-og-image-generation-fast-dynamic-social-card-images

Support testnets

It would be good to see the many differences between mainnets and testnets.

feat(data): Block Production

This issue is eligible for a bounty paid out in ETH on OP Mainnet. Read CONTRIBUTING.md to learn how to qualify.

We are only focused on things that affect execution:

  • Block time
  • Gas limit and pricing (1559) rules (this might be some duplicate data from #31 so consider how to reuse the data). For non-1559 chains this is a single gas limit value, for 1559 chains it's a gas target and a gas limit
  • Consensus mechanism, since this enables e.g. multi-block MEV
  • Finality definitions / reorg risks
  • Censorship / trust assumptions on e.g. sequencers. Maybe can outsource / link to L2Beat here since I think they have a lot of data around this
  • Timestamp manipulation

feat: transaction types

These were removed as part of #62 and should be added back. I'm not sure if there is a good way to automatically detect transaction types, so we may need to instead define a set of known transaction types, along with the test data for each (e.g. an RPC method name + associated input data)

feat(chains): support paths to spec-files to enable permissionless adding of new chains

The current plan is to first add the complete set of data for the 3 initial chains. From there, I'd like to add a helper method that makes it easy to add a new chain based off an existing chain, so the diffs for new chains can be small.

With both of those in place, we can support a feature that lets chains permissionlessly add their own data using e.g. a JSON file that has the same structure as the Chain type in src/types/chains.ts. Assuming we go the JSON route, we'd probably want to use JSON Schema to strongly type it and so users can validate their implementations. There are at least two actively maintained packages that support converting TS types to JSON Schema which would help the conversion:

A chain could define their JSON file and host it somewhere, then users can navigate directly to https://www.evmdiff.com/diff?base=1&target=encodedUrlToChainDiffFile. This would compare chain ID 1 (mainnet) to the chain whose path is specified in the URL.

We'll also have to show a warning that the data is unverified and may not be accurate.

Some chains may be limited by the shape of the Chains type depending on how EVM-(in)compatible they are. One way to mitigate this is to add a generic catch-call section to the end that supports arbitrary keys and values, which get rendered in their own section of the UI.

h/t @alcuadrado for the idea

build: upgrade to nextjs 14

I tried upgrading and there were a few broken things that need to be resolved, may be easy for someone more familiar with nextjs than me

feat(data): Nodes

This issue is eligible for a bounty paid out in OP tokens. Read CONTRIBUTING.md to learn how to qualify.

  • A list of all node implementations with links to their github repos and docs. I think it's ok to include both execution and consensus clients, but the latter doesn't feel necessary since data is currently execution focused
  • Differences in trace formats between nodes with e.g. debug_traceTransaction, structLog, callTracer, etc. (@charles-cooper I believe you suggested this originally, any references, etc. you can share? I'm not too familiar with the differences / uses for each )

feat: EVM playground for each supported chain

From @pcaversaccio in #4 (comment):

I love the playground feature from https://www.evm.codes/playground: it would be a nice feature to be able to simulate the differences at opcode or Solidity level, for example. Let me make a simple example: you have a contract compiled with solc version 0.8.20 and default EVM version shanghai. You copy-paste the contract into the playground, select "Optimism<>Ethereum diff" and press run. It will deploy on Ethereum but not on Optimism yet due to PUSH0. Diffs are very powerful on a documentation level, but many times you also want to understand the engineering implications. Thus, we should also think about how to integrate this best into the UI.

This would be nice to have, I think the main thing to figure out is how to actually implement it. The EVM Codes playground is powered by ethereumjs and solc-js, and there's no guarantee these exist for each chain. Since EVM compatible chains should support solc out of the box, ethereumjs is the main one to figure out. Probably can just query a node instead of simulating locally with etheruemjs

chore: exclude hardforks from diff algo

Right now it shows a diff for PUSH0 on optimism and mainnet which I believe is because of the hardfork difference. I think we should exclude the hardfork field from all diff comparisons, because we're showing current data, and in practice there is effectively no PUSH0 diff for users

feat(data): Data Structures and Computations

This issue is eligible for a bounty paid out in ETH on OP Mainnet. Read CONTRIBUTING.md to learn how to qualify.

Some chains like zkSync and other zkEVMs compute addresses differently and/or use hash functions that are not keccak256. As a result, CREATE and/or CREATE2 addresses can differ between chains.

Similarly, different block structures or state representations can result in different merkle proofs/roots for the same data.

Not yet sure the best name for this section, or the best way to represent the data. To illustrate why this is tricky: Imagine two chains both compute the address of a contract deployed via CREATE2 by keccak256( 0xff ++ address ++ salt ++ keccak256(init_code))[12:]. But one chain requires a custom solidity fork so the init code is different. The address computation is the same, but it's misleading to say "address calculation is the same" because in reality you won't get the same address due to the differing bytecode.

Maybe in this case we do things are the same, and we defer to a compilation/tooling section to explain the bytecode difference

feat: add Opcode diffs (bounty: 300 OP 🔴)

This issue is eligible for a 300 OP bounty. Read CONTRIBUTING.md to learn how to qualify.

This is basically a version of https://www.evm.codes/ for each chain.

Each opcode should include all information shown by evm codes. Specifically:

  • Opcode number
  • Opcode name
  • Description
  • Minimum gas
  • Gas computation
  • Stack input AND descriptions for each stack input item
  • Stack output AND description for each stack output item
  • Error cases
  • Notes
  • References (i.e. links to the data sources)

The data for each chain should live in src/chains/[chainName]/vm/opcodes.ts. Alternatively, if that file gets too big, the path can be src/chains/[chainName]/vm/opcodes/[group].ts, where group matches the Ethereum execution-specs file names here. In the case where it's split up across multiple files in an opcodes folder, include an index.ts file that exports the final opcodes list.

The component to render the diff of opcodes should live in src/components/diff/DiffOpcodes.tsx. Two opcodes should be considered equal if every item in the above bulleted list matches, i.e. JSON.stringify(a) === JSON.stringify(b). If there any any differences, they should not be considered equal

To keep PRs small and easy to review, let's split this up into two PRs:

  • First PR adds the data for each chain. I'd suggest adding a few mainnet opcodes then opening a draft PR so we can align on the type definitions and make sure things look directionally correct before adding all opcodes.
  • Second one adds the DiffOpcodes.tsx component.

Suggested type to define in src/chains/types.ts:

type OpcodeParam = {
  name: string;
  description: string;
};

export type Opcode = {
  number: number;
  name: string;
  minGas: number;
  description: string;
  input: OpcodeParam[];
  output: OpcodeParam[];
  // Gas computation can get complex, so I'm not sure of the best way to
  // represent this. If it's hard, this can be left out for now and we can
  // create a separate issue to track.
  gasComputation: TODO;
  errorCases: string[];
  notes: string[];
  references: string[];
};

// This type already exists and we're adding the `opcodes` field.
export type Chain = {
  metadata: Metadata;
  precompiles: (Precompile | Predeploy)[];
  opcodes: Opcode[];
};

feat: gas costs

Data to include

  • The 21,000 intrinsic gas cost, calldata costs, etc
  • Opcode gas cost differences. We already have their gas costs are in the opcode section, but differences should be listed here for completeness. We can import the opcode data as a starting point, since it has gas costs.
  • Things like memory expansion cost, SSTORE rules, access lists, refunds. This and this are good references for mainnet gas costs.
  • L2s post calldata on L1 which should be accounted for
  • Gas limit
  • Comparison of EIP-1559 parameters, since they can be adjusted
  • Maybe can show estimated transaction costs using something like https://l2fees.info/ (however we don't currently have any async calls and I'd like to keep it that way for now)

There's a lot here, so consider splitting this into multiple PRs for simplicity. There also might be some duplicate data from #35 so consider how to reuse the data

Between #35, #37, and the existing opcode data, I think this PR will add little new data and simply reformat existing data

chore(data-review): miscellaneous questions about current data

Mainnet

  • Should type 0 transactions be marked as deprecated: true? They are strictly worse than type 2 transactions because type 2 transactions provide a refund, so here it seems we should consider them deprecated.

Optimism

  • Should type 0 transactions be marked as deprecated: true? (Though, arguably type 0 is better for users since it has less calldata—expand the block below for more info on the cost difference)
Type 0 vs Type 2 cost analysis

Type 0 results in a bit less calldata to post on L1 since there's one less gas param and no prefix byte, so it should be cheaper. The refund for a type 2 transaction is priced in L2 gas, and the reduced calldata is priced in L1 gas which is ~10,000x more expensive. Therefore the reduced calldata of a type 0 tx should always outweigh the L2 refund, and maybe on L2 type 0 transactions are preferred? Perhaps something about the transaction serialization or compression makes this not true.

  • Tested this by serializing two transactions and pasting the results into the getL1Fee of the optimism gas oracle.
  • Tx 0: tx0 = utils.serializeTransaction({to: constants.AddressZero, gasPrice: 1, gasLimit: 21000, chainId: 10, type: 0}) = 0xdf800182520894000000000000000000000000000000000000000080800a8080
  • Tx 2: tx2 = utils.serializeTransaction({to: constants.AddressZero, maxPriorityFeePerGas: 1, maxFeePerGas: 1, gasLimit: 21000, chainId: 10, type: 2}) = 0x02df0a8001018252089400000000000000000000000000000000000000008080c0
  • Resulting fee in wei is 31753508714978 for tx0 and 34678476442615 for tx2, which meas tx 2 has an L1 fee that's 2.924967727637e-6 ETH more expensive
  • Conclusion: that difference is small enough that this is probably not relevant, especially after EIP-4844
  • The docs say the COINBASE opcode is undefined (sounds like that means it'd cause a revert), but by calling getCurrentBlockCoinbase (block.coinbase) on the Multicall3 contract it seems the actual value returned is 0x4200000000000000000000000000000000000011 (the sequencer fee vault)
  • Verify that prefix byte 0x03 is considered reserved and will not be used.

Arbitrum

  1. It seems there used to be a transaction type 0x67 (103) that was removed called ArbitrumWrappedTxType. This is worth documenting as deprecated: true. I searched their issues/PRs/codebase for that name and number but didn't find it
  2. The docs (1, 2) say "Accessing block numbers within an Arbitrum smart contract (i.e., block.number in Solidity) will return a value close to (but not necessarily exactly) the L1 block number at which the Sequencer received the transaction." — are there any guarantees on the bounds of the returned number? Can it be greater than the most recent L1 block number? Is there a maximum bound on how much lower it is? In general this description is vague, how is the estimated block number derived?
  3. The docs say the COINBASE opcode returns zero, but by calling getCurrentBlockCoinbase (block.coinbase) on the Multicall3 contract it seems the actual value returned is 0xA4b000000000000000000073657175656e636572.
    1. That address is the L1 batch poster, will the batch poster always be that address
  4. The docs say the DIFFICULTY (PREVRANDAO) opcode returns 2500000000000000, but by calling getCurrentBlockDifficulty (block.difficulty) on the Multicall3 contract it seems the actual value returned is 1
  5. The docs don't mention the CALLER opcode, but pretty sure it also has address aliasing and behaves the same as it does on optimism
  6. Verify that prefix byte 0x03 is considered reserved and will not be used.

Difficulty/Prev-randao content bug

The difficulty opcode in L1 Ethereum post-Merge returns beacon-chain RANDAO randomness, not just the difficulty. See https://eips.ethereum.org/EIPS/eip-4399

And in the Optimism diff it currently states that it "Returns a random value. As this value is set by the sequencer, it is not as reliably random as the L1 equivalent. You can use an oracle for randomness" but that is wrong: it actually copies over the randomness value from L1, and repeats it until the latest incorporated L1 block changes. See specs here: https://github.com/ethereum-optimism/optimism/blob/develop/specs/derivation.md#building-individual-payload-attributes

feat(data): Account Types

Ethereum has two types of accounts: EOAs with no code, and contract accounts with code. Some chains like zkSync and starknet have native account abstraction.

The initial set of chains (mainnet, arbitrum, optimism) all match, so this should be easy to implement. However, do some research into other chains that have native AA to inform how to shape the data structures.

feat(data): Contracts

This issue is eligible for a bounty paid out in OP tokens. Read CONTRIBUTING.md to learn how to qualify.

  • Are contracts like Multicall3, 0x4e59b44847b379578588920cA78FbF26c0B4956C, 0age's create2 factory, xdeployer, Gnosis Safe contracts, etc. deployed? If so, at what addresses, links to deploy instructions, and references.
  • Canonical addresses for popular protocols: Should we just show the top X protocols on DeFiLlama by TVL? TVL doesn't make sense for all protocols though, e.g. Umbra, so maybe this just becomes an "anything goes" list where everything is hidden by default and you can search, this way people can PR in their own protocols. We can either leave this out for the scope of the issue, or include e.g. Uniswap V3 just so there's a template for people to add more protocols in the future
  • Canonical addresses for popular tokens: Same question as previous bullet. We should include WETH (not always called WETH depending on the chain, e.g. Polygon's native token is MATIC so the wrapped version is WMATIC), DAI, USDC, and USDT to start

feat(data): EIPs

This issue is eligible for a bounty paid out in ETH on OP Mainnet. Read CONTRIBUTING.md to learn how to qualify.

This should contain all live EIPs for a given chan. Initial suggested types, subject to change as needed, and fields can be made optional as needed:

type EIPParameter = {
  name: string;
  value: string | number | bigint | boolean
}

type EIP = {
  number: number;
  title: string;
  link: string;
  // The status should always be `Final` for now.
  status: 'Draft' | 'Review' | 'Last Call' | 'Final' | 'Stagnant' | 'Withdrawn' | 'Living';
  activeHardforks: MainnetHardfork[]; // Would need other enums for other chains, this enum is for mainnet only.
  deprecated: boolean;
  // Some EIPs have parameters, such as EIP-1559, but these parameters may not be the same on all
  // chains. This field is intended to list the names and values of any parameters that exist.
  parameters?: EIPParameter[];
};

Notes:

  • All implemented mainnet EIPs can be found here. Trail of Bits' Building Secure Smart Contracts has a Forks <> EIPs section which may be helpful too. (They have this for Celo, Tron, and BSC also, but some/all of this might be out of date)
  • There are getHardforksFrom and getHardforksTo methods for helping generate the activeHardforks range
  • Other EIPs may be final and not yet active, since they'll be in a future hard fork. These can be ignored for now.

feat: hardfork support

Right now we only document the latest supported features. This is ok but doesn't scale nicely, consider:

  • All chains import mainnet data as their base, then modify it
  • Mainnet adds a new opcode or transaction type
  • Now you have to go into all other chains and remove that

An alternative approach would be for mainnet data to have an array of hard forks that supported that e.g. opcode (or, just the earliest hard fork), and other chains would filter the list based off the hard fork data. This probably would scale much better

This hard fork data can also be shown in the diffs

feat: add tests for opcode behavior

For precompiles, we verify their existence with a test case that includes input calldata and an expected response. We should do something similar for opcodes. A few examples of why this is valuable:

  • create/create2 are supported on most chains, but some have different behavior (i.e. deploying to different addresses, like zksync).
  • On L2s, many opcodes behave differently by e.g. returning constants. For examples, see optimism docs and arbitrum docs

Ethereum mainnet should be considered the source of truth for what opcodes are expected to return. When there are deviations in a behavior, the UI should indicate this (for example, color the cell yellow on the /features page)

If feasible, consider reporting on the gas usage as well to facilitate gas comparisons.

cc @fvictorio

feat: block production

Initial ideas below

Block Times

Fetch the latest block number, and fetch the latest n blocks.

  • If block time deltas are the same, assume that delta is a constant block time.
  • If block times are the same mod x, assume x is the block time (e.g. some blocks may have been missed, like on mainnet)
  • If block times vary, take the median value.
  • For some chains the JSON files will change every time bun fetch-data is run. We will need to add handling for this expected difference to avoid the hourly CI checks from constantly reporting diffs.

Gas Limits and Targets

For OP Stack chains we can get the gas limit from the L1 SystemConfig contract, but there's no way to automatically discover that contract address without adding it as an additional input. Unsure how to do this for other chains.

style: UI/UX and appearance improvements (bounty: 400 OP 🔴)

This issue is eligible for a 400 OP bounty. Read CONTRIBUTING.md to learn how to qualify.

The current design leaves room for improvement. In no particular order, below are some lists of things that should be improved. I don't really have any specific suggestions here because I'm not a good designer, so hoping to find someone that has a good eye for design and can use their judgment based off the existing appearance/color scheme.

Note that there's a lot of stuff in this issue, and it can definitely be chunked into separate PRs for a portion of the bounty. If you only want to tackle certain parts, just reply with the numbers below.

General

  1. Generically, a nicer site design (better font, colors, background, a logo, etc.)
  2. The combobox input (src/components/ui/ChainDiffSelectorChainCombobox.tsx) should show the logo of the selected chain, similar to how the dropdown does. It looks like this, but should look like this

DIff Page

This refers to the pages/diff.tsx view, e.g. http://localhost:3000/diff?base=1&target=10

  1. This page is currently not great looking, and I'm open to big changes here. Each diff component (e.g. DiffMetadata.tsx, DiffPrecompiles.tsx, etc.) that's rendered on this page should probably be wrapped in some container component to make it easier to change the style of each header/row without needing to change each Diff*.tsx component individually
  2. Sections should be collapsible/expandable, e.g. an arrow next to the "Metadata" header to hide all metadata
  3. This view should look good on both desktop and mobile
  4. Entries need a way to show more detail and should be able to render HTML to show e.g. hyperlinks, or expandable/collapsible info blobs
  5. Similarly, would be nice to have tooltips or something UI element that allows you get more info/descriptions
  6. Somewhere on the page (the bottom?) should include the ChainDiffSelector.tsx so you can easily compare two different chains without needing to go back to the home page
  7. When the "Only show differences" toggle is unchecked, i.e. everything is shown, there should be a visual indicator to make it easy to tell which rows are different and which match
  8. Addresses can always be displayed as 0xAAAA....BBBB where AAAA and BBBB are placeholders for the first and last 4 characters of the address. However, if two addresses match the first and last 4 but differ in the middle, then that difference should be visible and not hidden by ...

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.