Crypto Exchange vs. Wallet: Where Your Crypto Really Lives

A crypto exchange and a self-custody wallet can show the same balance, but they give you fundamentally different control over your money.

When an exchange app displays 1.42 ETH and a wallet extension displays the same amount, the interfaces look remarkably similar. Underneath, two very different systems are doing the work.

On a centralized exchange, your balance is usually an entry in the operator’s internal ledger. The exchange controls the keys, decides when to create a blockchain transaction, and determines whether a withdrawal can proceed.

With a self-custody wallet, you control the mechanism that authorizes transactions. That mechanism might be a private key, a hardware device, a threshold of signatures, or a smart account with its own authorization rules.

After 25 years in traditional finance, including banking, I instinctively separate the number a customer sees from the system that actually settles a payment. I apply that same distinction to crypto. A familiar-looking balance does not tell me who can move the assets.

The blockchain does not know that you logged in with an email address, passed identity verification, or have a balance recorded under your name at Coinbase, Kraken, or another platform. It sees addresses, spending conditions, contract accounts, signatures, and state changes.

For developers, this is not a philosophical distinction. It determines:

  • Who can stop a withdrawal.
  • Where the secret or authorization mechanism controlling the funds resides.
  • What happens when an API becomes unavailable.
  • How duplicate payouts are prevented.
  • Who pays the network fee.
  • How the system recovers after an incident.
  • Which system is authoritative for each kind of information.
  • How much infrastructure your team must build and maintain.

Throughout this article, “exchange” means a centralized, custodial exchange, not a decentralized exchange protocol.

Where Does Your Crypto Actually Live?

Crypto exists as blockchain state and spending conditions, not as coins stored inside an app, browser extension, hardware device, or seed phrase file.

Whether I am looking at Bitcoin or Ethereum’s public blockchain, I start with the same question: what conditions must be satisfied before these assets can move?

For a conventional Ethereum externally owned account, or EOA, a transaction is authorized with the corresponding private key. A smart account uses code to enforce its authorization rules. The distinction between these account types is explained in Ethereum’s documentation on accounts and private keys.

A Bitcoin wallet works differently. It tracks unspent transaction outputs, or UTXOs, and constructs transactions that satisfy those outputs’ spending conditions.

The more precise explanation is:

A wallet does not contain the coins. It stores, uses, or coordinates the authority needed to spend them.

That describes a wallet with spending capabilities. A watch-only wallet can display balances and transactions without possessing any spending authority.

When your crypto is held on an exchange, you generally do not control the exchange wallet’s spending authority. The operator does. Your account balance reflects what its internal system records for you.

What Actually Happens Inside a Crypto Exchange?

A centralized exchange combines a trading platform, an internal accounting system, and blockchain custody infrastructure.

The familiar app is only the customer-facing layer. Behind it, an exchange typically operates:

  • Account and identity systems.
  • Know Your Customer, or KYC, and risk services.
  • An internal financial ledger.
  • A trading engine.
  • Deposit-processing services.
  • Withdrawal-processing services.
  • Hot, warm, and cold wallet infrastructure.
  • Processes that reconcile internal records with onchain balances.

Suppose user A sells 0.1 BTC to user B on the same exchange. That trade does not necessarily create a Bitcoin transaction.

The trading engine executes the order, and the internal ledger changes the users’ balances. A simplified representation looks like this:

user_a.BTC -= 0.1
user_a.EUR += 5_800

user_b.EUR -= 5_800
user_b.BTC += 0.1

These amounts illustrate bookkeeping changes. They are not a current market quote.

This is also balance-change pseudocode, not a complete production double-entry ledger. A real implementation must account for fees, holds, and the appropriate accounting entries.

The important point is that the blockchain may see no change at all. The BTC can remain at an address controlled by the exchange while the platform changes its internal records.

For a concrete example, Coinbase’s trading rules describe settlement through debits and credits to customer account balances, rather than requiring a separate blockchain transaction for every matched trade.

An onchain transaction is generally needed when:

  • A customer deposits assets from an external address.
  • A customer withdraws assets to an external address.
  • The exchange consolidates deposits.
  • The exchange moves liquidity between its own wallets.
  • The exchange replenishes a hot wallet from more protected storage.

A Deposit Address Is Not Necessarily a Storage Location

An exchange deposit address often identifies whose account should receive credit. It does not necessarily identify where the assets will remain.

When an exchange assigns you a deposit address, its systems monitor incoming transactions and associate qualifying deposits with your customer account.

After the required confirmation checks, the exchange credits its internal ledger. It may then sweep the assets into a shared wallet.

On some networks, attribution uses a unique address. On others, a shared address is combined with an additional identifier, such as a memo, tag, or destination tag.

That is why I would not model a deposit route as merely:

asset + address

A more reliable minimum model is:

asset + network + address + optional_memo

USDC on Ethereum, USDC on Base, and USDC on another supported chain are different deposit routes, even if the interface displays the same ticker.

For example, Coinbase Prime’s multi-network asset model distinguishes network-specific transaction records from a consolidated trading balance.

The operational lesson is straightforward: a ticker tells you what the interface calls an asset. It does not fully specify how to deliver it.

An Exchange Balance Is Not the Same as an Onchain Balance

An exchange API reports the platform’s account records, not necessarily a dedicated blockchain balance belonging to one customer.

If an API returns 10 ETH for your exchange account, that does not mean a blockchain address containing exactly 10 ETH has been set aside exclusively for you.

It means the exchange’s internal system recognizes that balance for your account.

