Skip to content

Commit 0b4045b

Browse files
committed
wip
1 parent 1616b7f commit 0b4045b

File tree

16 files changed

+867
-12
lines changed

16 files changed

+867
-12
lines changed

apps/dashboard/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
"@radix-ui/react-separator": "^1.1.1",
4646
"@radix-ui/react-slot": "^1.1.1",
4747
"@radix-ui/react-switch": "^1.1.2",
48+
"@radix-ui/react-tabs": "^1.1.2",
4849
"@radix-ui/react-tooltip": "1.1.7",
4950
"@sentry/nextjs": "8.51.0",
5051
"@shazow/whatsabi": "^0.19.0",
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
"use server";
2+
3+
import assert from "node:assert";
4+
import type { TokenDetails } from "../hooks/useGetERC20Tokens";
5+
6+
interface SimpleHashResponse {
7+
fungibles: {
8+
name: string;
9+
symbol: string;
10+
decimals: number;
11+
total_quantity_string: string;
12+
total_value_usd_string: string;
13+
}[];
14+
next_cursor: string | null;
15+
}
16+
17+
export async function fetchERC20Tokens(args: {
18+
chainId: number;
19+
address: string;
20+
}): Promise<TokenDetails[]> {
21+
try {
22+
const { SIMPLEHASH_API_KEY } = process.env;
23+
assert(SIMPLEHASH_API_KEY, "SIMPLEHASH_API_KEY is not set");
24+
const { chainId, address } = args;
25+
26+
const response = await fetch(
27+
`https://api.simplehash.com/api/v0/fungibles/balances?chains=eip155:${chainId}&wallet_addresses=${address}&include_fungible_details=1&include_prices=1&count=0&limit=50&order_by=last_transferred_date__desc&include_native_tokens=0`,
28+
{
29+
headers: { "X-API-KEY": SIMPLEHASH_API_KEY },
30+
},
31+
);
32+
if (!response.ok) {
33+
throw new Error(
34+
`Unexpected status ${response.status}: ${await response.text()}`,
35+
);
36+
}
37+
38+
const data: SimpleHashResponse = await response.json();
39+
return data.fungibles.map((token) => ({
40+
name: token.name,
41+
symbol: token.symbol,
42+
decimals: token.decimals,
43+
balance: token.total_quantity_string,
44+
totalValueUsdString: token.total_value_usd_string,
45+
}));
46+
} catch (error) {
47+
console.error("Error fetching tokens:", error);
48+
return [];
49+
}
50+
}
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
"use server";
2+
3+
import assert from "node:assert";
4+
import type { NFTDetails } from "../hooks/useGetNFTs";
5+
6+
interface Previews {
7+
image_small_url: string;
8+
image_medium_url: string;
9+
image_large_url: string;
10+
image_opengraph_url: string;
11+
blurhash: string;
12+
predominant_color: string;
13+
}
14+
15+
interface Owner {
16+
owner_address: string;
17+
quantity: number;
18+
quantity_string: string;
19+
first_acquired_date: string;
20+
last_acquired_date: string;
21+
}
22+
23+
interface Contract {
24+
type: string;
25+
name: string;
26+
symbol: string;
27+
deployed_by: string;
28+
deployed_via_contract: null | string;
29+
owned_by: string;
30+
has_multiple_collections: boolean;
31+
has_erc5643_subscription_standard: boolean;
32+
}
33+
34+
interface MarketplacePage {
35+
marketplace_id: string;
36+
marketplace_name: string;
37+
marketplace_collection_id: string;
38+
nft_url?: string;
39+
collection_url: string;
40+
verified: boolean | null;
41+
}
42+
43+
interface PaymentToken {
44+
payment_token_id: string;
45+
name: string;
46+
symbol: string;
47+
address: null | string;
48+
decimals: number;
49+
}
50+
51+
interface FloorPrice {
52+
marketplace_id: string;
53+
marketplace_name: string;
54+
value: string;
55+
payment_token: PaymentToken;
56+
value_usd_cents: number;
57+
}
58+
59+
interface RoyaltyRecipient {
60+
address: string;
61+
percentage: number;
62+
basis_points: number;
63+
}
64+
65+
interface Royalty {
66+
source: string;
67+
total_creator_fee_basis_points: number;
68+
recipients: RoyaltyRecipient[];
69+
}
70+
71+
interface Collection {
72+
collection_id: string;
73+
name: string;
74+
description: string;
75+
image_url: string;
76+
banner_image_url: string | null;
77+
category: string | null;
78+
is_nsfw: boolean;
79+
external_url: string | null;
80+
twitter_username: string | null;
81+
discord_url: string | null;
82+
instagram_username: string | null;
83+
medium_username: string | null;
84+
telegram_url: string | null;
85+
marketplace_pages: MarketplacePage[];
86+
floor_prices: FloorPrice[];
87+
top_bids: any[];
88+
distinct_owner_count: number;
89+
distinct_nft_count: number;
90+
total_quantity: number;
91+
chains: string[];
92+
top_contracts: string[];
93+
collection_royalties: Royalty[];
94+
}
95+
96+
interface Sale {
97+
from_address: string | null;
98+
to_address: string;
99+
quantity: number;
100+
quantity_string: string;
101+
timestamp: string;
102+
transaction: string;
103+
marketplace_id: string;
104+
marketplace_name: string;
105+
is_bundle_sale: boolean;
106+
payment_token: PaymentToken;
107+
unit_price: string;
108+
total_price: string;
109+
unit_price_usd_cents: number;
110+
}
111+
112+
interface Attribute {
113+
trait_type: string;
114+
value: string;
115+
display_type: null | string;
116+
}
117+
118+
interface ExtraMetadata {
119+
attributes: Attribute[];
120+
tokenId: number;
121+
image_original_url: string;
122+
animation_original_url: null | string;
123+
metadata_original_url: string;
124+
}
125+
126+
interface NFT {
127+
nft_id: string;
128+
chain: string;
129+
contract_address: string;
130+
token_id: string;
131+
name: string;
132+
description: string;
133+
previews: Previews;
134+
image_url: string;
135+
background_color: null | string;
136+
external_url: null | string;
137+
created_date: string;
138+
status: string;
139+
token_count: number;
140+
owner_count: number;
141+
owners: Owner[];
142+
contract: Contract;
143+
collection: Collection;
144+
last_sale: Sale | null;
145+
primary_sale: Sale | null;
146+
royalty: Royalty[];
147+
extra_metadata: ExtraMetadata;
148+
queried_wallet_balances: Owner[];
149+
}
150+
151+
export interface SimpleHashResponse {
152+
next_cursor: null | string;
153+
next: null | string;
154+
previous: null | string;
155+
nfts: NFT[];
156+
}
157+
158+
export async function fetchNFTs(args: {
159+
chainId: number;
160+
address: string;
161+
}): Promise<NFTDetails[]> {
162+
try {
163+
const { SIMPLEHASH_API_KEY } = process.env;
164+
assert(SIMPLEHASH_API_KEY, "SIMPLEHASH_API_KEY is not set");
165+
const { chainId, address } = args;
166+
167+
const response = await fetch(
168+
`https://api.simplehash.com/api/v0/nfts/owners_v2?chains=eip155:${chainId}&wallet_addresses=${address}&queried_wallet_balances=1&filters=spam_score__lt%3D50&count=0&order_by=transfer_time__desc&limit=50`,
169+
{
170+
headers: { "X-API-KEY": SIMPLEHASH_API_KEY },
171+
},
172+
);
173+
if (!response.ok) {
174+
throw new Error(
175+
`Unexpected status ${response.status}: ${await response.text()}`,
176+
);
177+
}
178+
179+
const data: SimpleHashResponse = await response.json();
180+
return data.nfts.map((token) => ({
181+
name: token.name,
182+
contractAddress: token.contract_address,
183+
tokenId: token.token_id,
184+
imageUrl: token.previews.image_medium_url,
185+
blurHash: token.previews.blurhash,
186+
}));
187+
} catch (error) {
188+
console.error("Error fetching tokens:", error);
189+
return [];
190+
}
191+
}
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
import { Spinner } from "@/components/ui/Spinner/Spinner";
2+
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
3+
import {
4+
Table,
5+
TableBody,
6+
TableCell,
7+
TableHead,
8+
TableHeader,
9+
TableRow,
10+
} from "@/components/ui/table";
11+
import { TabButtons } from "@/components/ui/tabs";
12+
import {} from "@radix-ui/react-tabs";
13+
import { useState } from "react";
14+
15+
interface Transaction {
16+
id: number;
17+
type: string;
18+
amount: string;
19+
to?: string;
20+
from?: string;
21+
contract?: string;
22+
method?: string;
23+
date: string;
24+
}
25+
26+
interface Contract {
27+
address: string;
28+
name: string;
29+
lastInteraction: string;
30+
}
31+
32+
interface ActivityOverviewProps {
33+
transactions: Transaction[];
34+
contracts: Contract[];
35+
isLoading: boolean;
36+
}
37+
38+
export function ActivityOverview({
39+
transactions,
40+
contracts,
41+
isLoading,
42+
}: ActivityOverviewProps) {
43+
const [activeTab, setActiveTab] = useState<"transactions" | "contracts">(
44+
"transactions",
45+
);
46+
47+
return (
48+
<Card>
49+
<CardHeader>
50+
<CardTitle>Activity</CardTitle>
51+
</CardHeader>
52+
<CardContent>
53+
<TabButtons
54+
tabs={[
55+
{
56+
name: "Transactions",
57+
isActive: activeTab === "transactions",
58+
isEnabled: true,
59+
onClick: () => setActiveTab("transactions"),
60+
},
61+
{
62+
name: "Contracts",
63+
isActive: activeTab === "contracts",
64+
isEnabled: true,
65+
onClick: () => setActiveTab("contracts"),
66+
},
67+
]}
68+
tabClassName="font-medium !text-sm"
69+
/>
70+
71+
{isLoading ? (
72+
<Spinner />
73+
) : activeTab === "transactions" ? (
74+
<Table>
75+
<TableHeader>
76+
<TableRow>
77+
<TableHead>Type</TableHead>
78+
<TableHead>Amount</TableHead>
79+
<TableHead>Details</TableHead>
80+
<TableHead>Date</TableHead>
81+
</TableRow>
82+
</TableHeader>
83+
<TableBody>
84+
{transactions.map((tx) => (
85+
<TableRow key={tx.id}>
86+
<TableCell>{tx.type}</TableCell>
87+
<TableCell>{tx.amount}</TableCell>
88+
<TableCell>
89+
{tx.to && `To: ${tx.to}`}
90+
{tx.from && `From: ${tx.from}`}
91+
{tx.contract && `Contract: ${tx.contract}`}
92+
{tx.method && ` Method: ${tx.method}`}
93+
</TableCell>
94+
<TableCell>{tx.date}</TableCell>
95+
</TableRow>
96+
))}
97+
</TableBody>
98+
</Table>
99+
) : activeTab === "contracts" ? (
100+
<Table>
101+
<TableHeader>
102+
<TableRow>
103+
<TableHead>Name</TableHead>
104+
<TableHead>Address</TableHead>
105+
<TableHead>Last Interaction</TableHead>
106+
</TableRow>
107+
</TableHeader>
108+
<TableBody>
109+
{contracts.map((contract, index) => (
110+
<TableRow key={index}>
111+
<TableCell>{contract.name}</TableCell>
112+
<TableCell>{contract.address}</TableCell>
113+
<TableCell>{contract.lastInteraction}</TableCell>
114+
</TableRow>
115+
))}
116+
</TableBody>
117+
</Table>
118+
) : null}
119+
</CardContent>
120+
</Card>
121+
);
122+
}

0 commit comments

Comments
 (0)