@openzeppelin/contracts-ui-builder-adapter-stellar
Version:
Stellar Adapter for Contracts UI Builder
134 lines (123 loc) • 4.42 kB
text/typescript
import { scValToNative, xdr } from '@stellar/stellar-sdk';
import type { ContractFunction } from '@openzeppelin/contracts-ui-builder-types';
import { bytesToHex, logger } from '@openzeppelin/contracts-ui-builder-utils';
import { isSerializableObject, stringifyWithBigInt } from '../utils';
/**
* Formats the result of a Stellar view function call into a user-friendly string.
*
* @param result The result value (can be ScVal, native JS value, or other types).
* @param functionDetails The contract function details.
* @returns A string representation suitable for display.
*/
export function formatStellarFunctionResult(
result: unknown,
functionDetails: ContractFunction
): string {
if (!functionDetails.outputs || !Array.isArray(functionDetails.outputs)) {
logger.warn(
'formatStellarFunctionResult',
`Output definition missing or invalid for function ${functionDetails.name}.`
);
return '[Error: Output definition missing]';
}
try {
let valueToFormat: unknown;
// Handle null/undefined values
if (result === null || result === undefined) {
return '(null)';
}
// Check if result is an ScVal and convert to native JS value
if (isScVal(result)) {
try {
// Special handling for void ScVal
const scVal = result as xdr.ScVal;
if (scVal.switch().name === 'scvVoid') {
return '(void)';
}
valueToFormat = scValToNative(scVal);
// Convert Buffer to Uint8Array for cross-platform compatibility
// scValToNative may return Buffer objects in some environments
if (
valueToFormat &&
typeof valueToFormat === 'object' &&
'constructor' in valueToFormat &&
valueToFormat.constructor?.name === 'Buffer'
) {
valueToFormat = new Uint8Array(valueToFormat as ArrayLike<number>);
}
} catch (error) {
logger.error('formatStellarFunctionResult', 'Failed to convert ScVal to native', {
functionName: functionDetails.name,
error,
});
return '[Error: Failed to decode ScVal]';
}
} else {
valueToFormat = result;
}
// Format based on type
if (typeof valueToFormat === 'bigint') {
return valueToFormat.toString();
} else if (typeof valueToFormat === 'string') {
return valueToFormat;
} else if (typeof valueToFormat === 'number') {
return valueToFormat.toString();
} else if (typeof valueToFormat === 'boolean') {
return String(valueToFormat);
} else if (valueToFormat instanceof Uint8Array) {
// Handle byte arrays - convert to hex string
return bytesToHex(valueToFormat, true);
} else if (Array.isArray(valueToFormat)) {
// Handle arrays/vectors
if (valueToFormat.length === 0) {
return '[]';
}
// Use compact formatting for simple arrays, pretty formatting for complex ones
if (
valueToFormat.every(
(item) =>
typeof item === 'string' ||
typeof item === 'number' ||
typeof item === 'boolean' ||
typeof item === 'bigint'
)
) {
return stringifyWithBigInt(valueToFormat); // No spacing for simple arrays
}
return stringifyWithBigInt(valueToFormat, 2);
} else if (isSerializableObject(valueToFormat)) {
// Handle objects/maps/structs
if (Object.keys(valueToFormat as object).length === 0) {
return '{}';
}
return stringifyWithBigInt(valueToFormat, 2);
} else if (valueToFormat === null || valueToFormat === undefined) {
return '(null)';
} else {
// Handle any other type by stringifying
return stringifyWithBigInt(valueToFormat, 2);
}
} catch (error) {
const errorMessage = `Error formatting result for ${functionDetails.name}: ${(error as Error).message}`;
logger.error('formatStellarFunctionResult', errorMessage, {
functionName: functionDetails.name,
result,
error,
});
return `[${errorMessage}]`;
}
}
/**
* Type guard to check if a value is an ScVal
*/
function isScVal(value: unknown): boolean {
if (!value || typeof value !== 'object') {
return false;
}
try {
// Use instanceof check for robust type detection
return typeof xdr !== 'undefined' && xdr.ScVal && value instanceof xdr.ScVal;
} catch {
return false;
}
}