mirror of
https://github.com/zhigang1992/wallet.git
synced 2026-01-12 22:53:27 +08:00
feat: added use-bitcoin-contract hook and additional functions and configs
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -21,3 +21,6 @@ trace*
|
||||
/test-results/
|
||||
/playwright-report/
|
||||
/playwright/.cache/
|
||||
|
||||
# Local Netlify folder
|
||||
.netlify
|
||||
|
||||
@@ -183,6 +183,7 @@
|
||||
"coinselect": "3.1.13",
|
||||
"compare-versions": "4.1.3",
|
||||
"dayjs": "1.11.8",
|
||||
"dlc-wasm-wallet": "0.4.3",
|
||||
"dompurify": "3.0.4",
|
||||
"downshift": "6.1.7",
|
||||
"ecdsa-sig-formatter": "1.0.11",
|
||||
|
||||
228
src/app/common/hooks/use-bitcoin-contracts.ts
Normal file
228
src/app/common/hooks/use-bitcoin-contracts.ts
Normal file
@@ -0,0 +1,228 @@
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { bytesToHex } from '@stacks/common';
|
||||
import { JsDLCInterface } from 'dlc-wasm-wallet';
|
||||
|
||||
import { BITCOIN_API_BASE_URL_MAINNET, BITCOIN_API_BASE_URL_TESTNET } from '@shared/constants';
|
||||
import { deriveAddressIndexKeychainFromAccount } from '@shared/crypto/bitcoin/bitcoin.utils';
|
||||
import { createMoneyFromDecimal } from '@shared/models/money.model';
|
||||
import { RouteUrls } from '@shared/route-urls';
|
||||
import { makeRpcSuccessResponse } from '@shared/rpc/rpc-methods';
|
||||
|
||||
import { sendAcceptedBitcoinContractOfferToProtocolWallet } from '@app/query/bitcoin/contract/send-accepted-bitcoin-contract-offer';
|
||||
import {
|
||||
useCalculateBitcoinFiatValue,
|
||||
useCryptoCurrencyMarketData,
|
||||
} from '@app/query/common/market-data/market-data.hooks';
|
||||
import { useCurrentAccountIndex } from '@app/store/accounts/account';
|
||||
import {
|
||||
useCurrentAccountNativeSegwitSigner,
|
||||
useNativeSegwitActiveNetworkAccountPrivateKeychain,
|
||||
} from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks';
|
||||
|
||||
import { initialSearchParams } from '../initial-search-params';
|
||||
import { i18nFormatCurrency } from '../money/format-money';
|
||||
import { satToBtc } from '../money/unit-conversion';
|
||||
import { whenBitcoinNetwork } from '../utils';
|
||||
import { useDefaultRequestParams } from './use-default-request-search-params';
|
||||
|
||||
export interface SimplifiedBitcoinContract {
|
||||
bitcoinContractId: string;
|
||||
bitcoinContractCollateralAmount: number;
|
||||
bitcoinContractExpirationDate: string;
|
||||
}
|
||||
|
||||
interface CounterpartyWalletDetails {
|
||||
counterpartyWalletURL: string;
|
||||
counterpartyWalletName: string;
|
||||
counterpartyWalletIcon: string;
|
||||
}
|
||||
|
||||
export interface BitcoinContractOfferDetails {
|
||||
simplifiedBitcoinContract: SimplifiedBitcoinContract;
|
||||
counterpartyWalletDetails: CounterpartyWalletDetails;
|
||||
}
|
||||
|
||||
export function useBitcoinContracts() {
|
||||
const navigate = useNavigate();
|
||||
const defaultParams = useDefaultRequestParams();
|
||||
const bitcoinMarketData = useCryptoCurrencyMarketData('BTC');
|
||||
const calculateFiatValue = useCalculateBitcoinFiatValue();
|
||||
const getNativeSegwitSigner = useCurrentAccountNativeSegwitSigner();
|
||||
const currentIndex = useCurrentAccountIndex();
|
||||
const nativeSegwitPrivateKeychain =
|
||||
useNativeSegwitActiveNetworkAccountPrivateKeychain()?.(currentIndex);
|
||||
|
||||
async function getBitcoinContractInterface(): Promise<JsDLCInterface | undefined> {
|
||||
const bitcoinAccountDetails = getNativeSegwitSigner?.(0);
|
||||
|
||||
if (!nativeSegwitPrivateKeychain || !bitcoinAccountDetails) return;
|
||||
|
||||
const currentBitcoinNetwork = bitcoinAccountDetails.network;
|
||||
const currentAddress = bitcoinAccountDetails.address;
|
||||
const currentAccountIndex = bitcoinAccountDetails.addressIndex;
|
||||
|
||||
const currentAddressPrivateKey = deriveAddressIndexKeychainFromAccount(
|
||||
nativeSegwitPrivateKeychain
|
||||
)(currentAccountIndex).privateKey;
|
||||
|
||||
if (!currentAddressPrivateKey) return;
|
||||
|
||||
const blockchainAPI = whenBitcoinNetwork(currentBitcoinNetwork)({
|
||||
mainnet: BITCOIN_API_BASE_URL_MAINNET,
|
||||
testnet: BITCOIN_API_BASE_URL_TESTNET,
|
||||
regtest: BITCOIN_API_BASE_URL_TESTNET,
|
||||
signet: BITCOIN_API_BASE_URL_TESTNET,
|
||||
});
|
||||
|
||||
const oracleAPI = 'https://testnet.dlc.link/oracle';
|
||||
|
||||
const bitcoinContractInterface = JsDLCInterface.new(
|
||||
bytesToHex(currentAddressPrivateKey),
|
||||
currentAddress,
|
||||
currentBitcoinNetwork,
|
||||
blockchainAPI,
|
||||
oracleAPI
|
||||
);
|
||||
|
||||
return bitcoinContractInterface;
|
||||
}
|
||||
|
||||
function handleOffer(
|
||||
bitcoinContractOfferJSON: string,
|
||||
counterpartyWalletURL: string,
|
||||
counterpartyWalletName: string,
|
||||
counterpartyWalletIcon: string
|
||||
): BitcoinContractOfferDetails {
|
||||
const bitcoinContractOffer = JSON.parse(bitcoinContractOfferJSON);
|
||||
|
||||
const bitcoinContractId = bitcoinContractOffer.temporaryContractId;
|
||||
const bitcoinContractCollateralAmount =
|
||||
bitcoinContractOffer.contractInfo.singleContractInfo.totalCollateral;
|
||||
const bitcoinContractExpirationDate = new Date(
|
||||
bitcoinContractOffer.cetLocktime * 1000
|
||||
).toLocaleDateString();
|
||||
|
||||
const simplifiedBitcoinContractOffer: SimplifiedBitcoinContract = {
|
||||
bitcoinContractId,
|
||||
bitcoinContractCollateralAmount,
|
||||
bitcoinContractExpirationDate,
|
||||
};
|
||||
|
||||
const counterpartyWalletDetails: CounterpartyWalletDetails = {
|
||||
counterpartyWalletURL,
|
||||
counterpartyWalletName,
|
||||
counterpartyWalletIcon,
|
||||
};
|
||||
|
||||
const bitcoinContractOfferDetails: BitcoinContractOfferDetails = {
|
||||
simplifiedBitcoinContract: simplifiedBitcoinContractOffer,
|
||||
counterpartyWalletDetails,
|
||||
};
|
||||
|
||||
return bitcoinContractOfferDetails;
|
||||
}
|
||||
|
||||
async function handleAccept(
|
||||
bitcoinContractJSON: string,
|
||||
counterpartyWalletDetails: CounterpartyWalletDetails
|
||||
) {
|
||||
const bitcoinContractInterface = await getBitcoinContractInterface();
|
||||
|
||||
if (!bitcoinContractInterface) return;
|
||||
|
||||
const bitcoinContractOffer = JSON.parse(bitcoinContractJSON);
|
||||
|
||||
const bitcoinContractId = bitcoinContractOffer.temporaryContractId;
|
||||
const bitcoinContractCollateralAmount =
|
||||
bitcoinContractOffer.contractInfo.singleContractInfo.totalCollateral;
|
||||
|
||||
await bitcoinContractInterface.get_wallet_balance();
|
||||
|
||||
try {
|
||||
const acceptedBitcoinContract = await bitcoinContractInterface.accept_offer(
|
||||
bitcoinContractJSON
|
||||
);
|
||||
|
||||
const signedBitcoinContract = await sendAcceptedBitcoinContractOfferToProtocolWallet(
|
||||
acceptedBitcoinContract,
|
||||
counterpartyWalletDetails.counterpartyWalletURL
|
||||
);
|
||||
|
||||
const txId = await bitcoinContractInterface.countersign_and_broadcast(
|
||||
JSON.stringify(signedBitcoinContract)
|
||||
);
|
||||
|
||||
const { txMoney, txFiatValue, txFiatValueSymbol, txLink, symbol } = getTransactionDetails(
|
||||
txId,
|
||||
bitcoinContractCollateralAmount
|
||||
);
|
||||
|
||||
navigate(RouteUrls.BitcoinContractLockSuccess, {
|
||||
state: {
|
||||
txId,
|
||||
txMoney,
|
||||
txFiatValue,
|
||||
txFiatValueSymbol,
|
||||
symbol,
|
||||
txLink,
|
||||
},
|
||||
});
|
||||
|
||||
sendRpcResponse(bitcoinContractId, txId, 'accept');
|
||||
} catch (error) {
|
||||
navigate(RouteUrls.BitcoinContractLockError, {
|
||||
state: {
|
||||
error,
|
||||
title: 'There was an error with your Bitcoin Contract',
|
||||
body: 'Unable to lock bitcoin',
|
||||
},
|
||||
});
|
||||
sendRpcResponse(bitcoinContractId, '', 'failed');
|
||||
}
|
||||
}
|
||||
|
||||
function handleReject(bitcoinContractId: string) {
|
||||
sendRpcResponse(bitcoinContractId, '', 'reject');
|
||||
close();
|
||||
}
|
||||
|
||||
function getTransactionDetails(txId: string, bitcoinCollateral: number) {
|
||||
const bitcoinValue = satToBtc(bitcoinCollateral);
|
||||
const txMoney = createMoneyFromDecimal(bitcoinValue, 'BTC');
|
||||
const txFiatValue = i18nFormatCurrency(calculateFiatValue(txMoney)).toString();
|
||||
const txFiatValueSymbol = bitcoinMarketData.price.symbol;
|
||||
const txLink = { blockchain: 'bitcoin', txid: txId };
|
||||
|
||||
return {
|
||||
txId,
|
||||
txMoney,
|
||||
txFiatValue,
|
||||
txFiatValueSymbol,
|
||||
symbol: 'BTC',
|
||||
txLink,
|
||||
};
|
||||
}
|
||||
|
||||
function sendRpcResponse(bitcoinContractId: string, txId: string, action: 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,
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
handleOffer,
|
||||
handleAccept,
|
||||
handleReject,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
export async function sendAcceptedBitcoinContractOfferToProtocolWallet(
|
||||
acceptedBitcoinContractOffer: string,
|
||||
counterpartyWalletURL: string
|
||||
) {
|
||||
return fetch(`${counterpartyWalletURL}/offer/accept`, {
|
||||
method: 'put',
|
||||
body: JSON.stringify({
|
||||
acceptMessage: acceptedBitcoinContractOffer,
|
||||
}),
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
}).then(res => res.json());
|
||||
}
|
||||
@@ -117,6 +117,7 @@ const config = {
|
||||
runtimeChunk: false,
|
||||
},
|
||||
module: {
|
||||
noParse: /argon2\.wasm$/,
|
||||
rules: [
|
||||
{
|
||||
test: /\.(ts|tsx)$/,
|
||||
@@ -158,7 +159,7 @@ const config = {
|
||||
],
|
||||
},
|
||||
{
|
||||
test: /\.wasm$/,
|
||||
test: /argon2\.wasm$/,
|
||||
// Tells WebPack that this module should be included as
|
||||
// base64-encoded binary file and not as code
|
||||
loader: 'base64-loader',
|
||||
@@ -168,6 +169,13 @@ const config = {
|
||||
// Error: WebAssembly module is included in initial chunk.
|
||||
type: 'javascript/auto',
|
||||
},
|
||||
{
|
||||
test: /cfddlcjs_wasm\.wasm/,
|
||||
type: 'asset/resource',
|
||||
generator: {
|
||||
filename: '[name].wasm',
|
||||
},
|
||||
},
|
||||
].filter(Boolean),
|
||||
},
|
||||
watch: false,
|
||||
@@ -227,15 +235,16 @@ const config = {
|
||||
new webpack.DefinePlugin({
|
||||
VERSION: JSON.stringify(VERSION),
|
||||
}),
|
||||
|
||||
new webpack.ProvidePlugin({
|
||||
process: 'process/browser.js',
|
||||
process: 'process/browser',
|
||||
Buffer: ['buffer', 'Buffer'],
|
||||
fetch: 'cross-fetch',
|
||||
}),
|
||||
|
||||
new ProgressBarPlugin(),
|
||||
],
|
||||
experiments: {
|
||||
asyncWebAssembly: true,
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = config;
|
||||
@@ -247,4 +256,4 @@ if (IS_PROD) {
|
||||
}
|
||||
if (ANALYZE_BUNDLE) {
|
||||
module.exports.plugins.push(new BundleAnalyzerPlugin());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user