Skip to content

Pay Houdini ZEC unified addresses to their t1 receiver - #512

Merged
j0ntz merged 2 commits into
masterfrom
jon/houdini-zec-unified-payout
Nov 13, 2026
Merged

j0ntz merged 2 commits into
masterfrom
jon/houdini-zec-unified-payout

Conversation

@j0ntz

@j0ntz j0ntz commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

CHANGELOG

Does this branch warrant an entry to the CHANGELOG?

  • Yes
  • No

Dependencies

EdgeApp/edge-exchange-plugins#510 (this branch is stacked on it)

Description

Asana task

HoudiniSwap accepts only transparent (t1) Zcash payout addresses. With #510 alone, a Swap & Send or Stealth Send to a pasted unified address (u1) gets the t1-only error, even when the u1 carries a transparent receiver. This branch pays such a u1 out to that receiver.

  • src/util/zcashUnifiedAddress.ts decodes a mainnet ZIP-316 unified address in pure JS: bech32m without the 90-character limit, inverse F4Jumble (BLAKE2b with personalization), the HRP padding check, then the item list. It returns the transparent receiver as a t1 (P2PKH) or t3 (P2SH) address, or undefined when the u1 has none. It throws on malformed input or a non-u prefix, which rejects utest.
  • houdini.ts sends the decoded t1 to Houdini as addressTo on both the exchange and dex order bodies. A t1 passes through unchanged. A u1 without a t1 receiver (shielded-only or P2SH-only) and any other non-t1 address keep the clear error from Pay Houdini ZEC payouts to the t1 address #510.
  • The saved swapSend action keeps the u1 the user entered as payoutAddress.
  • @noble/hashes is now a direct dependency. The lockfile already held 1.4.0 as a production transitive dependency, and nothing new is fetched.

Tests:

  • All 60 official ZIP-316 unified address vectors: every P2PKH receiver matches a Base58Check encoding built with node's crypto, and shielded-only vectors return undefined.
  • A P2SH fixture built with a separate Python reference encoder, testnet rejection, and corrupted input.
  • Houdini offline cases: a u1 with a t1 receiver pays out to the t1 while the action keeps the u1; a t1 passes through; shielded-only, P2SH-only, sapling and malformed addresses are refused before quoting.

On the iOS simulator, the agent account's own Zcash u1 was pasted as the recipient in the BTC Send scene. HoudiniSwap quoted, and the swap of 0.00035569 BTC reached the success scene. Houdini's order record for bjoTdA9Kb45kPM8LZHxRZM has receiverAddress t1Pw5MC1ZFx79qZBf1f9VHgFe4bkz53AVoN, the t1 the decoder returns for that u1. The transaction's Exchange Details show the u1 as the payout address.


Note

Medium Risk
Changes which on-chain address Houdini orders use for Zcash payouts; incorrect decoding could misroute funds, though coverage includes official ZIP-316 vectors and integration tests.

Overview
HoudiniSwap can now accept a pasted mainnet Zcash unified (u1) address when it embeds a P2PKH transparent receiver, instead of failing before quote with a t1-only error or Houdini’s opaque maxLength 200 validation.

A new getUnifiedTransparentAddress helper decodes ZIP-316 unified addresses (bech32m, F4Jumble inverse, receiver parsing) and returns the embedded t1 or t3 transparent address. getZcashOrderAddress in the Houdini plugin uses it so addressTo on exchange and dex orders is always an accepted t1, while payoutAddress on the saved swap action still records what the user entered. Shielded-only unified addresses, t3-only unified receivers, Sapling, and malformed inputs are still rejected up front with the clear t1-only message.

Adds @noble/hashes for BLAKE2b/SHA256 in the decoder, plus ZIP-316 vector tests and expanded Houdini offline Zcash cases.

Reviewed by Cursor Bugbot for commit bb6a52e. Bugbot is set up for automated code reviews on this repo. Configure here.

