diff --git a/packages/bridge/src/hooks/useLockedFundsAccounts.tsx b/packages/bridge/src/hooks/useLockedFundsAccounts.tsx new file mode 100644 index 0000000..d2aadb9 --- /dev/null +++ b/packages/bridge/src/hooks/useLockedFundsAccounts.tsx @@ -0,0 +1,95 @@ +import {useEffect, useState} from "react"; +import {contexts} from "@oyster/common"; +import * as BufferLayout from 'buffer-layout' +import {WORMHOLE_PROGRAM_ID} from "../utils/ids"; +import BN from "bn.js"; +import {getAssetAmountInUSD, getAssetName, getAssetTokenSymbol} from "../utils/assets"; + +const { useConnection } = contexts.Connection; + +interface ParsedData { + amount: number, + rawAmount: string, + parsedAssetAddress: string, + parsedAccount: any, + assetDecimals: number, + tokenName: string, + tokenSymbol: string, + sourceAddress: string, + targetAddress: string, + amountInUSD: number, +} + +export const useLockedFundsAccounts = () => { + const connection = useConnection(); + + const [lockedSolanaAccounts, setLockedSolanaAccounts] = useState([]); + const [loading, setLoading] = useState(true); + useEffect(() => { + const queryTxs = async () => { + setLoading(true); + const programAccounts = await connection.getProgramAccounts( + WORMHOLE_PROGRAM_ID + ); + const dataLayout = BufferLayout.struct([ + BufferLayout.blob(32, 'amount'), + BufferLayout.u8('toChain'), + BufferLayout.blob(32, 'sourceAddress'), + BufferLayout.blob(32, 'targetAddress'), + BufferLayout.blob(32, 'assetAddress'), + BufferLayout.u8('assetChain'), + BufferLayout.u8('assetDecimals'), + BufferLayout.seq(BufferLayout.u8(), 1), // 4 byte alignment because a u32 is following + BufferLayout.u32('nonce'), + BufferLayout.blob(1001, 'vaa'), + BufferLayout.seq(BufferLayout.u8(), 3), // 4 byte alignment because a u32 is following + BufferLayout.u32('vaaTime'), + BufferLayout.u32('lockupTime'), + BufferLayout.u8('pokeCounter'), + BufferLayout.blob(32, 'signatureAccount'), + BufferLayout.u8('initialized'), + ]); + const filteredParsedAccounts: ParsedData[] = []; + programAccounts.map(acc => { + try { + const parsedAccount = dataLayout.decode(acc.account.data) + + if ((parsedAccount.assetChain === 1 || parsedAccount.assetChain ===2 ) && + (parsedAccount.toChain === 1 || parsedAccount.toChain === 2)) { + const dec = new BN(10).pow(new BN(parsedAccount.assetDecimals)); + const rawAmount = new BN(parsedAccount.amount, 2, "le") + const amount = rawAmount.div(dec).toNumber(); + const parsedAssetAddress: string = new Buffer(parsedAccount.assetAddress.slice(12)).toString("hex") + const parsedData: ParsedData = { + amount: amount, + rawAmount: rawAmount.toString(), + parsedAssetAddress: parsedAssetAddress, + parsedAccount: parsedAccount, + assetDecimals: parsedAccount.assetDecimals, + sourceAddress: new Buffer(parsedAccount.sourceAddress.slice(12)).toString("hex"), + targetAddress: new Buffer(parsedAccount.targetAddress.slice(12)).toString("hex"), + tokenName: getAssetName(parsedAssetAddress, parsedAccount.assetChain), + tokenSymbol: getAssetTokenSymbol(parsedAssetAddress, parsedAccount.assetChain), + amountInUSD: getAssetAmountInUSD(amount, parsedAssetAddress, parsedAccount.assetChain), + }; + filteredParsedAccounts.push(parsedData) + } + } catch (error){ + return + } + }); + return filteredParsedAccounts; + } + Promise.all([queryTxs()]).then((all) => { + setLoading(false); + setLockedSolanaAccounts(all[0]) + }); + }, []); + return { + loading, + lockedSolanaAccounts, + total: lockedSolanaAccounts.reduce((acc, val) => { + return acc + val.amountInUSD; + }, 0) + }; +} \ No newline at end of file diff --git a/packages/bridge/src/utils/assets.ts b/packages/bridge/src/utils/assets.ts new file mode 100644 index 0000000..1a661d2 --- /dev/null +++ b/packages/bridge/src/utils/assets.ts @@ -0,0 +1,21 @@ +export const getAssetName = ( + parsedAssetAddress: string, + assetChain: number, +) => { + return parsedAssetAddress.slice(0, 5); +}; + +export const getAssetTokenSymbol = ( + parsedAssetAddress: string, + assetChain: number, +) => { + return parsedAssetAddress.slice(0, 5); +}; + +export const getAssetAmountInUSD = ( + amount: number, + parsedAssetAddress: string, + assetChain: number, +) => { + return amount; +}; diff --git a/packages/bridge/src/utils/ids.ts b/packages/bridge/src/utils/ids.ts new file mode 100644 index 0000000..4211530 --- /dev/null +++ b/packages/bridge/src/utils/ids.ts @@ -0,0 +1,5 @@ +import { PublicKey } from '@solana/web3.js'; + +export const WORMHOLE_PROGRAM_ID = new PublicKey( + 'WormT3McKhFJ2RkiGpdw9GKvNCrB2aB54gb2uV9MfQC', +); diff --git a/packages/bridge/src/views/home/index.tsx b/packages/bridge/src/views/home/index.tsx index fdc5168..46472d9 100644 --- a/packages/bridge/src/views/home/index.tsx +++ b/packages/bridge/src/views/home/index.tsx @@ -1,14 +1,16 @@ import { MintInfo } from '@solana/spl-token'; import { Table, Tag, Space, Card, Col, Row, Statistic, Button } from 'antd'; -import React, { useEffect, useState } from 'react'; +import React, {useEffect, useMemo, useState} from 'react'; import { GUTTER, LABELS } from '../../constants'; -import { contexts, ParsedAccount, utils } from '@oyster/common'; +import {contexts, ExplorerLink, ParsedAccount, utils} from '@oyster/common'; import { useMarkets } from '../../contexts/market'; import { LendingReserveItem } from './item'; import './itemStyle.less'; import { Totals } from '../../models/totals'; import { Link } from 'react-router-dom'; +import {useLockedFundsAccounts} from "../../hooks/useLockedFundsAccounts"; +import {EtherscanLink} from "@oyster/common/dist/lib/components/EtherscanLink"; const { fromLamports, getTokenName, wadToLamports } = utils; const { cache } = contexts.Accounts; const { useConnectionConfig } = contexts.Connection; @@ -16,6 +18,7 @@ const { useConnectionConfig } = contexts.Connection; export const HomeView = () => { const { marketEmitter, midPriceInUSD } = useMarkets(); const { tokenMap } = useConnectionConfig(); + const {loading: loadingLockedAccounts, lockedSolanaAccounts, total: totalLocked } = useLockedFundsAccounts(); const [totals, setTotals] = useState({ marketSize: 0, numberOfAssets: 0, @@ -76,36 +79,50 @@ export const HomeView = () => { }; }, [marketEmitter, midPriceInUSD, setTotals, tokenMap]); - const dataSource = [ - { - key: '1', - name: 'Mike', - age: 32, - address: '10 Downing Street', - }, - { - key: '2', - name: 'John', - age: 42, - address: '10 Downing Street', - }, - ]; + const dataSource = useMemo(() => { + if (loadingLockedAccounts) return []; + console.log(lockedSolanaAccounts) + return lockedSolanaAccounts.map((acc, index) => { + return { + key: index.toString(), + symbol: acc.tokenSymbol, + name: acc.tokenName, + amount: acc.amountInUSD, + sourceAddress: acc.parsedAccount.assetChain === 1 ? + : + , + targetAddress: acc.parsedAccount.toChain === 1 ? + : + , + } + }) + }, [loadingLockedAccounts, lockedSolanaAccounts]) const columns = [ + { + title: 'Symbol', + dataIndex: 'symbol', + key: 'symbol', + }, { title: 'Name', dataIndex: 'name', key: 'name', }, { - title: 'Age', - dataIndex: 'age', - key: 'age', + title: 'Amount', + dataIndex: 'amount', + key: 'amount', }, { - title: 'Address', - dataIndex: 'address', - key: 'address', + title: 'Source Address', + dataIndex: 'sourceAddress', + key: 'sourceAddress', + }, + { + title: 'Target Address', + dataIndex: 'targetAddress', + key: 'targetAddress', }, ]; @@ -127,7 +144,7 @@ export const HomeView = () => { diff --git a/packages/bridge/src/views/home/itemStyle.less b/packages/bridge/src/views/home/itemStyle.less index eea3fcf..388fcd2 100644 --- a/packages/bridge/src/views/home/itemStyle.less +++ b/packages/bridge/src/views/home/itemStyle.less @@ -33,4 +33,5 @@ .home-info-row { margin-bottom: 10px; -} \ No newline at end of file + min-height: 45vh; +} diff --git a/packages/common/src/components/EtherscanLink/index.tsx b/packages/common/src/components/EtherscanLink/index.tsx new file mode 100644 index 0000000..321febb --- /dev/null +++ b/packages/common/src/components/EtherscanLink/index.tsx @@ -0,0 +1,39 @@ +import React from 'react'; +import { Typography } from 'antd'; +import { shortenAddress } from '../../utils/utils'; + +export const EtherscanLink = (props: { + address: string ; + type: string; + code?: boolean; + style?: React.CSSProperties; + length?: number; +}) => { + const { type, code } = props; + + const address = props.address; + + if (!address) { + return null; + } + + const length = props.length ?? 9; + + return ( + + {code ? ( + + {shortenAddress(address, length)} + + ) : ( + shortenAddress(address, length) + )} + + ); +};