Quantum Chain
Construct, sign, submit, and track canonical Quantum transactions from Node.js. Deploy and call EVM smart contracts with native post-quantum signing—without recreating chain formats in application code.
Validate the chain before using it.
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 | Q, 18 decimals | Q, 18 decimals |
Connect to Sandbox and fund a test wallet.
Quantum Sandbox is the Quantum Chain test network on chain ID 20804. Sandbox Q 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.ioStep 1 — prove that you reached Sandbox
curl --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.
Step 2 — generate and fund a disposable test wallet
import {
QuantumClient, QuantumWallet, formatQ, 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", formatQ(funding.balance), "Q");
console.log("faucet transaction", funding.transactionHash ?? "already funded");
console.log("explorer", client.getExplorerAddressUrl(wallet.address));
} finally {
wallet.destroy();
}npx tsx fund-sandbox.tsStep 3 — call the test-only faucet directly
export 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 Qwei, 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.
Private npm packages, standard npm workflow.
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 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}Fund a wallet and send a transaction.
This complete script validates the permanent Sandbox, funds a short-lived sender, transfers 0.01 Q, and waits for a successful receipt.
import {
QuantumClient,
QuantumWallet,
formatQ,
networks,
parseQ,
} 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", formatQ(funding.balance), "Q");
const submitted = await sender.sendTransaction(client, {
to: recipient.address,
value: parseQ("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.tsWhat the SDK does
- Validates chain ID and genesis hash.
- Resolves pending nonce, fees, and gas estimate.
- Constructs the canonical unsigned Quantum transaction.
- Signs its explicit 32-byte digest through the native package.
- Serializes signature and public key into the chain transaction.
- Submits once and verifies the node returned the expected local hash.
- Waits for a successful receipt and requested confirmations.
Compile and deploy a contract.
Quantum Chain executes EVM bytecode. The SDK handles the Quantum transaction. In this example solc compiles Solidity and 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.Read state, write state, verify the receipt.
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.
Construct and sign without RPC.
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, parseQ,
} 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: parseQ("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();
}Install the native Python SDK.
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.
Step 1 — get your private index credentials
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.
Step 2 — create a virtual environment
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 pipStep 3 — install the private wheel
python -m pip install \
--index-url "https://SDK_USERNAME:SDK_TOKEN@sdk.quantumapi.io/simple/" \
py-quantum==0.2.0Step 4 — generate, sign and verify
from 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.pyPython API
Signer.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
Build a submission-ready Sandbox transaction
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.
Public TypeScript surface.
Entry points
| 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 |
QuantumClient
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
Transaction request
interface TransactionRequest {
to?: string; // omit for contract creation
value?: bigint | string; // bigint Qwei, decimal string Q
data?: Uint8Array | `0x${string}`;
gasLimit?: bigint;
gasPrice?: bigint;
maxFeePerGas?: bigint;
maxPriorityFeePerGas?: bigint;
nonce?: bigint;
chainId?: bigint;
type?: "legacy" | "dynamic";
accessList?: readonly AccessTuple[];
}Scope-B result
interface SignTransactionResult {
unsignedTransaction: UnsignedQuantumTransaction;
signedTransaction: SignedQuantumTransaction;
signingDigest: Uint8Array;
signature: Uint8Array;
publicKey: Uint8Array;
rawTransaction: `0x${string}`;
rawTransactionBytes: Uint8Array;
transactionHash: `0x${string}`;
}Wallet, contract, utilities, errors
QuantumWallet exposes generate, fromPrivateKey, fromSigner, signTransaction, signPrepared, sendTransaction, anchorHash, resetNonce, and destroy. QuantumContract exposes call and send.
Utilities include parseQ, formatQ, 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.
The chain-native 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":[]}'Network, blocks, fees
qc_chainId qc_syncing qc_blockNumber qc_gasPrice qc_maxPriorityFeePerGas qc_feeHistory qc_getBlockByNumber qc_getBlockByHash qc_getHeaderByNumber qc_getHeaderByHash qc_getBlockReceipts
Account and state
qc_getBalance qc_getTransactionCount qc_getCode qc_getStorageAt qc_getProof qc_call qc_estimateGas qc_createAccessList
Transactions
qc_getTransactionByHash qc_getTransactionReceipt qc_getRawTransactionByHash qc_getTransactionByBlockHashAndIndex qc_getTransactionByBlockNumberAndIndex qc_getRawTransactionByBlockHashAndIndex qc_getRawTransactionByBlockNumberAndIndex qc_getBlockTransactionCountByHash qc_getBlockTransactionCountByNumber qc_sendRawTransaction
Logs and filters
qc_getLogs qc_newFilter qc_newBlockFilter qc_newPendingTransactionFilter qc_getFilterChanges qc_getFilterLogs qc_uninstallFilter
Standard utility namespaces
net_version net_listening net_peerCount web3_clientVersion web3_sha3
Click a method for its inputs, output and request
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.Raw transport
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; Q and fees use Qwei. Administrative, debug, personal, miner, engine, and txpool APIs are operator surfaces and are not part of the public application contract.
Keep signing explicit and binary.
- Validate chain ID and genesis before every sending workflow.
- Pass keys, payloads, signatures, and calldata as binary—not strings.
- Never log private keys or export them from managed signers.
- Use
QuantumSignerfor an external production security boundary. - Set deadlines and response-size bounds on every RPC request.
- Do not implement transaction encoding or cryptography in application JavaScript.
- Do not treat a submitted hash as success; wait for a successful receipt.