Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

## Unreleased (develop)

- added: (Zcash) Guided Orchard → Ironwood (NU6.3) pool migration: privacy (scheduled transfers) and immediate paths, wallet-scene banner, notification-center card, and wallet menu entry (iOS; Android follows its SDK).

## 4.50.0 (staging)

- added: Changelly swap provider
Expand Down
19 changes: 10 additions & 9 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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-a9637998/edge-currency-accountbased-4.86.1-ironwood-6b67fcfa.tgz",
"edge-currency-plugins": "^3.11.0",
"edge-exchange-plugins": "^2.51.0",
"edge-info-server": "^3.12.0",
Expand Down Expand Up @@ -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-a9637998/react-native-zcash-0.12.1-ironwood-a416a51.tgz",
"react-redux": "^8.1.1",
"redux": "^4.2.1",
"redux-thunk": "^2.3.0",
Expand Down
95 changes: 95 additions & 0 deletions src/__tests__/zcashMigration.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { describe, expect, it } from '@jest/globals'
import type { EdgeCurrencyWallet } from 'edge-core-js'

import {
asZcashMigrationPlan,
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()
})

it('round-trips a migration plan through its cleaner', () => {
const plan = {
strategy: 'privacy' as const,
transfers: [
{
id: 't1',
amountZatoshi: '350000000',
nextExecutableAfterHeight: 2600100,
expiryHeight: 2610000
}
],
estimatedDurationHours: 24,
totalAmountZatoshi: '350000000',
noteSplitFeeZatoshi: '15000',
noteSplitRequired: true,
noteSplitProposalBase64: 'cHJvcG9zYWw=',
scheduleBase64: 'c2NoZWR1bGU='
}
expect(asZcashMigrationPlan(plan)).toEqual(plan)
})
})
9 changes: 9 additions & 0 deletions src/actions/WalletListMenuActions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export type WalletListMenuKey =
| 'getRawKeys'
| 'rawDelete'
| 'togglePause'
| 'ironwoodMigration'
| string // for split keys like splitbitcoincash, splitethereum, etc.

export function walletListMenuAction(
Expand Down Expand Up @@ -76,6 +77,14 @@ export function walletListMenuAction(
}
}

case 'ironwoodMigration': {
return async (dispatch, getState) => {
navigation.navigate('zcashMigrationOptions' as any, {
walletId
})
}
}

