chore(send): min/max for direct send amount

This commit is contained in:
theborakompanioni 2026-04-30 13:10:02 +02:00 committed by Thebora Kompanioni
parent 161c404614
commit cfcc5d498f
3 changed files with 42 additions and 9 deletions

View file

@ -1,10 +1,12 @@
import { getAddressInfo, validate as isValidBitcoinAddress, Network } from 'bitcoin-address-validation'
import type { TFunction } from 'i18next'
import * as yup from 'yup'
import { TOTAL_COIN_SUPPLY } from '@/constants/jam'
import type { AddressSummary, Jar } from '@/context/JamWalletInfoContext'
import { TX_FEE_UNITS } from '@/lib/feeConfig'
import type { JamFeeConfigValues } from '@/lib/feeConfig'
import { pseudoRandomInteger } from '@/lib/utils'
import type { AmountSats } from '@/types/global'
import { toTxFeeFormDefaultValues, createTxFeeFormSchema } from './TxFeeForm.schema'
import type { SendFormValues } from './types'
@ -18,6 +20,10 @@ export const initialNumberOfCollaborators = (minValue: number): number => {
const MAX_NUM_COLLABORATORS = 99
// min amount joinmarket is able to send (without errorsing, last check with v0.9.11 on 2026-04-26)
export const MIN_SEND_AMOUNT: AmountSats = 292
export const MAX_SEND_AMOUNT: AmountSats = TOTAL_COIN_SUPPLY
const FORM_INPUT_DEFAULT_VALUES: Partial<SendFormValues> = {
source: undefined,
destination: undefined,
@ -96,12 +102,7 @@ export const createSendFormSchema = (
isSweep: yup.boolean().default(false).required(),
sweepAmount: yup.number().when('isSweep', {
is: (val: boolean) => val === true,
then: (schema) =>
schema
.integer()
.min(1)
.max(21_000_000 * 100_000_000)
.required(),
then: (schema) => schema.integer().min(1).max(MAX_SEND_AMOUNT).required(),
otherwise: (schema) =>
schema
.transform(() => null)
@ -120,8 +121,8 @@ export const createSendFormSchema = (
.integer(t('send.feedback_invalid_amount'))
.transform((value) => (Number.isSafeInteger(value) ? Number(value) : null))
.nonNullable(t('send.feedback_invalid_amount'))
.min(1, t('send.feedback_invalid_amount'))
.max(21_000_000 * 100_000_000, t('send.feedback_invalid_amount'))
.min(MIN_SEND_AMOUNT, t('send.feedback_invalid_amount'))
.max(MAX_SEND_AMOUNT, t('send.feedback_invalid_amount'))
.required(t('send.feedback_invalid_amount')),
}),
})

View file

@ -37,7 +37,13 @@ import { Spinner } from '../ui/spinner'
import { Switch } from '../ui/switch'
import JarSelectorDialog from './JarSelectorDialog'
import { SendCoinjoinPreconditionAlert } from './SendCoinjoinPreconditionAlert'
import { createSendFormSchema, initialNumberOfCollaborators, toSendFormDefaultValues } from './SendForm.schema'
import {
createSendFormSchema,
initialNumberOfCollaborators,
MAX_SEND_AMOUNT,
MIN_SEND_AMOUNT,
toSendFormDefaultValues,
} from './SendForm.schema'
import { TxFeeForm } from './TxFeeForm'
import { estimateMaxCollaboratorFee } from './feeEstimate'
import type { SendFormValues } from './types'
@ -451,6 +457,8 @@ export function SendForm({
required: false,
disabled,
})}
min={MIN_SEND_AMOUNT}
max={MAX_SEND_AMOUNT}
type="number"
className="h-auto pl-9"
placeholder={t('send.placeholder_amount_input')}

View file

@ -106,3 +106,27 @@ export const JAM_JM_WEBSOCKET_RECONNECT_INTERVAL_MAX: Milliseconds = Math.max(
* the `/display` endpoint taking more than ~15s.
*/
export const GAPLIMIT_WARN_THRESHOLD = 250
export function totalCoinSupply(): AmountSats {
const SATS_PER_COIN = 100_000_000
const initialSubsidy = 50 * SATS_PER_COIN
const blockPerHalving = 210_000
let subsidy = initialSubsidy
let total = 0
let halving = 0
// Continue until subsidy becomes zero
while (subsidy > 0n) {
total += blockPerHalving * subsidy
// Prepare next halving: integer division by 2 (floor)
subsidy = subsidy / 2
halving += 1
// Safety: break if loop would be infinite (not needed here but good practice)
if (halving > 1000) break
}
return total
}
export const TOTAL_COIN_SUPPLY: AmountSats = totalCoinSupply()