@onekeyfe/hd-core 1.2.0-alpha.67 → 1.2.0-alpha.69

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (316) hide show
  1. package/__tests__/AllNetworkGetAddressBase.tracing.test.ts +313 -0
  2. package/__tests__/DeviceCommands.test.ts +59 -13
  3. package/__tests__/DeviceEventRegistration.test.ts +6 -0
  4. package/__tests__/check-all-firmware-release-protocol-v2.test.ts +226 -3
  5. package/__tests__/connectSettings.test.ts +5 -47
  6. package/__tests__/core-initialization.test.ts +23 -0
  7. package/__tests__/device-connector-protocol.test.ts +81 -0
  8. package/__tests__/device-lifecycle-events.test.ts +105 -3
  9. package/__tests__/device-pool-state.test.ts +22 -0
  10. package/__tests__/device-settings.test.ts +39 -18
  11. package/__tests__/device-state-contract.test.ts +1 -0
  12. package/__tests__/device-state-mapper.test.ts +13 -1
  13. package/__tests__/device-utils.test.ts +48 -1
  14. package/__tests__/deviceSettings.test.ts +11 -1
  15. package/__tests__/deviceUploadNft.test.ts +316 -0
  16. package/__tests__/firmware-update/firmware-update-v2-download-before-boot.test.ts +31 -59
  17. package/__tests__/get-device-state.test.ts +132 -12
  18. package/__tests__/homescreen.test.ts +41 -1
  19. package/__tests__/kaspa-use-tweak-pro2.test.ts +2 -2
  20. package/__tests__/kaspaSignTransaction.test.ts +3 -26
  21. package/__tests__/logBlockEvent.test.ts +1 -1
  22. package/__tests__/method-protocol-support.test.ts +19 -0
  23. package/__tests__/open-wallet-session.test.ts +568 -37
  24. package/__tests__/pro2Nft.test.ts +108 -0
  25. package/__tests__/protocol-binding.test.ts +89 -0
  26. package/__tests__/protocol-v2-firmware-targets.test.ts +33 -1
  27. package/__tests__/protocol-v2-resources.test.ts +343 -0
  28. package/__tests__/protocol-v2-ui-lifecycle.test.ts +91 -0
  29. package/__tests__/protocol-v2-unlock-policy.test.ts +300 -37
  30. package/__tests__/protocol-v2.test.ts +1586 -1213
  31. package/__tests__/protocolV2FileWrite.test.ts +193 -0
  32. package/__tests__/protocolV2UiInteraction.test.ts +63 -0
  33. package/__tests__/public-pro2-api-boundary.test.ts +1 -0
  34. package/__tests__/search-devices.test.ts +12 -3
  35. package/__tests__/tron-sign-message-init.test.ts +2 -2
  36. package/__tests__/uiProgressThrottle.test.ts +74 -0
  37. package/__tests__/uiPromiseRegistry.test.ts +115 -0
  38. package/dist/api/BaseMethod.d.ts +3 -1
  39. package/dist/api/BaseMethod.d.ts.map +1 -1
  40. package/dist/api/CheckAllFirmwareRelease.d.ts +3 -1
  41. package/dist/api/CheckAllFirmwareRelease.d.ts.map +1 -1
  42. package/dist/api/DetectDeviceConnectProtocol.d.ts +7 -0
  43. package/dist/api/DetectDeviceConnectProtocol.d.ts.map +1 -0
  44. package/dist/api/FirmwareUpdate.d.ts.map +1 -1
  45. package/dist/api/FirmwareUpdateV2.d.ts +0 -10
  46. package/dist/api/FirmwareUpdateV2.d.ts.map +1 -1
  47. package/dist/api/FirmwareUpdateV3.d.ts +3 -7
  48. package/dist/api/FirmwareUpdateV3.d.ts.map +1 -1
  49. package/dist/api/FirmwareUpdateV4.d.ts +15 -27
  50. package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
  51. package/dist/api/GetPassphraseState.d.ts.map +1 -1
  52. package/dist/api/OpenWalletSession.d.ts.map +1 -1
  53. package/dist/api/SearchDevices.d.ts.map +1 -1
  54. package/dist/api/allnetwork/AllNetworkGetAddressBase.d.ts.map +1 -1
  55. package/dist/api/device/DeviceSettings.d.ts.map +1 -1
  56. package/dist/api/device/DeviceUnlock.d.ts +2 -2
  57. package/dist/api/device/DeviceUnlock.d.ts.map +1 -1
  58. package/dist/api/device/DeviceUpdateBootloader.d.ts +1 -5
  59. package/dist/api/device/DeviceUpdateBootloader.d.ts.map +1 -1
  60. package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts +4 -11
  61. package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts.map +1 -1
  62. package/dist/api/firmware/getBinary.d.ts +0 -7
  63. package/dist/api/firmware/getBinary.d.ts.map +1 -1
  64. package/dist/api/firmware/updateBootloader.d.ts +0 -2
  65. package/dist/api/firmware/updateBootloader.d.ts.map +1 -1
  66. package/dist/api/firmware/uploadFirmware.d.ts +1 -9
  67. package/dist/api/firmware/uploadFirmware.d.ts.map +1 -1
  68. package/dist/api/helpers/protocolV2FileWrite.d.ts +9 -0
  69. package/dist/api/helpers/protocolV2FileWrite.d.ts.map +1 -1
  70. package/dist/api/index.d.ts +2 -0
  71. package/dist/api/index.d.ts.map +1 -1
  72. package/dist/api/kaspa/KaspaGetAddress.d.ts +1 -1
  73. package/dist/api/kaspa/KaspaSignTransaction.d.ts +1 -1
  74. package/dist/api/kaspa/KaspaSignTransaction.d.ts.map +1 -1
  75. package/dist/api/polkadot/PolkadotGetAddress.d.ts +1 -1
  76. package/dist/api/polkadot/networks.d.ts +1 -1
  77. package/dist/api/protocol-v2/DeviceUploadNft.d.ts +31 -0
  78. package/dist/api/protocol-v2/DeviceUploadNft.d.ts.map +1 -0
  79. package/dist/api/protocol-v2/ProtocolInfoRequest.d.ts.map +1 -1
  80. package/dist/api/protocol-v2/helpers.d.ts.map +1 -1
  81. package/dist/api/solana/SolSignMessage.d.ts +1 -1
  82. package/dist/api/solana/SolSignOffchainMessage.d.ts +1 -1
  83. package/dist/api/solana/SolSignTransaction.d.ts +2 -2
  84. package/dist/api/stellar/StellarSignTransaction.d.ts +1 -1
  85. package/dist/api/sui/SuiGetAddress.d.ts +1 -1
  86. package/dist/api/sui/SuiGetPublicKey.d.ts +1 -1
  87. package/dist/api/sui/SuiSignMessage.d.ts +1 -1
  88. package/dist/api/sui/SuiSignTransaction.d.ts +1 -1
  89. package/dist/api/ton/TonGetAddress.d.ts +1 -1
  90. package/dist/api/ton/TonSignMessage.d.ts +1 -1
  91. package/dist/api/ton/TonSignProof.d.ts +1 -1
  92. package/dist/api/tron/TronSignMessage.d.ts +2 -2
  93. package/dist/api/tron/TronSignTransaction.d.ts +1 -1
  94. package/dist/core/deviceEventRegistration.d.ts +2 -0
  95. package/dist/core/deviceEventRegistration.d.ts.map +1 -1
  96. package/dist/core/index.d.ts.map +1 -1
  97. package/dist/core/uiPromiseRegistry.d.ts +6 -0
  98. package/dist/core/uiPromiseRegistry.d.ts.map +1 -0
  99. package/dist/data-manager/DataManager.d.ts +9 -4
  100. package/dist/data-manager/DataManager.d.ts.map +1 -1
  101. package/dist/data-manager/TransportManager.d.ts.map +1 -1
  102. package/dist/data-manager/connectSettings.d.ts.map +1 -1
  103. package/dist/device/Device.d.ts +29 -6
  104. package/dist/device/Device.d.ts.map +1 -1
  105. package/dist/device/DeviceCommands.d.ts +4 -4
  106. package/dist/device/DeviceCommands.d.ts.map +1 -1
  107. package/dist/device/DeviceConnector.d.ts +2 -1
  108. package/dist/device/DeviceConnector.d.ts.map +1 -1
  109. package/dist/device/DevicePool.d.ts.map +1 -1
  110. package/dist/deviceProfile/protocolV2DeviceIdentity.d.ts.map +1 -1
  111. package/dist/events/device.d.ts +4 -0
  112. package/dist/events/device.d.ts.map +1 -1
  113. package/dist/events/logBlockEvent.d.ts.map +1 -1
  114. package/dist/events/ui-promise.d.ts +4 -2
  115. package/dist/events/ui-promise.d.ts.map +1 -1
  116. package/dist/events/ui-request.d.ts +31 -2
  117. package/dist/events/ui-request.d.ts.map +1 -1
  118. package/dist/events/ui-response.d.ts +10 -2
  119. package/dist/events/ui-response.d.ts.map +1 -1
  120. package/dist/index.d.ts +243 -247
  121. package/dist/index.d.ts.map +1 -1
  122. package/dist/index.js +3135 -3785
  123. package/dist/inject.d.ts +6 -1
  124. package/dist/inject.d.ts.map +1 -1
  125. package/dist/lowLevelInject.d.ts.map +1 -1
  126. package/dist/protocols/protocol-v2/features.d.ts +0 -4
  127. package/dist/protocols/protocol-v2/features.d.ts.map +1 -1
  128. package/dist/protocols/protocol-v2/firmware.d.ts.map +1 -1
  129. package/dist/protocols/protocol-v2/resources.d.ts +30 -0
  130. package/dist/protocols/protocol-v2/resources.d.ts.map +1 -0
  131. package/dist/protocols/protocol-v2/uiInteraction.d.ts +7 -4
  132. package/dist/protocols/protocol-v2/uiInteraction.d.ts.map +1 -1
  133. package/dist/protocols/protocol-v2/unlockPolicy.d.ts +1 -3
  134. package/dist/protocols/protocol-v2/unlockPolicy.d.ts.map +1 -1
  135. package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts +22 -0
  136. package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts.map +1 -0
  137. package/dist/protocols/protocol-v2/walletSession.d.ts +6 -1
  138. package/dist/protocols/protocol-v2/walletSession.d.ts.map +1 -1
  139. package/dist/topLevelInject.d.ts.map +1 -1
  140. package/dist/types/api/checkAllFirmwareRelease.d.ts +3 -5
  141. package/dist/types/api/checkAllFirmwareRelease.d.ts.map +1 -1
  142. package/dist/types/api/detectDeviceConnectProtocol.d.ts +4 -0
  143. package/dist/types/api/detectDeviceConnectProtocol.d.ts.map +1 -0
  144. package/dist/types/api/deviceUnlock.d.ts +5 -1
  145. package/dist/types/api/deviceUnlock.d.ts.map +1 -1
  146. package/dist/types/api/deviceUpdateBootloader.d.ts +0 -6
  147. package/dist/types/api/deviceUpdateBootloader.d.ts.map +1 -1
  148. package/dist/types/api/export.d.ts +2 -4
  149. package/dist/types/api/export.d.ts.map +1 -1
  150. package/dist/types/api/firmwareUpdate.d.ts +4 -70
  151. package/dist/types/api/firmwareUpdate.d.ts.map +1 -1
  152. package/dist/types/api/index.d.ts +7 -10
  153. package/dist/types/api/index.d.ts.map +1 -1
  154. package/dist/types/api/protocolV2.d.ts +3 -0
  155. package/dist/types/api/protocolV2.d.ts.map +1 -1
  156. package/dist/types/device.d.ts +2 -2
  157. package/dist/types/device.d.ts.map +1 -1
  158. package/dist/types/params.d.ts +1 -0
  159. package/dist/types/params.d.ts.map +1 -1
  160. package/dist/types/settings.d.ts +38 -21
  161. package/dist/types/settings.d.ts.map +1 -1
  162. package/dist/utils/deviceFeaturesCompat.d.ts +0 -1
  163. package/dist/utils/deviceFeaturesCompat.d.ts.map +1 -1
  164. package/dist/utils/deviceFeaturesUtils.d.ts +4 -0
  165. package/dist/utils/deviceFeaturesUtils.d.ts.map +1 -1
  166. package/dist/utils/deviceInfoUtils.d.ts.map +1 -1
  167. package/dist/utils/deviceSettings.d.ts.map +1 -1
  168. package/dist/utils/homescreen.d.ts +4 -0
  169. package/dist/utils/homescreen.d.ts.map +1 -1
  170. package/dist/utils/index.d.ts +1 -1
  171. package/dist/utils/index.d.ts.map +1 -1
  172. package/dist/utils/patch.d.ts +1 -1
  173. package/dist/utils/patch.d.ts.map +1 -1
  174. package/dist/utils/pro2Nft.d.ts +31 -0
  175. package/dist/utils/pro2Nft.d.ts.map +1 -0
  176. package/dist/utils/pro2Wallpaper.d.ts +10 -0
  177. package/dist/utils/pro2Wallpaper.d.ts.map +1 -1
  178. package/dist/utils/uiProgressThrottle.d.ts +3 -0
  179. package/dist/utils/uiProgressThrottle.d.ts.map +1 -0
  180. package/package.json +4 -4
  181. package/src/api/BaseMethod.ts +8 -10
  182. package/src/api/CheckAllFirmwareRelease.ts +78 -64
  183. package/src/api/DetectDeviceConnectProtocol.ts +18 -0
  184. package/src/api/FirmwareUpdate.ts +7 -15
  185. package/src/api/FirmwareUpdateV2.ts +126 -298
  186. package/src/api/FirmwareUpdateV3.ts +63 -265
  187. package/src/api/FirmwareUpdateV4.ts +645 -1018
  188. package/src/api/GetPassphraseState.ts +7 -1
  189. package/src/api/OpenWalletSession.ts +71 -17
  190. package/src/api/SearchDevices.ts +3 -1
  191. package/src/api/allnetwork/AllNetworkGetAddress.ts +21 -21
  192. package/src/api/allnetwork/AllNetworkGetAddressBase.ts +36 -11
  193. package/src/api/device/DeviceSettings.ts +5 -4
  194. package/src/api/device/DeviceUnlock.ts +8 -3
  195. package/src/api/device/DeviceUpdateBootloader.ts +6 -125
  196. package/src/api/firmware/FirmwareUpdateBaseMethod.ts +27 -83
  197. package/src/api/firmware/updateBootloader.ts +4 -19
  198. package/src/api/firmware/uploadFirmware.ts +39 -144
  199. package/src/api/helpers/protocolV2FileWrite.ts +252 -63
  200. package/src/api/index.ts +2 -0
  201. package/src/api/kaspa/KaspaGetAddress.ts +1 -1
  202. package/src/api/kaspa/KaspaSignTransaction.ts +2 -13
  203. package/src/api/polkadot/networks.ts +3 -3
  204. package/src/api/protocol-v2/DeviceUploadNft.ts +197 -0
  205. package/src/api/protocol-v2/ProtocolInfoRequest.ts +3 -1
  206. package/src/api/protocol-v2/helpers.ts +1 -1
  207. package/src/api/solana/SolSignMessage.ts +1 -1
  208. package/src/api/solana/SolSignOffchainMessage.ts +1 -1
  209. package/src/api/solana/SolSignTransaction.ts +2 -2
  210. package/src/api/sui/SuiGetAddress.ts +1 -1
  211. package/src/api/sui/SuiGetPublicKey.ts +1 -1
  212. package/src/api/sui/SuiSignMessage.ts +1 -1
  213. package/src/api/sui/SuiSignTransaction.ts +1 -1
  214. package/src/api/ton/TonGetAddress.ts +1 -1
  215. package/src/api/ton/TonSignMessage.ts +1 -1
  216. package/src/api/ton/TonSignProof.ts +1 -1
  217. package/src/api/tron/TronSignMessage.ts +2 -2
  218. package/src/api/tron/TronSignTransaction.ts +1 -1
  219. package/src/core/deviceEventRegistration.ts +4 -0
  220. package/src/core/index.ts +213 -111
  221. package/src/core/uiPromiseRegistry.ts +63 -0
  222. package/src/data/messages/messages-protocol-v2.json +25 -0
  223. package/src/data-manager/DataManager.ts +113 -41
  224. package/src/data-manager/TransportManager.ts +6 -0
  225. package/src/data-manager/connectSettings.ts +0 -11
  226. package/src/device/Device.ts +274 -49
  227. package/src/device/DeviceCommands.ts +31 -4
  228. package/src/device/DeviceConnector.ts +33 -13
  229. package/src/device/DevicePool.ts +21 -7
  230. package/src/deviceProfile/buildDeviceFeatures.ts +2 -2
  231. package/src/deviceProfile/protocolV2DeviceIdentity.ts +3 -0
  232. package/src/events/device.ts +4 -0
  233. package/src/events/logBlockEvent.ts +1 -0
  234. package/src/events/ui-promise.ts +4 -2
  235. package/src/events/ui-request.ts +36 -2
  236. package/src/events/ui-response.ts +12 -2
  237. package/src/index.ts +0 -5
  238. package/src/inject.ts +60 -24
  239. package/src/lowLevelInject.ts +7 -8
  240. package/src/protocols/protocol-v2/features.ts +10 -7
  241. package/src/protocols/protocol-v2/firmware.ts +2 -0
  242. package/src/protocols/protocol-v2/resources.ts +394 -0
  243. package/src/protocols/protocol-v2/uiInteraction.ts +46 -6
  244. package/src/protocols/protocol-v2/unlockPolicy.ts +1 -105
  245. package/src/protocols/protocol-v2/unlockPolicyRunner.ts +131 -0
  246. package/src/protocols/protocol-v2/walletSession.ts +214 -36
  247. package/src/topLevelInject.ts +7 -8
  248. package/src/types/api/checkAllFirmwareRelease.ts +3 -5
  249. package/src/types/api/detectDeviceConnectProtocol.ts +6 -0
  250. package/src/types/api/deviceUnlock.ts +12 -1
  251. package/src/types/api/deviceUpdateBootloader.ts +0 -6
  252. package/src/types/api/export.ts +1 -18
  253. package/src/types/api/firmwareUpdate.ts +6 -78
  254. package/src/types/api/index.ts +13 -14
  255. package/src/types/api/protocolV2.ts +13 -0
  256. package/src/types/device.ts +15 -3
  257. package/src/types/params.ts +7 -1
  258. package/src/types/settings.ts +61 -22
  259. package/src/utils/deviceFeaturesCompat.ts +5 -6
  260. package/src/utils/deviceFeaturesUtils.ts +14 -3
  261. package/src/utils/deviceInfoUtils.ts +2 -0
  262. package/src/utils/deviceSettings.ts +3 -0
  263. package/src/utils/homescreen.ts +32 -6
  264. package/src/utils/index.ts +6 -1
  265. package/src/utils/pro2Nft.ts +142 -0
  266. package/src/utils/pro2Wallpaper.ts +35 -8
  267. package/src/utils/uiProgressThrottle.ts +63 -0
  268. package/__tests__/device-initialize-timeout.test.ts +0 -56
  269. package/__tests__/firmware-update/check-all-firmware-release.test.ts +0 -175
  270. package/__tests__/firmware-update/device-update-bootloader.test.ts +0 -87
  271. package/__tests__/firmware-update/firmware-artifact-source.test.ts +0 -119
  272. package/__tests__/firmware-update/firmware-host-binding.test.ts +0 -70
  273. package/__tests__/firmware-update/firmware-preparation-error.test.ts +0 -27
  274. package/__tests__/firmware-update/firmware-update-bootloader-poll.test.ts +0 -200
  275. package/__tests__/firmware-update/firmware-update-capabilities.test.ts +0 -13
  276. package/__tests__/firmware-update/firmware-update-plan-bootloader-identity.test.ts +0 -394
  277. package/__tests__/firmware-update/firmware-update-plan.test.ts +0 -593
  278. package/__tests__/firmware-update/firmware-update-prepared-plan.test.ts +0 -231
  279. package/__tests__/firmware-update/firmware-update-v3-reconnect.test.ts +0 -58
  280. package/__tests__/firmware-update/firmware-upload-source.test.ts +0 -70
  281. package/dist/api/firmware/FirmwareArtifactSource.d.ts +0 -25
  282. package/dist/api/firmware/FirmwareArtifactSource.d.ts.map +0 -1
  283. package/dist/api/firmware/FirmwareHostBinding.d.ts +0 -6
  284. package/dist/api/firmware/FirmwareHostBinding.d.ts.map +0 -1
  285. package/dist/api/firmware/FirmwarePreparationError.d.ts +0 -3
  286. package/dist/api/firmware/FirmwarePreparationError.d.ts.map +0 -1
  287. package/dist/api/firmware/FirmwareUpdateCapabilities.d.ts +0 -3
  288. package/dist/api/firmware/FirmwareUpdateCapabilities.d.ts.map +0 -1
  289. package/dist/api/firmware/FirmwareUpdatePlan.d.ts +0 -25
  290. package/dist/api/firmware/FirmwareUpdatePlan.d.ts.map +0 -1
  291. package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts +0 -32
  292. package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts.map +0 -1
  293. package/dist/api/firmware/progressThrottle.d.ts +0 -3
  294. package/dist/api/firmware/progressThrottle.d.ts.map +0 -1
  295. package/dist/protocols/protocol-v2/lockedError.d.ts +0 -2
  296. package/dist/protocols/protocol-v2/lockedError.d.ts.map +0 -1
  297. package/dist/protocols/protocol-v2/unlockRetry.d.ts +0 -10
  298. package/dist/protocols/protocol-v2/unlockRetry.d.ts.map +0 -1
  299. package/dist/types/api/firmwareUpdateCapabilities.d.ts +0 -9
  300. package/dist/types/api/firmwareUpdateCapabilities.d.ts.map +0 -1
  301. package/dist/types/api/firmwareUpdatePlan.d.ts +0 -28
  302. package/dist/types/api/firmwareUpdatePlan.d.ts.map +0 -1
  303. package/dist/types/api/firmwareUpdatePreparedPlan.d.ts +0 -42
  304. package/dist/types/api/firmwareUpdatePreparedPlan.d.ts.map +0 -1
  305. package/src/api/firmware/FirmwareArtifactSource.ts +0 -278
  306. package/src/api/firmware/FirmwareHostBinding.ts +0 -100
  307. package/src/api/firmware/FirmwarePreparationError.ts +0 -12
  308. package/src/api/firmware/FirmwareUpdateCapabilities.ts +0 -9
  309. package/src/api/firmware/FirmwareUpdatePlan.ts +0 -772
  310. package/src/api/firmware/FirmwareUpdatePreparedPlan.ts +0 -449
  311. package/src/api/firmware/progressThrottle.ts +0 -44
  312. package/src/protocols/protocol-v2/lockedError.ts +0 -10
  313. package/src/protocols/protocol-v2/unlockRetry.ts +0 -109
  314. package/src/types/api/firmwareUpdateCapabilities.ts +0 -9
  315. package/src/types/api/firmwareUpdatePlan.ts +0 -38
  316. package/src/types/api/firmwareUpdatePreparedPlan.ts +0 -53
