Skip to content

Support maya ZEC swaps#423

Merged
Jon-edge merged 1 commit intomasterfrom
jon/maya-zec
Dec 3, 2025
Merged

Support maya ZEC swaps#423
Jon-edge merged 1 commit intomasterfrom
jon/maya-zec

Conversation

@Jon-edge
Copy link
Contributor

@Jon-edge Jon-edge commented Dec 1, 2025

CHANGELOG

Does this branch warrant an entry to the CHANGELOG?

  • Yes
  • No

Dependencies

none

Description

none

Note

Add Maya Protocol Zcash swap support using ZIP-321 with shielded memo config and ZEC mainnet transcription.

  • Maya/Thorchain common:
    • Add zcash -> ZEC to MAINNET_CODE_TRANSCRIPTION.
    • Extend asInboundAddresses to include shielded_memo_config and fetch/store unified_address per chain.
    • Implement ZEC-specific swap path:
      • Build ZIP-321 URI with base64url memo, transparent vault address validation, and shielded memo recipient.
      • Disallow ZEC tokens; clear text memo; set publicAddress to vault; pass zip321Uri in spendInfo.otherParams.
    • Import @scure/base for base64url/UTF-8 handling.
  • Docs:
    • CHANGELOG: add “(Maya) Zcash support.”

Written by Cursor Bugbot for commit e0ebfa9. This will update automatically on new commits. Configure here.


Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

ℹ️ 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".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines 388 to 392
const toAddress = await getAddress(
toWallet,
toWallet.currencyInfo.pluginId === 'zcash'
? 'transparentAddress'
: undefined

Choose a reason for hiding this comment

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

P1 Badge Remove ZEC from invalid destination codes

The new transparent-address handling for ZEC destinations (lines 388‑392) never runs because checkInvalidCodes is still called earlier with INVALID_CURRENCY_CODES.to.zcash = ['ZEC'] (lines 129‑135), so any swap targeting ZEC throws SwapCurrencyError before quoting. As a result the commit’s intended support for Maya ZEC receives remains blocked; remove the invalid-code entry or gate it to paths that truly need to be disallowed.

Useful? React with 👍 / 👎.

Copy link
Contributor

Choose a reason for hiding this comment

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

This is valid, it's impossible to receive ZEC as-is

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Right this should be removed, to zec not supported.

Comment on lines 388 to 392
const toAddress = await getAddress(
toWallet,
toWallet.currencyInfo.pluginId === 'zcash'
? 'transparentAddress'
: undefined
Copy link
Contributor

Choose a reason for hiding this comment

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

This is valid, it's impossible to receive ZEC as-is

}

// Convert native to a decimal string per ZIP-321 spec
const multiplier =
Copy link
Contributor

Choose a reason for hiding this comment

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

Use the wallet's nativeToDenomination method instead

)

// Encode memo per ZIP-321 as base64url (unpadded).
const memoBase64Url = Buffer.from(memo, 'utf8')
Copy link
Contributor

Choose a reason for hiding this comment

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

Instead of handrolling use a library like base64url


// ZIP-321 requires grouping parameters by payment using paramindex.
// Payment 0 (unindexed): transparent vault recipient & swap amount.
// Payment 1 (indexed .1): shielded memo recipient, memo, and tiny amount to preserve memo.
Copy link
Contributor

Choose a reason for hiding this comment

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

The comment suggests sending a tiny amount to Payment 1 but mount.1 is 0

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Comment just needs to be updated. Impl is correct


// Special-case ZEC: If inbound is transparent and memo is non-empty,
// build a ZIP-321 URI to satisfy Maya's requirements
if (fromWallet.currencyInfo.pluginId === 'zcash' && thorAddress != null) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Move the zcash stuff into it's own else if (fromWallet.currencyInfo.pluginId === 'zcash') block. and then do the thorAddress check and copy any additional checks that the UTXO block is doing. While zcash is utxo based under the hood it really isn't treated as such in Edge so let's keep it separate

@Jon-edge Jon-edge merged commit de5b0b4 into master Dec 3, 2025
2 checks passed
)

// Encode memo per ZIP-321 as base64url (unpadded).
const memoBase64Url = base64urlnopad.encode(utf8.decode(memo))
Copy link

Choose a reason for hiding this comment

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

Bug: Wrong utf8 method: decode vs encode for string

The code calls utf8.decode(memo) but memo is a string and utf8.decode expects a Uint8Array. The @scure/base library's utf8.decode converts UTF-8 bytes to string, while utf8.encode converts string to UTF-8 bytes. Since the intent is to convert the string memo to bytes for base64url encoding, this should use utf8.encode(memo) instead of utf8.decode(memo). This will cause ZEC swaps to fail at runtime.

Fix in Cursor Fix in Web

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Looking at how @scure/base coders work:
decode = decode FROM the encoding representation → produces bytes
encode = encode TO the encoding representation → produces string
So for utf8:
utf8.decode(string) → Uint8Array (decodes the string into UTF-8 bytes)
utf8.encode(Uint8Array) → string (encodes bytes back to string)
The current code:
const memoBase64Url = base64urlnopad.encode(utf8.decode(memo))
Flow: string → utf8.decode() → Uint8Array → base64urlnopad.encode() → base64url string
This matches the original Buffer approach:
Buffer.from(memo, 'utf8') // string → bytes .toString('base64') // bytes → base64 string
The cursor bot comment is incorrect. The current code is right:
utf8.decode(memo) converts the string to bytes ✅
base64urlnopad.encode(...) converts bytes to base64url string ✅

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