@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.
- package/README.md +48 -0
- package/USAGE_EXAMPLES.md +476 -0
- package/assets/wallets/extension.svg +7 -0
- package/assets/wallets/hashpack.svg +6 -0
- package/assets/wallets/hsuite.svg +11 -0
- package/assets/wallets/kabila.svg +11 -0
- package/assets/wallets/walletconnect.svg +13 -0
- package/coverage/base.css +224 -0
- package/coverage/block-navigation.js +87 -0
- package/coverage/coverage-summary.json +50 -0
- package/coverage/favicon.png +0 -0
- package/coverage/index.html +476 -0
- package/coverage/lcov-report/base.css +224 -0
- package/coverage/lcov-report/block-navigation.js +87 -0
- package/coverage/lcov-report/favicon.png +0 -0
- package/coverage/lcov-report/index.html +476 -0
- package/coverage/lcov-report/lib/components/account-selector/account-actions/account-actions.component.ts.html +868 -0
- package/coverage/lcov-report/lib/components/account-selector/account-actions/index.html +116 -0
- package/coverage/lcov-report/lib/components/account-selector/account-filter/account-filter.component.ts.html +1288 -0
- package/coverage/lcov-report/lib/components/account-selector/account-filter/index.html +116 -0
- package/coverage/lcov-report/lib/components/account-selector/account-formatting.service.ts.html +685 -0
- package/coverage/lcov-report/lib/components/account-selector/account-grouping.service.ts.html +766 -0
- package/coverage/lcov-report/lib/components/account-selector/account-list/account-list.component.ts.html +1495 -0
- package/coverage/lcov-report/lib/components/account-selector/account-list/index.html +116 -0
- package/coverage/lcov-report/lib/components/account-selector/account-selector.component.ts.html +1495 -0
- package/coverage/lcov-report/lib/components/account-selector/account-selector.service.ts.html +1588 -0
- package/coverage/lcov-report/lib/components/account-selector/index.html +161 -0
- package/coverage/lcov-report/lib/components/wallet-account-display/index.html +116 -0
- package/coverage/lcov-report/lib/components/wallet-account-display/wallet-account-display.component.ts.html +505 -0
- package/coverage/lcov-report/lib/components/wallet-connect-button/index.html +116 -0
- package/coverage/lcov-report/lib/components/wallet-connect-button/wallet-connect-button.component.ts.html +805 -0
- package/coverage/lcov-report/lib/components/wallet-connect-prompt/index.html +116 -0
- package/coverage/lcov-report/lib/components/wallet-connect-prompt/wallet-connect-prompt.component.ts.html +409 -0
- package/coverage/lcov-report/lib/components/wallet-connected-guard/index.html +116 -0
- package/coverage/lcov-report/lib/components/wallet-connected-guard/wallet-connected-guard.component.ts.html +304 -0
- package/coverage/lcov-report/lib/components/wallet-connection-modal/connection-method-step/connection-method-step.component.ts.html +436 -0
- package/coverage/lcov-report/lib/components/wallet-connection-modal/connection-method-step/index.html +116 -0
- package/coverage/lcov-report/lib/components/wallet-connection-modal/index.html +116 -0
- package/coverage/lcov-report/lib/components/wallet-connection-modal/qr-pairing-step/index.html +116 -0
- package/coverage/lcov-report/lib/components/wallet-connection-modal/qr-pairing-step/qr-pairing-step.component.ts.html +2287 -0
- package/coverage/lcov-report/lib/components/wallet-connection-modal/wallet-connection-modal.component.ts.html +2275 -0
- package/coverage/lcov-report/lib/components/wallet-session-display/index.html +116 -0
- package/coverage/lcov-report/lib/components/wallet-session-display/wallet-session-display.component.ts.html +676 -0
- package/coverage/lcov-report/lib/components/wallet-transaction-status/index.html +116 -0
- package/coverage/lcov-report/lib/components/wallet-transaction-status/wallet-transaction-status.component.ts.html +703 -0
- package/coverage/lcov-report/lib/directives/index.html +146 -0
- package/coverage/lcov-report/lib/directives/wallet-connected.directive.ts.html +670 -0
- package/coverage/lcov-report/lib/directives/wallet-context.directive.ts.html +547 -0
- package/coverage/lcov-report/lib/directives/wallet-events.directive.ts.html +781 -0
- package/coverage/lcov-report/lib/hsuite-wallet.module.ts.html +715 -0
- package/coverage/lcov-report/lib/index.html +116 -0
- package/coverage/lcov-report/lib/models/connection-config.model.ts.html +280 -0
- package/coverage/lcov-report/lib/models/index.html +131 -0
- package/coverage/lcov-report/lib/models/provider-types.ts.html +577 -0
- package/coverage/lcov-report/lib/providers/base-wallet-provider.ts.html +1138 -0
- package/coverage/lcov-report/lib/providers/hsuite-native/channel-client.service.ts.html +2671 -0
- package/coverage/lcov-report/lib/providers/hsuite-native/index.html +116 -0
- package/coverage/lcov-report/lib/providers/hsuite-native-provider.ts.html +2347 -0
- package/coverage/lcov-report/lib/providers/index.html +146 -0
- package/coverage/lcov-report/lib/providers/p2p-native/index.html +131 -0
- package/coverage/lcov-report/lib/providers/p2p-native/p2p-native.provider.ts.html +2254 -0
- package/coverage/lcov-report/lib/providers/p2p-native/p2p-session-manager.ts.html +2170 -0
- package/coverage/lcov-report/lib/providers/wallet-error-handler.ts.html +1132 -0
- package/coverage/lcov-report/lib/providers/walletconnect/core/index.html +176 -0
- package/coverage/lcov-report/lib/providers/walletconnect/core/session-health.ts.html +673 -0
- package/coverage/lcov-report/lib/providers/walletconnect/core/walletconnect-client-manager.ts.html +1177 -0
- package/coverage/lcov-report/lib/providers/walletconnect/core/walletconnect-provider.ts.html +2563 -0
- package/coverage/lcov-report/lib/providers/walletconnect/core/walletconnect-session-store.ts.html +904 -0
- package/coverage/lcov-report/lib/providers/walletconnect/core/walletconnect-signing-orchestrator.ts.html +982 -0
- package/coverage/lcov-report/lib/providers/walletconnect/signers/hedera-signer.ts.html +1915 -0
- package/coverage/lcov-report/lib/providers/walletconnect/signers/index.html +146 -0
- package/coverage/lcov-report/lib/providers/walletconnect/signers/signer-factory.ts.html +445 -0
- package/coverage/lcov-report/lib/providers/walletconnect/signers/xrpl-signer.ts.html +1519 -0
- package/coverage/lcov-report/lib/services/index.html +191 -0
- package/coverage/lcov-report/lib/services/logger.service.ts.html +463 -0
- package/coverage/lcov-report/lib/services/transaction-builders/base-transaction-builder.service.ts.html +1840 -0
- package/coverage/lcov-report/lib/services/transaction-builders/hedera-amount-utils.ts.html +337 -0
- package/coverage/lcov-report/lib/services/transaction-builders/hedera-transaction-builder.service.ts.html +3940 -0
- package/coverage/lcov-report/lib/services/transaction-builders/index.html +161 -0
- package/coverage/lcov-report/lib/services/transaction-builders/xrpl-transaction-builder.service.ts.html +2581 -0
- package/coverage/lcov-report/lib/services/transaction.service.ts.html +1123 -0
- package/coverage/lcov-report/lib/services/unified-wallet.service.ts.html +2641 -0
- package/coverage/lcov-report/lib/services/wallet-context.service.ts.html +637 -0
- package/coverage/lcov-report/lib/services/wallet-event-bus.service.ts.html +643 -0
- package/coverage/lcov-report/lib/services/wallet-providers.service.ts.html +496 -0
- package/coverage/lcov-report/lib/transports/chrome-extension-transport.ts.html +823 -0
- package/coverage/lcov-report/lib/transports/index.html +116 -0
- package/coverage/lcov-report/lib/utils/index.html +116 -0
- package/coverage/lcov-report/lib/utils/ledger-icons.util.ts.html +319 -0
- package/coverage/lcov-report/prettify.css +1 -0
- package/coverage/lcov-report/prettify.js +2 -0
- package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/lcov-report/sorter.js +210 -0
- package/coverage/lcov.info +19252 -0
- package/coverage/lib/components/account-selector/account-actions/account-actions.component.ts.html +868 -0
- package/coverage/lib/components/account-selector/account-actions/index.html +116 -0
- package/coverage/lib/components/account-selector/account-filter/account-filter.component.ts.html +1288 -0
- package/coverage/lib/components/account-selector/account-filter/index.html +116 -0
- package/coverage/lib/components/account-selector/account-formatting.service.ts.html +685 -0
- package/coverage/lib/components/account-selector/account-grouping.service.ts.html +766 -0
- package/coverage/lib/components/account-selector/account-list/account-list.component.ts.html +1495 -0
- package/coverage/lib/components/account-selector/account-list/index.html +116 -0
- package/coverage/lib/components/account-selector/account-selector.component.ts.html +1495 -0
- package/coverage/lib/components/account-selector/account-selector.service.ts.html +1588 -0
- package/coverage/lib/components/account-selector/index.html +161 -0
- package/coverage/lib/components/wallet-account-display/index.html +116 -0
- package/coverage/lib/components/wallet-account-display/wallet-account-display.component.ts.html +505 -0
- package/coverage/lib/components/wallet-connect-button/index.html +116 -0
- package/coverage/lib/components/wallet-connect-button/wallet-connect-button.component.ts.html +805 -0
- package/coverage/lib/components/wallet-connect-prompt/index.html +116 -0
- package/coverage/lib/components/wallet-connect-prompt/wallet-connect-prompt.component.ts.html +409 -0
- package/coverage/lib/components/wallet-connected-guard/index.html +116 -0
- package/coverage/lib/components/wallet-connected-guard/wallet-connected-guard.component.ts.html +304 -0
- package/coverage/lib/components/wallet-connection-modal/connection-method-step/connection-method-step.component.ts.html +436 -0
- package/coverage/lib/components/wallet-connection-modal/connection-method-step/index.html +116 -0
- package/coverage/lib/components/wallet-connection-modal/index.html +116 -0
- package/coverage/lib/components/wallet-connection-modal/qr-pairing-step/index.html +116 -0
- package/coverage/lib/components/wallet-connection-modal/qr-pairing-step/qr-pairing-step.component.ts.html +2287 -0
- package/coverage/lib/components/wallet-connection-modal/wallet-connection-modal.component.ts.html +2275 -0
- package/coverage/lib/components/wallet-session-display/index.html +116 -0
- package/coverage/lib/components/wallet-session-display/wallet-session-display.component.ts.html +676 -0
- package/coverage/lib/components/wallet-transaction-status/index.html +116 -0
- package/coverage/lib/components/wallet-transaction-status/wallet-transaction-status.component.ts.html +703 -0
- package/coverage/lib/directives/index.html +146 -0
- package/coverage/lib/directives/wallet-connected.directive.ts.html +670 -0
- package/coverage/lib/directives/wallet-context.directive.ts.html +547 -0
- package/coverage/lib/directives/wallet-events.directive.ts.html +781 -0
- package/coverage/lib/hsuite-wallet.module.ts.html +715 -0
- package/coverage/lib/index.html +116 -0
- package/coverage/lib/models/connection-config.model.ts.html +280 -0
- package/coverage/lib/models/index.html +131 -0
- package/coverage/lib/models/provider-types.ts.html +577 -0
- package/coverage/lib/providers/base-wallet-provider.ts.html +1138 -0
- package/coverage/lib/providers/hsuite-native/channel-client.service.ts.html +2671 -0
- package/coverage/lib/providers/hsuite-native/index.html +116 -0
- package/coverage/lib/providers/hsuite-native-provider.ts.html +2347 -0
- package/coverage/lib/providers/index.html +146 -0
- package/coverage/lib/providers/p2p-native/index.html +131 -0
- package/coverage/lib/providers/p2p-native/p2p-native.provider.ts.html +2254 -0
- package/coverage/lib/providers/p2p-native/p2p-session-manager.ts.html +2170 -0
- package/coverage/lib/providers/wallet-error-handler.ts.html +1132 -0
- package/coverage/lib/providers/walletconnect/core/index.html +176 -0
- package/coverage/lib/providers/walletconnect/core/session-health.ts.html +673 -0
- package/coverage/lib/providers/walletconnect/core/walletconnect-client-manager.ts.html +1177 -0
- package/coverage/lib/providers/walletconnect/core/walletconnect-provider.ts.html +2563 -0
- package/coverage/lib/providers/walletconnect/core/walletconnect-session-store.ts.html +904 -0
- package/coverage/lib/providers/walletconnect/core/walletconnect-signing-orchestrator.ts.html +982 -0
- package/coverage/lib/providers/walletconnect/signers/hedera-signer.ts.html +1915 -0
- package/coverage/lib/providers/walletconnect/signers/index.html +146 -0
- package/coverage/lib/providers/walletconnect/signers/signer-factory.ts.html +445 -0
- package/coverage/lib/providers/walletconnect/signers/xrpl-signer.ts.html +1519 -0
- package/coverage/lib/services/index.html +191 -0
- package/coverage/lib/services/logger.service.ts.html +463 -0
- package/coverage/lib/services/transaction-builders/base-transaction-builder.service.ts.html +1840 -0
- package/coverage/lib/services/transaction-builders/hedera-amount-utils.ts.html +337 -0
- package/coverage/lib/services/transaction-builders/hedera-transaction-builder.service.ts.html +3940 -0
- package/coverage/lib/services/transaction-builders/index.html +161 -0
- package/coverage/lib/services/transaction-builders/xrpl-transaction-builder.service.ts.html +2581 -0
- package/coverage/lib/services/transaction.service.ts.html +1123 -0
- package/coverage/lib/services/unified-wallet.service.ts.html +2641 -0
- package/coverage/lib/services/wallet-context.service.ts.html +637 -0
- package/coverage/lib/services/wallet-event-bus.service.ts.html +643 -0
- package/coverage/lib/services/wallet-providers.service.ts.html +496 -0
- package/coverage/lib/transports/chrome-extension-transport.ts.html +823 -0
- package/coverage/lib/transports/index.html +116 -0
- package/coverage/lib/utils/index.html +116 -0
- package/coverage/lib/utils/ledger-icons.util.ts.html +319 -0
- package/coverage/prettify.css +1 -0
- package/coverage/prettify.js +2 -0
- package/coverage/sort-arrow-sprite.png +0 -0
- package/coverage/sorter.js +210 -0
- package/dist/README.md +48 -0
- package/dist/fesm2022/hsuite-native-connect-angular.mjs +14592 -0
- package/dist/fesm2022/hsuite-native-connect-angular.mjs.map +1 -0
- package/dist/index.d.ts +6949 -0
- package/examples/minimal-connect.ts +178 -0
- package/examples/multi-protocol.ts +495 -0
- package/examples/transaction-signing.ts +361 -0
- package/jest.config.json +45 -0
- package/karma.conf.js +42 -0
- package/ng-package.json +20 -0
- package/package.json +60 -0
- package/src/index.ts +203 -0
- package/src/lib/components/account-selector/account-actions/account-actions.component.ts +261 -0
- package/src/lib/components/account-selector/account-filter/account-filter.component.ts +401 -0
- package/src/lib/components/account-selector/account-formatting.service.ts +200 -0
- package/src/lib/components/account-selector/account-grouping.service.ts +227 -0
- package/src/lib/components/account-selector/account-list/account-list.component.ts +470 -0
- package/src/lib/components/account-selector/account-selector.component.html +135 -0
- package/src/lib/components/account-selector/account-selector.component.scss +2039 -0
- package/src/lib/components/account-selector/account-selector.component.ts +470 -0
- package/src/lib/components/account-selector/account-selector.service.ts +501 -0
- package/src/lib/components/wallet-account-display/wallet-account-display.component.html +34 -0
- package/src/lib/components/wallet-account-display/wallet-account-display.component.scss +99 -0
- package/src/lib/components/wallet-account-display/wallet-account-display.component.ts +140 -0
- package/src/lib/components/wallet-connect-button/wallet-connect-button.component.html +14 -0
- package/src/lib/components/wallet-connect-button/wallet-connect-button.component.scss +272 -0
- package/src/lib/components/wallet-connect-button/wallet-connect-button.component.ts +240 -0
- package/src/lib/components/wallet-connect-prompt/wallet-connect-prompt.component.html +24 -0
- package/src/lib/components/wallet-connect-prompt/wallet-connect-prompt.component.scss +50 -0
- package/src/lib/components/wallet-connect-prompt/wallet-connect-prompt.component.ts +108 -0
- package/src/lib/components/wallet-connected-guard/wallet-connected-guard.component.html +24 -0
- package/src/lib/components/wallet-connected-guard/wallet-connected-guard.component.ts +73 -0
- package/src/lib/components/wallet-connection-modal/connection-method-step/connection-method-step.component.html +56 -0
- package/src/lib/components/wallet-connection-modal/connection-method-step/connection-method-step.component.scss +218 -0
- package/src/lib/components/wallet-connection-modal/connection-method-step/connection-method-step.component.ts +117 -0
- package/src/lib/components/wallet-connection-modal/qr-pairing-step/qr-pairing-step.component.html +94 -0
- package/src/lib/components/wallet-connection-modal/qr-pairing-step/qr-pairing-step.component.scss +272 -0
- package/src/lib/components/wallet-connection-modal/qr-pairing-step/qr-pairing-step.component.ts +734 -0
- package/src/lib/components/wallet-connection-modal/wallet-connection-modal.component.html +197 -0
- package/src/lib/components/wallet-connection-modal/wallet-connection-modal.component.scss +678 -0
- package/src/lib/components/wallet-connection-modal/wallet-connection-modal.component.ts +730 -0
- package/src/lib/components/wallet-session-display/wallet-session-display.component.html +110 -0
- package/src/lib/components/wallet-session-display/wallet-session-display.component.scss +179 -0
- package/src/lib/components/wallet-session-display/wallet-session-display.component.ts +197 -0
- package/src/lib/components/wallet-transaction-status/wallet-transaction-status.component.html +65 -0
- package/src/lib/components/wallet-transaction-status/wallet-transaction-status.component.scss +254 -0
- package/src/lib/components/wallet-transaction-status/wallet-transaction-status.component.ts +206 -0
- package/src/lib/directives/wallet-connected.directive.ts +195 -0
- package/src/lib/directives/wallet-context.directive.ts +154 -0
- package/src/lib/directives/wallet-events.directive.ts +232 -0
- package/src/lib/hsuite-wallet.module.ts +210 -0
- package/src/lib/models/connection-config.model.ts +65 -0
- package/src/lib/models/provider-types.ts +164 -0
- package/src/lib/models/unified-account.model.ts +76 -0
- package/src/lib/models/wallet-context.model.ts +121 -0
- package/src/lib/models/wallet-events.model.ts +158 -0
- package/src/lib/providers/base-wallet-provider.ts +351 -0
- package/src/lib/providers/hsuite-native/channel-client.service.spec.ts +73 -0
- package/src/lib/providers/hsuite-native/channel-client.service.ts +862 -0
- package/src/lib/providers/hsuite-native/index.ts +8 -0
- package/src/lib/providers/hsuite-native-provider.ts +754 -0
- package/src/lib/providers/mobile-native/mobile-native.provider.spec.ts +19 -0
- package/src/lib/providers/p2p-native/index.ts +30 -0
- package/src/lib/providers/p2p-native/p2p-native.provider.spec.ts +523 -0
- package/src/lib/providers/p2p-native/p2p-native.provider.ts +723 -0
- package/src/lib/providers/p2p-native/p2p-session-manager.ts +695 -0
- package/src/lib/providers/wallet-error-handler.ts +349 -0
- package/src/lib/providers/walletconnect/core/base-signer.interface.ts +122 -0
- package/src/lib/providers/walletconnect/core/session-health.ts +196 -0
- package/src/lib/providers/walletconnect/core/walletconnect-client-manager.ts +364 -0
- package/src/lib/providers/walletconnect/core/walletconnect-provider.integration.spec.ts +348 -0
- package/src/lib/providers/walletconnect/core/walletconnect-provider.ts +826 -0
- package/src/lib/providers/walletconnect/core/walletconnect-session-store.ts +273 -0
- package/src/lib/providers/walletconnect/core/walletconnect-signing-orchestrator.ts +299 -0
- package/src/lib/providers/walletconnect/core/walletconnect-types.ts +48 -0
- package/src/lib/providers/walletconnect/index.ts +33 -0
- package/src/lib/providers/walletconnect/signers/hedera-signer.spec.ts +367 -0
- package/src/lib/providers/walletconnect/signers/hedera-signer.ts +610 -0
- package/src/lib/providers/walletconnect/signers/signer-factory.spec.ts +62 -0
- package/src/lib/providers/walletconnect/signers/signer-factory.ts +120 -0
- package/src/lib/providers/walletconnect/signers/xrpl-signer.spec.ts +296 -0
- package/src/lib/providers/walletconnect/signers/xrpl-signer.ts +478 -0
- package/src/lib/services/logger.service.ts +126 -0
- package/src/lib/services/transaction-builders/base-transaction-builder.service.ts +585 -0
- package/src/lib/services/transaction-builders/hedera-amount-utils.ts +84 -0
- package/src/lib/services/transaction-builders/hedera-transaction-builder.service.spec.ts +741 -0
- package/src/lib/services/transaction-builders/hedera-transaction-builder.service.ts +1285 -0
- package/src/lib/services/transaction-builders/index.ts +54 -0
- package/src/lib/services/transaction-builders/xrpl-transaction-builder.service.spec.ts +937 -0
- package/src/lib/services/transaction-builders/xrpl-transaction-builder.service.ts +832 -0
- package/src/lib/services/transaction.service.ts +346 -0
- package/src/lib/services/unified-wallet.service.spec.ts +1382 -0
- package/src/lib/services/unified-wallet.service.ts +852 -0
- package/src/lib/services/wallet-context.service.ts +184 -0
- package/src/lib/services/wallet-event-bus.service.ts +186 -0
- package/src/lib/services/wallet-providers.service.ts +137 -0
- package/src/lib/transports/chrome-extension-transport.ts +246 -0
- package/src/lib/utils/index.ts +14 -0
- package/src/lib/utils/ledger-icons.util.ts +78 -0
- package/test/test-setup.ts +21 -0
- package/test-setup.ts +63 -0
- package/tsconfig.build.json +11 -0
- package/tsconfig.json +29 -0
- package/tsconfig.spec.json +15 -0
- 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
|
+
}
|