@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,210 @@
1
+ import type {
2
+ NetworkServiceForm,
3
+ StellarNetworkConfig,
4
+ UserRpcProviderConfig,
5
+ } from '@openzeppelin/ui-types';
6
+ import { isValidUrl } from '@openzeppelin/ui-utils';
7
+
8
+ import { testStellarRpcConnection, validateStellarRpcEndpoint } from './rpc';
9
+
10
+ /**
11
+ * Returns the default service configuration values for a given service ID.
12
+ * Used for proactive health checks when no user overrides are configured.
13
+ *
14
+ * @param networkConfig The network configuration
15
+ * @param serviceId The service identifier (e.g., 'rpc', 'access-control-indexer')
16
+ * @returns The default configuration values, or null if not available
17
+ */
18
+ export function getStellarDefaultServiceConfig(
19
+ networkConfig: StellarNetworkConfig,
20
+ serviceId: string
21
+ ): Record<string, unknown> | null {
22
+ switch (serviceId) {
23
+ case 'rpc':
24
+ if (networkConfig.sorobanRpcUrl) {
25
+ return { sorobanRpcUrl: networkConfig.sorobanRpcUrl };
26
+ }
27
+ break;
28
+ case 'access-control-indexer':
29
+ // Access control indexer is optional for Stellar - only return if both URLs are configured
30
+ if (networkConfig.indexerUri && networkConfig.indexerWsUri) {
31
+ return {
32
+ indexerUri: networkConfig.indexerUri,
33
+ indexerWsUri: networkConfig.indexerWsUri,
34
+ };
35
+ }
36
+ break;
37
+ }
38
+ return null;
39
+ }
40
+
41
+ /**
42
+ * Returns the network service forms for Stellar networks.
43
+ * Defines the UI configuration for the RPC and Access Control Indexer services.
44
+ *
45
+ * @param exclude Optional array of service IDs to exclude from the returned forms
46
+ * @returns Array of network service forms
47
+ */
48
+ export function getStellarNetworkServiceForms(exclude: string[] = []): NetworkServiceForm[] {
49
+ const forms: NetworkServiceForm[] = [
50
+ {
51
+ id: 'rpc',
52
+ label: 'RPC Provider',
53
+ fields: [
54
+ {
55
+ id: 'stellar-rpc-url',
56
+ name: 'sorobanRpcUrl',
57
+ type: 'text',
58
+ label: 'Soroban RPC URL',
59
+ placeholder: 'https://soroban.stellar.org',
60
+ validation: { required: true, pattern: '^https?://.+' },
61
+ width: 'full',
62
+ },
63
+ ],
64
+ },
65
+ {
66
+ id: 'access-control-indexer',
67
+ label: 'Access Control Indexer',
68
+ description:
69
+ 'Optional GraphQL indexer endpoint for historical access control data. Overrides the default indexer URL for this network.',
70
+ supportsConnectionTest: true,
71
+ requiredFeature: 'access_control_indexer',
72
+ fields: [
73
+ {
74
+ id: 'stellar-access-control-indexer-uri',
75
+ name: 'indexerUri',
76
+ type: 'text',
77
+ label: 'Access Control Indexer GraphQL Endpoint',
78
+ placeholder: 'https://indexer.example.com/graphql',
79
+ validation: { required: false, pattern: '^https?://.+' },
80
+ width: 'full',
81
+ helperText:
82
+ 'Optional. Used for querying historical access control events and role discovery.',
83
+ },
84
+ {
85
+ id: 'stellar-access-control-indexer-ws-uri',
86
+ name: 'indexerWsUri',
87
+ type: 'text',
88
+ label: 'Access Control Indexer GraphQL WebSocket Endpoint',
89
+ placeholder: 'wss://indexer.example.com/graphql',
90
+ validation: { required: false, pattern: '^wss?://.+' },
91
+ width: 'full',
92
+ helperText: 'Optional. Used for real-time subscriptions.',
93
+ },
94
+ ],
95
+ },
96
+ ];
97
+
98
+ return forms.filter((form) => !exclude.includes(form.id));
99
+ }
100
+
101
+ /**
102
+ * Validates a network service configuration for Stellar networks.
103
+ */
104
+ export async function validateStellarNetworkServiceConfig(
105
+ serviceId: string,
106
+ values: Record<string, unknown>
107
+ ): Promise<boolean> {
108
+ if (serviceId === 'rpc') {
109
+ const cfg = {
110
+ url: String(values.sorobanRpcUrl || ''),
111
+ isCustom: true,
112
+ } as UserRpcProviderConfig;
113
+ return validateStellarRpcEndpoint(cfg);
114
+ }
115
+
116
+ if (serviceId === 'access-control-indexer') {
117
+ // Validate indexerUri if provided
118
+ if (values.indexerUri !== undefined && values.indexerUri !== null && values.indexerUri !== '') {
119
+ if (!isValidUrl(String(values.indexerUri))) {
120
+ return false;
121
+ }
122
+ }
123
+
124
+ // Validate indexerWsUri if provided
125
+ if (
126
+ values.indexerWsUri !== undefined &&
127
+ values.indexerWsUri !== null &&
128
+ values.indexerWsUri !== ''
129
+ ) {
130
+ if (!isValidUrl(String(values.indexerWsUri))) {
131
+ return false;
132
+ }
133
+ }
134
+
135
+ return true;
136
+ }
137
+
138
+ return true;
139
+ }
140
+
141
+ /**
142
+ * Tests a network service connection for Stellar networks.
143
+ */
144
+ export async function testStellarNetworkServiceConnection(
145
+ serviceId: string,
146
+ values: Record<string, unknown>
147
+ ): Promise<{ success: boolean; latency?: number; error?: string }> {
148
+ if (serviceId === 'rpc') {
149
+ const cfg = {
150
+ url: String(values.sorobanRpcUrl || ''),
151
+ isCustom: true,
152
+ } as UserRpcProviderConfig;
153
+ return testStellarRpcConnection(cfg);
154
+ }
155
+
156
+ if (serviceId === 'access-control-indexer') {
157
+ const indexerUri = values.indexerUri;
158
+
159
+ // If no indexer URI is provided, indexer is optional - return success (nothing to test)
160
+ if (!indexerUri || typeof indexerUri !== 'string' || indexerUri.trim() === '') {
161
+ return { success: true };
162
+ }
163
+
164
+ if (!isValidUrl(indexerUri)) {
165
+ return { success: false, error: 'Invalid indexer URI format' };
166
+ }
167
+
168
+ try {
169
+ const startTime = Date.now();
170
+ // Perform a simple GraphQL introspection query to test connectivity
171
+ const response = await fetch(indexerUri, {
172
+ method: 'POST',
173
+ headers: {
174
+ 'Content-Type': 'application/json',
175
+ },
176
+ body: JSON.stringify({
177
+ query: '{ __typename }',
178
+ }),
179
+ });
180
+
181
+ const latency = Date.now() - startTime;
182
+
183
+ if (!response.ok) {
184
+ return {
185
+ success: false,
186
+ latency,
187
+ error: `HTTP ${response.status}: ${response.statusText}`,
188
+ };
189
+ }
190
+
191
+ const data = await response.json();
192
+ if (data.errors) {
193
+ return {
194
+ success: false,
195
+ latency,
196
+ error: `GraphQL errors: ${JSON.stringify(data.errors)}`,
197
+ };
198
+ }
199
+
200
+ return { success: true, latency };
201
+ } catch (error) {
202
+ return {
203
+ success: false,
204
+ error: error instanceof Error ? error.message : 'Unknown error',
205
+ };
206
+ }
207
+ }
208
+
209
+ return { success: true };
210
+ }
@@ -0,0 +1,270 @@
1
+ import type { StellarNetworkConfig, UserRpcProviderConfig } from '@openzeppelin/ui-types';
2
+ import { appConfigService, isValidUrl, logger, userRpcConfigService } from '@openzeppelin/ui-utils';
3
+
4
+ /**
5
+ * Builds a complete RPC URL from a user RPC provider configuration.
6
+ * For Stellar (Soroban), this just returns the URL as-is since
7
+ * users are providing complete RPC URLs including any API keys.
8
+ *
9
+ * @param config The user RPC provider configuration
10
+ * @returns The RPC URL
11
+ */
12
+ export function buildRpcUrl(config: UserRpcProviderConfig): string {
13
+ return config.url;
14
+ }
15
+
16
+ /**
17
+ * Resolves the RPC URL for a given Stellar network configuration.
18
+ * Priority order:
19
+ * 1. User-provided RPC configuration (from UserRpcConfigService)
20
+ * 2. RPC URL override from AppConfigService
21
+ * 3. Default sorobanRpcUrl from the network configuration
22
+ *
23
+ * @param networkConfig - The Stellar network configuration.
24
+ * @returns The resolved RPC URL string.
25
+ * @throws If no RPC URL can be resolved (neither user config, override, nor default is present and valid).
26
+ */
27
+ export function resolveRpcUrl(networkConfig: StellarNetworkConfig): string {
28
+ const logSystem = 'StellarRpcResolver';
29
+ const networkId = networkConfig.id;
30
+
31
+ // First priority: Check user-provided RPC configuration
32
+ const userRpcConfig = userRpcConfigService.getUserRpcConfig(networkId);
33
+ if (userRpcConfig) {
34
+ const userRpcUrl = buildRpcUrl(userRpcConfig);
35
+ if (isValidUrl(userRpcUrl)) {
36
+ logger.info(
37
+ logSystem,
38
+ `Using user-configured Soroban RPC URL for network ${networkId}: ${userRpcConfig.name || 'Custom'}`
39
+ );
40
+ return userRpcUrl;
41
+ } else {
42
+ logger.warn(
43
+ logSystem,
44
+ `User-configured Soroban RPC URL for ${networkId} is invalid: ${userRpcUrl}. Falling back.`
45
+ );
46
+ }
47
+ }
48
+
49
+ // Second priority: Check AppConfigService for an override
50
+ const rpcOverrideSetting = appConfigService.getRpcEndpointOverride(networkId);
51
+ let rpcUrlFromOverride: string | undefined;
52
+
53
+ if (typeof rpcOverrideSetting === 'string') {
54
+ rpcUrlFromOverride = rpcOverrideSetting;
55
+ } else if (typeof rpcOverrideSetting === 'object' && rpcOverrideSetting) {
56
+ // Check if it's a UserRpcProviderConfig
57
+ if ('url' in rpcOverrideSetting && 'isCustom' in rpcOverrideSetting) {
58
+ const userConfig = rpcOverrideSetting as UserRpcProviderConfig;
59
+ rpcUrlFromOverride = buildRpcUrl(userConfig);
60
+ } else if ('http' in rpcOverrideSetting) {
61
+ // It's an RpcEndpointConfig
62
+ rpcUrlFromOverride = rpcOverrideSetting.http;
63
+ }
64
+ }
65
+
66
+ if (rpcUrlFromOverride) {
67
+ logger.info(
68
+ logSystem,
69
+ `Using overridden Soroban RPC URL for network ${networkId}: ${rpcUrlFromOverride}`
70
+ );
71
+ if (isValidUrl(rpcUrlFromOverride)) {
72
+ return rpcUrlFromOverride;
73
+ } else {
74
+ logger.warn(
75
+ logSystem,
76
+ `Overridden Soroban RPC URL for ${networkId} is invalid: ${rpcUrlFromOverride}. Falling back.`
77
+ );
78
+ }
79
+ }
80
+
81
+ // Third priority: Fallback to the sorobanRpcUrl in the networkConfig
82
+ if (networkConfig.sorobanRpcUrl && isValidUrl(networkConfig.sorobanRpcUrl)) {
83
+ logger.debug(
84
+ logSystem,
85
+ `Using default Soroban RPC URL for network ${networkId}: ${networkConfig.sorobanRpcUrl}`
86
+ );
87
+ return networkConfig.sorobanRpcUrl;
88
+ }
89
+
90
+ logger.error(
91
+ logSystem,
92
+ `No valid Soroban RPC URL could be resolved for network ${networkId}. Checked user config, override, and networkConfig.sorobanRpcUrl.`
93
+ );
94
+ throw new Error(
95
+ `No valid Soroban RPC URL configured for network ${networkConfig.name} (ID: ${networkId}).`
96
+ );
97
+ }
98
+
99
+ /**
100
+ * Validates an RPC endpoint configuration for Stellar networks.
101
+ * @param rpcConfig - The RPC provider configuration to validate
102
+ * @returns True if the configuration is valid, false otherwise
103
+ */
104
+ export function validateStellarRpcEndpoint(rpcConfig: UserRpcProviderConfig): boolean {
105
+ try {
106
+ // Check if it's a valid URL (our validator already ensures HTTP/HTTPS)
107
+ if (!isValidUrl(rpcConfig.url)) {
108
+ logger.error('validateStellarRpcEndpoint', `Invalid RPC URL format: ${rpcConfig.url}`);
109
+ return false;
110
+ }
111
+
112
+ // Additional Stellar-specific validation could be added here
113
+ // For example, checking if the URL follows known provider patterns
114
+
115
+ return true;
116
+ } catch (error) {
117
+ logger.error('validateStellarRpcEndpoint', 'Error validating RPC endpoint:', error);
118
+ return false;
119
+ }
120
+ }
121
+
122
+ /**
123
+ * Tests the connection to a Stellar (Soroban) RPC endpoint with a timeout.
124
+ * Uses the Soroban RPC getHealth method to test connectivity.
125
+ * @param rpcConfig - The RPC provider configuration to test
126
+ * @param timeoutMs - Timeout in milliseconds (default: 5000ms)
127
+ * @returns Connection test results including success status, latency, and any errors
128
+ */
129
+ export async function testStellarRpcConnection(
130
+ rpcConfig: UserRpcProviderConfig,
131
+ timeoutMs: number = 5000
132
+ ): Promise<{
133
+ success: boolean;
134
+ latency?: number;
135
+ error?: string;
136
+ }> {
137
+ if (!rpcConfig.url) {
138
+ return { success: false, error: 'Soroban RPC URL is required' };
139
+ }
140
+
141
+ // Create an AbortController for timeout
142
+ const controller = new AbortController();
143
+ const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
144
+
145
+ try {
146
+ const startTime = Date.now();
147
+
148
+ // Use fetch to make a JSON-RPC call to test the connection
149
+ // Using getHealth method which is standard for Soroban RPC endpoints
150
+ const response = await fetch(rpcConfig.url, {
151
+ method: 'POST',
152
+ headers: {
153
+ 'Content-Type': 'application/json',
154
+ },
155
+ body: JSON.stringify({
156
+ jsonrpc: '2.0',
157
+ id: 1,
158
+ method: 'getHealth',
159
+ }),
160
+ signal: controller.signal,
161
+ });
162
+
163
+ if (!response.ok) {
164
+ return { success: false, error: `HTTP error: ${response.status}` };
165
+ }
166
+
167
+ const data = await response.json();
168
+ const latency = Date.now() - startTime;
169
+
170
+ // Check for JSON-RPC error
171
+ if (data.error) {
172
+ return {
173
+ success: false,
174
+ error: `Soroban RPC error: ${data.error.message || 'Unknown RPC error'}`,
175
+ };
176
+ }
177
+
178
+ // For Soroban RPC getHealth, a successful response should contain result
179
+ if (!data.result) {
180
+ // Try fallback method - getLatestLedger
181
+ return await testWithFallbackMethod(rpcConfig, controller.signal, startTime);
182
+ }
183
+
184
+ // Check if the health status indicates the service is healthy
185
+ const healthStatus = data.result.status;
186
+ if (healthStatus && healthStatus !== 'healthy') {
187
+ return {
188
+ success: false,
189
+ error: `Soroban RPC service unhealthy: ${healthStatus}`,
190
+ latency,
191
+ };
192
+ }
193
+
194
+ return { success: true, latency };
195
+ } catch (error) {
196
+ logger.error('testStellarRpcConnection', 'Connection test failed:', error);
197
+
198
+ // Check if the error was due to timeout
199
+ if (error instanceof Error && error.name === 'AbortError') {
200
+ return {
201
+ success: false,
202
+ error: `Connection timeout after ${timeoutMs}ms`,
203
+ };
204
+ }
205
+
206
+ // Try fallback method if primary test failed
207
+ try {
208
+ return await testWithFallbackMethod(rpcConfig, controller.signal, Date.now());
209
+ } catch {
210
+ return {
211
+ success: false,
212
+ error: error instanceof Error ? error.message : 'Connection failed',
213
+ };
214
+ }
215
+ } finally {
216
+ // Clear the timeout
217
+ clearTimeout(timeoutId);
218
+ }
219
+ }
220
+
221
+ /**
222
+ * Fallback method to test Soroban RPC connection using getLatestLedger.
223
+ * This is used when getHealth is not available or fails.
224
+ */
225
+ async function testWithFallbackMethod(
226
+ rpcConfig: UserRpcProviderConfig,
227
+ signal: AbortSignal,
228
+ startTime: number
229
+ ): Promise<{
230
+ success: boolean;
231
+ latency?: number;
232
+ error?: string;
233
+ }> {
234
+ const response = await fetch(rpcConfig.url, {
235
+ method: 'POST',
236
+ headers: {
237
+ 'Content-Type': 'application/json',
238
+ },
239
+ body: JSON.stringify({
240
+ jsonrpc: '2.0',
241
+ id: 1,
242
+ method: 'getLatestLedger',
243
+ }),
244
+ signal,
245
+ });
246
+
247
+ if (!response.ok) {
248
+ return { success: false, error: `HTTP error: ${response.status}` };
249
+ }
250
+
251
+ const data = await response.json();
252
+ const latency = Date.now() - startTime;
253
+
254
+ if (data.error) {
255
+ return {
256
+ success: false,
257
+ error: `Soroban RPC error: ${data.error.message || 'Unknown RPC error'}`,
258
+ };
259
+ }
260
+
261
+ // If we get a valid response with ledger info, the connection is working
262
+ if (data.result && data.result.sequence) {
263
+ return { success: true, latency };
264
+ }
265
+
266
+ return {
267
+ success: false,
268
+ error: 'Unexpected response format from Soroban RPC endpoint',
269
+ };
270
+ }
@@ -0,0 +1,2 @@
1
+ // Barrel file for configuration modules
2
+ export * from './configuration/index';
@@ -0,0 +1,78 @@
1
+ import * as StellarSdk from '@stellar/stellar-sdk';
2
+ import { describe, expect, it } from 'vitest';
3
+
4
+ /**
5
+ * Comprehensive test to ensure we support ALL ScSpec types from Stellar SDK
6
+ * This test will fail if any new ScSpec types are added to the SDK that we don't handle
7
+ */
8
+ describe('Complete ScSpec Type Coverage', () => {
9
+ // Get all ScSpec types from the current SDK version
10
+ const ALL_SCSPEC_TYPES = Object.getOwnPropertyNames(StellarSdk.xdr.ScSpecType)
11
+ .filter(
12
+ (name) =>
13
+ name.startsWith('scSpecType') && typeof StellarSdk.xdr.ScSpecType[name] === 'function'
14
+ )
15
+ .sort();
16
+
17
+ describe('All ScSpec types must be handled', () => {
18
+ ALL_SCSPEC_TYPES.forEach((scSpecTypeName) => {
19
+ it(`should handle ${scSpecTypeName}`, () => {
20
+ // Get all static methods from ScSpecType
21
+ const availableTypes = Object.getOwnPropertyNames(StellarSdk.xdr.ScSpecType).filter(
22
+ (name) =>
23
+ name.startsWith('scSpecType') && typeof StellarSdk.xdr.ScSpecType[name] === 'function'
24
+ );
25
+
26
+ // Ensure our list is complete
27
+ expect(availableTypes).toContain(scSpecTypeName);
28
+
29
+ // This test will remind us to update the test when new types are added
30
+ console.log(`✓ ${scSpecTypeName} is accounted for`);
31
+ });
32
+ });
33
+ });
34
+
35
+ describe('Runtime type extraction coverage', () => {
36
+ it('should handle all primitive types without throwing', () => {
37
+ // We can't easily mock XDR objects, but we can test our type name mapping
38
+ // This ensures we have mappings for all expected type names
39
+ const expectedTypeNames = [
40
+ 'Val',
41
+ 'Bool',
42
+ 'Void',
43
+ 'Error',
44
+ 'U32',
45
+ 'I32',
46
+ 'U64',
47
+ 'I64',
48
+ 'Timepoint',
49
+ 'Duration',
50
+ 'U128',
51
+ 'I128',
52
+ 'U256',
53
+ 'I256',
54
+ 'Bytes',
55
+ 'ScString',
56
+ 'ScSymbol',
57
+ 'Address',
58
+ 'MuxedAddress',
59
+ ];
60
+
61
+ expectedTypeNames.forEach((typeName) => {
62
+ // These should all have field type mappings or be handled by our type mapper
63
+ console.log(`Expected type: ${typeName}`);
64
+ });
65
+ });
66
+ });
67
+
68
+ describe('SDK version change detection', () => {
69
+ it('should document all ScSpec types in current SDK version', () => {
70
+ // This test documents all the ScSpec types we're currently handling
71
+ console.log(`SDK has ${ALL_SCSPEC_TYPES.length} ScSpec types:`);
72
+ console.log(ALL_SCSPEC_TYPES.join(', '));
73
+
74
+ // Should have a reasonable number of types (at least 20+)
75
+ expect(ALL_SCSPEC_TYPES.length).toBeGreaterThanOrEqual(20);
76
+ });
77
+ });
78
+ });
@@ -0,0 +1,3 @@
1
+ // Barrel file
2
+
3
+ export * from './loader';