The underlying assets may be:

  • Distributed across several wallets.
  • Pooled with assets attributed to other customers.
  • Partly held in a hot wallet.
  • Partly held in colder storage.
  • Moving through a consolidation process.
  • Subject to internal reservations for open orders or withdrawals.

Even within the exchange’s own records, total balance and immediately available balance can differ. That distinction is visible in Coinbase Exchange’s account fields for balance, holds, and available funds.

This is why an exchange API and a blockchain node answer different questions.

The exchange API answers:

How much does the platform’s internal system recognize for this account?

A blockchain node answers:

What state currently exists on this network, and under what conditions can it change?

Neither answer, by itself, completes the financial reconciliation of your application.

In banking, I would never assume that a customer statement, a general ledger, and a settlement-system record were interchangeable. I do not make that assumption in crypto either.

What Does a Crypto Wallet Actually Do?

A crypto wallet is a collection of functions for viewing blockchain activity and, when authorized, preparing or approving transactions.

The word “wallet” is used for several different things:

  1. An application that displays balances and transaction history.
  2. A keystore that stores an encrypted private key.
  3. A signer that produces transaction signatures.
  4. A system that derives multiple accounts from shared seed material.
  5. A smart account with programmable authorization rules.
  6. A service combining custody, a policy engine, and an API.

A conventional full-service, key-based wallet performs at least four jobs:

  • Generates or imports a key.
  • Derives an address from the relevant public key or spending structure.
  • Reads balances and transaction information from the network.
  • Builds and signs transactions.

Those jobs do not have to run in the same process or on the same machine.

The separation between networking and signing is well established. The Bitcoin Developer Guide’s explanation of wallet components and signing-only wallets describes how one component can monitor the network and prepare transactions while another handles the keys.

For a server-side payment system, I would usually want that separation to be explicit.

A more protected transaction workflow can look like this:

  1. Application service: Determines that a specific payout is owed.
  2. Transaction builder: Converts the approved business request into a proposed transaction.
  3. Policy engine: Checks the network, amount, destination, and other restrictions.
  4. Signer or multisig: Authorizes the transaction only when the required conditions are satisfied.
  5. Broadcaster: Submits the signed transaction to the network.
  6. Blockchain node: Processes and exposes transaction and chain information.
  7. Indexer: Organizes the relevant onchain activity for application use.
  8. Reconciliation worker: Compares observed results with internal records and returns the updated financial state to the application.

The application service knows that a customer or contractor needs to be paid. The signer does not need to know anything about that person’s browser session.

It receives a proposed transaction and signs only if the transaction satisfies its rules.

That separation reduces the chance that a compromised web server automatically becomes a compromised treasury.

Private Key vs. Seed Phrase: What Is the Difference?

A private key authorizes activity for a conventional key-controlled account. A seed phrase can provide the starting material for many keys.

A seed phrase is a human-readable representation used in a deterministic wallet-generation process.

The BIP-39 specification for mnemonic phrases and seed generation explains how entropy becomes a sequence of words and how that mnemonic, together with an optional passphrase, produces a binary seed. That seed can then be used with a hierarchical deterministic wallet.

The practical benefit is significant: one properly maintained backup can restore a large collection of derived accounts and addresses.

The security consequence is just as significant. Compromising the seed material can compromise an entire account tree, not just one address.

If a wallet uses an additional BIP-39 passphrase, recovery also depends on that passphrase. Compatible derivation paths and wallet formats matter as well. “I wrote down some words” is not, by itself, a tested recovery plan.

A seed phrase is not:

  • An application password.
  • A value to place in a public-facing web server’s .env file.
  • A recovery code that can simply be reset.
  • Data to send to a telemetry system.
  • A string to print in a continuous integration log.

If you lose an exchange password, the operator may offer an identity-based account-recovery process.

If you lose the only copy of your self-custody seed material and have no other recovery mechanism, there is no central administrator who can issue replacement access to those existing assets.

That is the trade-off I want readers to understand. Self-custody replaces an operator’s recovery process with the recovery system you actually designed and maintained.

Why the Signature Is the Real Boundary of Control

In a self-custody integration, the application should request authorization from the wallet, not ask the user to hand over a private key.

The application prepares a request. The wallet decides how to present it, whether to request user approval, and whether to perform the operation.

On Ethereum Virtual Machine, or EVM, networks, browser wallets commonly expose a provider interface based on the EIP-1193 specification for the Ethereum JavaScript Provider API.

A minimal integration can look like this:

interface Eip1193Provider {
    request(args: {
        method: string;
        params?: readonly unknown[] | object;
    }): Promise<unknown>;

    on(event: string, listener: (...args: unknown[]) => void): void;
}

async function connectWallet(provider: Eip1193Provider) {
    const accounts = (await provider.request({
        method: "eth_requestAccounts",
    })) as string[];

    const chainId = (await provider.request({
        method: "eth_chainId",
    })) as string;

    if (!accounts[0]) {
        throw new Error("The wallet did not return an active account");
    }

    return {
        address: accounts[0],
        chainId,
    };
}

The application receives a public address and a chain identifier. It does not receive the private key.

This helper is intentionally minimal. The TypeScript assertions do not validate runtime responses, and production code also needs appropriate error handling and network checks.

A production integration must respond to events such as accountsChanged and chainChanged.

It is not safe to assume that the account selected at the start of a session will remain selected throughout checkout or signing.

Connecting a wallet also does not automatically prove account control to your backend. It establishes that a provider exposed an address to the application.

