Abstract
This ERC aims to standardize design choices for decentralized applications (dapps) in order to enhance censorship resistance across the ecosystem. At the core of the specification is a set of requirements and recommendations around how the dapp is hosted and distributed, as well as a set of extensions to the Web Application Manifest W3C spec.
Motivation
The decentralized web aims to provide applications that are resilient against censorship and single points of failure. However, many “decentralized” applications still depend on centralized infrastructure, creating vulnerabilities. This ERC addresses this by:
- Making dependency information explicit and machine-readable
- Providing redundancy options for critical services
- Making critical services reusable across dapps
- Standardizing how dapps interact with blockchain infrastructure
By following this specification, dapp developers can create applications that are more resilient, and users gain access to tools that can help bypass potential censorship.
Specification
The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “NOT RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119 and RFC 8174.
Naming and Distribution
This section deals with how to package, publish, and distribute your dapp.
ENS domain
Dapps conforming to this specification MUST use an ENS domain (ERC-137) as their primary identifier. The contenthash of the given ENS domain SHOULD be set to an IPFS CID (content identifier).
Name governance
Updating of the ENS contenthash for the dapp SHOULD be controlled by a multisig. The exact structure of this multisig is important, but will vary from a case to case basis, so is considered out of scope for this spec.
Immutable subdomains
Use of immutable ENS subdomains where <version>.example.eth
points to a specific version of the dapp is RECOMMENDED. The version subdomain contenthash MUST NOT be possible to change or update after it’s set once. The version name MAY be an incremental number, e.g. v1.example.eth
, or use semantic versioning, e.g. v1-2-5.example.eth
.
Distribution Packaging
The dapp MUST be a static webpage, meaning no server is needed to access or run it. This allows it to be hosted on decentralized storage like IPFS or served from any static hosting platform.
Resource integrity
IPFS is the RECOMMENDED way of distributing the content, e.g. set the ENS contenthash to a CID.
IPNS is NOT RECOMMENDED.
All static resources SHOULD be contained within the directory enclosed by the aforementioned contenthash. This means that all media and scripts transitively loaded from loading index.html
, or any other page MUST be contained in the content referred to by the contenthash.
All static resources SHOULD include a Subresource integrity hash where possible, e.g. on <script>
and <link>
tags.
Endpoint Priority
This section describes how a dapp should prioritize different endpoints for accessing blockchain state and submitting UserOperations.
Ethereum RPC
The ethereum RPC is used to access blockchain state on the L1 or other EVM compatible networks (specified with chain-id). The method of access MUST be prioritized in the order defined below.
1. Query Parameter Override
Dapps MUST support overriding the ethereum rpc endpoint for any chain-id using query parameters in the following format: ?ds-rpc-<CHAIN_ID>=<url>
. When provided, the dapp frontend MUST parse the URL and prioritize it over any other method of accessing chain state for the given chain-id. Note that the user may provide overrides for one or multiple chain-ids at the same time.
For example, a user may specify an endpoint for mainnet as follows:
example.eth?ds-rpc-1=https%3A%2F%2Fmainnet.infura.io%2Fv3%2FYOUR-API-KEY
2. Injected provider
If an injected provided, i.e. window.ethereum
is present it MUST be prioritized over all alternatives options, except the query parameter override.
3. Browser based light clients
Although not widely available today, browser based light clients might become a viable alternative in the future. For now they MUST be prioritized after the injected provider, if supported by the dapp, but security and privacy implications are not well known so this might need to be adjusted in the future.
4. Hardcoded RPC endpoints
Dapps MAY provide additional RPC endpoints. It is RECOMMENDED that they include multiple URLs from different providers to provide redundancy.
4337 Bundlers
4337 bundlers also provides a json-rpc endpoint per chain-id. The different methods of accesing these RPCs MUST be prioritized as follows.
1. Query Parameter Override
Dapps MUST support overriding bundler rpc endpoints for any chain-id using query parameters in the following format: ?ds-rpc-<CHAIN_ID>=<url>
. When provided, the dapp frontend MUST parse the URL and prioritize it over any other method of interacting with a bundler for the given chain-id. Note that the user may provide overrides for one or multiple chain-ids at the same time.
For example, a user may specify an endpoint for mainnet as follows:
example.eth?ds-bundler-1=https%3A%2F%2Fbundler.example.com
2. P2P browser based mempool
Although no current implementation exists, in theory it could be supported by the bundler mempool p2p network. If a dapp implements such a protocol, it MUST be prioritized after the query parameter.
3. Hardcoded Bundler Endpoints
Dapps MAY provide additional bundler endpoints. It is RECOMMENDED that they include multiple URLs from different providers to provide redundancy.
Use of Web Application Manifest
Your dapp MUST have a Web Application Manifest. Just as the W3C spec recommends, use a link
element to link to your manifest.webmanifest
file.
Required Members
Fields in the web app manifest is referred to as “members”. A dapp should consider the following:
- A
name
member is REQUIRED - At least one item in the
icon
member is REQUIRED - A
description
member (as defined in the Application Info spec) is RECOMMENDED - A
screenshots
member (as defined in the Application Info spec) is RECOMMENDED
Code Repository Extension
A dapp MUST add a dapp_repository
member. It MUST contain a url
pointing to the source code of the dapp. Ideally both frontend code, and any backend/indexing service if relevant.
History Preservation Extension
A dapp MAY add a dapp_preserve_history
member. If present it MUST contain an integer. This value indicates how many historical versions pinning services should maintain. A value of -1
means all historical versions should be preserved. The default value if not present is assumed to be 0
, e.g. only the current version is pinned.
A version here means a unique update to the contenthash field on the dapps ENS name, i.e. every time the contenthash is updated it is considered a new version. Although pinning services SHOULD follow the value specified here, there is no guarantee that they won’t store historical versions for longer.
Dapp Service Extension
A dapp MAY add a dapp_service
member. If present it MUST contain an array of urls
.
A dapp service, or dservice for short, is a backend service that provides the dapp with specialized functionality beyond what is provided through ethereum RPCs and bundlers. Each dapp MAY implement one dservice (it MUST NOT implement more than one) and it is RECOMMENDED to provide multiple endpoints for this service that are hosted on independent infrastructure (these urls are to be provided in the dapp_service
member).
DService Requirements
- MUST only rely on indexed data from blockchains (Ethereum L1/L2s) or content-addressed data
- MUST be deterministic - all DService nodes given the same input should produce the same state
- MUST be open source so that anyone can run their own instance
- Dapps SHOULD provide multiple endpoints for redundancy
- Each endpoint MUST be listed in the
$dservices.self
array
Query Parameter Overrides
If the dapp is using a dservice it MUST provide the ability to override the service endpoint with the following query parameter: ?ds-self=<url>
.
External Dapp Services Extension
A dapp MAY add a dapp_external_services
member. If present it MUST contain an array of ENS domain names.
If a dapp consumes the dservice of one or multiple other dapps it MUST list the ENS names of the DServices it consumes.
For example, if dappA consumes the the dservice of dappB, it MUST list dappB.eth
in the dapp_external_services
array. This SHOULD be implemented in dappA by first fetching the Web Application Manifest from dappB.eth
to get the latest endpoints for its dservice, then query those endpoints directly.
Query Parameter Overrides
If the dapp is using external dservices, it MUST provide the ability to override each of them using the following query parameter: ?ds-<ens-name>=<url>
Auxiliary Services Extension
If a dapp is using any additional endpoints besides RPCs, Bundlers, or DServices, they MUST be listed in the dapp_auxiliary_services
member. If present it MUST contain an array of objects, with the following schema:
interface Auxiliary {
/*
* A description of what this service is used for
*/
motivation: string;
/*
* A list of domains required to make use of the functionality described
*/
domains: string[];
}
The dapp_auxiliary_services
array lists domains for non-essential services that the dapp uses but aren’t critical to its core functionality:
- Analytics platforms
- WalletConnect, or similar wallet connectivity approach
- Monitoring services
- Feature flagging services
- Non-critical API integrations
The dapp MUST list all auxiliary service domains it utilizes.
Contracts Extension
A dapp MAY add a dapp_contracts
member. If present it MUST contain the chain-id and addresses of contracts which the dapp supports interacting with. The dapp_contracts
must be an array of objects with the following schema:
interface Contract {
/*
* The chain id for the blockchain which the contract is deployed on.
*/
chain_id: string;
/*
* The address of the smart contract.
*/
address: string;
}
Example manifest.webmanifest
To get a sense of what a manifest file might look like, here’s an example:
{
"name": "ExampleDapp",
"description": "Just a simple example dapp",
"icons": [
{
"src": "favicon.webp",
"sizes": "any",
"type": "image/webp"
}
],
"screenshots": [{
"src": "images/screenshot.png",
"sizes": "800x600",
"form_factor": "wide",
"label": "With ExampleDapp you can try many different decentralized things"
}],
"dapp_repository": "https://github.com/example-org/example-dapp",
"dapp_preserve_history": -1,
"dapp_service": ["https://example.com", "https://example.org"],
"dapp_external_services": ["other-example.eth"],
"dapp_auxiliary_services": [{
"motivation": "Required by the token search widget to display token names, prices and icons",
"domains": ["live.crypto-prices.com", "token-icons.com"]
}],
"dapp_contracts": [{
"chain_id": 1,
"address": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"
}]
}
Rationale
This specification is designed to highlight design choices necessary to build truly resilient applications.
Naming and Distribution Recommendations
While ENS supports other protocols. IPFS is recommended because:
- It has wide adoption
- Can be run locally and well packaged dapps can be persisted completely offline
- Has no token dependencies
- Standardizing on one protocol simplifies ecosystem tooling
IPNS is not recommended because:
- Content can change at any time at the key holder’s discretion
- There is no persistent log of changes, unlike publishing CIDs directly on-chain
- There is no simple way to create stronger governance mechanism around updating the CID
Containing all static resources within the contenthash is necessary because any resource loaded from a third party server poses a privacy or even security risk.
Subresource integrity checks are recommended because ENS gateways (such as eth.limo / link / sucks / ac) could in theory serve malicious content since IPFS hashes of subresources are not verified by these gateways (note that this is not the case for the in browser IPFS gateway at inbrowser.link)
The ENS contenthash updates that happens onchain should ideally be controlled by as strict of a governance process as possible. The exact approach will vary on a case to case basis. This ERC recommends using a multisig to provide a baseline. Ideally approaches where permanent versions of dapps are hosted on subdomains, e.g. <version>.mydapp.eth
is used as well.
RPC and Bundler Priority
By specifying a priority for Eth RPCs and Bundler endpoints dapps can provide users with both redundancy in case underlaying services go down, but also a way to increase privacy by letting users specify their own custom endpoints. User might run their own nodes, or trust certain providers to not exploit their data. By prioritizing window.ethereum
the choice of rpc gets outsourced to the browser or wallet, which is a choice the user already has made and in general should be better than the arbitrary choice of provider that the dapp makes.
Web Application Manifest Members
The name
and icon
are necessary if you want to enable the dapp to be installed as a PWA (Progressive Web Application). Additionally the description
and screenshots
provide useful information to dapp explorers or wallets that want to show more information about the app to the user before they decide to open it. The same is true for dapp_repository
as it can be used to build trust between the user and developer.
The dapp_preserve_history
is useful to help dapp pinning services to know how to persist old versions of your dapp. Currently most IPFS pinning services have no way of dealing with this.
Why are DServices needed?
The introduction of the dservice concept is an acknowledgement that most applications need to rely on some sort of indexer to access blockchain data. The ethereum rpc api is simply not sophisticated enough for advanced query functionality. Unfortunately most applications simply build a backend indexer and call it a day. If their endpoint goes down the application goes down with it, losing the benefit of distributing the frontend over ENS and IPFS in the first place. DServices mitigates this by allowing apps to specify multiple backups. Additionally since the dapp is required to support the dservice fallback query param, even if all provided endpoints go down, users can run their own indexer as a last resort.
Another benefit of dservices is that they can become a canonical way for dapps to expose backend apis. By allowing dapps to define other ENS names as $dservices.external
, infrastructure can be shared across multiple dapps. For example, dappA.eth
can expose a dservice with three backing endpoints, dappB.eth
can now consume the dservice of Dapp A by resovling dappA.eth/.well-known/dappspec.json
and using the resolved urls to make requests. Dapps could even implement payment or subscription services where they charge other dapps to consume the default endpoints.
Auxiliary services
By default dapp browsers, or browser extensions may choose to block any requests made to arbitrary services in order to improve security and user privacy. By listing domains of auxiliary services the dapp uses along with a description, browsers could give users the ability to selectively approve certain services.
Contracts
While listing the addresses of smart contract your dapp interacts with is by no means necessary, it could provide users with additional information if the dapp is listed in some sort of dapp explorer. For example, a user might want to search for apps that affords them to interact with a certain contract.
Backwards Compatibility
Adding a dappspec.json
or following the requirements provided by this ERC should be completely backwards compatible with existing dapps.
Reference Implementation
TODO - this section is out of date
These two applications conform to the specification in the ERC:
- dapprank.eth - manifest.webmanifest
- justw.eth - manifest.json (there’s currently a bug in Safe where it doesn’t accept manifest.webmanifest, or read manifest from the link tag)
Additionally dapprank.eth benchmarks applications based on how well they follow this specification (as well as further in depth, per dapp analysis).
Security Considerations
Even though a dapp provides a valid manifest.webmanifest
file doesn’t mean its implementation actually provides the functionality it claims. Browsers or extensions that wish to utilize this ERC needs to take this into consideration. It’s also possible for third party evaluation tools to analyze the claims made by a dapp in its manifest.webmanifest
and how well it conforms to this spec in general.
Copyright
Copyright and related rights waived via CC0.