Manage a vault
Create, encrypt, unlock, and use a vault for secure seed storage using @qubic.org/wallet.
A vault stores one or more seeds encrypted with AES-256-GCM using a password-derived key (PBKDF2-SHA256, 600,000 iterations). Seeds never leave the vault in plaintext — you get identities out, not seeds.
Create a vault
import { createVault, generateSeed } from "@qubic.org/wallet"
const seed = generateSeed()
const vault = await createVault({
password: "my-strong-password",
seeds: [seed],
})createVault returns a VaultData object — a serializable blob you can store anywhere (localStorage, a file, a database). It contains no plaintext seed material.
Export and persist
VaultData is a plain object. Serialize it however you prefer:
import { exportVault } from "@qubic.org/wallet"
const json = exportVault(vault)
localStorage.setItem("qubic-vault", json)Import and unlock
import { importVault, unlockVault } from "@qubic.org/wallet"
const raw = localStorage.getItem("qubic-vault")!
const vault = importVault(raw)
const unlockedVault = await unlockVault(vault, "my-strong-password")unlockVault throws if the password is wrong or the vault data is corrupt.
Derive identities
After unlocking, iterate the identities without ever touching raw seed bytes:
for (const identity of unlockedVault.identities) {
console.log("Identity:", identity)
}Sign transactions
Create a wallet from an unlocked vault identity to sign:
import { createWalletFromVault } from "@qubic.org/wallet"
import { createLiveClient } from "@qubic.org/rpc"
import { toIdentity } from "@qubic.org/types"
const live = createLiveClient()
const { tick } = await live.getTickInfo()
const wallet = createWalletFromVault(unlockedVault, unlockedVault.identities[0])
const { encoded, hash } = await wallet.buildTransfer({
destination: toIdentity("CFBMEMZOIDEXQAUXYYSZIURADQLAPWPMNJXQSNVQZAHYVOPYUKKJBJUCTVJL"),
amount: 1_000_000n,
targetTick: tick + 5,
currentTick: tick,
})
await live.broadcastTransaction(encoded)Add seeds to an existing vault
import { addSeedToVault } from "@qubic.org/wallet"
const newSeed = generateSeed()
const updatedVault = await addSeedToVault(vault, newSeed, "my-strong-password")Re-export and persist the updated vault data after adding seeds.
React with VaultProvider
In a React app, VaultProvider manages vault lifecycle. The unlocked seed is stored in a useRef — it never appears in React state or DevTools.
import { QubicProvider, VaultProvider, useVaultState } from "@qubic.org/react"
import { createLiveClient } from "@qubic.org/rpc"
import { importVault } from "@qubic.org/wallet"
const live = createLiveClient()
const vaultData = importVault(localStorage.getItem("qubic-vault")!)
export function App() {
return (
<QubicProvider liveClient={live}>
<VaultProvider vaultData={vaultData} persistSession autoLockMs={15 * 60_000}>
<WalletUI />
</VaultProvider>
</QubicProvider>
)
}
function WalletUI() {
const { isUnlocked, identities, isUnlocking, error, unlock, lock } = useVaultState()
if (!isUnlocked) {
return (
<form onSubmit={(e) => {
e.preventDefault()
const pw = (e.currentTarget.elements.namedItem("password") as HTMLInputElement).value
unlock(pw)
}}>
<input name="password" type="password" placeholder="Password" />
<button type="submit" disabled={isUnlocking}>
{isUnlocking ? "Unlocking…" : "Unlock"}
</button>
{error && <p>{error.message}</p>}
</form>
)
}
return (
<div>
{identities.map((id) => (
<p key={id}>Identity: {id}</p>
))}
<button type="button" onClick={lock}>Lock</button>
</div>
)
}Session persistence
Pass persistSession to store the unlocked key in IndexedDB with extractable: false. The vault stays unlocked through page reloads within the same browser session:
<VaultProvider vaultData={vaultData} persistSession>
{children}
</VaultProvider>The raw key bytes can never be read back — it can only sign. Auto-lock clears the key after inactivity:
<VaultProvider vaultData={vaultData} persistSession autoLockMs={10 * 60_000}>Complete server-side example
import {
generateSeed,
createVault,
exportVault,
importVault,
unlockVault,
createWalletFromVault,
} from "@qubic.org/wallet"
import { createLiveClient } from "@qubic.org/rpc"
import { toIdentity } from "@qubic.org/types"
import { writeFileSync, readFileSync } from "node:fs"
// 1. Create and persist vault
const seed = generateSeed()
const vault = await createVault({ password: "hunter2", seeds: [seed] })
writeFileSync("vault.json", exportVault(vault))
// 2. Later: load and unlock
const loaded = importVault(readFileSync("vault.json", "utf8"))
const unlocked = await unlockVault(loaded, "hunter2")
// 3. Sign and broadcast a transfer
const live = createLiveClient()
const { tick } = await live.getTickInfo()
const wallet = createWalletFromVault(unlocked, unlocked.identities[0])
const { encoded, hash } = await wallet.buildTransfer({
destination: toIdentity("CFBMEMZOIDEXQAUXYYSZIURADQLAPWPMNJXQSNVQZAHYVOPYUKKJBJUCTVJL"),
amount: 500_000n,
targetTick: tick + 5,
currentTick: tick,
})
const result = await live.broadcastTransaction(encoded)
console.log(`TX ${hash} broadcast to ${result.peersBroadcastedTo} peers`)