Wallets & Signing
Derive deterministic wallets, sign transfers, secure secrets, and monitor balances.
@qubiq/core/wallet exposes everything you need to build wallet-aware libraries, CLIs, or desktop apps. This page summarizes the key APIs and how they compose.
Deterministic derivation
import { deriveWalletFromSeed } from "@qubiq/core";
const wallet = await deriveWalletFromSeed("wqbdupxgcaimwdsnchitjmsplzclkqokhadgehdxqogeeiovzvadstt");
console.log(wallet.identity); // SUZFF...Internally we ship the same FourQ + KangarooTwelve WASM pipeline as the native node, so IDs match official tooling. Pass { accountIndex } or use deriveWalletFromPath(seed, "m/0/1'" ) for HD-style derivation.
HD derivation & account catalogs
Derive multiple accounts from the same seed using BIP32-inspired paths. Every helper accepts a derivation path so you can deterministically produce custodial portfolios or multi-sig participants.
import { deriveWalletFromPath } from "@qubiq/core/wallet";
const family = await Promise.all(
Array.from({ length: 3 }).map((_, index) =>
deriveWalletFromPath(seed, `m/0/${index}`),
),
);
family.forEach(({ identity }, index) => {
console.log(`account[${index}] -> ${identity}`);
});Use account indices for app-managed wallets and custom paths for hardware-backed or team escrow flows. The helper validates path syntax so malformed derivations never reach WASM.
Identity helpers
Normalize and convert identities whenever you accept user input or integrate with external registries.
import { identityToPublicKey, publicKeyToIdentity, normalizeIdentity } from "@qubiq/core/wallet";
const normalized = normalizeIdentity(" suzffqscvphyybdcqodemfao... ".trim());
const publicKeyHex = identityToPublicKey(normalized);
const roundTrip = publicKeyToIdentity(publicKeyHex); // === normalizedPair these utilities with schema validation (IdentityStringSchema) to keep REST handlers and CLI flags consistent.
Schnorr signing
const { tickInfo } = await client.getTickInfo();
const signed = await wallet.signTransfer({
destinationPublicKey: "cd".repeat(32),
amount: BigInt(1_000_000),
tick: tickInfo.tick + 10,
});
await client.broadcastTransaction({ encodedTransaction: Buffer.from(signed.bytes).toString("base64") });signTransfer returns canonical bytes + metadata (public key, hash, signature) so you can encode, store, or simulate transactions. Combine with encodeTransaction when you need a raw hex representation for debugging.
Signed transfer payload
The response contains:
bytes– raw transaction bytes ready for base64 encoding.transactionId– hash of the signed bytes (matches the native node output).sourcePublicKey/destinationPublicKey– 32-byte hex strings.signature– Schnorr signature as 64-byte hex.digest– hex-encoded KangarooTwelve digest for auditing.
Secure storage
import { encryptSecret, decryptSecret } from "@qubiq/core";
const encrypted = encryptSecret(seed, process.env.WALLET_PASS!);
const decrypted = decryptSecret(encrypted, process.env.WALLET_PASS!);Use these helpers to persist seeds/keys on disk; we zeroize buffers after use to reduce leakage.
Storage patterns
- Encrypt at rest using user-provided passphrases (never hard-code keys).
- Wrap
encryptSecretbefore persisting to disk/KV so secrets only exist in memory briefly. - Pair with secrets-management APIs (e.g., AWS KMS) by storing the encrypted blob plus metadata about the wrapping key.
Offline bundles
import { createOfflineTransferBundle, signOfflineTransferBundle } from "@qubiq/core";
const bundle = createOfflineTransferBundle({
sourcePublicKey: wallet.publicKey,
destinationPublicKey: "cd".repeat(32),
amount: BigInt(10_000),
tick: tickInfo.tick + 20,
});
const signed = await signOfflineTransferBundle(bundle, wallet.privateKey);Bundles let you pre-approve transfers, pass them through air-gapped review, and broadcast later.
Air-gapped workflow
createOfflineTransferBundleon an online machine (no private key required).- Transfer the bundle to an air-gapped signer and call
signOfflineTransferBundle. - Return the signed payload and broadcast via
LiveServiceClient.broadcastTransaction.
You can serialize bundles as JSON to build approval queues or custody dashboards.
Watching balances
import { WalletWatcher } from "@qubiq/core";
const watcher = new WalletWatcher({ identity: wallet.identity, pollIntervalMs: 5_000 });
watcher.on("balanceChanged", ({ current }) => console.log("balance", current.balance));
await watcher.start();Under the hood this wraps LiveServiceClient and emits events only when values change.
Watcher events
balanceChanged— fires with{ identity, previous, current }.error— emit network/parse errors for retry logic.started/stopped— lifecycle hooks for metrics or logging.
Hook watchers into the automation runtime to automatically emit Prometheus metrics (see /automation/monitoring).
Unsigned transfer payload
When building custom tooling, construct an object with:
sourcePublicKey– 32-byte hex string.destinationPublicKey– 32-byte hex string.amount–bigintrepresenting QUs.tick– execution tick (typicallycurrent + offset).inputType/inputData– optional contract payload.
Feed that payload into wallet.signTransfer or encodeTransaction depending on your flow.
Build on top
- Desktop wallet prototype (
@qubiq/wallet) will consume these primitives. - Automation runtime wires watchers into pipelines automatically.
- CLI tooling can reuse derivation + signing without touching WASM details.
Reference implementations live in /examples (watcher, send, proposals) and tests/wallet/*. Start from those scripts when building sandboxes, and plug in ReactBits-powered UI (see landing page) for polished developer previews.