Skip to content

Commit

Permalink
feat: add send transfer validation
Browse files Browse the repository at this point in the history
  • Loading branch information
alter-eggo committed Apr 16, 2024
1 parent a9ac595 commit e387351
Show file tree
Hide file tree
Showing 18 changed files with 128 additions and 24 deletions.
2 changes: 1 addition & 1 deletion src/app/common/error-formatters.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Money } from '@shared/models/money.model';
import { isFunction } from '@shared/utils';

import { FormErrorMessages } from '@app/common/error-messages';
import { FormErrorMessages } from '@shared/error-messages';

export function formatPrecisionError(num?: Money) {
if (!num) return FormErrorMessages.CannotDeterminePrecision;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,14 +156,14 @@ export function useGenerateUnsignedNativeSegwitMultipleRecipientsTx() {
});
}

outputs.forEach((output, index) => {
outputs.forEach(output => {
// When coin selection returns output with no address we assume it is
// a change output
if (!output.address) {
tx.addOutputAddress(signer.address, BigInt(output.value), networkMode);
return;
}
tx.addOutputAddress(values.recipients[index].address, BigInt(output.value), networkMode);
tx.addOutputAddress(output.address, BigInt(output.value), networkMode);
});

return { hex: tx.hex, fee, psbt: tx.toPSBT(), inputs };
Expand Down
2 changes: 1 addition & 1 deletion src/app/common/validation/forms/address-validators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import * as yup from 'yup';
import { BitcoinNetworkModes, NetworkConfiguration } from '@shared/constants';
import { isString } from '@shared/utils';

import { FormErrorMessages } from '@app/common/error-messages';
import { FormErrorMessages } from '@shared/error-messages';
import { validateAddressChain, validateStacksAddress } from '@app/common/stacks-utils';

function notCurrentAddressValidatorFactory(currentAddress: string) {
Expand Down
2 changes: 1 addition & 1 deletion src/app/common/validation/forms/amount-validators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
import { UtxoResponseItem } from '@app/query/bitcoin/bitcoin-client';

import { formatInsufficientBalanceError, formatPrecisionError } from '../../error-formatters';
import { FormErrorMessages } from '../../error-messages';
import { FormErrorMessages } from '../../../../shared/error-messages';
import { currencyAmountValidator, stxAmountPrecisionValidator } from './currency-validators';

const minSpendAmountInSats = 6000;
Expand Down
2 changes: 1 addition & 1 deletion src/app/common/validation/forms/currency-validators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import * as yup from 'yup';
import { BTC_DECIMALS, STX_DECIMALS } from '@shared/constants';
import { isNumber } from '@shared/utils';

import { FormErrorMessages } from '@app/common/error-messages';
import { FormErrorMessages } from '@shared/error-messages';
import { countDecimals } from '@app/common/math/helpers';

export function currencyAmountValidator() {
Expand Down
2 changes: 1 addition & 1 deletion src/app/common/validation/forms/recipient-validators.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { NetworkConfiguration } from '@shared/constants';
import { stacksChainIdToCoreNetworkMode } from '@shared/crypto/stacks/stacks.utils';

import { FormErrorMessages } from '@app/common/error-messages';
import { FormErrorMessages } from '@shared/error-messages';

import {
notCurrentAddressValidator,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ export function RpcSendTransferConfirmation() {

return (
<>
<Stack gap={4}>
<Stack gap={4} width="100%">
{recipients.map((recipient, index) => (
<SendTransferConfirmationDetails
key={index}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { OrdinalSendFormValues } from '@shared/models/form.model';
import { RouteUrls } from '@shared/route-urls';
import { isError } from '@shared/utils';

import { FormErrorMessages } from '@app/common/error-messages';
import { FormErrorMessages } from '@shared/error-messages';
import { useAnalytics } from '@app/common/hooks/analytics/use-analytics';
import { formFeeRowValue } from '@app/common/send/utils';
import { InsufficientFundsError } from '@app/common/transactions/bitcoin/coinselect/local-coin-selection';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { useFormikContext } from 'formik';
import { logger } from '@shared/logger';
import { BitcoinSendFormValues, StacksSendFormValues } from '@shared/models/form.model';

import { FormErrorMessages } from '@app/common/error-messages';
import { FormErrorMessages } from '@shared/error-messages';
import { StacksClient } from '@app/query/stacks/stacks-client';
import { useStacksClient } from '@app/store/common/api-clients.hooks';
import { useCurrentNetworkState } from '@app/store/networks/networks.hooks';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { StacksSendFormValues } from '@shared/models/form.model';
import { Money } from '@shared/models/money.model';
import { isEmpty } from '@shared/utils';

import { FormErrorMessages } from '@app/common/error-messages';
import { FormErrorMessages } from '@shared/error-messages';
import { stxMemoValidator } from '@app/common/validation/forms/memo-validators';
import { stxRecipientValidator } from '@app/common/validation/forms/recipient-validators';
import { nonceValidator } from '@app/common/validation/nonce-validators';
Expand Down
2 changes: 1 addition & 1 deletion src/app/pages/swap/hooks/use-swap-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { FeeTypes } from '@shared/models/fees/fees.model';
import { StacksTransactionFormValues } from '@shared/models/form.model';
import { Money, createMoney } from '@shared/models/money.model';

import { FormErrorMessages } from '@app/common/error-messages';
import { FormErrorMessages } from '@shared/error-messages';
import { convertAmountToFractionalUnit } from '@app/common/money/calculate-money';
import { useNextNonce } from '@app/query/stacks/nonce/account-nonces.hooks';

Expand Down
File renamed without changes.
3 changes: 3 additions & 0 deletions src/shared/forms/amount-validators.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function checkIfDigitsOnly(value: string) {
return /^\d+$/.test(value);
}
53 changes: 53 additions & 0 deletions src/shared/forms/bitcoin-address-validators.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { AddressType, Network, getAddressInfo, validate } from 'bitcoin-address-validation';
import * as yup from 'yup';

import { BitcoinNetworkModes } from '@shared/constants';
import { FormErrorMessages } from '@shared/error-messages';
import { isString } from '@shared/utils';

export function btcAddressValidator() {
return yup
.string()
.defined(FormErrorMessages.AddressRequired)
.test((input, context) => {
if (!input) return false;
if (!validate(input))
return context.createError({
message: FormErrorMessages.InvalidAddress,
});
return true;
});
}

// ts-unused-exports:disable-next-line
export function btcTaprootAddressValidator() {
return yup.string().test((input, context) => {
if (!input || !validate(input)) return false;
if (getAddressInfo(input).type !== AddressType.p2tr)
return context.createError({
message: 'Only taproot addresses are supported',
});
return true;
});
}

function btcAddressNetworkValidatorFactory(network: BitcoinNetworkModes) {
function getAddressNetworkType(network: BitcoinNetworkModes): Network {
// Signet uses testnet address format, this parsing is to please the
// validation library
if (network === 'signet') return Network.testnet;
return network as Network;
}

return (value?: string) => {
if (!isString(value)) return false;
return validate(value, getAddressNetworkType(network));
};
}

export function btcAddressNetworkValidator(network: BitcoinNetworkModes) {
return yup.string().test({
test: btcAddressNetworkValidatorFactory(network),
message: FormErrorMessages.IncorrectNetworkAddress,
});
}
8 changes: 4 additions & 4 deletions src/shared/rpc/methods/send-transfer.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,11 @@ describe('`sendTransfer` method', () => {
recipients: [
{
address: 'bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq',
amount: '0.0001',
amount: '10000',
},
{
address: 'bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq',
amount: '0.0001',
amount: '800',
},
],
};
Expand All @@ -50,7 +50,7 @@ describe('`sendTransfer` method', () => {
network: 'mainnet',
account: 0,
address: 'bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq',
amount: '0.0001',
amount: '10000',
};

const newParams = {
Expand All @@ -59,7 +59,7 @@ describe('`sendTransfer` method', () => {
recipients: [
{
address: 'bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq',
amount: '0.0001',
amount: '10000',
},
],
};
Expand Down
34 changes: 28 additions & 6 deletions src/shared/rpc/methods/send-transfer.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import type { SendTransferRequestParams } from '@btckit/types';
import * as yup from 'yup';

import { WalletDefaultNetworkConfigurationIds } from '@shared/constants';
import { type BitcoinNetworkModes, WalletDefaultNetworkConfigurationIds } from '@shared/constants';
import { FormErrorMessages } from '@shared/error-messages';
import { checkIfDigitsOnly } from '@shared/forms/amount-validators';
import {
btcAddressNetworkValidator,
btcAddressValidator,
} from '@shared/forms/bitcoin-address-validators';

import {
accountSchema,
Expand All @@ -19,11 +25,27 @@ export const rpcSendTransferParamsSchemaLegacy = yup.object().shape({

export const rpcSendTransferParamsSchema = yup.object().shape({
account: accountSchema,
recipients: yup
.array()
.of(yup.object().shape({ address: yup.string().required(), amount: yup.string().required() }))
.required(),
network: yup.string().oneOf(Object.values(WalletDefaultNetworkConfigurationIds)),
network: yup.string().required().oneOf(Object.values(WalletDefaultNetworkConfigurationIds)),
recipients: yup.array().of(
yup.object().shape({
// check network is valid for address
address: btcAddressValidator().test(
'address-network-validation',
FormErrorMessages.IncorrectNetworkAddress,
(value, context) => {
const contextOptions = context.options as any;
const network = contextOptions.from[1].value.network as BitcoinNetworkModes;
return btcAddressNetworkValidator(network).isValidSync(value);
}
),
amount: yup
.string()
.required()
.test('amount-validation', 'Sat denominated amounts only', value => {
return checkIfDigitsOnly(value);
}),
})
),
});

export interface RpcSendTransferParamsLegacy extends SendTransferRequestParams {
Expand Down
28 changes: 27 additions & 1 deletion test-app/src/components/bitcoin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -417,7 +417,7 @@ export const Bitcoin = () => {
},
{
address: TEST_TESTNET_ACCOUNT_2_BTC_ADDRESS,
amount: '10000',
amount: '0.010000',
},
],
network: 'testnet',
Expand All @@ -432,6 +432,32 @@ export const Bitcoin = () => {
>
Send transfer to multiple addresses
</styled.button>
<styled.button
mt={3}
onClick={() => {
console.log('requesting');
(window as any).LeatherProvider?.request('sendTransfer', {
recipients: [
{
address: TEST_TESTNET_ACCOUNT_2_BTC_ADDRESS,
amount: '10000',
},
{
address: TEST_TESTNET_ACCOUNT_2_BTC_ADDRESS,
amount: '10000',
},
],
})
.then((resp: any) => {
console.log({ sucesss: resp });
})
.catch((error: Error) => {
console.log({ error });
});
}}
>
Send transfer validate error
</styled.button>
</Box>
);
};
2 changes: 1 addition & 1 deletion tests/specs/send/send-stx.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { getDisplayerAddress } from '@tests/utils';

import { STX_DECIMALS } from '@shared/constants';

import { FormErrorMessages } from '@app/common/error-messages';
import { FormErrorMessages } from '@shared/error-messages';

import { test } from '../../fixtures/fixtures';

Expand Down

0 comments on commit e387351

Please sign in to comment.