             ledger.app.setExecutionMode(ledger.app.Modes.FirmwareUpdate)
             ledger.app.router.go '/update/index', {hidePreviousButton: yes}
           else
-            @navigateContinue()
+            @_navigateContinue()
+
+  _navigateError: ->
+    @_stopTimer()
+    ledger.app.router.go '/onboarding/device/failed'
 
   _listenEvents: ->
+    ledger.app.once 'dongle:connected', => @_navigateContinue()
+    ledger.app.once 'dongle:forged', => @_navigateForged()
+    ledger.app.once 'dongle:communication_error', => @_navigateError()
+
+  _stopTimer: ->
+    if @timer?
+      clearTimeout @timer
+      @timer = null
+
+  _launchAttestation: ->
     if ledger.app.wallet?
       ledger.app.performDongleAttestation()
-    ledger.app.once 'dongle:connected', => do @navigateContinue
-    ledger.app.once 'dongle:forged', => do @navigateError
-    ledger.app.once 'dongle:communication_error', => ledger.app.router.go '/onboarding/device/failed'+    @timer = setTimeout =>
+      # attestation timed out
+      @_navigateError()
+    , 30000

  defaultParams:
    animateIntro: no

  view:
    currentAction: "#current_action"

  onAfterRender: ->
    super
    do @_listenEvents
    @view.spinner = ledger.spinners.createLargeSpinner(@select('div.greyed-container')[0])
    @view.currentAction.text(t 'onboarding.device.connecting.is_connecting')

  openSupport: ->
    window.open t 'application.support_url'

    ledger.app.off 'dongle:forged'
    ledger.app.off 'dongle:communication_error'

  _navigateContinue: ->
    @_stopTimer()
    ledger.app.wallet?.isFirmwareUpdateAvailable (isAvailable) =>
      if isAvailable
        ledger.app.router.go '/onboarding/device/update'