For authentication, I would normally require a fresh, purpose-specific challenge that the user signs, with a server-validated nonce and a limited validity period.

A Safe Local Experiment: Sign a Message Without Moving Funds

You can demonstrate signature verification locally without broadcasting a transaction or using real funds.

The following TypeScript example uses an ephemeral wallet and the APIs documented in the ethers v6 wallet and signing library:

import { randomUUID } from "node:crypto";
import { Wallet, verifyMessage } from "ethers";

const wallet = Wallet.createRandom();
const challenge = `wallet-demo:${randomUUID()}`;

const signature = await wallet.signMessage(challenge);
const recoveredAddress = verifyMessage(challenge, signature);

console.log({
    expected: wallet.address,
    recovered: recoveredAddress,
    valid: recoveredAddress === wallet.address,
});

This example assumes an ESM-capable Node.js setup with ethers v6 available. It does not connect to a blockchain provider.

The basic idea is simple: given the original message and a compatible signature, the backend can recover the signing key’s address and compare it with the expected address.

Use throwaway key material for this demonstration. There is no reason to import a production seed or fund this wallet.

For real authentication, the challenge should contain at least:

  • The application’s domain.
  • A one-time nonce.
  • An issuance time.
  • An expiration time.
  • The expected chain or other relevant context.
  • A clear explanation of what the signature is for.

The server must validate those fields and prevent nonce reuse. The Sign-In with Ethereum specification for structured authentication messages provides a standardized framework for this process.

The local example demonstrates ordinary key-based signature recovery. Contract accounts can require a different validation path, such as the ERC-1271 standard for smart contract signature validation.

A login signature must never be treated by your application as blanket approval for arbitrary transactions.

At the same time, “it is only a message signature” is not a sufficient safety explanation. Some structured signatures authorize spending permissions, as demonstrated by the ERC-2612 specification for signed token approvals.

The interface must clearly distinguish between:

  • Signing in.
  • Granting a token-spending allowance.
  • Authorizing a delegated capability.
  • Sending funds.

What matters is the authorization being created, not simply whether the wallet labels the interaction “sign” or “send.”

EOA, Hardware Wallet, Multisig, MPC, and Smart Account Explained

The phrase “we use a wallet” does not describe a security model.

I need to know who can authorize transactions, what they can authorize, how the authorization process can fail, and how the system recovers.

A Plain EOA Controlled by One Key

A conventional single-key EOA can authorize transactions with one private key.

Its advantages include:

  • A straightforward security model.
  • Broad compatibility.
  • Relatively low integration overhead.
  • Easy local testing.

Its disadvantages include:

  • One key can become a single point of failure.
  • A compromised key can directly authorize spending.
  • There is no native business-approval workflow in the basic single-key model.
  • Replacing the controlling key generally means moving to a different account address.

An EOA whose private key sits in an environment variable may technically be self-custodial. That does not make it a sound custody architecture.

There is also an important modern qualification. The EIP-7702 specification for delegating execution code from an EOA allows supporting networks to add smart-account-style behavior to an existing EOA address.

That does not make the original key irrelevant. I still want the threat model to explain what that key can authorize and how delegation can be changed.

In other words, “EOA” and “smart features” are no longer mutually exclusive labels. The actual authorization path matters more than the category name.

Hardware Wallet

A hardware wallet is designed to keep the private key inside a dedicated device while producing signatures without exposing that key to the host computer.

That reduces the risk of key extraction from the application computer.

It does not solve every problem.

A user can still approve a malicious transaction, misunderstand a spending allowance, or rely on an incomplete transaction display.

Security depends partly on what the device can clearly show before the user approves. Protecting the key and understanding the transaction are separate requirements.

Multisig Wallet

A multisig requires a configured threshold of authorizations, such as two signatures from three owners.

On EVM networks, multisig functionality is often implemented through a smart account. The account stores its owners and threshold, checks the required authorizations, and then executes the requested operation.

The Safe Smart Account documentation on owners, thresholds, and signature verification provides a concrete implementation of this model.

Advantages include:

  • A two-of-three configuration avoids dependence on one irreplaceable signer.
  • Technical control and business approval can be separated.
  • Losing one signer’s device need not mean losing the treasury, provided a valid quorum remains.
  • The approval process can reflect organizational responsibilities.

Disadvantages include:

  • More operational steps.
  • More onchain execution overhead than a simple EOA native-asset transfer.
  • The need to maintain owners and thresholds carefully.
  • The possibility that a bad configuration change locks out legitimate access.

Approval collection and execution are also separate stages. The Safe workflow for creating transactions and collecting signatures offchain illustrates how a proposal can gather approvals before an onchain transaction is executed.

A fully approved proposal is not yet a settled payment.

MPC Wallet

Multi-party computation, or MPC, can distribute the signing process across multiple participants so that a complete private key does not need to exist in one place.

MPC is not another name for multisig.

In a conventional multisig arrangement, the network or smart contract validates multiple authorizations.

In a threshold-signature MPC system, several participants can cooperate to produce what the network sees as one ordinary signature.

The distinction is described in Fireblocks’ explanation of MPC and multisignature custody architectures.

It affects:

  • Protocol compatibility.
  • Onchain cost.
  • The audit trail.
  • Recovery procedures.
  • Dependence on the MPC infrastructure.

I also separate the signing technology from the custody classification. An MPC product is not automatically self-custodial simply because it uses multiple shares. The relevant questions are who controls the required quorum and whether access or recovery depends on another operator.

Smart Account

A smart account is an account whose authorization rules are enforced through code.

