import { getCredentialVault } from "./credential-vault.mjs";
import { guardedFetch } from "./endpoint-allowlist.mjs";
//#region extensions/crypto/src/services/nft-service.ts
/**
* NFT Service — Reservoir API client for NFT operations.
*
* Supports:
*   - Collection floor prices and metadata
*   - Token viewing and metadata resolution
*   - Portfolio listing (user's NFTs)
*   - Buy/list/offer execution via Reservoir order flow
*   - Transfer via standard ERC-721 safeTransferFrom
*
* Uses Reservoir API (covers OpenSea, Blur, LooksRare marketplaces).
* Requires RESERVOIR_API_KEY env var (free tier: 4 req/sec).
*/
const CHAIN_API_BASE = {
	1: "https://api.reservoir.tools",
	8453: "https://api-base.reservoir.tools",
	42161: "https://api-arbitrum.reservoir.tools",
	10: "https://api-optimism.reservoir.tools",
	137: "https://api-polygon.reservoir.tools"
};
const CHAIN_NAMES = {
	1: "ethereum",
	8453: "base",
	42161: "arbitrum",
	10: "optimism",
	137: "polygon"
};
const ERC721_TRANSFER_ABI = [{
	name: "safeTransferFrom",
	inputs: [
		{
			name: "from",
			type: "address"
		},
		{
			name: "to",
			type: "address"
		},
		{
			name: "tokenId",
			type: "uint256"
		}
	],
	outputs: [],
	stateMutability: "nonpayable",
	type: "function"
}, {
	name: "ownerOf",
	inputs: [{
		name: "tokenId",
		type: "uint256"
	}],
	outputs: [{
		name: "",
		type: "address"
	}],
	stateMutability: "view",
	type: "function"
}];
var NftService = class {
	metadataCache = /* @__PURE__ */ new Map();
	CACHE_TTL = 3e5;
	getApiKey() {
		return getCredentialVault().getSecret("nft.reservoir.apiKey", "nft");
	}
	getApiBase(chainId) {
		return CHAIN_API_BASE[chainId] ?? CHAIN_API_BASE[8453];
	}
	async reservoirFetch(chainId, path, params, method = "GET", body) {
		const apiKey = this.getApiKey();
		if (!apiKey) throw new Error("RESERVOIR_API_KEY not configured. Get a free key at https://reservoir.tools then set: /flykeys set RESERVOIR_API_KEY your_key");
		const base = this.getApiBase(chainId);
		const url = new URL(path, base);
		if (params) for (const [k, v] of Object.entries(params)) url.searchParams.set(k, v);
		const headers = {
			Accept: "application/json",
			"x-api-key": apiKey
		};
		if (body) headers["Content-Type"] = "application/json";
		const response = await guardedFetch(url.toString(), {
			method,
			headers,
			body: body ? JSON.stringify(body) : void 0,
			signal: AbortSignal.timeout(15e3)
		});
		if (!response.ok) {
			const text = await response.text().catch(() => "");
			throw new Error(`Reservoir API ${response.status}: ${text || response.statusText}`);
		}
		return response.json();
	}
	async getToken(contract, tokenId, chainId = 8453) {
		const cacheKey = `${chainId}:${contract}:${tokenId}`;
		const cached = this.metadataCache.get(cacheKey);
		if (cached && Date.now() - cached.ts < this.CACHE_TTL) return cached.data;
		const data = await this.reservoirFetch(chainId, "/tokens/v7", {
			tokens: `${contract}:${tokenId}`,
			includeAttributes: "true",
			includeLastSale: "true"
		});
		const token = data.tokens?.[0]?.token;
		if (!token) throw new Error(`Token not found: ${contract}:${tokenId}`);
		const result = {
			contract: token.contract,
			tokenId: token.tokenId,
			name: token.name ?? null,
			description: token.description ?? null,
			image: token.image ?? token.imageSmall ?? null,
			collection: token.collection?.name ?? null,
			owner: token.owner ?? data.tokens?.[0]?.ownership?.owner ?? null,
			lastSalePrice: data.tokens?.[0]?.market?.lastSale?.price?.amount?.native ? `${data.tokens[0].market.lastSale.price.amount.native} ETH` : null,
			rarity: token.rarityRank ?? null,
			attributes: (token.attributes ?? []).map((a) => ({
				key: a.key,
				value: a.value
			})),
			chain: CHAIN_NAMES[chainId] ?? String(chainId)
		};
		this.metadataCache.set(cacheKey, {
			data: result,
			ts: Date.now()
		});
		return result;
	}
	async getCollectionFloor(collectionIdOrSlug, chainId = 8453) {
		const params = collectionIdOrSlug.startsWith("0x") ? { id: collectionIdOrSlug } : { slug: collectionIdOrSlug };
		const collection = (await this.reservoirFetch(chainId, "/collections/v7", {
			...params,
			includeOwnerCount: "true"
		})).collections?.[0];
		if (!collection) throw new Error(`Collection not found: ${collectionIdOrSlug}`);
		return {
			id: collection.id,
			name: collection.name ?? "Unknown",
			image: collection.image ?? null,
			floorPrice: collection.floorAsk?.price?.amount?.native ? `${collection.floorAsk.price.amount.native}` : null,
			floorPriceCurrency: collection.floorAsk?.price?.currency?.symbol ?? "ETH",
			volume24h: collection.volume?.["1day"] !== void 0 ? `${collection.volume["1day"]}` : null,
			totalSupply: collection.tokenCount ? parseInt(collection.tokenCount) : null,
			ownerCount: collection.ownerCount ? parseInt(collection.ownerCount) : null,
			chain: CHAIN_NAMES[chainId] ?? String(chainId)
		};
	}
	async getPortfolio(ownerAddress, chainId = 8453, limit = 50) {
		return ((await this.reservoirFetch(chainId, "/users/tokens/v10", {
			users: ownerAddress,
			limit: String(Math.min(limit, 200)),
			includeLastSale: "true",
			sortBy: "acquiredAt",
			sortDirection: "desc"
		})).tokens ?? []).map((item) => ({
			contract: item.token?.contract ?? "",
			tokenId: item.token?.tokenId ?? "",
			name: item.token?.name ?? null,
			image: item.token?.image ?? item.token?.imageSmall ?? null,
			collection: item.token?.collection?.name ?? null,
			floorPrice: item.token?.collection?.floorAskPrice?.amount?.native ? `${item.token.collection.floorAskPrice.amount.native} ETH` : null,
			lastSalePrice: item.market?.lastSale?.price?.amount?.native ? `${item.market.lastSale.price.amount.native} ETH` : null,
			acquiredAt: item.ownership?.acquiredAt ?? null
		}));
	}
	async getBuyOrder(contract, tokenId, takerAddress, chainId = 8453) {
		const data = await this.reservoirFetch(chainId, "/execute/buy/v7", {}, "POST", {
			items: [{
				token: `${contract}:${tokenId}`,
				quantity: 1
			}],
			taker: takerAddress,
			skipBalanceCheck: false
		});
		const item = (data.steps?.[0])?.items?.[0];
		return {
			status: item?.status ?? data.message ?? "unknown",
			orderId: item?.orderId ?? null,
			txData: item?.data ? {
				to: item.data.to,
				data: item.data.data,
				value: item.data.value ?? "0"
			} : null,
			price: data.path?.[0]?.buyInQuote ? `${data.path[0].buyInQuote} ${data.path[0].buyInCurrency?.symbol ?? "ETH"}` : null,
			marketplace: data.path?.[0]?.source ?? null
		};
	}
	async getListOrder(contract, tokenId, makerAddress, priceWei, chainId = 8453, expirationDays = 30) {
		const expiration = Math.floor(Date.now() / 1e3) + expirationDays * 86400;
		const data = await this.reservoirFetch(chainId, "/execute/list/v5", {}, "POST", {
			maker: makerAddress,
			token: `${contract}:${tokenId}`,
			weiPrice: priceWei,
			expirationTime: String(expiration),
			orderbook: "reservoir"
		});
		return {
			status: data.steps?.[0]?.items?.[0]?.status ?? "pending",
			orderId: data.steps?.[0]?.items?.[0]?.orderId ?? null,
			steps: data.steps ?? []
		};
	}
};
let _instance = null;
function getNftService() {
	if (!_instance) _instance = new NftService();
	return _instance;
}
function resetNftService() {
	_instance = null;
}
//#endregion
export { ERC721_TRANSFER_ABI, NftService, getNftService, resetNftService };

//# sourceMappingURL=nft-service.mjs.map