@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,156 @@
1
+ import { rpc as StellarRpc } from '@stellar/stellar-sdk';
2
+
3
+ import type {
4
+ ExecutionConfig,
5
+ StellarNetworkConfig,
6
+ TransactionStatusUpdate,
7
+ TxStatus,
8
+ } from '@openzeppelin/ui-types';
9
+ import { logger, userRpcConfigService } from '@openzeppelin/ui-utils';
10
+
11
+ import { EoaExecutionStrategy } from './eoa';
12
+ import { ExecutionStrategy } from './execution-strategy';
13
+ import type { StellarTransactionData } from './formatter';
14
+ import { RelayerExecutionStrategy } from './relayer';
15
+
16
+ const SYSTEM_LOG_TAG = 'adapter-stellar';
17
+
18
+ // --- Helper Functions ---
19
+
20
+ /**
21
+ * Get Soroban RPC Server instance with proper configuration
22
+ */
23
+ function getSorobanRpcServer(networkConfig: StellarNetworkConfig): StellarRpc.Server {
24
+ const customRpcConfig = userRpcConfigService.getUserRpcConfig(networkConfig.id);
25
+ const rpcUrl = customRpcConfig?.url || networkConfig.sorobanRpcUrl;
26
+
27
+ if (!rpcUrl) {
28
+ throw new Error(`No Soroban RPC URL available for network ${networkConfig.name}`);
29
+ }
30
+
31
+ // Allow HTTP for localhost development
32
+ const allowHttp = new URL(rpcUrl).hostname === 'localhost';
33
+
34
+ return new StellarRpc.Server(rpcUrl, {
35
+ allowHttp,
36
+ });
37
+ }
38
+
39
+ /**
40
+ * Sign and broadcast a Stellar transaction using the strategy pattern.
41
+ * This follows the same architecture as the EVM adapter.
42
+ *
43
+ * @param transactionData - The formatted transaction data from formatStellarTransactionData
44
+ * @param executionConfig - Execution configuration specifying method (eoa, relayer, etc.)
45
+ * @param networkConfig - Stellar network configuration
46
+ * @param onStatusChange - Callback for status updates
47
+ * @param runtimeApiKey - Optional session-only API key for methods like Relayer
48
+ * @returns Promise resolving to the transaction hash
49
+ */
50
+ export async function signAndBroadcastStellarTransaction(
51
+ transactionData: unknown,
52
+ executionConfig: ExecutionConfig,
53
+ networkConfig: StellarNetworkConfig,
54
+ onStatusChange?: (status: TxStatus, details: TransactionStatusUpdate) => void,
55
+ runtimeApiKey?: string
56
+ ): Promise<{ txHash: string }> {
57
+ logger.info(
58
+ SYSTEM_LOG_TAG,
59
+ 'Stellar signAndBroadcast called with executionConfig:',
60
+ executionConfig
61
+ );
62
+
63
+ // Validate network config
64
+ if (!networkConfig || networkConfig.ecosystem !== 'stellar') {
65
+ throw new Error('Invalid Stellar network configuration provided.');
66
+ }
67
+
68
+ const txData = transactionData as StellarTransactionData;
69
+ let strategy: ExecutionStrategy;
70
+
71
+ // Select execution strategy based on method
72
+ switch (executionConfig.method) {
73
+ case 'eoa':
74
+ strategy = new EoaExecutionStrategy();
75
+ break;
76
+ case 'relayer':
77
+ strategy = new RelayerExecutionStrategy();
78
+ break;
79
+ case 'multisig':
80
+ // TODO: Implement MultisigExecutionStrategy when Stellar multisig support is added
81
+ throw new Error('Multisig execution method not yet implemented for Stellar.');
82
+ default: {
83
+ const exhaustiveCheck: never = executionConfig;
84
+ logger.error(SYSTEM_LOG_TAG, `Unsupported execution method encountered: ${exhaustiveCheck}`);
85
+ throw new Error(`Unsupported execution method: ${exhaustiveCheck}`);
86
+ }
87
+ }
88
+
89
+ return strategy.execute(
90
+ txData,
91
+ executionConfig,
92
+ networkConfig,
93
+ onStatusChange || (() => {}),
94
+ runtimeApiKey
95
+ );
96
+ }
97
+
98
+ /**
99
+ * Waits for a transaction to be confirmed on the blockchain.
100
+ *
101
+ * @param txHash - The hash of the transaction to wait for.
102
+ * @param networkConfig - The network configuration.
103
+ * @returns A promise resolving to the final status and receipt/error.
104
+ */
105
+ export async function waitForStellarTransactionConfirmation(
106
+ txHash: string,
107
+ networkConfig: StellarNetworkConfig
108
+ ): Promise<{
109
+ status: 'success' | 'error';
110
+ receipt?: unknown;
111
+ error?: Error;
112
+ }> {
113
+ try {
114
+ const rpcServer = getSorobanRpcServer(networkConfig);
115
+ const MAX_ATTEMPTS = 20; // More attempts for confirmation
116
+ let attempts = 0;
117
+
118
+ while (attempts++ < MAX_ATTEMPTS) {
119
+ try {
120
+ const txResponse = await rpcServer.getTransaction(txHash);
121
+
122
+ switch (txResponse.status) {
123
+ case 'SUCCESS':
124
+ return {
125
+ status: 'success',
126
+ receipt: txResponse,
127
+ };
128
+ case 'FAILED':
129
+ return {
130
+ status: 'error',
131
+ error: new Error(`Transaction failed: ${JSON.stringify(txResponse.resultXdr)}`),
132
+ };
133
+ case 'NOT_FOUND':
134
+ // Continue waiting
135
+ break;
136
+ default:
137
+ // Continue waiting for other statuses
138
+ }
139
+
140
+ await new Promise((resolve) => setTimeout(resolve, 1000));
141
+ } catch (error) {
142
+ logger.error('waitForStellarTransactionConfirmation', `Attempt ${attempts} failed:`, error);
143
+ }
144
+ }
145
+
146
+ return {
147
+ status: 'error',
148
+ error: new Error('Transaction confirmation timeout'),
149
+ };
150
+ } catch (error) {
151
+ return {
152
+ status: 'error',
153
+ error: error as Error,
154
+ };
155
+ }
156
+ }
@@ -0,0 +1,4 @@
1
+ // Barrel file
2
+
3
+ export * from './input-parser';
4
+ export * from './output-formatter';
@@ -0,0 +1,9 @@
1
+ // Re-export all types and functions for backward compatibility
2
+ export type {
3
+ SorobanArgumentValue,
4
+ SorobanEnumValue,
5
+ SorobanMapEntry,
6
+ SorobanComplexValue,
7
+ } from './parsers';
8
+
9
+ export { parseStellarInput, getScValsFromArgs, valueToScVal } from './parsers';
@@ -0,0 +1,133 @@
1
+ import { scValToNative, xdr } from '@stellar/stellar-sdk';
2
+
3
+ import type { ContractFunction } from '@openzeppelin/ui-types';
4
+ import { bytesToHex, logger } from '@openzeppelin/ui-utils';
5
+
6
+ import { isSerializableObject, stringifyWithBigInt } from '../utils';
7
+
8
+ /**
9
+ * Formats the result of a Stellar view function call into a user-friendly string.
10
+ *
11
+ * @param result The result value (can be ScVal, native JS value, or other types).
12
+ * @param functionDetails The contract function details.
13
+ * @returns A string representation suitable for display.
14
+ */
15
+ export function formatStellarFunctionResult(
16
+ result: unknown,
17
+ functionDetails: ContractFunction
18
+ ): string {
19
+ if (!functionDetails.outputs || !Array.isArray(functionDetails.outputs)) {
20
+ logger.warn(
21
+ 'formatStellarFunctionResult',
22
+ `Output definition missing or invalid for function ${functionDetails.name}.`
23
+ );
24
+ return '[Error: Output definition missing]';
25
+ }
26
+
27
+ try {
28
+ let valueToFormat: unknown;
29
+
30
+ // Handle null/undefined values
31
+ if (result === null || result === undefined) {
32
+ return '(null)';
33
+ }
34
+
35
+ // Check if result is an ScVal and convert to native JS value
36
+ if (isScVal(result)) {
37
+ try {
38
+ // Special handling for void ScVal
39
+ const scVal = result as xdr.ScVal;
40
+ if (scVal.switch().name === 'scvVoid') {
41
+ return '(void)';
42
+ }
43
+ valueToFormat = scValToNative(scVal);
44
+
45
+ // Convert Buffer to Uint8Array for cross-platform compatibility
46
+ // scValToNative may return Buffer objects in some environments
47
+ if (
48
+ valueToFormat &&
49
+ typeof valueToFormat === 'object' &&
50
+ 'constructor' in valueToFormat &&
51
+ valueToFormat.constructor?.name === 'Buffer'
52
+ ) {
53
+ valueToFormat = new Uint8Array(valueToFormat as ArrayLike<number>);
54
+ }
55
+ } catch (error) {
56
+ logger.error('formatStellarFunctionResult', 'Failed to convert ScVal to native', {
57
+ functionName: functionDetails.name,
58
+ error,
59
+ });
60
+ return '[Error: Failed to decode ScVal]';
61
+ }
62
+ } else {
63
+ valueToFormat = result;
64
+ }
65
+
66
+ // Format based on type
67
+ if (typeof valueToFormat === 'bigint') {
68
+ return valueToFormat.toString();
69
+ } else if (typeof valueToFormat === 'string') {
70
+ return valueToFormat;
71
+ } else if (typeof valueToFormat === 'number') {
72
+ return valueToFormat.toString();
73
+ } else if (typeof valueToFormat === 'boolean') {
74
+ return String(valueToFormat);
75
+ } else if (valueToFormat instanceof Uint8Array) {
76
+ // Handle byte arrays - convert to hex string
77
+ return bytesToHex(valueToFormat, true);
78
+ } else if (Array.isArray(valueToFormat)) {
79
+ // Handle arrays/vectors
80
+ if (valueToFormat.length === 0) {
81
+ return '[]';
82
+ }
83
+ // Use compact formatting for simple arrays, pretty formatting for complex ones
84
+ if (
85
+ valueToFormat.every(
86
+ (item) =>
87
+ typeof item === 'string' ||
88
+ typeof item === 'number' ||
89
+ typeof item === 'boolean' ||
90
+ typeof item === 'bigint'
91
+ )
92
+ ) {
93
+ return stringifyWithBigInt(valueToFormat); // No spacing for simple arrays
94
+ }
95
+ return stringifyWithBigInt(valueToFormat, 2);
96
+ } else if (isSerializableObject(valueToFormat)) {
97
+ // Handle objects/maps/structs
98
+ if (Object.keys(valueToFormat as object).length === 0) {
99
+ return '{}';
100
+ }
101
+ return stringifyWithBigInt(valueToFormat, 2);
102
+ } else if (valueToFormat === null || valueToFormat === undefined) {
103
+ return '(null)';
104
+ } else {
105
+ // Handle any other type by stringifying
106
+ return stringifyWithBigInt(valueToFormat, 2);
107
+ }
108
+ } catch (error) {
109
+ const errorMessage = `Error formatting result for ${functionDetails.name}: ${(error as Error).message}`;
110
+ logger.error('formatStellarFunctionResult', errorMessage, {
111
+ functionName: functionDetails.name,
112
+ result,
113
+ error,
114
+ });
115
+ return `[${errorMessage}]`;
116
+ }
117
+ }
118
+
119
+ /**
120
+ * Type guard to check if a value is an ScVal
121
+ */
122
+ function isScVal(value: unknown): boolean {
123
+ if (!value || typeof value !== 'object') {
124
+ return false;
125
+ }
126
+
127
+ try {
128
+ // Use instanceof check for robust type detection
129
+ return typeof xdr !== 'undefined' && xdr.ScVal && value instanceof xdr.ScVal;
130
+ } catch {
131
+ return false;
132
+ }
133
+ }
@@ -0,0 +1,157 @@
1
+ import { nativeToScVal, xdr } from '@stellar/stellar-sdk';
2
+
3
+ import { detectBytesEncoding, stringToBytes } from '@openzeppelin/ui-utils';
4
+
5
+ import { convertStellarTypeToScValType } from '../../utils/formatting';
6
+ import {
7
+ convertEnumToScVal,
8
+ convertObjectToMap,
9
+ convertObjectToScVal,
10
+ convertTupleToScVal,
11
+ getScValFromArg,
12
+ getScValFromPrimitive,
13
+ isComplexObjectArray,
14
+ isEnumArgumentSet,
15
+ isMapArray,
16
+ isObjectWithTypedValues,
17
+ isPrimitiveArgumentSet,
18
+ isPrimitiveArray,
19
+ isPrimitiveValue,
20
+ isTupleArray,
21
+ } from '../../utils/input-parsing';
22
+ import type {
23
+ SorobanArgumentValue,
24
+ SorobanComplexValue,
25
+ SorobanEnumValue,
26
+ SorobanMapEntry,
27
+ } from './types';
28
+
29
+ /**
30
+ * Advanced ScVal generation from complex Soroban arguments.
31
+ * Handles Maps, Enums, Tuples, and nested structures.
32
+ *
33
+ * @param args - Complex argument structure from advanced UI forms
34
+ * @param scVals - Accumulator for generated ScVals (used for recursion)
35
+ * @returns Array of ScVals ready for contract invocation
36
+ */
37
+ export function getScValsFromArgs(
38
+ args: Record<string, SorobanComplexValue> | SorobanComplexValue[],
39
+ scVals: xdr.ScVal[] = []
40
+ ): xdr.ScVal[] {
41
+ // Handle array input (multiple arguments)
42
+ if (Array.isArray(args)) {
43
+ return args.map((arg) => {
44
+ if (typeof arg === 'object' && 'value' in arg && 'type' in arg) {
45
+ return getScValFromPrimitive(arg as SorobanArgumentValue);
46
+ }
47
+ return getScValFromArg(arg, []);
48
+ });
49
+ }
50
+
51
+ // Handle primitive case - all values have type and value
52
+ if (isPrimitiveArgumentSet(args)) {
53
+ const primitiveScVals = Object.values(args).map((v) => {
54
+ return getScValFromPrimitive(v as SorobanArgumentValue);
55
+ });
56
+
57
+ return primitiveScVals;
58
+ }
59
+
60
+ // Handle enum case - values have tag or enum properties
61
+ if (isEnumArgumentSet(args)) {
62
+ const enumScVals = Object.values(args).map((v) => {
63
+ return convertEnumToScVal(v as SorobanEnumValue, scVals);
64
+ });
65
+
66
+ return enumScVals;
67
+ }
68
+
69
+ // Handle complex cases (maps, objects, arrays)
70
+ for (const argKey in args) {
71
+ const argValue = args[argKey];
72
+
73
+ if (Array.isArray(argValue)) {
74
+ // Map case - array of key-value pair objects
75
+ if (isMapArray(argValue)) {
76
+ const { mapVal, mapType } = convertObjectToMap(argValue as SorobanMapEntry[]);
77
+ const mapScVal = nativeToScVal(mapVal, { type: mapType });
78
+ scVals.push(mapScVal);
79
+ continue;
80
+ }
81
+
82
+ // Vector case #1: array of complex objects or tuples
83
+ if (isComplexObjectArray(argValue)) {
84
+ const arrayScVals = argValue.map((v) => {
85
+ // Use proper type guards to ensure safe casting
86
+ if (typeof v === 'object' && v !== null && ('tag' in v || 'enum' in v)) {
87
+ return convertEnumToScVal(v as SorobanEnumValue, scVals);
88
+ }
89
+ if (
90
+ typeof v === 'object' &&
91
+ v !== null &&
92
+ !Array.isArray(v) &&
93
+ !('0' in v) &&
94
+ !('1' in v)
95
+ ) {
96
+ return convertObjectToScVal(v as Record<string, SorobanArgumentValue>);
97
+ }
98
+ // Fallback for other types
99
+ return nativeToScVal(v);
100
+ });
101
+
102
+ const tupleScValsVec = xdr.ScVal.scvVec(arrayScVals);
103
+ scVals.push(tupleScValsVec);
104
+ continue;
105
+ }
106
+
107
+ // Vector case #2: array of primitives (homogeneous type)
108
+ if (isPrimitiveArray(argValue)) {
109
+ // TypeScript now knows argValue is SorobanArgumentValue[] thanks to type predicate
110
+ const arrayScVals = argValue.reduce((acc: unknown[], v) => {
111
+ const primitive = v as unknown as SorobanArgumentValue;
112
+ if (primitive.type === 'bool') {
113
+ acc.push(primitive.value === 'true' ? true : false);
114
+ } else if (primitive.type === 'bytes') {
115
+ const encoding = detectBytesEncoding(primitive.value as string);
116
+ acc.push(stringToBytes(primitive.value as string, encoding));
117
+ } else {
118
+ acc.push(primitive.value);
119
+ }
120
+ return acc;
121
+ }, []);
122
+
123
+ const firstItem = argValue[0];
124
+ const scValType = convertStellarTypeToScValType(firstItem.type);
125
+ const typeHint = Array.isArray(scValType) ? scValType[0] : scValType;
126
+ const scVal = nativeToScVal(arrayScVals, {
127
+ type: typeHint,
128
+ });
129
+
130
+ scVals.push(scVal);
131
+ continue;
132
+ }
133
+
134
+ // Tuple case - mixed types in array
135
+ if (isTupleArray(argValue)) {
136
+ // TypeScript now knows argValue is SorobanArgumentValue[] thanks to type predicate
137
+ const tupleScValsVec = convertTupleToScVal(argValue);
138
+ scVals.push(tupleScValsVec);
139
+ continue;
140
+ }
141
+ }
142
+
143
+ // Object case - structured data
144
+ if (isObjectWithTypedValues(argValue)) {
145
+ const convertedObj = convertObjectToScVal(argValue as Record<string, SorobanArgumentValue>);
146
+ scVals.push(convertedObj);
147
+ continue;
148
+ }
149
+
150
+ // Single primitive value
151
+ if (isPrimitiveValue(argValue)) {
152
+ scVals.push(getScValFromPrimitive(argValue as SorobanArgumentValue));
153
+ }
154
+ }
155
+
156
+ return scVals;
157
+ }
@@ -0,0 +1,171 @@
1
+ import { isMapEntryArray } from '@openzeppelin/ui-types';
2
+ import { logger } from '@openzeppelin/ui-utils';
3
+
4
+ const SYSTEM_LOG_TAG = 'GenericParser';
5
+
6
+ /**
7
+ * Parses a generic type string and extracts the base type and parameters
8
+ * @param typeString - Type like "Vec<U32>", "Map<U32,Address>", "Option<Vec<U32>>"
9
+ * @returns Object with baseType and parameters, or null if not generic
10
+ */
11
+ export function parseGenericType(typeString: string): {
12
+ baseType: string;
13
+ parameters: string[];
14
+ } | null {
15
+ const match = typeString.match(/^(\w+)<(.*)>$/);
16
+ if (!match) return null;
17
+
18
+ const baseType = match[1];
19
+ const paramString = match[2];
20
+
21
+ // Handle nested generics by counting angle brackets
22
+ const parameters: string[] = [];
23
+ let current = '';
24
+ let depth = 0;
25
+ let i = 0;
26
+
27
+ while (i < paramString.length) {
28
+ const char = paramString[i];
29
+
30
+ if (char === '<') {
31
+ depth++;
32
+ current += char;
33
+ } else if (char === '>') {
34
+ depth--;
35
+ current += char;
36
+ } else if (char === ',' && depth === 0) {
37
+ parameters.push(current.trim());
38
+ current = '';
39
+ } else {
40
+ current += char;
41
+ }
42
+ i++;
43
+ }
44
+
45
+ if (current.trim()) {
46
+ parameters.push(current.trim());
47
+ }
48
+
49
+ return { baseType, parameters };
50
+ }
51
+
52
+ /**
53
+ * Parses generic types like Vec<T>, Map<K,V>, and Option<T>.
54
+ *
55
+ * @param value - The input value from the form
56
+ * @param parameterType - The generic parameter type (e.g., 'Vec<U32>', 'Map<Symbol,Bytes>')
57
+ * @param parseInnerValue - Function to recursively parse inner values
58
+ * @returns The parsed value suitable for further processing
59
+ */
60
+ export function parseGeneric(
61
+ value: unknown,
62
+ parameterType: string,
63
+ parseInnerValue: (val: unknown, type: string) => unknown
64
+ ): unknown {
65
+ try {
66
+ const genericInfo = parseGenericType(parameterType);
67
+ if (!genericInfo) {
68
+ return null; // Not a generic type
69
+ }
70
+
71
+ const { baseType, parameters } = genericInfo;
72
+
73
+ switch (baseType) {
74
+ case 'Vec': {
75
+ // Handle Vec<T> types
76
+ if (!Array.isArray(value)) {
77
+ throw new Error(`Array expected for Vec type ${parameterType}, got ${typeof value}`);
78
+ }
79
+
80
+ const innerType = parameters[0];
81
+ if (!innerType) {
82
+ throw new Error(`Could not parse Vec inner type: ${parameterType}`);
83
+ }
84
+
85
+ return value.map((item) => parseInnerValue(item, innerType));
86
+ }
87
+
88
+ case 'Map': {
89
+ // Handle Map<K,V> types
90
+ if (!isMapEntryArray(value)) {
91
+ throw new Error(`Array of MapEntry objects expected for Map type, got ${typeof value}`);
92
+ }
93
+
94
+ if (parameters.length < 2) {
95
+ throw new Error(`Could not parse Map types: ${parameterType}`);
96
+ }
97
+
98
+ const mapKeyType = parameters[0];
99
+ const mapValueType = parameters[1];
100
+
101
+ // Convert MapEntry array to Stellar SDK expected format
102
+ return value.map((entry) => ({
103
+ 0: {
104
+ value: entry.key,
105
+ type: mapKeyType,
106
+ },
107
+ 1: {
108
+ value: entry.value,
109
+ type: mapValueType,
110
+ },
111
+ }));
112
+ }
113
+
114
+ case 'Option': {
115
+ // Handle Option<T> types - empty string should be treated as null
116
+ if (value === null || value === undefined || value === '') {
117
+ return null;
118
+ }
119
+
120
+ const innerType = parameters[0];
121
+ if (!innerType) {
122
+ throw new Error(`Could not parse Option inner type: ${parameterType}`);
123
+ }
124
+
125
+ return parseInnerValue(value, innerType);
126
+ }
127
+
128
+ case 'Result': {
129
+ // Handle Result<T,E> types (basic support)
130
+ if (parameters.length < 2) {
131
+ throw new Error(`Could not parse Result types: ${parameterType}`);
132
+ }
133
+
134
+ // Result types typically come as {ok: value} or {err: error}
135
+ if (typeof value === 'object' && value !== null) {
136
+ const resultObj = value as Record<string, unknown>;
137
+ if ('ok' in resultObj) {
138
+ return {
139
+ ok: parseInnerValue(resultObj.ok, parameters[0]),
140
+ };
141
+ } else if ('err' in resultObj) {
142
+ return {
143
+ err: parseInnerValue(resultObj.err, parameters[1]),
144
+ };
145
+ }
146
+ }
147
+
148
+ return value; // Pass through if not in expected format
149
+ }
150
+
151
+ default:
152
+ // Unknown generic type
153
+ logger.warn(SYSTEM_LOG_TAG, `Unknown generic type: ${baseType}`);
154
+ return null;
155
+ }
156
+ } catch (error) {
157
+ logger.error(SYSTEM_LOG_TAG, `Failed to parse generic type ${parameterType}:`, error);
158
+ throw error;
159
+ }
160
+ }
161
+
162
+ /**
163
+ * Checks if the given parameter type is a generic type that can be handled by this parser.
164
+ *
165
+ * @param parameterType - The Stellar parameter type
166
+ * @returns True if this is a generic type
167
+ */
168
+ export function isGenericType(parameterType: string): boolean {
169
+ const genericInfo = parseGenericType(parameterType);
170
+ return genericInfo !== null && ['Vec', 'Map', 'Option', 'Result'].includes(genericInfo.baseType);
171
+ }