Skip to content

Commit

Permalink
Merge pull request #605 from cocrafts/ltminhthu/profit-and-lost
Browse files Browse the repository at this point in the history
[MS-323][wallet] feat: PnL UI and API integration
  • Loading branch information
tanlethanh authored Aug 12, 2024
2 parents 70197ab + 7a34a91 commit f8ed0f0
Show file tree
Hide file tree
Showing 16 changed files with 379 additions and 88 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -74,4 +74,5 @@ cli/tokens/**/cache.json
cli/tokens/**/config.json
cli/tokens/**/sugar.log

playground
playground
.nvmrc
28 changes: 26 additions & 2 deletions apps/wallet/src/components/TokenList/Item.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { FC } from 'react';
import type { StyleProp, ViewStyle } from 'react-native';
import { Image, StyleSheet } from 'react-native';
import type { TokenPnL } from '@walless/core';
import { Hoverable, Text, View } from '@walless/gui';
import type { TokenDocument } from '@walless/store';
import assets from 'utils/assets';
Expand All @@ -10,22 +11,31 @@ interface Props {
style?: StyleProp<ViewStyle>;
token: TokenDocument;
onPress?: () => void;
tokenPnL?: TokenPnL;
}

export const TokenItem: FC<Props> = ({ style, token, onPress }) => {
export const TokenItem: FC<Props> = ({ style, token, onPress, tokenPnL }) => {
const pnl = tokenPnL?.priceChangePercentage24H || 0;
const { symbol, image, quotes, balance } = token;
const unitQuote = quotes?.usd;
const totalQuote = unitQuote && unitQuote * balance;
const iconSource = image ? { uri: image } : assets.misc.unknownToken;

const itemName = symbol || 'Unknown';
const isLost = pnl && pnl < 0;

return (
<Hoverable style={[styles.container, style]} onPress={onPress}>
<Image style={styles.iconImg} source={iconSource} resizeMode="cover" />
<View style={styles.infoContainer}>
<Text style={styles.primaryText}>{itemName}</Text>
<Text style={styles.secondaryText}>{formatQuote(unitQuote)}</Text>
<View style={styles.unitQuoteContainer}>
<Text style={styles.secondaryText}>{formatQuote(unitQuote)}</Text>
<Text style={isLost ? styles.lostText : styles.profitText}>
{isLost ? '-' : '+'}{' '}
{Math.abs(Math.round(pnl * 10000) / 10000 ?? 0)}%
</Text>
</View>
</View>
<View style={styles.balanceContainer}>
<Text style={styles.primaryText}>{balance}</Text>
Expand Down Expand Up @@ -75,4 +85,18 @@ const styles = StyleSheet.create({
color: '#566674',
fontSize: 13,
},
unitQuoteContainer: {
flexDirection: 'row',
alignItems: 'center',
gap: 8,
},
profitText: {
color: '#60C591',
fontSize: 10,
alignSelf: 'flex-end',
},
lostText: {
color: '#AE3939',
fontSize: 10,
},
});
1 change: 1 addition & 0 deletions apps/wallet/src/components/TokenList/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export const TokenList = <T extends Token>({
index === tokens.length - 1 && styles.lastItem,
]}
onPress={handlePressItem}
tokenPnL={item.pnl}
/>
);
};
Expand Down
113 changes: 113 additions & 0 deletions apps/wallet/src/components/TotalPnL.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import type { FC } from 'react';
import { StyleSheet } from 'react-native';
import { Text, View } from '@walless/gui';

interface Props {
value: number;
percentage: number;
isDarkTheme: boolean;
}

const TotalPnL: FC<Props> = ({ value, percentage, isDarkTheme = false }) => {
const isLost = value < 0;

return (
<View style={styles.container}>
<Text
style={
isDarkTheme
? isLost
? styles.darkThemeLostValue
: styles.darkThemeProfitValue
: isLost
? styles.lightThemeLostValue
: styles.lightThemeProfitValue
}
>
{isLost ? `-$${-value}` : `+$${value}`}
</Text>
<View
style={[
styles.percentageContainer,
isDarkTheme
? isLost
? styles.darkThemeLostPercentageContainer
: styles.darkThemeProfitPercentageContainer
: isLost
? styles.lightThemeLostPercentageContainer
: styles.lightThemeProfitPercentageContainer,
]}
>
<Text
style={
isDarkTheme
? isLost
? styles.darkThemeLostPercentage
: styles.darkThemeProfitPercentage
: isLost
? styles.lightThemeLostPercentage
: styles.lightThemeProfitPercentage
}
>
{!isLost && '+'}
{percentage}%
</Text>
</View>
</View>
);
};