Depending on its implementation and supporting infrastructure, it may provide:

  • Spending limits over defined periods.
  • Session keys.
  • Destination allowlists.
  • Recovery modules.
  • Batched operations.
  • Sponsored network fees.
  • Separate roles for proposing and executing transactions.

Sponsored fees do not mean the network stops charging. They mean another party or mechanism handles payment under a defined arrangement.

Programmability brings flexibility, but it also adds attack surface.

A vulnerable module, permissive policy, or unsafe upgrade mechanism can be as damaging as a compromised private key.

My question is never just, “Does this wallet have more features?” It is, “Which features can move money, and who can change their rules?”

Real-World Use Case: Paying Contractors in Stablecoins

Stablecoin payouts are a treasury and settlement workflow, not merely a series of token transfers.

Imagine a SaaS platform that pays several hundred contractors each week.

The platform receives fiat revenue, converts some of it into a stablecoin, calculates what each contractor is owed, and sends funds to their chosen addresses.

The requirement sounds simple until I start asking operational questions:

  • Who chooses the network?
  • Who verifies that the destination is appropriate for that network?
  • What happens when the same token symbol appears on several chains?
  • Who supplies the native token needed for gas?
  • What happens if a transaction remains pending?
  • When is a payout considered final?
  • How do we prevent a duplicate payment after a timeout?
  • How do we preserve evidence of the destination information the contractor submitted?
  • How are refunds handled?
  • Who approves a large payout?
  • What happens when the exchange API works but withdrawals are temporarily suspended?

Address validation deserves special care. On EVM networks, the same address format can be valid on multiple chains. A syntactically valid address does not prove that the recipient expects payment on the selected network.

The recipient’s network choice, token route, and any required memo must be captured explicitly.

Likewise, a refund is generally a new authorized transfer, not an instruction that reverses a settled blockchain payment.

There are three reasonable architectural models for this kind of system.

Model 1: Send Payouts Through an Exchange

Exchange-based payouts delegate blockchain signing and much of the withdrawal infrastructure to the operator.

The application holds operational funds in a business exchange account and requests withdrawals through an API.

The workflow looks like this:

  1. Payroll service: Calculates the amount owed.
  2. Internal ledger: Records the obligation and reserves the required funds.
  3. Approval and risk checks: Confirm that the payout is permitted.
  4. Exchange API: Receives the withdrawal request.
  5. Exchange operator: Performs its checks and processes the withdrawal.
  6. Blockchain: Records the resulting transaction if it is broadcast and included.
  7. Contractor’s address: Receives the intended asset on the selected network.

What the Exchange Handles

Depending on the service, the exchange takes responsibility for:

  • Managing private keys.
  • Maintaining blockchain node infrastructure.
  • Selecting and replenishing hot wallets.
  • Broadcasting transactions.
  • Handling part of the fee-management process.
  • Maintaining its supported network integrations.

This can give a small team a relatively fast path to a production workflow.

Where Exchange Dependence Remains

The application depends on:

  • API availability.
  • The business account’s status.
  • Withdrawal limits.
  • The operator’s risk and compliance decisions.
  • Availability of the specific withdrawal network.
  • Internal withdrawal-processing times.
  • Changes to APIs and authentication requirements.

An API response saying the request was accepted is not the same as an onchain confirmation.

The exchange may first create an internal transfer record, then perform further checks, and only later broadcast a blockchain transaction.

For that reason, accepted or pending must not automatically become paid in your application.

I treat the provider’s response as evidence of one processing stage, not evidence that the entire financial obligation has been discharged.

Model 2: Operate a Self-Custody Hot Wallet

A self-custody hot wallet gives the application team direct control over transaction submission while making that team responsible for signing security and operations.

The backend controls an operational wallet and submits transactions itself.

A typical workflow is:

  1. Payroll service: Creates an approved payout intent.
  2. Queue: Schedules and coordinates processing.
  3. Policy engine: Checks the payout against defined restrictions.
  4. Signer: Authorizes the permitted transaction.
  5. RPC node: Receives the signed transaction.
  6. Blockchain: Includes and executes the transaction.
  7. Indexer: Detects and interprets the relevant activity.
  8. Reconciliation process: Matches the result to the original obligation.

What the Team Controls

The team can control:

  • When transactions are submitted.
  • The fee strategy.
  • Batching.
  • Nonce management.
  • Replacement of pending transactions.
  • RPC and indexing providers.
  • The policy used to recognize settlement.

What the Team Must Operate

The team becomes responsible for:

  • Protecting keys and signing authority.
  • Keeping the signer available.
  • Replenishing the native token used for gas.
  • Coordinating concurrent nonce allocation.
  • Monitoring pending transactions and mempool behavior where available.
  • Maintaining recovery procedures.
  • Responding to security incidents.
  • Verifying token contracts and network configuration.

The most dangerous implementation mistake is connecting a public API server directly to an unrestricted treasury key.

If an attacker gains remote code execution on the application server, the architecture should still prevent that attacker from transferring the entire treasury to an arbitrary address.

That requires a genuine policy boundary between the application and the signer.

Moving the same unrestricted key into another process does not create much protection if the compromised application can ask that process to sign anything.

Model 3: Use a Hybrid Treasury Architecture

A hybrid treasury separates conversion, routine payouts, and reserves so that one operational failure does not expose every asset.

This model typically uses:

  • An exchange for conversion and fiat flows.
  • A relatively small hot wallet for automated payouts.
  • A multisig or another protected account for reserves.
  • Periodic replenishment of the operational wallet.
  • Limits per transaction and over defined periods.

