@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,1285 @@
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 Service for building Hedera transactions using the Hashgraph SDK.
13
+ *
14
+ * @module services/transaction-builders/hedera
15
+ *
16
+ * @description
17
+ * HederaTransactionBuilderService provides methods to construct various Hedera transaction
18
+ * types and serialize them to base64 payloads ready for signing. It uses the @hashgraph/sdk
19
+ * library for proper transaction construction and automatically freezes transactions with
20
+ * the correct network client.
21
+ *
22
+ * **Supported Transaction Types:**
23
+ * - HBAR transfers (single and atomic multi-transfer)
24
+ * - Fungible token transfers
25
+ * - NFT transfers
26
+ * - Token association/dissociation
27
+ * - Token creation (with auto key configuration)
28
+ * - Token minting/burning
29
+ * - Topic creation (with auto key configuration)
30
+ * - Topic message submission
31
+ * - Account creation (with multisig support)
32
+ * - Account updates
33
+ * - Batch transactions
34
+ *
35
+ * **Auto-Key Configuration:**
36
+ * When creating entities (topics, tokens, accounts), the builder automatically
37
+ * inherits the key structure from the active account:
38
+ * - Single-sig accounts -> entity uses account's public key
39
+ * - Multisig accounts -> entity uses threshold key with all signers
40
+ */
41
+
42
+ import { Injectable, inject } from '@angular/core';
43
+ import {
44
+ TransferTransaction,
45
+ TokenAssociateTransaction,
46
+ TokenDissociateTransaction,
47
+ TokenCreateTransaction,
48
+ TokenMintTransaction,
49
+ TokenBurnTransaction,
50
+ TopicCreateTransaction,
51
+ TopicMessageSubmitTransaction,
52
+ AccountCreateTransaction,
53
+ AccountUpdateTransaction,
54
+ ScheduleCreateTransaction,
55
+ BatchTransaction,
56
+ Transaction,
57
+ Hbar,
58
+ AccountId,
59
+ TokenId,
60
+ NftId,
61
+ TransactionId,
62
+ Client,
63
+ PrivateKey,
64
+ PublicKey,
65
+ TokenType,
66
+ TokenSupplyType,
67
+ KeyList,
68
+ Key,
69
+ } from '@hashgraph/sdk';
70
+
71
+ import { LoggerService } from '../logger.service';
72
+ import { UnifiedWalletService } from '../unified-wallet.service';
73
+
74
+ import { scaleHederaAmountToBaseUnits, assertSafeInteger } from './hedera-amount-utils';
75
+
76
+ /**
77
+ * Supported inner transaction types for batch transactions.
78
+ * Excludes 'batch-transaction' since batches cannot be nested.
79
+ */
80
+ export type BatchInnerTransactionType =
81
+ | 'send-hbar'
82
+ | 'send-token'
83
+ | 'send-nft'
84
+ | 'token-associate'
85
+ | 'token-dissociate'
86
+ | 'token-create'
87
+ | 'token-mint'
88
+ | 'token-burn'
89
+ | 'topic-create'
90
+ | 'topic-message'
91
+ | 'account-create'
92
+ | 'account-update';
93
+
94
+ /**
95
+ * Inner transaction descriptor for batch transactions.
96
+ * Contains all possible fields for different transaction types.
97
+ */
98
+ export interface BatchInnerTransaction {
99
+ /** Type of transaction */
100
+ type: BatchInnerTransactionType;
101
+
102
+ // Transfer fields (send-hbar, send-token, send-nft)
103
+ /** Recipient account */
104
+ toAccount?: string;
105
+ /** Amount for HBAR (tinybars) or token transfers */
106
+ amount?: number;
107
+ /** Token ID for token operations */
108
+ tokenId?: string;
109
+ /** Serial number for NFT transfers */
110
+ serialNumber?: number;
111
+
112
+ // Token create fields
113
+ /** Token name for creation */
114
+ tokenName?: string;
115
+ /** Token symbol for creation */
116
+ tokenSymbol?: string;
117
+ /** Token decimals for creation */
118
+ decimals?: number;
119
+ /** Initial supply for fungible tokens */
120
+ initialSupply?: number;
121
+ /** Max supply (0 = infinite) */
122
+ maxSupply?: number;
123
+ /** Whether supply is finite or infinite */
124
+ supplyType?: 'finite' | 'infinite';
125
+
126
+ // Token mint/burn fields
127
+ /** Metadata for NFT minting (array for multiple NFTs) */
128
+ metadata?: string | string[];
129
+
130
+ // Topic fields
131
+ /** Topic ID for message submission */
132
+ topicId?: string;
133
+ /** Message content for topic submission */
134
+ message?: string;
135
+ /** Topic memo for creation */
136
+ topicMemo?: string;
137
+
138
+ // Account fields
139
+ /** Initial balance for account creation (tinybars) */
140
+ initialBalance?: number;
141
+ /** Memo for account update */
142
+ memo?: string;
143
+ /** Max auto associations for account */
144
+ maxAutoAssociations?: number;
145
+ }
146
+
147
+ /**
148
+ * Key configuration for entity creation transactions.
149
+ *
150
+ * For single-sig accounts: provide a single publicKey string
151
+ * For multisig accounts: provide signerPublicKeys array + threshold
152
+ */
153
+ export interface HederaKeyConfig {
154
+ /**
155
+ * Single public key (hex/DER encoded) for single-sig accounts.
156
+ * If provided alone, this key is used directly.
157
+ */
158
+ publicKey?: string;
159
+
160
+ /**
161
+ * Array of public keys for multisig (threshold key) configuration.
162
+ * Requires threshold to be set.
163
+ */
164
+ signerPublicKeys?: string[];
165
+
166
+ /**
167
+ * Threshold for multisig - number of signatures required.
168
+ * Only used when signerPublicKeys is provided.
169
+ */
170
+ threshold?: number;
171
+ }
172
+
173
+ /**
174
+ * Extended options for topic creation with key configuration.
175
+ */
176
+ export interface TopicCreateOptions {
177
+ /** Topic memo/description */
178
+ memo?: string;
179
+ /** Admin key - can update/delete the topic. Uses creator's key if not specified. */
180
+ adminKey?: HederaKeyConfig;
181
+ /** Submit key - required to submit messages. If not set, anyone can submit. */
182
+ submitKey?: HederaKeyConfig;
183
+ /** Auto-renew account for fees */
184
+ autoRenewAccountId?: string;
185
+ }
186
+
187
+ /**
188
+ * Extended options for token creation with key configuration.
189
+ */
190
+ export interface TokenCreateOptions {
191
+ /** Token name */
192
+ name: string;
193
+ /** Token symbol */
194
+ symbol: string;
195
+ /** Decimal places (0 for NFTs) */
196
+ decimals: number;
197
+ /** Initial supply (0 for NFTs) */
198
+ initialSupply: number;
199
+ /** Token type: FUNGIBLE or NFT */
200
+ tokenType?: 'FUNGIBLE' | 'NFT';
201
+ /** Admin key - can update/delete token */
202
+ adminKey?: HederaKeyConfig;
203
+ /** Supply key - can mint/burn tokens */
204
+ supplyKey?: HederaKeyConfig;
205
+ /** Freeze key - can freeze/unfreeze accounts */
206
+ freezeKey?: HederaKeyConfig;
207
+ /** Wipe key - can wipe token balance from accounts */
208
+ wipeKey?: HederaKeyConfig;
209
+ /** Pause key - can pause/unpause token operations */
210
+ pauseKey?: HederaKeyConfig;
211
+ /** KYC key - can grant/revoke KYC */
212
+ kycKey?: HederaKeyConfig;
213
+ /** Fee schedule key - can update custom fees */
214
+ feeScheduleKey?: HederaKeyConfig;
215
+ /** Token memo */
216
+ memo?: string;
217
+ /** Max supply (for finite tokens) */
218
+ maxSupply?: number;
219
+ }
220
+
221
+ /**
222
+ * Extended options for account creation with key configuration.
223
+ */
224
+ export interface AccountCreateOptions {
225
+ /** Initial HBAR balance */
226
+ initialBalance: number;
227
+ /**
228
+ * Key configuration for the new account.
229
+ * For single-sig: provide publicKey
230
+ * For multisig: provide signerPublicKeys + threshold
231
+ */
232
+ key?: HederaKeyConfig;
233
+ /** Account memo */
234
+ memo?: string;
235
+ /** Max automatic token associations */
236
+ maxAutomaticTokenAssociations?: number;
237
+ /** Receiver signature required */
238
+ receiverSignatureRequired?: boolean;
239
+ }
240
+
241
+ /**
242
+ * Service for building Hedera transactions using the Hashgraph SDK.
243
+ *
244
+ * @service HederaTransactionBuilderService
245
+ *
246
+ * @description
247
+ * Provides methods to construct various Hedera transaction types and serialize them
248
+ * to base64 payloads ready for signing. Transactions are automatically frozen with
249
+ * the correct network client (mainnet/testnet) based on the active wallet session.
250
+ *
251
+ * All build methods return a base64-encoded string that can be passed to
252
+ * UnifiedWalletService.signTransaction() or submitTransaction().
253
+ *
254
+ * @providedIn root
255
+ *
256
+ * @Component({ ... })
257
+ * export class SendPage {
258
+ * private builder = inject(HederaTransactionBuilderService);
259
+ * private wallet = inject(UnifiedWalletService);
260
+ *
261
+ * async sendHbar(to: string, amount: number) {
262
+ * const from = this.wallet.activeAccount()?.address;
263
+ * const payload = await this.builder.buildSendHbar(from!, to, amount);
264
+ * return this.wallet.submitTransaction({ payload });
265
+ * }
266
+ * }
267
+ * ```
268
+ */
269
+ @Injectable({
270
+ providedIn: 'root',
271
+ })
272
+ export class HederaTransactionBuilderService {
273
+ private readonly wallet = inject(UnifiedWalletService);
274
+ private readonly logger = inject(LoggerService).scoped('HederaTransactionBuilder');
275
+
276
+ /**
277
+ * Gets the network ID from the active wallet account.
278
+ *
279
+ * @description
280
+ * Extracts the network identifier (e.g., 'hedera:mainnet', 'hedera:testnet')
281
+ * from the currently active account. Used to create the appropriate Hedera
282
+ * client for transaction freezing.
283
+ *
284
+ * @returns Network ID string, or undefined if no active account
285
+ *
286
+ * @throws {Error} When used in methods that require a network, if no account is active
287
+ */
288
+ private getNetworkId(): string | undefined {
289
+ const activeAccount = this.wallet.activeAccount();
290
+ return activeAccount?.networkId;
291
+ }
292
+
293
+ /**
294
+ * Build a Hedera Key from configuration.
295
+ *
296
+ * For single-sig: returns PublicKey directly
297
+ * For multisig: returns KeyList (threshold key) with all signer public keys
298
+ *
299
+ * @param config - Key configuration (single key or threshold key)
300
+ * @returns Hedera Key object or undefined if config is empty
301
+ */
302
+ private buildKeyFromConfig(config?: HederaKeyConfig): Key | undefined {
303
+ if (!config) {
304
+ return undefined;
305
+ }
306
+
307
+ // Multisig: threshold key with multiple signers
308
+ if (config.signerPublicKeys && config.signerPublicKeys.length > 0 && config.threshold) {
309
+ const publicKeys = config.signerPublicKeys.map((pk) => PublicKey.fromString(pk));
310
+ const keyList = new KeyList(publicKeys, config.threshold);
311
+ this.logger.debug('Built threshold key', {
312
+ signerCount: publicKeys.length,
313
+ threshold: config.threshold,
314
+ });
315
+ return keyList;
316
+ }
317
+
318
+ // Single-sig: direct public key
319
+ if (config.publicKey) {
320
+ const key = PublicKey.fromString(config.publicKey);
321
+ this.logger.debug('Built single public key');
322
+ return key;
323
+ }
324
+
325
+ return undefined;
326
+ }
327
+
328
+ /**
329
+ * Get the key configuration for the active account.
330
+ *
331
+ * For single-sig: returns { publicKey: '...' }
332
+ * For multisig: returns { signerPublicKeys: [...], threshold: N }
333
+ *
334
+ * This allows automatic key configuration for entity creation (topics, tokens, etc.)
335
+ * so that entities inherit the same key structure as the creating account.
336
+ *
337
+ * @returns Key configuration or undefined if no active account
338
+ */
339
+ private getActiveAccountKeyConfig(): HederaKeyConfig | undefined {
340
+ const activeAccount = this.wallet.activeAccount();
341
+ if (!activeAccount) {
342
+ this.logger.debug('No active account for key config');
343
+ return undefined;
344
+ }
345
+
346
+ const metadata = activeAccount.metadata;
347
+
348
+ // Check if this is a multisig account
349
+ if (metadata?.['isMultisig'] === true) {
350
+ const signerPublicKeys = metadata['signerPublicKeys'] as string[] | undefined;
351
+ const threshold = metadata['threshold'] as number | undefined;
352
+
353
+ if (signerPublicKeys && signerPublicKeys.length > 0 && threshold) {
354
+ this.logger.debug('Active account is multisig', {
355
+ threshold,
356
+ signerCount: signerPublicKeys.length,
357
+ });
358
+ return { signerPublicKeys, threshold };
359
+ }
360
+
361
+ this.logger.warn('Multisig account missing signer keys or threshold in metadata');
362
+ return undefined;
363
+ }
364
+
365
+ // Single-sig account - get public key from metadata
366
+ const publicKey = metadata?.['publicKey'] as string | undefined;
367
+ if (publicKey) {
368
+ this.logger.debug('Active account is single-sig');
369
+ return { publicKey };
370
+ }
371
+
372
+ this.logger.debug('No public key in active account metadata');
373
+ return undefined;
374
+ }
375
+
376
+ /**
377
+ * Builds a simple HBAR transfer transaction.
378
+ *
379
+ * @description
380
+ * Creates a TransferTransaction that moves HBAR from one account to another.
381
+ * The transaction is automatically frozen with the appropriate network client.
382
+ *
383
+ * @param fromAccount - Sender account ID (e.g., "0.0.12345")
384
+ * @param toAccount - Recipient account ID
385
+ * @param amount - Amount in HBAR (e.g., 1.5 for 1.5 HBAR)
386
+ *
387
+ * @returns Base64 encoded transaction bytes
388
+ *
389
+ * @throws {Error} If no active wallet session
390
+ */
391
+ async buildSendHbar(fromAccount: string, toAccount: string, amount: number): Promise<string> {
392
+ const transaction = new TransferTransaction()
393
+ .addHbarTransfer(AccountId.fromString(fromAccount), new Hbar(-amount))
394
+ .addHbarTransfer(AccountId.fromString(toAccount), new Hbar(amount));
395
+
396
+ return this.serializeTransaction(transaction, fromAccount);
397
+ }
398
+
399
+ /**
400
+ * Builds a fungible token transfer transaction.
401
+ *
402
+ * @description
403
+ * Creates a TransferTransaction that moves fungible tokens between accounts.
404
+ * The amount is automatically converted to the smallest unit based on decimals.
405
+ *
406
+ * @param fromAccount - Sender account ID
407
+ * @param toAccount - Recipient account ID
408
+ * @param tokenId - Token ID (e.g., "0.0.98765")
409
+ * @param amount - Token amount in display units (e.g., 100 tokens)
410
+ * @param decimals - Token decimals for conversion (default 0)
411
+ *
412
+ * @returns Base64 encoded transaction bytes
413
+ *
414
+ * @throws {Error} If no active wallet session
415
+ */
416
+ async buildSendToken(
417
+ fromAccount: string,
418
+ toAccount: string,
419
+ tokenId: string,
420
+ amount: number,
421
+ decimals = 0,
422
+ ): Promise<string> {
423
+ const token = TokenId.fromString(tokenId);
424
+ // bigint scaling avoids the silent precision loss that
425
+ // `amount * Math.pow(10, decimals)` exhibits past Number.MAX_SAFE_INTEGER
426
+ // (Hedera supports up to 18 decimals). Narrow once at the SDK boundary
427
+ // with an explicit overflow assertion.
428
+ const scaled = scaleHederaAmountToBaseUnits(amount, decimals);
429
+ const actualAmount = assertSafeInteger(scaled, `buildSendToken tokenId=${tokenId}`);
430
+
431
+ const transaction = new TransferTransaction()
432
+ .addTokenTransfer(token, AccountId.fromString(fromAccount), -actualAmount)
433
+ .addTokenTransfer(token, AccountId.fromString(toAccount), actualAmount);
434
+
435
+ return this.serializeTransaction(transaction, fromAccount);
436
+ }
437
+
438
+ /**
439
+ * Builds an NFT transfer transaction.
440
+ *
441
+ * @description
442
+ * Creates a TransferTransaction that moves an NFT (identified by token ID
443
+ * and serial number) from one account to another.
444
+ *
445
+ * @param fromAccount - Sender account ID
446
+ * @param toAccount - Recipient account ID
447
+ * @param tokenId - NFT token ID (e.g., "0.0.98765")
448
+ * @param serialNumber - NFT serial number (e.g., 1, 2, 3)
449
+ *
450
+ * @returns Base64 encoded transaction bytes
451
+ *
452
+ * @throws {Error} If no active wallet session
453
+ */
454
+ async buildSendNft(
455
+ fromAccount: string,
456
+ toAccount: string,
457
+ tokenId: string,
458
+ serialNumber: number,
459
+ ): Promise<string> {
460
+ const nftId = new NftId(TokenId.fromString(tokenId), serialNumber);
461
+
462
+ const transaction = new TransferTransaction().addNftTransfer(
463
+ nftId,
464
+ AccountId.fromString(fromAccount),
465
+ AccountId.fromString(toAccount),
466
+ );
467
+
468
+ return this.serializeTransaction(transaction, fromAccount);
469
+ }
470
+
471
+ /**
472
+ * Builds a token association transaction.
473
+ *
474
+ * @description
475
+ * Creates a TokenAssociateTransaction that associates one or more tokens
476
+ * with an account. This is required before an account can receive tokens
477
+ * on Hedera.
478
+ *
479
+ * @param account - Account to associate tokens with
480
+ * @param tokenIds - Array of token IDs to associate
481
+ *
482
+ * @returns Base64 encoded transaction bytes
483
+ *
484
+ * @throws {Error} If no active wallet session
485
+ */
486
+ async buildAssociateToken(account: string, tokenIds: string[]): Promise<string> {
487
+ const tokens = tokenIds.map((id) => TokenId.fromString(id));
488
+
489
+ const transaction = new TokenAssociateTransaction()
490
+ .setAccountId(AccountId.fromString(account))
491
+ .setTokenIds(tokens);
492
+
493
+ return this.serializeTransaction(transaction, account);
494
+ }
495
+
496
+ /**
497
+ * Builds a token dissociation transaction.
498
+ *
499
+ * @description
500
+ * Creates a TokenDissociateTransaction that removes the association between
501
+ * an account and one or more tokens. The account must have zero balance of
502
+ * each token before dissociating.
503
+ *
504
+ * @param account - Account to dissociate tokens from
505
+ * @param tokenIds - Array of token IDs to dissociate
506
+ *
507
+ * @returns Base64 encoded transaction bytes
508
+ *
509
+ * @throws {Error} If no active wallet session
510
+ */
511
+ async buildDissociateToken(account: string, tokenIds: string[]): Promise<string> {
512
+ const tokens = tokenIds.map((id) => TokenId.fromString(id));
513
+
514
+ const transaction = new TokenDissociateTransaction()
515
+ .setAccountId(AccountId.fromString(account))
516
+ .setTokenIds(tokens);
517
+
518
+ return this.serializeTransaction(transaction, account);
519
+ }
520
+
521
+ /**
522
+ * Build a scheduled transaction
523
+ * @param innerTransactionBytes - Base64 encoded inner transaction
524
+ * @param payerAccount - Payer account for the scheduled transaction (required)
525
+ * @param memo - Optional memo
526
+ * @returns Base64 encoded scheduled transaction bytes
527
+ */
528
+ async buildScheduledTransaction(
529
+ innerTransactionBytes: string,
530
+ payerAccount: string,
531
+ memo?: string,
532
+ ): Promise<string> {
533
+ const exampleInnerTx = new TransferTransaction()
534
+ .addHbarTransfer(AccountId.fromString(payerAccount), new Hbar(-1))
535
+ .addHbarTransfer(AccountId.fromString('0.0.3'), new Hbar(1));
536
+
537
+ const transaction = new ScheduleCreateTransaction()
538
+ .setScheduledTransaction(exampleInnerTx)
539
+ .setPayerAccountId(AccountId.fromString(payerAccount));
540
+
541
+ if (memo) {
542
+ transaction.setScheduleMemo(memo);
543
+ }
544
+
545
+ return this.serializeTransaction(transaction, payerAccount);
546
+ }
547
+
548
+ /**
549
+ * Build a batch of transactions with the same payer
550
+ * @param transactions - Array of base64 encoded transactions
551
+ * @returns Array of base64 encoded transactions (ready for batch submission)
552
+ */
553
+ async buildBatchTransactions(transactions: string[]): Promise<string[]> {
554
+ return transactions;
555
+ }
556
+
557
+ /**
558
+ * Build a complex atomic transaction (multiple transfers in one transaction)
559
+ * @param transfers - Array of transfer operations
560
+ * @returns Base64 encoded transaction bytes
561
+ */
562
+ async buildAtomicTransaction(
563
+ transfers: Array<{
564
+ type: 'hbar' | 'token';
565
+ fromAccount: string;
566
+ toAccount: string;
567
+ amount: number;
568
+ tokenId?: string;
569
+ decimals?: number;
570
+ }>,
571
+ ): Promise<string> {
572
+ if (transfers.length === 0) {
573
+ throw new Error('At least one transfer is required for an atomic transaction');
574
+ }
575
+
576
+ const transaction = new TransferTransaction();
577
+ const payerAccount = transfers[0].fromAccount;
578
+
579
+ for (const transfer of transfers) {
580
+ if (transfer.type === 'hbar') {
581
+ transaction
582
+ .addHbarTransfer(AccountId.fromString(transfer.fromAccount), new Hbar(-transfer.amount))
583
+ .addHbarTransfer(AccountId.fromString(transfer.toAccount), new Hbar(transfer.amount));
584
+ } else if (transfer.type === 'token' && transfer.tokenId) {
585
+ const token = TokenId.fromString(transfer.tokenId);
586
+ const decimals = transfer.decimals || 0;
587
+ const scaled = scaleHederaAmountToBaseUnits(transfer.amount, decimals);
588
+ const actualAmount = assertSafeInteger(
589
+ scaled,
590
+ `atomicTransaction token=${transfer.tokenId}`,
591
+ );
592
+
593
+ transaction
594
+ .addTokenTransfer(token, AccountId.fromString(transfer.fromAccount), -actualAmount)
595
+ .addTokenTransfer(token, AccountId.fromString(transfer.toAccount), actualAmount);
596
+ }
597
+ }
598
+
599
+ return this.serializeTransaction(transaction, payerAccount);
600
+ }
601
+
602
+ /**
603
+ * Build a token creation transaction with automatic key configuration.
604
+ *
605
+ * **Auto-key behavior**: If no adminKey/supplyKey is provided, the token will automatically
606
+ * inherit the creating account's key structure:
607
+ * - Single-sig account → token keys = account's public key
608
+ * - Multisig account → token keys = threshold key with all signers
609
+ *
610
+ * This ensures tokens created from multisig accounts are also controlled by the multisig.
611
+ *
612
+ * @param treasuryAccount - Account that will hold the tokens
613
+ * @param options - Token creation options (keys auto-detected if not provided)
614
+ * @param optionsOrName
615
+ * @param symbol
616
+ * @param decimals
617
+ * @param initialSupply
618
+ * @param tokenType
619
+ * @returns Base64 encoded transaction bytes
620
+ */
621
+ async buildTokenCreate(
622
+ treasuryAccount: string,
623
+ optionsOrName: TokenCreateOptions | string,
624
+ symbol?: string,
625
+ decimals?: number,
626
+ initialSupply?: number,
627
+ tokenType: 'FUNGIBLE' | 'NFT' = 'FUNGIBLE',
628
+ ): Promise<string> {
629
+ // Support both object and positional arguments
630
+ const opts: TokenCreateOptions =
631
+ typeof optionsOrName === 'string'
632
+ ? {
633
+ name: optionsOrName,
634
+ symbol: symbol!,
635
+ decimals: decimals!,
636
+ initialSupply: initialSupply!,
637
+ tokenType,
638
+ }
639
+ : optionsOrName;
640
+
641
+ const transaction = new TokenCreateTransaction()
642
+ .setTokenName(opts.name)
643
+ .setTokenSymbol(opts.symbol)
644
+ .setDecimals(opts.decimals)
645
+ .setInitialSupply(opts.initialSupply)
646
+ .setTreasuryAccountId(AccountId.fromString(treasuryAccount))
647
+ .setTokenType(
648
+ opts.tokenType === 'NFT' ? TokenType.NonFungibleUnique : TokenType.FungibleCommon,
649
+ )
650
+ .setSupplyType(opts.maxSupply ? TokenSupplyType.Finite : TokenSupplyType.Infinite);
651
+
652
+ // Set max supply if finite
653
+ if (opts.maxSupply) {
654
+ transaction.setMaxSupply(opts.maxSupply);
655
+ }
656
+
657
+ // Set memo if provided
658
+ if (opts.memo) {
659
+ transaction.setTokenMemo(opts.memo);
660
+ }
661
+
662
+ // Auto-detect key config from active account if not explicitly provided
663
+ const autoKeyConfig = this.getActiveAccountKeyConfig();
664
+ const isAutoDetected = !!autoKeyConfig;
665
+
666
+ // Set admin key - use provided or auto-detected
667
+ const adminKeyConfig = opts.adminKey ?? autoKeyConfig;
668
+ const adminKey = this.buildKeyFromConfig(adminKeyConfig);
669
+ if (adminKey) {
670
+ transaction.setAdminKey(adminKey);
671
+ this.logger.debug('Token admin key set', { autoDetected: !opts.adminKey && isAutoDetected });
672
+ }
673
+
674
+ // Set supply key - use provided or auto-detected
675
+ const supplyKeyConfig = opts.supplyKey ?? autoKeyConfig;
676
+ const supplyKey = this.buildKeyFromConfig(supplyKeyConfig);
677
+ if (supplyKey) {
678
+ transaction.setSupplyKey(supplyKey);
679
+ this.logger.debug('Token supply key set', {
680
+ autoDetected: !opts.supplyKey && isAutoDetected,
681
+ });
682
+ }
683
+
684
+ // Set freeze key - only if explicitly provided (not auto-set)
685
+ const freezeKey = this.buildKeyFromConfig(opts.freezeKey);
686
+ if (freezeKey) {
687
+ transaction.setFreezeKey(freezeKey);
688
+ this.logger.debug('Token freeze key set');
689
+ }
690
+
691
+ // Set wipe key - only if explicitly provided (not auto-set)
692
+ const wipeKey = this.buildKeyFromConfig(opts.wipeKey);
693
+ if (wipeKey) {
694
+ transaction.setWipeKey(wipeKey);
695
+ this.logger.debug('Token wipe key set');
696
+ }
697
+
698
+ // Set pause key - only if explicitly provided (not auto-set)
699
+ const pauseKey = this.buildKeyFromConfig(opts.pauseKey);
700
+ if (pauseKey) {
701
+ transaction.setPauseKey(pauseKey);
702
+ this.logger.debug('Token pause key set');
703
+ }
704
+
705
+ // Set KYC key - only if explicitly provided (not auto-set)
706
+ const kycKey = this.buildKeyFromConfig(opts.kycKey);
707
+ if (kycKey) {
708
+ transaction.setKycKey(kycKey);
709
+ this.logger.debug('Token KYC key set');
710
+ }
711
+
712
+ // Set fee schedule key - only if explicitly provided (not auto-set)
713
+ const feeScheduleKey = this.buildKeyFromConfig(opts.feeScheduleKey);
714
+ if (feeScheduleKey) {
715
+ transaction.setFeeScheduleKey(feeScheduleKey);
716
+ this.logger.debug('Token fee schedule key set');
717
+ }
718
+
719
+ return this.serializeTransaction(transaction, treasuryAccount);
720
+ }
721
+
722
+ /**
723
+ * Build a token mint transaction
724
+ * @param tokenId - Token ID to mint
725
+ * @param amount - Amount to mint
726
+ * @param account - Account performing the mint
727
+ * @returns Base64 encoded transaction bytes
728
+ */
729
+ async buildTokenMint(tokenId: string, amount: number, account: string): Promise<string> {
730
+ const transaction = new TokenMintTransaction()
731
+ .setTokenId(TokenId.fromString(tokenId))
732
+ .setAmount(amount);
733
+
734
+ return this.serializeTransaction(transaction, account);
735
+ }
736
+
737
+ /**
738
+ * Build a token burn transaction
739
+ * @param tokenId - Token ID to burn
740
+ * @param amount - Amount to burn
741
+ * @param account - Account performing the burn
742
+ * @returns Base64 encoded transaction bytes
743
+ */
744
+ async buildTokenBurn(tokenId: string, amount: number, account: string): Promise<string> {
745
+ const transaction = new TokenBurnTransaction()
746
+ .setTokenId(TokenId.fromString(tokenId))
747
+ .setAmount(amount);
748
+
749
+ return this.serializeTransaction(transaction, account);
750
+ }
751
+
752
+ /**
753
+ * Build a topic creation transaction with automatic key configuration.
754
+ *
755
+ * **Auto-key behavior**: If no adminKey is provided, the topic will automatically
756
+ * inherit the creating account's key structure:
757
+ * - Single-sig account → topic adminKey = account's public key
758
+ * - Multisig account → topic adminKey = threshold key with all signers
759
+ *
760
+ * This ensures topics created from multisig accounts are also controlled by the multisig.
761
+ *
762
+ * @param account - Account creating the topic
763
+ * @param options - Topic creation options (keys auto-detected if not provided)
764
+ * @returns Base64 encoded transaction bytes
765
+ */
766
+ async buildTopicCreate(
767
+ account: string,
768
+ options?: TopicCreateOptions | string, // string treated as memo
769
+ ): Promise<string> {
770
+ const transaction = new TopicCreateTransaction();
771
+
772
+ // Support both object and string (memo shorthand)
773
+ const opts: TopicCreateOptions =
774
+ typeof options === 'string' ? { memo: options } : (options ?? {});
775
+
776
+ if (opts.memo) {
777
+ transaction.setTopicMemo(opts.memo);
778
+ }
779
+
780
+ // Auto-detect key config from active account if not explicitly provided
781
+ const autoKeyConfig = this.getActiveAccountKeyConfig();
782
+
783
+ // Set admin key - use provided or auto-detected
784
+ const adminKeyConfig = opts.adminKey ?? autoKeyConfig;
785
+ const adminKey = this.buildKeyFromConfig(adminKeyConfig);
786
+ if (adminKey) {
787
+ transaction.setAdminKey(adminKey);
788
+ this.logger.debug('Topic admin key set', {
789
+ isThreshold: adminKeyConfig?.signerPublicKeys ? true : false,
790
+ autoDetected: !opts.adminKey && !!autoKeyConfig,
791
+ });
792
+ }
793
+
794
+ // Set submit key - use provided or auto-detected (same as admin by default)
795
+ const submitKeyConfig = opts.submitKey ?? autoKeyConfig;
796
+ const submitKey = this.buildKeyFromConfig(submitKeyConfig);
797
+ if (submitKey) {
798
+ transaction.setSubmitKey(submitKey);
799
+ this.logger.debug('Topic submit key set', {
800
+ isThreshold: submitKeyConfig?.signerPublicKeys ? true : false,
801
+ autoDetected: !opts.submitKey && !!autoKeyConfig,
802
+ });
803
+ }
804
+
805
+ // Set auto-renew account if provided
806
+ if (opts.autoRenewAccountId) {
807
+ transaction.setAutoRenewAccountId(AccountId.fromString(opts.autoRenewAccountId));
808
+ }
809
+
810
+ return this.serializeTransaction(transaction, account);
811
+ }
812
+
813
+ /**
814
+ * Build a topic message submit transaction
815
+ * @param topicId - Topic ID (e.g., "0.0.12345")
816
+ * @param message - Message to submit
817
+ * @param account - Account submitting the message
818
+ * @returns Base64 encoded transaction bytes
819
+ */
820
+ async buildTopicMessageSubmit(
821
+ topicId: string,
822
+ message: string,
823
+ account: string,
824
+ ): Promise<string> {
825
+ const transaction = new TopicMessageSubmitTransaction().setTopicId(topicId).setMessage(message);
826
+
827
+ return this.serializeTransaction(transaction, account);
828
+ }
829
+
830
+ /**
831
+ * Build an account creation transaction with key configuration.
832
+ *
833
+ * For single-sig accounts: pass { publicKey: '...' }
834
+ * For multisig accounts: pass { signerPublicKeys: [...], threshold: N }
835
+ *
836
+ * If no key is provided, a random key is generated (NOT RECOMMENDED for production).
837
+ *
838
+ * @param creatorAccount - Account creating the new account
839
+ * @param options - Account creation options including key config
840
+ * @param optionsOrBalance
841
+ * @returns Base64 encoded transaction bytes
842
+ */
843
+ async buildAccountCreate(
844
+ creatorAccount: string,
845
+ optionsOrBalance: AccountCreateOptions | number,
846
+ ): Promise<string> {
847
+ // Support both object and number (initial balance shorthand)
848
+ const opts: AccountCreateOptions =
849
+ typeof optionsOrBalance === 'number'
850
+ ? { initialBalance: optionsOrBalance }
851
+ : optionsOrBalance;
852
+
853
+ const transaction = new AccountCreateTransaction().setInitialBalance(
854
+ new Hbar(opts.initialBalance),
855
+ );
856
+
857
+ // Auto-detect key config from active account if not explicitly provided
858
+ // This ensures accounts created from multisig wallets inherit the multisig key structure
859
+ const autoKeyConfig = this.getActiveAccountKeyConfig();
860
+ const keyConfig = opts.key ?? autoKeyConfig;
861
+
862
+ // Set account key - use provided or auto-detected
863
+ const accountKey = this.buildKeyFromConfig(keyConfig);
864
+ if (accountKey) {
865
+ transaction.setKey(accountKey);
866
+ this.logger.debug('Account key set', {
867
+ isThreshold: keyConfig?.signerPublicKeys ? true : false,
868
+ autoDetected: !opts.key && !!autoKeyConfig,
869
+ });
870
+ } else {
871
+ // Fallback: generate random key (not recommended for production)
872
+ const newKey = PrivateKey.generateED25519();
873
+ transaction.setKey(newKey.publicKey);
874
+ this.logger.warn(
875
+ 'Generated random key for new account - provide key config for production use',
876
+ );
877
+ }
878
+
879
+ // Set memo if provided
880
+ if (opts.memo) {
881
+ transaction.setAccountMemo(opts.memo);
882
+ }
883
+
884
+ // Set max automatic token associations
885
+ if (opts.maxAutomaticTokenAssociations !== undefined) {
886
+ transaction.setMaxAutomaticTokenAssociations(opts.maxAutomaticTokenAssociations);
887
+ }
888
+
889
+ // Set receiver signature required
890
+ if (opts.receiverSignatureRequired !== undefined) {
891
+ transaction.setReceiverSignatureRequired(opts.receiverSignatureRequired);
892
+ }
893
+
894
+ return this.serializeTransaction(transaction, creatorAccount);
895
+ }
896
+
897
+ /**
898
+ * Build an account update transaction
899
+ * @param accountId - Account to update
900
+ * @param memo - New account memo
901
+ * @returns Base64 encoded transaction bytes
902
+ */
903
+ async buildAccountUpdate(accountId: string, memo: string): Promise<string> {
904
+ const transaction = new AccountUpdateTransaction()
905
+ .setAccountId(AccountId.fromString(accountId))
906
+ .setAccountMemo(memo);
907
+
908
+ return this.serializeTransaction(transaction, accountId);
909
+ }
910
+
911
+ /**
912
+ * Build a batch transaction with multiple inner transactions
913
+ * @param innerTransactions - Array of transaction descriptors
914
+ * @param payerAccount - Account paying for the batch transaction
915
+ * @returns Base64 encoded batch transaction bytes
916
+ */
917
+ async buildBatchTransaction(
918
+ innerTransactions: Array<{
919
+ type: 'send-hbar' | 'send-token' | 'token-mint';
920
+ toAccount?: string;
921
+ amount: number;
922
+ tokenId?: string;
923
+ }>,
924
+ payerAccount: string,
925
+ ): Promise<string> {
926
+ const networkId = this.getNetworkId();
927
+
928
+ if (!networkId) {
929
+ throw new Error('No active wallet session. Please connect your wallet first.');
930
+ }
931
+
932
+ const client = this.createClientForNetwork(networkId);
933
+ const batchKey = PrivateKey.generateED25519();
934
+ const batchPublicKey = batchKey.publicKey;
935
+ const preparedTransactions = [];
936
+
937
+ for (const tx of innerTransactions) {
938
+ let transaction;
939
+
940
+ switch (tx.type) {
941
+ case 'send-hbar':
942
+ transaction = new TransferTransaction()
943
+ .addHbarTransfer(AccountId.fromString(payerAccount), new Hbar(-tx.amount))
944
+ .addHbarTransfer(AccountId.fromString(tx.toAccount!), new Hbar(tx.amount))
945
+ .setTransactionId(TransactionId.generate(AccountId.fromString(payerAccount)))
946
+ .setBatchKey(batchPublicKey);
947
+ break;
948
+
949
+ case 'send-token':
950
+ transaction = new TransferTransaction()
951
+ .addTokenTransfer(
952
+ TokenId.fromString(tx.tokenId!),
953
+ AccountId.fromString(payerAccount),
954
+ -tx.amount,
955
+ )
956
+ .addTokenTransfer(
957
+ TokenId.fromString(tx.tokenId!),
958
+ AccountId.fromString(tx.toAccount!),
959
+ tx.amount,
960
+ )
961
+ .setTransactionId(TransactionId.generate(AccountId.fromString(payerAccount)))
962
+ .setBatchKey(batchPublicKey);
963
+ break;
964
+
965
+ case 'token-mint':
966
+ transaction = new TokenMintTransaction()
967
+ .setTokenId(TokenId.fromString(tx.tokenId!))
968
+ .setAmount(tx.amount)
969
+ .setTransactionId(TransactionId.generate(AccountId.fromString(payerAccount)))
970
+ .setBatchKey(batchPublicKey);
971
+ break;
972
+
973
+ default:
974
+ continue;
975
+ }
976
+
977
+ if (transaction) {
978
+ const frozen = await transaction.freezeWith(client);
979
+ preparedTransactions.push(frozen);
980
+ }
981
+ }
982
+
983
+ const batchTransaction = new BatchTransaction().setTransactionId(
984
+ TransactionId.generate(AccountId.fromString(payerAccount)),
985
+ );
986
+
987
+ for (const tx of preparedTransactions) {
988
+ batchTransaction.addInnerTransaction(tx);
989
+ }
990
+
991
+ const frozenBatch = await batchTransaction.freezeWith(client);
992
+ const signedBatch = await frozenBatch.sign(batchKey);
993
+ const bytes = signedBatch.toBytes();
994
+
995
+ return this.uint8ArrayToBase64(bytes);
996
+ }
997
+
998
+ /**
999
+ * Build inner transactions for batch (unsigned, with batchKey set)
1000
+ * @param account - Account executing the batch
1001
+ * @param batchKey - Public key for batch transactions
1002
+ * @param innerTransactions - Array of transaction descriptors
1003
+ * @returns Array of base64 encoded unsigned inner transactions
1004
+ */
1005
+ async buildBatchInnerTransactions(
1006
+ account: string,
1007
+ batchKey: string,
1008
+ innerTransactions: Array<BatchInnerTransaction>,
1009
+ ): Promise<Array<{ payload: string; description: string }>> {
1010
+ const accountId = AccountId.fromString(account);
1011
+ const batchPublicKey = PublicKey.fromString(batchKey);
1012
+
1013
+ const networkId = this.getNetworkId();
1014
+
1015
+ if (!networkId) {
1016
+ throw new Error('No active wallet session. Please connect your wallet first.');
1017
+ }
1018
+
1019
+ const client = this.createClientForNetwork(networkId);
1020
+ const results: Array<{ payload: string; description: string }> = [];
1021
+
1022
+ for (const tx of innerTransactions) {
1023
+ let transaction: Transaction;
1024
+ let description: string;
1025
+
1026
+ try {
1027
+ switch (tx.type) {
1028
+ case 'send-hbar':
1029
+ transaction = new TransferTransaction()
1030
+ .setTransactionId(TransactionId.generate(accountId))
1031
+ .addHbarTransfer(accountId, Hbar.fromTinybars(-(tx.amount || 0)))
1032
+ .addHbarTransfer(
1033
+ AccountId.fromString(tx.toAccount!),
1034
+ Hbar.fromTinybars(tx.amount || 0),
1035
+ )
1036
+ .setBatchKey(batchPublicKey);
1037
+ description = `Send ${tx.amount} tinybar to ${tx.toAccount}`;
1038
+ break;
1039
+
1040
+ case 'send-token':
1041
+ transaction = new TransferTransaction()
1042
+ .setTransactionId(TransactionId.generate(accountId))
1043
+ .addTokenTransfer(TokenId.fromString(tx.tokenId!), accountId, -(tx.amount || 0))
1044
+ .addTokenTransfer(
1045
+ TokenId.fromString(tx.tokenId!),
1046
+ AccountId.fromString(tx.toAccount!),
1047
+ tx.amount || 0,
1048
+ )
1049
+ .setBatchKey(batchPublicKey);
1050
+ description = `Send ${tx.amount} of token ${tx.tokenId} to ${tx.toAccount}`;
1051
+ break;
1052
+
1053
+ case 'send-nft':
1054
+ transaction = new TransferTransaction()
1055
+ .setTransactionId(TransactionId.generate(accountId))
1056
+ .addNftTransfer(
1057
+ new NftId(TokenId.fromString(tx.tokenId!), tx.serialNumber || 1),
1058
+ accountId,
1059
+ AccountId.fromString(tx.toAccount!),
1060
+ )
1061
+ .setBatchKey(batchPublicKey);
1062
+ description = `Send NFT ${tx.tokenId}#${tx.serialNumber} to ${tx.toAccount}`;
1063
+ break;
1064
+
1065
+ case 'token-associate':
1066
+ transaction = new TokenAssociateTransaction()
1067
+ .setTransactionId(TransactionId.generate(accountId))
1068
+ .setAccountId(accountId)
1069
+ .setTokenIds([TokenId.fromString(tx.tokenId!)])
1070
+ .setBatchKey(batchPublicKey);
1071
+ description = `Associate token ${tx.tokenId}`;
1072
+ break;
1073
+
1074
+ case 'token-dissociate':
1075
+ transaction = new TokenDissociateTransaction()
1076
+ .setTransactionId(TransactionId.generate(accountId))
1077
+ .setAccountId(accountId)
1078
+ .setTokenIds([TokenId.fromString(tx.tokenId!)])
1079
+ .setBatchKey(batchPublicKey);
1080
+ description = `Dissociate token ${tx.tokenId}`;
1081
+ break;
1082
+
1083
+ case 'token-create': {
1084
+ const createTx = new TokenCreateTransaction()
1085
+ .setTransactionId(TransactionId.generate(accountId))
1086
+ .setTokenName(tx.tokenName || 'Token')
1087
+ .setTokenSymbol(tx.tokenSymbol || 'TKN')
1088
+ .setDecimals(tx.decimals ?? 0)
1089
+ .setInitialSupply(tx.initialSupply ?? 0)
1090
+ .setTreasuryAccountId(accountId)
1091
+ .setTokenType(
1092
+ tx.decimals === 0 ? TokenType.NonFungibleUnique : TokenType.FungibleCommon,
1093
+ )
1094
+ .setSupplyType(
1095
+ tx.supplyType === 'finite' ? TokenSupplyType.Finite : TokenSupplyType.Infinite,
1096
+ )
1097
+ .setBatchKey(batchPublicKey);
1098
+ if (tx.maxSupply && tx.supplyType === 'finite') {
1099
+ createTx.setMaxSupply(tx.maxSupply);
1100
+ }
1101
+ transaction = createTx;
1102
+ description = `Create token ${tx.tokenName} (${tx.tokenSymbol})`;
1103
+ break;
1104
+ }
1105
+
1106
+ case 'token-mint': {
1107
+ const mintTx = new TokenMintTransaction()
1108
+ .setTransactionId(TransactionId.generate(accountId))
1109
+ .setTokenId(TokenId.fromString(tx.tokenId!))
1110
+ .setBatchKey(batchPublicKey);
1111
+ // Handle NFT metadata (array) vs fungible amount
1112
+ if (tx.metadata) {
1113
+ const encoder = new TextEncoder();
1114
+ if (Array.isArray(tx.metadata)) {
1115
+ mintTx.setMetadata(tx.metadata.map((m) => encoder.encode(m)));
1116
+ } else {
1117
+ mintTx.setMetadata([encoder.encode(tx.metadata)]);
1118
+ }
1119
+ } else {
1120
+ mintTx.setAmount(tx.amount || 1);
1121
+ }
1122
+ transaction = mintTx;
1123
+ description = tx.metadata
1124
+ ? `Mint NFT for token ${tx.tokenId}`
1125
+ : `Mint ${tx.amount} of token ${tx.tokenId}`;
1126
+ break;
1127
+ }
1128
+
1129
+ case 'token-burn': {
1130
+ const burnTx = new TokenBurnTransaction()
1131
+ .setTransactionId(TransactionId.generate(accountId))
1132
+ .setTokenId(TokenId.fromString(tx.tokenId!))
1133
+ .setBatchKey(batchPublicKey);
1134
+ if (tx.serialNumber) {
1135
+ burnTx.setSerials([tx.serialNumber]);
1136
+ } else {
1137
+ burnTx.setAmount(tx.amount || 1);
1138
+ }
1139
+ transaction = burnTx;
1140
+ description = tx.serialNumber
1141
+ ? `Burn NFT ${tx.tokenId}#${tx.serialNumber}`
1142
+ : `Burn ${tx.amount} of token ${tx.tokenId}`;
1143
+ break;
1144
+ }
1145
+
1146
+ case 'topic-create': {
1147
+ const topicTx = new TopicCreateTransaction()
1148
+ .setTransactionId(TransactionId.generate(accountId))
1149
+ .setBatchKey(batchPublicKey);
1150
+ if (tx.topicMemo) {
1151
+ topicTx.setTopicMemo(tx.topicMemo);
1152
+ }
1153
+ transaction = topicTx;
1154
+ description = tx.topicMemo ? `Create topic: ${tx.topicMemo}` : 'Create topic';
1155
+ break;
1156
+ }
1157
+
1158
+ case 'topic-message':
1159
+ transaction = new TopicMessageSubmitTransaction()
1160
+ .setTransactionId(TransactionId.generate(accountId))
1161
+ .setTopicId(tx.topicId!)
1162
+ .setMessage(tx.message || '')
1163
+ .setBatchKey(batchPublicKey);
1164
+ description = `Submit message to topic ${tx.topicId}`;
1165
+ break;
1166
+
1167
+ case 'account-create': {
1168
+ const acctTx = new AccountCreateTransaction()
1169
+ .setTransactionId(TransactionId.generate(accountId))
1170
+ .setInitialBalance(Hbar.fromTinybars(tx.initialBalance || 0))
1171
+ .setBatchKey(batchPublicKey);
1172
+ if (tx.maxAutoAssociations !== undefined) {
1173
+ acctTx.setMaxAutomaticTokenAssociations(tx.maxAutoAssociations);
1174
+ }
1175
+ transaction = acctTx;
1176
+ description = `Create account with ${tx.initialBalance} tinybar`;
1177
+ break;
1178
+ }
1179
+
1180
+ case 'account-update': {
1181
+ const updateTx = new AccountUpdateTransaction()
1182
+ .setTransactionId(TransactionId.generate(accountId))
1183
+ .setAccountId(accountId)
1184
+ .setBatchKey(batchPublicKey);
1185
+ if (tx.memo !== undefined) {
1186
+ updateTx.setAccountMemo(tx.memo);
1187
+ }
1188
+ if (tx.maxAutoAssociations !== undefined) {
1189
+ updateTx.setMaxAutomaticTokenAssociations(tx.maxAutoAssociations);
1190
+ }
1191
+ transaction = updateTx;
1192
+ description = `Update account ${account}`;
1193
+ break;
1194
+ }
1195
+
1196
+ default:
1197
+ throw new Error(`Unknown transaction type: ${(tx as any).type}`);
1198
+ }
1199
+
1200
+ this.logger.debug('Freezing transaction', { type: tx.type });
1201
+ const frozen = await transaction.freezeWith(client);
1202
+
1203
+ this.logger.debug('Frozen transaction, serializing', { type: tx.type });
1204
+ const bytes = frozen.toBytes();
1205
+
1206
+ this.logger.debug('Serialized transaction', { type: tx.type, length: bytes.length });
1207
+
1208
+ results.push({
1209
+ payload: this.uint8ArrayToBase64(bytes),
1210
+ description,
1211
+ });
1212
+
1213
+ this.logger.debug('Successfully processed transaction', { type: tx.type });
1214
+ } catch (error) {
1215
+ this.logger.error('Failed to process transaction', { type: tx.type, error });
1216
+ throw new Error(`Failed to build ${tx.type} transaction: ${error}`);
1217
+ }
1218
+ }
1219
+
1220
+ return results;
1221
+ }
1222
+
1223
+ /**
1224
+ * Serialize a transaction to base64 encoded bytes
1225
+ * @param transaction
1226
+ * @param payerAccountId
1227
+ */
1228
+ private async serializeTransaction(transaction: any, payerAccountId: string): Promise<string> {
1229
+ const networkId = this.getNetworkId();
1230
+
1231
+ if (!networkId) {
1232
+ throw new Error('No active wallet session. Please connect your wallet first.');
1233
+ }
1234
+
1235
+ transaction.setTransactionId(TransactionId.generate(AccountId.fromString(payerAccountId)));
1236
+ const client = this.createClientForNetwork(networkId);
1237
+ const frozen = await transaction.freezeWith(client);
1238
+ const bytes = frozen.toBytes();
1239
+
1240
+ return this.uint8ArrayToBase64(bytes);
1241
+ }
1242
+
1243
+ /**
1244
+ * Create a Hedera client for the specified network
1245
+ * @param networkId
1246
+ */
1247
+ private createClientForNetwork(networkId: string): Client {
1248
+ const network = networkId.split(':')[1]?.toLowerCase();
1249
+
1250
+ switch (network) {
1251
+ case 'mainnet':
1252
+ return Client.forMainnet();
1253
+ case 'testnet':
1254
+ return Client.forTestnet();
1255
+ default:
1256
+ this.logger.warn('Unknown Hedera network, defaulting to testnet', { network });
1257
+ return Client.forTestnet();
1258
+ }
1259
+ }
1260
+
1261
+ /**
1262
+ * Convert Uint8Array to base64 string
1263
+ * @param bytes
1264
+ */
1265
+ private uint8ArrayToBase64(bytes: Uint8Array): string {
1266
+ let binary = '';
1267
+ for (let i = 0; i < bytes.length; i++) {
1268
+ binary += String.fromCharCode(bytes[i]);
1269
+ }
1270
+ return btoa(binary);
1271
+ }
1272
+
1273
+ /**
1274
+ * Convert base64 string to Uint8Array
1275
+ * @param base64
1276
+ */
1277
+ private base64ToUint8Array(base64: string): Uint8Array {
1278
+ const binary = atob(base64);
1279
+ const bytes = new Uint8Array(binary.length);
1280
+ for (let i = 0; i < binary.length; i++) {
1281
+ bytes[i] = binary.charCodeAt(i);
1282
+ }
1283
+ return bytes;
1284
+ }
1285
+ }