mirror of
https://github.com/zhigang1992/wallet.git
synced 2026-01-12 22:53:27 +08:00
feat: updated bitcoin contract api, modified responses, added error handling
This commit is contained in:
@@ -184,7 +184,7 @@
|
||||
"coinselect": "3.1.13",
|
||||
"compare-versions": "4.1.3",
|
||||
"dayjs": "1.11.8",
|
||||
"dlc-wasm-wallet": "0.4.3",
|
||||
"dlc-wasm-wallet": "0.4.7",
|
||||
"dompurify": "3.0.4",
|
||||
"downshift": "6.1.7",
|
||||
"ecdsa-sig-formatter": "1.0.11",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { RpcErrorCode } from '@btckit/types';
|
||||
import { bytesToHex } from '@stacks/common';
|
||||
import { JsDLCInterface } from 'dlc-wasm-wallet';
|
||||
|
||||
@@ -10,7 +11,9 @@ import {
|
||||
} from '@shared/crypto/bitcoin/bitcoin.utils';
|
||||
import { createMoneyFromDecimal } from '@shared/models/money.model';
|
||||
import { RouteUrls } from '@shared/route-urls';
|
||||
import { BitcoinContractResponseStatus } from '@shared/rpc/methods/accept-bitcoin-contract';
|
||||
import { makeRpcSuccessResponse } from '@shared/rpc/rpc-methods';
|
||||
import { makeRpcErrorResponse } from '@shared/rpc/rpc-methods';
|
||||
|
||||
import { sendAcceptedBitcoinContractOfferToProtocolWallet } from '@app/query/bitcoin/contract/send-accepted-bitcoin-contract-offer';
|
||||
import {
|
||||
@@ -54,9 +57,10 @@ export function useBitcoinContracts() {
|
||||
const getNativeSegwitSigner = useCurrentAccountNativeSegwitSigner();
|
||||
const currentIndex = useCurrentAccountIndex();
|
||||
const nativeSegwitPrivateKeychain = useNativeSegwitAccountBuilder()?.(currentIndex);
|
||||
const oracleAPI = 'https://testnet.dlc.link/oracle';
|
||||
|
||||
async function getBitcoinContractInterface(): Promise<JsDLCInterface | undefined> {
|
||||
async function getBitcoinContractInterface(
|
||||
attestorURLs: string[]
|
||||
): Promise<JsDLCInterface | undefined> {
|
||||
const bitcoinAccountDetails = getNativeSegwitSigner?.(0);
|
||||
|
||||
if (!nativeSegwitPrivateKeychain || !bitcoinAccountDetails) return;
|
||||
@@ -83,7 +87,7 @@ export function useBitcoinContracts() {
|
||||
currentAddress,
|
||||
currentBitcoinNetwork,
|
||||
blockchainAPI,
|
||||
oracleAPI
|
||||
JSON.stringify(attestorURLs)
|
||||
);
|
||||
|
||||
return bitcoinContractInterface;
|
||||
@@ -91,11 +95,10 @@ export function useBitcoinContracts() {
|
||||
|
||||
function handleOffer(
|
||||
bitcoinContractOfferJSON: string,
|
||||
counterpartyWalletURL: string,
|
||||
counterpartyWalletName: string,
|
||||
counterpartyWalletIcon: string
|
||||
counterpartyWalletDetailsJSON: string
|
||||
): BitcoinContractOfferDetails {
|
||||
const bitcoinContractOffer = JSON.parse(bitcoinContractOfferJSON);
|
||||
const counterpartyWalletDetails = JSON.parse(counterpartyWalletDetailsJSON);
|
||||
|
||||
const bitcoinContractId = bitcoinContractOffer.temporaryContractId;
|
||||
const bitcoinContractCollateralAmount =
|
||||
@@ -110,12 +113,6 @@ export function useBitcoinContracts() {
|
||||
bitcoinContractExpirationDate,
|
||||
};
|
||||
|
||||
const counterpartyWalletDetails: CounterpartyWalletDetails = {
|
||||
counterpartyWalletURL,
|
||||
counterpartyWalletName,
|
||||
counterpartyWalletIcon,
|
||||
};
|
||||
|
||||
const bitcoinContractOfferDetails: BitcoinContractOfferDetails = {
|
||||
simplifiedBitcoinContract: simplifiedBitcoinContractOffer,
|
||||
counterpartyWalletDetails,
|
||||
@@ -126,27 +123,27 @@ export function useBitcoinContracts() {
|
||||
|
||||
async function handleAccept(
|
||||
bitcoinContractJSON: string,
|
||||
counterpartyWalletDetails: CounterpartyWalletDetails
|
||||
counterpartyWalletDetails: CounterpartyWalletDetails,
|
||||
attestorURLs: string[]
|
||||
) {
|
||||
let bitcoinContractInterface: JsDLCInterface | undefined;
|
||||
try {
|
||||
bitcoinContractInterface = await getBitcoinContractInterface();
|
||||
bitcoinContractInterface = await getBitcoinContractInterface(attestorURLs);
|
||||
} catch (error) {
|
||||
navigate(RouteUrls.BitcoinContractLockError, {
|
||||
state: {
|
||||
error,
|
||||
title: 'There was an error with getting the bitcoin contract interface',
|
||||
body: 'Unable to setup interface',
|
||||
title: 'There was an error with getting the Bitcoin Contract Interface',
|
||||
body: 'Unable to setup Bitcoin Contract Interface',
|
||||
},
|
||||
});
|
||||
sendRpcResponse('none', '', 'failed');
|
||||
sendRpcResponse(BitcoinContractResponseStatus.INTERFACE_ERROR);
|
||||
}
|
||||
|
||||
if (!bitcoinContractInterface) return;
|
||||
|
||||
const bitcoinContractOffer = JSON.parse(bitcoinContractJSON);
|
||||
|
||||
const bitcoinContractId = bitcoinContractOffer.temporaryContractId;
|
||||
const bitcoinContractCollateralAmount =
|
||||
bitcoinContractOffer.contractInfo.singleContractInfo.totalCollateral;
|
||||
|
||||
@@ -162,6 +159,8 @@ export function useBitcoinContracts() {
|
||||
counterpartyWalletDetails.counterpartyWalletURL
|
||||
);
|
||||
|
||||
const bitcoinContractId = signedBitcoinContract.contractId;
|
||||
|
||||
const txId = await bitcoinContractInterface.countersign_and_broadcast(
|
||||
JSON.stringify(signedBitcoinContract)
|
||||
);
|
||||
@@ -182,7 +181,7 @@ export function useBitcoinContracts() {
|
||||
},
|
||||
});
|
||||
|
||||
sendRpcResponse(bitcoinContractId, txId, 'accept');
|
||||
sendRpcResponse(BitcoinContractResponseStatus.SUCCESS, bitcoinContractId, txId);
|
||||
} catch (error) {
|
||||
navigate(RouteUrls.BitcoinContractLockError, {
|
||||
state: {
|
||||
@@ -191,12 +190,12 @@ export function useBitcoinContracts() {
|
||||
body: 'Unable to lock bitcoin',
|
||||
},
|
||||
});
|
||||
sendRpcResponse(bitcoinContractId, '', 'failed');
|
||||
sendRpcResponse(BitcoinContractResponseStatus.BROADCAST_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
function handleReject(bitcoinContractId: string) {
|
||||
sendRpcResponse(bitcoinContractId, '', 'reject');
|
||||
function handleReject() {
|
||||
sendRpcResponse(BitcoinContractResponseStatus.REJECTED);
|
||||
close();
|
||||
}
|
||||
|
||||
@@ -217,25 +216,66 @@ export function useBitcoinContracts() {
|
||||
};
|
||||
}
|
||||
|
||||
function sendRpcResponse(bitcoinContractId: string, txId: string, action: string) {
|
||||
function sendRpcResponse(
|
||||
responseStatus: BitcoinContractResponseStatus,
|
||||
bitcoinContractId?: string,
|
||||
txId?: string
|
||||
) {
|
||||
if (!defaultParams.tabId || !initialSearchParams.get('requestId')) return;
|
||||
|
||||
chrome.tabs.sendMessage(
|
||||
defaultParams.tabId,
|
||||
makeRpcSuccessResponse('acceptBitcoinContractOffer', {
|
||||
id: initialSearchParams.get('requestId') as string,
|
||||
result: {
|
||||
contractId: bitcoinContractId,
|
||||
txId,
|
||||
action,
|
||||
},
|
||||
})
|
||||
);
|
||||
const requestId = initialSearchParams.get('requestId') as string;
|
||||
let response;
|
||||
|
||||
switch (responseStatus) {
|
||||
case BitcoinContractResponseStatus.REJECTED:
|
||||
response = makeRpcErrorResponse('acceptBitcoinContractOffer', {
|
||||
id: requestId,
|
||||
error: {
|
||||
code: RpcErrorCode.USER_REJECTION,
|
||||
message: responseStatus,
|
||||
},
|
||||
});
|
||||
break;
|
||||
|
||||
case BitcoinContractResponseStatus.NETWORK_ERROR:
|
||||
response = makeRpcErrorResponse('acceptBitcoinContractOffer', {
|
||||
id: requestId,
|
||||
error: {
|
||||
code: RpcErrorCode.INVALID_REQUEST,
|
||||
message: responseStatus,
|
||||
},
|
||||
});
|
||||
break;
|
||||
|
||||
case BitcoinContractResponseStatus.BROADCAST_ERROR:
|
||||
case BitcoinContractResponseStatus.INTERFACE_ERROR:
|
||||
response = makeRpcErrorResponse('acceptBitcoinContractOffer', {
|
||||
id: requestId,
|
||||
error: {
|
||||
code: RpcErrorCode.INTERNAL_ERROR,
|
||||
message: responseStatus,
|
||||
},
|
||||
});
|
||||
break;
|
||||
|
||||
default:
|
||||
response = makeRpcSuccessResponse('acceptBitcoinContractOffer', {
|
||||
id: requestId,
|
||||
result: {
|
||||
contractId: bitcoinContractId,
|
||||
txId,
|
||||
},
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
chrome.tabs.sendMessage(defaultParams.tabId, response);
|
||||
}
|
||||
|
||||
return {
|
||||
handleOffer,
|
||||
handleAccept,
|
||||
handleReject,
|
||||
sendRpcResponse,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import { useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { RouteUrls } from '@shared/route-urls';
|
||||
import { BitcoinContractResponseStatus } from '@shared/rpc/methods/accept-bitcoin-contract';
|
||||
|
||||
import { useBitcoinContracts } from '@app/common/hooks/use-bitcoin-contracts';
|
||||
import { BitcoinContractOfferDetails } from '@app/common/hooks/use-bitcoin-contracts';
|
||||
@@ -14,48 +18,68 @@ import { BitcoinContractRequestWarningLabel } from './components/bitcoin-contrac
|
||||
|
||||
export function BitcoinContractRequest() {
|
||||
const getNativeSegwitSigner = useCurrentAccountNativeSegwitSigner();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const { handleOffer, handleAccept, handleReject } = useBitcoinContracts();
|
||||
const { handleOffer, handleAccept, handleReject, sendRpcResponse } = useBitcoinContracts();
|
||||
|
||||
const [bitcoinContractJSON, setBitcoinContractJSON] = useState<string>();
|
||||
const [bitcoinContractOfferDetails, setBitcoinContractOfferDetails] =
|
||||
useState<BitcoinContractOfferDetails>();
|
||||
const [bitcoinAddress, setBitcoinAddress] = useState<string>();
|
||||
const [attestorURLs, setAttestorURLs] = useState<string[]>([]);
|
||||
|
||||
const [isLoading, setLoading] = useState(true);
|
||||
const [isProcessing, setProcessing] = useState(false);
|
||||
|
||||
const handleAcceptClick = async () => {
|
||||
if (!bitcoinContractJSON || !bitcoinContractOfferDetails) return;
|
||||
|
||||
await handleAccept(bitcoinContractJSON, bitcoinContractOfferDetails.counterpartyWalletDetails);
|
||||
setProcessing(true);
|
||||
await handleAccept(
|
||||
bitcoinContractJSON,
|
||||
bitcoinContractOfferDetails.counterpartyWalletDetails,
|
||||
attestorURLs
|
||||
);
|
||||
setProcessing(false);
|
||||
};
|
||||
|
||||
const handleRejectClick = async () => {
|
||||
if (!bitcoinContractOfferDetails) return;
|
||||
|
||||
await handleReject(bitcoinContractOfferDetails.simplifiedBitcoinContract.bitcoinContractId);
|
||||
handleReject();
|
||||
};
|
||||
|
||||
useOnMount(() => {
|
||||
const bitcoinContractOfferJSON = initialSearchParams.get('bitcoinContractOffer');
|
||||
const counterpartyWalletURL = initialSearchParams.get('counterpartyWalletURL');
|
||||
const counterpartyWalletName = initialSearchParams.get('counterpartyWalletName');
|
||||
const counterpartyWalletIcon = initialSearchParams.get('counterpartyWalletIcon');
|
||||
const counterpartyWalletDetailsJSON = initialSearchParams.get('counterpartyWalletDetails');
|
||||
const attestorURLs = initialSearchParams.get('attestorURLs');
|
||||
|
||||
const bitcoinAccountDetails = getNativeSegwitSigner?.(0);
|
||||
|
||||
if (!bitcoinAccountDetails) return;
|
||||
|
||||
const currentBitcoinNetwork = bitcoinAccountDetails.network;
|
||||
|
||||
if (currentBitcoinNetwork !== 'testnet') {
|
||||
navigate(RouteUrls.BitcoinContractLockError, {
|
||||
state: {
|
||||
error: new Error('Invalid Network'),
|
||||
title: "Network doesn't support Bitcoin Contracts",
|
||||
body: "The wallet's current selected network doesn't support Bitcoin Contracts",
|
||||
},
|
||||
});
|
||||
sendRpcResponse(BitcoinContractResponseStatus.NETWORK_ERROR);
|
||||
}
|
||||
|
||||
if (
|
||||
!getNativeSegwitSigner ||
|
||||
!bitcoinContractOfferJSON ||
|
||||
!counterpartyWalletURL ||
|
||||
!counterpartyWalletName ||
|
||||
!counterpartyWalletIcon
|
||||
!counterpartyWalletDetailsJSON ||
|
||||
!attestorURLs
|
||||
)
|
||||
return;
|
||||
|
||||
const currentBitcoinContractOfferDetails = handleOffer(
|
||||
bitcoinContractOfferJSON,
|
||||
counterpartyWalletURL,
|
||||
counterpartyWalletName,
|
||||
counterpartyWalletIcon
|
||||
counterpartyWalletDetailsJSON
|
||||
);
|
||||
|
||||
const currentAddress = getNativeSegwitSigner(0).address;
|
||||
@@ -63,6 +87,7 @@ export function BitcoinContractRequest() {
|
||||
setBitcoinContractJSON(bitcoinContractOfferJSON);
|
||||
setBitcoinContractOfferDetails(currentBitcoinContractOfferDetails);
|
||||
setBitcoinAddress(currentAddress);
|
||||
setAttestorURLs(JSON.parse(attestorURLs));
|
||||
setLoading(false);
|
||||
});
|
||||
|
||||
@@ -82,7 +107,7 @@ export function BitcoinContractRequest() {
|
||||
appName={bitcoinContractOfferDetails.counterpartyWalletDetails.counterpartyWalletName}
|
||||
/>
|
||||
<BitcoinContractRequestActions
|
||||
isLoading={isLoading}
|
||||
isLoading={isProcessing}
|
||||
bitcoinAddress={bitcoinAddress}
|
||||
requiredAmount={
|
||||
bitcoinContractOfferDetails.simplifiedBitcoinContract.bitcoinContractCollateralAmount
|
||||
|
||||
@@ -2,11 +2,17 @@ export async function sendAcceptedBitcoinContractOfferToProtocolWallet(
|
||||
acceptedBitcoinContractOffer: string,
|
||||
counterpartyWalletURL: string
|
||||
) {
|
||||
return fetch(`${counterpartyWalletURL}/offer/accept`, {
|
||||
const response = await fetch(`${counterpartyWalletURL}/offer/accept`, {
|
||||
method: 'put',
|
||||
body: JSON.stringify({
|
||||
acceptMessage: acceptedBitcoinContractOffer,
|
||||
}),
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
}).then(res => res.json());
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('The counterparty was unable to process the Bitcoin Contract.');
|
||||
}
|
||||
|
||||
return response.json();
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { RpcErrorCode } from '@btckit/types';
|
||||
|
||||
import { RouteUrls } from '@shared/route-urls';
|
||||
import { BitcoinContractRequest } from '@shared/rpc/methods/accept-bitcoin-contract';
|
||||
import { BitcoinContractResponseStatus } from '@shared/rpc/methods/accept-bitcoin-contract';
|
||||
import { makeRpcErrorResponse } from '@shared/rpc/rpc-methods';
|
||||
|
||||
import {
|
||||
@@ -16,12 +17,20 @@ export async function rpcAcceptBitcoinContractOffer(
|
||||
message: BitcoinContractRequest,
|
||||
port: chrome.runtime.Port
|
||||
) {
|
||||
if (!message.params) {
|
||||
if (
|
||||
!message.params ||
|
||||
!message.params.bitcoinContractOffer ||
|
||||
!message.params.attestorURLs ||
|
||||
!message.params.counterpartyWalletDetails
|
||||
) {
|
||||
chrome.tabs.sendMessage(
|
||||
getTabIdFromPort(port),
|
||||
makeRpcErrorResponse('acceptBitcoinContractOffer', {
|
||||
id: message.id,
|
||||
error: { code: RpcErrorCode.INVALID_PARAMS, message: 'Invalid parameters' },
|
||||
error: {
|
||||
code: RpcErrorCode.INVALID_PARAMS,
|
||||
message: 'The provided parameters are not valid.',
|
||||
},
|
||||
})
|
||||
);
|
||||
return;
|
||||
@@ -29,26 +38,11 @@ export async function rpcAcceptBitcoinContractOffer(
|
||||
|
||||
const params: RequestParams = [
|
||||
['bitcoinContractOffer', message.params.bitcoinContractOffer],
|
||||
['counterpartyWalletURL', message.params.counterpartyWalletURL],
|
||||
['counterpartyWalletName', message.params.counterpartyWalletName],
|
||||
['counterpartyWalletIcon', message.params.counterpartyWalletIcon],
|
||||
['attestorURLs', message.params.attestorURLs],
|
||||
['counterpartyWalletDetails', message.params.counterpartyWalletDetails],
|
||||
['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(
|
||||
@@ -63,7 +57,7 @@ export async function rpcAcceptBitcoinContractOffer(
|
||||
id: message.id,
|
||||
error: {
|
||||
code: RpcErrorCode.USER_REJECTION,
|
||||
message: 'User rejected the offer',
|
||||
message: BitcoinContractResponseStatus.REJECTED,
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -3,16 +3,21 @@ import { AllowAdditionalProperties } from '@btckit/types/dist/types/utils';
|
||||
|
||||
interface BitcoinContractResponseParams extends AllowAdditionalProperties {
|
||||
bitcoinContractOffer: string;
|
||||
counterpartyWalletURL: string;
|
||||
counterpartyWalletName: string;
|
||||
counterpartyWalletIcon: string;
|
||||
attestorURLs: string;
|
||||
counterpartyWalletDetails: string;
|
||||
}
|
||||
|
||||
interface BitcoinContractResponseBody extends AllowAdditionalProperties {
|
||||
contractId: string;
|
||||
action: string;
|
||||
contractId?: string;
|
||||
txId?: string;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export enum BitcoinContractResponseStatus {
|
||||
SUCCESS = 'Accepting Bitcoin Contract offer was successful',
|
||||
BROADCAST_ERROR = 'There was an error while broadcasting the Bitcoin Contract transaction',
|
||||
INTERFACE_ERROR = 'There was an error while interacting with the Bitcoin Contract interface',
|
||||
NETWORK_ERROR = "The wallet's current selected network is not supported",
|
||||
REJECTED = 'Bitcoin Contract offer was rejected',
|
||||
}
|
||||
|
||||
export type BitcoinContractRequest = RpcRequest<
|
||||
|
||||
@@ -9763,10 +9763,10 @@ dir-glob@^3.0.1:
|
||||
dependencies:
|
||||
path-type "^4.0.0"
|
||||
|
||||
dlc-wasm-wallet@0.4.3:
|
||||
version "0.4.3"
|
||||
resolved "https://registry.yarnpkg.com/dlc-wasm-wallet/-/dlc-wasm-wallet-0.4.3.tgz#1070e3a79edb2330ef912a0e182741b37ce49615"
|
||||
integrity sha512-y8eZu1thQKYgU0EFYTqY0YswYJDenqR4tjMaBymp6vh//YTtsXSyK1LLRSz3oSL60Y1ZwBD9JBWDm1JnQigxlg==
|
||||
dlc-wasm-wallet@0.4.7:
|
||||
version "0.4.7"
|
||||
resolved "https://registry.yarnpkg.com/dlc-wasm-wallet/-/dlc-wasm-wallet-0.4.7.tgz#5c726ab83bf549d09e4ae8c7827ca63a9cff5060"
|
||||
integrity sha512-Q5ZW3jknqFf99/pNFPE/ETK/Kvoo3M98mACkZJ+s8XVQ4rxRjmNw+9m9HumItdGDLGBNFzCsew1joNrwfkl9Ow==
|
||||
|
||||
dlv@^1.1.3:
|
||||
version "1.1.3"
|
||||
|
||||
Reference in New Issue
Block a user