From 3b5eaabdef416308de402b697c3a8aa14c5182f5 Mon Sep 17 00:00:00 2001 From: peachbits Date: Tue, 21 Jul 2026 12:21:23 -0700 Subject: [PATCH 1/4] Zcash: Add Ironwood migration status util and hook Typed access to the engine's v1 Orchard -> Ironwood detection surface (wallet.otherMethods.getMigrationStatus, cleaner-validated) plus a polling hook safe to call unconditionally from shared scenes. iOS-gated until the Android bridge lands. Strings use a recommended tone by design: the sweep is worthwhile, not mandatory - Orchard stays spendable post-fork and drains passively through ordinary spends, so nothing is framed as "required". Co-Authored-By: Claude Fable 5 --- src/__tests__/zcashMigration.test.ts | 71 ++++++++++++++++++++++++++++ src/hooks/useZcashMigrationStatus.ts | 24 ++++++++++ src/locales/en_US.ts | 12 +++++ src/locales/strings/enUS.json | 6 +++ src/util/zcashMigration.ts | 65 +++++++++++++++++++++++++ 5 files changed, 178 insertions(+) create mode 100644 src/__tests__/zcashMigration.test.ts create mode 100644 src/hooks/useZcashMigrationStatus.ts create mode 100644 src/util/zcashMigration.ts diff --git a/src/__tests__/zcashMigration.test.ts b/src/__tests__/zcashMigration.test.ts new file mode 100644 index 00000000000..89c6f276c4c --- /dev/null +++ b/src/__tests__/zcashMigration.test.ts @@ -0,0 +1,71 @@ +import { describe, expect, it } from '@jest/globals' +import type { EdgeCurrencyWallet } from 'edge-core-js' + +import { getZcashMigrationStatus } from '../util/zcashMigration' + +const goodStatus = { + state: 'required', + completedTransfers: 0, + totalTransfers: 0, + remainingOrchardZatoshi: '123', + hasOverdueTransfers: false, + isSynced: true +} + +const makeFakeWallet = (opts: { + pluginId: string + otherMethods?: object +}): EdgeCurrencyWallet => + ({ + currencyInfo: { pluginId: opts.pluginId }, + otherMethods: opts.otherMethods ?? {} + } as any) + +describe('zcashMigration util', () => { + it('returns status for a migration-capable zcash wallet', async () => { + const wallet = makeFakeWallet({ + pluginId: 'zcash', + otherMethods: { + getMigrationStatus: async () => goodStatus + } + }) + const status = await getZcashMigrationStatus(wallet) + expect(status?.state).toBe('required') + expect(status?.remainingOrchardZatoshi).toBe('123') + }) + + it('returns undefined for non-zcash wallets', async () => { + const wallet = makeFakeWallet({ + pluginId: 'bitcoin', + otherMethods: { getMigrationStatus: async () => goodStatus } + }) + expect(await getZcashMigrationStatus(wallet)).toBeUndefined() + }) + + it('returns undefined when the engine lacks the method (old accountbased)', async () => { + const wallet = makeFakeWallet({ pluginId: 'zcash' }) + expect(await getZcashMigrationStatus(wallet)).toBeUndefined() + }) + + it('returns undefined when the engine call throws', async () => { + const wallet = makeFakeWallet({ + pluginId: 'zcash', + otherMethods: { + getMigrationStatus: async () => { + throw new Error('engine broke') + } + } + }) + expect(await getZcashMigrationStatus(wallet)).toBeUndefined() + }) + + it('returns undefined on malformed status shapes', async () => { + const wallet = makeFakeWallet({ + pluginId: 'zcash', + otherMethods: { + getMigrationStatus: async () => ({ state: 'bogus' }) + } + }) + expect(await getZcashMigrationStatus(wallet)).toBeUndefined() + }) +}) diff --git a/src/hooks/useZcashMigrationStatus.ts b/src/hooks/useZcashMigrationStatus.ts new file mode 100644 index 00000000000..60a350cf47a --- /dev/null +++ b/src/hooks/useZcashMigrationStatus.ts @@ -0,0 +1,24 @@ +import type { EdgeCurrencyWallet } from 'edge-core-js' +import * as React from 'react' + +import { + getZcashMigrationStatus, + type ZcashMigrationStatus +} from '../util/zcashMigration' +import { useRefresher } from './useRefresher' + +/** + * Polls the Zcash Orchard -> Ironwood migration status for a wallet. + * Returns undefined (forever) for anything that is not a migration-capable + * Zcash wallet, so it is safe to call unconditionally from shared scenes. + */ +export function useZcashMigrationStatus( + wallet: EdgeCurrencyWallet, + delay: number = 10000 +): ZcashMigrationStatus | undefined { + const refresher = React.useCallback( + async () => await getZcashMigrationStatus(wallet), + [wallet] + ) + return useRefresher(refresher, undefined, delay) +} diff --git a/src/locales/en_US.ts b/src/locales/en_US.ts index 49926310b8e..cf6d7ac808a 100644 --- a/src/locales/en_US.ts +++ b/src/locales/en_US.ts @@ -764,6 +764,18 @@ const strings = { string_first_pivx_wallet_name: 'My PIVX', string_first_zano_wallet_name: 'My Zano', string_first_zcash_wallet_name: 'My Zcash', + + // Zcash Orchard -> Ironwood (NU6.3) migration (v1: recommended sweep via + // the ordinary send scene — not framed as mandatory, because Orchard stays + // spendable post-fork and drains passively through ordinary spends): + zcash_migration_recommended_title: 'Move to the new Ironwood pool', + zcash_migration_recommended_body: + "Zcash's Ironwood network upgrade added a new shielded pool. We recommend moving your shielded ZEC to it now with a single send to yourself. Your funds stay safe and spendable either way.", + zcash_migration_recommended_button: 'Move my funds', + zcash_migration_info_tile_label: 'Ironwood migration', + zcash_migration_info_tile_value: + 'This send moves your shielded ZEC to your own address in the new Ironwood pool. The amount is your spendable balance minus the network fee.', + zcash_migration_tx_notes: 'Ironwood migration', string_first_tron_wallet_name: 'My Tron', string_first_doge_wallet_name: 'My Doge', string_first_fantom_wallet_name: 'My Fantom', diff --git a/src/locales/strings/enUS.json b/src/locales/strings/enUS.json index fe138434cfc..4c6bc82ea50 100644 --- a/src/locales/strings/enUS.json +++ b/src/locales/strings/enUS.json @@ -593,6 +593,12 @@ "string_first_pivx_wallet_name": "My PIVX", "string_first_zano_wallet_name": "My Zano", "string_first_zcash_wallet_name": "My Zcash", + "zcash_migration_recommended_title": "Move to the new Ironwood pool", + "zcash_migration_recommended_body": "Zcash's Ironwood network upgrade added a new shielded pool. We recommend moving your shielded ZEC to it now with a single send to yourself. Your funds stay safe and spendable either way.", + "zcash_migration_recommended_button": "Move my funds", + "zcash_migration_info_tile_label": "Ironwood migration", + "zcash_migration_info_tile_value": "This send moves your shielded ZEC to your own address in the new Ironwood pool. The amount is your spendable balance minus the network fee.", + "zcash_migration_tx_notes": "Ironwood migration", "string_first_tron_wallet_name": "My Tron", "string_first_doge_wallet_name": "My Doge", "string_first_fantom_wallet_name": "My Fantom", diff --git a/src/util/zcashMigration.ts b/src/util/zcashMigration.ts new file mode 100644 index 00000000000..b31df078604 --- /dev/null +++ b/src/util/zcashMigration.ts @@ -0,0 +1,65 @@ +import { + asBoolean, + asMaybe, + asNumber, + asObject, + asOptional, + asString, + asValue +} from 'cleaners' +import type { EdgeCurrencyWallet } from 'edge-core-js' +import { Platform } from 'react-native' + +// +// Typed access to the Zcash engine's Orchard -> Ironwood migration surface +// (edge-currency-accountbased exposes it through wallet.otherMethods, which is +// untyped). Cleaners mirror the engine's shapes. +// +// v1 is detection-only: 'required' means a sweep is worthwhile (recommended, +// not mandatory — Orchard stays spendable post-fork and drains passively +// through ordinary spends). The GUI answers it with a prefilled max +// send-to-self through the regular send scene. The 'scheduled'/'error' states +// belong to the guided (v2) migration lifecycle and are dormant in v1. +// + +/** + * The react-native-zcash Android bridge stubs reject until the Android SDK + * ships Ironwood; hide the feature there entirely. Delete this gate when the + * Android bridge lands. + */ +export const ZCASH_MIGRATION_PLATFORMS = ['ios'] + +export const asZcashMigrationStatus = asObject({ + state: asValue('notNeeded', 'required', 'scheduled', 'complete', 'error'), + completedTransfers: asMaybe(asNumber, 0), + totalTransfers: asMaybe(asNumber, 0), + remainingOrchardZatoshi: asMaybe(asString, '0'), + hasOverdueTransfers: asMaybe(asBoolean, false), + isSynced: asMaybe(asBoolean, false), + nextTransferReadyAtHeight: asOptional(asNumber) +}) +export type ZcashMigrationStatus = ReturnType + +const isMigrationCapable = (wallet: EdgeCurrencyWallet): boolean => + wallet.currencyInfo.pluginId === 'zcash' && + ZCASH_MIGRATION_PLATFORMS.includes(Platform.OS) && + typeof wallet.otherMethods.getMigrationStatus === 'function' + +/** + * The wallet's migration status, or undefined when the wallet is not a + * migration-capable Zcash wallet (wrong plugin, old accountbased build, + * gated platform) or the engine call fails. + */ +export async function getZcashMigrationStatus( + wallet: EdgeCurrencyWallet +): Promise { + if (!isMigrationCapable(wallet)) return undefined + try { + return asZcashMigrationStatus( + await wallet.otherMethods.getMigrationStatus() + ) + } catch (error) { + console.warn('getZcashMigrationStatus failed', error) + return undefined + } +} From ca318898dff9a12f12489c3d186dd938fb0a35ae Mon Sep 17 00:00:00 2001 From: peachbits Date: Tue, 21 Jul 2026 12:31:44 -0700 Subject: [PATCH 2/4] Zcash: Ironwood migration card -> locked max send-to-self The Zcash wallet scene's migration card (shown when the engine reports a sweep is worthwhile) prefills the ordinary send scene with the wallet's own unified address and its max spendable amount, with the address/amount/wallet tiles locked - the account-activation locked-send pattern. Post-activation, plain proposals route outputs and change into Ironwood, so this is a normal transaction through the normal pipeline: fee and final amount come from the engine's makeSpend, and the tx is labeled via metadata notes. Copy is deliberately recommended-tone, not required: Orchard stays spendable post-fork and drains passively through ordinary spends. The card clears via the status poll once Orchard empties by any means (sweep sent, or drained organically). A max send sweeps every shielded pool into Ironwood - the strings say "shielded ZEC", not just Orchard. Co-Authored-By: Claude Fable 5 --- src/components/themed/TransactionListTop.tsx | 101 ++++++++++++++++++- src/locales/en_US.ts | 13 ++- src/locales/strings/enUS.json | 6 +- 3 files changed, 110 insertions(+), 10 deletions(-) diff --git a/src/components/themed/TransactionListTop.tsx b/src/components/themed/TransactionListTop.tsx index d8332b3b2cf..1a14918fdf7 100644 --- a/src/components/themed/TransactionListTop.tsx +++ b/src/components/themed/TransactionListTop.tsx @@ -1,5 +1,9 @@ -import { add, gt, mul } from 'biggystring' -import type { EdgeCurrencyWallet, EdgeTokenId } from 'edge-core-js' +import { add, div, gt, mul } from 'biggystring' +import type { + EdgeCurrencyWallet, + EdgeSpendInfo, + EdgeTokenId +} from 'edge-core-js' import * as React from 'react' import { View } from 'react-native' import type { AirshipBridge } from 'react-native-airship' @@ -24,6 +28,7 @@ import { useAsyncValue } from '../../hooks/useAsyncValue' import { useHandler } from '../../hooks/useHandler' import { useWalletName } from '../../hooks/useWalletName' import { useWatch } from '../../hooks/useWatch' +import { useZcashMigrationStatus } from '../../hooks/useZcashMigrationStatus' import { formatNumber, toPercentString } from '../../locales/intl' import { lstrings } from '../../locales/strings' import { getStakePlugins } from '../../plugins/stake-plugins/stakePlugins' @@ -156,6 +161,7 @@ export const TransactionListTop: React.FC = props => { const walletName = useWalletName(wallet) const balanceMap = useWatch(wallet, 'balanceMap') const syncStatus = useWatch(wallet, 'syncStatus') + const migrationStatus = useZcashMigrationStatus(wallet) // Track sync card visibility with 1-second delay after sync completes: const isSyncing = syncStatus.totalRatio < DONE_THRESHOLD @@ -665,6 +671,96 @@ export const TransactionListTop: React.FC = props => { ) } + /** + * Orchard -> Ironwood migration card (Zcash only; appears when the engine + * reports a sweep is worthwhile). Tapping prefills a locked max + * send-to-self in the ordinary send scene — post-activation, plain + * proposals route outputs and change into Ironwood, so no special + * transaction type exists. Recommended, not mandatory: Orchard stays + * spendable and drains passively through ordinary spends, and the card + * clears via the status poll once Orchard empties by any means. + */ + function renderZcashMigrationCard(): React.ReactElement | null { + if (tokenId != null || migrationStatus == null) return null + if (migrationStatus.state !== 'required') return null + + // ZIP 318 requires the entry point to display the Orchard-pool-specific + // balance at risk — not the wallet's whole shielded balance, since only + // the Orchard funds cross the turnstile. + const orchardDisplayAmount = formatNumber( + div( + migrationStatus.remainingOrchardZatoshi, + displayDenomination.multiplier, + DECIMAL_PRECISION + ), + { toFixed: 6 } + ) + const orchardBalanceText = `${orchardDisplayAmount} ${displayDenomination.name}` + + const handleMigratePress = async (): Promise => { + const addresses = await wallet.getAddresses({ tokenId: null }) + const unifiedAddress = addresses.find( + address => address.addressType === 'unifiedAddress' + ) + if (unifiedAddress == null) return + const spendInfo: EdgeSpendInfo = { + tokenId: null, + spendTargets: [{ publicAddress: unifiedAddress.publicAddress }], + metadata: { notes: lstrings.zcash_migration_tx_notes }, + // Top-level otherParams reaches the engine intact (per-target + // otherParams gets overwritten by the send scene's address handlers). + // The flag makes the engine quote and build an Orchard-ONLY sweep + // instead of an ordinary max send across every shielded pool. + otherParams: { ironwoodMigration: true } + } + // The locked amount tile makes the MAX modal unreachable, so prefill the + // engine's quote and lock it. For a migration spend that quote is the + // Orchard balance minus fee, and re-proposing is deterministic, so the + // amount shown here is the amount the user signs. + const migrationAmount = await wallet.getMaxSpendable(spendInfo) + navigation.push('send2', { + walletId: wallet.id, + tokenId: null, + spendInfo: { + ...spendInfo, + spendTargets: [ + { + publicAddress: unifiedAddress.publicAddress, + nativeAmount: migrationAmount + } + ] + }, + lockTilesMap: { address: true, amount: true, wallet: true }, + infoTiles: [ + { + label: lstrings.zcash_migration_info_tile_label, + value: lstrings.zcash_migration_info_tile_value + } + ] + }) + } + + return ( + { + handleMigratePress().catch((error: unknown) => { + showError(error) + }) + } + }} + /> + ) + } + function renderButtons(): React.ReactElement { const styles = getStyles(theme) const hideStaking = !isStakingAvailable @@ -795,6 +891,7 @@ export const TransactionListTop: React.FC = props => { {!isStakingAvailable ? null : renderStakedBalance()} {renderSyncStatus()} + {renderZcashMigrationCard()} {renderButtons()} )} diff --git a/src/locales/en_US.ts b/src/locales/en_US.ts index cf6d7ac808a..238edce4f3c 100644 --- a/src/locales/en_US.ts +++ b/src/locales/en_US.ts @@ -767,14 +767,17 @@ const strings = { // Zcash Orchard -> Ironwood (NU6.3) migration (v1: recommended sweep via // the ordinary send scene — not framed as mandatory, because Orchard stays - // spendable post-fork and drains passively through ordinary spends): - zcash_migration_recommended_title: 'Move to the new Ironwood pool', - zcash_migration_recommended_body: - "Zcash's Ironwood network upgrade added a new shielded pool. We recommend moving your shielded ZEC to it now with a single send to yourself. Your funds stay safe and spendable either way.", + // spendable post-fork and drains passively through ordinary spends). + // Per ZIP 318 the entry point states the Orchard-pool balance at risk, and + // per ZIP 315 it discloses that the migrated amount becomes public before + // any funds leave the pool. + zcash_migration_recommended_title: 'Move your Orchard funds to Ironwood', + zcash_migration_recommended_body_1s: + "Zcash's Ironwood network upgrade added a new shielded pool, and %s of your ZEC is still in the older Orchard pool. We recommend moving it with a single send to yourself. The amount you move will be publicly visible on the blockchain. Your funds stay safe and spendable either way.", zcash_migration_recommended_button: 'Move my funds', zcash_migration_info_tile_label: 'Ironwood migration', zcash_migration_info_tile_value: - 'This send moves your shielded ZEC to your own address in the new Ironwood pool. The amount is your spendable balance minus the network fee.', + 'This send moves your Orchard-pool ZEC to your own address in the new Ironwood pool, leaving your other funds untouched. The amount is your Orchard balance minus the network fee, and it will be publicly visible on the blockchain.', zcash_migration_tx_notes: 'Ironwood migration', string_first_tron_wallet_name: 'My Tron', string_first_doge_wallet_name: 'My Doge', diff --git a/src/locales/strings/enUS.json b/src/locales/strings/enUS.json index 4c6bc82ea50..99c4966e2f3 100644 --- a/src/locales/strings/enUS.json +++ b/src/locales/strings/enUS.json @@ -593,11 +593,11 @@ "string_first_pivx_wallet_name": "My PIVX", "string_first_zano_wallet_name": "My Zano", "string_first_zcash_wallet_name": "My Zcash", - "zcash_migration_recommended_title": "Move to the new Ironwood pool", - "zcash_migration_recommended_body": "Zcash's Ironwood network upgrade added a new shielded pool. We recommend moving your shielded ZEC to it now with a single send to yourself. Your funds stay safe and spendable either way.", + "zcash_migration_recommended_title": "Move your Orchard funds to Ironwood", + "zcash_migration_recommended_body_1s": "Zcash's Ironwood network upgrade added a new shielded pool, and %s of your ZEC is still in the older Orchard pool. We recommend moving it with a single send to yourself. The amount you move will be publicly visible on the blockchain. Your funds stay safe and spendable either way.", "zcash_migration_recommended_button": "Move my funds", "zcash_migration_info_tile_label": "Ironwood migration", - "zcash_migration_info_tile_value": "This send moves your shielded ZEC to your own address in the new Ironwood pool. The amount is your spendable balance minus the network fee.", + "zcash_migration_info_tile_value": "This send moves your Orchard-pool ZEC to your own address in the new Ironwood pool, leaving your other funds untouched. The amount is your Orchard balance minus the network fee, and it will be publicly visible on the blockchain.", "zcash_migration_tx_notes": "Ironwood migration", "string_first_tron_wallet_name": "My Tron", "string_first_doge_wallet_name": "My Doge", From 3532f033545e3d2cceff337a58057ee95548d880 Mon Sep 17 00:00:00 2001 From: peachbits Date: Tue, 21 Jul 2026 12:33:20 -0700 Subject: [PATCH 3/4] Zcash: Pin Ironwood prebuilt dependency builds react-native-zcash and edge-currency-accountbased pin to the Edge-hosted Ironwood v1 builds on the ironwood-ffi-2922143e release (release/2.6.0 SDK line; NU6.3 activation heights are set - mainnet 3428143 / testnet 4134000). Tarball URLs and integrities point at builds of the v1 branches of each repo (react-native-zcash zcash-ironwood @ 124ee19, edge-currency-accountbased matthew/zec-ironwood-migration @ 6f579238). Swaps to npm releases when upstream tags the 2.6.0 SDK. Co-Authored-By: Claude Fable 5 --- package-lock.json | 19 ++++++++++--------- package.json | 4 ++-- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/package-lock.json b/package-lock.json index cfa26610c0f..bd0d7eee43e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -48,7 +48,7 @@ "detect-bundler": "^1.1.0", "disklet": "^0.6.0", "edge-core-js": "^2.47.0", - "edge-currency-accountbased": "^4.86.0", + "edge-currency-accountbased": "https://github.com/EdgeApp/react-native-zcash/releases/download/ironwood-ffi-2922143e/edge-currency-accountbased-4.86.1-ironwood-6f579238.tgz", "edge-currency-plugins": "^3.11.0", "edge-exchange-plugins": "^2.51.0", "edge-info-server": "^3.12.0", @@ -113,7 +113,7 @@ "react-native-wheel-picker-android": "^2.0.6", "react-native-worklets": "^0.6.1", "react-native-zano": "^0.3.0", - "react-native-zcash": "^0.12.2", + "react-native-zcash": "https://github.com/EdgeApp/react-native-zcash/releases/download/ironwood-ffi-2922143e/react-native-zcash-0.12.1-ironwood-124ee19.tgz", "react-redux": "^8.1.1", "redux": "^4.2.1", "redux-thunk": "^2.3.0", @@ -15407,9 +15407,9 @@ "license": "MIT" }, "node_modules/edge-currency-accountbased": { - "version": "4.86.0", - "resolved": "https://registry.npmjs.org/edge-currency-accountbased/-/edge-currency-accountbased-4.86.0.tgz", - "integrity": "sha512-/I9/XjcnBSS2c48uOmWrXVO4XLftwNPMuxwHzHB+ngE98KyVgRzh8VtMGYf1U+CLLBF+OGEQHGP/pPItol0XMw==", + "version": "4.86.1", + "resolved": "https://github.com/EdgeApp/react-native-zcash/releases/download/ironwood-ffi-6e29ab42/edge-currency-accountbased-4.86.1-ironwood-ac7aa5d5.tgz", + "integrity": "sha512-WP091BDa5XnhcWSMD1gnzQE7nn8IZHTdRGqE7En7pQv8f/L91XjvkJuy1d9VAW3K1G280dT7DIsOZbOX4mOZdA==", "license": "SEE LICENSE IN LICENSE", "dependencies": { "@bitcoinerlab/secp256k1": "^1.2.0", @@ -15456,6 +15456,7 @@ "ethereumjs-wallet": "^0.6.5", "ethers": "^5.6.0", "isomorphic-ws": "^5.0.0", + "react-native-zcash": "https://github.com/EdgeApp/react-native-zcash/releases/download/ironwood-ffi-2922143e/react-native-zcash-0.12.1-ironwood-124ee19.tgz", "rfc4648": "^1.5.0", "stellar-sdk": "^13.3.0", "tezos-uri": "^1.0.3", @@ -15473,7 +15474,7 @@ "react-native-monero": "^0.3.0", "react-native-piratechain": "v0.5.0", "react-native-zano": "^0.2.7", - "react-native-zcash": "^0.10.1" + "react-native-zcash": "^0.12.1" } }, "node_modules/edge-currency-accountbased/node_modules/isomorphic-ws": { @@ -24546,9 +24547,9 @@ } }, "node_modules/react-native-zcash": { - "version": "0.12.2", - "resolved": "https://registry.npmjs.org/react-native-zcash/-/react-native-zcash-0.12.2.tgz", - "integrity": "sha512-Fl99JoR7d9AYX/45UxDIjHbCT0K6SefxWKLV9z3J0X3UBGQImaguC8Zd1qLDJ9Ae5pBdD6t1Oj+o5BYBp+hT7w==", + "version": "0.12.1", + "resolved": "https://github.com/EdgeApp/react-native-zcash/releases/download/ironwood-ffi-6e29ab42/react-native-zcash-0.12.1-ironwood-91f50eb.tgz", + "integrity": "sha512-QQfeLzjH/5mEqIrdYcZpwYd1FFXODzG0hBq6TS/n6uOvh8xT+2jpJ0FB9RcBzDKrD9lTLc5oZX7eA9W8joPkpg==", "license": "MIT", "dependencies": { "biggystring": "^4.2.3", diff --git a/package.json b/package.json index 7e74850f60f..a0a1fac8080 100644 --- a/package.json +++ b/package.json @@ -105,7 +105,7 @@ "detect-bundler": "^1.1.0", "disklet": "^0.6.0", "edge-core-js": "^2.47.0", - "edge-currency-accountbased": "^4.86.0", + "edge-currency-accountbased": "https://github.com/EdgeApp/react-native-zcash/releases/download/ironwood-ffi-6e29ab42/edge-currency-accountbased-4.86.1-ironwood-ac7aa5d5.tgz", "edge-currency-plugins": "^3.11.0", "edge-exchange-plugins": "^2.51.0", "edge-info-server": "^3.12.0", @@ -170,7 +170,7 @@ "react-native-wheel-picker-android": "^2.0.6", "react-native-worklets": "^0.6.1", "react-native-zano": "^0.3.0", - "react-native-zcash": "^0.12.2", + "react-native-zcash": "https://github.com/EdgeApp/react-native-zcash/releases/download/ironwood-ffi-6e29ab42/react-native-zcash-0.12.1-ironwood-91f50eb.tgz", "react-redux": "^8.1.1", "redux": "^4.2.1", "redux-thunk": "^2.3.0", From 58a1bb58a09f746d3e16ccee031e44e72b28cd88 Mon Sep 17 00:00:00 2001 From: peachbits Date: Tue, 21 Jul 2026 12:34:19 -0700 Subject: [PATCH 4/4] Changelog Co-Authored-By: Claude Fable 5 --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fc34ebe29e7..2fb4ca06c8c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased (develop) +- added: Zcash: Orchard -> Ironwood (NU6.3) migration card on the wallet scene - when the engine reports a sweep is worthwhile, it prefills a locked max send-to-self through the ordinary send scene (recommended-tone: funds stay spendable either way). iOS only until the Android SDK ships Ironwood. + ## 4.50.0 (staging) - added: Changelly swap provider