Logo
New RPC users get 35% off their first monthView the offer
OnFinality Learn
Infrastructure & Operations11 min read

What Is a Non-Custodial Wallet? Key Control, Signing, and Verification

A practical explanation of non-custodial wallets: how key control works, how signing differs from custodial accounts, and how to verify self-custody behavior against a live RPC endpoint.

TL;DR

A non-custodial wallet is a wallet where the private keys that authorize spending are generated and held by the user, not by a platform. The wallet software is a signing client: it derives addresses from keys, builds transactions, and signs them locally before broadcasting through an RPC endpoint. This article explains the key-control model, contrasts it with custodial accounts, and gives a runnable procedure for verifying that signing happens client-side and that the RPC endpoint only sees signed transactions. It also covers seed phrase recovery limits, common failure modes, and how to choose infrastructure that does not undermine self-custody.

Definition and scope of a non-custodial wallet

A non-custodial wallet, also called a self-custody wallet, is software or hardware in which the private keys controlling an address are created and stored under the user's control. The wallet does not hold coins; it holds the ability to authorize state changes on a blockchain. Whoever can produce a valid signature for an address controls the assets at that address.

This article covers the technical mechanics of that control: key generation, address derivation, transaction signing, broadcast through an RPC endpoint, and recovery from a seed phrase. It does not recommend a specific wallet brand. The goal is to give you a mental model you can test, plus a procedure you can run against any EVM-compatible chain and RPC provider.

  • Non-custodial means user-held keys, not user-held tokens.
  • The wallet is a client; the blockchain is the source of truth.
  • An RPC endpoint relays signed transactions but should never need your private key.

Assumptions and prerequisites for the verification procedure

The examples below assume an EVM-compatible network, Node.js 18 or later, and a funded test account on a testnet. You need an RPC_URL for the target chain; obtain one from your provider's dashboard or from the OnFinality API service. Never use a mainnet private key in a tutorial script.

You also need a basic understanding of how transactions are structured. If nonce, gas, and chain ID are unfamiliar, read the explanation of a blockchain nonce and the RPC endpoints guide before continuing.

  • Node.js 18+ and npm for the signing example.
  • curl for the raw JSON-RPC checks.
  • A testnet account with a small balance and its private key stored in an environment variable, not in source code.

How key control differs from custodial account access

In a custodial model, a platform holds the keys and you hold a login. When you request a withdrawal, the platform signs with its own key. Your access can be suspended, rate-limited, or lost if the platform fails. In a non-custodial model, you hold the key and the platform, if any, only provides connectivity.

The practical difference appears at signing time. A non-custodial wallet constructs the transaction payload locally, signs it with the private key, and sends only the signed bytes to an RPC endpoint. The endpoint can see the transaction and may log it, but it cannot alter the signature or move funds without the key.

  • Custodial: platform signs; you authenticate.
  • Non-custodial: you sign; the network verifies.
  • RPC providers in a non-custodial flow are transport, not custodians.

The signing pipeline from seed phrase to broadcast

A non-custodial wallet follows a deterministic pipeline. A seed phrase is stretched into a master key, from which account keys and addresses are derived. The wallet builds a transaction with the correct nonce, chain ID, gas parameters, recipient, and value. It hashes and signs that payload locally, then calls eth_sendRawTransaction on an RPC endpoint.

The endpoint validates the transaction format, forwards it to the network, and returns a transaction hash. Confirmation is a separate step: the wallet polls eth_getTransactionReceipt until a block includes the transaction. At no point does the endpoint receive the private key.

const { ethers } = require("ethers");

async function signAndBroadcast() {
  const provider = new ethers.JsonRpcProvider(process.env.RPC_URL);
  const wallet = new ethers.Wallet(process.env.PRIVATE_KEY, provider);

  const tx = {
    to: process.env.RECIPIENT,
    value: ethers.parseEther("0.001"),
    chainId: Number(process.env.CHAIN_ID)
  };

  const signed = await wallet.signTransaction(tx);
  console.log("Signed locally. First 20 bytes:", signed.slice(0, 42));

  const response = await provider.broadcastTransaction(signed);
  console.log("Broadcast hash:", response.hash);

  const receipt = await response.wait();
  console.log("Included in block:", receipt.blockNumber);
}

