Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

For exchanges

You want to list EXFER. You need to:

  1. Generate deposit addresses per user.
  2. Detect deposits as they confirm.
  3. Credit user balances after enough confirmations.
  4. Process withdrawals by signing and broadcasting transactions.
  5. Handle reorgs — sometimes a confirmed deposit gets reorganized out and you must debit.

This page walks through each of those. The JSON-RPC reference has the per-method details.

Generate deposit addresses

Exfer is not BIP-32 HD. Each wallet file = one keypair = one address. For deposit addresses, the simplest pattern is one wallet file per user:

exfer wallet generate --output users/user_42.key --no-encrypt --json
{
    "file":    "users/user_42.key",
    "pubkey":  "fcbd...c69d",
    "address": "8d89...d750"
}

Store the mapping user_id -> address in your database. The 64-hex address is what you display in the user's deposit page.

Why --no-encrypt? Server-side, you can't prompt for a passphrase on every signature operation. Use OS-level encryption (LUKS, FileVault, dm-crypt) on the disk that holds the key files instead.

Higher-volume alternative: derive keys deterministically from an HD seed outside of the CLI and use send_raw_transaction to broadcast. See For wallet developers.

Detect deposits

Recommended pattern: block scan, not per-address polling.

Per-address get_address_utxos polling does not scale — it counts against the UTXO-scan rate limit (30 / min / IP) and wastes work on inactive addresses.

Block scan loop:

H = last_scanned_height   # persisted in your DB

every 5-10 seconds:
    tip = get_block_height()
    for h in (H+1 .. tip):
        block = get_block(height=h)
        for tx_id in block.transactions:
            tx = get_transaction(hash=tx_id)
            decode tx.tx_hex
            for each output:
                if output.address in (issued_deposit_addresses):
                    record (tx_id, output_index, value, h, block.hash)
        H = h
    persist H

Decoding tx_hex requires understanding Exfer's serialization — see Protocol specification §5 and For wallet developers.

If you have a small set of deposit addresses (< 100), per-address polling via get_address_utxos every 30–60 s is also fine.

Credit user balances

After detecting a deposit at height h, wait N confirmations before crediting:

TierConfirmationsWall time
Small deposits30~5 min
Standard deposits60~10 min
Large deposits (> ~$10k equiv)360~1 hr

The "matches coinbase maturity" depth of 360 blocks is a hard ceiling on what a reorg could undo, in practice.

Watch for reorgs

A confirmed transaction can be reorged out and reappear in a different block (or disappear entirely). Defend by re-checking block_hash:

every minute, for each watched deposit:
    cur = get_transaction(hash=tx_id)
    if cur.block_hash != stored.block_hash:
        if cur is missing:
            # the tx is reorged out and not back in the mempool
            debit the user, mark deposit as "reverted"
        else:
            # tx moved to a different block, recount confirmations from new height
            update stored.block_hash, stored.block_height
            require N more confirmations before re-crediting

For high-value flows, consider deferring credit beyond N=360 and running this reorg watch indefinitely.

Process withdrawals

Easiest path: let the CLI build and sign for you, then submit via RPC.

exfer wallet send \
    --wallet  hotwallet.key \
    --to      <USER_WITHDRAWAL_ADDRESS> \
    --amount  "10 EXFER" \
    --fee     "0.001 EXFER" \
    --rpc     http://127.0.0.1:9334 \
    --json

This:

  1. Fetches UTXOs of the hot wallet via get_address_utxos.
  2. Builds an unsigned transaction.
  3. Signs locally. The private key never leaves the machine running this command.
  4. Submits via send_raw_transaction.

For air-gapped cold custody, build + sign on the offline machine using the same binary, then move the resulting hex to an online machine and call send_raw_transaction directly.

Hot / warm / cold split

Standard exchange practice. A rough mapping:

LayerWhat it holdsKey location
Hot walletenough for daily withdrawalsonline, encrypted at rest, automated signing
Warm walletrolling reserve refilling hotonline but isolated, manual signing
Cold storagebulk reservesoffline, multisig 2-of-3 or vault

Periodically sweep deposits to cold; refill hot from warm. Treat deposit address keys as low-privilege — sweep them out promptly.

Health checks

A node is healthy for production service when:

curl -s -X POST http://127.0.0.1:9334 \
    -H 'content-type: application/json' \
    -d '{"jsonrpc":"2.0","method":"get_block_height","params":{},"id":1}'

returns within 1 s and the returned height advances within the expected block cadence (~10 s per block). Alarm if the gap to a peer node grows beyond 5 blocks.

The Live Nodes page on this site shows the same probe result for the community nodes; use that as a quick comparison point.

Fee policy

Exfer fees are per-transaction (not per-byte). No fee auction. There is a consensus-enforced minimum relay fee. 0.001 EXFER (100_000 exfers) clears the mempool reliably under normal conditions.

Don't bid up withdrawal fees aggressively — bias instead toward batched withdrawals (one transaction with N outputs) if you process volume.

See also