mirror of
https://github.com/zhigang1992/wallet.git
synced 2026-01-12 22:53:27 +08:00
feat: extended rpcmethods with bitcoin contract related case, added routes and success/failure pages
This commit is contained in:
@@ -11,16 +11,18 @@ export function BroadcastError() {
|
||||
const { state } = useLocation();
|
||||
const analytics = useAnalytics();
|
||||
const msg = get(state, 'error.message', 'Unknown error response');
|
||||
const title = get(state, 'title', 'There was an error broadcasting your transaction');
|
||||
const body = get(state, 'body', 'Unable to broadcast transaction');
|
||||
|
||||
useOnMount(() => void analytics.track('bitcoin_broadcast_tx_error', { msg }));
|
||||
useOnMount(() => void analytics.track('bitcoin_contract_error', { msg }));
|
||||
|
||||
return (
|
||||
<BroadcastErrorLayout
|
||||
my="loose"
|
||||
textAlign="center"
|
||||
errorPayload={msg}
|
||||
title="There was an error broadcasting your transaction"
|
||||
body="Unable to broadcast transaction"
|
||||
title={title}
|
||||
body={body}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
import { toast } from 'react-hot-toast';
|
||||
import { FiCheck, FiCopy, FiExternalLink } from 'react-icons/fi';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
|
||||
import { Stack, useClipboard } from '@stacks/ui';
|
||||
import { Text } from '@stacks/ui';
|
||||
|
||||
import { useAnalytics } from '@app/common/hooks/analytics/use-analytics';
|
||||
import { useExplorerLink } from '@app/common/hooks/use-explorer-link';
|
||||
import { useRouteHeader } from '@app/common/hooks/use-route-header';
|
||||
import { satToBtc } from '@app/common/money/unit-conversion';
|
||||
import {
|
||||
InfoCard,
|
||||
InfoCardAssetValue,
|
||||
InfoCardBtn,
|
||||
InfoCardFooter,
|
||||
} from '@app/components/info-card/info-card';
|
||||
import { ModalHeader } from '@app/components/modal-header';
|
||||
|
||||
export function LockBitcoinSummary() {
|
||||
const { state } = useLocation();
|
||||
|
||||
const { txId, txMoney, txFiatValue, txFiatValueSymbol, symbol, txLink } = state;
|
||||
|
||||
const { onCopy } = useClipboard(txId);
|
||||
const { handleOpenTxLink } = useExplorerLink();
|
||||
const analytics = useAnalytics();
|
||||
|
||||
function onClickLink() {
|
||||
void analytics.track('view_transaction_confirmation', { symbol: 'BTC' });
|
||||
handleOpenTxLink(txLink);
|
||||
}
|
||||
|
||||
function onClickCopy() {
|
||||
onCopy();
|
||||
toast.success('ID copied!');
|
||||
}
|
||||
|
||||
useRouteHeader(<ModalHeader hideActions defaultClose title="Locked Bitcoin" />);
|
||||
|
||||
return (
|
||||
<InfoCard>
|
||||
<InfoCardAssetValue
|
||||
value={Number(satToBtc(txMoney.amount))}
|
||||
fiatValue={txFiatValue}
|
||||
fiatSymbol={txFiatValueSymbol}
|
||||
symbol={symbol}
|
||||
icon={FiCheck}
|
||||
my="loose"
|
||||
px="loose"
|
||||
/>
|
||||
<Text fontSize={2} fontWeight={200} padding={'25px'} textAlign={'justify'}>
|
||||
<span style={{ fontWeight: 500 }}>Success!</span> Your bitcoin has been locked securely. All
|
||||
that's left is for it to be confirmed on the blockchain. After confirmation, you can proceed
|
||||
with borrowing against it.
|
||||
</Text>
|
||||
<InfoCardFooter>
|
||||
<Stack spacing="base" isInline width="100%">
|
||||
<InfoCardBtn onClick={onClickLink} icon={FiExternalLink} label="View Details" />
|
||||
<InfoCardBtn onClick={onClickCopy} icon={FiCopy} label="Copy ID" />
|
||||
</Stack>
|
||||
</InfoCardFooter>
|
||||
</InfoCard>
|
||||
);
|
||||
}
|
||||
@@ -24,6 +24,7 @@ import { AddNetwork } from '@app/features/message-signer/add-network/add-network
|
||||
import { RetrieveTaprootToNativeSegwit } from '@app/features/retrieve-taproot-to-native-segwit/retrieve-taproot-to-native-segwit';
|
||||
import { ThemesDrawer } from '@app/features/theme-drawer/theme-drawer';
|
||||
import { AllowDiagnosticsPage } from '@app/pages/allow-diagnostics/allow-diagnostics';
|
||||
import { BitcoinContractRequest } from '@app/pages/bitcoin-contract-request/bitcoin-contract-request';
|
||||
import { ChooseAccount } from '@app/pages/choose-account/choose-account';
|
||||
import { FundPage } from '@app/pages/fund/fund';
|
||||
import { Home } from '@app/pages/home/home';
|
||||
@@ -42,6 +43,7 @@ import { rpcSendTransferRoutes } from '@app/pages/rpc-send-transfer/rpc-send-tra
|
||||
import { RpcSignPsbt } from '@app/pages/rpc-sign-psbt/rpc-sign-psbt';
|
||||
import { SelectNetwork } from '@app/pages/select-network/select-network';
|
||||
import { BroadcastError } from '@app/pages/send/broadcast-error/broadcast-error';
|
||||
import { LockBitcoinSummary } from '@app/pages/send/locked-bitcoin-summary/locked-bitcoin-summary';
|
||||
import { SendInscriptionContainer } from '@app/pages/send/ordinal-inscription/components/send-inscription-container';
|
||||
import { SendInscriptionChooseFee } from '@app/pages/send/ordinal-inscription/send-inscription-choose-fee';
|
||||
import { SendInscriptionForm } from '@app/pages/send/ordinal-inscription/send-inscription-form';
|
||||
@@ -136,6 +138,18 @@ function useAppRoutes() {
|
||||
{settingsModalRoutes}
|
||||
{ledgerStacksTxSigningRoutes}
|
||||
</Route>
|
||||
<Route
|
||||
path={RouteUrls.RpcReceiveBitcoinContractOffer}
|
||||
element={
|
||||
<AccountGate>
|
||||
<Suspense fallback={<LoadingSpinner height="600px" />}>
|
||||
<BitcoinContractRequest />
|
||||
</Suspense>
|
||||
</AccountGate>
|
||||
}
|
||||
></Route>
|
||||
<Route path={RouteUrls.BitcoinContractLockSuccess} element={<LockBitcoinSummary />} />
|
||||
<Route path={RouteUrls.BitcoinContractLockError} element={<BroadcastError />} />
|
||||
<Route
|
||||
path={RouteUrls.Onboarding}
|
||||
element={
|
||||
|
||||
@@ -3,6 +3,7 @@ import { RpcErrorCode } from '@btckit/types';
|
||||
import { WalletRequests, makeRpcErrorResponse } from '@shared/rpc/rpc-methods';
|
||||
|
||||
import { getTabIdFromPort } from './messaging-utils';
|
||||
import { rpcAcceptBitcoinContractOffer } from './rpc-methods/accept-bitcoin-contract';
|
||||
import { rpcGetAddresses } from './rpc-methods/get-addresses';
|
||||
import { rpcSendTransfer } from './rpc-methods/send-transfer';
|
||||
import { rpcSignMessage } from './rpc-methods/sign-message';
|
||||
@@ -36,6 +37,11 @@ export async function rpcMessageHandler(message: WalletRequests, port: chrome.ru
|
||||
break;
|
||||
}
|
||||
|
||||
case 'acceptBitcoinContractOffer': {
|
||||
await rpcAcceptBitcoinContractOffer(message, port);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
chrome.tabs.sendMessage(
|
||||
getTabIdFromPort(port),
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
import { RpcErrorCode } from '@btckit/types';
|
||||
|
||||
import { RouteUrls } from '@shared/route-urls';
|
||||
import { BitcoinContractRequest } from '@shared/rpc/methods/accept-bitcoin-contract';
|
||||
import { makeRpcErrorResponse } from '@shared/rpc/rpc-methods';
|
||||
|
||||
import {
|
||||
RequestParams,
|
||||
getTabIdFromPort,
|
||||
listenForPopupClose,
|
||||
makeSearchParamsWithDefaults,
|
||||
triggerRequestWindowOpen,
|
||||
} from '../messaging-utils';
|
||||
|
||||
export async function rpcAcceptBitcoinContractOffer(
|
||||
message: BitcoinContractRequest,
|
||||
port: chrome.runtime.Port
|
||||
) {
|
||||
if (!message.params) {
|
||||
chrome.tabs.sendMessage(
|
||||
getTabIdFromPort(port),
|
||||
makeRpcErrorResponse('acceptBitcoinContractOffer', {
|
||||
id: message.id,
|
||||
error: { code: RpcErrorCode.INVALID_PARAMS, message: 'Invalid parameters' },
|
||||
})
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const params: RequestParams = [
|
||||
['bitcoinContractOffer', message.params.bitcoinContractOffer],
|
||||
['counterpartyWalletURL', message.params.counterpartyWalletURL],
|
||||
['counterpartyWalletName', message.params.counterpartyWalletName],
|
||||
['counterpartyWalletIcon', message.params.counterpartyWalletIcon],
|
||||
['requestId', message.id],
|
||||
];
|
||||
|
||||
if (message.params.bitcoinContractOffer.includes('Invalid state: Not enough fund in utxos')) {
|
||||
chrome.tabs.sendMessage(
|
||||
getTabIdFromPort(port),
|
||||
makeRpcErrorResponse('acceptBitcoinContractOffer', {
|
||||
id: message.id,
|
||||
error: {
|
||||
code: RpcErrorCode.INVALID_REQUEST,
|
||||
message: 'The counterparty does not have enough funds to complete the offer',
|
||||
},
|
||||
})
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const { urlParams, tabId } = makeSearchParamsWithDefaults(port, params);
|
||||
|
||||
const { id } = await triggerRequestWindowOpen(
|
||||
RouteUrls.RpcReceiveBitcoinContractOffer,
|
||||
urlParams
|
||||
);
|
||||
|
||||
listenForPopupClose({
|
||||
tabId,
|
||||
id,
|
||||
response: makeRpcErrorResponse('acceptBitcoinContractOffer', {
|
||||
id: message.id,
|
||||
error: {
|
||||
code: RpcErrorCode.USER_REJECTION,
|
||||
message: 'User rejected the offer',
|
||||
},
|
||||
}),
|
||||
});
|
||||
}
|
||||
@@ -52,6 +52,11 @@ export enum RouteUrls {
|
||||
// Locked wallet route
|
||||
Unlock = '/unlock',
|
||||
|
||||
// Bitcoin Contract routes
|
||||
BitcoinContractLockSuccess = '/bitcoin-contract-lock-success',
|
||||
BitcoinContractLockError = '/bitcoin-contract-lock-error',
|
||||
BitcoinContractList = '/bitcoin-contract-list',
|
||||
|
||||
// Modal routes
|
||||
ChangeTheme = 'change-theme',
|
||||
EditNonce = 'edit-nonce',
|
||||
@@ -92,5 +97,6 @@ export enum RouteUrls {
|
||||
RpcSendTransferChooseFee = '/send-transfer/choose-fee',
|
||||
RpcSendTransferConfirmation = '/send-transfer/confirm',
|
||||
RpcSendTransferSummary = '/send-transfer/summary',
|
||||
RpcReceiveBitcoinContractOffer = '/bitcoin-contract-offer/:bitcoinContractOffer/:counterpartyWalletURL',
|
||||
RpcSignBip322Message = '/sign-bip322-message',
|
||||
}
|
||||
|
||||
26
src/shared/rpc/methods/accept-bitcoin-contract.ts
Normal file
26
src/shared/rpc/methods/accept-bitcoin-contract.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { DefineRpcMethod, RpcRequest, RpcResponse } from '@btckit/types';
|
||||
import { AllowAdditionaProperties } from '@btckit/types/dist/types/utils';
|
||||
|
||||
interface BitcoinContractResponseParams extends AllowAdditionaProperties {
|
||||
bitcoinContractOffer: string;
|
||||
counterpartyWalletURL: string;
|
||||
counterpartyWalletName: string;
|
||||
counterpartyWalletIcon: string;
|
||||
}
|
||||
|
||||
interface BitcoinContractResponseBody extends AllowAdditionaProperties {
|
||||
contractId: string;
|
||||
action: string;
|
||||
txId?: string;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export type BitcoinContractRequest = RpcRequest<
|
||||
'acceptBitcoinContractOffer',
|
||||
BitcoinContractResponseParams
|
||||
>;
|
||||
type BitcoinContractResponse = RpcResponse<BitcoinContractResponseBody>;
|
||||
export type AcceptBitcoinContract = DefineRpcMethod<
|
||||
BitcoinContractRequest,
|
||||
BitcoinContractResponse
|
||||
>;
|
||||
@@ -2,11 +2,12 @@ import { BtcKitMethodMap, ExtractErrorResponse, ExtractSuccessResponse } from '@
|
||||
|
||||
import { ValueOf } from '@shared/utils/type-utils';
|
||||
|
||||
import { AcceptBitcoinContract } from './methods/accept-bitcoin-contract';
|
||||
import { SignPsbt } from './methods/sign-psbt';
|
||||
import { SupportedMethods } from './methods/supported-methods';
|
||||
|
||||
// Supports BtcKit methods, as well as custom Hiro Wallet methods
|
||||
export type WalletMethodMap = BtcKitMethodMap & SupportedMethods & SignPsbt;
|
||||
export type WalletMethodMap = BtcKitMethodMap & SupportedMethods & SignPsbt & AcceptBitcoinContract;
|
||||
|
||||
export type WalletRequests = ValueOf<WalletMethodMap>['request'];
|
||||
export type WalletResponses = ValueOf<WalletMethodMap>['response'];
|
||||
|
||||
Reference in New Issue
Block a user