@deserialize/multi-vm-wallet 1.2.31 → 1.2.41

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.
@@ -1,4 +1,4 @@
1
- import { Balance, ChainWalletConfig, SUPPORTED_VM, UserTokenBalance, TokenInfo } from '../types'
1
+ import { Balance, ChainWalletConfig, SUPPORTED_VM, UserTokenBalance, TokenInfo, EVMNFT, NFT } from '../types'
2
2
  import { JsonRpcProvider, Contract, Wallet, TransactionRequest, TransactionResponse, TransactionReceipt, parseUnits, formatUnits, ethers } from 'ethers'
3
3
  import BN from 'bn.js'
4
4
  import { HelperAPI } from '../helpers';
@@ -954,4 +954,91 @@ export function prepareSwapParams(
954
954
 
955
955
  export function convertSlippageForDebonk(slippageBps: number): number {
956
956
  return slippageBps / 100;
957
+ }
958
+
959
+ export const transformEVMNFTToUnified = (nft: EVMNFT): NFT => {
960
+ // Extract image URL from various sources
961
+ const imageUrl = nft.image?.cachedUrl ||
962
+ nft.image?.thumbnailUrl ||
963
+ nft.image?.pngUrl ||
964
+ nft.image?.originalUrl ||
965
+ nft.openSeaMetadata?.imageUrl ||
966
+ nft.raw?.metadata?.image ||
967
+ undefined;
968
+
969
+ // Extract attributes
970
+ const attributes = nft.raw?.metadata?.attributes?.map(attr => ({
971
+ trait_type: attr.trait_type,
972
+ value: attr.value,
973
+ display_type: attr.display_type
974
+ }));
975
+
976
+ return {
977
+ id: `${nft.contract.address}:${nft.tokenId}`,
978
+ name: nft.name || nft.raw?.metadata?.name || 'Unknown',
979
+ symbol: nft.contract.symbol,
980
+ description: nft.description || nft.raw?.metadata?.description || '',
981
+ image: imageUrl,
982
+ uri: nft.raw?.tokenUri || '',
983
+ collection: {
984
+ address: nft.contract.address,
985
+ name: nft.openSeaMetadata?.collectionName || nft.contract.name,
986
+ verified: nft.openSeaMetadata?.safelistRequestStatus === 'verified'
987
+ },
988
+ chainType: 'EVM',
989
+ balance: nft.balance,
990
+ attributes,
991
+ tokenStandard: nft.tokenType,
992
+ isSpam: nft.contract.isSpam,
993
+ raw: {
994
+ evm: nft
995
+ }
996
+ };
997
+ };
998
+
999
+ export const discoverNFTs = async (wallet: string, chain: ChainWalletConfig): Promise<NFT[]> => {
1000
+ console.log('discoverNFTs: Starting NFT discovery');
1001
+ console.log('Wallet:', wallet);
1002
+ console.log('Chain:', chain.name, 'ChainId:', chain.chainId);
1003
+
1004
+ try {
1005
+ const response = await HelperAPI.getUserNFTs(wallet, chain.vmType ?? "EVM", chain.chainId);
1006
+
1007
+ console.log('discoverNFTs: Successfully fetched', response.data.length, 'NFTs');
1008
+
1009
+ // Filter out spam NFTs if desired (optional)
1010
+ const evmNfts = response.data.filter(nft => !nft.contract.isSpam);
1011
+
1012
+ console.log('discoverNFTs: After spam filtering:', evmNfts.length, 'NFTs');
1013
+
1014
+ // Transform to unified NFT format
1015
+ const nfts = evmNfts.map(transformEVMNFTToUnified);
1016
+
1017
+ return nfts;
1018
+ } catch (error) {
1019
+ console.error('discoverNFTs: Error fetching NFTs:', error);
1020
+ console.error('Error details:', error instanceof Error ? error.message : 'Unknown error');
1021
+ throw error;
1022
+ }
1023
+ }
1024
+
1025
+ export const discoverAllNFTs = async (wallet: string, chain: ChainWalletConfig): Promise<NFT[]> => {
1026
+ console.log('discoverAllNFTs: Starting NFT discovery (including spam)');
1027
+ console.log('Wallet:', wallet);
1028
+ console.log('Chain:', chain.name, 'ChainId:', chain.chainId);
1029
+
1030
+ try {
1031
+ const response = await HelperAPI.getUserNFTs(wallet, chain.vmType ?? "EVM", chain.chainId);
1032
+
1033
+ console.log('discoverAllNFTs: Successfully fetched', response.data.length, 'NFTs (including spam)');
1034
+
1035
+ // Transform to unified NFT format
1036
+ const nfts = response.data.map(transformEVMNFTToUnified);
1037
+
1038
+ return nfts;
1039
+ } catch (error) {
1040
+ console.error('discoverAllNFTs: Error fetching NFTs:', error);
1041
+ console.error('Error details:', error instanceof Error ? error.message : 'Unknown error');
1042
+ throw error;
1043
+ }
957
1044
  }
@@ -1,4 +1,4 @@
1
- import { vmTypes } from "../types";
1
+ import { vmTypes, EVMNFTResponse } from "../types";
2
2
 
3
3
  const BASE_URL = "https://helper.decane.app"
4
4
 
@@ -8,4 +8,10 @@ export class HelperAPI {
8
8
  const data = await res.json();
9
9
  return data;
10
10
  }
11
+
12
+ static async getUserNFTs(wallet: string, vm: vmTypes, chainId: number): Promise<EVMNFTResponse> {
13
+ const res = await fetch("" + BASE_URL + `/${vm}/nfts/discover?wallet=${wallet}&chainId=${chainId}`);
14
+ const data = await res.json();
15
+ return data;
16
+ }
11
17
  }
