@openzeppelin/contracts-ui-builder-adapter-stellar
Version:
Stellar Adapter for Contracts UI Builder
158 lines (139 loc) • 5.1 kB
text/typescript
import { nativeToScVal, xdr } from '@stellar/stellar-sdk';
import { detectBytesEncoding, stringToBytes } from '@openzeppelin/contracts-ui-builder-utils';
import { convertStellarTypeToScValType } from '../../utils/formatting';
import {
convertEnumToScVal,
convertObjectToMap,
convertObjectToScVal,
convertTupleToScVal,
getScValFromArg,
getScValFromPrimitive,
isComplexObjectArray,
isEnumArgumentSet,
isMapArray,
isObjectWithTypedValues,
isPrimitiveArgumentSet,
isPrimitiveArray,
isPrimitiveValue,
isTupleArray,
} from '../../utils/input-parsing';
import type {
SorobanArgumentValue,
SorobanComplexValue,
SorobanEnumValue,
SorobanMapEntry,
} from './types';
/**
* Advanced ScVal generation from complex Soroban arguments.
* Handles Maps, Enums, Tuples, and nested structures.
*
* @param args - Complex argument structure from advanced UI forms
* @param scVals - Accumulator for generated ScVals (used for recursion)
* @returns Array of ScVals ready for contract invocation
*/
export function getScValsFromArgs(
args: Record<string, SorobanComplexValue> | SorobanComplexValue[],
scVals: xdr.ScVal[] = []
): xdr.ScVal[] {
// Handle array input (multiple arguments)
if (Array.isArray(args)) {
return args.map((arg) => {
if (typeof arg === 'object' && 'value' in arg && 'type' in arg) {
return getScValFromPrimitive(arg as SorobanArgumentValue);
}
return getScValFromArg(arg, []);
});
}
// Handle primitive case - all values have type and value
if (isPrimitiveArgumentSet(args)) {
const primitiveScVals = Object.values(args).map((v) => {
return getScValFromPrimitive(v as SorobanArgumentValue);
});
return primitiveScVals;
}
// Handle enum case - values have tag or enum properties
if (isEnumArgumentSet(args)) {
const enumScVals = Object.values(args).map((v) => {
return convertEnumToScVal(v as SorobanEnumValue, scVals);
});
return enumScVals;
}
// Handle complex cases (maps, objects, arrays)
for (const argKey in args) {
const argValue = args[argKey];
if (Array.isArray(argValue)) {
// Map case - array of key-value pair objects
if (isMapArray(argValue)) {
const { mapVal, mapType } = convertObjectToMap(argValue as SorobanMapEntry[]);
const mapScVal = nativeToScVal(mapVal, { type: mapType });
scVals.push(mapScVal);
continue;
}
// Vector case #1: array of complex objects or tuples
if (isComplexObjectArray(argValue)) {
const arrayScVals = argValue.map((v) => {
// Use proper type guards to ensure safe casting
if (typeof v === 'object' && v !== null && ('tag' in v || 'enum' in v)) {
return convertEnumToScVal(v as SorobanEnumValue, scVals);
}
if (
typeof v === 'object' &&
v !== null &&
!Array.isArray(v) &&
!('0' in v) &&
!('1' in v)
) {
return convertObjectToScVal(v as Record<string, SorobanArgumentValue>);
}
// Fallback for other types
return nativeToScVal(v);
});
const tupleScValsVec = xdr.ScVal.scvVec(arrayScVals);
scVals.push(tupleScValsVec);
continue;
}
// Vector case #2: array of primitives (homogeneous type)
if (isPrimitiveArray(argValue)) {
// TypeScript now knows argValue is SorobanArgumentValue[] thanks to type predicate
const arrayScVals = argValue.reduce((acc: unknown[], v) => {
const primitive = v as unknown as SorobanArgumentValue;
if (primitive.type === 'bool') {
acc.push(primitive.value === 'true' ? true : false);
} else if (primitive.type === 'bytes') {
const encoding = detectBytesEncoding(primitive.value as string);
acc.push(stringToBytes(primitive.value as string, encoding));
} else {
acc.push(primitive.value);
}
return acc;
}, []);
const firstItem = argValue[0];
const scValType = convertStellarTypeToScValType(firstItem.type);
const typeHint = Array.isArray(scValType) ? scValType[0] : scValType;
const scVal = nativeToScVal(arrayScVals, {
type: typeHint,
});
scVals.push(scVal);
continue;
}
// Tuple case - mixed types in array
if (isTupleArray(argValue)) {
// TypeScript now knows argValue is SorobanArgumentValue[] thanks to type predicate
const tupleScValsVec = convertTupleToScVal(argValue);
scVals.push(tupleScValsVec);
continue;
}
}
// Object case - structured data
if (isObjectWithTypedValues(argValue)) {
const convertedObj = convertObjectToScVal(argValue as Record<string, SorobanArgumentValue>);
scVals.push(convertedObj);
continue;
}
// Single primitive value
if (isPrimitiveValue(argValue)) {
scVals.push(getScValFromPrimitive(argValue as SorobanArgumentValue));
}
}
return scVals;
}