signAndBroadcast().catch(console.error);

Runnable check that the RPC endpoint never receives your key

You can confirm the separation of duties with two curl calls. The first asks the endpoint for the current nonce of your address; the second submits a pre-signed raw transaction. Neither call includes a private key. If a provider ever asks for a private key to send a transaction, that is a custodial pattern and should be treated as a red flag.

Run the nonce query first, then sign offline with the script above, then submit the raw transaction. Replace the placeholders with your own values.

curl -s -X POST "$RPC_URL" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"eth_getTransactionCount","params":["0xYOUR_ADDRESS","pending"]}'

curl -s -X POST "$RPC_URL" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":2,"method":"eth_sendRawTransaction","params":["0xSIGNED_RAW_TRANSACTION"]}'

Expected verification results and how to read them

A correct non-custodial flow produces three observable results. The nonce query returns a hexadecimal count for your address. The signing script prints a signed payload without contacting the network for key material. The broadcast call returns a transaction hash, and a later receipt query returns a block number and status.

Record each result in a table so you can compare providers or retry after a failure. Do not fill in values you did not observe; the table is a measurement template, not a claim about any provider.

  • Nonce result: hex string from eth_getTransactionCount.
  • Signed payload: local output from signTransaction, no network dependency.
  • Broadcast result: transaction hash from eth_sendRawTransaction.
  • Receipt result: block number and status from eth_getTransactionReceipt.

Results table procedure for comparing RPC behavior

When you test more than one RPC endpoint, keep the signed transaction constant and change only RPC_URL. This isolates transport behavior from signing behavior. Fill the table with observed values only.

Endpoint labelNonce returnedBroadcast hash returnedReceipt blockReceipt statusNotes
Provider A
Provider B
Provider C

If a broadcast succeeds but the receipt never appears, the transaction may be pending, dropped, or rejected by the network. Check the hash on a block explorer before assuming the endpoint is faulty. For production workloads, compare dedicated node options and review RPC pricing so that rate limits do not masquerade as wallet failures.

Troubleshooting common non-custodial wallet failures

Most non-custodial wallet problems fall into four categories: key handling, transaction construction, endpoint connectivity, and network state. Separate them before changing anything.

If signing fails, the key or derivation path is wrong. If broadcast fails with a nonce error, another transaction from the same address is pending. If the endpoint times out, the wallet cannot reach the network but your keys are unaffected. If the receipt is missing, the transaction may be underfunded for gas or dropped from the mempool.

  • Nonce too low: wait for the pending transaction or replace it.
  • Insufficient funds: check balance and gas price separately.
  • Chain ID mismatch: confirm the wallet network matches RPC_URL.
  • Seed phrase rejected: verify word order and language before retrying.

Recovery limits and the responsibility trade-off

A seed phrase is a backup of key material, not an account recovery service. If the phrase is lost and the device fails, the funds are unrecoverable. If the phrase is exposed, anyone can sign. This is the central trade-off of self-custody: you remove platform risk and accept operational risk.

Non-custodial wallets also depend on infrastructure. A wallet can sign offline, but it needs an RPC endpoint to read balances and broadcast. Choosing a reliable endpoint is therefore part of self-custody operations, not a separate concern. Review network coverage and dedicated node options when you move from testing to production.

Next steps for safe self-custody operations

Start on a testnet, run the signing and broadcast procedure, and record the results table. Then move to small mainnet amounts with a hardware signer if possible. Keep the private key out of shell history and source control, and rotate test keys after each exercise.

For endpoint selection, use the RPC Assistant guidance on best RPC providers for Web3 projects and the multi-chain RPC endpoints guide. If you need predictable throughput, compare dedicated node offerings and pricing before committing a production wallet flow.

  • Test signing and broadcast on a testnet first.
  • Store seed phrases offline and never enter them into a website.
  • Monitor endpoint health separately from wallet software.

Never Worry about Infrastructure Again

OnFinality takes away the heavy lifting of DevOps so you can build smarter and faster.

Get Started