Currently the way to use sourcify is through the repo.sourcify.dev domain. This app talks to the sourcify backend in order to resolve metadata about the contract. This is fragile because if the sourcify backend were to for some reason be censored or disappear access to smart contract metadata would not be possible.
This document outlines an approach to publishing contract metadata directly on Ethereum using ENS+IPFS, without having to make any additional on-chain transactions. The core idea is to resolve <address>.<chain-id>.sourcify.eth to a website with full verification of smart contract at that address.
The overall approach relies on the on-chain contract metadata hash, a custom ENS resolver, as well as precomputing IPFS objects off-chain.
In order to enable domains such as https://0x99acdb2946b7240aac9aeb290cc0af145c48007e.1.sourcify.eth.limo to resolve we need to implement a custom ENS resolver. More specifically we need to implement a wildcard resolver (ENSIP-10), so that even names without a specific resolver can be handled by a parent resolver, e.g. all domains that match *.example.eth can be handled.
We want to support two levels of subdomain resolution:
chain-id- since we want to support multiple networksaddress- the contract address itself
For (1) we can use a static resolver that just points to a wildcard resolver for addresses. On mainnet this is trivial, just use the address of that resolver. To support L2 networks we need to point to a resolver on that network. We will cover that in a later section. For now let's focus on how we would implement the wildcard resolver
According to the ENSIP-10 spec the resolver will be called like this:
result = resolver.resolve(dnsencode(name), calldata)
We can parse the DNS encoded name and parse the contract address. Then we find the contract code (i.e. address(contractAddress).code) and parse the contract metadata hash. We can convert this hash to a CID in the following way:
function hashToCid(bytes32 metadataHash) internal pure returns (bytes memory) {
// Create multihash: 0x1b (keccak-256) + 0x20 (32 bytes) + hash
bytes memory multihash = abi.encodePacked(hex"1b20", metadataHash);
// Create CIDv1: 0x01 (version) + multihash
return abi.encodePacked(hex"01", multihash);
}Now if we only returned this CID the resolver would simply result in the user loading the metadata.json, but we want to return a functioning webapp similar to the existing repo.sourcify.dev website. This can be achieved in the following way:
- The resolver contract should hold two variables:
indexHtmlCid- the CID of the index.html of the webappassets- the CID of a folder containing assets needed by the webapp, e.g. javascript, css, etc.
- The resolver takes these two variables along with the metadata.json CID and constructs an IPFS UnixFs folder with the following structure
This can be easily achived using the UnixFs portion of the CIDv1.sol library.
{ "metadata.json": <metadata-cid>, "index.html": <index-html-cid>, "assets": <asset-folder-cid> } - Next we hash the folder constructed in (2) and calculate it's CID
- Finally the resolver encodes the CID in the ENS contenthash format and returns it as the resolution result
In order to support chainIds other than 1(mainnet) we need to deploy the wildcard resolver on each L2, then point to it using the CCIP-read standard. This is a system that enables off-chain and L2 support for name resolution in ENS. We won't cover how to do it here because it is already extensively documented by ENS.
The webapp mentioned above is a generalized webapp with a similar look and feel to the current contract page of repo.sourcify.dev. It has a few requirements:
- Loads contract metadata from
/metadata.json - All javascript, css, fonts, and other assets needs to be loaded from
/assets/* - Should ideally verify the contract source code client side in the users browser
In order for urls like https://0x99acdb2946b7240aac9aeb290cc0af145c48007e.1.sourcify.eth.limo to resolve to an actual website, the folder created by the wildcard resolver also needs to be available off-chain (in particular, it has to be retrievable over IPFS). We can achieve this by running an off-chain process that precomputes it and stores it in an IPFS node (ideally the node(s) sourcify already operate). It likely makes sense to do this for all contracts that have been verified on sourcify.