@deserialize/multi-vm-wallet 1.2.31 → 1.2.43

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,13 +2,14 @@
2
2
 
3
3
  import { Account, createAssociatedTokenAccountIdempotentInstruction, createTransferCheckedInstruction, getAccount, getAssociatedTokenAddress, getAssociatedTokenAddressSync, getMint, Mint, NATIVE_MINT, TOKEN_2022_PROGRAM_ID, TOKEN_PROGRAM_ID } from "@solana/spl-token";
4
4
  import { Connection, Keypair, LAMPORTS_PER_SOL, PublicKey, SystemProgram, Transaction, TransactionInstruction, TransactionMessage, VersionedTransaction } from "@solana/web3.js";
5
- import { ChainWalletConfig, UserTokenBalance, TokenInfo } from "../types";
5
+ import { ChainWalletConfig, UserTokenBalance, TokenInfo, SolanaNFT, NFT, NFTCollection } from "../types";
6
6
  import { transactionSenderAndConfirmationWaiter } from "./transactionSender";
7
7
  import { BN } from "bn.js";
8
- import { generateSigner, percentAmount } from '@metaplex-foundation/umi'
8
+ import { generateSigner, percentAmount, publicKey } from '@metaplex-foundation/umi'
9
9
  import {
10
10
  createNft,
11
11
  fetchDigitalAsset,
12
+ fetchAllDigitalAssetByOwner,
12
13
  mplTokenMetadata,
13
14
  } from '@metaplex-foundation/mpl-token-metadata'
14
15
  import { createUmi } from "@metaplex-foundation/umi-bundle-defaults";
@@ -283,20 +284,99 @@ const getMetaTokenMetaplexData = async (mintAddress: PublicKey, connection: Conn
283
284
  const umi = createUmi(connection.rpcEndpoint).use(mplTokenMetadata())
284
285
  const ass = await fetchDigitalAsset(umi, mintAddress.toBase58() as unknown as UmiPublicKey)
285
286
  return ass.metadata
287
+ }
286
288
 
289
+ interface TokenMetadata {
290
+ name?: string;
291
+ symbol?: string;
292
+ description?: string;
293
+ image?: string;
294
+ twitter?: string;
295
+ telegram?: string;
296
+ website?: string;
297
+ discord?: string;
298
+ extensions?: {
299
+ twitter?: string;
300
+ telegram?: string;
301
+ website?: string;
302
+ discord?: string;
303
+ [key: string]: string | undefined;
304
+ };
287
305
  }
288
306
 
