@openzeppelin/adapter-stellar 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (165) hide show
  1. package/README.md +272 -0
  2. package/dist/config.cjs +21 -0
  3. package/dist/config.cjs.map +1 -0
  4. package/dist/config.d.cts +8 -0
  5. package/dist/config.d.cts.map +1 -0
  6. package/dist/config.d.mts +8 -0
  7. package/dist/config.d.mts.map +1 -0
  8. package/dist/config.mjs +20 -0
  9. package/dist/config.mjs.map +1 -0
  10. package/dist/index.cjs +7564 -0
  11. package/dist/index.cjs.map +1 -0
  12. package/dist/index.d.cts +261 -0
  13. package/dist/index.d.cts.map +1 -0
  14. package/dist/index.d.mts +263 -0
  15. package/dist/index.d.mts.map +1 -0
  16. package/dist/index.mjs +7529 -0
  17. package/dist/index.mjs.map +1 -0
  18. package/dist/metadata.cjs +22 -0
  19. package/dist/metadata.cjs.map +1 -0
  20. package/dist/metadata.d.cts +7 -0
  21. package/dist/metadata.d.cts.map +1 -0
  22. package/dist/metadata.d.mts +7 -0
  23. package/dist/metadata.d.mts.map +1 -0
  24. package/dist/metadata.mjs +21 -0
  25. package/dist/metadata.mjs.map +1 -0
  26. package/dist/networks-BrV516-R.d.cts +15 -0
  27. package/dist/networks-BrV516-R.d.cts.map +1 -0
  28. package/dist/networks-C0MmhJcu.d.mts +15 -0
  29. package/dist/networks-C0MmhJcu.d.mts.map +1 -0
  30. package/dist/networks-DgUFSTiC.cjs +76 -0
  31. package/dist/networks-DgUFSTiC.cjs.map +1 -0
  32. package/dist/networks-QbEPbaGT.mjs +46 -0
  33. package/dist/networks-QbEPbaGT.mjs.map +1 -0
  34. package/dist/networks.cjs +8 -0
  35. package/dist/networks.d.cts +2 -0
  36. package/dist/networks.d.mts +2 -0
  37. package/dist/networks.mjs +3 -0
  38. package/dist/vite-config.cjs +43 -0
  39. package/dist/vite-config.cjs.map +1 -0
  40. package/dist/vite-config.d.cts +35 -0
  41. package/dist/vite-config.d.cts.map +1 -0
  42. package/dist/vite-config.d.mts +35 -0
  43. package/dist/vite-config.d.mts.map +1 -0
  44. package/dist/vite-config.mjs +42 -0
  45. package/dist/vite-config.mjs.map +1 -0
  46. package/package.json +114 -0
  47. package/src/__tests__/getDefaultServiceConfig.test.ts +105 -0
  48. package/src/access-control/actions.ts +214 -0
  49. package/src/access-control/feature-detection.ts +238 -0
  50. package/src/access-control/index.ts +54 -0
  51. package/src/access-control/indexer-client.ts +1474 -0
  52. package/src/access-control/onchain-reader.ts +446 -0
  53. package/src/access-control/service.ts +1431 -0
  54. package/src/access-control/validation.ts +256 -0
  55. package/src/adapter.ts +659 -0
  56. package/src/config.ts +43 -0
  57. package/src/configuration/__tests__/explorer.test.ts +80 -0
  58. package/src/configuration/__tests__/rpc.test.ts +355 -0
  59. package/src/configuration/execution.ts +83 -0
  60. package/src/configuration/explorer.ts +105 -0
  61. package/src/configuration/index.ts +5 -0
  62. package/src/configuration/network-services.ts +210 -0
  63. package/src/configuration/rpc.ts +270 -0
  64. package/src/configuration.ts +2 -0
  65. package/src/contract/__tests__/complete-type-coverage.test.ts +78 -0
  66. package/src/contract/index.ts +3 -0
  67. package/src/contract/loader.ts +498 -0
  68. package/src/contract/transformer.ts +1 -0
  69. package/src/contract/type.ts +65 -0
  70. package/src/index.ts +23 -0
  71. package/src/mapping/constants.ts +89 -0
  72. package/src/mapping/enum-metadata.ts +237 -0
  73. package/src/mapping/field-generator.ts +296 -0
  74. package/src/mapping/index.ts +5 -0
  75. package/src/mapping/struct-fields.ts +106 -0
  76. package/src/mapping/tuple-components.ts +43 -0
  77. package/src/mapping/type-coverage-validator.ts +151 -0
  78. package/src/mapping/type-mapper.ts +203 -0
  79. package/src/metadata.ts +16 -0
  80. package/src/networks/README.md +84 -0
  81. package/src/networks/index.ts +19 -0
  82. package/src/networks/mainnet.ts +20 -0
  83. package/src/networks/testnet.ts +20 -0
  84. package/src/networks.ts +2 -0
  85. package/src/query/handler.ts +411 -0
  86. package/src/query/index.ts +4 -0
  87. package/src/query/view-checker.ts +32 -0
  88. package/src/sac/spec-cache.ts +68 -0
  89. package/src/sac/spec-source.ts +35 -0
  90. package/src/sac/xdr.ts +101 -0
  91. package/src/transaction/components/AdvancedInfo.tsx +34 -0
  92. package/src/transaction/components/FeeConfiguration.tsx +41 -0
  93. package/src/transaction/components/StellarRelayerOptions.tsx +60 -0
  94. package/src/transaction/components/TransactionTiming.tsx +77 -0
  95. package/src/transaction/components/index.ts +5 -0
  96. package/src/transaction/components/useStellarRelayerOptions.ts +114 -0
  97. package/src/transaction/eoa.ts +229 -0
  98. package/src/transaction/execution-strategy.ts +33 -0
  99. package/src/transaction/formatter.ts +296 -0
  100. package/src/transaction/index.ts +4 -0
  101. package/src/transaction/relayer.ts +575 -0
  102. package/src/transaction/sender.ts +156 -0
  103. package/src/transform/index.ts +4 -0
  104. package/src/transform/input-parser.ts +9 -0
  105. package/src/transform/output-formatter.ts +133 -0
  106. package/src/transform/parsers/complex-parser.ts +157 -0
  107. package/src/transform/parsers/generic-parser.ts +171 -0
  108. package/src/transform/parsers/index.ts +86 -0
  109. package/src/transform/parsers/primitive-parser.ts +123 -0
  110. package/src/transform/parsers/scval-converter.ts +405 -0
  111. package/src/transform/parsers/struct-parser.ts +324 -0
  112. package/src/transform/parsers/types.ts +35 -0
  113. package/src/types/__tests__/artifacts.test.ts +89 -0
  114. package/src/types/artifacts.ts +19 -0
  115. package/src/utils/__tests__/artifacts.test.ts +77 -0
  116. package/src/utils/artifacts.ts +30 -0
  117. package/src/utils/formatting.ts +122 -0
  118. package/src/utils/index.ts +6 -0
  119. package/src/utils/input-parsing.ts +336 -0
  120. package/src/utils/safe-type-parser.ts +303 -0
  121. package/src/utils/stellar-types.ts +35 -0
  122. package/src/utils/type-detection.ts +163 -0
  123. package/src/utils/xdr-ordering.ts +36 -0
  124. package/src/validation/__tests__/address.test.ts +267 -0
  125. package/src/validation/address.ts +136 -0
  126. package/src/validation/eoa.ts +33 -0
  127. package/src/validation/index.ts +3 -0
  128. package/src/validation/relayer.ts +13 -0
  129. package/src/vite-config.ts +67 -0
  130. package/src/wallet/README.md +93 -0
  131. package/src/wallet/__tests__/connection.test.ts +72 -0
  132. package/src/wallet/components/StellarWalletUiRoot.tsx +161 -0
  133. package/src/wallet/components/account/AccountDisplay.tsx +50 -0
  134. package/src/wallet/components/connect/ConnectButton.tsx +100 -0
  135. package/src/wallet/components/connect/ConnectorDialog.tsx +125 -0
  136. package/src/wallet/components/index.ts +3 -0
  137. package/src/wallet/connection.ts +151 -0
  138. package/src/wallet/context/StellarWalletContext.ts +32 -0
  139. package/src/wallet/context/index.ts +4 -0
  140. package/src/wallet/context/useStellarWalletContext.ts +17 -0
  141. package/src/wallet/hooks/facade-hooks.ts +31 -0
  142. package/src/wallet/hooks/index.ts +7 -0
  143. package/src/wallet/hooks/useStellarAccount.ts +27 -0
  144. package/src/wallet/hooks/useStellarConnect.ts +60 -0
  145. package/src/wallet/hooks/useStellarDisconnect.ts +47 -0
  146. package/src/wallet/hooks/useUiKitConfig.ts +40 -0
  147. package/src/wallet/implementation/wallets-kit-implementation.ts +379 -0
  148. package/src/wallet/index.ts +11 -0
  149. package/src/wallet/services/__tests__/configResolutionService.test.ts +163 -0
  150. package/src/wallet/services/configResolutionService.ts +65 -0
  151. package/src/wallet/stellar-wallets-kit/StellarWalletsKitConnectButton.tsx +82 -0
  152. package/src/wallet/stellar-wallets-kit/__mocks__/@creit.tech/stellar-wallets-kit.ts +48 -0
  153. package/src/wallet/stellar-wallets-kit/__tests__/export-service.test.ts +93 -0
  154. package/src/wallet/stellar-wallets-kit/__tests__/stellarUiKitManager.test.ts +0 -0
  155. package/src/wallet/stellar-wallets-kit/config-generator.ts +75 -0
  156. package/src/wallet/stellar-wallets-kit/export-service.ts +19 -0
  157. package/src/wallet/stellar-wallets-kit/index.ts +3 -0
  158. package/src/wallet/stellar-wallets-kit/stellarUiKitManager.ts +235 -0
  159. package/src/wallet/types.ts +19 -0
  160. package/src/wallet/utils/__tests__/filterWalletComponents.test.ts +150 -0
  161. package/src/wallet/utils/__tests__/uiKitService.test.ts +189 -0
  162. package/src/wallet/utils/filterWalletComponents.ts +89 -0
  163. package/src/wallet/utils/index.ts +3 -0
  164. package/src/wallet/utils/stellarWalletImplementationManager.ts +118 -0
  165. package/src/wallet/utils/uiKitService.ts +74 -0
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Stellar Adapter: Vite Configuration Export
3
+ *
4
+ * This module exports Vite configuration fragments for the Stellar adapter.
5
+ * Currently minimal, but provides a consistent interface for adapter-specific
6
+ * build requirements.
7
+ *
8
+ * USAGE:
9
+ * 1. In the main builder app: Import and merge into packages/builder/vite.config.ts
10
+ * 2. In exported apps: The export system injects these configs when Stellar is used
11
+ *
12
+ * See: docs/ADAPTER_ARCHITECTURE.md § "Build-Time Requirements"
13
+ */
14
+
15
+ import type { UserConfig } from 'vite';
16
+
17
+ /**
18
+ * Returns the Vite configuration required for Stellar adapter compatibility
19
+ *
20
+ * @returns Vite configuration object to be merged with your main vite.config
21
+ *
22
+ * @example
23
+ * ```typescript
24
+ * // vite.config.ts
25
+ * import { getStellarViteConfig } from '@openzeppelin/adapter-stellar/vite-config';
26
+ *
27
+ * export default defineConfig(({ mode }) => {
28
+ * const stellarConfig = getStellarViteConfig();
29
+ *
30
+ * return {
31
+ * plugins: [
32
+ * react(),
33
+ * ...stellarConfig.plugins,
34
+ * ],
35
+ * resolve: {
36
+ * dedupe: [
37
+ * ...stellarConfig.resolve.dedupe,
38
+ * ],
39
+ * },
40
+ * };
41
+ * });
42
+ * ```
43
+ */
44
+ export function getStellarViteConfig(): UserConfig {
45
+ return {
46
+ // Currently no Stellar-specific plugins required
47
+ plugins: [],
48
+
49
+ resolve: {
50
+ // Module Deduplication
51
+ // Ensure singleton instances of shared dependencies
52
+ dedupe: [
53
+ // Stellar-specific dependencies that may need deduplication
54
+ '@stellar/stellar-sdk',
55
+ '@creit.tech/stellar-wallets-kit',
56
+ ],
57
+ },
58
+
59
+ optimizeDeps: {
60
+ // Force Pre-Bundling (CommonJS → ESM conversion)
61
+ // Stellar dependencies are typically already ESM, but we include them here
62
+ // for consistency and to ensure proper module resolution
63
+ include: [],
64
+ exclude: [],
65
+ },
66
+ };
67
+ }
@@ -0,0 +1,93 @@
1
+ # Stellar Adapter Wallet Module
2
+
3
+ This directory contains the wallet integration layer for the Stellar adapter, providing all wallet‑related UI, hooks, context, and utilities using `@creit.tech/stellar-wallets-kit`.
4
+
5
+ ## Architectural Approach: Dual UI Support
6
+
7
+ The Stellar adapter supports two UI modes:
8
+
9
+ 1. Native UI Mode (`stellar-wallets-kit`): Uses the Stellar Wallets Kit’s native button and modal with address display and account management.
10
+ 2. Custom UI Mode (`custom`): Uses custom React components that match the Builder’s design system.
11
+
12
+ ## Purpose
13
+
14
+ - UI Environment Provision: `StellarWalletUiRoot` provides a stable provider root for wallet state.
15
+ - Facade Hooks: `stellarFacadeHooks` expose standardized hooks for connection and account status.
16
+ - UI Components: Custom‑styled wallet components (`ConnectButton`, `AccountDisplay`).
17
+ - Configuration: Layered config via `AppConfigService` and programmatic overrides.
18
+
19
+ ## Directory Structure
20
+
21
+ ```text
22
+ wallet/
23
+ ├── components/ # Wallet UI components & root
24
+ │ ├── StellarWalletUiRoot.tsx
25
+ │ ├── account/
26
+ │ ├── connect/
27
+ │ └── network/ # (Not used: no network switching in Stellar)
28
+ ├── context/
29
+ ├── hooks/
30
+ ├── implementation/ # Stellar Wallets Kit implementation
31
+ ├── services/ # Wallet UI config resolution
32
+ ├── stellar-wallets-kit/
33
+ ├── utils/
34
+ └── connection.ts
35
+ ```
36
+
37
+ ## Key Components & Concepts
38
+
39
+ - Adapter UI methods:
40
+ - `getEcosystemReactUiContextProvider()` → `StellarWalletUiRoot`
41
+ - `getEcosystemReactHooks()` → `stellarFacadeHooks`
42
+ - `getEcosystemWalletComponents()` → components for active kit
43
+ - `stellarUiKitManager`: Singleton controller for kit state and network config
44
+ - `StellarWalletUiRoot`: Stable provider that wires the kit and context
45
+ - Facade hooks: `useStellarAccount`, `useStellarConnect`, `useStellarDisconnect`, `useUiKitConfig`
46
+
47
+ ## Differences from EVM Wallet Module
48
+
49
+ - No network switching component (Stellar wallets do not switch networks dynamically)
50
+ - Single primary wallet kit (Stellar Wallets Kit) vs multiple kits on EVM
51
+ - No native TypeScript config files are loaded for kit setup (can be added later)
52
+
53
+ ## Configuration
54
+
55
+ 1. Global App Config (`app.config.json`):
56
+
57
+ Ecosystem‑namespaced configuration under `globalServiceConfigs.walletui.stellar`.
58
+
59
+ Custom UI components:
60
+
61
+ ```json
62
+ {
63
+ "globalServiceConfigs": {
64
+ "walletui": {
65
+ "stellar": {
66
+ "kitName": "custom",
67
+ "kitConfig": {
68
+ "showInjectedConnector": false,
69
+ "components": { "exclude": ["NetworkSwitcher"] }
70
+ }
71
+ }
72
+ }
73
+ }
74
+ }
75
+ ```
76
+
77
+ Native kit button:
78
+
79
+ ```json
80
+ {
81
+ "globalServiceConfigs": {
82
+ "walletui": {
83
+ "stellar": { "kitName": "stellar-wallets-kit", "kitConfig": {} }
84
+ }
85
+ }
86
+ }
87
+ ```
88
+
89
+ 2. Programmatic overrides: pass to `adapter.configureUiKit(overrides)`
90
+
91
+ ## Usage in Application
92
+
93
+ `StellarWalletUiRoot` is returned by the adapter and used by the Builder’s `WalletStateProvider`. Use `useWalletState()` and facade hooks from `@openzeppelin/ui-react` to render the wallet UI components from `getEcosystemWalletComponents()`.
@@ -0,0 +1,72 @@
1
+ import { describe, expect, it, vi } from 'vitest';
2
+
3
+ import {
4
+ disconnectStellarWallet,
5
+ getStellarWalletConnectionStatus,
6
+ supportsStellarWalletConnection,
7
+ } from '../connection';
8
+
9
+ // Mock the stellar-wallets-kit to avoid CommonJS import issues
10
+ vi.mock('@creit.tech/stellar-wallets-kit', () => ({
11
+ StellarWalletsKit: vi.fn(() => ({
12
+ getSupportedWallets: vi.fn().mockResolvedValue([]),
13
+ setWallet: vi.fn(),
14
+ getAddress: vi.fn(),
15
+ signTransaction: vi.fn(),
16
+ })),
17
+ WalletNetwork: {
18
+ PUBLIC: 'PUBLIC',
19
+ TESTNET: 'TESTNET',
20
+ },
21
+ allowAllModules: vi.fn(() => ({})),
22
+ }));
23
+
24
+ // Mock the stellarUiKitManager
25
+ vi.mock('../stellar-wallets-kit', () => ({
26
+ stellarUiKitManager: {
27
+ getState: vi.fn(() => ({
28
+ isConfigured: true,
29
+ currentFullUiKitConfig: {
30
+ kitName: 'custom',
31
+ kitConfig: {},
32
+ },
33
+ stellarKitProvider: null,
34
+ networkConfig: {
35
+ type: 'testnet',
36
+ horizon: 'https://horizon-testnet.stellar.org',
37
+ passphrase: 'Test SDF Network ; September 2015',
38
+ },
39
+ hasConfigError: false,
40
+ lastConfigError: null,
41
+ })),
42
+ },
43
+ }));
44
+
45
+ describe('Stellar Wallet Connection', () => {
46
+ describe('supportsStellarWalletConnection', () => {
47
+ it('should return true', () => {
48
+ expect(supportsStellarWalletConnection()).toBe(true);
49
+ });
50
+ });
51
+
52
+ describe('disconnectStellarWallet', () => {
53
+ it('should disconnect wallet successfully', async () => {
54
+ await disconnectStellarWallet();
55
+
56
+ // Since disconnectStellarWallet only clears internal state and doesn't
57
+ // call any methods on the kit, we just verify it doesn't throw
58
+ expect(true).toBe(true);
59
+ });
60
+ });
61
+
62
+ describe('getStellarWalletConnectionStatus', () => {
63
+ it('should return disconnected status when no address', () => {
64
+ const status = getStellarWalletConnectionStatus();
65
+
66
+ expect(status).toHaveProperty('isConnected', false);
67
+ expect(status).toHaveProperty('address');
68
+ expect(status).toHaveProperty('walletId');
69
+ expect(status).toHaveProperty('chainId');
70
+ });
71
+ });
72
+ });
@@ -0,0 +1,161 @@
1
+ import type { ISupportedWallet } from '@creit.tech/stellar-wallets-kit';
2
+ import { ReactNode, useCallback, useEffect, useState } from 'react';
3
+
4
+ import type { UiKitConfiguration } from '@openzeppelin/ui-types';
5
+ import { logger } from '@openzeppelin/ui-utils';
6
+
7
+ import {
8
+ connectStellarWallet,
9
+ disconnectStellarWallet,
10
+ getStellarAvailableConnectors,
11
+ getStellarWalletConnectionStatus,
12
+ onStellarWalletConnectionChange,
13
+ } from '../connection';
14
+ import {
15
+ StellarWalletContext,
16
+ type StellarWalletContextType,
17
+ } from '../context/StellarWalletContext';
18
+ import { stellarUiKitManager, type StellarUiKitManagerState } from '../stellar-wallets-kit';
19
+
20
+ /**
21
+ * Props for the StellarWalletUiRoot provider
22
+ */
23
+ interface StellarWalletUiRootProps {
24
+ children: ReactNode;
25
+ /** UI kit configuration */
26
+ uiKitConfig?: UiKitConfiguration;
27
+ }
28
+
29
+ /**
30
+ * Stellar wallet UI root provider component
31
+ * This component manages the wallet connection state and provides it to child components
32
+ */
33
+ export function StellarWalletUiRoot({ children, uiKitConfig }: StellarWalletUiRootProps) {
34
+ // UI Kit manager state
35
+ const [uiKitManagerState, setUiKitManagerState] = useState<StellarUiKitManagerState>(
36
+ stellarUiKitManager.getState()
37
+ );
38
+
39
+ // Connection state
40
+ const [address, setAddress] = useState<string | null>(null);
41
+ const [isConnecting, setIsConnecting] = useState(false);
42
+ const [availableWallets, setAvailableWallets] = useState<ISupportedWallet[]>([]);
43
+
44
+ // Initialize UI kit on mount
45
+ useEffect(() => {
46
+ // Only configure if a specific UI kit config is provided or if not already configured
47
+ const currentState = stellarUiKitManager.getState();
48
+
49
+ if (uiKitConfig || !currentState.currentFullUiKitConfig) {
50
+ const configToUse = uiKitConfig || { kitName: 'custom' as const, kitConfig: {} };
51
+
52
+ logger.debug('StellarWalletUiRoot', 'Configuring UI kit with:', configToUse);
53
+
54
+ stellarUiKitManager.configure(configToUse).catch((error) => {
55
+ logger.error('Failed to configure Stellar UI kit:', error);
56
+ });
57
+ }
58
+ }, [uiKitConfig]);
59
+
60
+ // Subscribe to UI kit state changes
61
+ useEffect(() => {
62
+ const unsubscribe = stellarUiKitManager.subscribe(() => {
63
+ setUiKitManagerState(stellarUiKitManager.getState());
64
+ });
65
+
66
+ return unsubscribe;
67
+ }, []);
68
+
69
+ // Event-driven connection status updates
70
+ useEffect(() => {
71
+ const unsubscribeFromConnectionChanges = onStellarWalletConnectionChange(
72
+ (currentStatus, _previousStatus) => {
73
+ setAddress(currentStatus.address || null);
74
+ logger.debug(
75
+ 'StellarWalletUiRoot',
76
+ `Connection status changed: ${currentStatus.isConnected ? 'connected' : 'disconnected'}`,
77
+ currentStatus.address
78
+ );
79
+ }
80
+ );
81
+
82
+ // Initial update to sync current state
83
+ const initialStatus = getStellarWalletConnectionStatus();
84
+ setAddress(initialStatus.address || null);
85
+
86
+ return () => {
87
+ unsubscribeFromConnectionChanges();
88
+ };
89
+ }, [address]);
90
+
91
+ // Load available wallets
92
+ useEffect(() => {
93
+ const loadWallets = async () => {
94
+ try {
95
+ const connectors = await getStellarAvailableConnectors();
96
+ setAvailableWallets(connectors as unknown as ISupportedWallet[]);
97
+ } catch (error) {
98
+ logger.error('Failed to load available wallets:', String(error));
99
+ }
100
+ };
101
+
102
+ if (!uiKitManagerState.isInitializing && uiKitManagerState.stellarKitProvider) {
103
+ loadWallets();
104
+ }
105
+ }, [uiKitManagerState.isInitializing, uiKitManagerState.stellarKitProvider]);
106
+
107
+ /**
108
+ * Connect to a wallet
109
+ */
110
+ const connect = useCallback(async (walletId: string) => {
111
+ setIsConnecting(true);
112
+
113
+ try {
114
+ const result = await connectStellarWallet(walletId);
115
+
116
+ if (result.connected && result.address) {
117
+ setAddress(result.address);
118
+ } else {
119
+ throw new Error(result.error || 'Failed to connect wallet');
120
+ }
121
+ } catch (error) {
122
+ logger.error('Failed to connect:', String(error));
123
+ throw error;
124
+ } finally {
125
+ setIsConnecting(false);
126
+ }
127
+ }, []);
128
+
129
+ /**
130
+ * Disconnect from the current wallet
131
+ */
132
+ const disconnect = useCallback(async () => {
133
+ try {
134
+ const result = await disconnectStellarWallet();
135
+
136
+ if (result.disconnected) {
137
+ setAddress(null);
138
+ } else {
139
+ throw new Error(result.error || 'Failed to disconnect wallet');
140
+ }
141
+ } catch (error) {
142
+ logger.error('Failed to disconnect:', String(error));
143
+ throw error;
144
+ }
145
+ }, []);
146
+
147
+ const contextValue: StellarWalletContextType = {
148
+ address,
149
+ isConnected: address !== null,
150
+ isConnecting,
151
+ availableWallets,
152
+ connect,
153
+ disconnect,
154
+ uiKitManagerState,
155
+ kit: uiKitManagerState.stellarKitProvider,
156
+ };
157
+
158
+ return (
159
+ <StellarWalletContext.Provider value={contextValue}>{children}</StellarWalletContext.Provider>
160
+ );
161
+ }
@@ -0,0 +1,50 @@
1
+ import { LogOut } from 'lucide-react';
2
+ import React from 'react';
3
+
4
+ import { Button } from '@openzeppelin/ui-components';
5
+ import type { BaseComponentProps } from '@openzeppelin/ui-types';
6
+ import { cn, getWalletAccountDisplaySizeProps, truncateMiddle } from '@openzeppelin/ui-utils';
7
+
8
+ import { useStellarAccount, useStellarDisconnect } from '../../hooks';
9
+
10
+ /**
11
+ * A component that displays the connected account address.
12
+ * Also includes a disconnect button.
13
+ */
14
+ export const CustomAccountDisplay: React.FC<BaseComponentProps> = ({
15
+ className,
16
+ size,
17
+ variant,
18
+ fullWidth,
19
+ }) => {
20
+ const { isConnected, address } = useStellarAccount();
21
+ const { disconnect } = useStellarDisconnect();
22
+
23
+ const sizeProps = getWalletAccountDisplaySizeProps(size);
24
+
25
+ if (!isConnected || !address || !disconnect) {
26
+ return null;
27
+ }
28
+
29
+ return (
30
+ <div className={cn('flex items-center gap-2', fullWidth && 'w-full', className)}>
31
+ <div className={cn('flex flex-col', fullWidth && 'flex-1')}>
32
+ <span className={cn(sizeProps.textSize, 'font-medium')}>
33
+ {truncateMiddle(address, 4, 4)}
34
+ </span>
35
+ <span className={cn(sizeProps.subTextSize, 'text-muted-foreground -mt-0.5')}>
36
+ Stellar Account
37
+ </span>
38
+ </div>
39
+ <Button
40
+ onClick={() => disconnect()}
41
+ variant={variant || 'ghost'}
42
+ size="icon"
43
+ className={cn(sizeProps.iconButtonSize, 'p-0')}
44
+ title="Disconnect wallet"
45
+ >
46
+ <LogOut className={sizeProps.iconSize} />
47
+ </Button>
48
+ </div>
49
+ );
50
+ };
@@ -0,0 +1,100 @@
1
+ import { Loader2, Wallet } from 'lucide-react';
2
+ import React, { useEffect, useState } from 'react';
3
+
4
+ import { Button } from '@openzeppelin/ui-components';
5
+ import type { BaseComponentProps } from '@openzeppelin/ui-types';
6
+ import { cn, getWalletButtonSizeProps } from '@openzeppelin/ui-utils';
7
+
8
+ import { useStellarAccount } from '../../hooks';
9
+ import { ConnectorDialog } from './ConnectorDialog';
10
+
11
+ /**
12
+ * A button that allows users to connect their wallet.
13
+ * Opens a dialog to select from available connectors.
14
+ * @param hideWhenConnected - Whether to hide the button when wallet is connected (default: true)
15
+ */
16
+ export interface ConnectButtonProps extends BaseComponentProps {
17
+ hideWhenConnected?: boolean;
18
+ }
19
+
20
+ export const CustomConnectButton: React.FC<ConnectButtonProps> = ({
21
+ className,
22
+ size,
23
+ variant,
24
+ fullWidth,
25
+ hideWhenConnected = true,
26
+ }) => {
27
+ const [dialogOpen, setDialogOpen] = useState(false);
28
+ const { isConnected, isConnecting } = useStellarAccount();
29
+
30
+ // Local state to indicate the button has been clicked and dialog is open, awaiting user selection
31
+ const [isManuallyInitiated, setIsManuallyInitiated] = useState(false);
32
+
33
+ const sizeProps = getWalletButtonSizeProps(size);
34
+
35
+ useEffect(() => {
36
+ if (isConnected && hideWhenConnected) {
37
+ setDialogOpen(false);
38
+ setIsManuallyInitiated(false); // Reset if dialog closes due to connection
39
+ }
40
+ }, [isConnected, hideWhenConnected]);
41
+
42
+ // If dialog is closed, reset manual initiation state
43
+ useEffect(() => {
44
+ if (!dialogOpen) {
45
+ setIsManuallyInitiated(false);
46
+ }
47
+ }, [dialogOpen]);
48
+
49
+ // If wallet reports it's connecting, we no longer need our manual pending state
50
+ useEffect(() => {
51
+ if (isConnecting) {
52
+ setIsManuallyInitiated(false);
53
+ }
54
+ }, [isConnecting]);
55
+
56
+ const handleConnectClick = () => {
57
+ if (!isConnected) {
58
+ setIsManuallyInitiated(true); // User clicked, show pending on button
59
+ setDialogOpen(true);
60
+ }
61
+ };
62
+
63
+ if (isConnected && hideWhenConnected) {
64
+ return null;
65
+ }
66
+
67
+ // Button shows loading if either hook reports connecting OR if user just clicked to open dialog
68
+ const showButtonLoading = isConnecting || isManuallyInitiated;
69
+
70
+ return (
71
+ <div className={cn('flex items-center', fullWidth && 'w-full', className)}>
72
+ <Button
73
+ onClick={handleConnectClick}
74
+ disabled={showButtonLoading || isConnected}
75
+ variant={variant || 'outline'}
76
+ size={sizeProps.size}
77
+ className={cn(sizeProps.className, fullWidth && 'w-full')}
78
+ title={isConnected ? 'Connected' : 'Connect Wallet'}
79
+ >
80
+ {showButtonLoading ? (
81
+ <Loader2 className={cn(sizeProps.iconSize, 'animate-spin mr-1')} />
82
+ ) : (
83
+ <Wallet className={cn(sizeProps.iconSize, 'mr-1')} />
84
+ )}
85
+ {showButtonLoading ? 'Connecting...' : 'Connect Wallet'}
86
+ </Button>
87
+
88
+ <ConnectorDialog
89
+ open={dialogOpen}
90
+ onOpenChange={(open) => {
91
+ setDialogOpen(open);
92
+ // If dialog is closed manually by user before selection, reset manual initiation
93
+ if (!open) {
94
+ setIsManuallyInitiated(false);
95
+ }
96
+ }}
97
+ />
98
+ </div>
99
+ );
100
+ };
@@ -0,0 +1,125 @@
1
+ import React, { useEffect, useState } from 'react';
2
+
3
+ import {
4
+ Button,
5
+ Dialog,
6
+ DialogContent,
7
+ DialogDescription,
8
+ DialogHeader,
9
+ DialogTitle,
10
+ } from '@openzeppelin/ui-components';
11
+ import type { Connector } from '@openzeppelin/ui-types';
12
+ import { logger } from '@openzeppelin/ui-utils';
13
+
14
+ import { getStellarAvailableConnectors } from '../../connection';
15
+ import { useStellarAccount, useStellarConnect } from '../../hooks';
16
+
17
+ /**
18
+ * Dialog component for selecting a wallet connector
19
+ */
20
+ interface ConnectorDialogProps {
21
+ open: boolean;
22
+ onOpenChange: (open: boolean) => void;
23
+ }
24
+
25
+ export const ConnectorDialog: React.FC<ConnectorDialogProps> = ({ open, onOpenChange }) => {
26
+ const { connect } = useStellarConnect();
27
+ const { isConnected, isConnecting } = useStellarAccount();
28
+ const [connectingId, setConnectingId] = useState<string | null>(null);
29
+ const [error, setError] = useState<string | null>(null);
30
+ const [connectors, setConnectors] = useState<Connector[]>([]);
31
+ const [loadingConnectors, setLoadingConnectors] = useState(true);
32
+
33
+ // Load available connectors
34
+ useEffect(() => {
35
+ const loadConnectors = async () => {
36
+ try {
37
+ const availableConnectors = await getStellarAvailableConnectors();
38
+ setConnectors(availableConnectors);
39
+ } catch (err) {
40
+ logger.error('Failed to load Stellar connectors:', String(err));
41
+ setError('Failed to load available wallets');
42
+ } finally {
43
+ setLoadingConnectors(false);
44
+ }
45
+ };
46
+
47
+ if (open) {
48
+ loadConnectors();
49
+ }
50
+ }, [open]);
51
+
52
+ // Track connection attempts for dialog closure
53
+ useEffect(() => {
54
+ // If we're connected and there was a connection attempt, close the dialog
55
+ if (isConnected && connectingId) {
56
+ onOpenChange(false);
57
+ setConnectingId(null);
58
+ setError(null);
59
+ }
60
+ }, [isConnected, connectingId, onOpenChange]);
61
+
62
+ // If connect function itself is not available
63
+ if (!connect) {
64
+ return (
65
+ <Dialog open={open} onOpenChange={onOpenChange}>
66
+ <DialogContent className="sm:max-w-[425px]">
67
+ <DialogHeader>
68
+ <DialogTitle>Error</DialogTitle>
69
+ </DialogHeader>
70
+ <p>Wallet connection function is not available.</p>
71
+ </DialogContent>
72
+ </Dialog>
73
+ );
74
+ }
75
+
76
+ const handleConnectorSelect = async (selectedConnector: Connector) => {
77
+ setConnectingId(selectedConnector.id);
78
+ setError(null);
79
+
80
+ try {
81
+ await connect({ connector: selectedConnector });
82
+ } catch (err) {
83
+ setError(err instanceof Error ? err.message : 'Failed to connect');
84
+ setConnectingId(null);
85
+ }
86
+ };
87
+
88
+ return (
89
+ <Dialog open={open} onOpenChange={onOpenChange}>
90
+ <DialogContent className="sm:max-w-[425px]">
91
+ <DialogHeader>
92
+ <DialogTitle>Connect Wallet</DialogTitle>
93
+ <DialogDescription>
94
+ Select a wallet provider to connect with this application.
95
+ </DialogDescription>
96
+ </DialogHeader>
97
+
98
+ <div className="grid gap-4 py-4">
99
+ {loadingConnectors ? (
100
+ <p className="text-center text-muted-foreground">Loading available wallets...</p>
101
+ ) : connectors.length === 0 ? (
102
+ <p className="text-center text-muted-foreground">No wallet connectors available.</p>
103
+ ) : (
104
+ connectors.map((connector: Connector) => (
105
+ <Button
106
+ key={connector.id}
107
+ onClick={() => handleConnectorSelect(connector)}
108
+ disabled={isConnecting && connectingId === connector.id}
109
+ variant="outline"
110
+ className="flex justify-between items-center w-full py-6"
111
+ >
112
+ <span>{connector.name}</span>
113
+ {isConnecting && connectingId === connector.id && (
114
+ <span className="ml-2 text-xs">Connecting...</span>
115
+ )}
116
+ </Button>
117
+ ))
118
+ )}
119
+ </div>
120
+
121
+ {error && <p className="text-sm text-red-500 mt-1">{error}</p>}
122
+ </DialogContent>
123
+ </Dialog>
124
+ );
125
+ };
@@ -0,0 +1,3 @@
1
+ // Export all wallet UI components
2
+ export { CustomConnectButton } from './connect/ConnectButton';
3
+ export { CustomAccountDisplay } from './account/AccountDisplay';