-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
70cf8d4
commit c3073d9
Showing
20 changed files
with
441 additions
and
40 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
68 changes: 68 additions & 0 deletions
68
apps/mobile/src/features/send/send-form/validation/btc.amount-validators.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
import BigNumber from 'bignumber.js'; | ||
import { z } from 'zod'; | ||
|
||
import { BTC_DECIMALS } from '@leather.io/constants'; | ||
import { UtxoResponseItem } from '@leather.io/query'; | ||
import { btcToSat, satToBtc } from '@leather.io/utils'; | ||
|
||
import { FormErrorMessages, currencyPrecisionValidatorFactory } from './common.validation'; | ||
|
||
const minSpendAmountInSats = 546; | ||
|
||
interface BtcInsufficientBalanceValidatorArgs { | ||
calcMaxSpend( | ||
recipient: string, | ||
utxos: UtxoResponseItem[] | ||
): { | ||
spendableBitcoin: BigNumber; | ||
}; | ||
recipient: string; | ||
utxos: UtxoResponseItem[]; | ||
} | ||
export function btcInsufficientBalanceValidator({ | ||
calcMaxSpend, | ||
recipient, | ||
utxos, | ||
}: BtcInsufficientBalanceValidatorArgs) { | ||
return z | ||
.number({ | ||
invalid_type_error: FormErrorMessages.MustBeNumber, | ||
}) | ||
.refine( | ||
value => { | ||
if (!value) return false; | ||
const maxSpend = calcMaxSpend(recipient, utxos); | ||
if (!maxSpend) return false; | ||
const desiredSpend = new BigNumber(value); | ||
if (desiredSpend.isGreaterThan(maxSpend.spendableBitcoin)) return false; | ||
return true; | ||
}, | ||
{ | ||
message: FormErrorMessages.InsufficientFunds, | ||
} | ||
); | ||
} | ||
|
||
export function btcMinimumSpendValidator() { | ||
return z | ||
.number({ | ||
invalid_type_error: FormErrorMessages.MustBeNumber, | ||
}) | ||
.refine( | ||
value => { | ||
if (!value) return false; | ||
const desiredSpend = btcToSat(value); | ||
if (desiredSpend.isLessThan(minSpendAmountInSats)) return false; | ||
return true; | ||
}, | ||
{ | ||
// FIXME: LEA-1647 - move to packages | ||
/* eslint-disable-next-line lingui/no-unlocalized-strings */ | ||
message: `Minimum is ${satToBtc(minSpendAmountInSats)}`, | ||
} | ||
); | ||
} | ||
// btc and stx doing the same thing basically so just use currencyPrecisionValidatorFactory | ||
export function btcAmountPrecisionValidator(errorMsg: string) { | ||
return currencyPrecisionValidatorFactory(BTC_DECIMALS, errorMsg); | ||
} |
16 changes: 16 additions & 0 deletions
16
apps/mobile/src/features/send/send-form/validation/btc.fee-validators.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
// TODO: LEA-1617 - this was unused in extension | ||
import { Money } from '@leather.io/models'; | ||
import { btcToSat } from '@leather.io/utils'; | ||
|
||
import { btcAmountPrecisionValidator } from './btc.amount-validators'; | ||
import { feeValidatorFactory } from './common.validation'; | ||
|
||
// Maybe better to just get rid of this and simplify? | ||
// ts-unused-exports:disable-next-line | ||
export function btcFeeValidator(availableBalance?: Money) { | ||
return feeValidatorFactory({ | ||
availableBalance, | ||
unitConverter: btcToSat, | ||
validator: btcAmountPrecisionValidator, | ||
}); | ||
} |
25 changes: 25 additions & 0 deletions
25
apps/mobile/src/features/send/send-form/validation/btc.validation.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import { t } from '@lingui/macro'; | ||
|
||
import { BitcoinErrorKey } from '@leather.io/bitcoin'; | ||
import { match } from '@leather.io/utils'; | ||
|
||
export function formatBitcoinError(errorMessage: BitcoinErrorKey) { | ||
return match<BitcoinErrorKey>()(errorMessage, { | ||
InvalidAddress: t({ | ||
id: 'bitcoin-error.invalid-address', | ||
message: 'Invalid address', | ||
}), | ||
NoInputsToSign: t({ | ||
id: 'bitcoin-error.no-inputs-to-sign', | ||
message: 'No inputs to sign', | ||
}), | ||
NoOutputsToSign: t({ | ||
id: 'bitcoin-error.no-outputs-to-sign', | ||
message: 'No outputs to sign', | ||
}), | ||
InsufficientFunds: t({ | ||
id: 'bitcoin-error.insufficient-funds', | ||
message: 'Insufficient funds', | ||
}), | ||
}); | ||
} |
103 changes: 103 additions & 0 deletions
103
apps/mobile/src/features/send/send-form/validation/common.validation.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
import BigNumber from 'bignumber.js'; | ||
import { z } from 'zod'; | ||
|
||
import type { Money } from '@leather.io/models'; | ||
import { countDecimals, isFunction, isNumber, moneyToBaseUnit } from '@leather.io/utils'; | ||
|
||
// from extension/src/shared/error-messages | ||
export enum FormErrorMessages { | ||
AdjustedFeeExceedsBalance = 'Fee added exceeds current balance', | ||
AddressRequired = 'Enter an address', | ||
AmountRequired = 'Enter an amount', | ||
BnsAddressNotFound = 'Address not found', | ||
CannotDetermineBalance = 'Cannot determine balance', | ||
CannotDeterminePrecision = 'Cannot determine decimal precision', | ||
CastToNumber = 'Amount must be a `number` type, but the final value was: `NaN`', | ||
DoesNotSupportDecimals = 'Token does not support decimal places', | ||
IncorrectNetworkAddress = 'Address is for incorrect network', | ||
InvalidAddress = 'Address is not valid', | ||
InsufficientBalance = 'Insufficient balance. Available:', | ||
InsufficientFunds = 'Insufficient funds', | ||
MemoExceedsLimit = 'Memo must be less than 34-bytes', | ||
MustBeNumber = 'Amount must be a number', | ||
MustBePositive = 'Amount must be greater than zero', | ||
MustSelectAsset = 'Select a valid token to transfer', | ||
SameAddress = 'Cannot send to yourself', | ||
TooMuchPrecision = 'Token can only have {decimals} decimals', | ||
NonZeroOffsetInscription = 'Sending inscriptions at non-zero offsets is unsupported', | ||
UtxoWithMultipleInscriptions = 'Sending inscription from utxo with multiple inscriptions is unsupported', | ||
InsufficientFundsToCoverFee = 'Insufficient funds to cover fee. Deposit some BTC to your Native Segwit address.', | ||
} | ||
|
||
export function currencyAmountValidator() { | ||
return z | ||
.number({ | ||
// FIXME: LEA-1647 - move to packages | ||
/* eslint-disable-next-line lingui/no-unlocalized-strings */ | ||
invalid_type_error: 'Currency must be a number', | ||
}) | ||
.positive(FormErrorMessages.MustBePositive); | ||
} | ||
|
||
export function currencyPrecisionValidatorFactory(precision: number, errorMessage: string) { | ||
return z | ||
.number({ | ||
required_error: FormErrorMessages.AmountRequired, | ||
invalid_type_error: FormErrorMessages.MustBeNumber, | ||
}) | ||
.refine( | ||
value => { | ||
if (!isNumber(value)) return false; | ||
return countDecimals(value) <= precision; | ||
}, | ||
{ | ||
message: errorMessage, | ||
} | ||
); | ||
} | ||
|
||
export function formatPrecisionError(num?: Money) { | ||
if (!num) return FormErrorMessages.CannotDeterminePrecision; | ||
const error = FormErrorMessages.TooMuchPrecision; | ||
return error.replace('{decimals}', String(num.decimals)); | ||
} | ||
|
||
export function formatInsufficientBalanceError( | ||
sum?: Money, | ||
formatterFn?: (amount: Money) => string | ||
) { | ||
if (!sum) return FormErrorMessages.CannotDetermineBalance; | ||
const isAmountLessThanZero = sum.amount.lt(0); | ||
|
||
const formattedAmount = isFunction(formatterFn) ? formatterFn(sum) : sum.amount.toString(10); | ||
|
||
return `${FormErrorMessages.InsufficientBalance} ${ | ||
isAmountLessThanZero ? '0' : formattedAmount | ||
} ${sum.symbol}`; | ||
} | ||
|
||
interface FeeValidatorFactoryArgs { | ||
availableBalance?: Money; | ||
unitConverter(unit: string | number | BigNumber): BigNumber; | ||
validator(errorMsg: string): z.ZodType<number | undefined>; | ||
} | ||
export function feeValidatorFactory({ | ||
availableBalance, | ||
unitConverter, | ||
validator, | ||
}: FeeValidatorFactoryArgs) { | ||
return validator(formatPrecisionError(availableBalance)).refine( | ||
(fee: unknown) => { | ||
if (!availableBalance || !isNumber(fee)) return false; | ||
return availableBalance.amount.isGreaterThanOrEqualTo(unitConverter(fee)); | ||
}, | ||
{ | ||
message: formatInsufficientBalanceError(availableBalance, sum => | ||
moneyToBaseUnit(sum).toString() | ||
), | ||
} | ||
); | ||
} | ||
|
||
// <<<< PETE continue here migrating the fee stuff | ||
// - then move them to packages to avoid the stupid lingui errors |
Oops, something went wrong.