@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,163 @@
1
+ import * as StellarSdk from '@stellar/stellar-sdk';
2
+
3
+ import { isDevelopmentOrTestEnvironment, logger } from '@openzeppelin/ui-utils';
4
+
5
+ /**
6
+ * Utility functions for detecting and analyzing Stellar/Soroban parameter types
7
+ */
8
+
9
+ /**
10
+ * Extract human-readable Soroban type name from ScSpecTypeDef
11
+ * Based on the Stellar SDK type system
12
+ */
13
+ export function extractSorobanTypeFromScSpec(scSpecType: StellarSdk.xdr.ScSpecTypeDef): string {
14
+ try {
15
+ const typeSwitch = scSpecType.switch();
16
+
17
+ switch (typeSwitch) {
18
+ case StellarSdk.xdr.ScSpecType.scSpecTypeVal():
19
+ return 'Val';
20
+ case StellarSdk.xdr.ScSpecType.scSpecTypeBool():
21
+ return 'Bool';
22
+ case StellarSdk.xdr.ScSpecType.scSpecTypeVoid():
23
+ return 'Void';
24
+ case StellarSdk.xdr.ScSpecType.scSpecTypeError():
25
+ return 'Error';
26
+ case StellarSdk.xdr.ScSpecType.scSpecTypeU32():
27
+ return 'U32';
28
+ case StellarSdk.xdr.ScSpecType.scSpecTypeI32():
29
+ return 'I32';
30
+ case StellarSdk.xdr.ScSpecType.scSpecTypeU64():
31
+ return 'U64';
32
+ case StellarSdk.xdr.ScSpecType.scSpecTypeI64():
33
+ return 'I64';
34
+ case StellarSdk.xdr.ScSpecType.scSpecTypeTimepoint():
35
+ return 'Timepoint';
36
+ case StellarSdk.xdr.ScSpecType.scSpecTypeDuration():
37
+ return 'Duration';
38
+ case StellarSdk.xdr.ScSpecType.scSpecTypeU128():
39
+ return 'U128';
40
+ case StellarSdk.xdr.ScSpecType.scSpecTypeI128():
41
+ return 'I128';
42
+ case StellarSdk.xdr.ScSpecType.scSpecTypeU256():
43
+ return 'U256';
44
+ case StellarSdk.xdr.ScSpecType.scSpecTypeI256():
45
+ return 'I256';
46
+ case StellarSdk.xdr.ScSpecType.scSpecTypeBytes():
47
+ return 'Bytes';
48
+ case StellarSdk.xdr.ScSpecType.scSpecTypeBytesN(): {
49
+ const bytesNType = scSpecType.bytesN();
50
+ const size = bytesNType.n();
51
+ return `BytesN<${size}>`;
52
+ }
53
+ case StellarSdk.xdr.ScSpecType.scSpecTypeString():
54
+ return 'ScString';
55
+ case StellarSdk.xdr.ScSpecType.scSpecTypeSymbol():
56
+ return 'ScSymbol';
57
+ case StellarSdk.xdr.ScSpecType.scSpecTypeVec(): {
58
+ const vecType = scSpecType.vec();
59
+ const elementType = extractSorobanTypeFromScSpec(vecType.elementType());
60
+ return `Vec<${elementType}>`;
61
+ }
62
+ case StellarSdk.xdr.ScSpecType.scSpecTypeMap(): {
63
+ const mapType = scSpecType.map();
64
+ const keyType = extractSorobanTypeFromScSpec(mapType.keyType());
65
+ const valueType = extractSorobanTypeFromScSpec(mapType.valueType());
66
+ return `Map<${keyType}, ${valueType}>`;
67
+ }
68
+ case StellarSdk.xdr.ScSpecType.scSpecTypeTuple(): {
69
+ const tupleType = scSpecType.tuple();
70
+ const valueTypes = tupleType.valueTypes();
71
+ const typeNames = valueTypes.map((t) => extractSorobanTypeFromScSpec(t));
72
+ return `Tuple<${typeNames.join(', ')}>`;
73
+ }
74
+ case StellarSdk.xdr.ScSpecType.scSpecTypeOption(): {
75
+ const optionType = scSpecType.option();
76
+ const valueType = extractSorobanTypeFromScSpec(optionType.valueType());
77
+ return `Option<${valueType}>`;
78
+ }
79
+ case StellarSdk.xdr.ScSpecType.scSpecTypeResult(): {
80
+ const resultType = scSpecType.result();
81
+ const okType = extractSorobanTypeFromScSpec(resultType.okType());
82
+ const errorType = extractSorobanTypeFromScSpec(resultType.errorType());
83
+ return `Result<${okType}, ${errorType}>`;
84
+ }
85
+ case StellarSdk.xdr.ScSpecType.scSpecTypeAddress():
86
+ return 'Address';
87
+ case StellarSdk.xdr.ScSpecType.scSpecTypeMuxedAddress():
88
+ return 'MuxedAddress';
89
+ case StellarSdk.xdr.ScSpecType.scSpecTypeUdt(): {
90
+ const udtType = scSpecType.udt();
91
+ return udtType.name().toString();
92
+ }
93
+ default:
94
+ // COMPREHENSIVE TYPE MISS DETECTION
95
+ logger.error('extractSorobanTypeFromScSpec', `🚨 MISSING SCSPEC TYPE HANDLER 🚨`, {
96
+ typeSwitchValue: typeSwitch.value,
97
+ typeSwitchName: typeSwitch.name,
98
+ rawScSpecType: scSpecType,
99
+ message: 'This indicates a missing case in extractSorobanTypeFromScSpec switch statement',
100
+ actionRequired: 'Add support for this ScSpec type immediately',
101
+ sdkVersion: process.env.npm_package_dependencies_stellar_sdk || 'unknown',
102
+ });
103
+
104
+ // Create detailed error report for unknown types
105
+ const errorReport = {
106
+ type: 'MISSING_SCSPEC_TYPE',
107
+ scSpecType: typeSwitch.name,
108
+ value: typeSwitch.value,
109
+ timestamp: new Date().toISOString(),
110
+ };
111
+
112
+ // In development, throw an error to fail fast
113
+ if (isDevelopmentOrTestEnvironment()) {
114
+ throw new Error(
115
+ `Missing ScSpec type handler: ${typeSwitch.name} (value: ${typeSwitch.value}). Please add support for this type.`
116
+ );
117
+ }
118
+
119
+ // In production, log extensively but don't break
120
+ logger.error('STELLAR_ADAPTER_MISSING_TYPE', 'Missing ScSpec type handler:', errorReport);
121
+ return 'unknown';
122
+ }
123
+ } catch (error) {
124
+ logger.error('extractSorobanTypeFromScSpec', 'Failed to extract type:', error);
125
+ return 'unknown';
126
+ }
127
+ }
128
+
129
+ /**
130
+ * Determines if a parameter type is likely an enum based on naming conventions
131
+ * and common patterns in Stellar/Soroban contracts.
132
+ *
133
+ * @param parameterType The parameter type string to analyze
134
+ * @returns true if the type appears to be an enum based on naming patterns
135
+ */
136
+ export function isLikelyEnumType(parameterType: string): boolean {
137
+ // Direct enum name patterns
138
+ if (parameterType.includes('Enum') || parameterType.includes('enum')) {
139
+ return true;
140
+ }
141
+
142
+ // Common enum naming patterns in Stellar/Soroban
143
+ const enumPatterns = [
144
+ /^(Status|State|Type|Kind|Mode|Level|Priority|Category)$/i,
145
+ /^.*?(Status|State|Type|Kind|Mode|Level|Priority|Category)$/i,
146
+ /^(Token|Asset|Account|Contract|Network)Type$/i,
147
+ ];
148
+
149
+ // Only apply pattern matching if it's not a generic "UnknownType" or similar
150
+ if (
151
+ parameterType === 'UnknownType' ||
152
+ parameterType === 'CustomStruct' ||
153
+ parameterType === 'UserInfo'
154
+ ) {
155
+ return false;
156
+ }
157
+
158
+ return enumPatterns.some((pattern) => pattern.test(parameterType));
159
+ }
160
+
161
+ export function isBytesNType(parameterType: string): boolean {
162
+ return /^BytesN<\d+>$/.test(parameterType);
163
+ }
@@ -0,0 +1,36 @@
1
+ import { xdr } from '@stellar/stellar-sdk';
2
+
3
+ /**
4
+ * Compare two ScVals based on their XDR encoding without relying on Node's Buffer.
5
+ */
6
+ export function compareScValsByXdr(a: xdr.ScVal, b: xdr.ScVal): number {
7
+ const aBytes = getBytes(a);
8
+ const bBytes = getBytes(b);
9
+
10
+ const minLength = Math.min(aBytes.length, bBytes.length);
11
+ for (let index = 0; index < minLength; index += 1) {
12
+ const diff = aBytes[index] - bBytes[index];
13
+ if (diff !== 0) {
14
+ return diff;
15
+ }
16
+ }
17
+
18
+ return aBytes.length - bBytes.length;
19
+ }
20
+
21
+ function getBytes(scVal: xdr.ScVal): Uint8Array {
22
+ const xdrValue = scVal.toXDR();
23
+
24
+ // In browsers, toXDR may already return a Uint8Array (Buffer is a Uint8Array subclass in Node).
25
+ if (xdrValue instanceof Uint8Array) {
26
+ return xdrValue;
27
+ }
28
+
29
+ // Handle array-like objects (e.g., Buffer in Node.js)
30
+ const arrayLike = xdrValue as ArrayLike<number>;
31
+ const result = new Uint8Array(arrayLike.length);
32
+ for (let index = 0; index < arrayLike.length; index += 1) {
33
+ result[index] = arrayLike[index];
34
+ }
35
+ return result;
36
+ }
@@ -0,0 +1,267 @@
1
+ // Import the mocked StrKey for use in tests
2
+ import { StrKey } from '@stellar/stellar-sdk';
3
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
4
+
5
+ import {
6
+ isValidAccountAddress,
7
+ isValidAddress,
8
+ isValidContractAddress,
9
+ isValidMuxedAddress,
10
+ isValidSecretSeed,
11
+ isValidSignedPayloadAddress,
12
+ type StellarAddressType,
13
+ } from '../address';
14
+
15
+ // Mock StrKey from stellar-sdk
16
+ vi.mock('@stellar/stellar-sdk', () => ({
17
+ StrKey: {
18
+ isValidEd25519PublicKey: vi.fn(),
19
+ isValidContract: vi.fn(),
20
+ isValidMed25519PublicKey: vi.fn(),
21
+ isValidEd25519SecretSeed: vi.fn(),
22
+ isValidSignedPayload: vi.fn(),
23
+ decodePreAuthTx: vi.fn(),
24
+ decodeSha256Hash: vi.fn(),
25
+ },
26
+ }));
27
+
28
+ const mockStrKey = StrKey as unknown as {
29
+ isValidEd25519PublicKey: ReturnType<typeof vi.fn>;
30
+ isValidContract: ReturnType<typeof vi.fn>;
31
+ isValidMed25519PublicKey: ReturnType<typeof vi.fn>;
32
+ isValidEd25519SecretSeed: ReturnType<typeof vi.fn>;
33
+ isValidSignedPayload: ReturnType<typeof vi.fn>;
34
+ decodePreAuthTx: ReturnType<typeof vi.fn>;
35
+ decodeSha256Hash: ReturnType<typeof vi.fn>;
36
+ };
37
+
38
+ describe('Specific validation functions', () => {
39
+ beforeEach(() => {
40
+ vi.clearAllMocks();
41
+ });
42
+
43
+ describe('isValidAccountAddress', () => {
44
+ it('should return true for valid account address (G...)', () => {
45
+ const accountAddress = 'GDQP2KPQGKIHYJGXNUIYOMHARUARCA7DJT5FO2FFOOKY3B2WSQHG4W37';
46
+ mockStrKey.isValidEd25519PublicKey.mockReturnValue(true);
47
+
48
+ const result = isValidAccountAddress(accountAddress);
49
+
50
+ expect(result).toBe(true);
51
+ expect(mockStrKey.isValidEd25519PublicKey).toHaveBeenCalledWith(accountAddress);
52
+ });
53
+
54
+ it('should return false for invalid account address', () => {
55
+ const invalidAddress = 'INVALID_ACCOUNT';
56
+ mockStrKey.isValidEd25519PublicKey.mockReturnValue(false);
57
+
58
+ const result = isValidAccountAddress(invalidAddress);
59
+
60
+ expect(result).toBe(false);
61
+ });
62
+ });
63
+
64
+ describe('isValidContractAddress', () => {
65
+ it('should return true for valid contract address (C...)', () => {
66
+ const contractAddress = 'CDLZFC3SYJYDZT7K67VZ75HPJVIEUVNIXF47ZG2FB2RMQQAUHKENIFEJ';
67
+ mockStrKey.isValidContract.mockReturnValue(true);
68
+
69
+ const result = isValidContractAddress(contractAddress);
70
+
71
+ expect(result).toBe(true);
72
+ expect(mockStrKey.isValidContract).toHaveBeenCalledWith(contractAddress);
73
+ });
74
+ });
75
+
76
+ describe('isValidMuxedAddress', () => {
77
+ it('should return true for valid muxed address (M...)', () => {
78
+ const muxedAddress = 'MDQP2KPQGKIHYJGXNUIYOMHARUARCA7DJT5FO2FFOOKY3B2WSQHG4W37AAAAAAAAAAAA';
79
+ mockStrKey.isValidMed25519PublicKey.mockReturnValue(true);
80
+
81
+ const result = isValidMuxedAddress(muxedAddress);
82
+
83
+ expect(result).toBe(true);
84
+ expect(mockStrKey.isValidMed25519PublicKey).toHaveBeenCalledWith(muxedAddress);
85
+ });
86
+ });
87
+
88
+ describe('isValidSecretSeed', () => {
89
+ it('should return true for valid secret seed (S...)', () => {
90
+ const secretSeed = 'SDQP2KPQGKIHYJGXNUIYOMHARUARCA7DJT5FO2FFOOKY3B2WSQHG4W37';
91
+ mockStrKey.isValidEd25519SecretSeed.mockReturnValue(true);
92
+
93
+ const result = isValidSecretSeed(secretSeed);
94
+
95
+ expect(result).toBe(true);
96
+ expect(mockStrKey.isValidEd25519SecretSeed).toHaveBeenCalledWith(secretSeed);
97
+ });
98
+ });
99
+
100
+ describe('isValidSignedPayloadAddress', () => {
101
+ it('should return true for valid signed payload address (P...)', () => {
102
+ const signedPayload = 'PDQP2KPQGKIHYJGXNUIYOMHARUARCA7DJT5FO2FFOOKY3B2WSQHG4W37';
103
+ mockStrKey.isValidSignedPayload.mockReturnValue(true);
104
+
105
+ const result = isValidSignedPayloadAddress(signedPayload);
106
+
107
+ expect(result).toBe(true);
108
+ expect(mockStrKey.isValidSignedPayload).toHaveBeenCalledWith(signedPayload);
109
+ });
110
+ });
111
+ });
112
+
113
+ describe('isValidAddress (main function)', () => {
114
+ beforeEach(() => {
115
+ vi.clearAllMocks();
116
+ });
117
+
118
+ describe('without specific type (validates common types)', () => {
119
+ it('should return true for valid account address (G...)', () => {
120
+ const accountAddress = 'GDQP2KPQGKIHYJGXNUIYOMHARUARCA7DJT5FO2FFOOKY3B2WSQHG4W37';
121
+ mockStrKey.isValidEd25519PublicKey.mockReturnValue(true);
122
+ mockStrKey.isValidContract.mockReturnValue(false);
123
+ mockStrKey.isValidMed25519PublicKey.mockReturnValue(false);
124
+
125
+ const result = isValidAddress(accountAddress);
126
+
127
+ expect(result).toBe(true);
128
+ });
129
+
130
+ it('should return true for valid contract address (C...)', () => {
131
+ const contractAddress = 'CDLZFC3SYJYDZT7K67VZ75HPJVIEUVNIXF47ZG2FB2RMQQAUHKENIFEJ';
132
+ mockStrKey.isValidEd25519PublicKey.mockReturnValue(false);
133
+ mockStrKey.isValidContract.mockReturnValue(true);
134
+ mockStrKey.isValidMed25519PublicKey.mockReturnValue(false);
135
+
136
+ const result = isValidAddress(contractAddress);
137
+
138
+ expect(result).toBe(true);
139
+ });
140
+
141
+ it('should return true for valid muxed address (M...)', () => {
142
+ const muxedAddress = 'MDQP2KPQGKIHYJGXNUIYOMHARUARCA7DJT5FO2FFOOKY3B2WSQHG4W37AAAAAAAAAAAA';
143
+ mockStrKey.isValidEd25519PublicKey.mockReturnValue(false);
144
+ mockStrKey.isValidContract.mockReturnValue(false);
145
+ mockStrKey.isValidMed25519PublicKey.mockReturnValue(true);
146
+
147
+ const result = isValidAddress(muxedAddress);
148
+
149
+ expect(result).toBe(true);
150
+ });
151
+
152
+ it('should return false for secret seeds (security)', () => {
153
+ const secretSeed = 'SDQP2KPQGKIHYJGXNUIYOMHARUARCA7DJT5FO2FFOOKY3B2WSQHG4W37';
154
+ mockStrKey.isValidEd25519PublicKey.mockReturnValue(false);
155
+ mockStrKey.isValidContract.mockReturnValue(false);
156
+ mockStrKey.isValidMed25519PublicKey.mockReturnValue(false);
157
+
158
+ const result = isValidAddress(secretSeed);
159
+
160
+ expect(result).toBe(false);
161
+ // Secret validation should NOT be called for security
162
+ expect(mockStrKey.isValidEd25519SecretSeed).not.toHaveBeenCalled();
163
+ });
164
+ });
165
+
166
+ describe('with specific address type', () => {
167
+ it('should validate only account type when specified', () => {
168
+ const accountAddress = 'GDQP2KPQGKIHYJGXNUIYOMHARUARCA7DJT5FO2FFOOKY3B2WSQHG4W37';
169
+ mockStrKey.isValidEd25519PublicKey.mockReturnValue(true);
170
+
171
+ const result = isValidAddress(accountAddress, 'account');
172
+
173
+ expect(result).toBe(true);
174
+ expect(mockStrKey.isValidEd25519PublicKey).toHaveBeenCalledWith(accountAddress);
175
+ // Other validation methods should not be called
176
+ expect(mockStrKey.isValidContract).not.toHaveBeenCalled();
177
+ });
178
+
179
+ it('should validate only contract type when specified', () => {
180
+ const contractAddress = 'CDLZFC3SYJYDZT7K67VZ75HPJVIEUVNIXF47ZG2FB2RMQQAUHKENIFEJ';
181
+ mockStrKey.isValidContract.mockReturnValue(true);
182
+
183
+ const result = isValidAddress(contractAddress, 'contract');
184
+
185
+ expect(result).toBe(true);
186
+ expect(mockStrKey.isValidContract).toHaveBeenCalledWith(contractAddress);
187
+ // Other validation methods should not be called
188
+ expect(mockStrKey.isValidEd25519PublicKey).not.toHaveBeenCalled();
189
+ });
190
+
191
+ it('should validate only muxed type when specified', () => {
192
+ const muxedAddress = 'MDQP2KPQGKIHYJGXNUIYOMHARUARCA7DJT5FO2FFOOKY3B2WSQHG4W37AAAAAAAAAAAA';
193
+ mockStrKey.isValidMed25519PublicKey.mockReturnValue(true);
194
+
195
+ const result = isValidAddress(muxedAddress, 'muxed');
196
+
197
+ expect(result).toBe(true);
198
+ expect(mockStrKey.isValidMed25519PublicKey).toHaveBeenCalledWith(muxedAddress);
199
+ });
200
+
201
+ it('should validate secret seeds when specifically requested', () => {
202
+ const secretSeed = 'SDQP2KPQGKIHYJGXNUIYOMHARUARCA7DJT5FO2FFOOKY3B2WSQHG4W37';
203
+ mockStrKey.isValidEd25519SecretSeed.mockReturnValue(true);
204
+
205
+ const result = isValidAddress(secretSeed, 'secret');
206
+
207
+ expect(result).toBe(true);
208
+ expect(mockStrKey.isValidEd25519SecretSeed).toHaveBeenCalledWith(secretSeed);
209
+ });
210
+
211
+ it('should validate pre-auth-tx when specified', () => {
212
+ const preAuthTx = 'TDQP2KPQGKIHYJGXNUIYOMHARUARCA7DJT5FO2FFOOKY3B2WSQHG4W37';
213
+ mockStrKey.decodePreAuthTx.mockReturnValue(new Uint8Array([1, 2, 3, 4]));
214
+
215
+ const result = isValidAddress(preAuthTx, 'pre-auth-tx');
216
+
217
+ expect(result).toBe(true);
218
+ expect(mockStrKey.decodePreAuthTx).toHaveBeenCalledWith(preAuthTx);
219
+ });
220
+
221
+ it('should validate hash-x when specified', () => {
222
+ const hashX = 'XDQP2KPQGKIHYJGXNUIYOMHARUARCA7DJT5FO2FFOOKY3B2WSQHG4W37';
223
+ mockStrKey.decodeSha256Hash.mockReturnValue(new Uint8Array([1, 2, 3, 4]));
224
+
225
+ const result = isValidAddress(hashX, 'hash-x');
226
+
227
+ expect(result).toBe(true);
228
+ expect(mockStrKey.decodeSha256Hash).toHaveBeenCalledWith(hashX);
229
+ });
230
+
231
+ it('should return false for unknown address type', () => {
232
+ const address = 'GDQP2KPQGKIHYJGXNUIYOMHARUARCA7DJT5FO2FFOOKY3B2WSQHG4W37';
233
+
234
+ const result = isValidAddress(address, 'unknown' as StellarAddressType);
235
+
236
+ expect(result).toBe(false);
237
+ });
238
+ });
239
+
240
+ describe('edge cases', () => {
241
+ it('should return false for empty string', () => {
242
+ const result = isValidAddress('');
243
+ expect(result).toBe(false);
244
+ });
245
+
246
+ it('should return false for null/undefined input', () => {
247
+ // @ts-expect-error Testing invalid input
248
+ const resultNull = isValidAddress(null);
249
+ // @ts-expect-error Testing invalid input
250
+ const resultUndefined = isValidAddress(undefined);
251
+
252
+ expect(resultNull).toBe(false);
253
+ expect(resultUndefined).toBe(false);
254
+ });
255
+
256
+ it('should handle exceptions gracefully', () => {
257
+ const address = 'MALFORMED_ADDRESS';
258
+ mockStrKey.isValidEd25519PublicKey.mockImplementation(() => {
259
+ throw new Error('Invalid format');
260
+ });
261
+
262
+ const result = isValidAddress(address);
263
+
264
+ expect(result).toBe(false);
265
+ });
266
+ });
267
+ });
@@ -0,0 +1,136 @@
1
+ import { StrKey } from '@stellar/stellar-sdk';
2
+
3
+ /**
4
+ * Stellar address types supported by the validation functions
5
+ */
6
+ export type StellarAddressType =
7
+ | 'account' // G... - Ed25519 public keys (standard account addresses)
8
+ | 'contract' // C... - Contract addresses
9
+ | 'muxed' // M... - Muxed account addresses (for exchanges/sub-accounts)
10
+ | 'secret' // S... - Secret seeds (private keys)
11
+ | 'signed-payload' // P... - Signed payload addresses
12
+ | 'pre-auth-tx' // T... - Pre-authorized transaction hashes
13
+ | 'hash-x'; // X... - Hash-x condition addresses
14
+
15
+ /**
16
+ * Validate a standard Stellar account address (Ed25519 public key starting with 'G')
17
+ * @param address The address to validate
18
+ * @returns Whether the address is a valid account address
19
+ */
20
+ export function isValidAccountAddress(address: string): boolean {
21
+ try {
22
+ return StrKey.isValidEd25519PublicKey(address);
23
+ } catch {
24
+ return false;
25
+ }
26
+ }
27
+
28
+ /**
29
+ * Validate a Stellar contract address (starting with 'C')
30
+ * @param address The address to validate
31
+ * @returns Whether the address is a valid contract address
32
+ */
33
+ export function isValidContractAddress(address: string): boolean {
34
+ try {
35
+ return StrKey.isValidContract(address);
36
+ } catch {
37
+ return false;
38
+ }
39
+ }
40
+
41
+ /**
42
+ * Validate a Stellar muxed account address (starting with 'M')
43
+ * @param address The address to validate
44
+ * @returns Whether the address is a valid muxed account address
45
+ */
46
+ export function isValidMuxedAddress(address: string): boolean {
47
+ try {
48
+ return StrKey.isValidMed25519PublicKey(address);
49
+ } catch {
50
+ return false;
51
+ }
52
+ }
53
+
54
+ /**
55
+ * Validate a Stellar secret seed (private key starting with 'S')
56
+ * @param seed The secret seed to validate
57
+ * @returns Whether the seed is a valid secret seed
58
+ */
59
+ export function isValidSecretSeed(seed: string): boolean {
60
+ try {
61
+ return StrKey.isValidEd25519SecretSeed(seed);
62
+ } catch {
63
+ return false;
64
+ }
65
+ }
66
+
67
+ /**
68
+ * Validate a signed payload address (starting with 'P')
69
+ * @param address The address to validate
70
+ * @returns Whether the address is a valid signed payload address
71
+ */
72
+ export function isValidSignedPayloadAddress(address: string): boolean {
73
+ try {
74
+ return StrKey.isValidSignedPayload(address);
75
+ } catch {
76
+ return false;
77
+ }
78
+ }
79
+
80
+ /**
81
+ * Main validation function that supports all Stellar address types
82
+ * @param address The address to validate
83
+ * @param addressType Optional specific address type to validate
84
+ * @returns Whether the address is valid for the specified or any supported type
85
+ */
86
+ export function isValidAddress(address: string, addressType?: StellarAddressType): boolean {
87
+ if (!address || typeof address !== 'string') {
88
+ return false;
89
+ }
90
+
91
+ // If specific type is requested, validate only that type
92
+ if (addressType) {
93
+ switch (addressType) {
94
+ case 'account':
95
+ return isValidAccountAddress(address);
96
+ case 'contract':
97
+ return isValidContractAddress(address);
98
+ case 'muxed':
99
+ return isValidMuxedAddress(address);
100
+ case 'secret':
101
+ return isValidSecretSeed(address);
102
+ case 'signed-payload':
103
+ return isValidSignedPayloadAddress(address);
104
+ case 'pre-auth-tx':
105
+ try {
106
+ // Pre-auth transactions start with 'T' - validate by trying to decode
107
+ StrKey.decodePreAuthTx(address);
108
+ return true;
109
+ } catch {
110
+ return false;
111
+ }
112
+ case 'hash-x':
113
+ try {
114
+ // Hash-x conditions start with 'X' - validate by trying to decode
115
+ StrKey.decodeSha256Hash(address);
116
+ return true;
117
+ } catch {
118
+ return false;
119
+ }
120
+ default:
121
+ return false;
122
+ }
123
+ }
124
+
125
+ // If no specific type requested, validate against common address types
126
+ // (excluding secrets and special-purpose addresses for security)
127
+ try {
128
+ return (
129
+ StrKey.isValidEd25519PublicKey(address) || // G... - accounts (most common)
130
+ StrKey.isValidContract(address) || // C... - contracts
131
+ StrKey.isValidMed25519PublicKey(address) // M... - muxed accounts
132
+ );
133
+ } catch {
134
+ return false;
135
+ }
136
+ }
@@ -0,0 +1,33 @@
1
+ import { EoaExecutionConfig } from '@openzeppelin/ui-types';
2
+ import { logger } from '@openzeppelin/ui-utils';
3
+
4
+ import { StellarWalletConnectionStatus } from '../wallet/types';
5
+ import { isValidAddress } from './address';
6
+
7
+ const SYSTEM_LOG_TAG = 'StellarEoaValidator';
8
+
9
+ export async function validateEoaConfig(
10
+ config: EoaExecutionConfig,
11
+ walletStatus: StellarWalletConnectionStatus
12
+ ): Promise<true | string> {
13
+ if (!config.allowAny) {
14
+ if (!config.specificAddress) {
15
+ return "EOA execution selected, but no specific address was provided when 'allowAny' is false.";
16
+ }
17
+ if (!isValidAddress(config.specificAddress)) {
18
+ return `Invalid specific Stellar address format: ${config.specificAddress}`;
19
+ }
20
+ if (walletStatus.isConnected && walletStatus.address) {
21
+ if (walletStatus.address !== config.specificAddress) {
22
+ return `Connected wallet address (${walletStatus.address}) does not match the required specific Stellar address (${config.specificAddress}). Please connect the correct wallet.`;
23
+ }
24
+ } else if (walletStatus.isConnected && !walletStatus.address) {
25
+ logger.warn(
26
+ SYSTEM_LOG_TAG,
27
+ 'Wallet is connected but address is unavailable for Stellar EOA validation.'
28
+ );
29
+ return 'Connected wallet address is not available for validation against specific Stellar address.';
30
+ }
31
+ }
32
+ return true;
33
+ }
@@ -0,0 +1,3 @@
1
+ export * from './address';
2
+ export * from './eoa';
3
+ export * from './relayer';
@@ -0,0 +1,13 @@
1
+ import { RelayerExecutionConfig } from '@openzeppelin/ui-types';
2
+
3
+ export async function validateRelayerConfig(
4
+ config: RelayerExecutionConfig
5
+ ): Promise<true | string> {
6
+ if (!config.serviceUrl) {
7
+ return 'Relayer execution selected, but no service URL was provided.';
8
+ }
9
+ if (!config.relayer?.relayerId) {
10
+ return 'Relayer execution selected, but no relayer was chosen from the list.';
11
+ }
12
+ return true;
13
+ }