case 'rawDelete': {
return async (dispatch, getState) => {
const state = getState()
Expand Down
28 changes: 28 additions & 0 deletions src/components/Main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,10 @@ import { WcConnectionsScene as WcConnectionsSceneComponent } from './scenes/WcCo
import { WcConnectScene as WcConnectSceneComponent } from './scenes/WcConnectScene'
import { WcDisconnectScene as WcDisconnectSceneComponent } from './scenes/WcDisconnectScene'
import { WebViewScene as WebViewSceneComponent } from './scenes/WebViewScene'
import { ZcashMigrationInfoScene as ZcashMigrationInfoSceneComponent } from './scenes/ZcashMigrationInfoScene'
import { ZcashMigrationOptionsScene as ZcashMigrationOptionsSceneComponent } from './scenes/ZcashMigrationOptionsScene'
import { ZcashMigrationPlanConfirmScene as ZcashMigrationPlanConfirmSceneComponent } from './scenes/ZcashMigrationPlanConfirmScene'
import { ZcashMigrationStatusScene as ZcashMigrationStatusSceneComponent } from './scenes/ZcashMigrationStatusScene'
import { DeepLinkingManager } from './services/DeepLinkingManager'
import { useTheme } from './services/ThemeContext'
import { MenuTabs } from './themed/MenuTabs'
Expand Down Expand Up @@ -265,6 +269,14 @@ const MigrateWalletCompletionScene = ifLoggedIn(
const MigrateWalletSelectCryptoScene = ifLoggedIn(
MigrateWalletSelectCryptoSceneComponent
)
const ZcashMigrationInfoScene = ifLoggedIn(ZcashMigrationInfoSceneComponent)
const ZcashMigrationOptionsScene = ifLoggedIn(
ZcashMigrationOptionsSceneComponent
)
const ZcashMigrationPlanConfirmScene = ifLoggedIn(
ZcashMigrationPlanConfirmSceneComponent
)
const ZcashMigrationStatusScene = ifLoggedIn(ZcashMigrationStatusSceneComponent)
const NotificationCenterScene = ifLoggedIn(NotificationCenterSceneComponent)
const NotificationScene = ifLoggedIn(NotificationSceneComponent)
const OtpRepairScene = ifLoggedIn(OtpRepairSceneComponent)
Expand Down Expand Up @@ -1012,6 +1024,22 @@ const EdgeAppStack: React.FC = () => {
name="migrateWalletSelectCrypto"
component={MigrateWalletSelectCryptoScene}
/>
<AppStack.Screen
name="zcashMigrationOptions"
component={ZcashMigrationOptionsScene}
/>
<AppStack.Screen
name="zcashMigrationInfo"
component={ZcashMigrationInfoScene}
/>
<AppStack.Screen
name="zcashMigrationPlanConfirm"
component={ZcashMigrationPlanConfirmScene}
/>
<AppStack.Screen
name="zcashMigrationStatus"
component={ZcashMigrationStatusScene}
/>
<AppStack.Screen
name="notificationCenter"
component={NotificationCenterScene}
Expand Down
5 changes: 5 additions & 0 deletions src/components/modals/WalletListMenuModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,11 @@ export const WALLET_LIST_MENU: Array<{
label: lstrings.fragment_wallets_view_private_view_key,
value: 'viewPrivateViewKey'
},
{
pluginIds: ['zcash'],
label: lstrings.zcash_migration_menu_item,
value: 'ironwoodMigration'
},
{
label: lstrings.string_get_raw_keys,
value: 'getRawKeys'
Expand Down
44 changes: 44 additions & 0 deletions src/components/notification/NotificationView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { config } from '../../theme/appConfig'
import { useDispatch, useSelector } from '../../types/reactRedux'
import type { NavigationBase } from '../../types/routerTypes'
import { getThemedIconUri } from '../../util/CdnUris'
import { getWalletName } from '../../util/CurrencyWalletHelpers'
import { showOtpReminderModal } from '../../util/otpReminder'
import { openBrowserUri } from '../../util/WebUtils'
import { styled } from '../hoc/styled'
Expand Down Expand Up @@ -174,6 +175,18 @@ export const NotificationView: React.FC<Props> = props => {
}
})

// Zcash Ironwood migration cards per wallet
Object.keys(wallets).forEach(walletId => {
const migrationKey = `ironwoodMigration-${walletId}`
if (
notifState[migrationKey] != null &&
!notifState[migrationKey].isBannerHidden &&
!notifState[migrationKey].isCompleted
) {
visibleIds.push(migrationKey)
}
})

if (!otpReminder.isBannerHidden && !account.isDuressAccount)
visibleIds.push('otpReminder')

Expand Down Expand Up @@ -327,9 +340,40 @@ export const NotificationView: React.FC<Props> = props => {
)
}

if (id.startsWith('ironwoodMigration-')) {
const walletId = id.slice('ironwoodMigration-'.length)
const wallet = wallets[walletId]
if (wallet == null) return null

const handleCloseMigration = async (): Promise<void> => {
await writeAccountNotifInfo(account, id, { isBannerHidden: true })
}
const handlePressMigration = async (): Promise<void> => {
navigationDebounced.navigate('zcashMigrationOptions' as any, {
walletId
})
}

return (
<NotificationCard
key={id}
type="warning"
title={lstrings.zcash_migration_notif_title}
message={sprintf(
lstrings.zcash_migration_notif_body_1s,
getWalletName(wallet)
)}
onPress={handlePressMigration}
onDismiss={handleCloseMigration}
isPriority={isPriority}
/>
)
}

return null
},
[
account,
dispatch,
detectedTokensRedux,
handle2FaEnabledClose,
Expand Down
26 changes: 26 additions & 0 deletions src/components/scenes/NotificationCenterScene.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { config } from '../../theme/appConfig'
import { useDispatch, useSelector } from '../../types/reactRedux'
import type { EdgeAppSceneProps, NavigationBase } from '../../types/routerTypes'
import { getThemedIconUri } from '../../util/CdnUris.ts'
import { getWalletName } from '../../util/CurrencyWalletHelpers'
import { showOtpReminderModal } from '../../util/otpReminder.tsx'
import { openBrowserUri } from '../../util/WebUtils.ts'
import { SceneWrapper } from '../common/SceneWrapper'
Expand Down Expand Up @@ -242,6 +243,31 @@ export const NotificationCenterScene = (props: Props) => {
onClose={completeNotif(key)}
/>
)
} else if (key.includes('ironwoodMigration')) {
const { params } = notifState[key]
if (params == null) return null
const { walletId } = params
if (walletId == null || wallets[walletId] == null) return null
const wallet = wallets[walletId]

const handlePressMigration = async () => {
navigation.navigate('zcashMigrationOptions' as any, { walletId })
}

return (
<NotificationCenterRow
key={key}
date={date}
type="warning"
title={lstrings.zcash_migration_notif_title}
message={sprintf(
lstrings.zcash_migration_notif_body_1s,
getWalletName(wallet)
)}
onPress={handlePressMigration}
onClose={completeNotif(key)}
/>
)
} else if (key.includes('promoCard-')) {
// Handle promo card notifications
const { promoCard } = notifState[key].params ?? {}
Expand Down
Loading