diff --git a/src/features/abi-methods/components/abi-struct-value.tsx b/src/features/abi-methods/components/abi-struct-value.tsx new file mode 100644 index 00000000..8c347cee --- /dev/null +++ b/src/features/abi-methods/components/abi-struct-value.tsx @@ -0,0 +1,42 @@ +import { AbiValue } from '@/features/abi-methods/components/abi-value' +import { AbiStructValue as AbiStructValueModel } from '@/features/abi-methods/models' + +type Props = { + struct: AbiStructValueModel +} + +export function AbiStructValue({ struct }: Props) { + if (struct.multiline) { + return ( + <> + {'{'} + + {'}'} + + ) + } else { + return ( + <> + {'{'} +
+ {struct.values.map((item, index, array) => ( +
+ {item.name}: + + {index < array.length - 1 ? {', '} : null} +
+ ))} +
+ {'}'} + + ) + } +} diff --git a/src/features/abi-methods/components/abi-value.tsx b/src/features/abi-methods/components/abi-value.tsx index 95babf3a..bbd2859e 100644 --- a/src/features/abi-methods/components/abi-value.tsx +++ b/src/features/abi-methods/components/abi-value.tsx @@ -2,6 +2,7 @@ import { AbiArrayValue } from '@/features/abi-methods/components/abi-array-value import { AbiTupleValue } from '@/features/abi-methods/components/abi-tuple-value' import { AbiValue as AbiValueModel, AbiType } from '@/features/abi-methods/models' import { AccountLink } from '@/features/accounts/components/account-link' +import { AbiStructValue } from '@/features/abi-methods/components/abi-struct-value' type Props = { abiValue: AbiValueModel @@ -11,6 +12,9 @@ export function AbiValue({ abiValue }: Props) { if (abiValue.type === AbiType.Tuple) { return } + if (abiValue.type === AbiType.Struct) { + return + } if (abiValue.type === AbiType.Array) { return } diff --git a/src/features/abi-methods/components/decoded-abi-method-arguments.tsx b/src/features/abi-methods/components/decoded-abi-method-arguments.tsx index 861c0122..336af9af 100644 --- a/src/features/abi-methods/components/decoded-abi-method-arguments.tsx +++ b/src/features/abi-methods/components/decoded-abi-method-arguments.tsx @@ -8,9 +8,9 @@ import { useMemo, useCallback } from 'react' type Props = { arguments: AbiMethodArgument[] - multiLine: boolean + multiline: boolean } -export function DecodedAbiMethodArguments({ arguments: argumentsProp, multiLine }: Props) { +export function DecodedAbiMethodArguments({ arguments: argumentsProp, multiline }: Props) { const renderArgumentValue = useCallback((argument: AbiMethodArgument) => { if (argument.type === AbiType.Transaction) { return ( @@ -52,7 +52,7 @@ export function DecodedAbiMethodArguments({ arguments: argumentsProp, multiLine [argumentsProp, renderArgumentValue] ) - if (multiLine) { + if (multiline) { return (
    {components.map((component, index, array) => ( diff --git a/src/features/abi-methods/components/decoded-abi-method.tsx b/src/features/abi-methods/components/decoded-abi-method.tsx index 0f702f7e..10914e3f 100644 --- a/src/features/abi-methods/components/decoded-abi-method.tsx +++ b/src/features/abi-methods/components/decoded-abi-method.tsx @@ -11,7 +11,7 @@ export function DecodedAbiMethod({ abiMethod }: Props) {
    {abiMethod.name}( - ) + )
    Returns: diff --git a/src/features/abi-methods/data/abi-method.ts b/src/features/abi-methods/data/abi-method.ts index 9cf5af1e..b432b096 100644 --- a/src/features/abi-methods/data/abi-method.ts +++ b/src/features/abi-methods/data/abi-method.ts @@ -12,6 +12,7 @@ import { invariant } from '@/utils/invariant' import { isArc32AppSpec, isArc4AppSpec } from '@/features/common/utils' import { createAppInterfaceAtom } from '@/features/app-interfaces/data' import { sum } from '@/utils/sum' +import { Hint, Struct } from '@/features/app-interfaces/data/types/arc-32/application' const MAX_LINE_LENGTH = 20 @@ -21,17 +22,17 @@ export const abiMethodResolver = (transaction: TransactionResult): Atom argument.multiline) || sum(methodArguments.map((arg) => arg.length)) > MAX_LINE_LENGTH return { - name: abiMethod.name, + name: abiMethodWithHint.abiMethod.name, arguments: methodArguments, return: methodReturn, multiline, @@ -39,7 +40,7 @@ export const abiMethodResolver = (transaction: TransactionResult): Atom> => { +const createAbiMethodWithHintAtom = (transaction: TransactionResult): Atom> => { return atom(async (get) => { invariant(transaction['application-transaction'], 'application-transaction is not set') @@ -56,28 +57,43 @@ const createAbiMethodAtom = (transaction: TransactionResult): Atom { const abiMethod = new algosdk.ABIMethod(m) return uint8ArrayToBase64(abiMethod.getSelector()) === transactionArgs[0] }) - if (contractMethod) return new algosdk.ABIMethod(contractMethod) + if (contractMethod) { + const abiMethod = new algosdk.ABIMethod(contractMethod) + const hint = hints?.[abiMethod.getSignature()] + return { abiMethod, hint } + } } } return undefined }) } -const createMethodArgumentsAtom = (transaction: TransactionResult, abiMethod: algosdk.ABIMethod): Atom> => { +const createMethodArgumentsAtom = ( + transaction: TransactionResult, + abiMethodWithHint: AbiMethodWithHint +): Atom> => { return atom(async (get) => { invariant(transaction['application-transaction'], 'application-transaction is not set') invariant(transaction['application-transaction']?.['application-args'], 'application-transaction application-args is not set') + const { abiMethod, hint } = abiMethodWithHint const referencedTransactionIds = await get(getReferencedTransactionIdsAtom(transaction, abiMethod)) const abiValues = getAbiValueArgs(transaction, abiMethod) const abiArguments: AbiMethodArgument[] = abiMethod.args.map((argumentSpec, index) => { const argName = argumentSpec.name ?? `arg${index}` + const argHint = + hint && argumentSpec.name && (hint.structs?.[argumentSpec.name] || hint.default_arguments?.[argumentSpec.name]) + ? ({ + struct: hint.structs?.[argumentSpec.name], + } satisfies AbiValueHint) + : undefined if (algosdk.abiTypeIsTransaction(argumentSpec.type)) { const transactionId = referencedTransactionIds.shift()! @@ -136,7 +152,7 @@ const createMethodArgumentsAtom = (transaction: TransactionResult, abiMethod: al return { name: argName, - ...getAbiValue(argumentSpec.type, abiValue), + ...getAbiValue(argumentSpec.type, abiValue, argHint), } }) @@ -144,18 +160,27 @@ const createMethodArgumentsAtom = (transaction: TransactionResult, abiMethod: al }) } -const getMethodReturn = (transaction: TransactionResult, abiMethod: algosdk.ABIMethod): AbiMethodReturn => { +const getMethodReturn = (transaction: TransactionResult, abiMethodWithHint: AbiMethodWithHint): AbiMethodReturn => { + const { abiMethod, hint } = abiMethodWithHint + if (abiMethod.returns.type === 'void') return 'void' invariant(transaction.logs && transaction.logs.length > 0, 'transaction logs is not set') + const returnHint = + hint && hint.structs?.['output'] + ? ({ + struct: hint.structs?.['output'], + } satisfies AbiValueHint) + : undefined + const abiType = algosdk.ABIType.from(abiMethod.returns.type.toString()) // The first 4 bytes are SHA512_256 hash of the string "return" const bytes = base64ToBytes(transaction.logs.slice(-1)[0]).subarray(4) const abiValue = abiType.decode(bytes) - return getAbiValue(abiType, abiValue) + return getAbiValue(abiType, abiValue, returnHint) } -const getAbiValue = (abiType: algosdk.ABIType, abiValue: algosdk.ABIValue): AbiValue => { +const getAbiValue = (abiType: algosdk.ABIType, abiValue: algosdk.ABIValue, hint?: AbiValueHint): AbiValue => { if (abiType instanceof algosdk.ABITupleType) { const childTypes = abiType.childTypes const abiValues = abiValue as algosdk.ABIValue[] @@ -164,14 +189,38 @@ const getAbiValue = (abiType: algosdk.ABIType, abiValue: algosdk.ABIValue): AbiV } const childrenValues = abiValues.map((abiValue, index) => getAbiValue(childTypes[index], abiValue)) - const length = sum(childrenValues.map((v) => v.length)) - const multiline = childrenValues.some((v) => v.multiline) || length > MAX_LINE_LENGTH - return { - type: AbiType.Tuple, - values: childrenValues, - multiline, - length, + if (hint?.struct) { + const values = childTypes.map( + (_, index) => { + const value = childrenValues[index] + const name = hint.struct!.elements[index][0] + return { + name: name, + value: value, + } + }, + [{} as Record] + ) + const length = sum(values.map((v) => `${v.name}: ${v.value}`.length)) + const multiline = values.some((v) => v.value.multiline) || length > MAX_LINE_LENGTH + + return { + type: AbiType.Struct, + values: values, + multiline: multiline, + length: length, + } + } else { + const length = sum(childrenValues.map((v) => v.length)) + const multiline = childrenValues.some((v) => v.multiline) || length > MAX_LINE_LENGTH + + return { + type: AbiType.Tuple, + values: childrenValues, + multiline, + length, + } } } if (abiType instanceof algosdk.ABIArrayStaticType || abiType instanceof algosdk.ABIArrayDynamicType) { @@ -335,3 +384,12 @@ const bigintToString = (value: bigint, decimalScale: number): string => { const fractionString = valueString.slice(valueString.length - decimalScale) return `${numberString}.${fractionString}` } + +type AbiValueHint = { + struct?: Struct +} + +type AbiMethodWithHint = { + abiMethod: algosdk.ABIMethod + hint?: Hint +} diff --git a/src/features/abi-methods/models/index.ts b/src/features/abi-methods/models/index.ts index 44442a5d..c8758465 100644 --- a/src/features/abi-methods/models/index.ts +++ b/src/features/abi-methods/models/index.ts @@ -11,6 +11,7 @@ export enum AbiType { Transaction = 'Transaction', Application = 'Application', Asset = 'Asset', + Struct = 'Struct', } type RepresentationProps = { @@ -19,6 +20,11 @@ type RepresentationProps = { } export type AbiTupleValue = { type: AbiType.Tuple; values: AbiValue[] } & RepresentationProps export type AbiArrayValue = { type: AbiType.Array; values: AbiValue[] } & RepresentationProps +export type AbiStructElementValue = { + name: string + value: AbiValue +} +export type AbiStructValue = { type: AbiType.Struct; values: AbiStructElementValue[] } & RepresentationProps export type AbiValue = | ({ type: AbiType.String @@ -46,6 +52,7 @@ export type AbiValue = } & RepresentationProps) | AbiArrayValue | AbiTupleValue + | AbiStructValue export type AbiReferenceValue = | ({ type: AbiType.Account; value: string } & RepresentationProps) diff --git a/src/features/applications/components/application-method-definitions.tsx b/src/features/applications/components/application-method-definitions.tsx index a8d7c7cb..e8813157 100644 --- a/src/features/applications/components/application-method-definitions.tsx +++ b/src/features/applications/components/application-method-definitions.tsx @@ -93,6 +93,9 @@ function Method({ applicationId, method, appSpec, r addr: activeAddress, signer, }, + sendParams: { + populateAppCallResources: true, + }, }) const sentTxns = asTransactionFromSendResult(result)