Unshield Tail Calls

From Kohaku CLI Wiki — Unshield and call a contract in one step

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 — target is the recipient, calldata is empty (0x), and msg.value is the amount
  • ERC-20 send — target is the token contract, calldata encodes transfer(recipient, amount), and there is no msg.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 exact Y; 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 0x data)
  • 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.