Skip to main content

Wallet: send and receive

A transfer is built jointly by both parties, so sending is a sequence rather than a single call. See interactive transactions.

These examples build on the client from connect and read, and run from the examples directory.

InitTxArgs carries every field

init_send_tx and process_invoice_tx both take an InitTxArgs object, and all twelve of its keys are required when the wallet deserializes it (libwallet/src/api_impl/types.rs:53). A partial object is rejected. init_tx_args in the Python client returns a complete one on every call:

examples/python/tx_args.py
"""Argument builders for the wallet Owner API transfer calls.

`InitTxArgs` and `IssueInvoiceTxArgs` derive Deserialize without serde defaults, so every key is
required on the wire and a partial object is rejected with a missing-field error. These builders
return every key on every call, which is why the examples pass them rather than hand-written dicts.

Imported by send.py, send_manual.py, invoice.py and payment_proof.py, and re-exported from
epic_wallet.py.
"""

from __future__ import annotations

from typing import Any

EPIC = 100_000_000
"""Freemen in one EPIC. Every amount on the API is an integer count of freemen."""


def init_tx_args(
amount: int,
*,
src_acct_name: str | None = None,
minimum_confirmations: int = 3,
max_outputs: int = 500,
num_change_outputs: int = 1,
selection_strategy_is_use_all: bool = False,
message: str | None = None,
target_slate_version: int | None = None,
ttl_blocks: int | None = None,
payment_proof_recipient_address: str | None = None,
estimate_only: bool = False,
send_args: dict[str, Any] | None = None,
) -> dict[str, Any]:
"""Build a complete InitTxArgs object for init_send_tx and process_invoice_tx.

All twelve keys are required when the wallet deserializes the object, so this
returns every one of them on every call. `amount` is in freemen.
"""
return {
"src_acct_name": src_acct_name,
"amount": amount,
"minimum_confirmations": minimum_confirmations,
"max_outputs": max_outputs,
"num_change_outputs": num_change_outputs,
"selection_strategy_is_use_all": selection_strategy_is_use_all,
"message": message,
"target_slate_version": target_slate_version,
"ttl_blocks": ttl_blocks,
"payment_proof_recipient_address": payment_proof_recipient_address,
"estimate_only": estimate_only,
"send_args": send_args,
}


def invoice_tx_args(
amount: int,
*,
dest_acct_name: str | None = None,
message: str | None = None,
target_slate_version: int | None = None,
) -> dict[str, Any]:
"""Build a complete IssueInvoiceTxArgs object. All four keys are required."""
return {
"dest_acct_name": dest_acct_name,
"amount": amount,
"message": message,
"target_slate_version": target_slate_version,
}

IssueInvoiceTxArgs works the same way, with four required keys: dest_acct_name, amount, message and target_slate_version. Field meanings are in the Owner API reference.

Let the wallet drive the exchange

Supply send_args and init_send_tx performs the exchange for you.

epic-wallet send -m epicbox -d 'esYQ...52chars@epicbox.epiccash.com' -c 3 1.5

# Over HTTP to a reachable listener
epic-wallet send -m http -d http://receiver.example:3415 -c 3 1.5

method accepts http, keybase and epicbox (api/src/owner.rs:749). On the http and keybase paths the call blocks until the counterparty answers, then honours finalize and post_tx (api/src/owner.rs:787), so queue it rather than running it on a request thread. On the epicbox path the call publishes the slate to the relay, locks the inputs and returns the sender's round-one slate (api/src/owner.rs:761); the listener it starts completes the transfer when the counterparty replies.

On the CLI, --min_conf defaults to 10 and --selection to smallest (src/cmd/wallet_args.rs:143), which is selection_strategy_is_use_all: false. Pass -c 3 for the confirmation floor these examples use.

Quote a fee without committing

estimate_only computes the fee and selects inputs without reserving anything.

# Prints the fee for both selection strategies and sends nothing.
epic-wallet send -e -c 3 1.5

In Python, estimate_fee() in send.py is the same call with estimate_only=True passed through init_tx_args.

The fee is a function of transaction shape, not demand:

fee = max(4 × outputs + kernels − inputs, 1) × 0.001 EPIC

There is no fee market, so the estimate is exact rather than a bid. See emission and fees.

Driving the steps yourself

