Skip to main content
If you have been running Quantum Chain with geth --unlock for development or a small production deployment, this guide walks through moving signing into a custody platform. The end state: your application calls Qustody instead of submitting eth_sendTransaction directly, and private keys live behind a hardened signing service rather than inside geth.

Why migrate

Migration overview

Two boundaries change:
  1. The application no longer sends signed transactions through geth. It calls the Qustody REST API.
  2. Geth no longer holds keys. It runs as a read-only RPC node that Qustody talks to.

Phase 1 — Prepare

Inventory what you have:
  • List addresses currently unlocked in geth (personal_listAccounts).
  • Note the locations of their keystore files and passphrases.
  • Identify every code path that calls eth_sendTransaction, personal_sendTransaction, or signs locally with ethers/web3.
  • Decide on a confirmation policy. Qustody defaults to 12 blocks (CUSTODY_CONFIRMATION_DEPTH).
  • Choose a signer mode: grpc (recommended), remote, or callback.

Phase 2 — Stand up infrastructure

Run side-by-side with the existing --unlock setup:
  1. Deploy Qustody following Deployment. Point CUSTODY_NODE_RPC_URL at your existing geth node.
  2. Deploy the signing service. Configure mTLS or bearer auth between Qustody and the signer.
  3. Smoke-test with Configure a development network on a non-production address.

Phase 3 — Move keys

For each address that will move to custody:
  1. Create a fresh keypair on the signing service. Do not copy your geth keystore — the migration is also an opportunity to rotate.
  2. Register the wallet with Qustody:
  3. Drain the old address. Transfer funds from the old --unlock-managed address to the new custody-managed address with one final manual transaction.
  4. Verify on chain that the receiving address shows the expected balance.
  5. Lock the old account permanently — remove its keystore file, revoke the unlock passphrase, and stop using it.

Phase 4 — Switch traffic

Refactor your application:
Receipts no longer come back synchronously — they arrive as transaction.broadcast and transaction.confirmed webhooks. Update any code that polled eth_getTransactionReceipt to consume those events instead.

Phase 5 — Harden geth

Once the application no longer talks signing methods to geth, restrict it:
Remove personal and admin namespaces. Remove --unlock. Remove --allow-insecure-unlock. The geth keystore directory can be archived offline.

Phase 6 — Add policies and approvers

Now that signing is centralized, layer in controls:
See Policies for the complete rule set.

Phase 7 — Verify

  • Send a small test transaction through Qustody. Confirm it lands on chain.
  • Verify webhook signatures end-to-end.
  • Pull the audit log: GET /v1/audit. Verify it: GET /v1/audit/verify.
  • Run eth_listAccounts on geth — should be empty (or only contain irrelevant historical accounts).
  • Confirm there are no personal_* or admin_* calls in your application logs.

Common pitfalls

If any process still sends transactions from a custody-managed address through eth_sendRawTransaction directly to geth, nonces collide.Fix: route 100% of outbound transactions for a given address through Qustody.
Qustody retries failed webhook deliveries up to 5 times. Without idempotent handling, you’ll process the same transaction.confirmed event multiple times.Fix: track delivered events by id and ignore duplicates.
If the signer reads its passphrase from the same env that Qustody reads from, a Qustody compromise still leaks signing access.Fix: put the signer in a separate trust zone (HSM, separate VM, separate Kubernetes namespace with its own service account).

Where to go next