From 4d524c668393f33c7c200a81f75cab87a3114e9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bernard=20Stojanovi=C4=87?= Date: Thu, 24 Oct 2024 17:47:16 +0200 Subject: [PATCH] feat: get bridge history for an address (#68) # Summary Added method to fetch bridge transactions history of an address # Closes: #67 # Description The method should return aggregated data from all bridges that are supported by Sprinter API. Right now, only Sygma is added but later other bridge will be added # Chores: - [ ] Unit tests - [x] TS Docs / Comments --------- Co-authored-by: Saad Ahmed Siddiqui --- packages/react/lib/context.tsx | 52 ++++++++++----- packages/react/lib/hooks.ts | 34 +++++++--- packages/react/src/Action.tsx | 21 ++++-- packages/sdk/src/helpers/getBridgeHistory.ts | 46 ++++++++++++++ packages/sdk/src/helpers/index.ts | 1 + packages/sdk/src/index.ts | 1 + packages/sdk/src/sygma/api.ts | 29 +++++++++ packages/sdk/src/sygma/index.ts | 2 + packages/sdk/src/sygma/types.ts | 67 ++++++++++++++++++++ 9 files changed, 222 insertions(+), 31 deletions(-) create mode 100644 packages/sdk/src/helpers/getBridgeHistory.ts create mode 100644 packages/sdk/src/sygma/api.ts create mode 100644 packages/sdk/src/sygma/index.ts create mode 100644 packages/sdk/src/sygma/types.ts diff --git a/packages/react/lib/context.tsx b/packages/react/lib/context.tsx index 8ae55fc..01f52cc 100644 --- a/packages/react/lib/context.tsx +++ b/packages/react/lib/context.tsx @@ -1,11 +1,14 @@ -import {createContext, ReactNode, useEffect, useState} from "react"; -import {Sprinter} from "@chainsafe/sprinter-sdk"; -import {useTokens} from "./internal/useTokens.ts"; -import {useChains} from "./internal/useChains.ts"; -import {useBalances} from "./internal/useBalances.ts"; -import {useTransfers} from "./internal/useTransfers.ts"; +import { createContext, ReactNode, useEffect, useState } from "react"; +import { Sprinter } from "@chainsafe/sprinter-sdk"; +import { useTokens } from "./internal/useTokens.ts"; +import { useChains } from "./internal/useChains.ts"; +import { useBalances } from "./internal/useBalances.ts"; +import { useTransfers } from "./internal/useTransfers.ts"; -type SprinterContext = ReturnType & ReturnType & ReturnType & ReturnType; +type SprinterContext = ReturnType & + ReturnType & + ReturnType & + ReturnType; export const Context = createContext(null); @@ -42,7 +45,7 @@ interface SprinterContextProps { * ``` */ export function SprinterContext({ children, baseUrl }: SprinterContextProps) { - const [sprinter] = useState(new Sprinter({baseUrl})); + const [sprinter] = useState(new Sprinter({ baseUrl })); /** Balances */ const { balances, getUserBalances } = useBalances(sprinter); @@ -54,7 +57,13 @@ export function SprinterContext({ children, baseUrl }: SprinterContextProps) { const { chains, getAvailableChains } = useChains(sprinter); /** Solutions */ - const { solution, getTransfer, getTransferWithHook, getPoolAssetOnDestination, getPoolAssetOnDestinationWithHook } = useTransfers(sprinter); + const { + solution, + getTransfer, + getTransferWithHook, + getPoolAssetOnDestination, + getPoolAssetOnDestinationWithHook, + } = useTransfers(sprinter); /** Initialization */ useEffect(() => { @@ -62,10 +71,23 @@ export function SprinterContext({ children, baseUrl }: SprinterContextProps) { getAvailableChains(); }, [sprinter]); - return {children}; + return ( + + {children} + + ); } diff --git a/packages/react/lib/hooks.ts b/packages/react/lib/hooks.ts index 9e88b85..04b1c76 100644 --- a/packages/react/lib/hooks.ts +++ b/packages/react/lib/hooks.ts @@ -1,7 +1,7 @@ -import {useCallback, useContext} from "react"; -import {Context} from "./context.tsx"; -import {Address} from "@chainsafe/sprinter-sdk"; -import {BalancesEntry} from "./internal/useBalances.ts"; +import { useCallback, useContext } from "react"; +import { Context } from "./context.tsx"; +import { Address } from "@chainsafe/sprinter-sdk"; +import { BalancesEntry } from "./internal/useBalances.ts"; /** * A hook to access the full Sprinter context, including balances, tokens, chains, and transfer solutions. @@ -20,7 +20,7 @@ import {BalancesEntry} from "./internal/useBalances.ts"; export function useSprinter() { const context = useContext(Context); - if (!context) throw new Error('Sprinter Context is not defined'); + if (!context) throw new Error("Sprinter Context is not defined"); return context; } @@ -77,10 +77,14 @@ const balancesEmptyState = { * ``` */ export function useSprinterBalances(account: Address) { - const { balances: _balances, getUserBalances: _getUserBalances } = useSprinter(); + const { balances: _balances, getUserBalances: _getUserBalances } = + useSprinter(); const balances: BalancesEntry = _balances[account] || balancesEmptyState; - const getUserBalances = useCallback(() => _getUserBalances(account), [account]); + const getUserBalances = useCallback( + () => _getUserBalances(account), + [account] + ); return { balances, getUserBalances }; } @@ -259,6 +263,18 @@ export function useSprinterChains() { * ``` */ export function useSprinterTransfers() { - const { solution, getTransfer, getTransferWithHook, getPoolAssetOnDestination, getPoolAssetOnDestinationWithHook } = useSprinter(); - return { solution, getTransfer, getTransferWithHook, getPoolAssetOnDestination, getPoolAssetOnDestinationWithHook }; + const { + solution, + getTransfer, + getTransferWithHook, + getPoolAssetOnDestination, + getPoolAssetOnDestinationWithHook, + } = useSprinter(); + return { + solution, + getTransfer, + getTransferWithHook, + getPoolAssetOnDestination, + getPoolAssetOnDestinationWithHook, + }; } diff --git a/packages/react/src/Action.tsx b/packages/react/src/Action.tsx index 6054888..d194ff6 100644 --- a/packages/react/src/Action.tsx +++ b/packages/react/src/Action.tsx @@ -1,10 +1,17 @@ -import {useSprinterBalances} from "../lib/hooks.ts"; +import { useSprinterBalances } from "../lib/hooks.ts"; export function Action() { - const hook = useSprinterBalances("0x3E101Ec02e7A48D16DADE204C96bFF842E7E2519"); + const hook = useSprinterBalances( + "0x3E101Ec02e7A48D16DADE204C96bFF842E7E2519" + ); - return () -} \ No newline at end of file + return ( + + ); +} diff --git a/packages/sdk/src/helpers/getBridgeHistory.ts b/packages/sdk/src/helpers/getBridgeHistory.ts new file mode 100644 index 0000000..7d00bbf --- /dev/null +++ b/packages/sdk/src/helpers/getBridgeHistory.ts @@ -0,0 +1,46 @@ +import { Environment } from "../enums"; +import { getTransfers } from "../sygma/api"; +import type { Status, SygmaTransfer } from "../sygma/types"; +import type { Address } from "../types"; + +interface History { + originTx: string; + originName: string; + destinationTx?: string; + destinationName: string; + amount: string; + tokenSymbol: string; + status: Status; +} + +function handleSygmaResponseEntry(entry: SygmaTransfer): History { + return { + originTx: entry.deposit?.txHash || "0x0", + originName: entry.fromDomain.name, + destinationTx: entry.execution?.txHash, + destinationName: entry.toDomain.name, + amount: entry.amount, + tokenSymbol: entry.fee.tokenSymbol, + status: entry.status, + }; +} + +/** + * Returns bridging history + * for an address + * @param {Address} address + * @param {Environment} environment + * @returns {Promise} + */ +export async function getBridgeHistory( + address: Address, + environment: Environment = Environment.MAINNET, +): Promise { + // TODO: add logic for all supported bridges + const transactions = await getTransfers(address, environment).then( + (sygmaTransfers) => + sygmaTransfers.map((transfer) => handleSygmaResponseEntry(transfer)), + ); + + return transactions; +} diff --git a/packages/sdk/src/helpers/index.ts b/packages/sdk/src/helpers/index.ts index 81f0490..73bfd75 100644 --- a/packages/sdk/src/helpers/index.ts +++ b/packages/sdk/src/helpers/index.ts @@ -1 +1,2 @@ export { experimental_getTrackingUrl } from "./getTrackingUrl"; +export { getBridgeHistory } from "./getBridgeHistory"; diff --git a/packages/sdk/src/index.ts b/packages/sdk/src/index.ts index 2dea3a5..50c416a 100644 --- a/packages/sdk/src/index.ts +++ b/packages/sdk/src/index.ts @@ -3,3 +3,4 @@ export { setBaseUrl, BASE_URL } from "./api"; export * as api from "./api"; export { ChainType, Environment } from "./enums"; export { Sprinter } from "./sprinter"; +export * from "./helpers"; diff --git a/packages/sdk/src/sygma/api.ts b/packages/sdk/src/sygma/api.ts new file mode 100644 index 0000000..34653ee --- /dev/null +++ b/packages/sdk/src/sygma/api.ts @@ -0,0 +1,29 @@ +import { Environment } from "../enums"; + +import type { SygmaTransfer } from "./types"; + +const SYGMA_API_ENDPOINT: Record = { + [Environment.MAINNET]: "https://api.buildwithsygma.com/", + [Environment.TESTNET]: "https://api.test.buildwithsygma.com/", +}; + +/** + * Returns list of sygma transfers for an address + * @param {`0x${string}`} address EVM address + * @param {Environment} environment TESTNET or MAINNET + * @returns {Promise>} + */ +export async function getTransfers( + address: string, + environment: Environment, +): Promise> { + const transfersPath = `/api/sender/${address}/transfers`; + const url = new URL(transfersPath, SYGMA_API_ENDPOINT[environment]); + url.searchParams.set("limit", "100"); + + const response: SygmaTransfer[] = await fetch(url.toString()).then( + (response): Promise => response.json(), + ); + + return response; +} diff --git a/packages/sdk/src/sygma/index.ts b/packages/sdk/src/sygma/index.ts new file mode 100644 index 0000000..1d58e1a --- /dev/null +++ b/packages/sdk/src/sygma/index.ts @@ -0,0 +1,2 @@ +export * from "./api"; +export * from "./types"; diff --git a/packages/sdk/src/sygma/types.ts b/packages/sdk/src/sygma/types.ts new file mode 100644 index 0000000..3502bc4 --- /dev/null +++ b/packages/sdk/src/sygma/types.ts @@ -0,0 +1,67 @@ +export enum Status { + pending = "pending", + executed = "executed", + failed = "failed", +} + +export interface SygmaTransfer { + id: string; + depositNonce: number; + resource: Resource; + fromDomain: Domain; + fromDomainId: number; + toDomain: Domain; + toDomainId: number; + sender: string; + destination: string; + amount: string; + timestamp?: string; + status: Status; + deposit?: Deposit; + execution?: Execution; + fee: Fee; + resourceID: string; + usdValue: number; + accountId: string; +} + +export interface Resource { + id: string; + type: string; +} + +export interface Domain { + id: string; + name: string; + lastIndexedBlock: string; +} + +export interface Deposit { + id: string; + transferId: string; + type: string; + txHash: string; + blockNumber: string; + depositData: string; + handlerResponse: string; + timestamp: string; +} + +export interface Execution { + id: string; + transferId: string; + type: string; + txHash: string; + blockNumber: string; + executionEvent: string; + timestamp: string; +} + +export interface Fee { + amount: string; + id: string; + tokenAddress: string; + tokenSymbol: string; + transferId: string; + decimals: number; +}