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

Sending

Reference for the synchronous send methods sendtoaddress and sendmany. For the asynchronous zcashd-style send (z_sendmany and the operation-tracking trio), see async operations.

Send semantics

Everything in this section applies to both methods.

Synchronous. The call builds the transaction, computes the Orchard proof, commits it to the wallet, and broadcasts it, all inside the HTTP request; the txid returns only after broadcast is attempted. Unlike bitcoind's millisecond sends, the proof takes on the order of seconds (far longer in debug builds), plus any queueing behind other sends. Set client-side send timeouts well above that. A client that times out and blindly retries a send that actually succeeded pays twice: on timeout, reconcile with listtransactions before retrying. Once the transaction is committed, a transport failure during initial relay does not surface as an error; the txid is returned and a background loop rebroadcasts until the transaction mines or expires. Only an explicit rejection by the Zebra node returns an error (-26), and the spent notes stay locked until the transaction's expiry height.

Sends serialize per wallet. Each wallet is owned by a single-writer actor, the analog of Bitcoin Core's cs_wallet: concurrent sends to one wallet are processed one at a time and never select the same note, so there is no double-spend and no note-locking API to manage. Queued sends hold their HTTP connection longer.

Fees are ZIP-317, never client-settable. The wallet computes the conventional fee; there is no estimator and no fee knob. subtractfeefromamount (sendtoaddress) and subtractfeefrom (sendmany) are rejected with -8 when engaged, because silently ignoring them would move different amounts than the caller intended. fee_rate is rejected with -8 for the same reason. These guards fire before any wallet access, so passing the defaults (false, null, []) still works. conf_target and estimate_mode are estimation hints and are silently ignored; settxfee always returns -8.

Insufficient funds is self-diagnosing. Shielded change is unspendable until it confirms (3 confirmations for trusted change by default, see configuration), so rapid back-to-back sends exhaust spendable notes and return -6 until a block arrives. The -6 message appends any balance awaiting confirmations, so a client can tell "retry after the next block" from "the wallet needs funding":

Insufficient funds: 0 zatoshis spendable, 10001000 required (including fee);
awaiting confirmations: 0 zatoshis incoming, 49990000 zatoshis change

Privacy policy is enforced per recipient. Under the wallet's configured [spend] privacy_policy (default AllowRevealedRecipients), a recipient with no shielded receiver (a bare t1/t3 address) is rejected up front with -8 when the policy is FullPrivacy or AllowRevealedAmounts. FullPrivacy additionally rejects, on the built proposal, any send that crosses the Sapling to Orchard turnstile. Neither method takes a per-call policy argument; the config value applies. See the privacy policy ladder.

Action limit. [spend] orchard_action_limit (default 50, 0 disables) caps the Orchard actions of a single send to bound its memory and proving cost. A proposal that exceeds it returns -8 naming whether inputs or outputs overflowed.

Common errors (both methods; verified in the handlers and src/error.rs):

CodeWhen
-1Missing required argument; more positional arguments than the method accepts
-3Amount not a number or string; zero or unparseable amount (Invalid amount); negative or above 21,000,000 ZEC (Amount out of range); non-boolean verbose
-5Unparseable address, or an address for the wrong network
-6Insufficient spendable funds (see the enrichment above)
-8subtractfeefromamount/subtractfeefrom or fee_rate engaged; privacy-policy rejection of a transparent-only recipient; orchard_action_limit exceeded
-4Watch-only wallet (Error: Private keys are disabled for this wallet); other wallet-level build failures
-13Passphrase-encrypted wallet is locked (walletpassphrase first)
-18/wallet/<name> names no loaded wallet
-26The Zebra node examined the transaction and rejected it

sendtoaddress

sendtoaddress "address" amount ( "comment" "comment_to" subtractfeefromamount replaceable conf_target "estimate_mode" avoid_reuse fee_rate verbose "memo" )

Pay one recipient from the wallet's shielded notes (or, under AllowFullyTransparent with a transparent recipient, from transparent UTXOs; see transparent). Returns the txid after proving and broadcast.

Parameters

#NameTypeDefaultDescription
1addressstringrequiredRecipient: unified, Sapling, or transparent address
2amountnumber or stringrequiredDecimal ZEC, 8 places; zero is rejected (-3)
3commentstringomittedIgnored: zecd persists no local metadata (see statelessness)
4comment_tostringomittedIgnored, as above
5subtractfeefromamountbooleanfalseRejected with -8 if true (fees are ZIP-317, paid by the sender)
6replaceablebooleanomittedIgnored (no RBF in Zcash)
7conf_targetnumberomittedIgnored (no fee estimator; ZIP-317 buys next-block inclusion)
8estimate_modestringomittedIgnored
9avoid_reusebooleanomittedIgnored (shielded receiving addresses are diversified)
10fee_ratenumberomittedRejected with -8 if set
11verbosebooleanfalseReturn an object with fee_reason instead of a bare txid
12memostring (hex)omittedzecd extension: hex-encoded ZIP-302 memo for the shielded recipient, at most 512 bytes

