Deploy a smart contract and verify it on ZondScan
Updated 8 min read
QRL 2.0 runs an EVM compatible execution layer, so deploying a smart contract feels familiar if you have shipped one on any Ethereum style chain. The toolchain differs in two places: contracts are compiled with Hyperion (hypc), the QRL toolchain's contract compiler, and transactions are signed with post-quantum ML-DSA-87 keys held by Q-prefixed accounts.
This tutorial walks the full loop: write a small Hyperion contract, compile it with hypc, deploy it to the QRL 2.0 testnet with a short @theqrl/web3 script, and then verify the source on ZondScan so anyone can read it, call it, and audit it. If the chain itself is new to you, start with What is QRL 2.0?.
You need Node.js, a funded testnet account, and about twenty minutes. If your goal is a standard token without writing contract code, the wallet has a one-click path: see Launch a QRC20 token.
Hyperion and hypc
Hyperion is a statically typed, contract-oriented language derived from Solidity, andhypc is its compiler. Source files use the .hyp extension and open with a pragma hyperion line. The compiler is developed in the open in the theQRL GitHub organization and is published to npm as @theqrl/hypc, so a plain Node.js project can compile contracts without any extra toolchain.
The compiled bytecode is regular EVM bytecode targeting the chain's virtual machine, which is why the deploy flow below looks like any web3 deploy. In the compiler's standard JSON output the artifacts sit under a zvm key, so the bytecode path is contracts[file][name].zvm.bytecode.object.
Write a simple contract
Save the following as Counter.hyp. It stores one number, exposes a public getter, and emits an event on every increment, enough surface to see the verified Code, Read, and Write tabs in action later.
// SPDX-License-Identifier: MIT
pragma hyperion ^0.0.2;
contract Counter {
uint256 public count;
event Incremented(address indexed by, uint256 newCount);
function increment() external {
count += 1;
emit Incremented(msg.sender, count);
}
}If you know Solidity you can read this without a manual. Existing contracts generally port by renaming the file to .hyp and switching the pragma; standard ERC20 style sources have been ported this way on this network.
Compile with hypc
hypc accepts the standard JSON input format: a language field set to Hyperion, a sources map keyed by filename, and a settings block for the optimizer and output selection. This script compiles the contract and writes the ABI and bytecode to a JSON artifact:
const fs = require('fs');
const hypc = require('@theqrl/hypc');
const input = {
language: 'Hyperion',
sources: {
'Counter.hyp': { content: fs.readFileSync('./Counter.hyp', 'utf8') },
},
settings: { outputSelection: { '*': { '*': ['*'] } } },
};
const output = JSON.parse(hypc.compile(JSON.stringify(input)));
const artifact = output.contracts['Counter.hyp']['Counter'];
fs.writeFileSync('Counter.json', JSON.stringify({
abi: artifact.abi,
bytecode: artifact.zvm.bytecode.object,
}));Record the exact @theqrl/hypc version and optimizer settings you compile with. Verification recompiles your source and byte-matches the result, so those settings must match at verification time.
Deploy to the testnet
The deploy script signs with an ML-DSA-87 account derived from a 34-word mnemonic and talks to the public testnet RPC proxy. The QRL 2.0 testnet uses chain ID 1337, and the script aborts on a mismatch before signing anything.
const { Web3 } = require('@theqrl/web3');
const { MLDSA87 } = require('@theqrl/wallet.js');
const { abi, bytecode } = require('./Counter.json');
const web3 = new Web3(new Web3.providers.HttpProvider(
'https://qrlwallet.com/api/qrl-rpc/testnet',
));
// A 34-word test mnemonic. Never hardcode one; never reuse a real wallet.
const mnemonic = process.env.MNEMONIC;
const hexseed = MLDSA87.newWalletFromMnemonic(mnemonic).getHexExtendedSeed();
const acc = web3.qrl.accounts.seedToAccount(hexseed);
web3.qrl.wallet.add(hexseed);
async function main() {
const chainId = BigInt(await web3.qrl.getChainId());
if (chainId !== 1337n) throw new Error('unexpected chain: ' + chainId);
const deploy = new web3.qrl.Contract(abi)
.deploy({ data: bytecode, arguments: [] });
const estimated = await deploy.estimateGas({ from: acc.address });
const receipt = await web3.qrl.sendTransaction({
from: acc.address,
data: deploy.encodeABI(),
gas: (estimated * 12n) / 10n, // 20% headroom
gasPrice: await web3.qrl.getGasPrice(),
});
console.log('Contract address:', receipt.contractAddress);
}
main().catch((e) => { console.error(e); process.exitCode = 1; });Run it with MNEMONIC set in your environment. On success it prints the new Q-prefixed contract address. Paste that address into the ZondScan search bar to see the creation transaction, or open the deployment hash directly; How to read a transaction explains every field, and the gas page shows current prices. Your contract also appears in the contracts list.
Verify the source on ZondScan
Right after deployment the explorer only knows your contract's bytecode. Verification publishes the source: you submit it, the backend recompiles it with the pinned Hyperion build you select, and byte-matches the output against the on-chain runtime code. On success the source, ABI, and compiler settings appear on the contract's page.
Open the verify form
Go to /verify-contract. The form loads the list of supported compiler builds from the backend and preselects the default.
Pick the compiler build
Choose the exact Hyperion build your contract was compiled with. The byte-match only succeeds against that build, so a version mismatch is the most common cause of a failed verification.
Fill in address, name, and settings
Enter the Q-prefixed contract address and the contract name, which must match a contract declared in the source (
Counterhere). Set the optimizer checkbox and runs to whatever you compiled with, pick a license, and optionally set an EVM version.Paste the source
Paste the full
.hypsource into the source field. Multi-file projects put the main file there and supply the rest through the Imports field as a JSON object mapping import paths to source contents, for example{"Context.hyp": "...source..."}. Constructor arguments are an optional hex field; the byte-match runs against the deployed runtime code either way.Submit and watch the job
Press Verify & Publish. Verification runs as an async job: the page shows it moving from queued to compiling and polls until it lands on verified or failed. Failures come back with the compiler error text so you can fix the input and resubmit. If someone already verified the address, the form says so and links straight to the contract page.
What verification unlocks
A verified contract gets a verified badge on its address page, and its Contract section grows three tabs:
- Code: the published source with license header, the compiler settings used for the match, the full ABI, and the deployed bytecode.
- Read: every
viewandpurefunction becomes callable directly from the explorer through a read-only call proxy, without connecting a wallet. - Write: state-changing functions can be dispatched by pairing a wallet over QRL Connect.
Verified contracts also unlock an AI explanation card on the Code tab, which produces a plain-language summary of what the contract does. The explainer only works with verified source, one more reason to publish it.
Verify programmatically
Everything the form does is available over the public API at https://zondscan.com/api, which suits CI pipelines that verify every deployment automatically:
GET /api/contract/compiler-infolists the supported Hyperion builds and marks the default.POST /api/contract/verifyenqueues a job. The JSON body requiresaddress,sourceCode, andcontractName; optional fields includecompilerVersion,optimizerEnabled,optimizerRuns,evmVersion,imports, andlicense. It returns ajobId. An unknowncompilerVersionreturns 400 with the supported build list. The endpoint is rate-limited per IP and caps the request body at 1 MiB.GET /api/contract/verify/{jobId}polls the job until it reports success or failed.
The API explorer documents these endpoints alongside the rest of the public API, with example requests you can run in the browser.
FAQ
Can I reuse my existing Solidity contracts?
Largely yes. Hyperion is derived from Solidity, so typical contracts port by renaming files to .hyp and switching the pragma to pragma hyperion. Test the compile with @theqrl/hypc and fix anything the compiler flags.
Verification fails even though my source is correct. Why?
The byte-match requires the exact compiler build and the exact optimizer settings used at deploy time. Check the build list on the form or at /api/contract/compiler-info, and make sure the optimizer checkbox and runs value match your compile. The failed job includes the compiler output to help you diagnose the difference.
Do I need to supply constructor arguments to verify?
No. The field is optional and advisory. The match runs against the deployed runtime code, which does not include constructor arguments.
Does verification cost anything?
Verification is free. Deployment costs gas in testnet Quanta, which you can get for free from the faucet and which carries no monetary value.
My contract imports several files. How do I verify it?
Put the main file in the source field and the imported files in the Imports field as a JSON object mapping each import path to its full source, matching the paths your import statements use. The API accepts the same structure in the imports body field.
