Skip to content

Commit c06f1af

Browse files
committed
Merge remote-tracking branch 'origin/hackweek-wallet-explainer' into hackweek-wallet-explainer
2 parents 2f65b34 + dca48c9 commit c06f1af

File tree

7 files changed

+220
-60
lines changed

7 files changed

+220
-60
lines changed

apps/dashboard/src/app/(dashboard)/hackweek/[chain_id]/[address]/actions/fetchNFTs.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ interface QueriedWalletBalance {
168168
last_acquired_date: string;
169169
}
170170

171-
export interface SimpleHashResponse {
171+
interface SimpleHashResponse {
172172
next_cursor: null | string;
173173
next: null | string;
174174
previous: null | string;
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import { DASHBOARD_THIRDWEB_CLIENT_ID } from "@/constants/env";
2+
3+
interface Transaction {
4+
chain_id: number;
5+
hash: string;
6+
nonce: number;
7+
block_hash: string;
8+
block_number: number;
9+
block_timestamp: number;
10+
transaction_index: number;
11+
from_address: string;
12+
to_address: string | null;
13+
value: number;
14+
gas: number;
15+
gas_price: number | null;
16+
data: string | null;
17+
function_selector: string | null;
18+
max_fee_per_gas: number | null;
19+
max_priority_fee_per_gas: number | null;
20+
transaction_type: number | null;
21+
r: string | null;
22+
s: string | null;
23+
v: number | null;
24+
access_list_json: string | null;
25+
contract_address: string | null;
26+
gas_used: number | null;
27+
cumulative_gas_used: number | null;
28+
effective_gas_price: number | null;
29+
blob_gas_used: number | null;
30+
blob_gas_price: number | null;
31+
logs_bloom: string | null;
32+
status: boolean | null; // true for success, false for failure
33+
}
34+
35+
interface InsightsResponse {
36+
meta: {
37+
address: string;
38+
signature: string;
39+
page: number;
40+
total_items: number;
41+
total_pages: number;
42+
limit_per_chain: number;
43+
chain_ids: number[];
44+
};
45+
data: Transaction[];
46+
}
47+
48+
export async function fetchTxActivity(args: {
49+
chainId: number;
50+
address: string;
51+
limit_per_type?: number;
52+
page?: number;
53+
}): Promise<Transaction[]> {
54+
let { chainId, address, limit_per_type, page } = args;
55+
if (!limit_per_type) limit_per_type = 100;
56+
if (!page) page = 0;
57+
58+
const outgoingTxsResponse = await fetch(
59+
`https://insight.thirdweb-dev.com/v1/transactions?chain=${chainId}&filter_from_address=${address}&page=${page}&limit=${limit_per_type}&sort_by=block_number&sort_order=desc`,
60+
{
61+
headers: {
62+
"x-client-id": DASHBOARD_THIRDWEB_CLIENT_ID,
63+
},
64+
},
65+
);
66+
67+
const incomingTxsResponse = await fetch(
68+
`https://insight.thirdweb-dev.com/v1/transactions?chain=${chainId}&filter_to_address=${address}&page=${page}&limit=${limit_per_type}&sort_by=block_number&sort_order=desc`,
69+
{
70+
headers: {
71+
"x-client-id": DASHBOARD_THIRDWEB_CLIENT_ID,
72+
},
73+
},
74+
);
75+
76+
if (!outgoingTxsResponse.ok || !incomingTxsResponse.ok) {
77+
throw new Error("Failed to fetch transaction history");
78+
}
79+
80+
const outgoingTxsData: InsightsResponse = await outgoingTxsResponse.json();
81+
const incomingTxsData: InsightsResponse = await incomingTxsResponse.json();
82+
83+
return [...outgoingTxsData.data, ...incomingTxsData.data].sort(
84+
(a, b) => b.block_number - a.block_number,
85+
);
86+
}

apps/dashboard/src/app/(dashboard)/hackweek/[chain_id]/[address]/components/ActivityOverview.tsx

Lines changed: 69 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,11 @@ import {
99
TableRow,
1010
} from "@/components/ui/table";
1111
import { TabButtons } from "@/components/ui/tabs";
12-
import {} from "@radix-ui/react-tabs";
1312
import { useState } from "react";
1413

1514
interface Transaction {
16-
id: number;
17-
type: string;
15+
id: string;
16+
type: "out" | "in";
1817
amount: string;
1918
to?: string;
2019
from?: string;
@@ -43,6 +42,17 @@ export function ActivityOverview({
4342
const [activeTab, setActiveTab] = useState<"transactions" | "contracts">(
4443
"transactions",
4544
);
45+
const [currentPage, setCurrentPage] = useState(1);
46+
const itemsPerPage = 5;
47+
48+
// Calculate the index of the last transaction on the current page
49+
const lastIndex = currentPage * itemsPerPage;
50+
// Calculate the index of the first transaction on the current page
51+
const firstIndex = lastIndex - itemsPerPage;
52+
// Get the current transactions to display
53+
const currentTransactions = transactions.slice(firstIndex, lastIndex);
54+
// Calculate total pages
55+
const totalPages = Math.ceil(transactions.length / itemsPerPage);
4656

4757
return (
4858
<Card>
@@ -71,31 +81,62 @@ export function ActivityOverview({
7181
{isLoading ? (
7282
<Spinner />
7383
) : 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>
84+
<>
85+
<Table>
86+
<TableHeader>
87+
<TableRow>
88+
<TableHead>Type</TableHead>
89+
<TableHead>Amount</TableHead>
90+
<TableHead>Details</TableHead>
91+
<TableHead>Date</TableHead>
9592
</TableRow>
96-
))}
97-
</TableBody>
98-
</Table>
93+
</TableHeader>
94+
<TableBody>
95+
{currentTransactions.map((tx) => (
96+
<TableRow key={tx.id}>
97+
<TableCell>{tx.type}</TableCell>
98+
<TableCell>{tx.amount}</TableCell>
99+
<TableCell>
100+
{tx.to && `To: ${tx.to} `}
101+
{tx.from && `From: ${tx.from} `}
102+
{tx.contract && `Contract: ${tx.contract} `}
103+
{tx.method && ` Method: ${tx.method}`}
104+
</TableCell>
105+
<TableCell>{tx.date}</TableCell>
106+
</TableRow>
107+
))}
108+
</TableBody>
109+
</Table>
110+
111+
{/* Pagination Controls */}
112+
<div className="pagination">
113+
<TabButtons
114+
tabs={[
115+
{
116+
name: "Previous",
117+
isActive: currentPage === 1,
118+
isEnabled: currentPage > 1,
119+
onClick: () =>
120+
setCurrentPage((prev) => Math.max(prev - 1, 1)),
121+
},
122+
{
123+
name: `Page ${currentPage} of ${totalPages}`,
124+
isActive: true,
125+
isEnabled: false,
126+
onClick: () => {}, // No action needed
127+
},
128+
{
129+
name: "Next",
130+
isActive: currentPage === totalPages,
131+
isEnabled: currentPage < totalPages,
132+
onClick: () =>
133+
setCurrentPage((prev) => Math.min(prev + 1, totalPages)),
134+
},
135+
]}
136+
tabClassName="font-medium !text-sm"
137+
/>
138+
</div>
139+
</>
99140
) : activeTab === "contracts" ? (
100141
<Table>
101142
<TableHeader>
@@ -107,7 +148,7 @@ export function ActivityOverview({
107148
</TableHeader>
108149
<TableBody>
109150
{contracts.map((contract, index) => (
110-
<TableRow key={index}>
151+
<TableRow key={`${contract.address}-${index}`}>
111152
<TableCell>{contract.name}</TableCell>
112153
<TableCell>{contract.address}</TableCell>
113154
<TableCell>{contract.lastInteraction}</TableCell>

apps/dashboard/src/app/(dashboard)/hackweek/[chain_id]/[address]/components/TokenHoldings.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,11 @@ export function TokenHoldings({
6363
) : activeTab === "nft" ? (
6464
<div className="mt-4 grid grid-cols-2 gap-4 md:grid-cols-3 lg:grid-cols-4">
6565
{nfts.map((nft, idx) => (
66-
<NFTCard key={idx} nft={nft} chain={chain} />
66+
<NFTCard
67+
key={`${nft.contractAddress}-${idx}`}
68+
nft={nft}
69+
chain={chain}
70+
/>
6771
))}
6872
</div>
6973
) : null}

apps/dashboard/src/app/(dashboard)/hackweek/[chain_id]/[address]/components/WalletDashboard.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import type { ChainMetadata } from "thirdweb/chains";
33
import { useGetERC20Tokens } from "../hooks/useGetERC20Tokens";
44
import { useGetNFTs } from "../hooks/useGetNFTs";
5+
import { useGetTxActivity } from "../hooks/useGetTxActivity";
56
import { mockWalletData } from "../utils/mockData";
67
import { ActivityOverview } from "./ActivityOverview";
78
import { NebulaInterface } from "./NebulaInterface";
@@ -19,6 +20,10 @@ export function WalletDashboard(props: {
1920
props.chain.chainId,
2021
props.address,
2122
);
23+
const { txActivity, isLoading: isLoadingActivity } = useGetTxActivity(
24+
props.chain.chainId,
25+
props.address,
26+
);
2227

2328
return (
2429
<div className="grid gap-6">
@@ -34,7 +39,8 @@ export function WalletDashboard(props: {
3439
)}
3540

3641
<ActivityOverview
37-
transactions={mockWalletData.transactions}
42+
transactions={txActivity}
43+
isLoading={isLoadingActivity}
3844
contracts={mockWalletData.contracts}
3945
/>
4046
<TokenHoldings
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { useEffect, useState } from "react";
2+
import { fetchTxActivity } from "../actions/fetchTxActivity";
3+
4+
interface TxActivityItem {
5+
id: string;
6+
// all txs we retrieve for now are outgoing
7+
// TODO: add incoming
8+
type: "out" | "in";
9+
amount: string;
10+
to?: string;
11+
from?: string;
12+
method?: string;
13+
date: string;
14+
}
15+
16+
export function useGetTxActivity(chainId: number, address: string) {
17+
const [txActivity, setTxActivity] = useState<TxActivityItem[]>([]);
18+
const [isLoading, setIsLoading] = useState(true);
19+
20+
useEffect(() => {
21+
(async () => {
22+
const response = await fetchTxActivity({ chainId, address });
23+
const activity = response.map((tx): TxActivityItem => {
24+
const type =
25+
tx.to_address?.toLowerCase() === address.toLowerCase() ? "in" : "out";
26+
return {
27+
id: tx.hash,
28+
type,
29+
amount: `${tx.value / 10 ** 18} ETH`,
30+
to: tx.to_address || undefined,
31+
from: tx.from_address,
32+
method: tx.function_selector || undefined,
33+
date: new Date(tx.block_timestamp * 1000).toLocaleString(),
34+
};
35+
});
36+
setTxActivity(activity);
37+
setIsLoading(false);
38+
})();
39+
}, [address, chainId]);
40+
41+
return { txActivity, isLoading };
42+
}

0 commit comments

Comments
 (0)