lib-sourcify is Sourcify's reusable backbone library for verifying contracts. Version 2 introduces a completely redesigned architecture with improved abstractions, better support for both Solidity and Vyper contracts, and a more flexible verification process.
lib-sourcify provides tools to validate, compile, and verify smart contracts.
The library supports verification with Solidity metadata.json and with both Solidity and Vyper's standard-JSON input formats
npm install @ethereum-sourcify/lib-sourcify
import {
SolidityCompilation,
Verification,
SourcifyChain,
ISolidityCompiler,
SolidityJsonInput,
SolidityOutput,
} from "@ethereum-sourcify/lib-sourcify";
import { useSolidityCompiler } from "@ethereum-sourcify/compilers";
import * as fs from "fs";
// Step 1: Setup your compiler
class Solc implements ISolidityCompiler {
private solcRepoPath: string;
private solJsonRepoPath: string;
constructor(solcRepoPath: string, solJsonRepoPath: string) {
this.solcRepoPath = solcRepoPath;
this.solJsonRepoPath = solJsonRepoPath;
}
async compile(
version: string,
solcJsonInput: SolidityJsonInput,
forceEmscripten: boolean = false
): Promise<SolidityOutput> {
return await useSolidityCompiler(
this.solcRepoPath, // useSolidityCompiler will automatically download and store solc here
this.solJsonRepoPath, // useSolidityCompiler will automatically download and store solcjs here
version,
solcJsonInput,
forceEmscripten
);
}
}
const solc = new Solc("/path/to/solc", "/path/to/solcjs");
// Step 2: Prepare your standard JSON input
const jsonInput = {
language: "Solidity",
sources: {
"Contract.sol": {
content: "contract MyContract { function foo() public {} }",
},
},
settings: {
optimizer: {
enabled: true,
runs: 200,
},
outputSelection: {
"*": [],
},
},
} as SolidityJsonInput;
// Step 3: Create a compilation
const compilation = new SolidityCompilation(
solc,
"0.8.20", // compiler version
jsonInput,
{
path: "Contract.sol",
name: "MyContract", // The name of your contract
}
);
// Step 4: Set up a SourcifyChain instance
const myChain = new SourcifyChain({
name: "My EVM Chain",
chainId: 1337,
rpc: ["http://localhost:8545"],
supported: true,
});
// Step 5: Verify the contract
const verification = new Verification(
compilation,
myChain,
"0xc0ffee254729296a45a3885639AC7E10F9d54979"
);
await verification.verify();
// Step 6: Check verification status
console.log(verification.status); // { runtimeMatch: 'perfect', creationMatch: null }
This example shows the complete verification flow for a Solidity contract using standard JSON input. For Vyper contracts or more advanced use cases, see the detailed sections below.
lib-sourcify v2 consists of several key components:
-
Validation: Based on a Solidity metadata.json file that describes a contract build, checks if all source files are present and valid. Fetches missing sources from IPFS if necessary.
SolidityMetadataContract
: Represents a Solidity contract with its metadata and source filesprocessFiles.ts
: Utility functions for creatingSolidityMetadataContract
instances from files
-
Compilation: Handles contract compilation
AbstractCompilation
: Base class for compilationSolidityCompilation
: Handles Solidity compilationVyperCompilation
: Handles Vyper compilation
-
SourcifyChain: Handles blockchain interactions such as fetching bytecode and transaction data
-
Verification: Compares compiled bytecode from a
Compilation
with an on-chain bytecode from aSourcifyChain
Verification
: Main class orchestrating the verification process
The lib-sourcify
library does not come with compilers as dependencies. Instead, you need to provide a class that implements either the ISolidityCompiler
or IVyperCompiler
interface and pass it to any function that requires a compiler. We suggest you to use the official @ethereum-sourcify/compilers
package.
import {
SolidityOutput,
ISolidityCompiler,
SolidityJsonInput,
} from "@ethereum-sourcify/lib-sourcify";
import { useSolidityCompiler } from "@ethereum-sourcify/compilers";
class Solc implements ISolidityCompiler {
constructor(private solcRepoPath: string, private solJsonRepoPath: string) {}
async compile(
version: string,
solcJsonInput: SolidityJsonInput,
forceEmscripten: boolean = false
): Promise<SolidityOutput> {
return await useSolidityCompiler(
this.solcRepoPath,
this.solJsonRepoPath,
version,
solcJsonInput,
forceEmscripten
);
}
}
const solc = new Solc("/path/to/solc/repo", "/path/to/solcjs/repo");
import { useVyperCompiler } from '@ethereum-sourcify/compilers';
import {
IVyperCompiler,
VyperOutput,
VyperJsonInput,
} from '@ethereum-sourcify/lib-sourcify';
class Vyper implements IVyperCompiler {
constructor(private vyperRepoPath: string) {}
async compile(
version: string,
vyperJsonInput: VyperJsonInput,
): Promise<VyperOutput> {
return await useVyperCompiler(this.vyperRepoPath, version, vyperJsonInput);
}
}
const vyper = new Vyper('/path/to/vyper/repo');
SourcifyChain is a class that handles blockchain interactions such as fetching bytecode and transaction data.
It supports HTTP header authenticated RPCs through ethers.js FetchRequest
objects.
Also supports RPCs with debug_traceTransaction
and trace_call
methods to be able to fetch creation bytecode for factory contracts.
import { SourcifyChain } from '@ethereum-sourcify/lib-sourcify';
import { FetchRequest } from 'ethers';
// Create a SourcifyChain for Ethereum Mainnet
const mainnetChain = new SourcifyChain({
name: 'Ethereum Mainnet',
chainId: 1,
rpc: [
'https://eth.llamarpc.com',
new FetchRequest({
url: 'https://authenticated-rpc.com',
headers: {
Authorization: 'Bearer ...',
},
}),
'https://eth-mainnet.g.alchemy.com/v2/MY_API_KEY',
],
traceSupportedRPCs: [
{
type: 'trace_transaction', // or 'debug_traceTransaction'
index: 2, // index of the RPC in the rpc array
},
],
supported: true,
});
Note: Vyper contracts do not support validation through metadata files since they do not include a metadata JSON field in their bytecode. Instead, Vyper contracts must be verified directly using their source files and compiler settings.
v2 introduces the SolidityMetadataContract
class which manages the validation workflow:
import { SolidityMetadataContract } from '@ethereum-sourcify/lib-sourcify';
// Create a SolidityMetadataContract with metadata and sources
const metadataContract = new SolidityMetadataContract(metadata, sources);
// Create a compilation object that can be used for verification
const compilation = await metadataContract.createCompilation(solidityCompiler);
For file-based validation:
import { createMetadataContractsFromFiles, PathBuffer } from '@ethereum-sourcify/lib-sourcify';
const pathBuffers: PathBuffer[] = [];
pathBuffers.push({
path: filePath,
buffer: fs.readFileSync(filePath),
});
// Create SolidityMetadataContract objects from files
const metadataContracts = await createMetadataContractsFromFiles(pathBuffers);
// Create compilation objects
for (const contract of metadataContracts) {
const compilation = await contract.createCompilation(solidityCompiler);
// Use compilation for verification
}
Each contract source either has a content
field containing the Solidity code as a string, or URLs to fetch the sources from (Github, IPFS, Swarm etc.). If sources are missing, you can fetch them:
// Fetch missing sources
await metadataContract.fetchMissing();
By default, IPFS resources will be fetched via https://ipfs.io/ipfs. You can specify a custom IPFS gateway using process.env.IPFS_GATEWAY=https://custom-gateway
, if you need to pass additional headers to the request (e.g. for authentication) you can use process.env.IPFS_GATEWAY_HEADERS={ 'custom-header': 'value' }
.
You can check if a contract is ready to be compiled with:
const isCompilable = metadataContract.isCompilable(); // true or false
In v2, the verification process is handled by the Verification
class:
import { Verification, SourcifyChain } from '@ethereum-sourcify/lib-sourcify';
// Set up the SourcifyChain
const chain = new SourcifyChain({
name: 'Ethereum Mainnet',
chainId: 1,
rpc: ['https://eth.llamarpc.com'],
supported: true,
});
// Create a verification instance
const verification = new Verification(
compilation,
chain,
contractAddress,
creatorTxHash, // optional, for creation bytecode verification
);
// Perform verification
await verification.verify();
// Check verification status
console.log(verification.status.runtimeMatch); // 'perfect', 'partial', or null
Sourcify adopts the Verifier Alliance format called "transformations" to describe the changes from the compiled bytecode to onchain bytecode for a verification.
E.g. while immutable variables are set to 0x00..00
after compilation, they are set to the actual value in the onchain bytecode.
Refer to VerA Docs for more information.
During verification, various errors can occur. In lib-sourcify v2, errors related to verification are thrown as VerificationError
instances, which extend the SourcifyLibError
class. These errors include a message and an error code that can be used to handle different error cases.
See VerificationErrorCode
type in VerificationTypes.ts for the full list of error codes.
lib-sourcify
has a basic logging system
You can specify the log level using the setLibSourcifyLoggerLevel(level)
where:
0
is errors1
is warnings2
is infos5
is debug6
is silly
You can override the logger by calling setLogger(logger: ILibSourcifyLogger)
. This is an example:
const winston = require('winston');
const myCustomLogger = winston.createLogger({
// ...
});
setLibSourcifyLogger({
logLevel: 2, // What levels to log. E.g. Log anything lower than info: error and warning
setLevel(level: number) {
this.logLevel = level;
},
log(level, msg) {
if (level <= this.logLevel) {
switch (level) {
case 0:
myCustomLogger.error(msg);
break;
case 1:
myCustomLogger.warn(msg);
break;
case 2:
myCustomLogger.info(msg);
break;
case 5:
myCustomLogger.debug(msg);
break;
}
}
},
});
Version 2 of lib-sourcify brings a significant redesign of the library's architecture. Here's how to migrate from v1 to v2:
-
Class Structure Changes:
AbstractCheckedContract
is replaced by language-specific compilation classesSolidityCheckedContract
functionality is now split betweenSolidityMetadataContract
andSolidityCompilation
VyperCheckedContract
is now represented byVyperCompilation
- New
Verification
class handles the verification process
-
Workflow Changes:
- Validation, compilation, and verification are now separate steps
- More explicit control over the verification process
// V1 code
const checkedContracts = await checkFilesWithMetadata(solc, pathBuffers);
const solidityCheckedContract = checkedContracts[0];
await solidityCheckedContract.fetchMissing();
const match = await verifyDeployed(
solidityCheckedContract,
sourcifyChain,
contractAddress,
);
// V2 code
import {
createMetadataContractsFromFiles,
Verification,
} from '@ethereum-sourcify/lib-sourcify';
// Create metadata contracts from files
const metadataContracts = await createMetadataContractsFromFiles(pathBuffers);
const metadataContract = metadataContracts[0];
// Fetch missing sources
await metadataContract.fetchMissing();
// Create compilation
const compilation = await metadataContract.createCompilation(solc);
// Create verification instance
const verification = new Verification(
compilation,
sourcifyChain,
contractAddress,
);
// Perform verification
await verification.verify();
// Check verification status
const status = verification.status;
v1 Function/Class | v2 Equivalent |
---|---|
SolidityCheckedContract |
SolidityMetadataContract + SolidityCompilation |
VyperCheckedContract |
VyperCompilation |
checkFilesWithMetadata |
createMetadataContractsFromFiles |
verifyDeployed |
Verification.verify() |
fetchMissing |
metadataContract.fetchMissing() |
isValid |
metadataContract.isCompilable() |