Deploy and interact with a smart contract.
Quantum Chain executes Solidity bytecode in the QVM. Use normal Solidity and ABI tooling for compilation and calldata. Use the Quantum SDK for account keys, transaction signing, canonical serialization, submission, and receipts.
Create a small TypeScript project.
mkdir quantum-counter
cd quantum-counter
npm init -y
npm install @quantum_chain/sdk @quantum_chain/pqc viem solc
npm install --save-dev tsx typescript @types/node
mkdir contracts scripts artifactssolc compiles Solidity. viem is used only to encode and decode ABI values. Neither library signs the Quantum transaction.
Create the contract.
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.24;
contract Counter {
uint256 public number;
event NumberChanged(uint256 number);
constructor(uint256 initialNumber) {
number = initialNumber;
}
function setNumber(uint256 nextNumber) external {
number = nextNumber;
emit NumberChanged(nextNumber);
}
function increment() external {
number += 1;
emit NumberChanged(number);
}
}Compile ABI and creation bytecode.
import { mkdir, readFile, writeFile } from "node:fs/promises";
import solc from "solc";
const source = await readFile("contracts/Counter.sol", "utf8");
const input = {
language: "Solidity",
sources: { "Counter.sol": { content: source } },
settings: { outputSelection: { "*": { "*": ["abi", "evm.bytecode.object"] } } },
};
const output = JSON.parse(solc.compile(JSON.stringify(input)));
const failures = (output.errors ?? []).filter((item) => item.severity === "error");
if (failures.length) {
throw new Error(failures.map((item) => item.formattedMessage).join("\n"));
}
const compiled = output.contracts["Counter.sol"].Counter;
await mkdir("artifacts", { recursive: true });
await writeFile("artifacts/Counter.json", JSON.stringify({
abi: compiled.abi,
bytecode: `0x${compiled.evm.bytecode.object}`,
}, null, 2));
console.log("wrote artifacts/Counter.json");node scripts/compile.mjsFund a disposable deployer and submit the bytecode.
import { readFile, writeFile } from "node:fs/promises";
import {
QuantumClient,
QuantumWallet,
bytesToHex,
hexToBytes,
networks,
} from "@quantum_chain/sdk";
import { encodeAbiParameters } from "viem";
const artifact = JSON.parse(await readFile("artifacts/Counter.json", "utf8"));
const constructorArguments = encodeAbiParameters([{ type: "uint256" }], [41n]);
const deploymentData = new Uint8Array([
...hexToBytes(artifact.bytecode),
...hexToBytes(constructorArguments),
]);
const client = new QuantumClient(networks.sandbox);
const deployer = QuantumWallet.generate();
try {
await client.validateNetwork();
await client.sandbox!.fund(deployer.address);
const submitted = await deployer.sendTransaction(client, {
data: deploymentData,
value: 0n,
});
const receipt = await submitted.wait({ confirmations: 1, timeoutMs: 180_000 });
if (!receipt.contractAddress) throw new Error("missing contract address");
const code = await client.getCode(receipt.contractAddress);
if (code.length === 0) throw new Error("deployment produced no runtime code");
const deployment = {
network: "quantum-sandbox",
chainId: "20804",
deployer: deployer.address,
publicKey: bytesToHex(deployer.publicKey),
transactionHash: submitted.hash,
blockNumber: receipt.blockNumber.toString(),
contractAddress: receipt.contractAddress,
};
await writeFile("deployment.json", JSON.stringify(deployment, null, 2));
console.log(deployment);
} finally {
deployer.destroy();
}npx tsx scripts/deploy.tsContract creation is a normal transaction with creation bytecode in data and no to. The successful receipt contains contractAddress.
Read state, send a write, and verify it.
import { readFile } from "node:fs/promises";
import {
QuantumClient,
QuantumContract,
QuantumWallet,
bytesToHex,
networks,
} from "@quantum_chain/sdk";
import { decodeFunctionResult, encodeFunctionData } from "viem";
const artifact = JSON.parse(await readFile("artifacts/Counter.json", "utf8"));
const deployment = JSON.parse(await readFile("deployment.json", "utf8"));
const client = new QuantumClient(networks.sandbox);
const contract = new QuantumContract(deployment.contractAddress, client);
const writer = QuantumWallet.generate();
async function readNumber(): Promise<bigint> {
const data = encodeFunctionData({ abi: artifact.abi, functionName: "number" });
const output = await contract.call(data);
return decodeFunctionResult({
abi: artifact.abi,
functionName: "number",
data: bytesToHex(output),
}) as bigint;
}
try {
await client.validateNetwork();
console.log("before", (await readNumber()).toString());
await client.sandbox!.fund(writer.address);
const data = encodeFunctionData({ abi: artifact.abi, functionName: "increment" });
const submitted = await contract.send(writer, data);
const receipt = await submitted.wait({ confirmations: 1, timeoutMs: 120_000 });
console.log("transaction", submitted.hash);
console.log("status", receipt.status);
console.log("after", (await readNumber()).toString());
} finally {
writer.destroy();
}npx tsx scripts/interact.tscontract.call() performs read-only execution and returns raw bytes. contract.send() constructs a state-changing transaction and returns a response with its hash and wait(). Decode results and logs with the ABI.
Audit signature verification, not ordinary business logic.
Most contracts that use storage, arithmetic, access control by msg.sender, events, token balances, and normal external calls follow familiar EVM patterns. The critical exception is code that verifies an off-chain signature.
- Search for
ecrecoverand ECDSA recovery libraries. - Review permit, meta-transaction, multisig, bridge, and account-abstraction flows.
- Pass the digest, Quantum public key, and Quantum signature to the verification precompile at
0x0b. - Derive and compare the expected address separately because the precompile returns a boolean.
- Re-estimate gas and check calldata limits with the larger signing material.
Promote an artifact, not a tutorial wallet.
- Pin the Solidity compiler version, optimizer settings, source, ABI, and bytecode.
- Validate the target chain ID and genesis hash before deployment.
- Replace
QuantumWallet.generate()with an approved productionQuantumSigner. - Apply an explicit deployment policy and independent review of constructor arguments.
- Retain the raw transaction, transaction hash, receipt, block, contract address, and artifact.
- Compare deployed runtime bytecode with the expected build output.
- Wait for the required confirmation depth before publishing the address to dependent systems.
- Never reuse Sandbox keys, balances, or contract addresses on Mainnet.