feat: extended rpcmethods with bitcoin contract related case, added routes and success/failure pages

This commit is contained in:
Polybius93
2023-06-27 12:22:51 +02:00
committed by kyranjamie
parent 626ef0beeb
commit f0ff7afba1
8 changed files with 194 additions and 4 deletions

View File

@@ -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}
/>
);
}

View File

@@ -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>
);
}

View File

@@ -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={

View File

@@ -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),

View File

@@ -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',
},
}),
});
}

View File

@@ -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',
}

View 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
>;

View File

@@ -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'];