The funding and replenishment loop can work like this:

  1. Fiat revenue arrives in the business’s payment and banking systems.
  2. The exchange converts an approved portion into the required crypto asset.
  3. The operational hot wallet receives funding for routine payouts.
  4. The protected reserve can also replenish the hot wallet through an independently authorized process.
  5. The payout service sends approved payments from the operational wallet.
  6. Recipients receive funds at their selected addresses and networks.
  7. Monitoring detects a low operational balance and raises an alert.
  8. The treasury system creates a replenishment proposal.
  9. Reserve signers review and authorize the top-up, restarting the funding cycle.

The objective is to limit the damage.

If the hot wallet is compromised, its operational balance is exposed rather than the entire reserve. That protection only works if the attacker cannot also trigger unrestricted replenishments from the reserve.

If the exchange suspends withdrawals, funds already held in the operational wallet may still cover some obligations.

The cost is more complex reconciliation across at least three systems:

  • The internal business ledger.
  • The exchange account.
  • The onchain wallets.

For a payment platform with fiat revenue, automated payouts, and a meaningful reserve, I consider this a more realistic starting point than either “everything stays on the exchange” or “one private key lives on the server.”

How to Integrate an Exchange API Reliably

A reliable exchange integration models withdrawals as a financial state machine, not as a single HTTP request.

Individual APIs differ, but the core workflow follows similar principles.

1. Model the Asset and Network Separately

Do not use the ticker as the complete asset identifier.

A simplified route model might look like this:

type AssetRoute = {
    asset: "USDC";
    network: "ethereum" | "base";
    contractAddress?: string;
    decimals: number;
};

This is a sketch, not a complete token registry.

For an actual onchain ERC-20 transfer, the transaction builder must resolve a verified token contract on the intended chain, even if an upstream provider’s metadata omits that field.

In a production system, I would also want the precise chain ID and a distinction between native and bridged token variants where relevant.

These values should not be casually hardcoded and forgotten. Configuration needs versioning and verification against provider documentation and network data.

2. Separate API Permissions

A credential used to read balances should not automatically have permission to withdraw funds.

Where the provider supports granular access, create separate identities for:

  • Read-only reporting.
  • Trading.
  • Withdrawal creation.
  • Address administration.
  • Reconciliation workers.

An IP allowlist is not a substitute for protecting credentials. It can, however, reduce the attack surface.

The principle is familiar from banking operations: the system that prepares a report should not automatically have the authority to move money.

3. Use an Internal Idempotency Key

A network timeout does not tell you whether the exchange processed the request.

Consider this sequence:

  1. The application submits a withdrawal request.
  2. The exchange accepts it.
  3. The connection drops before the HTTP response reaches the application.
  4. The worker retries the request.
  5. Two withdrawals are created.

If the API supports an idempotency key or a client reference with documented duplicate-prevention behavior, use it.

If it does not, maintain an internal state machine and check the provider’s records before retrying.

An internal identifier alone cannot force a remote provider to deduplicate requests. If the outcome cannot be established safely, stop automated retries and route the operation to manual review.

Uncertainty is a valid operational state. Pretending that it is failure is how duplicate payments happen.

4. Do Not Map HTTP Status Directly to Financial Status

200 OK means the API call was handled according to that endpoint’s semantics. It does not necessarily mean the recipient received funds.

A minimum payout status model might look like this:

type PayoutStatus =
    | "created"
    | "awaiting_approval"
    | "submitting"
    | "provider_accepted"
    | "broadcast"
    | "confirming"
    | "settled"
    | "rejected"
    | "failed"
    | "manual_review";

Even failed requires care.

If you do not know whether the provider accepted the withdrawal, the accurate state is manual_review or another explicitly uncertain category, not a status that encourages an immediate new payment.

I want financial status names to describe what the system knows, not what a developer hopes happened.

5. Reconcile Provider Transfers With Onchain Transactions

Keep separate records for:

  • Your internal payout ID.
  • The provider’s transfer ID.
  • The transaction hash.
  • The asset.
  • The network.
  • The destination address.
  • Any memo or tag.
  • The gross amount.
  • The provider fee.
  • The network fee, when available.
  • The creation time.
  • The broadcast time.
  • The settlement time.

The Coinbase Exchange API’s account transfer records illustrate why provider-side transfer objects belong in their own part of the model.

One provider operation does not always correspond one-to-one with an onchain transaction.

An exchange may batch several withdrawals into one transaction. A payout may also involve multiple transaction attempts or replacement hashes.

That is why provider_transfer_id and transaction_hash are different identifiers with different meanings.

For batched transfers, reconciliation may also need the specific output or event associated with your payout. A shared transaction hash does not, by itself, identify which recipient received which amount.

How to Build a Self-Custody Payout Workflow

A reliable self-custody workflow separates business intent, transaction construction, authorization, broadcasting, and settlement.

The application assumes more responsibility than it does with an exchange API, but it gains more direct control over each stage.

1. Create an Intent Before Creating a Transaction

The business service should first create a payment intent:

Pay 250 USDC to user 1842 on Base.

A separate component then translates that intent into a specific blockchain transaction.

This allows the system to verify:

  • The underlying business obligation.
  • The recipient’s address.
  • The selected network.
  • The token contract.
  • Applicable limits.
  • Duplicate requests.
  • Compliance rules.
  • The wallet’s current available funds.

The intent describes what the business owes. The transaction describes one technical attempt to fulfill it.

Keeping those concepts separate makes retries and investigations much easier.

2. Reserve Funds in the Internal Ledger

