mirror of
https://github.com/luxfi/market.git
synced 2026-09-24 00:25:57 +00:00
refactor: use @luxfi/chains, @luxfi/contracts, @luxfi/hooks shared packages
- Replace inline chain/contract/hook implementations with @luxfi/* imports - All 6 lib/hook files now thin re-exports from shared packages - Standalone market at github.com/luxfi/market (removed from exchange monorepo)
This commit is contained in:
@@ -12,6 +12,9 @@
|
||||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@luxfi/chains": "^1.0.0",
|
||||
"@luxfi/contracts": "^1.0.0",
|
||||
"@luxfi/hooks": "^1.0.0",
|
||||
"@radix-ui/react-dialog": "^1.1.0",
|
||||
"@radix-ui/react-dropdown-menu": "^2.1.0",
|
||||
"@radix-ui/react-slot": "^1.1.0",
|
||||
|
||||
+1
-17
@@ -1,17 +1 @@
|
||||
'use client'
|
||||
|
||||
import { createContext, useContext } from 'react'
|
||||
|
||||
export interface ChainContextValue {
|
||||
chainId: number
|
||||
setChainId: (id: number) => void
|
||||
}
|
||||
|
||||
export const ChainContext = createContext<ChainContextValue>({
|
||||
chainId: 96369,
|
||||
setChainId: () => {},
|
||||
})
|
||||
|
||||
export function useChainContext() {
|
||||
return useContext(ChainContext)
|
||||
}
|
||||
export { useChainContext, ChainContext, type ChainContextValue } from '@luxfi/hooks'
|
||||
|
||||
+10
-108
@@ -1,108 +1,10 @@
|
||||
'use client'
|
||||
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import {
|
||||
getCollections,
|
||||
getToken,
|
||||
getTokenInstances,
|
||||
getTokenInstance,
|
||||
getTokenInstanceTransfers,
|
||||
getTokenTransfers,
|
||||
getAddressTokens,
|
||||
searchTokens,
|
||||
} from '@/lib/explorer'
|
||||
|
||||
// ---- Query key factory ----
|
||||
const nftKeys = {
|
||||
all: ['nft'] as const,
|
||||
collections: (chainId: number) => [...nftKeys.all, 'collections', chainId] as const,
|
||||
collection: (chainId: number, address: string) => [...nftKeys.all, 'collection', chainId, address] as const,
|
||||
instances: (chainId: number, address: string) => [...nftKeys.all, 'instances', chainId, address] as const,
|
||||
instance: (chainId: number, address: string, tokenId: string) =>
|
||||
[...nftKeys.all, 'instance', chainId, address, tokenId] as const,
|
||||
transfers: (chainId: number, address: string, tokenId: string) =>
|
||||
[...nftKeys.all, 'transfers', chainId, address, tokenId] as const,
|
||||
collectionTransfers: (chainId: number, address: string) =>
|
||||
[...nftKeys.all, 'collection-transfers', chainId, address] as const,
|
||||
portfolio: (chainId: number, wallet: string) => [...nftKeys.all, 'portfolio', chainId, wallet] as const,
|
||||
search: (chainId: number, query: string) => [...nftKeys.all, 'search', chainId, query] as const,
|
||||
}
|
||||
|
||||
/** Fetch NFT collections on a chain */
|
||||
export function useCollections(chainId: number, search?: string) {
|
||||
return useQuery({
|
||||
queryKey: search ? nftKeys.search(chainId, search) : nftKeys.collections(chainId),
|
||||
queryFn: () => getCollections(chainId, { q: search }),
|
||||
staleTime: 60_000,
|
||||
})
|
||||
}
|
||||
|
||||
/** Fetch a single collection/token by address */
|
||||
export function useCollection(chainId: number, address: string) {
|
||||
return useQuery({
|
||||
queryKey: nftKeys.collection(chainId, address),
|
||||
queryFn: () => getToken(chainId, address),
|
||||
enabled: !!address,
|
||||
staleTime: 60_000,
|
||||
})
|
||||
}
|
||||
|
||||
/** Fetch NFT instances (items) in a collection */
|
||||
export function useTokenInstances(chainId: number, address: string) {
|
||||
return useQuery({
|
||||
queryKey: nftKeys.instances(chainId, address),
|
||||
queryFn: () => getTokenInstances(chainId, address),
|
||||
enabled: !!address,
|
||||
staleTime: 30_000,
|
||||
})
|
||||
}
|
||||
|
||||
/** Fetch a single NFT instance with full metadata */
|
||||
export function useTokenInstance(chainId: number, address: string, tokenId: string) {
|
||||
return useQuery({
|
||||
queryKey: nftKeys.instance(chainId, address, tokenId),
|
||||
queryFn: () => getTokenInstance(chainId, address, tokenId),
|
||||
enabled: !!address && !!tokenId,
|
||||
staleTime: 30_000,
|
||||
})
|
||||
}
|
||||
|
||||
/** Fetch transfer history for a specific NFT */
|
||||
export function useTokenInstanceTransfers(chainId: number, address: string, tokenId: string) {
|
||||
return useQuery({
|
||||
queryKey: nftKeys.transfers(chainId, address, tokenId),
|
||||
queryFn: () => getTokenInstanceTransfers(chainId, address, tokenId),
|
||||
enabled: !!address && !!tokenId,
|
||||
staleTime: 30_000,
|
||||
})
|
||||
}
|
||||
|
||||
/** Fetch all transfers for a collection */
|
||||
export function useCollectionTransfers(chainId: number, address: string) {
|
||||
return useQuery({
|
||||
queryKey: nftKeys.collectionTransfers(chainId, address),
|
||||
queryFn: () => getTokenTransfers(chainId, address),
|
||||
enabled: !!address,
|
||||
staleTime: 30_000,
|
||||
})
|
||||
}
|
||||
|
||||
/** Fetch NFTs owned by a wallet on a chain */
|
||||
export function usePortfolioNFTs(chainId: number, walletAddress: string | undefined) {
|
||||
return useQuery({
|
||||
queryKey: nftKeys.portfolio(chainId, walletAddress ?? ''),
|
||||
queryFn: () => getAddressTokens(chainId, walletAddress!),
|
||||
enabled: !!walletAddress,
|
||||
staleTime: 30_000,
|
||||
})
|
||||
}
|
||||
|
||||
/** Search for NFT collections */
|
||||
export function useSearchCollections(chainId: number, query: string) {
|
||||
return useQuery({
|
||||
queryKey: nftKeys.search(chainId, query),
|
||||
queryFn: () => searchTokens(chainId, query),
|
||||
enabled: query.length >= 2,
|
||||
staleTime: 15_000,
|
||||
})
|
||||
}
|
||||
export {
|
||||
useCollections,
|
||||
useCollection,
|
||||
useTokenInstances,
|
||||
useTokenInstance,
|
||||
useTokenInstanceTransfers,
|
||||
useCollectionTransfers,
|
||||
usePortfolioNFTs,
|
||||
useSearchCollections,
|
||||
} from '@luxfi/hooks'
|
||||
|
||||
+10
-81
@@ -1,81 +1,10 @@
|
||||
import { defineChain } from 'viem'
|
||||
|
||||
export const luxMainnet = defineChain({
|
||||
id: 96369,
|
||||
name: 'Lux',
|
||||
nativeCurrency: { name: 'LUX', symbol: 'LUX', decimals: 18 },
|
||||
rpcUrls: {
|
||||
default: { http: ['https://api.lux.network/mainnet/ext/bc/C/rpc'] },
|
||||
},
|
||||
blockExplorers: {
|
||||
default: { name: 'Lux Explorer', url: 'https://explore.lux.network' },
|
||||
},
|
||||
})
|
||||
|
||||
export const zooMainnet = defineChain({
|
||||
id: 200200,
|
||||
name: 'Zoo',
|
||||
nativeCurrency: { name: 'ZOO', symbol: 'ZOO', decimals: 18 },
|
||||
rpcUrls: {
|
||||
default: { http: ['https://api.lux.network/mainnet/ext/bc/zoo/rpc'] },
|
||||
},
|
||||
blockExplorers: {
|
||||
default: { name: 'Zoo Explorer', url: 'https://explore-zoo.lux.network' },
|
||||
},
|
||||
})
|
||||
|
||||
export const hanzoMainnet = defineChain({
|
||||
id: 36963,
|
||||
name: 'Hanzo',
|
||||
nativeCurrency: { name: 'HANZO', symbol: 'HNZ', decimals: 18 },
|
||||
rpcUrls: {
|
||||
default: { http: ['https://api.lux.network/mainnet/ext/bc/hanzo/rpc'] },
|
||||
},
|
||||
blockExplorers: {
|
||||
default: { name: 'Hanzo Explorer', url: 'https://explore-hanzo.lux.network' },
|
||||
},
|
||||
})
|
||||
|
||||
export const spcMainnet = defineChain({
|
||||
id: 36911,
|
||||
name: 'SPC',
|
||||
nativeCurrency: { name: 'SPC', symbol: 'SPC', decimals: 18 },
|
||||
rpcUrls: {
|
||||
default: { http: ['https://api.lux.network/mainnet/ext/bc/spc/rpc'] },
|
||||
},
|
||||
blockExplorers: {
|
||||
default: { name: 'SPC Explorer', url: 'https://explore-spc.lux.network' },
|
||||
},
|
||||
})
|
||||
|
||||
export const parsMainnet = defineChain({
|
||||
id: 494949,
|
||||
name: 'Pars',
|
||||
nativeCurrency: { name: 'PARS', symbol: 'PARS', decimals: 18 },
|
||||
rpcUrls: {
|
||||
default: { http: ['https://api.lux.network/mainnet/ext/bc/pars/rpc'] },
|
||||
},
|
||||
blockExplorers: {
|
||||
default: { name: 'Pars Explorer', url: 'https://explore-pars.lux.network' },
|
||||
},
|
||||
})
|
||||
|
||||
export const supportedChains = [luxMainnet, zooMainnet, hanzoMainnet, spcMainnet, parsMainnet] as const
|
||||
|
||||
/** Map chain ID to Explorer API base URL */
|
||||
export const EXPLORER_API: Record<number, string> = {
|
||||
96369: 'https://explore.lux.network/api/v2',
|
||||
200200: 'https://explore-zoo.lux.network/api/v2',
|
||||
36963: 'https://explore-hanzo.lux.network/api/v2',
|
||||
36911: 'https://explore-spc.lux.network/api/v2',
|
||||
494949: 'https://explore-pars.lux.network/api/v2',
|
||||
}
|
||||
|
||||
/** Chain display info for UI */
|
||||
export const CHAIN_INFO: Record<number, { name: string; symbol: string; color: string }> = {
|
||||
96369: { name: 'Lux', symbol: 'LUX', color: '#E8E8E8' },
|
||||
200200: { name: 'Zoo', symbol: 'ZOO', color: '#7C5CFC' },
|
||||
36963: { name: 'Hanzo', symbol: 'HNZ', color: '#FF6B35' },
|
||||
36911: { name: 'SPC', symbol: 'SPC', color: '#00C2FF' },
|
||||
494949: { name: 'Pars', symbol: 'PARS', color: '#FFD700' },
|
||||
}
|
||||
export {
|
||||
lux as luxMainnet,
|
||||
zoo as zooMainnet,
|
||||
hanzo as hanzoMainnet,
|
||||
spc as spcMainnet,
|
||||
pars as parsMainnet,
|
||||
chains as supportedChains,
|
||||
EXPLORER_API,
|
||||
CHAIN_INFO,
|
||||
} from '@luxfi/chains'
|
||||
|
||||
+7
-356
@@ -1,356 +1,7 @@
|
||||
import type { Address } from 'viem'
|
||||
|
||||
// Contract addresses per chain
|
||||
export const CONTRACTS: Record<
|
||||
number,
|
||||
{
|
||||
markets: Address
|
||||
lssvmPairFactory: Address
|
||||
linearCurve: Address
|
||||
exponentialCurve: Address
|
||||
}
|
||||
> = {
|
||||
// Lux Mainnet (C-Chain)
|
||||
96369: {
|
||||
markets: '0x308EBD39eB5E27980944630A0af6F8B0d19e31C6',
|
||||
lssvmPairFactory: '0x29E3E018C3C19F7713B2dffa3A3c340fD2c7089E',
|
||||
linearCurve: '0x360149cC47A3996522376E4131b4A6eB2A1Ca3D3',
|
||||
exponentialCurve: '0x28EBC6764A1c7Ed47b38772E197761102b08f3bb',
|
||||
},
|
||||
// Zoo Mainnet
|
||||
200200: {
|
||||
markets: '0x49B76d9ca9BcA9e9eDef5e2EC4eD425b2e6b2445',
|
||||
lssvmPairFactory: '0x28EBC6764A1c7Ed47b38772E197761102b08f3bb',
|
||||
linearCurve: '0x2BaeF607871FB40515Fb42A299a1E0b03F0C681f',
|
||||
exponentialCurve: '0x360149cC47A3996522376E4131b4A6eB2A1Ca3D3',
|
||||
},
|
||||
// Hanzo Mainnet
|
||||
36963: {
|
||||
markets: '0x6B7D3c38A3e030B95E101927Ee6ff9913ef626d4',
|
||||
lssvmPairFactory: '0x60b2d8E6B5B0FEeE529DcC6c460C44eed7b2E82A',
|
||||
linearCurve: '0x719685C371ce4C4720d3D7877CBf9bc867Ac39a6',
|
||||
exponentialCurve: '0xB4e242f9417872A843B2D0b92FCf89055349ABb5',
|
||||
},
|
||||
// SPC Mainnet
|
||||
36911: {
|
||||
markets: '0x9BC44B0De1aBe2e436C8fA5Cd5cA519026b9D8fD',
|
||||
lssvmPairFactory: '0x38E71f39f3f46907E03C48983F6578F9a8c2e72e',
|
||||
linearCurve: '0x9607F59f2377Dff9A56D0A76F0313040070c08CD',
|
||||
exponentialCurve: '0xdE5280a9306da7829A4Ba1fBf87B58e1bB4F4A53',
|
||||
},
|
||||
// Pars Mainnet
|
||||
494949: {
|
||||
markets: '0x3589fd09e7dfF3f7653fc4965B7CE1b8d8fdA9Bd',
|
||||
lssvmPairFactory: '0xb43dB9AF0C5CACb99f783E30398Ee0AEe6744212',
|
||||
linearCurve: '0xd13AB81F02449B1630ecd940Be5Fb9CD367225B4',
|
||||
exponentialCurve: '0xBc92f4e290F8Ad03F5348F81a27fb2Af3B37ec47',
|
||||
},
|
||||
}
|
||||
|
||||
// Market.sol ABI (Seaport-based)
|
||||
export const MARKET_ABI = [
|
||||
{
|
||||
name: 'list',
|
||||
type: 'function',
|
||||
stateMutability: 'nonpayable',
|
||||
inputs: [
|
||||
{ name: 'nftContract', type: 'address' },
|
||||
{ name: 'tokenId', type: 'uint256' },
|
||||
{ name: 'paymentToken', type: 'address' },
|
||||
{ name: 'price', type: 'uint256' },
|
||||
{ name: 'duration', type: 'uint256' },
|
||||
],
|
||||
outputs: [{ name: 'listingId', type: 'bytes32' }],
|
||||
},
|
||||
{
|
||||
name: 'cancelListing',
|
||||
type: 'function',
|
||||
stateMutability: 'nonpayable',
|
||||
inputs: [{ name: 'listingId', type: 'bytes32' }],
|
||||
outputs: [],
|
||||
},
|
||||
{
|
||||
name: 'buy',
|
||||
type: 'function',
|
||||
stateMutability: 'payable',
|
||||
inputs: [{ name: 'listingId', type: 'bytes32' }],
|
||||
outputs: [],
|
||||
},
|
||||
{
|
||||
name: 'makeOffer',
|
||||
type: 'function',
|
||||
stateMutability: 'payable',
|
||||
inputs: [
|
||||
{ name: 'nftContract', type: 'address' },
|
||||
{ name: 'tokenId', type: 'uint256' },
|
||||
{ name: 'paymentToken', type: 'address' },
|
||||
{ name: 'amount', type: 'uint256' },
|
||||
{ name: 'duration', type: 'uint256' },
|
||||
],
|
||||
outputs: [{ name: 'offerId', type: 'bytes32' }],
|
||||
},
|
||||
{
|
||||
name: 'cancelOffer',
|
||||
type: 'function',
|
||||
stateMutability: 'nonpayable',
|
||||
inputs: [{ name: 'offerId', type: 'bytes32' }],
|
||||
outputs: [],
|
||||
},
|
||||
{
|
||||
name: 'acceptOffer',
|
||||
type: 'function',
|
||||
stateMutability: 'nonpayable',
|
||||
inputs: [{ name: 'offerId', type: 'bytes32' }],
|
||||
outputs: [],
|
||||
},
|
||||
{
|
||||
name: 'getListing',
|
||||
type: 'function',
|
||||
stateMutability: 'view',
|
||||
inputs: [{ name: 'listingId', type: 'bytes32' }],
|
||||
outputs: [
|
||||
{ name: 'seller', type: 'address' },
|
||||
{ name: 'nftContract', type: 'address' },
|
||||
{ name: 'tokenId', type: 'uint256' },
|
||||
{ name: 'paymentToken', type: 'address' },
|
||||
{ name: 'price', type: 'uint256' },
|
||||
{ name: 'expiry', type: 'uint256' },
|
||||
{ name: 'active', type: 'bool' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Listing',
|
||||
type: 'event',
|
||||
inputs: [
|
||||
{ name: 'listingId', type: 'bytes32', indexed: true },
|
||||
{ name: 'seller', type: 'address', indexed: true },
|
||||
{ name: 'nftContract', type: 'address', indexed: false },
|
||||
{ name: 'tokenId', type: 'uint256', indexed: false },
|
||||
{ name: 'price', type: 'uint256', indexed: false },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Sale',
|
||||
type: 'event',
|
||||
inputs: [
|
||||
{ name: 'listingId', type: 'bytes32', indexed: true },
|
||||
{ name: 'buyer', type: 'address', indexed: true },
|
||||
{ name: 'price', type: 'uint256', indexed: false },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'OfferMade',
|
||||
type: 'event',
|
||||
inputs: [
|
||||
{ name: 'offerId', type: 'bytes32', indexed: true },
|
||||
{ name: 'buyer', type: 'address', indexed: true },
|
||||
{ name: 'nftContract', type: 'address', indexed: false },
|
||||
{ name: 'tokenId', type: 'uint256', indexed: false },
|
||||
{ name: 'amount', type: 'uint256', indexed: false },
|
||||
],
|
||||
},
|
||||
] as const
|
||||
|
||||
// ERC721 ABI (minimal for NFT queries)
|
||||
export const ERC721_ABI = [
|
||||
{
|
||||
name: 'name',
|
||||
type: 'function',
|
||||
stateMutability: 'view',
|
||||
inputs: [],
|
||||
outputs: [{ type: 'string' }],
|
||||
},
|
||||
{
|
||||
name: 'symbol',
|
||||
type: 'function',
|
||||
stateMutability: 'view',
|
||||
inputs: [],
|
||||
outputs: [{ type: 'string' }],
|
||||
},
|
||||
{
|
||||
name: 'tokenURI',
|
||||
type: 'function',
|
||||
stateMutability: 'view',
|
||||
inputs: [{ name: 'tokenId', type: 'uint256' }],
|
||||
outputs: [{ type: 'string' }],
|
||||
},
|
||||
{
|
||||
name: 'ownerOf',
|
||||
type: 'function',
|
||||
stateMutability: 'view',
|
||||
inputs: [{ name: 'tokenId', type: 'uint256' }],
|
||||
outputs: [{ type: 'address' }],
|
||||
},
|
||||
{
|
||||
name: 'balanceOf',
|
||||
type: 'function',
|
||||
stateMutability: 'view',
|
||||
inputs: [{ name: 'owner', type: 'address' }],
|
||||
outputs: [{ type: 'uint256' }],
|
||||
},
|
||||
{
|
||||
name: 'approve',
|
||||
type: 'function',
|
||||
stateMutability: 'nonpayable',
|
||||
inputs: [
|
||||
{ name: 'to', type: 'address' },
|
||||
{ name: 'tokenId', type: 'uint256' },
|
||||
],
|
||||
outputs: [],
|
||||
},
|
||||
{
|
||||
name: 'setApprovalForAll',
|
||||
type: 'function',
|
||||
stateMutability: 'nonpayable',
|
||||
inputs: [
|
||||
{ name: 'operator', type: 'address' },
|
||||
{ name: 'approved', type: 'bool' },
|
||||
],
|
||||
outputs: [],
|
||||
},
|
||||
{
|
||||
name: 'isApprovedForAll',
|
||||
type: 'function',
|
||||
stateMutability: 'view',
|
||||
inputs: [
|
||||
{ name: 'owner', type: 'address' },
|
||||
{ name: 'operator', type: 'address' },
|
||||
],
|
||||
outputs: [{ type: 'bool' }],
|
||||
},
|
||||
{
|
||||
name: 'totalSupply',
|
||||
type: 'function',
|
||||
stateMutability: 'view',
|
||||
inputs: [],
|
||||
outputs: [{ type: 'uint256' }],
|
||||
},
|
||||
{
|
||||
name: 'Transfer',
|
||||
type: 'event',
|
||||
inputs: [
|
||||
{ name: 'from', type: 'address', indexed: true },
|
||||
{ name: 'to', type: 'address', indexed: true },
|
||||
{ name: 'tokenId', type: 'uint256', indexed: true },
|
||||
],
|
||||
},
|
||||
] as const
|
||||
|
||||
// ERC1155 minimal ABI
|
||||
export const ERC1155_ABI = [
|
||||
{
|
||||
name: 'balanceOf',
|
||||
type: 'function',
|
||||
stateMutability: 'view',
|
||||
inputs: [
|
||||
{ name: 'account', type: 'address' },
|
||||
{ name: 'id', type: 'uint256' },
|
||||
],
|
||||
outputs: [{ type: 'uint256' }],
|
||||
},
|
||||
{
|
||||
name: 'uri',
|
||||
type: 'function',
|
||||
stateMutability: 'view',
|
||||
inputs: [{ name: 'id', type: 'uint256' }],
|
||||
outputs: [{ type: 'string' }],
|
||||
},
|
||||
{
|
||||
name: 'setApprovalForAll',
|
||||
type: 'function',
|
||||
stateMutability: 'nonpayable',
|
||||
inputs: [
|
||||
{ name: 'operator', type: 'address' },
|
||||
{ name: 'approved', type: 'bool' },
|
||||
],
|
||||
outputs: [],
|
||||
},
|
||||
{
|
||||
name: 'isApprovedForAll',
|
||||
type: 'function',
|
||||
stateMutability: 'view',
|
||||
inputs: [
|
||||
{ name: 'owner', type: 'address' },
|
||||
{ name: 'operator', type: 'address' },
|
||||
],
|
||||
outputs: [{ type: 'bool' }],
|
||||
},
|
||||
] as const
|
||||
|
||||
// LSSVM Pair ABI (sudoswap AMM for NFTs)
|
||||
export const LSSVM_PAIR_ABI = [
|
||||
{
|
||||
name: 'swapTokenForSpecificNFTs',
|
||||
type: 'function',
|
||||
stateMutability: 'payable',
|
||||
inputs: [
|
||||
{ name: 'nftIds', type: 'uint256[]' },
|
||||
{ name: 'maxExpectedTokenInput', type: 'uint256' },
|
||||
{ name: 'nftRecipient', type: 'address' },
|
||||
{ name: 'isRouter', type: 'bool' },
|
||||
{ name: 'routerCaller', type: 'address' },
|
||||
],
|
||||
outputs: [{ name: 'inputAmount', type: 'uint256' }],
|
||||
},
|
||||
{
|
||||
name: 'swapNFTsForToken',
|
||||
type: 'function',
|
||||
stateMutability: 'nonpayable',
|
||||
inputs: [
|
||||
{ name: 'nftIds', type: 'uint256[]' },
|
||||
{ name: 'minExpectedTokenOutput', type: 'uint256' },
|
||||
{ name: 'tokenRecipient', type: 'address' },
|
||||
{ name: 'isRouter', type: 'bool' },
|
||||
{ name: 'routerCaller', type: 'address' },
|
||||
],
|
||||
outputs: [{ name: 'outputAmount', type: 'uint256' }],
|
||||
},
|
||||
{
|
||||
name: 'getBuyNFTQuote',
|
||||
type: 'function',
|
||||
stateMutability: 'view',
|
||||
inputs: [
|
||||
{ name: 'assetId', type: 'uint256' },
|
||||
{ name: 'numNFTs', type: 'uint256' },
|
||||
],
|
||||
outputs: [
|
||||
{ name: 'error', type: 'uint8' },
|
||||
{ name: 'newSpotPrice', type: 'uint256' },
|
||||
{ name: 'newDelta', type: 'uint256' },
|
||||
{ name: 'inputAmount', type: 'uint256' },
|
||||
{ name: 'protocolFee', type: 'uint256' },
|
||||
{ name: 'royaltyAmount', type: 'uint256' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'getSellNFTQuote',
|
||||
type: 'function',
|
||||
stateMutability: 'view',
|
||||
inputs: [
|
||||
{ name: 'assetId', type: 'uint256' },
|
||||
{ name: 'numNFTs', type: 'uint256' },
|
||||
],
|
||||
outputs: [
|
||||
{ name: 'error', type: 'uint8' },
|
||||
{ name: 'newSpotPrice', type: 'uint256' },
|
||||
{ name: 'newDelta', type: 'uint256' },
|
||||
{ name: 'outputAmount', type: 'uint256' },
|
||||
{ name: 'protocolFee', type: 'uint256' },
|
||||
{ name: 'royaltyAmount', type: 'uint256' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'spotPrice',
|
||||
type: 'function',
|
||||
stateMutability: 'view',
|
||||
inputs: [],
|
||||
outputs: [{ type: 'uint128' }],
|
||||
},
|
||||
{
|
||||
name: 'nft',
|
||||
type: 'function',
|
||||
stateMutability: 'view',
|
||||
inputs: [],
|
||||
outputs: [{ type: 'address' }],
|
||||
},
|
||||
] as const
|
||||
export {
|
||||
CONTRACTS,
|
||||
MARKET_ABI,
|
||||
ERC721_ABI,
|
||||
ERC1155_ABI,
|
||||
LSSVM_PAIR_ABI,
|
||||
} from '@luxfi/contracts'
|
||||
|
||||
+21
-201
@@ -1,201 +1,21 @@
|
||||
/**
|
||||
* Explorer API client for NFT data.
|
||||
* Uses the /api/v2/ endpoints from our deployed Lux Explorer instances.
|
||||
*/
|
||||
import { EXPLORER_API } from './chains'
|
||||
|
||||
// ---- Types matching Explorer v2 API response shapes ----
|
||||
|
||||
export interface ExplorerToken {
|
||||
address: string
|
||||
name: string
|
||||
symbol: string
|
||||
total_supply: string
|
||||
type: 'ERC-20' | 'ERC-721' | 'ERC-1155' | 'ERC-404'
|
||||
holders: string
|
||||
icon_url: string | null
|
||||
exchange_rate: string | null
|
||||
}
|
||||
|
||||
export interface ExplorerTokenInstance {
|
||||
id: string
|
||||
token: ExplorerToken
|
||||
animation_url: string | null
|
||||
external_app_url: string | null
|
||||
image_url: string | null
|
||||
is_unique: boolean
|
||||
metadata: NFTMetadata | null
|
||||
owner: { hash: string } | null
|
||||
}
|
||||
|
||||
export interface NFTMetadata {
|
||||
name?: string
|
||||
description?: string
|
||||
image?: string
|
||||
image_data?: string
|
||||
external_url?: string
|
||||
animation_url?: string
|
||||
background_color?: string
|
||||
attributes?: NFTTrait[]
|
||||
properties?: Record<string, unknown>
|
||||
}
|
||||
|
||||
export interface NFTTrait {
|
||||
trait_type: string
|
||||
value: string | number
|
||||
display_type?: string
|
||||
max_value?: number
|
||||
}
|
||||
|
||||
export interface ExplorerTransfer {
|
||||
from: { hash: string }
|
||||
to: { hash: string }
|
||||
total: { token_id: string; value: string } | null
|
||||
token: ExplorerToken
|
||||
tx_hash: string
|
||||
timestamp: string
|
||||
method: string
|
||||
block_number: number
|
||||
}
|
||||
|
||||
export interface ExplorerAddress {
|
||||
hash: string
|
||||
is_contract: boolean
|
||||
name: string | null
|
||||
token_balances: unknown[]
|
||||
}
|
||||
|
||||
export interface PaginatedResponse<T> {
|
||||
items: T[]
|
||||
next_page_params: Record<string, string> | null
|
||||
}
|
||||
|
||||
// ---- API Client ----
|
||||
|
||||
function getApiBase(chainId: number): string {
|
||||
return EXPLORER_API[chainId] ?? EXPLORER_API[96369]
|
||||
}
|
||||
|
||||
async function fetchApi<T>(chainId: number, path: string, params?: Record<string, string>): Promise<T> {
|
||||
const base = getApiBase(chainId)
|
||||
const url = new URL(`${base}${path}`)
|
||||
if (params) {
|
||||
Object.entries(params).forEach(([k, v]) => url.searchParams.set(k, v))
|
||||
}
|
||||
const res = await fetch(url.toString(), {
|
||||
headers: { Accept: 'application/json' },
|
||||
next: { revalidate: 30 },
|
||||
})
|
||||
if (!res.ok) throw new Error(`Explorer API error: ${res.status} ${res.statusText}`)
|
||||
return res.json()
|
||||
}
|
||||
|
||||
/** List NFT collections (ERC-721 + ERC-1155 tokens) on a chain */
|
||||
export async function getCollections(
|
||||
chainId: number,
|
||||
params?: { type?: string; q?: string }
|
||||
): Promise<PaginatedResponse<ExplorerToken>> {
|
||||
return fetchApi(chainId, '/tokens', {
|
||||
type: params?.type ?? 'ERC-721,ERC-1155',
|
||||
...(params?.q ? { q: params.q } : {}),
|
||||
})
|
||||
}
|
||||
|
||||
/** Get a specific token/collection by address */
|
||||
export async function getToken(chainId: number, address: string): Promise<ExplorerToken> {
|
||||
return fetchApi(chainId, `/tokens/${address}`)
|
||||
}
|
||||
|
||||
/** List token instances (NFTs) within a collection */
|
||||
export async function getTokenInstances(
|
||||
chainId: number,
|
||||
address: string,
|
||||
params?: { holder_address_hash?: string }
|
||||
): Promise<PaginatedResponse<ExplorerTokenInstance>> {
|
||||
return fetchApi(chainId, `/tokens/${address}/instances`, params as Record<string, string>)
|
||||
}
|
||||
|
||||
/** Get a single NFT instance */
|
||||
export async function getTokenInstance(
|
||||
chainId: number,
|
||||
address: string,
|
||||
tokenId: string
|
||||
): Promise<ExplorerTokenInstance> {
|
||||
return fetchApi(chainId, `/tokens/${address}/instances/${tokenId}`)
|
||||
}
|
||||
|
||||
/** Get transfers for a specific NFT instance */
|
||||
export async function getTokenInstanceTransfers(
|
||||
chainId: number,
|
||||
address: string,
|
||||
tokenId: string
|
||||
): Promise<PaginatedResponse<ExplorerTransfer>> {
|
||||
return fetchApi(chainId, `/tokens/${address}/instances/${tokenId}/transfers`)
|
||||
}
|
||||
|
||||
/** Get holders of a specific NFT instance */
|
||||
export async function getTokenInstanceHolders(
|
||||
chainId: number,
|
||||
address: string,
|
||||
tokenId: string
|
||||
): Promise<PaginatedResponse<{ address: ExplorerAddress; value: string }>> {
|
||||
return fetchApi(chainId, `/tokens/${address}/instances/${tokenId}/holders`)
|
||||
}
|
||||
|
||||
/** Get all token transfers for a collection */
|
||||
export async function getTokenTransfers(
|
||||
chainId: number,
|
||||
address: string
|
||||
): Promise<PaginatedResponse<ExplorerTransfer>> {
|
||||
return fetchApi(chainId, `/tokens/${address}/token-transfers`)
|
||||
}
|
||||
|
||||
/** Get NFTs owned by an address across a specific collection */
|
||||
export async function getAddressTokenInstances(
|
||||
chainId: number,
|
||||
walletAddress: string,
|
||||
tokenAddress: string
|
||||
): Promise<PaginatedResponse<ExplorerTokenInstance>> {
|
||||
return fetchApi(chainId, `/tokens/${tokenAddress}/instances`, {
|
||||
holder_address_hash: walletAddress,
|
||||
})
|
||||
}
|
||||
|
||||
/** Get all NFT tokens held by an address */
|
||||
export async function getAddressTokens(
|
||||
chainId: number,
|
||||
address: string,
|
||||
type: string = 'ERC-721'
|
||||
): Promise<PaginatedResponse<{ token: ExplorerToken; value: string; token_id: string | null; token_instance: ExplorerTokenInstance | null }>> {
|
||||
return fetchApi(chainId, `/addresses/${address}/tokens`, { type })
|
||||
}
|
||||
|
||||
/** Search for tokens by name */
|
||||
export async function searchTokens(
|
||||
chainId: number,
|
||||
query: string
|
||||
): Promise<{ items: Array<{ address: string; name: string; symbol: string; type: string; icon_url: string | null }> }> {
|
||||
return fetchApi(chainId, '/search', { q: query })
|
||||
}
|
||||
|
||||
/** Resolve IPFS URLs to gateway URLs */
|
||||
export function resolveMediaUrl(url: string | null | undefined): string | null {
|
||||
if (!url) return null
|
||||
if (url.startsWith('ipfs://')) {
|
||||
return `https://ipfs.io/ipfs/${url.slice(7)}`
|
||||
}
|
||||
if (url.startsWith('ar://')) {
|
||||
return `https://arweave.net/${url.slice(5)}`
|
||||
}
|
||||
return url
|
||||
}
|
||||
|
||||
/** Get the best image URL for an NFT instance */
|
||||
export function getNftImageUrl(instance: ExplorerTokenInstance): string | null {
|
||||
return (
|
||||
resolveMediaUrl(instance.image_url) ??
|
||||
resolveMediaUrl(instance.metadata?.image) ??
|
||||
resolveMediaUrl(instance.metadata?.image_data) ??
|
||||
null
|
||||
)
|
||||
}
|
||||
export {
|
||||
type ExplorerToken,
|
||||
type ExplorerTokenInstance,
|
||||
type NFTMetadata,
|
||||
type NFTTrait,
|
||||
type ExplorerTransfer,
|
||||
type ExplorerAddress,
|
||||
type PaginatedResponse,
|
||||
getCollections,
|
||||
getToken,
|
||||
getTokenInstances,
|
||||
getTokenInstance,
|
||||
getTokenInstanceTransfers,
|
||||
getTokenInstanceHolders,
|
||||
getTokenTransfers,
|
||||
getAddressTokenInstances,
|
||||
getAddressTokens,
|
||||
searchTokens,
|
||||
resolveMediaUrl,
|
||||
getNftImageUrl,
|
||||
} from '@luxfi/chains/explorer'
|
||||
|
||||
+1
-10
@@ -1,10 +1 @@
|
||||
import { createConfig, http } from 'wagmi'
|
||||
import { injected } from 'wagmi/connectors'
|
||||
import { supportedChains } from './chains'
|
||||
|
||||
export const config = createConfig({
|
||||
chains: supportedChains,
|
||||
connectors: [injected()],
|
||||
transports: Object.fromEntries(supportedChains.map((c) => [c.id, http()])) as any,
|
||||
ssr: true,
|
||||
})
|
||||
export { config } from '@luxfi/chains/wagmi'
|
||||
|
||||
Reference in New Issue
Block a user