Skip to content

Commit ad6c014

Browse files
committed
Merge remote-tracking branch 'origin/hackweek-wallet-explainer' into hackweek-wallet-explainer
2 parents a22457a + 8a8fa70 commit ad6c014

File tree

21 files changed

+1765
-18
lines changed

21 files changed

+1765
-18
lines changed
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
"use server";
2+
import assert from "node:assert";
3+
import { DASHBOARD_THIRDWEB_SECRET_KEY } from "@/constants/env";
4+
5+
export async function createNebulaSesssion(args: {
6+
chainId: number;
7+
address: string;
8+
}): Promise<string> {
9+
try {
10+
const { NEXT_PUBLIC_NEBULA_URL } = process.env;
11+
assert(NEXT_PUBLIC_NEBULA_URL, "NEXT_PUBLIC_NEBULA_URL is not set");
12+
const { chainId, address } = args;
13+
14+
const response = await fetch(`${NEXT_PUBLIC_NEBULA_URL}/session`, {
15+
method: "POST",
16+
headers: {
17+
"Content-Type": "application/json",
18+
Authorization: `Bearer ${DASHBOARD_THIRDWEB_SECRET_KEY}`,
19+
},
20+
body: JSON.stringify({
21+
title: `Wallet Overview - ${chainId}:${address}`,
22+
context_filter: {
23+
chainIds: [chainId],
24+
walletAddresses: [address],
25+
},
26+
}),
27+
});
28+
if (!response.ok) {
29+
throw new Error(
30+
`Unexpected status ${response.status}: ${await response.text()}`,
31+
);
32+
}
33+
34+
const data = await response.json();
35+
return data.result.id;
36+
} catch (error) {
37+
console.error("Error creating Nebula session:", error);
38+
throw error;
39+
}
40+
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
"use server";
2+
3+
import assert from "node:assert";
4+
import { toTokens } from "thirdweb";
5+
import type { TokenDetails } from "../hooks/useGetERC20Tokens";
6+
7+
interface QueriedWalletBalance {
8+
address: string;
9+
quantity_string: string;
10+
value_usd_cents: number;
11+
first_transferred_date: string;
12+
last_transferred_date: string;
13+
}
14+
15+
interface FungibleToken {
16+
fungible_id: string;
17+
name: string;
18+
symbol: string;
19+
decimals: number;
20+
total_quantity_string: string;
21+
total_value_usd_cents: number;
22+
queried_wallet_balances: QueriedWalletBalance[];
23+
}
24+
25+
interface NativeToken {
26+
token_id: string;
27+
name: string;
28+
symbol: string;
29+
decimals: number;
30+
total_quantity_string: string;
31+
total_value_usd_cents: number;
32+
queried_wallet_balances: QueriedWalletBalance[];
33+
}
34+
35+
interface SimpleHashResponse {
36+
fungibles: FungibleToken[];
37+
native_tokens: NativeToken[];
38+
next_cursor: string | null;
39+
}
40+
41+
export async function fetchERC20Tokens(args: {
42+
chainId: number;
43+
address: string;
44+
}): Promise<TokenDetails[]> {
45+
try {
46+
const { SIMPLEHASH_API_KEY } = process.env;
47+
assert(SIMPLEHASH_API_KEY, "SIMPLEHASH_API_KEY is not set");
48+
const { chainId, address } = args;
49+
50+
const response = await fetch(
51+
`https://api.simplehash.com/api/v0/fungibles/balances?chains=eip155:${chainId}&wallet_addresses=${address}&include_fungible_details=0&include_prices=1&count=0&limit=50&order_by=last_transferred_date__desc&include_native_tokens=1`,
52+
{
53+
headers: { "X-API-KEY": SIMPLEHASH_API_KEY },
54+
},
55+
);
56+
if (!response.ok) {
57+
throw new Error(
58+
`Unexpected status ${response.status}: ${await response.text()}`,
59+
);
60+
}
61+
62+
const data: SimpleHashResponse = await response.json();
63+
const nativeTokens = data.native_tokens.map((token) => ({
64+
name: token.name,
65+
symbol: token.symbol,
66+
contractAddress: "Native",
67+
decimals: token.decimals,
68+
balance: BigInt(token.total_quantity_string ?? 0n),
69+
balanceTokens: Number(
70+
toTokens(BigInt(token.total_quantity_string), token.decimals),
71+
),
72+
totalValueUsdCents: token.total_value_usd_cents,
73+
firstTransferredDate:
74+
token.queried_wallet_balances[0]?.first_transferred_date,
75+
lastTransferredDate:
76+
token.queried_wallet_balances[0]?.last_transferred_date,
77+
}));
78+
let fungibleTokens = data.fungibles.map((token) => ({
79+
name: token.name,
80+
symbol: token.symbol,
81+
contractAddress: token.fungible_id.split(".")[1] ?? "--",
82+
decimals: token.decimals,
83+
balance: BigInt(token.total_quantity_string ?? 0n),
84+
balanceTokens: Number(
85+
toTokens(BigInt(token.total_quantity_string), token.decimals),
86+
),
87+
totalValueUsdCents: token.total_value_usd_cents,
88+
firstTransferredDate:
89+
token.queried_wallet_balances[0]?.first_transferred_date,
90+
lastTransferredDate:
91+
token.queried_wallet_balances[0]?.last_transferred_date,
92+
}));
93+
fungibleTokens = fungibleTokens.filter(d => d.name != null || d.symbol != null);
94+
return [...nativeTokens, ...fungibleTokens];
95+
} catch (error) {
96+
console.error("Error fetching tokens:", error);
97+
return [];
98+
}
99+
}
Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
"use server";
2+
3+
import assert from "node:assert";
4+
import type { NFTDetails } from "../hooks/useGetNFTs";
5+
6+
interface NFT {
7+
nft_id: string;
8+
chain: string;
9+
contract_address: string;
10+
token_id: string;
11+
name: string;
12+
description: string;
13+
previews: NFTPreviews;
14+
image_url: string;
15+
image_properties: ImageProperties;
16+
video_url: string | null;
17+
video_properties: VideoProperties | null;
18+
audio_url: string | null;
19+
audio_properties: AudioProperties | null;
20+
model_url: string | null;
21+
model_properties: ModelProperties | null;
22+
other_url: string | null;
23+
other_properties: OtherProperties | null;
24+
background_color: string | null;
25+
external_url: string | null;
26+
created_date: string;
27+
status: string;
28+
token_count: number;
29+
owner_count: number;
30+
contract: NFTContract;
31+
collection: NFTCollection;
32+
first_created: FirstCreated;
33+
rarity: Rarity;
34+
royalty: Royalty[];
35+
extra_metadata: ExtraMetadata;
36+
queried_wallet_balances: QueriedWalletBalance[];
37+
}
38+
39+
interface NFTPreviews {
40+
image_small_url: string;
41+
image_medium_url: string;
42+
image_large_url: string;
43+
image_opengraph_url: string;
44+
blurhash: string;
45+
predominant_color: string;
46+
}
47+
48+
interface ImageProperties {
49+
width: number;
50+
height: number;
51+
size?: number;
52+
mime_type: string;
53+
exif_orientation: number | null;
54+
}
55+
56+
interface VideoProperties {
57+
width: number;
58+
height: number;
59+
size: number;
60+
mime_type: string;
61+
duration: number;
62+
}
63+
64+
interface AudioProperties {
65+
size: number;
66+
mime_type: string;
67+
duration: number;
68+
}
69+
70+
interface ModelProperties {
71+
format: string;
72+
size: number;
73+
}
74+
75+
interface OtherProperties {
76+
format: string;
77+
size: number;
78+
}
79+
80+
interface NFTContract {
81+
type: "ERC721" | "ERC1155";
82+
name: string;
83+
symbol: string;
84+
deployed_by: string;
85+
deployed_via_contract: string | null;
86+
owned_by: string | null;
87+
has_multiple_collections: boolean;
88+
has_erc5643_subscription_standard: boolean;
89+
}
90+
91+
interface NFTCollection {
92+
collection_id: string;
93+
name: string;
94+
description: string;
95+
image_url: string;
96+
image_properties: ImageProperties;
97+
banner_image_url: string | null;
98+
category: string | null;
99+
is_nsfw: boolean;
100+
external_url: string | null;
101+
twitter_username: string | null;
102+
discord_url: string | null;
103+
instagram_username: string | null;
104+
medium_username: string | null;
105+
telegram_url: string | null;
106+
marketplace_pages: MarketplacePage[];
107+
metaplex_mint: string | null;
108+
metaplex_candy_machine: string | null;
109+
metaplex_first_verified_creator: string | null;
110+
mpl_core_collection_address: string | null;
111+
distinct_owner_count: number;
112+
distinct_nft_count: number;
113+
total_quantity: number;
114+
chains: string[];
115+
top_contracts: string[];
116+
collection_royalties: CollectionRoyalty[];
117+
}
118+
119+
interface MarketplacePage {
120+
marketplace_id: string;
121+
marketplace_name: string;
122+
marketplace_collection_id: string;
123+
nft_url: string;
124+
collection_url: string;
125+
verified: boolean | null;
126+
}
127+
128+
interface CollectionRoyalty {
129+
source: string;
130+
total_creator_fee_basis_points: number;
131+
recipients: any[];
132+
}
133+
134+
interface FirstCreated {
135+
minted_to: string;
136+
quantity: number;
137+
quantity_string: string;
138+
timestamp: string;
139+
block_number: number;
140+
transaction: string;
141+
transaction_initiator: string;
142+
}
143+
144+
interface Rarity {
145+
rank: number | null;
146+
score: number | null;
147+
unique_attributes: any | null;
148+
}
149+
150+
interface Royalty {
151+
source: string;
152+
total_creator_fee_basis_points: number;
153+
recipients: string[];
154+
}
155+
156+
interface ExtraMetadata {
157+
attributes: any[];
158+
image_original_url: string;
159+
animation_original_url: string | null;
160+
metadata_original_url: string;
161+
}
162+
163+
interface QueriedWalletBalance {
164+
address: string;
165+
quantity: number;
166+
quantity_string: string;
167+
first_acquired_date: string;
168+
last_acquired_date: string;
169+
}
170+
171+
interface SimpleHashResponse {
172+
next_cursor: null | string;
173+
next: null | string;
174+
previous: null | string;
175+
nfts: NFT[];
176+
}
177+
178+
export async function fetchNFTs(args: {
179+
chainId: number;
180+
address: string;
181+
}): Promise<NFTDetails[]> {
182+
try {
183+
const { SIMPLEHASH_API_KEY } = process.env;
184+
assert(SIMPLEHASH_API_KEY, "SIMPLEHASH_API_KEY is not set");
185+
const { chainId, address } = args;
186+
187+
// @TODO: pagination
188+
const response = await fetch(
189+
`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`,
190+
{
191+
headers: { "X-API-KEY": SIMPLEHASH_API_KEY },
192+
},
193+
);
194+
if (!response.ok) {
195+
throw new Error(
196+
`Unexpected status ${response.status}: ${await response.text()}`,
197+
);
198+
}
199+
200+
const data: SimpleHashResponse = await response.json();
201+
return data.nfts.map((token) => ({
202+
name: token.name,
203+
description: token.description,
204+
contractAddress: token.contract_address,
205+
contractType: token.contract.type,
206+
tokenId: token.token_id,
207+
quantity: token.queried_wallet_balances[0]?.quantity ?? 1,
208+
firstAcquiredDate: token.queried_wallet_balances[0]?.first_acquired_date,
209+
lastAcquiredDate: token.queried_wallet_balances[0]?.last_acquired_date,
210+
imageUrl: token.previews.image_medium_url,
211+
createdAt: token.created_date ?? undefined,
212+
tokenCount: token.token_count,
213+
ownerCount: token.owner_count,
214+
}));
215+
} catch (error) {
216+
console.error("Error fetching tokens:", error);
217+
return [];
218+
}
219+
}

0 commit comments

Comments
 (0)