From cross-nft
This skill should be used when the user asks to browse, list, buy, offer on, cancel, or accept offers on NFTs at https://www.crossnft.io. Drives the same `api.crossnft.io` GraphQL indexer and the same on-chain `MarketplaceV1` contract (0x0df40a50f2c09885c18245dc90e8e9dcd0e4c3bc on CROSS Chain 612055) as the live frontend. Read-path (`collections`, `token`, `tokens`, `listing`, `offers`, `offer-onchain`, `activities`, `stats`, `search`) hits GraphQL with `apollo-require-preflight` enabled; write-path (`list`, `cancel-listing`, `buy`, `offer`, `cancel-offer`, `accept-offer`) signs MarketplaceV1 calls via viem, auto-running `setApprovalForAll` for the NFT contract and `approve()` for the ERC-20 payment token. Triggers on phrases like "CROSS NFT 마켓", "crossnft.io 구매", "NFT 리스팅", "make offer", "buy listing 321699", "list NFT for sale", "0xa3bd…/30164746843126 사기".
npx claudepluginhub to-nexus/skill-cross-nftThis skill uses the workspace's default tool permissions.
A distributable skill that lets Claude drive the **CROSS NFT** marketplace at `https://www.crossnft.io` — list, browse, buy, make/cancel/accept offers — using the **exact same backends** the live site does:
package-lock.jsonpackage.jsonreferences/cross-nft.mdscripts/_abi.mjsscripts/_addresses.mjsscripts/_api.mjsscripts/_chain.mjsscripts/_format.mjsscripts/_guard.mjsscripts/_io.mjsscripts/_nft.mjsscripts/_signer.mjsscripts/accept-offer.mjsscripts/activities.mjsscripts/buy.mjsscripts/cancel-listing.mjsscripts/cancel-offer.mjsscripts/collections.mjsscripts/list.mjsscripts/listing.mjsGuides Next.js Cache Components and Partial Prerendering (PPR): 'use cache' directives, cacheLife(), cacheTag(), revalidateTag() for caching, invalidation, static/dynamic optimization. Auto-activates on cacheComponents: true.
Processes PDFs: extracts text/tables/images, merges/splits/rotates pages, adds watermarks, creates/fills forms, encrypts/decrypts, OCRs scans. Activates on PDF mentions or output requests.
Share bugs, ideas, or general feedback.
A distributable skill that lets Claude drive the CROSS NFT marketplace at https://www.crossnft.io — list, browse, buy, make/cancel/accept offers — using the exact same backends the live site does:
https://api.crossnft.io/graphql (introspection disabled, apollo-require-preflight: true required)MarketplaceV1 at 0x0df40a50f2c09885c18245dc90e8e9dcd0e4c3bc on CROSS Chain (chain id 612055)MGT, decimals 18) at 0x5b1579a758916560f00212b78a7af728eaa0ffa9Stack: EOA + viem + raw HTTPS. No browser automation, no ERC-4337, no relayer.
Deeper protocol details (ABI provenance, GraphQL filter shapes, fee model) live in
references/cross-nft.md. Read it only when needed — it stays out of context otherwise.
Activate when the user wants to:
listingId / offerId (current price, isActive, Dutch-auction status)If the user pastes a https://www.crossnft.io/collections/<addr>/tokens/<id> URL, extract <addr> and <id> and route them verbatim to token.mjs (or to list.mjs / offer.mjs if they want to act on it).
Trigger phrases (Korean + English):
"CROSS NFT 마켓" / "크로스NFT 마켓플레이스" / "crossnft.io 보여줘""NFT 리스팅 해줘" / "NFT 판매 등록" / "이 NFT 30 MGT에 팔기""NFT 사기 listing 321699" / "이 NFT 구매" / "buy listing""오퍼 넣기" / "NFT 오퍼 25 MGT" / "make offer 25""오퍼 수락" / "내 NFT 오퍼 받기" / "accept offer 30""내 listing 취소" / "오퍼 취소 30""materials 컬렉션 floor" / "crossnft 활동내역" / "거래내역 보여줘""crossnft list NFT" / "list NFT for sale on crossnft""buy crossnft listing 321699" / "buy this NFT""make offer 25 MGT for 0x… token 30164746843126""cancel my offer 30" / "accept offer 30""top crossnft collections by volume 7d" / "crossnft activity sale,listing"Run these checks in order. Stop and report to the user at the first failure.
node --version # require >= 20
Then ensure deps are installed (one-time):
SKILL_DIR="$HOME/.claude/skills/cross-nft"
[ -d "$SKILL_DIR/node_modules" ] || (cd "$SKILL_DIR" && npm install --silent)
Read-path commands (collections, token, tokens, listing, offers, offer-onchain, activities, stats, search) work without any private key. Only the write commands need one.
Resolve the EOA in this order. Never echo the private key back to the user, never write it into the conversation transcript, never log it.
process.env.PRIVATE_KEY — passed via the spawned Bash invocation.
./.env in the user's current working directory — read PRIVATE_KEY and (optionally) WALLET_ADDRESS, CROSS_RPC_URL, MAX_TRADE_NOTIONAL, CONFIRM_THRESHOLD, MIN_GAS_NATIVE.
$HOME/.claude/skills/cross-nft/.env — same vars, used as the personal default.
Ask the user — only if all three sources lack PRIVATE_KEY and the requested subcommand actually needs it. Use this exact prompt:
"I need an EOA private key (0x-prefixed, 64 hex chars) to sign a CROSS NFT marketplace transaction. Option A (recommended): stop here, paste this into
~/.claude/skills/cross-nft/.env:PRIVATE_KEY=0x... MAX_TRADE_NOTIONAL=100 CONFIRM_THRESHOLD=1 MIN_GAS_NATIVE=0.001then re-ask. I won't see it.
Option B (one-shot): paste it now. It will be passed to the script via process env only and will NOT be saved to disk by me. It will appear once in this transcript."
If the user picks B, accept the PK as a string, do not echo it, pass it to the script as PRIVATE_KEY=... on the same Bash command line, and after the action tell the user to consider rotating the key if the transcript is shared.
Validation: the value must match ^0x[0-9a-fA-F]{64}$. Reject otherwise without retrying silently. Read-path subcommands NEVER prompt for a PK.
Read-path scripts cannot lose funds. The rails below are enforced verbatim by _guard.mjs for every write op:
eth_chainId == 612055.MAX_TRADE_NOTIONAL cap — env var; if set, abort if the per-trade payable exceeds it.CONFIRM_THRESHOLD + --confirm gate — any trade above this notional aborts with {ok:false, error:"awaiting_confirm", ...} exit 2 unless --confirm is passed. Default 1 payment-token unit.MIN_GAS_NATIVE pre-flight — abort before signing if CROSS native balance is below the floor (default 0.001). Set MIN_GAS_NATIVE=0 to skip.list/offer, calls whitelistedPaymentTokens(addr); aborts with unsupported_payment_token if not allowed.cancel-listing / cancel-offer / buy / accept-offer pre-fetch the on-chain object and abort cleanly if isActive == false.cancel-listing aborts if signer != listing seller; cancel-offer aborts if signer != offeror.list and accept-offer check isApprovedForAll(operator=marketplace); only if false do they run setApprovalForAll(true) and wait for receipt before the marketplace call.buy and offer check allowance(spender=marketplace) and approve exactly the needed amount (or unlimited with --max-approve) before the marketplace call. Native paymentToken (sentinel 0x0…0) skips approval and uses msg.value.WALLET_ADDRESS mismatch warning — non-null signerWarn field when env-declared address ≠ PK-derived address. Surface to user before continuing.All subcommands run via Bash and emit a single JSON object on stdout (no decorative prose). Parse the envelope and report key fields back. Stderr stays empty unless DEBUG=1.
cd "$HOME/.claude/skills/cross-nft"
node scripts/<subcommand>.mjs [args]
| User says (KR / EN) | Subcommand |
|---|---|
| "trending collections" / "TOP 컬렉션" | node scripts/collections.mjs --days 7d --top 20 |
| "search materials" / "materials 검색" | node scripts/search.mjs "materials" |
| "이 토큰 보여줘 (URL 붙여넣기)" / "show this token URL" | node scripts/token.mjs --url <URL> |
| "0xa3bd… 30164… token" / "token at 0x... id 30164" | node scripts/token.mjs 0xa3bd… 30164746843126 |
| "내 보유 NFT" / "tokens owned by 0x..." | node scripts/tokens.mjs --owner 0x... |
| "컬렉션 4 토큰들" / "list tokens in collection-id 4" | node scripts/tokens.mjs --collection-id 4 --top 20 |
| "이 NFT의 오퍼들" / "offers on token X" | node scripts/offers.mjs --collection 0x... --token 30164746843126 |
| "내가 한 오퍼 목록" / "offers I made" | node scripts/offers.mjs --offeror 0x... |
| "최근 거래내역" / "activities (sales/listings/offers)" | node scripts/activities.mjs --collection 0x... --types SALE,LISTING,OFFER |
| "listing 321699 상세" / "on-chain listing 321699" | node scripts/listing.mjs 321699 |
| "offer 30 onchain" / "offer 30 detail" | node scripts/offer-onchain.mjs 30 |
| "컬렉션 4 통계" / "stats for collection-id 4" | node scripts/stats.mjs --collection-id 4 |
| "30 MGT 에 NFT 판매 (URL 붙여넣기)" / "list NFT for sale 30 MGT" | node scripts/list.mjs 0xa3bd… 30164746843126 30 --confirm |
| "Dutch 옥션 50→10 over 24h" / "dutch auction 50 to 10 in 24h" | node scripts/list.mjs 0x... <id> 50 --end-price 10 --duration 86400 --confirm |
| "내 listing 321699 취소" / "cancel my listing 321699" | node scripts/cancel-listing.mjs 321699 |
| "listing 321699 사기" / "buy listing 321699" | node scripts/buy.mjs 321699 --confirm |
| "이 NFT에 25 MGT 오퍼" / "make offer 25 MGT for token X" | node scripts/offer.mjs 0x... 30164746843126 25 --duration 604800 --confirm |
| "내 오퍼 30 취소" / "cancel offer 30" | node scripts/cancel-offer.mjs 30 |
| "오퍼 30 수락" / "accept offer 30" | node scripts/accept-offer.mjs 30 --confirm |
If the user asks to execute any write command (list / buy / offer / accept-offer / cancel-*), follow this protocol:
token.mjs for list, listing.mjs for buy/cancel-listing, offer-onchain.mjs for cancel-offer/accept-offer, etc.) to confirm price + status.parsedIntent, totalPayableHuman (if applicable), paymentSymbol, pageUrl to the user and ask for explicit confirmation ("yes / 진행").--confirm (and --max-approve if the user explicitly opted in).For awaiting_confirm errors (exit code 2), summarize the parsed intent and ask the user explicitly for "yes / 진행" before re-running with --confirm.
collections [--days 24h|7d|30d|all] [--top N] [--skip N] — paginated ListCollectionStatistics. Returns floor / volume / sales / supply / owners per collection.token <address> <tokenId> (or --url <crossnft.io URL>) — GetToken GraphQL. Shows metadata, owner, and the active listing if any (with listingId, startPrice, endPrice, paymentSymbol, isDutchAuction).tokens --collection-id <id> | --owner <0x…> — paginated ListTokens. (The indexer keys collections by internal numeric id, not contract address; resolve it via collections.mjs first.)offers --collection <0x…> [--token <id>] [--offeror <0x…>] — paginated ListOffers. Returns offerId, offerPriceHuman, paymentSymbol, expirationTime.activities --collection <0x…> [--types SALE,LISTING,OFFER,TRANSFER,BURN] — paginated ListActivities. Includes txHash for cross-checking on the explorer.listing <listingId> — pure on-chain getListingDetails + getCurrentPrice + calculateFees for a listing. Use this BEFORE buy to confirm the price (Dutch auctions decay).offer-onchain <offerId> — pure on-chain getOfferDetails. Use BEFORE cancel-offer / accept-offer.stats --collection-id <id> [--depth] — GetStatistics + ListTradeHistories (24h/7d/30d) + optional GetMarketDepth.search "<keyword>" — ListSearchResults (collections + games).list <nftContract> <tokenId> <price> [--quantity N] [--end-price P] [--duration sec] [--payment MGT|native|0x…] [--confirm] [--max-approve] — signs createListing. Auto-runs setApprovalForAll.cancel-listing <listingId> — signs cancelListing. Aborts if signer ≠ seller.buy <listingId> [--quantity N] [--confirm] [--max-approve] — fetches currentPrice, approves ERC-20 if needed (or attaches msg.value for native), signs buy.offer <nftContract> <tokenId> <pricePerUnit> [--quantity N] [--duration sec] [--payment MGT|native|0x…] [--confirm] [--max-approve] — approves ERC-20 if needed (or msg.value for native), signs createOffer. Default duration 7 days.cancel-offer <offerId> — signs cancelOffer. Aborts if signer ≠ offeror.accept-offer <offerId> [--confirm] — auto-runs setApprovalForAll, signs acceptOffer.After every action, surface to the user:
parsedIntent field if present.collections: count + per-row {name, contractAddress, floorPrice, volume}.token: name, protocol, owner, and the listing block if non-null (with startPriceHuman + paymentSymbol + listingId).tokens / offers / activities: total (server count) + a digest of the first 5 rows.listing: seller, tokenId, startPriceHuman + currentPriceHuman + paymentSymbol, isActive, isDutchAuction, pageUrl.offer-onchain: offeror, offerPriceHuman, paymentSymbol, expirationTime (as a date), isActive.list: mode, listingId, txHash, explorerUrl, pageUrl (and approveTxHash if a one-time setApprovalForAll ran).buy / accept-offer: mode, parsedIntent.totalPayableHuman, txHash, explorerUrl. Mention approveTxHash if any.offer: offerId, txHash, explorerUrl, parsedIntent.totalPayableHuman.cancel-*: txHash, explorerUrl.Never include the PK or full env contents in the report. If the envelope contains a non-null signerWarn, surface the mismatch to the user before declaring success.
This skill folder is the unit of distribution. Recipients:
cross-nft/ folder into ~/.claude/skills/cd ~/.claude/skills/cross-nft && npm install once (or let the skill do it on first use)~/.claude/skills/cross-nft/.env from .env.example, or let the skill prompt them on first runCross-link: deeper details (contract address provenance, full GraphQL filter shapes, fee model) live in references/cross-nft.md. Lazy-load it only when an endpoint returns an unfamiliar shape.