For wallet developers
Building a wallet (CLI, mobile, web, hardware integration)? You need to:
- Derive keys — single key per wallet, or HD-style many keys from one seed.
- Read state — fetch UTXOs and confirmations via RPC.
- Build transactions — serialize correctly so the network accepts them.
- Sign — Ed25519 over a canonical message.
- Broadcast — submit via
send_raw_transaction.
This page is a roadmap. The byte-level details live in Protocol specification §5 (Transactions); the RPC contract in JSON-RPC reference.
Key model
An Exfer keypair is Ed25519. The address is SHA-256 of the
canonical pubkey commitment (see Protocol spec
§5).
There is no native HD scheme. You can:
- Use the CLI's encrypted file format directly — one keypair per file. Simple but doesn't compose well for many addresses.
- Roll your own deterministic derivation — apply BIP-32 or your preferred scheme to derive Ed25519 keys from a seed, then compute the address-hash yourself. This is the right approach for a multi-account wallet.
If you derive your own keys, the only thing you have to match is the
address derivation: address = SHA-256(canonical_pubkey_commitment).
The CLI's wallet generate is a reference implementation.
Read state
For a given address, the RPC surface you'll touch:
| Method | Purpose |
|---|---|
get_balance | Cheap "any value here?" check |
get_address_utxos | Itemized spendable outputs (for building txs) |
get_transaction | Status of a specific tx |
get_block_height | Compute confirmation depth |
Confirmation count for a UTXO returned by get_address_utxos:
tip_height - utxo.height + 1.
Coinbase maturity: skip UTXOs with is_coinbase: true if
tip_height - utxo.height < 360.
Build a transaction
The CLI does this for you when you call exfer wallet send. For a
native wallet you'll reimplement the same logic:
- Select inputs. Walk UTXOs in some deterministic order (e.g.
largest-first to minimize change tx size, or oldest-first to age
coins). Accumulate until
total_input >= amount + fee. Skip dust (< 200 exfers) and immature coinbase. - Build outputs.
- One output to recipient (
amountexfers, locked by recipient's address script). - One change output back to sender (
total_input - amount - feeexfers, locked by sender's address script). Skip if change would be below dust threshold; the surplus becomes additional fee.
- One output to recipient (
- Serialize unsigned tx. Follow the canonical byte layout from Protocol spec §5.
- Compute signing message. Domain-separated hash over the canonical unsigned tx. See Protocol spec §3 for the domain string.
- Sign each input with the corresponding private key, producing Ed25519 signatures.
- Embed signatures in the witness fields, producing the final canonical serialized tx.
- Submit via
send_raw_transaction.
A reference implementation lives in src/wallet/ and src/tx/ in the
upstream exfer source tree.
Script-locked outputs
If your wallet supports anything beyond plain address payments — HTLC, multisig, vault, etc. — you'll be constructing custom locking scripts. Two paths:
- Shell out to the CLI for
exfer script <pattern>-lockand-spend. Easiest to ship, hardest to integrate into a native UI. - Reproduce the script construction natively. See the script
patterns in the upstream source (
src/script/patterns.rsor similar) and the script language definition in Protocol spec §6.
For non-trivial scripts, also use
get_script_utxos to enumerate outputs
locked to your custom script.
Fee selection
There is no fee market auction. Pick a fixed default (e.g.
0.001 EXFER = 100_000 exfers); fall back to bumping the fee only when
the mempool is hot. Replace-by-fee is not yet supported — once you
broadcast, you wait.
Common implementation bugs
- Wrong serialization endianness. Exfer uses a specific byte order for length-prefixed fields. Use the upstream serializer if at all possible.
- Off-by-one in maturity check. Coinbase is mature when
tip_height - utxo.height >= 360, not> 360. Triple-check on a testnet. - Forgetting domain separation for signing. Plain
SHA-256(tx)is not the signing message. The protocol uses domain-separated hashing — see Protocol spec §3. - Using SDK Ed25519 with wrong curve params. Exfer follows the RFC-8032 Ed25519 spec; some libraries default to a non-standard variant. Verify against test vectors.
Test against community nodes
Build your wallet against a community node, but do not ship a production wallet that always uses public RPC:
- It leaks user privacy (their balance queries hit a third party).
- It depends on third-party uptime.
- It's rate-limited.
A reasonable model: ship with a list of default RPCs (community + your own), let the user paste their own node URL, encourage them to run a node for serious use. The Live Nodes page is a fine default-list source.
See also
- JSON-RPC reference
- Protocol specification
- For exchanges — overlapping concerns: HD keys, deposit detection, withdrawals.