package/utils/svm/svm.ts CHANGED
@@ -2,7 +2,7 @@ import { Connection, Keypair, PublicKey, Transaction, VersionedTransaction } fro
2
2
  import { SVMDeriveChildPrivateKey } from "../walletBip32";
3
3
  import { VM } from "../vm";
4
4
  import { ChainWallet } from "../IChainWallet";
5
- import { Balance, ChainWalletConfig, UserTokenBalance, TokenInfo, TransactionResult } from "../types";
5
+ import { Balance, ChainWalletConfig, UserTokenBalance, TokenInfo, TransactionResult, NFT } from "../types";
6
6
  import {
7
7
  getSvmNativeBalance,
8
8
  getTokenBalance,
@@ -18,7 +18,8 @@ import {
18
18
  getTokenInfo,
19
19
  discoverTokens,
20
20
  signTransaction,
21
- sendTransaction
21
+ sendTransaction,
22
+ fetchWalletNfts
22
23
  } from "./utils";
23
24
  import BN from "bn.js";
24
25
  import nacl from "tweetnacl";
@@ -110,6 +111,12 @@ export class SVMChainWallet extends ChainWallet<PublicKey, Keypair, Connection>
110
111
  return tokens
111
112
  }
112
113
 
114
+ async discoverNFT(): Promise<NFT[]> {
115
+ // Implement NFT discovery logic here
116
+ const nfts = await fetchWalletNfts(this.address, this.connection!)
117
+ return nfts
118
+ }
119
+
113
120
  async transferNative(to: PublicKey, amount: number): Promise<TransactionResult> {
114
121
  // Implement native transfer logic here
115
122
  const transaction = await getTransferNativeTransaction(this.privateKey, to, amount, this.connection!)
@@ -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 } 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,80 @@ 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
+
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.
@@ -125,4 +132,26 @@ const connection = new Connection(RPC_URL);
125
132
  // console.error("Error fetching EVM transaction history:", error);
126
133
  // });
127
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);
151
+ // });
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,146 @@ 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
+ export interface SolanaNFT {
105
+ mint: string;
106
+ name: string;
107
+ symbol: string;
108
+ uri: string;
109
+ updateAuthority: string;
110
+ sellerFeeBasisPoints: number;
111
+ creators?: Array<{
112
+ address: string;
113
+ verified: boolean;
114
+ share: number;
115
+ }>;
116
+ }
117
+
118
+ export interface EVMNFTContract {
119
+ address: string;
120
+ name: string;
121
+ symbol: string;
122
+ totalSupply: string;
123
+ tokenType: string;
124
+ contractDeployer: string;
125
+ deployedBlockNumber: number;
126
+ isSpam: boolean;
127
+ spamClassifications?: string[];
128
+ }
129
+
130
+ export interface EVMNFTOpenSeaMetadata {
131
+ collectionName: string;
132
+ collectionSlug: string;
133
+ floorPrice?: number;
134
+ safelistRequestStatus: string;
135
+ imageUrl?: string;
136
+ lastIngestedAt: string;
137
+ }
138
+
139
+ export interface EVMNFTImage {
140
+ cachedUrl?: string;
141
+ thumbnailUrl?: string;
142
+ pngUrl?: string;
143
+ contentType?: string;
144
+ size?: number;
145
+ originalUrl?: string;
146
+ }
147
+
148
+ export interface EVMNFTAttribute {
149
+ trait_type: string;
150
+ value: string | number;
151
+ display_type?: string;
152
+ }
153
+
154
+ export interface EVMNFTRawMetadata {
155
+ tokenUri?: string;
156
+ metadata?: {
157
+ name?: string;
158
+ description?: string;
159
+ image?: string;
160
+ attributes?: EVMNFTAttribute[];
161
+ [key: string]: any;
162
+ };
163
+ }
164
+
165
+ export interface EVMNFTMint {
166
+ mintAddress?: string;
167
+ blockNumber?: number;
168
+ timestamp?: string;
169
+ transactionHash?: string;
170
+ }
171
+
172
+ export interface EVMNFT {
173
+ contract: EVMNFTContract;
174
+ openSeaMetadata?: EVMNFTOpenSeaMetadata;
175
+ tokenId: string;
176
+ tokenType: string;
177
+ name: string;
178
+ description: string;
179
+ balance: string;
180
+ image?: EVMNFTImage;
181
+ raw?: EVMNFTRawMetadata;
182
+ mint?: EVMNFTMint;
183
+ timeLastUpdated: string;
184
+ acquiredAt?: string;
185
+ }
186
+
187
+ export interface EVMNFTResponse {
188
+ data: EVMNFT[];
189
+ }
190
+
42
191
  export interface TransactionResult {
43
192
  hash: string;
44
193
  success: boolean;