Do not wait for the blockchain to prevent application-level overcommitment.

When a payout enters processing, move the amount from available to reserved.

After settlement, it moves to paid. If the operation is safely canceled, the reservation can be released.

A blockchain balance does not know which portion of the funds your application has already promised to other recipients.

I would apply the same discipline here that I would expect in any payment ledger: available funds must account for obligations already in progress.

3. Build the Transaction Deterministically

The transaction builder receives an approved intent and resolves the concrete fields needed for execution:

  • The destination address.
  • The amount in atomic units.
  • The calldata for a token transfer.
  • The chain ID.
  • The nonce.
  • The fee parameters.
  • An expiration or deadline where the transaction or contract format supports one.

The fields and lifecycle are covered in Ethereum’s documentation on transaction structure, signing, and broadcasting.

Once the approved inputs, nonce, and fee parameters are fixed, the result should be reproducible and auditable.

A plain Ethereum transfer does not have a general-purpose expiration field. An application deadline does not automatically prevent an already signed transaction from executing later.

Financial amounts should not be stored in JavaScript’s number type. Use integer amounts in the smallest unit or a well-tested decimal library.

For transaction construction, I want exact amounts, not floating-point approximations that happen to look correct in the interface.

4. Enforce Policy Before Signing

The signer, or an independently enforced service in front of it, should check:

  • Whether the chain is permitted.
  • Whether the token contract is permitted.
  • The maximum transaction amount.
  • The cumulative daily limit.
  • The destination address.
  • The number of payouts in a batch.
  • The expected calldata selector and decoded operation.
  • The link to an approved payout intent.

These checks must apply to what the transaction actually does.

A limit on a displayed amount is not useful if the signer accepts different calldata that transfers something else.

A signer that blindly signs any payload is not a security boundary. It is a remotely accessible private key.

5. Treat Broadcast and Settlement as Different Events

After broadcasting, a transaction may be:

  • Waiting in a mempool.
  • Temporarily invisible to some nodes.
  • Replaced by another transaction using the same nonce.
  • Included in a block.
  • Included and later removed by a chain reorganization.
  • Included with failed smart contract execution.

Those stages should remain distinct:

signed -> broadcast -> included -> confirmed -> settled

Here, settled is your application’s business classification based on successful execution and a documented, network-specific confirmation or finality policy.

On an EVM network, a receipt indicating failed execution means the transaction was included, but the requested operation did not succeed. Gas can still be charged.

A successful outer transaction must also be matched to the expected transfer, especially when smart accounts or batches are involved.

Persist the signed transaction, its selected nonce, and its hash before broadcasting. If the response times out, first investigate that known transaction rather than immediately creating a new payout.

Rebroadcasting the same signed transaction is different from creating a fresh payment. A fee-bumped replacement must remain linked to the same business intent.

6. Run Reconciliation Independently of the Request Handler

The HTTP handler that starts a payout should not wait for the network’s final settlement conditions.

A background worker should periodically:

  • Retrieve the transaction receipt.
  • Check confirmations or the applicable finality state.
  • Decode relevant events.
  • Compare the expected transfer with the observed transfer.
  • Detect replacement, missing, or reorganized transactions.
  • Update the internal ledger.
  • Raise an alert when the result cannot be explained automatically.

The Ethereum JSON-RPC methods for transaction receipts provide execution and inclusion information, but the application still has to interpret that information correctly.

A receipt is evidence. Reconciliation is the process that connects that evidence to the business obligation.

Why EVM Nonces and Bitcoin UTXOs Require Different Designs

EVM accounts and Bitcoin wallets coordinate concurrent spending in fundamentally different ways.

An abstraction called sendPayment() can hide some details. It cannot make those details disappear.

EVM Account Model: Coordinate Nonces

Ordinary outbound EOA transactions use sequential nonces.

Two workers submitting transactions from the same account must not accidentally allocate the same nonce to unrelated payouts.

If ten workers ask a node for the current nonce at the same time, they can all receive the same answer.

You need a coordinated nonce manager, serialized submission, or another reliable allocation mechanism. The account nonce behavior is described in Ethereum’s account state documentation.

A transaction stuck at a lower nonce can also hold up later transactions from that account.

This makes nonce allocation an operational dependency, not a small implementation detail.

Bitcoin UTXO Model: Reserve Inputs

A Bitcoin transaction selects particular unspent outputs as its inputs.

Concurrency management means preventing two workers from trying to spend the same UTXO for unrelated payouts.

The Bitcoin Developer Guide’s explanation of transaction inputs, outputs, and spending conditions describes this model.

A Bitcoin wallet must handle:

  • Coin selection.
  • Input reservation.
  • Change outputs.
  • Transaction-size estimation.
  • Fee rates.
  • Tracking unconfirmed change.

Bitcoin Core also exposes mechanisms such as locking selected unspent outputs to exclude them from automatic coin selection, illustrating how input coordination becomes part of wallet operations.

As a result, “send this amount” is not the same backend operation on every blockchain.

An abstraction that hides the network model completely usually starts leaking details as soon as you introduce batching, fee bumping, and concurrent payouts.

Crypto Exchange Fees vs. Self-Custody Costs

The true cost of a custody model includes transaction fees, infrastructure, operational labor, and the capital tied up in the process.

There is no single fee number that gives an honest comparison.

What an Exchange-Based Workflow Can Cost

The total cost may include:

  • Trading fees.
  • The bid-ask spread or conversion spread.
  • Withdrawal fees.
  • Network-specific charges.
  • Minimum withdrawal requirements.
  • Failed requests and manual-processing work.
  • Waiting time and unavailable working capital.
  • Integration development and maintenance.
  • Compliance and operational overhead.

