p-sdk-wallet
Version:
A comprehensive wallet SDK for React Native (pwc), supporting multi-chain and multi-account features.
163 lines (162 loc) • 5.49 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.MetadataResolver = void 0;
/**
* Service for resolving NFT metadata from various sources (IPFS, HTTP)
*/
class MetadataResolver {
/**
* Resolves metadata from a URI (IPFS or HTTP)
* @param uri - The metadata URI to resolve
* @returns Promise resolving to NFT metadata
*/
async resolveMetadata(uri) {
// Check cache first
const cached = this.getCachedMetadata(uri);
if (cached) {
return cached;
}
let resolvedUri;
if (uri.startsWith('ipfs://')) {
resolvedUri = await this.resolveIPFS(uri);
}
else if (uri.startsWith('http://') || uri.startsWith('https://')) {
resolvedUri = uri;
}
else {
throw new Error(`Unsupported URI format: ${uri}`);
}
const metadata = await this.fetchMetadata(resolvedUri);
this.cacheMetadata(uri, metadata);
return metadata;
}
/**
* Resolves IPFS URI to HTTP URL using multiple gateways
* @param ipfsUri - IPFS URI (ipfs://...)
* @returns Promise resolving to HTTP URL
*/
async resolveIPFS(ipfsUri) {
const ipfsHash = ipfsUri.replace('ipfs://', '');
// Try multiple gateways for redundancy
for (const gateway of MetadataResolver.IPFS_GATEWAYS) {
try {
const url = gateway + ipfsHash;
const response = await fetch(url, { method: 'HEAD' });
if (response.ok) {
return url;
}
}
catch (error) {
console.warn(`IPFS gateway ${gateway} failed:`, error);
continue;
}
}
throw new Error(`Failed to resolve IPFS URI: ${ipfsUri}`);
}
/**
* Fetches metadata from HTTP URL
* @param url - HTTP URL to fetch metadata from
* @returns Promise resolving to NFT metadata
*/
async fetchMetadata(url) {
try {
const response = await fetch(url, {
method: 'GET',
headers: {
'Accept': 'application/json',
'User-Agent': 'PWC-Wallet-SDK/1.0'
}
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const rawData = await response.json();
return this.parseMetadata(rawData);
}
catch (error) {
throw new Error(`Failed to fetch metadata from ${url}: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
}
/**
* Parses raw metadata into standardized format
* @param rawData - Raw metadata from contract
* @returns Parsed NFT metadata
*/
parseMetadata(rawData) {
// Handle different metadata formats
const metadata = {
name: rawData.name || rawData.title || 'Unknown NFT',
description: rawData.description || '',
image: rawData.image || rawData.image_url || '',
external_url: rawData.external_url || rawData.externalUrl || undefined,
attributes: []
};
// Parse attributes
if (rawData.attributes && Array.isArray(rawData.attributes)) {
metadata.attributes = rawData.attributes.map((attr) => ({
trait_type: attr.trait_type || attr.traitType || '',
value: attr.value || '',
display_type: attr.display_type || attr.displayType || undefined
}));
}
// Validate required fields
if (!metadata.name || !metadata.image) {
throw new Error('Invalid metadata: missing required fields (name, image)');
}
return metadata;
}
/**
* Gets cached metadata if available and not expired
* @param uri - Metadata URI
* @returns Cached metadata or null if not found/expired
*/
getCachedMetadata(uri) {
const cached = MetadataResolver.metadataCache.get(uri);
if (!cached) {
return null;
}
const now = Date.now();
if (now - cached.timestamp > MetadataResolver.CACHE_DURATION) {
MetadataResolver.metadataCache.delete(uri);
return null;
}
return cached.data;
}
/**
* Caches metadata for future use
* @param uri - Metadata URI
* @param metadata - Metadata to cache
*/
cacheMetadata(uri, metadata) {
MetadataResolver.metadataCache.set(uri, {
data: metadata,
timestamp: Date.now()
});
}
/**
* Clears the metadata cache
*/
static clearCache() {
MetadataResolver.metadataCache.clear();
}
/**
* Gets cache statistics
* @returns Cache statistics
*/
static getCacheStats() {
return {
size: MetadataResolver.metadataCache.size,
entries: Array.from(MetadataResolver.metadataCache.values()).length
};
}
}
exports.MetadataResolver = MetadataResolver;
MetadataResolver.IPFS_GATEWAYS = [
'https://ipfs.io/ipfs/',
'https://gateway.pinata.cloud/ipfs/',
'https://cloudflare-ipfs.com/ipfs/',
'https://dweb.link/ipfs/',
'https://gateway.ipfs.io/ipfs/'
];
MetadataResolver.CACHE_DURATION = 5 * 60 * 1000; // 5 minutes
MetadataResolver.metadataCache = new Map();