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;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-table-container table > thead > tr th {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-notification {
|
.ant-notification {
|
||||||
a {
|
a {
|
||||||
color: blue;
|
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 { MintInfo } from '@solana/spl-token';
|
||||||
import { Table, Tag, Space, Card, Col, Row, Statistic, Button } from 'antd';
|
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 { 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 { useMarkets } from '../../contexts/market';
|
||||||
|
|
||||||
import { LendingReserveItem } from './item';
|
import { LendingReserveItem } from './item';
|
||||||
import './itemStyle.less';
|
import './itemStyle.less';
|
||||||
import { Totals } from '../../models/totals';
|
import { Totals } from '../../models/totals';
|
||||||
import { Link } from 'react-router-dom';
|
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 { fromLamports, getTokenName, wadToLamports } = utils;
|
||||||
const { cache } = contexts.Accounts;
|
const { cache } = contexts.Accounts;
|
||||||
const { useConnectionConfig } = contexts.Connection;
|
const { useConnectionConfig } = contexts.Connection;
|
||||||
|
@ -16,6 +19,7 @@ const { useConnectionConfig } = contexts.Connection;
|
||||||
export const HomeView = () => {
|
export const HomeView = () => {
|
||||||
const { marketEmitter, midPriceInUSD } = useMarkets();
|
const { marketEmitter, midPriceInUSD } = useMarkets();
|
||||||
const { tokenMap } = useConnectionConfig();
|
const { tokenMap } = useConnectionConfig();
|
||||||
|
const {loading: loadingLockedAccounts, lockedSolanaAccounts, total: totalLocked } = useLockedFundsAccounts();
|
||||||
const [totals, setTotals] = useState<Totals>({
|
const [totals, setTotals] = useState<Totals>({
|
||||||
marketSize: 0,
|
marketSize: 0,
|
||||||
numberOfAssets: 0,
|
numberOfAssets: 0,
|
||||||
|
@ -76,36 +80,49 @@ export const HomeView = () => {
|
||||||
};
|
};
|
||||||
}, [marketEmitter, midPriceInUSD, setTotals, tokenMap]);
|
}, [marketEmitter, midPriceInUSD, setTotals, tokenMap]);
|
||||||
|
|
||||||
const dataSource = [
|
const dataSource = useMemo(() => {
|
||||||
{
|
if (loadingLockedAccounts) return [];
|
||||||
key: '1',
|
return lockedSolanaAccounts.map((acc, index) => {
|
||||||
name: 'Mike',
|
return {
|
||||||
age: 32,
|
key: index.toString(),
|
||||||
address: '10 Downing Street',
|
symbol: acc.tokenSymbol,
|
||||||
},
|
name: acc.tokenName,
|
||||||
{
|
amount: acc.amountInUSD,
|
||||||
key: '2',
|
sourceAddress: acc.parsedAccount.assetChain === ASSET_CHAIN.Solana ?
|
||||||
name: 'John',
|
<ExplorerLink address={acc.sourceAddress} type={"address"} /> :
|
||||||
age: 42,
|
<EtherscanLink address={acc.sourceAddress} type={"address"} />,
|
||||||
address: '10 Downing Street',
|
targetAddress: acc.parsedAccount.toChain === ASSET_CHAIN.Solana ?
|
||||||
},
|
<ExplorerLink address={acc.targetAddress} type={"address"} /> :
|
||||||
];
|
<EtherscanLink address={acc.targetAddress} type={"address"} />,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}, [loadingLockedAccounts, lockedSolanaAccounts])
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
|
{
|
||||||
|
title: 'Symbol',
|
||||||
|
dataIndex: 'symbol',
|
||||||
|
key: 'symbol',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: 'Name',
|
title: 'Name',
|
||||||
dataIndex: 'name',
|
dataIndex: 'name',
|
||||||
key: 'name',
|
key: 'name',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Age',
|
title: 'Amount',
|
||||||
dataIndex: 'age',
|
dataIndex: 'amount',
|
||||||
key: 'age',
|
key: 'amount',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Address',
|
title: 'Source Address',
|
||||||
dataIndex: 'address',
|
dataIndex: 'sourceAddress',
|
||||||
key: 'address',
|
key: 'sourceAddress',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Target Address',
|
||||||
|
dataIndex: 'targetAddress',
|
||||||
|
key: 'targetAddress',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -127,7 +144,7 @@ export const HomeView = () => {
|
||||||
<Col xs={24} xl={12}>
|
<Col xs={24} xl={12}>
|
||||||
<Statistic
|
<Statistic
|
||||||
className="home-statistic"
|
className="home-statistic"
|
||||||
title="$1,231"
|
title={`$${totalLocked}`}
|
||||||
value="TOTAL VALUE LOCKED"
|
value="TOTAL VALUE LOCKED"
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
|
|
|
@ -33,4 +33,5 @@
|
||||||
|
|
||||||
.home-info-row {
|
.home-info-row {
|
||||||
margin-bottom: 10px;
|
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