A withdrawal fee does not necessarily equal the network fee of the specific transaction carrying your withdrawal.

An exchange may charge a fixed amount, use dynamic pricing, or batch withdrawals. Be careful not to count a network charge twice if it is already embedded in another fee.

The metric I find more useful is total provider-related cost divided by the number of successfully completed payouts.

That calculation should include operational work, not just the fee field returned by an API.

A provider that looks inexpensive per withdrawal can become expensive if a significant share of transactions requires investigation.

What a Self-Custody Workflow Can Cost

Onchain costs depend on the network and the type of operation.

For a conventional EVM execution fee, multiply gas used by the effective gas price. The relationship is explained in Ethereum’s technical overview of gas and transaction fees.

A token transfer is a smart contract call and generally requires more gas than a simple native-asset transfer between ordinary accounts.

Smart accounts, multisig execution, and batches have different execution-cost profiles.

The execution-fee formula also does not capture every possible network-specific component. For example, Base’s network-fee documentation distinguishes L2 execution fees from L1 security fees.

On Bitcoin, a target fee is commonly estimated by multiplying transaction virtual size in vbytes by the fee rate in satoshis per vbyte.

The actual transaction fee is the difference between the total input value and the total output value.

The number and type of inputs and outputs therefore affect cost. A wallet containing many small UTXOs can face expensive spending or consolidation even when its total balance is substantial.

Beyond network fees, self-custody introduces costs for:

  • RPC and indexing infrastructure.
  • Hardware security modules, MPC infrastructure, or signer services.
  • Hardware devices.
  • Audits.
  • Monitoring.
  • Incident-response processes.
  • Protected backup and recovery systems.
  • Reconciliation development and maintenance.

Self-custody can remove some dependencies on an exchange. It does not make financial infrastructure free.

The Biggest Crypto Custody Implementation Mistakes

Most serious implementation failures come from unclear authorization boundaries, incomplete financial states, and unsafe assumptions about retries.

These are the mistakes I would look for first in a design review.

Using One completed Status

A financial operation passes through multiple stages.

One completed boolean discards the information needed to decide whether to retry, wait, reconcile, or investigate.

“Not completed” might mean not submitted, accepted by a provider, pending onchain, or included with failed execution. Those are not interchangeable conditions.

Using the Ticker as the Entire Asset Identity

USDC without a network and verified token identity is not a sufficiently precise instruction.

A system must know which asset, on which chain, through which supported route, it is handling.

Keeping the Private Key on the Public API Server

In that design, an application compromise can immediately become a funds compromise.

The signer needs a separately enforced authorization policy, not merely a different function name in the same codebase.

Retrying Without Idempotency Protection

A timeout is not proof of failure.

Blind retries can create duplicate withdrawals or duplicate payout attempts.

The system must preserve the original operation and investigate its outcome before authorizing another payment.

Relying Only on Webhooks

A webhook can arrive late, arrive twice, or never arrive.

Verify webhook authenticity, deduplicate events, and maintain periodic polling and reconciliation as a backstop.

Relying Only on Polling

Polling alone can increase latency and infrastructure load.

A strong design uses events for responsiveness and periodic reconciliation for correctness.

I do not view these as competing approaches. They solve different parts of the problem.

Crediting Deposits as Soon as a Transaction Is Seen

A transaction that has been observed is not necessarily confirmed, successfully executed, or sufficiently final for your risk policy.

Deposit-crediting rules should depend on the network, the amount, and the risk the business is prepared to accept.

Logging Complete Request Payloads

Request payloads can contain API authentication material, signatures, addresses, memos, and sensitive financial metadata.

Signer logs deserve particular care because the payloads they process can carry spending authority.

Keep the audit evidence you need, but redact secrets and control access to financial and signing records.

Having No Compromise Procedure

“The key is in an HSM” is not a complete incident-response plan.

The team should know in advance:

  • How payouts are stopped.
  • How permissions are revoked.
  • How operational wallets are replaced.
  • Who has authority to make those decisions.
  • How dependent services are notified.
  • How investigators determine what was signed.

Protection, detection, containment, and recovery are different jobs. A custody architecture needs all of them.

Define a Source of Truth for Every Financial Question

A reliable system assigns authority by data type instead of expecting one database or API to answer every question.

This is one of the most useful habits I brought from traditional finance.

DataPrimary source of truth
Amount owed to a customer or contractorInternal business ledger
Exchange account balanceExchange API and account reports
Onchain stateBlockchain node or appropriately verified indexer
Business approval statusInternal approval system
Broadcast trackingNetwork and transaction hash, supported by submission acknowledgment or observed network activity
Execution resultReceipt, execution status, and relevant events
Authority to move fundsPrivate key, threshold-signing arrangement, or smart account authorization policy

There is an important qualification in the broadcast row: a transaction hash is an identifier, not independent proof that a transaction reached the network.

A hash can be available for a signed transaction before broadcast, as reflected in the ethers transaction object’s distinction between signed transactions and their hashes.

Errors occur when one system is used to answer a question it was not designed to answer.

The blockchain does not know about an unpaid invoice.

The internal ledger cannot prove that a transaction was included in a block.

An exchange API cannot prove that the intended recipient controls a destination address.

Each system contributes evidence. Reconciliation connects those pieces.

How to Choose Between an Exchange and Self-Custody

The right custody model depends on your operating requirements and the controls your team can sustain.

