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 wallet developers

Building a wallet (CLI, mobile, web, hardware integration)? You need to:

  1. Derive keys — single key per wallet, or HD-style many keys from one seed.
  2. Read state — fetch UTXOs and confirmations via RPC.
  3. Build transactions — serialize correctly so the network accepts them.
  4. Sign — Ed25519 over a canonical message.
  5. 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:

MethodPurpose
get_balanceCheap "any value here?" check
get_address_utxosItemized spendable outputs (for building txs)
get_transactionStatus of a specific tx
get_block_heightCompute 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:

  1. 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.
  2. Build outputs.
    • One output to recipient (amount exfers, locked by recipient's address script).
    • One change output back to sender (total_input - amount - fee exfers, locked by sender's address script). Skip if change would be below dust threshold; the surplus becomes additional fee.
  3. Serialize unsigned tx. Follow the canonical byte layout from Protocol spec §5.
  4. Compute signing message. Domain-separated hash over the canonical unsigned tx. See Protocol spec §3 for the domain string.
  5. Sign each input with the corresponding private key, producing Ed25519 signatures.
  6. Embed signatures in the witness fields, producing the final canonical serialized tx.
  7. 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>-lock and -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.rs or 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