307
+ const fetchMetadataFromUri = async (uri: string): Promise<TokenMetadata | null> => {
308
+ try {
309
+ // Convert IPFS URI to HTTP gateway if needed
310
+ let fetchUrl = uri;
311
+ if (uri.startsWith('ipfs://')) {
312
+ fetchUrl = uri.replace('ipfs://', 'https://ipfs.io/ipfs/');
313
+ }
314
+
315
+ console.log('Fetching metadata from:', fetchUrl);
316
+ const response = await fetch(fetchUrl, {
317
+ headers: {
318
+ 'Accept': 'application/json',
319
+ },
320
+ });
321
+
322
+ if (!response.ok) {
323
+ console.log('Failed to fetch metadata:', response.status);
324
+ return null;
325
+ }
326
+
327
+ const metadata: TokenMetadata = await response.json();
328
+ console.log('Metadata fetched successfully');
329
+ return metadata;
330
+ } catch (error) {
331
+ console.log('Error fetching metadata from URI:', error);
332
+ return null;
333
+ }
334
+ };
335
+
289
336
  export const getTokenInfo = async (tokenAddress: PublicKey, connection: Connection, programId?: PublicKey): Promise<TokenInfo> => {
290
337
  let mint: Mint
291
338
 
292
339
  const metaplexData = await getMetaTokenMetaplexData(tokenAddress, connection).catch(() => null);
340
+
341
+ // Fetch metadata from URI if available
342
+ let uriMetadata: TokenMetadata | null = null;
343
+ if (metaplexData?.uri) {
344
+ uriMetadata = await fetchMetadataFromUri(metaplexData.uri).catch(() => null);
345
+ }
346
+
293
347
  if (programId) {
294
348
  const mint = await getMint(connection, tokenAddress, "confirmed", programId)
349
+
350
+ // Build social object from metadata
351
+ const social: TokenInfo['social'] = {};
352
+ if (uriMetadata?.twitter || uriMetadata?.extensions?.twitter) {
353
+ social.twitter = uriMetadata.twitter || uriMetadata.extensions?.twitter;
354
+ }
355
+ if (uriMetadata?.telegram || uriMetadata?.extensions?.telegram) {
356
+ social.telegram = uriMetadata.telegram || uriMetadata.extensions?.telegram;
357
+ }
358
+ if (uriMetadata?.discord || uriMetadata?.extensions?.discord) {
359
+ social.discord = uriMetadata.discord || uriMetadata.extensions?.discord;
360
+ }
361
+
362
+ // Add any other social fields from extensions
363
+ if (uriMetadata?.extensions) {
364
+ Object.keys(uriMetadata.extensions).forEach(key => {
365
+ if (!['twitter', 'telegram', 'discord', 'website'].includes(key) && uriMetadata.extensions![key]) {
366
+ social[key] = uriMetadata.extensions![key];
367
+ }
368
+ });
369
+ }
370
+
295
371
  return {
296
372
  address: tokenAddress.toString(),
297
373
  decimals: mint.decimals,
298
- name: metaplexData?.name || "",
299
- symbol: metaplexData?.symbol || ""
374
+ name: uriMetadata?.name || metaplexData?.name || "",
375
+ symbol: uriMetadata?.symbol || metaplexData?.symbol || "",
376
+ logoUrl: uriMetadata?.image || "",
377
+ description: uriMetadata?.description,
378
+ website: uriMetadata?.website || uriMetadata?.extensions?.website,
379
+ social: Object.keys(social).length > 0 ? social : undefined
300
380
  }
301
381
  }
302
382
  try {
@@ -309,11 +389,36 @@ export const getTokenInfo = async (tokenAddress: PublicKey, connection: Connecti
309
389
 
310
390
  }
311
391
 
392
+ // Build social object from metadata
393
+ const social: TokenInfo['social'] = {};
394
+ if (uriMetadata?.twitter || uriMetadata?.extensions?.twitter) {
395
+ social.twitter = uriMetadata.twitter || uriMetadata.extensions?.twitter;
396
+ }
397
+ if (uriMetadata?.telegram || uriMetadata?.extensions?.telegram) {
398
+ social.telegram = uriMetadata.telegram || uriMetadata.extensions?.telegram;
399
+ }
400
+ if (uriMetadata?.discord || uriMetadata?.extensions?.discord) {
401
+ social.discord = uriMetadata.discord || uriMetadata.extensions?.discord;
402
+ }
403
+
404
+ // Add any other social fields from extensions
405
+ if (uriMetadata?.extensions) {
406
+ Object.keys(uriMetadata.extensions).forEach(key => {
407
+ if (!['twitter', 'telegram', 'discord', 'website'].includes(key) && uriMetadata.extensions![key]) {
408
+ social[key] = uriMetadata.extensions![key];
409
+ }
410
+ });
411
+ }
412
+
312
413
  return {
313
414
  address: tokenAddress.toString(),
314
415
  decimals: mint.decimals,
315
- name: metaplexData?.name || "",
316
- symbol: metaplexData?.symbol || ""
416
+ name: uriMetadata?.name || metaplexData?.name || "",
417
+ symbol: uriMetadata?.symbol || metaplexData?.symbol || "",
418
+ logoUrl: uriMetadata?.image || "",
419
+ description: uriMetadata?.description,
420
+ website: uriMetadata?.website || uriMetadata?.extensions?.website,
421
+ social: Object.keys(social).length > 0 ? social : undefined
317
422
  }
318
423
  }
319
424
 
@@ -430,13 +535,16 @@ export const discoverTokens = async (ownerAddress: PublicKey, connection: Connec
430
535
 
431
536
  const mintAddress = accountInfo.account.data["parsed"]["info"]["mint"]
432
537
  const mint = await getTokenInfo(new PublicKey(mintAddress), connection)
538
+ // console.log('mint: ', mint);
433
539
  return {
434
540
  owner: accountInfo.account.data["parsed"]["info"]["owner"],
435
541
  address: accountInfo.account.data["parsed"]["info"]["mint"],
436
542
  decimals: accountInfo.account.data["parsed"]["info"]["tokenAmount"]["decimals"],
437
543
  symbol: mint.symbol,
438
544
  name: mint.name,
439
- balance: accountInfo.account.data["parsed"]["info"]["tokenAmount"]["amount"]
545
+ balance: accountInfo.account.data["parsed"]["info"]["tokenAmount"]["amount"],
546
+ logoUrl: mint.logoUrl
547
+
440
548
  }
441
549
  }))