I would not choose on ideology alone.

An exchange-based model makes sense when the dominant requirements are:

  • Frequent fiat-to-crypto or crypto-to-fiat conversion.
  • A small team and a short integration timeline.
  • Limited key-management experience.
  • A need for provider-generated business reports.
  • An acceptable level of dependence on the operator.

Self-custody makes sense when the dominant requirements are:

  • Direct control over payout timing and authorization rules.
  • Interaction with smart contracts.
  • Batching or programmable treasury operations.
  • Reduced dependence on a single provider.
  • The ability to invest in signing, monitoring, and recovery infrastructure.

A multisig or another well-designed smart account makes sense when:

  • The assets belong to an organization.
  • One person should not control them alone.
  • Business approval rules must be enforced.
  • Decisions require an audit trail.
  • The temporary unavailability of one signer must not be fatal.

A hybrid model is often the strongest fit when the business simultaneously has fiat flows, automated payouts, and a larger reserve.

The key phrase is “well-designed.” More components do not automatically create better controls.

Practical Decision Matrix: Exchange, Hot Wallet, or Smart Account?

This matrix compares common operating patterns, not universal guarantees.

An exchange can offer advanced custody controls, and a smart account can be configured badly. The actual implementation still needs review.

QuestionExchangeSelf-custody hot walletMultisig or policy-based smart account
Who controls signing authority?The operatorThe application team or its signing infrastructureOwner keys, other authenticators, or configured modules
Initial integration speedUsually fastestModerateOften slower initially
AutomationConstrained by provider APIs and rulesHighHigh, with policy and approval requirements
Withdrawal-freeze exposureSubject to the operator’s controlsNo exchange-level withdrawal gate; asset restrictions can remainNo exchange-level withdrawal gate; asset restrictions can remain
Private-key theft exposureOperationally handled by the operator, but customers remain exposed to custody failuresDirect exposure unless additional controls limit damageDistributed or policy-limited, depending on configuration
Account recoveryOperator’s recovery procedureYour backup and recovery designOwner or authenticator changes where the policy permits
Protocol interactionLimited to supported servicesDirectDirect and programmable
Onchain fee controlPartialDirectDirect or delegated to a configured executor
Operational complexityLower for the application teamHighHigh
Suitability for reservesDepends on the trust and custody modelWeak without additional safeguardsOften a stronger fit when properly configured

One qualification matters especially for stablecoins: self-custody does not remove token-issuer controls.

For example, Circle’s USDC terms describe its ability to block onchain transfers involving specified addresses. That is different from an exchange suspending your withdrawal, but it still affects whether a token can move.

Controlling the wallet’s keys does not give you control over every rule in the asset’s contract.

A Minimal Test That Demonstrates the Difference

You can test the core distinction between an exchange balance and wallet authorization without risking real funds.

I would build a small workflow that makes the authorization and settlement stages visible:

  1. Generate an ephemeral wallet locally.
  2. Sign a challenge and verify the recovered address.
  3. Connect a browser wallet through an EIP-1193 provider.
  4. Switch the wallet to a supported test network.
  5. Construct a transfer without broadcasting it and inspect its fields.
  6. Approve a test transaction through the wallet, using the signing or sending methods that wallet supports.
  7. Broadcast only to the test network.
  8. Track the transitions through broadcast, included, and confirmed.
  9. Store the business intent separately from the transaction hash.
  10. Simulate a timeout after broadcast and verify that retry handling does not create another payout.

Some browser wallets combine approval, signing, and broadcast through their supported transaction-submission method. Do not assume every wallet exposes a standalone “sign this transaction without sending it” operation.

The successful transaction is not the most revealing part of the exercise.

You learn more by deliberately testing uncertain and unsuccessful states:

  • An RPC timeout immediately after broadcast.
  • A change to the wallet’s active account.
  • An incorrect chain ID.
  • Insufficient native tokens for gas.
  • Fee parameters too low for timely inclusion.
  • A duplicated webhook.
  • A provider transfer that has no transaction hash yet.
  • A contract call included in a block with failed execution.

These are the situations where the difference between a demo and a financial system becomes obvious.

A demo shows that money can move. A financial system must also know what happened when the answer is incomplete.

The Real Difference Is Authority, Not the Interface

An exchange and a wallet can display the same balance and the same Send button while performing entirely different operations.

On an exchange, you send an instruction to an operator.

The operator checks the account, its internal limits, and its own rules. If the withdrawal proceeds, it authorizes the relevant onchain transaction through its custody infrastructure.

With a self-custody wallet, authorization occurs under the user’s control or an organization’s defined policy.

The network validates the applicable signatures and execution rules. It does not need the person’s customer record from a business database.

That is why I translate “Where is your crypto?” into a more useful question:

Who can currently produce valid authorization to move these assets, under which rules, and without whose permission?

I also include existing token allowances and delegated modules in that review. Previously granted authority can matter just as much as the next signature.

If the answer is “the exchange,” you may have a simpler integration and a substantial operational dependency.

If the answer is “one private key on a server,” you have technical control and highly concentrated risk.

If the answer is “an isolated signer with enforced limits” or “a multisig with a tested recovery process,” you are building a custody architecture rather than merely connecting a wallet.

A good system does not choose between an exchange and a wallet as a matter of identity.

It chooses a boundary of trust, automation, and responsibility that the team can realistically operate, monitor, and recover.


Sponsored Partner: Volet.com

Looking for a simple, everyday way to buy, hold, and sell cryptocurrencies alongside your traditional currencies and payment cards?