Result

"85a13a0895c9ef2e26b1a29321581e19b6cb51b0e6b1e4f0d68f4d5cba1b7f4e"

With verbose = true (fee_reason is always the ZIP-317 conventional fee):

{
  "txid": "85a13a0895c9ef2e26b1a29321581e19b6cb51b0e6b1e4f0d68f4d5cba1b7f4e",
  "fee_reason": "ZIP 317"
}

Errors (beyond the common table)

CodeWhen
-3memo present but not a string
-8memo is not valid hex (Invalid parameter, expected memo data in hexadecimal format.); memo longer than 512 bytes; memo paired with a transparent recipient (Memo cannot be used with a transparent recipient)

vs Bitcoin Core

Parameter positions 1-11 match Core master's sendtoaddress exactly (verified against src/wallet/rpc/spend.cpp), including the verbose result shape. Differences: comment/ comment_to are accepted but never stored; subtractfeefromamount and fee_rate are hard -8 rejections instead of honored; replaceable/conf_target/estimate_mode/ avoid_reuse are ignored; position 12 (memo) does not exist in Core.

vs zcashd

zcashd retains sendtoaddress but as a transparent-only legacy method: it selects funds exclusively from the transparent pool, its help says "THIS API PROVIDES NO PRIVACY", and it takes only 5 arguments (through subtractfeefromamount, which it honors). The recommended zcashd send is z_sendmany. zecd inverts this: sendtoaddress is the primary, shielded send, and its memo parameter follows z_sendmany's conventions (hex, 512-byte cap). Unlike z_sendmany, zecd's sendtoaddress keeps Core's rejection of zero amounts (-3), so a memo-only send needs z_sendmany.

Example

curl -u u:p --max-time 120 -d '{
  "jsonrpc": "1.0", "id": 1, "method": "sendtoaddress",
  "params": ["u1abc...", 0.1, "", "", false, false, null, "", false, null, false,
             "74616b652074686520686f626269747320746f2069736574686172"]
}' http://127.0.0.1:8232/

sendmany

sendmany "" {"address":amount,...} ( minconf "comment" ["address",...] replaceable conf_target "estimate_mode" fee_rate verbose )

Pay several recipients in one transaction: one ZIP-317 fee, one anchor. Recipients may mix shielded and transparent addresses (under the default policy a transparent recipient is paid from shielded notes, with shielded change).

Parameters

#NameTypeDefaultDescription
1dummystring""Legacy placeholder; zecd ignores it entirely (Core rejects a non-empty value)
2amountsobjectrequired{"address": amount, ...}; amounts are decimal ZEC, 8 places, number or string; zero is rejected (-3)
3minconfnumberomittedIgnored dummy value, as in Core master
4commentstringomittedIgnored (not stored)
5subtractfeefromarrayomittedRejected with -8 if non-empty
6replaceablebooleanomittedIgnored
7conf_targetnumberomittedIgnored
8estimate_modestringomittedIgnored
9fee_ratenumberomittedRejected with -8 if set
10verbosebooleanfalseReturn an object with fee_reason instead of a bare txid

Duplicate recipient keys collapse silently. Recipients arrive as a JSON object, and JSON parsing keeps only the last occurrence of a duplicated key before zecd sees it, so listing the same address twice sends only the last amount. Bitcoin Core's Invalid parameter, duplicated address error cannot be reproduced here. Do not list an address twice; combine the amounts instead. (z_sendmany, whose recipients are an array, does reject duplicates with -8.)

Transparent-to-transparent spends. sendmany has no per-call privacy argument, so its only route to a fully transparent spend (transparent UTXOs in, transparent change) is the [spend] privacy_policy = "AllowFullyTransparent" config knob, and it engages only when every recipient is a bare transparent address. Under the default policy a wallet holding only transparent funds gets -6. See transparent.

Result

Same shape as sendtoaddress: a bare txid string, or {"txid", "fee_reason"} with verbose = true.

Errors (beyond the common table)

CodeWhen
-3amounts present but not an object
-8amounts is an empty object (sendmany requires at least one recipient)

vs Bitcoin Core

Parameter positions 1-10 match Core master exactly (verified against src/wallet/rpc/spend.cpp); Core also treats minconf as an ignored dummy. Differences: zecd never validates the dummy argument (Core returns -8 for a non-empty one), rejects subtractfeefrom/fee_rate with -8 instead of honoring them, and does not store the comment.

vs zcashd

zcashd's sendmany is a discouraged transparent-only legacy method ("Prefer to use z_sendmany instead"); it takes 5 arguments, honors minconf, and honors subtractfeefromamount. zecd's nearest zcashd equivalent for a multi-recipient shielded send is z_sendmany, which zecd also implements (async operations) with per-output memos, a per-call minconf and privacyPolicy, and zero-amount outputs.

Example

curl -u u:p --max-time 120 -d '{
  "jsonrpc": "1.0", "id": 1, "method": "sendmany",
  "params": ["", {"u1abc...": 0.05, "t1M72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd": 0.02}]
}' http://127.0.0.1:8232/