Skip to main content

Onay: 2FA for Signing

· 8 min read
A person holds a hardware wallet showing a calldata digest and compares it with the same digest in the Onay app on their laptop

If you've signed anything on Ethereum you know this feeling. The wallet pops up: an address, some hex, a function name... Where are these coming from? Am I even talking to the correct contract? What am I even signing? What is this action going to do?

You just press "Confirm" and hope for the best.

Introduction to ERC-7730: Clear Signing for Ethereum

· 9 min read

I guess the problem needs no introduction. Blind signing has been a big pain, arguably the biggest UX pain, of Ethereum since its inception. It's just not possible for humans to know what they are about to sign and authorize on their wallets. Any step compromised on the transaction/signature supply chain can cause a malicious signature to enter your wallet and you unknowingly approve a transaction that drains your funds.

We puzzled over this a while ago at Sourcify and we shared our learnings in this blog post at the time. In short we didn't have the means and resources and we focused back on our main value proposition, that is verifying contract source code and making open-source the default and opening up the siloed verified contract data.

Recently we formed a larger working group around the ERC-7730 standard for clear signing and we are excited to be part of it. We believe this movement and the standard is capable enough technically to solve the issue and has the necessary momentum to reach escape velocity and be the shared standard.

However, building things is hard, especially in a decentralized manner with tens of different stakeholders. It's non-trivial to scale this to millions of contracts, and dozens of wallets and applications that have different needs. We need active feedback to understand better the needs of different users of the standard.

Here I'll give a short technical introduction to how the ERC-7730 standard works for the human reader, and ask how this fits your wallet or application.

ERC-7730: Structured Data Clear Signing Format​

The idea​

The ERC-7730 standard was first authored by Ledger, and has been evolving since the formation of the working group mentioned above. The spec defines a JSON file called a "descriptor" that enriches the ABI letting a processor turn a formatted calldata or structured EIP-712 message into human readable intent messages.

