refactor: add account to rpc methods

This commit is contained in:
fbwoolf
2023-06-23 15:15:09 -05:00
committed by Fara Woolf
parent 4a3a8a5e36
commit c08b15421e
12 changed files with 203 additions and 61 deletions

View File

@@ -3,7 +3,7 @@ import BigNumber from 'bignumber.js';
import { createMoney } from '@shared/models/money.model';
import { formatMoneyPadded } from '@app/common/money/format-money';
import { useCurrentAccountNativeSegwitAddressIndexZero } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks';
import { useCurrentAccountNativeSegwitIndexZeroSigner } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks';
import { SendTransferActions } from './components/send-transfer-actions';
import { SendTransferDetails } from './components/send-transfer-details';
@@ -11,7 +11,7 @@ import { SendTransferHeader } from './components/send-transfer-header';
import { useRpcSendTransfer } from './use-rpc-send-transfer';
export function RpcSendTransfer() {
const bitcoinAddress = useCurrentAccountNativeSegwitAddressIndexZero();
const nativeSegwitSigner = useCurrentAccountNativeSegwitIndexZeroSigner();
const { address, amount, onChooseTransferFee, origin } = useRpcSendTransfer();
const amountAsMoney = createMoney(new BigNumber(amount), 'BTC');
@@ -24,7 +24,7 @@ export function RpcSendTransfer() {
<SendTransferDetails
address={address}
amount={formattedMoney}
currentAddress={bitcoinAddress}
currentAddress={nativeSegwitSigner.address}
/>
<SendTransferActions action="Continue" onApprove={onChooseTransferFee} />
</>

View File

@@ -17,11 +17,10 @@ function useRpcSignPsbtParams() {
const { origin, tabId } = useDefaultRequestParams();
const requestId = searchParams.get('requestId');
const psbtHex = searchParams.get('hex');
const publicKey = searchParams.get('publicKey');
const allowedSighash = searchParams.getAll('allowedSighash');
const signAtIndex = searchParams.getAll('signAtIndex');
if (!requestId || !psbtHex || !publicKey || !origin) throw new Error('Invalid params');
if (!requestId || !psbtHex || !origin) throw new Error('Invalid params');
return useMemo(
() => ({
@@ -29,13 +28,12 @@ function useRpcSignPsbtParams() {
tabId: tabId ?? 0,
requestId,
psbtHex,
publicKey,
allowedSighash: undefinedIfLengthZero(
allowedSighash.map(h => Number(h)) as btc.SignatureHash[]
),
signAtIndex: undefinedIfLengthZero(signAtIndex.map(h => Number(h))),
}),
[allowedSighash, origin, psbtHex, publicKey, requestId, signAtIndex, tabId]
[allowedSighash, origin, psbtHex, requestId, signAtIndex, tabId]
);
}

View File

@@ -7,7 +7,6 @@ import { isTypedArray } from '@shared/utils';
import { Prettify } from '@shared/utils/type-utils';
import { QueryPrefixes } from '@app/query/query-prefixes';
import { useCurrentNetwork } from '@app/store/networks/networks.selectors';
import { TaprootUtxo } from './use-taproot-address-utxos.query';
@@ -59,14 +58,11 @@ const queryOptions = {
} as const;
export function useOrdinalsAwareUtxoQueries(utxos: TaprootUtxo[] | btc.TransactionInputRequired[]) {
const network = useCurrentNetwork();
return useQueries({
queries: utxos.map(utxo => {
const txId = isTypedArray(utxo.txid) ? bytesToHex(utxo.txid) : utxo.txid;
const txIndex = 'index' in utxo ? utxo.index : utxo.vout;
return {
enabled: network.chain.bitcoin.network === 'mainnet',
queryKey: makeOrdinalsAwareUtxoQueryKey(txId, txIndex),
queryFn: () => fetchOrdinalsAwareUtxo(txId, txIndex),
select: (resp: OrdApiInscriptionTxOutput) =>

View File

@@ -1,9 +1,15 @@
import { RpcErrorCode, SendTransferRequest } from '@btckit/types';
import { RouteUrls } from '@shared/route-urls';
import {
getRpcSendTransferParamErrors,
validateRpcSendTransferParams,
} from '@shared/rpc/methods/send-transfer';
import { makeRpcErrorResponse } from '@shared/rpc/rpc-methods';
import { isDefined, isUndefined } from '@shared/utils';
import {
RequestParams,
getTabIdFromPort,
listenForPopupClose,
makeSearchParamsWithDefaults,
@@ -11,26 +17,45 @@ import {
} from '../messaging-utils';
export async function rpcSendTransfer(message: SendTransferRequest, port: chrome.runtime.Port) {
if (!message.params || !message.params.address || !message.params.amount) {
if (isUndefined(message.params)) {
chrome.tabs.sendMessage(
getTabIdFromPort(port),
makeRpcErrorResponse('sendTransfer', {
id: message.id,
error: { code: RpcErrorCode.INVALID_REQUEST, message: 'Parameters undefined' },
})
);
return;
}
if (!validateRpcSendTransferParams(message.params)) {
chrome.tabs.sendMessage(
getTabIdFromPort(port),
makeRpcErrorResponse('sendTransfer', {
id: message.id,
error: {
code: RpcErrorCode.INVALID_PARAMS,
message:
'Invalid parameters. See the btckit spec for more information: https://btckit.org/docs/spec',
message: getRpcSendTransferParamErrors(message.params),
},
})
);
return;
}
const { urlParams, tabId } = makeSearchParamsWithDefaults(port, [
const requestParams: RequestParams = [
['address', message.params.address],
['amount', message.params.amount],
['requestId', message.id],
]);
];
if (isDefined((message.params as any).account)) {
requestParams.push(['accountIndex', (message.params as any).account.toString()]);
}
const { urlParams, tabId } = makeSearchParamsWithDefaults(port, requestParams);
const { id } = await triggerRequestWindowOpen(RouteUrls.RpcSendTransfer, urlParams);
listenForPopupClose({
tabId,
id,

View File

@@ -1,12 +1,17 @@
import { RpcErrorCode } from '@btckit/types';
import { PaymentTypes, RpcErrorCode } from '@btckit/types';
import { SignMessageRequest } from '@btckit/types/dist/types/methods/sign-message';
import { isSupportedMessageSigningPaymentType } from '@shared/crypto/bitcoin/bip322/bip322-utils';
import { RouteUrls } from '@shared/route-urls';
import {
getRpcSignMessageParamErrors,
validateRpcSignMessageParams,
} from '@shared/rpc/methods/sign-message';
import { makeRpcErrorResponse } from '@shared/rpc/rpc-methods';
import { isString } from '@shared/utils';
import { isDefined, isUndefined } from '@shared/utils';
import {
RequestParams,
getTabIdFromPort,
listenForPopupClose,
makeSearchParamsWithDefaults,
@@ -14,21 +19,34 @@ import {
} from '../messaging-utils';
export async function rpcSignMessage(message: SignMessageRequest, port: chrome.runtime.Port) {
if (!message.params || !isString(message.params.message)) {
if (isUndefined(message.params)) {
chrome.tabs.sendMessage(
getTabIdFromPort(port),
makeRpcErrorResponse('signMessage', {
id: message.id,
error: { code: RpcErrorCode.INVALID_REQUEST, message: 'Parameters undefined' },
})
);
return;
}
if (!validateRpcSignMessageParams(message.params)) {
chrome.tabs.sendMessage(
getTabIdFromPort(port),
makeRpcErrorResponse('signMessage', {
id: message.id,
error: {
code: RpcErrorCode.INVALID_PARAMS,
message:
'Invalid parameters. Message signing requires a message. See the btckit spec for more information: https://btckit.org/docs/spec',
message: getRpcSignMessageParamErrors(message.params),
},
})
);
return;
}
const paymentType = (message.params as any).paymentType ?? 'p2wpkh';
const paymentType: Extract<'p2tr' | 'p2wpkh', PaymentTypes> =
(message.params as any).paymentType ?? 'p2wpkh';
if (!isSupportedMessageSigningPaymentType(paymentType)) {
chrome.tabs.sendMessage(
getTabIdFromPort(port),
@@ -43,12 +61,20 @@ export async function rpcSignMessage(message: SignMessageRequest, port: chrome.r
);
return;
}
const { urlParams, tabId } = makeSearchParamsWithDefaults(port, [
const requestParams: RequestParams = [
['message', message.params.message],
['requestId', message.id],
['paymentType', paymentType],
]);
];
if (isDefined((message.params as any).account)) {
requestParams.push(['accountIndex', (message.params as any).account.toString()]);
}
const { urlParams, tabId } = makeSearchParamsWithDefaults(port, requestParams);
const { id } = await triggerRequestWindowOpen(RouteUrls.RpcSignBip322Message, urlParams);
listenForPopupClose({
tabId,
id,

View File

@@ -9,7 +9,7 @@ import {
validateRpcSignPsbtParams,
} from '@shared/rpc/methods/sign-psbt';
import { makeRpcErrorResponse } from '@shared/rpc/rpc-methods';
import { ensureArray, isDefined } from '@shared/utils';
import { ensureArray, isDefined, isUndefined } from '@shared/utils';
import {
RequestParams,
@@ -29,17 +29,25 @@ function validatePsbt(hex: string) {
}
export async function rpcSignPsbt(message: SignPsbtRequest, port: chrome.runtime.Port) {
if (isUndefined(message.params)) {
chrome.tabs.sendMessage(
getTabIdFromPort(port),
makeRpcErrorResponse('signPsbt', {
id: message.id,
error: { code: RpcErrorCode.INVALID_REQUEST, message: 'Parameters undefined' },
})
);
return;
}
if (!validateRpcSignPsbtParams(message.params)) {
const errors = getRpcSignPsbtParamErrors(message.params);
chrome.tabs.sendMessage(
getTabIdFromPort(port),
makeRpcErrorResponse('signPsbt', {
id: message.id,
error: {
code: RpcErrorCode.INVALID_PARAMS,
message:
'Invalid parameters: ' +
errors.map(e => `Error in path ${e.path}, ${e.message}.`).join(' '),
message: getRpcSignPsbtParamErrors(message.params),
},
})
);
@@ -60,7 +68,6 @@ export async function rpcSignPsbt(message: SignPsbtRequest, port: chrome.runtime
const requestParams: RequestParams = [
['requestId', message.id],
['hex', message.params.hex],
['publicKey', message.params.publicKey],
['network', message.params.network ?? 'mainnet'],
];
@@ -68,11 +75,15 @@ export async function rpcSignPsbt(message: SignPsbtRequest, port: chrome.runtime
requestParams.push(['accountIndex', message.params.account.toString()]);
}
if (isDefined(message.params.allowedSighash))
message.params.allowedSighash.forEach(hash =>
requestParams.push(['allowedSighash', (hash ?? btc.SignatureHash.ALL).toString()])
if (isDefined(message.params.allowedSighash) && message.params.allowedSighash.length)
message.params.allowedSighash.forEach((hash: any) =>
requestParams.push(['allowedSighash', hash.toString()])
);
if (isDefined(message.params.publicKey)) {
requestParams.push(['publicKey', message.params.publicKey.toString()]);
}
if (isDefined(message.params.signAtIndex))
ensureArray(message.params.signAtIndex).forEach(index =>
requestParams.push(['signAtIndex', index.toString()])
@@ -81,6 +92,7 @@ export async function rpcSignPsbt(message: SignPsbtRequest, port: chrome.runtime
const { urlParams, tabId } = makeSearchParamsWithDefaults(port, requestParams);
const { id } = await triggerRequestWindowOpen(RouteUrls.RpcSignPsbt, urlParams);
listenForPopupClose({
tabId,
id,

View File

@@ -0,0 +1,23 @@
import * as yup from 'yup';
import {
accountSchema,
formatValidationErrors,
getRpcParamErrors,
validateRpcParams,
} from './validation.utils';
const rpcSendTransferParamsSchema = yup.object().shape({
account: accountSchema,
address: yup.string().required(),
amount: yup.string().required(),
});
// TODO: Import param types from btckit when updated
export function validateRpcSendTransferParams(obj: unknown) {
return validateRpcParams(obj, rpcSendTransferParamsSchema);
}
export function getRpcSendTransferParamErrors(obj: unknown) {
return formatValidationErrors(getRpcParamErrors(obj, rpcSendTransferParamsSchema));
}

View File

@@ -0,0 +1,28 @@
import { PaymentTypes } from '@btckit/types';
import * as yup from 'yup';
import {
accountSchema,
formatValidationErrors,
getRpcParamErrors,
validateRpcParams,
} from './validation.utils';
// TODO: Import Bip322MessageTypes from btckit when updated
type SupportedBip322MessageTypes = 'bip322';
const rpcSignMessageParamsSchema = yup.object().shape({
type: yup.string<SupportedBip322MessageTypes>(),
account: accountSchema,
message: yup.string().required(),
paymentType: yup.string<PaymentTypes>().required(),
});
// TODO: Import param types from btckit when updated
export function validateRpcSignMessageParams(obj: unknown) {
return validateRpcParams(obj, rpcSignMessageParamsSchema);
}
export function getRpcSignMessageParamErrors(obj: unknown) {
return formatValidationErrors(getRpcParamErrors(obj, rpcSignMessageParamsSchema));
}

View File

@@ -3,44 +3,35 @@ import * as btc from '@scure/btc-signer';
import * as yup from 'yup';
import { networkModes } from '@shared/constants';
import { isNumber, isUndefined } from '@shared/utils';
function testIsNumberOrArrayOfNumbers(value: unknown) {
if (isUndefined(value)) return true;
if (Array.isArray(value)) return value.every(item => isNumber(item));
return isNumber(value);
}
import {
accountSchema,
formatValidationErrors,
getRpcParamErrors,
testIsNumberOrArrayOfNumbers,
validateRpcParams,
} from './validation.utils';
const rpcSignPsbtValidator = yup.object().shape({
publicKey: yup.string().required(),
const rpcSignPsbtParamsSchema = yup.object().shape({
account: accountSchema,
allowedSighash: yup.array().of(yup.mixed().oneOf(Object.values(btc.SignatureHash))),
hex: yup.string().required(),
signAtIndex: yup.mixed<number | number[]>().test(testIsNumberOrArrayOfNumbers),
network: yup.string().oneOf(networkModes),
account: yup.number().integer(),
publicKey: yup.string(),
signAtIndex: yup.mixed<number | number[]>().test(testIsNumberOrArrayOfNumbers),
});
type SignPsbtRequestParams = yup.InferType<typeof rpcSignPsbtValidator>;
export function validateRpcSignPsbtParams(obj: unknown): obj is SignPsbtRequestParams {
try {
rpcSignPsbtValidator.validateSync(obj, { abortEarly: false });
return true;
} catch (e) {
return false;
}
// TODO: Import param types from btckit when updated
export function validateRpcSignPsbtParams(obj: unknown) {
return validateRpcParams(obj, rpcSignPsbtParamsSchema);
}
export function getRpcSignPsbtParamErrors(obj: unknown) {
try {
rpcSignPsbtValidator.validateSync(obj, { abortEarly: false });
return [];
} catch (e) {
if (e instanceof yup.ValidationError) return e.inner;
return [];
}
return formatValidationErrors(getRpcParamErrors(obj, rpcSignPsbtParamsSchema));
}
type SignPsbtRequestParams = yup.InferType<typeof rpcSignPsbtParamsSchema>;
export type SignPsbtRequest = RpcRequest<'signPsbt', SignPsbtRequestParams>;
type SignPsbtResponse = RpcResponse<{ hex: string }>;

View File

@@ -0,0 +1,42 @@
import * as yup from 'yup';
import { isNumber, isUndefined } from '@shared/utils';
export const accountSchema = yup.number().integer();
export function validateRpcParams(
obj: unknown,
validator: yup.ObjectSchema<yup.Maybe<yup.AnyObject>>
) {
try {
validator.validateSync(obj, { abortEarly: false });
return true;
} catch (e) {
return false;
}
}
export function getRpcParamErrors(
obj: unknown,
validator: yup.ObjectSchema<yup.Maybe<yup.AnyObject>>
) {
try {
validator.validateSync(obj, { abortEarly: false });
return [];
} catch (e) {
if (e instanceof yup.ValidationError) return e.inner;
return [];
}
}
export function formatValidationErrors(errors: yup.ValidationError[]) {
return (
'Invalid parameters: ' + errors.map(e => `Error in path ${e.path}, ${e.message}.`).join(' ')
);
}
export function testIsNumberOrArrayOfNumbers(value: unknown) {
if (isUndefined(value)) return true;
if (Array.isArray(value)) return value.every(item => isNumber(item));
return isNumber(value);
}

View File

@@ -1,9 +1,9 @@
import { test } from '@playwright/test';
import { fetchInscription } from '@app/query/bitcoin/ordinals/inscription.query';
import { getNumberOfInscriptionOnUtxo } from '@app/query/bitcoin/ordinals/ordinals-aware-utxo.query';
import { fetchData } from '@app/query/utils';
import { test } from './../fixtures/fixtures';
test.describe('OrdAPI.xyz', () => {
test('should return 3 in case of 3 inscriptions', async () => {
const resp = await getNumberOfInscriptionOnUtxo(

View File

@@ -24,6 +24,7 @@ test.describe('Message signing', () =>
async message =>
(window as any).HiroWalletProvider.request('signMessage', {
message,
paymentType: 'p2wpkh',
}).catch((e: unknown) => e),
message
);