@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,498 @@
1
+ import * as StellarSdk from '@stellar/stellar-sdk';
2
+ import { xdr } from '@stellar/stellar-sdk';
3
+
4
+ import type {
5
+ ContractFunction,
6
+ ContractSchema,
7
+ FunctionParameter,
8
+ StellarNetworkConfig,
9
+ } from '@openzeppelin/ui-types';
10
+ import { logger } from '@openzeppelin/ui-utils';
11
+
12
+ import { getStellarExplorerAddressUrl } from '../configuration/explorer';
13
+ import { extractStructFields, isStructType } from '../mapping/struct-fields';
14
+ import { checkStellarFunctionStateMutability } from '../query/handler';
15
+ import { getSacSpecArtifacts } from '../sac/spec-cache';
16
+ import type { StellarContractArtifacts } from '../types/artifacts';
17
+ import { extractSorobanTypeFromScSpec } from '../utils/type-detection';
18
+ import { getStellarContractType } from './type';
19
+
20
+ /**
21
+ * Load a Stellar contract using the official Stellar SDK approach
22
+ * Based on the patterns from the official Stellar laboratory
23
+ */
24
+ export async function loadStellarContractFromAddress(
25
+ contractAddress: string,
26
+ networkConfig: StellarNetworkConfig
27
+ ): Promise<ContractSchema> {
28
+ logger.info('loadStellarContractFromAddress', 'Loading contract:', {
29
+ contractAddress,
30
+ network: networkConfig.name,
31
+ rpcUrl: networkConfig.sorobanRpcUrl,
32
+ networkPassphrase: networkConfig.networkPassphrase,
33
+ });
34
+
35
+ try {
36
+ // Validate contract address
37
+ if (!StellarSdk.StrKey.isValidContract(contractAddress)) {
38
+ throw new Error(`Invalid contract address: ${contractAddress}`);
39
+ }
40
+
41
+ // Special-case: detect SAC and construct spec locally
42
+ try {
43
+ const execType = await getStellarContractType(contractAddress, networkConfig);
44
+
45
+ if (execType === 'contractExecutableStellarAsset') {
46
+ const { base64Entries, specEntries } = await getSacSpecArtifacts();
47
+ const spec = new StellarSdk.contract.Spec(base64Entries);
48
+
49
+ const functions = await extractFunctionsFromSpec(
50
+ spec,
51
+ contractAddress,
52
+ specEntries,
53
+ networkConfig
54
+ );
55
+
56
+ return {
57
+ name: `Stellar Asset Contract ${contractAddress.slice(0, 8)}...`,
58
+ ecosystem: 'stellar',
59
+ functions,
60
+ metadata: {
61
+ specEntries,
62
+ },
63
+ };
64
+ }
65
+ } catch (e) {
66
+ // If detection path fails unexpectedly, fall back to SDK client path below
67
+ logger.warn(
68
+ 'loadStellarContractFromAddress',
69
+ 'SAC detection failed, falling back to regular client:',
70
+ e
71
+ );
72
+ }
73
+
74
+ // Create contract client using the official Stellar SDK approach
75
+ // Laboratory note: some contracts may be missing Wasm/definition retrievable via RPC.
76
+ // In that case the SDK can throw an internal error like
77
+ // "Cannot destructure property 'length'...". Detect and surface an explicit error.
78
+ let contractClient: StellarSdk.contract.Client;
79
+ try {
80
+ contractClient = await StellarSdk.contract.Client.from({
81
+ contractId: contractAddress,
82
+ networkPassphrase: networkConfig.networkPassphrase,
83
+ rpcUrl: networkConfig.sorobanRpcUrl,
84
+ });
85
+ } catch (e) {
86
+ const message = (e as Error)?.message || String(e);
87
+ if (message.includes("Cannot destructure property 'length'")) {
88
+ const friendly =
89
+ 'Unable to fetch contract metadata from RPC. The contract appears to have no published Wasm/definition on this network.';
90
+ logger.error('loadStellarContractFromAddress', friendly);
91
+ throw new Error(`NO_WASM: ${friendly}`);
92
+ }
93
+ throw e;
94
+ }
95
+
96
+ logger.info('loadStellarContractFromAddress', 'Contract client created successfully');
97
+
98
+ // Get spec entries - try different approaches to access them
99
+ let specEntries: xdr.ScSpecEntry[] = [];
100
+ try {
101
+ // Access spec entries from the spec object
102
+
103
+ // Try to access spec entries through different possible properties/methods
104
+ if (contractClient.spec && typeof contractClient.spec === 'object') {
105
+ const spec = contractClient.spec as unknown as Record<string, unknown>;
106
+
107
+ // Try common property names
108
+ if (Array.isArray(spec.entries)) {
109
+ specEntries = spec.entries as xdr.ScSpecEntry[];
110
+ } else if (Array.isArray(spec._entries)) {
111
+ specEntries = spec._entries as xdr.ScSpecEntry[];
112
+ } else if (Array.isArray(spec.specEntries)) {
113
+ specEntries = spec.specEntries as xdr.ScSpecEntry[];
114
+ } else if (typeof spec.entries === 'function') {
115
+ // Maybe it's a method after all, but with different signature
116
+ try {
117
+ specEntries = (spec.entries as () => xdr.ScSpecEntry[])();
118
+ } catch (e) {
119
+ logger.warn('loadStellarContractFromAddress', 'entries() method failed:', e);
120
+ }
121
+ }
122
+
123
+ // Try the method directly on spec if it has the method
124
+ if (specEntries.length === 0 && typeof spec.entries === 'function') {
125
+ try {
126
+ specEntries = (spec.entries as () => xdr.ScSpecEntry[])();
127
+ } catch (e) {
128
+ logger.warn('loadStellarContractFromAddress', 'direct entries() method failed:', e);
129
+ }
130
+ }
131
+
132
+ logger.info('loadStellarContractFromAddress', `Found ${specEntries.length} spec entries`);
133
+ }
134
+ } catch (specError) {
135
+ logger.warn('loadStellarContractFromAddress', 'Could not extract spec entries:', specError);
136
+ }
137
+
138
+ // Extract functions using the official laboratory approach with spec entries for struct extraction
139
+ const functions = await extractFunctionsFromSpec(
140
+ contractClient.spec,
141
+ contractAddress,
142
+ specEntries,
143
+ networkConfig
144
+ );
145
+
146
+ logger.info(
147
+ 'loadStellarContractFromAddress',
148
+ `Successfully extracted ${functions.length} functions`
149
+ );
150
+
151
+ return {
152
+ name: `Soroban Contract ${contractAddress.slice(0, 8)}...`,
153
+ ecosystem: 'stellar',
154
+ functions,
155
+ metadata: {
156
+ specEntries,
157
+ },
158
+ };
159
+ } catch (error) {
160
+ const msg = (error as Error)?.message || String(error);
161
+ // Preserve explicit NO_WASM error so downstream can surface it to the user
162
+ if (msg.startsWith('NO_WASM:')) {
163
+ logger.error('loadStellarContractFromAddress', msg);
164
+ throw new Error(msg);
165
+ }
166
+ logger.error('loadStellarContractFromAddress', 'Failed to load contract:', error);
167
+ throw new Error(`Failed to load contract: ${msg}`);
168
+ }
169
+ }
170
+
171
+ /**
172
+ * Extract functions from contract spec using the official Stellar laboratory approach
173
+ * with simulation-based state mutability detection
174
+ */
175
+ async function extractFunctionsFromSpec(
176
+ spec: StellarSdk.contract.Spec,
177
+ contractAddress: string,
178
+ specEntries?: xdr.ScSpecEntry[],
179
+ networkConfig?: StellarNetworkConfig
180
+ ): Promise<ContractFunction[]> {
181
+ try {
182
+ // Get all functions using the official SDK method
183
+ const specFunctions = spec.funcs();
184
+
185
+ logger.info('extractFunctionsFromSpec', `Found ${specFunctions.length} functions in spec`);
186
+
187
+ return await Promise.all(
188
+ specFunctions.map(async (func, index) => {
189
+ try {
190
+ // Extract function name using the official SDK method
191
+ const functionName = func.name().toString();
192
+
193
+ logger.info('extractFunctionsFromSpec', `Processing function: ${functionName}`);
194
+
195
+ // Get function inputs and outputs using the official SDK methods
196
+ const inputs: FunctionParameter[] = func.inputs().map((input, inputIndex) => {
197
+ try {
198
+ const inputName = input.name().toString();
199
+ const inputType = extractSorobanTypeFromScSpec(input.type());
200
+
201
+ if (inputType === 'unknown') {
202
+ logger.warn(
203
+ 'extractFunctionsFromSpec',
204
+ `Unknown type for parameter "${inputName}" in function "${functionName}"`
205
+ );
206
+ }
207
+
208
+ // Check if this is a struct type and extract components
209
+ let components: FunctionParameter[] | undefined;
210
+ if (specEntries && specEntries.length > 0 && isStructType(specEntries, inputType)) {
211
+ const structFields = extractStructFields(specEntries, inputType);
212
+ if (structFields && structFields.length > 0) {
213
+ components = structFields;
214
+ logger.debug(
215
+ 'extractFunctionsFromSpec',
216
+ `Extracted ${structFields.length} fields for struct type "${inputType}": ${structFields.map((f) => `${f.name}:${f.type}`).join(', ')}`
217
+ );
218
+ } else {
219
+ logger.warn(
220
+ 'extractFunctionsFromSpec',
221
+ `No fields extracted for struct "${inputType}"`
222
+ );
223
+ }
224
+ }
225
+
226
+ return {
227
+ name: inputName || `param_${inputIndex}`,
228
+ type: inputType,
229
+ ...(components && { components }),
230
+ };
231
+ } catch (error) {
232
+ logger.warn(
233
+ 'extractFunctionsFromSpec',
234
+ `Failed to parse input ${inputIndex}:`,
235
+ error
236
+ );
237
+ return {
238
+ name: `param_${inputIndex}`,
239
+ type: 'unknown',
240
+ };
241
+ }
242
+ });
243
+
244
+ const outputs: FunctionParameter[] = func.outputs().map((output, outputIndex) => {
245
+ try {
246
+ // Outputs are ScSpecTypeDef objects, they don't have names, only types
247
+ const outputType = extractSorobanTypeFromScSpec(output);
248
+
249
+ return {
250
+ name: `result_${outputIndex}`,
251
+ type: outputType,
252
+ };
253
+ } catch (error) {
254
+ logger.warn(
255
+ 'extractFunctionsFromSpec',
256
+ `Failed to parse output ${outputIndex}:`,
257
+ error
258
+ );
259
+ return {
260
+ name: `result_${outputIndex}`,
261
+ type: 'unknown',
262
+ };
263
+ }
264
+ });
265
+
266
+ // Determine if function is read-only (view function) using simulation-based detection
267
+ // This follows the same approach as the official Stellar Laboratory
268
+ let modifiesState = true; // Default assumption for safety
269
+ let stateMutability: 'view' | 'pure' | 'nonpayable' = 'nonpayable';
270
+
271
+ if (networkConfig) {
272
+ try {
273
+ // Extract input types for simulation
274
+ const inputTypes = inputs.map((input) => input.type);
275
+
276
+ logger.debug(
277
+ 'extractFunctionsFromSpec',
278
+ `Checking state mutability for ${functionName} with input types: ${inputTypes.join(', ')}`
279
+ );
280
+
281
+ // Use simulation-based state mutability detection
282
+ modifiesState = await checkStellarFunctionStateMutability(
283
+ contractAddress,
284
+ functionName,
285
+ networkConfig,
286
+ inputTypes
287
+ );
288
+
289
+ stateMutability = modifiesState ? 'nonpayable' : 'view';
290
+
291
+ logger.info(
292
+ 'extractFunctionsFromSpec',
293
+ `Function ${functionName} state mutability determined:`,
294
+ { modifiesState, stateMutability }
295
+ );
296
+ } catch (error) {
297
+ logger.warn(
298
+ 'extractFunctionsFromSpec',
299
+ `Failed to determine state mutability for ${functionName}, assuming it modifies state:`,
300
+ error
301
+ );
302
+ // Keep defaults: modifiesState = true, stateMutability = 'nonpayable'
303
+ }
304
+ } else {
305
+ logger.warn(
306
+ 'extractFunctionsFromSpec',
307
+ `No network config provided for ${functionName}, assuming it modifies state`
308
+ );
309
+ }
310
+
311
+ // Generate a unique ID for the function
312
+ const functionId = `${functionName}_${inputs.map((i) => i.type).join('_')}`;
313
+
314
+ return {
315
+ id: functionId,
316
+ name: functionName,
317
+ displayName:
318
+ functionName.charAt(0).toUpperCase() + functionName.slice(1).replace(/_/g, ' '),
319
+ description: `Soroban function: ${functionName}`,
320
+ inputs,
321
+ outputs,
322
+ type: 'function',
323
+ modifiesState,
324
+ stateMutability,
325
+ };
326
+ } catch (error) {
327
+ logger.error('extractFunctionsFromSpec', `Failed to process function ${index}:`, error);
328
+
329
+ // Return a basic function entry for failed parsing
330
+ return {
331
+ id: `function_${index}`,
332
+ name: `function_${index}`,
333
+ displayName: `Function ${index}`,
334
+ description: `Failed to parse function ${index}: ${(error as Error).message}`,
335
+ inputs: [],
336
+ outputs: [],
337
+ type: 'function',
338
+ modifiesState: true,
339
+ stateMutability: 'nonpayable',
340
+ };
341
+ }
342
+ })
343
+ );
344
+ } catch (error) {
345
+ logger.error('extractFunctionsFromSpec', 'Failed to extract functions from spec:', error);
346
+ throw new Error(`Failed to extract functions: ${(error as Error).message}`);
347
+ }
348
+ }
349
+
350
+ /**
351
+ * Enhanced result type for Stellar contract loading with metadata
352
+ */
353
+ export interface StellarContractLoadResult {
354
+ schema: ContractSchema;
355
+ source: 'fetched' | 'manual';
356
+ contractDefinitionOriginal?: string;
357
+ metadata?: {
358
+ fetchedFrom?: string;
359
+ contractName?: string;
360
+ fetchTimestamp?: Date;
361
+ definitionHash?: string;
362
+ };
363
+ }
364
+
365
+ /**
366
+ * Load Stellar contract with basic metadata
367
+ */
368
+ export async function loadStellarContract(
369
+ artifacts: StellarContractArtifacts,
370
+ networkConfig: StellarNetworkConfig
371
+ ): Promise<StellarContractLoadResult> {
372
+ if (typeof artifacts.contractAddress !== 'string') {
373
+ throw new Error('A contract address must be provided.');
374
+ }
375
+
376
+ const schema = await loadStellarContractFromAddress(artifacts.contractAddress, networkConfig);
377
+
378
+ const schemaWithAddress = { ...schema, address: artifacts.contractAddress };
379
+
380
+ return {
381
+ schema: schemaWithAddress,
382
+ source: 'fetched',
383
+ contractDefinitionOriginal: JSON.stringify(schemaWithAddress),
384
+ metadata: {
385
+ fetchedFrom:
386
+ getStellarExplorerAddressUrl(artifacts.contractAddress, networkConfig) ||
387
+ networkConfig.sorobanRpcUrl,
388
+ contractName: schema.name,
389
+ fetchTimestamp: new Date(),
390
+ },
391
+ };
392
+ }
393
+
394
+ /**
395
+ * Load Stellar contract with extended metadata
396
+ */
397
+ export async function loadStellarContractWithMetadata(
398
+ artifacts: StellarContractArtifacts,
399
+ networkConfig: StellarNetworkConfig
400
+ ): Promise<StellarContractLoadResult> {
401
+ if (typeof artifacts.contractAddress !== 'string') {
402
+ throw new Error('A contract address must be provided.');
403
+ }
404
+
405
+ try {
406
+ const contractData = await loadStellarContractFromAddress(
407
+ artifacts.contractAddress,
408
+ networkConfig
409
+ );
410
+
411
+ const schema = {
412
+ ...contractData,
413
+ address: artifacts.contractAddress,
414
+ };
415
+
416
+ return {
417
+ schema,
418
+ source: 'fetched',
419
+ contractDefinitionOriginal: JSON.stringify(schema),
420
+ metadata: {
421
+ fetchedFrom:
422
+ getStellarExplorerAddressUrl(artifacts.contractAddress, networkConfig) ||
423
+ networkConfig.sorobanRpcUrl,
424
+ contractName: schema.name,
425
+ fetchTimestamp: new Date(),
426
+ },
427
+ };
428
+ } catch (error) {
429
+ // Check if this is a network/connection error
430
+ const errorMessage = (error as Error).message || '';
431
+ // Surface Laboratory-style explicit message if Wasm is missing
432
+ if (errorMessage.startsWith('NO_WASM:')) {
433
+ // Re-throw without swallowing details so UI can show this immediately
434
+ throw new Error(errorMessage.replace(/^NO_WASM:\s*/, ''));
435
+ }
436
+ if (errorMessage.includes('Failed to load contract')) {
437
+ throw new Error(
438
+ `Contract at ${artifacts.contractAddress} could not be loaded from the network. ` +
439
+ `Please verify the contract ID is correct and the network is accessible.`
440
+ );
441
+ }
442
+
443
+ // Re-throw other errors
444
+ throw error;
445
+ }
446
+ }
447
+
448
+ /**
449
+ * Integration points for manual contract definition input (future work):
450
+ *
451
+ * Single Input with Auto-Detection (simplified UX):
452
+ * - Add a new loader path: `loadStellarContractFromDefinition(definition, networkConfig)`
453
+ * - Auto-detect content type using magic bytes and structure:
454
+ * - Wasm binary: starts with magic bytes `[0x00, 0x61, 0x73, 0x6D]` (`\0asm`)
455
+ * - JSON spec: valid JSON array with Soroban spec entry objects
456
+ * - For JSON: Parse and validate, use `transformStellarSpecToSchema()` to build schema
457
+ * - For Wasm: Extract embedded spec from binary locally (no RPC), then build schema
458
+ * - Return `{ schema, source: 'manual' }` with `contractDefinitionOriginal` set to
459
+ * the raw input (JSON string or Wasm binary) for auto-save restoration
460
+ *
461
+ * The builder UI provides a single input field (code editor with file upload support)
462
+ * that accepts either format, eliminating user confusion about format selection.
463
+ * The auto-save system will store the resulting schema and `contractDefinitionOriginal`
464
+ * so the configuration restores seamlessly.
465
+ */
466
+
467
+ /**
468
+ * Transform Stellar contract spec to our internal schema format.
469
+ *
470
+ * This function is intentionally minimal at the moment and primarily used by
471
+ * tests. The production load path derives function metadata using the
472
+ * Stellar SDK via `loadStellarContractFromAddress`/`extractFunctionsFromSpec`.
473
+ * A full spec-to-schema converter (with robust type mapping for inputs/outputs
474
+ * and state mutability inference) is planned under the upcoming
475
+ * "Type Mapping and Data Transformation" work in
476
+ * `.agent-os/specs/2025-08-20-stellar-adapter-integration/tasks.md`.
477
+ */
478
+ export function transformStellarSpecToSchema(
479
+ contractSpec: Record<string, unknown>,
480
+ contractAddress: string,
481
+ ecosystem: 'stellar' = 'stellar'
482
+ ): ContractSchema {
483
+ logger.info('transformStellarSpecToSchema', 'Transforming Stellar spec to schema format');
484
+
485
+ const schema: ContractSchema = {
486
+ name: (contractSpec.name as string) || `Soroban Contract ${contractAddress.slice(0, 8)}...`,
487
+ ecosystem,
488
+ functions: (contractSpec.functions as ContractFunction[]) || [],
489
+ };
490
+
491
+ logger.info('transformStellarSpecToSchema', 'Generated schema:', {
492
+ name: schema.name,
493
+ ecosystem: schema.ecosystem,
494
+ functionCount: Array.isArray(schema.functions) ? schema.functions.length : 0,
495
+ });
496
+
497
+ return schema;
498
+ }
@@ -0,0 +1 @@
1
+ // Placeholder
@@ -0,0 +1,65 @@
1
+ import { Contract, rpc as StellarRpc, xdr } from '@stellar/stellar-sdk';
2
+
3
+ import type { StellarNetworkConfig } from '@openzeppelin/ui-types';
4
+ import { logger, userRpcConfigService } from '@openzeppelin/ui-utils';
5
+
6
+ /**
7
+ * Returns a Soroban RPC server instance honoring user overrides.
8
+ */
9
+ function getSorobanRpcServer(networkConfig: StellarNetworkConfig): StellarRpc.Server {
10
+ const customRpcConfig = userRpcConfigService.getUserRpcConfig(networkConfig.id);
11
+ const rpcUrl = customRpcConfig?.url || networkConfig.sorobanRpcUrl;
12
+ if (!rpcUrl) {
13
+ throw new Error(`No Soroban RPC URL available for network ${networkConfig.name}`);
14
+ }
15
+ const allowHttp = new URL(rpcUrl).hostname === 'localhost';
16
+ return new StellarRpc.Server(rpcUrl, { allowHttp });
17
+ }
18
+
19
+ export type StellarContractExecutableType =
20
+ | 'contractExecutableWasm'
21
+ | 'contractExecutableStellarAsset'
22
+ | null;
23
+
24
+ /**
25
+ * Detects executable type for a given Stellar contract ID via RPC ledger entries.
26
+ */
27
+ export async function getStellarContractType(
28
+ contractId: string,
29
+ networkConfig: StellarNetworkConfig
30
+ ): Promise<StellarContractExecutableType> {
31
+ try {
32
+ if (!contractId) {
33
+ return null;
34
+ }
35
+
36
+ const rpcServer = getSorobanRpcServer(networkConfig);
37
+
38
+ // Build ledger key footprint for the contract
39
+ const ledgerKey = new Contract(contractId).getFootprint();
40
+ const ledgerEntries = await rpcServer.getLedgerEntries(ledgerKey);
41
+
42
+ const first = ledgerEntries?.entries?.[0]?.val;
43
+ if (!first) {
44
+ throw new Error('Could not obtain contract data from server.');
45
+ }
46
+
47
+ const executable = first.contractData()?.val()?.instance()?.executable();
48
+ if (!executable) {
49
+ throw new Error('Could not get executable from contract data.');
50
+ }
51
+
52
+ const execWasmType = xdr.ContractExecutableType.contractExecutableWasm().name;
53
+ const execStellarAssetType = xdr.ContractExecutableType.contractExecutableStellarAsset().name;
54
+ const detected = executable.switch()?.name as string | undefined;
55
+
56
+ if (detected === execWasmType) return 'contractExecutableWasm';
57
+ if (detected === execStellarAssetType) return 'contractExecutableStellarAsset';
58
+ return null;
59
+ } catch (error) {
60
+ logger.error('stellar:contract-type', 'Failed to detect contract type:', error);
61
+ throw new Error(
62
+ `Something went wrong getting contract type by contract ID. ${(error as Error).message}`
63
+ );
64
+ }
65
+ }
package/src/index.ts ADDED
@@ -0,0 +1,23 @@
1
+ import type { EcosystemExport, StellarNetworkConfig } from '@openzeppelin/ui-types';
2
+
3
+ import { StellarAdapter } from './adapter';
4
+ import { stellarAdapterConfig } from './config';
5
+ import { ecosystemMetadata } from './metadata';
6
+ import { stellarNetworks } from './networks';
7
+
8
+ export { ecosystemMetadata } from './metadata';
9
+ export { StellarAdapter } from './adapter';
10
+
11
+ export const ecosystemDefinition: EcosystemExport = {
12
+ ...ecosystemMetadata,
13
+ networks: stellarNetworks,
14
+ createAdapter: (config) => new StellarAdapter(config as StellarNetworkConfig),
15
+ adapterConfig: stellarAdapterConfig,
16
+ };
17
+
18
+ // Adapter-specific types
19
+ export type { StellarContractArtifacts } from './types/artifacts';
20
+ export { isStellarContractArtifacts } from './types/artifacts';
21
+
22
+ // Individual network exports
23
+ export { stellarPublic, stellarTestnet } from './networks';
@@ -0,0 +1,89 @@
1
+ import type { FieldType, TypeMappingInfo } from '@openzeppelin/ui-types';
2
+
3
+ /**
4
+ * Stellar/Soroban-specific type mapping to default form field types.
5
+ * Based on Soroban type system: https://developers.stellar.org/docs/learn/fundamentals/contract-development/types
6
+ *
7
+ * Note: Large integer types (U64, U128, U256, I64, I128, I256) are mapped to 'bigint'
8
+ * instead of 'number' to avoid JavaScript's Number precision limitations.
9
+ * JavaScript's Number type can only safely represent integers up to 2^53 - 1,
10
+ * but these types can hold much larger values. The BigIntField component stores values
11
+ * as strings and the Stellar adapter handles conversion automatically.
12
+ */
13
+ export const STELLAR_TYPE_TO_FIELD_TYPE: Record<string, FieldType> = {
14
+ // Address types
15
+ Address: 'blockchain-address',
16
+ MuxedAddress: 'blockchain-address',
17
+
18
+ // String types
19
+ ScString: 'text',
20
+ ScSymbol: 'text',
21
+
22
+ // Numeric types - unsigned integers
23
+ U32: 'number',
24
+ U64: 'bigint',
25
+ U128: 'bigint',
26
+ U256: 'bigint',
27
+
28
+ // Numeric types - signed integers
29
+ I32: 'number',
30
+ I64: 'bigint',
31
+ I128: 'bigint',
32
+ I256: 'bigint',
33
+
34
+ // Boolean type
35
+ Bool: 'checkbox',
36
+
37
+ // Byte types
38
+ Bytes: 'bytes',
39
+ DataUrl: 'bytes',
40
+
41
+ // Collection types
42
+ Vec: 'array',
43
+ Map: 'map',
44
+
45
+ // Complex types
46
+ Tuple: 'object',
47
+ Enum: 'select',
48
+
49
+ // Instance types (for compatibility)
50
+ Instance: 'object',
51
+ };
52
+
53
+ /**
54
+ * Stellar dynamic type patterns handled through pattern matching.
55
+ */
56
+ const STELLAR_DYNAMIC_PATTERNS: TypeMappingInfo['dynamicPatterns'] = [
57
+ { name: 'vec', syntax: 'Vec<T>', mapsTo: null, description: 'Array (maps based on inner type)' },
58
+ { name: 'map', syntax: 'Map<K,V>', mapsTo: 'map', description: 'Key-value map' },
59
+ {
60
+ name: 'option',
61
+ syntax: 'Option<T>',
62
+ mapsTo: 'unwrap',
63
+ description: 'Optional, resolves to inner type',
64
+ },
65
+ {
66
+ name: 'result',
67
+ syntax: 'Result<T>',
68
+ mapsTo: 'unwrap',
69
+ description: 'Result, resolves to inner type',
70
+ },
71
+ { name: 'bytes-n', syntax: 'BytesN<N>', mapsTo: 'bytes', description: 'Fixed-size byte array' },
72
+ {
73
+ name: 'struct',
74
+ syntax: 'StructName',
75
+ mapsTo: 'object',
76
+ description: 'Custom struct (PascalCase)',
77
+ },
78
+ { name: 'enum', syntax: 'EnumName', mapsTo: 'select', description: 'Enum type' },
79
+ ];
80
+
81
+ /**
82
+ * Returns complete type mapping information for Stellar.
83
+ */
84
+ export function getStellarTypeMappingInfo(): TypeMappingInfo {
85
+ return {
86
+ primitives: { ...STELLAR_TYPE_TO_FIELD_TYPE },
87
+ dynamicPatterns: STELLAR_DYNAMIC_PATTERNS,
88
+ };
89
+ }