Quantum Chain
The public blockchain and its native currency, Quantum (QUANTUM). Use this documentation for JSON-RPC, transaction construction, signing, smart contracts, Sandbox, and direct node integration.
Construct and sign canonical Quantum transactions from JavaScript or Python. Validate networks, submit raw transactions, track receipts, and deploy or call QVM smart contracts without recreating chain formats or cryptography in application code.
Both paths use the same canonical Quantum transaction format and native signing implementation. JavaScript includes the complete RPC lifecycle. Python returns submission-ready raw bytes and the quickstart supplies a minimal RPC caller around it.
quantum-sandbox on chain ID 20804. Sandbox QUANTUM has no monetary value. Do not use tutorial keys on Mainnet.The SDK identifies known networks with both chain ID and genesis hash. This prevents a matching numeric ID from silently selecting the wrong chain.
| Field | Quantum Sandbox | Quantum Mainnet |
|---|---|---|
| Network code | quantum-sandbox | quantum-mainnet |
| Chain / P2P ID | 20804 | 20803 |
| Genesis hash | 0x3d9c6813ee3edd227abb5345aff55d6e2e76bc9db3a031d92d0e2bd54c8997e7 | 0x3517886b1e875e8ab5229f4ced47be7020c81775d93abac25824ca113b7a0816 |
| Currency | Quantum (QUANTUM), 18 decimals | Quantum (QUANTUM), 18 decimals |
They solve different problems and can be used independently.
The public blockchain and its native currency, Quantum (QUANTUM). Use this documentation for JSON-RPC, transaction construction, signing, smart contracts, Sandbox, and direct node integration.
The custody and wallet operations platform. Use Qustody for managed wallets, approvals, policies, teams, vaults, and EnQlave-connected signing workflows.
Read Qustody docsQuantum Sandbox is the Quantum Chain test network on chain ID 20804. Sandbox QUANTUM has no monetary value. The faucet works only on this test network and must never be configured for Mainnet.
sandbox.qustody.io names the environment; it is not the JSON-RPC URL. Applications must connect to the exact rpc.sandbox.qustody.io service shown below.
https://rpc.sandbox.qustody.iohttps://faucet.sandbox.qustody.iohttps://explorer.sandbox.qustody.iocurl --fail-with-body https://rpc.sandbox.qustody.io \
-H 'content-type: application/json' \
--data '{"jsonrpc":"2.0","id":1,"method":"qc_chainId","params":[]}'The result must be 0x5144, which is decimal 20804. Then request block zero and confirm its hash:
curl --fail-with-body https://rpc.sandbox.qustody.io \
-H 'content-type: application/json' \
--data '{"jsonrpc":"2.0","id":2,"method":"qc_getBlockByNumber","params":["0x0",false]}'result.hash must equal 0x3d9c6813ee3edd227abb5345aff55d6e2e76bc9db3a031d92d0e2bd54c8997e7.
import {
QuantumClient, QuantumWallet, formatQuantum, networks,
} from "@quantum_chain/sdk";
const client = new QuantumClient(networks.sandbox);
const wallet = QuantumWallet.generate();
try {
const network = await client.validateNetwork();
if (network.network !== "quantum-sandbox" || network.chainId !== 20804n) {
throw new Error("refusing faucet use outside Quantum Sandbox");
}
console.log("address", wallet.address);
const funding = await client.sandbox!.fund(wallet.address);
console.log("faucet created transaction", funding.funded);
console.log("balance", formatQuantum(funding.balance), "QUANTUM");
console.log("faucet transaction", funding.transactionHash ?? "already funded");
console.log("explorer", client.getExplorerAddressUrl(wallet.address));
} finally {
wallet.destroy();
}npx tsx fund-sandbox.tsexport SANDBOX_ADDRESS='0xREPLACE_WITH_YOUR_40_HEX_CHARACTER_ADDRESS'
curl --fail-with-body https://faucet.sandbox.qustody.io/fund \
-H 'content-type: application/json' \
--data "{\"address\":\"${SANDBOX_ADDRESS}\"}"The faucet response includes the address, balance and target balance in atomic units, whether it funded the account, and an optional transaction hash. It is idempotent up to its configured target. Calling it repeatedly does not create unlimited funds.
Ask Quantum Chain for read access to the quantum_chain npm organization. Put the issued read-only token in your user or deployment secret configuration. Never put it in the application source.
mkdir quantum-sandbox-demo
cd quantum-sandbox-demo
npm init -y
npm install @quantum_chain/sdk @quantum_chain/pqc
npm install --save-dev tsx typescript @types/nodeExample user-level npm configuration:
@quantum_chain:registry=https://registry.npmjs.org/
//registry.npmjs.org/:_authToken=${NPM_TOKEN}This complete script validates the permanent Sandbox, funds a short-lived sender, transfers 0.01 QUANTUM, and waits for a successful receipt.
import {
QuantumClient,
QuantumWallet,
formatQuantum,
networks,
parseQuantum,
} from "@quantum_chain/sdk";
const client = new QuantumClient(networks.sandbox);
const sender = QuantumWallet.generate();
const recipient = QuantumWallet.generate();
try {
const identity = await client.validateNetwork();
console.log("network", identity.network, identity.chainId.toString());
console.log("sender", sender.address);
const funding = await client.sandbox!.fund(sender.address);
console.log("balance", formatQuantum(funding.balance), "QUANTUM");
const submitted = await sender.sendTransaction(client, {
to: recipient.address,
value: parseQuantum("0.01"),
});
console.log("transaction", submitted.hash);
const receipt = await submitted.wait({
confirmations: 1,
timeoutMs: 120_000,
pollIntervalMs: 1_000,
});
console.log("status", receipt.status);
console.log("block", receipt.blockNumber.toString());
console.log("explorer", client.getExplorerTxUrl(submitted.hash));
} finally {
sender.destroy();
recipient.destroy();
}npx tsx send.tsQuantum Chain executes EVM bytecode. The SDK handles the Quantum transaction. In this example solc compiles Solidity. viem is used only to encode ABI values, not for signing or RPC.
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.24;
contract Counter {
uint256 public number;
event NumberChanged(uint256 number);
constructor(uint256 initialNumber) { number = initialNumber; }
function increment() external {
number += 1;
emit NumberChanged(number);
}
}Install solc and viem, then compile the contract into an artifact containing its 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 errors = (output.errors ?? []).filter((item) => item.severity === "error");
if (errors.length) throw new Error(errors.map((item) => item.formattedMessage).join("\n"));
const result = output.contracts["Counter.sol"].Counter;
await mkdir("artifacts", { recursive: true });
await writeFile("artifacts/Counter.json", JSON.stringify({
abi: result.abi,
bytecode: `0x${result.evm.bytecode.object}`,
}, null, 2));node scripts/compile.mjsDeploy the compiled creation bytecode by omitting to:
import { readFile, writeFile } from "node:fs/promises";
import {
QuantumClient, QuantumWallet, hexToBytes, networks,
} from "@quantum_chain/sdk";
import { encodeAbiParameters } from "viem";
const artifact = JSON.parse(await readFile("artifacts/Counter.json", "utf8"));
const args = encodeAbiParameters([{ type: "uint256" }], [41n]);
const deploymentData = new Uint8Array([
...hexToBytes(artifact.bytecode),
...hexToBytes(args),
]);
const client = new QuantumClient(networks.sandbox);
const deployer = QuantumWallet.generate();
try {
await client.validateNetwork();
await client.sandbox!.fund(deployer.address);
const tx = await deployer.sendTransaction(client, {
data: deploymentData, // no `to`: contract creation
value: 0n,
});
const receipt = await tx.wait({ timeoutMs: 180_000 });
if (!receipt.contractAddress) throw new Error("missing contract address");
const code = await client.getCode(receipt.contractAddress);
if (!code.length) throw new Error("deployment produced no runtime code");
const deployment = {
network: "quantum-sandbox",
chainId: "20804",
transactionHash: tx.hash,
blockNumber: receipt.blockNumber.toString(),
contractAddress: receipt.contractAddress,
};
await writeFile("deployment.json", JSON.stringify(deployment, null, 2));
console.log(deployment);
} finally {
deployer.destroy();
}QuantumSigner.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({ timeoutMs: 120_000 });
console.log("hash", submitted.hash);
console.log("status", receipt.status);
console.log("after", (await readNumber()).toString());
} finally {
writer.destroy();
}contract.call() returns raw bytes. contract.send() returns a QuantumTransactionResponse with the transaction hash and wait(). Decode return data and events with the contract ABI.
Applications that own nonce resolution, submission, and lifecycle handling can import the RPC-free subpaths. The result includes every artifact needed by an adapter.
import {
buildAndSignTransaction, parseQuantum,
} from "@quantum_chain/sdk/transactions";
import { LocalPQCSigner } from "@quantum_chain/sdk/signers";
const signer = new LocalPQCSigner();
try {
const result = await buildAndSignTransaction({
type: "legacy",
chainId: 20804n,
nonce: 12n,
gasLimit: 21_000n,
gasPrice: 1_000_000_000n,
to: "0x0000000000000000000000000000000000000001",
value: parseQuantum("1"),
}, signer);
console.log(result.rawTransaction); // submit through your RPC layer
console.log(result.transactionHash); // locally computed hash
console.log(result.publicKey); // embedded public key
console.log(result.signature); // embedded signature
console.log(result.signingDigest); // exact 32-byte digest
} finally {
signer.destroy();
}py-quantum generates native Quantum keys, derives addresses, signs an exact 32-byte digest, verifies signatures, and constructs canonical RPC-free transactions. It does not implement cryptography in Python.
Quantum Chain issues an SDK username and token for your organization. Keep them in a password manager or CI secret store. Do not commit the authenticated URL.
mkdir quantum-python-demo
cd quantum-python-demo
python3 -m venv .venv
. .venv/bin/activate
python -m pip install --upgrade pipmkdir quantum-python-demo
cd quantum-python-demo
py -m venv .venv
.\.venv\Scripts\Activate.ps1
python -m pip install --upgrade pippython -m pip install \
--index-url "https://SDK_USERNAME:SDK_TOKEN@sdk.quantumapi.io/simple/" \
py-quantum==0.3.0from py_quantum import (
ADDRESS_SIZE, DIGEST_SIZE, PUBLIC_KEY_SIZE, SIGNATURE_SIZE,
Signer, verify_digest,
)
# Tutorial fixture only. Transaction digests must come from the canonical
# Quantum Chain transaction implementation.
digest = bytes.fromhex(
"5b336411c12d54f162ef83653f01897ffb8b0987c666d34bed2920641832a4ef"
)
assert len(digest) == DIGEST_SIZE
with Signer.generate() as signer:
public_key = signer.public_key
signature = signer.sign_digest(digest)
assert len(bytes.fromhex(signer.address_hex[2:])) == ADDRESS_SIZE
assert len(public_key) == PUBLIC_KEY_SIZE
assert len(signature) == SIGNATURE_SIZE
assert verify_digest(public_key, digest, signature)
print("address", signer.address_hex)
print("public key bytes", len(public_key))
print("signature bytes", len(signature))
print("verified", True)python sign.pySigner.generate()new opaque native signer
signer.public_key1,952-byte public key
signer.address20-byte address
signer.address_hexlowercase 0x address
signer.sign_digest(digest)3,293-byte signature
signer.close()release native key handle
verify_digest(key, digest, signature)boolean
build_transaction(request)unsigned transaction
sign_transaction(transaction, signer)complete signing result
build_and_sign_transaction(request, signer)build and sign without RPC
First obtain the pending nonce, gas estimate, and gas price from RPC. Supply those resolved fields to the Python SDK:
from py_quantum import (
LegacyTransactionRequest, Signer, build_and_sign_transaction,
)
with Signer.generate() as signer:
result = build_and_sign_transaction(
LegacyTransactionRequest(
chain_id=20804,
nonce=12, # qc_getTransactionCount
gas_limit=21_000, # qc_estimateGas
gas_price=1_000_000_000, # qc_gasPrice
to="0x0000000000000000000000000000000000000001",
value="1.0",
),
signer,
)
print(result.raw_transaction) # qc_sendRawTransaction parameter
print(result.transaction_hash) # computed locally
print(result.public_key)
print(result.signature)
print(result.signing_digest)The result also contains raw_transaction_bytes, unsigned_transaction, and signed_transaction. Do not blindly retry submission if an HTTP response is lost; query the local hash and reconcile the pending nonce.
Supported wheels: Linux x86-64 with glibc 2.34 or newer and Windows x86-64, for CPython 3.10-3.14. Normal users need no compiler, CMake, or system native library.
| Import | Purpose | RPC |
|---|---|---|
@quantum_chain/sdk | Client, wallet, contracts, transactions, networks, units, errors | Optional |
@quantum_chain/sdk/transactions | Build, sign, serialize, hash | Never |
@quantum_chain/sdk/signers | QuantumSigner and native local signer | Never |
@quantum_chain/sdk/rpc | Client, HTTP transport, RPC types and errors | Yes |
getChainId()Promise<bigint>
detectNetwork()detected identity
validateNetwork()validated identity
getBalance(address, block?)Promise<bigint>
getTransactionCount(address, block?)Promise<bigint>
getGasPrice()Promise<bigint>
getMaxPriorityFeePerGas()Promise<bigint>
estimateGas(call)Promise<bigint>
call(call, block?)Promise<Uint8Array>
getCode(address, block?)Promise<Uint8Array>
getBlockNumber()Promise<bigint>
getBlockByNumber(block, full?)block or null
getBlockByHash(hash, full?)block or null
getTransaction(hash)transaction or null
getTransactionReceipt(hash)receipt or null
prepareTransaction(from, request)unsigned transaction
getSigningDigest(transaction)32-byte Uint8Array
broadcastTransaction(signed)transaction response
interface TransactionRequest {
to?: string; // omit for contract creation
value?: bigint | string; // bigint atomic units, decimal string Quantum
data?: Uint8Array | `0x${string}`;
gasLimit?: bigint;
gasPrice?: bigint;
maxFeePerGas?: bigint;
maxPriorityFeePerGas?: bigint;
nonce?: bigint;
chainId?: bigint;
type?: "legacy" | "dynamic";
accessList?: readonly AccessTuple[];
}interface SignTransactionResult {
unsignedTransaction: UnsignedQuantumTransaction;
signedTransaction: SignedQuantumTransaction;
signingDigest: Uint8Array;
signature: Uint8Array;
publicKey: Uint8Array;
rawTransaction: `0x${string}`;
rawTransactionBytes: Uint8Array;
transactionHash: `0x${string}`;
}QuantumWallet exposes generate, fromPrivateKey, fromSigner, signTransaction, signPrepared, sendTransaction, anchorHash, resetNonce, and destroy. QuantumContract exposes call and send.
Utilities include parseQuantum, formatQuantum, bytesToHex, hexToBytes, toQuantity, fromQuantity, normalizeAddress, normalizeHash, and known networks.
Typed failures cover address/transaction validation, serialization, signing, chain/genesis mismatch, transport, JSON-RPC errors, rejection, revert, and timeout.
qc namespace.Current nodes also expose these calls under the eth compatibility alias. New applications should use qc. Hosted gateways may apply a smaller allowlist.
curl --fail-with-body https://rpc.sandbox.qustody.io \
-H 'content-type: application/json' \
--data '{"jsonrpc":"2.0","id":1,"method":"qc_chainId","params":[]}'qc_chainId qc_syncing qc_blockNumber qc_gasPrice qc_maxPriorityFeePerGas qc_feeHistory qc_getBlockByNumber qc_getBlockByHash qc_getHeaderByNumber qc_getHeaderByHash qc_getBlockReceipts
qc_getBalance qc_getTransactionCount qc_getCode qc_getStorageAt qc_getProof qc_call qc_estimateGas qc_createAccessList
qc_getTransactionByHash qc_getTransactionReceipt qc_getRawTransactionByHash qc_getTransactionByBlockHashAndIndex qc_getTransactionByBlockNumberAndIndex qc_getRawTransactionByBlockHashAndIndex qc_getRawTransactionByBlockNumberAndIndex qc_getBlockTransactionCountByHash qc_getBlockTransactionCountByNumber qc_sendRawTransaction
qc_getLogs qc_newFilter qc_newBlockFilter qc_newPendingTransactionFilter qc_getFilterChanges qc_getFilterLogs qc_uninstallFilter
net_version net_listening net_peerCount web3_clientVersion web3_sha3
Every entry below expands into a plain-language description and a copyable JSON-RPC request. Replace values marked REPLACE_... before running the command.
qc_sendRawTransaction. Retain the locally computed hash, query it, and reconcile the pending nonce. The SDK submits once and verifies the returned hash.import { HttpRpcTransport } from "@quantum_chain/sdk/rpc";
const rpc = new HttpRpcTransport("https://rpc.sandbox.qustody.io", {
timeoutMs: 15_000,
maxResponseBytes: 8 * 1024 * 1024,
});
const logs = await rpc.request<unknown[]>("qc_getLogs", [{
fromBlock: "0x1",
toBlock: "latest",
address: CONTRACT_ADDRESS,
}]);Quantities are minimal hexadecimal. Binary values are even-length 0x bytes. Addresses are exactly 20 bytes. Quantum values and fees use atomic units. Administrative, debug, personal, miner, engine, and txpool APIs are operator surfaces and are not part of the public application contract.
QuantumSigner for an external production security boundary.