Build directly on
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 |
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();
}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
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.