diff --git a/package-lock.json b/package-lock.json
index d322bcb6..0242ab56 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -36,6 +36,7 @@
"prettier": "^3.0.3",
"react": "^18.2.0",
"react-dom": "^18.2.0",
+ "react-query": "^3.39.3",
"react-redux": "^8.1.3",
"react-youtube": "^10.1.0",
"redux": "^4.2.1",
@@ -470,8 +471,9 @@
}
},
"node_modules/@babel/runtime": {
- "version": "7.23.4",
- "license": "MIT",
+ "version": "7.24.1",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.1.tgz",
+ "integrity": "sha512-+BIznRzyqBf+2wCTxcKE3wDjfGeCoVE61KSHGpkzqrLi8qxqFwBeUFyId2cxkTmm55fzDGnm0+yCxaxygrLUnQ==",
"dependencies": {
"regenerator-runtime": "^0.14.0"
},
@@ -3515,6 +3517,14 @@
"version": "1.1.4",
"license": "MIT"
},
+ "node_modules/big-integer": {
+ "version": "1.6.52",
+ "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz",
+ "integrity": "sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==",
+ "engines": {
+ "node": ">=0.6"
+ }
+ },
"node_modules/bip174": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/bip174/-/bip174-2.1.1.tgz",
@@ -3566,6 +3576,21 @@
"node": ">=8"
}
},
+ "node_modules/broadcast-channel": {
+ "version": "3.7.0",
+ "resolved": "https://registry.npmjs.org/broadcast-channel/-/broadcast-channel-3.7.0.tgz",
+ "integrity": "sha512-cIAKJXAxGJceNZGTZSBzMxzyOn72cVgPnKx4dc6LRjQgbaJUQqhy5rzL3zbMxkMWsGKkv2hSFkPRMEXfoMZ2Mg==",
+ "dependencies": {
+ "@babel/runtime": "^7.7.2",
+ "detect-node": "^2.1.0",
+ "js-sha3": "0.8.0",
+ "microseconds": "0.2.0",
+ "nano-time": "1.0.0",
+ "oblivious-set": "1.0.0",
+ "rimraf": "3.0.2",
+ "unload": "2.2.0"
+ }
+ },
"node_modules/brorand": {
"version": "1.1.0",
"license": "MIT"
@@ -3920,6 +3945,11 @@
"node": ">=6"
}
},
+ "node_modules/detect-node": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz",
+ "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g=="
+ },
"node_modules/detect-node-es": {
"version": "1.1.0",
"license": "MIT"
@@ -5559,6 +5589,15 @@
"node": ">=12"
}
},
+ "node_modules/match-sorter": {
+ "version": "6.3.4",
+ "resolved": "https://registry.npmjs.org/match-sorter/-/match-sorter-6.3.4.tgz",
+ "integrity": "sha512-jfZW7cWS5y/1xswZo8VBOdudUiSd9nifYRWphc9M5D/ee4w4AoXLgBEdRbgVaxbMuagBPeUC5y2Hi8DO6o9aDg==",
+ "dependencies": {
+ "@babel/runtime": "^7.23.8",
+ "remove-accents": "0.5.0"
+ }
+ },
"node_modules/merge2": {
"version": "1.4.1",
"license": "MIT",
@@ -5588,6 +5627,11 @@
"node": ">=8.6"
}
},
+ "node_modules/microseconds": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/microseconds/-/microseconds-0.2.0.tgz",
+ "integrity": "sha512-n7DHHMjR1avBbSpsTBj6fmMGh2AGrifVV4e+WYc3Q9lO+xnSZ3NyhcBND3vzzatt05LFhoKFRxrIyklmLlUtyA=="
+ },
"node_modules/minimalistic-assert": {
"version": "1.0.1",
"license": "ISC"
@@ -5627,6 +5671,14 @@
"version": "2.1.2",
"license": "MIT"
},
+ "node_modules/nano-time": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/nano-time/-/nano-time-1.0.0.tgz",
+ "integrity": "sha512-flnngywOoQ0lLQOTRNexn2gGSNuM9bKj9RZAWSzhQ+UJYaAFG9bac4DW9VHjUAzrOaIcajHybCTHe/bkvozQqA==",
+ "dependencies": {
+ "big-integer": "^1.6.16"
+ }
+ },
"node_modules/nanoid": {
"version": "3.3.7",
"funding": [
@@ -5752,6 +5804,11 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/oblivious-set": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/oblivious-set/-/oblivious-set-1.0.0.tgz",
+ "integrity": "sha512-z+pI07qxo4c2CulUHCDf9lcqDlMSo72N/4rLUpRXf6fu+q8vjt8y0xS+Tlf8NTJDdTXHbdeO1n3MlbctwEoXZw=="
+ },
"node_modules/once": {
"version": "1.4.0",
"license": "ISC",
@@ -6121,6 +6178,31 @@
"version": "16.13.1",
"license": "MIT"
},
+ "node_modules/react-query": {
+ "version": "3.39.3",
+ "resolved": "https://registry.npmjs.org/react-query/-/react-query-3.39.3.tgz",
+ "integrity": "sha512-nLfLz7GiohKTJDuT4us4X3h/8unOh+00MLb2yJoGTPjxKs2bc1iDhkNx2bd5MKklXnOD3NrVZ+J2UXujA5In4g==",
+ "dependencies": {
+ "@babel/runtime": "^7.5.5",
+ "broadcast-channel": "^3.4.1",
+ "match-sorter": "^6.0.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
+ },
+ "peerDependenciesMeta": {
+ "react-dom": {
+ "optional": true
+ },
+ "react-native": {
+ "optional": true
+ }
+ }
+ },
"node_modules/react-redux": {
"version": "8.1.3",
"license": "MIT",
@@ -6345,6 +6427,11 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/remove-accents": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/remove-accents/-/remove-accents-0.5.0.tgz",
+ "integrity": "sha512-8g3/Otx1eJaVD12e31UbJj1YzdtVvzH85HV7t+9MJYk/u3XmkOUJ5Ys9wQrf9PCPK8+xn4ymzqYCiZl6QWKn+A=="
+ },
"node_modules/require-directory": {
"version": "2.1.1",
"license": "MIT",
@@ -6984,6 +7071,15 @@
"version": "5.26.5",
"license": "MIT"
},
+ "node_modules/unload": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/unload/-/unload-2.2.0.tgz",
+ "integrity": "sha512-B60uB5TNBLtN6/LsgAf3udH9saB5p7gqJwcFfbOEZ8BcBHnGwCf6G/TGiEqkRAxX7zAFIUtzdrXQSdL3Q/wqNA==",
+ "dependencies": {
+ "@babel/runtime": "^7.6.2",
+ "detect-node": "^2.0.4"
+ }
+ },
"node_modules/update-browserslist-db": {
"version": "1.0.13",
"dev": true,
diff --git a/package.json b/package.json
index 605dc358..15c8b6cd 100644
--- a/package.json
+++ b/package.json
@@ -46,6 +46,7 @@
"prettier": "^3.0.3",
"react": "^18.2.0",
"react-dom": "^18.2.0",
+ "react-query": "^3.39.3",
"react-redux": "^8.1.3",
"react-youtube": "^10.1.0",
"redux": "^4.2.1",
diff --git a/src/app/app.tsx b/src/app/app.tsx
index 79583d98..aebf098f 100644
--- a/src/app/app.tsx
+++ b/src/app/app.tsx
@@ -1,9 +1,11 @@
+import { QueryClient, QueryClientProvider } from 'react-query';
import { Route } from 'react-router-dom';
import { AppLayout } from '@components/app.layout';
import { MyVaults } from '@pages/my-vaults/my-vaults';
import { ProofOfReservePage } from '@pages/proof-of-reserve/proof-of-reserve-page';
import { BalanceContextProvider } from '@providers/balance-context-provider';
+import { BlockchainHeightContextProvider } from '@providers/bitcoin-query-provider';
import { EthereumObserverProvider } from '@providers/ethereum-observer-provider';
import { About } from './pages/about/about';
@@ -11,20 +13,26 @@ import { Dashboard } from './pages/dashboard/dashboard';
import { EthereumContextProvider } from './providers/ethereum-context-provider';
import { VaultContextProvider } from './providers/vault-context-provider';
+const queryClient = new QueryClient();
+
export function App(): React.JSX.Element {
return (
-
-
-
- } />
- } />
- } />
- } />
-
-
-
+
+
+
+
+
+ } />
+ } />
+ } />
+ } />
+
+
+
+
+
);
diff --git a/src/app/components/mint-unmint/mint-unmint.tsx b/src/app/components/mint-unmint/mint-unmint.tsx
index dfe95e35..682e4441 100644
--- a/src/app/components/mint-unmint/mint-unmint.tsx
+++ b/src/app/components/mint-unmint/mint-unmint.tsx
@@ -25,6 +25,7 @@ export function MintUnmint({ address }: MintUnmintContainerProps): React.JSX.Ele
setTimeout(() => {
setAnimate(false);
}, 1000);
+ // eslint-disable-next-line react-hooks/exhaustive-deps
}, [mintStep, unmintStep]);
function handleTabsChange(index: number) {
diff --git a/src/app/components/proof-of-reserve/proof-of-reserve.tsx b/src/app/components/proof-of-reserve/proof-of-reserve.tsx
index ae6151b9..f481306c 100644
--- a/src/app/components/proof-of-reserve/proof-of-reserve.tsx
+++ b/src/app/components/proof-of-reserve/proof-of-reserve.tsx
@@ -14,6 +14,7 @@ export function ProofOfReserve(): React.JSX.Element {
setContent(data);
})
.catch(error => {
+ // eslint-disable-next-line no-console
console.error('Error fetching data:', error);
});
}, []);
diff --git a/src/app/hooks/use-blockchain-height-query.ts b/src/app/hooks/use-blockchain-height-query.ts
new file mode 100644
index 00000000..a122a936
--- /dev/null
+++ b/src/app/hooks/use-blockchain-height-query.ts
@@ -0,0 +1,18 @@
+import { useQuery } from 'react-query';
+
+export function useBlockchainHeightQuery(): number | undefined {
+ const bitcoinBlockchainAPIURL = import.meta.env.VITE_BITCOIN_BLOCKCHAIN_API_URL;
+ const bitcoinExplorerHeightURL = `${bitcoinBlockchainAPIURL}/blocks/tip/height`;
+
+ async function getBlockchainHeight() {
+ const response = await fetch(bitcoinExplorerHeightURL);
+ if (!response.ok) throw new Error('Network response was not ok');
+ return response.json();
+ }
+
+ const { data: blockHeight } = useQuery('blockHeight', () => getBlockchainHeight(), {
+ refetchInterval: 10000,
+ });
+
+ return blockHeight;
+}
diff --git a/src/app/hooks/use-confirmation-checker.ts b/src/app/hooks/use-confirmation-checker.ts
index cbdb513d..f3e37c35 100644
--- a/src/app/hooks/use-confirmation-checker.ts
+++ b/src/app/hooks/use-confirmation-checker.ts
@@ -1,63 +1,69 @@
-import { useEffect, useMemo, useRef, useState } from 'react';
+import { useContext, useEffect, useState } from 'react';
+import { useQuery } from 'react-query';
import { Vault, VaultState } from '@models/vault';
+import { BlockchainHeightContext } from '@providers/bitcoin-query-provider';
export function useConfirmationChecker(vault?: Vault): number {
- const txID = vault?.state === VaultState.FUNDING ? vault?.fundingTX : vault?.closingTX;
+ const { blockHeight } = useContext(BlockchainHeightContext);
+
+ let txID;
+ switch (vault?.state) {
+ case VaultState.FUNDING:
+ txID = vault?.fundingTX;
+ break;
+ case VaultState.CLOSED:
+ txID = vault?.closingTX;
+ break;
+ default:
+ txID = undefined;
+ }
+
+ const [blockHeightAtBroadcast, setBlockHeightAtBroadcast] = useState(
+ undefined
+ );
+ const [transactionProgress, setTransactionProgress] = useState(0);
const bitcoinBlockchainAPIURL = import.meta.env.VITE_BITCOIN_BLOCKCHAIN_API_URL;
-
const bitcoinExplorerTXURL = `${bitcoinBlockchainAPIURL}/tx/${txID}`;
- const bitcoinExplorerHeightURL = `${bitcoinBlockchainAPIURL}/blocks/tip/height`;
- const fetchInterval = useRef(undefined);
-
- const [transactionProgress, setTransactionProgress] = useState(0);
- const memoizedTransactionProgress = useMemo(() => transactionProgress, [transactionProgress]);
-
- const fetchTransactionDetails = async () => {
- if (!txID || (vault?.state && ![VaultState.FUNDING, VaultState.CLOSED].includes(vault.state))) {
- clearInterval(fetchInterval.current);
- return;
+ async function fetchTransactionDetails() {
+ const response = await fetch(bitcoinExplorerTXURL);
+ if (!response.ok) throw new Error('Network response was not ok');
+ const transactionDetails = await response.json();
+ return transactionDetails.status.block_height;
+ }
+
+ const { data: txBlockHeightAtBroadcast } = useQuery(
+ ['transactionDetails', txID],
+ () => fetchTransactionDetails(),
+ {
+ enabled:
+ !!txID &&
+ (vault?.state === VaultState.FUNDING || vault?.state === VaultState.CLOSED) &&
+ !blockHeightAtBroadcast,
+ refetchInterval: 10000,
}
+ );
- let bitcoinCurrentBlockHeight;
- try {
- const response = await fetch(bitcoinExplorerHeightURL, {
- headers: { Accept: 'application/json' },
- });
- bitcoinCurrentBlockHeight = await response.json();
- } catch (error) {
- console.error(error);
- }
-
- let bitcoinTransactionBlockHeight;
-
- try {
- const response = await fetch(bitcoinExplorerTXURL, {
- headers: { Accept: 'application/json' },
- });
- const bitcoinTransactionDetails = await response.json();
- bitcoinTransactionBlockHeight = bitcoinTransactionDetails.status.block_height;
- } catch (error) {
- console.error(error);
+ useEffect(() => {
+ if (txBlockHeightAtBroadcast && typeof txBlockHeightAtBroadcast === 'number') {
+ setBlockHeightAtBroadcast(txBlockHeightAtBroadcast);
}
+ }, [txBlockHeightAtBroadcast]);
- const difference = bitcoinCurrentBlockHeight - bitcoinTransactionBlockHeight;
-
- setTransactionProgress(difference > 0 ? difference : 0);
+ useEffect(() => {
+ if (
+ (vault?.state != VaultState.FUNDING && vault?.state != VaultState.CLOSED) ||
+ transactionProgress > 6
+ )
+ return;
- if (difference > 6) {
- clearInterval(fetchInterval.current);
+ const blockHeightDifference = (blockHeight as number) - (blockHeightAtBroadcast as number);
+ if (typeof blockHeightDifference === 'number') {
+ setTransactionProgress(blockHeightDifference);
}
- };
-
- fetchTransactionDetails();
-
- useEffect(() => {
- fetchInterval.current = setInterval(fetchTransactionDetails, 10000) as unknown as number; // Cleanup the interval when the component unmounts
- return () => clearInterval(fetchInterval.current);
- }, [vault?.state, txID]);
+ }, [blockHeightAtBroadcast, blockHeight, vault?.state, transactionProgress]);
- return memoizedTransactionProgress;
+ return transactionProgress;
}
diff --git a/src/app/hooks/use-ethereum-contracts.ts b/src/app/hooks/use-ethereum-contracts.ts
index 41ad3ea5..69336d44 100644
--- a/src/app/hooks/use-ethereum-contracts.ts
+++ b/src/app/hooks/use-ethereum-contracts.ts
@@ -76,6 +76,7 @@ export function useEthereumContracts(): UseEthereumContractsReturnType {
const contractVersion = import.meta.env.VITE_ETHEREUM_DEPLOYMENT_VERSION;
const deploymentPlanURL = `https://raw.githubusercontent.com/DLC-link/dlc-solidity/${branchName}/deploymentFiles/${ethereumNetwork.name.toLowerCase()}/v${contractVersion}/${contractName}.json`;
+ // eslint-disable-next-line no-console
console.log(
`Fetching deployment info for ${contractName} on ${ethereumNetwork.name} from dlc-solidity/${branchName}`
);
diff --git a/src/app/hooks/use-ethereum-observer.ts b/src/app/hooks/use-ethereum-observer.ts
index 8899abf3..61a9afc5 100644
--- a/src/app/hooks/use-ethereum-observer.ts
+++ b/src/app/hooks/use-ethereum-observer.ts
@@ -92,5 +92,6 @@ export function useEthereumObserver(): void {
);
});
});
+ // eslint-disable-next-line react-hooks/exhaustive-deps
}, [protocolContract, dlcBTCContract, network]);
}
diff --git a/src/app/hooks/use-ethereum.ts b/src/app/hooks/use-ethereum.ts
index df3efb6d..42d83a82 100644
--- a/src/app/hooks/use-ethereum.ts
+++ b/src/app/hooks/use-ethereum.ts
@@ -24,6 +24,7 @@ interface UseEthereumReturnType {
closeVault: (vaultUUID: string) => Promise;
}
+// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
export function throwEthereumError(message: string, error: any): void {
if (error.code === Logger.errors.CALL_EXCEPTION) {
throw new EthereumError(
diff --git a/src/app/hooks/use-vaults.ts b/src/app/hooks/use-vaults.ts
index c98fac06..0344cad9 100644
--- a/src/app/hooks/use-vaults.ts
+++ b/src/app/hooks/use-vaults.ts
@@ -35,7 +35,9 @@ export function useVaults(): UseVaultsReturnType {
};
useEffect(() => {
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
fetchVaultsIfReady();
+ // eslint-disable-next-line react-hooks/exhaustive-deps
}, [contractsLoaded]);
const allVaults = useMemo(
diff --git a/src/app/providers/bitcoin-query-provider.tsx b/src/app/providers/bitcoin-query-provider.tsx
new file mode 100644
index 00000000..97a56fa4
--- /dev/null
+++ b/src/app/providers/bitcoin-query-provider.tsx
@@ -0,0 +1,22 @@
+import { createContext } from 'react';
+
+import { useBlockchainHeightQuery } from '@hooks/use-blockchain-height-query';
+import { HasChildren } from '@models/has-children';
+
+interface BlockchainHeightContextProviderType {
+ blockHeight: number | undefined;
+}
+
+export const BlockchainHeightContext = createContext({
+ blockHeight: undefined,
+});
+
+export function BlockchainHeightContextProvider({ children }: HasChildren): React.JSX.Element {
+ const blockHeight = useBlockchainHeightQuery();
+
+ return (
+
+ {children}
+
+ );
+}
diff --git a/src/app/providers/ethereum-observer-provider.tsx b/src/app/providers/ethereum-observer-provider.tsx
index 33ea511a..3308b552 100644
--- a/src/app/providers/ethereum-observer-provider.tsx
+++ b/src/app/providers/ethereum-observer-provider.tsx
@@ -4,6 +4,5 @@ import { HasChildren } from '@models/has-children';
export function EthereumObserverProvider({ children }: HasChildren): React.JSX.Element {
useEthereumObserver();
-
return {children};
}