@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,86 @@
1
+ import { isEnumValue } from '@openzeppelin/ui-types';
2
+ import { isPlainObject, logger } from '@openzeppelin/ui-utils';
3
+
4
+ import { isLikelyEnumType } from '../../utils/type-detection';
5
+ import { isGenericType, parseGeneric } from './generic-parser';
6
+ import { isPrimitiveType, parsePrimitive } from './primitive-parser';
7
+
8
+ // Re-export types for backward compatibility
9
+ export type {
10
+ SorobanArgumentValue,
11
+ SorobanEnumValue,
12
+ SorobanMapEntry,
13
+ SorobanComplexValue,
14
+ } from './types';
15
+
16
+ // Re-export specific functions for backward compatibility
17
+ export { getScValsFromArgs } from './complex-parser';
18
+ export { valueToScVal } from './scval-converter';
19
+
20
+ const SYSTEM_LOG_TAG = 'StellarInputParser';
21
+
22
+ /**
23
+ * Parses form input values for Stellar/Soroban contracts.
24
+ * Handles both simple UI form inputs and complex structures.
25
+ *
26
+ * @param value - The input value from the form
27
+ * @param parameterType - The Stellar parameter type (e.g., 'Address', 'U128', 'Vec<U32>')
28
+ * @returns The parsed value suitable for nativeToScVal conversion
29
+ */
30
+ export function parseStellarInput(value: unknown, parameterType: string): unknown {
31
+ try {
32
+ // Handle null/undefined values
33
+ if (value === null || value === undefined) {
34
+ return null;
35
+ }
36
+
37
+ // Try primitive types first (most common)
38
+ if (isPrimitiveType(parameterType)) {
39
+ const result = parsePrimitive(value, parameterType);
40
+ if (result !== null) {
41
+ return result;
42
+ }
43
+ }
44
+
45
+ // Try generic types (Vec, Map, Option, Result)
46
+ if (isGenericType(parameterType)) {
47
+ const result = parseGeneric(value, parameterType, parseStellarInput);
48
+ // For generic types, we always return the result (including null for Option<T> with empty/null values)
49
+ return result;
50
+ }
51
+
52
+ // Handle remaining custom types and special cases
53
+ // Check if this is an enum type and transform from chain-agnostic to Soroban format
54
+ if (isEnumValue(value) && isLikelyEnumType(parameterType)) {
55
+ // Chain-agnostic enum format: { tag: "VariantName", values?: [...] }
56
+ // Return the chain-agnostic enum value as-is
57
+ // The Stellar-specific transformation will happen in valueToScVal
58
+ return value;
59
+ }
60
+
61
+ // Check if this is a struct type (object with primitive fields)
62
+ if (isPlainObject(value)) {
63
+ // For structs, we need to recursively process each field
64
+ // But since we don't have field type information here, we'll pass it through
65
+ // and handle the conversion in valueToScVal where we have access to contract schema
66
+ return value;
67
+ }
68
+
69
+ // Accept array-shaped values for struct-like types (e.g., tuple structs with numeric keys).
70
+ // RHF treats numeric path segments as arrays, so a struct with fields "0", "1" can arrive as an array.
71
+ // Pass through; valueToScVal/convertStructToScVal will serialize correctly using the schema.
72
+ if (Array.isArray(value)) {
73
+ return value;
74
+ }
75
+
76
+ // For other types, try to return raw value with validation
77
+ if (typeof value === 'string' || typeof value === 'number') {
78
+ return value;
79
+ }
80
+
81
+ throw new Error(`Unsupported parameter type: ${parameterType} with value type ${typeof value}`);
82
+ } catch (error) {
83
+ logger.error(SYSTEM_LOG_TAG, 'Failed to parse Stellar input:', error);
84
+ throw error;
85
+ }
86
+ }
@@ -0,0 +1,123 @@
1
+ import { Address } from '@stellar/stellar-sdk';
2
+
3
+ import { detectBytesEncoding, logger, stringToBytes } from '@openzeppelin/ui-utils';
4
+
5
+ const SYSTEM_LOG_TAG = 'PrimitiveParser';
6
+
7
+ /**
8
+ * Parses primitive Stellar/Soroban types from form inputs.
9
+ * Handles: Bool, Bytes, Address, ScString, ScSymbol, and numeric types (U32, U64, U128, U256, I32, I64, I128, I256).
10
+ *
11
+ * @param value - The input value from the form
12
+ * @param parameterType - The Stellar parameter type
13
+ * @returns The parsed primitive value
14
+ */
15
+ export function parsePrimitive(value: unknown, parameterType: string): unknown {
16
+ try {
17
+ // Handle null/undefined values
18
+ if (value === null || value === undefined) {
19
+ return null;
20
+ }
21
+
22
+ switch (parameterType) {
23
+ // Boolean: convert string "true"/"false" to actual boolean
24
+ case 'Bool':
25
+ if (typeof value === 'boolean') {
26
+ return value;
27
+ }
28
+ if (typeof value === 'string') {
29
+ return value.toLowerCase() === 'true';
30
+ }
31
+ throw new Error(`Boolean parameter expected, got ${typeof value}`);
32
+
33
+ // Bytes: handle encoding detection
34
+ case 'Bytes':
35
+ if (typeof value === 'string') {
36
+ // Remove 0x prefix if present
37
+ const cleanValue = value.startsWith('0x') ? value.slice(2) : value;
38
+ const encoding = detectBytesEncoding(cleanValue);
39
+ return stringToBytes(cleanValue, encoding);
40
+ }
41
+ throw new Error(`Bytes parameter must be a string, got ${typeof value}`);
42
+
43
+ // DataUrl: handle base64 encoded data (similar to Bytes)
44
+ case 'DataUrl':
45
+ if (typeof value === 'string') {
46
+ // DataUrl is typically base64 encoded
47
+ const encoding = detectBytesEncoding(value);
48
+ return stringToBytes(value, encoding);
49
+ }
50
+ throw new Error(`DataUrl parameter must be a string, got ${typeof value}`);
51
+
52
+ // Address: validate format
53
+ case 'Address':
54
+ if (typeof value === 'string') {
55
+ try {
56
+ Address.fromString(value); // Validate format
57
+ return value; // Return raw string - nativeToScVal will handle conversion
58
+ } catch {
59
+ throw new Error(`Invalid Stellar address format: ${value}`);
60
+ }
61
+ }
62
+ throw new Error(`Address parameter must be a string, got ${typeof value}`);
63
+
64
+ // String types: return as-is
65
+ case 'ScString':
66
+ case 'ScSymbol':
67
+ if (typeof value === 'string') {
68
+ return value;
69
+ }
70
+ throw new Error(`String parameter expected, got ${typeof value}`);
71
+
72
+ default:
73
+ // Handle BytesN<size> patterns
74
+ if (/^BytesN<\d+>$/.test(parameterType)) {
75
+ if (typeof value === 'string') {
76
+ const cleanValue = value.startsWith('0x') ? value.slice(2) : value;
77
+ const encoding = detectBytesEncoding(cleanValue);
78
+ return stringToBytes(cleanValue, encoding);
79
+ }
80
+ throw new Error(`Bytes parameter must be a string, got ${typeof value}`);
81
+ }
82
+
83
+ // Handle numeric types: U32, U64, U128, U256, I32, I64, I128, I256
84
+ if (/^[UI](32|64|128|256)$/.test(parameterType)) {
85
+ if (typeof value === 'string') {
86
+ // Validate it's a valid integer (no decimals, letters)
87
+ if (!/^-?\d+$/.test(value.trim())) {
88
+ throw new Error(`Invalid number format for ${parameterType}: ${value}`);
89
+ }
90
+ return value; // Return raw string - let nativeToScVal handle conversion with type hints
91
+ }
92
+ if (typeof value === 'number') {
93
+ return value.toString(); // Convert to string for consistency
94
+ }
95
+ throw new Error(`Numeric parameter expected for ${parameterType}, got ${typeof value}`);
96
+ }
97
+
98
+ return null; // Not a primitive type - let other parsers handle it
99
+ }
100
+ } catch (error) {
101
+ logger.error(SYSTEM_LOG_TAG, `Failed to parse primitive ${parameterType}:`, error);
102
+ throw error;
103
+ }
104
+ }
105
+
106
+ /**
107
+ * Checks if the given parameter type is a primitive type that can be handled by this parser.
108
+ *
109
+ * @param parameterType - The Stellar parameter type
110
+ * @returns True if this is a primitive type
111
+ */
112
+ export function isPrimitiveType(parameterType: string): boolean {
113
+ return (
114
+ parameterType === 'Bool' ||
115
+ parameterType === 'Bytes' ||
116
+ parameterType === 'DataUrl' ||
117
+ parameterType === 'Address' ||
118
+ parameterType === 'ScString' ||
119
+ parameterType === 'ScSymbol' ||
120
+ /^BytesN<\d+>$/.test(parameterType) ||
121
+ /^[UI](32|64|128|256)$/.test(parameterType)
122
+ );
123
+ }
@@ -0,0 +1,405 @@
1
+ import { nativeToScVal, xdr } from '@stellar/stellar-sdk';
2
+
3
+ import { isEnumValue, type FunctionParameter } from '@openzeppelin/ui-types';
4
+
5
+ import { convertStellarTypeToScValType } from '../../utils/formatting';
6
+ import { convertEnumToScVal } from '../../utils/input-parsing';
7
+ import { isPrimitiveParamType } from '../../utils/stellar-types';
8
+ import { isBytesNType, isLikelyEnumType } from '../../utils/type-detection';
9
+ import { compareScValsByXdr } from '../../utils/xdr-ordering';
10
+ import { parseGenericType } from './generic-parser';
11
+ import { parsePrimitive } from './primitive-parser';
12
+ import { convertStructToScVal, isStructType } from './struct-parser';
13
+ import type { SorobanEnumValue } from './types';
14
+
15
+ // FunctionParameter already includes enumMetadata in its type definition (from @openzeppelin/ui-types)
16
+ // No need for a separate type wrapper
17
+ type EnumAwareFunctionParameter = FunctionParameter;
18
+
19
+ /**
20
+ * Converts a value to ScVal with comprehensive generic type support.
21
+ * This should be used in the transaction execution instead of calling nativeToScVal directly.
22
+ *
23
+ * @param value - The parsed value from parseStellarInput
24
+ * @param parameterType - The Stellar parameter type
25
+ * @param paramSchema - Optional parameter schema with struct field definitions
26
+ * @param parseInnerValue - Function to recursively parse inner values (defaults to parseStellarInput)
27
+ * @returns ScVal ready for contract calls
28
+ */
29
+ export function valueToScVal(
30
+ value: unknown,
31
+ parameterType: string,
32
+ paramSchema?: FunctionParameter,
33
+ parseInnerValue?: (val: unknown, type: string) => unknown
34
+ ): xdr.ScVal {
35
+ // Default parseInnerValue to a basic pass-through function if not provided
36
+ // This handles the common case where callers don't need recursive parsing
37
+ const parseValue = parseInnerValue || ((val: unknown) => val);
38
+ const genericInfo = parseGenericType(parameterType);
39
+
40
+ // Helper: detect SorobanArgumentValue wrapper { type, value }
41
+ const isTypedWrapper = (v: unknown): v is { type: string; value: unknown } =>
42
+ !!v &&
43
+ typeof v === 'object' &&
44
+ 'type' in (v as Record<string, unknown>) &&
45
+ 'value' in (v as Record<string, unknown>);
46
+
47
+ const enumMetadata = (paramSchema as EnumAwareFunctionParameter | undefined)?.enumMetadata;
48
+ const possibleEnumValue =
49
+ typeof value === 'string' && (enumMetadata || isLikelyEnumType(parameterType))
50
+ ? { tag: value }
51
+ : value;
52
+
53
+ if (!genericInfo) {
54
+ // Integer-only enums (discriminant enums) → encode as u32 (matches Lab behavior)
55
+ if (enumMetadata && enumMetadata.variants.every((v) => v.type === 'integer')) {
56
+ // Derive numeric discriminant from name, tag, or numeric input
57
+ let numericValue: number | undefined;
58
+ if (typeof value === 'string') {
59
+ const byName = enumMetadata.variants.find((v) => v.name === value);
60
+ numericValue = byName?.value ?? Number(value);
61
+ } else if (typeof value === 'number') {
62
+ numericValue = value;
63
+ } else if (isEnumValue(value)) {
64
+ const byTag = enumMetadata.variants.find((v) => v.name === value.tag);
65
+ numericValue = byTag?.value;
66
+ }
67
+ if (numericValue === undefined || Number.isNaN(numericValue)) {
68
+ const validNames = enumMetadata.variants.map((v) => v.name).join(', ');
69
+ throw new Error(
70
+ `Invalid integer enum value for ${parameterType}: ${String(value)}. Expected one of: ${validNames}`
71
+ );
72
+ }
73
+ return nativeToScVal(numericValue, { type: 'u32' });
74
+ }
75
+ // If a typed wrapper is provided, convert directly using the wrapped type/value
76
+ if (isTypedWrapper(possibleEnumValue)) {
77
+ const wrapped = possibleEnumValue;
78
+ const parsed = parsePrimitive(wrapped.value, wrapped.type);
79
+ const finalVal = parsed !== null ? parsed : wrapped.value;
80
+ const scValType = convertStellarTypeToScValType(wrapped.type);
81
+ const typeHint = Array.isArray(scValType) ? scValType[0] : scValType;
82
+ return nativeToScVal(finalVal, { type: typeHint });
83
+ }
84
+
85
+ // Check if this is an enum object (has 'tag' or 'enum' property)
86
+ if (
87
+ isEnumValue(possibleEnumValue) ||
88
+ (typeof possibleEnumValue === 'object' &&
89
+ possibleEnumValue !== null &&
90
+ 'enum' in possibleEnumValue)
91
+ ) {
92
+ const enumValue = possibleEnumValue as { tag: string; values?: unknown[]; enum?: number };
93
+
94
+ // Handle integer enums
95
+ if ('enum' in enumValue && typeof enumValue.enum === 'number') {
96
+ return nativeToScVal(enumValue.enum, { type: 'u32' });
97
+ }
98
+
99
+ // Handle tagged enums with metadata for proper type conversion
100
+ const tagSymbol = nativeToScVal(enumValue.tag, { type: 'symbol' });
101
+
102
+ if (!enumValue.values || enumValue.values.length === 0) {
103
+ // Unit variant - ScVec containing single ScSymbol
104
+ return xdr.ScVal.scvVec([tagSymbol]);
105
+ }
106
+
107
+ const payloadValues = enumValue.values as unknown[];
108
+
109
+ // Tuple variant - convert each payload value with proper types
110
+ let payloadScVals: xdr.ScVal[];
111
+ // Use the variant type from EnumAwareFunctionParameter's enumMetadata
112
+ type EnumVariant = NonNullable<
113
+ EnumAwareFunctionParameter['enumMetadata']
114
+ >['variants'][number];
115
+ let variant: EnumVariant | undefined;
116
+
117
+ if (enumMetadata) {
118
+ variant = enumMetadata.variants.find((variantEntry) => variantEntry.name === enumValue.tag);
119
+ if (!variant || !variant.payloadTypes) {
120
+ // No variant metadata or payloadTypes - use convertEnumToScVal fallback
121
+ return convertEnumToScVal(enumValue as SorobanEnumValue);
122
+ }
123
+ // Convert each payload value with its corresponding type
124
+ // variant is guaranteed to be defined here due to the check above
125
+ const payloadTypes = variant.payloadTypes;
126
+ const payloadComponents = variant.payloadComponents;
127
+ payloadScVals = payloadTypes.map((payloadType, index) => {
128
+ const payloadSchema = payloadComponents?.[index]
129
+ ? {
130
+ name: `payload_${index}`,
131
+ type: payloadType,
132
+ components: payloadComponents[index],
133
+ }
134
+ : { name: `payload_${index}`, type: payloadType };
135
+
136
+ const val = payloadValues[index];
137
+ return valueToScVal(val, payloadType, payloadSchema, parseValue);
138
+ });
139
+ } else {
140
+ // No enum metadata - use convertEnumToScVal fallback
141
+ return convertEnumToScVal(enumValue as SorobanEnumValue);
142
+ }
143
+
144
+ // For single Tuple payload, wrap all payload ScVals in another ScVec
145
+ // Example: Some((Address, i128)) → ScVec([Symbol("Some"), ScVec([Address, I128])])
146
+ if (variant?.isSingleTuplePayload) {
147
+ const tuplePayloadVec = xdr.ScVal.scvVec(payloadScVals);
148
+ return xdr.ScVal.scvVec([tagSymbol, tuplePayloadVec]);
149
+ }
150
+
151
+ // Return ScVec with tag symbol followed by payload values
152
+ return xdr.ScVal.scvVec([tagSymbol, ...payloadScVals]);
153
+ }
154
+
155
+ // Check if this is a struct or tuple type
156
+ // Accept array-shaped values for tuple-structs when schema components are provided
157
+ if (
158
+ Array.isArray(possibleEnumValue) &&
159
+ paramSchema?.components &&
160
+ paramSchema.components.length
161
+ ) {
162
+ // Runtime validation: ensure array length matches schema components
163
+ if (possibleEnumValue.length !== paramSchema.components.length) {
164
+ throw new Error(
165
+ `Tuple-struct value length (${possibleEnumValue.length}) does not match schema components (${paramSchema.components.length}) for type ${parameterType}`
166
+ );
167
+ }
168
+ return convertStructToScVal(
169
+ possibleEnumValue as unknown as Record<string, unknown>,
170
+ parameterType,
171
+ paramSchema,
172
+ parseValue,
173
+ (innerValue, innerType, innerSchema) =>
174
+ valueToScVal(innerValue, innerType, innerSchema, parseInnerValue)
175
+ );
176
+ }
177
+
178
+ if (!isPrimitiveParamType(parameterType) && isStructType(value, parameterType)) {
179
+ return convertStructToScVal(
180
+ value as Record<string, unknown>,
181
+ parameterType,
182
+ paramSchema,
183
+ parseValue,
184
+ (innerValue, innerType, innerSchema) =>
185
+ valueToScVal(innerValue, innerType, innerSchema, parseInnerValue)
186
+ );
187
+ }
188
+
189
+ // Non-generic types - use existing logic
190
+ if (parameterType === 'Bool' || parameterType === 'Bytes') {
191
+ return nativeToScVal(value);
192
+ }
193
+ if (isBytesNType(parameterType)) {
194
+ const match = parameterType.match(/^BytesN<(\d+)>$/);
195
+ if (!match) {
196
+ throw new Error(`Invalid BytesN parameterType format: ${parameterType}`);
197
+ }
198
+ const expectedBytes = Number.parseInt(match[1], 10);
199
+ const decoded = parsePrimitive(value, 'Bytes');
200
+ const bytesValue = decoded instanceof Uint8Array ? decoded : (decoded ?? value);
201
+
202
+ if (
203
+ Number.isFinite(expectedBytes) &&
204
+ bytesValue instanceof Uint8Array &&
205
+ bytesValue.length !== expectedBytes
206
+ ) {
207
+ throw new Error(
208
+ `BytesN value must be exactly ${expectedBytes} bytes, received ${bytesValue.length}`
209
+ );
210
+ }
211
+
212
+ return nativeToScVal(bytesValue);
213
+ }
214
+ const scValType = convertStellarTypeToScValType(parameterType);
215
+ const typeHint = Array.isArray(scValType) ? scValType[0] : scValType;
216
+ return nativeToScVal(value, { type: typeHint });
217
+ }
218
+
219
+ const { baseType, parameters } = genericInfo;
220
+
221
+ switch (baseType) {
222
+ case 'Vec': {
223
+ // Handle Vec<T> types
224
+ const innerType = parameters[0];
225
+ if (Array.isArray(value)) {
226
+ // For enum element types, we need to pass enum metadata
227
+ // Check if paramSchema has enumMetadata or components that should be used for elements
228
+ let elementSchema: FunctionParameter | undefined;
229
+ if (enumMetadata) {
230
+ // This Vec is of enum type - pass the enum metadata to each element
231
+ elementSchema = {
232
+ name: 'element',
233
+ type: innerType,
234
+ enumMetadata,
235
+ } as EnumAwareFunctionParameter;
236
+ } else if (paramSchema?.components) {
237
+ // This Vec is of struct type - pass the components to each element
238
+ elementSchema = {
239
+ name: 'element',
240
+ type: innerType,
241
+ components: paramSchema.components,
242
+ };
243
+ }
244
+
245
+ const convertedElements = value.map((element) =>
246
+ valueToScVal(element, innerType, elementSchema, parseValue)
247
+ );
248
+ return nativeToScVal(convertedElements);
249
+ }
250
+ return nativeToScVal(value);
251
+ }
252
+
253
+ case 'Map': {
254
+ // Handle Map<K,V> types in Stellar SDK format
255
+ if (Array.isArray(value)) {
256
+ // Expect Stellar SDK format: [{ 0: {value, type}, 1: {value, type} }, ...]
257
+ const mapEntries: xdr.ScMapEntry[] = [];
258
+
259
+ value.forEach(
260
+ (entry: { 0: { value: string; type: string }; 1: { value: string; type: string } }) => {
261
+ if (
262
+ typeof entry !== 'object' ||
263
+ entry === null ||
264
+ !entry[0] ||
265
+ !entry[1] ||
266
+ typeof entry[0].value === 'undefined' ||
267
+ typeof entry[1].value === 'undefined'
268
+ ) {
269
+ throw new Error('Invalid Stellar SDK map format in valueToScVal');
270
+ }
271
+
272
+ // Process key and value through parsePrimitive for bytes conversion
273
+ let processedKey: unknown = entry[0].value;
274
+ let processedValue: unknown = entry[1].value;
275
+
276
+ // Handle bytes conversion for keys
277
+ const keyPrimitive = parsePrimitive(entry[0].value, entry[0].type);
278
+ if (keyPrimitive !== null) {
279
+ processedKey = keyPrimitive;
280
+ }
281
+
282
+ // Handle bytes conversion for values
283
+ const valuePrimitive = parsePrimitive(entry[1].value, entry[1].type);
284
+ if (valuePrimitive !== null) {
285
+ processedValue = valuePrimitive;
286
+ }
287
+
288
+ // Create ScVals for key and value
289
+ const keyScValType = convertStellarTypeToScValType(entry[0].type);
290
+ const keyTypeHint = Array.isArray(keyScValType) ? keyScValType[0] : keyScValType;
291
+ const keyScVal = nativeToScVal(processedKey, { type: keyTypeHint });
292
+
293
+ const valueScValType = convertStellarTypeToScValType(entry[1].type);
294
+ const valueTypeHint = Array.isArray(valueScValType)
295
+ ? valueScValType[0]
296
+ : valueScValType;
297
+ const valueScVal = nativeToScVal(processedValue, { type: valueTypeHint });
298
+
299
+ mapEntries.push(
300
+ new xdr.ScMapEntry({
301
+ key: keyScVal,
302
+ val: valueScVal,
303
+ })
304
+ );
305
+ }
306
+ );
307
+
308
+ // Sort map entries by XDR-encoded keys (required by Soroban)
309
+ const sortedMapEntries = mapEntries.sort((a, b) => compareScValsByXdr(a.key(), b.key()));
310
+ return xdr.ScVal.scvMap(sortedMapEntries);
311
+ }
312
+
313
+ return nativeToScVal(value);
314
+ }
315
+
316
+ case 'Tuple': {
317
+ if (!paramSchema?.components || paramSchema.components.length === 0) {
318
+ throw new Error(
319
+ `Tuple parameter "${paramSchema?.name ?? 'unknown'}" is missing component metadata`
320
+ );
321
+ }
322
+
323
+ const tupleComponents = paramSchema.components;
324
+ const tupleValues: xdr.ScVal[] = [];
325
+
326
+ tupleComponents.forEach((component, index) => {
327
+ const key = component.name ?? `item_${index}`;
328
+ let elementValue: unknown;
329
+
330
+ if (Array.isArray(value)) {
331
+ elementValue = value[index];
332
+ } else if (value && typeof value === 'object') {
333
+ elementValue = (value as Record<string, unknown>)[key];
334
+ }
335
+
336
+ if (typeof elementValue === 'undefined') {
337
+ const expectedTypes = tupleComponents.map((c) => c.type).join(', ');
338
+ throw new Error(
339
+ `Missing tuple value for "${key}" in parameter "${paramSchema.name ?? 'unknown'}". Expected ${tupleComponents.length} values of types [${expectedTypes}] but received: ${JSON.stringify(value)}`
340
+ );
341
+ }
342
+
343
+ if (typeof elementValue === 'string' && isLikelyEnumType(component.type)) {
344
+ elementValue = { tag: elementValue };
345
+ }
346
+
347
+ tupleValues.push(valueToScVal(elementValue, component.type, component, parseInnerValue));
348
+ });
349
+
350
+ return xdr.ScVal.scvVec(tupleValues);
351
+ }
352
+
353
+ case 'Option': {
354
+ // Handle Option<T> types
355
+ const innerType = parameters[0];
356
+
357
+ if (value === null || value === undefined) {
358
+ return nativeToScVal(null); // None variant
359
+ } else {
360
+ // Some variant - convert the inner value
361
+ let innerSchema: FunctionParameter | undefined;
362
+ if (enumMetadata) {
363
+ innerSchema = {
364
+ name: 'inner',
365
+ type: innerType,
366
+ ...({ enumMetadata } as unknown as Record<string, unknown>),
367
+ } as unknown as FunctionParameter;
368
+ } else if (paramSchema?.components) {
369
+ innerSchema = {
370
+ name: 'inner',
371
+ type: innerType,
372
+ components: paramSchema.components,
373
+ };
374
+ }
375
+ return valueToScVal(value, innerType, innerSchema, parseInnerValue);
376
+ }
377
+ }
378
+
379
+ case 'Result': {
380
+ // Handle Result<T,E> types
381
+ const okType = parameters[0];
382
+ const errType = parameters[1];
383
+
384
+ // Result types typically come as {ok: value} or {err: error}
385
+ if (typeof value === 'object' && value !== null) {
386
+ const resultObj = value as Record<string, unknown>;
387
+ if ('ok' in resultObj) {
388
+ const okScVal = valueToScVal(resultObj.ok, okType);
389
+ return nativeToScVal({ ok: okScVal });
390
+ } else if ('err' in resultObj) {
391
+ const errScVal = valueToScVal(resultObj.err, errType);
392
+ return nativeToScVal({ err: errScVal });
393
+ }
394
+ }
395
+ return nativeToScVal(value);
396
+ }
397
+
398
+ default: {
399
+ // Unknown generic type - fallback to basic conversion
400
+ const scValType = convertStellarTypeToScValType(parameterType);
401
+ const typeHint = Array.isArray(scValType) ? scValType[0] : scValType;
402
+ return nativeToScVal(value, { type: typeHint });
403
+ }
404
+ }
405
+ }