A non-custodial wallet is software or hardware that generates and stores private keys locally, signs transactions on the user's device, and broadcasts signed payloads through an RPC endpoint. The wallet never delegates key custody to a server, but it still depends on remote infrastructure for chain data, nonce state, fee estimates, and transaction propagation. This article explains the key-management model, shows how to verify that a wallet is actually non-custodial, and provides runnable procedures for checking balances, nonces, and local signing against an RPC endpoint you control.
Direct answer and scope
A non-custodial wallet is a crypto wallet in which the private keys are generated and stored on infrastructure the user controls, typically a mobile device, browser extension, or hardware device. The wallet signs transactions locally and submits only signed payloads to the network. No company holds the key material, so no company can move, freeze, or recover funds on the user's behalf.
This article covers the technical mechanics that matter when you build, integrate, or audit non-custodial wallet flows: key derivation, local signing, RPC dependencies, and verification procedures. It does not rank wallet brands. If you need a concrete RPC endpoint recommendation for a specific chain, use the RPC Assistant rather than treating this page as a provider list.
- In scope: key custody model, signing flow, RPC calls a wallet makes, reproducible checks.
- Out of scope: brand comparisons, token price commentary, and legal advice on self-custody.
- Assumption: you have a wallet address, access to an RPC endpoint, and basic command-line tooling.
Custodial versus non-custodial custody model
The dividing line is who controls the private key. In a custodial model, an exchange or app holds the key and signs on your behalf; your balance is an entry in that provider's ledger until you withdraw on-chain. In a non-custodial model, the key exists only on your device or hardware module, and the provider never sees it.
This difference has operational consequences. A custodial provider can reset your password, freeze withdrawals, or lose funds in a platform failure. A non-custodial wallet cannot do any of those things, but it also cannot restore access if you lose your recovery phrase. The trade is convenience and a support desk against sole responsibility for backup.
- Custodial: provider holds keys, signs transactions, and controls withdrawal timing.
- Non-custodial: user holds keys, signs locally, and controls backup and recovery.
- Hybrid: MPC or smart-account wallets split signing authority, but key material is still not held solely by a platform.
How a non-custodial wallet derives and stores keys
Most non-custodial wallets follow a deterministic hierarchy. A recovery phrase, usually 12 or 24 words, encodes a seed. From that seed the wallet derives a master key and then per-account keys along a derivation path. For EVM chains the common path is m/44'/60'/0'/0/0; Bitcoin and Solana use different coin types and path conventions.
The private key never leaves the signing environment. In a software wallet it is encrypted at rest on the device and decrypted only to sign. In a hardware wallet it stays inside a secure element, and the host computer sends an unsigned payload for the device to sign after physical confirmation. The recovery phrase is the backup of the seed, not a password, and anyone who obtains it can reconstruct every derived key.
- Seed phrase → master key → derivation path → account private key → public address.
- Software wallets encrypt keys locally; hardware wallets isolate keys in a secure chip.
- The recovery phrase is equivalent to all derived private keys and must never be entered into a website.
What the wallet asks an RPC endpoint to do
A non-custodial wallet is not a node. It reads chain state and broadcasts transactions through an RPC endpoint. The endpoint sees your public address and signed transactions, but it never sees your private key. That separation is what makes remote RPC compatible with self-custody.
Typical calls include balance queries, nonce lookups, fee estimation, chain ID checks, and transaction submission. If the endpoint is stale or rate-limited, the wallet may show an incorrect balance, reuse a nonce, or fail to broadcast. Choosing a reliable endpoint is therefore part of wallet reliability, not an afterthought. See the multi-chain RPC endpoints guide for the general model and the RPC endpoint guide for configuration patterns.
- Read calls: balance, nonce, chain ID, fee history, contract state.
- Write path: locally signed raw transaction submitted via
eth_sendRawTransactionor the chain equivalent. - Failure modes: stale nonce, wrong chain ID, rate limiting, and dropped transactions.
Prerequisites for the verification procedure
The checks below assume an EVM-compatible chain and a JSON-RPC endpoint. Replace RPC_URL with an endpoint you control or a provider endpoint you are evaluating. Never paste a real private key into a shared shell history; use an environment variable and a throwaway test key for signing experiments.
You need curl for read calls and either Node.js with ethers or Python with web3.py for signing. The procedures are read-only until the signing section, which uses a test key and does not broadcast.
export RPC_URL="https://your-endpoint.example"
export WALLET_ADDRESS="0xYourPublicAddress"
export PRIVATE_KEY="0xYourThrowawayTestKey"Checking chain identity and balance through RPC
Before trusting any balance display, confirm the endpoint is on the chain you expect. A wallet pointed at the wrong chain ID can show a zero balance or submit a transaction that another network rejects. The two calls below return the chain ID and the account balance in wei.
Record the outputs in a results table alongside the endpoint, timestamp, and block number. Do not assume values; measure them. A simple table with columns for endpoint, chain ID, block number, balance in wei, and observed latency makes endpoint comparisons reproducible.
curl -s -X POST "$RPC_URL" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"eth_chainId","params":[]}'
curl -s -X POST "$RPC_URL" \
-H "Content-Type: application/json" \
-d "{\"jsonrpc\":\"2.0\",\"id\":2,\"method\":\"eth_getBalance\",\"params\":[\"$WALLET_ADDRESS\",\"latest\"]}"Verifying nonce state before signing
The account nonce is the count of transactions sent from an address. A wallet that signs with a stale nonce will produce a transaction that cannot be included until the earlier nonce is mined. Query both the latest and pending nonce to see whether the endpoint has visibility of queued transactions.
If pending equals latest, there are no queued transactions from that address in the endpoint's mempool view. If pending is higher, a previous transaction is still pending. This distinction matters when a user retries a stuck transaction; replacing it requires the same nonce with a higher fee, not a new nonce.
curl -s -X POST "$RPC_URL" \
-H "Content-Type: application/json" \
-d "{\"jsonrpc\":\"2.0\",\"id\":3,\"method\":\"eth_getTransactionCount\",\"params\":[\"$WALLET_ADDRESS\",\"latest\"]}"
curl -s -X POST "$RPC_URL" \
-H "Content-Type: application/json" \
-d "{\"jsonrpc\":\"2.0\",\"id\":4,\"method\":\"eth_getTransactionCount\",\"params\":[\"$WALLET_ADDRESS\",\"pending\"]}"Demonstrating local signing without broadcasting
The core property of a non-custodial wallet is that signing happens locally. You can demonstrate this by constructing and signing a transaction in your own process, then inspecting the raw signed payload. No key material is sent to the RPC endpoint; only the resulting hex string would be broadcast.
The Node.js example below uses ethers to sign a transaction offline. Run it with a throwaway key and a chain ID that matches your endpoint. The output is a signed raw transaction you can decode or discard. The Python example performs the same operation with web3.py.
// npm install ethers
const { Wallet } = require("ethers");
async function main() {
const wallet = new Wallet(process.env.PRIVATE_KEY);
const tx = {
to: process.env.WALLET_ADDRESS,
value: 0n,
nonce: 0,
gasLimit: 21000n,
maxFeePerGas: 1000000000n,
maxPriorityFeePerGas: 1000000000n,
chainId: 1,
type: 2
};
const signed = await wallet.signTransaction(tx);
console.log("from:", wallet.address);
console.log("signed raw tx:", signed);
}
main().catch(console.error);Expected verification results
A correctly configured non-custodial setup should produce consistent results across checks. Use the table below as a template and fill it with your own measurements rather than assumed values.
| Check | Expected observation | What a mismatch means |
|---|---|---|
eth_chainId | Matches the network the wallet UI displays | Endpoint is on a different chain or a proxy is misrouting |
eth_getBalance | Non-zero for a funded address, consistent across two endpoints | Stale endpoint, wrong address, or wrong chain |
eth_getTransactionCount latest vs pending | Pending greater than or equal to latest | Pending greater than latest indicates queued transactions |
| Local signing | Signed payload produced without any network call | If signing requires a remote call, key custody is not local |
| Broadcast | Transaction hash returned and visible on a block explorer | Rejection usually indicates nonce, fee, or chain ID error |
If the signed payload is produced locally and the endpoint only receives the raw transaction, the wallet flow is non-custodial with respect to key material. If any step requires sending the private key or seed to a server, the flow is custodial regardless of marketing language.
Troubleshooting common non-custodial wallet failures
Most wallet failures are infrastructure or state problems, not key problems. A balance that will not refresh usually means the endpoint is rate-limited or behind the chain head. A transaction that never confirms usually means the nonce is stale or the fee is below the current base fee. A wallet that shows the wrong network usually has a chain ID mismatch between the app configuration and the endpoint.
For stuck transactions, query the pending nonce and resubmit with the same nonce and a higher priority fee. For balance discrepancies, compare two independent endpoints at the same block height. For signing errors, confirm the derivation path and that the key corresponds to the address you expect. If you operate your own node, the blockchain node hosting guide covers sync and health checks that affect wallet reliability.
- Stale balance: compare endpoints at a fixed block number.
- Stuck transaction: reuse the nonce with higher fees.
- Wrong network: verify chain ID in both wallet config and endpoint response.
- Signing failure: check derivation path and key-address correspondence.
Limitations, trade-offs, and next steps
Non-custodial wallets remove counterparty risk but concentrate operational risk on the user. Lost recovery phrases are unrecoverable, phishing sites target seed entry, and malicious token approvals can drain funds even when keys remain local. Hardware wallets and transaction simulation reduce but do not eliminate these risks.
RPC dependency is the other trade-off. A self-custody wallet still trusts an endpoint for chain data and propagation. For production integrations, evaluate endpoint reliability, rate limits, and chain coverage before committing. You can compare plans on the RPC pricing page, review supported networks, or explore dedicated node options for workloads that need consistent throughput. For a concrete endpoint choice by chain, consult the RPC Assistant.
- Back up the recovery phrase offline and verify the backup before funding.
- Simulate or decode transactions before signing unfamiliar contracts.
- Test endpoint failover so a single provider outage does not block signing and broadcast.
- Review /en/learn for related wallet and RPC fundamentals before production rollout.