ABI is the default formatting within EVM, and it's oriented for machines, not humans. For example, a token's amount is encoded as 1000000000000000000 but to make this understandable for a human you need to know which token it is, its ticker, and its decimals so that you can show them 1 UNI. This information is not in the ABI and needs an extra metadata file, hence the ERC-7730 enrichment. (Sadly today a lot of wallets won't even do basic ABI decoding)

The example above is a popular but trivial one. It gets infinitely complicated for arbitrary calldata that can have arrays, tuples, nested calldata etc. What ERC-7730 does is, it declares what each parameter of a function is: this is a token, that one is a date, this is a calldata so decode that recursively etc. and explains how to do the formatting.

The important thing and the tradeoff here is that we can't allow a Turing complete expression system that would open a can of worms for wallets. Wallets need to be isolated and minimal devices. That's why even ERC-7730 is not a silver bullet and can't possibly solve everything, but it can solve maybe 99% of cases.

Example​

Let's continue with examples. Here is the Tether descriptor in the registry today.

{
"$schema": "../../specs/erc7730-v2.schema.json",
"context": {
"$id": "Tether USD",
"contract": {
"deployments": [
{ "chainId": 1, "address": "0xdAC17F958D2ee523a2206206994597C13D831ec7" },
{ "chainId": 137, "address": "0xc2132D05D31c914a87C6611C10748AEb04B58e8F" }
]
}
},
"metadata": {
"owner": "Tether Limited",
"info": { "url": "https://tether.to/", "deploymentDate": "2017-11-28T12:41:21Z" },
"token": { "ticker": "USDT", "name": "Tether USD", "decimals": 6 },
"contractName": "Tether USD"
},
"display": {
"formats": {
"transfer(address _to, uint256 _value)": {
"intent": "Send",
"fields": [
{ "path": "#._value", "label": "Amount", "format": "tokenAmount", "params": { "tokenPath": "@.to" } },
{ "path": "#._to", "label": "To", "format": "addressName", "params": { "types": ["eoa"], "sources": ["local", "ens"] } }
]
},
"approve(address _spender, uint256 _value)": {
"intent": "Approve",
"fields": [
{ "path": "#._spender", "label": "Spender", "format": "addressName", "params": { "types": ["eoa", "contract"] } },
{
"path": "#._value",
"label": "Amount",
"format": "tokenAmount",
"params": { "tokenPath": "@.to", "threshold": "0x8000000000000000000000000000000000000000000000000000000000000000" }
}
]
}
}
}
}

Let's break it down.

  "$schema": "../../specs/erc7730-v2.schema.json",

The first line says which JSON schema this JSON file conforms to. The spec declares versioned schema files and it will evolve over time in new versions. You usually want to bind this to a major e.g. v2 but you can also pin a specific version v2.1.3.

  "context": {
"$id": "Tether USD",
"contract": {
"deployments": [
{ "chainId": 1, "address": "0xdAC17F958D2ee523a2206206994597C13D831ec7" },
{ "chainId": 137, "address": "0xc2132D05D31c914a87C6611C10748AEb04B58e8F" }
]
}
},

Here we have the context, i.e. the chainIds and addresses this descriptor describes.

  "metadata": {
"owner": "Tether Limited",
"info": { "url": "https://tether.to/", "deploymentDate": "2017-11-28T12:41:21Z" },
"token": { "ticker": "USDT", "name": "Tether USD", "decimals": 6 },
"contractName": "Tether USD"
},

Some metadata here about the contract, and also a place where you can declare constants like the nativeAssetAddress: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE or enums like:

{
"metadata": {
"enums": {
"interestRateMode": {
"1": "stable",
"2": "variable"
}
}
}
}

Finally we have the display section with .formats:

      "transfer(address _to, uint256 _value)": {
"intent": "Send",
"fields": [
{ "path": "#._value", "label": "Amount", "format": "tokenAmount", "params": { "tokenPath": "@.to" } },
{ "path": "#._to", "label": "To", "format": "addressName", "params": { "types": ["eoa"], "sources": ["local", "ens"] } }
]
},

Here this formatting is bound to the transfer function which has the signature transfer(address,uint256) with the selector 0xa9059cbb. Note the parameter names above: the path fields (#._value, #._to) reference the parameters by these names. Here instead of using the full ABI JSON, we pass a shorthand format, what's called "Human-Readable ABI" by ethers.js.

The intent for this action is "Send" so the first message the user will see will be this.

Following, the user will see the "Amount". Besides the "label", the descriptor tells what "format" this is, in this case it says "the parameter _value is a tokenAmount and the token is tokenPath which is the tx destination .to".

Finally, the "To" is an addressName, marked in the function param _to.

In the end the formatter will do its magic and this will translate to:

Send
Amount 1 USDT
To vitalik.eth

This is of course a very basic example. You can find other examples in our playground https://clear-signing.sourcify.dev/.

The registry​

The ERC spec explains the individual descriptors and how to format but does not prescribe where to find the descriptors.

The registry initially was published under Ledger's Github. Recently it's been migrated under the ethereum organization as a neutral host. It's open and available at https://github.com/ethereum/clear-signing-erc7730-registry.

Attestations​

As a sibling to this spec, we define an attestation spec based on Ethereum Attestation Service (EAS) under ERC-8176.

The idea is that the registry will become more permissive. Being included in the registry does not mean that a descriptor is "safe" or "approved". Instead auditors, wallets, and protocols can attest that a descriptor faithfully represents what the underlying contract does.

Under EAS, attestations are made with an Ethereum address. Your wallet or application can choose which attesters to trust. You can say that you trust Ledger and Cyfrin and only use the descriptors if there's an attestation by them. Your app should also check revocations, and revocations have to be done onchain. Attestations can be done onchain or offchain.

Additionally, this gives provenance guarantees to the descriptors. Just embed some attester addresses you trust and implicitly you can be sure the descriptor the device received is not a tampered one.

In theory any attestation scheme is possible but we think EAS is a good starting point. It also does not matter where the attestations come from (e.g. offchain) since they are signed anyway. A first round of attestations are published by Cyfrin and can be seen in Cyfrin's first attestations PR.

Onchain registry​

Currently the registry is under Github for convenience and as a widespread tool. Our goal is to move to an onchain registry that's permissionless.

Design work is currently underway. The high level idea is that attesters push attestations to the onchain registry under their own address. Hence the namespaces are isolated and anyone can attest descriptors. The attesters will also declare where to find the attestation blobs and the descriptor blobs under Mirrors. This can be through ipfs:// or https:// links. Anyone can host mirrors, i.e. host attestation and descriptor blobs.

A simple explainer for the registry is here: https://gist.github.com/kuzdogan/d07974040f505460e48c55e3badcece5

The full spec is an ongoing ERC work: https://github.com/ethereum/ERCs/pull/1789. It is long; an LLM can help you read it. Feedback is more than welcome.

Next Steps and Feedback​

Our fear is if we are building this in isolation and it turns out what's needed and useful by the wallets is a completely different thing than what we've built. That's why we need to better understand your view and how this fits your needs.

Some example questions to think about:

  • What is currently holding you back from implementing ERC-7730?
    • You don't trust yet the implementations or the registry?
    • Are you worried about the security implications?
    • This is not a high enough priority for you and your users?
  • Is your wallet capable of doing clear signing locally? What are the limitations?
    • Can you fit the full json and decode?
    • Can you check attestations?
    • What are the screen size and format limits?
  • Would you use the existing SDKs (below) or build your own?
  • How would your wallet's backend integrate ERC-7730 on a high level? How will you serve the descriptor files and attestations?
    • Do you use an in-house cache or DB? How do you update it?
    • For example, the spec prescribes to attach descriptors to implementations and not proxies. Can you resolve proxies on your backend to serve clear signing for proxy contracts?

Please reach out on X: kaanuzdogan, Telegram: kuzdogan, or Matrix: kuzdogan:matrix.org.

Check out the registry and submit your descriptors: https://github.com/ethereum/clear-signing-erc7730-registry

Reference​

Spec​

The spec is available here: https://eip.tools/eip/7730

Registry​

The registry is available here: https://github.com/ethereum/clear-signing-erc7730-registry

Implementations​

Below are the live implementations:

Sourcify Mid-2026 Recap & Roadmap

· 4 min read

Sourcify ensures verified EVM smart contract data remains open and accessible, and is powered by open-source tools.

Our biggest focus this half-year has been on the Clear Signing Working Group efforts, as this has the highest impact for the ecosystem. On top of that, we kept pushing on increasing the contract coverage and on parallel verification in frameworks to widen the top of the verification funnel. The team worked at roughly 70% capacity this period, as one of the team members has been on parental leave.

Current tasks and milestones can be followed on the GitHub Project Board.

Making Clear Signing the default on Ethereum

· 4 min read

Clear Signing Working Group

An Ethereum Working Group consisting of wallet developers, security firms and the Ethereum Foundation's Trillion Dollar Security Initiative today launched an open standard designed to end blind signing — bringing a major UX and Security upgrade to transaction signing on Ethereum. It ships as an open standard (ERC-7730), a neutral registry, an attestation framework, and developer libraries. Sourcify is one of the contributors.

For years, Sourcify has been concerned about what we see as one of the biggest missing pieces in the Ethereum stack: even when source code is verified and open, users still cannot meaningfully read what they are about to sign.

This is not a new concern for us. Back in 2023 we started a Working Group on Human-Readable Transactions, and revisited the whole problem space in March 2025 after the ByBit hack. In fact it's been years we have been thinking about the blind signing problem and advocating to find the right solution.

We are glad to now be part of a working group, with an open spec and a neutral registry, that is finally positioned to solve this problem once and for all.

API v1 Brownouts: Time to Move to v2

· 3 min read

Sourcify's legacy v1 API has been deprecated for a while. v2 has been the primary, recommended API for over a year: async job-based verification (no more hanging requests), richer responses (storage layouts, source maps, transformations, proxy resolution), clearer naming (exact_match / match instead of the confusing "perfect" / "partial"), and it's already serving 15M+ requests/day.

But deprecation notices alone don't move the needle. So we're following the GitHub playbook and rolling out scheduled brownouts to push the remaining v1 traffic onto v2.

Sourcify 2025 Recap

· 7 min read

2025 is coming to an end and it's a good time to look back at what we've achieved this year.

This year was the year we shipped the most within Sourcify. We were mostly heads down building. We now have the feeling that Sourcify in its current form has a solid foundation and we completed most of the "must have" features.

Next year we will be focusing on getting out there and promoting Sourcify, and having such a strong foundation will let us go all in on community building and outreach. We still saw a lot of traction even if we haven't put much effort into it this year.

This also means the next year will be more about getting out there and promoting Sourcify, as well as focusing our efforts elsewhere where we think we can make a bigger impact. More on that in a separate post.

Argot: Before we get into the technical achievements, the biggest news for Sourcify has been the foundation of the Argot Collective. Argot is an Ethereum Foundation spin-out with Ethereum infrastructure projects, including the Solidity language, and will be the new home for Sourcify!

Next, here's a quick recap of what we've achieved this year at Sourcify:

APIv2​

The biggest change this year was finalizing the APIv2. We first shipped endpoints to lookup verified contracts (see the blog post), and later we shipped the verify endpoints.

There were many reasons to move into this direction. The main ones were:

  • The legacy API was based on a filesystem storage. The filesystem does not scale and limits us in many ways.
  • After switching to a database storage, we were able to share a lot more data around the verification process, which wasn't reflected in the legacy API.
  • After submitting a verification, users received a response only after compilation and verification completes. This often led to timeouts and hanging requests causing both bad UX and server load.

This was a very well executed series of milestones with a clear design process, through thinking, discussion, and feedback from the community. We are proud of how and what we shipped.

Our APIs serve over 15 Million requests per day as of now with minimal downtime this year (all Sourcify APIs).

Verifier Alliance​

The other most impactful change this year was the Verifier Alliance. We teamed up with Blockscout and Routescan to create a shared database of verified contracts. The initiative's seeds were planted in 2024 and this year we finally shipped the first version of the database and got all members to start sharing their verified contracts.

This dataset is crucial for the decentralization and openness of the verified contract data, and we believe this will enable a lot of innovation in the EVM tooling space. Our efforts were also recognized by the Trillion Dollar Security Initiative by the Ethereum Foundation, as well as through direct feedback from the community.

Verify Everywhere​

"Verify Everywhere" is our motto moving forward and the direction we are trying to push the whole ecosystem towards. We believe contracts should be verified everywhere instead of a single verifier/explorer. This should not cause any friction for the user and should be seamless.

In this direction we shipped the Remix plugin and it received a lot of traction from day 1.

We also implemented the necessary changes in Hardhat to get our PR accepted and shipped, and since recently Hardhat also supports verifying on multiple verifiers at once.

We are hoping to have this in Foundry ASAP as well.

Finally, apart from the tooling, we also implemented this on Sourcify side. This means, whenever a user verifies a contract on Sourcify, we will submit this verification to all verifiers (Blockscout, Etherscan, Routescan).

Vyper support​

We shipped Vyper support this year. This was a long time coming and we are happy to have Vyper on board! Our language agnostic architecture allows us to support any EVM language without any friction.

verify.sourcify.dev UI​

Our new verification UI verify.sourcify.dev was live this year. It leverages the new APIv2 capabilities such as showing job status, user friendly error messages, bytecode diffs, etc. See one example here.

repo.sourcify.dev UI​

We also shipped the new repo.sourcify.dev based on the rich information APIv2 endpoints provide.

All information a user might need related to a compilation and a verified contract is available in the repo.

4byte.sourcify.dev​

We took over the most popular 4byte signature service openchain.xyz's database and API. Today this service serves over 7 Million requests per day.

We seeded the database with the existing openchain dataset, the 4byte.directory dataset, and the etherface data, as well as our own verified contracts dataset. We implemented the exact openchain API and now Sourcify serves all signature requests to openchain.xyz as well.

We also shipped a friendly web interface to search for signatures in the database. See 4byte.sourcify.dev for more details, and this short analysis on signature collisions.

BigQuery and AI Playground​

We started to mirror the Sourcify dataset on BigQuery. This allows fast querying and analysis of the data, as well as using Google Colab Python notebooks to analyze data, combined with other datasets (example).

We also built an AI playground on our main page to demonstrate the power of the open Sourcify dataset. Users can ask questions like "How many contracts are verified on Sourcify?" or "What is the most verified contract?" and the AI will answer with the necessary SQL query to be executed on the BigQuery dataset.

Verified Contract Coverage​

We reached a whopping 11 million verified contracts on Sourcify at this year's end, from 6M in January. We are mostly either on par with or with higher verified contract count per chain than other verifiers.

This open dataset is used in many ways. As an example, we were happy to see it being used by the Solidity team to benchmark the compiler's performance on real world contracts with the latest Solidity versions (see the talk).

Lib-sourcify on Browser (Local Verification)​

We refactored lib-sourcify to work on browser. This allows us to use Sourcify in block explorers, wallets and other interfaces, and to verify contracts locally on users' machines.

Indeed this is exactly what Otterscan allows their users to do, and we are happy to see this becoming a reality. See this example contract and click "Verify Locally" to see the local verification in action.

The new similarity search API endpoint (/v2/verify/similarity) allows users to find similar contracts by bytecode. This is a powerful feature that allows block explorers to try to use similarity search if users visit a non-verified contract.

EIP-7834​

We had an EIP within the EOF proposal: EIP-7834: Separate Metadata Section for EOF. EOF would have simplified the verification process massively and we'd be ready day-1. Very unfortunate EOF was not shipped. We still need to continue workarounds and heuristics to separate the code from data in the EVM bytecode. Unstructured bytecode is a pain.

Talks​

As said, we were mostly heads down building this year but we still gave a few talks and presentations at the end of the year around Devconnect:

Spam or Legit? Analyzing 4byte Selector Collisions

· 6 min read

Recently Sourcify took over openchain.xyz's 4byte signature APIs as well as the domain itself, maintained by @samczsun. We also built the database and I wanted to run a quick analysis on the selector collisions and see how many collisions are legit vs. deliberately generated (spam).

samczsun's tweet

We built the dataset and a service to serve the data. The dataset contains:

  1. Data from openchain's dataset
  2. Data from 4byte.directory
  3. Signatures from verified contracts in Sourcify.

You can see the database schema in the database docs and the service in services/4byte in the repo. As of now we have 4.7 million signatures, of which 1.9 million are not from verified contracts, and the rest of the majority appear in at least one verified contract (stats).

While it's possible to submit signatures to the database via the /import endpoint, we also add the signatures to the database automatically when a contract is verified. The 4byte databases are known to be spam prone, as function signatures are only 4 bytes and it's trivial to find a collusion to an otherwise legit signature.

For example, see the ERC20 transfer(address,uint256) function's collisions under its selector 0xa9059cbb in our 4byte.sourcify.dev page: https://4byte.sourcify.dev/?q=0xa9059cbb

4byte.sourcify.dev transfer(address,uint256) collisions

Seeing this and having the data I wanted do a quick analysis on the selector collisions and see how many collisions are legit vs. deliberately generated (spam).

Analysis​

Running a simple query to find the signatures that share the same 4byte selector:

Query
SELECT
concat('0x', encode(signature_hash_4, 'hex')) AS signature_hash_4,
COUNT(*) AS num_signatures,
ARRAY_AGG(signature ORDER BY signature) AS signatures
FROM public.signatures
GROUP BY signature_hash_4
HAVING COUNT(*) > 1
ORDER BY num_signatures DESC;

In the end we find 2789 4byte selectors that have more than one signature. Here are the top 5 with most collisions:

collisions.csv

collisions.json

signature_hash_4num_signaturessignatures
0x0000000061AaANwg8((address,address,address,uint136,uint40,uint40,uint24,uint8,uint256,bytes32,bytes32,uint256))
abcei51243fdgjkh(bytes)
adfepixw()
...
0x0000000115account_info_rotate_tine(uint256)
exec_606BaXt(bytes[])
f00000001_bdmvamqo()
...
0xa9059cbb8_____$_$__$___$$$___$$___$__$$(address,uint256)
fakeTransfer_4570999670(bytes)
func_2093253501(bytes)
...
0x095ea7b38__$$$$___$$_$_$$__$_$_$$__$$$$(address,uint256)
approve(address,uint256)
as9q06we_7x8z(uint256,address[],address[],uint256)
...
0x70a082317$_$$$_$$$$$_$_$____$$$$_$$_$__(address)
balanceOf(address)
branch_passphrase_public(uint256,bytes8)
...

Looking at the top collisions, it might look a lot. But still the spamming seems not excessive and spammers generally find a single funny signature and call it a day. Out of the 2789 selectors, 2740 have only 2 signatures (ie. a single collusion) and only 49 with more than 2 signatures.

The interesting question is, how many of these collisions are actually unintended collisions vs. how many are deliberately generated (spam)?

Looking at them one by one would take some time. First I want to actually see only the collisions that have a verified contract. Ie. if f00000001_bdmvamqo() is not seen on a verified contract, let's assume it's a spam.

Query
SELECT
concat('0x', encode(s.signature_hash_4, 'hex')) AS signature_hash_4,
COUNT(*) AS num_signatures,
ARRAY_AGG(DISTINCT s.signature ORDER BY s.signature) AS signatures
FROM public.signatures s
WHERE EXISTS (
SELECT 1
FROM public.compiled_contracts_signatures ccs
WHERE ccs.signature_hash_32 = s.signature_hash_32
)
GROUP BY s.signature_hash_4
HAVING COUNT(*) > 1
ORDER BY num_signatures desc;

Now we're left with 1023 "verified" collisions:

collisions_verified.csv

collisions_verified.json

signature_hash_4num_signaturessignatures
0x0000000028AaANwg8((address,address,address,uint136,uint40,uint40,uint24,uint8,uint256,bytes32,bytes32,uint256))
arb_wcnwzblucpyf()
batchLock_63efZf()
buyAndFree22457070633(uint256)
call_g0oyU7o(address,uint256,bytes32,bytes)
...
......(4 rows with 4-5 signatures skipped)
0x415565b03JunionYoutubeXD_clgqmmkfvuba()
Sub2JunionOnYouTube_wuatcyecupza()
transformERC20(address,address,uint256,uint256,(uint32,bytes)[])
0x000000023callWithPlaceholders4845164670(address,uint256,bytes32,bytes,(address,bytes,uint64,uint64,uint64)[])
wipeBlockchain_EkJWPe()
yoov6(address,address,uint256)
0x6c5b47d23addDegree(uint256,string)
isBlacklisted5(address)
RenounceFungibleOwnership()
0x9aa7c0e53gain_network883718828((address,uint256,uint256,uint256,uint256,uint256,bool,uint256,uint256,uint256),uint8,uint256,uint256,address)
openTrade((address,uint256,uint256,uint256,uint256,uint256,bool,uint256,uint256,uint256),uint8,uint256,uint256,address)
TigrisTrade(int8,int56,uint80,bytes15,int88,int16)
0x014ed8d22CannotChangePaymentToken()
ModelRegistered(uint256,address,string,uint256)
0x0161a64a2cleanupExpiredListing(uint256)
MissingRole(address,bytes32)
0x0182a6da2initiateWalletTransfer(address)
withdrawStakingAmount(uint256)
0x01a754a32AutoSwap()
updateTeamFeeContract(address)
0x025313a22getACLRole5999294130779334338()
proxyOwner()

Now it starts to get interesting. Again the selectors with many collisions have mostly spam. But for 3 and less collisions we have some legitimate collisions.

For example for 0x01a754a3 we have AutoSwap() and updateTeamFeeContract(address). It's really difficult to tell if this is a spam or not.

But for the last row, 0x025313a2, we have getACLRole5999294130779334338() and proxyOwner(). Here the former is clearly a spam and the latter is not.

Next, we can actually ask an LLM to filter the ones looking like a spam! Since the data is not excessive, I shoved all of it into Claude and asked it to filter the ones looking like a spam. In the end it gave me a list of 648 collisions that it thinks are legitimate. I peeked in the list and it seems to be mostly accurate:

legitimate_collisions.csv

Here are 10 interesting examples of legitimate unintended collisions:

signature_hash_4num_signaturessignatures
0x04d742dc2adminResetRank()
startSale(uint256,uint256,uint256)
0x0536f7552FreeMintTokenSent(address,uint256)
NFTReward(address)
0x092338cc2maxPurchasableInOneTx()
usdcGHSTOracle()
0x17915e8d2getCluster(address)
getTotalFeeBps()
0x2025e52c2createSaleTokensVault()
mintWithERC721(uint256)
0x220613792getBaseStakeAmountForPlay()
vaultFees(uint256)
0x55fcd0272DepositAmountTooLow()
masterLogicAddress()
0x667022fd2bought(address)
iceCreamVan()
0x67bf975c2NotAllowedToRecover()
RewardThresholdReached(uint256)
0x706b87222pauseAtId()
USDTBorrowed(address,uint256)

These are all legitimate functions and events from different smart contracts that happen to share the same 4-byte selector purely by chance. This demonstrates that while 4-byte collisions are rare, they do happen naturally in the wild!

At this stage I just ran this for fun. We only have a list of popular signatures that we know the "correct" signature for (canonical-signatures.json). We filter out non-canonical ones by default and have a filtered field (can turn off filtering in the API response ). But if the community thinks this is useful, we can do a more thorough analysis and filter out the spam via LLMs.

Sourcify in 2025: Mission, Goals, and Roadmap

· 10 min read

Sourcify is a project that has been around for technically more than 5 years. Now as part of the Argot Collective, we will be sharing our detailed roadmaps frequently. While we are on it, we thought it's a good chance to look back into why we exist, what problems we are trying to solve, what we've done so far and lay out our roadmap.

Problem Statement​

Ethereum has an established open-source culture and is built on open-source tooling. The Ethereum community also demands the smart contracts to be open-sourced (i.e. source-code verified). After all, Ethereum's core principles such as trustlessness, transparency, verifiability etc. do rely on the contract code being verified and open-sourced.

However, we believe the source-code verification ecosystem plateaued at a suboptimal point:

  • Lack of open-source: The most used verification service itself is not open source and the whole verification process is a black-box, while the majority of people (wrongfully) assume that it is open-source.

  • Centralization: There is centralization on who verifies contracts. Contracts should be verified in multiple places and when needed cross-checked across verifiers. Because all verifiers will make mistakes and have bugs

  • Closed datasets: Right now smart contract data is siloed in proprietary services. Yes, one can "see and read" individual contracts but the dataset is closed and almost impossible to assemble through APIs, or scraping (usually against the terms of the service). The reliance on closed and proprietary datasets poses a long-term existential risk to Ethereum if those services were to vanish. In a way this already happened with older testnets and part of the Ethereum's history is lost alongside the verified contracts there.

    The absence of accessible and open datasets is holding the innovation on many fronts:

    • Bulk testing EVM tooling with smart contracts
    • Finding vulnerability patterns
    • Training AI models
    • Any kind of data analysis over source code
  • Tooling stagnation: The APIs and tooling around verification are old and not improving. For example, there are so many verification artifacts that can be useful for tooling (storageLayout, sourceMaps, cborAuxdata, transformations) but they are not available in current APIs.

  • No integrity hash support: Existing verifiers don't leverage "exact matches" i.e. the integrity hashes provided by the Solidity and Vyper compilers. This gives a cryptographic guarantee that the whole compilation, including the source code, is exactly the same as the original (even whitespaces etc.)

  • Lack of standards and public knowledge: There are no standards around source-code verification and no knowledge exchange across verifiers. But also no public resources to learn about the verification process. For instance, we weren't able to figure out how to verify contracts with multiple metadata hashes (e.g. nested contracts with factories) until we started discussing it with Blockscout. This led us to use another workaround as detailed in this blog post. Another example is that there is no place to look for the edge cases and tests for verification.

By observing the current direction and the incentives of ecosystem players we believe the ecosystem will not be able to break out of this "plateau", unless there is intentional intervention to drive it forward.

Our mission at Sourcify is to play the coordinator and the builder role to create an open, decentralized, and dynamic smart contract verification ecosystem, powered by open-source tools, accessible datasets, and shared standards, to empower the EVM tooling and uphold Ethereum’s core values.

Goals​

How do we plan to achieve these?

To create decentralization in the space, it's important to tackle the issue (i.e. contracts only verified at one place) at the beginning (funnel) of the verification pipeline. The contracts should be verified everywhere and it shouldn't be a burden for the user. For this, an important goal is to:

  • Convince frameworks to verify everywhere in parallel by default. We already shipped the Remix plugin that does this. Next step is to do the same in Foundry and Hardhat.
  • Secondly, maintain and promote the Verifier Alliance as a shared database of verified contracts.

In addition, to empower the EVM tooling and developers we want to:

  • Provide as much information from verified contracts as possible: raw bytecodes, abi, storageLayout, sourcemaps, transformations, cborAuxdata. Anything that can be useful for tooling.
  • Build a 4-bytes DB based on the verified contracts
  • Export and publish the DB and make it publicly queryable.

Finally, to improve the verification experience we want to:

  • Provide as much context and information to the user if a verification fails. Give detailed error messages, show differences between onchain vs recompiled bytecodes. Visualize this data on the UI.
  • Make it very easy for users to report issues if they really think a verification unexpectedly fails. Close the loop between the user and the verifier.

Achievements​

What we did so far since about 12 months ago:

Roadmap​

We work in 2-week sprints (sprint board) and every quarter we have a retrospective and a planning session (quarterly board). Our concrete quarterly goals as of today:

2025 Q3:

2025 Q4:

Non-milestone goals:

  • Now that the product is technically sound and ready to scale, boost visibility.
  • Get back to giving talks at conferences. Talk about problems, Sourcify's role, and the Verifier Alliance

Costs​

We are running on a fully scalable containerized GCP setup. Currently it costs us around $4.5k-5.5k a month to run Sourcify + Verifier Alliance.

  • 50% Cloud Run
  • 25% Cloud Filestore (NFS) (Used for RepositoryV2. Will be phased out in favor of just DB)
  • 10% Logging
  • 15% Networking, SQL, Monitoring, Misc

Still, financial sustainability is one big question we need to solve long term. Sourcify has been an EF funded project and now as part of the Argot Collective, we are still funded for the foreseeable future.

Being non-profit and funded gives us more credibility and a neutral position, and allows us to act more as a coordinator between the ecosystem players. For the project to be sustainable and to succeed long term, we need to find a way to make it self-sustaining, while avoiding capture.

History and background​

Sourcify started within the Ethereum Foundation as a Solidity side project around 2019 and an MVP was developed by Shard Labs until Kaan was hired in Sep 2021 and the project was brought back in.

Sourcify's UI in 2021

Sourcify's UI in 2021

At the time, the project's main focus was fostering the use of the Solidity metadata and the IPFS/Swarm hashes that act as an integrity hash here for the compilation and verification, and also allowed the decentralized storage of these files. The whole repository at the time was a file system and pinned on IPFS (nodes that we run ourselves, not fun). In fact the whole setup was being run in an old bare-metal server in the EF Berlin office and manually managed. That didn't last long as it was very cumbersome and time consuming and we moved to a VPS. Both running a bare-metal and a VPS are almost part-time jobs. Now we are on a fully scalable containerized GCP setup.

Another focus we had was to promote the NatSpec comments which are found in the metadata.json file and use them to enable human-readable transactions. We hacked some helper libraries, talked about it in the conferences, and created a working group but after a while we realized this is not something we can tackle with 2 people and we have to prioritize source-code verification and having a well-functioning platform. This was around mid-2023 and Marco joined around 6 months ago. Finally, Manuel joined the team mid-2024 making us a team of 3.

Looking back, we've made some good and bad decisions:

  • Running bare metal was never a good idea. It consumed so much time for us and we were firefighting all the time.

  • Natspec and human-readability focus also distracted us a lot and it took a while to find our real value proposition.

  • In the meantime we had a lot of technical debt we had to solve as the project has grown. Running everything on a filesystem on scale is impossible. At the time this might have been a natural choice for an MVP since also everything was hosted on IPFS (which is a filesystem). We should've moved away from this much earlier.

  • We attended a lot of conferences and it helped a lot with the visibility and meeting people who both help us and care about our mission.

  • We decided to accept all EVM chains for verification and not only those "close" to Ethereum e.g. L2s. This exposed us to a long tail of EVM developers and they all learned about Sourcify. Thanks to that, we also have a good number of self-hosted instances running.

  • It's very difficult to run such a project as a one-man show. Minimum two brains and two pairs of eyes are required to make technical progress and three to thrive.

  • It's all about attention. Technical problems are easier to solve but the difficult part is to change the user behavior. As long as people don't have Sourcify in mind when verifying their contract, having the most elegant technical solution is useless.

We tried to fix blind signing, here's what we learned

· 17 min read

In the earlier days of Sourcify, there were other things we were focusing on besides source-code verification. Sourcify was there to foster the adoption of the Solidity metadata and to make transactions human-readable.

Since September 2021, I've been the only one working on the project until Marco joined in July 2022 and Manuel in April 2024. You will see in our earlier talks we start with the problem statement of "YOLO signing" and explain how Sourcify, Solidity metadata.json, and NatSpec can solve this. That talk is actually my first ever conference talk (can be seen from how nervous I am), and I was naive. The idea was that if you document your Solidity methods with NatSpec, this userdoc/devdoc will be in the metadata.json and you can use these human-readable messages to tell the user what is going to happen before calling this contract method (more below).

Old Sourcify website showing human-readable transactions

The old Sourcify website highlighting human-readable transactions as a key feature

Getting more familiar with the problem, soon I realized this huge problem can't be solved with metadata.json + NatSpec only. We had to attack the problem from different angles for different cases. This led us to start the initiative "Human-Readable Transactions Working Group" (kudos to Dustin and Karen for the help) with actually many people ranging from Safe, Metamask, Coinbase Wallet etc. We held multiple calls but unfortunately we as Sourcify couldn't continue our coordinating role and the initiative died around August 2023. Marco and I didn't have enough bandwidth and experience, and had to go back to focusing on our core value proposition of making source-code verification open-source and the verified contract data open and accessible.

Slaying blind signing meme

The problem of blind-signing turned out to be a bigger dragon than we can slay.

Still we learned a lot and I got to see what people have been proposing to solve this. Following the ByBit hack that was essentially caused by blind-signing, I'll take this chance to gather everything we've learned here and share with everyone. There have been countless cases of people losing significant amounts of money because of blind signing. But the ByBit hack, the biggest hack ever in crypto history, with 1.4 billion USD stolen, has brought the attention back to this issue.

I started this article with blind-signing in mind but the problem is actually even larger: it's transaction safety. It means nothing if you can read a transaction but it does not do what you read or if you're talking to the wrong contract. It's layers and layers and layers of security. Which also means layers and layers and layers of coordination effort. That's why it's so difficult.

note

By no means this is a complete guide. I'm more than happy to extend this. Please get in touch if you have anything to add!

1. Source Code Verification​

This is a MUST. Period. Without verification there's no way to tell what a contract really does.

DO NOT INTERACT WITH CONTRACTS THAT ARE UNVERIFIED

The contract should be verified by a verifier, but ideally it is verified at multiple places. Even better if the verification is a "perfect/exact match", this gives you a cryptographic guarantee that what you see is exactly what the contract author deployed, even the comments, whitespaces etc.

Players here: Blockscout, Etherscan, Routescan, Tenderly, Sourcify, and the Verified Alliance initiative.

Wallets MUST warn users about unverified contracts and use multiple sources to check verification. Unfortunately, even in 2025, not many do even this simple requirement.

The tricky thing here is to not lead the user to believe verified == safe. A verified contract can still be malicious and verification does not check what the contract does. I actually wish we had a better name like "open-sourced contracts" instead of verification, but it is what it is.

2. Labeling​

The second step is to make sure you are talking to the correct contract. You think you are making a swap on Uniswap but is it really Uniswap or just a verified Uniswap copy at another address? Maybe the contract you are talking to is already marked malicious and it'd be such a shame to lose money to an already known scammer.

So this entails whitelists and blacklists of contracts. I don't have deep knowledge around this but I don't really recall seeing contract labels in my wallets. There are "web3 security" companies or Etherscan labels but the problem with these is everyone solves it for themselves.

I'll be saying this many times:

In order to solve this problem once and for all, THE SOLUTION HAS TO BE OPEN.

Closed, siloed data will only get us so far.

Here the project I want to highlight is the Open Labels Initiative stewarded by growthepie.xyz. If there are other solutions/projects worth sharing, please share them.

3. Audits​

This is part of labeling but it deserves its own mentioning. We need open, public audit lists of contracts. Wallets need to show if the contract you are about to interact with is audited and if not warn you against this.

The second step could be to create a ranking/reputation system of auditors, as you all know, not all auditors are created equal. And again, all these lists MUST be open and public. Note that I don't mean "decentralized" necessarily. A good enough centralized solution is better than nothing as long as the data is fully open, not monetized, and incentives are aligned to keep it that way. This can be an alliance of multiple parties, or a project sustained by grants. As you can see, this by itself is already a big enough problem to solve.

This is also not a part of the ecosystem I'm not too knowledgeable in so please share what you know.

4. Human-Understandability of Transactions​

I named this "understandability" instead of "readability" because readability is just a necessary but non-sufficient part of our main goal i.e., being able to understand what you are doing. Reading swapTokensForExactTokens is quite a meaningless word for a normal person, even though readable. If you have no idea what you're doing, you're basically giving out your money.

POV signing transactions

Dog-readable transactions, anyone?

4.1 ABI-Decoding​

It's year 2025 and I can't believe I have to mention this.

By now it should be trivial to take the ABI JSON of a contract (verification required), and decode the calldata.

Most of the time this is not really helpful to the user though.

Tx 0x5a45979e2a4a22855a1a2aee1ecb346b4ecee2d4e498817e8ed98d86476809ce on Mainnet

If you're given only the raw calldata:

0xe56461ad000000000000000000000000000000000000000000000000000000000000a86a000000000000000000000000ad26509077edd15f7cc9bf04cc58891c8f51a75d

A really simple decoding yields a much more meaningful message (decoded in Blockscout):

Decoded calldata

This is incredibly straightforward, just pull the ABI from a verifier, and decode with a framework like in ethers.js: abiCoder.decode(abiParamInputs, calldata). It's really sad even this is lacking in a lot of the places.

ABI-decoding is not always this helpful though. Usually the called methods have a lot more complex arguments and not as simple as the one above.

4.2 Function Metadata​

This category refers to leveraging function metadata, apart from the function itself (e.g. ABI) to provide more information about it.

ABI-Decoding + NatSpec​

There's additional human-readable information found in well written Solidity contracts in the form of NatSpec, a commenting syntax.

NatSpec has @notice and @dev fields that are intended to explain the "user" and the "developer" respectively what this method does, as well as the @param method to document the "parameters".

Example from my talk at the BlockSplit 2022 conference (link to slides):

  /// @dev Allows to swap/replace an owner from the Safe with another address.
/// This can only be done via a Safe transaction.
/// @notice Replaces the owner `oldOwner` in the Safe with `newOwner`.
/// @param prevOwner Owner that pointed to the owner to be replaced in the linked list
/// @param oldOwner Owner address to be replaced.
/// @param newOwner New owner address.
function swapOwner(
address prevOwner,
address oldOwner,
address newOwner
) public authorized {...

So that a user can see a more human-readable message about the method they are calling:

userdoc: Replaces the owner `oldOwner` in the Safe with `newOwner`
devdoc: Allows to swap/replace an owner from the Safe with another address. This can only be done via a Safe transaction.
function: swapOwner(address prevOwner, address oldOwner, address newOwner)
- prevOwner:
- documentation: Owner that pointed to the owner to be replaced in the linked list
- value: 0x1F98431c8aD98523631AE4a59f267346ea31F984
- oldOwner:
- documentation: Owner address to be replaced.
- value: 0xcC60F45e0507032036033b361d3a6457b9F0283D
- newOwner:
- documentation: New owner address.
- value: 0x83D0360050703233b361d3a6457b9F2cC60F45e0

Additionally NatSpec also suggests using "dynamic expressions" to put variable names in backticks like `oldOwner` for the consumers of the spec to dynamically fill the values. With that a user calling this function can see a much more human readable message:

userdoc: Replaces the owner 0xcC60F45e0507032036033b361d3a6457b9F0283D in the Safe with 0x83D0360050703233b361d3a6457b9F2cC60F45e0

Note that these meaningful messages all assume you are talking to a benign and secure contract. A malicious contract can still have nice human readable NatSpec docs while doing a completely different thing than what it says. All points mentioned above like verification, labeling etc. are requirements for this to make any sense.

NatSpec documentation can be obtained by requesting userdoc and devdoc under outputSelection in compilers settings. Additionally it's found under Solidity contract metadata.json. The hash of this file is appended at the end of the contract and provides extra cryptographic guarantees about the verification. No other verifier leverages or provides this output except Sourcify (obtainable via APIv2). Also see playground.sourcify.dev to learn more.

We forked the aragon/radspec repo and played around with it as a Metamask Snap but didn't pursue this further for reasons mentioned in the beginning.

As said, NatSpec also falls short in many cases. First of all, it's not backwards compatible. We can't inject NatSpec to already deployed contracts. It's also not always sufficient to make sense of complex transactions. Here's a Uniswap call that actually isn't complex:

A Uniswap Call

commands? inputs? no idea what's going on. Looking at the NatSpec helps a little:

/// @notice Executes encoded commands along with provided inputs. Reverts if deadline has expired.
/// @param commands A set of concatenated commands, each 1 byte in length
/// @param inputs An array of byte strings containing abi encoded inputs for each command
/// @param deadline The deadline by which the transaction must be executed
function execute(bytes calldata commands, bytes[] calldata inputs, uint256 deadline) external payable;

At least now we know what the parameters mean but still we would have to look at the Commands implementation in the contract to make sense of it. NatSpec gets us so far.

ERC-4430: Described Transactions​

This was an old EIP from @ricmoo and @arachnid to place the human-readable descriptions of functions in the contract itself.

The high-level idea is to have a "describer" function within the contract itself. Wallets etc. would call that describer to show the user a message before sending a tx.

As in the spec:

In many cases, the information that would be necessary for a meaningful description is not present in the final encoded transaction data or message data.

For example, the commit(bytes32) method of ENS places a commitment hash on-chain. The hash contains the blinded name and address; since the name is blinded, the encoded data (i.e. the hash) no longer contains the original values and is insufficient to access the necessary values to be included in a description.

By instead describing the commitment indirectly (with the original information intact: NAME, ADDRESS and SECRET) a meaningful description can be computed (e.g. "commit to NAME for ADDRESS (with SECRET)") and the matching data can be computed (i.e. commit(hash(name, owner, secret))).

The spec proposes an additional contract method such as:

function eip4430Describe(bytes inputs, bytes32 reserved) view returns (string description, bytes execcode)

Instead of calling transfer() directly, you'd call this function which would return your wallet a human-readable description and the execcode the wallet can execute in a subsequent transaction if the description is "accepted".

This is a more "onchain" solution that requires defining these descriptions inside the contract. But also because of that it's not backwards compatible for existing contracts.

Also related for signatures ERC-3224: Described Data

Rich Site-Proposed Contract Metadata​

This was proposed by @danfinlay in Ethereum Magicians Forum but didn't proceed to an EIP.

My understanding of this proposal is that the first point of contact of the wallet to the contract (e.g. dapp the website) provides human-readable metadata to be saved in the wallet. From then on, the wallet always shows human-readable information to the user in the wallet.

This would be backwards compatible for existing contracts.

I like this proposal's approach to provide this information to the user in the first point of contact ever with the dApp. The website would be responsible for passing this information and in a kind of secure way if provided with HTTPS.

Function/Event Templating and ERC-7730​

Similar to above proposal, we can generate a "function templates registry" that will map a chainId+address+function to a string template that can be dynamically filled and human-readable. While the above, from my understanding is more decentralized, that every dApp provides its own messages, here it's a centralized registry possibly with a template language.

E.g. the function or event on a specific contract

borrowAsset(uint256 _borrowAmount,uint256 _collateralAmount,address _receiver)
BorrowAsset (index_topic_1 address _borrower, index_topic_2 address _receiver, uint256 _borrowAmount, uint256 _sharesAdded)

can become "Borrowed {tokenAmount} {tokenName} on {dAppName}".

I guess a lot of the explorers, portfolio managing apps, wallets do this for popular contracts and functions. The problem again here is everyone's trying to solve it for themselves. There's no coordinated effort that I'm aware of that is open and solves this once and for all! (Edit: See below) Except maybe rotki because it's purely open-source but I don't know their registry or DB for this.

IMO it's fine that this is centralized initially and held in a place even like Github to begin with. Eventually there will be the problem of "who" provides these messages and if they are trusted parties. E.g. what if an attacker is able to inject a misleading message? Or is it going to be permissionless that attackers can also provide human-readable messages for their own malicious contracts. One can start small here with a trusted set of participants and expand slowly.

ERC-7730 (by Ledger)

Turns out ERC-7730 is exactly what I describe above (see full EIP). The spec proposes a JSON schema to provide human readable messages and annotate function parameters to format them nicely.

Example from a Lido contract:

  "display": {
"formats": {
"wrap(uint256)": {
"intent": "Exchange stETH to wstETH",
"fields": [
{
"path": "_stETHAmount",
"label": "Amount to exchange",
"format": "tokenAmount",
"params": { "token": "$.metadata.constants.stETHaddress" }
}
]
},

When the user calls wrap(10000000000000000000) they will be shown:

Review transaction to: Exchange stETH to wstETH 

Amount to exchange: 1 stETH

From what I understand this also supports nested calls or multicalls if this is specified properly.

The whole submission to wallet workflow is depicted nicely in this diagram from the spec:

Clear sign workflow

The biggest con I see with this initiative is it's branded everywhere as "by Ledger". Kudos to Ledger for coming up with this as an open spec but as long as this remains under Ledger's ownership in their repos, there's no incentive for other wallets to adopt this standard. Such an initiative should be led by a neutral entity or a consortium, and really not be marketed with the wallet's name everywhere. This is already mentioned in the original EIP:

  • Foundation operated repository, like ethereum chainID list: good alternative between decentralization and discoverability.
  • Ledger repository: as a short term solution, Ledger is providing a central repository (See Ledger GitHub repository)

To me this is the biggest drawback of this intiative and Ledger should stop marketing this through themselves if they really want this to succeed.

4.3 Transaction Simulation​

This is one field that good progress was made. A lot of services provide tools or APIs to simulate the steps and outcomes of a transaction and it's somewhat well integrated into wallets.

A lot of services provide Transaction Simulation through their APIs and it should be easy to hook. Here are some I'm aware of:

Rabby Wallet does a great job here to show the simulation results to the user:

Rabby Uniswap swap simulation

But also as a reminder this is not a silver bullet again. For example it isn't able to find out what I'll receive from the CoWSwap swap, likely because how CoWSwap works with solving auctions etc:

Rabby Cowswap swap simulation

5. Browser Wallet to Hardware Wallet Integrity​

Even if we did everything above perfectly, we wouldn't be able to prevent the ByBit attack because what the signers were seeing on their machines were benign but they were actually signing a malicious transaction. Of course they should've checked what they were signing on their hardware wallets but this is obviously not working. A lot of the users slack, don't want to read hexadecimals (understandably) and just accept what's provided.

I think there's a lot of room for improvement in the SW to HW wallet integrity field and a lot of low-hanging fruits.

One example I thought about in this tweet is using emojis to do integrity checks instead of hexadecimals.

8 emojis = 32 bytes, we could hash the tx content—if anything changes, the emojis shift completely.

  • Browser: 😒🙏👰👨‍🦼🦷👳‍♂️👷‍♀️🧓
  • HW Wallet: 😒🙏👰👨‍🦼🦷👳‍♂️👷‍♀️🧓

✅ Pass

  • Browser: 😒🙏👰👨‍🦼🦷👳‍♂️👷‍♀️🧓
  • HW Wallet: 😁☺️😾🤲🙎‍♂️☂️🩰🩳

❌ Fail

HW Wallet limitations​

One thing to keep in mind is that a large portion on the hardware wallets are limited in their resources as well as their displays. There is so much information we can put in it. Newer wallets have larger and more information rich displays but they are also expensive. An ideal solution should cover cheaper alternatives too even if not fully backwards compatible to the exiting lower-grade hardware wallets.

Conclusion​

The journey to solve blind signing has taught us some valuable lessons about the complexity of the problem and I wanted to share what we have found here. As you can see "transaction safety" is not just clear-signing and it's layers and layers of security and coordination. The clear-signing part also has had multiple attempts to solve and none is perfect. Still the main issue is none of the proposals is on production (except simulation) and they all remained as wishful specs. Having something is better than having nothing.

While we started with the goal of making transactions more human-readable, we discovered that this challenge requires a coordinated effort across the entire ecosystem. I am hoping the ByBit hack serves as a wake-up call to the entire ecosystem and we start working on this intentionally. As Sourcify we have more bandwidth compared to back than and will be willing to support the coordination efforts.

If you liked this, please share and spread the word and we can start some open discussion. If you have feedback or other ideas, also let me know on X! @kaanuzdogan

APIv2: Getting Verified Contracts

· 8 min read

We have been working on the new APIv2 for Sourcify and just shipped the first set of endpoints to lookup verified contracts and GET various types of information about the verified contract. The new information-rich endpoints allows selectively fetching the specific fields needed about the verified contract and provides a wide selection of fields.

In this post we will walkthrough the new functionality brought by these endpoints and see how to use them.

(Edit: In addition to lookup endpoints, the verification endpoints are also live! The full API spec is in our docs)

Background​

APIv2 has been the biggest priority of our project since Q4 2024 and we aim to ship it fully by Q1 2025. We've been discussing and having fruitful conversations around how to design the new API as can be seen in the design issue. Thanks everyone who contributed to the conversation!

The main problems we wanted to solve with and the main features we wanted from the APIv2 were the following:

  • With the legacy API users had to wait for the response ie. wait for the compilation and the verification to finish. This can easily take couple minutes and the requests are left hanging. The new design should have ticketing/job-ids and users should poll with this id.
  • The "perfect" vs "partial" naming is confusing.
  • The legacy API is fully based on the metadata.json. While we want to keep full support for metadata.json verification and "perfect" matching, we wanted to have standard JSON input as our main endpoint's base.
  • We were able to share a lot more data around the verification after moving from a filesystem based storage to the database and legacy API didn't have this information.

You can read more in this issue.

(Edit: The design is live in our docs)

The design specifies endpoints to "Verify Contracts", check for "Verification Jobs", and to do "Contract Lookup". Here we'll talk about the "Contract Lookup" that we shipped. The others are still being developed.

New endpoints​

With this release we have 2 new endpoints:

  • GET /v2/contracts/{chainId}: retrieve the latest verified contracts for a chain
  • GET /v2/contract/{chainId}/{address}: retrieve a specific contract and related information

GET /v2/contracts/{chainId}​

This enpoint is fairly straightforward and returns an array of verified contracts. Users can provide the following parameters:

  • limit: number of contracts to return (max. 200)
  • sort: by most recent first (desc, default), or by oldest first (asc)
  • afterMatchId: The last matchId (an incremental contract ID) returned to get contracts older or newer than it (depending on sort)
Response
{
"results": [
{
"match": "exact_match",
"creationMatch": "exact_match",
"runtimeMatch": "exact_match",
"chainId": "11155111",
"address": "0x7Bec3080cdf73a9a39997C860c19377Ac1E6E6BE",
"verifiedAt": "2025-02-11T11:49:45Z",
"matchId": "855557"
},
...
]
}

Check this example:

https://sourcify.dev/server/v2/contracts/1?limit=10&sort=asc

GET /v2/contract/{chainId}/{address}​

This endpoint is the more interesting one, in that, it allows us to get all details beyond just if a contract is verified.

By default this endpoint returns the minimal verification information:

https://sourcify.dev/server/v2/contract/1/0x00000000219ab540356cBB839Cbe05303d7705Fa

{
"matchId": "2115",
"creationMatch": "exact_match",
"runtimeMatch": "exact_match",
"verifiedAt": "2024-08-08T10:05:44Z",
"match": "exact_match",
"chainId": "1",
"address": "0x00000000219ab540356cBB839Cbe05303d7705Fa"
}

As you can see we no longer use perfect and partial to refer to matches. Instead we use exact_match and match respectively. This is because the wording "partial" was causing confusion leading users to think their contract is not verified. This way we both convey that the contract is indeed verified but also by "exact_match" we express this is a superior match than just a "match".

Above is the minimal contract information. Besides, users can choose additional fields by passing the fields query parameter or omit fields with omit.

For example, if you just need the ABI: https://sourcify.dev/server/v2/contract/1/0x00000000219ab540356cBB839Cbe05303d7705Fa?fields=abi

{
"abi": [
{ "type": "constructor", "inputs": [], "stateMutability": "nonpayable" },
...
{
"name": "supportsInterface",
"type": "function",
"inputs": [{ "name": "interfaceId", "type": "bytes4", "internalType": "bytes4" }],
"outputs": [{ "name": "", "type": "bool", "internalType": "bool" }],
"stateMutability": "pure"
}
],
"matchId": "2115",
"creationMatch": "exact_match",
"runtimeMatch": "exact_match",
"verifiedAt": "2024-08-08T10:05:44Z",
"match": "exact_match",
"chainId": "1",
"address": "0x00000000219ab540356cBB839Cbe05303d7705Fa"
}

Or you can ask for every single field by passing all: https://sourcify.dev/server/v2/contract/1/0x00000000219ab540356cBB839Cbe05303d7705Fa?fields=all

Let's have a look at each field:

{
// This is the minimal verification information
"match": "match",
"creationMatch": "match",
"runtimeMatch": "match",
"chainId": "11155111",
"address": "0xDFEBAd708F803af22e81044aD228Ff77C83C935c",
"verifiedAt": "2024-07-24T12:00:00Z",
"matchId": "3266227",
// All information related to the creation bytecode of the contract is under this field.
"creationBytecode": {
"onchainBytecode": "0x608060405234801561001057600080fd5b5060043610610036570565b6000819050919050565b600080fd5b61010c816100f4565b811461011757600080fd5b5056fea264697066735821220404e37f487a89a932dca5e77faaf6ca2de3b991f93d230604b1b8daaef64766264736f6c63430008070033",
"recompiledBytecode": "0x608060405234801561001057600080fd5b5060043610610036570565b6000819050919050565b600080fd5b61010c816100f4565b811461011757600080fd5b5056fea264697066735821220404e37f487a89a932dca5e77faaf6ca2de3b991f93d230604b1b8daaef64766264736f6c63430008070033",
"sourceMap": "73951:11562:0:-:0;;;;;;;;;;;;-1:-1:-1;63357:7:0;:15;;-1:-1:-1;;63357:15:0;;;73951:11562;;;;;;",
// Positions of the linked library addresses of the given libraries in the bytecode. "evm.bytecode.linkReferences" output of the compiler
"linkReferences": {
"contracts/AmplificationUtils.sol": {
"AmplificationUtils": [
{
"start": 3078,
"length": 20
}
]
},
"contracts/SwapUtils.sol": {
"SwapUtils": [
{
"start": 2931,
"length": 20
}
]
}
},
// The position and the value of the CBOR auxdata (or metadata) section in the bytecode. See playground.sourcify.dev and https://docs.sourcify.dev/blog/finding-auxdatas-in-bytecode/ for details
"cborAuxdata": {
"1": {
"value": "0xa2646970667358221220d6808f0352d5e503f1f878b19b1bf46c893bac1e20b3c51884efb58a87435b5564736f6c634300080a0033",
"offset": 18685
},
"2": {
"value": "0xa264697066735822122017bf4253b73b339897d7c117916781f30b434e6caa783b20eb15065469814dcf64736f6c634300080a0033",
"offset": 18465
}
},
// Transformations are the operations done on the compiled bytecode to reach the matching onchain bytecode.
// This is based on the Verified Alliance schema: https://github.com/verifier-alliance/database-specs/tree/master/json-schemas
// Also read for more info: https://verifieralliance.org/docs/transformations
// Creation bytecode can have "library", "cborAuxdata", and "constructorArguments" type transformations
"transformations": [
{
"id": "1",
"type": "replace",
"offset": 18040,
"reason": "cborAuxdata"
},
{
"type": "insert",
"offset": 6183,
"reason": "constructorArguments"
},
{
"id": "sources/lib/MyLib.sol:MyLib",
"type": "replace",
"offset": 582,
"reason": "library"
}
],
// Corresponding values for each transformation
"transformationValues": {
"libraries": {
"sources/lib/MyLib.sol:MyLib": "0x40b70a4904fad0ff86f8c901b231eac759a0ebb0"
},
"constructorArguments": "0x00000000000000000000000085fe79b998509b77bf10a8bd4001d58475d29386",
"cborAuxdata": {
"0": "0xa26469706673582212201c37bb166aa1bc4777a7471cda1bbba7ef75600cd859180fa30d503673b99f0264736f6c63430008190033"
}
}
},
// All information related to the runtime bytecode
"runtimeBytecode": {
"onchainBytecode": "0x608060405234801561001057600080fd5b5060043610610036570565b6000819050919050565b600080fd5b61010c816100f4565b811461011757600080fd5b5056fea264697066735821220404e37f487a89a932dca5e77faaf6ca2de3b991f93d230604b1b8daaef64766264736f6c63430008070033",
"recompiledBytecode": "0x608060405234801561001057600080fd5b5060043610610036570565b6000819050919050565b600080fd5b61010c816100f4565b811461011757600080fd5b5056fea264697066735821220404e37f487a89a932dca5e77faaf6ca2de3b991f93d230604b1b8daaef64766264736f6c63430008070033",
"sourceMap": "73951:11562:0:-:0;;;;;;;;;;;;-1:-1:-1;63357:7:0;:15;;-1:-1:-1;;63357:15:0;;;73951:11562;;;;;;",
// Same as in creation bytecode, but for runtime bytecode
"linkReferences": {
"contracts/AmplificationUtils.sol": {
"AmplificationUtils": [
{
"start": 3078,
"length": 20
}
]
},
"contracts/SwapUtils.sol": {
"SwapUtils": [
{
"start": 2931,
"length": 20
}
]
}
},
// Same as creation bytecode for runtime bytecode.
"cborAuxdata": {
"1": {
"value": "0xa2646970667358221220d6808f0352d5e503f1f878b19b1bf46c893bac1e20b3c51884efb58a87435b5564736f6c634300080a0033",
"offset": 18685
},
"2": {
"value": "0xa264697066735822122017bf4253b73b339897d7c117916781f30b434e6caa783b20eb15065469814dcf64736f6c634300080a0033",
"offset": 18465
}
},
// "evm.deployedBytecode.immutableReferences" output of the compiler
"immutableReferences": {
"1050": [
{
"start": 312,
"length": 32
},
{
"start": 2631,
"length": 32
}
]
},
// Same as the creation bytecode
// The runtime bytecode can take the following transformation types: "library", "cborAuxdata", "immutable", "callProtection"
"transformations": [
{
"id": "CriminalDogs.sol:SafeMath",
"type": "replace",
"offset": 1863,
"reason": "library"
},
{
"id": "1",
"type": "replace",
"offset": 2747,
"reason": "cborAuxdata"
},
{
"id": "1466",
"type": "replace",
"offset": 18703,
"reason": "immutable"
},
{
"id": "1466",
"type": "replace",
"offset": 18939,
"reason": "immutable"
},
{
"type": "replace",
"offset": 1,
"reason": "callProtection"
}
],
// Corresponding values for the transformations
"transformationValues": {
"libraries": {
"contracts/order/OrderUtils.sol:OrderUtilsLib": "0x40b70a4904fad0ff86f8c901b231eac759a0ebb0"
},
"immutables": {
"1466": "0x000000000000000000000000000000007f56768de3133034fa730a909003a165"
},
"cborAuxdata": {
"1": "0xa26469706673582212201c37bb166aa1bc4777a7471cda1bbba7ef75600cd859180fa30d503673b99f0264736f6c63430008190033"
},
"callProtection": "0x9deba23b95205127e906108f191a26f5d520896a"
}
},
// Information related to the onchain deployment of this contract
"deployment": {
"transactionHash": "0xb6ee9d528b336942dd70d3b41e2811be10a473776352009fd73f85604f5ed206",
"blockNumber": "21721660",
"transactionIndex": "0",
"deployer": "0xDFEBAd708F803af22e81044aD228Ff77C83C935c"
},
// The source files of this contract.
"sources": {
"contracts/Storage.sol": {
"content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\ncontract Storage {\n uint256 number;\n\n function setNumber(uint256 newNumber) public {\n number = newNumber;\n }\n\n function getNumber() public view returns (uint256) {\n return number;\n }\n}\n"
},
"contracts/Owner.sol": {
"content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\ncontract Owner {\n address public owner;\n\n constructor() {\n owner = msg.sender;\n }\n}\n"
}
},
// Compilation related information
"compilation": {
"language": "Solidity",
"compiler": "solc",
"compilerVersion": "v0.8.12+commit.f00d7308",
"compilerSettings": {},
"name": "MyContract",
"fullyQualifiedName": "contracts/MyContract.sol:MyContract"
},
"abi": [
{}
],
"userdoc": {},
"devdoc": {},
"storageLayout": {},
// metadata.json output of the Solidity compiler. For Vyper contracts, Sourcify generates and writes a metadata file on its own for compatibility reasons.
"metadata": {},
// This essentially contains duplicate information as above.
// The purpose of this field is to easily integrate into tooling that uses the standard JSON syntax.
"stdJsonInput": {
"language": "Solidity",
"sources": {
"contracts/Storage.sol": {
"content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\ncontract Storage {\n uint256 number;\n\n function setNumber(uint256 newNumber) public {\n number = newNumber;\n }\n\n function getNumber() public view returns (uint256) {\n return number;\n }\n}\n"
},
"contracts/Owner.sol": {
"content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\ncontract Owner {\n address public owner;\n\n constructor() {\n owner = msg.sender;\n }\n}\n"
}
},
// compilation.compilerSettings above
"settings": {}
},
// Similarly here for easy tooling integration as the stdJsonInput.
// Only contains the target contract under "contracts".
"stdJsonOutput": {
"sources": {},
"contracts": {}
},
// Proxy information. The proxy resolution is done on the fly on every call.
"proxyResolution": {
"isProxy": true,
"proxyType": "ZeppelinOSProxy",
"implementations": [
{
"address": "0x43506849D7C04F9138D1A2050bbF3A0c054402dd"
}
]
}
}

You can be really specific about the fields you need:

https://sourcify.dev/server/v2/contract/1/0x00000000219ab540356cBB839Cbe05303d7705Fa?fields=runtimeBytecode.onchainBytecode,compilation.language,deployment.transactionHash

or just omit the fields you don't need

https://sourcify.dev/server/v2/contract/1/0x00000000219ab540356cBB839Cbe05303d7705Fa?omit=compilation,runtimeBytecode,creationBytecode

Proxy Resolution​

The API uses the WhatsABI library to resolve proxies from the runtime bytecode of a contract.

These are the supported proxy types as of now:

export type ProxyType =
| "EIP1167Proxy"
| "FixedProxy"
| "EIP1967Proxy"
| "GnosisSafeProxy"
| "DiamondProxy"
| "PROXIABLEProxy"
| "ZeppelinOSProxy"
| "SequenceWalletProxy";

See proxy-utils.ts to see how resolution is done.

Next Steps​

As said, this is just the lookup endpoints of the APIv2. In the upcoming weeks we will be developing the verificaiton and verificationJob endpoints that will support ticketing and polling, instead of hanging requests.

Once again, you can see the full up-to-date API spec at https://docs.sourcify.dev/docs/api/

You can follow along the development in our tracker issue: https://github.com/argotorg/sourcify/issues/1367

Beyond that, you can see our roadmap in our Milestones View for the next quarters and what we are currently working in our Sprint Board. We welcome feedback and discussions!