Skip to content

Commit

Permalink
Subgraph integration with dApp (#397)
Browse files Browse the repository at this point in the history
Closes #347

This PR integrates a subgraph with dApp. Subgraph can be run locally or
use a production/development URL. To select the right endpoint, set the
URL for the following environmental variable:

```
VITE_ACRE_SUBGRAPH_URL=<SUBGRAPH_URL>
```

By default, the development URL is set to subgraph.

- If you want to use a local subgraph you need to follow the steps
[here](https://github.com/thesis/acre/blob/main/subgraph/README.md).
Then set the subgraph URL to localhost.
- To find the production URL you need to connect to the [Subgraph
Studio](https://thegraph.com/studio/) with Acre Wallet and open the
dedicated page for the selected subgraph. Copy the URL and use the API
key from the "API Keys" tab.

### Subgraph API

- `fetchActivityDatas` - Returns activities for the Ethereum account
  • Loading branch information
r-czajkowski authored May 9, 2024
2 parents 876a304 + 76d525b commit f4e29b1
Show file tree
Hide file tree
Showing 18 changed files with 161 additions and 1 deletion.
2 changes: 2 additions & 0 deletions dapp/.env
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,5 @@ VITE_REFERRAL=123

# TODO: Pass this value as an environment variable during the build process.
VITE_DEFENDER_RELAYER_WEBHOOK_URL="https://api.defender.openzeppelin.com/actions/a0d6d2e2-ce9c-4619-aa2b-6c874fe97af7/runs/webhook/b1f17c89-8230-46e3-866f-a3213887974c/Sbddsy54cJ6sPg2bLPyuHJ"

VITE_ACRE_SUBGRAPH_URL="https://api.studio.thegraph.com/query/73600/acre/version/latest"
1 change: 1 addition & 0 deletions dapp/src/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export * from "./store"
export * from "./toasts"
export * from "./sdk"
export * from "./subgraph"
export * from "./useDetectThemeMode"
export * from "./useRequestBitcoinAccount"
export * from "./useRequestEthereumAccount"
Expand Down
2 changes: 2 additions & 0 deletions dapp/src/hooks/store/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@ export * from "./useAppSelector"
export * from "./useEstimatedBTCBalance"
export * from "./useSharesBalance"
export * from "./useMinDepositAmount"
// TODO: Rename when the old hook is deleted.
export { useActivities as useActivitiesNEW } from "./useActivities"
6 changes: 6 additions & 0 deletions dapp/src/hooks/store/useActivities.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { selectActivities } from "#/store/wallet"
import { useAppSelector } from "./useAppSelector"

export function useActivities() {
return useAppSelector(selectActivities)
}
2 changes: 2 additions & 0 deletions dapp/src/hooks/subgraph/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "./useInitDataFromSubgraph"
export * from "./useFetchActivities"
24 changes: 24 additions & 0 deletions dapp/src/hooks/subgraph/useFetchActivities.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { useCallback } from "react"
import { subgraphAPI } from "#/utils"
import { setActivities } from "#/store/wallet"
import { useAppDispatch } from "../store/useAppDispatch"
import { useWalletContext } from "../useWalletContext"

// TODO: Use the correct function from SDK
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const calculateEthAddress = (btcAddress: string) =>
"0x4c9e39e5ff458a811708c03aea21b8327118cf13"

export function useFetchActivities() {
const dispatch = useAppDispatch()
const { btcAccount } = useWalletContext()

return useCallback(async () => {
if (!btcAccount) return

const ethAddress = calculateEthAddress(btcAccount.address)
const result = await subgraphAPI.fetchActivityData(ethAddress)

dispatch(setActivities(result))
}, [btcAccount, dispatch])
}
15 changes: 15 additions & 0 deletions dapp/src/hooks/subgraph/useInitDataFromSubgraph.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { useEffect } from "react"
import { logPromiseFailure } from "#/utils"
import { useFetchActivities } from "./useFetchActivities"
import { useWalletContext } from "../useWalletContext"

export function useInitDataFromSubgraph() {
const { btcAccount } = useWalletContext()
const fetchActivities = useFetchActivities()

useEffect(() => {
if (btcAccount) {
logPromiseFailure(fetchActivities())
}
}, [btcAccount, fetchActivities])
}
5 changes: 4 additions & 1 deletion dapp/src/hooks/useInitApp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@ import { useInitDataFromSdk, useInitializeAcreSdk } from "./sdk"
import { useSentry } from "./sentry"
import { useFetchBTCPriceUSD } from "./useFetchBTCPriceUSD"
import { useInitGlobalToasts } from "./toasts/useInitGlobalToasts"
import { useInitDataFromSubgraph } from "./subgraph"

export function useInitApp() {
// TODO: Let's uncomment when dark mode is ready
// useDetectThemeMode()
useSentry()
useInitializeAcreSdk()
useInitDataFromSdk()
useFetchBTCPriceUSD()
useInitGlobalToasts()

useInitDataFromSdk()
useInitDataFromSubgraph()
}
2 changes: 2 additions & 0 deletions dapp/src/store/reducer.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { combineReducers } from "@reduxjs/toolkit"
import { btcSlice } from "./btc/btcSlice"
import { walletSlice } from "./wallet/walletSlice"

export const reducer = combineReducers({
btc: btcSlice.reducer,
wallet: walletSlice.reducer,
})
2 changes: 2 additions & 0 deletions dapp/src/store/wallet/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "./walletSelector"
export * from "./walletSlice"
5 changes: 5 additions & 0 deletions dapp/src/store/wallet/walletSelector.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { Activity } from "#/types"
import { RootState } from ".."

export const selectActivities = (state: RootState): Activity[] =>
state.wallet.activities
22 changes: 22 additions & 0 deletions dapp/src/store/wallet/walletSlice.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Activity } from "#/types"
import { PayloadAction, createSlice } from "@reduxjs/toolkit"

type WalletState = {
activities: Activity[]
}

const initialState: WalletState = {
activities: [],
}

export const walletSlice = createSlice({
name: "wallet",
initialState,
reducers: {
setActivities(state, action: PayloadAction<Activity[]>) {
state.activities = action.payload
},
},
})

export const { setActivities } = walletSlice.actions
7 changes: 7 additions & 0 deletions dapp/src/types/activity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,10 @@ export type ActivityInfo = {
txHash: string
status: ActivityInfoStatus
}

export type Activity = {
txHash: string
amount: bigint
type: "deposit" | "withdraw"
status: "completed" | "pending"
}
1 change: 1 addition & 0 deletions dapp/src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@ export * from "./time"
export * from "./size"
export * from "./toast"
export * from "./fee"
export * from "./subgraphAPI"
6 changes: 6 additions & 0 deletions dapp/src/types/subgraphAPI.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export type ActivityDataResponse = {
id: string
bitcoinTransactionId: string
amountToDeposit: string
events: { type: "Initialized" | "Finalized" }[]
}
1 change: 1 addition & 0 deletions dapp/src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ export * from "./promise"
export * from "./exchangeApi"
export * from "./verifyDepositAddress"
export * from "./json"
export * from "./subgraphAPI"
58 changes: 58 additions & 0 deletions dapp/src/utils/subgraphAPI.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import axios from "axios"
import { Activity, ActivityDataResponse } from "#/types"

const ACRE_SUBGRAPH_URL = import.meta.env.VITE_ACRE_SUBGRAPH_URL

const mapToActivity = (activityData: ActivityDataResponse): Activity => {
const { bitcoinTransactionId: txHash, amountToDeposit, events } = activityData

const status = events.some(({ type }) => type === "Finalized")
? "completed"
: "pending"

return {
txHash,
amount: BigInt(amountToDeposit),
type: "deposit",
status,
}
}

// TODO: Fetch transactions for withdrawals
/**
* Returns the activities for a given account.
* @param account The Ethereum address for which the activities will be fetched.
*/
async function fetchActivityData(account: string): Promise<Activity[]> {
const response = await axios.post<{
errors?: unknown
data: { activityDatas: ActivityDataResponse[] }
}>(ACRE_SUBGRAPH_URL, {
query: `query {
activityDatas(
where: {depositOwner_: {id: "${account}"}}
) {
id
bitcoinTransactionId
events {
type
}
... on Deposit {
amountToDeposit
}
}
}`,
})

if (response.data && response.data.errors) {
const errorMsg = "Failed to fetch data from Acre subgraph API."
console.error(errorMsg, response.data.errors)
throw new Error(errorMsg)
}

return response.data.data.activityDatas.map(mapToActivity)
}

export const subgraphAPI = {
fetchActivityData,
}
1 change: 1 addition & 0 deletions dapp/src/vite-env.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ interface ImportMetaEnv {
readonly VITE_ETH_HOSTNAME_HTTP: string
readonly VITE_REFERRAL: number
readonly VITE_TBTC_API_ENDPOINT: string
readonly VITE_ACRE_SUBGRAPH_URL: string
}

interface ImportMeta {
Expand Down

0 comments on commit f4e29b1

Please sign in to comment.