@@ -1,10 +1,15 @@
1
1
  import { EDeviceType, HardwareErrorCode } from '@onekeyfe/hd-shared';
2
- import { DeviceSessionPinType } from '@onekeyfe/hd-transport';
2
+ import { DeviceSessionPinType, DeviceSessionSeedDomain } from '@onekeyfe/hd-transport';
3
3
 
4
4
  import GetPassphraseState from '../src/api/GetPassphraseState';
5
5
  import OpenWalletSession from '../src/api/OpenWalletSession';
6
6
  import { Device } from '../src/device/Device';
7
7
  import { deviceWalletSessionStore } from '../src/device/DeviceWalletSessionStore';
8
+ import { DEVICE } from '../src/events';
9
+ import {
10
+ ensureProtocolV2WalletSessionUnlocked,
11
+ getProtocolV2WalletSession,
12
+ } from '../src/protocols/protocol-v2/walletSession';
8
13
 
9
14
  jest.mock('../src/data/config', () => ({
10
15
  getSDKVersion: jest.fn(() => '1.0.0'),
@@ -13,10 +18,14 @@ jest.mock('../src/data/config', () => ({
13
18
 
14
19
  const createDevice = ({
15
20
  passphraseProtection = true,
21
+ unlockedAttachPin = false,
22
+ refreshedUnlockedAttachPin = unlockedAttachPin,
16
23
  typedCall = jest.fn(),
17
24
  promptPassphrase = jest.fn(),
18
25
  }: {
19
26
  passphraseProtection?: boolean;
27
+ unlockedAttachPin?: boolean;
28
+ refreshedUnlockedAttachPin?: boolean;
20
29
  typedCall?: jest.Mock;
21
30
  promptPassphrase?: jest.Mock;
22
31
  } = {}) => {
@@ -25,7 +34,8 @@ const createDevice = ({
25
34
  features: {
26
35
  unlocked: true,
27
36
  passphraseProtection,
28
- attachToPinEnabled: false,
37
+ attachToPinEnabled: unlockedAttachPin,
38
+ unlockedAttachPin,
29
39
  },
30
40
  commands: {
31
41
  typedCall: jest.fn((request: string, ...args: unknown[]) => {
@@ -34,7 +44,9 @@ const createDevice = ({
34
44
  message: {
35
45
  device_id: 'device-1',
36
46
  unlocked: true,
37
- unlocked_attach_pin: false,
47
+ attach_to_pin_enabled: unlockedAttachPin,
48
+ unlocked_attach_pin: refreshedUnlockedAttachPin,
49
+ unlocked_by_attach_to_pin: refreshedUnlockedAttachPin,
38
50
  passphrase_enabled: passphraseProtection,
39
51
  },
40
52
  };
@@ -43,10 +55,21 @@ const createDevice = ({
43
55
  }),
44
56
  promptPassphrase,
45
57
  },
46
- getDeviceState: jest.fn().mockResolvedValue({
47
- identity: { deviceId: 'device-1' },
48
- status: { passphraseProtection },
49
- }),
58
+ ensureProtocolV2RuntimeContext: jest.fn(() =>
59
+ device.commands.typedCall('ProtocolInfoRequest', 'ProtocolInfo', {
60
+ eventless_wallet_session: true,
61
+ })
62
+ ),
63
+ getDeviceState: jest.fn(() =>
64
+ Promise.resolve({
65
+ identity: { deviceId: 'device-1' },
66
+ status: {
67
+ unlocked: device.features.unlocked,
68
+ passphraseProtection: device.features.passphraseProtection,
69
+ unlockedAttachPin: device.features.unlockedAttachPin,
70
+ },
71
+ })
72
+ ),
50
73
  getCurrentDeviceId: () => 'device-1',
51
74
  getCurrentPassphraseProtection: () => passphraseProtection,
52
75
  getInternalState: () =>
@@ -65,8 +88,12 @@ const createDevice = ({
65
88
  unlockDevice: jest.fn(),
66
89
  updateProtocolV2Status: jest.fn((status: Record<string, unknown>) => {
67
90
  device.features.unlocked = status.unlocked ?? device.features.unlocked;
91
+ device.features.attachToPinEnabled =
92
+ status.attach_to_pin_enabled ?? device.features.attachToPinEnabled;
68
93
  device.features.unlockedAttachPin =
69
- status.unlocked_attach_pin ?? device.features.unlockedAttachPin;
94
+ status.unlocked_by_attach_to_pin ??
95
+ status.unlocked_attach_pin ??
96
+ device.features.unlockedAttachPin;
70
97
  return device.features;
71
98
  }),
72
99
  initialize: jest.fn(),
@@ -82,6 +109,148 @@ describe('openWalletSession', () => {
82
109
  deviceWalletSessionStore.clear();
83
110
  });
84
111
 
112
+ test('selects the standard wallet explicitly when passphrase is enabled', async () => {
113
+ const typedCall = jest.fn((request: string) => {
114
+ if (request === 'ProtocolInfoRequest') {
115
+ return { message: { version: 2 } };
116
+ }
117
+ if (request === 'DeviceSessionAskPassphrase') {
118
+ return { message: {} };
119
+ }
120
+ if (request === 'DeviceSessionGet') {
121
+ return {
122
+ message: {
123
+ btc_test_address: 'standard-state',
124
+ session_id: 'standard-session',
125
+ },
126
+ };
127
+ }
128
+ throw new Error(`Unexpected request: ${request}`);
129
+ });
130
+ const device = createDevice({ typedCall });
131
+
132
+ await expect(
133
+ getProtocolV2WalletSession(device as any, {
134
+ onlyMainPin: true,
135
+ })
136
+ ).resolves.toMatchObject({
137
+ passphraseState: 'standard-state',
138
+ newSession: 'standard-session',
139
+ });
140
+
141
+ expect(device.unlockDevice).not.toHaveBeenCalled();
142
+ expect(typedCall).toHaveBeenCalledWith('DeviceSessionAskPassphrase', 'Success', {
143
+ passphrase: '',
144
+ on_device: false,
145
+ });
146
+ expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
147
+ seed_domains: [],
148
+ });
149
+ });
150
+
151
+ test('opens the standard wallet directly when passphrase is disabled', async () => {
152
+ const typedCall = jest.fn((request: string) => {
153
+ if (request === 'ProtocolInfoRequest') {
154
+ return { message: { version: 2 } };
155
+ }
156
+ if (request === 'DeviceSessionGet') {
157
+ return {
158
+ message: {
159
+ btc_test_address: 'standard-state',
160
+ session_id: 'standard-session',
161
+ },
162
+ };
163
+ }
164
+ throw new Error(`Unexpected request: ${request}`);
165
+ });
166
+ const device = createDevice({ passphraseProtection: false, typedCall });
167
+
168
+ await getProtocolV2WalletSession(device as any, { onlyMainPin: true });
169
+
170
+ expect(typedCall).not.toHaveBeenCalledWith(
171
+ 'DeviceSessionAskPassphrase',
172
+ 'Success',
173
+ expect.anything()
174
+ );
175
+ expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
176
+ seed_domains: [],
177
+ });
178
+ });
179
+
180
+ test('does not treat an Attach PIN DeviceStatus as the Main wallet', async () => {
181
+ const typedCall = jest.fn((request: string) => {
182
+ if (request === 'ProtocolInfoRequest') {
183
+ return { message: { version: 2 } };
184
+ }
185
+ if (request === 'DeviceSessionAskPassphrase') {
186
+ return { message: {} };
187
+ }
188
+ if (request === 'DeviceSessionGet') {
189
+ return {
190
+ message: {
191
+ btc_test_address: 'standard-state',
192
+ session_id: 'standard-session',
193
+ },
194
+ };
195
+ }
196
+ throw new Error(`Unexpected request: ${request}`);
197
+ });
198
+ const device = createDevice({ typedCall });
199
+ device.features.unlockedAttachPin = true;
200
+
201
+ await getProtocolV2WalletSession(device as any, { onlyMainPin: true });
202
+
203
+ expect(device.unlockDevice).toHaveBeenCalledWith(DeviceSessionPinType.Main, {
204
+ source: 'wallet-session-coordinator',
205
+ reason: 'open-wallet',
206
+ deviceOnly: true,
207
+ });
208
+ });
209
+
210
+ test('unlocks a locked Protocol V2 device before restoring a wallet session', async () => {
211
+ const device = {
212
+ commands: {
213
+ typedCall: jest.fn().mockResolvedValue({ message: { unlocked: false } }),
214
+ },
215
+ isBootloader: jest.fn().mockReturnValue(false),
216
+ isProtocolV2: jest.fn().mockReturnValue(true),
217
+ isRomloader: jest.fn().mockReturnValue(false),
218
+ state: { status: { unlocked: false } },
219
+ unlockDevice: jest.fn().mockResolvedValue(undefined),
220
+ updateProtocolV2Status: jest.fn(function updateProtocolV2Status(
221
+ this: { state: { status: { unlocked: boolean } } },
222
+ status: { unlocked?: boolean }
223
+ ) {
224
+ this.state.status.unlocked = status.unlocked ?? this.state.status.unlocked;
225
+ }),
226
+ };
227
+
228
+ await expect(ensureProtocolV2WalletSessionUnlocked(device as any)).resolves.toBe(true);
229
+ expect(device.commands.typedCall).toHaveBeenCalledWith('DeviceStatusGet', 'DeviceStatus', {});
230
+ expect(device.unlockDevice).toHaveBeenCalledWith(DeviceSessionPinType.Main, {
231
+ source: 'unlock-coordinator',
232
+ reason: 'device-locked',
233
+ deviceOnly: true,
234
+ });
235
+ });
236
+
237
+ test('does not request PIN when wallet-session recovery finds the device unlocked', async () => {
238
+ const device = {
239
+ commands: {
240
+ typedCall: jest.fn().mockResolvedValue({ message: { unlocked: true } }),
241
+ },
242
+ isBootloader: jest.fn().mockReturnValue(false),
243
+ isProtocolV2: jest.fn().mockReturnValue(true),
244
+ isRomloader: jest.fn().mockReturnValue(false),
245
+ state: { status: { unlocked: true } },
246
+ unlockDevice: jest.fn(),
247
+ updateProtocolV2Status: jest.fn(),
248
+ };
249
+
250
+ await expect(ensureProtocolV2WalletSessionUnlocked(device as any)).resolves.toBe(false);
251
+ expect(device.unlockDevice).not.toHaveBeenCalled();
252
+ });
253
+
85
254
  test.each([{ useEmptyPassphrase: true }, { initSession: true }, {}])(
86
255
  'requires the explicit mode in the new public API: %p',
87
256
  legacyParams => {
@@ -133,7 +302,9 @@ describe('openWalletSession', () => {
133
302
  passphrase: 'host hidden wallet',
134
303
  on_device: false,
135
304
  });
136
- expect(typedCall).toHaveBeenNthCalledWith(3, 'DeviceSessionGet', 'DeviceSession', {});
305
+ expect(typedCall).toHaveBeenNthCalledWith(3, 'DeviceSessionGet', 'DeviceSession', {
306
+ seed_domains: [],
307
+ });
137
308
  });
138
309
 
139
310
  test('keeps Legacy Protocol V1 getPassphraseState parameterless', async () => {
@@ -251,7 +422,9 @@ describe('openWalletSession', () => {
251
422
  passphrase: 'host hidden wallet',
252
423
  on_device: false,
253
424
  });
254
- expect(typedCall).toHaveBeenNthCalledWith(3, 'DeviceSessionGet', 'DeviceSession', {});
425
+ expect(typedCall).toHaveBeenNthCalledWith(3, 'DeviceSessionGet', 'DeviceSession', {
426
+ seed_domains: [],
427
+ });
255
428
  });
256
429
 
257
430
  test('refreshes Pro2 status and unlocks before selecting a hidden wallet when locked', async () => {
@@ -273,10 +446,24 @@ describe('openWalletSession', () => {
273
446
  typedCall,
274
447
  promptPassphrase: jest.fn().mockResolvedValue({ passphraseOnDevice: true }),
275
448
  });
276
- device.getDeviceState = jest.fn().mockResolvedValue({
277
- identity: { deviceId: 'device-1' },
278
- status: { unlocked: false, passphraseProtection: true },
279
- });
449
+ device.getDeviceState = jest
450
+ .fn()
451
+ .mockResolvedValueOnce({
452
+ identity: { deviceId: 'device-1' },
453
+ status: {
454
+ unlocked: false,
455
+ passphraseProtection: null,
456
+ unlockedAttachPin: null,
457
+ },
458
+ })
459
+ .mockResolvedValue({
460
+ identity: { deviceId: 'device-1' },
461
+ status: {
462
+ unlocked: true,
463
+ passphraseProtection: true,
464
+ unlockedAttachPin: false,
465
+ },
466
+ });
280
467
  method.device = device as any;
281
468
 
282
469
  await expect(method.run()).resolves.toMatchObject({
@@ -330,6 +517,12 @@ describe('openWalletSession', () => {
330
517
  status.passphrase_enabled ?? device.features.passphraseProtection;
331
518
  return device.features;
332
519
  });
520
+ device.unlockDevice = jest.fn().mockImplementation(() => {
521
+ device.features.unlocked = true;
522
+ device.features.passphraseProtection = true;
523
+ device.features.unlockedAttachPin = false;
524
+ return Promise.resolve(device.features);
525
+ });
333
526
  method.device = device as any;
334
527
 
335
528
  await expect(method.run()).resolves.toEqual({
@@ -339,11 +532,99 @@ describe('openWalletSession', () => {
339
532
  passphraseState: 'hidden-state-after-unlock',
340
533
  resumed: false,
341
534
  });
342
- expect(device.unlockDevice).not.toHaveBeenCalled();
535
+ expect(device.unlockDevice).toHaveBeenCalledTimes(1);
343
536
  expect(promptPassphrase).toHaveBeenCalledTimes(1);
344
537
  });
345
538
 
346
- test('rejects a Pro2 standard-wallet fallback after selecting a hidden wallet', async () => {
539
+ test('unlocks before wallet selection when the Protocol V2 wallet status is unknown', async () => {
540
+ const typedCall = jest
541
+ .fn()
542
+ .mockResolvedValueOnce({ message: { version: 2 } })
543
+ .mockResolvedValueOnce({ message: {} })
544
+ .mockResolvedValueOnce({
545
+ message: {
546
+ btc_test_address: 'hidden-state-after-unknown-status',
547
+ session_id: 'hidden-session-after-unknown-status',
548
+ },
549
+ });
550
+ const promptPassphrase = jest.fn().mockResolvedValue({ passphrase: 'host hidden wallet' });
551
+ const method = new OpenWalletSession({
552
+ payload: { method: 'openWalletSession', connectId: 'connect-id', mode: 'select-hidden' },
553
+ });
554
+ method.init();
555
+ const device = createDevice({ typedCall, promptPassphrase });
556
+ device.getDeviceState = jest
557
+ .fn()
558
+ .mockResolvedValueOnce({
559
+ identity: { deviceId: 'device-1' },
560
+ status: {
561
+ unlocked: undefined,
562
+ passphraseProtection: null,
563
+ unlockedAttachPin: null,
564
+ },
565
+ })
566
+ .mockResolvedValue({
567
+ identity: { deviceId: 'device-1' },
568
+ status: {
569
+ unlocked: true,
570
+ passphraseProtection: true,
571
+ unlockedAttachPin: false,
572
+ },
573
+ });
574
+ device.unlockDevice = jest.fn().mockImplementation(() => {
575
+ device.features.unlocked = true;
576
+ device.features.passphraseProtection = true;
577
+ device.features.unlockedAttachPin = false;
578
+ return Promise.resolve(device.features);
579
+ });
580
+ method.device = device as any;
581
+
582
+ await expect(method.run()).resolves.toMatchObject({
583
+ walletType: 'hidden',
584
+ passphraseState: 'hidden-state-after-unknown-status',
585
+ });
586
+ expect(device.unlockDevice).toHaveBeenCalledTimes(1);
587
+ expect(device.unlockDevice.mock.invocationCallOrder[0]).toBeLessThan(
588
+ promptPassphrase.mock.invocationCallOrder[0]
589
+ );
590
+ });
591
+
592
+ test('rejects wallet selection when the post-unlock Protocol V2 status remains incomplete', async () => {
593
+ const typedCall = jest
594
+ .fn()
595
+ .mockResolvedValueOnce({ message: { version: 2 } })
596
+ .mockResolvedValueOnce({ message: {} })
597
+ .mockResolvedValueOnce({
598
+ message: {
599
+ btc_test_address: 'hidden-state-from-incomplete-status',
600
+ session_id: 'hidden-session-from-incomplete-status',
601
+ },
602
+ });
603
+ const promptPassphrase = jest.fn().mockResolvedValue({ passphrase: 'host hidden wallet' });
604
+ const method = new OpenWalletSession({
605
+ payload: { method: 'openWalletSession', connectId: 'connect-id', mode: 'select-hidden' },
606
+ });
607
+ method.init();
608
+ const device = createDevice({ typedCall, promptPassphrase });
609
+ device.getDeviceState = jest.fn().mockResolvedValue({
610
+ identity: { deviceId: 'device-1' },
611
+ status: {
612
+ unlocked: true,
613
+ passphraseProtection: null,
614
+ unlockedAttachPin: null,
615
+ },
616
+ });
617
+ device.unlockDevice = jest.fn().mockResolvedValue(device.features);
618
+ method.device = device as any;
619
+
620
+ await expect(method.run()).rejects.toMatchObject({
621
+ errorCode: HardwareErrorCode.DeviceInitializeFailed,
622
+ });
623
+ expect(device.unlockDevice).toHaveBeenCalledTimes(1);
624
+ expect(promptPassphrase).not.toHaveBeenCalled();
625
+ });
626
+
627
+ test('rejects before prompting for a hidden wallet when Pro2 passphrase is disabled', async () => {
347
628
  const typedCall = jest
348
629
  .fn()
349
630
  .mockResolvedValueOnce({ message: { version: 2 } })
@@ -370,6 +651,114 @@ describe('openWalletSession', () => {
370
651
  errorCode: HardwareErrorCode.DeviceNotOpenedPassphrase,
371
652
  });
372
653
  expect(device.clearInternalState).toHaveBeenCalled();
654
+ expect(promptPassphrase).not.toHaveBeenCalled();
655
+ expect(typedCall).not.toHaveBeenCalledWith(
656
+ 'DeviceSessionAskPassphrase',
657
+ 'Success',
658
+ expect.anything()
659
+ );
660
+ });
661
+
662
+ test('reuses the current Attach PIN session without reopening wallet selection', async () => {
663
+ const typedCall = jest.fn((request: string) => {
664
+ if (request === 'ProtocolInfoRequest') {
665
+ return { message: { version: 2 } };
666
+ }
667
+ if (request === 'DeviceSessionAskPassphrase') {
668
+ return { message: {} };
669
+ }
670
+ if (request === 'DeviceSessionGet') {
671
+ return {
672
+ message: {
673
+ btc_test_address: 'attach-state',
674
+ session_id: 'attach-session',
675
+ },
676
+ };
677
+ }
678
+ throw new Error(`Unexpected request: ${request}`);
679
+ });
680
+ const promptPassphrase = jest.fn().mockResolvedValue({ passphrase: 'must not be requested' });
681
+ const method = new OpenWalletSession({
682
+ payload: { method: 'openWalletSession', connectId: 'connect-id', mode: 'select-hidden' },
683
+ });
684
+ method.init();
685
+ const device = createDevice({
686
+ typedCall,
687
+ promptPassphrase,
688
+ unlockedAttachPin: true,
689
+ });
690
+ method.device = device as any;
691
+
692
+ await expect(method.run()).resolves.toEqual({
693
+ protocol: 'V2',
694
+ walletType: 'hidden',
695
+ deviceId: 'device-1',
696
+ passphraseState: 'attach-state',
697
+ resumed: false,
698
+ });
699
+ expect(promptPassphrase).not.toHaveBeenCalled();
700
+ expect(device.unlockDevice).not.toHaveBeenCalled();
701
+ expect(device.commands.typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
702
+ seed_domains: [],
703
+ });
704
+ expect(device.commands.typedCall).not.toHaveBeenCalledWith(
705
+ 'DeviceSessionAskPassphrase',
706
+ 'Success',
707
+ expect.anything()
708
+ );
709
+ expect(device.commands.typedCall).not.toHaveBeenCalledWith(
710
+ 'DeviceSessionAskPin',
711
+ 'Success',
712
+ expect.anything()
713
+ );
714
+ });
715
+
716
+ test('fails closed when the Attach PIN state changes before reading the current session', async () => {
717
+ const typedCall = jest.fn((request: string) => {
718
+ if (request === 'ProtocolInfoRequest') {
719
+ return { message: { version: 2 } };
720
+ }
721
+ if (request === 'DeviceSessionAskPassphrase') {
722
+ return { message: {} };
723
+ }
724
+ if (request === 'DeviceSessionGet') {
725
+ return {
726
+ message: {
727
+ btc_test_address: 'unexpected-state',
728
+ session_id: 'unexpected-session',
729
+ },
730
+ };
731
+ }
732
+ throw new Error(`Unexpected request: ${request}`);
733
+ });
734
+ const promptPassphrase = jest.fn().mockResolvedValue({ passphrase: 'must not be requested' });
735
+ const method = new OpenWalletSession({
736
+ payload: { method: 'openWalletSession', connectId: 'connect-id', mode: 'select-hidden' },
737
+ });
738
+ method.init();
739
+ const device = createDevice({
740
+ typedCall,
741
+ promptPassphrase,
742
+ unlockedAttachPin: true,
743
+ refreshedUnlockedAttachPin: false,
744
+ });
745
+ method.device = device as any;
746
+
747
+ await expect(method.run()).rejects.toMatchObject({
748
+ errorCode: HardwareErrorCode.DeviceCheckUnlockTypeError,
749
+ });
750
+ expect(device.clearInternalState).toHaveBeenCalled();
751
+ expect(promptPassphrase).not.toHaveBeenCalled();
752
+ expect(device.commands.typedCall).not.toHaveBeenCalledWith(
753
+ 'DeviceSessionAskPassphrase',
754
+ 'Success',
755
+ expect.anything()
756
+ );
757
+ expect(device.commands.typedCall).not.toHaveBeenCalledWith(
758
+ 'DeviceSessionGet',
759
+ 'DeviceSession',
760
+ expect.anything()
761
+ );
373
762
  });
374
763
 
375
764
  test('select-hidden starts a fresh hidden-wallet session on Protocol V2', async () => {
@@ -408,7 +797,9 @@ describe('openWalletSession', () => {
408
797
  passphrase: 'host hidden wallet',
409
798
  on_device: false,
410
799
  });
411
- expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {});
800
+ expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
801
+ seed_domains: [],
802
+ });
412
803
  expect(promptPassphrase).toHaveBeenCalled();
413
804
  expect(deviceWalletSessionStore.get('device-1', 'new-hidden-state')).toBe('new-hidden-session');
414
805
  });
@@ -479,6 +870,8 @@ describe('openWalletSession', () => {
479
870
  });
480
871
  expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
481
872
  session_id: 'known-session',
873
+ btc_test_address: 'hidden-state',
874
+ seed_domains: [],
482
875
  });
483
876
  expect(promptPassphrase).not.toHaveBeenCalled();
484
877
  });
@@ -630,6 +1023,7 @@ describe('openWalletSession', () => {
630
1023
  const typedCall = jest
631
1024
  .fn()
632
1025
  .mockResolvedValueOnce({ message: { version: 2 } })
1026
+ .mockResolvedValueOnce({ message: {} })
633
1027
  .mockResolvedValueOnce({
634
1028
  message: {
635
1029
  btc_test_address: 'main-wallet-state',
@@ -652,12 +1046,18 @@ describe('openWalletSession', () => {
652
1046
  passphraseState: null,
653
1047
  resumed: false,
654
1048
  });
655
- expect(typedCall).toHaveBeenCalledTimes(2);
1049
+ expect(typedCall).toHaveBeenCalledTimes(3);
656
1050
  expect(typedCall).toHaveBeenCalledWith('ProtocolInfoRequest', 'ProtocolInfo', {
657
1051
  eventless_wallet_session: true,
658
1052
  });
659
- expect(device.unlockDevice).toHaveBeenCalledWith(DeviceSessionPinType.Main);
660
- expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {});
1053
+ expect(device.unlockDevice).not.toHaveBeenCalled();
1054
+ expect(typedCall).toHaveBeenCalledWith('DeviceSessionAskPassphrase', 'Success', {
1055
+ passphrase: '',
1056
+ on_device: false,
1057
+ });
1058
+ expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
1059
+ seed_domains: [],
1060
+ });
661
1061
  expect(promptPassphrase).not.toHaveBeenCalled();
662
1062
  expect(device.passphraseState).toBeUndefined();
663
1063
  });
@@ -666,6 +1066,7 @@ describe('openWalletSession', () => {
666
1066
  const typedCall = jest
667
1067
  .fn()
668
1068
  .mockResolvedValueOnce({ message: { version: 2 } })
1069
+ .mockResolvedValueOnce({ message: {} })
669
1070
  .mockResolvedValueOnce({
670
1071
  message: {
671
1072
  btc_test_address: 'main-wallet-state',
@@ -686,7 +1087,11 @@ describe('openWalletSession', () => {
686
1087
  })
687
1088
  .mockResolvedValueOnce({
688
1089
  identity: { deviceId: 'device-1' },
689
- status: { passphraseProtection: true },
1090
+ status: {
1091
+ unlocked: true,
1092
+ passphraseProtection: true,
1093
+ unlockedAttachPin: false,
1094
+ },
690
1095
  });
691
1096
  device.unlockDevice = jest.fn().mockImplementation(() => {
692
1097
  device.features.unlocked = true;
@@ -701,7 +1106,11 @@ describe('openWalletSession', () => {
701
1106
  passphraseState: null,
702
1107
  resumed: false,
703
1108
  });
704
- expect(device.unlockDevice).toHaveBeenCalledWith(DeviceSessionPinType.Main);
1109
+ expect(device.unlockDevice).toHaveBeenCalledWith(DeviceSessionPinType.Main, {
1110
+ source: 'wallet-session-coordinator',
1111
+ reason: 'open-wallet',
1112
+ deviceOnly: true,
1113
+ });
705
1114
  expect(device.getDeviceState).toHaveBeenCalledTimes(2);
706
1115
  });
707
1116
 
@@ -709,6 +1118,7 @@ describe('openWalletSession', () => {
709
1118
  const typedCall = jest
710
1119
  .fn()
711
1120
  .mockResolvedValueOnce({ message: { version: 2 } })
1121
+ .mockResolvedValueOnce({ message: {} })
712
1122
  .mockResolvedValueOnce({
713
1123
  message: {
714
1124
  btc_test_address: 'main-wallet-state',
@@ -721,6 +1131,24 @@ describe('openWalletSession', () => {
721
1131
  method.init();
722
1132
  const device = createDevice({ typedCall });
723
1133
  device.features.unlockedAttachPin = true;
1134
+ device.getDeviceState = jest
1135
+ .fn()
1136
+ .mockResolvedValueOnce({
1137
+ identity: { deviceId: 'device-1' },
1138
+ status: {
1139
+ unlocked: true,
1140
+ unlockedAttachPin: true,
1141
+ passphraseProtection: true,
1142
+ },
1143
+ })
1144
+ .mockResolvedValue({
1145
+ identity: { deviceId: 'device-1' },
1146
+ status: {
1147
+ unlocked: true,
1148
+ unlockedAttachPin: false,
1149
+ passphraseProtection: true,
1150
+ },
1151
+ });
724
1152
  device.unlockDevice = jest.fn().mockImplementation(() => {
725
1153
  device.features.unlockedAttachPin = false;
726
1154
  return Promise.resolve(device.features);
@@ -731,8 +1159,14 @@ describe('openWalletSession', () => {
731
1159
  walletType: 'standard',
732
1160
  passphraseState: null,
733
1161
  });
734
- expect(device.unlockDevice).toHaveBeenCalledWith(DeviceSessionPinType.Main);
735
- expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {});
1162
+ expect(device.unlockDevice).toHaveBeenCalledWith(DeviceSessionPinType.Main, {
1163
+ source: 'wallet-session-coordinator',
1164
+ reason: 'open-wallet',
1165
+ deviceOnly: true,
1166
+ });
1167
+ expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
1168
+ seed_domains: [],
1169
+ });
736
1170
  });
737
1171
 
738
1172
  test('selects a hidden wallet without exposing the internal device session', async () => {
@@ -766,7 +1200,9 @@ describe('openWalletSession', () => {
766
1200
  passphrase: 'host hidden wallet',
767
1201
  on_device: false,
768
1202
  });
769
- expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {});
1203
+ expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
1204
+ seed_domains: [],
1205
+ });
770
1206
  expect(promptPassphrase).toHaveBeenCalled();
771
1207
  expect(device.passphraseState).toBeUndefined();
772
1208
  expect(deviceWalletSessionStore.get('device-1', 'hidden-state')).toBe('hidden-session');
@@ -795,7 +1231,9 @@ describe('openWalletSession', () => {
795
1231
  passphrase: 'host hidden wallet',
796
1232
  on_device: false,
797
1233
  });
798
- expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {});
1234
+ expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
1235
+ seed_domains: [],
1236
+ });
799
1237
  });
800
1238
 
801
1239
  test('selects an on-device passphrase wallet with an explicit on_device request', async () => {
@@ -811,7 +1249,28 @@ describe('openWalletSession', () => {
811
1249
  payload: { method: 'openWalletSession', connectId: 'connect-id', mode: 'select-hidden' },
812
1250
  });
813
1251
  method.init();
814
- method.device = createDevice({ typedCall, promptPassphrase }) as any;
1252
+ const device = createDevice({ typedCall, promptPassphrase });
1253
+ const passphraseInteraction = {
1254
+ interactionId: 'interaction-1',
1255
+ phaseId: 'interaction-1:phase-1',
1256
+ sequence: 1,
1257
+ phase: 'passphrase',
1258
+ transition: 'start',
1259
+ protocol: 'V2',
1260
+ } as const;
1261
+ const passphraseOnDeviceInteraction = {
1262
+ interactionId: 'interaction-1',
1263
+ phaseId: 'interaction-1:phase-2',
1264
+ sequence: 2,
1265
+ phase: 'passphrase-on-device',
1266
+ transition: 'start',
1267
+ protocol: 'V2',
1268
+ } as const;
1269
+ device.createProtocolV2UiPhaseMetadata = jest
1270
+ .fn()
1271
+ .mockReturnValueOnce(passphraseInteraction)
1272
+ .mockReturnValueOnce(passphraseOnDeviceInteraction);
1273
+ method.device = device as any;
815
1274
 
816
1275
  await expect(method.run()).resolves.toMatchObject({
817
1276
  walletType: 'hidden',
@@ -820,7 +1279,19 @@ describe('openWalletSession', () => {
820
1279
  expect(typedCall).toHaveBeenNthCalledWith(2, 'DeviceSessionAskPassphrase', 'Success', {
821
1280
  on_device: true,
822
1281
  });
823
- expect(typedCall).toHaveBeenNthCalledWith(3, 'DeviceSessionGet', 'DeviceSession', {});
1282
+ expect(typedCall).toHaveBeenNthCalledWith(3, 'DeviceSessionGet', 'DeviceSession', {
1283
+ seed_domains: [],
1284
+ });
1285
+ expect(device.createProtocolV2UiPhaseMetadata).toHaveBeenNthCalledWith(
1286
+ 2,
1287
+ 'passphrase-on-device',
1288
+ 'start'
1289
+ );
1290
+ expect(device.emit).toHaveBeenCalledWith(DEVICE.PASSPHRASE_ON_DEVICE, device, {
1291
+ source: 'wallet-session-coordinator',
1292
+ reason: 'open-wallet',
1293
+ interaction: passphraseOnDeviceInteraction,
1294
+ });
824
1295
  });
825
1296
 
826
1297
  test('forwards a host passphrase to Pro2 firmware', async () => {
@@ -862,7 +1333,9 @@ describe('openWalletSession', () => {
862
1333
  passphrase: 'host hidden wallet',
863
1334
  on_device: false,
864
1335
  });
865
- expect(typedCall).toHaveBeenNthCalledWith(3, 'DeviceSessionGet', 'DeviceSession', {});
1336
+ expect(typedCall).toHaveBeenNthCalledWith(3, 'DeviceSessionGet', 'DeviceSession', {
1337
+ seed_domains: [],
1338
+ });
866
1339
  });
867
1340
 
868
1341
  test('normalizes a Unicode Host passphrase before sending it to Pro2 firmware', async () => {
@@ -939,8 +1412,12 @@ describe('openWalletSession', () => {
939
1412
  },
940
1413
  { cancelDeviceOnReject: false }
941
1414
  );
942
- expect(device.unlockDevice).toHaveBeenCalledWith(DeviceSessionPinType.AttachToPin);
943
- expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {});
1415
+ expect(device.unlockDevice).toHaveBeenCalledWith(DeviceSessionPinType.AttachToPin, {
1416
+ emitUiEvent: false,
1417
+ });
1418
+ expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
1419
+ seed_domains: [],
1420
+ });
944
1421
  });
945
1422
 
946
1423
  test('uses the complete Attach PIN wire flow without a main PIN unlock', async () => {
@@ -969,7 +1446,7 @@ describe('openWalletSession', () => {
969
1446
  ['ProtocolInfoRequest', 'ProtocolInfo', { eventless_wallet_session: true }],
970
1447
  ['DeviceSessionAskPin', 'Success', { type: DeviceSessionPinType.AttachToPin }],
971
1448
  ['DeviceStatusGet', 'DeviceStatus', {}],
972
- ['DeviceSessionGet', 'DeviceSession', {}],
1449
+ ['DeviceSessionGet', 'DeviceSession', { seed_domains: [] }],
973
1450
  ]);
974
1451
  expect(device.commands.typedCall).not.toHaveBeenCalledWith('DeviceSessionAskPin', 'Success', {
975
1452
  type: DeviceSessionPinType.Main,
@@ -1026,7 +1503,7 @@ describe('openWalletSession', () => {
1026
1503
 
1027
1504
  await expect(method.run()).rejects.toThrow(message);
1028
1505
  expect(typedCall).toHaveBeenCalledTimes(2);
1029
- expect(typedCall).not.toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {});
1506
+ expect(typedCall.mock.calls.some(([name]) => name === 'DeviceSessionGet')).toBe(false);
1030
1507
  }
1031
1508
  );
1032
1509
 
@@ -1180,6 +1657,8 @@ describe('openWalletSession', () => {
1180
1657
  });
1181
1658
  expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
1182
1659
  session_id: 'known-session',
1660
+ btc_test_address: 'hidden-state',
1661
+ seed_domains: [],
1183
1662
  });
1184
1663
  expect(promptPassphrase).not.toHaveBeenCalled();
1185
1664
  });
@@ -1215,7 +1694,11 @@ describe('openWalletSession', () => {
1215
1694
  })
1216
1695
  .mockResolvedValueOnce({
1217
1696
  identity: { deviceId: 'device-1' },
1218
- status: { passphraseProtection: true },
1697
+ status: {
1698
+ unlocked: true,
1699
+ passphraseProtection: true,
1700
+ unlockedAttachPin: false,
1701
+ },
1219
1702
  });
1220
1703
  device.unlockDevice = jest.fn().mockImplementation(() => {
1221
1704
  device.features.unlocked = true;
@@ -1234,6 +1717,8 @@ describe('openWalletSession', () => {
1234
1717
  expect(device.getDeviceState).toHaveBeenCalledTimes(2);
1235
1718
  expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
1236
1719
  session_id: 'known-session',
1720
+ btc_test_address: 'hidden-state',
1721
+ seed_domains: [],
1237
1722
  });
1238
1723
  const sessionGetCall = typedCall.mock.calls.findIndex(
1239
1724
  ([requestName]) => requestName === 'DeviceSessionGet'
@@ -1271,7 +1756,11 @@ describe('openWalletSession', () => {
1271
1756
  })
1272
1757
  .mockResolvedValueOnce({
1273
1758
  identity: { deviceId: 'other-device' },
1274
- status: { passphraseProtection: true },
1759
+ status: {
1760
+ unlocked: true,
1761
+ passphraseProtection: true,
1762
+ unlockedAttachPin: false,
1763
+ },
1275
1764
  });
1276
1765
  device.unlockDevice = jest.fn().mockImplementation(() => {
1277
1766
  device.features.unlocked = true;
@@ -1326,6 +1815,8 @@ describe('openWalletSession', () => {
1326
1815
  });
1327
1816
  expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
1328
1817
  session_id: 'expired-session',
1818
+ btc_test_address: 'hidden-state',
1819
+ seed_domains: [],
1329
1820
  });
1330
1821
  expect(promptPassphrase).toHaveBeenCalledTimes(1);
1331
1822
  expect(deviceWalletSessionStore.get('device-1', 'hidden-state')).toBe('renewed-session');
@@ -1335,7 +1826,6 @@ describe('openWalletSession', () => {
1335
1826
  const typedCall = jest
1336
1827
  .fn()
1337
1828
  .mockResolvedValueOnce({ message: { version: 2 } })
1338
- .mockResolvedValueOnce({ message: {} })
1339
1829
  .mockResolvedValueOnce({
1340
1830
  message: {
1341
1831
  btc_test_address: 'hidden-state',
@@ -1362,7 +1852,48 @@ describe('openWalletSession', () => {
1362
1852
  passphraseState: 'hidden-state',
1363
1853
  resumed: false,
1364
1854
  });
1365
- expect(promptPassphrase).toHaveBeenCalledTimes(1);
1855
+ expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
1856
+ btc_test_address: 'hidden-state',
1857
+ seed_domains: [],
1858
+ });
1859
+ expect(promptPassphrase).not.toHaveBeenCalled();
1366
1860
  expect(deviceWalletSessionStore.get('device-1', 'hidden-state')).toBe('new-session');
1367
1861
  });
1862
+
1863
+ test.each([false, true])('keeps deriveCardano API compatibility: %s', async deriveCardano => {
1864
+ const typedCall = jest
1865
+ .fn()
1866
+ .mockResolvedValueOnce({ message: { version: 2 } })
1867
+ .mockResolvedValueOnce({ message: {} })
1868
+ .mockResolvedValueOnce({
1869
+ message: {
1870
+ btc_test_address: 'hidden-state',
1871
+ session_id: 'new-session',
1872
+ },
1873
+ });
1874
+ const method = new OpenWalletSession({
1875
+ payload: {
1876
+ method: 'openWalletSession',
1877
+ connectId: 'connect-id',
1878
+ mode: 'select-hidden',
1879
+ deriveCardano,
1880
+ },
1881
+ });
1882
+ method.init();
1883
+ method.device = createDevice({
1884
+ typedCall,
1885
+ promptPassphrase: jest.fn().mockResolvedValue({ passphrase: 'host hidden wallet' }),
1886
+ }) as any;
1887
+
1888
+ await expect(method.run()).resolves.toMatchObject({
1889
+ walletType: 'hidden',
1890
+ passphraseState: 'hidden-state',
1891
+ });
1892
+ expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
1893
+ seed_domains: [
1894
+ DeviceSessionSeedDomain.SeedDomain_Standard,
1895
+ ...(deriveCardano ? [DeviceSessionSeedDomain.SeedDomain_Cardano] : []),
1896
+ ],
1897
+ });
1898
+ });
1368
1899
  });