@hsuite/native-connect-angular 1.0.0

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 (276) hide show
  1. package/README.md +48 -0
  2. package/USAGE_EXAMPLES.md +476 -0
  3. package/assets/wallets/extension.svg +7 -0
  4. package/assets/wallets/hashpack.svg +6 -0
  5. package/assets/wallets/hsuite.svg +11 -0
  6. package/assets/wallets/kabila.svg +11 -0
  7. package/assets/wallets/walletconnect.svg +13 -0
  8. package/coverage/base.css +224 -0
  9. package/coverage/block-navigation.js +87 -0
  10. package/coverage/coverage-summary.json +50 -0
  11. package/coverage/favicon.png +0 -0
  12. package/coverage/index.html +476 -0
  13. package/coverage/lcov-report/base.css +224 -0
  14. package/coverage/lcov-report/block-navigation.js +87 -0
  15. package/coverage/lcov-report/favicon.png +0 -0
  16. package/coverage/lcov-report/index.html +476 -0
  17. package/coverage/lcov-report/lib/components/account-selector/account-actions/account-actions.component.ts.html +868 -0
  18. package/coverage/lcov-report/lib/components/account-selector/account-actions/index.html +116 -0
  19. package/coverage/lcov-report/lib/components/account-selector/account-filter/account-filter.component.ts.html +1288 -0
  20. package/coverage/lcov-report/lib/components/account-selector/account-filter/index.html +116 -0
  21. package/coverage/lcov-report/lib/components/account-selector/account-formatting.service.ts.html +685 -0
  22. package/coverage/lcov-report/lib/components/account-selector/account-grouping.service.ts.html +766 -0
  23. package/coverage/lcov-report/lib/components/account-selector/account-list/account-list.component.ts.html +1495 -0
  24. package/coverage/lcov-report/lib/components/account-selector/account-list/index.html +116 -0
  25. package/coverage/lcov-report/lib/components/account-selector/account-selector.component.ts.html +1495 -0
  26. package/coverage/lcov-report/lib/components/account-selector/account-selector.service.ts.html +1588 -0
  27. package/coverage/lcov-report/lib/components/account-selector/index.html +161 -0
  28. package/coverage/lcov-report/lib/components/wallet-account-display/index.html +116 -0
  29. package/coverage/lcov-report/lib/components/wallet-account-display/wallet-account-display.component.ts.html +505 -0
  30. package/coverage/lcov-report/lib/components/wallet-connect-button/index.html +116 -0
  31. package/coverage/lcov-report/lib/components/wallet-connect-button/wallet-connect-button.component.ts.html +805 -0
  32. package/coverage/lcov-report/lib/components/wallet-connect-prompt/index.html +116 -0
  33. package/coverage/lcov-report/lib/components/wallet-connect-prompt/wallet-connect-prompt.component.ts.html +409 -0
  34. package/coverage/lcov-report/lib/components/wallet-connected-guard/index.html +116 -0
  35. package/coverage/lcov-report/lib/components/wallet-connected-guard/wallet-connected-guard.component.ts.html +304 -0
  36. package/coverage/lcov-report/lib/components/wallet-connection-modal/connection-method-step/connection-method-step.component.ts.html +436 -0
  37. package/coverage/lcov-report/lib/components/wallet-connection-modal/connection-method-step/index.html +116 -0
  38. package/coverage/lcov-report/lib/components/wallet-connection-modal/index.html +116 -0
  39. package/coverage/lcov-report/lib/components/wallet-connection-modal/qr-pairing-step/index.html +116 -0
  40. package/coverage/lcov-report/lib/components/wallet-connection-modal/qr-pairing-step/qr-pairing-step.component.ts.html +2287 -0
  41. package/coverage/lcov-report/lib/components/wallet-connection-modal/wallet-connection-modal.component.ts.html +2275 -0
  42. package/coverage/lcov-report/lib/components/wallet-session-display/index.html +116 -0
  43. package/coverage/lcov-report/lib/components/wallet-session-display/wallet-session-display.component.ts.html +676 -0
  44. package/coverage/lcov-report/lib/components/wallet-transaction-status/index.html +116 -0
  45. package/coverage/lcov-report/lib/components/wallet-transaction-status/wallet-transaction-status.component.ts.html +703 -0
  46. package/coverage/lcov-report/lib/directives/index.html +146 -0
  47. package/coverage/lcov-report/lib/directives/wallet-connected.directive.ts.html +670 -0
  48. package/coverage/lcov-report/lib/directives/wallet-context.directive.ts.html +547 -0
  49. package/coverage/lcov-report/lib/directives/wallet-events.directive.ts.html +781 -0
  50. package/coverage/lcov-report/lib/hsuite-wallet.module.ts.html +715 -0
  51. package/coverage/lcov-report/lib/index.html +116 -0
  52. package/coverage/lcov-report/lib/models/connection-config.model.ts.html +280 -0
  53. package/coverage/lcov-report/lib/models/index.html +131 -0
  54. package/coverage/lcov-report/lib/models/provider-types.ts.html +577 -0
  55. package/coverage/lcov-report/lib/providers/base-wallet-provider.ts.html +1138 -0
  56. package/coverage/lcov-report/lib/providers/hsuite-native/channel-client.service.ts.html +2671 -0
  57. package/coverage/lcov-report/lib/providers/hsuite-native/index.html +116 -0
  58. package/coverage/lcov-report/lib/providers/hsuite-native-provider.ts.html +2347 -0
  59. package/coverage/lcov-report/lib/providers/index.html +146 -0
  60. package/coverage/lcov-report/lib/providers/p2p-native/index.html +131 -0
  61. package/coverage/lcov-report/lib/providers/p2p-native/p2p-native.provider.ts.html +2254 -0
  62. package/coverage/lcov-report/lib/providers/p2p-native/p2p-session-manager.ts.html +2170 -0
  63. package/coverage/lcov-report/lib/providers/wallet-error-handler.ts.html +1132 -0
  64. package/coverage/lcov-report/lib/providers/walletconnect/core/index.html +176 -0
  65. package/coverage/lcov-report/lib/providers/walletconnect/core/session-health.ts.html +673 -0
  66. package/coverage/lcov-report/lib/providers/walletconnect/core/walletconnect-client-manager.ts.html +1177 -0
  67. package/coverage/lcov-report/lib/providers/walletconnect/core/walletconnect-provider.ts.html +2563 -0
  68. package/coverage/lcov-report/lib/providers/walletconnect/core/walletconnect-session-store.ts.html +904 -0
  69. package/coverage/lcov-report/lib/providers/walletconnect/core/walletconnect-signing-orchestrator.ts.html +982 -0
  70. package/coverage/lcov-report/lib/providers/walletconnect/signers/hedera-signer.ts.html +1915 -0
  71. package/coverage/lcov-report/lib/providers/walletconnect/signers/index.html +146 -0
  72. package/coverage/lcov-report/lib/providers/walletconnect/signers/signer-factory.ts.html +445 -0
  73. package/coverage/lcov-report/lib/providers/walletconnect/signers/xrpl-signer.ts.html +1519 -0
  74. package/coverage/lcov-report/lib/services/index.html +191 -0
  75. package/coverage/lcov-report/lib/services/logger.service.ts.html +463 -0
  76. package/coverage/lcov-report/lib/services/transaction-builders/base-transaction-builder.service.ts.html +1840 -0
  77. package/coverage/lcov-report/lib/services/transaction-builders/hedera-amount-utils.ts.html +337 -0
  78. package/coverage/lcov-report/lib/services/transaction-builders/hedera-transaction-builder.service.ts.html +3940 -0
  79. package/coverage/lcov-report/lib/services/transaction-builders/index.html +161 -0
  80. package/coverage/lcov-report/lib/services/transaction-builders/xrpl-transaction-builder.service.ts.html +2581 -0
  81. package/coverage/lcov-report/lib/services/transaction.service.ts.html +1123 -0
  82. package/coverage/lcov-report/lib/services/unified-wallet.service.ts.html +2641 -0
  83. package/coverage/lcov-report/lib/services/wallet-context.service.ts.html +637 -0
  84. package/coverage/lcov-report/lib/services/wallet-event-bus.service.ts.html +643 -0
  85. package/coverage/lcov-report/lib/services/wallet-providers.service.ts.html +496 -0
  86. package/coverage/lcov-report/lib/transports/chrome-extension-transport.ts.html +823 -0
  87. package/coverage/lcov-report/lib/transports/index.html +116 -0
  88. package/coverage/lcov-report/lib/utils/index.html +116 -0
  89. package/coverage/lcov-report/lib/utils/ledger-icons.util.ts.html +319 -0
  90. package/coverage/lcov-report/prettify.css +1 -0
  91. package/coverage/lcov-report/prettify.js +2 -0
  92. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  93. package/coverage/lcov-report/sorter.js +210 -0
  94. package/coverage/lcov.info +19252 -0
  95. package/coverage/lib/components/account-selector/account-actions/account-actions.component.ts.html +868 -0
  96. package/coverage/lib/components/account-selector/account-actions/index.html +116 -0
  97. package/coverage/lib/components/account-selector/account-filter/account-filter.component.ts.html +1288 -0
  98. package/coverage/lib/components/account-selector/account-filter/index.html +116 -0
  99. package/coverage/lib/components/account-selector/account-formatting.service.ts.html +685 -0
  100. package/coverage/lib/components/account-selector/account-grouping.service.ts.html +766 -0
  101. package/coverage/lib/components/account-selector/account-list/account-list.component.ts.html +1495 -0
  102. package/coverage/lib/components/account-selector/account-list/index.html +116 -0
  103. package/coverage/lib/components/account-selector/account-selector.component.ts.html +1495 -0
  104. package/coverage/lib/components/account-selector/account-selector.service.ts.html +1588 -0
  105. package/coverage/lib/components/account-selector/index.html +161 -0
  106. package/coverage/lib/components/wallet-account-display/index.html +116 -0
  107. package/coverage/lib/components/wallet-account-display/wallet-account-display.component.ts.html +505 -0
  108. package/coverage/lib/components/wallet-connect-button/index.html +116 -0
  109. package/coverage/lib/components/wallet-connect-button/wallet-connect-button.component.ts.html +805 -0
  110. package/coverage/lib/components/wallet-connect-prompt/index.html +116 -0
  111. package/coverage/lib/components/wallet-connect-prompt/wallet-connect-prompt.component.ts.html +409 -0
  112. package/coverage/lib/components/wallet-connected-guard/index.html +116 -0
  113. package/coverage/lib/components/wallet-connected-guard/wallet-connected-guard.component.ts.html +304 -0
  114. package/coverage/lib/components/wallet-connection-modal/connection-method-step/connection-method-step.component.ts.html +436 -0
  115. package/coverage/lib/components/wallet-connection-modal/connection-method-step/index.html +116 -0
  116. package/coverage/lib/components/wallet-connection-modal/index.html +116 -0
  117. package/coverage/lib/components/wallet-connection-modal/qr-pairing-step/index.html +116 -0
  118. package/coverage/lib/components/wallet-connection-modal/qr-pairing-step/qr-pairing-step.component.ts.html +2287 -0
  119. package/coverage/lib/components/wallet-connection-modal/wallet-connection-modal.component.ts.html +2275 -0
  120. package/coverage/lib/components/wallet-session-display/index.html +116 -0
  121. package/coverage/lib/components/wallet-session-display/wallet-session-display.component.ts.html +676 -0
  122. package/coverage/lib/components/wallet-transaction-status/index.html +116 -0
  123. package/coverage/lib/components/wallet-transaction-status/wallet-transaction-status.component.ts.html +703 -0
  124. package/coverage/lib/directives/index.html +146 -0
  125. package/coverage/lib/directives/wallet-connected.directive.ts.html +670 -0
  126. package/coverage/lib/directives/wallet-context.directive.ts.html +547 -0
  127. package/coverage/lib/directives/wallet-events.directive.ts.html +781 -0
  128. package/coverage/lib/hsuite-wallet.module.ts.html +715 -0
  129. package/coverage/lib/index.html +116 -0
  130. package/coverage/lib/models/connection-config.model.ts.html +280 -0
  131. package/coverage/lib/models/index.html +131 -0
  132. package/coverage/lib/models/provider-types.ts.html +577 -0
  133. package/coverage/lib/providers/base-wallet-provider.ts.html +1138 -0
  134. package/coverage/lib/providers/hsuite-native/channel-client.service.ts.html +2671 -0
  135. package/coverage/lib/providers/hsuite-native/index.html +116 -0
  136. package/coverage/lib/providers/hsuite-native-provider.ts.html +2347 -0
  137. package/coverage/lib/providers/index.html +146 -0
  138. package/coverage/lib/providers/p2p-native/index.html +131 -0
  139. package/coverage/lib/providers/p2p-native/p2p-native.provider.ts.html +2254 -0
  140. package/coverage/lib/providers/p2p-native/p2p-session-manager.ts.html +2170 -0
  141. package/coverage/lib/providers/wallet-error-handler.ts.html +1132 -0
  142. package/coverage/lib/providers/walletconnect/core/index.html +176 -0
  143. package/coverage/lib/providers/walletconnect/core/session-health.ts.html +673 -0
  144. package/coverage/lib/providers/walletconnect/core/walletconnect-client-manager.ts.html +1177 -0
  145. package/coverage/lib/providers/walletconnect/core/walletconnect-provider.ts.html +2563 -0
  146. package/coverage/lib/providers/walletconnect/core/walletconnect-session-store.ts.html +904 -0
  147. package/coverage/lib/providers/walletconnect/core/walletconnect-signing-orchestrator.ts.html +982 -0
  148. package/coverage/lib/providers/walletconnect/signers/hedera-signer.ts.html +1915 -0
  149. package/coverage/lib/providers/walletconnect/signers/index.html +146 -0
  150. package/coverage/lib/providers/walletconnect/signers/signer-factory.ts.html +445 -0
  151. package/coverage/lib/providers/walletconnect/signers/xrpl-signer.ts.html +1519 -0
  152. package/coverage/lib/services/index.html +191 -0
  153. package/coverage/lib/services/logger.service.ts.html +463 -0
  154. package/coverage/lib/services/transaction-builders/base-transaction-builder.service.ts.html +1840 -0
  155. package/coverage/lib/services/transaction-builders/hedera-amount-utils.ts.html +337 -0
  156. package/coverage/lib/services/transaction-builders/hedera-transaction-builder.service.ts.html +3940 -0
  157. package/coverage/lib/services/transaction-builders/index.html +161 -0
  158. package/coverage/lib/services/transaction-builders/xrpl-transaction-builder.service.ts.html +2581 -0
  159. package/coverage/lib/services/transaction.service.ts.html +1123 -0
  160. package/coverage/lib/services/unified-wallet.service.ts.html +2641 -0
  161. package/coverage/lib/services/wallet-context.service.ts.html +637 -0
  162. package/coverage/lib/services/wallet-event-bus.service.ts.html +643 -0
  163. package/coverage/lib/services/wallet-providers.service.ts.html +496 -0
  164. package/coverage/lib/transports/chrome-extension-transport.ts.html +823 -0
  165. package/coverage/lib/transports/index.html +116 -0
  166. package/coverage/lib/utils/index.html +116 -0
  167. package/coverage/lib/utils/ledger-icons.util.ts.html +319 -0
  168. package/coverage/prettify.css +1 -0
  169. package/coverage/prettify.js +2 -0
  170. package/coverage/sort-arrow-sprite.png +0 -0
  171. package/coverage/sorter.js +210 -0
  172. package/dist/README.md +48 -0
  173. package/dist/fesm2022/hsuite-native-connect-angular.mjs +14592 -0
  174. package/dist/fesm2022/hsuite-native-connect-angular.mjs.map +1 -0
  175. package/dist/index.d.ts +6949 -0
  176. package/examples/minimal-connect.ts +178 -0
  177. package/examples/multi-protocol.ts +495 -0
  178. package/examples/transaction-signing.ts +361 -0
  179. package/jest.config.json +45 -0
  180. package/karma.conf.js +42 -0
  181. package/ng-package.json +20 -0
  182. package/package.json +60 -0
  183. package/src/index.ts +203 -0
  184. package/src/lib/components/account-selector/account-actions/account-actions.component.ts +261 -0
  185. package/src/lib/components/account-selector/account-filter/account-filter.component.ts +401 -0
  186. package/src/lib/components/account-selector/account-formatting.service.ts +200 -0
  187. package/src/lib/components/account-selector/account-grouping.service.ts +227 -0
  188. package/src/lib/components/account-selector/account-list/account-list.component.ts +470 -0
  189. package/src/lib/components/account-selector/account-selector.component.html +135 -0
  190. package/src/lib/components/account-selector/account-selector.component.scss +2039 -0
  191. package/src/lib/components/account-selector/account-selector.component.ts +470 -0
  192. package/src/lib/components/account-selector/account-selector.service.ts +501 -0
  193. package/src/lib/components/wallet-account-display/wallet-account-display.component.html +34 -0
  194. package/src/lib/components/wallet-account-display/wallet-account-display.component.scss +99 -0
  195. package/src/lib/components/wallet-account-display/wallet-account-display.component.ts +140 -0
  196. package/src/lib/components/wallet-connect-button/wallet-connect-button.component.html +14 -0
  197. package/src/lib/components/wallet-connect-button/wallet-connect-button.component.scss +272 -0
  198. package/src/lib/components/wallet-connect-button/wallet-connect-button.component.ts +240 -0
  199. package/src/lib/components/wallet-connect-prompt/wallet-connect-prompt.component.html +24 -0
  200. package/src/lib/components/wallet-connect-prompt/wallet-connect-prompt.component.scss +50 -0
  201. package/src/lib/components/wallet-connect-prompt/wallet-connect-prompt.component.ts +108 -0
  202. package/src/lib/components/wallet-connected-guard/wallet-connected-guard.component.html +24 -0
  203. package/src/lib/components/wallet-connected-guard/wallet-connected-guard.component.ts +73 -0
  204. package/src/lib/components/wallet-connection-modal/connection-method-step/connection-method-step.component.html +56 -0
  205. package/src/lib/components/wallet-connection-modal/connection-method-step/connection-method-step.component.scss +218 -0
  206. package/src/lib/components/wallet-connection-modal/connection-method-step/connection-method-step.component.ts +117 -0
  207. package/src/lib/components/wallet-connection-modal/qr-pairing-step/qr-pairing-step.component.html +94 -0
  208. package/src/lib/components/wallet-connection-modal/qr-pairing-step/qr-pairing-step.component.scss +272 -0
  209. package/src/lib/components/wallet-connection-modal/qr-pairing-step/qr-pairing-step.component.ts +734 -0
  210. package/src/lib/components/wallet-connection-modal/wallet-connection-modal.component.html +197 -0
  211. package/src/lib/components/wallet-connection-modal/wallet-connection-modal.component.scss +678 -0
  212. package/src/lib/components/wallet-connection-modal/wallet-connection-modal.component.ts +730 -0
  213. package/src/lib/components/wallet-session-display/wallet-session-display.component.html +110 -0
  214. package/src/lib/components/wallet-session-display/wallet-session-display.component.scss +179 -0
  215. package/src/lib/components/wallet-session-display/wallet-session-display.component.ts +197 -0
  216. package/src/lib/components/wallet-transaction-status/wallet-transaction-status.component.html +65 -0
  217. package/src/lib/components/wallet-transaction-status/wallet-transaction-status.component.scss +254 -0
  218. package/src/lib/components/wallet-transaction-status/wallet-transaction-status.component.ts +206 -0
  219. package/src/lib/directives/wallet-connected.directive.ts +195 -0
  220. package/src/lib/directives/wallet-context.directive.ts +154 -0
  221. package/src/lib/directives/wallet-events.directive.ts +232 -0
  222. package/src/lib/hsuite-wallet.module.ts +210 -0
  223. package/src/lib/models/connection-config.model.ts +65 -0
  224. package/src/lib/models/provider-types.ts +164 -0
  225. package/src/lib/models/unified-account.model.ts +76 -0
  226. package/src/lib/models/wallet-context.model.ts +121 -0
  227. package/src/lib/models/wallet-events.model.ts +158 -0
  228. package/src/lib/providers/base-wallet-provider.ts +351 -0
  229. package/src/lib/providers/hsuite-native/channel-client.service.spec.ts +73 -0
  230. package/src/lib/providers/hsuite-native/channel-client.service.ts +862 -0
  231. package/src/lib/providers/hsuite-native/index.ts +8 -0
  232. package/src/lib/providers/hsuite-native-provider.ts +754 -0
  233. package/src/lib/providers/mobile-native/mobile-native.provider.spec.ts +19 -0
  234. package/src/lib/providers/p2p-native/index.ts +30 -0
  235. package/src/lib/providers/p2p-native/p2p-native.provider.spec.ts +523 -0
  236. package/src/lib/providers/p2p-native/p2p-native.provider.ts +723 -0
  237. package/src/lib/providers/p2p-native/p2p-session-manager.ts +695 -0
  238. package/src/lib/providers/wallet-error-handler.ts +349 -0
  239. package/src/lib/providers/walletconnect/core/base-signer.interface.ts +122 -0
  240. package/src/lib/providers/walletconnect/core/session-health.ts +196 -0
  241. package/src/lib/providers/walletconnect/core/walletconnect-client-manager.ts +364 -0
  242. package/src/lib/providers/walletconnect/core/walletconnect-provider.integration.spec.ts +348 -0
  243. package/src/lib/providers/walletconnect/core/walletconnect-provider.ts +826 -0
  244. package/src/lib/providers/walletconnect/core/walletconnect-session-store.ts +273 -0
  245. package/src/lib/providers/walletconnect/core/walletconnect-signing-orchestrator.ts +299 -0
  246. package/src/lib/providers/walletconnect/core/walletconnect-types.ts +48 -0
  247. package/src/lib/providers/walletconnect/index.ts +33 -0
  248. package/src/lib/providers/walletconnect/signers/hedera-signer.spec.ts +367 -0
  249. package/src/lib/providers/walletconnect/signers/hedera-signer.ts +610 -0
  250. package/src/lib/providers/walletconnect/signers/signer-factory.spec.ts +62 -0
  251. package/src/lib/providers/walletconnect/signers/signer-factory.ts +120 -0
  252. package/src/lib/providers/walletconnect/signers/xrpl-signer.spec.ts +296 -0
  253. package/src/lib/providers/walletconnect/signers/xrpl-signer.ts +478 -0
  254. package/src/lib/services/logger.service.ts +126 -0
  255. package/src/lib/services/transaction-builders/base-transaction-builder.service.ts +585 -0
  256. package/src/lib/services/transaction-builders/hedera-amount-utils.ts +84 -0
  257. package/src/lib/services/transaction-builders/hedera-transaction-builder.service.spec.ts +741 -0
  258. package/src/lib/services/transaction-builders/hedera-transaction-builder.service.ts +1285 -0
  259. package/src/lib/services/transaction-builders/index.ts +54 -0
  260. package/src/lib/services/transaction-builders/xrpl-transaction-builder.service.spec.ts +937 -0
  261. package/src/lib/services/transaction-builders/xrpl-transaction-builder.service.ts +832 -0
  262. package/src/lib/services/transaction.service.ts +346 -0
  263. package/src/lib/services/unified-wallet.service.spec.ts +1382 -0
  264. package/src/lib/services/unified-wallet.service.ts +852 -0
  265. package/src/lib/services/wallet-context.service.ts +184 -0
  266. package/src/lib/services/wallet-event-bus.service.ts +186 -0
  267. package/src/lib/services/wallet-providers.service.ts +137 -0
  268. package/src/lib/transports/chrome-extension-transport.ts +246 -0
  269. package/src/lib/utils/index.ts +14 -0
  270. package/src/lib/utils/ledger-icons.util.ts +78 -0
  271. package/test/test-setup.ts +21 -0
  272. package/test-setup.ts +63 -0
  273. package/tsconfig.build.json +11 -0
  274. package/tsconfig.json +29 -0
  275. package/tsconfig.spec.json +15 -0
  276. package/vitest.config.ts +48 -0
