Getting Started
Project Setup
We recommend scaffolding your project using the zksync-cli.
Configuration
In order to obtain a Sophscan API Key, you need to create an account and get an API Key
here.
Set up your project with the following configuration:
import type { HardhatUserConfig } from "hardhat/config";
import "@matterlabs/hardhat-zksync";
import * as dotenv from "dotenv";
// Load environment variables from .env file
dotenv.config();
const config: HardhatUserConfig = {
defaultNetwork: "sophonTestnet",
networks: {
hardhat: {
zksync: true,
},
sophonMainnet: {
url: "https://rpc.sophon.xyz",
ethNetwork: "mainnet",
verifyURL: "https://verification-explorer.sophon.xyz/contract_verification",
browserVerifyURL: "https://explorer.sophon.xyz/",
enableVerifyURL: true,
zksync: true,
accounts: [process.env.WALLET_PRIVATE_KEY as string],
},
sophonTestnet: {
url: "https://rpc.testnet.sophon.xyz",
ethNetwork: "sepolia",
verifyURL: "https://api-explorer-verify.testnet.sophon.xyz/contract_verification",
browserVerifyURL: "https://explorer.testnet.sophon.xyz/",
enableVerifyURL: true,
zksync: true,
accounts: [process.env.WALLET_PRIVATE_KEY as string],
},
},
zksolc: {
version: "latest",
settings: {},
},
solidity: {
version: "0.8.27",
},
etherscan: {
enabled: true,
apiKey: {
sophonTestnet: process.env.ETHERSCAN_SOPHON_API_KEY as string,
sophonMainnet: process.env.ETHERSCAN_SOPHON_API_KEY as string,
},
customChains: [
{
network: "sophonTestnet",
chainId: 531050104,
urls: {
apiURL: "https://api-testnet.sophscan.xyz/api",
browserURL: "https://testnet.sophscan.xyz",
},
},
{
network: "sophonMainnet",
chainId: 50104,
urls: {
apiURL: "https://api.sophscan.xyz/api",
browserURL: "https://sophscan.xyz",
},
},
],
},
};
export default config;
Contract Deployment
Basic Deployment
Here’s how to deploy a contract without using the paymaster:
import { utils } from "zksync-ethers";
const deployer = new Deployer(hre, wallet);
const artifact = await deployer.loadArtifact("Your_Contract");
const contract = await deployer.deploy(artifact, constructorArguments || []);
Deployment with Paymaster
During the alpha stage, you’ll need to use our Paymaster to sponsor transactions. Here’s how to deploy using the paymaster:
import { Provider, Wallet, utils } from "zksync-ethers";
import { Deployer } from "@matterlabs/hardhat-zksync-deploy";
import { HardhatRuntimeEnvironment } from "hardhat/types";
export default async function (hre: HardhatRuntimeEnvironment) {
const provider = new Provider(hre.network.config.url);
const wallet = new Wallet(process.env.WALLET_PRIVATE_KEY!, provider);
const deployer = new Deployer(hre, wallet);
const artifact = await deployer.loadArtifact("your_contract");
const params = utils.getPaymasterParams(
"0x98546B226dbbA8230cf620635a1e4ab01F6A99B2", // Paymaster address
{
type: "General",
innerInput: new Uint8Array(),
}
);
const contract = await deployer.deploy(
artifact,
[], // Constructor arguments
undefined, // Deployment type (use undefined for regular contract deployment)
{
customData: {
paymasterParams: params,
gasPerPubdata: utils.DEFAULT_GAS_PER_PUBDATA_LIMIT,
},
}
);
}
Proxy Deployment
To deploy proxy contracts with paymaster support:
import { utils } from "zksync-ethers";
const deployer = new Deployer(hre, wallet);
const artifact = await deployer.loadArtifact("Your_Contract");
await hre.zkUpgrades.deployProxy(deployer.zkWallet, artifact, [initializerArgs], {
initializer: "initialize",
paymasterProxyParams: params,
paymasterImplParams: params,
});
Contract Interaction
To interact with deployed contracts using the paymaster:
import { utils } from "zksync-ethers";
const paymasterParams = utils.getPaymasterParams("0x98546B226dbbA8230cf620635a1e4ab01F6A99B2", {
type: "General",
innerInput: new Uint8Array(),
});
const tx = await contract.yourFunction(params, {
customData: {
gasPerPubdata: utils.DEFAULT_GAS_PER_PUBDATA_LIMIT,
paymasterParams: paymasterParams,
},
});
Contract Verification
You can verify contracts on both Sophscan and Sophon’s explorer:
npx hardhat verify --network sophonTestnet DEPLOYED_CONTRACT_ADDRESS constructor_arguments
Check if a contract is whitelisted by the paymaster
Before making calls to a newly deployed contract using a paymaster, it’s important to verify that the contract has been added to the paymaster’s whitelist. Our automated contract whitelisting service typically takes 2-3 seconds to whitelist new contracts under normal network conditions. While optional, checking the whitelist status before proceeding makes deployment scripts more robust by ensuring contracts are ready to interact with the paymaster.
The code below provides a reference implementation for checking the whitelist status:
const restrictionContractAddress = (hre.network.config as unknown as CustomNetworkConfig)
?.restrictionContract;
if (restrictionContractAddress && usePaymaster) {
const contractAddress = await contract.getAddress();
logger.info(
`Checking if ${contractName} (${contractAddress}) is whitelisted on restriction contract...`
);
// Create interface for the restriction contract
const restrictionContractABI = [
{
inputs: [
{
internalType: "address",
name: "",
type: "address",
},
],
name: "contractWhitelist",
outputs: [
{
internalType: "bool",
name: "",
type: "bool",
},
],
stateMutability: "view",
type: "function",
},
];
const provider = hre.ethers.provider;
const restrictionContract = new hre.ethers.Contract(
restrictionContractAddress,
restrictionContractABI,
provider
);
let isWhitelisted = false;
let attempts = 0;
const maxAttempts = 5;
while (!isWhitelisted && attempts < maxAttempts) {
try {
isWhitelisted = await restrictionContract.contractWhitelist(contractAddress);
if (isWhitelisted) {
logger.info(`${contractName} is now whitelisted on restriction contract!`);
} else {
attempts++;
if (attempts < maxAttempts) {
logger.info(
`${contractName} is not yet whitelisted. Waiting ${
waitTime / 1000
} seconds before retry (attempt ${attempts}/${maxAttempts})...`
);
await new Promise((resolve) => setTimeout(resolve, waitTime));
} else {
logger.warn(
`${contractName} is still not whitelisted after ${maxAttempts} attempts. Proceeding anyway, but paymaster transactions might fail.`
);
}
}
} catch (error) {
logger.error(`Error checking whitelist status: ${error}`);
attempts++;
if (attempts < maxAttempts) {
await new Promise((resolve) => setTimeout(resolve, waitTime));
}
}
}
}