Skip to content

Commit

Permalink
Multchain NFT Wallet Token List (#20)
Browse files Browse the repository at this point in the history
  • Loading branch information
nguyenlejoe authored Dec 5, 2023
1 parent 862b990 commit f559e31
Show file tree
Hide file tree
Showing 6 changed files with 139 additions and 55 deletions.
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,13 @@ Be sure to secure your key to prevent unauthorized use in the Covalent platform
<AddressActivityListView address="0xfc43f5f9dd45258b3aff31bdbe6561d97e8b71de" />
<NFTWalletTokenListView
address="0xfc43f5f9dd45258b3aff31bdbe6561d97e8b71de"
chain_name="eth-mainnet"
chain_names={[
"eth-mainnet",
"matic-mainnet",
"bsc-mainnet",
"avalanche-mainnet",
"optimism-mainnet",
]}
/>
</GoldRushProvider>
```
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,23 @@ export default meta;

export const NFTWalletTokenList: Story = {
args: {
chain_name: "eth-mainnet",
chain_names: [
"eth-mainnet",
],
address: "0x1ae705a28f1cca0363b5d709159220aa2fe551de",
},
};

export const MultiChainNFTWalletTokenList: Story = {
args: {
chain_names: [
"eth-mainnet",
"matic-mainnet",
"bsc-mainnet",
"avalanche-mainnet",
"optimism-mainnet",
],
address: "0x1ae705a28f1cca0363b5d709159220aa2fe551de",
}
};

Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { GRK_SIZES } from "@/utils/constants/shared.constants";
import { type Option, Some, None } from "@/utils/option";
import type {
ChainItem} from "@covalenthq/client-sdk";
import {
prettifyCurrency,
type NftTokenContractBalanceItem,
type NftTokenContractBalanceItem
} from "@covalenthq/client-sdk";
import { useEffect, useState } from "react";
import {
Expand All @@ -16,35 +18,70 @@ import { AccountCardView } from "@/components/Molecules/AccountCardView/AccountC
import { Skeleton } from "@/components/ui/skeleton";
import { useGoldrush } from "@/utils/store/Goldrush";
import { type NFTWalletTokenListViewProps } from "@/utils/types/organisms.types";
import { TokenAvatar } from "@/components/Atoms/TokenAvatar/TokenAvatar";

export const NFTWalletTokenListView: React.FC<NFTWalletTokenListViewProps> = ({
chain_name,
chain_names,
address,
}) => {
const [maybeResult, setResult] =
useState<Option<NftTokenContractBalanceItem[]>>(None);
const { covalentClient } = useGoldrush();
const [error, setError] = useState({ error: false, error_message: "" });
const [allChains, setAllChains] = useState<Option<ChainItem[]>>(None);

useEffect(() => {
(async () => {
const handleAllChains = async () => {
const allChainsResp = await covalentClient.BaseService.getAllChains();
setAllChains(new Some(allChainsResp.data.items));
};

const handleNftsToken = async () => {
const promises = chain_names.map(async (chain) => {
const allowedCacheChains = [
'bsc-mainnet',
'eth-mainnet',
'bsc-testnet',
'eth-sepolia',
'gnosis-mainnet',
'gnosis-testnet',
'matic-mainnet',
'matic-mumbai'
];
const cache = !allowedCacheChains.includes(chain);
let response;
try {
response = await covalentClient.NftService.getNftsForAddress(
chain_name,
address.trim()
);
response =
await covalentClient.NftService.getNftsForAddress(
chain,
address.trim(),
{
withUncached: cache
}
);

setError({ error: false, error_message: "" });
setResult(new Some(response.data.items));
} catch (exception) {
setResult(new Some([]));
return response.data.items.map((o) => {
return { ...o, chain };
});
} catch (error) {
console.error(`Error fetching balances for ${chain}:`, error);
setError({
error: response ? response.error : false,
error_message: response ? response.error_message : "",
});
return [];
}
})();
}, [chain_name, address]);
});

const results = await Promise.all(promises);
setResult(new Some(results.flat()));
};

useEffect(() => {
handleAllChains();
handleNftsToken();
}, [chain_names, address]);


return maybeResult.match({
None: () => <>Loading</>,
Expand All @@ -55,50 +92,76 @@ export const NFTWalletTokenListView: React.FC<NFTWalletTokenListViewProps> = ({
} else if (!error.error && result.length === 0) {
body = <>No results</>;
} else if (result.length > 0) {
body = result.map((items) => {
body = result.map((items: any) => {
return flatMap(items.nft_data, (it) => {
return (
<Card className="w-[230px] rounded border">
<CardContent>
<img
className={`block h-[10rem] w-full rounded-t ${it.external_data?.image_512 ? "object-cover" : "p-2"}`}
src={it.external_data?.image_512 ? it.external_data.image_512 : "https://www.datocms-assets.com/86369/1685489960-nft.svg"}
onError={(e) => {
e.currentTarget.classList.remove(
"object-cover"
);
e.currentTarget.classList.add(
"p-2"
);
e.currentTarget.src =
"https://www.datocms-assets.com/86369/1685489960-nft.svg";
}}
/>
</CardContent>
<div className="p-4">
<CardDescription>
{" "}
{items.contract_name}
</CardDescription>
<CardTitle className="truncate">
#{it.token_id?.toString()}
</CardTitle>
<div className="mt-2">
<small className="text-muted-foreground">
Est. Value
</small>
<p> {items.pretty_floor_price_quote ? items.pretty_floor_price_quote : <span>-</span>}</p>
</div>
</div>
</Card>
);
return allChains.match({
None: () => <></>,
Some: (chains) => {
const chain: ChainItem = chains.filter(
(o) => o.name === items.chain
)[0];
const chainColor = chain.color_theme.hex;
const isDarkMode = document.documentElement.classList.contains("dark");
return (
<Card className="w-[230px] rounded border">
<CardContent className="relative bg-slate-100 rounded" >
<img
className={`block h-[10rem] w-full rounded-t ${it.external_data?.image_512 ? "object-cover" : "p-2"}`}
src={it.external_data?.image_512 ? it.external_data.image_512 : "https://www.datocms-assets.com/86369/1685489960-nft.svg"}
onError={(e) => {
e.currentTarget.classList.remove(
"object-cover"
);
e.currentTarget.classList.add(
"p-2"
);
e.currentTarget.src =
"https://www.datocms-assets.com/86369/1685489960-nft.svg";
}}
/>
<div className={`w-9 h-9 flex items-center justify-center rounded-[100%] absolute -bottom-4 right-2 p-1 ${!isDarkMode ? "bg-white" : "bg-black"} tokenAvatar`}
style={{
border: `2px solid `,
borderColor: `${chainColor}`
}}
>
<TokenAvatar
is_chain_logo
size={GRK_SIZES.EXTRA_SMALL}
chain_color={chainColor}
token_url={chain.logo_url}

/>
</div>

</CardContent>
<div className="p-4">
<CardDescription>
{" "}
{items.contract_name}
</CardDescription>
<CardTitle className="truncate">
#{it.token_id?.toString()}
</CardTitle>
<div className="mt-2">
<small className="text-muted-foreground">
Est. Value
</small>
<p> {items.pretty_floor_price_quote ? items.pretty_floor_price_quote : <span>-</span>}</p>
</div>
</div>
</Card>
);
}
});

});
});
}

return (
<div className="space-y-4 ">
<div className="flex flex-wrap place-content-between gap-2">
<div className="flex flex-wrap place-content-between gap-2 ">
<AccountCardView address={address} />

<div className="w-full rounded border p-2 md:max-w-[15rem] lg:max-w-[15rem]">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,6 @@ export const TokenBalancesListView: React.FC<TokenBalancesListViewProps> = ({
const { covalentClient } = useGoldrush();
const [error, setError] = useState({ error: false, error_message: "" });
const [allChains, setAllChains] = useState<Option<ChainItem[]>>(None);

const [filterResult, setFilterResult] =
useState<Option<CrossChainBalanceItem[]>>(None);
const [windowWidth, setWindowWidth] = useState(0);
Expand Down
2 changes: 1 addition & 1 deletion src/tailwind-input.css
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@
@apply bg-slate-900;
@apply !text-white;
}
:root.dark > body * {
:root.dark > body *:not(.tokenAvatar) {
@apply !border-slate-700;
}

Expand Down
2 changes: 1 addition & 1 deletion src/utils/types/organisms.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export interface NFTWalletCollectionViewProps {
}

export interface NFTWalletTokenListViewProps {
chain_name: Chain;
chain_names: Chain[];
address: string;
}

Expand Down

0 comments on commit f559e31

Please sign in to comment.