Skip to main content

2 posts tagged with "Verified Contracts"

View All Tags

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.

If you want to just look at the full up-to-age spec, you can go to: https://sourcify.stoplight.io/docs/sourcify-apiv2/branches/main

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.

In the end we settled for the following API design (as of 11 Feb 2025) https://sourcify.stoplight.io/docs/sourcify-apiv2/branches/main/8ujkqluwvkwbl-sourcify-v2-draft.

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://docs.sourcify.dev/blog/technical-verification-walkthrough/#matching-and-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://sourcify.stoplight.io/docs/sourcify-apiv2/branches/main

You can follow along the development in our tracker issue: https://github.com/ethereum/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!