mirror of https://github.com/certusone/oyster.git
Merge pull request #10 from yamijuan/solana-tokens
Added list of locked funds accounts in the front page
This commit is contained in:
commit
9b74f8ba75
|
@ -152,10 +152,6 @@ em {
|
|||
display: none;
|
||||
}
|
||||
|
||||
.ant-table-container table > thead > tr th {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.ant-notification {
|
||||
a {
|
||||
color: blue;
|
||||
|
|
|
@ -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 {ASSET_CHAIN, 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<ParsedData[]>([]);
|
||||
const [loading, setLoading] = useState<boolean>(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)
|
||||
const chains = [ASSET_CHAIN.Solana, ASSET_CHAIN.Ethereum]
|
||||
if (chains.indexOf(parsedAccount.assetChain) >= 0 &&
|
||||
chains.indexOf(parsedAccount.toChain) >= 0) {
|
||||
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)
|
||||
};
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
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;
|
||||
};
|
||||
|
||||
export enum ASSET_CHAIN {
|
||||
Solana = 1,
|
||||
Ethereum = 2,
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
import { PublicKey } from '@solana/web3.js';
|
||||
|
||||
export const WORMHOLE_PROGRAM_ID = new PublicKey(
|
||||
'WormT3McKhFJ2RkiGpdw9GKvNCrB2aB54gb2uV9MfQC',
|
||||
);
|
|
@ -1,14 +1,17 @@
|
|||
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";
|
||||
import {ASSET_CHAIN} from "../../utils/assets";
|
||||
const { fromLamports, getTokenName, wadToLamports } = utils;
|
||||
const { cache } = contexts.Accounts;
|
||||
const { useConnectionConfig } = contexts.Connection;
|
||||
|
@ -16,6 +19,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<Totals>({
|
||||
marketSize: 0,
|
||||
numberOfAssets: 0,
|
||||
|
@ -76,36 +80,49 @@ 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 [];
|
||||
return lockedSolanaAccounts.map((acc, index) => {
|
||||
return {
|
||||
key: index.toString(),
|
||||
symbol: acc.tokenSymbol,
|
||||
name: acc.tokenName,
|
||||
amount: acc.amountInUSD,
|
||||
sourceAddress: acc.parsedAccount.assetChain === ASSET_CHAIN.Solana ?
|
||||
<ExplorerLink address={acc.sourceAddress} type={"address"} /> :
|
||||
<EtherscanLink address={acc.sourceAddress} type={"address"} />,
|
||||
targetAddress: acc.parsedAccount.toChain === ASSET_CHAIN.Solana ?
|
||||
<ExplorerLink address={acc.targetAddress} type={"address"} /> :
|
||||
<EtherscanLink address={acc.targetAddress} type={"address"} />,
|
||||
}
|
||||
})
|
||||
}, [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 = () => {
|
|||
<Col xs={24} xl={12}>
|
||||
<Statistic
|
||||
className="home-statistic"
|
||||
title="$1,231"
|
||||
title={`$${totalLocked}`}
|
||||
value="TOTAL VALUE LOCKED"
|
||||
/>
|
||||
</Col>
|
||||
|
|
|
@ -33,4 +33,5 @@
|
|||
|
||||
.home-info-row {
|
||||
margin-bottom: 10px;
|
||||
min-height: 45vh;
|
||||
}
|
|
@ -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 (
|
||||
<a
|
||||
href={`https://etherscan.io/${type}/${address}`}
|
||||
// eslint-disable-next-line react/jsx-no-target-blank
|
||||
target="_blank"
|
||||
title={address}
|
||||
style={props.style}
|
||||
>
|
||||
{code ? (
|
||||
<Typography.Text style={props.style} code>
|
||||
{shortenAddress(address, length)}
|
||||
</Typography.Text>
|
||||
) : (
|
||||
shortenAddress(address, length)
|
||||
)}
|
||||
</a>
|
||||
);
|
||||
};
|
Loading…
Reference in New Issue