@@ -0,0 +1,610 @@
1
+ /**
2
+ * HSuite Native Connect
3
+ * Copyright 2024-2025 HSuite (https://hsuite.finance)
4
+ *
5
+ * SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
6
+ *
7
+ * This file is part of HSuite Native Connect. For commercial licensing,
8
+ * visit https://hsuite.finance/licensing
9
+ */
10
+
11
+ /**
12
+ * @file hedera-signer.ts
13
+ * @description Hedera-specific WalletConnect signer implementation
14
+ *
15
+ * Implements the IWalletConnectSigner interface for Hedera ledger.
16
+ * Handles Hedera-specific JSON-RPC methods and transaction formats.
17
+ */
18
+
19
+ import { getLogger } from '@hsuite/native-connect-sdk';
20
+
21
+ import type { SignResult, SubmitResult } from '../../base-wallet-provider';
22
+ import type { IWalletConnectSigner, SignerOperationParams } from '../core/base-signer.interface';
23
+
24
+ const logger = getLogger().scoped?.('HederaSigner') ?? getLogger();
25
+
26
+ /**
27
+ * Extract a meaningful error message from various error shapes.
28
+ * WalletConnect SDK throws errors that are not standard Error instances.
29
+ *
30
+ * Handles:
31
+ * - Standard Error objects
32
+ * - WalletConnect SDK errors: { code, message }
33
+ * - Nested errors: { error: { message } }
34
+ * - PIN_REQUIRED errors from wallet
35
+ * - DOMException objects
36
+ * - Objects that stringify to {} (enumerable property check)
37
+ *
38
+ * @param error - Error from WalletConnect SDK (can be Error, object, or string)
39
+ * @returns Human-readable error message
40
+ */
41
+ function extractErrorMessage(error: unknown): string {
42
+ // Log the raw error for debugging
43
+ logger.debug('Extracting error message', {
44
+ errorType: typeof error,
45
+ isError: error instanceof Error,
46
+ constructorName: error?.constructor?.name,
47
+ protoName: Object.getPrototypeOf(error)?.constructor?.name,
48
+ });
49
+
50
+ if (error instanceof Error) {
51
+ return error.message;
52
+ }
53
+
54
+ if (typeof error === 'object' && error !== null) {
55
+ const errorObj = error as Record<string, unknown>;
56
+
57
+ // Try to get all properties including non-enumerable and from prototype
58
+ const allProps = Object.getOwnPropertyNames(error).concat(
59
+ Object.getOwnPropertyNames(Object.getPrototypeOf(error) || {}),
60
+ );
61
+
62
+ // Log ALL property values for debugging
63
+ const propDebug: Record<string, string> = {};
64
+ for (const prop of allProps.slice(0, 15)) {
65
+ try {
66
+ const val = (error as any)[prop];
67
+ if (typeof val === 'function') {
68
+ propDebug[prop] = '[fn]';
69
+ } else if (val === undefined) {
70
+ propDebug[prop] = 'undef';
71
+ } else if (val === null) {
72
+ propDebug[prop] = 'null';
73
+ } else if (typeof val === 'object') {
74
+ propDebug[prop] = JSON.stringify(val)?.substring(0, 60) || '[obj]';
75
+ } else {
76
+ propDebug[prop] = String(val).substring(0, 60);
77
+ }
78
+ } catch (_e) {
79
+ propDebug[prop] = '[err]';
80
+ }
81
+ }
82
+ logger.error('WC Error object dump', propDebug);
83
+
84
+ // Check for code property first (common in SDK errors like PIN_REQUIRED)
85
+ if (typeof errorObj['code'] === 'string') {
86
+ const codeMsg = errorObj['message']
87
+ ? `${errorObj['code']}: ${errorObj['message']}`
88
+ : errorObj['code'];
89
+ return codeMsg as string;
90
+ }
91
+
92
+ // WalletConnect error format: { code, message }
93
+ if ('message' in errorObj && typeof errorObj['message'] === 'string') {
94
+ return errorObj['message'];
95
+ }
96
+
97
+ // Nested error: { error: { message } }
98
+ if (
99
+ 'error' in errorObj &&
100
+ typeof errorObj['error'] === 'object' &&
101
+ errorObj['error'] !== null
102
+ ) {
103
+ const nested = errorObj['error'] as Record<string, unknown>;
104
+ if ('message' in nested && typeof nested['message'] === 'string') {
105
+ return nested['message'];
106
+ }
107
+ if ('code' in nested && typeof nested['code'] === 'string') {
108
+ return nested['code'];
109
+ }
110
+ }
111
+
112
+ // Check for DOMException
113
+ if (error instanceof DOMException) {
114
+ return error.message || error.name;
115
+ }
116
+
117
+ // Check for reason/cause properties (common in rejected promises)
118
+ if ('reason' in errorObj && typeof errorObj['reason'] === 'string') {
119
+ return errorObj['reason'];
120
+ }
121
+ if ('cause' in errorObj) {
122
+ const cause = errorObj['cause'];
123
+ if (typeof cause === 'string') return cause;
124
+ if (cause instanceof Error) return cause.message;
125
+ if (typeof cause === 'object' && cause !== null && 'message' in (cause as any)) {
126
+ return String((cause as any).message);
127
+ }
128
+ }
129
+
130
+ // WalletConnect SDK specific error formats
131
+ if ('context' in errorObj && typeof errorObj['context'] === 'string') {
132
+ return errorObj['context'];
133
+ }
134
+ if ('data' in errorObj && typeof errorObj['data'] === 'object' && errorObj['data'] !== null) {
135
+ const data = errorObj['data'] as Record<string, unknown>;
136
+ if ('message' in data && typeof data['message'] === 'string') {
137
+ return data['message'];
138
+ }
139
+ }
140
+
141
+ // WalletConnect JSON-RPC error format: { code: number, message: string }
142
+ if ('code' in errorObj && 'message' in errorObj) {
143
+ const code = errorObj['code'];
144
+ const msg = errorObj['message'];
145
+ if (typeof msg === 'string') {
146
+ return typeof code === 'number' ? `[${code}] ${msg}` : msg;
147
+ }
148
+ }
149
+
150
+ // Error with 'name' property (common for built-in errors)
151
+ if ('name' in errorObj && typeof errorObj['name'] === 'string') {
152
+ const name = errorObj['name'];
153
+ if (name && name !== 'Error' && name !== 'Object') {
154
+ return name;
155
+ }
156
+ }
157
+
158
+ // Try to stringify, but check if result is empty object
159
+ try {
160
+ const jsonStr = JSON.stringify(error);
161
+ // Don't return empty objects as error messages
162
+ if (jsonStr && jsonStr !== '{}' && jsonStr !== '[]') {
163
+ return jsonStr;
164
+ }
165
+
166
+ // If stringify returned {}, try Object.keys to get property names
167
+ const keys = Object.keys(errorObj);
168
+ if (keys.length > 0) {
169
+ return `Error object with keys: ${keys.join(', ')}`;
170
+ }
171
+
172
+ // Try getting own property names (includes non-enumerable)
173
+ const ownProps = Object.getOwnPropertyNames(errorObj);
174
+ logger.debug('Error object properties', { props: ownProps });
175
+
176
+ if (ownProps.length > 0) {
177
+ // Try to read ALL properties for better debugging
178
+ const propValues: string[] = [];
179
+ for (const k of ownProps) {
180
+ try {
181
+ const val = errorObj[k];
182
+ const valStr = typeof val === 'object' ? JSON.stringify(val) : String(val);
183
+ propValues.push(`${k}=${valStr?.substring(0, 100)}`);
184
+
185
+ // Check if any property contains error info
186
+ if (typeof val === 'object' && val !== null) {
187
+ if ('message' in val) return String((val as any).message);
188
+ if ('error' in val) return String((val as any).error);
189
+ }
190
+ } catch {
191
+ propValues.push(k);
192
+ }
193
+ }
194
+ logger.debug('Error property values', { values: propValues });
195
+
196
+ // Return a more informative error
197
+ return `Error with properties: ${propValues.slice(0, 5).join(', ')}`;
198
+ }
199
+ } catch (_e) {
200
+ // If stringify fails, try toString
201
+ const str = String(error);
202
+ if (str !== '[object Object]') {
203
+ return str;
204
+ }
205
+ }
206
+
207
+ // Last resort - log what we know about the object
208
+ logger.warn('Could not extract error message', {
209
+ constructor: error?.constructor?.name,
210
+ prototype: Object.getPrototypeOf(error)?.constructor?.name,
211
+ });
212
+ return `Unknown error (type: ${error?.constructor?.name || 'object'})`;
213
+ }
214
+
215
+ if (typeof error === 'string') {
216
+ return error;
217
+ }
218
+
219
+ return 'Unknown error';
220
+ }
221
+
222
+ /**
223
+ * Signer implementation for Hedera ledger.
224
+ *
225
+ * Supports Hedera-specific WalletConnect methods:
226
+ * - hedera_signTransaction (sign-only, returns signature map)
227
+ * - hedera_signAndExecuteTransaction (sign + submit, returns transaction ID)
228
+ * - hedera_executeTransaction
229
+ * - hedera_signMessage (arbitrary message signing)
230
+ *
231
+ * Uses HIP-30 account format: hedera:<network>:<accountId>
232
+ */
233
+ export class HederaSigner implements IWalletConnectSigner {
234
+ readonly ledgerId = 'hedera';
235
+
236
+ /**
237
+ * Build Hedera namespace configuration for WalletConnect.
238
+ * Defines the JSON-RPC methods and chains this signer supports.
239
+ *
240
+ * IMPORTANT: We request ALL Hedera networks (testnet, mainnet)
241
+ * to ensure wallets like Kabila can show accounts from unknown network.
242
+ * The wallet will return all accounts it has across all networks.
243
+ *
244
+ * @param networkId - Network identifier (e.g., 'hedera:testnet', 'hedera:mainnet')
245
+ * @param _networkId
246
+ * @returns Namespace configuration object
247
+ */
248
+ buildNamespace(_networkId: string): {
249
+ methods: string[];
250
+ chains: string[];
251
+ events: string[];
252
+ } {
253
+ // Request ALL Hedera networks to allow wallets to show all their accounts
254
+ // Wallets like Kabila may have accounts on multiple networks
255
+
256
+ return {
257
+ methods: [
258
+ 'hedera_signTransaction',
259
+ 'hedera_signAndExecuteTransaction',
260
+ 'hedera_executeTransaction',
261
+ 'hedera_signMessage',
262
+ ],
263
+ // Request all Hedera networks so wallet can show all accounts
264
+ chains: ['hedera:testnet', 'hedera:mainnet'],
265
+ events: ['chainChanged', 'accountsChanged'],
266
+ };
267
+ }
268
+
269
+ /**
270
+ * Sign a Hedera transaction without submitting to network.
271
+ * Uses hedera_signTransaction method with TransactionList format.
272
+ *
273
+ * The payload comes as TransactionList (base64 frozen transaction bytes).
274
+ * We send the full TransactionList to preserve canonical bytes for signing.
275
+ * The wallet will sign and return the signature map without executing.
276
+ *
277
+ * @param params - Signing parameters (payload is TransactionList base64)
278
+ * @returns Promise resolving to signed transaction with signature map
279
+ */
280
+ async signTransaction(params: SignerOperationParams): Promise<SignResult> {
281
+ // Extract network name from networkId (mainnet or testnet)
282
+ const network = params.networkId.includes('mainnet') ? 'mainnet' : 'testnet';
283
+
284
+ // Format account ID in HIP-30 format
285
+ const signerAccountId = `hedera:${network}:${params.accountAddress}`;
286
+
287
+ try {
288
+ // Send the full TransactionList (frozen transaction bytes) to preserve canonical bytes.
289
+ // The wallet needs the complete transaction structure to sign correctly.
290
+ // Previously we converted to TransactionBody which caused INVALID_SIGNATURE errors
291
+ // because the signature was computed over different bytes than the network expected.
292
+
293
+ // Use 5-minute expiry to give users time to review and approve
294
+ const result = await params.client.request({
295
+ topic: params.topic,
296
+ chainId: `hedera:${network}`,
297
+ request: {
298
+ method: 'hedera_signTransaction',
299
+ params: {
300
+ signerAccountId,
301
+ transactionList: params.payload, // Send full TransactionList for correct signing
302
+ },
303
+ },
304
+ expiry: 300, // 5 minutes in seconds
305
+ });
306
+
307
+ return {
308
+ signedPayload: String(
309
+ (result as { signatureMap?: any }).signatureMap ||
310
+ (result as { signedTransaction?: any }).signedTransaction ||
311
+ JSON.stringify(result),
312
+ ),
313
+ metadata: { result },
314
+ };
315
+ } catch (error) {
316
+ // Check if this is an empty error object (WalletConnect SDK bug)
317
+ const isEmptyError =
318
+ typeof error === 'object' &&
319
+ error !== null &&
320
+ Object.keys(error).length === 0 &&
321
+ !(error instanceof Error);
322
+
323
+ if (isEmptyError) {
324
+ logger.warn('WalletConnect SDK empty error - relay subscription may have been stale', {
325
+ topic: params.topic?.substring(0, 16) + '...',
326
+ });
327
+ throw new Error('WalletConnect connection issue - please try again');
328
+ }
329
+
330
+ const errorMsg = extractErrorMessage(error);
331
+ logger.error('Sign failed', { error: errorMsg });
332
+ throw new Error(`Hedera sign failed: ${errorMsg}`);
333
+ }
334
+ }
335
+
336
+ /**
337
+ * Sign and submit a Hedera transaction to the network.
338
+ * Uses hedera_signAndExecuteTransaction method with TransactionList format.
339
+ *
340
+ * @param params - Submission parameters
341
+ * @returns Promise resolving to transaction ID and metadata
342
+ */
343
+ async submitTransaction(params: SignerOperationParams): Promise<SubmitResult> {
344
+ // Extract network name from networkId (mainnet or testnet)
345
+ const network = params.networkId.includes('mainnet') ? 'mainnet' : 'testnet';
346
+
347
+ // Format account ID in HIP-30 format
348
+ const signerAccountId = `hedera:${network}:${params.accountAddress}`;
349
+
350
+ logger.debug('Sending hedera_signAndExecuteTransaction request', {
351
+ topic: params.topic?.substring(0, 16) + '...',
352
+ chainId: `hedera:${network}`,
353
+ signerAccountId,
354
+ payloadLength: params.payload?.length,
355
+ });
356
+
357
+ // Create the request promise - keep-alive pings are handled by WalletConnectClientManager
358
+ const requestPromise = params.client.request({
359
+ topic: params.topic,
360
+ chainId: `hedera:${network}`,
361
+ request: {
362
+ method: 'hedera_signAndExecuteTransaction',
363
+ params: {
364
+ signerAccountId,
365
+ transactionList: params.payload, // TransactionList protobuf (base64)
366
+ },
367
+ },
368
+ expiry: 300, // 5 minutes in seconds
369
+ });
370
+
371
+ // Create a 5-minute timeout to override any internal SDK timeouts
372
+ const timeoutPromise = new Promise<never>((_, reject) => {
373
+ setTimeout(
374
+ () => {
375
+ reject(new Error('Transaction approval timed out after 5 minutes'));
376
+ },
377
+ 5 * 60 * 1000,
378
+ ); // 5 minutes
379
+ });
380
+
381
+ try {
382
+ // Race between the request and our timeout
383
+ const result = await Promise.race([requestPromise, timeoutPromise]);
384
+
385
+ logger.debug('Received hedera_signAndExecuteTransaction response', {
386
+ hasResult: !!result,
387
+ resultKeys: result ? Object.keys(result as object) : [],
388
+ });
389
+
390
+ return {
391
+ transactionId:
392
+ (result as { transactionId?: string }).transactionId ||
393
+ (result as { response?: { transactionId?: string } }).response?.transactionId ||
394
+ '',
395
+ transactionHash: (result as { transactionHash?: string }).transactionHash,
396
+ metadata: { result },
397
+ };
398
+ } catch (error) {
399
+ // Log full error for debugging - try to get more info from WalletConnect error
400
+ const wcError = error as any;
401
+
402
+ // Try to extract all available error info
403
+ const errorInfo = {
404
+ errorType: typeof error,
405
+ errorConstructor: error?.constructor?.name,
406
+ errorKeys: error && typeof error === 'object' ? Object.keys(error) : [],
407
+ errorString: String(error),
408
+ // WalletConnect specific error properties
409
+ wcCode: wcError?.code,
410
+ wcMessage: wcError?.message,
411
+ wcContext: wcError?.context,
412
+ wcData: wcError?.data,
413
+ // Try Symbol properties
414
+ symbols:
415
+ error && typeof error === 'object'
416
+ ? Object.getOwnPropertySymbols(error).map((s) => s.toString())
417
+ : [],
418
+ // Get all own property names
419
+ ownProps: error && typeof error === 'object' ? Object.getOwnPropertyNames(error) : [],
420
+ };
421
+
422
+ logger.error('Submit failed - full error analysis', errorInfo);
423
+
424
+ // Check if this is an empty error object (WalletConnect SDK bug)
425
+ // This typically happens when relay subscription is stale
426
+ const isEmptyError =
427
+ typeof error === 'object' &&
428
+ error !== null &&
429
+ Object.keys(error).length === 0 &&
430
+ !(error instanceof Error);
431
+
432
+ if (isEmptyError) {
433
+ // Empty error object is a known WalletConnect SDK issue
434
+ // It usually means the relay subscription was stale
435
+ // The SessionHealthManager should have pinged before the request,
436
+ // but if we still get this error, the subscription dropped after the ping
437
+ logger.warn('WalletConnect SDK empty error - relay subscription may have been stale', {
438
+ topic: params.topic?.substring(0, 16) + '...',
439
+ hint: 'This can happen when the relay WebSocket connection drops',
440
+ });
441
+ // NOTE: We can't retry because the wallet may have already executed the transaction.
442
+ // A retry could cause a duplicate transaction on Hedera.
443
+ throw new Error(
444
+ 'WalletConnect connection dropped - if you approved the transaction in your wallet, ' +
445
+ 'please check your transaction history as it may have been executed successfully.',
446
+ );
447
+ }
448
+
449
+ const errorMsg = extractErrorMessage(error);
450
+ logger.error('Submit failed', { error: errorMsg });
451
+ throw new Error(`Hedera submit failed: ${errorMsg}`);
452
+ }
453
+ }
454
+
455
+ /**
456
+ * Sign and execute a transaction in one call (alias for submitTransaction).
457
+ * Uses hedera_signAndExecuteTransaction WalletConnect RPC method.
458
+ * This prevents double prompts by using the native WalletConnect method.
459
+ *
460
+ * @param params - Transaction parameters
461
+ * @returns Promise resolving to transaction ID and metadata
462
+ */
463
+ async signAndExecuteTransaction(params: SignerOperationParams): Promise<SubmitResult> {
464
+ // Simply delegate to submitTransaction which already uses hedera_signAndExecuteTransaction
465
+ return this.submitTransaction(params);
466
+ }
467
+
468
+ /**
469
+ * Sign an arbitrary message using hedera_signMessage method.
470
+ * Used for DAO votes, authentication proofs, and other non-transaction signing.
471
+ *
472
+ * @param params - Signing parameters with message string
473
+ * @returns Promise resolving to signature and metadata
474
+ */
475
+ async signMessage(
476
+ params: SignerOperationParams & { message: string; encoding?: 'utf-8' | 'base64' },
477
+ ): Promise<import('../../base-wallet-provider').SignMessageResult> {
478
+ // Extract network name from networkId (mainnet or testnet)
479
+ const network = params.networkId.includes('mainnet') ? 'mainnet' : 'testnet';
480
+
481
+ // Format account ID in HIP-30 format
482
+ const signerAccountId = `hedera:${network}:${params.accountAddress}`;
483
+
484
+ try {
485
+ // Use 5-minute expiry to give users time to review and approve
486
+ const result = await params.client.request({
487
+ topic: params.topic,
488
+ chainId: `hedera:${network}`,
489
+ request: {
490
+ method: 'hedera_signMessage',
491
+ params: {
492
+ signerAccountId,
493
+ message: params.message, // Can be base64 or utf-8
494
+ },
495
+ },
496
+ expiry: 300, // 5 minutes in seconds
497
+ });
498
+
499
+ return {
500
+ signature: String(
501
+ (result as { signatureMap?: any }).signatureMap ||
502
+ (result as { signature?: any }).signature ||
503
+ JSON.stringify(result),
504
+ ),
505
+ publicKey: (result as { publicKey?: string }).publicKey,
506
+ metadata: { result },
507
+ };
508
+ } catch (error) {
509
+ // Check if this is an empty error object (WalletConnect SDK bug)
510
+ const isEmptyError =
511
+ typeof error === 'object' &&
512
+ error !== null &&
513
+ Object.keys(error).length === 0 &&
514
+ !(error instanceof Error);
515
+
516
+ if (isEmptyError) {
517
+ logger.warn('WalletConnect SDK empty error - relay subscription may have been stale', {
518
+ topic: params.topic?.substring(0, 16) + '...',
519
+ });
520
+ throw new Error('WalletConnect connection issue - please try again');
521
+ }
522
+
523
+ const errorMsg = extractErrorMessage(error);
524
+ logger.error('Sign message failed', { error: errorMsg });
525
+ throw new Error(`Hedera sign message failed: ${errorMsg}`);
526
+ }
527
+ }
528
+
529
+ /**
530
+ * Parse accounts from Hedera namespace in WalletConnect session.
531
+ * Extracts account IDs from HIP-30 format: hedera:<network>:<accountId>
532
+ *
533
+ * Optionally filters accounts to only include those matching a target network.
534
+ * This is important for respecting the user's network selection in the dApp,
535
+ * since wallets may return accounts from multiple networks.
536
+ *
537
+ * @param namespace - Hedera namespace object from session
538
+ * @param targetNetworkId - Optional network ID to filter accounts (e.g., 'hedera:mainnet')
539
+ * @returns Array of parsed account objects, filtered by target network if specified
540
+ */
541
+ parseAccounts(
542
+ namespace: any,
543
+ targetNetworkId?: string,
544
+ ): Array<{ address: string; chainId: string }> {
545
+ if (!namespace) {
546
+ logger.warn('Namespace is null or undefined');
547
+ return [];
548
+ }
549
+
550
+ if (!(namespace as { accounts?: string[] }).accounts) {
551
+ logger.warn('Namespace has no accounts property');
552
+ return [];
553
+ }
554
+
555
+ if (!Array.isArray((namespace as { accounts?: string[] }).accounts)) {
556
+ logger.warn('Namespace accounts is not an array', {
557
+ type: typeof (namespace as { accounts?: string[] }).accounts,
558
+ });
559
+ return [];
560
+ }
561
+
562
+ // Parse all accounts from namespace
563
+ const allAccounts = ((namespace as { accounts?: string[] }).accounts || []).map(
564
+ (account: string) => {
565
+ // HIP-30 format: hedera:testnet:0.0.12345
566
+ const parts = account.split(':');
567
+
568
+ if (parts.length < 3) {
569
+ logger.warn('Invalid account format', { account, partsLength: parts.length });
570
+ return { address: account, chainId: 'hedera:testnet' };
571
+ }
572
+
573
+ return {
574
+ address: parts[parts.length - 1], // Account ID (0.0.12345)
575
+ chainId: `${parts[0]}:${parts[1]}`, // hedera:testnet
576
+ };
577
+ },
578
+ );
579
+
580
+ // If target network specified, filter accounts to only that network
581
+ if (targetNetworkId) {
582
+ const targetNetwork = targetNetworkId.split(':')[1]?.toLowerCase(); // e.g., 'mainnet'
583
+
584
+ if (targetNetwork) {
585
+ const filteredAccounts = allAccounts.filter((acc) =>
586
+ acc.chainId.toLowerCase().includes(targetNetwork),
587
+ );
588
+
589
+ logger.debug('Filtered accounts by target network', {
590
+ targetNetworkId,
591
+ targetNetwork,
592
+ totalAccounts: allAccounts.length,
593
+ filteredCount: filteredAccounts.length,
594
+ });
595
+
596
+ // If no accounts match the target network, log a warning
597
+ if (filteredAccounts.length === 0 && allAccounts.length > 0) {
598
+ logger.warn('No accounts found for target network', {
599
+ targetNetwork,
600
+ availableNetworks: [...new Set(allAccounts.map((a) => a.chainId))],
601
+ });
602
+ }
603
+
604
+ return filteredAccounts;
605
+ }
606
+ }
607
+
608
+ return allAccounts;
609
+ }
610
+ }
@@ -0,0 +1,62 @@
1
+ /**
2
+ * @file signer-factory.spec.ts
3
+ * @description Unit tests for SignerFactory
4
+ *
5
+ * @compodoc
6
+ * SignerFactory uses static methods for signer management,
7
+ * so tests call static methods directly rather than using DI.
8
+ */
9
+
10
+ import { describe, it, expect, beforeEach, vi } from 'vitest';
11
+ import { SignerFactory } from './signer-factory';
12
+
13
+ describe('SignerFactory', () => {
14
+ describe('getSigner', () => {
15
+ it('should return a signer for "hedera" ledger ID', () => {
16
+ const signer = SignerFactory.getSigner('hedera');
17
+
18
+ expect(signer).toBeDefined();
19
+ expect(signer.ledgerId).toBe('hedera');
20
+ });
21
+
22
+ it('should return a signer for "xrpl" ledger ID', () => {
23
+ const signer = SignerFactory.getSigner('xrpl');
24
+
25
+ expect(signer).toBeDefined();
26
+ expect(signer.ledgerId).toBe('xrpl');
27
+ });
28
+
29
+ it('should throw error for unknown ledger ID', () => {
30
+ expect(() => {
31
+ SignerFactory.getSigner('unknown');
32
+ }).toThrow('Unsupported ledger: unknown');
33
+ });
34
+
35
+ it('should return same instance on multiple calls (singleton)', () => {
36
+ const signer1 = SignerFactory.getSigner('hedera');
37
+ const signer2 = SignerFactory.getSigner('hedera');
38
+
39
+ expect(signer1).toBe(signer2);
40
+ });
41
+ });
42
+
43
+ describe('hasSigner', () => {
44
+ it('should return true for registered ledger', () => {
45
+ expect(SignerFactory.hasSigner('hedera')).toBe(true);
46
+ expect(SignerFactory.hasSigner('xrpl')).toBe(true);
47
+ });
48
+
49
+ it('should return false for unregistered ledger', () => {
50
+ expect(SignerFactory.hasSigner('solana')).toBe(false);
51
+ });
52
+ });
53
+
54
+ describe('getAvailableLedgers', () => {
55
+ it('should return list of available ledgers', () => {
56
+ const ledgers = SignerFactory.getAvailableLedgers();
57
+
58
+ expect(ledgers).toContain('hedera');
59
+ expect(ledgers).toContain('xrpl');
60
+ });
61
+ });
62
+ });