@openzeppelin/ui-builder-adapter-stellar 1.2.0 → 1.4.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openzeppelin/ui-builder-adapter-stellar",
3
- "version": "1.2.0",
3
+ "version": "1.4.0",
4
4
  "description": "Stellar Adapter for UI Builder",
5
5
  "keywords": [
6
6
  "openzeppelin",
@@ -35,9 +35,9 @@
35
35
  "dependencies": {
36
36
  "@creit.tech/stellar-wallets-kit": "^1.8.0",
37
37
  "@openzeppelin/relayer-sdk": "1.4.0",
38
- "@openzeppelin/ui-components": "^1.0.2",
39
- "@openzeppelin/ui-types": "^1.1.0",
40
- "@openzeppelin/ui-utils": "^1.0.0",
38
+ "@openzeppelin/ui-components": "^1.2.0",
39
+ "@openzeppelin/ui-types": "^1.5.0",
40
+ "@openzeppelin/ui-utils": "^1.2.0",
41
41
  "@stellar/stellar-sdk": "^14.1.1",
42
42
  "@stellar/stellar-xdr-json": "^23.0.0",
43
43
  "@web3icons/react": "^4.0.19",
@@ -0,0 +1,105 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import type { StellarNetworkConfig } from '@openzeppelin/ui-types';
4
+
5
+ import { getStellarDefaultServiceConfig } from '../configuration/network-services';
6
+
7
+ /**
8
+ * Tests for getStellarDefaultServiceConfig function.
9
+ *
10
+ * Note: We test the pure function directly instead of instantiating StellarAdapter
11
+ * to avoid loading heavy Stellar SDK dependencies (WASM modules, etc.) which can
12
+ * cause memory issues during test execution.
13
+ */
14
+ describe('getStellarDefaultServiceConfig', () => {
15
+ const createMockNetworkConfig = (
16
+ overrides: Partial<StellarNetworkConfig> = {}
17
+ ): StellarNetworkConfig =>
18
+ ({
19
+ id: 'stellar-testnet',
20
+ exportConstName: 'stellarTestnet',
21
+ name: 'Stellar Testnet',
22
+ ecosystem: 'stellar',
23
+ network: 'testnet',
24
+ type: 'testnet',
25
+ isTestnet: true,
26
+ sorobanRpcUrl: 'https://soroban-testnet.stellar.org',
27
+ networkPassphrase: 'Test SDF Network ; September 2015',
28
+ ...overrides,
29
+ }) as StellarNetworkConfig;
30
+
31
+ describe('rpc service', () => {
32
+ it('should return RPC config when sorobanRpcUrl is present', () => {
33
+ const networkConfig = createMockNetworkConfig();
34
+
35
+ const result = getStellarDefaultServiceConfig(networkConfig, 'rpc');
36
+
37
+ expect(result).toEqual({
38
+ sorobanRpcUrl: 'https://soroban-testnet.stellar.org',
39
+ });
40
+ });
41
+
42
+ it('should return null when sorobanRpcUrl is missing', () => {
43
+ const networkConfig = createMockNetworkConfig({
44
+ sorobanRpcUrl: undefined,
45
+ });
46
+
47
+ const result = getStellarDefaultServiceConfig(networkConfig, 'rpc');
48
+
49
+ expect(result).toBeNull();
50
+ });
51
+ });
52
+
53
+ describe('indexer service', () => {
54
+ it('should return indexer config when both URLs are present', () => {
55
+ const networkConfig = createMockNetworkConfig({
56
+ indexerUri: 'https://indexer.stellar.example/graphql',
57
+ indexerWsUri: 'wss://indexer.stellar.example/graphql',
58
+ });
59
+
60
+ const result = getStellarDefaultServiceConfig(networkConfig, 'indexer');
61
+
62
+ expect(result).toEqual({
63
+ indexerUri: 'https://indexer.stellar.example/graphql',
64
+ indexerWsUri: 'wss://indexer.stellar.example/graphql',
65
+ });
66
+ });
67
+
68
+ it('should return null when indexerUri is missing', () => {
69
+ const networkConfig = createMockNetworkConfig({
70
+ indexerWsUri: 'wss://indexer.stellar.example/graphql',
71
+ });
72
+
73
+ const result = getStellarDefaultServiceConfig(networkConfig, 'indexer');
74
+
75
+ expect(result).toBeNull();
76
+ });
77
+
78
+ it('should return null when indexerWsUri is missing', () => {
79
+ const networkConfig = createMockNetworkConfig({
80
+ indexerUri: 'https://indexer.stellar.example/graphql',
81
+ });
82
+
83
+ const result = getStellarDefaultServiceConfig(networkConfig, 'indexer');
84
+
85
+ expect(result).toBeNull();
86
+ });
87
+
88
+ it('should return null when neither indexer URL is present', () => {
89
+ const networkConfig = createMockNetworkConfig();
90
+
91
+ const result = getStellarDefaultServiceConfig(networkConfig, 'indexer');
92
+
93
+ expect(result).toBeNull();
94
+ });
95
+ });
96
+
97
+ describe('unknown service', () => {
98
+ it('should return null for unknown service IDs', () => {
99
+ const networkConfig = createMockNetworkConfig();
100
+
101
+ expect(getStellarDefaultServiceConfig(networkConfig, 'explorer')).toBeNull();
102
+ expect(getStellarDefaultServiceConfig(networkConfig, 'unknown')).toBeNull();
103
+ });
104
+ });
105
+ });
package/src/adapter.ts CHANGED
@@ -33,6 +33,7 @@ import { logger } from '@openzeppelin/ui-utils';
33
33
  import { getCurrentLedger } from './access-control/onchain-reader';
34
34
  import { createStellarAccessControlService } from './access-control/service';
35
35
  import {
36
+ getStellarDefaultServiceConfig,
36
37
  getStellarNetworkServiceForms,
37
38
  testStellarNetworkServiceConnection,
38
39
  validateStellarNetworkServiceConfig,
@@ -157,6 +158,13 @@ export class StellarAdapter implements ContractAdapter {
157
158
  return testStellarNetworkServiceConnection(serviceId, values);
158
159
  }
159
160
 
161
+ /**
162
+ * @inheritdoc
163
+ */
164
+ public getDefaultServiceConfig(serviceId: string): Record<string, unknown> | null {
165
+ return getStellarDefaultServiceConfig(this.networkConfig, serviceId);
166
+ }
167
+
160
168
  /**
161
169
  * NOTE about artifact inputs (single input with auto-detection):
162
170
  *
@@ -1,8 +1,43 @@
1
- import type { NetworkServiceForm, UserRpcProviderConfig } from '@openzeppelin/ui-types';
1
+ import type {
2
+ NetworkServiceForm,
3
+ StellarNetworkConfig,
4
+ UserRpcProviderConfig,
5
+ } from '@openzeppelin/ui-types';
2
6
  import { isValidUrl } from '@openzeppelin/ui-utils';
3
7
 
4
8
  import { testStellarRpcConnection, validateStellarRpcEndpoint } from './rpc';
5
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', '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 'indexer':
29
+ // 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
+
6
41
  /**
7
42
  * Returns the network service forms for Stellar networks.
8
43
  * Defines the UI configuration for the RPC and Indexer services.
@@ -3,7 +3,7 @@ import React from 'react';
3
3
 
4
4
  import { Button } from '@openzeppelin/ui-components';
5
5
  import type { BaseComponentProps } from '@openzeppelin/ui-types';
6
- import { cn, truncateMiddle } from '@openzeppelin/ui-utils';
6
+ import { cn, getWalletAccountDisplaySizeProps, truncateMiddle } from '@openzeppelin/ui-utils';
7
7
 
8
8
  import { useStellarAccount, useStellarDisconnect } from '../../hooks';
9
9
 
@@ -11,28 +11,39 @@ import { useStellarAccount, useStellarDisconnect } from '../../hooks';
11
11
  * A component that displays the connected account address.
12
12
  * Also includes a disconnect button.
13
13
  */
14
- export const CustomAccountDisplay: React.FC<BaseComponentProps> = ({ className }) => {
14
+ export const CustomAccountDisplay: React.FC<BaseComponentProps> = ({
15
+ className,
16
+ size,
17
+ variant,
18
+ fullWidth,
19
+ }) => {
15
20
  const { isConnected, address } = useStellarAccount();
16
21
  const { disconnect } = useStellarDisconnect();
17
22
 
23
+ const sizeProps = getWalletAccountDisplaySizeProps(size);
24
+
18
25
  if (!isConnected || !address || !disconnect) {
19
26
  return null;
20
27
  }
21
28
 
22
29
  return (
23
- <div className={cn('flex items-center gap-2', className)}>
24
- <div className="flex flex-col">
25
- <span className="text-xs font-medium">{truncateMiddle(address, 4, 4)}</span>
26
- <span className="text-[9px] text-muted-foreground -mt-0.5">Stellar Account</span>
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>
27
38
  </div>
28
39
  <Button
29
40
  onClick={() => disconnect()}
30
- variant="ghost"
41
+ variant={variant || 'ghost'}
31
42
  size="icon"
32
- className="size-6 p-0"
43
+ className={cn(sizeProps.iconButtonSize, 'p-0')}
33
44
  title="Disconnect wallet"
34
45
  >
35
- <LogOut className="size-3.5" />
46
+ <LogOut className={sizeProps.iconSize} />
36
47
  </Button>
37
48
  </div>
38
49
  );
@@ -3,7 +3,7 @@ import React, { useEffect, useState } from 'react';
3
3
 
4
4
  import { Button } from '@openzeppelin/ui-components';
5
5
  import type { BaseComponentProps } from '@openzeppelin/ui-types';
6
- import { cn } from '@openzeppelin/ui-utils';
6
+ import { cn, getWalletButtonSizeProps } from '@openzeppelin/ui-utils';
7
7
 
8
8
  import { useStellarAccount } from '../../hooks';
9
9
  import { ConnectorDialog } from './ConnectorDialog';
@@ -19,6 +19,9 @@ export interface ConnectButtonProps extends BaseComponentProps {
19
19
 
20
20
  export const CustomConnectButton: React.FC<ConnectButtonProps> = ({
21
21
  className,
22
+ size,
23
+ variant,
24
+ fullWidth,
22
25
  hideWhenConnected = true,
23
26
  }) => {
24
27
  const [dialogOpen, setDialogOpen] = useState(false);
@@ -27,6 +30,8 @@ export const CustomConnectButton: React.FC<ConnectButtonProps> = ({
27
30
  // Local state to indicate the button has been clicked and dialog is open, awaiting user selection
28
31
  const [isManuallyInitiated, setIsManuallyInitiated] = useState(false);
29
32
 
33
+ const sizeProps = getWalletButtonSizeProps(size);
34
+
30
35
  useEffect(() => {
31
36
  if (isConnected && hideWhenConnected) {
32
37
  setDialogOpen(false);
@@ -63,19 +68,19 @@ export const CustomConnectButton: React.FC<ConnectButtonProps> = ({
63
68
  const showButtonLoading = isConnecting || isManuallyInitiated;
64
69
 
65
70
  return (
66
- <div className={cn('flex items-center', className)}>
71
+ <div className={cn('flex items-center', fullWidth && 'w-full', className)}>
67
72
  <Button
68
73
  onClick={handleConnectClick}
69
74
  disabled={showButtonLoading || isConnected}
70
- variant="outline"
71
- size="sm"
72
- className="h-8 px-2 text-xs"
75
+ variant={variant || 'outline'}
76
+ size={sizeProps.size}
77
+ className={cn(sizeProps.className, fullWidth && 'w-full')}
73
78
  title={isConnected ? 'Connected' : 'Connect Wallet'}
74
79
  >
75
80
  {showButtonLoading ? (
76
- <Loader2 className="size-3.5 animate-spin mr-1" />
81
+ <Loader2 className={cn(sizeProps.iconSize, 'animate-spin mr-1')} />
77
82
  ) : (
78
- <Wallet className="size-3.5 mr-1" />
83
+ <Wallet className={cn(sizeProps.iconSize, 'mr-1')} />
79
84
  )}
80
85
  {showButtonLoading ? 'Connecting...' : 'Connect Wallet'}
81
86
  </Button>