Skill: koba42corp/spacescan (version 1.0.0)

# Spacescan Skill Explore the Chia blockchain via Spacescan.io API. ## What It Does - View blocks and transactions - Check address balances - Monitor network statistics - Search blockchain data - Track CAT tokens and NFTs - Get XCH price ## ⚠️ API Key Required Spacescan requires an API key. Get yours at: https://www.spacescan.io/apis Set the environment variable: ```bash export SPACESCAN_API_KEY=your_key_here ``` Or add to your shell profile (~/.zshrc or ~/.bashrc): ```bash echo 'export SPACESCAN_API_KEY=your_key_here' >> ~/.zshrc source ~/.zshrc ``` ## Commands All commands can be triggered via: - `/scan <command>` in Telegram - `/spacescan <command>` in Telegram - `scan <command>` in CLI - `spacescan <command>` in CLI ### Blocks ```bash /scan block latest Get latest block /scan block <height> Get block by height /scan block <hash> Get block by hash /scan blocks <start> <end> Get block range ``` ### Transactions ```bash /scan tx <id> Get transaction details ``` ### Addresses ```bash /scan address <addr> Get address info /scan address balance <a> Get address balance /scan address txs <addr> Get recent transactions ``` ### Coins ```bash /scan coin <id> Get coin details ``` ### Network ```bash /scan stats Network statistics /scan network Network info /scan space Network space (EiB) /scan mempool Mempool status /scan price XCH price ``` ### Tokens ```bash /scan cats List CAT tokens /scan cat <id> Get CAT details ``` ### NFTs ```bash /scan nft <id> Get NFT details ``` ### Search ```bash /scan search <query> Search blockchain /scan <long_hash> Quick search ``` ## Agent Usage ```javascript const { handleCommand } = require('./skills/spacescan'); // Requires SPACESCAN_API_KEY environment variable const output = await handleCommand('block latest'); ``` ## API Client ```javascript const SpacescanAPI = require('./skills/spacescan/lib/api'); const api = new SpacescanAPI('your-api-key'); // Get latest block const block = await api.getLatestBlock(); // Get address balance const balance = await api.getAddressBalance('xch1...'); // Get network stats const stats = await api.getNetworkStats(); // Search const result = await api.search('xch1...'); ``` ## Installation ```bash cd skills/spacescan npm install chmod +x cli.js npm link # Makes 'scan' and 'spacescan' global ``` ## Configuration **Required:** Set your API key ```bash export SPACESCAN_API_KEY=your_key_here ``` Get your key at: https://www.spacescan.io/apis Free tier available with rate limits. ## Examples **Check latest block:** ```bash /scan block latest ``` **Get address balance:** ```bash /scan address balance xch1... ``` **Network stats:** ```bash /scan stats ``` **XCH price:** ```bash /scan price ``` ## Support - Spacescan: https://www.spacescan.io - API Plans: https://www.spacescan.io/apis - Bug reports: File in skill repository

path: CHANGELOG.md
content: # Changelog

All notable changes to the Spacescan skill will be documented in this file.

## [1.0.0] - 2026-01-29

### Added
- Initial release
- Complete Spacescan.io API wrapper
- API key authentication support
- CLI commands via `scan` and `spacescan`
- Telegram integration with `/scan` command
- Natural language command parsing
- Block browsing (latest, by height, by hash, ranges)
- Transaction lookup
- Address info, balance, and transaction history
- Coin details and tracking
- Network statistics and info
- Network space monitoring
- Mempool status
- XCH price lookup
- CAT token listing and details
- NFT details
- Universal blockchain search
- Formatted output for CLI and messaging platforms
- API client for programmatic access
- ClawdHub package structure
- Comprehensive documentation (SKILL.md, README.md)
- Installation script with API key instructions
- MIT License

### Features
- 🧱 Blocks: Latest, by height/hash, ranges
- 💸 Transactions: Lookup and history
- 👤 Addresses: Balance, transactions, coins
- 🪙 Coins: Details and lineage
- 📊 Network: Stats, space, mempool
- 🎨 NFTs: Details and collections
- 🪙 CATs: Token list and info
- 💰 Price: Real-time XCH price
- 🔍 Search: Universal blockchain search

### Technical
- Node.js 18+ compatibility
- Axios for HTTP requests
- 30-second timeout for API calls
- Graceful error handling with API key validation
- Rate limit error messaging
- API key via environment variable or constructor
- Production-ready code

### Requirements
- **API Key Required:** Get from https://www.spacescan.io/apis
- Set `SPACESCAN_API_KEY` environment variable

---

[1.0.0]: https://github.com/Koba42Corp/spacescan-skill/releases/tag/v1.0.0


path: README.md
content: <p align="center">
  <h1 align="center">🔍 Spacescan.io API Client</h1>
  <p align="center">
    <strong>Explore the Chia blockchain - blocks, transactions, addresses, network stats</strong>
  </p>
