@openzeppelin/contracts-ui-builder-adapter-stellar
Version:
Stellar Adapter for Contracts UI Builder
200 lines (174 loc) • 6.19 kB
text/typescript
import type { FieldType } from '@openzeppelin/contracts-ui-builder-types';
import { isLikelyEnumType } from '../utils/type-detection';
import { STELLAR_TYPE_TO_FIELD_TYPE } from './constants';
/**
* Map a Stellar-specific parameter type to a default form field type.
* @param parameterType The Stellar parameter type (e.g., 'U128', 'Address', 'Vec<U32>')
* @returns The appropriate default form field type (e.g., 'number', 'blockchain-address', 'array')
*/
export function mapStellarParameterTypeToFieldType(parameterType: string): FieldType {
// Check if this is a Vec of custom/complex types (e.g., Vec<CustomStruct>)
const vecComplexMatch = parameterType.match(/^Vec<([^>]+)>$/);
if (vecComplexMatch) {
const innerType = vecComplexMatch[1];
// If inner type is not a primitive, treat as array of objects
if (!STELLAR_TYPE_TO_FIELD_TYPE[innerType]) {
return 'array-object';
}
return 'array';
}
// Check if this is a simple Vec type
if (parameterType === 'Vec' || parameterType.startsWith('Vec<')) {
return 'array';
}
// Check if this is a Map type
if (parameterType === 'Map' || parameterType.startsWith('Map<')) {
return 'map' as FieldType;
}
// Extract base type for generic types (e.g., Option<U32> -> U32)
const genericMatch = parameterType.match(/^(\w+)<(.+)>$/);
if (genericMatch) {
const baseType = genericMatch[1];
// For Option and Result types, use the inner type
if (baseType === 'Option' || baseType === 'Result') {
const innerType = genericMatch[2];
return mapStellarParameterTypeToFieldType(innerType);
}
}
// Map known Stellar/Soroban types to appropriate field types
const mappedType = STELLAR_TYPE_TO_FIELD_TYPE[parameterType];
if (mappedType) {
return mappedType;
}
// Handle BytesN types (fixed-size byte arrays like BytesN<32> for hashes)
if (parameterType.startsWith('BytesN<')) {
return 'textarea'; // Users input as hex strings
}
// Handle custom types (structs, enums) - default to object unless it's clearly an enum
if (isLikelyEnumType(parameterType)) {
return 'select';
}
// Check if this looks like a custom struct (capitalized name and not a known primitive)
if (parameterType[0] && parameterType[0] === parameterType[0].toUpperCase()) {
// Check if it's not a known Stellar primitive type that starts with uppercase
const knownUppercaseTypes = [
'U32',
'U64',
'U128',
'U256',
'I32',
'I64',
'I128',
'I256',
'Bool',
'Bytes',
];
if (
!knownUppercaseTypes.includes(parameterType) &&
!parameterType.startsWith('Vec') &&
!parameterType.startsWith('Map') &&
!parameterType.includes('Unknown')
) {
// Don't treat Unknown* as structs
return 'object';
}
}
// Final fallback to text for truly unknown types
return 'text';
}
/**
* Get field types compatible with a specific parameter type.
* @param parameterType The Stellar parameter type.
* @returns Array of compatible form field types.
*/
export function getStellarCompatibleFieldTypes(parameterType: string): FieldType[] {
// Handle Vec of complex types
const vecComplexMatch = parameterType.match(/^Vec<([^>]+)>$/);
if (vecComplexMatch) {
const innerType = vecComplexMatch[1];
if (!STELLAR_TYPE_TO_FIELD_TYPE[innerType]) {
return ['array-object', 'textarea', 'text'];
}
return ['array', 'textarea', 'text'];
}
// Handle Vec types - allow array field or fallback to textarea/text
if (parameterType === 'Vec' || parameterType.startsWith('Vec<')) {
return ['array', 'textarea', 'text'];
}
// Handle Map types
if (parameterType === 'Map' || parameterType.startsWith('Map<')) {
return ['map' as FieldType, 'textarea', 'text'];
}
// Handle generic types
const genericMatch = parameterType.match(/^(\w+)<(.+)>$/);
if (genericMatch) {
const baseType = genericMatch[1];
if (baseType === 'Option' || baseType === 'Result') {
const innerType = genericMatch[2];
return getStellarCompatibleFieldTypes(innerType);
}
}
// Define compatibility map for known types
const compatibilityMap: Record<string, FieldType[]> = {
Address: ['blockchain-address', 'text'],
// Unsigned integers
U32: ['number', 'amount', 'text'],
U64: ['number', 'amount', 'text'],
U128: ['number', 'amount', 'text'],
U256: ['number', 'amount', 'text'],
// Signed integers
I32: ['number', 'amount', 'text'],
I64: ['number', 'amount', 'text'],
I128: ['number', 'amount', 'text'],
I256: ['number', 'amount', 'text'],
// Boolean
Bool: ['checkbox', 'select', 'radio', 'text'],
// String types
ScString: ['text', 'textarea', 'email', 'password'],
ScSymbol: ['text', 'textarea'],
// Byte types
Bytes: ['bytes', 'textarea', 'text'],
DataUrl: ['bytes', 'textarea', 'text'],
// BytesN types like BytesN<32> for hashes
'BytesN<32>': ['bytes', 'textarea', 'text'],
// Complex types
Tuple: ['object', 'textarea', 'text'],
Instance: ['object', 'textarea', 'text'],
};
// Check if we have a specific compatibility mapping
const compatibleTypes = compatibilityMap[parameterType];
if (compatibleTypes) {
return compatibleTypes;
}
// Handle enums
if (isLikelyEnumType(parameterType)) {
return ['enum', 'select', 'radio', 'text'];
}
// Handle custom types (assumed to be structs)
if (parameterType[0] && parameterType[0] === parameterType[0].toUpperCase()) {
// Check if it's not a known Stellar primitive type that starts with uppercase
const knownUppercaseTypes = [
'U32',
'U64',
'U128',
'U256',
'I32',
'I64',
'I128',
'I256',
'Bool',
'Bytes',
];
if (
!knownUppercaseTypes.includes(parameterType) &&
!parameterType.startsWith('Vec') &&
!parameterType.startsWith('Map') &&
!parameterType.includes('Unknown')
) {
// Don't treat Unknown* as structs
return ['object', 'textarea', 'text'];
}
}
// Default fallback for truly unknown types
return ['text'];
}