Skip to content

Commit

Permalink
Refactor balances.ts to use protobuf types
Browse files Browse the repository at this point in the history
  • Loading branch information
grod220 committed Feb 5, 2024
1 parent 48bc192 commit 7d4e776
Show file tree
Hide file tree
Showing 61 changed files with 886 additions and 603 deletions.
6 changes: 3 additions & 3 deletions apps/extension/src/routes/popup/home/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { IndexHeader } from './index-header';
import { useStore } from '../../../state';
import { BlockSync } from './block-sync';
import { localExtStorage, sessionExtStorage } from '@penumbra-zone/storage';
import { accountAddrSelector } from '../../../state/wallets';
import { addrByIndexSelector } from '../../../state/wallets';

export interface PopupLoaderData {
lastBlockSynced: number;
Expand All @@ -32,14 +32,14 @@ export const popupIndexLoader = async (): Promise<Response | PopupLoaderData> =>
};

export const PopupIndex = () => {
const getAccount = useStore(accountAddrSelector);
const getAccount = useStore(addrByIndexSelector);

return (
<div className='relative flex h-full flex-col items-stretch justify-start bg-left-bottom px-[30px]'>
<div className='absolute bottom-[50px] left-[-10px] -z-10 h-[715px] w-[900px] overflow-hidden bg-logo opacity-10' />
<IndexHeader />
<div className='my-32'>
<SelectAccount getAccount={getAccount} />
<SelectAccount getAddrByIndex={getAccount} />
</div>
<BlockSync />
</div>
Expand Down
23 changes: 10 additions & 13 deletions apps/extension/src/state/wallets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import {
} from '@penumbra-zone/wasm-ts';
import { Key } from '@penumbra-zone/crypto-web';
import { ExtensionStorage, LocalStorageState } from '@penumbra-zone/storage';
import { Wallet, WalletCreate, bech32Address } from '@penumbra-zone/types';
import { Wallet, WalletCreate } from '@penumbra-zone/types';
import { Address } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/keys/v1alpha1/keys_pb';

export interface WalletsSlice {
all: Wallet[];
Expand Down Expand Up @@ -74,17 +75,13 @@ export const createWalletsSlice =
export const walletsSelector = (state: AllSlices) => state.wallets;
export const getActiveWallet = (state: AllSlices) => state.wallets.all[0];

export const accountAddrSelector = (state: AllSlices) => (index: number, ephemeral: boolean) => {
const active = getActiveWallet(state);
if (!active) return;
export const addrByIndexSelector =
(state: AllSlices) =>
(index: number, ephemeral: boolean): Address => {
const active = getActiveWallet(state);
if (!active) throw new Error('No active wallet');

const addr = ephemeral
? getEphemeralByIndex(active.fullViewingKey, index)
: getAddressByIndex(active.fullViewingKey, index);
const bech32Addr = bech32Address(addr);

return {
address: bech32Addr,
index,
return ephemeral
? getEphemeralByIndex(active.fullViewingKey, index)
: getAddressByIndex(active.fullViewingKey, index);
};
};
2 changes: 2 additions & 0 deletions apps/webapp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,14 @@
"@radix-ui/react-icons": "^1.3.0",
"@tanstack/react-query": "^5.18.1",
"bignumber.js": "^9.1.2",
"immer": "^10.0.3",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-helmet": "^6.1.0",
"react-loader-spinner": "^6.1.6",
"react-router-dom": "^6.22.0",
"tailwindcss": "^3.4.1",
"zod": "^3.22.4",
"zustand": "^4.5.0"
},
"devDependencies": {
Expand Down
2 changes: 1 addition & 1 deletion apps/webapp/src/clients/penumbra-port.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ declare global {
export const getPenumbraPort = (serviceTypeName: string) => {
const { port1: port, port2: transferPort } = new MessageChannel();
if (!(penumbra in window)) throw Error('No Penumbra global (extension not installed)');
const initPort = window[penumbra].services?.[serviceTypeName];
const initPort = window[penumbra]?.services?.[serviceTypeName];
if (!initPort) throw Error(`No init port for service ${serviceTypeName}`);
initPort.postMessage(
{
Expand Down
112 changes: 53 additions & 59 deletions apps/webapp/src/components/dashboard/assets-table.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,23 @@
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@penumbra-zone/ui';
import { displayUsd, fromBaseUnitAmountAndMetadata } from '@penumbra-zone/types';
import { LoaderFunction, useLoaderData } from 'react-router-dom';
import { throwIfExtNotInstalled } from '../../fetchers/is-connected.ts';
import { AccountBalance, getBalancesByAccount } from '../../fetchers/balances.ts';
import { AssetIcon } from '../shared/asset-icon.tsx';
import { AddressIcon } from '@penumbra-zone/ui/components/ui/address-icon';
import { Address } from '@penumbra-zone/ui/components/ui/address.tsx';
import { AddressComponent } from '@penumbra-zone/ui/components/ui/address-component';
import {
AccountGroupedBalances,
getBalancesByAccount,
} from '../../fetchers/balances/by-account.ts';
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@penumbra-zone/ui';
import { AssetIcon } from '../shared/asset-icon.tsx';
import { displayUsd, hasDenomMetadata } from '@penumbra-zone/types';
import { ValueViewComponent } from '@penumbra-zone/ui/components/ui/tx/view/value.tsx';

export const AssetsLoader: LoaderFunction = async (): Promise<AccountBalance[]> => {
export const AssetsLoader: LoaderFunction = async (): Promise<AccountGroupedBalances[]> => {
throwIfExtNotInstalled();
return await getBalancesByAccount();
};

export default function AssetsTable() {
const data = useLoaderData() as AccountBalance[];
const data = useLoaderData() as AccountGroupedBalances[];

if (data.length === 0) {
return (
Expand All @@ -31,73 +35,63 @@ export default function AssetsTable() {

return (
<div className='flex flex-col gap-6'>
{data.map(a => {
return (
<div key={a.index} className='flex flex-col gap-4'>
<div className='flex flex-col items-center justify-center'>
<div className='flex flex-col items-center justify-center gap-2 md:flex-row'>
<div className='flex items-center gap-2'>
<AddressIcon address={a.address} size={20} />
<h2 className='font-bold md:text-base xl:text-xl'>Account #{a.index}</h2>{' '}
</div>

<Address address={a.address} />
{data.map((a, index) => (
<div key={index} className='flex flex-col gap-4'>
<div className='flex flex-col items-center justify-center'>
<div className='flex flex-col items-center justify-center gap-2 md:flex-row'>
<div className='flex items-center gap-2'>
<AddressIcon address={a.address} size={20} />
<h2 className='font-bold md:text-base xl:text-xl'>Account #{a.index.account}</h2>
</div>
<AddressComponent address={a.address} />
</div>
<div className='flex flex-col gap-[34px] md:hidden'>
{a.balances.map((asset, i) => (
<div key={i} className='flex items-center justify-between border-b pb-3'>
<div className='flex items-center gap-2'>
<AssetIcon name={asset.metadata.display} />
<p className='font-mono text-base font-bold'>{asset.metadata.display}</p>
</div>
<p className='font-mono text-base font-bold'>
{fromBaseUnitAmountAndMetadata(asset.amount, asset.metadata).toFormat()}
</p>
<p className='font-mono text-base font-bold'>
{asset.usdcValue == 0 ? '$–' : `$${displayUsd(asset.usdcValue)}`}
</p>
</div>
))}
</div>
<Table className='hidden md:table'>
<TableHeader>
<TableRow>
<TableHead className='w-1/3 text-center'>Asset</TableHead>
<TableHead className='w-1/3 text-center'>Balance</TableHead>
<TableHead className='w-1/3 text-center'>Value</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{a.balances.map((asset, i) => (
<TableRow key={i}>
</div>

<Table className='md:table'>
<TableHeader>
<TableRow>
<TableHead className='w-1/3 text-center'>Asset</TableHead>
<TableHead className='w-1/3 text-center'>Balance</TableHead>
<TableHead className='w-1/3 text-center'>Value</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{a.balances.map((assetBalance, index) => {
return (
<TableRow key={index}>
<TableCell className='w-1/3'>
<div className='flex items-center gap-2'>
<AssetIcon name={asset.metadata.display} />
<p className='font-mono text-base font-bold'>{asset.metadata.display}</p>
{hasDenomMetadata(assetBalance.value) && (
<>
<AssetIcon metadata={assetBalance.value.valueView.value.metadata} />
<p className='font-mono text-base font-bold'>
{assetBalance.value.valueView.value.metadata.display}
</p>
</>
)}
</div>
</TableCell>
<TableCell className='w-1/3 text-center font-mono'>
<div className='flex flex-col'>
<p className='text-base font-bold'>
{fromBaseUnitAmountAndMetadata(asset.amount, asset.metadata).toFormat()}
</p>
</div>
<p className='flex flex-col items-center text-base font-bold'>
<ValueViewComponent view={assetBalance.value} showDenom={false} />
</p>
</TableCell>
<TableCell className='w-1/3 text-center font-mono'>
<div className='flex flex-col'>
<p className='text-base font-bold'>
{asset.usdcValue == 0 ? '$–' : `$${displayUsd(asset.usdcValue)}`}
{assetBalance.usdcValue == 0
? '$–'
: `$${displayUsd(assetBalance.usdcValue)}`}
</p>
</div>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
);
})}
);
})}
</TableBody>
</Table>
</div>
))}
</div>
);
}
20 changes: 8 additions & 12 deletions apps/webapp/src/components/send/ibc/ibc-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,28 +8,24 @@ import InputToken from '../../shared/input-token';
import { sendValidationErrors } from '../../../state/send';
import { useMemo } from 'react';
import { LoaderFunction, useLoaderData } from 'react-router-dom';
import { AccountBalance, getBalancesByAccount } from '../../../fetchers/balances';
import { AssetBalance, getAssetBalances } from '../../../fetchers/balances/balances.ts';
import { penumbraAddrValidation } from '../helpers';

export const IbcAssetBalanceLoader: LoaderFunction = async (): Promise<AccountBalance[]> => {
const balancesByAccount = await getBalancesByAccount();
export const IbcAssetBalanceLoader: LoaderFunction = async (): Promise<AssetBalance[]> => {
const balancesByAccount = await getAssetBalances();

if (balancesByAccount[0]) {
// set initial account if accounts exist and asset if account has asset list
useStore.setState(state => {
state.ibc.selection = {
address: balancesByAccount[0]?.address,
accountIndex: balancesByAccount[0]?.index,
asset: balancesByAccount[0]?.balances[0],
};
state.ibc.selection = balancesByAccount[0];
});
}

return balancesByAccount;
};

export default function IbcForm() {
const accountBalances = useLoaderData() as AccountBalance[];
const accountBalances = useLoaderData() as AssetBalance[];
const { toast } = useToast();
const {
sendIbcWithdraw,
Expand All @@ -42,8 +38,8 @@ export default function IbcForm() {
} = useStore(ibcSelector);

const validationErrors = useMemo(() => {
return sendValidationErrors(selection?.asset, amount, destinationChainAddress);
}, [selection?.asset, amount, destinationChainAddress]);
return sendValidationErrors(selection, amount, destinationChainAddress);
}, [selection, amount, destinationChainAddress]);

return (
<form
Expand Down Expand Up @@ -95,7 +91,7 @@ export default function IbcForm() {
!Number(amount) ||
!destinationChainAddress ||
!!Object.values(validationErrors).find(Boolean) ||
!selection?.asset
!selection
}
>
Send
Expand Down
4 changes: 2 additions & 2 deletions apps/webapp/src/components/send/receive.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { SelectAccount } from '@penumbra-zone/ui';
import { getAccountAddr } from '../../fetchers/address';
import { getAddrByIndex } from '../../fetchers/address';

export const Receive = () => {
return (
<div className='pb-3 md:pb-5'>
<SelectAccount getAccount={getAccountAddr} />
<SelectAccount getAddrByIndex={getAddrByIndex} />
</div>
);
};
20 changes: 8 additions & 12 deletions apps/webapp/src/components/send/send-form/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,34 +4,30 @@ import { sendSelector, sendValidationErrors } from '../../../state/send.ts';
import { useToast } from '@penumbra-zone/ui/components/ui/use-toast';
import { InputBlock } from '../../shared/input-block';
import { LoaderFunction, useLoaderData } from 'react-router-dom';
import { AccountBalance, getBalancesByAccount } from '../../../fetchers/balances';
import { AssetBalance, getAssetBalances } from '../../../fetchers/balances/balances.ts';
import { useMemo } from 'react';
import { penumbraAddrValidation } from '../helpers';
import { throwIfExtNotInstalled } from '../../../fetchers/is-connected';
import InputToken from '../../shared/input-token.tsx';
import { useRefreshFee } from './use-refresh-fee.ts';
import { GasFee } from '../../shared/gas-fee.tsx';

export const SendAssetBalanceLoader: LoaderFunction = async (): Promise<AccountBalance[]> => {
export const SendAssetBalanceLoader: LoaderFunction = async (): Promise<AssetBalance[]> => {
throwIfExtNotInstalled();
const balancesByAccount = await getBalancesByAccount();
const balancesByAccount = await getAssetBalances();

if (balancesByAccount[0]) {
// set initial account if accounts exist and asset if account has asset list
useStore.setState(state => {
state.send.selection = {
address: balancesByAccount[0]?.address,
accountIndex: balancesByAccount[0]?.index,
asset: balancesByAccount[0]?.balances[0],
};
state.send.selection = balancesByAccount[0];
});
}

return balancesByAccount;
};

export const SendForm = () => {
const accountBalances = useLoaderData() as AccountBalance[];
const accountBalances = useLoaderData() as AssetBalance[];
const { toast } = useToast();
const {
selection,
Expand All @@ -52,8 +48,8 @@ export const SendForm = () => {
useRefreshFee();

const validationErrors = useMemo(() => {
return sendValidationErrors(selection?.asset, amount, recipient);
}, [selection?.asset, amount, recipient]);
return sendValidationErrors(selection, amount, recipient);
}, [selection, amount, recipient]);

return (
<form
Expand Down Expand Up @@ -128,7 +124,7 @@ export const SendForm = () => {
!recipient ||
!!Object.values(validationErrors).find(Boolean) ||
txInProgress ||
!selection?.asset
!selection
}
>
Send
Expand Down
14 changes: 10 additions & 4 deletions apps/webapp/src/components/shared/asset-icon.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,27 @@
import { localAssets } from '@penumbra-zone/constants';
import { Identicon } from '@penumbra-zone/ui';
import { useMemo } from 'react';
import { Metadata } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/asset/v1alpha1/asset_pb';

export const AssetIcon = ({ name }: { name: string }) => {
export const AssetIcon = ({ metadata }: { metadata: Metadata }) => {
const icon = useMemo(() => {
const assetImage = localAssets.find(i => i.display === name)?.images[0];
const assetImage = localAssets.find(d => d.display === metadata.display)?.images[0];
// Image default is "" and thus cannot do nullish-coalescing
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
return assetImage?.png || assetImage?.svg;
}, [name]);
}, [metadata]);

return (
<>
{icon ? (
<img className='size-6 rounded-full' src={icon} alt='Asset icon' />
) : (
<Identicon name={name} size={24} className='rounded-full' type='solid' />
<Identicon
uniqueIdentifier={metadata.display}
size={24}
className='rounded-full'
type='solid'
/>
)}
</>
);
Expand Down
Loading

0 comments on commit 7d4e776

Please sign in to comment.