</p>

<p align="center">
  <a href="https://opensource.org/licenses/MIT">
    <img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT">
  </a>
  <a href="https://nodejs.org/">
    <img src="https://img.shields.io/badge/Node.js-v18+-green.svg" alt="Node.js: v18+">
  </a>
  <a href="https://spacescan.io">
    <img src="https://img.shields.io/badge/Explorer-Spacescan.io-blue.svg" alt="Spacescan.io">
  </a>
  <a href="https://clawd.bot">
    <img src="https://img.shields.io/badge/Framework-Clawdbot-orange.svg" alt="Built with Clawdbot">
  </a>
</p>

<p align="center">
  <img src="https://img.shields.io/badge/Status-Production%20Ready-brightgreen.svg" alt="Status">
  <img src="https://img.shields.io/badge/Version-1.0.0-blue.svg" alt="Version">
  <img src="https://img.shields.io/badge/API%20Key-Required-red.svg" alt="API Key Required">
</p>

---

## ⚠️ API Key Required

Spacescan.io requires an API key for access. Get yours at:

**🔑 https://www.spacescan.io/apis**

Free tier available with rate limits.

---

## 🎯 Overview

Access Spacescan.io's comprehensive Chia blockchain API. View blocks, track transactions, monitor addresses, check network stats, and explore CAT tokens and NFTs.

## ✨ Features

- 🧱 **Blocks** — Latest blocks, block ranges, block details
- 💸 **Transactions** — Transaction lookup and history
- 👤 **Addresses** — Balance, transactions, coin tracking
- 🪙 **Coins** — Coin details and lineage
- 📊 **Network** — Stats, space, mempool, blockchain state
- 🎨 **NFTs** — NFT details and collections
- 🪙 **CATs** — Token list and details
- 💰 **Price** — Real-time XCH price
- 🔍 **Search** — Universal blockchain search

## 🚀 Quick Start

### Setup

1. **Get API Key:** https://www.spacescan.io/apis
2. **Set Environment Variable:**

```bash
export SPACESCAN_API_KEY=your_key_here
```

Add to your shell profile for persistence:

```bash
echo 'export SPACESCAN_API_KEY=your_key_here' >> ~/.zshrc
source ~/.zshrc
```

### Installation

```bash
# Via ClawdHub (recommended)
clawdhub install spacescan

# Or manually
cd ~/.clawd/skills
git clone https://github.com/Koba42Corp/spacescan-skill.git spacescan
cd spacescan
npm install
chmod +x cli.js
npm link
```

### Usage

#### CLI

```bash
# Blocks
scan block latest
scan block 5000000
scan blocks 5000000 5000010

# Transactions
scan tx <tx_id>

# Addresses
scan address xch1...
scan address balance xch1...
scan address txs xch1...

# Network
scan stats
scan price
scan mempool
scan space

# Tokens
scan cats
scan cat <asset_id>

# NFTs
scan nft <nft_id>

# Search
scan search xch1...
```

#### Telegram

```
/scan block latest
/scan price
/scan stats
/scan address xch1...
```

#### Clawdbot Agent

```javascript
const { handleCommand } = require('./skills/spacescan');

// Requires SPACESCAN_API_KEY environment variable
const output = await handleCommand('block latest');
console.log(output);
```

#### API Client

```javascript
const SpacescanAPI = require('./skills/spacescan/lib/api');

// Pass API key directly or use SPACESCAN_API_KEY env var
const api = new SpacescanAPI('your-api-key');

// Get latest block
const block = await api.getLatestBlock();
console.log(`Height: ${block.height}`);

// Check address balance
const balance = await api.getAddressBalance('xch1...');
console.log(`Balance: ${balance.balance / 1e12} XCH`);

// Network stats
const stats = await api.getNetworkStats();
console.log(`Peak: ${stats.peak_height}`);

// Search
const result = await api.search('xch1...');
console.log(`Found: ${result.type}`);
```

## 📖 Command Reference

### Blocks

| Command | Description | Example |
|---------|-------------|---------|
| `scan block latest` | Get latest block | `scan block latest` |
| `scan block <height>` | Get block by height | `scan block 5000000` |
| `scan block <hash>` | Get block by hash | `scan block 0x...` |
| `scan blocks 
 <e>` | Get block range | `scan blocks 100 110` |

### Transactions

| Command | Description | Example |
|---------|-------------|---------|
| `scan tx <id>` | Transaction details | `scan tx 0x...` |

### Addresses

| Command | Description | Example |
|---------|-------------|---------|
| `scan address <addr>` | Address info | `scan address xch1...` |
| `scan address balance <a>` | Get balance | `scan address balance xch1...` |
| `scan address txs <addr>` | Recent transactions | `scan address txs xch1...` |

