Sometimes you do not want the full unshield amount sitting idle on a fresh address. With --tail-calls, the payout lands and then immediately runs one or more calls in the same flow — pay someone, hit a router, etc. — while any leftover stays on your fresh account.
This is the preferred path when you can build custom calldata without a dapp frontend. For a plain “pay exact Y to someone,” prefer private-transfer instead. See Using Dapps for the full preference order.
--tail-calls works on Tornado, Privacy Pools, and Railgun (all submit ERC-4337 user ops), including ERC-20 unshields and stored stealth recipients (--to s0). Below we assume DEFAULT_PRIVACY_PROTOCOL=tornado (or else add --protocol tornado / privacy-pools).
Tornado and Privacy Pools tails require --next, a stored public HD --to, or a stored stealth --to (sN). That account is the EIP-7702 delegator: tails run there and leftover funds stay there. Do not pass a peeked / custom address as --to together with --tail-calls — peek to learn the next address, then unshield with --next. ERC-20 tails cannot include msg.value.
# Peek the destination first
--next will pay a brand-new address. Peek it before you build calldata that needs to know that address (swaps, some dapp payloads):
kohaku next-fresh-address --peek
--peek prints the next fresh address without saving it.
# Format
0x<target>:0x<calldata>[:0x<msg-value>]
- ETH send —
targetis the recipient, calldata is empty (0x), andmsg.valueis the amount - ERC-20 send —
targetis the token contract, calldata encodestransfer(recipient, amount), and there is nomsg.value
# Example: pay someone in ETH
You owe 0.15 ETH, but Tornado notes are fixed sizes — so unshield a note you actually hold (e.g. a 0.3 ETH note) and forward exactly 0.15 in a tail call. The leftover (~0.15 ETH minus fees) stays on your fresh address.
0.15 ETH = 150000000000000000 wei = 0x214e8348c4f0000:
kohaku unshield --next --amount-formatted 0.3 \
--tail-calls 0xRecipient:0x:0x214e8348c4f0000
This is the low-level form of private-transfer — same shape (unshield --next + one transfer tail). Prefer private-transfer for plain pays; use --tail-calls when you need a custom target / payload.
# Example: pay someone in tokens
Same idea for USDC (or any ERC-20): unshield a full Tornado note of that token, then tail-call transfer for exactly Y to the recipient. Leftover tokens stay on your fresh address.
This example unshields 1000 USDC and immediately sends 250 USDC to
0xabcdeF0123456789012345678901234567890Abc. USDC uses 6 decimals, so
250 tokens = 250000000 base units (0xee6b280). Mainnet USDC is
0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48:
kohaku unshield --next --token USDC --amount-formatted 1000 \
--tail-calls 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48:0xa9059cbb000000000000000000000000abcdef0123456789012345678901234567890abc000000000000000000000000000000000000000000000000000000000ee6b280
The payload is standard ERC-20 transfer(address,uint256) calldata:
selector 0xa9059cbb, then the recipient, then the amount. No third
:value field — token amounts live in the payload, not msg.value.
# Example: swap / dapp call
If you have router calldata, same pattern — unshield a bit more than you spend:
kohaku unshield --next --amount-formatted 0.3 \
--tail-calls 0x<swap-router>:0x<swap-payload>:0x<msg-value-in-hex>
Dry-run without --broadcast until the printed operation looks right, then broadcast.
Tip: Unshield a full note that is larger than the payment / spend (
Y + delta). The tail call moves exactY; delta (minus fees) remains yours on the fresh account — all in one atomic interaction.
# When to use this
- You can build the calldata (or it is a plain ETH send with empty
0xdata) - You want “fresh address appears mid-flow,” not “fresh address sits idle then separately sends”
If you already unshielded, use transfer for plain sends or transact-raw for contract calls. If you cannot get a clean payload at all, use the browser wallet path instead.