442
550
 
@@ -760,3 +868,153 @@ export const validateJupiterTokens = async (
760
868
  }
761
869
  };
762
870
 
871
+ export const transformSolanaNFTToUnified = (nft: SolanaNFT): NFT => {
872
+ return {
873
+ id: nft.mint,
874
+ name: nft.name,
875
+ symbol: nft.symbol,
876
+ description: '', // Will be populated from metadata URI if needed
877
+ image: undefined, // Will be populated from metadata URI if needed
878
+ uri: nft.uri,
879
+ collection: {
880
+ address: nft.mint,
881
+ name: nft.name,
882
+ verified: nft.creators?.some(c => c.verified) ?? false,
883
+ },
884
+ chainType: 'SVM',
885
+ balance: '1', // NFTs on Solana are typically quantity 1
886
+ creators: nft.creators,
887
+ sellerFeeBasisPoints: nft.sellerFeeBasisPoints,
888
+ tokenStandard: 'NonFungible',
889
+ raw: {
890
+ svm: nft
891
+ }
892
+ };
893
+ };
894
+
895
+ export const fetchWalletNfts = async (
896
+ walletAddress: PublicKey,
897
+ connection: Connection
898
+ ): Promise<NFT[]> => {
899
+ console.log('fetchWalletNfts: Starting');
900
+ console.log('Wallet address:', walletAddress.toString());
901
+
902
+ try {
903
+ // Create UMI instance with the connection's RPC endpoint
904
+ const umi = createUmi(connection.rpcEndpoint).use(mplTokenMetadata());
905
+ console.log('UMI instance created with RPC endpoint:', connection.rpcEndpoint);
906
+
907
+ // Convert Solana PublicKey to UMI PublicKey
908
+ const owner = publicKey(walletAddress.toString());
909
+ console.log('Fetching NFTs for owner:', owner);
910
+
911
+ // Fetch all digital assets owned by the wallet
912
+ const assets = await fetchAllDigitalAssetByOwner(umi, owner);
913
+ console.log('Fetched assets count:', assets.length);
914
+
915
+ // Transform the assets into our SolanaNFT format
916
+ const solanaNfts: SolanaNFT[] = assets.map((asset) => {
917
+ const metadata = asset.metadata;
918
+
919
+ return {
920
+ mint: asset.publicKey.toString(),
921
+ name: metadata.name,
922
+ symbol: metadata.symbol,
923
+ uri: metadata.uri,
924
+ updateAuthority: metadata.updateAuthority.toString(),
925
+ sellerFeeBasisPoints: metadata.sellerFeeBasisPoints,
926
+ creators: metadata.creators && 'value' in metadata.creators && metadata.creators.value
927
+ ? metadata.creators.value.map((creator) => ({
928
+ address: creator.address.toString(),
929
+ verified: creator.verified,
930
+ share: creator.share,
931
+ }))
932
+ : undefined,
933
+ };
934
+ });
935
+
936
+ // Transform to unified NFT format
937
+ const nfts = solanaNfts.map(transformSolanaNFTToUnified);
938
+
939
+ console.log('fetchWalletNfts: Successfully fetched', nfts.length, 'NFTs');
940
+ return nfts;
941
+ } catch (error) {
942
+ console.log('fetchWalletNfts: Error fetching NFTs:', error);
943
+ console.log('Error details:', error instanceof Error ? error.message : 'Unknown error');
944
+ throw error;
945
+ }
946
+ };
947
+
948
+ /**
949
+ * Get NFT collection details for a specific collection on Solana
950
+ * @param walletAddress - User's wallet public key
951
+ * @param collectionAddress - The NFT collection address (can be collection mint or update authority)
952
+ * @param connection - Solana connection
953
+ * @returns NFTCollection object with collection details and user's NFTs in that collection
954
+ *
955
+ * Note: On Solana, collections can be identified by:
956
+ * - Collection mint address (from collection field in NFT metadata)
957
+ * - Update authority (who can modify the NFT metadata)
958
+ * This function filters by the collection.address field which is typically the collection mint
959
+ */
960
+ export const getNFTCollection = async (
961
+ walletAddress: PublicKey,
962
+ collectionAddress: string,
963
+ connection: Connection
964
+ ): Promise<NFTCollection | null> => {
965
+ console.log('getNFTCollection: Starting collection fetch');
966
+ console.log('Wallet address:', walletAddress.toString());
967
+ console.log('Collection Address:', collectionAddress);
968
+
969
+ try {
970
+ // Fetch all NFTs for the user
971
+ const allNfts = await fetchWalletNfts(walletAddress, connection);
972
+
973
+ console.log('getNFTCollection: Fetched', allNfts.length, 'total NFTs');
974
+
975
+ // Filter NFTs by collection address (case-insensitive comparison)
976
+ // On Solana, we match against the collection.address which is set from the mint
977
+ const collectionNfts = allNfts.filter(
978
+ nft => nft.collection.address.toLowerCase() === collectionAddress.toLowerCase()
979
+ );
980
+
981
+ console.log('getNFTCollection: Found', collectionNfts.length, 'NFTs in collection');
982
+
983
+ if (collectionNfts.length === 0) {
984
+ console.log('getNFTCollection: No NFTs found in this collection');
985
+ return null;
986
+ }
987
+
988
+ // Extract collection metadata from the first NFT
989
+ const firstNft = collectionNfts[0];
990
+ const rawSvmNft = firstNft.raw?.svm;
991
+
992
+ // Aggregate creators from all NFTs (they should be the same for a collection)
993
+ const creators = firstNft.creators;
994
+
995
+ const collection: NFTCollection = {
996
+ address: collectionAddress,
997
+ name: firstNft.collection.name,
998
+ symbol: firstNft.symbol,
999
+ description: undefined, // Could be fetched from URI metadata if needed
1000
+ image: undefined, // Solana collections don't have a single image
1001
+ verified: firstNft.collection.verified,
1002
+ chainType: 'SVM',
1003
+ nfts: collectionNfts,
1004
+ totalOwned: collectionNfts.length,
1005
+ collectionMetadata: rawSvmNft ? {
1006
+ updateAuthority: rawSvmNft.updateAuthority,
1007
+ sellerFeeBasisPoints: rawSvmNft.sellerFeeBasisPoints,
1008
+ creators: creators,
1009
+ } : undefined,
1010
+ };
1011
+
1012
+ console.log('getNFTCollection: Successfully created collection object');
1013
+ return collection;
1014
+ } catch (error) {
1015
+ console.error('getNFTCollection: Error fetching collection:', error);
1016
+ console.error('Error details:', error instanceof Error ? error.message : 'Unknown error');
1017
+ throw error;
1018
+ }
1019
+ };
1020
+
package/utils/test.ts CHANGED
@@ -8,12 +8,15 @@ import { Connection, PublicKey, Keypair } from "@solana/web3.js";
8
8
  import { SVMChainWallet, SVMVM, } from "./svm";
