diff --git a/src/app/[lang]/txn/compose/components/fields/AssetConfigFields/AssetId.tsx b/src/app/[lang]/txn/compose/components/fields/AssetConfigFields/AssetId.tsx index 3af5ec2d..a8cae80e 100644 --- a/src/app/[lang]/txn/compose/components/fields/AssetConfigFields/AssetId.tsx +++ b/src/app/[lang]/txn/compose/components/fields/AssetConfigFields/AssetId.tsx @@ -95,7 +95,7 @@ export default function AssetId({ t }: { t: TFunction }) { {t('fields.caid.getting_info')} } {assetInfoSuccess && - (retrievedAssetInfo?.name ?? {t('fields.caid.get_info_unknown')}) + (retrievedAssetInfo?.value?.name ?? {t('fields.caid.get_info_unknown')}) } {assetInfoFail && <> diff --git a/src/app/[lang]/txn/compose/components/fields/AssetFreezeFields/AssetId.tsx b/src/app/[lang]/txn/compose/components/fields/AssetFreezeFields/AssetId.tsx index 340bff1a..614b1905 100644 --- a/src/app/[lang]/txn/compose/components/fields/AssetFreezeFields/AssetId.tsx +++ b/src/app/[lang]/txn/compose/components/fields/AssetFreezeFields/AssetId.tsx @@ -91,7 +91,7 @@ export default function AssetId({ t }: { t: TFunction }) { {t('fields.faid.getting_info')} } {assetInfoSuccess && - (retrievedAssetInfo?.name ?? {t('fields.faid.get_info_unknown')}) + (retrievedAssetInfo?.value?.name ?? {t('fields.faid.get_info_unknown')}) } {assetInfoFail && <> diff --git a/src/app/[lang]/txn/compose/components/fields/AssetTransferFields/Amount.tsx b/src/app/[lang]/txn/compose/components/fields/AssetTransferFields/Amount.tsx index c9a0c8e1..a7ac440f 100644 --- a/src/app/[lang]/txn/compose/components/fields/AssetTransferFields/Amount.tsx +++ b/src/app/[lang]/txn/compose/components/fields/AssetTransferFields/Amount.tsx @@ -2,16 +2,19 @@ import { NumberField } from '@/app/[lang]/components/form'; import { type TFunction } from 'i18next'; import { useAtomValue } from 'jotai'; import { + aamtConditionalMaxAtom, assetTransferFormControlAtom, showFormErrorsAtom, tipBtnClass, tipContentClass, txnDataAtoms, } from '@/app/lib/txn-data'; +import { baseUnitsToDecimal } from '@/app/lib/utils'; import FieldErrorMessage from '../FieldErrorMessage'; export default function Amount({ t }: { t: TFunction }) { const form = useAtomValue(assetTransferFormControlAtom); + const aamtCondMax = useAtomValue(aamtConditionalMaxAtom); const showFormErrors = useAtomValue(showFormErrorsAtom); const retrievedAssetInfo = useAtomValue(txnDataAtoms.retrievedAssetInfo); return (<> @@ -29,12 +32,17 @@ export default function Amount({ t }: { t: TFunction }) { inputInsideLabel={false} containerId='aamt-field' containerClass='mt-4 max-w-xs' - inputClass={ - ((showFormErrors || form.touched.aamt) && form.fieldErrors.aamt) ? 'input-error' : '' + inputClass={((showFormErrors || form.touched.aamt) && + (form.fieldErrors.aamt || (!aamtCondMax.isValid && aamtCondMax.error)) + ) + ? 'input-error' : '' } - afterSideLabel={retrievedAssetInfo?.unitName ?? t('unit_other')} + afterSideLabel={retrievedAssetInfo?.value?.unitName ?? t('unit_other')} min={0} - step={10**-(retrievedAssetInfo?.decimals ?? 0)} + max={ + baseUnitsToDecimal(retrievedAssetInfo?.value?.total, retrievedAssetInfo?.value?.decimals) + } + step={10**-(retrievedAssetInfo?.value?.decimals ?? 0)} value={form.values.aamt ?? ''} onChange={(e) => form.handleOnChange('aamt')(e.target.value)} onFocus={form.handleOnFocus('aamt')} @@ -46,5 +54,11 @@ export default function Amount({ t }: { t: TFunction }) { dict={form.fieldErrors.aamt.message.dict} /> } + {(showFormErrors || form.touched.aamt) && !aamtCondMax.isValid && aamtCondMax.error && + + } ); } diff --git a/src/app/[lang]/txn/compose/components/fields/AssetTransferFields/AssetId.tsx b/src/app/[lang]/txn/compose/components/fields/AssetTransferFields/AssetId.tsx index bd0c5578..cddc227a 100644 --- a/src/app/[lang]/txn/compose/components/fields/AssetTransferFields/AssetId.tsx +++ b/src/app/[lang]/txn/compose/components/fields/AssetTransferFields/AssetId.tsx @@ -84,7 +84,7 @@ export default function AssetId({ t }: { t: TFunction }) { {t('fields.xaid.getting_info')} } {assetInfoSuccess && - (retrievedAssetInfo?.name ?? {t('fields.xaid.get_info_unknown')}) + (retrievedAssetInfo?.value?.name ?? {t('fields.xaid.get_info_unknown')}) } {assetInfoFail && <> diff --git a/src/app/lib/txn-data/atoms.ts b/src/app/lib/txn-data/atoms.ts index a16f6cf4..ce1dffef 100644 --- a/src/app/lib/txn-data/atoms.ts +++ b/src/app/lib/txn-data/atoms.ts @@ -115,7 +115,9 @@ export const close = atomWithValidate('', { * Retrieved Asset Information */ -export const retrievedAssetInfo = atom(undefined); +export const retrievedAssetInfo = atomWithValidate(undefined, { + validate: v => v +}); /* * Asset Transfer diff --git a/src/app/lib/txn-data/field-validation.ts b/src/app/lib/txn-data/field-validation.ts index 71f48c2a..1454046a 100644 --- a/src/app/lib/txn-data/field-validation.ts +++ b/src/app/lib/txn-data/field-validation.ts @@ -3,8 +3,9 @@ import { OnApplicationComplete } from 'algosdk'; import { atom } from 'jotai'; import { atomWithFormControls, atomWithValidate, validateAtoms } from 'jotai-form'; +import { baseUnitsToDecimal } from '@/app/lib/utils'; import * as txnDataAtoms from './atoms'; -import { ValidationMessage } from './types'; +import { RetrievedAssetInfo, ValidationMessage } from './types'; import { Preset, MAX_APP_GLOBALS, MAX_APP_KEY_LENGTH, MAX_APP_LOCALS } from './constants'; import { YupMixed, YupNumber, YupString, addressSchema, idSchema } from './validation-rules'; @@ -151,6 +152,17 @@ export const acloseConditionalRequireAtom = validateAtoms({ YupString().required().validateSync(values.aclose); } }); +export const aamtConditionalMaxAtom = validateAtoms({ + assetInfo: txnDataAtoms.retrievedAssetInfo, + aamt: txnDataAtoms.aamt, +}, (values) => { + if (values.assetInfo) { + const assetInfo = values.assetInfo as RetrievedAssetInfo; + YupNumber() + .max(parseFloat(baseUnitsToDecimal(assetInfo.total, assetInfo.decimals))) + .validateSync(values.aamt === '' ? undefined : values.aamt); + } +}); /* * Asset configuration validation form groups diff --git a/src/app/lib/txn-data/stored.ts b/src/app/lib/txn-data/stored.ts index 476e377f..4fa2632a 100644 --- a/src/app/lib/txn-data/stored.ts +++ b/src/app/lib/txn-data/stored.ts @@ -384,7 +384,7 @@ export function extractTxnDataFromAtoms( aclose: assetTransferForm.values.aclose || undefined, }; - retrievedAssetInfo = jotaiStore.get(txnDataAtoms.retrievedAssetInfo); + retrievedAssetInfo = jotaiStore.get(txnDataAtoms.retrievedAssetInfo).value; if (retrievedAssetInfo) { // Remove asset addresses from asset information retrievedAssetInfo = { @@ -445,7 +445,7 @@ export function extractTxnDataFromAtoms( apar_rUseSnd: !!assetConfigForm.values.apar_rUseSnd, }; } else { // Not creating an asset - retrievedAssetInfo = jotaiStore.get(txnDataAtoms.retrievedAssetInfo); + retrievedAssetInfo = jotaiStore.get(txnDataAtoms.retrievedAssetInfo).value; if (retrievedAssetInfo) { // Remove asset addresses from asset information retrievedAssetInfo = { @@ -476,7 +476,7 @@ export function extractTxnDataFromAtoms( afrz: assetFreezeForm.values.afrz, }; - retrievedAssetInfo = jotaiStore.get(txnDataAtoms.retrievedAssetInfo); + retrievedAssetInfo = jotaiStore.get(txnDataAtoms.retrievedAssetInfo).value; if (retrievedAssetInfo) { // Remove asset addresses from asset information retrievedAssetInfo = {