@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.
- package/dist/IChainWallet.d.ts +2 -1
- package/dist/IChainWallet.js.map +1 -1
- package/dist/constant.js +1 -1
- package/dist/evm/evm.d.ts +2 -1
- package/dist/evm/evm.js +4 -0
- package/dist/evm/evm.js.map +1 -1
- package/dist/evm/utils.d.ts +4 -1
- package/dist/evm/utils.js +78 -1
- package/dist/evm/utils.js.map +1 -1
- package/dist/helpers/index.d.ts +2 -1
- package/dist/helpers/index.js +5 -0
- package/dist/helpers/index.js.map +1 -1
- package/dist/svm/svm.d.ts +2 -1
- package/dist/svm/svm.js +5 -0
- package/dist/svm/svm.js.map +1 -1
- package/dist/svm/utils.d.ts +3 -1
- package/dist/svm/utils.js +154 -6
- package/dist/svm/utils.js.map +1 -1
- package/dist/test.d.ts +1 -2
- package/dist/test.js +26 -5
- package/dist/test.js.map +1 -1
- package/dist/types.d.ts +119 -0
- package/dist/types.js.map +1 -1
- package/package.json +1 -1
- package/utils/IChainWallet.ts +2 -1
- package/utils/constant.ts +1 -1
- package/utils/evm/evm.ts +8 -2
- package/utils/evm/utils.ts +88 -1
- package/utils/helpers/index.ts +7 -1
- package/utils/svm/svm.ts +9 -2
- package/utils/svm/utils.ts +192 -7
- package/utils/test.ts +35 -6
- package/utils/types.ts +149 -0
package/utils/evm/utils.ts
CHANGED
|
@@ -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
|
}
|
package/utils/helpers/index.ts
CHANGED
|
@@ -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!)
|
package/utils/svm/utils.ts
CHANGED
|
@@ -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
|
-
|
|
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/
|
|
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/
|
|
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;
|