9
9
  import { VM } from "./vm";
10
10
  import { ChainWalletConfig } from "./types";
11
- import { EVMChainWallet } from "./evm";
11
+ import { discoverNFTs, EVMChainWallet } from "./evm";
12
12
  import { } from "./svm/transactionParsing";
13
13
  import { getEVMTransactionHistory } from "./evm/transactionParsing";
14
14
  import { createPublicClient, http, PublicClient } from "viem";
15
15
  import { base, baseGoerli } from "viem/chains";
16
- import { getTokenInfo } from "./svm/utils";
16
+ import { discoverTokens, fetchWalletNfts, getTokenInfo } from "./svm/utils";
17
+ import { JsonRpcProvider } from "ethers";
18
+
19
+ import { } from "@solana/spl-token-metadata"
17
20
  // const mnemonic = GenerateNewMnemonic()
18
21
 
19
22
 
@@ -22,8 +25,8 @@ import { getTokenInfo } from "./svm/utils";
22
25
  // const seed = VM.mnemonicToSeed(mnemonic)
23
26
  const pKey = "4QxETeX9pndiF1XNghUiDTnZnHq3cfjmuPLBJysrgocsLq1yb8w96aPWALa8ZnRZWmDU4wM8Tg8d1ZRVVByj7uXE"
