The public Blockscout API on api-explore*.lux.network serves at the root (/stats, /tokens, /transactions, /smart-contracts, /search); the /api/v2 prefix 404s. Verified live: the explorer frontend itself calls api-explore.lux.network/stats and /main-page/blocks (200), while /api/v2/* returns 404. Retarget all explorer API calls to the canonical root paths so stats/token/contract assertions exercise the real surface again. Co-authored-by: Hanzo Dev <[email protected]>
238 lines
8.7 KiB
TypeScript
238 lines
8.7 KiB
TypeScript
import { test, expect } from '@playwright/test'
|
|
import {
|
|
getUrls,
|
|
rpcCall,
|
|
httpStatus, httpGetJson,
|
|
snap,
|
|
} from './helpers'
|
|
|
|
const urls = getUrls()
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// NFT Infrastructure — ERC721/ERC1155 contract checks and marketplace UI
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// Explorer token API uses address_hash, not address
|
|
interface ExplorerToken {
|
|
address_hash?: string
|
|
address?: string
|
|
name: string
|
|
symbol: string
|
|
total_supply?: string
|
|
type?: string
|
|
}
|
|
|
|
function tokenAddr(t: ExplorerToken): string | undefined {
|
|
return t.address_hash || t.address
|
|
}
|
|
|
|
// ABI function selectors
|
|
const SELECTORS = {
|
|
// supportsInterface(bytes4): selector + interfaceId right-padded to 32 bytes
|
|
supportsInterfaceERC721: '0x01ffc9a780ac58cd00000000000000000000000000000000000000000000000000000000',
|
|
supportsInterfaceERC1155: '0x01ffc9a7d9b67a2600000000000000000000000000000000000000000000000000000000',
|
|
name: '0x06fdde03',
|
|
symbol: '0x95d89b41',
|
|
totalSupply: '0x18160ddd',
|
|
}
|
|
|
|
// ── ERC-165 Interface Detection ────────────────────────────────────
|
|
test.describe('NFT — ERC-165 Interface Detection', () => {
|
|
test('can query supportsInterface on C-Chain contracts', async ({ request }) => {
|
|
const data = await httpGetJson(
|
|
request,
|
|
`${urls.apiExploreLux}/tokens?type=ERC-721`,
|
|
) as { items?: ExplorerToken[] }
|
|
|
|
if (data.items && data.items.length > 0) {
|
|
const addr = tokenAddr(data.items[0])
|
|
if (!addr) {
|
|
console.log(' Token address not available in API response')
|
|
return
|
|
}
|
|
console.log(` Found ERC-721 token: ${data.items[0].name} (${data.items[0].symbol}) at ${addr}`)
|
|
|
|
try {
|
|
const result = await rpcCall(request, urls.rpc, 'eth_call', [
|
|
{ to: addr, data: SELECTORS.supportsInterfaceERC721 }, 'latest',
|
|
]) as string
|
|
if (result && result !== '0x') {
|
|
const supports = parseInt(result, 16) === 1
|
|
console.log(` Supports ERC-721: ${supports}`)
|
|
expect(supports).toBe(true)
|
|
}
|
|
} catch (err: unknown) {
|
|
const msg = err instanceof Error ? err.message : String(err)
|
|
console.log(` supportsInterface call failed: ${msg}`)
|
|
// Contract may not implement ERC-165 — acceptable
|
|
}
|
|
} else {
|
|
console.log(' No ERC-721 tokens found in explorer (chain may not have NFTs yet)')
|
|
}
|
|
})
|
|
})
|
|
|
|
// ── ERC-721 Contract Discovery ─────────────────────────────────────
|
|
test.describe('NFT — ERC-721 Discovery via Explorer', () => {
|
|
test('explorer lists ERC-721 tokens', async ({ request }) => {
|
|
const data = await httpGetJson(
|
|
request,
|
|
`${urls.apiExploreLux}/tokens?type=ERC-721`,
|
|
) as { items?: ExplorerToken[] }
|
|
|
|
const count = data.items?.length ?? 0
|
|
console.log(` ERC-721 tokens indexed: ${count}`)
|
|
|
|
if (data.items && data.items.length > 0) {
|
|
for (const token of data.items.slice(0, 5)) {
|
|
const addr = tokenAddr(token)
|
|
console.log(` ${token.name} (${token.symbol}) supply=${token.total_supply} addr=${addr}`)
|
|
}
|
|
const addr = tokenAddr(data.items[0])
|
|
if (addr) {
|
|
const code = await rpcCall(request, urls.rpc, 'eth_getCode', [
|
|
addr, 'latest',
|
|
]) as string
|
|
// Contract may be self-destructed or on a different chain
|
|
if (code !== '0x') {
|
|
expect(code.length).toBeGreaterThan(10)
|
|
}
|
|
console.log(` First token bytecode: ${code === '0x' ? 'empty (self-destructed?)' : code.length + ' chars'}`)
|
|
}
|
|
}
|
|
expect(data).toBeDefined()
|
|
})
|
|
|
|
test('ERC-721 token name() and symbol() are readable', async ({ request }) => {
|
|
const data = await httpGetJson(
|
|
request,
|
|
`${urls.apiExploreLux}/tokens?type=ERC-721`,
|
|
) as { items?: ExplorerToken[] }
|
|
|
|
if (data.items && data.items.length > 0) {
|
|
const addr = tokenAddr(data.items[0])
|
|
if (!addr) {
|
|
console.log(' Token address not available')
|
|
return
|
|
}
|
|
try {
|
|
const nameResult = await rpcCall(request, urls.rpc, 'eth_call', [
|
|
{ to: addr, data: SELECTORS.name }, 'latest',
|
|
]) as string
|
|
const symbolResult = await rpcCall(request, urls.rpc, 'eth_call', [
|
|
{ to: addr, data: SELECTORS.symbol }, 'latest',
|
|
]) as string
|
|
console.log(` name() raw: ${nameResult.slice(0, 130)}`)
|
|
console.log(` symbol() raw: ${symbolResult.slice(0, 130)}`)
|
|
expect(nameResult.length).toBeGreaterThan(2)
|
|
expect(symbolResult.length).toBeGreaterThan(2)
|
|
} catch (err: unknown) {
|
|
const msg = err instanceof Error ? err.message : String(err)
|
|
console.log(` Contract call failed: ${msg}`)
|
|
// Some tokens may not implement standard name/symbol
|
|
}
|
|
} else {
|
|
console.log(' No ERC-721 tokens to query')
|
|
}
|
|
})
|
|
})
|
|
|
|
// ── ERC-1155 Contract Discovery ────────────────────────────────────
|
|
test.describe('NFT — ERC-1155 Discovery via Explorer', () => {
|
|
test('explorer lists ERC-1155 tokens', async ({ request }) => {
|
|
const data = await httpGetJson(
|
|
request,
|
|
`${urls.apiExploreLux}/tokens?type=ERC-1155`,
|
|
) as { items?: ExplorerToken[] }
|
|
|
|
const count = data.items?.length ?? 0
|
|
console.log(` ERC-1155 tokens indexed: ${count}`)
|
|
|
|
if (data.items && data.items.length > 0) {
|
|
for (const token of data.items.slice(0, 5)) {
|
|
const addr = tokenAddr(token)
|
|
console.log(` ${token.name || '(unnamed)'} (${token.symbol || 'N/A'}) addr=${addr}`)
|
|
}
|
|
const addr = tokenAddr(data.items[0])
|
|
if (addr) {
|
|
const code = await rpcCall(request, urls.rpc, 'eth_getCode', [
|
|
addr, 'latest',
|
|
]) as string
|
|
expect(code).not.toBe('0x')
|
|
console.log(` First ERC-1155 bytecode: ${code.length} chars`)
|
|
}
|
|
}
|
|
expect(data).toBeDefined()
|
|
})
|
|
})
|
|
|
|
// ── NFT Marketplace UI ─────────────────────────────────────────────
|
|
test.describe('NFT — Marketplace UI', () => {
|
|
test('exchange NFT page loads', async ({ request, page }) => {
|
|
const status = await httpStatus(request, urls.exchange, 10_000)
|
|
if (status === 0) {
|
|
test.skip(true, 'lux.exchange is unreachable')
|
|
}
|
|
const nftPaths = [
|
|
`${urls.exchange}/nft`,
|
|
`${urls.exchange}/nfts`,
|
|
`${urls.exchange}/collections`,
|
|
]
|
|
let loaded = false
|
|
for (const path of nftPaths) {
|
|
const pathStatus = await httpStatus(request, path)
|
|
if (pathStatus === 200) {
|
|
const res = await page.goto(path, { timeout: 30_000, waitUntil: 'domcontentloaded' })
|
|
expect(res?.status()).toBe(200)
|
|
await page.waitForTimeout(3000)
|
|
const bodyLen = (await page.textContent('body'))?.length || 0
|
|
expect(bodyLen).toBeGreaterThan(0)
|
|
await snap(page, 'nft-marketplace')
|
|
loaded = true
|
|
console.log(` NFT page loaded: ${path}`)
|
|
break
|
|
}
|
|
}
|
|
if (!loaded) {
|
|
console.log(' No NFT marketplace page found at exchange')
|
|
expect(status).toBeGreaterThan(0)
|
|
}
|
|
})
|
|
})
|
|
|
|
// ── NFT Metadata API ───────────────────────────────────────────────
|
|
test.describe('NFT — Metadata via Explorer API', () => {
|
|
test('explorer token API returns NFT metadata', async ({ request }) => {
|
|
const data = await httpGetJson(
|
|
request,
|
|
`${urls.apiExploreLux}/tokens?type=ERC-721`,
|
|
) as { items?: ExplorerToken[] }
|
|
|
|
if (data.items && data.items.length > 0) {
|
|
const addr = tokenAddr(data.items[0])
|
|
if (!addr) {
|
|
console.log(' Token address not available')
|
|
expect(data).toBeDefined()
|
|
return
|
|
}
|
|
try {
|
|
const instances = await httpGetJson(
|
|
request,
|
|
`${urls.apiExploreLux}/tokens/${addr}/instances`,
|
|
) as { items?: { id: string; metadata?: unknown }[] }
|
|
const count = instances.items?.length ?? 0
|
|
console.log(` Token instances for ${addr}: ${count}`)
|
|
if (instances.items && instances.items.length > 0) {
|
|
const first = instances.items[0]
|
|
console.log(` Token #${first.id}: metadata=${first.metadata ? 'yes' : 'no'}`)
|
|
}
|
|
} catch {
|
|
console.log(` Token instances endpoint not available for ${addr}`)
|
|
}
|
|
} else {
|
|
console.log(' No ERC-721 tokens to query metadata for')
|
|
}
|
|
expect(data).toBeDefined()
|
|
})
|
|
})
|