### Network

| Command | Description | Example |
|---------|-------------|---------|
| `scan stats` | Network statistics | `scan stats` |
| `scan network` | Network info | `scan network` |
| `scan space` | Network space | `scan space` |
| `scan mempool` | Mempool status | `scan mempool` |
| `scan price` | XCH price | `scan price` |

### Tokens & NFTs

| Command | Description | Example |
|---------|-------------|---------|
| `scan cats` | List CAT tokens | `scan cats` |
| `scan cat <id>` | CAT details | `scan cat 0x...` |
| `scan nft <id>` | NFT details | `scan nft nft1...` |

## 🛠️ API Methods

Complete method reference:

### Blockchain
- `getLatestBlock()` — Latest block
- `getBlock(heightOrHash)` — Block details
- `getBlockRange(start, end)` — Block range

### Transactions
- `getTransaction(txId)` — Transaction details
- `getTransactionsByBlock(height)` — Block transactions

### Addresses
- `getAddress(address)` — Address info
- `getAddressBalance(address)` — Balance
- `getAddressTransactions(address, options)` — Transaction history
- `getAddressCoins(address, options)` — Coin list

### Coins
- `getCoin(coinId)` — Coin details
- `getCoinChildren(coinId)` — Child coins

### Network
- `getNetworkStats()` — Statistics
- `getNetworkInfo()` — Network info
- `getNetworkSpace()` — Network space
- `getBlockchainState()` — Blockchain state

### Mempool
- `getMempool()` — Mempool status
- `getMempoolTransaction(txId)` — Mempool transaction

### Tokens & NFTs
- `getCAT(assetId)` — CAT details
- `getCATList(options)` — List CATs
- `getNFT(nftId)` — NFT details
- `getNFTCollection(collectionId)` — NFT collection

### Utilities
- `search(query)` — Universal search
- `getXCHPrice()` — Current XCH price

## 📊 Output Examples

### Latest Block
```
🧱 Latest Block

Height: 5,234,567
Hash: 0x123abc...
Timestamp: 1/29/2026, 3:45:00 PM
Transactions: 42
Farmer: 0x456def...
```

### Network Stats
```
📊 Network Statistics

Peak Height: 5,234,567
Network Space: 32.45 EiB
Total Supply: 8,234,567.1234 XCH
Block Time: 18.75s
Total Transactions: 12,345,678
```

### Price
```
💰 XCH Price

$3.87
₿0.00005432
```

## 🔧 Configuration

### Required

```bash
export SPACESCAN_API_KEY=your_key_here
```

### Optional

Pass API key directly to constructor:

```javascript
const api = new SpacescanAPI('your-api-key');
```

## 🧪 Examples

### Monitor latest blocks

```javascript
const api = new SpacescanAPI();

setInterval(async () => {
  const block = await api.getLatestBlock();
  console.log(`Block ${block.height}: ${block.tx_count} transactions`);
}, 30000); // Every 30 seconds
```

### Track address

```javascript
const api = new SpacescanAPI();
const address = 'xch1...';

const info = await api.getAddress(address);
console.log(`Balance: ${info.balance / 1e12} XCH`);

const txs = await api.getAddressTransactions(address);
console.log(`Recent transactions: ${txs.transactions.length}`);
```

## 🤝 Contributing

Contributions welcome! Please:

1. Fork the repository
2. Create a feature branch
3. Make your changes
4. Add tests if applicable
5. Submit a pull request

## 📄 License

MIT License - see [LICENSE](LICENSE) for details

## 🔗 Links

- **Spacescan**: https://www.spacescan.io
- **API Plans**: https://www.spacescan.io/apis
- **Clawdbot**: https://clawd.bot
- **ClawdHub**: https://clawdhub.com
- **Chia Network**: https://chia.net

## 💬 Support

