mirror of
https://github.com/zhigang1992/wallet.git
synced 2026-03-22 09:35:18 +08:00
refactor: add account to rpc methods
This commit is contained in:
@@ -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} />
|
||||
</>
|
||||
|
||||
@@ -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]
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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) =>
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
23
src/shared/rpc/methods/send-transfer.ts
Normal file
23
src/shared/rpc/methods/send-transfer.ts
Normal 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));
|
||||
}
|
||||
28
src/shared/rpc/methods/sign-message.ts
Normal file
28
src/shared/rpc/methods/sign-message.ts
Normal 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));
|
||||
}
|
||||
@@ -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 }>;
|
||||
|
||||
42
src/shared/rpc/methods/validation.utils.ts
Normal file
42
src/shared/rpc/methods/validation.utils.ts
Normal 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);
|
||||
}
|
||||
@@ -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(
|
||||
|
||||
@@ -24,6 +24,7 @@ test.describe('Message signing', () =>
|
||||
async message =>
|
||||
(window as any).HiroWalletProvider.request('signMessage', {
|
||||
message,
|
||||
paymentType: 'p2wpkh',
|
||||
}).catch((e: unknown) => e),
|
||||
message
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user