@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,6 @@
1
+ // Barrel file
2
+
3
+ export * from './artifacts';
4
+ export * from './formatting';
5
+ export * from './input-parsing';
6
+ export * from './type-detection';
@@ -0,0 +1,336 @@
1
+ import { nativeToScVal, xdr } from '@stellar/stellar-sdk';
2
+
3
+ import { detectBytesEncoding, logger, stringToBytes } from '@openzeppelin/ui-utils';
4
+
5
+ // Import types for internal use
6
+ import type {
7
+ SorobanArgumentValue,
8
+ SorobanComplexValue,
9
+ SorobanEnumValue,
10
+ SorobanMapEntry,
11
+ } from '../transform/input-parser';
12
+ import { convertStellarTypeToScValType } from './formatting';
13
+ import { compareScValsByXdr } from './xdr-ordering';
14
+
15
+ const SYSTEM_LOG_TAG = 'StellarInputParsingUtils';
16
+
17
+ // Re-export types for convenience
18
+ export type {
19
+ SorobanArgumentValue,
20
+ SorobanEnumValue,
21
+ SorobanMapEntry,
22
+ SorobanComplexValue,
23
+ } from '../transform/input-parser';
24
+
25
+ // ================================
26
+ // TYPE GUARDS AND UTILITIES
27
+ // ================================
28
+
29
+ export function isPrimitiveArgumentSet(args: Record<string, SorobanComplexValue>): boolean {
30
+ return Object.values(args).every(
31
+ (v) => typeof v === 'object' && v !== null && 'type' in v && 'value' in v
32
+ );
33
+ }
34
+
35
+ export function isEnumArgumentSet(args: Record<string, SorobanComplexValue>): boolean {
36
+ return Object.values(args).some(
37
+ (v) => typeof v === 'object' && v !== null && ('tag' in v || 'enum' in v)
38
+ );
39
+ }
40
+
41
+ export function isMapArray(argValue: unknown[]): boolean {
42
+ try {
43
+ return (
44
+ Array.isArray(argValue) &&
45
+ argValue.every((obj: unknown) => {
46
+ if (typeof obj !== 'object' || obj === null) return false;
47
+
48
+ const keys = Object.keys(obj);
49
+ if (keys.length !== 2 || !keys.includes('0') || !keys.includes('1')) {
50
+ return false;
51
+ }
52
+
53
+ const keyEntry = (obj as Record<string, unknown>)['0'];
54
+ return (
55
+ typeof keyEntry === 'object' &&
56
+ keyEntry !== null &&
57
+ 'value' in keyEntry &&
58
+ 'type' in keyEntry
59
+ );
60
+ })
61
+ );
62
+ } catch {
63
+ return false;
64
+ }
65
+ }
66
+
67
+ export function isComplexObjectArray(argValue: unknown[]): boolean {
68
+ return argValue.some(
69
+ (v) =>
70
+ typeof v === 'object' &&
71
+ v !== null &&
72
+ typeof Object.values(v as Record<string, unknown>)[0] === 'object'
73
+ );
74
+ }
75
+
76
+ export function isPrimitiveArray(argValue: unknown[]): argValue is SorobanArgumentValue[] {
77
+ if (argValue.length === 0) return false;
78
+
79
+ // Check if all items are SorobanArgumentValue (not SorobanMapEntry)
80
+ const allArePrimitives = argValue.every(
81
+ (v) =>
82
+ typeof v === 'object' &&
83
+ v !== null &&
84
+ 'value' in v &&
85
+ 'type' in v &&
86
+ !('0' in v) && // Not a map entry
87
+ !('1' in v) // Not a map entry
88
+ );
89
+
90
+ if (!allArePrimitives) return false;
91
+
92
+ const firstItem = argValue[0] as SorobanArgumentValue;
93
+ return argValue.every((v) => {
94
+ const item = v as SorobanArgumentValue;
95
+ return item.type === firstItem.type;
96
+ });
97
+ }
98
+
99
+ export function isTupleArray(argValue: unknown[]): argValue is SorobanArgumentValue[] {
100
+ return argValue.every(
101
+ (v: unknown) =>
102
+ typeof v === 'object' &&
103
+ v !== null &&
104
+ 'type' in v &&
105
+ 'value' in v &&
106
+ !('0' in v) && // Not a map entry
107
+ !('1' in v) // Not a map entry
108
+ );
109
+ }
110
+
111
+ export function isObjectWithTypedValues(argValue: SorobanComplexValue): boolean {
112
+ return (
113
+ typeof argValue === 'object' &&
114
+ argValue !== null &&
115
+ !Array.isArray(argValue) &&
116
+ Object.values(argValue).every(
117
+ (v: unknown) => typeof v === 'object' && v !== null && 'type' in v && 'value' in v
118
+ )
119
+ );
120
+ }
121
+
122
+ export function isPrimitiveValue(argValue: SorobanComplexValue): boolean {
123
+ return (
124
+ typeof argValue === 'object' && argValue !== null && 'type' in argValue && 'value' in argValue
125
+ );
126
+ }
127
+
128
+ // ================================
129
+ // SCVAL CONVERSION UTILITIES
130
+ // ================================
131
+
132
+ export function getScValFromPrimitive(v: SorobanArgumentValue): xdr.ScVal {
133
+ try {
134
+ if (v.type === 'bool') {
135
+ const boolValue = typeof v.value === 'boolean' ? v.value : v.value === 'true';
136
+ return nativeToScVal(boolValue);
137
+ }
138
+
139
+ if (v.type === 'bytes') {
140
+ const stringValue = v.value as string;
141
+ const encoding = detectBytesEncoding(stringValue);
142
+ return nativeToScVal(stringToBytes(stringValue, encoding));
143
+ }
144
+
145
+ // Use our improved type conversion utility
146
+ const typeHint = convertStellarTypeToScValType(v.type);
147
+ return nativeToScVal(v.value, { type: typeHint });
148
+ } catch (error) {
149
+ logger.error(SYSTEM_LOG_TAG, `Failed to convert primitive ${v.type}:`, error);
150
+ throw new Error(`Failed to convert primitive value of type ${v.type}: ${error}`);
151
+ }
152
+ }
153
+
154
+ export function getScValFromArg(arg: SorobanComplexValue, scVals: xdr.ScVal[]): xdr.ScVal {
155
+ // Handle array of arrays with numeric objects (nested structures)
156
+ if (Array.isArray(arg) && arg.length > 0) {
157
+ const arrayScVals = arg.map((subArray) => {
158
+ if (Array.isArray(subArray) && isMapArray(subArray)) {
159
+ const { mapVal, mapType } = convertObjectToMap(subArray as SorobanMapEntry[]);
160
+
161
+ // Better map key-value pair handling
162
+ const items = Object.keys(mapVal);
163
+ if (items.length > 1) {
164
+ items.forEach((item) => {
165
+ const mapScVal = nativeToScVal(mapVal[item], {
166
+ type: mapType[item],
167
+ });
168
+ scVals.push(mapScVal);
169
+ });
170
+ }
171
+
172
+ return nativeToScVal(mapVal, { type: mapType });
173
+ }
174
+ return getScValFromArg(subArray as SorobanComplexValue, scVals);
175
+ });
176
+
177
+ return xdr.ScVal.scvVec(arrayScVals);
178
+ }
179
+
180
+ // For single values, handle based on type
181
+ if (typeof arg === 'object' && arg !== null && 'type' in arg && 'value' in arg) {
182
+ return getScValFromPrimitive(arg as SorobanArgumentValue);
183
+ }
184
+
185
+ // Fallback to nativeToScVal for simple values
186
+ return nativeToScVal(arg);
187
+ }
188
+
189
+ export function convertEnumToScVal(obj: SorobanEnumValue, scVals?: xdr.ScVal[]): xdr.ScVal {
190
+ try {
191
+ // Integer variant - has enum property (keep as-is for integer enums)
192
+ if (obj.enum !== undefined) {
193
+ const enumScVal = nativeToScVal(obj.enum, { type: 'u32' });
194
+ return enumScVal;
195
+ }
196
+
197
+ if (!obj.tag) {
198
+ throw new Error('Enum object must have either "tag" or "enum" property');
199
+ }
200
+
201
+ // Use Vector format with Symbol for variant names (as per Soroban documentation)
202
+ // Unit variants: ScVec containing single ScSymbol
203
+ // Tuple variants: ScVec with ScSymbol + payload elements
204
+ const tagSymbol = nativeToScVal(obj.tag, { type: 'symbol' });
205
+
206
+ if (!obj.values || obj.values.length === 0) {
207
+ // Unit variant - ScVec containing single ScSymbol
208
+ const unitVec = xdr.ScVal.scvVec([tagSymbol]);
209
+ return unitVec;
210
+ }
211
+
212
+ // Tuple variant - ScVec with ScSymbol + payload elements
213
+ const valuesVal = obj.values.map((v) => getScValFromArg(v, scVals || []));
214
+ const tupleVec = xdr.ScVal.scvVec([tagSymbol, ...valuesVal]);
215
+ return tupleVec;
216
+ } catch (error) {
217
+ logger.error(SYSTEM_LOG_TAG, 'Failed to convert enum:', error);
218
+ throw new Error(`Failed to convert enum: ${error}`);
219
+ }
220
+ }
221
+
222
+ export function convertValuesToScVals(
223
+ values: SorobanArgumentValue[],
224
+ scVals: xdr.ScVal[]
225
+ ): xdr.ScVal[] {
226
+ return values.map((v) => getScValFromArg(v, scVals));
227
+ }
228
+
229
+ export function convertObjectToScVal(obj: Record<string, SorobanArgumentValue>): xdr.ScVal {
230
+ try {
231
+ const convertedValue: Record<string, unknown> = {};
232
+ const typeHints: Record<string, string | string[]> = {};
233
+
234
+ // Process each field in the object
235
+ for (const key in obj) {
236
+ const field = obj[key];
237
+
238
+ if (field.type === 'bool') {
239
+ convertedValue[key] =
240
+ typeof field.value === 'boolean' ? field.value : field.value === 'true';
241
+ typeHints[key] = ['symbol']; // Key is always symbol, value type varies
242
+ } else {
243
+ convertedValue[key] = field.value;
244
+ const fieldTypeHint = convertStellarTypeToScValType(field.type);
245
+ typeHints[key] = [
246
+ 'symbol',
247
+ ...(Array.isArray(fieldTypeHint) ? fieldTypeHint : [fieldTypeHint]),
248
+ ];
249
+ }
250
+ }
251
+
252
+ return nativeToScVal(convertedValue, { type: typeHints });
253
+ } catch (error) {
254
+ logger.error(SYSTEM_LOG_TAG, 'Failed to convert object:', error);
255
+ throw new Error(`Failed to convert object to ScVal: ${error}`);
256
+ }
257
+ }
258
+
259
+ export function convertObjectToMap(mapArray: SorobanMapEntry[]): {
260
+ mapVal: Record<string, unknown>;
261
+ mapType: Record<string, string | string[]>;
262
+ } {
263
+ try {
264
+ const sortedEntries = [...mapArray].sort((a, b) => {
265
+ const aKey = getScValFromPrimitive(a['0'] as SorobanArgumentValue);
266
+ const bKey = getScValFromPrimitive(b['0'] as SorobanArgumentValue);
267
+ return compareScValsByXdr(aKey, bKey);
268
+ });
269
+
270
+ const mapVal = sortedEntries.reduce((acc: Record<string, unknown>, pair) => {
271
+ const key = String(pair['0'].value);
272
+
273
+ if (Array.isArray(pair['1'])) {
274
+ // Handle nested array values
275
+ const valueScVal = getScValFromArg(pair['1'], []);
276
+ acc[key] = valueScVal;
277
+ } else {
278
+ // Handle primitive values
279
+ const value = pair['1'].value;
280
+ if (pair['1'].type === 'bool') {
281
+ if (typeof value === 'boolean') {
282
+ acc[key] = value;
283
+ } else if (typeof value === 'string') {
284
+ acc[key] = value === 'true';
285
+ } else {
286
+ acc[key] = Boolean(value);
287
+ }
288
+ } else {
289
+ acc[key] = value;
290
+ }
291
+ }
292
+ return acc;
293
+ }, {});
294
+
295
+ const mapType = sortedEntries.reduce((acc: Record<string, string[]>, pair) => {
296
+ const key = String(pair['0'].value);
297
+ const keyTypeHint = convertStellarTypeToScValType(pair['0'].type);
298
+ const valueTypeHint = convertStellarTypeToScValType(pair['1'].type);
299
+ acc[key] = [
300
+ ...(Array.isArray(keyTypeHint) ? keyTypeHint : [keyTypeHint]),
301
+ ...(Array.isArray(valueTypeHint) ? valueTypeHint : [valueTypeHint]),
302
+ ];
303
+ return acc;
304
+ }, {});
305
+
306
+ return { mapVal, mapType };
307
+ } catch (error) {
308
+ logger.error(SYSTEM_LOG_TAG, 'Failed to convert map:', error);
309
+ throw new Error(`Failed to convert map: ${error}`);
310
+ }
311
+ }
312
+
313
+ export function convertTupleToScVal(tupleArray: SorobanArgumentValue[]): xdr.ScVal {
314
+ try {
315
+ const tupleScVals = tupleArray.map((v) => {
316
+ if (v.type === 'bool') {
317
+ const boolValue = typeof v.value === 'boolean' ? v.value : v.value === 'true';
318
+ return nativeToScVal(boolValue);
319
+ }
320
+
321
+ if (v.type === 'bytes') {
322
+ const encoding = detectBytesEncoding(v.value as string);
323
+ return nativeToScVal(stringToBytes(v.value as string, encoding));
324
+ }
325
+
326
+ const typeHint = convertStellarTypeToScValType(v.type);
327
+ return nativeToScVal(v.value, { type: typeHint });
328
+ });
329
+
330
+ // JS SDK's nativeToScVal doesn't support mixed-type arrays, so use xdr.ScVal.scvVec
331
+ return xdr.ScVal.scvVec(tupleScVals);
332
+ } catch (error) {
333
+ logger.error(SYSTEM_LOG_TAG, 'Failed to convert tuple:', error);
334
+ throw new Error(`Failed to convert tuple: ${error}`);
335
+ }
336
+ }
@@ -0,0 +1,303 @@
1
+ /**
2
+ * Safe type parsing utilities for Stellar generic types.
3
+ *
4
+ * This module provides secure, performant alternatives to regex-based parsing
5
+ * to prevent ReDoS (Regular Expression Denial of Service) attacks when processing
6
+ * Stellar contract parameter types like Vec<T>, Map<K,V>, and Option<T>.
7
+ *
8
+ * Uses iterative parsing instead of vulnerable regex patterns with greedy quantifiers.
9
+ */
10
+
11
+ /**
12
+ * Configuration constants for safe parsing
13
+ */
14
+ const PARSING_LIMITS = {
15
+ /** Maximum depth for nested generic types to prevent stack overflow */
16
+ MAX_NESTING_DEPTH: 10,
17
+ /** Maximum string length for type parsing to prevent DoS */
18
+ MAX_TYPE_STRING_LENGTH: 1000,
19
+ } as const;
20
+
21
+ /**
22
+ * Result type for type extraction operations
23
+ */
24
+ type ExtractionResult<T> = T | null;
25
+
26
+ /**
27
+ * Safely extracts the inner type from a Stellar Vec type.
28
+ *
29
+ * @param parameterType - The parameter type (e.g., 'Vec<U32>', 'Vec<Vec<Address>>')
30
+ * @returns The inner type (e.g., 'U32', 'Vec<Address>') or null if not a Vec type
31
+ *
32
+ * @example
33
+ * ```typescript
34
+ * extractVecElementType('Vec<U32>') // → 'U32'
35
+ * extractVecElementType('Vec<Vec<Address>>') // → 'Vec<Address>'
36
+ * extractVecElementType('U32') // → null
37
+ * ```
38
+ */
39
+ export function extractVecElementType(parameterType: string): ExtractionResult<string> {
40
+ if (!isValidTypeString(parameterType) || !parameterType.startsWith('Vec<')) {
41
+ return null;
42
+ }
43
+
44
+ return extractGenericInnerType(parameterType, 'Vec');
45
+ }
46
+
47
+ /**
48
+ * Safely extracts the key and value types from a Stellar Map type.
49
+ *
50
+ * @param parameterType - The parameter type (e.g., 'Map<Symbol, Bytes>', 'Map<U32, Vec<Address>>')
51
+ * @returns An object with keyType and valueType, or null if not a Map type
52
+ *
53
+ * @example
54
+ * ```typescript
55
+ * extractMapTypes('Map<U32, Address>') // → { keyType: 'U32', valueType: 'Address' }
56
+ * extractMapTypes('Map<Symbol, Vec<U32>>') // → { keyType: 'Symbol', valueType: 'Vec<U32>' }
57
+ * extractMapTypes('U32') // → null
58
+ * ```
59
+ */
60
+ export function extractMapTypes(
61
+ parameterType: string
62
+ ): ExtractionResult<{ keyType: string; valueType: string }> {
63
+ if (!isValidTypeString(parameterType) || !parameterType.startsWith('Map<')) {
64
+ return null;
65
+ }
66
+
67
+ const innerContent = extractGenericInnerType(parameterType, 'Map');
68
+ if (!innerContent) {
69
+ return null;
70
+ }
71
+
72
+ // Find the top-level comma that separates key and value types
73
+ const commaIndex = findTopLevelComma(innerContent);
74
+ if (commaIndex === -1) {
75
+ return null;
76
+ }
77
+
78
+ const keyType = innerContent.slice(0, commaIndex).trim();
79
+ const valueType = innerContent.slice(commaIndex + 1).trim();
80
+
81
+ // Validate both types are non-empty and don't contain invalid characters
82
+ if (!keyType || !valueType || hasInvalidCharacters(keyType) || hasInvalidCharacters(valueType)) {
83
+ return null;
84
+ }
85
+
86
+ return { keyType, valueType };
87
+ }
88
+
89
+ /**
90
+ * Safely extracts the inner type from a Stellar Option type.
91
+ *
92
+ * @param parameterType - The parameter type (e.g., 'Option<U32>', 'Option<Vec<Address>>')
93
+ * @returns The inner type or null if not an Option type
94
+ *
95
+ * @example
96
+ * ```typescript
97
+ * extractOptionElementType('Option<U32>') // → 'U32'
98
+ * extractOptionElementType('Option<Vec<Address>>') // → 'Vec<Address>'
99
+ * extractOptionElementType('U32') // → null
100
+ * ```
101
+ */
102
+ export function extractOptionElementType(parameterType: string): ExtractionResult<string> {
103
+ if (!isValidTypeString(parameterType) || !parameterType.startsWith('Option<')) {
104
+ return null;
105
+ }
106
+
107
+ return extractGenericInnerType(parameterType, 'Option');
108
+ }
109
+
110
+ /**
111
+ * Safely extracts the element types from a Stellar Tuple type.
112
+ *
113
+ * @param parameterType - The parameter type (e.g., 'Tuple<U32, Bool>')
114
+ * @returns Array of element types or null if not a Tuple type
115
+ */
116
+ export function extractTupleTypes(parameterType: string): ExtractionResult<string[]> {
117
+ if (!isValidTypeString(parameterType) || !parameterType.startsWith('Tuple<')) {
118
+ return null;
119
+ }
120
+
121
+ const innerContent = extractGenericInnerType(parameterType, 'Tuple');
122
+ if (!innerContent) {
123
+ return null;
124
+ }
125
+
126
+ const parts = splitTopLevelTypes(innerContent);
127
+ if (parts.length === 0) {
128
+ return null;
129
+ }
130
+
131
+ return parts;
132
+ }
133
+
134
+ /**
135
+ * Generic function to extract inner content from generic types.
136
+ *
137
+ * @param parameterType - The full parameter type
138
+ * @param genericName - The generic type name (e.g., 'Vec', 'Map', 'Option')
139
+ * @returns The inner content or null if extraction fails
140
+ */
141
+ function extractGenericInnerType(
142
+ parameterType: string,
143
+ genericName: string
144
+ ): ExtractionResult<string> {
145
+ const prefix = `${genericName}<`;
146
+
147
+ if (!parameterType.startsWith(prefix) || !parameterType.endsWith('>')) {
148
+ return null;
149
+ }
150
+
151
+ const innerContent = parameterType.slice(prefix.length, -1);
152
+
153
+ if (!innerContent || hasInvalidCharacters(innerContent)) {
154
+ return null;
155
+ }
156
+
157
+ // Validate that brackets are properly balanced
158
+ if (!isBalancedBrackets(innerContent)) {
159
+ return null;
160
+ }
161
+
162
+ return innerContent.trim();
163
+ }
164
+
165
+ /**
166
+ * Finds the first top-level comma in a type string, ignoring commas inside nested brackets.
167
+ *
168
+ * @param content - The content to search in
169
+ * @returns The index of the top-level comma, or -1 if not found
170
+ *
171
+ * @example
172
+ * ```typescript
173
+ * findTopLevelComma('U32, Address') // → 3
174
+ * findTopLevelComma('Vec<U32, U64>, Address') // → 13 (after the first >)
175
+ * ```
176
+ */
177
+ function findTopLevelComma(content: string): number {
178
+ let angleLevel = 0;
179
+
180
+ for (let i = 0; i < content.length; i++) {
181
+ const char = content[i];
182
+
183
+ switch (char) {
184
+ case '<':
185
+ angleLevel++;
186
+ break;
187
+ case '>':
188
+ angleLevel--;
189
+ if (angleLevel < 0) return -1; // Malformed input
190
+ break;
191
+ case ',':
192
+ if (angleLevel === 0) {
193
+ return i;
194
+ }
195
+ break;
196
+ }
197
+ }
198
+
199
+ return -1;
200
+ }
201
+
202
+ /**
203
+ * Checks if brackets are properly balanced in a string and enforces nesting depth limits.
204
+ *
205
+ * @param content - The content to validate
206
+ * @returns True if brackets are balanced and within depth limits, false otherwise
207
+ */
208
+ function isBalancedBrackets(content: string): boolean {
209
+ let angleLevel = 0;
210
+ let maxNesting = 0;
211
+
212
+ for (const char of content) {
213
+ switch (char) {
214
+ case '<':
215
+ angleLevel++;
216
+ maxNesting = Math.max(maxNesting, angleLevel);
217
+ // Enforce nesting depth limit
218
+ if (maxNesting > PARSING_LIMITS.MAX_NESTING_DEPTH) {
219
+ return false;
220
+ }
221
+ break;
222
+ case '>':
223
+ angleLevel--;
224
+ if (angleLevel < 0) return false;
225
+ break;
226
+ }
227
+ }
228
+
229
+ return angleLevel === 0;
230
+ }
231
+
232
+ /**
233
+ * Validates that a type string is safe to process.
234
+ *
235
+ * @param typeString - The type string to validate
236
+ * @returns True if the type string is safe to process
237
+ */
238
+ export function isValidTypeString(typeString: string): boolean {
239
+ if (!typeString || typeof typeString !== 'string') {
240
+ return false;
241
+ }
242
+
243
+ // Length check to prevent DoS
244
+ if (typeString.length > PARSING_LIMITS.MAX_TYPE_STRING_LENGTH) {
245
+ return false;
246
+ }
247
+
248
+ // Check for invalid characters
249
+ if (hasInvalidCharacters(typeString)) {
250
+ return false;
251
+ }
252
+
253
+ // Validate bracket balance
254
+ return isBalancedBrackets(typeString);
255
+ }
256
+
257
+ /**
258
+ * Checks if a string contains invalid characters for type names.
259
+ *
260
+ * @param str - The string to check
261
+ * @returns True if the string contains invalid characters
262
+ */
263
+ function hasInvalidCharacters(str: string): boolean {
264
+ // No control characters or line breaks
265
+ if (/[\x00-\x1F\x7F\r\n]/.test(str)) {
266
+ return true;
267
+ }
268
+
269
+ // Only allow alphanumeric, angle brackets, commas, underscores, and spaces
270
+ // Note: Parentheses are NOT allowed in Stellar type strings
271
+ return !/^[A-Za-z0-9<>,\s_]+$/.test(str);
272
+ }
273
+
274
+ /**
275
+ * Splits a comma-separated list of types while respecting nested generics.
276
+ */
277
+ function splitTopLevelTypes(content: string): string[] {
278
+ const types: string[] = [];
279
+ let start = 0;
280
+ let level = 0;
281
+
282
+ for (let i = 0; i < content.length; i += 1) {
283
+ const char = content[i];
284
+ if (char === '<') {
285
+ level += 1;
286
+ } else if (char === '>') {
287
+ level -= 1;
288
+ } else if (char === ',' && level === 0) {
289
+ const segment = content.slice(start, i).trim();
290
+ if (segment) {
291
+ types.push(segment);
292
+ }
293
+ start = i + 1;
294
+ }
295
+ }
296
+
297
+ const lastSegment = content.slice(start).trim();
298
+ if (lastSegment) {
299
+ types.push(lastSegment);
300
+ }
301
+
302
+ return types;
303
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Set of primitive Stellar/Soroban parameter types.
3
+ * These types do not have nested components and can be directly serialized.
4
+ *
5
+ * Used to distinguish primitive types from complex types (structs, enums, tuples, maps, vecs)
6
+ * when processing parameters for serialization and validation.
7
+ */
8
+ export const PRIMITIVE_STELLAR_TYPES = new Set([
9
+ 'Bool',
10
+ 'ScString',
11
+ 'ScSymbol',
12
+ 'Address',
13
+ 'Bytes',
14
+ 'U8',
15
+ 'U16',
16
+ 'U32',
17
+ 'U64',
18
+ 'U128',
19
+ 'U256',
20
+ 'I8',
21
+ 'I16',
22
+ 'I32',
23
+ 'I64',
24
+ 'I128',
25
+ 'I256',
26
+ ]);
27
+
28
+ /**
29
+ * Check if a Stellar type is a primitive type.
30
+ * @param type - The Stellar type to check
31
+ * @returns True if the type is primitive, false otherwise
32
+ */
33
+ export function isPrimitiveParamType(type: string): boolean {
34
+ return PRIMITIVE_STELLAR_TYPES.has(type);
35
+ }