Feat/view utxos (#15)

* feat: get and view utxos

* chore: bump version
This commit is contained in:
Anthony Potdevin 2020-04-01 15:32:32 +02:00 committed by GitHub
parent 603f5de7f1
commit a9df78ac2d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 225 additions and 18 deletions

View File

@ -1,6 +1,6 @@
{
"name": "thunderhub-client",
"version": "0.1.15",
"version": "0.1.16",
"description": "",
"scripts": {
"start": "react-scripts start",

View File

@ -187,6 +187,10 @@ export const OverflowText = styled.div`
word-wrap: break-word;
-ms-word-break: break-all;
word-break: break-all;
@media (${mediaWidths.mobile}) {
margin-left: 8px;
}
`;
export const ResponsiveLine = styled(SingleLine)`

View File

@ -362,3 +362,17 @@ export const GET_PEERS = gql`
}
}
`;
export const GET_UTXOS = gql`
query GetUtxos($auth: authType!) {
getUtxos(auth: $auth) {
address
address_format
confirmation_count
output_script
tokens
transaction_id
transaction_vout
}
}
`;

View File

@ -9,7 +9,6 @@ import { ChannelView } from '../../views/channels/ChannelView';
import { SettingsView } from '../../views/settings/Settings';
import { TransactionList } from '../../views/transactions/TransactionList';
import { FeesView } from '../../views/fees/Fees';
import { ChainTransactions } from '../../views/chain/ChainTransactions';
import { ForwardsList } from '../../views/forwards/ForwardList';
import { TermsView } from '../../views/other/terms/TermsView';
import { PrivacyView } from '../../views/other/privacy/PrivacyView';
@ -23,6 +22,7 @@ import { LoadingView, ErrorView } from 'views/stateViews/StateCards';
import { BalanceView } from 'views/balance/Balance';
import { PeersList } from 'views/peers/PeersList';
import { ToolsView } from 'views/tools';
import { ChainView } from 'views/chain/ChainView';
const Container = styled.div`
display: grid;
@ -75,7 +75,7 @@ const Content = () => {
<Route path="/forwards" render={() => getGrid(ForwardsList)} />
<Route
path="/chaintransactions"
render={() => getGrid(ChainTransactions)}
render={() => getGrid(ChainView)}
/>
<Route path="/settings" render={() => getGrid(SettingsView)} />
<Route path="/fees" render={() => getGrid(FeesView)} />

View File

@ -0,0 +1,13 @@
import React from 'react';
import { ChainTransactions } from './transactions/ChainTransactions';
import { ChainUtxos } from './utxos/ChainUtxos';
export const ChainView = () => {
return (
<>
<ChainUtxos />
<ChainTransactions />
</>
);
};

View File

@ -1,11 +1,15 @@
import React, { useState } from 'react';
import { SubTitle, Card, CardWithTitle } from '../../components/generic/Styled';
import { useAccount } from '../../context/AccountContext';
import { GET_CHAIN_TRANSACTIONS } from '../../graphql/query';
import {
SubTitle,
Card,
CardWithTitle,
} from '../../../components/generic/Styled';
import { useAccount } from '../../../context/AccountContext';
import { GET_CHAIN_TRANSACTIONS } from '../../../graphql/query';
import { useQuery } from '@apollo/react-hooks';
import { toast } from 'react-toastify';
import { getErrorContent } from '../../utils/error';
import { LoadingCard } from '../../components/loading/LoadingCard';
import { getErrorContent } from '../../../utils/error';
import { LoadingCard } from '../../../components/loading/LoadingCard';
import { TransactionsCard } from './TransactionsCard';
export const ChainTransactions = () => {

View File

@ -5,19 +5,19 @@ import {
SingleLine,
DarkSubTitle,
ResponsiveLine,
} from '../../components/generic/Styled';
import { MainInfo } from '../channels/Channels.style';
} from '../../../components/generic/Styled';
import { MainInfo } from '../../channels/Channels.style';
import {
getDateDif,
getFormatDate,
renderLine,
} from '../../components/generic/Helpers';
} from '../../../components/generic/Helpers';
import styled from 'styled-components';
import { getPrice } from 'components/price/Price';
import { useSettings } from 'context/SettingsContext';
import { usePriceState } from 'context/PriceContext';
export const AddMargin = styled.div`
const AddMargin = styled.div`
margin-right: 10px;
`;

View File

@ -0,0 +1,49 @@
import React, { useState } from 'react';
import {
SubTitle,
Card,
CardWithTitle,
} from '../../../components/generic/Styled';
import { useAccount } from '../../../context/AccountContext';
import { GET_UTXOS } from '../../../graphql/query';
import { useQuery } from '@apollo/react-hooks';
import { toast } from 'react-toastify';
import { getErrorContent } from '../../../utils/error';
import { LoadingCard } from '../../../components/loading/LoadingCard';
import { UtxoCard } from './UtxoCard';
export const ChainUtxos = () => {
const [indexOpen, setIndexOpen] = useState(0);
const { host, viewOnly, cert, sessionAdmin } = useAccount();
const auth = {
host,
macaroon: viewOnly !== '' ? viewOnly : sessionAdmin,
cert,
};
const { loading, data } = useQuery(GET_UTXOS, {
variables: { auth },
onError: (error) => toast.error(getErrorContent(error)),
});
if (loading || !data || !data.getUtxos) {
return <LoadingCard title={'Unspent Utxos'} />;
}
return (
<CardWithTitle>
<SubTitle>Unspent Utxos</SubTitle>
<Card>
{data.getUtxos.map((utxo: any, index: number) => (
<UtxoCard
utxo={utxo}
key={index}
index={index + 1}
setIndexOpen={setIndexOpen}
indexOpen={indexOpen}
/>
))}
</Card>
</CardWithTitle>
);
};

View File

@ -0,0 +1,68 @@
import React from 'react';
import { Separation, SubCard } from '../../../components/generic/Styled';
import { MainInfo } from '../../channels/Channels.style';
import { renderLine } from '../../../components/generic/Helpers';
import { getPrice } from 'components/price/Price';
import { useSettings } from 'context/SettingsContext';
import { usePriceState } from 'context/PriceContext';
interface TransactionsCardProps {
utxo: any;
index: number;
setIndexOpen: (index: number) => void;
indexOpen: number;
}
export const UtxoCard = ({
utxo,
index,
setIndexOpen,
indexOpen,
}: TransactionsCardProps) => {
const { currency } = useSettings();
const priceContext = usePriceState();
const format = getPrice(currency, priceContext);
const {
address,
address_format,
confirmation_count,
output_script,
tokens,
transaction_id,
transaction_vout,
} = utxo;
const formatAmount = format({ amount: tokens });
const handleClick = () => {
if (indexOpen === index) {
setIndexOpen(0);
} else {
setIndexOpen(index);
}
};
const renderDetails = () => {
return (
<>
<Separation />
{renderLine('Address Format:', address_format)}
{renderLine('Confirmations: ', confirmation_count)}
{renderLine('Output Script: ', output_script)}
{renderLine('Transaction Id: ', transaction_id)}
{renderLine('Transaction Vout: ', transaction_vout)}
</>
);
};
return (
<SubCard key={index}>
<MainInfo onClick={() => handleClick()}>
{renderLine('Address', address)}
{renderLine('Amount', formatAmount)}
</MainInfo>
{index === indexOpen && renderDetails()}
</SubCard>
);
};

View File

@ -66,6 +66,10 @@ export const DetailLine = styled.div`
word-wrap: break-word;
display: flex;
justify-content: space-between;
@media (${mediaWidths.mobile}) {
flex-wrap: wrap;
}
`;
export const MainInfo = styled.div`

View File

@ -1,6 +1,6 @@
{
"name": "thunderhub-server",
"version": "0.1.15",
"version": "0.1.16",
"description": "",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",

View File

@ -0,0 +1,43 @@
import { getUtxos as getLnUtxos } from 'ln-service';
import { logger } from '../../../helpers/logger';
import { requestLimiter } from '../../../helpers/rateLimiter';
import {
GraphQLInt,
GraphQLObjectType,
GraphQLString,
GraphQLList,
} from 'graphql';
import { getAuthLnd, getErrorMsg } from '../../../helpers/helpers';
import { defaultParams } from '../../../helpers/defaultProps';
const GetUtxosType = new GraphQLObjectType({
name: 'getUtxosType',
fields: () => ({
address: { type: GraphQLString },
address_format: { type: GraphQLString },
confirmation_count: { type: GraphQLInt },
output_script: { type: GraphQLString },
tokens: { type: GraphQLInt },
transaction_id: { type: GraphQLString },
transaction_vout: { type: GraphQLInt },
}),
});
export const getUtxos = {
type: new GraphQLList(GetUtxosType),
args: defaultParams,
resolve: async (root: any, params: any, context: any) => {
await requestLimiter(context.ip, 'getUtxos');
const lnd = getAuthLnd(params.auth);
try {
const { utxos } = await getLnUtxos({ lnd });
return utxos;
} catch (error) {
params.logger && logger.error('Error getting utxos: %o', error);
throw new Error(getErrorMsg(error));
}
},
};

View File

@ -0,0 +1,10 @@
import { getChainBalance, getPendingChainBalance } from './chainBalance';
import { getChainTransactions } from './chainTransactions';
import { getUtxos } from './getUtxos';
export const chainQueries = {
getChainBalance,
getPendingChainBalance,
getChainTransactions,
getUtxos,
};

View File

@ -1,11 +1,8 @@
import { getChainBalance, getPendingChainBalance } from './chainBalance';
import { getNetworkInfo } from './networkInfo';
import { getNodeInfo } from './nodeInfo';
import { adminCheck } from './adminCheck';
export const generalQueries = {
getChainBalance,
getPendingChainBalance,
getNetworkInfo,
getNodeInfo,
adminCheck,

View File

@ -8,6 +8,7 @@ import { backupQueries } from './backup';
import { routeQueries } from './route';
import { peerQueries } from './peer';
import { messageQueries } from './message';
import { chainQueries } from './chain';
export const query = {
...channelQueries,
@ -20,4 +21,5 @@ export const query = {
...routeQueries,
...peerQueries,
...messageQueries,
...chainQueries,
};

View File

@ -1,9 +1,7 @@
import { getForwards } from './forwards';
import { getResume } from './resume';
import { getChainTransactions } from './chainTransactions';
export const invoiceQueries = {
getResume,
getForwards,
getChainTransactions,
};

View File

@ -46,4 +46,5 @@ export const RateConfig: RateConfigProps = {
removePeer: { max: 10, window: '1s' },
signMessage: { max: 10, window: '1s' },
verifyMessage: { max: 10, window: '1s' },
getUtxos: { max: 10, window: '1s' },
};