export default TotalPnL;

const styles = StyleSheet.create({
container: {
flexDirection: 'row',
alignItems: 'center',
gap: 8,
},
lightThemeProfitValue: {
color: '#60C591',
fontSize: 20,
},
darkThemeProfitValue: {
color: '#ffffff',
fontSize: 20,
},
lightThemeLostValue: {
color: '#AE3939',
fontSize: 20,
},
darkThemeLostValue: {
color: '#ffffff',
fontSize: 20,
},
lightThemeProfitPercentage: {
color: '#60C591',
},
lightThemeLostPercentage: {
color: '#AE3939',
},
darkThemeProfitPercentage: {
color: '#ffffff',
},
darkThemeLostPercentage: {
color: '#ffffff',
},
percentageContainer: {
borderRadius: 4,
paddingVertical: 4,
paddingHorizontal: 8,
},
lightThemeProfitPercentageContainer: {
backgroundColor: '#AE393933',
},
darkThemeProfitPercentageContainer: {
backgroundColor: '#2A9960',
},
lightThemeLostPercentageContainer: {
backgroundColor: '#AE393933',
},
darkThemeLostPercentageContainer: {
backgroundColor: '#941200',
},
});
2 changes: 2 additions & 0 deletions apps/wallet/src/engine/runners/solana/subscription.ts
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,8 @@ const handleInitAccountOnLogsChange = async (
const quotes = await getTokenQuotes([
{ address: token.mint, network: token.network },
]);
token.pnl =
quotes[makeHashId({ address: token.mint, network: token.network })].pnl;
token.quotes =
quotes[
makeHashId({ address: token.mint, network: token.network })
Expand Down
12 changes: 7 additions & 5 deletions apps/wallet/src/engine/runners/solana/tokens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { LAMPORTS_PER_SOL } from '@solana/web3.js';
import type { NetworkCluster, SolanaToken } from '@walless/core';
import { Networks } from '@walless/core';
import type { TokenDocument } from '@walless/store';
import { getTokenQuote } from 'utils/api';
import { getTokenQuote as getTokenInfo } from 'utils/api';
import { solMint, wrappedSolMint } from 'utils/constants';
import { addTokenToStorage } from 'utils/storage';

Expand All @@ -23,11 +23,12 @@ export const queryTokens = async (
cluster,
wallet,
).then(async (doc) => {
const quotes = await getTokenQuote({
const tokenInfo = await getTokenInfo({
address: wrappedSolMint,
network: doc.network,
});
doc.quotes = quotes?.quotes;
doc.quotes = tokenInfo?.quotes;
doc.pnl = tokenInfo?.pnl;

await addTokenToStorage(doc);
return doc;
Expand All @@ -42,11 +43,12 @@ export const queryTokens = async (
account,
);

const quotes = await getTokenQuote({
const tokenInfo = await getTokenInfo({
address: doc.mint,
network: doc.network,
});
doc.quotes = quotes?.quotes;
doc.quotes = tokenInfo?.quotes;
doc.pnl = tokenInfo?.pnl;

await addTokenToStorage(doc);
return doc;
Expand Down
43 changes: 24 additions & 19 deletions apps/wallet/src/features/Home/TokenValue.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,37 +2,43 @@ import type { FC } from 'react';
import { StyleSheet, View } from 'react-native';
import { Hoverable, Text } from '@walless/gui';
import { Eye, EyeOff } from '@walless/icons';
import { useSettings } from 'utils/hooks';
import TotalPnL from 'components/TotalPnL';
import { useSettings, useTokens } from 'utils/hooks';

interface Props {
value: number;
}

const TokenValue: FC<Props> = ({ value }) => {
const { setting, setPrivacy } = useSettings();
const balanceTextStyle = [
styles.balanceText,
setting.hideBalance && styles.protectedBalance,
];
const { valuation, pnl } = useTokens();

const handleToggleTokenValue = async () => {
setPrivacy(!setting.hideBalance);
};
const pnlRates = (pnl / (valuation != 0 ? valuation : 1)) * 100;

return (
<View style={styles.container}>
<Text style={styles.headingText}>Token value</Text>
<View style={styles.balanceContainer}>
<Text style={balanceTextStyle}>
{setting.hideBalance ? '****' : '$' + value.toFixed(2)}
</Text>
<Hoverable onPress={handleToggleTokenValue}>
{setting.hideBalance ? (
<Eye size={20} color="#566674" />
) : (
<EyeOff size={20} color="#566674" />
)}
</Hoverable>
<View style={styles.balanceAndPercentageContainer}>
<View style={styles.balanceContainer}>
<Text style={styles.balanceText}>
{setting.hideBalance ? '****' : '$' + value.toFixed(2)}
</Text>
<Hoverable onPress={handleToggleTokenValue}>
{setting.hideBalance ? (
<Eye size={20} color="#566674" />
) : (
<EyeOff size={20} color="#566674" />
)}
</Hoverable>
</View>
<TotalPnL
value={Math.round(pnl * 100) / 100}
percentage={Math.round(pnlRates * 100) / 100}
isDarkTheme={true}
/>
</View>
</View>
);
Expand All @@ -53,14 +59,13 @@ const styles = StyleSheet.create({
flexDirection: 'row',
alignItems: 'center',
gap: 12,
minHeight: 84,
},
balanceText: {
color: '#FFFFFF',
fontSize: 40,
fontWeight: '500',
},
protectedBalance: {
paddingTop: 16,
balanceAndPercentageContainer: {
alignItems: 'center',
},
});
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,27 @@ import type { FC } from 'react';
import { StyleSheet } from 'react-native';
import { Hoverable, Text, View } from '@walless/gui';
import { Eye, EyeOff } from '@walless/icons';
import TotalPnL from 'components/TotalPnL';
import { getValuationDisplay } from 'utils/helper';

interface Props {
onHide: (next: boolean) => void;
hideBalance: boolean;
valuation?: number;
pnl?: number;
}

export const WalletBalance: FC<Props> = ({
onHide,
hideBalance,
valuation = 0,
pnl = 0,
}) => {
const balanceTextStyle = [
styles.balanceText,
hideBalance && styles.protectedBalance,
];
const pnlRates = (pnl / (valuation != 0 ? valuation : 1)) * 100;

return (
<View style={styles.container}>
Expand All @@ -30,6 +34,13 @@ export const WalletBalance: FC<Props> = ({
{getValuationDisplay(valuation, hideBalance)}
</Text>
</View>
<View style={styles.pnLContainer}>
<TotalPnL
value={Math.round(pnl * 100) / 100}
percentage={Math.round(pnlRates * 100) / 100}
isDarkTheme={false}
/>
</View>
</View>
);
};
Expand All @@ -44,7 +55,6 @@ const styles = StyleSheet.create({
flexDirection: 'row',
alignItems: 'center',
paddingLeft: 5,
paddingBottom: 20,
gap: 10,
},
balanceText: {
Expand All @@ -60,4 +70,7 @@ const styles = StyleSheet.create({
opacity: 0.6,
marginLeft: 34,
},
pnLContainer: {
paddingLeft: 10,
},
});
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ interface Props {
item: PublicKeyDocument;
skin: CardSkin;
valuation?: number;
pnl?: number;
hideBalance: boolean;
onCopyAddress?: (value: string) => void;
onChangePrivateSetting?: (value: boolean) => void;
Expand All @@ -26,6 +27,7 @@ export const WalletCard: FC<Props> = ({
item,
skin,
valuation = 0,
pnl = 0,
hideBalance,
onCopyAddress,
onChangePrivateSetting,
Expand Down Expand Up @@ -58,6 +60,7 @@ export const WalletCard: FC<Props> = ({
hideBalance={hideBalance}
valuation={valuation}
onHide={handleHide}
pnl={pnl}
/>
{skin.largeIconSrc && (
<View pointerEvents="none" style={styles.markContainer}>
Expand Down
Loading

0 comments on commit f8ed0f0

Please sign in to comment.