@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,754 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Provider implementation for HSuite Native Connect protocol.
|
|
3
|
+
* @module providers/hsuite-native-provider
|
|
4
|
+
*
|
|
5
|
+
* @description
|
|
6
|
+
* HsuiteNativeProvider implements the {@link BaseWalletProvider} interface for the
|
|
7
|
+
* HSuite Native Connect protocol. This provider enables secure communication between
|
|
8
|
+
* dApps and HSuite Wallet using the `hsc:` protocol over Nostr relays with automatic
|
|
9
|
+
* WebRTC P2P upgrade.
|
|
10
|
+
*
|
|
11
|
+
* Key Features:
|
|
12
|
+
* - **Local-First Discovery**: Uses BroadcastChannel for instant same-browser wallet detection
|
|
13
|
+
* - **Nostr Communication**: Uses Nostr relays for encrypted channel messaging
|
|
14
|
+
* - **Automatic P2P Upgrade**: Upgrades to direct WebRTC when possible for lower latency
|
|
15
|
+
* - **Session Persistence**: Supports automatic reconnection after page reload
|
|
16
|
+
* - **Cross-Platform**: Works with desktop browsers, mobile browsers, and native apps
|
|
17
|
+
*
|
|
18
|
+
* Connection Flow:
|
|
19
|
+
* ```
|
|
20
|
+
* 1. dApp calls connect() with configuration
|
|
21
|
+
* 2. Provider checks local discovery for same-browser wallets (instant!)
|
|
22
|
+
* 3. If wallet found -> sends invite via BroadcastChannel (<1ms)
|
|
23
|
+
* 4. If not found -> opens wallet tab/window with invite URL
|
|
24
|
+
* 5. Wallet receives invite and establishes channel via Nostr relay
|
|
25
|
+
* 6. User approves connection in wallet
|
|
26
|
+
* 7. Channel becomes active, accounts are available
|
|
27
|
+
* 8. P2P upgrade happens automatically in background
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
import { Injectable, signal, computed, inject, effect, NgZone } from '@angular/core';
|
|
32
|
+
import { getLogger, LocalDiscoveryService } from '@hsuite/native-connect-sdk';
|
|
33
|
+
|
|
34
|
+
import type {
|
|
35
|
+
ConnectionStatus,
|
|
36
|
+
ProviderMetadata,
|
|
37
|
+
ConnectionConfig,
|
|
38
|
+
HsuiteNativeConfig,
|
|
39
|
+
} from '../models/provider-types';
|
|
40
|
+
import { DEFAULT_WALLET_URL } from '../models/provider-types';
|
|
41
|
+
import type { UnifiedAccount } from '../models/unified-account.model';
|
|
42
|
+
import { WalletEventBus } from '../services/wallet-event-bus.service';
|
|
43
|
+
|
|
44
|
+
import {
|
|
45
|
+
BaseWalletProvider,
|
|
46
|
+
SignTransactionOptions,
|
|
47
|
+
SubmitTransactionOptions,
|
|
48
|
+
SignResult,
|
|
49
|
+
SubmitResult,
|
|
50
|
+
SignMessageOptions,
|
|
51
|
+
SignMessageResult,
|
|
52
|
+
} from './base-wallet-provider';
|
|
53
|
+
import { ChannelClientService } from './hsuite-native/channel-client.service';
|
|
54
|
+
|
|
55
|
+
const logger = getLogger().scoped?.('HsuiteNativeProvider') ?? getLogger();
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* HSuite Native Connect Provider.
|
|
59
|
+
*
|
|
60
|
+
* @description
|
|
61
|
+
* Angular injectable service that manages wallet connections via the HSuite Native
|
|
62
|
+
* Connect protocol. This provider uses the unified channel system for secure,
|
|
63
|
+
* encrypted communication between dApps and the HSuite Wallet.
|
|
64
|
+
*
|
|
65
|
+
* Architecture:
|
|
66
|
+
* - Uses {@link ChannelClientService} for unified channel-based communication
|
|
67
|
+
* - Implements {@link BaseWalletProvider} interface for integration with {@link UnifiedWalletService}
|
|
68
|
+
* - Supports both session (1:1 dApp-wallet) and party (N:N multisig) channels
|
|
69
|
+
* - Auto-registers with UnifiedWalletService on injection
|
|
70
|
+
*
|
|
71
|
+
* Transport States (exposed via `transportState` signal):
|
|
72
|
+
* - `'idle'` - No active connection
|
|
73
|
+
* - `'nostr-only'` - Connected via Nostr relay only
|
|
74
|
+
* - `'upgrading'` - P2P upgrade in progress
|
|
75
|
+
* - `'p2p-connected'` - Direct P2P connection established
|
|
76
|
+
* - `'p2p-failed'` - P2P upgrade failed, using Nostr fallback
|
|
77
|
+
*
|
|
78
|
+
* @Component({
|
|
79
|
+
* template: `
|
|
80
|
+
* <ion-badge [color]="transportColor()">
|
|
81
|
+
* {{ transportState() }}
|
|
82
|
+
* </ion-badge>
|
|
83
|
+
* `
|
|
84
|
+
* })
|
|
85
|
+
* export class ConnectionStatus {
|
|
86
|
+
* private provider = inject(HsuiteNativeProvider);
|
|
87
|
+
*
|
|
88
|
+
* transportState = this.provider.transportState;
|
|
89
|
+
*
|
|
90
|
+
* transportColor = computed(() => {
|
|
91
|
+
* const state = this.transportState();
|
|
92
|
+
* return state === 'p2p-connected' ? 'success' : 'warning';
|
|
93
|
+
* });
|
|
94
|
+
* }
|
|
95
|
+
* ```
|
|
96
|
+
*
|
|
97
|
+
* @publicApi
|
|
98
|
+
*/
|
|
99
|
+
@Injectable({ providedIn: 'root' })
|
|
100
|
+
export class HsuiteNativeProvider extends BaseWalletProvider {
|
|
101
|
+
private readonly eventBus = inject(WalletEventBus);
|
|
102
|
+
private readonly zone = inject(NgZone);
|
|
103
|
+
private readonly channelService = inject(ChannelClientService);
|
|
104
|
+
|
|
105
|
+
readonly id = 'hsuite-native';
|
|
106
|
+
|
|
107
|
+
readonly metadata: ProviderMetadata = {
|
|
108
|
+
id: this.id,
|
|
109
|
+
name: 'HSuite Native',
|
|
110
|
+
type: 'hsuite-native',
|
|
111
|
+
description: 'Connect via HSuite Native Connect protocol',
|
|
112
|
+
supportsMultipleAccounts: true,
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Error signal (reactive)
|
|
117
|
+
*/
|
|
118
|
+
readonly error = signal<string | null>(null);
|
|
119
|
+
|
|
120
|
+
// Track previous connection state for disconnect detection
|
|
121
|
+
private previouslyConnected = false;
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Local discovery service instance.
|
|
125
|
+
* Uses BroadcastChannel for instant, private, same-browser wallet discovery.
|
|
126
|
+
*/
|
|
127
|
+
private localDiscoveryService: LocalDiscoveryService | null = null;
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
*
|
|
131
|
+
*/
|
|
132
|
+
constructor() {
|
|
133
|
+
super();
|
|
134
|
+
|
|
135
|
+
// Initialize local discovery service for same-browser wallet detection
|
|
136
|
+
this.initializeLocalDiscovery();
|
|
137
|
+
|
|
138
|
+
// Synchronize error state from channel service
|
|
139
|
+
effect(() => {
|
|
140
|
+
const channelError = this.channelService.error();
|
|
141
|
+
if (channelError) {
|
|
142
|
+
this.error.set(channelError);
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
// Listen to channel state changes to detect wallet-initiated disconnects
|
|
147
|
+
effect(() => {
|
|
148
|
+
const status = this.status();
|
|
149
|
+
const isConnected = this.channelService.isConnected();
|
|
150
|
+
|
|
151
|
+
// If we were connected but now disconnected, emit disconnect event
|
|
152
|
+
if (this.previouslyConnected && !isConnected && status === 'disconnected') {
|
|
153
|
+
logger.info('[HsuiteNativeProvider] Channel terminated');
|
|
154
|
+
this.eventBus.disconnected.emit({
|
|
155
|
+
providerId: this.id,
|
|
156
|
+
providerType: 'hsuite-native',
|
|
157
|
+
reason: 'session_terminated',
|
|
158
|
+
timestamp: Date.now(),
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Update tracking state
|
|
163
|
+
this.previouslyConnected = isConnected;
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
// Attempt automatic reconnect when session exists after reload
|
|
167
|
+
void this.attemptReconnect().catch((error) => {
|
|
168
|
+
logger.debug('[HsuiteNativeProvider] Auto-reconnect skipped', { error });
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Initialize local discovery for same-browser wallet detection.
|
|
174
|
+
* Uses BroadcastChannel for instant, private discovery.
|
|
175
|
+
* Cross-device connections use QR/URL flow (no discovery needed).
|
|
176
|
+
*/
|
|
177
|
+
private initializeLocalDiscovery(): void {
|
|
178
|
+
this.localDiscoveryService = LocalDiscoveryService.getDAppInstance();
|
|
179
|
+
this.localDiscoveryService.start();
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Maps the internal channel state to the provider connection status.
|
|
184
|
+
*
|
|
185
|
+
* @description
|
|
186
|
+
* Computed signal that translates ChannelClientService states to
|
|
187
|
+
* standard ConnectionStatus values for UI binding.
|
|
188
|
+
*
|
|
189
|
+
* @returns Current connection status
|
|
190
|
+
*/
|
|
191
|
+
readonly status = computed<ConnectionStatus>(() => {
|
|
192
|
+
const state = this.channelService.state();
|
|
193
|
+
|
|
194
|
+
switch (state) {
|
|
195
|
+
case 'idle':
|
|
196
|
+
return 'idle';
|
|
197
|
+
case 'connecting':
|
|
198
|
+
case 'pending':
|
|
199
|
+
return 'connecting';
|
|
200
|
+
case 'active':
|
|
201
|
+
case 'approved':
|
|
202
|
+
return 'connected';
|
|
203
|
+
case 'disconnected':
|
|
204
|
+
return 'disconnected';
|
|
205
|
+
case 'error':
|
|
206
|
+
return 'error';
|
|
207
|
+
default:
|
|
208
|
+
return 'idle';
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Current transport state (nostr-only, upgrading, p2p-connected, p2p-failed).
|
|
214
|
+
* Use this to show users whether they're connected via Nostr relay or P2P.
|
|
215
|
+
*/
|
|
216
|
+
readonly transportState = computed(() => this.channelService.transportState());
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Connected accounts from the wallet in UnifiedAccount format.
|
|
220
|
+
*
|
|
221
|
+
* @description
|
|
222
|
+
* Computed signal that exposes wallet accounts from the channel service
|
|
223
|
+
* in the standard UnifiedAccount format for UI binding.
|
|
224
|
+
*
|
|
225
|
+
* @returns Array of connected accounts (empty when disconnected)
|
|
226
|
+
*/
|
|
227
|
+
readonly accounts = computed<UnifiedAccount[]>(() => {
|
|
228
|
+
return this.channelService.unifiedAccounts();
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Check if provider is connected
|
|
233
|
+
*/
|
|
234
|
+
override isConnected(): boolean {
|
|
235
|
+
return this.channelService.isConnected();
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Connects to HSuite Wallet via the Native Connect protocol.
|
|
240
|
+
*
|
|
241
|
+
* @description
|
|
242
|
+
* Initiates a connection to HSuite Wallet using the optimal method for the
|
|
243
|
+
* current platform:
|
|
244
|
+
*
|
|
245
|
+
* **Desktop Browsers**:
|
|
246
|
+
* 1. Checks if wallet is already online via Nostr discovery (instant!)
|
|
247
|
+
* 2. If found, sends invite directly via relay
|
|
248
|
+
* 3. If not found, opens wallet in new tab/window
|
|
249
|
+
*
|
|
250
|
+
* **Mobile Browsers**:
|
|
251
|
+
* 1. Tries hsuite: deep link to open native wallet app
|
|
252
|
+
* 2. Falls back to opening wallet in same tab (PWA) or new tab
|
|
253
|
+
*
|
|
254
|
+
* The connection completes when the wallet user approves the session.
|
|
255
|
+
*
|
|
256
|
+
* @param config - HSuite Native connection configuration
|
|
257
|
+
*
|
|
258
|
+
* @throws {Error} If configuration is invalid
|
|
259
|
+
* @throws {Error} If connection times out (default 120 seconds)
|
|
260
|
+
* @throws {Error} If popup is blocked and wallet cannot be opened
|
|
261
|
+
*/
|
|
262
|
+
override async connect(config: ConnectionConfig): Promise<void> {
|
|
263
|
+
if (!this.isHsuiteNativeConfig(config)) {
|
|
264
|
+
throw new Error('Invalid HSuite Native configuration');
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Guard: Don't start a new connection if already connecting
|
|
268
|
+
const currentStatus = this.status();
|
|
269
|
+
if (currentStatus === 'connecting') {
|
|
270
|
+
logger.warn('Connection already in progress, ignoring duplicate request');
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
logger.info('Connecting to HSuite Wallet via unified channel protocol', { config });
|
|
275
|
+
|
|
276
|
+
try {
|
|
277
|
+
const invite = await this.channelService.connect({
|
|
278
|
+
type: 'session',
|
|
279
|
+
appId: config.appId || 'hsuite-dapp',
|
|
280
|
+
appName: config.appName || 'HSuite dApp',
|
|
281
|
+
ledgerId: config.ledgerId,
|
|
282
|
+
networkId: config.networkId,
|
|
283
|
+
walletUrl: config.walletUrl || DEFAULT_WALLET_URL,
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
// Open wallet with invite URL
|
|
287
|
+
const walletUrl = config.walletUrl || DEFAULT_WALLET_URL;
|
|
288
|
+
const inviteUrl = this.channelService.getWalletInviteUrl(invite, walletUrl);
|
|
289
|
+
|
|
290
|
+
// Open wallet using Nostr-based discovery (works cross-origin, cross-port)
|
|
291
|
+
await this.openWalletWindow(inviteUrl, walletUrl, config);
|
|
292
|
+
|
|
293
|
+
// Wait for connection to be established
|
|
294
|
+
await this.waitForConnection();
|
|
295
|
+
|
|
296
|
+
// Run in NgZone to trigger Angular change detection
|
|
297
|
+
this.zone.run(() => {
|
|
298
|
+
const accounts = this.accounts();
|
|
299
|
+
this.eventBus.connected.emit({
|
|
300
|
+
providerId: this.id,
|
|
301
|
+
providerType: 'hsuite-native',
|
|
302
|
+
accounts,
|
|
303
|
+
timestamp: Date.now(),
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
logger.info('Connected successfully via unified channel', {
|
|
307
|
+
channelId: invite.id.slice(0, 8),
|
|
308
|
+
accountCount: accounts.length,
|
|
309
|
+
});
|
|
310
|
+
});
|
|
311
|
+
} catch (error) {
|
|
312
|
+
logger.error('Connection failed', { error });
|
|
313
|
+
throw error;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Open wallet window with LOCAL-FIRST discovery.
|
|
319
|
+
*
|
|
320
|
+
* LOCAL DISCOVERY MODEL:
|
|
321
|
+
* 1. Check if wallet is online in same browser via BroadcastChannel (instant!)
|
|
322
|
+
* 2. If not immediately found, wait briefly (500ms) for wallet heartbeat
|
|
323
|
+
* 3. If wallet found → send invite via BroadcastChannel (<1ms), focus existing tab
|
|
324
|
+
* 4. If not found → open wallet tab or use QR/deep link
|
|
325
|
+
*
|
|
326
|
+
* Cross-device connections use QR/URL flow - the invite is self-contained,
|
|
327
|
+
* no discovery needed.
|
|
328
|
+
*
|
|
329
|
+
* @param inviteUrl - Full URL with invite parameter
|
|
330
|
+
* @param walletBaseUrl - Base URL of the wallet
|
|
331
|
+
* @param config - Connection configuration with app metadata
|
|
332
|
+
*/
|
|
333
|
+
private async openWalletWindow(
|
|
334
|
+
inviteUrl: string,
|
|
335
|
+
walletBaseUrl: string,
|
|
336
|
+
config?: HsuiteNativeConfig,
|
|
337
|
+
): Promise<void> {
|
|
338
|
+
const isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
|
|
339
|
+
|
|
340
|
+
// LOCAL-FIRST DISCOVERY
|
|
341
|
+
// Wait briefly for wallet heartbeat before opening new tab
|
|
342
|
+
if (this.localDiscoveryService?.active) {
|
|
343
|
+
// Wait up to 500ms for a wallet to announce itself
|
|
344
|
+
const wallet = await this.localDiscoveryService.waitForWallet({
|
|
345
|
+
timeoutMs: 500,
|
|
346
|
+
ledgerId: config?.ledgerId,
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
if (wallet) {
|
|
350
|
+
// Send invite directly via BroadcastChannel - instant, no network!
|
|
351
|
+
this.localDiscoveryService.sendInvite(
|
|
352
|
+
wallet.fingerprint,
|
|
353
|
+
inviteUrl,
|
|
354
|
+
config?.appId,
|
|
355
|
+
config?.appName,
|
|
356
|
+
);
|
|
357
|
+
logger.info('[HsuiteNativeProvider] Sent invite via local discovery', {
|
|
358
|
+
wallet: wallet.fingerprint.slice(0, 8),
|
|
359
|
+
});
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// MOBILE: Prefer same-tab navigation for PWA wallet
|
|
365
|
+
if (isMobile) {
|
|
366
|
+
const isSameOrigin = this.isSameOrigin(walletBaseUrl);
|
|
367
|
+
|
|
368
|
+
if (isSameOrigin) {
|
|
369
|
+
window.location.href = inviteUrl;
|
|
370
|
+
return;
|
|
371
|
+
} else {
|
|
372
|
+
// Try deep link first
|
|
373
|
+
const deepLinkUrl = inviteUrl.replace(/^https?:/, 'hsuite:');
|
|
374
|
+
try {
|
|
375
|
+
window.location.href = deepLinkUrl;
|
|
376
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
377
|
+
} catch {
|
|
378
|
+
// Deep link failed, continue to open tab
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// FALLBACK: Open wallet in a new tab (for same-browser) or show QR (cross-device)
|
|
384
|
+
// §29.8 SOC2 CC6.1: noopener,noreferrer prevents a compromised dApp-supplied
|
|
385
|
+
// inviteUrl from hijacking the dApp tab via window.opener. A side effect is
|
|
386
|
+
// that window.open() returns null, so we can no longer use the return value
|
|
387
|
+
// to detect popup-blocked; modern browsers surface that via their own UI
|
|
388
|
+
// and the caller's waitForConnection() timeout will error out if the user
|
|
389
|
+
// dismissed the popup dialog without allowing.
|
|
390
|
+
const walletWindow = window.open(inviteUrl, 'hsuite-wallet', 'noopener,noreferrer');
|
|
391
|
+
// .focus() is a no-op with noopener (walletWindow === null); fresh tabs
|
|
392
|
+
// are already focused in every mainstream browser.
|
|
393
|
+
void walletWindow;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Check if a URL is same origin as current page.
|
|
398
|
+
* @param url
|
|
399
|
+
*/
|
|
400
|
+
private isSameOrigin(url: string): boolean {
|
|
401
|
+
try {
|
|
402
|
+
const walletOrigin = new URL(url).origin;
|
|
403
|
+
return walletOrigin === window.location.origin;
|
|
404
|
+
} catch {
|
|
405
|
+
return false;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Wait for channel connection to be established
|
|
411
|
+
* @param timeoutMs
|
|
412
|
+
*/
|
|
413
|
+
private async waitForConnection(timeoutMs = 120000): Promise<void> {
|
|
414
|
+
const startTime = Date.now();
|
|
415
|
+
|
|
416
|
+
return new Promise((resolve, reject) => {
|
|
417
|
+
const checkConnection = () => {
|
|
418
|
+
if (this.channelService.isConnected()) {
|
|
419
|
+
resolve();
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
const state = this.channelService.state();
|
|
424
|
+
if (state === 'error') {
|
|
425
|
+
reject(new Error(this.channelService.error() || 'Connection failed'));
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// Also handle disconnected state (wallet rejected or terminated)
|
|
430
|
+
if (state === 'disconnected') {
|
|
431
|
+
reject(new Error(this.channelService.error() || 'Connection rejected by wallet'));
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
if (Date.now() - startTime > timeoutMs) {
|
|
436
|
+
reject(new Error('Connection timeout'));
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
setTimeout(checkConnection, 100);
|
|
441
|
+
};
|
|
442
|
+
|
|
443
|
+
checkConnection();
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* Disconnects from the HSuite Wallet.
|
|
449
|
+
*
|
|
450
|
+
* @description
|
|
451
|
+
* Terminates the channel connection and clears session state. The wallet
|
|
452
|
+
* is notified of the disconnection. Emits a 'disconnected' event via
|
|
453
|
+
* WalletEventBus regardless of success or failure.
|
|
454
|
+
*
|
|
455
|
+
* @throws {Error} If disconnection fails (event still emitted)
|
|
456
|
+
*/
|
|
457
|
+
override async disconnect(): Promise<void> {
|
|
458
|
+
logger.info('Disconnecting from HSuite Wallet');
|
|
459
|
+
|
|
460
|
+
try {
|
|
461
|
+
await this.channelService.disconnect();
|
|
462
|
+
|
|
463
|
+
// Emit disconnection event
|
|
464
|
+
this.eventBus.disconnected.emit({
|
|
465
|
+
providerId: this.id,
|
|
466
|
+
providerType: 'hsuite-native',
|
|
467
|
+
reason: 'user_initiated',
|
|
468
|
+
timestamp: Date.now(),
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
logger.info('Disconnected successfully');
|
|
472
|
+
} catch (error) {
|
|
473
|
+
logger.error('Disconnection failed', { error });
|
|
474
|
+
// Still emit event even if disconnect fails
|
|
475
|
+
this.eventBus.disconnected.emit({
|
|
476
|
+
providerId: this.id,
|
|
477
|
+
providerType: 'hsuite-native',
|
|
478
|
+
reason: 'error',
|
|
479
|
+
timestamp: Date.now(),
|
|
480
|
+
});
|
|
481
|
+
throw error;
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
/**
|
|
486
|
+
* Signs a transaction without submitting it to the network.
|
|
487
|
+
*
|
|
488
|
+
* @description
|
|
489
|
+
* Sends the transaction payload to the wallet for signing. The wallet
|
|
490
|
+
* displays the transaction details and prompts the user for approval.
|
|
491
|
+
* If approved, the signed transaction is returned.
|
|
492
|
+
*
|
|
493
|
+
* @param options - Transaction signing options
|
|
494
|
+
*
|
|
495
|
+
* @returns Promise resolving to the signing result with signed payload
|
|
496
|
+
*
|
|
497
|
+
* @throws {Error} If user rejects the signing request
|
|
498
|
+
* @throws {Error} If wallet is not connected
|
|
499
|
+
* @throws {Error} If channel communication fails
|
|
500
|
+
*/
|
|
501
|
+
override async signTransaction(options: SignTransactionOptions): Promise<SignResult> {
|
|
502
|
+
logger.info('Signing transaction', { options });
|
|
503
|
+
|
|
504
|
+
try {
|
|
505
|
+
const response = await this.channelService.signTransaction({
|
|
506
|
+
accountAddress: options.accountAddress,
|
|
507
|
+
payload: options.payload,
|
|
508
|
+
ledgerId: options.ledgerId,
|
|
509
|
+
networkId: options.networkId,
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
logger.info('Transaction signed');
|
|
513
|
+
|
|
514
|
+
return {
|
|
515
|
+
signedPayload: response.signedPayload || '',
|
|
516
|
+
signature: response.signature,
|
|
517
|
+
metadata: response.metadata,
|
|
518
|
+
};
|
|
519
|
+
} catch (error) {
|
|
520
|
+
logger.error('Transaction signing failed', { error });
|
|
521
|
+
throw error;
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
/**
|
|
526
|
+
* Signs and executes a transaction atomically in a single prompt.
|
|
527
|
+
*
|
|
528
|
+
* @description
|
|
529
|
+
* Performs sign and submit as a single atomic operation with one user
|
|
530
|
+
* approval prompt. This is the recommended method for transaction submission
|
|
531
|
+
* as it provides better UX and matches WalletConnect's behavior.
|
|
532
|
+
*
|
|
533
|
+
* Called by {@link UnifiedWalletService.signAndExecuteTransaction} to ensure
|
|
534
|
+
* consistent single-prompt behavior across all provider types.
|
|
535
|
+
*
|
|
536
|
+
* @param options - Transaction submission options
|
|
537
|
+
*
|
|
538
|
+
* @returns Promise resolving to submission result with transaction ID
|
|
539
|
+
*
|
|
540
|
+
* @throws {Error} If user rejects the transaction
|
|
541
|
+
* @throws {Error} If wallet is not connected
|
|
542
|
+
* @throws {Error} If transaction submission fails
|
|
543
|
+
*/
|
|
544
|
+
async signAndExecuteTransaction(options: SubmitTransactionOptions): Promise<SubmitResult> {
|
|
545
|
+
logger.info('signAndExecuteTransaction called (single prompt)', {
|
|
546
|
+
accountAddress: options.accountAddress,
|
|
547
|
+
ledgerId: options.ledgerId,
|
|
548
|
+
networkId: options.networkId,
|
|
549
|
+
});
|
|
550
|
+
|
|
551
|
+
return this.submitTransactionInternal(options);
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
/**
|
|
555
|
+
* Signs and submits a transaction to the blockchain.
|
|
556
|
+
*
|
|
557
|
+
* @description
|
|
558
|
+
* The **preferred method** for submitting transactions. Performs sign and
|
|
559
|
+
* submit as a single atomic operation requiring only ONE user approval.
|
|
560
|
+
*
|
|
561
|
+
* Advantages over separate sign/submit:
|
|
562
|
+
* - Single wallet prompt (better UX)
|
|
563
|
+
* - Matches WalletConnect's `hedera_signAndExecuteTransaction` behavior
|
|
564
|
+
* - Atomic operation (no partial state)
|
|
565
|
+
*
|
|
566
|
+
* @param options - Transaction submission options
|
|
567
|
+
*
|
|
568
|
+
* @returns Promise resolving to submission result with transaction ID
|
|
569
|
+
*
|
|
570
|
+
* @throws {Error} If user rejects the transaction
|
|
571
|
+
* @throws {Error} If wallet is not connected
|
|
572
|
+
* @throws {Error} If transaction submission fails
|
|
573
|
+
*/
|
|
574
|
+
override async submitTransaction(options: SubmitTransactionOptions): Promise<SubmitResult> {
|
|
575
|
+
return this.submitTransactionInternal(options);
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
/**
|
|
579
|
+
* Internal implementation of sign and submit.
|
|
580
|
+
* Both submitTransaction() and signAndExecuteTransaction() delegate here.
|
|
581
|
+
*
|
|
582
|
+
* @param options
|
|
583
|
+
*/
|
|
584
|
+
private async submitTransactionInternal(
|
|
585
|
+
options: SubmitTransactionOptions,
|
|
586
|
+
): Promise<SubmitResult> {
|
|
587
|
+
logger.info('Signing and submitting transaction (single prompt)', {
|
|
588
|
+
accountAddress: options.accountAddress,
|
|
589
|
+
ledgerId: options.ledgerId,
|
|
590
|
+
networkId: options.networkId,
|
|
591
|
+
});
|
|
592
|
+
|
|
593
|
+
try {
|
|
594
|
+
const response = await this.channelService.signAndSubmitTransaction({
|
|
595
|
+
accountAddress: options.accountAddress,
|
|
596
|
+
payload: options.payload,
|
|
597
|
+
ledgerId: options.ledgerId,
|
|
598
|
+
networkId: options.networkId,
|
|
599
|
+
// Pass batch transaction fields if present
|
|
600
|
+
...(options.isBatch && {
|
|
601
|
+
isBatch: options.isBatch,
|
|
602
|
+
batchKey: options.batchKey,
|
|
603
|
+
innerTransactions: options.innerTransactions,
|
|
604
|
+
}),
|
|
605
|
+
});
|
|
606
|
+
|
|
607
|
+
logger.info('Transaction signed and submitted successfully', {
|
|
608
|
+
transactionId: response.transactionId,
|
|
609
|
+
transactionHash: response.transactionHash?.substring(0, 16),
|
|
610
|
+
});
|
|
611
|
+
|
|
612
|
+
return {
|
|
613
|
+
transactionId: response.transactionId,
|
|
614
|
+
transactionHash: response.transactionHash,
|
|
615
|
+
metadata: response.metadata,
|
|
616
|
+
};
|
|
617
|
+
} catch (error) {
|
|
618
|
+
logger.error('Transaction sign+submit failed', { error });
|
|
619
|
+
throw error;
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
/**
|
|
624
|
+
* Signs an arbitrary message for authentication or verification.
|
|
625
|
+
*
|
|
626
|
+
* @description
|
|
627
|
+
* Requests the wallet to sign a non-transaction message. Common use cases:
|
|
628
|
+
* - Authentication challenges (prove account ownership)
|
|
629
|
+
* - DAO voting
|
|
630
|
+
* - Off-chain signatures
|
|
631
|
+
* - Message verification
|
|
632
|
+
*
|
|
633
|
+
* @param options - Message signing options
|
|
634
|
+
*
|
|
635
|
+
* @returns Promise resolving to signature result
|
|
636
|
+
*
|
|
637
|
+
* @throws {Error} If user rejects the signing request
|
|
638
|
+
* @throws {Error} If wallet is not connected
|
|
639
|
+
*/
|
|
640
|
+
override async signMessage(options: SignMessageOptions): Promise<SignMessageResult> {
|
|
641
|
+
logger.info('Signing message', {
|
|
642
|
+
accountAddress: options.accountAddress,
|
|
643
|
+
encoding: options.encoding,
|
|
644
|
+
messageLength: options.message?.length,
|
|
645
|
+
});
|
|
646
|
+
|
|
647
|
+
try {
|
|
648
|
+
const response = await this.channelService.request<{
|
|
649
|
+
signature?: string;
|
|
650
|
+
publicKey?: string;
|
|
651
|
+
// §21.3 extended shape — new wallets include these fields.
|
|
652
|
+
// Older wallets (pre-§21.3) omit them; the optional chain below
|
|
653
|
+
// keeps us backwards-compatible with both.
|
|
654
|
+
algorithm?: 'ed25519' | 'ecdsa_secp256k1' | 'secp256k1';
|
|
655
|
+
encoding?: 'utf-8' | 'base64';
|
|
656
|
+
caipChainId?: string;
|
|
657
|
+
messageLength?: number;
|
|
658
|
+
metadata?: Record<string, unknown>;
|
|
659
|
+
}>('ledger/signMessage', {
|
|
660
|
+
accountAddress: options.accountAddress,
|
|
661
|
+
message: options.message,
|
|
662
|
+
encoding: options.encoding || 'utf-8',
|
|
663
|
+
ledgerId: options.ledgerId,
|
|
664
|
+
networkId: options.networkId,
|
|
665
|
+
});
|
|
666
|
+
|
|
667
|
+
logger.info('Message signed successfully', {
|
|
668
|
+
algorithm: response.algorithm,
|
|
669
|
+
caipChainId: response.caipChainId,
|
|
670
|
+
});
|
|
671
|
+
|
|
672
|
+
// Pass ALL fields through so the dApp's TransactionService sees the
|
|
673
|
+
// full MessageSignResult shape (§21.3 strict end-to-end contract).
|
|
674
|
+
return {
|
|
675
|
+
signature: response.signature || '',
|
|
676
|
+
publicKey: response.publicKey,
|
|
677
|
+
algorithm: response.algorithm,
|
|
678
|
+
encoding: response.encoding,
|
|
679
|
+
caipChainId: response.caipChainId,
|
|
680
|
+
messageLength: response.messageLength,
|
|
681
|
+
metadata: response.metadata,
|
|
682
|
+
};
|
|
683
|
+
} catch (error) {
|
|
684
|
+
logger.error('Message signing failed', { error });
|
|
685
|
+
throw error;
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
/**
|
|
690
|
+
* Checks if HSuite Native provider is available.
|
|
691
|
+
*
|
|
692
|
+
* @description
|
|
693
|
+
* HSuite Native is always available because it can use either:
|
|
694
|
+
* - Direct Nostr relay connection (if wallet is online)
|
|
695
|
+
* - Opening wallet in a new browser tab/window
|
|
696
|
+
* - Deep linking to native app on mobile
|
|
697
|
+
*
|
|
698
|
+
* @returns Promise always resolving to true
|
|
699
|
+
*/
|
|
700
|
+
override async isAvailable(): Promise<boolean> {
|
|
701
|
+
// HSuite Native is always available (can open wallet window or use extension)
|
|
702
|
+
return true;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
/**
|
|
706
|
+
* Attempts to restore a previous session and reconnect automatically.
|
|
707
|
+
*
|
|
708
|
+
* @description
|
|
709
|
+
* Checks for stored session data and attempts to re-establish the wallet
|
|
710
|
+
* connection without requiring user interaction. This is called automatically
|
|
711
|
+
* on provider initialization.
|
|
712
|
+
*
|
|
713
|
+
* If successful, emits a 'sessionRestored' event via WalletEventBus.
|
|
714
|
+
*
|
|
715
|
+
* @returns Promise resolving to true if reconnection succeeded, false otherwise
|
|
716
|
+
*/
|
|
717
|
+
async attemptReconnect(): Promise<boolean> {
|
|
718
|
+
logger.info('Attempting reconnect via unified channel');
|
|
719
|
+
|
|
720
|
+
try {
|
|
721
|
+
const success = await this.channelService.attemptRestore();
|
|
722
|
+
|
|
723
|
+
if (success) {
|
|
724
|
+
const accounts = this.accounts();
|
|
725
|
+
const invite = this.channelService.currentInvite();
|
|
726
|
+
|
|
727
|
+
this.eventBus.sessionRestored.emit({
|
|
728
|
+
providerId: this.id,
|
|
729
|
+
sessionKey: invite?.id || '',
|
|
730
|
+
accounts,
|
|
731
|
+
metadata: { autoReconnected: true },
|
|
732
|
+
timestamp: Date.now(),
|
|
733
|
+
});
|
|
734
|
+
|
|
735
|
+
logger.info('Reconnect successful');
|
|
736
|
+
} else {
|
|
737
|
+
logger.warn('Reconnect failed - no session or unable to connect');
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
return success;
|
|
741
|
+
} catch (error) {
|
|
742
|
+
logger.error('Reconnect failed', { error });
|
|
743
|
+
return false;
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
/**
|
|
748
|
+
* Type guard for HSuite Native config
|
|
749
|
+
* @param config
|
|
750
|
+
*/
|
|
751
|
+
private isHsuiteNativeConfig(config: ConnectionConfig): config is HsuiteNativeConfig {
|
|
752
|
+
return config.type === 'hsuite-native';
|
|
753
|
+
}
|
|
754
|
+
}
|