Use this when the slate travels over your own transport. This is also the sequence the CLI runs.

  1. init_send_tx builds the slate.
  2. tx_lock_outputs reserves the inputs. Can spend funds From here the outputs are unavailable until the transfer completes or cancel_tx releases them.
  3. You deliver the slate, by whichever transport.
  4. The receiver returns it signed.
  5. finalize_tx completes the aggregate signature.
  6. post_tx broadcasts the transaction carried in the finalized slate.

tx_lock_outputs takes four parameters, token, slate, participant_id and addr_to (api/src/owner_rpc_s.rs:897). The sender is participant 0. Nothing releases outputs on a timer, so everything after the lock belongs inside error handling that calls cancel_tx. See what releases them.

examples/shell/send-file.sh
#!/usr/bin/env bash
# Sender side of a file-transport transfer, with the cancel branch wired in.
# Needs epic-wallet and jq. epic-wallet prompts for the password at each step.
# Usage: bash send-file.sh <amount in EPIC> [slate file]
# See https://devdocs.epiccash.com/examples/send-receive
set -euo pipefail

AMOUNT="${1:?usage: send-file.sh <amount in EPIC> [slate file]}"
SLATE="${2:-slate.tx}"
MIN_CONF="${MIN_CONF:-3}"
TIMEOUT="${TIMEOUT:-600}"

command -v jq >/dev/null || { echo "this script needs jq" >&2; exit 1; }

# Round one. Writes the slate file and reserves the inputs.
epic-wallet send -m file -d "$SLATE" -c "$MIN_CONF" -s smallest "$AMOUNT"

# tr -d '\r' because a Windows jq writes CRLF. Harmless on Linux and macOS.
slate_id=$(jq -r '.id' "$SLATE" | tr -d '\r')
echo "slate $slate_id written to $SLATE, inputs reserved"

# The receiver runs `epic-wallet receive -m file -i <slate>` and returns <slate>.response.
echo "waiting up to ${TIMEOUT}s for $SLATE.response"
deadline=$(( $(date +%s) + TIMEOUT ))
while [ ! -f "$SLATE.response" ]; do
if [ "$(date +%s)" -ge "$deadline" ]; then
echo "no response, releasing the reserved inputs" >&2
epic-wallet cancel -t "$slate_id"
exit 1
fi
sleep 5
done

epic-wallet finalize -m file -i "$SLATE.response"
echo "transfer $slate_id posted"

Receiving

A listener handles the receiving side:

epic-wallet listen -m epicbox # queues while you are offline
epic-wallet listen # HTTP, requires reachability

To process a slate yourself, the Foreign API method is receive_tx. It is on port 3415, takes no token, and takes four positional params, slate, dest_acct_name, message and addr_from (api/src/foreign_rpc.rs:359).

curl -s -H 'Content-Type: application/json' \
-d "{\"jsonrpc\":\"2.0\",\"method\":\"receive_tx\",\"params\":[$(cat slate.json),null,null,null],\"id\":1}" \
http://127.0.0.1:3415/v2/foreign | jq '.result.Ok'

The Foreign API takes no credential. Restrict access to it at the proxy, and see authentication.

Finding and clearing stuck transfers

epic-wallet info # spendable below total means something is reserved
epic-wallet txs # look for Sent (Created) with Confirmed? false
epic-wallet cancel -i 42 # release that transfer's inputs
epic-wallet scan # rescan if the figures still disagree with the chain

Cancelling needs a reachable node and a transfer that is not yet confirmed. The decision procedure is in stuck transactions.

Invoices

The payee requests an amount and the payer funds it. The cryptography is the same; the initiator changes. finalize_invoice_tx is a Foreign API method, so the payee finalizes there and posts from the Owner API.

epic-wallet invoice -d invoice.tx 2 # payee
epic-wallet pay -c 3 -i invoice.tx # payer, writes invoice.tx.response
epic-wallet finalize -i invoice.tx.response # payee

Payment proofs

Request the proof when you send; it cannot be added afterwards. Epicbox carries slate V2 and a payment proof is a V3 field, so send over HTTP when you need one. See payment proofs.

epic-wallet send --request_payment_proof \
--proof_address <recipient proof address> \
-m http -d http://receiver.example:3415 -c 3 1

epic-wallet export_proof -i 42 proof.json
epic-wallet verify_proof proof.json

verify_payment_proof returns two booleans, covered in payment proofs.

Next