diff --git a/src/app/modules/main/wallet_section/send_new/module.nim b/src/app/modules/main/wallet_section/send_new/module.nim index 537b9e8e4cf..b3d0c151761 100644 --- a/src/app/modules/main/wallet_section/send_new/module.nim +++ b/src/app/modules/main/wallet_section/send_new/module.nim @@ -190,7 +190,7 @@ method authenticateAndTransfer*(self: Module, uuid: string, fromAddr: string) = self.controller.authenticate() method onUserAuthenticated*(self: Module, password: string, pin: string) = - if password.len == 0: + if password.len == 0 and pin.len == 0: self.transactionWasSent(uuid = self.tmpSendTransactionDetails.uuid, chainId = 0, approvalTx = false, txHash = "", error = authenticationCanceled) self.clearTmpData() else: diff --git a/src/app/modules/shared_modules/keycard_popup/internal/insert_keycard_state.nim b/src/app/modules/shared_modules/keycard_popup/internal/insert_keycard_state.nim index 12b4795cdca..328663fa560 100644 --- a/src/app/modules/shared_modules/keycard_popup/internal/insert_keycard_state.nim +++ b/src/app/modules/shared_modules/keycard_popup/internal/insert_keycard_state.nim @@ -43,6 +43,20 @@ method resolveKeycardNextState*(self: InsertKeycardState, keycardFlowType: strin return nil if keycardFlowType == ResponseTypeValueCardInserted: controller.setKeycardData(updatePredefinedKeycardData(controller.getKeycardData(), PredefinedKeycardData.WronglyInsertedCard, add = false)) + + # Special handling for LoadAccount flow - return to the state we came from + # (RepeatPin or PinSet) to continue waiting for ENTER_MNEMONIC event + if (self.flowType == FlowType.SetupNewKeycard or + self.flowType == FlowType.SetupNewKeycardNewSeedPhrase or + self.flowType == FlowType.SetupNewKeycardOldSeedPhrase) and + controller.getCurrentKeycardServiceFlow() == KCSFlowType.LoadAccount and + not self.getBackState.isNil: + let backStateType = self.getBackState.stateType + if backStateType == StateType.RepeatPin or backStateType == StateType.PinSet: + # Return to the previous state to continue waiting for mnemonic entry + return self.getBackState + + # Default behavior for other flows if self.flowType == FlowType.SetupNewKeycard: return createState(StateType.KeycardInserted, self.flowType, self.getBackState) return createState(StateType.KeycardInserted, self.flowType, nil) diff --git a/src/app/modules/shared_modules/keycard_popup/internal/pin_set_state.nim b/src/app/modules/shared_modules/keycard_popup/internal/pin_set_state.nim index 8644563e7af..359229d6a57 100644 --- a/src/app/modules/shared_modules/keycard_popup/internal/pin_set_state.nim +++ b/src/app/modules/shared_modules/keycard_popup/internal/pin_set_state.nim @@ -31,4 +31,28 @@ method executeCancelCommand*(self: PinSetState, controller: Controller) = self.flowType == FlowType.SetupNewKeycardNewSeedPhrase or self.flowType == FlowType.SetupNewKeycardOldSeedPhrase or self.flowType == FlowType.UnlockKeycard: - controller.terminateCurrentFlow(lastStepInTheCurrentFlow = false) \ No newline at end of file + controller.terminateCurrentFlow(lastStepInTheCurrentFlow = false) + +method resolveKeycardNextState*(self: PinSetState, keycardFlowType: string, keycardEvent: KeycardEvent, + controller: Controller): State = + # Handle temporary card disconnection during LoadAccount flow (after card initialization) + # This can happen if the user hasn't tapped "Continue" yet and the card disconnects + if self.flowType == FlowType.SetupNewKeycard or + self.flowType == FlowType.SetupNewKeycardNewSeedPhrase or + self.flowType == FlowType.SetupNewKeycardOldSeedPhrase: + # INSERT_CARD during LoadAccount flow means card is reconnecting after initialization + if keycardFlowType == ResponseTypeValueInsertCard and + keycardEvent.error.len > 0 and + keycardEvent.error == ErrorConnection and + controller.getCurrentKeycardServiceFlow() == KCSFlowType.LoadAccount: + # Don't cancel the flow - transition to InsertKeycard state and wait for reconnection + controller.reRunCurrentFlowLater() + return createState(StateType.InsertKeycard, self.flowType, self) + # CARD_INSERTED after temporary disconnection - stay in PinSet and continue + if keycardFlowType == ResponseTypeValueCardInserted and + controller.getCurrentKeycardServiceFlow() == KCSFlowType.LoadAccount: + # Card reconnected successfully, stay in PinSet + return nil + + # No specific handling needed - this state transitions via primary button + return nil \ No newline at end of file diff --git a/src/app/modules/shared_modules/keycard_popup/internal/repeat_pin_state.nim b/src/app/modules/shared_modules/keycard_popup/internal/repeat_pin_state.nim index 67a4ab5833d..a9c6592d1c1 100644 --- a/src/app/modules/shared_modules/keycard_popup/internal/repeat_pin_state.nim +++ b/src/app/modules/shared_modules/keycard_popup/internal/repeat_pin_state.nim @@ -42,6 +42,25 @@ method executeCancelCommand*(self: RepeatPinState, controller: Controller) = method resolveKeycardNextState*(self: RepeatPinState, keycardFlowType: string, keycardEvent: KeycardEvent, controller: Controller): State = + # Handle temporary card disconnection during LoadAccount flow (after card initialization) + # This happens on Android/iOS when card is disconnected and needs to be re-detected + if self.flowType == FlowType.SetupNewKeycard or + self.flowType == FlowType.SetupNewKeycardNewSeedPhrase or + self.flowType == FlowType.SetupNewKeycardOldSeedPhrase: + # INSERT_CARD during LoadAccount flow means card is reconnecting after initialization + if keycardFlowType == ResponseTypeValueInsertCard and + keycardEvent.error.len > 0 and + keycardEvent.error == ErrorConnection and + controller.getCurrentKeycardServiceFlow() == KCSFlowType.LoadAccount: + # Don't cancel the flow - transition to InsertKeycard state and wait for reconnection + controller.reRunCurrentFlowLater() + return createState(StateType.InsertKeycard, self.flowType, self) + # CARD_INSERTED after temporary disconnection - stay in RepeatPin and continue waiting + if keycardFlowType == ResponseTypeValueCardInserted and + controller.getCurrentKeycardServiceFlow() == KCSFlowType.LoadAccount: + # Card reconnected successfully, continue waiting for ENTER_MNEMONIC event + return nil + let state = ensureReaderAndCardPresence(self, keycardFlowType, keycardEvent, controller) if not state.isNil: return state diff --git a/src/app_service/service/keycard/service.nim b/src/app_service/service/keycard/service.nim index 2767ea6e1c5..32eec8db972 100644 --- a/src/app_service/service/keycard/service.nim +++ b/src/app_service/service/keycard/service.nim @@ -151,8 +151,15 @@ QtObject: return seedPhrase proc updateLocalPayloadForCurrentFlow(self: Service, obj: JsonNode, cleanBefore = false) {.featureGuard(KEYCARD_ENABLED).} = + # CRITICAL FIX: Check if obj is the same reference as setPayloadForCurrentFlow + # This happens when onTimeout calls startFlow(self.setPayloadForCurrentFlow) + # If we iterate and modify the same object, the iterator gets corrupted! + if cast[pointer](obj) == cast[pointer](self.setPayloadForCurrentFlow): + return + if cleanBefore: self.setPayloadForCurrentFlow = %* {} + for k, v in obj: self.setPayloadForCurrentFlow[k] = v