- Issues: [GitHub Issues](https://github.com/Koba42Corp/spacescan-skill/issues)
- Discord: [Clawdbot Community](https://discord.gg/clawd)
- Telegram: [@clawdbot](https://t.me/clawdbot)

---

<p align="center">Made with 🖖 by the Clawdbot community</p>


path: cli.js
content: #!/usr/bin/env node

const { handleCommand } = require('./index');

// CLI interface
(async () => {
  const [,, ...args] = process.argv;
  const input = args.join(' ').trim();

  if (!input || input === 'help' || input === '--help' || input === '-h') {
    console.log(showHelp());
    process.exit(0);
  }

  try {
    const output = await handleCommand(input);
    console.log(output);
  } catch (error) {
    console.error(`❌ Error: ${error.message}`);
    process.exit(1);
  }
})();

function showHelp() {
  return `🔍 Spacescan.io API Client

⚠️  Requires API Key: Set SPACESCAN_API_KEY environment variable
   Get your key at: https://www.spacescan.io/apis

BLOCKS
  scan block latest         Get latest block
  scan block <height|hash>  Get specific block
  scan blocks <start> <end> Get block range

TRANSACTIONS
  scan tx <id>              Get transaction details

ADDRESSES
  scan address <addr>       Get address info
  scan address balance <a>  Get address balance
  scan address txs <addr>   Get address transactions

COINS
  scan coin <id>            Get coin details

NETWORK
  scan stats                Network statistics
  scan network              Network info
  scan space                Network space
  scan mempool              Mempool status
  scan price                XCH price

TOKENS
  scan cats                 List CAT tokens
  scan cat <id>             Get CAT details

NFTs
  scan nft <id>             Get NFT details

SEARCH
  scan search <query>       Search by hash/address/block
  scan <hash>               Quick search (long hashes)

Use 'scan help' anytime for this message.`;
}


path: index.js
content: const SpacescanAPI = require('./lib/api');

const api = new SpacescanAPI();

/**
 * Handle Spacescan commands from CLI or Clawdbot
 * @param {string} input - Raw command string
 * @returns {Promise<string>} Formatted output
 */
async function handleCommand(input) {
  input = input.trim().toLowerCase();

  // Strip common prefixes
  input = input.replace(/^(\/scan|\/spacescan|scan|spacescan)\s+/, '');

  // Check API key
  if (!api.hasApiKey()) {
    return '❌ Spacescan API key required.\n\n' +
           'Set environment variable:\n' +
           'export SPACESCAN_API_KEY=your_key_here\n\n' +
           'Get your key at: https://www.spacescan.io/apis';
  }

  const parts = input.split(/\s+/);
  const command = parts[0];
  const subcommand = parts[1];
  const args = parts.slice(2);

  switch (command) {
    // BLOCKS
    case 'block':
      if (subcommand === 'latest') {
        return await getLatestBlock();
      }
      return await getBlock(subcommand);

    case 'blocks':
      return await getBlockRange(subcommand, args[0]);

    // TRANSACTIONS
    case 'tx':
    case 'transaction':
      return await getTransaction(subcommand);

    // ADDRESSES
    case 'address':
    case 'addr':
      if (subcommand === 'balance') {
        return await getAddressBalance(args[0]);
      }
      if (subcommand === 'txs') {
        return await getAddressTransactions(args[0]);
      }
      return await getAddress(subcommand);

    // COINS
    case 'coin':
      return await getCoin(subcommand);

    // STATS
    case 'stats':
      return await getNetworkStats();

    case 'network':
      return await getNetworkInfo();

    case 'space':
      return await getNetworkSpace();

    // MEMPOOL
    case 'mempool':
      return await getMempool();

    // PRICE
    case 'price':
      return await getXCHPrice();

    // SEARCH
    case 'search':
      return await search(parts.slice(1).join(' '));

    // CATS
    case 'cats':
      return await getCATList();

    case 'cat':
      return await getCAT(subcommand);

    // NFT
    case 'nft':
      return await getNFT(subcommand);

    default:
      // Try as search
      if (command && command.length > 10) {
        return await search(input);
      }
      return `❌ Unknown command: ${command}\n\nTry 'scan help' for usage.`;
  }
}

// === COMMAND IMPLEMENTATIONS ===

async function getLatestBlock() {
  const block = await api.getLatestBlock();
  
  let output = `🧱 Latest Block\n\n`;
  output += `Height: ${block.height?.toLocaleString()}\n`;
  output += `Hash: ${block.header_hash}\n`;
  output += `Timestamp: ${new Date(block.timestamp * 1000).toLocaleString()}\n`;
  output += `Transactions: ${block.tx_count || 0}\n`;
  output += `Farmer: ${block.farmer_puzzle_hash?.slice(0, 16)}...\n`;
  
  return output;
}

async function getBlock(heightOrHash) {
  if (!heightOrHash) return '❌ Please provide a block height or hash';
  
  const block = await api.getBlock(heightOrHash);
  
  let output = `🧱 Block ${block.height}\n\n`;
  output += `Hash: ${block.header_hash}\n`;
  output += `Timestamp: ${new Date(block.timestamp * 1000).toLocaleString()}\n`;
  output += `Transactions: ${block.tx_count || 0}\n`;
  output += `Farmer: ${block.farmer_puzzle_hash}\n`;
  
  if (block.prev_hash) {
    output += `Previous: ${block.prev_hash}\n`;
  }
  
  return output;
}

async function getBlockRange(start, end) {
  if (!start || !end) return '❌ Please provide start and end heights';
  
  const blocks = await api.getBlockRange(start, end);
  
  let output = `🧱 Blocks ${start} to ${end}:\n\n`;
  
  if (blocks.length > 0) {
    blocks.forEach(block => {
      output += `${block.height}: ${block.header_hash}\n`;
    });
    return output;
  }
  
  return '❌ No blocks found';
}

async function getTransaction(txId) {
  if (!txId) return '❌ Please provide a transaction ID';
  
  const tx = await api.getTransaction(txId);
  
  let output = `💸 Transaction\n\n`;
  output += `ID: ${tx.id}\n`;
  output += `Block: ${tx.block_height}\n`;
  output += `Timestamp: ${new Date(tx.timestamp * 1000).toLocaleString()}\n`;
  output += `Type: ${tx.type}\n`;
  output += `Fee: ${formatXCH(tx.fee)}\n`;
  
  return output;
}

async function getAddress(address) {
  if (!address) return '❌ Please provide an address';
  
  const addr = await api.getAddress(address);
  
  let output = `👤 Address\n\n`;
  output += `${address}\n\n`;
  output += `Balance: ${formatXCH(addr.balance)}\n`;
  output += `Transactions: ${addr.tx_count || 0}\n`;
  
  return output;
}

async function getAddressBalance(address) {
  if (!address) return '❌ Please provide an address';
  
  const balance = await api.getAddressBalance(address);
  
  let output = `💰 Balance\n\n`;
  output += `${formatXCH(balance.balance)}\n`;
  
  return output;
}

async function getAddressTransactions(address) {
  if (!address) return '❌ Please provide an address';
  
  const result = await api.getAddressTransactions(address, { limit: 20 });
  
  let output = `💸 Recent Transactions:\n\n`;
  
  if (result.transactions?.length > 0) {
    result.transactions.forEach((tx, i) => {
      output += `${i + 1}. ${tx.id}\n`;
      output += `   Block: ${tx.block_height}\n`;
      output += `   Amount: ${formatXCH(tx.amount)}\n\n`;
    });
    return output;
  }
  
  return '❌ No transactions found';
}

async function getCoin(coinId) {
  if (!coinId) return '❌ Please provide a coin ID';
  
  const coin = await api.getCoin(coinId);
  
  let output = `🪙 Coin\n\n`;
  output += `ID: ${coin.coin_name}\n`;
  output += `Amount: ${formatXCH(coin.amount)}\n`;
  output += `Puzzlehash: ${coin.puzzle_hash}\n`;
  output += `Parent: ${coin.parent_coin_info}\n`;
  output += `Spent: ${coin.spent ? 'Yes' : 'No'}\n`;
  
  return output;
}

async function getNetworkStats() {
  const stats = await api.getNetworkStats();
  
  let output = `📊 Network Statistics\n\n`;
  output += `Peak Height: ${stats.peak_height?.toLocaleString()}\n`;
  output += `Network Space: ${formatNetworkSpace(stats.network_space)}\n`;
  output += `Total Supply: ${formatXCH(stats.total_supply)}\n`;
  output += `Block Time: ${stats.block_time}s\n`;
  
  if (stats.total_transactions) {
    output += `Total Transactions: ${stats.total_transactions.toLocaleString()}\n`;
  }
  
  return output;
}

async function getNetworkInfo() {
  const info = await api.getNetworkInfo();
  
  let output = `🌐 Network Info\n\n`;
  output += `Network: ${info.network_name}\n`;
  output += `Version: ${info.version}\n`;
  output += `Peak: ${info.peak_height?.toLocaleString()}\n`;
  
  return output;
}

async function getNetworkSpace() {
  const space = await api.getNetworkSpace();
  
  let output = `💾 Network Space\n\n`;
  output += `${formatNetworkSpace(space.space)}\n`;
  
  return output;
}

async function getMempool() {
  const mempool = await api.getMempool();
  
  let output = `⏳ Mempool\n\n`;
  output += `Pending Transactions: ${mempool.count || 0}\n`;
  output += `Total Fees: ${formatXCH(mempool.total_fees)}\n`;
  
  return output;
}

async function getXCHPrice() {
  const price = await api.getXCHPrice();
  
  let output = `💰 XCH Price\n\n`;
  output += `$${price.usd?.toFixed(2) || 'N/A'}\n`;
  
  if (price.btc) {
    output += `₿${price.btc.toFixed(8)}\n`;
  }
  
  return output;
}

async function search(query) {
  if (!query) return '❌ Please provide a search query';
  
  const result = await api.search(query);
  
  let output = `🔍 Search Results:\n\n`;
  
  if (result.type === 'block') {
    output += `Block: ${result.data.height}\n`;
  } else if (result.type === 'transaction') {
    output += `Transaction: ${result.data.id}\n`;
  } else if (result.type === 'address') {
    output += `Address: ${result.data.address}\n`;
  } else if (result.type === 'coin') {
    output += `Coin: ${result.data.coin_name}\n`;
  } else {
    output += 'No results found\n';
  }
  
  return output;
}

async function getCATList() {
  const result = await api.getCATList({ limit: 30 });
  
  let output = `🪙 CAT Tokens:\n\n`;
  
  if (result.cats?.length > 0) {
    result.cats.forEach((cat, i) => {
      output += `${i + 1}. ${cat.name} (${cat.code})\n`;
      output += `   ID: ${cat.asset_id}\n\n`;
    });
    return output;
  }
  
  return '❌ No CATs found';
}

async function getCAT(assetId) {
  if (!assetId) return '❌ Please provide a CAT asset ID';
  
  const cat = await api.getCAT(assetId);
  
  let output = `🪙 ${cat.name || 'Unknown CAT'}\n\n`;
  output += `Code: ${cat.code}\n`;
  output += `Asset ID: ${cat.asset_id}\n`;
  
  if (cat.description) {
    output += `Description: ${cat.description}\n`;
  }
  
  return output;
}

async function getNFT(nftId) {
  if (!nftId) return '❌ Please provide an NFT ID';
  
  const nft = await api.getNFT(nftId);
  
  let output = `🖼️  ${nft.name || 'Unnamed NFT'}\n\n`;
  output += `ID: ${nft.nft_id}\n`;
  output += `Collection: ${nft.collection_name || 'None'}\n`;
  output += `Owner: ${nft.owner_address}\n`;
  
  return output;
}

// === FORMATTING HELPERS ===

function formatXCH(mojos) {
  if (!mojos && mojos !== 0) return 'N/A';
  const xch = mojos / 1_000_000_000_000;
  return `${xch.toFixed(4)} XCH`;
}

function formatNetworkSpace(bytes) {
  if (!bytes) return 'N/A';
  
  const eb = bytes / (1024 ** 6);
  if (eb >= 1) return `${eb.toFixed(2)} EiB`;
  
  const pb = bytes / (1024 ** 5);
  if (pb >= 1) return `${pb.toFixed(2)} PiB`;
  
  const tb = bytes / (1024 ** 4);
  return `${tb.toFixed(2)} TiB`;
}

module.exports = { handleCommand };


path: install.sh
content: #!/bin/bash

cd "$(dirname "$0")"

echo "📦 Installing Spacescan skill dependencies..."
npm install --production

if [ $? -eq 0 ]; then
  echo "✅ Spacescan skill installed successfully!"
  echo ""
  echo "⚠️  API Key Required:"
  echo "  Set SPACESCAN_API_KEY environment variable"
  echo "  Get your key at: https://www.spacescan.io/apis"
  echo ""
  echo "Usage:"
  echo "  CLI: scan <command>"
  echo "  Telegram: /scan <command>"
  echo ""
  echo "Run 'scan help' for command reference."
else
  echo "❌ Installation failed. Please check npm logs."
  exit 1
fi


path: lib/api.js
content: const axios = require('axios');

const BASE_URL = 'https://api.spacescan.io';

class SpacescanAPI {
  constructor(apiKey = null, baseURL = BASE_URL) {
    this.[REDACTED_SECRET] || process.env.SPACESCAN_API_KEY;
    
    if (!this.apiKey) {
      console.warn('⚠️  No Spacescan API key found. Set SPACESCAN_API_KEY environment variable.');
    }
    
    this.client = axios.create({
      baseURL,
      timeout: 30000,
      headers: {
        'Accept': 'application/json',
        'User-Agent': 'Clawdbot-Spacescan-Skill/1.0'
      }
    });
    
    // Add API key to requests if available
    if (this.apiKey) {
      this.client.defaults.headers.common['Authorization'] = `Bearer ${this.apiKey}`;
    }
  }

  // === BLOCKCHAIN ===
  
  async getLatestBlock() {
    return this._get('/block/latest');
  }

  async getBlock(heightOrHash) {
    return this._get(`/block/${heightOrHash}`);
  }

  async getBlockRange(start, end) {
    return this._get('/blocks', { start, end });
  }

  // === TRANSACTIONS ===
  
  async getTransaction(txId) {
    return this._get(`/tx/${txId}`);
  }

  async getTransactionsByBlock(height) {
    return this._get(`/block/${height}/txs`);
  }

  // === ADDRESSES ===
  
  async getAddress(address) {
    return this._get(`/address/${address}`);
  }

  async getAddressBalance(address) {
    return this._get(`/address/${address}/balance`);
  }

  async getAddressTransactions(address, options = {}) {
    const { page = 1, limit = 50 } = options;
    return this._get(`/address/${address}/txs`, { page, limit });
  }

  async getAddressCoins(address, options = {}) {
    const { page = 1, limit = 50 } = options;
    return this._get(`/address/${address}/coins`, { page, limit });
  }

  // === COINS ===
  
  async getCoin(coinId) {
    return this._get(`/coin/${coinId}`);
  }

  async getCoinChildren(coinId) {
    return this._get(`/coin/${coinId}/children`);
  }

  // === PUZZLEHASH ===
  
  async getPuzzlehash(puzzlehash) {
    return this._get(`/puzzlehash/${puzzlehash}`);
  }

  async getPuzzlehashBalance(puzzlehash) {
    return this._get(`/puzzlehash/${puzzlehash}/balance`);
  }

  // === NETWORK STATS ===
  
  async getNetworkStats() {
    return this._get('/stats');
  }

  async getNetworkInfo() {
    return this._get('/network/info');
  }

  async getNetworkSpace() {
    return this._get('/network/space');
  }

  async getBlockchainState() {
    return this._get('/blockchain/state');
  }

  // === MEMPOOL ===
  
  async getMempool() {
    return this._get('/mempool');
  }

  async getMempoolTransaction(txId) {
    return this._get(`/mempool/${txId}`);
  }

  // === CATS (Chia Asset Tokens) ===
  
  async getCAT(assetId) {
    return this._get(`/cat/${assetId}`);
  }

  async getCATList(options = {}) {
    const { page = 1, limit = 50 } = options;
    return this._get('/cats', { page, limit });
  }

  // === NFTS ===
  
  async getNFT(nftId) {
    return this._get(`/nft/${nftId}`);
  }

  async getNFTCollection(collectionId) {
    return this._get(`/nft/collection/${collectionId}`);
  }

  // === SEARCH ===
  
  async search(query) {
    return this._get('/search', { q: query });
  }

  // === PRICE ===
  
  async getXCHPrice() {
    return this._get('/coin/xch/price');
  }

  // === INTERNAL ===
  
  async _get(path, params = {}) {
    if (!this.apiKey) {
      throw new Error(
        'Spacescan API key required. Set SPACESCAN_API_KEY environment variable.\n' +
        'Get your key at: https://www.spacescan.io/apis'
      );
    }
    
    try {
      const response = await this.client.get(path, { params });
      return response.data;
    } catch (error) {
      if (error.response) {
        const msg = error.response.data?.message || error.response.statusText;
        
        if (error.response.status === 429) {
          throw new Error('Rate limit exceeded. Upgrade your Spacescan API plan at https://www.spacescan.io/apis');
        }
        
        if (error.response.status === 401 || error.response.status === 403) {
          throw new Error('Invalid API key. Check your SPACESCAN_API_KEY at https://www.spacescan.io/apis');
        }
        
        throw new Error(`Spacescan API error: ${error.response.status} - ${msg}`);
      } else if (error.request) {
        throw new Error('Spacescan API request failed: No response received');
      } else {
        throw new Error(`Spacescan API error: ${error.message}`);
      }
    }
  }

  // Check if API key is configured
  hasApiKey() {
    return !!this.apiKey;
  }
}

module.exports = SpacescanAPI;


path: package-lock.json
content: {
  "name": "@clawdbot/skill-spacescan",
  "version": "1.0.0",
  "lockfileVersion": 3,
  "requires": true,
  "packages": {
    "": {
      "name": "@clawdbot/skill-spacescan",
      "version": "1.0.0",
      "license": "MIT",
      "dependencies": {
        "axios": "^1.6.0"
      },
      "bin": {
        "scan": "cli.js",
        "spacescan": "cli.js"
      },
      "engines": {
        "node": ">=18.0.0"
      }
    },
    "node_modules/asynckit": {
      "version": "0.4.0",
      "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT"
    },
    "node_modules/axios": {
      "version": "1.13.4",
      "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.4.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "follow-redirects": "^1.15.6",
        "form-data": "^4.0.4",
        "proxy-from-env": "^1.1.0"
      }
    },
    "node_modules/call-bind-apply-helpers": {
      "version": "1.0.2",
      "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "es-errors": "^1.3.0",
        "function-bind": "^1.1.2"
      },
      "engines": {
        "node": ">= 0.4"
      }
    },
    "node_modules/combined-stream": {
      "version": "1.0.8",
      "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "delayed-stream": "~1.0.0"
      },
      "engines": {
        "node": ">= 0.8"
      }
    },
    "node_modules/delayed-stream": {
      "version": "1.0.0",
      "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">=0.4.0"
      }
    },
    "node_modules/dunder-proto": {
      "version": "1.0.1",
      "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "call-bind-apply-helpers": "^1.0.1",
        "es-errors": "^1.3.0",
        "gopd": "^1.2.0"
      },
      "engines": {
        "node": ">= 0.4"
      }
    },
    "node_modules/es-define-property": {
      "version": "1.0.1",
      "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">= 0.4"
      }
    },
    "node_modules/es-errors": {
      "version": "1.3.0",
      "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">= 0.4"
      }
    },
    "node_modules/es-object-atoms": {
      "version": "1.1.1",
      "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "es-errors": "^1.3.0"
      },
      "engines": {
        "node": ">= 0.4"
      }
    },
    "node_modules/es-set-tostringtag": {
      "version": "2.1.0",
      "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "es-errors": "^1.3.0",
        "get-intrinsic": "^1.2.6",
        "has-tostringtag": "^1.0.2",
        "hasown": "^2.0.2"
      },
      "engines": {
        "node": ">= 0.4"
      }
    },
    "node_modules/follow-redirects": {
      "version": "1.15.11",
      "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
      "integrity": [REDACTED_SECRET],
      "funding": [
        {
          "type": "individual",
          "url": "https://github.com/sponsors/RubenVerborgh"
        }
      ],
      "license": "MIT",
      "engines": {
        "node": ">=4.0"
      },
      "peerDependenciesMeta": {
        "debug": {
          "optional": true
        }
      }
    },
    "node_modules/form-data": {
      "version": "4.0.5",
      "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "asynckit": "^0.4.0",
        "combined-stream": "^1.0.8",
        "es-set-tostringtag": "^2.1.0",
        "hasown": "^2.0.2",
        "mime-types": "^2.1.12"
      },
      "engines": {
        "node": ">= 6"
      }
    },
    "node_modules/function-bind": {
      "version": "1.1.2",
      "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "funding": {
        "url": "https://github.com/sponsors/ljharb"
      }
    },
    "node_modules/get-intrinsic": {
      "version": "1.3.0",
      "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "call-bind-apply-helpers": "^1.0.2",
        "es-define-property": "^1.0.1",
        "es-errors": "^1.3.0",
        "es-object-atoms": "^1.1.1",
        "function-bind": "^1.1.2",
        "get-proto": "^1.0.1",
        "gopd": "^1.2.0",
        "has-symbols": "^1.1.0",
        "hasown": "^2.0.2",
        "math-intrinsics": "^1.1.0"
      },
      "engines": {
        "node": ">= 0.4"
      },
      "funding": {
        "url": "https://github.com/sponsors/ljharb"
      }
    },
    "node_modules/get-proto": {
      "version": "1.0.1",
      "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "dunder-proto": "^1.0.1",
        "es-object-atoms": "^1.0.0"
      },
      "engines": {
        "node": ">= 0.4"
      }
    },
    "node_modules/gopd": {
      "version": "1.2.0",
      "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">= 0.4"
      },
      "funding": {
        "url": "https://github.com/sponsors/ljharb"
      }
    },
    "node_modules/has-symbols": {
      "version": "1.1.0",
      "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">= 0.4"
      },
      "funding": {
        "url": "https://github.com/sponsors/ljharb"
      }
    },
    "node_modules/has-tostringtag": {
      "version": "1.0.2",
      "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "has-symbols": "^1.0.3"
      },
      "engines": {
        "node": ">= 0.4"
      },
      "funding": {
        "url": "https://github.com/sponsors/ljharb"
      }
    },
    "node_modules/hasown": {
      "version": "2.0.2",
      "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "function-bind": "^1.1.2"
      },
      "engines": {
        "node": ">= 0.4"
      }
    },
    "node_modules/math-intrinsics": {
      "version": "1.1.0",
      "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">= 0.4"
      }
    },
    "node_modules/mime-db": {
      "version": "1.52.0",
      "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">= 0.6"
      }
    },
    "node_modules/mime-types": {
      "version": "2.1.35",
      "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "mime-db": "1.52.0"
      },
      "engines": {
        "node": ">= 0.6"
      }
    },
    "node_modules/proxy-from-env": {
      "version": "1.1.0",
      "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT"
    }
  }
}


path: package.json
content: {
  "name": "@clawdbot/skill-spacescan",
  "version": "1.0.0",
  "description": "Chia blockchain explorer via Spacescan.io API - blocks, transactions, addresses, network stats",
  "main": "index.js",
  "bin": {
    "spacescan": "./cli.js",
    "scan": "./cli.js"
  },
  "scripts": {
    "test": "echo \"No tests yet\" && exit 0"
  },
  "keywords": [
    "chia",
    "blockchain",
    "explorer",
    "spacescan",
    "clawdbot-skill"
  ],
  "author": "Jeff Coleman",
  "license": "MIT",
  "dependencies": {
    "axios": "^1.6.0"
  },
  "engines": {
    "node": ">=18.0.0"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/Koba42Corp/spacescan-skill.git"
  },
  "homepage": "https://github.com/Koba42Corp/spacescan-skill#readme",
  "bugs": {
    "url": "https://github.com/Koba42Corp/spacescan-skill/issues"
  }
}


Security scan verdicts — ClawScan: clean | static analysis: suspicious | VirusTotal: clean | SkillSpector: clean (LOW)