For exchanges
You want to list EXFER. You need to:
- Generate deposit addresses per user.
- Detect deposits as they confirm.
- Credit user balances after enough confirmations.
- Process withdrawals by signing and broadcasting transactions.
- 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:
| Tier | Confirmations | Wall time |
|---|---|---|
| Small deposits | 30 | ~5 min |
| Standard deposits | 60 | ~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:
- Fetches UTXOs of the hot wallet via
get_address_utxos. - Builds an unsigned transaction.
- Signs locally. The private key never leaves the machine running this command.
- 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:
| Layer | What it holds | Key location |
|---|---|---|
| Hot wallet | enough for daily withdrawals | online, encrypted at rest, automated signing |
| Warm wallet | rolling reserve refilling hot | online but isolated, manual signing |
| Cold storage | bulk reserves | offline, 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
- JSON-RPC reference — every method called above.
- For wallet developers — byte-level transaction construction and HD-style address derivation.
- For block explorers — full-archive scanning patterns.