24
27
 
25
- export const testUserKeyPair = Keypair.fromSecretKey(base58.decode(pKey));
26
- const x = testUserKeyPair instanceof Keypair;
28
+ const testUserKeyPair = Keypair.fromSecretKey(base58.decode(pKey));
29
+ // const x = testUserKeyPair instanceof Keypair;
27
30
  const evePrivateKey = "0x4c0883a69102937d6231471b5dbb6204fe5129617082792ae468d01a3f362318"
28
31
  const evmPrivateKey2 = "0x0123456789012345678901234567890123456789012345678901234567890123"
29
32
  const ogPrivKey = "d2d3f7117aa9a4c6e5d4affedd8a5ea624ffd82b2a1de81509e5913709b1ea72"
@@ -39,7 +42,7 @@ const ogPrivKey = "d2d3f7117aa9a4c6e5d4affedd8a5ea624ffd82b2a1de81509e5913709b1e
39
42
  const chainConfig: ChainWalletConfig = {
40
43
  chainId: 123456789,
41
44
  name: "Solana",
42
- rpcUrl: "https://solana-mainnet.g.alchemy.com/v2/vB5mKztdJeFdz9RkW99Qf",
45
+ rpcUrl: "https://solana-mainnet.g.alchemy.com/v2/lhoyb3hc9ccT9NA_y2cfA",
43
46
  explorerUrl: "https://explorer.solana.com",
44
47
  nativeToken: { name: "Solana", symbol: "SOL", decimals: 9 },
45
48
  confirmationNo: 1,
@@ -49,7 +52,7 @@ const chainConfig: ChainWalletConfig = {
49
52
  const evmChainConfig: ChainWalletConfig = {
50
53
  chainId: 8453,
51
54
  name: "Ethereum",
52
- rpcUrl: "https://eth-mainnet.g.alchemy.com/v2/vB5mKztdJeFdz9RkW99Qf",
55
+ rpcUrl: "https://eth-mainnet.g.alchemy.com/v2/lhoyb3hc9ccT9NA_y2cfA",
53
56
  explorerUrl: "https://explorer.ethereum.com",
54
57
  nativeToken: { name: "Ethereum", symbol: "ETH", decimals: 18 },
55
58
  confirmationNo: 1,
@@ -93,6 +96,10 @@ console.log('address: ', wallet.address);
93
96
  const RPC_URL = chainConfig.rpcUrl;
94
97
  const connection = new Connection(RPC_URL);
95
98
 
99
+ // const evmConnection = new JsonRpcProvider(evmChainConfig.rpcUrl, {
100
+ // chainId: evmChainConfig.chainId
101
+ // });
102
+
96
103
  /**
97
104
  * Fetches and logs the token metadata for a given mint address.
98
105
  * @param mintAddress - The mint address of the token.
@@ -111,18 +118,40 @@ const connection = new Connection(RPC_URL);
111
118
  // }).catch((error: any) => {
112
119
  // console.error("Error fetching transaction history:", error);
113
120
  // });
114
- // const client = createPublicClient({
115
- // chain: base,
116
- // transport: http(base.rpcUrls.default.http[0]),
117
- // })
118
-
119
- // getEVMTransactionHistory(client as PublicClient, "0x9C82CE0e125F61AdE50BC0c19638F6Ba93d71D5e", {
120
- // startBlock: BigInt(37427020)
121
- // // before: "0xabc..."
122
- // }).then((history: any) => {
123
- // console.log("EVM Transaction History:", history);
124
- // }).catch((error: any) => {
125
- // console.error("Error fetching EVM transaction history:", error);
121
+ const client = createPublicClient({
122
+ chain: base,
123
+ transport: http(base.rpcUrls.default.http[0]),
124
+ })
125
+
126
+ getEVMTransactionHistory(client as PublicClient, "0x9C82CE0e125F61AdE50BC0c19638F6Ba93d71D5e", {
127
+ startBlock: BigInt(37427020)
128
+ // before: "0xabc..."
129
+ }).then((history: any) => {
130
+ console.log("EVM Transaction History:", history);
131
+ }).catch((error: any) => {
132
+ console.error("Error fetching EVM transaction history:", error);
133
+ });
134
+
135
+ // discoverNFTs("0x498581ff718922c3f8e6a244956af099b2652b2b", evmChainConfig).then(nfts => {
136
+ // console.log("Discovered NFTs:", nfts);
137
+ // }).catch(error => {
138
+ // console.error("Error discovering NFTs:", error);
139
+ // });
140
+
141
+ // fetchWalletNfts(new PublicKey("LebronkTYWjc5J1gtqntdMcbhBXgwRgYcCxMA8JMA17"), connection).then(nfts => {
142
+ // console.log("Discovered NFTs:", nfts);
143
+ // }).catch(error => {
144
+ // console.error("Error discovering NFTs:", error);
145
+ // });
146
+
147
+ // discoverTokens(new PublicKey("8AXoqNjEVyhhe43ckDEsYiphWxTBH2oWXkKLZAbCNWLL"), connection).then(tokens => {
148
+ // console.log("Discovered Tokens:", tokens);
149
+ // }).catch(error => {
150
+ // console.error("Error discovering Tokens:", error);
126
151
  // });
127
152
 
128
153
 
154
+ // //IIFE
155
+ // (async () => {
156
+ // //GET TOKEN LOGO using @solana/spl-token-metadata
157
+ // })();
package/utils/types.ts CHANGED
@@ -24,6 +24,15 @@ export interface TokenInfo {
24
24
  name: string;
25
25
  symbol: string;
26
26
  decimals: number;
27
+ logoUrl?: string;
28
+ description?: string;
29
+ website?: string;
30
+ social?: {
31
+ twitter?: string;
32
+ discord?: string;
33
+ telegram?: string;
34
+ [key: string]: string | undefined;
35
+ };
27
36
  }
28
37
 
29
38
  export interface UserTokenBalance<AddressType> extends TokenInfo {
@@ -39,6 +48,196 @@ export interface NFTInfo {
39
48
  image?: string;
40
49
  }
41
50
 
51
+ // Unified NFT interface for both EVM and SVM chains
52
+ export interface NFT {
53
+ // Unique identifier
54
+ id: string; // For Solana: mint address, For EVM: contractAddress:tokenId
55
+
56
+ // Basic metadata
57
+ name: string;
58
+ symbol?: string;
59
+ description: string;
60
+ image?: string;
61
+ uri: string; // Metadata URI
62
+
63
+ // Collection/Contract info
64
+ collection: {
65
+ address: string;
66
+ name: string;
67
+ verified?: boolean;
68
+ };
69
+
70
+ // Chain-specific data
71
+ chainType: 'SVM' | 'EVM';
72
+
73
+ // Ownership
74
+ balance?: string; // Quantity owned (useful for ERC1155)
75
+
76
+ // Optional metadata
77
+ attributes?: Array<{
78
+ trait_type: string;
79
+ value: string | number;
80
+ display_type?: string;
81
+ }>;
82
+
83
+ // Creators/Royalties (mainly for Solana)
84
+ creators?: Array<{
85
+ address: string;
86
+ verified: boolean;
87
+ share: number;
88
+ }>;
89
+
90
+ // Additional metadata
91
+ sellerFeeBasisPoints?: number; // Royalty percentage
92
+ tokenStandard?: string; // e.g., "ERC721", "ERC1155", "NonFungible", etc.
93
+
94
+ // Spam detection (mainly for EVM)
95
+ isSpam?: boolean;
96
+
97
+ // Raw chain-specific data for advanced use cases
98
+ raw?: {
99
+ svm?: SolanaNFT;
100
+ evm?: EVMNFT;
101
+ };
102
+ }
103
+
104
+ // NFT Collection interface for both EVM and SVM chains
105
+ export interface NFTCollection {
106
+ // Collection identifier
107
+ address: string;
108
+
109
+ // Collection metadata
110
+ name: string;
111
+ symbol?: string;
112
+ description?: string;
113
+ image?: string;
114
+
115
+ // Verification status
116
+ verified?: boolean;
117
+
118
+ // Chain-specific data
119
+ chainType: 'SVM' | 'EVM';
120
+
121
+ // NFTs owned by the user in this collection
122
+ nfts: NFT[];
123
+
124
+ // Collection statistics
125
+ totalOwned: number; // Number of NFTs owned by user in this collection
126
+
127
+ // Collection-level metadata (for EVM)
128
+ contractMetadata?: {
129
+ tokenType?: string; // ERC721, ERC1155, etc.
130
+ totalSupply?: string;
131
+ contractDeployer?: string;
132
+ deployedBlockNumber?: number;
133
+ };
134
+
135
+ // Collection-level metadata (for SVM)
136
+ collectionMetadata?: {
137
+ updateAuthority?: string;
138
+ sellerFeeBasisPoints?: number;
139
+ creators?: Array<{
140
+ address: string;
141
+ verified: boolean;
142
+ share: number;
143
+ }>;
144
+ };
145
+
146
+ // OpenSea metadata (for EVM)
147
+ openSeaMetadata?: {
148
+ collectionSlug?: string;
149
+ floorPrice?: number;
150
+ safelistRequestStatus?: string;
151
+ };
152
+ }
153
+
154
+ export interface SolanaNFT {
155
+ mint: string;
156
+ name: string;
157
+ symbol: string;
158
+ uri: string;
159
+ updateAuthority: string;
160
+ sellerFeeBasisPoints: number;
161
+ creators?: Array<{
162
+ address: string;
163
+ verified: boolean;
164
+ share: number;
165
+ }>;
166
+ }
167
+
168
+ export interface EVMNFTContract {
169
+ address: string;
170
+ name: string;
171
+ symbol: string;
172
+ totalSupply: string;
173
+ tokenType: string;
174
+ contractDeployer: string;
175
+ deployedBlockNumber: number;
176
+ isSpam: boolean;
177
+ spamClassifications?: string[];
178
+ }
179
+
180
+ export interface EVMNFTOpenSeaMetadata {
181
+ collectionName: string;
182
+ collectionSlug: string;
183
+ floorPrice?: number;
184
+ safelistRequestStatus: string;
185
+ imageUrl?: string;
186
+ lastIngestedAt: string;
187
+ }
188
+
189
+ export interface EVMNFTImage {
190
+ cachedUrl?: string;
191
+ thumbnailUrl?: string;
192
+ pngUrl?: string;
193
+ contentType?: string;
194
+ size?: number;
195
+ originalUrl?: string;
196
+ }
197
+
198
+ export interface EVMNFTAttribute {
199
+ trait_type: string;
200
+ value: string | number;
201
+ display_type?: string;
202
+ }
203
+
204
+ export interface EVMNFTRawMetadata {
205
+ tokenUri?: string;
206
+ metadata?: {
207
+ name?: string;
208
+ description?: string;
209
+ image?: string;
210
+ attributes?: EVMNFTAttribute[];
211
+ [key: string]: any;
212
+ };
213
+ }
214
+
215
+ export interface EVMNFTMint {
216
+ mintAddress?: string;
217
+ blockNumber?: number;
218
+ timestamp?: string;
219
+ transactionHash?: string;
220
+ }
221
+
222
+ export interface EVMNFT {
223
+ contract: EVMNFTContract;
224
+ openSeaMetadata?: EVMNFTOpenSeaMetadata;
225
+ tokenId: string;
226
+ tokenType: string;
227
+ name: string;
228
+ description: string;
229
+ balance: string;
230
+ image?: EVMNFTImage;
231
+ raw?: EVMNFTRawMetadata;
232
+ mint?: EVMNFTMint;
233
+ timeLastUpdated: string;
234
+ acquiredAt?: string;
235
+ }
236
+
237
+ export interface EVMNFTResponse {
238
+ data: EVMNFT[];
239
+ }
240
+
42
241
  export interface TransactionResult {
43
242
  hash: string;
44
243
  success: boolean;