Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Validate the calculated Bitcoin deposit address and track it in Sentry #257

Merged
merged 14 commits into from
Feb 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions dapp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@
"@ledgerhq/wallet-api-client": "^1.5.0",
"@ledgerhq/wallet-api-client-react": "^1.3.0",
"@sentry/react": "^7.98.0",
"@sentry/types": "^7.102.0",
"@tanstack/react-table": "^8.11.3",
"axios": "^1.6.7",
"ethers": "^6.10.0",
"formik": "^2.4.5",
"framer-motion": "^10.16.5",
Expand Down
5 changes: 2 additions & 3 deletions dapp/src/acre-react/hooks/useStakeFlow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ export type UseStakeFlowReturn = {
referral: number,
) => Promise<void>
btcAddress?: string
depositReceipt?: DepositReceipt
signMessage: () => Promise<void>
stake: () => Promise<void>
depositReceipt?: DepositReceipt
}

export function useStakeFlow(): UseStakeFlowReturn {
Expand All @@ -25,7 +25,6 @@ export function useStakeFlow(): UseStakeFlowReturn {
undefined,
)
const [btcAddress, setBtcAddress] = useState<string | undefined>(undefined)

const [depositReceipt, setDepositReceipt] = useState<
DepositReceipt | undefined
>(undefined)
Expand Down Expand Up @@ -71,8 +70,8 @@ export function useStakeFlow(): UseStakeFlowReturn {
return {
initStake,
btcAddress,
depositReceipt,
signMessage,
stake,
depositReceipt,
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import React, { useCallback } from "react"
import {
useDepositBTCTransaction,
useDepositTelemetry,
useExecuteFunction,
useModalFlowContext,
useSendGeneratedDepositToSentry,
useStakeFlowContext,
useTransactionContext,
useWalletContext,
} from "#/hooks"
import Alert from "#/components/shared/Alert"
import { TextMd } from "#/components/shared/Typography"
Expand All @@ -14,10 +15,11 @@ import { PROCESS_STATUSES } from "#/types"
import StakingStepsModalContent from "./StakingStepsModalContent"

export default function DepositBTCModal() {
const { ethAccount } = useWalletContext()
const { tokenAmount } = useTransactionContext()
const { setStatus } = useModalFlowContext()
const { btcAddress, stake } = useStakeFlowContext()
useSendGeneratedDepositToSentry()
const { btcAddress, depositReceipt, stake } = useStakeFlowContext()
const depositTelemetry = useDepositTelemetry()

const onStakeBTCSuccess = useCallback(() => {
setStatus(PROCESS_STATUSES.SUCCEEDED)
Expand Down Expand Up @@ -47,17 +49,38 @@ export default function DepositBTCModal() {
const { sendBitcoinTransaction } =
useDepositBTCTransaction(onDepositBTCSuccess)

const handledDepositBTC = useCallback(() => {
if (!tokenAmount?.amount || !btcAddress) return
const handledDepositBTC = useCallback(async () => {
if (!tokenAmount?.amount || !btcAddress || !depositReceipt || !ethAccount)
return

const response = await depositTelemetry(
depositReceipt,
btcAddress,
ethAccount.address,
)

// TODO: Display the correct message for the user
if (response.verificationStatus !== "valid") return

asyncWrapper(sendBitcoinTransaction(tokenAmount?.amount, btcAddress))
}, [btcAddress, sendBitcoinTransaction, tokenAmount])
}, [
btcAddress,
depositReceipt,
depositTelemetry,
ethAccount,
sendBitcoinTransaction,
tokenAmount?.amount,
])

const handledDepositBTCWrapper = useCallback(() => {
asyncWrapper(handledDepositBTC())
}, [handledDepositBTC])

return (
<StakingStepsModalContent
buttonText="Deposit BTC"
activeStep={1}
onClick={handledDepositBTC}
onClick={handledDepositBTCWrapper}
>
<Alert>
<TextMd>
Expand Down
7 changes: 6 additions & 1 deletion dapp/src/constants/chains.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Chain } from "#/types"
import { EthereumNetwork } from "@acre-btc/sdk"
import { EthereumNetwork, BitcoinNetwork } from "@acre-btc/sdk"

export const BLOCK_EXPLORER: Record<Chain, { title: string; url: string }> = {
ethereum: { title: "Etherscan", url: "https://etherscan.io" },
Expand All @@ -8,3 +8,8 @@ export const BLOCK_EXPLORER: Record<Chain, { title: string; url: string }> = {

export const ETHEREUM_NETWORK: EthereumNetwork =
import.meta.env.VITE_USE_TESTNET === "true" ? "sepolia" : "mainnet"

export const BITCOIN_NETWORK: BitcoinNetwork =
import.meta.env.VITE_USE_TESTNET === "true"
? BitcoinNetwork.Testnet
: BitcoinNetwork.Mainnet
2 changes: 1 addition & 1 deletion dapp/src/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@ export * from "./useInitializeAcreSdk"
export * from "./useTransactionHistoryTable"
export * from "./useExecuteFunction"
export * from "./useStakeFlowContext"
export * from "./useSendGeneratedDepositToSentry"
export * from "./useDepositTelemetry"
12 changes: 7 additions & 5 deletions dapp/src/hooks/sentry/useCaptureMessage.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import { useCallback } from "react"
import { captureMessage } from "#/sdk/sentry"
import { Primitive } from "@sentry/types"
import { captureMessage } from "#/sentry"

export const useCaptureMessage = () =>
useCallback(
(
message: string,
params?: { [key: string]: unknown },
tags?: { [key: string]: string },
tags?: { [key: string]: Primitive },
) => {
if (import.meta.env.VITE_SENTRY_SUPPORT) {
captureMessage(message, params, tags)
}
const { VITE_SENTRY_SUPPORT } = import.meta.env

if (VITE_SENTRY_SUPPORT === "false") return
captureMessage(message, params, tags)
},
[],
)
4 changes: 2 additions & 2 deletions dapp/src/hooks/sentry/useSentry.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { initializeSentry } from "#/sdk/sentry"
import { initializeSentry } from "#/sentry"
import { useEffect } from "react"

export const useSentry = () => {
useEffect(() => {
const { VITE_SENTRY_SUPPORT, VITE_SENTRY_DSN } = import.meta.env

if (VITE_SENTRY_SUPPORT) {
if (VITE_SENTRY_SUPPORT === "true") {
initializeSentry(VITE_SENTRY_DSN)
}
}, [])
Expand Down
55 changes: 55 additions & 0 deletions dapp/src/hooks/useDepositTelemetry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { useCallback } from "react"
import { DepositReceipt } from "@acre-btc/sdk"
import { verifyDepositAddress } from "#/utils"
import { BITCOIN_NETWORK } from "#/constants"
import { useCaptureMessage } from "./sentry"

export function useDepositTelemetry() {
const captureMessage = useCaptureMessage()

return useCallback(
async (
deposit: DepositReceipt,
depositAddress: string,
ethAddress: string,
) => {
const { status, response } = await verifyDepositAddress(
deposit,
depositAddress,
BITCOIN_NETWORK,
)

const {
depositor,
blindingFactor,
walletPublicKeyHash,
refundPublicKeyHash,
refundLocktime,
extraData,
} = deposit

const verificationStatus = { verificationStatus: status }

captureMessage(
`Generated deposit [${depositAddress}]`,
{
depositor: depositor.identifierHex,
blindingFactor: blindingFactor.toString(),
walletPublicKeyHash: walletPublicKeyHash.toString(),
refundPublicKeyHash: refundPublicKeyHash.toString(),
refundLocktime: refundLocktime.toString(),
extraData: extraData?.toString(),
verificationStatus: status,
verificationResponse: response,
},
{
ethAddress,
...verificationStatus,
},
)

return verificationStatus
},
[captureMessage],
)
}
36 changes: 0 additions & 36 deletions dapp/src/hooks/useSendGeneratedDepositToSentry.ts

This file was deleted.

3 changes: 2 additions & 1 deletion dapp/src/sdk/sentry/index.ts → dapp/src/sentry/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as Sentry from "@sentry/react"
import { Primitive } from "@sentry/types"

export const initializeSentry = (dsn: string) => {
Sentry.init({
Expand All @@ -13,7 +14,7 @@ export const initializeSentry = (dsn: string) => {
export const captureMessage = (
message: string,
params?: { [key: string]: unknown },
tags?: { [key: string]: string },
tags?: { [key: string]: Primitive },
) => {
Sentry.withScope((scope) => {
if (params) {
Expand Down
1 change: 1 addition & 0 deletions dapp/src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ export * from "./chain"
export * from "./text"
export * from "./time"
export * from "./async"
export * from "./verifyDepositAddress"
51 changes: 51 additions & 0 deletions dapp/src/utils/verifyDepositAddress.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { ONE_SEC_IN_MILLISECONDS } from "#/constants"
import { BitcoinNetwork, DepositReceipt } from "@acre-btc/sdk"
import axios from "axios"

const endpoint =
"https://us-central1-keep-prd-210b.cloudfunctions.net/verify-deposit-address"

function createURL(deposit: DepositReceipt, network: BitcoinNetwork): string {
const {
depositor,
blindingFactor,
refundPublicKeyHash,
refundLocktime,
extraData,
} = deposit

const jsonType = extraData ? "json-extradata" : "json"
const baseUrl = `${endpoint}/${jsonType}/${network}/latest/${
depositor.identifierHex
}/${blindingFactor.toString()}/${refundPublicKeyHash.toString()}/${refundLocktime.toString()}`

return extraData ? `${baseUrl}/${extraData.toString()}` : baseUrl
}

export async function verifyDepositAddress(
deposit: DepositReceipt,
depositAddress: string,
network: BitcoinNetwork,
): Promise<{
status: "valid" | "invalid" | "error"
response: unknown
}> {
try {
const url = createURL(deposit, network)
const response = await axios.get<{ address: string }>(url, {
timeout: ONE_SEC_IN_MILLISECONDS * 10,
})

const match = response.data.address === depositAddress

return {
status: match ? "valid" : "invalid",
response: response.data,
}
} catch (err) {
return {
status: "error",
response: err,
}
}
}
1 change: 0 additions & 1 deletion dapp/src/vite-env.d.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
/// <reference types="vite/client" />

interface ImportMetaEnv {
readonly VITE_SENTRY_SUPPORT: boolean
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did we remove it? Looks like we use this env variable in useSentry hook.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's right. However, take a look here. Even though we set the type VITE_SENTRY_SUPPORT here, it will return a string. I treated it as a temporary solution for now. We need to check/improve working with env vars when these are of boolean type. I will create a separate task for this.

As shown above, VITE_SOME_KEY is a number but returns a string when parsed. The same would also happen for boolean env variables. Make sure to convert to the desired type when using it in your code.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

readonly VITE_SENTRY_DSN: string
readonly VITE_ETH_HOSTNAME_HTTP: string
readonly VITE_REFERRAL: number
Expand Down
Loading
Loading