@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.
- package/README.md +272 -0
- package/dist/config.cjs +21 -0
- package/dist/config.cjs.map +1 -0
- package/dist/config.d.cts +8 -0
- package/dist/config.d.cts.map +1 -0
- package/dist/config.d.mts +8 -0
- package/dist/config.d.mts.map +1 -0
- package/dist/config.mjs +20 -0
- package/dist/config.mjs.map +1 -0
- package/dist/index.cjs +7564 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +261 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.mts +263 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +7529 -0
- package/dist/index.mjs.map +1 -0
- package/dist/metadata.cjs +22 -0
- package/dist/metadata.cjs.map +1 -0
- package/dist/metadata.d.cts +7 -0
- package/dist/metadata.d.cts.map +1 -0
- package/dist/metadata.d.mts +7 -0
- package/dist/metadata.d.mts.map +1 -0
- package/dist/metadata.mjs +21 -0
- package/dist/metadata.mjs.map +1 -0
- package/dist/networks-BrV516-R.d.cts +15 -0
- package/dist/networks-BrV516-R.d.cts.map +1 -0
- package/dist/networks-C0MmhJcu.d.mts +15 -0
- package/dist/networks-C0MmhJcu.d.mts.map +1 -0
- package/dist/networks-DgUFSTiC.cjs +76 -0
- package/dist/networks-DgUFSTiC.cjs.map +1 -0
- package/dist/networks-QbEPbaGT.mjs +46 -0
- package/dist/networks-QbEPbaGT.mjs.map +1 -0
- package/dist/networks.cjs +8 -0
- package/dist/networks.d.cts +2 -0
- package/dist/networks.d.mts +2 -0
- package/dist/networks.mjs +3 -0
- package/dist/vite-config.cjs +43 -0
- package/dist/vite-config.cjs.map +1 -0
- package/dist/vite-config.d.cts +35 -0
- package/dist/vite-config.d.cts.map +1 -0
- package/dist/vite-config.d.mts +35 -0
- package/dist/vite-config.d.mts.map +1 -0
- package/dist/vite-config.mjs +42 -0
- package/dist/vite-config.mjs.map +1 -0
- package/package.json +114 -0
- package/src/__tests__/getDefaultServiceConfig.test.ts +105 -0
- package/src/access-control/actions.ts +214 -0
- package/src/access-control/feature-detection.ts +238 -0
- package/src/access-control/index.ts +54 -0
- package/src/access-control/indexer-client.ts +1474 -0
- package/src/access-control/onchain-reader.ts +446 -0
- package/src/access-control/service.ts +1431 -0
- package/src/access-control/validation.ts +256 -0
- package/src/adapter.ts +659 -0
- package/src/config.ts +43 -0
- package/src/configuration/__tests__/explorer.test.ts +80 -0
- package/src/configuration/__tests__/rpc.test.ts +355 -0
- package/src/configuration/execution.ts +83 -0
- package/src/configuration/explorer.ts +105 -0
- package/src/configuration/index.ts +5 -0
- package/src/configuration/network-services.ts +210 -0
- package/src/configuration/rpc.ts +270 -0
- package/src/configuration.ts +2 -0
- package/src/contract/__tests__/complete-type-coverage.test.ts +78 -0
- package/src/contract/index.ts +3 -0
- package/src/contract/loader.ts +498 -0
- package/src/contract/transformer.ts +1 -0
- package/src/contract/type.ts +65 -0
- package/src/index.ts +23 -0
- package/src/mapping/constants.ts +89 -0
- package/src/mapping/enum-metadata.ts +237 -0
- package/src/mapping/field-generator.ts +296 -0
- package/src/mapping/index.ts +5 -0
- package/src/mapping/struct-fields.ts +106 -0
- package/src/mapping/tuple-components.ts +43 -0
- package/src/mapping/type-coverage-validator.ts +151 -0
- package/src/mapping/type-mapper.ts +203 -0
- package/src/metadata.ts +16 -0
- package/src/networks/README.md +84 -0
- package/src/networks/index.ts +19 -0
- package/src/networks/mainnet.ts +20 -0
- package/src/networks/testnet.ts +20 -0
- package/src/networks.ts +2 -0
- package/src/query/handler.ts +411 -0
- package/src/query/index.ts +4 -0
- package/src/query/view-checker.ts +32 -0
- package/src/sac/spec-cache.ts +68 -0
- package/src/sac/spec-source.ts +35 -0
- package/src/sac/xdr.ts +101 -0
- package/src/transaction/components/AdvancedInfo.tsx +34 -0
- package/src/transaction/components/FeeConfiguration.tsx +41 -0
- package/src/transaction/components/StellarRelayerOptions.tsx +60 -0
- package/src/transaction/components/TransactionTiming.tsx +77 -0
- package/src/transaction/components/index.ts +5 -0
- package/src/transaction/components/useStellarRelayerOptions.ts +114 -0
- package/src/transaction/eoa.ts +229 -0
- package/src/transaction/execution-strategy.ts +33 -0
- package/src/transaction/formatter.ts +296 -0
- package/src/transaction/index.ts +4 -0
- package/src/transaction/relayer.ts +575 -0
- package/src/transaction/sender.ts +156 -0
- package/src/transform/index.ts +4 -0
- package/src/transform/input-parser.ts +9 -0
- package/src/transform/output-formatter.ts +133 -0
- package/src/transform/parsers/complex-parser.ts +157 -0
- package/src/transform/parsers/generic-parser.ts +171 -0
- package/src/transform/parsers/index.ts +86 -0
- package/src/transform/parsers/primitive-parser.ts +123 -0
- package/src/transform/parsers/scval-converter.ts +405 -0
- package/src/transform/parsers/struct-parser.ts +324 -0
- package/src/transform/parsers/types.ts +35 -0
- package/src/types/__tests__/artifacts.test.ts +89 -0
- package/src/types/artifacts.ts +19 -0
- package/src/utils/__tests__/artifacts.test.ts +77 -0
- package/src/utils/artifacts.ts +30 -0
- package/src/utils/formatting.ts +122 -0
- package/src/utils/index.ts +6 -0
- package/src/utils/input-parsing.ts +336 -0
- package/src/utils/safe-type-parser.ts +303 -0
- package/src/utils/stellar-types.ts +35 -0
- package/src/utils/type-detection.ts +163 -0
- package/src/utils/xdr-ordering.ts +36 -0
- package/src/validation/__tests__/address.test.ts +267 -0
- package/src/validation/address.ts +136 -0
- package/src/validation/eoa.ts +33 -0
- package/src/validation/index.ts +3 -0
- package/src/validation/relayer.ts +13 -0
- package/src/vite-config.ts +67 -0
- package/src/wallet/README.md +93 -0
- package/src/wallet/__tests__/connection.test.ts +72 -0
- package/src/wallet/components/StellarWalletUiRoot.tsx +161 -0
- package/src/wallet/components/account/AccountDisplay.tsx +50 -0
- package/src/wallet/components/connect/ConnectButton.tsx +100 -0
- package/src/wallet/components/connect/ConnectorDialog.tsx +125 -0
- package/src/wallet/components/index.ts +3 -0
- package/src/wallet/connection.ts +151 -0
- package/src/wallet/context/StellarWalletContext.ts +32 -0
- package/src/wallet/context/index.ts +4 -0
- package/src/wallet/context/useStellarWalletContext.ts +17 -0
- package/src/wallet/hooks/facade-hooks.ts +31 -0
- package/src/wallet/hooks/index.ts +7 -0
- package/src/wallet/hooks/useStellarAccount.ts +27 -0
- package/src/wallet/hooks/useStellarConnect.ts +60 -0
- package/src/wallet/hooks/useStellarDisconnect.ts +47 -0
- package/src/wallet/hooks/useUiKitConfig.ts +40 -0
- package/src/wallet/implementation/wallets-kit-implementation.ts +379 -0
- package/src/wallet/index.ts +11 -0
- package/src/wallet/services/__tests__/configResolutionService.test.ts +163 -0
- package/src/wallet/services/configResolutionService.ts +65 -0
- package/src/wallet/stellar-wallets-kit/StellarWalletsKitConnectButton.tsx +82 -0
- package/src/wallet/stellar-wallets-kit/__mocks__/@creit.tech/stellar-wallets-kit.ts +48 -0
- package/src/wallet/stellar-wallets-kit/__tests__/export-service.test.ts +93 -0
- package/src/wallet/stellar-wallets-kit/__tests__/stellarUiKitManager.test.ts +0 -0
- package/src/wallet/stellar-wallets-kit/config-generator.ts +75 -0
- package/src/wallet/stellar-wallets-kit/export-service.ts +19 -0
- package/src/wallet/stellar-wallets-kit/index.ts +3 -0
- package/src/wallet/stellar-wallets-kit/stellarUiKitManager.ts +235 -0
- package/src/wallet/types.ts +19 -0
- package/src/wallet/utils/__tests__/filterWalletComponents.test.ts +150 -0
- package/src/wallet/utils/__tests__/uiKitService.test.ts +189 -0
- package/src/wallet/utils/filterWalletComponents.ts +89 -0
- package/src/wallet/utils/index.ts +3 -0
- package/src/wallet/utils/stellarWalletImplementationManager.ts +118 -0
- package/src/wallet/utils/uiKitService.ts +74 -0
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import { xdr } from '@stellar/stellar-sdk';
|
|
2
|
+
|
|
3
|
+
import type { FunctionParameter } from '@openzeppelin/ui-types';
|
|
4
|
+
import { logger } from '@openzeppelin/ui-utils';
|
|
5
|
+
|
|
6
|
+
// Import the type extraction utility from the shared utils module
|
|
7
|
+
import { extractSorobanTypeFromScSpec } from '../utils/type-detection';
|
|
8
|
+
import { extractStructFields, isStructType } from './struct-fields';
|
|
9
|
+
import { buildTupleComponents } from './tuple-components';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Represents a single enum variant with its type and optional payload information
|
|
13
|
+
*/
|
|
14
|
+
export interface EnumVariant {
|
|
15
|
+
/** Name of the variant (e.g., 'One', 'Two', 'Three') */
|
|
16
|
+
name: string;
|
|
17
|
+
/** Type of variant: 'void' for unit variants, 'tuple' for variants with payload, 'integer' for numeric enums */
|
|
18
|
+
type: 'void' | 'tuple' | 'integer';
|
|
19
|
+
/** For tuple variants: array of payload type names (e.g., ['U32', 'ScString']) */
|
|
20
|
+
payloadTypes?: string[];
|
|
21
|
+
/** Optional detailed component metadata for payload types */
|
|
22
|
+
payloadComponents?: (FunctionParameter[] | undefined)[];
|
|
23
|
+
/** For integer variants: the numeric value */
|
|
24
|
+
value?: number;
|
|
25
|
+
/** Flag indicating if this variant has a single Tuple payload that needs wrapping during serialization */
|
|
26
|
+
isSingleTuplePayload?: boolean;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Metadata about an enum extracted from Stellar contract spec
|
|
31
|
+
*/
|
|
32
|
+
export interface EnumMetadata {
|
|
33
|
+
/** Name of the enum type */
|
|
34
|
+
name: string;
|
|
35
|
+
/** Array of variants in the enum */
|
|
36
|
+
variants: EnumVariant[];
|
|
37
|
+
/** True if all variants are unit variants (no payloads), suitable for simple select/radio */
|
|
38
|
+
isUnitOnly: boolean;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Helper function to flatten a single payload type.
|
|
43
|
+
* Handles tuples, structs, and primitive types differently.
|
|
44
|
+
*
|
|
45
|
+
* @param payloadType - The type to flatten
|
|
46
|
+
* @param entries - Spec entries for struct/enum resolution
|
|
47
|
+
* @param flattenedTypes - Array to accumulate flattened type names
|
|
48
|
+
* @param flattenedComponents - Array to accumulate component metadata
|
|
49
|
+
*/
|
|
50
|
+
function flattenPayloadType(
|
|
51
|
+
payloadType: string,
|
|
52
|
+
entries: xdr.ScSpecEntry[],
|
|
53
|
+
flattenedTypes: string[],
|
|
54
|
+
flattenedComponents: (FunctionParameter[] | undefined)[]
|
|
55
|
+
): void {
|
|
56
|
+
if (payloadType.startsWith('Tuple<')) {
|
|
57
|
+
// Extract tuple components and add them individually for UI rendering
|
|
58
|
+
const tupleComponents = buildTupleComponents(payloadType, entries);
|
|
59
|
+
if (tupleComponents && tupleComponents.length > 0) {
|
|
60
|
+
tupleComponents.forEach((component) => {
|
|
61
|
+
flattenedTypes.push(component.type);
|
|
62
|
+
if (isStructType(entries, component.type)) {
|
|
63
|
+
flattenedComponents.push(extractStructFields(entries, component.type) ?? undefined);
|
|
64
|
+
} else {
|
|
65
|
+
flattenedComponents.push(component.components);
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
} else {
|
|
69
|
+
flattenedTypes.push(payloadType);
|
|
70
|
+
flattenedComponents.push(undefined);
|
|
71
|
+
}
|
|
72
|
+
} else if (isStructType(entries, payloadType)) {
|
|
73
|
+
flattenedTypes.push(payloadType);
|
|
74
|
+
flattenedComponents.push(extractStructFields(entries, payloadType) ?? undefined);
|
|
75
|
+
} else {
|
|
76
|
+
flattenedTypes.push(payloadType);
|
|
77
|
+
flattenedComponents.push(undefined);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Extracts enum variant metadata from Stellar contract spec entries
|
|
83
|
+
*
|
|
84
|
+
* @param entries Array of ScSpecEntry from contract spec
|
|
85
|
+
* @param enumName Name of the enum type to extract variants for
|
|
86
|
+
* @returns EnumMetadata if found, null if not found or not an enum
|
|
87
|
+
*/
|
|
88
|
+
export function extractEnumVariants(
|
|
89
|
+
entries: xdr.ScSpecEntry[],
|
|
90
|
+
enumName: string
|
|
91
|
+
): EnumMetadata | null {
|
|
92
|
+
try {
|
|
93
|
+
// Find the entry for the requested enum name
|
|
94
|
+
const entry = entries.find((e) => {
|
|
95
|
+
try {
|
|
96
|
+
return e.value().name().toString() === enumName;
|
|
97
|
+
} catch {
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
if (!entry) {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const entryKind = entry.switch();
|
|
107
|
+
|
|
108
|
+
// Handle UDT Union (tagged union enum like DemoEnum { One, Two(u32), Three(String) })
|
|
109
|
+
if (entryKind.value === xdr.ScSpecEntryKind.scSpecEntryUdtUnionV0().value) {
|
|
110
|
+
const unionUdt = entry.udtUnionV0();
|
|
111
|
+
const cases = unionUdt.cases();
|
|
112
|
+
const variants: EnumVariant[] = [];
|
|
113
|
+
let isUnitOnly = true;
|
|
114
|
+
|
|
115
|
+
for (const caseEntry of cases) {
|
|
116
|
+
const caseKind = caseEntry.switch();
|
|
117
|
+
|
|
118
|
+
if (caseKind.value === xdr.ScSpecUdtUnionCaseV0Kind.scSpecUdtUnionCaseVoidV0().value) {
|
|
119
|
+
// Void case (unit variant)
|
|
120
|
+
const voidCase = caseEntry.voidCase();
|
|
121
|
+
variants.push({
|
|
122
|
+
name: voidCase.name().toString(),
|
|
123
|
+
type: 'void',
|
|
124
|
+
});
|
|
125
|
+
} else if (
|
|
126
|
+
caseKind.value === xdr.ScSpecUdtUnionCaseV0Kind.scSpecUdtUnionCaseTupleV0().value
|
|
127
|
+
) {
|
|
128
|
+
// Tuple case (variant with payload)
|
|
129
|
+
const tupleCase = caseEntry.tupleCase();
|
|
130
|
+
const rawPayloadTypes = tupleCase
|
|
131
|
+
.type()
|
|
132
|
+
.map((typeDef) => extractSorobanTypeFromScSpec(typeDef));
|
|
133
|
+
|
|
134
|
+
// Track if we have a single Tuple payload that needs special handling
|
|
135
|
+
const isSingleTuplePayload =
|
|
136
|
+
rawPayloadTypes.length === 1 && rawPayloadTypes[0].startsWith('Tuple<');
|
|
137
|
+
|
|
138
|
+
// Flatten tuple payloads for UI rendering
|
|
139
|
+
// Example: Some((Address, i128)) → payloadTypes: ['Address', 'I128'] for UI
|
|
140
|
+
// But we keep the original structure info for serialization
|
|
141
|
+
const flattenedPayloadTypes: string[] = [];
|
|
142
|
+
const flattenedPayloadComponents: (FunctionParameter[] | undefined)[] = [];
|
|
143
|
+
|
|
144
|
+
for (const payloadType of rawPayloadTypes) {
|
|
145
|
+
flattenPayloadType(
|
|
146
|
+
payloadType,
|
|
147
|
+
entries,
|
|
148
|
+
flattenedPayloadTypes,
|
|
149
|
+
flattenedPayloadComponents
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
variants.push({
|
|
154
|
+
name: tupleCase.name().toString(),
|
|
155
|
+
type: 'tuple',
|
|
156
|
+
payloadTypes: flattenedPayloadTypes,
|
|
157
|
+
...(flattenedPayloadComponents.some(
|
|
158
|
+
(components) => components && components.length > 0
|
|
159
|
+
) && {
|
|
160
|
+
payloadComponents: flattenedPayloadComponents,
|
|
161
|
+
}),
|
|
162
|
+
// Store metadata about whether this needs tuple wrapping during serialization
|
|
163
|
+
...(isSingleTuplePayload && { isSingleTuplePayload: true }),
|
|
164
|
+
});
|
|
165
|
+
isUnitOnly = false;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return {
|
|
170
|
+
name: enumName,
|
|
171
|
+
variants,
|
|
172
|
+
isUnitOnly,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Handle UDT Enum (integer enum like Priority { Low = 0, Medium = 1, High = 2 })
|
|
177
|
+
if (entryKind.value === xdr.ScSpecEntryKind.scSpecEntryUdtEnumV0().value) {
|
|
178
|
+
const enumUdt = entry.udtEnumV0();
|
|
179
|
+
const cases = enumUdt.cases();
|
|
180
|
+
const variants: EnumVariant[] = [];
|
|
181
|
+
|
|
182
|
+
for (const caseEntry of cases) {
|
|
183
|
+
variants.push({
|
|
184
|
+
name: caseEntry.name().toString(),
|
|
185
|
+
type: 'integer',
|
|
186
|
+
value: caseEntry.value(),
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return {
|
|
191
|
+
name: enumName,
|
|
192
|
+
variants,
|
|
193
|
+
isUnitOnly: true, // Integer enums are considered unit-only for UI purposes
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return null;
|
|
198
|
+
} catch (error) {
|
|
199
|
+
logger.error('extractEnumVariants', `Failed to extract enum variants for ${enumName}:`, error);
|
|
200
|
+
return null;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Checks if a parameter type name refers to a user-defined enum/union type
|
|
206
|
+
* by looking for it in the contract spec entries
|
|
207
|
+
*
|
|
208
|
+
* @param entries Array of ScSpecEntry from contract spec
|
|
209
|
+
* @param typeName Name of the type to check
|
|
210
|
+
* @returns true if the type is a UDT enum or union
|
|
211
|
+
*/
|
|
212
|
+
export function isEnumType(entries: xdr.ScSpecEntry[], typeName: string): boolean {
|
|
213
|
+
try {
|
|
214
|
+
const entry = entries.find((e) => {
|
|
215
|
+
try {
|
|
216
|
+
const entryName = e.value().name().toString();
|
|
217
|
+
return entryName === typeName;
|
|
218
|
+
} catch {
|
|
219
|
+
return false;
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
if (!entry) {
|
|
224
|
+
return false;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const entryKind = entry.switch();
|
|
228
|
+
const isEnum =
|
|
229
|
+
entryKind.value === xdr.ScSpecEntryKind.scSpecEntryUdtUnionV0().value ||
|
|
230
|
+
entryKind.value === xdr.ScSpecEntryKind.scSpecEntryUdtEnumV0().value;
|
|
231
|
+
|
|
232
|
+
return isEnum;
|
|
233
|
+
} catch (error) {
|
|
234
|
+
logger.error('isEnumType', `Failed to check if ${typeName} is enum:`, error);
|
|
235
|
+
return false;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
import { xdr } from '@stellar/stellar-sdk';
|
|
2
|
+
import { startCase } from 'lodash';
|
|
3
|
+
|
|
4
|
+
import type {
|
|
5
|
+
ContractSchema,
|
|
6
|
+
FieldType,
|
|
7
|
+
FieldValidation,
|
|
8
|
+
FieldValue,
|
|
9
|
+
FormFieldType,
|
|
10
|
+
FunctionParameter,
|
|
11
|
+
} from '@openzeppelin/ui-types';
|
|
12
|
+
import {
|
|
13
|
+
enhanceNumericValidation,
|
|
14
|
+
getDefaultValueForType,
|
|
15
|
+
logger,
|
|
16
|
+
type NumericBoundsMap,
|
|
17
|
+
} from '@openzeppelin/ui-utils';
|
|
18
|
+
|
|
19
|
+
import { extractMapTypes, extractVecElementType } from '../utils/safe-type-parser';
|
|
20
|
+
import { isBytesNType, isLikelyEnumType } from '../utils/type-detection';
|
|
21
|
+
import { extractEnumVariants, isEnumType, type EnumMetadata } from './enum-metadata';
|
|
22
|
+
import { extractStructFields, isStructType } from './struct-fields';
|
|
23
|
+
import { buildTupleComponents } from './tuple-components';
|
|
24
|
+
import { mapStellarParameterTypeToFieldType } from './type-mapper';
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Get default validation rules for a parameter type.
|
|
28
|
+
* Only includes serializable validation rules - no custom functions.
|
|
29
|
+
*/
|
|
30
|
+
function getDefaultValidationForType(): FieldValidation {
|
|
31
|
+
return { required: true };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Stellar/Soroban numeric type bounds.
|
|
36
|
+
* Maps Stellar type names to their min/max value constraints.
|
|
37
|
+
*
|
|
38
|
+
* Note: U64, U128, U256, I64, I128, and I256 are not included here because they exceed
|
|
39
|
+
* JavaScript's Number.MAX_SAFE_INTEGER (2^53 - 1). These types are mapped to 'bigint' field type
|
|
40
|
+
* and handle validation through BigIntField component's internal validation mechanism.
|
|
41
|
+
*/
|
|
42
|
+
const STELLAR_NUMERIC_BOUNDS: NumericBoundsMap = {
|
|
43
|
+
U8: { min: 0, max: 255 },
|
|
44
|
+
U16: { min: 0, max: 65_535 },
|
|
45
|
+
U32: { min: 0, max: 4_294_967_295 },
|
|
46
|
+
I8: { min: -128, max: 127 },
|
|
47
|
+
I16: { min: -32_768, max: 32_767 },
|
|
48
|
+
I32: { min: -2_147_483_648, max: 2_147_483_647 },
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Generate default form configuration for a Stellar function parameter. Supports:
|
|
53
|
+
* - WASM artifacts: parameters arrive with baked-in enum metadata and struct components.
|
|
54
|
+
* - SAC runtime specs: metadata is fetched on demand, so we reconstruct the same shape from
|
|
55
|
+
* `metadata.specEntries` to keep the UI identical across contract types.
|
|
56
|
+
*/
|
|
57
|
+
export function generateStellarDefaultField<T extends FieldType = FieldType>(
|
|
58
|
+
parameter: FunctionParameter,
|
|
59
|
+
contractSchema?: ContractSchema
|
|
60
|
+
): FormFieldType<T> {
|
|
61
|
+
// Extract spec entries from contract schema metadata if available
|
|
62
|
+
const specEntries = contractSchema?.metadata?.specEntries as xdr.ScSpecEntry[] | undefined;
|
|
63
|
+
const fieldType = mapStellarParameterTypeToFieldType(parameter.type) as T;
|
|
64
|
+
|
|
65
|
+
// Debug logging for unmapped types
|
|
66
|
+
if (parameter.type === 'unknown') {
|
|
67
|
+
logger.warn(
|
|
68
|
+
'adapter-stellar',
|
|
69
|
+
`[generateStellarDefaultField] Parameter "${parameter.name}" has type "unknown"`
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
let enumMetadata: EnumMetadata | null = null;
|
|
74
|
+
// WASM artifacts still include `components` on struct inputs because we bake them during
|
|
75
|
+
// the build step. SAC contracts fetch their spec at runtime, so struct parameters show up
|
|
76
|
+
// without that data. Cache whatever the SAC spec gives us so the final field mirrors the
|
|
77
|
+
// WASM experience.
|
|
78
|
+
let structComponents: FunctionParameter[] | null = null;
|
|
79
|
+
let finalFieldType = fieldType;
|
|
80
|
+
let options: { label: string; value: string }[] | undefined;
|
|
81
|
+
|
|
82
|
+
// Check if this parameter is an enum type (by spec or heuristic) and extract metadata
|
|
83
|
+
const enumDetectedBySpec = !!(specEntries && isEnumType(specEntries, parameter.type));
|
|
84
|
+
const enumDetectedHeuristic = isLikelyEnumType(parameter.type);
|
|
85
|
+
if (enumDetectedBySpec || enumDetectedHeuristic) {
|
|
86
|
+
if (enumDetectedBySpec) {
|
|
87
|
+
// We have spec entries, extract full metadata
|
|
88
|
+
enumMetadata = extractEnumVariants(specEntries!, parameter.type);
|
|
89
|
+
if (enumMetadata) {
|
|
90
|
+
if (enumMetadata.isUnitOnly) {
|
|
91
|
+
// Unit-only enums can use select/radio with options
|
|
92
|
+
finalFieldType = 'select' as T;
|
|
93
|
+
options = enumMetadata.variants.map((variant) => ({
|
|
94
|
+
label: variant.name,
|
|
95
|
+
value: variant.type === 'integer' ? variant.value!.toString() : variant.name,
|
|
96
|
+
}));
|
|
97
|
+
} else {
|
|
98
|
+
// Tagged enums with payloads use the composite enum field
|
|
99
|
+
finalFieldType = 'enum' as T;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
} else {
|
|
103
|
+
// No spec entries available, but type looks like enum - use enum field as fallback
|
|
104
|
+
finalFieldType = 'enum' as T;
|
|
105
|
+
// Create minimal enum metadata for fallback
|
|
106
|
+
enumMetadata = {
|
|
107
|
+
name: parameter.type,
|
|
108
|
+
variants: [], // Empty variants will trigger fallback UI
|
|
109
|
+
isUnitOnly: false,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Same story here: WASM schemas already embed struct components, but SAC schemas only
|
|
115
|
+
// expose them inside the downloaded spec. Pull them out up front so the UI sees the
|
|
116
|
+
// same shape regardless of how the metadata was sourced.
|
|
117
|
+
if (specEntries && isStructType(specEntries, parameter.type)) {
|
|
118
|
+
const structFields = extractStructFields(specEntries, parameter.type);
|
|
119
|
+
if (structFields && structFields.length > 0) {
|
|
120
|
+
structComponents = structFields;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (!structComponents) {
|
|
125
|
+
const tupleComponents = buildTupleComponents(parameter.type, specEntries);
|
|
126
|
+
if (tupleComponents && tupleComponents.length > 0) {
|
|
127
|
+
structComponents = tupleComponents;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const baseField: FormFieldType<T> = {
|
|
132
|
+
id: `field-${Math.random().toString(36).substring(2, 9)}`,
|
|
133
|
+
name: parameter.name || parameter.type, // Use type if name missing
|
|
134
|
+
label: startCase(parameter.displayName || parameter.name || parameter.type),
|
|
135
|
+
type: finalFieldType,
|
|
136
|
+
placeholder: enumMetadata
|
|
137
|
+
? `Select ${parameter.displayName || parameter.name || parameter.type}`
|
|
138
|
+
: `Enter ${parameter.displayName || parameter.name || parameter.type}`,
|
|
139
|
+
helperText: parameter.description || '',
|
|
140
|
+
defaultValue: getDefaultValueForType(finalFieldType) as FieldValue<T>,
|
|
141
|
+
validation: getDefaultValidationForType(),
|
|
142
|
+
width: 'full',
|
|
143
|
+
options,
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
baseField.validation = enhanceNumericValidation(
|
|
147
|
+
baseField.validation,
|
|
148
|
+
parameter.type,
|
|
149
|
+
STELLAR_NUMERIC_BOUNDS
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
// Propagate max byte length for BytesN types so the UI can enforce it
|
|
153
|
+
if (isBytesNType(parameter.type)) {
|
|
154
|
+
const sizeMatch = parameter.type.match(/^BytesN<(\d+)>$/);
|
|
155
|
+
const maxBytes = sizeMatch ? Number.parseInt(sizeMatch[1], 10) : undefined;
|
|
156
|
+
|
|
157
|
+
if (!Number.isNaN(maxBytes) && Number.isFinite(maxBytes)) {
|
|
158
|
+
baseField.metadata = {
|
|
159
|
+
...(baseField.metadata ?? {}),
|
|
160
|
+
maxBytes,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// For array types (including arrays of complex types), provide element type information
|
|
166
|
+
if (fieldType === 'array' || fieldType === 'array-object') {
|
|
167
|
+
const elementType = extractVecElementType(parameter.type);
|
|
168
|
+
if (elementType) {
|
|
169
|
+
const elementFieldType = mapStellarParameterTypeToFieldType(elementType);
|
|
170
|
+
|
|
171
|
+
// Check if the element type is an enum or struct and needs additional metadata
|
|
172
|
+
let elementEnumMetadata: EnumMetadata | undefined;
|
|
173
|
+
let elementComponents: FunctionParameter[] | undefined;
|
|
174
|
+
let finalElementFieldType = elementFieldType;
|
|
175
|
+
|
|
176
|
+
// Extract enum metadata for array elements
|
|
177
|
+
if (isLikelyEnumType(elementType)) {
|
|
178
|
+
if (specEntries && isEnumType(specEntries, elementType)) {
|
|
179
|
+
elementEnumMetadata = extractEnumVariants(specEntries, elementType) ?? undefined;
|
|
180
|
+
if (elementEnumMetadata) {
|
|
181
|
+
// Override field type based on enum characteristics
|
|
182
|
+
finalElementFieldType = elementEnumMetadata.isUnitOnly ? 'select' : 'enum';
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Extract struct components for array elements
|
|
188
|
+
if (specEntries && isStructType(specEntries, elementType)) {
|
|
189
|
+
const structFields = extractStructFields(specEntries, elementType);
|
|
190
|
+
if (structFields && structFields.length > 0) {
|
|
191
|
+
elementComponents = structFields;
|
|
192
|
+
finalElementFieldType = 'object';
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// For array-object, if we have components from the parameter, use them
|
|
197
|
+
if (fieldType === 'array-object' && parameter.components) {
|
|
198
|
+
elementComponents = parameter.components;
|
|
199
|
+
finalElementFieldType = 'object';
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Build element validation with bounds
|
|
203
|
+
let elementValidation: FieldValidation = { required: true };
|
|
204
|
+
elementValidation = enhanceNumericValidation(
|
|
205
|
+
elementValidation,
|
|
206
|
+
elementType,
|
|
207
|
+
STELLAR_NUMERIC_BOUNDS
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
// Add array-specific properties with full element metadata
|
|
211
|
+
const arrayField = {
|
|
212
|
+
...baseField,
|
|
213
|
+
type: 'array' as FieldType, // Always use 'array' as the field type
|
|
214
|
+
elementType: finalElementFieldType,
|
|
215
|
+
elementFieldConfig: {
|
|
216
|
+
type: finalElementFieldType,
|
|
217
|
+
validation: elementValidation,
|
|
218
|
+
placeholder: `Enter ${elementType}`,
|
|
219
|
+
originalParameterType: elementType,
|
|
220
|
+
...(elementEnumMetadata && { enumMetadata: elementEnumMetadata }),
|
|
221
|
+
...(elementComponents && { components: elementComponents }),
|
|
222
|
+
},
|
|
223
|
+
};
|
|
224
|
+
return arrayField as unknown as FormFieldType<T>;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// For map types, provide key and value type information
|
|
229
|
+
if (fieldType === ('map' as FieldType)) {
|
|
230
|
+
const mapTypes = extractMapTypes(parameter.type);
|
|
231
|
+
if (mapTypes) {
|
|
232
|
+
const keyFieldType = mapStellarParameterTypeToFieldType(mapTypes.keyType);
|
|
233
|
+
const valueFieldType = mapStellarParameterTypeToFieldType(mapTypes.valueType);
|
|
234
|
+
|
|
235
|
+
// Add map-specific properties
|
|
236
|
+
const mapField = {
|
|
237
|
+
...baseField,
|
|
238
|
+
mapMetadata: {
|
|
239
|
+
keyType: keyFieldType,
|
|
240
|
+
valueType: valueFieldType,
|
|
241
|
+
keyFieldConfig: {
|
|
242
|
+
type: keyFieldType,
|
|
243
|
+
validation: enhanceNumericValidation(
|
|
244
|
+
{ required: true },
|
|
245
|
+
mapTypes.keyType,
|
|
246
|
+
STELLAR_NUMERIC_BOUNDS
|
|
247
|
+
),
|
|
248
|
+
placeholder: `Enter ${mapTypes.keyType}`,
|
|
249
|
+
originalParameterType: mapTypes.keyType,
|
|
250
|
+
},
|
|
251
|
+
valueFieldConfig: {
|
|
252
|
+
type: valueFieldType,
|
|
253
|
+
validation: enhanceNumericValidation(
|
|
254
|
+
{ required: true },
|
|
255
|
+
mapTypes.valueType,
|
|
256
|
+
STELLAR_NUMERIC_BOUNDS
|
|
257
|
+
),
|
|
258
|
+
placeholder: `Enter ${mapTypes.valueType}`,
|
|
259
|
+
originalParameterType: mapTypes.valueType,
|
|
260
|
+
},
|
|
261
|
+
},
|
|
262
|
+
validation: {
|
|
263
|
+
...getDefaultValidationForType(),
|
|
264
|
+
min: 0,
|
|
265
|
+
// No max limit - users can add as many map entries as needed
|
|
266
|
+
},
|
|
267
|
+
};
|
|
268
|
+
return mapField;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Preserve components for object and array-object types
|
|
273
|
+
if (fieldType === 'object' || fieldType === 'array-object') {
|
|
274
|
+
const componentsToUse = parameter.components || structComponents;
|
|
275
|
+
if (componentsToUse) {
|
|
276
|
+
// Prefer the baked-in WASM components, otherwise fall back to the SAC-derived
|
|
277
|
+
// components we cached above.
|
|
278
|
+
const result = {
|
|
279
|
+
...baseField,
|
|
280
|
+
components: componentsToUse,
|
|
281
|
+
};
|
|
282
|
+
return result;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Add enum metadata if available
|
|
287
|
+
if (enumMetadata) {
|
|
288
|
+
const result = {
|
|
289
|
+
...baseField,
|
|
290
|
+
enumMetadata,
|
|
291
|
+
};
|
|
292
|
+
return result;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
return baseField;
|
|
296
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { xdr } from '@stellar/stellar-sdk';
|
|
2
|
+
|
|
3
|
+
import type { FunctionParameter } from '@openzeppelin/ui-types';
|
|
4
|
+
import { logger } from '@openzeppelin/ui-utils';
|
|
5
|
+
|
|
6
|
+
// Import the type extraction utility from the shared utils module
|
|
7
|
+
import { extractSorobanTypeFromScSpec } from '../utils/type-detection';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Extracts struct field definitions from Stellar contract spec entries
|
|
11
|
+
*
|
|
12
|
+
* @param entries Array of ScSpecEntry from contract spec
|
|
13
|
+
* @param structName Name of the struct type to extract fields for
|
|
14
|
+
* @returns Array of FunctionParameter representing struct fields, or null if not found
|
|
15
|
+
*/
|
|
16
|
+
export function extractStructFields(
|
|
17
|
+
entries: xdr.ScSpecEntry[],
|
|
18
|
+
structName: string
|
|
19
|
+
): FunctionParameter[] | null {
|
|
20
|
+
try {
|
|
21
|
+
// Find the entry for the requested struct name
|
|
22
|
+
const entry = entries.find((e) => {
|
|
23
|
+
try {
|
|
24
|
+
return e.value().name().toString() === structName;
|
|
25
|
+
} catch {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
if (!entry) {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const entryKind = entry.switch();
|
|
35
|
+
|
|
36
|
+
// Handle UDT Struct (like DemoStruct { id: u32, flag: bool, info: Symbol })
|
|
37
|
+
if (entryKind.value === xdr.ScSpecEntryKind.scSpecEntryUdtStructV0().value) {
|
|
38
|
+
const structUdt = entry.udtStructV0();
|
|
39
|
+
const fields = structUdt.fields();
|
|
40
|
+
const structFields: FunctionParameter[] = [];
|
|
41
|
+
|
|
42
|
+
for (const field of fields) {
|
|
43
|
+
const fieldName = field.name().toString();
|
|
44
|
+
const fieldType = extractSorobanTypeFromScSpec(field.type());
|
|
45
|
+
|
|
46
|
+
const fieldParam: FunctionParameter = {
|
|
47
|
+
name: fieldName,
|
|
48
|
+
type: fieldType,
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// Recursively extract nested struct components
|
|
52
|
+
if (isStructType(entries, fieldType)) {
|
|
53
|
+
const nestedFields = extractStructFields(entries, fieldType);
|
|
54
|
+
if (nestedFields && nestedFields.length > 0) {
|
|
55
|
+
fieldParam.components = nestedFields;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
structFields.push(fieldParam);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return structFields;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return null;
|
|
66
|
+
} catch (error) {
|
|
67
|
+
logger.error(
|
|
68
|
+
'extractStructFields',
|
|
69
|
+
`Failed to extract struct fields for ${structName}:`,
|
|
70
|
+
error
|
|
71
|
+
);
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Determines if a given type name is a struct type in the contract spec
|
|
78
|
+
*
|
|
79
|
+
* @param entries Array of ScSpecEntry from contract spec
|
|
80
|
+
* @param typeName Name of the type to check
|
|
81
|
+
* @returns true if the type is a struct, false otherwise
|
|
82
|
+
*/
|
|
83
|
+
export function isStructType(entries: xdr.ScSpecEntry[], typeName: string): boolean {
|
|
84
|
+
try {
|
|
85
|
+
const entry = entries.find((e) => {
|
|
86
|
+
try {
|
|
87
|
+
const entryName = e.value().name().toString();
|
|
88
|
+
return entryName === typeName;
|
|
89
|
+
} catch {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
if (!entry) {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const entryKind = entry.switch();
|
|
99
|
+
const isStruct = entryKind.value === xdr.ScSpecEntryKind.scSpecEntryUdtStructV0().value;
|
|
100
|
+
|
|
101
|
+
return isStruct;
|
|
102
|
+
} catch (error) {
|
|
103
|
+
logger.error('isStructType', `Failed to check if ${typeName} is struct:`, error);
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { xdr } from '@stellar/stellar-sdk';
|
|
2
|
+
|
|
3
|
+
import type { FunctionParameter } from '@openzeppelin/ui-types';
|
|
4
|
+
|
|
5
|
+
import { extractTupleTypes } from '../utils/safe-type-parser';
|
|
6
|
+
import { extractStructFields, isStructType } from './struct-fields';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Builds synthetic FunctionParameter definitions for tuple types so they can be rendered
|
|
10
|
+
* using the existing object field UI.
|
|
11
|
+
*/
|
|
12
|
+
export function buildTupleComponents(
|
|
13
|
+
parameterType: string,
|
|
14
|
+
specEntries?: xdr.ScSpecEntry[]
|
|
15
|
+
): FunctionParameter[] | null {
|
|
16
|
+
const tupleElements = extractTupleTypes(parameterType);
|
|
17
|
+
if (!tupleElements || tupleElements.length === 0) {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return tupleElements.map((elementType, index) => {
|
|
22
|
+
let nestedComponents: FunctionParameter[] | undefined;
|
|
23
|
+
|
|
24
|
+
if (specEntries && isStructType(specEntries, elementType)) {
|
|
25
|
+
const structFields = extractStructFields(specEntries, elementType);
|
|
26
|
+
if (structFields && structFields.length > 0) {
|
|
27
|
+
nestedComponents = structFields;
|
|
28
|
+
}
|
|
29
|
+
} else if (elementType.startsWith('Tuple<')) {
|
|
30
|
+
const tupleStruct = buildTupleComponents(elementType, specEntries);
|
|
31
|
+
if (tupleStruct && tupleStruct.length > 0) {
|
|
32
|
+
nestedComponents = tupleStruct;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
name: `item_${index}`,
|
|
38
|
+
type: elementType,
|
|
39
|
+
displayName: `Value ${index + 1} (${elementType})`,
|
|
40
|
+
...(nestedComponents && { components: nestedComponents }),
|
|
41
|
+
};
|
|
42
|
+
});
|
|
43
|
+
}
|