feat: added use-bitcoin-contract hook and additional functions and configs

This commit is contained in:
Polybius93
2023-06-27 12:21:18 +02:00
committed by kyranjamie
parent 83cf0491e4
commit 626ef0beeb
6 changed files with 1641 additions and 1951 deletions

3
.gitignore vendored
View File

@@ -21,3 +21,6 @@ trace*
/test-results/
/playwright-report/
/playwright/.cache/
# Local Netlify folder
.netlify

View File

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

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

View File

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

View File

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

3329
yarn.lock

File diff suppressed because it is too large Load Diff