mirror of
https://github.com/zhigang1992/wallet.git
synced 2026-01-12 17:53:19 +08:00
Merge pull request #3901 from hirosystems/release/psbt-updates
Release/psbt updates
This commit is contained in:
@@ -22,7 +22,7 @@ RUN apt-get update -y \
|
||||
&& ./build-ext.sh /stacks-wallet-chromium.zip
|
||||
|
||||
|
||||
FROM alpine:3.15.3
|
||||
FROM alpine:3.16
|
||||
COPY --from=builder /stacks-wallet-chromium.zip .
|
||||
|
||||
# Wait for extension.zip to be copied into local
|
||||
|
||||
@@ -11,21 +11,19 @@ import {
|
||||
} from '@app/common/transactions/bitcoin/coinselect/local-coin-selection';
|
||||
import { useSpendableCurrentNativeSegwitAccountUtxos } from '@app/query/bitcoin/address/use-spendable-native-segwit-utxos';
|
||||
import { useBitcoinScureLibNetworkConfig } from '@app/store/accounts/blockchain/bitcoin/bitcoin-keychain';
|
||||
import {
|
||||
useCurrentAccountNativeSegwitAddressIndexZero,
|
||||
useCurrentAccountNativeSegwitSigner,
|
||||
useCurrentBitcoinNativeSegwitAddressIndexPublicKeychain,
|
||||
} from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks';
|
||||
import { useCurrentAccountNativeSegwitIndexZeroSigner } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks';
|
||||
|
||||
interface GenerateNativeSegwitTxValues {
|
||||
amount: Money;
|
||||
recipient: string;
|
||||
}
|
||||
export function useGenerateSignedNativeSegwitTx() {
|
||||
const currentAccountBtcAddress = useCurrentAccountNativeSegwitAddressIndexZero();
|
||||
const { data: utxos } = useSpendableCurrentNativeSegwitAccountUtxos();
|
||||
const currentAddressIndexKeychain = useCurrentBitcoinNativeSegwitAddressIndexPublicKeychain();
|
||||
const createSigner = useCurrentAccountNativeSegwitSigner();
|
||||
const {
|
||||
address,
|
||||
publicKeychain: currentAddressIndexKeychain,
|
||||
sign,
|
||||
} = useCurrentAccountNativeSegwitIndexZeroSigner();
|
||||
|
||||
const networkMode = useBitcoinScureLibNetworkConfig();
|
||||
|
||||
@@ -33,11 +31,8 @@ export function useGenerateSignedNativeSegwitTx() {
|
||||
(values: GenerateNativeSegwitTxValues, feeRate: number, isSendingMax?: boolean) => {
|
||||
if (!utxos) return;
|
||||
if (!feeRate) return;
|
||||
if (!createSigner) return;
|
||||
|
||||
try {
|
||||
const signer = createSigner(0);
|
||||
|
||||
const tx = new btc.Transaction();
|
||||
|
||||
const amountAsNumber = values.amount.amount.toNumber();
|
||||
@@ -77,12 +72,12 @@ export function useGenerateSignedNativeSegwitTx() {
|
||||
// When coin selection returns output with no address we assume it is
|
||||
// a change output
|
||||
if (!output.address) {
|
||||
tx.addOutputAddress(currentAccountBtcAddress, BigInt(output.value), networkMode);
|
||||
tx.addOutputAddress(address, BigInt(output.value), networkMode);
|
||||
return;
|
||||
}
|
||||
tx.addOutputAddress(values.recipient, BigInt(output.value), networkMode);
|
||||
});
|
||||
signer.sign(tx);
|
||||
sign(tx);
|
||||
tx.finalize();
|
||||
|
||||
return { hex: tx.hex, fee };
|
||||
@@ -92,12 +87,6 @@ export function useGenerateSignedNativeSegwitTx() {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
[
|
||||
createSigner,
|
||||
currentAccountBtcAddress,
|
||||
currentAddressIndexKeychain?.publicKey,
|
||||
networkMode,
|
||||
utxos,
|
||||
]
|
||||
[address, currentAddressIndexKeychain?.publicKey, networkMode, sign, utxos]
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
import {
|
||||
Brc20Token,
|
||||
useBrc20TokensByAddressQuery,
|
||||
useBrc20TokensQuery,
|
||||
} from '@app/query/bitcoin/ordinals/brc20/brc20-tokens.query';
|
||||
import { useCurrentAccountTaprootAddressIndexZeroPayment } from '@app/store/accounts/blockchain/bitcoin/taproot-account.hooks';
|
||||
|
||||
interface Brc20TokensLoaderProps {
|
||||
children(brc20Tokens: Brc20Token[]): JSX.Element;
|
||||
}
|
||||
export function Brc20TokensLoader({ children }: Brc20TokensLoaderProps) {
|
||||
const { address: bitcoinAddressTaproot } = useCurrentAccountTaprootAddressIndexZeroPayment();
|
||||
const { data: brc20Tokens } = useBrc20TokensByAddressQuery(bitcoinAddressTaproot);
|
||||
if (!bitcoinAddressTaproot || !brc20Tokens) return null;
|
||||
const { data: allBrc20TokensResponse } = useBrc20TokensQuery();
|
||||
const brc20Tokens = allBrc20TokensResponse?.pages
|
||||
.flatMap(page => page.brc20Tokens)
|
||||
.filter(token => token.length > 0)
|
||||
.flatMap(token => token);
|
||||
|
||||
if (!brc20Tokens) return null;
|
||||
return children(brc20Tokens);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,10 @@ import { BitcoinTransactionItem } from '@app/features/activity-list/components/t
|
||||
import { StacksTransaction } from './stacks-transaction/stacks-transaction';
|
||||
import { TransactionListTxs } from './transaction-list.model';
|
||||
|
||||
function renderTransaction(tx: TransactionListTxs) {
|
||||
interface TransactionListItemProps {
|
||||
tx: TransactionListTxs;
|
||||
}
|
||||
export function TransactionListItem({ tx }: TransactionListItemProps) {
|
||||
switch (tx.blockchain) {
|
||||
case 'bitcoin':
|
||||
return <BitcoinTransactionItem transaction={tx.transaction} />;
|
||||
@@ -13,10 +16,3 @@ function renderTransaction(tx: TransactionListTxs) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
interface TransactionListItemProps {
|
||||
tx: TransactionListTxs;
|
||||
}
|
||||
export function TransactionListItem({ tx }: TransactionListItemProps) {
|
||||
return renderTransaction(tx);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import * as btc from '@scure/btc-signer';
|
||||
|
||||
import {
|
||||
PsbtDecodedUtxosMainnet,
|
||||
PsbtDecodedUtxosTestnet,
|
||||
} from '@app/features/psbt-signer/hooks/use-psbt-decoded-utxos';
|
||||
|
||||
import { PsbtPlaceholderNode } from '../psbt-decoded-request-node/psbt-placeholder-node';
|
||||
import { PsbtUnsignedInputList } from '../psbt-unsigned-input-list/psbt-unsigned-input-list';
|
||||
import { PsbtUnsignedOutputList } from '../psbt-unsigned-output-list/psbt-unsigned-output-list';
|
||||
@@ -7,16 +12,17 @@ import { PsbtUnsignedOutputList } from '../psbt-unsigned-output-list/psbt-unsign
|
||||
interface PsbtDecodedRequestSimpleProps {
|
||||
bitcoinAddressNativeSegwit: string;
|
||||
bitcoinAddressTaproot: string;
|
||||
inputs: btc.TransactionInputRequired[];
|
||||
inputs: PsbtDecodedUtxosMainnet | PsbtDecodedUtxosTestnet;
|
||||
outputs: btc.TransactionOutputRequired[];
|
||||
showPlaceholder: boolean;
|
||||
}
|
||||
|
||||
export function PsbtDecodedRequestSimple({
|
||||
bitcoinAddressNativeSegwit,
|
||||
bitcoinAddressTaproot,
|
||||
inputs,
|
||||
outputs,
|
||||
showPlaceholder,
|
||||
inputs,
|
||||
}: PsbtDecodedRequestSimpleProps) {
|
||||
if (showPlaceholder) return <PsbtPlaceholderNode />;
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import * as btc from '@scure/btc-signer';
|
||||
import { Stack, color } from '@stacks/ui';
|
||||
|
||||
import { useCurrentAccountNativeSegwitIndexZeroSigner } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks';
|
||||
import { useCurrentAccountTaprootAddressIndexZeroPayment } from '@app/store/accounts/blockchain/bitcoin/taproot-account.hooks';
|
||||
import { useCurrentAccountTaprootIndexZeroSigner } from '@app/store/accounts/blockchain/bitcoin/taproot-account.hooks';
|
||||
|
||||
import { usePsbtDecodedRequest } from '../../hooks/use-psbt-decoded-request';
|
||||
import { DecodedPsbt } from '../../hooks/use-psbt-signer';
|
||||
@@ -15,7 +15,7 @@ interface PsbtDecodedRequestProps {
|
||||
}
|
||||
export function PsbtDecodedRequest({ psbt }: PsbtDecodedRequestProps) {
|
||||
const nativeSegwitSigner = useCurrentAccountNativeSegwitIndexZeroSigner();
|
||||
const { address: bitcoinAddressTaproot } = useCurrentAccountTaprootAddressIndexZeroPayment();
|
||||
const { address: bitcoinAddressTaproot } = useCurrentAccountTaprootIndexZeroSigner();
|
||||
const unsignedInputs: btc.TransactionInputRequired[] = psbt.global.unsignedTx?.inputs ?? [];
|
||||
const unsignedOutputs: btc.TransactionOutputRequired[] = psbt.global.unsignedTx?.outputs ?? [];
|
||||
|
||||
@@ -24,6 +24,7 @@ export function PsbtDecodedRequest({ psbt }: PsbtDecodedRequestProps) {
|
||||
shouldDefaultToAdvancedView,
|
||||
shouldShowPlaceholder,
|
||||
showAdvancedView,
|
||||
unsignedUtxos,
|
||||
} = usePsbtDecodedRequest({
|
||||
unsignedInputs,
|
||||
unsignedOutputs,
|
||||
@@ -45,7 +46,7 @@ export function PsbtDecodedRequest({ psbt }: PsbtDecodedRequestProps) {
|
||||
<PsbtDecodedRequestSimple
|
||||
bitcoinAddressNativeSegwit={nativeSegwitSigner.address}
|
||||
bitcoinAddressTaproot={bitcoinAddressTaproot}
|
||||
inputs={unsignedInputs}
|
||||
inputs={unsignedUtxos}
|
||||
outputs={unsignedOutputs}
|
||||
showPlaceholder={shouldShowPlaceholder}
|
||||
/>
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import { truncateMiddle } from '@stacks/ui-utils';
|
||||
|
||||
import { logger } from '@shared/logger';
|
||||
|
||||
import { i18nFormatCurrency } from '@app/common/money/format-money';
|
||||
import { satToBtc } from '@app/common/money/unit-conversion';
|
||||
import { OrdApiInscriptionTxOutput } from '@app/query/bitcoin/ordinals/ordinals-aware-utxo.query';
|
||||
import { TaprootUtxo } from '@app/query/bitcoin/ordinals/use-taproot-address-utxos.query';
|
||||
import { useCalculateBitcoinFiatValue } from '@app/query/common/market-data/market-data.hooks';
|
||||
|
||||
import { PsbtDecodedNodeLayout } from '../../psbt-decoded-request-node/psbt-decoded-node.layout';
|
||||
import { PsbtUnsignedInputWithInscription } from './psbt-unsigned-input-with-inscription';
|
||||
|
||||
interface PsbtUnsignedInputItemWithPossibleInscriptionProps {
|
||||
addressNativeSegwit: string;
|
||||
addressTaproot: string;
|
||||
utxo: TaprootUtxo & OrdApiInscriptionTxOutput;
|
||||
}
|
||||
export function PsbtUnsignedInputItemWithPossibleInscription({
|
||||
addressNativeSegwit,
|
||||
addressTaproot,
|
||||
utxo,
|
||||
}: PsbtUnsignedInputItemWithPossibleInscriptionProps) {
|
||||
const calculateBitcoinFiatValue = useCalculateBitcoinFiatValue();
|
||||
|
||||
const isInputCurrentAddress =
|
||||
utxo.address === addressNativeSegwit || utxo.address === addressTaproot;
|
||||
const inputValue = satToBtc(utxo.value).toString();
|
||||
|
||||
if (!utxo.address) {
|
||||
logger.error('UTXO does not have an address');
|
||||
return null;
|
||||
}
|
||||
|
||||
return utxo.inscriptions ? (
|
||||
<PsbtUnsignedInputWithInscription
|
||||
address={utxo.address}
|
||||
inputValue={inputValue}
|
||||
path={utxo.inscriptions}
|
||||
/>
|
||||
) : (
|
||||
<PsbtDecodedNodeLayout
|
||||
hoverLabel={utxo.address}
|
||||
subtitle={truncateMiddle(utxo.address)}
|
||||
subValue={i18nFormatCurrency(calculateBitcoinFiatValue(inputValue))}
|
||||
value={`${isInputCurrentAddress ? '-' : '+'}${inputValue}`}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -1,18 +1,17 @@
|
||||
import { truncateMiddle } from '@stacks/ui-utils';
|
||||
|
||||
import { BitcoinTransactionVectorOutput } from '@shared/models/transactions/bitcoin-transaction.model';
|
||||
|
||||
import { i18nFormatCurrency } from '@app/common/money/format-money';
|
||||
import { satToBtc } from '@app/common/money/unit-conversion';
|
||||
import { OrdApiInscriptionTxOutput } from '@app/query/bitcoin/ordinals/ordinals-aware-utxo.query';
|
||||
import { TaprootUtxo } from '@app/query/bitcoin/ordinals/use-taproot-address-utxos.query';
|
||||
import { useCalculateBitcoinFiatValue } from '@app/query/common/market-data/market-data.hooks';
|
||||
|
||||
import { PsbtDecodedNodeLayout } from '../../psbt-decoded-request-node/psbt-decoded-node.layout';
|
||||
import { PsbtUnsignedInputWithInscription } from './psbt-unsigned-input-with-inscription';
|
||||
|
||||
interface PsbtUnsignedInputItemProps {
|
||||
addressNativeSegwit: string;
|
||||
addressTaproot: string;
|
||||
utxo: TaprootUtxo & OrdApiInscriptionTxOutput;
|
||||
utxo: BitcoinTransactionVectorOutput;
|
||||
}
|
||||
export function PsbtUnsignedInputItem({
|
||||
addressNativeSegwit,
|
||||
@@ -22,24 +21,15 @@ export function PsbtUnsignedInputItem({
|
||||
const calculateBitcoinFiatValue = useCalculateBitcoinFiatValue();
|
||||
|
||||
const isInputCurrentAddress =
|
||||
utxo.address === addressNativeSegwit || utxo.address === addressTaproot;
|
||||
utxo.scriptpubkey_address === addressNativeSegwit ||
|
||||
utxo.scriptpubkey_address === addressTaproot;
|
||||
const inputValue = satToBtc(utxo.value).toString();
|
||||
const fiatValue = i18nFormatCurrency(calculateBitcoinFiatValue(utxo.value));
|
||||
const inscription = utxo.inscriptions;
|
||||
|
||||
if (!utxo.address) return null;
|
||||
|
||||
return inscription ? (
|
||||
<PsbtUnsignedInputWithInscription
|
||||
address={utxo.address}
|
||||
inputValue={inputValue}
|
||||
path={inscription}
|
||||
/>
|
||||
) : (
|
||||
return (
|
||||
<PsbtDecodedNodeLayout
|
||||
hoverLabel={utxo.address}
|
||||
subtitle={truncateMiddle(utxo.address)}
|
||||
subValue={`${fiatValue} USD`}
|
||||
hoverLabel={utxo.scriptpubkey_address}
|
||||
subtitle={truncateMiddle(utxo.scriptpubkey_address)}
|
||||
subValue={i18nFormatCurrency(calculateBitcoinFiatValue(inputValue))}
|
||||
value={`${isInputCurrentAddress ? '-' : '+'}${inputValue}`}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import { Box, Text } from '@stacks/ui';
|
||||
|
||||
import { HasChildren } from '@app/common/has-children';
|
||||
|
||||
export function PsbtUnsignedInputListLayout({ children }: HasChildren) {
|
||||
return (
|
||||
<Box background="white" borderTopLeftRadius="16px" borderTopRightRadius="16px" p="loose">
|
||||
<Text fontWeight={500}>Inputs</Text>
|
||||
{children}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -1,40 +1,62 @@
|
||||
import * as btc from '@scure/btc-signer';
|
||||
import { Box, Text } from '@stacks/ui';
|
||||
|
||||
import { isUndefined } from '@shared/utils';
|
||||
|
||||
import { useOrdinalsAwareUtxoQueries } from '@app/query/bitcoin/ordinals/ordinals-aware-utxo.query';
|
||||
import {
|
||||
PsbtDecodedUtxosMainnet,
|
||||
PsbtDecodedUtxosTestnet,
|
||||
} from '@app/features/psbt-signer/hooks/use-psbt-decoded-utxos';
|
||||
|
||||
import { PsbtDecodedNodeLayout } from '../psbt-decoded-request-node/psbt-decoded-node.layout';
|
||||
import { PsbtUnsignedInputItem } from './components/psbt-unsigned-input-item';
|
||||
import { PsbtUnsignedInputItemWithPossibleInscription } from './components/psbt-unsigned-input-item-with-possible-inscription';
|
||||
import { PsbtUnsignedInputListLayout } from './components/psbt-unsigned-input-list.layout';
|
||||
|
||||
interface PsbtUnsignedInputListProps {
|
||||
addressNativeSegwit: string;
|
||||
addressTaproot: string;
|
||||
inputs: btc.TransactionInputRequired[];
|
||||
inputs: PsbtDecodedUtxosMainnet | PsbtDecodedUtxosTestnet;
|
||||
}
|
||||
export function PsbtUnsignedInputList({
|
||||
addressNativeSegwit,
|
||||
addressTaproot,
|
||||
inputs,
|
||||
}: PsbtUnsignedInputListProps) {
|
||||
const unsignedUtxos = useOrdinalsAwareUtxoQueries(inputs).map(query => query.data);
|
||||
if (!inputs.utxos.length)
|
||||
return (
|
||||
<PsbtUnsignedInputListLayout>
|
||||
<PsbtDecodedNodeLayout value="No inputs found" />
|
||||
</PsbtUnsignedInputListLayout>
|
||||
);
|
||||
|
||||
return (
|
||||
<Box background="white" borderTopLeftRadius="16px" borderTopRightRadius="16px" p="loose">
|
||||
<Text fontWeight={500}>Inputs</Text>
|
||||
{unsignedUtxos.map(utxo => {
|
||||
if (isUndefined(utxo)) return <PsbtDecodedNodeLayout value="No input data found" />;
|
||||
|
||||
return (
|
||||
<PsbtUnsignedInputItem
|
||||
addressNativeSegwit={addressNativeSegwit}
|
||||
addressTaproot={addressTaproot}
|
||||
key={utxo.transaction}
|
||||
utxo={utxo}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</Box>
|
||||
);
|
||||
switch (inputs.network) {
|
||||
case 'mainnet':
|
||||
return (
|
||||
<PsbtUnsignedInputListLayout>
|
||||
{inputs.utxos.map((utxo, i) => {
|
||||
return (
|
||||
<PsbtUnsignedInputItemWithPossibleInscription
|
||||
addressNativeSegwit={addressNativeSegwit}
|
||||
addressTaproot={addressTaproot}
|
||||
key={i}
|
||||
utxo={utxo}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</PsbtUnsignedInputListLayout>
|
||||
);
|
||||
case 'testnet':
|
||||
return (
|
||||
<PsbtUnsignedInputListLayout>
|
||||
{inputs.utxos.map((utxo, i) => {
|
||||
return (
|
||||
<PsbtUnsignedInputItem
|
||||
addressNativeSegwit={addressNativeSegwit}
|
||||
addressTaproot={addressTaproot}
|
||||
key={i}
|
||||
utxo={utxo}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</PsbtUnsignedInputListLayout>
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ export function PsbtUnsignedOutputItem({
|
||||
<PsbtDecodedNodeLayout
|
||||
hoverLabel={addressFromScript}
|
||||
subtitle={truncateMiddle(addressFromScript)}
|
||||
subValue={`${i18nFormatCurrency(calculateBitcoinFiatValue(outputValue))} USD`}
|
||||
subValue={i18nFormatCurrency(calculateBitcoinFiatValue(outputValue))}
|
||||
value={`${isOutputCurrentAddress ? '+' : ' '}${outputValue}`}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import { Box, Text } from '@stacks/ui';
|
||||
|
||||
import { HasChildren } from '@app/common/has-children';
|
||||
|
||||
export function PsbtUnsignedOutputListLayout({ children }: HasChildren) {
|
||||
return (
|
||||
<Box background="white" borderTopLeftRadius="16px" borderTopRightRadius="16px" p="loose">
|
||||
<Text fontWeight={500}>Outputs</Text>
|
||||
{children}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
import * as btc from '@scure/btc-signer';
|
||||
import { Box, Text } from '@stacks/ui';
|
||||
|
||||
import { PsbtDecodedNodeLayout } from '../psbt-decoded-request-node/psbt-decoded-node.layout';
|
||||
import { PsbtUnsignedOutputItem } from './components/psbt-unsigned-output-item';
|
||||
import { PsbtUnsignedOutputListLayout } from './components/psbt-unsigned-output-list.layout';
|
||||
|
||||
interface PsbtUnsignedOutputListProps {
|
||||
addressNativeSegwit: string;
|
||||
@@ -13,9 +14,15 @@ export function PsbtUnsignedOutputList({
|
||||
addressTaproot,
|
||||
outputs,
|
||||
}: PsbtUnsignedOutputListProps) {
|
||||
if (!outputs.length)
|
||||
return (
|
||||
<PsbtUnsignedOutputListLayout>
|
||||
<PsbtDecodedNodeLayout value="No outputs found" />
|
||||
</PsbtUnsignedOutputListLayout>
|
||||
);
|
||||
|
||||
return (
|
||||
<Box background="white" borderBottomLeftRadius="16px" borderBottomRightRadius="16px" p="loose">
|
||||
<Text fontWeight={500}>Outputs</Text>
|
||||
<PsbtUnsignedOutputListLayout>
|
||||
{outputs.map((output, i) => {
|
||||
return (
|
||||
<PsbtUnsignedOutputItem
|
||||
@@ -26,6 +33,6 @@ export function PsbtUnsignedOutputList({
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</Box>
|
||||
</PsbtUnsignedOutputListLayout>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -16,8 +16,10 @@ export function PsbtRequestHeader({ origin }: PsbtRequestHeaderProps) {
|
||||
Sign transaction
|
||||
</Title>
|
||||
{caption && (
|
||||
<Flag align="middle" img={<Favicon origin={origin} />} pl="tight">
|
||||
<Caption wordBreak="break-word">{caption}</Caption>
|
||||
<Flag align="top" img={<Favicon origin={origin} />} pl="tight">
|
||||
<Caption wordBreak="break-word" lineHeight={1.3}>
|
||||
{caption}
|
||||
</Caption>
|
||||
</Flag>
|
||||
)}
|
||||
</Flex>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { WarningLabel } from '@app/components/warning-label';
|
||||
|
||||
export function PsbtRequestWarningLabel(props: { appName?: string }) {
|
||||
export function PsbtRequestAppWarningLabel(props: { appName?: string }) {
|
||||
const { appName } = props;
|
||||
const title = `Do not proceed unless you trust ${appName ?? 'Unknown'}!`;
|
||||
|
||||
|
||||
@@ -4,29 +4,41 @@ import * as btc from '@scure/btc-signer';
|
||||
|
||||
import { BitcoinNetworkModes } from '@shared/constants';
|
||||
import { getAddressFromOutScript } from '@shared/crypto/bitcoin/bitcoin.utils';
|
||||
import { isUndefined } from '@shared/utils';
|
||||
|
||||
import {
|
||||
OrdApiInscriptionTxOutput,
|
||||
useOrdinalsAwareUtxoQueries,
|
||||
} from '@app/query/bitcoin/ordinals/ordinals-aware-utxo.query';
|
||||
import { useCurrentAccountNativeSegwitIndexZeroSigner } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks';
|
||||
import { useCurrentNetwork } from '@app/store/networks/networks.selectors';
|
||||
|
||||
import {
|
||||
PsbtDecodedUtxosMainnet,
|
||||
PsbtDecodedUtxosTestnet,
|
||||
usePsbtDecodedUtxos,
|
||||
} from './use-psbt-decoded-utxos';
|
||||
|
||||
function isPlaceholderTransaction(
|
||||
address: string,
|
||||
inputs: (OrdApiInscriptionTxOutput | undefined)[],
|
||||
outputs: btc.TransactionOutputRequired[],
|
||||
network: BitcoinNetworkModes
|
||||
network: BitcoinNetworkModes,
|
||||
unsignedOutputs: btc.TransactionOutputRequired[],
|
||||
unsignedUtxos: PsbtDecodedUtxosMainnet | PsbtDecodedUtxosTestnet
|
||||
) {
|
||||
const inputsNotFromCurrentAddress = inputs.filter(input => {
|
||||
return input?.address !== address;
|
||||
});
|
||||
const outputsNotToCurrentAddress = outputs.filter(output => {
|
||||
let utxosNotFromCurrentAddress = [];
|
||||
|
||||
switch (unsignedUtxos.network) {
|
||||
case 'mainnet':
|
||||
utxosNotFromCurrentAddress = unsignedUtxos.utxos.filter(utxo => utxo.address !== address);
|
||||
break;
|
||||
case 'testnet':
|
||||
utxosNotFromCurrentAddress = unsignedUtxos.utxos.filter(
|
||||
vo => vo.scriptpubkey_address !== address
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
const outputsNotToCurrentAddress = unsignedOutputs.filter(output => {
|
||||
const addressFromScript = getAddressFromOutScript(output.script, network);
|
||||
return addressFromScript !== address;
|
||||
});
|
||||
return inputsNotFromCurrentAddress.length === 0 && outputsNotToCurrentAddress.length === 0;
|
||||
|
||||
return utxosNotFromCurrentAddress.length === 0 && outputsNotToCurrentAddress.length === 0;
|
||||
}
|
||||
|
||||
interface UsePsbtDecodedRequestArgs {
|
||||
@@ -40,20 +52,20 @@ export function usePsbtDecodedRequest({
|
||||
const [showAdvancedView, setShowAdvancedView] = useState(false);
|
||||
const network = useCurrentNetwork();
|
||||
const nativeSegwitSigner = useCurrentAccountNativeSegwitIndexZeroSigner();
|
||||
const unsignedUtxos = useOrdinalsAwareUtxoQueries(unsignedInputs).map(query => query.data);
|
||||
const unsignedUtxos = usePsbtDecodedUtxos(unsignedInputs);
|
||||
|
||||
const defaultToAdvancedView = useCallback(() => {
|
||||
const noInputs = isUndefined(unsignedUtxos) || !unsignedUtxos.length;
|
||||
const noOutputs = isUndefined(unsignedOutputs) || !unsignedOutputs.length;
|
||||
const noInputs = !unsignedInputs.length;
|
||||
const noOutputs = !unsignedOutputs.length;
|
||||
return noInputs || noOutputs;
|
||||
}, [unsignedOutputs, unsignedUtxos]);
|
||||
}, [unsignedInputs.length, unsignedOutputs.length]);
|
||||
|
||||
const showPlaceholder = useCallback(() => {
|
||||
return isPlaceholderTransaction(
|
||||
nativeSegwitSigner.address,
|
||||
unsignedUtxos,
|
||||
network.chain.bitcoin.network,
|
||||
unsignedOutputs,
|
||||
network.chain.bitcoin.network
|
||||
unsignedUtxos
|
||||
);
|
||||
}, [nativeSegwitSigner.address, network.chain.bitcoin.network, unsignedOutputs, unsignedUtxos]);
|
||||
|
||||
@@ -62,5 +74,6 @@ export function usePsbtDecodedRequest({
|
||||
shouldDefaultToAdvancedView: defaultToAdvancedView(),
|
||||
shouldShowPlaceholder: showPlaceholder(),
|
||||
showAdvancedView,
|
||||
unsignedUtxos,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
import * as btc from '@scure/btc-signer';
|
||||
|
||||
import { WalletDefaultNetworkConfigurationIds } from '@shared/constants';
|
||||
import { BitcoinTransactionVectorOutput } from '@shared/models/transactions/bitcoin-transaction.model';
|
||||
import { isDefined } from '@shared/utils';
|
||||
|
||||
import {
|
||||
OrdApiInscriptionTxOutput,
|
||||
useOrdinalsAwareUtxoQueries,
|
||||
} from '@app/query/bitcoin/ordinals/ordinals-aware-utxo.query';
|
||||
import { TaprootUtxo } from '@app/query/bitcoin/ordinals/use-taproot-address-utxos.query';
|
||||
import { useGetBitcoinTransactionQueries } from '@app/query/bitcoin/transaction/transaction.query';
|
||||
import { useCurrentNetwork } from '@app/store/networks/networks.selectors';
|
||||
|
||||
export interface PsbtDecodedUtxosMainnet {
|
||||
network: WalletDefaultNetworkConfigurationIds.mainnet;
|
||||
utxos: (TaprootUtxo & OrdApiInscriptionTxOutput)[];
|
||||
}
|
||||
|
||||
export interface PsbtDecodedUtxosTestnet {
|
||||
network: WalletDefaultNetworkConfigurationIds.testnet;
|
||||
utxos: BitcoinTransactionVectorOutput[];
|
||||
}
|
||||
|
||||
export function usePsbtDecodedUtxos(
|
||||
unsignedInputs: btc.TransactionInputRequired[]
|
||||
): PsbtDecodedUtxosMainnet | PsbtDecodedUtxosTestnet {
|
||||
const network = useCurrentNetwork();
|
||||
const unsignedUtxos = useGetBitcoinTransactionQueries(unsignedInputs)
|
||||
.map(query => query.data)
|
||||
.filter(isDefined)
|
||||
.map((input, i) => input.vout[unsignedInputs[i].index]);
|
||||
// Mainnet only enabled query
|
||||
const unsignedUtxosWithInscriptions = useOrdinalsAwareUtxoQueries(unsignedInputs)
|
||||
.map(query => query.data)
|
||||
.filter(isDefined);
|
||||
|
||||
return network.chain.bitcoin.network === 'mainnet' && unsignedUtxosWithInscriptions.length
|
||||
? {
|
||||
network: WalletDefaultNetworkConfigurationIds.mainnet,
|
||||
utxos: unsignedUtxosWithInscriptions,
|
||||
}
|
||||
: { network: WalletDefaultNetworkConfigurationIds.testnet, utxos: unsignedUtxos };
|
||||
}
|
||||
@@ -20,8 +20,6 @@ export function usePsbtSigner() {
|
||||
|
||||
return useMemo(
|
||||
() => ({
|
||||
nativeSegwitSigner,
|
||||
taprootSigner,
|
||||
signPsbtAtIndex(allowedSighash: btc.SignatureHash[], idx: number, tx: btc.Transaction) {
|
||||
try {
|
||||
nativeSegwitSigner?.signIndex(tx, idx, allowedSighash);
|
||||
@@ -33,6 +31,17 @@ export function usePsbtSigner() {
|
||||
}
|
||||
}
|
||||
},
|
||||
signPsbt(tx: btc.Transaction) {
|
||||
try {
|
||||
nativeSegwitSigner?.sign(tx);
|
||||
} catch (e1) {
|
||||
try {
|
||||
taprootSigner?.sign(tx);
|
||||
} catch (e2) {
|
||||
logger.error('Error signing PSBT', e1, e2);
|
||||
}
|
||||
}
|
||||
},
|
||||
getPsbtAsTransaction(psbt: string | Uint8Array) {
|
||||
const bytes = isString(psbt) ? hexToBytes(psbt) : psbt;
|
||||
return btc.Transaction.fromPSBT(bytes);
|
||||
|
||||
@@ -5,7 +5,7 @@ import { useOnOriginTabClose } from '@app/routes/hooks/use-on-tab-closed';
|
||||
import { PsbtDecodedRequest } from './components/psbt-decoded-request/psbt-decoded-request';
|
||||
import { PsbtRequestActions } from './components/psbt-request-actions';
|
||||
import { PsbtRequestHeader } from './components/psbt-request-header';
|
||||
import { PsbtRequestWarningLabel } from './components/psbt-request-warning-label';
|
||||
import { PsbtRequestAppWarningLabel } from './components/psbt-request-warning-label';
|
||||
import { PsbtRequestLayout } from './components/psbt-request.layout';
|
||||
import { DecodedPsbt } from './hooks/use-psbt-signer';
|
||||
|
||||
@@ -26,7 +26,7 @@ export function PsbtSigner(props: PsbtSignerProps) {
|
||||
<>
|
||||
<PsbtRequestLayout>
|
||||
<PsbtRequestHeader origin={appName} />
|
||||
<PsbtRequestWarningLabel appName={appName} />
|
||||
<PsbtRequestAppWarningLabel appName={appName} />
|
||||
<PsbtDecodedRequest psbt={psbt} />
|
||||
</PsbtRequestLayout>
|
||||
<PsbtRequestActions isLoading={false} onCancel={onCancel} onSignPsbt={onSignPsbt} />
|
||||
|
||||
@@ -57,7 +57,7 @@ function SetPasswordPage() {
|
||||
const { decodedAuthRequest } = useOnboardingState();
|
||||
const analytics = useAnalytics();
|
||||
|
||||
useRouteHeader(<Header hideActions onClose={() => navigate(RouteUrls.BackUpSecretKey)} />);
|
||||
useRouteHeader(<Header hideActions onClose={() => navigate(-1)} />);
|
||||
|
||||
useEffect(() => {
|
||||
void analytics.page('view', '/set-password');
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useCallback, useEffect, useRef } from 'react';
|
||||
import { ChangeEvent, useCallback, useEffect, useRef, useState } from 'react';
|
||||
import toast from 'react-hot-toast';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
@@ -24,6 +24,8 @@ async function simulateShortDelayToAvoidImmediateNavigation() {
|
||||
|
||||
export function useSignIn() {
|
||||
const [error, setError] = useSeedInputErrorState();
|
||||
const [isKeyMasked, setIsKeyMasked] = useState(true);
|
||||
const [sanitizedSecretKey, setSanitizedSecretKey] = useState('');
|
||||
|
||||
const { isLoading, setIsLoading, setIsIdle } = useLoading('useSignIn');
|
||||
const navigate = useNavigate();
|
||||
@@ -97,6 +99,33 @@ export function useSignIn() {
|
||||
[submitMnemonicForm]
|
||||
);
|
||||
|
||||
const onChange = useCallback(
|
||||
(
|
||||
event: ChangeEvent<HTMLInputElement>,
|
||||
handleFormChange: (e: ChangeEvent<HTMLInputElement>) => void
|
||||
) => {
|
||||
const { value } = event.target;
|
||||
setSanitizedSecretKey(previousSanitizedKey => {
|
||||
// if value is shorter than sanitized secret key, remove characters
|
||||
// from sanitized secret key
|
||||
if (value.length < previousSanitizedKey.length) {
|
||||
const removedChars = previousSanitizedKey.slice(value.length);
|
||||
return previousSanitizedKey.slice(0, -removedChars.length);
|
||||
} else {
|
||||
// append new characters if value is longer than sanitized secret key
|
||||
const newChars = value.slice(previousSanitizedKey.length);
|
||||
return previousSanitizedKey + newChars;
|
||||
}
|
||||
});
|
||||
handleFormChange(event);
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const toggleKeyMask = useCallback(() => {
|
||||
setIsKeyMasked(prev => !prev);
|
||||
}, []);
|
||||
|
||||
useEffect(
|
||||
() => () => {
|
||||
setError(undefined);
|
||||
@@ -107,5 +136,15 @@ export function useSignIn() {
|
||||
[setError]
|
||||
);
|
||||
|
||||
return { onPaste, submitMnemonicForm, ref: textAreaRef, error, isLoading };
|
||||
return {
|
||||
onPaste,
|
||||
submitMnemonicForm,
|
||||
ref: textAreaRef,
|
||||
error,
|
||||
isLoading,
|
||||
onChange,
|
||||
toggleKeyMask,
|
||||
isKeyMasked,
|
||||
sanitizedSecretKey,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { ChangeEvent } from 'react';
|
||||
import { FiEye, FiEyeOff } from 'react-icons/fi';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import YourSecretKey from '@assets/images/onboarding/your-secret-key.png';
|
||||
import { Box, Input, Stack, Text, color, useMediaQuery } from '@stacks/ui';
|
||||
import { Box, Input, Stack, Text, color, useClipboard, useMediaQuery } from '@stacks/ui';
|
||||
import { OnboardingSelectors } from '@tests/selectors/onboarding.selectors';
|
||||
import { Form, Formik } from 'formik';
|
||||
|
||||
@@ -15,14 +17,26 @@ import {
|
||||
DESKTOP_VIEWPORT_MIN_WIDTH,
|
||||
} from '@app/components/global-styles/full-page-styles';
|
||||
import { Header } from '@app/components/header';
|
||||
import { Link } from '@app/components/link';
|
||||
import { PageTitle } from '@app/components/page-title';
|
||||
import { PrimaryButton } from '@app/components/primary-button';
|
||||
import { Title } from '@app/components/typography';
|
||||
import { useSignIn } from '@app/pages/onboarding/sign-in/hooks/use-sign-in';
|
||||
|
||||
export function SignIn() {
|
||||
const { onPaste, submitMnemonicForm, error, isLoading, ref } = useSignIn();
|
||||
const {
|
||||
onPaste,
|
||||
submitMnemonicForm,
|
||||
error,
|
||||
isLoading,
|
||||
ref,
|
||||
onChange,
|
||||
toggleKeyMask,
|
||||
isKeyMasked,
|
||||
sanitizedSecretKey,
|
||||
} = useSignIn();
|
||||
const navigate = useNavigate();
|
||||
const { onCopy } = useClipboard(sanitizedSecretKey);
|
||||
|
||||
const [desktopViewport] = useMediaQuery(`(min-width: ${DESKTOP_VIEWPORT_MIN_WIDTH})`);
|
||||
|
||||
@@ -32,7 +46,7 @@ export function SignIn() {
|
||||
<CenteredPageContainer>
|
||||
<Formik
|
||||
initialValues={{ secretKey: '' }}
|
||||
onSubmit={values => submitMnemonicForm(values.secretKey)}
|
||||
onSubmit={_values => submitMnemonicForm(sanitizedSecretKey)}
|
||||
>
|
||||
{form => (
|
||||
<Form>
|
||||
@@ -65,14 +79,19 @@ export function SignIn() {
|
||||
borderRadius="10px"
|
||||
fontSize="16px"
|
||||
minHeight="168px"
|
||||
onChange={form.handleChange}
|
||||
onChange={event => {
|
||||
onChange(event as ChangeEvent<HTMLInputElement>, form.handleChange);
|
||||
}}
|
||||
onKeyDown={e => e.key === 'Enter' && form.submitForm()}
|
||||
onPaste={onPaste}
|
||||
onCopy={onCopy}
|
||||
placeholder="Paste or type your Secret Key"
|
||||
ref={ref as any}
|
||||
spellCheck={false}
|
||||
style={{ resize: 'none' }}
|
||||
value={form.values.secretKey}
|
||||
value={
|
||||
isKeyMasked ? form.values.secretKey.replace(/[^ ]/g, '*') : sanitizedSecretKey
|
||||
}
|
||||
width="100%"
|
||||
/>
|
||||
{error && (
|
||||
@@ -89,6 +108,14 @@ export function SignIn() {
|
||||
</Text>
|
||||
</ErrorLabel>
|
||||
)}
|
||||
<Stack alignItems="center">
|
||||
<Link fontSize="14px" _hover={{ textDecoration: 'none' }} onClick={toggleKeyMask}>
|
||||
<Stack alignItems="center" isInline spacing="tight">
|
||||
{isKeyMasked ? <FiEye /> : <FiEyeOff />}
|
||||
<Text>{isKeyMasked ? 'Show' : 'Hide'} Secret Key</Text>
|
||||
</Stack>
|
||||
</Link>
|
||||
</Stack>
|
||||
</Stack>
|
||||
<PrimaryButton
|
||||
data-testid={OnboardingSelectors.SignInBtn}
|
||||
|
||||
@@ -14,13 +14,7 @@ import { usePsbtRequestSearchParams } from '@app/pages/psbt-request/psbt-request
|
||||
export function usePsbtRequest() {
|
||||
const { requestToken, tabId } = usePsbtRequestSearchParams();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const {
|
||||
signPsbtAtIndex,
|
||||
getDecodedPsbt,
|
||||
nativeSegwitSigner,
|
||||
taprootSigner,
|
||||
getPsbtAsTransaction,
|
||||
} = usePsbtSigner();
|
||||
const { signPsbt, signPsbtAtIndex, getDecodedPsbt, getPsbtAsTransaction } = usePsbtSigner();
|
||||
const analytics = useAnalytics();
|
||||
return useMemo(() => {
|
||||
if (!requestToken) throw new Error('Cannot decode psbt without request token');
|
||||
@@ -56,15 +50,7 @@ export function usePsbtRequest() {
|
||||
if (!isUndefined(indexOrIndexes) && !isUndefined(allowedSighash)) {
|
||||
ensureArray(indexOrIndexes).forEach(idx => signPsbtAtIndex(allowedSighash, idx, tx));
|
||||
} else {
|
||||
try {
|
||||
nativeSegwitSigner?.sign(tx);
|
||||
} catch (e1) {
|
||||
try {
|
||||
taprootSigner?.sign(tx);
|
||||
} catch (e2) {
|
||||
logger.error('Error signing tx', e1, e2);
|
||||
}
|
||||
}
|
||||
signPsbt(tx);
|
||||
}
|
||||
|
||||
const psbt = tx.toPSBT();
|
||||
@@ -85,10 +71,9 @@ export function usePsbtRequest() {
|
||||
getDecodedPsbt,
|
||||
getPsbtAsTransaction,
|
||||
isLoading,
|
||||
nativeSegwitSigner,
|
||||
requestToken,
|
||||
signPsbt,
|
||||
signPsbtAtIndex,
|
||||
tabId,
|
||||
taprootSigner,
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import { useAnalytics } from '@app/common/hooks/analytics/use-analytics';
|
||||
import { useDefaultRequestParams } from '@app/common/hooks/use-default-request-search-params';
|
||||
import { initialSearchParams } from '@app/common/initial-search-params';
|
||||
import { useCurrentAccountNativeSegwitIndexZeroSigner } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks';
|
||||
import { useCurrentAccountTaprootAddressIndexZeroPayment } from '@app/store/accounts/blockchain/bitcoin/taproot-account.hooks';
|
||||
import { useCurrentAccountTaprootIndexZeroSigner } from '@app/store/accounts/blockchain/bitcoin/taproot-account.hooks';
|
||||
import { useCurrentStacksAccount } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks';
|
||||
import { useAppPermissions } from '@app/store/app-permissions/app-permissions.slice';
|
||||
|
||||
@@ -32,14 +32,15 @@ export function useGetAddresses() {
|
||||
const { tabId, origin, requestId } = useRpcRequestParams();
|
||||
|
||||
const nativeSegwitSigner = useCurrentAccountNativeSegwitIndexZeroSigner();
|
||||
const taprootPayment = useCurrentAccountTaprootAddressIndexZeroPayment();
|
||||
const taprootSigner = useCurrentAccountTaprootIndexZeroSigner();
|
||||
const stacksAccount = useCurrentStacksAccount();
|
||||
|
||||
const taprootAddressResponse: BtcAddress = {
|
||||
symbol: 'BTC',
|
||||
type: 'p2tr',
|
||||
address: taprootPayment.address,
|
||||
publicKey: bytesToHex(taprootPayment.publicKey),
|
||||
address: taprootSigner.address,
|
||||
publicKey: bytesToHex(taprootSigner.publicKey),
|
||||
derivationPath: taprootSigner.derivationPath,
|
||||
};
|
||||
|
||||
const nativeSegwitAddressResponse: BtcAddress = {
|
||||
@@ -47,6 +48,7 @@ export function useGetAddresses() {
|
||||
type: 'p2wpkh',
|
||||
address: nativeSegwitSigner.address,
|
||||
publicKey: bytesToHex(nativeSegwitSigner.publicKey),
|
||||
derivationPath: nativeSegwitSigner.derivationPath,
|
||||
};
|
||||
|
||||
const stacksAddressResponse = {
|
||||
|
||||
@@ -5,7 +5,6 @@ import { RpcErrorCode } from '@btckit/types';
|
||||
import { bytesToHex } from '@noble/hashes/utils';
|
||||
import * as btc from '@scure/btc-signer';
|
||||
|
||||
import { logger } from '@shared/logger';
|
||||
import { makeRpcErrorResponse, makeRpcSuccessResponse } from '@shared/rpc/rpc-methods';
|
||||
import { isUndefined } from '@shared/utils';
|
||||
|
||||
@@ -40,13 +39,7 @@ function useRpcSignPsbtParams() {
|
||||
|
||||
function useRpcSignPsbt() {
|
||||
const { origin, tabId, requestId, psbtHex, allowedSighash, signAtIndex } = useRpcSignPsbtParams();
|
||||
const {
|
||||
signPsbtAtIndex,
|
||||
getDecodedPsbt,
|
||||
nativeSegwitSigner,
|
||||
taprootSigner,
|
||||
getPsbtAsTransaction,
|
||||
} = usePsbtSigner();
|
||||
const { signPsbt, signPsbtAtIndex, getDecodedPsbt, getPsbtAsTransaction } = usePsbtSigner();
|
||||
|
||||
const tx = getPsbtAsTransaction(psbtHex);
|
||||
|
||||
@@ -61,15 +54,7 @@ function useRpcSignPsbt() {
|
||||
signPsbtAtIndex(allowedSighash, idx, tx);
|
||||
});
|
||||
} else {
|
||||
try {
|
||||
nativeSegwitSigner?.sign(tx);
|
||||
} catch (e1) {
|
||||
try {
|
||||
taprootSigner?.sign(tx);
|
||||
} catch (e2) {
|
||||
logger.error('Error signing tx', e1, e2);
|
||||
}
|
||||
}
|
||||
signPsbt(tx);
|
||||
}
|
||||
const psbt = tx.toPSBT();
|
||||
|
||||
|
||||
@@ -1,7 +1,16 @@
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useCallback, useEffect } from 'react';
|
||||
|
||||
import { AppUseQueryConfig } from '@app/query/query-config';
|
||||
import { useInfiniteQuery } from '@tanstack/react-query';
|
||||
|
||||
import { useAnalytics } from '@app/common/hooks/analytics/use-analytics';
|
||||
import { createNumArrayOfRange } from '@app/common/utils';
|
||||
import { QueryPrefixes } from '@app/query/query-prefixes';
|
||||
import { useCurrentAccountNativeSegwitIndexZeroSigner } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks';
|
||||
import { useCurrentAccountTaprootSigner } from '@app/store/accounts/blockchain/bitcoin/taproot-account.hooks';
|
||||
import { useCurrentNetwork } from '@app/store/networks/networks.selectors';
|
||||
|
||||
const addressesSimultaneousFetchLimit = 5;
|
||||
const stopSearchAfterNumberAddressesWithoutBrc20Tokens = 5;
|
||||
|
||||
interface Brc20TokenResponse {
|
||||
available_balance: string;
|
||||
@@ -53,15 +62,81 @@ async function fetchBrc20TokensByAddress(address: string): Promise<Brc20Token[]>
|
||||
});
|
||||
}
|
||||
|
||||
type FetchBrc20TokensByAddressResp = Awaited<ReturnType<typeof fetchBrc20TokensByAddress>>;
|
||||
export function useBrc20TokensQuery() {
|
||||
const network = useCurrentNetwork();
|
||||
const nativeSegwitSigner = useCurrentAccountNativeSegwitIndexZeroSigner();
|
||||
const currentBitcoinAddress = nativeSegwitSigner.address;
|
||||
const createSigner = useCurrentAccountTaprootSigner();
|
||||
const analytics = useAnalytics();
|
||||
|
||||
export function useBrc20TokensByAddressQuery<T extends unknown = FetchBrc20TokensByAddressResp>(
|
||||
address: string,
|
||||
options?: AppUseQueryConfig<FetchBrc20TokensByAddressResp, T>
|
||||
) {
|
||||
return useQuery({
|
||||
queryKey: [QueryPrefixes.Brc20TokenBalance, address],
|
||||
queryFn: () => fetchBrc20TokensByAddress(address),
|
||||
...options,
|
||||
if (!createSigner) throw new Error('No signer');
|
||||
|
||||
const getNextTaprootAddressBatch = useCallback(
|
||||
(fromIndex: number, toIndex: number) => {
|
||||
return createNumArrayOfRange(fromIndex, toIndex - 1).map(num => {
|
||||
const address = createSigner(num).address;
|
||||
return address;
|
||||
});
|
||||
},
|
||||
[createSigner]
|
||||
);
|
||||
const query = useInfiniteQuery({
|
||||
queryKey: [QueryPrefixes.Brc20InfiniteQuery, currentBitcoinAddress, network.id],
|
||||
async queryFn({ pageParam }) {
|
||||
const fromIndex: number = pageParam?.fromIndex ?? 0;
|
||||
let addressesWithoutTokens = pageParam?.addressesWithoutTokens ?? 0;
|
||||
|
||||
const addressesData = getNextTaprootAddressBatch(
|
||||
fromIndex,
|
||||
fromIndex + addressesSimultaneousFetchLimit
|
||||
);
|
||||
const brc20TokensPromises = addressesData.map(address => {
|
||||
return fetchBrc20TokensByAddress(address);
|
||||
});
|
||||
|
||||
const brc20Tokens = await Promise.all(brc20TokensPromises);
|
||||
addressesWithoutTokens += brc20Tokens.filter(tokens => tokens.length === 0).length;
|
||||
|
||||
return {
|
||||
addressesWithoutTokens,
|
||||
brc20Tokens,
|
||||
fromIndex,
|
||||
};
|
||||
},
|
||||
getNextPageParam(prevInscriptionQuery) {
|
||||
const { fromIndex, brc20Tokens, addressesWithoutTokens } = prevInscriptionQuery;
|
||||
|
||||
if (addressesWithoutTokens >= stopSearchAfterNumberAddressesWithoutBrc20Tokens) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
fromIndex: fromIndex + addressesSimultaneousFetchLimit,
|
||||
addressesWithoutTokens,
|
||||
brc20Tokens,
|
||||
};
|
||||
},
|
||||
refetchOnMount: false,
|
||||
refetchOnReconnect: false,
|
||||
refetchOnWindowFocus: false,
|
||||
staleTime: 3 * 60 * 1000,
|
||||
});
|
||||
|
||||
// Auto-trigger next request
|
||||
useEffect(() => {
|
||||
void query.fetchNextPage();
|
||||
}, [query, query.data]);
|
||||
useEffect(() => {
|
||||
const brc20AcrossAddressesCount = query.data?.pages.reduce((acc, page) => {
|
||||
return acc + page.brc20Tokens.flatMap(item => item).length;
|
||||
}, 0);
|
||||
|
||||
if (!query.hasNextPage && brc20AcrossAddressesCount && brc20AcrossAddressesCount > 0) {
|
||||
void analytics.identify({
|
||||
brc20_across_addresses_count: brc20AcrossAddressesCount,
|
||||
});
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [analytics, query.hasNextPage]);
|
||||
return query;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useConfigOrdinalsbot } from '@app/query/common/remote-config/remote-config.query';
|
||||
import { useAppDispatch } from '@app/store';
|
||||
import { useCurrentAccountIndex } from '@app/store/accounts/account';
|
||||
import { useCurrentAccountTaprootAddressIndexZeroPayment } from '@app/store/accounts/blockchain/bitcoin/taproot-account.hooks';
|
||||
import { useCurrentAccountTaprootIndexZeroSigner } from '@app/store/accounts/blockchain/bitcoin/taproot-account.hooks';
|
||||
import { useCurrentNetwork } from '@app/store/networks/networks.selectors';
|
||||
import { brc20TransferInitiated } from '@app/store/ordinals/ordinals.slice';
|
||||
|
||||
@@ -34,7 +34,7 @@ export function useBrc20Transfers() {
|
||||
const dispatch = useAppDispatch();
|
||||
const currentAccountIndex = useCurrentAccountIndex();
|
||||
const ordinalsbotClient = useOrdinalsbotClient();
|
||||
const { address } = useCurrentAccountTaprootAddressIndexZeroPayment();
|
||||
const { address } = useCurrentAccountTaprootIndexZeroSigner();
|
||||
const { data: fees } = useAverageBitcoinFeeRates();
|
||||
|
||||
return {
|
||||
|
||||
@@ -7,6 +7,7 @@ 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';
|
||||
|
||||
@@ -58,11 +59,14 @@ 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,4 +1,8 @@
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import * as btc from '@scure/btc-signer';
|
||||
import { bytesToHex } from '@stacks/common';
|
||||
import { UseQueryResult, useQueries, useQuery } from '@tanstack/react-query';
|
||||
|
||||
import { BitcoinTransaction } from '@shared/models/transactions/bitcoin-transaction.model';
|
||||
|
||||
import { AppUseQueryConfig } from '@app/query/query-config';
|
||||
import { useBitcoinClient } from '@app/store/common/api-clients.hooks';
|
||||
@@ -29,3 +33,26 @@ export function useGetBitcoinTransaction<T extends unknown = FetchBitcoinTransac
|
||||
...options,
|
||||
});
|
||||
}
|
||||
|
||||
const queryOptions = {
|
||||
cacheTime: Infinity,
|
||||
staleTime: 15 * 60 * 1000,
|
||||
refetchOnWindowFocus: false,
|
||||
} as const;
|
||||
|
||||
export function useGetBitcoinTransactionQueries(
|
||||
inputs: btc.TransactionInputRequired[]
|
||||
): UseQueryResult<BitcoinTransaction>[] {
|
||||
const client = useBitcoinClient();
|
||||
|
||||
return useQueries({
|
||||
queries: inputs.map(input => {
|
||||
const txId = bytesToHex(input.txid);
|
||||
return {
|
||||
queryKey: ['bitcoin-transaction', txId],
|
||||
queryFn: () => fetchBitcoinTransaction(client)(txId),
|
||||
...queryOptions,
|
||||
};
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import { CryptoCurrencies } from '@shared/models/currencies.model';
|
||||
import { MarketData, createMarketData, createMarketPair } from '@shared/models/market.model';
|
||||
import { createMoney, currencyDecimalsMap } from '@shared/models/money.model';
|
||||
import { createMoneyFromDecimal } from '@shared/models/money.model';
|
||||
import { isNumber } from '@shared/utils';
|
||||
|
||||
import { calculateMeanAverage } from '@app/common/math/calculate-averages';
|
||||
import { convertAmountToFractionalUnit } from '@app/common/money/calculate-money';
|
||||
@@ -58,8 +59,11 @@ export function useCalculateBitcoinFiatValue() {
|
||||
const btcMarketData = useCryptoCurrencyMarketData('BTC');
|
||||
|
||||
return useCallback(
|
||||
(value: string) =>
|
||||
baseCurrencyAmountInQuote(createMoneyFromDecimal(Number(value), 'BTC'), btcMarketData),
|
||||
(value: string | number) =>
|
||||
baseCurrencyAmountInQuote(
|
||||
createMoneyFromDecimal(isNumber(value) ? value : Number(value), 'BTC'),
|
||||
btcMarketData
|
||||
),
|
||||
[btcMarketData]
|
||||
);
|
||||
}
|
||||
|
||||
@@ -16,4 +16,5 @@ export enum QueryPrefixes {
|
||||
|
||||
StampCollection = 'stamp-collection',
|
||||
StampsByAddress = 'stamps-by-address',
|
||||
Brc20InfiniteQuery = 'brc20-infinite-query',
|
||||
}
|
||||
|
||||
@@ -9,11 +9,16 @@ import { getBtcSignerLibNetworkConfigByMode } from '@shared/crypto/bitcoin/bitco
|
||||
import {
|
||||
deriveAddressIndexKeychainFromAccount,
|
||||
deriveAddressIndexZeroFromAccount,
|
||||
whenPaymentType,
|
||||
} from '@shared/crypto/bitcoin/bitcoin.utils';
|
||||
import { deriveTaprootAccountFromRootKeychain } from '@shared/crypto/bitcoin/p2tr-address-gen';
|
||||
import {
|
||||
deriveTaprootAccountFromRootKeychain,
|
||||
getTaprootAddressIndexDerivationPath,
|
||||
} from '@shared/crypto/bitcoin/p2tr-address-gen';
|
||||
import {
|
||||
deriveNativeSegWitAccountKeychain,
|
||||
getNativeSegWitPaymentFromAddressIndex,
|
||||
getNativeSegwitAddressIndexDerivationPath,
|
||||
} from '@shared/crypto/bitcoin/p2wpkh-address-gen';
|
||||
|
||||
import { mnemonicToRootNode } from '@app/common/keychain/keychain';
|
||||
@@ -77,12 +82,13 @@ export function useBitcoinScureLibNetworkConfig() {
|
||||
}
|
||||
|
||||
interface BitcoinSignerFactoryArgs {
|
||||
accountIndex: number;
|
||||
accountKeychain: HDKey;
|
||||
paymentFn(keychain: HDKey, network: BitcoinNetworkModes): any;
|
||||
network: BitcoinNetworkModes;
|
||||
}
|
||||
export function bitcoinSignerFactory<T extends BitcoinSignerFactoryArgs>(args: T) {
|
||||
const { network, paymentFn, accountKeychain } = args;
|
||||
const { accountIndex, network, paymentFn, accountKeychain } = args;
|
||||
return (addressIndex: number) => {
|
||||
const addressIndexKeychain =
|
||||
deriveAddressIndexKeychainFromAccount(accountKeychain)(addressIndex);
|
||||
@@ -96,6 +102,13 @@ export function bitcoinSignerFactory<T extends BitcoinSignerFactoryArgs>(args: T
|
||||
payment,
|
||||
addressIndex,
|
||||
publicKeychain,
|
||||
derivationPath: whenPaymentType(payment.type)({
|
||||
p2wpkh: getNativeSegwitAddressIndexDerivationPath(network, accountIndex, addressIndex),
|
||||
p2tr: getTaprootAddressIndexDerivationPath(network, accountIndex, addressIndex),
|
||||
'p2wpkh-p2sh': 'Not supported',
|
||||
p2pkh: 'Not supported',
|
||||
p2sh: 'Not supported',
|
||||
}),
|
||||
get address() {
|
||||
if (!payment.address) throw new Error('Unable to get address from payment');
|
||||
return payment.address;
|
||||
@@ -136,6 +149,7 @@ function createSignersForAllNetworkTypes<T extends CreateSignersForAllNetworkTyp
|
||||
|
||||
function makeNetworkSigner(keychain: HDKey, network: BitcoinNetworkModes) {
|
||||
return bitcoinSignerFactory({
|
||||
accountIndex,
|
||||
accountKeychain: keychain,
|
||||
paymentFn: paymentFn as T['paymentFn'],
|
||||
network,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { useMemo } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { bitcoinNetworkModeToCoreNetworkMode } from '@shared/crypto/bitcoin/bitcoin.utils';
|
||||
@@ -16,18 +17,21 @@ import {
|
||||
|
||||
function useNativeSegwitActiveNetworkAccountPrivateKeychain() {
|
||||
const network = useCurrentNetwork();
|
||||
return useSelector(
|
||||
whenNetwork(bitcoinNetworkModeToCoreNetworkMode(network.chain.bitcoin.network))({
|
||||
mainnet: selectMainnetNativeSegWitKeychain,
|
||||
testnet: selectTestnetNativeSegWitKeychain,
|
||||
})
|
||||
const selector = useMemo(
|
||||
() =>
|
||||
whenNetwork(bitcoinNetworkModeToCoreNetworkMode(network.chain.bitcoin.network))({
|
||||
mainnet: selectMainnetNativeSegWitKeychain,
|
||||
testnet: selectTestnetNativeSegWitKeychain,
|
||||
}),
|
||||
[network.chain.bitcoin.network]
|
||||
);
|
||||
return useSelector(selector);
|
||||
}
|
||||
|
||||
export function useNativeSegwitCurrentAccountPrivateKeychain() {
|
||||
const keychain = useNativeSegwitActiveNetworkAccountPrivateKeychain();
|
||||
const currentAccountIndex = useCurrentAccountIndex();
|
||||
return keychain?.(currentAccountIndex);
|
||||
return useMemo(() => keychain?.(currentAccountIndex), [currentAccountIndex, keychain]);
|
||||
}
|
||||
|
||||
export function useNativeSegwitNetworkSigners() {
|
||||
@@ -44,13 +48,16 @@ export function useNativeSegwitNetworkSigners() {
|
||||
function useNativeSegwitSigner(accountIndex: number) {
|
||||
const network = useCurrentNetwork();
|
||||
const accountKeychain = useNativeSegwitActiveNetworkAccountPrivateKeychain()?.(accountIndex);
|
||||
if (!accountKeychain) return;
|
||||
|
||||
return bitcoinSignerFactory({
|
||||
accountKeychain,
|
||||
paymentFn: getNativeSegWitPaymentFromAddressIndex,
|
||||
network: network.chain.bitcoin.network,
|
||||
});
|
||||
return useMemo(() => {
|
||||
if (!accountKeychain) return;
|
||||
return bitcoinSignerFactory({
|
||||
accountIndex,
|
||||
accountKeychain,
|
||||
paymentFn: getNativeSegWitPaymentFromAddressIndex,
|
||||
network: network.chain.bitcoin.network,
|
||||
});
|
||||
}, [accountIndex, accountKeychain, network.chain.bitcoin.network]);
|
||||
}
|
||||
|
||||
export function useCurrentAccountNativeSegwitSigner() {
|
||||
@@ -60,8 +67,10 @@ export function useCurrentAccountNativeSegwitSigner() {
|
||||
|
||||
export function useCurrentAccountNativeSegwitIndexZeroSigner() {
|
||||
const signer = useCurrentAccountNativeSegwitSigner();
|
||||
if (!signer) throw new Error('No signer');
|
||||
return signer(0);
|
||||
return useMemo(() => {
|
||||
if (!signer) throw new Error('No signer');
|
||||
return signer(0);
|
||||
}, [signer]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -69,7 +78,7 @@ export function useCurrentAccountNativeSegwitIndexZeroSigner() {
|
||||
*/
|
||||
export function useCurrentAccountNativeSegwitAddressIndexZero() {
|
||||
const signer = useCurrentAccountNativeSegwitSigner();
|
||||
return signer?.(0).payment.address as string;
|
||||
return useMemo(() => signer?.(0).payment.address, [signer]) as string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -79,11 +88,3 @@ export function useNativeSegwitAccountIndexAddressIndexZero(accountIndex: number
|
||||
const signer = useNativeSegwitSigner(accountIndex)?.(0);
|
||||
return signer?.payment.address as string;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use signer.publicKeychain directly instead
|
||||
*/
|
||||
export function useCurrentBitcoinNativeSegwitAddressIndexPublicKeychain() {
|
||||
const signer = useCurrentAccountNativeSegwitSigner();
|
||||
return signer?.(0).publicKeychain;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { useMemo } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { BitcoinNetworkModes } from '@shared/constants';
|
||||
@@ -32,8 +33,10 @@ export function useTaprootCurrentAccountPrivateKeychain() {
|
||||
|
||||
export function useTaprootAccountKeychain(accountIndex: number) {
|
||||
const accountKeychain = useTaprootActiveNetworkAccountPrivateKeychain();
|
||||
if (!accountKeychain) return; // TODO: Revisit this return early
|
||||
return accountKeychain(accountIndex);
|
||||
return useMemo(() => {
|
||||
if (!accountKeychain) return; // TODO: Revisit this return early
|
||||
return accountKeychain(accountIndex);
|
||||
}, [accountIndex, accountKeychain]);
|
||||
}
|
||||
|
||||
export function useTaprootNetworkSigners() {
|
||||
@@ -49,13 +52,24 @@ export function useTaprootNetworkSigners() {
|
||||
|
||||
function useTaprootSigner(accountIndex: number, network: BitcoinNetworkModes) {
|
||||
const accountKeychain = useTaprootAccountKeychain(accountIndex);
|
||||
if (!accountKeychain) return; // TODO: Revisit this return early
|
||||
|
||||
return bitcoinSignerFactory({
|
||||
accountKeychain,
|
||||
paymentFn: getTaprootPaymentFromAddressIndex,
|
||||
network,
|
||||
});
|
||||
return useMemo(() => {
|
||||
if (!accountKeychain) return; // TODO: Revisit this return early
|
||||
return bitcoinSignerFactory({
|
||||
accountIndex,
|
||||
accountKeychain,
|
||||
paymentFn: getTaprootPaymentFromAddressIndex,
|
||||
network,
|
||||
});
|
||||
}, [accountIndex, accountKeychain, network]);
|
||||
}
|
||||
|
||||
export function useCurrentAccountTaprootIndexZeroSigner() {
|
||||
const signer = useCurrentAccountTaprootSigner();
|
||||
return useMemo(() => {
|
||||
if (!signer) throw new Error('No signer');
|
||||
return signer(0);
|
||||
}, [signer]);
|
||||
}
|
||||
|
||||
export function useCurrentAccountTaprootSigner() {
|
||||
@@ -63,17 +77,3 @@ export function useCurrentAccountTaprootSigner() {
|
||||
const network = useCurrentNetwork();
|
||||
return useTaprootSigner(currentAccountIndex, network.chain.bitcoin.network);
|
||||
}
|
||||
|
||||
export function useCurrentAccountTaprootAddressIndexZeroPayment() {
|
||||
const createSigner = useCurrentAccountTaprootSigner();
|
||||
const indexZeroSigner = createSigner?.(0);
|
||||
if (!indexZeroSigner?.payment.address) throw new Error('No address found');
|
||||
const publicKey = indexZeroSigner.publicKeychain.publicKey;
|
||||
if (!publicKey) throw new Error('No public key found');
|
||||
// Creating new object to have known property types
|
||||
return {
|
||||
address: indexZeroSigner.payment.address,
|
||||
publicKey,
|
||||
type: indexZeroSigner.payment.type,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ import { useSelector } from 'react-redux';
|
||||
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
|
||||
import { initialSearchParams } from '@app/common/initial-search-params';
|
||||
import { initBigNumber } from '@app/common/math/helpers';
|
||||
import { RootState } from '@app/store';
|
||||
|
||||
import { selectStacksChain } from '../chains/stx-chain.selectors';
|
||||
@@ -19,6 +21,10 @@ export function useCurrentKeyDetails() {
|
||||
}
|
||||
|
||||
export const selectCurrentAccountIndex = createSelector(selectStacksChain, state => {
|
||||
const customAccountIndex = initialSearchParams.get('accountIndex');
|
||||
if (customAccountIndex && initBigNumber(customAccountIndex).isInteger()) {
|
||||
return initBigNumber(customAccountIndex).toNumber();
|
||||
}
|
||||
return state[defaultKeyId].currentAccountIndex;
|
||||
});
|
||||
|
||||
|
||||
@@ -3,7 +3,11 @@ import * as btc from '@scure/btc-signer';
|
||||
import { hexToBytes } from '@stacks/common';
|
||||
|
||||
import { RouteUrls } from '@shared/route-urls';
|
||||
import { SignPsbtRequest } from '@shared/rpc/methods/sign-psbt';
|
||||
import {
|
||||
SignPsbtRequest,
|
||||
getRpcSignPsbtParamErrors,
|
||||
validateRpcSignPsbtParams,
|
||||
} from '@shared/rpc/methods/sign-psbt';
|
||||
import { makeRpcErrorResponse } from '@shared/rpc/rpc-methods';
|
||||
import { ensureArray, isDefined } from '@shared/utils';
|
||||
|
||||
@@ -25,24 +29,23 @@ function validatePsbt(hex: string) {
|
||||
}
|
||||
|
||||
export async function rpcSignPsbt(message: SignPsbtRequest, port: chrome.runtime.Port) {
|
||||
if (!message.params || !message.params.hex) {
|
||||
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' },
|
||||
error: {
|
||||
code: RpcErrorCode.INVALID_PARAMS,
|
||||
message:
|
||||
'Invalid parameters: ' +
|
||||
errors.map(e => `Error in path ${e.path}, ${e.message}.`).join(' '),
|
||||
},
|
||||
})
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const params: RequestParams = [
|
||||
['requestId', message.id],
|
||||
['hex', message.params.hex],
|
||||
['publicKey', message.params.publicKey],
|
||||
['network', message.params.network ?? 'mainnet'],
|
||||
];
|
||||
|
||||
if (!validatePsbt(message.params.hex)) {
|
||||
chrome.tabs.sendMessage(
|
||||
getTabIdFromPort(port),
|
||||
@@ -54,17 +57,28 @@ export async function rpcSignPsbt(message: SignPsbtRequest, port: chrome.runtime
|
||||
return;
|
||||
}
|
||||
|
||||
const requestParams: RequestParams = [
|
||||
['requestId', message.id],
|
||||
['hex', message.params.hex],
|
||||
['publicKey', message.params.publicKey],
|
||||
['network', message.params.network ?? 'mainnet'],
|
||||
];
|
||||
|
||||
if (isDefined(message.params.account)) {
|
||||
requestParams.push(['accountIndex', message.params.account.toString()]);
|
||||
}
|
||||
|
||||
if (isDefined(message.params.allowedSighash))
|
||||
ensureArray(message.params.allowedSighash).forEach(hash =>
|
||||
params.push(['allowedSighash', hash.toString()])
|
||||
requestParams.push(['allowedSighash', hash.toString()])
|
||||
);
|
||||
|
||||
if (isDefined(message.params.signAtIndex))
|
||||
ensureArray(message.params.signAtIndex).forEach(index =>
|
||||
params.push(['signAtIndex', index.toString()])
|
||||
requestParams.push(['signAtIndex', index.toString()])
|
||||
);
|
||||
|
||||
const { urlParams, tabId } = makeSearchParamsWithDefaults(port, params);
|
||||
const { urlParams, tabId } = makeSearchParamsWithDefaults(port, requestParams);
|
||||
|
||||
const { id } = await triggerRequestWindowOpen(RouteUrls.RpcSignPsbt, urlParams);
|
||||
listenForPopupClose({
|
||||
|
||||
@@ -25,6 +25,13 @@ export function rpcSupportedMethods(message: SupportedMethodsRequest, port: chro
|
||||
},
|
||||
{
|
||||
name: 'sendTransfer',
|
||||
docsUrl:
|
||||
'https://hirowallet.gitbook.io/developers/bitcoin/sign-transactions/sending-bitcoin',
|
||||
},
|
||||
{
|
||||
name: 'signPsbt',
|
||||
docsUrl:
|
||||
'https://hirowallet.gitbook.io/developers/bitcoin/sign-transactions/partially-signed-bitcoin-transactions-psbts',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -38,7 +38,9 @@ export enum WalletDefaultNetworkConfigurationIds {
|
||||
|
||||
export type DefaultNetworkConfigurations = keyof typeof WalletDefaultNetworkConfigurationIds;
|
||||
|
||||
export type NetworkModes = 'mainnet' | 'testnet';
|
||||
export const networkModes = ['mainnet', 'testnet'] as const;
|
||||
|
||||
export type NetworkModes = (typeof networkModes)[number];
|
||||
|
||||
type BitcoinTestnetModes = 'testnet' | 'regtest' | 'signet';
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { PaymentTypes } from '@btckit/types';
|
||||
import { hexToBytes } from '@noble/hashes/utils';
|
||||
import { HDKey } from '@scure/bip32';
|
||||
import * as btc from '@scure/btc-signer';
|
||||
@@ -84,3 +85,32 @@ export function getAddressFromOutScript(script: Uint8Array, network: BitcoinNetw
|
||||
logger.error(`Unknown address type=${outScript.type}`);
|
||||
return '';
|
||||
}
|
||||
|
||||
type BtcSignerLibPaymentTypeIdentifers = 'wpkh' | 'wsh' | 'tr' | 'pkh' | 'sh';
|
||||
|
||||
const paymentTypeMap: Record<BtcSignerLibPaymentTypeIdentifers, PaymentTypes> = {
|
||||
wpkh: 'p2wpkh',
|
||||
wsh: 'p2wpkh-p2sh',
|
||||
tr: 'p2tr',
|
||||
pkh: 'p2pkh',
|
||||
sh: 'p2sh',
|
||||
};
|
||||
|
||||
function btcSignerLibPaymentTypeToPaymentTypeMap(payment: BtcSignerLibPaymentTypeIdentifers) {
|
||||
return paymentTypeMap[payment];
|
||||
}
|
||||
|
||||
function isBtcSignerLibPaymentType(payment: string): payment is BtcSignerLibPaymentTypeIdentifers {
|
||||
return payment in paymentTypeMap;
|
||||
}
|
||||
|
||||
function parseKnownPaymentType(payment: BtcSignerLibPaymentTypeIdentifers | PaymentTypes) {
|
||||
return isBtcSignerLibPaymentType(payment)
|
||||
? btcSignerLibPaymentTypeToPaymentTypeMap(payment)
|
||||
: payment;
|
||||
}
|
||||
|
||||
type PaymentTypeMap<T> = Record<PaymentTypes, T>;
|
||||
export function whenPaymentType(mode: PaymentTypes | BtcSignerLibPaymentTypeIdentifers) {
|
||||
return <T>(paymentMap: PaymentTypeMap<T>): T => paymentMap[parseKnownPaymentType(mode)];
|
||||
}
|
||||
|
||||
@@ -11,6 +11,14 @@ function getTaprootAccountDerivationPath(network: BitcoinNetworkModes, accountIn
|
||||
return `m/86'/${getBitcoinCoinTypeIndexByNetwork(network)}'/${accountIndex}'`;
|
||||
}
|
||||
|
||||
export function getTaprootAddressIndexDerivationPath(
|
||||
network: BitcoinNetworkModes,
|
||||
accountIndex: number,
|
||||
addressIndex: number
|
||||
) {
|
||||
return getTaprootAccountDerivationPath(network, accountIndex) + `/0/${addressIndex}`;
|
||||
}
|
||||
|
||||
export function deriveTaprootAccountFromRootKeychain(
|
||||
keychain: HDKey,
|
||||
network: BitcoinNetworkModes
|
||||
|
||||
@@ -10,13 +10,21 @@ import {
|
||||
getBitcoinCoinTypeIndexByNetwork,
|
||||
} from './bitcoin.utils';
|
||||
|
||||
function getNativeSegWitAccountDerivationPath(network: BitcoinNetworkModes, accountIndex: number) {
|
||||
function getNativeSegwitAccountDerivationPath(network: BitcoinNetworkModes, accountIndex: number) {
|
||||
return `m/84'/${getBitcoinCoinTypeIndexByNetwork(network)}'/${accountIndex}'`;
|
||||
}
|
||||
|
||||
export function getNativeSegwitAddressIndexDerivationPath(
|
||||
network: BitcoinNetworkModes,
|
||||
accountIndex: number,
|
||||
addressIndex: number
|
||||
) {
|
||||
return getNativeSegwitAccountDerivationPath(network, accountIndex) + `/0/${addressIndex}`;
|
||||
}
|
||||
|
||||
export function deriveNativeSegWitAccountKeychain(keychain: HDKey, network: NetworkModes) {
|
||||
if (keychain.depth !== DerivationPathDepth.Root) throw new Error('Keychain passed is not a root');
|
||||
return (index: number) => keychain.derive(getNativeSegWitAccountDerivationPath(network, index));
|
||||
return (index: number) => keychain.derive(getNativeSegwitAccountDerivationPath(network, index));
|
||||
}
|
||||
|
||||
export function getNativeSegWitPaymentFromAddressIndex(
|
||||
|
||||
@@ -25,7 +25,7 @@ interface BitcoinTransactionStatus {
|
||||
block_time: number | null;
|
||||
}
|
||||
|
||||
interface BitcoinTransactionVectorOutput {
|
||||
export interface BitcoinTransactionVectorOutput {
|
||||
scriptpubkey: string;
|
||||
scriptpubkey_asm: string;
|
||||
scriptpubkey_type: string;
|
||||
|
||||
@@ -1,14 +1,36 @@
|
||||
import { DefineRpcMethod, RpcRequest, RpcResponse } from '@btckit/types';
|
||||
import { SignatureHash } from '@scure/btc-signer';
|
||||
import * as yup from 'yup';
|
||||
|
||||
import { NetworkModes } from '@shared/constants';
|
||||
import { networkModes } from '@shared/constants';
|
||||
|
||||
interface SignPsbtRequestParams {
|
||||
publicKey: string;
|
||||
allowedSighash?: SignatureHash[];
|
||||
hex: string;
|
||||
signAtIndex?: number | number[];
|
||||
network?: NetworkModes;
|
||||
const rpcSignPsbtValidator = yup.object().shape({
|
||||
publicKey: yup.string().required(),
|
||||
allowedSighash: yup.array().of(yup.number().required()),
|
||||
hex: yup.string().required(),
|
||||
signAtIndex: yup.number().integer().positive(),
|
||||
network: yup.string().oneOf(networkModes),
|
||||
account: yup.number().integer().positive(),
|
||||
});
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
export function getRpcSignPsbtParamErrors(obj: unknown) {
|
||||
try {
|
||||
rpcSignPsbtValidator.validateSync(obj, { abortEarly: false });
|
||||
return [];
|
||||
} catch (e) {
|
||||
if (e instanceof yup.ValidationError) return e.inner;
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
export type SignPsbtRequest = RpcRequest<'signPsbt', SignPsbtRequestParams>;
|
||||
|
||||
@@ -23,6 +23,10 @@ const bitcoinTestnet: BitcoinNetwork = {
|
||||
wif: 0xef,
|
||||
};
|
||||
|
||||
// For testing mainnet
|
||||
const tempHex =
|
||||
'70736274ff0100fd36010200000004350db8bb43dc351cf4f611aab22363c1fcfb27c61f2f7e55f3e38a1384248c660100000000ffffffff8fcf2bcf05c76ef86595c3bbe2f944064b34eede0c4c3c5dce4f1f94e663183d0000000000ffffffff8391b15b124ad2276c13916a61efabbfcc34f4211b3e6123829eaeab8096b8420000000000ffffffffe4653717d3c1544b073f97978998558be6c8e8aa7e51922303ec4814551fa16e0000000000ffffffff041027000000000000225120e25ef2545bce7dd7bd4215cdc76d16fe72c63163dca3f7a0757efa928841c96b7c920000000000001600146a9d9d7679bfeaf45c3a7d5b88826ec657ea91e2c4090000000000001600146a9d9d7679bfeaf45c3a7d5b88826ec657ea91e2983a0000000000001600149c1a4f6160d35ffada2e230f8bf1c4cfaf6ff52f00000000000100fdaa0301000000000102371586ae4679b5e411fced4b36ec421f65527447be8e3d0206419f979214fdf20000000000fdffffff5466bdc917b6a13f1db9429ee649a08b6b3860366e87202e0934d193e07a0d5b0100000000fdffffff02102700000000000022512044cab551f8178996c12870fcfd0399e3b0e4b9bb0312fc1c8b07b02e2dc17b0af92a0000000000002251207f4d47576eeaaa79a4cf320bf0b1876e50d943bdbbfc2823d99df37e642a37880140c6dff1e2084c2ae245c8b4b7751b9a5b1145f347d9d0ae06b8e2ec4591a2ab53fea2e5c6a356dfc6a54f51a7f15f1d1db8f056e8713842bfed58a1e063841c090341b00ba699988211aaa2e2464290bbc7db231765c5eaff65e0a7c2be27315093caa4009f2b302912f125d6bc28680bc1cfca7796647259e003a601f12683c1808081fd4c02204878d26dbb1678c06558222e1fd68e9ed68783fbb68d9f2bcb7ace09b225149dac0063036f726401032468eb6429a613f2564a36ce39589cda9ae404605267c4e0908c27ad1f144a0de900000000010117746578742f68746d6c3b636861727365743d7574662d38004dde013c626f64792f3e3c7363726970743e6a3d323b643d646f63756d656e743b623d642e626f64790a636c6173732055524c536561726368506172616d737b6765743d6b3d3e6b3d3d22746f6b656e4944223f6a3a307d286173796e6328293d3e7b713d225c6e220a6a3d393532302b282b617761697428617761697420666574636828222f636f6e74656e742f6463656538306631316133313034616233326633313665623763656466323839653436616531313533353362383165376432343362643431616462343734376669302229292e7465787428292b6a29253330300a69662869734e614e286a292972657475726e3b0a683d28617761697428617761697420666574636828222f636f6e74656e742f6539306434613134316661643237386339306530633436373532363030346534396164613963353833396365333634613536663231336136323936346562363869302229292e746578742829292e73706c69742871290a622e696e6e657248544d4c3d685b305d3b0a7a3d646f63756d656e742e637265617465456c656d656e74282273637269707422290a7a2e696e6e657248544d4c3d682e736c69636528322c34292e6a6f696e2871290a622e617070656e644368696c64287a297d2928293c2f7363726970743e0a6821c04878d26dbb1678c06558222e1fd68e9ed68783fbb68d9f2bcb7ace09b225149d0000000001012bf92a0000000000002251207f4d47576eeaaa79a4cf320bf0b1876e50d943bdbbfc2823d99df37e642a3788011720937077dcbafef4c74be898fbf723c628787f0d9922c384e6e80eded948d033ee000100df01000000000101e4653717d3c1544b073f97978998558be6c8e8aa7e51922303ec4814551fa16e0100000000fdffffff0260ea0000000000001600149c1a4f6160d35ffada2e230f8bf1c4cfaf6ff52fc81adf01000000001600143c2a59281416c1a9e9dc55170be53c5577d420e202483045022100bac8690acbd849d57687f889854f8abaf688f0b5e02eafa555ff3d86e62a8aad022071439d2bbae03118a0dc8fdb3e85405a459e0e7279f6f4659491fbfe11f1ce6601210234943fdac5b5d4d6c28e03b89a6adb6c7a3f55e782e718af3a251764a320da7500000000000100de0100000000010113edda3c24c6c3e0bdd37418ff173c558075010d763bb037773968e8f5f8da3f0000000000fdffffff0230750000000000001600149c1a4f6160d35ffada2e230f8bf1c4cfaf6ff52f712df800000000001600142547a6fb036a168e7cfb3359e7553c63e4252170024730440220060f1f5614a368bc319531050b043af5aa2d87222dc8cf134dc2b209588c6dfc0220069f1c978ed6cc68f5eef40e319697215a7f0f4ce5c5411a40d6bd08c69b3430012102567b081243c6b8439932d52e2f8a8bd2133ce18c4e63b6ee4a40e09dc73d897300000000000100de010000000001016c21dd4dc9508a4383dd364f5249d7f9c126093845dc9186e18bae449ff13e7a0000000000fdffffff02a8610000000000001600149c1a4f6160d35ffada2e230f8bf1c4cfaf6ff52fdf32e001000000001600142547a6fb036a168e7cfb3359e7553c63e4252170024730440220703a99388b1d04c7c1d22e79994e04100f19abb165105710bbe12a338930e45902204d8ed7eae8be52d06912911a0d3ad1dd2bf9d2f9ef0acf2e56293c3213d726e5012102d5369b8460ed3e38d6313bc308a207c7eed33ac6d18ced08ece56f885ce54657000000000000000000';
|
||||
|
||||
const ecdsaPublicKeyLength = 33;
|
||||
const TEST_TESTNET_ACCOUNT_1_PUBKEY_P2WPKH =
|
||||
'02b6b0afe5f620bc8e532b640b148dd9dea0ed19d11f8ab420fcce488fe3974893';
|
||||
@@ -67,7 +71,9 @@ function buildTestNativeSegwitPsbtRequest(pubKey: Uint8Array): PsbtRequestOption
|
||||
|
||||
const psbt = tx.toPSBT();
|
||||
|
||||
return { hex: bytesToHex(psbt) };
|
||||
// For testing mainnet
|
||||
return { hex: tempHex };
|
||||
// return { hex: bytesToHex(psbt) };
|
||||
}
|
||||
|
||||
function buildTestNativeSegwitPsbtRequestWithIndexes(pubKey: Uint8Array): PsbtRequestOptions {
|
||||
@@ -121,11 +127,7 @@ function buildTestTaprootPsbtRequest(pubKey: Uint8Array): PsbtRequestOptions {
|
||||
|
||||
const psbt = tx.toPSBT();
|
||||
|
||||
console.log('lksjdflksjdflkj');
|
||||
|
||||
return {
|
||||
hex: '70736274ff0100fd36010200000004350db8bb43dc351cf4f611aab22363c1fcfb27c61f2f7e55f3e38a1384248c660100000000ffffffff8fcf2bcf05c76ef86595c3bbe2f944064b34eede0c4c3c5dce4f1f94e663183d0000000000ffffffff8391b15b124ad2276c13916a61efabbfcc34f4211b3e6123829eaeab8096b8420000000000ffffffffe4653717d3c1544b073f97978998558be6c8e8aa7e51922303ec4814551fa16e0000000000ffffffff041027000000000000225120e25ef2545bce7dd7bd4215cdc76d16fe72c63163dca3f7a0757efa928841c96b7c920000000000001600146a9d9d7679bfeaf45c3a7d5b88826ec657ea91e2c4090000000000001600146a9d9d7679bfeaf45c3a7d5b88826ec657ea91e2983a0000000000001600149c1a4f6160d35ffada2e230f8bf1c4cfaf6ff52f00000000000100fdaa0301000000000102371586ae4679b5e411fced4b36ec421f65527447be8e3d0206419f979214fdf20000000000fdffffff5466bdc917b6a13f1db9429ee649a08b6b3860366e87202e0934d193e07a0d5b0100000000fdffffff02102700000000000022512044cab551f8178996c12870fcfd0399e3b0e4b9bb0312fc1c8b07b02e2dc17b0af92a0000000000002251207f4d47576eeaaa79a4cf320bf0b1876e50d943bdbbfc2823d99df37e642a37880140c6dff1e2084c2ae245c8b4b7751b9a5b1145f347d9d0ae06b8e2ec4591a2ab53fea2e5c6a356dfc6a54f51a7f15f1d1db8f056e8713842bfed58a1e063841c090341b00ba699988211aaa2e2464290bbc7db231765c5eaff65e0a7c2be27315093caa4009f2b302912f125d6bc28680bc1cfca7796647259e003a601f12683c1808081fd4c02204878d26dbb1678c06558222e1fd68e9ed68783fbb68d9f2bcb7ace09b225149dac0063036f726401032468eb6429a613f2564a36ce39589cda9ae404605267c4e0908c27ad1f144a0de900000000010117746578742f68746d6c3b636861727365743d7574662d38004dde013c626f64792f3e3c7363726970743e6a3d323b643d646f63756d656e743b623d642e626f64790a636c6173732055524c536561726368506172616d737b6765743d6b3d3e6b3d3d22746f6b656e4944223f6a3a307d286173796e6328293d3e7b713d225c6e220a6a3d393532302b282b617761697428617761697420666574636828222f636f6e74656e742f6463656538306631316133313034616233326633313665623763656466323839653436616531313533353362383165376432343362643431616462343734376669302229292e7465787428292b6a29253330300a69662869734e614e286a292972657475726e3b0a683d28617761697428617761697420666574636828222f636f6e74656e742f6539306434613134316661643237386339306530633436373532363030346534396164613963353833396365333634613536663231336136323936346562363869302229292e746578742829292e73706c69742871290a622e696e6e657248544d4c3d685b305d3b0a7a3d646f63756d656e742e637265617465456c656d656e74282273637269707422290a7a2e696e6e657248544d4c3d682e736c69636528322c34292e6a6f696e2871290a622e617070656e644368696c64287a297d2928293c2f7363726970743e0a6821c04878d26dbb1678c06558222e1fd68e9ed68783fbb68d9f2bcb7ace09b225149d0000000001012bf92a0000000000002251207f4d47576eeaaa79a4cf320bf0b1876e50d943bdbbfc2823d99df37e642a3788011720937077dcbafef4c74be898fbf723c628787f0d9922c384e6e80eded948d033ee000100df01000000000101e4653717d3c1544b073f97978998558be6c8e8aa7e51922303ec4814551fa16e0100000000fdffffff0260ea0000000000001600149c1a4f6160d35ffada2e230f8bf1c4cfaf6ff52fc81adf01000000001600143c2a59281416c1a9e9dc55170be53c5577d420e202483045022100bac8690acbd849d57687f889854f8abaf688f0b5e02eafa555ff3d86e62a8aad022071439d2bbae03118a0dc8fdb3e85405a459e0e7279f6f4659491fbfe11f1ce6601210234943fdac5b5d4d6c28e03b89a6adb6c7a3f55e782e718af3a251764a320da7500000000000100de0100000000010113edda3c24c6c3e0bdd37418ff173c558075010d763bb037773968e8f5f8da3f0000000000fdffffff0230750000000000001600149c1a4f6160d35ffada2e230f8bf1c4cfaf6ff52f712df800000000001600142547a6fb036a168e7cfb3359e7553c63e4252170024730440220060f1f5614a368bc319531050b043af5aa2d87222dc8cf134dc2b209588c6dfc0220069f1c978ed6cc68f5eef40e319697215a7f0f4ce5c5411a40d6bd08c69b3430012102567b081243c6b8439932d52e2f8a8bd2133ce18c4e63b6ee4a40e09dc73d897300000000000100de010000000001016c21dd4dc9508a4383dd364f5249d7f9c126093845dc9186e18bae449ff13e7a0000000000fdffffff02a8610000000000001600149c1a4f6160d35ffada2e230f8bf1c4cfaf6ff52fdf32e001000000001600142547a6fb036a168e7cfb3359e7553c63e4252170024730440220703a99388b1d04c7c1d22e79994e04100f19abb165105710bbe12a338930e45902204d8ed7eae8be52d06912911a0d3ad1dd2bf9d2f9ef0acf2e56293c3213d726e5012102d5369b8460ed3e38d6313bc308a207c7eed33ac6d18ced08ece56f885ce54657000000000000000000',
|
||||
};
|
||||
return { hex: bytesToHex(psbt) };
|
||||
}
|
||||
|
||||
function buildTestTaprootPsbtRequestWithIndex(pubKey: Uint8Array): PsbtRequestOptions {
|
||||
|
||||
18
yarn.lock
18
yarn.lock
@@ -59,9 +59,9 @@
|
||||
"@apollo/utils.logger" "^2.0.0"
|
||||
|
||||
"@apollo/server@^4.3.0":
|
||||
version "4.7.1"
|
||||
resolved "https://registry.yarnpkg.com/@apollo/server/-/server-4.7.1.tgz#3063fb8ca4a5a94cdf15f219811ac50c3e097b9f"
|
||||
integrity sha512-rFxd8jsMlqEYzmhuxATaDAPoRH905R56FKP4TnZWaiDYJtjhHe3hxZOWl24V7s0dB52DIp6S/x+zjQX8fwD37w==
|
||||
version "4.7.4"
|
||||
resolved "https://registry.yarnpkg.com/@apollo/server/-/server-4.7.4.tgz#b2a999b5a1f6bb909ec1749106b0c6704296a622"
|
||||
integrity sha512-7MwEsBR+4JRfjAzrFIOP0m/NdzNCyVOlRWCO/86zs73CC+trr08FklTD7JD0yx5BJDfyzjdhF2BZ1sEYvp/Waw==
|
||||
dependencies:
|
||||
"@apollo/cache-control-types" "^1.0.2"
|
||||
"@apollo/server-gateway-interface" "^1.1.0"
|
||||
@@ -71,7 +71,7 @@
|
||||
"@apollo/utils.isnodelike" "^2.0.0"
|
||||
"@apollo/utils.keyvaluecache" "^2.1.0"
|
||||
"@apollo/utils.logger" "^2.0.0"
|
||||
"@apollo/utils.usagereporting" "^2.0.0"
|
||||
"@apollo/utils.usagereporting" "^2.1.0"
|
||||
"@apollo/utils.withrequired" "^2.0.0"
|
||||
"@graphql-tools/schema" "^9.0.0"
|
||||
"@josephg/resolvable" "^1.0.0"
|
||||
@@ -155,12 +155,12 @@
|
||||
resolved "https://registry.yarnpkg.com/@apollo/utils.stripsensitiveliterals/-/utils.stripsensitiveliterals-2.0.1.tgz#2f3350483be376a98229f90185eaf19888323132"
|
||||
integrity sha512-QJs7HtzXS/JIPMKWimFnUMK7VjkGQTzqD9bKD1h3iuPAqLsxd0mUNVbkYOPTsDhUKgcvUOfOqOJWYohAKMvcSA==
|
||||
|
||||
"@apollo/utils.usagereporting@^2.0.0":
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@apollo/utils.usagereporting/-/utils.usagereporting-2.0.1.tgz#6926231ad1f5837beb3cac06afcd22944ac0f644"
|
||||
integrity sha512-18smkNfiSfu5yj2mpCIfSzmpDNh90a4PQ6t8kSwGKcPRD3KD83TfK7fF37fSRdnvO93dBkGreWisLXnCpqfWXg==
|
||||
"@apollo/utils.usagereporting@^2.1.0":
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@apollo/utils.usagereporting/-/utils.usagereporting-2.1.0.tgz#11bca6a61fcbc6e6d812004503b38916e74313f4"
|
||||
integrity sha512-LPSlBrn+S17oBy5eWkrRSGb98sWmnEzo3DPTZgp8IQc8sJe0prDgDuppGq4NeQlpoqEHz0hQeYHAOA0Z3aQsxQ==
|
||||
dependencies:
|
||||
"@apollo/usage-reporting-protobuf" "^4.0.0"
|
||||
"@apollo/usage-reporting-protobuf" "^4.1.0"
|
||||
"@apollo/utils.dropunuseddefinitions" "^2.0.1"
|
||||
"@apollo/utils.printwithreducedwhitespace" "^2.0.1"
|
||||
"@apollo/utils.removealiases" "2.0.1"
|
||||
|
||||
Reference in New Issue
Block a user