Test evidence

bb6a52e
Pay Houdini ZEC unified addresses to their t1
2026-10-17
🪓 Other swap providers disabled locally in corePlugins.ts so the Send scene quote comes from HoudiniSwap; reverted before commit

1. 🪓 houdini quote pasted u1

2. 🪓 swap success

3. 🪓 exchange details

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ⚠️ Failed 2026-10-17T02:22:28.787308Z bb6a52e PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@j0ntz
j0ntz force-pushed the jon/houdini-zec-transparent-dest branch from 503c762 to 1621b78 Compare October 20, 2026 21:10

@peachbits peachbits left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I checked the decoder against a separate ZIP-316 encoder written on Python's hashlib.blake2b (with person) and a hand-written bech32m and F4Jumble. It rebuilds all 60 vector strings from their receivers, and each vector's P2PKH receiver is the BIP-44 key m/44'/133'/account'/0/index of its root seed. Against getUnifiedTransparentAddress, 200 random P2PKH UAs, 50 P2SH UAs and 50 shielded-only UAs round-trip, as do an all-uppercase UA, unknown one- and three-byte typecodes, and a 17 KB item that runs the G block counter past 255. The utest, uregtest and tex HRPs, mixed case, a bech32 checksum, a flipped or dropped character, padding for another HRP, a 47-byte payload, a truncated item, a non-canonical size and 19- or 21-byte receivers all throw. The only gap I found is ZIP-316's item rules (inline 1).

In houdini.ts, only a t1 reaches addressTo, the saved action keeps the pasted address, and a t3, shielded-only or malformed address fails before any quote. Accepting t3, sending the pasted address to Houdini, saving the t1 on the action, letting decoder errors through, breaking F4Jumble, changing the P2SH prefix and throwing on unknown typecodes each fail a new test. Locally tsc and eslint are clean and both suites pass.

Non-blocking: inline 1 (ZIP-316's item rules) and inline 2 (tests for the checks after the checksum).

Comment thread src/util/zcashUnifiedAddress.ts Resolved
Comment thread test/zcashUnifiedAddress.test.ts Resolved
@j0ntz
j0ntz force-pushed the jon/houdini-zec-unified-payout branch from bb6a52e to 3929ce9 Compare November 13, 2026 20:29
@j0ntz

j0ntz commented Nov 13, 2026

Copy link
Copy Markdown
Contributor Author

Both inlines are addressed in 24523b3: the ZIP-316 item rules (order, uniqueness, one transparent receiver) and tests for the checks after the checksum. tsc is clean and all 286 tests pass.

Decodes a mainnet ZIP-316 unified address (bech32m, F4Jumble, item
parse) and returns its transparent receiver as a t1 or t3 address.
Tested against the official zcash-test-vectors set.
A pasted unified address now pays out to its transparent P2PKH
receiver, since Houdini accepts t1 only. The saved swapSend action
keeps the unified address the user entered. A unified address without
a t1 receiver still gets the clear transparent-only error.
@j0ntz
j0ntz force-pushed the jon/houdini-zec-unified-payout branch from 3929ce9 to 2aa7a98 Compare November 13, 2026 20:39
@j0ntz
j0ntz deleted the branch jon/houdini-zec-transparent-dest November 13, 2026 20:41
@j0ntz j0ntz closed this Nov 13, 2026
@j0ntz j0ntz reopened this Nov 13, 2026
@j0ntz
j0ntz changed the base branch from jon/houdini-zec-transparent-dest to master November 13, 2026 20:42
@j0ntz
j0ntz enabled auto-merge November 13, 2026 20:43
@j0ntz
j0ntz disabled auto-merge November 13, 2026 20:43
@j0ntz j0ntz closed this Nov 13, 2026
@j0ntz j0ntz reopened this Nov 13, 2026
@j0ntz
j0ntz merged commit 0dc9759 into master Nov 13, 2026
1 of 3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants