Skip to content

Commit

Permalink
feat: Support creating Ledger Nano profiles (#8003)
Browse files Browse the repository at this point in the history
* refactor(2.0): Dynamic password

* refactor(2.0): Remove Profile Manager (wip)

* clean up and fix

* clean up

* clean up more code

* clean up

* adapt and remove for profile-manager related code

* clean up more code

* fmt

* more cleanup

* more cleanup

* small tweaks

* more cleanup

* format

* update

* clean up

* https://api.testnet.shimmer.network

* revert Wallet.svelte

* clean up

* remove account debris

* feat: Profile-centric file scheme for wallets

* chore: Clean up 2.0 comments

* more

* more

* more

* works

* clean up

* clean up

* clean up

* chore: Simplify path management

* fmt

* chore: Adapt to sdk changes

* fmt

* clean up

* feat: Restore from mnemonic

* enable features

* feat: WIP Support for LedgerNano profiles

* fixes and improvements

* small improvements

* mm

* I think this is working

* clean up

* tweaks

* fix: Sync with SDK types

* fmt

* tweaks

---------

Co-authored-by: Begoña Álvarez de la Cruz <[email protected]>
Co-authored-by: cpl121 <[email protected]>
  • Loading branch information
3 people authored Mar 7, 2024
1 parent bf03f93 commit 81e4d8e
Show file tree
Hide file tree
Showing 22 changed files with 135 additions and 102 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
const STEPS = [1, 2, 3, 4]
$: if ($ledgerNanoStatus.blindSigningEnabled) {
closePopup()
closePopup(true)
checkOrConnectLedger(async () => {
try {
if ($ledgerPreparedOutput) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import { localize } from '@core/i18n'
import { formatHexString } from '@core/utils'
import { onDestroy } from 'svelte'
import { showInternalVerificationPopup, resetShowInternalVerificationPopup } from '@core/ledger'
import { verificationPopupMode, resetShowInternalVerificationPopup, VerificationPopupMode } from '@core/ledger'
import { TextHintVariant, TextType } from '@ui/enums'
import { AnimationEnum } from '@auxiliary/animation'
Expand All @@ -12,10 +12,18 @@
export let hash: string
const hasSendConfirmationProps = (toAddress && toAmount) || hash
const locale = $showInternalVerificationPopup
? 'popups.verifyInternalLedgerTransaction'
: 'popups.verifyLedgerTransaction'
let locale
$: switch ($verificationPopupMode) {
case VerificationPopupMode.Block:
locale = 'popups.verifyLedgerBlock'
break
case VerificationPopupMode.Internal:
locale = 'popups.verifyInternalLedgerTransaction'
break
case VerificationPopupMode.Default:
locale = 'popups.verifyLedgerTransaction'
break
}
onDestroy(() => {
resetShowInternalVerificationPopup()
Expand All @@ -36,7 +44,7 @@
<KeyValueBox keyText={localize('general.sendTo')} valueText={toAddress} />
<KeyValueBox keyText={localize('general.amount')} valueText={toAmount} />
{/if}
{:else if $showInternalVerificationPopup}
{:else if $verificationPopupMode === VerificationPopupMode.Internal}
<TextHint variant={TextHintVariant.Info} text={localize('popups.verifyInternalLedgerTransaction.hint')} />
{/if}
</div>
2 changes: 1 addition & 1 deletion packages/desktop/features/onboarding.features.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ const onboardingFeaturesForTestnet: IOnboardingFeaturesForNetwork = {
enabled: true,
},
ledgerProfile: {
enabled: false,
enabled: true,
},
},
restoreProfile: {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,34 @@
<script lang="ts">
import { Button, FontWeight, PasswordInput, Text, TextType } from '@ui'
import { Button, FontWeight, PasswordInput, Text, TextType, LedgerAnimation } from '@ui'
import { localize } from '@core/i18n'
import { selectedWallet, selectedWalletId } from '@core/wallet'
import { unlockStronghold, updateActiveWallet } from '@core/profile'
import { Icon } from '@auxiliary/icon'
import { LedgerAppName, ledgerAppName } from '@core/ledger'
import { IllustrationEnum } from '@auxiliary/illustration'
import { unlockStronghold, updateActiveWallet, isSoftwareProfile } from '@core/profile'
import { OutputId } from '@iota/sdk/out/types'
export let outputId: string | undefined
let error = ''
let isBusy = false
let strongholdPassword = ''
$: validStronghold = $isSoftwareProfile ? strongholdPassword && strongholdPassword.length !== 0 : true
$: disabledActive = !validStronghold || isBusy
$: iconNetwork = $ledgerAppName === LedgerAppName.Shimmer ? Icon.Shimmer : Icon.Iota
async function unlockWalletAndCreateAccount(): Promise<void> {
isBusy = true
error = ''
try {
if (!strongholdPassword || $selectedWallet?.implicitAccountOutputs.length === 0) return
if ($selectedWallet?.implicitAccountOutputs.length === 0) return
if ($isSoftwareProfile) {
if (!strongholdPassword) return
await unlockStronghold(strongholdPassword)
}
let outputIdForTransition: OutputId
await unlockStronghold(strongholdPassword)
if (outputId) {
outputIdForTransition = $selectedWallet?.implicitAccountOutputs.find(
(implicitAccounts) => implicitAccounts.outputId.toString() === outputId
Expand All @@ -42,31 +52,32 @@
<step-content class="flex flex-col items-center justify-between h-full pt-28">
<div class="flex flex-col h-full justify-between space-y-8">
<div class="flex flex-col text-center justify-center px-4 space-y-9 max-w-md">
<div class="flex items-center justify-center">
<img
src="assets/illustrations/implicit-account-creation/step3.svg"
alt={localize('views.implicit-account-creation.steps.step3.title')}
{#if $isSoftwareProfile}
<div class="flex items-center justify-center">
<img
src="assets/illustrations/implicit-account-creation/step3.svg"
alt={localize('views.implicit-account-creation.steps.step3.title')}
/>
</div>
<Text type={TextType.h3} fontWeight={FontWeight.semibold}
>{localize('views.implicit-account-creation.steps.step3.view.title')}</Text
>
<PasswordInput
bind:error
bind:value={strongholdPassword}
autofocus
submitHandler={unlockWalletAndCreateAccount}
placeholder={localize('views.implicit-account-creation.steps.step3.view.placeholder')}
disabled={$selectedWallet?.hasImplicitAccountCreationTransactionInProgress}
/>
</div>
<Text type={TextType.h3} fontWeight={FontWeight.semibold}
>{localize('views.implicit-account-creation.steps.step3.view.title')}</Text
>
<PasswordInput
bind:error
bind:value={strongholdPassword}
autofocus
submitHandler={unlockWalletAndCreateAccount}
placeholder={localize('views.implicit-account-creation.steps.step3.view.placeholder')}
disabled={$selectedWallet?.hasImplicitAccountCreationTransactionInProgress}
/>
{:else}
<LedgerAnimation illustration={IllustrationEnum.LedgerConnected2Desktop} {iconNetwork} />
{/if}
<button-wrapper class="flex items-center justify-center">
<Button onClick={unlockWalletAndCreateAccount} disabled={disabledActive} {isBusy}
>{localize('views.implicit-account-creation.steps.step2.view.action')}</Button
>
</button-wrapper>
</div>
<button-wrapper class="flex items-center justify-center">
<Button
onClick={unlockWalletAndCreateAccount}
disabled={!strongholdPassword || strongholdPassword.length === 0 || isBusy}
isBusy={$selectedWallet?.hasImplicitAccountCreationTransactionInProgress}
>{localize('views.implicit-account-creation.steps.step2.view.action')}</Button
></button-wrapper
>
</div>
</step-content>
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
import { createFromLedgerRouter } from '@core/router'
import { Icon as IconEnum } from '@auxiliary/icon'
import { IllustrationEnum } from '@auxiliary/illustration'
import { buildOnboardingSecretManager, onboardingProfileSecretManager } from '@contexts/onboarding'
import { get } from 'svelte/store'
function onContinueClick(): void {
$createFromLedgerRouter.next()
Expand All @@ -24,8 +26,9 @@
})
}
onMount(() => {
pollLedgerNanoStatus()
onMount(async () => {
await buildOnboardingSecretManager()
pollLedgerNanoStatus(get(onboardingProfileSecretManager))
})
</script>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
import { Icon as IconEnum } from '@lib/auxiliary/icon'
import { Position, TextHintVariant } from '@ui/enums'
import { localize } from '@core/i18n'
import { showInternalVerificationPopup } from '@core/ledger'
import { VerificationPopupMode, verificationPopupMode } from '@core/ledger'
import { checkActiveProfileAuth, isActiveLedgerProfile } from '@core/profile'
import { closePopup, openPopup, PopupId } from '@auxiliary/popup'
Expand Down Expand Up @@ -45,7 +45,7 @@
function onClaimClick(): void {
if ($isActiveLedgerProfile) {
$showInternalVerificationPopup = true
$verificationPopupMode = VerificationPopupMode.Internal
}
checkActiveProfileAuth(() => claimActivity(activity))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export async function buildOnboardingSecretManager(): Promise<void> {
const { strongholdPassword, secretManagerOptions, mnemonic } = profile
const mnemonicStringified = mnemonic?.join(' ') ?? ''

if (!strongholdPassword || !secretManagerOptions || !mnemonic) {
if (!secretManagerOptions) {
return
}

Expand All @@ -24,11 +24,13 @@ export async function buildOnboardingSecretManager(): Promise<void> {
await secretManager.setStrongholdPassword(strongholdPassword)
}

// 3. Verify Mnemonic
await verifyMnemonic(mnemonicStringified)
if (mnemonicStringified) {
// 3. Verify Mnemonic
await verifyMnemonic(mnemonicStringified)

// 4. Store Mnemonic
await secretManager.storeMnemonic(mnemonicStringified)
// 4. Store Mnemonic
await secretManager.storeMnemonic(mnemonicStringified)
}

onboardingProfileSecretManager.set(secretManager)
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ import { getLedgerNanoStatus } from '@core/secret-manager'
import { SecretManager } from '@iota/sdk'
import { resetLedgerNanoStatus, updateLedgerNanoStatus } from '../stores'

export async function getAndUpdateLedgerNanoStatus(secretManager: SecretManager, forwardErrors = false): Promise<void> {
export async function getAndUpdateLedgerNanoStatus(
secretManager?: SecretManager,
forwardErrors = false
): Promise<void> {
try {
const ledgerNanoStatusResponse = await getLedgerNanoStatus(secretManager)
updateLedgerNanoStatus(ledgerNanoStatusResponse)
Expand Down
1 change: 0 additions & 1 deletion packages/shared/lib/core/ledger/actions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,3 @@ export * from './checkOrConnectLedger'
export * from './displayNotificationForLedgerProfile'
export * from './getLedgerDeviceStatus'
export * from './pollLedgerNanoStatus'
export * from './promptUserToConnectLedger'
11 changes: 4 additions & 7 deletions packages/shared/lib/core/ledger/actions/pollLedgerNanoStatus.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,19 @@
import { get } from 'svelte/store'
import { DEFAULT_LEDGER_NANO_STATUS_POLL_INTERVAL } from '../constants'
import { deconstructLedgerNanoStatusPollingConfiguration } from '../helpers'
import { ILedgerNanoStatusPollingConfiguration } from '../interfaces'
import { isPollingLedgerDeviceStatus, ledgerNanoStatus } from '../stores'
import { getAndUpdateLedgerNanoStatus } from './getAndUpdateLedgerNanoStatus'
import { SecretManager } from '@iota/sdk'

let timeoutTimer: ReturnType<typeof setTimeout> | undefined

export function pollLedgerNanoStatus(config?: ILedgerNanoStatusPollingConfiguration): void {
const { pollInterval, secretManager } = deconstructLedgerNanoStatusPollingConfiguration(config)

const defaultPollInterval = pollInterval || DEFAULT_LEDGER_NANO_STATUS_POLL_INTERVAL
export function pollLedgerNanoStatus(secretManager?: SecretManager): void {
const defaultPollInterval = DEFAULT_LEDGER_NANO_STATUS_POLL_INTERVAL
const slowedPollInterval = 10 * defaultPollInterval

if (!get(isPollingLedgerDeviceStatus)) {
isPollingLedgerDeviceStatus.set(true)
const pollingFunction = async (): Promise<void> => {
await getAndUpdateLedgerNanoStatus(get(secretManager))
await getAndUpdateLedgerNanoStatus(secretManager)
const isLedgerBusy = get(ledgerNanoStatus)?.busy
const currentPollInterval = isLedgerBusy ? slowedPollInterval : defaultPollInterval
timeoutTimer = setTimeout(() => void pollingFunction(), currentPollInterval)
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1 +1 @@
export const USE_LEDGER_SIMULATOR = false
export const USE_LEDGER_SIMULATOR = true
1 change: 0 additions & 1 deletion packages/shared/lib/core/ledger/helpers/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
export * from './deconstructLedgerNanoStatusPollingConfiguration'
export * from './deconstructLedgerVerificationProps'
export * from './deriveLedgerError'
2 changes: 1 addition & 1 deletion packages/shared/lib/core/ledger/stores/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@ export * from './is-polling-ledger-device-status.store'
export * from './ledger-connection-state.store'
export * from './ledger-nano-status.store'
export * from './ledger-prepared-output.store'
export * from './show-internal-verification-popup'
export * from './verification-popup-mode'
export * from './ledger-app-name.store'

This file was deleted.

13 changes: 13 additions & 0 deletions packages/shared/lib/core/ledger/stores/verification-popup-mode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { writable } from 'svelte/store'

export enum VerificationPopupMode {
Default,
Internal,
Block,
}

export const verificationPopupMode = writable<VerificationPopupMode>(VerificationPopupMode.Default)

export function resetShowInternalVerificationPopup(): void {
verificationPopupMode.set(VerificationPopupMode.Default)
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { LedgerConnectionState } from '../interfaces'
import { LedgerAppName } from '../enums'
import { LedgerNanoStatus } from '@iota/sdk/out/types'
import { USE_LEDGER_SIMULATOR } from '../constants'
import { LedgerAppName } from '../enums'
import { LedgerConnectionState } from '../interfaces'

export function determineLedgerConnectionState(
status: LedgerNanoStatus,
Expand All @@ -9,10 +10,18 @@ export function determineLedgerConnectionState(
const { connected, app } = status
if (connected) {
if (app) {
if (app.name === appName) {
return LedgerConnectionState.CorrectAppOpen
if (USE_LEDGER_SIMULATOR) {
if (app?.version === '0.8.7') {
return LedgerConnectionState.CorrectAppOpen
} else {
return LedgerConnectionState.AppNotOpen
}
} else {
return LedgerConnectionState.AppNotOpen
if (app.name === appName) {
return LedgerConnectionState.CorrectAppOpen
} else {
return LedgerConnectionState.AppNotOpen
}
}
} else {
return LedgerConnectionState.Locked
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { LedgerNanoStatus } from '@iota/sdk/out/types'
import { get } from 'svelte/store'
import { activeProfileSecretManager } from '../stores'

// TODO(2.0): Fix all of usages of this
export async function getLedgerNanoStatus(
secretManager = get(activeProfileSecretManager)
): Promise<LedgerNanoStatus | null> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,15 @@ let oldSecretManagerOptions: SecretManagerType | null = null
// activeProfileSecretManager is used outside of svelte components too
activeProfile.subscribe((profile) => {
// Dont create a new instance of secretManager when it is already initialized
if (profile.secretManagerOptions && oldSecretManagerOptions !== profile.secretManagerOptions) {
api.createSecretManager(profile.secretManagerOptions).then((secretManager) => {
activeProfileSecretManager.set(secretManager)
})
oldSecretManagerOptions = profile.secretManagerOptions
if (profile.secretManagerOptions) {
if (oldSecretManagerOptions !== profile.secretManagerOptions) {
api.createSecretManager(profile.secretManagerOptions).then((secretManager) => {
activeProfileSecretManager.set(secretManager)
})
oldSecretManagerOptions = profile.secretManagerOptions
}
} else {
activeProfileSecretManager.set(null)
oldSecretManagerOptions = null
}
})
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,6 @@ export async function handleNewOutputEventInternal(walletId: string, payload: Ne
isTransferring: false,
})
}
closePopup() // close ActivateAccountPopup when the account output is created
}
}

Expand Down
Loading

0 comments on commit 81e4d8e

Please sign in to comment.