Skip to content

Commit

Permalink
Standardise fiat <-> token conversions
Browse files Browse the repository at this point in the history
  • Loading branch information
hbriese committed Jul 20, 2023
1 parent c7b9fbe commit 470e42f
Show file tree
Hide file tree
Showing 19 changed files with 132 additions and 199 deletions.
18 changes: 2 additions & 16 deletions api/src/features/transfers/transfers.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@ import { selectAccount } from '../accounts/accounts.util';
import { uuid } from 'edgedb/dist/codecs/ifaces';
import { Shape } from '../database/database.select';
import { PricesService } from '../prices/prices.service';
import { Address, BigIntlike } from 'lib';
import { formatUnits } from 'viem';
import { Address, tokenToFiat } from 'lib';

export const TRANSFER_VALUE_FIELDS_SHAPE = {
token: {
Expand All @@ -23,19 +22,6 @@ export const TRANSFER_VALUE_FIELDS_SHAPE = {
const s = e.select(e.TransferDetails, () => TRANSFER_VALUE_FIELDS_SHAPE);
export type TransferValueSelectFields = $infer<typeof s>[0];

const FIAT_DECIMALS = 8;
const fiatAsBigInt = (value: number): bigint => BigInt(Math.floor(value * 10 ** FIAT_DECIMALS));

export interface TokenValueOptions {
amount: BigIntlike;
decimals: number;
price: number;
}

export function getTokenValue({ amount, decimals, price }: TokenValueOptions): number {
return parseFloat(formatUnits(BigInt(amount) * fiatAsBigInt(price), decimals + FIAT_DECIMALS));
}

@Injectable()
export class TransfersService {
constructor(private db: DatabaseService, private prices: PricesService) {}
Expand Down Expand Up @@ -76,7 +62,7 @@ export class TransfersService {
);
if (!p) return null;

const value = getTokenValue({ amount, decimals: token.decimals, price: p.current });
const value = tokenToFiat(amount, p.current, token.decimals);

return direction === 'In' ? value : -value;
}
Expand Down
11 changes: 5 additions & 6 deletions app/src/components/InputsView.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import { FragmentType, gql, useFragment } from '@api/gen';
import { SwapVerticalIcon } from '@theme/icons';
import { makeStyles } from '@theme/makeStyles';
import { fiatAsBigInt, tokenToFiat, valueAsTokenAmount } from '@token/fiat';
import { formatUnits, parseUnits } from 'ethers/lib/utils';
import { asBigInt } from 'lib';
import { asBigInt, fiatToToken, tokenToFiat } from 'lib';
import { Dispatch, SetStateAction } from 'react';
import { View } from 'react-native';
import { Button, IconButton, Text } from 'react-native-paper';
import { FiatValue } from '~/components/fiat/FiatValue';
import { TokenAmount } from '~/components/token/TokenAmount2';
import { TokenAmount } from '~/components/token/TokenAmount';
import { logWarning } from '~/util/analytics';

const FragmentDoc = gql(/* GraphQL */ `
Expand Down Expand Up @@ -52,12 +51,12 @@ export const InputsView = ({ input, setInput, type, setType, ...props }: InputsV
const tokenAmount =
type === InputType.Token
? parseUnits(inputAmount, token.decimals).toBigInt()
: valueAsTokenAmount(parseFloat(inputAmount), token.price?.current ?? 0, token.decimals);
: fiatToToken(parseFloat(inputAmount), token.price?.current ?? 0, token.decimals);

const fiatValue =
type === InputType.Token
? tokenToFiat(tokenAmount, asBigInt(token.price?.current) ?? 0n, token.decimals)
: fiatAsBigInt(inputAmount);
? tokenToFiat(tokenAmount, token.price?.current ?? 0, token.decimals)
: parseFloat(inputAmount);

return (
<View style={styles.container}>
Expand Down
2 changes: 1 addition & 1 deletion app/src/components/fiat/FiatValue.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { FIAT_DECIMALS } from '~/util/token/fiat';
import { FIAT_DECIMALS } from 'lib';
import { FormattedNumberOptions, useFormattedNumber } from '../format/FormattedNumber';

const currency = 'USD';
Expand Down
10 changes: 2 additions & 8 deletions app/src/components/token/TokenItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { ListItem, ListItemProps } from '../list/ListItem';
import { ListItemSkeleton } from '../list/ListItemSkeleton';
import { withSuspense } from '../skeleton/withSuspense';
import { TokenAmount } from './TokenAmount';
import { BigIntlike, getTokenValue } from 'lib';
import { BigIntlike, tokenToFiat } from 'lib';
import { FragmentType, gql, useFragment } from '@api/gen';
import { TokenIcon } from './TokenIcon/TokenIcon';

Expand Down Expand Up @@ -54,13 +54,7 @@ export const TokenItem = withSuspense(
trailing={({ Text }) =>
token.price && (
<Text variant="labelLarge">
<FiatValue
value={getTokenValue({
amount,
price: token.price.current,
decimals: token.decimals,
})}
/>
<FiatValue value={tokenToFiat(amount, token.price.current, token.decimals)} />
</Text>
)
}
Expand Down
27 changes: 23 additions & 4 deletions app/src/screens/contract-permissions/ContractPermissionsScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,27 @@ import { Appbar } from '~/components/Appbar/Appbar';
import { useAddressLabel } from '~/components/address/AddressLabel';
import { SpendingLimit } from './SpendingLimit';
import { ListHeader } from '~/components/list/ListHeader';
import { useMaybeToken } from '@token/useToken';
import { getFunctionSelector } from 'viem';
import { ContractFunction } from '@api/contracts/types';
import { ScrollView } from 'react-native';
import { ListItem } from '~/components/list/ListItem';
import { Switch } from 'react-native-paper';
import { gql } from '@api/gen';
import { useSuspenseQuery } from '@apollo/client';
import {
ContractPermissionsScreenQuery,
ContractPermissionsScreenQueryVariables,
} from '@api/gen/graphql';
import { ContractPermissionsScreenDocument } from '@api/generated';

gql(/* GraphQL */ `
query ContractPermissionsScreen($contract: Address!) {
token(input: { address: $contract }) {
id
...SpendingLimit_token
}
}
`);

const ERC20_FUNCTIONS: ContractFunction[] = ERC20_ABI.filter(
(abi): abi is Extract<typeof abi, { type: 'function' }> => abi.type === 'function',
Expand All @@ -42,15 +57,19 @@ export type ContractPermissionsScreenProps = StackNavigatorScreenProps<'Contract
export const ContractPermissionsScreen = withSuspense(
({ route }: ContractPermissionsScreenProps) => {
const { contract } = route.params;
const isToken = !!useMaybeToken(contract);

const { token } = useSuspenseQuery<
ContractPermissionsScreenQuery,
ContractPermissionsScreenQueryVariables
>(ContractPermissionsScreenDocument, { variables: { contract } }).data;

const [{ permissions }, updatePolicy] = useImmerAtom(POLICY_DRAFT_ATOM);

const target: Target | undefined = permissions.targets.contracts[contract];
const functions = filterFirst(
[
...useContractFunctions(contract),
...(isToken ? ERC20_FUNCTIONS : []),
...(token ? ERC20_FUNCTIONS : []),
...Object.keys(target?.functions ?? []).map((selector) => ({
selector: selector as Selector,
abi: undefined,
Expand All @@ -67,7 +86,7 @@ export const ContractPermissionsScreen = withSuspense(
<Appbar mode="large" leading="back" headline={useAddressLabel(contract)} />

<ScrollView showsVerticalScrollIndicator={false}>
<SpendingLimit contract={contract} />
{token && <SpendingLimit token={token} />}

<ListHeader>Actions</ListHeader>

Expand Down
34 changes: 20 additions & 14 deletions app/src/screens/contract-permissions/SpendingLimit.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { makeStyles } from '@theme/makeStyles';
import { useMaybeToken } from '@token/useToken';
import { useImmerAtom } from 'jotai-immer';
import { Address, TransferLimit } from 'lib';
import { TransferLimit } from 'lib';
import { Duration } from 'luxon';
import { View } from 'react-native';
import { BasicTextField } from '~/components/fields/BasicTextField';
Expand All @@ -10,6 +9,15 @@ import { ListHeader } from '~/components/list/ListHeader';
import { POLICY_DRAFT_ATOM } from '../policy/PolicyDraft';
import { useBigIntInput } from '~/components/fields/useBigIntInput';
import { ClockOutlineIcon } from '@theme/icons';
import { FragmentType, gql, useFragment } from '@api/gen';

const Fragment = gql(/* GraphQL */ `
fragment SpendingLimit_token on Token {
id
address
decimals
}
`);

const DEFAULT_DURATION = Duration.fromObject({ day: 1 });

Expand All @@ -23,34 +31,32 @@ const DURATIONS = [
] as const;

export interface SpendingLimitProps {
contract: Address;
token: FragmentType<typeof Fragment>;
}

export function SpendingLimit({ contract }: SpendingLimitProps) {
export function SpendingLimit(props: SpendingLimitProps) {
const styles = useStyles();
const token = useMaybeToken(contract);
const t = useFragment(Fragment, props.token);

const [policy, updatePolicy] = useImmerAtom(POLICY_DRAFT_ATOM);
const limit: TransferLimit | undefined = policy.permissions.transfers.limits[contract];
const limit: TransferLimit | undefined = policy.permissions.transfers.limits[t.address];

const inputProps = useBigIntInput({
value: limit?.amount,
decimals: token?.decimals ?? 0,
decimals: t.decimals,
onChange: (amount) =>
updatePolicy(({ permissions: { transfers } }) => {
if (amount !== undefined) {
transfers.limits[contract] = {
transfers.limits[t.address] = {
amount,
duration: transfers.limits[contract]?.duration ?? DEFAULT_DURATION.as('seconds'),
duration: transfers.limits[t.address]?.duration ?? DEFAULT_DURATION.as('seconds'),
};
} else {
delete transfers.limits[contract];
delete transfers.limits[t.address];
}
}),
});

if (!token) return null;

return (
<>
<ListHeader>Spending limit</ListHeader>
Expand All @@ -68,8 +74,8 @@ export function SpendingLimit({ contract }: SpendingLimitProps) {
value={limit ? Duration.fromObject({ seconds: limit.duration }) : DEFAULT_DURATION}
onChange={(duration) =>
updatePolicy(({ permissions: { transfers } }) => {
transfers.limits[contract] = {
amount: transfers.limits[contract]?.amount ?? 0n,
transfers.limits[t.address] = {
amount: transfers.limits[t.address]?.amount ?? 0n,
duration: duration.as('seconds'),
};
})
Expand Down
9 changes: 2 additions & 7 deletions app/src/screens/home/AccountValue.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { FragmentType, gql, useFragment } from '@api/gen';
import { makeStyles } from '@theme/makeStyles';
import { getTokenValue } from '@token/token';
import { tokenToFiat } from 'lib';
import { Text } from 'react-native-paper';
import { FiatValue } from '~/components/fiat/FiatValue';

Expand All @@ -27,12 +27,7 @@ export function AccountValue(props: AccountValueProps) {
const { tokens } = useFragment(FragmentDoc, props.tokensQuery);

const total = tokens.reduce(
(sum, token) =>
getTokenValue({
amount: token.balance,
price: token.price?.current ?? 0,
decimals: token.decimals,
}) + sum,
(sum, token) => tokenToFiat(token.balance, token.price?.current ?? 0, token.decimals) + sum,
0,
);

Expand Down
8 changes: 2 additions & 6 deletions app/src/screens/home/Tabs/TokensTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { withSuspense } from '~/components/skeleton/withSuspense';
import { TabScreenSkeleton } from '~/components/tab/TabScreenSkeleton';
import { StyleSheet } from 'react-native';
import { Text } from 'react-native-paper';
import { Address, getTokenValue } from 'lib';
import { Address, tokenToFiat } from 'lib';
import { gql } from '@api/gen';
import { useSuspenseQuery } from '@apollo/client';
import { TokensTabQuery, TokensTabQueryVariables } from '@api/gen/graphql';
Expand Down Expand Up @@ -42,11 +42,7 @@ export const TokensTab = withSuspense(
const tokens = data.tokens
.map((t) => ({
...t,
value: getTokenValue({
amount: t.balance,
price: t.price?.current ?? 0,
decimals: t.decimals,
}),
value: tokenToFiat(t.balance, t.price?.current ?? 0, t.decimals),
}))
.sort((a, b) => b.value - a.value);

Expand Down
17 changes: 4 additions & 13 deletions app/src/screens/proposal/TransactionTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,16 @@ import { Timestamp } from '~/components/format/Timestamp';
import { ListItem, ListItemProps } from '~/components/list/ListItem';
import { withSuspense } from '~/components/skeleton/withSuspense';
import { TabScreenSkeleton } from '~/components/tab/TabScreenSkeleton';
import { TokenAmount } from '~/components/token/TokenAmount2';
import { TokenAmount } from '~/components/token/TokenAmount';
import { TokenIcon } from '~/components/token/TokenIcon/TokenIcon';
import { TabNavigatorScreenProp } from './Tabs';
import { makeStyles } from '@theme/makeStyles';
import { Hex, asBigInt } from 'lib';
import { Hex, asBigInt, tokenToFiat } from 'lib';
import { ReactNode } from 'react';
import { gql, useFragment } from '@api/gen';
import { useSuspenseQuery } from '@apollo/client';
import { TransactionTabQuery, TransactionTabQueryVariables } from '@api/gen/graphql';
import { TransactionTabDocument, useTransactionTabSubscriptionSubscription } from '@api/generated';
import { getTokenValue } from '@token/token';

const FragmentDoc = gql(/* GraphQL */ `
fragment TransactionTab_TransactionProposalFragment on TransactionProposal {
Expand Down Expand Up @@ -177,11 +176,7 @@ export const TransactionTab = withSuspense(({ route }: TransactionTabProps) => {
supporting={<TokenAmount token={p.feeToken} amount={actualFee} />}
trailing={
<FiatValue
value={getTokenValue({
amount: actualFee,
price: p.feeToken.price?.current ?? 0,
decimals: p.feeToken.decimals,
})}
value={tokenToFiat(actualFee, p.feeToken.price?.current ?? 0, p.feeToken.decimals)}
/>
}
/>
Expand All @@ -192,11 +187,7 @@ export const TransactionTab = withSuspense(({ route }: TransactionTabProps) => {
supporting={<TokenAmount token={p.feeToken} amount={estimatedFee} />}
trailing={
<FiatValue
value={getTokenValue({
amount: estimatedFee,
price: p.feeToken.price?.current ?? 0,
decimals: p.feeToken.decimals,
})}
value={tokenToFiat(estimatedFee, p.feeToken.price?.current ?? 0, p.feeToken.decimals)}
/>
}
/>
Expand Down
4 changes: 2 additions & 2 deletions app/src/screens/send/SendScreen.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { usePropose } from '@api/proposal';
import { CloseIcon } from '@theme/icons';
import { parseUnits } from 'ethers/lib/utils';
import { Address, FIAT_DECIMALS, valueAsTokenAmount } from 'lib';
import { Address, FIAT_DECIMALS, fiatToToken } from 'lib';
import { useState } from 'react';
import { StyleSheet, View } from 'react-native';
import { Appbar, Divider } from 'react-native-paper';
Expand Down Expand Up @@ -72,7 +72,7 @@ export const SendScreen = withSuspense(
const tokenAmount =
type === InputType.Token
? parseUnits(inputAmount, token.decimals).toBigInt()
: valueAsTokenAmount(parseFloat(inputAmount), token.price?.current ?? 0, token.decimals);
: fiatToToken(parseFloat(inputAmount), token.price?.current ?? 0, token.decimals);

return (
<Screen>
Expand Down
4 changes: 2 additions & 2 deletions app/src/screens/swap/SwapScreen.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { usePropose } from '@api/proposal';
import { parseUnits } from 'ethers/lib/utils';
import { Address, FIAT_DECIMALS, valueAsTokenAmount } from 'lib';
import { Address, FIAT_DECIMALS, fiatToToken } from 'lib';
import { useState } from 'react';
import { InputType, InputsView } from '~/components/InputsView';
import { ScreenSkeleton } from '~/components/skeleton/ScreenSkeleton';
Expand Down Expand Up @@ -88,7 +88,7 @@ export const SwapScreen = withSuspense(({ route, navigation: { navigate } }: Swa
const fromAmount =
type === InputType.Token
? parseUnits(fromInput, from.decimals).toBigInt()
: valueAsTokenAmount(parseFloat(fromInput), from.price?.current ?? 0, from.decimals);
: fiatToToken(parseFloat(fromInput), from.price?.current ?? 0, from.decimals);

return (
<Screen>
Expand Down
4 changes: 2 additions & 2 deletions app/src/screens/swap/ToTokenItem.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { FragmentType, gql, useFragment } from '@api/gen';
import { Address, convertToDecimals } from 'lib';
import { Address, tokenToToken } from 'lib';
import { useFormattedNumber } from '~/components/format/FormattedNumber';
import { ListItem, ListItemProps } from '~/components/list/ListItem';
import { ListItemSkeleton } from '~/components/list/ListItemSkeleton';
Expand Down Expand Up @@ -62,7 +62,7 @@ function ToTokenItem({
from: { token: from.address, amount: ratioFromAmount },
});
const ratio =
(ratioToAmount * RATIO_FACTOR) / convertToDecimals(ratioFromAmount, from.decimals, to.decimals);
(ratioToAmount * RATIO_FACTOR) / tokenToToken(ratioFromAmount, from.decimals, to.decimals);

return (
<ListItem
Expand Down
Loading

0 comments on commit 470e42f

Please sign in to comment.