Skill: endogen/xian-node-skill (version 0.1.0)

--- name: xian-node description: Set up and manage Xian blockchain nodes. Use when deploying a Xian node to join mainnet/testnet, creating a new Xian network, or managing running nodes. Covers Docker-based setup via xian-stack, CometBFT configuration, and node monitoring. --- # Xian Node Skill Deploy and manage [Xian](https://xian.org) blockchain nodes — an L1 with native Python smart contracts on CometBFT. ## Quick Reference | Task | Command | |------|---------| | Join mainnet | `make setup && make core-build && make core-up && make init && make configure CONFIGURE_ARGS='--genesis-file-name genesis-mainnet.json --seed-node-address <seed> --copy-genesis'` | | Start node | `make core-shell` then `make up` inside container | | View logs | `pm2 logs --lines 100` (inside container) | | Stop node | `make down` (inside container) or `make core-down` (stop container) | | Check sync | `curl -s localhost:26657/status \| jq '.result.sync_info'` | ## Setup: Join Existing Network ### 1. Clone and Build ```bash git clone https://github.com/xian-network/xian-stack.git cd xian-stack make setup CORE_BRANCH=mainnet CONTRACTING_BRANCH=mainnet make core-build make core-up ``` ### 2. Initialize CometBFT ```bash make init ``` ### 3. Configure Node **Mainnet:** ```bash make configure CONFIGURE_ARGS='--moniker "my-node" --genesis-file-name "genesis-mainnet.json" --seed-node-address "c3861ffd16cf6708aef6683d3d0471b6dedb3116@152.53.18.220" --copy-genesis' ``` **Testnet:** ```bash make configure CONFIGURE_ARGS='--moniker "my-node" --genesis-file-name "genesis-testnet.json" --seed-node-address "<testnet-seed>" --copy-genesis' ``` **Validator node** (add private key): ```bash make configure CONFIGURE_ARGS='--moniker "my-validator" --genesis-file-name "genesis-mainnet.json" --validator-privkey "<your-privkey>" --seed-node-address "..." --copy-genesis' ``` **Service node** (with BDS - Blockchain Data Service): ```bash make configure CONFIGURE_ARGS='--moniker "my-service" --genesis-file-name "genesis-mainnet.json" --seed-node-address "..." --copy-genesis --service-node' ``` ### 4. Start Node ```bash make core-shell # Enter container make up # Start pm2 processes pm2 logs # Watch sync progress exit # Leave shell (node keeps running) ``` ## Setup: Create New Network ### 1. Build Stack ```bash git clone https://github.com/xian-network/xian-stack.git cd xian-stack make setup CORE_BRANCH=mainnet CONTRACTING_BRANCH=mainnet make core-build make core-up make init ``` ### 2. Generate Validator Keys Inside container (`make core-shell`): ```bash # Generate new validator key python -c " from nacl.signing import SigningKey import secrets sk = SigningKey(secrets.token_bytes(32)) print(f'Private key: {sk.encode().hex()}') print(f'Public key: {sk.verify_key.encode().hex()}') " ``` ### 3. Create Genesis File Create `genesis.json` with initial validators and state. See `references/genesis-template.md`. ### 4. Configure as Genesis Validator ```bash make configure CONFIGURE_ARGS='--moniker "genesis-validator" --genesis-file-name "genesis-custom.json" --validator-privkey "<privkey>"' ``` ### 5. Start Network ```bash make core-shell make up ``` Other nodes join using your node as seed. ## Node Management ### Inside Container Commands | Command | Description | |---------|-------------| | `make up` | Start xian + cometbft via pm2 | | `make down` | Stop all pm2 processes | | `make restart` | Restart node | | `make logs` | View pm2 logs | | `make wipe` | Clear node data (keeps config) | | `make dwu` | Down + wipe + init + up (full reset) | ### Monitoring **Sync status:** ```bash curl -s localhost:26657/status | jq '.result.sync_info' ``` **Response fields:** - `latest_block_height`: Current height - `catching_up`: `true` if still syncing - `earliest_block_height`: Lowest available block **Node info:** ```bash curl -s localhost:26657/status | jq '.result.node_info' make node-id # Get node ID for peering ``` **Validators:** ```bash curl -s localhost:26657/validators | jq '.result.validators' ``` ### Docker Commands | Command | Description | |---------|-------------| | `make core-up` | Start container | | `make core-down` | Stop container | | `make core-shell` | Enter container shell | | `make core-bds-up` | Start with BDS (PostgreSQL + GraphQL) | ## Ports | Port | Service | |------|---------| | 26656 | P2P (peering) | | 26657 | RPC (queries) | | 26660 | Prometheus metrics | | 5000 | GraphQL (BDS only) | ## Troubleshooting **Database lock error** (`resource temporarily unavailable`): ```bash # Duplicate pm2 processes - clean up: pm2 delete all make up ``` **Sync stuck**: ```bash # Check peer connections curl -s localhost:26657/net_info | jq '.result.n_peers' # Verify seed node is reachable make wipe make init # Re-run configure with correct seed ``` **Container not starting**: ```bash make core-down make core-build --no-cache make core-up ``` ## File Locations | Path | Contents | |------|----------| | `.cometbft/` | CometBFT data + config | | `.cometbft/config/genesis.json` | Network genesis | | `.cometbft/config/config.toml` | Node configuration | | `.cometbft/data/` | Blockchain data | | `xian-core/` | Xian ABCI application | | `xian-contracting/` | Python contracting engine | ## Test Your Node After syncing, verify your node works with [xian-py](https://github.com/xian-network/xian-py): ```bash pip install xian-py ``` ```python from xian_py import Xian, Wallet # Connect to your local node xian = Xian('http://localhost:26657') # Query balance balance = xian.get_balance('your_address') print(f"Balance: {balance}") # Get contract state state = xian.get_state('currency', 'balances', 'some_address') print(f"State: {state}") # Create wallet and send transaction wallet = Wallet() # or Wallet('your_private_key') xian = Xian('http://localhost:26657', wallet=wallet) result = xian.send(amount=10, to_address='recipient_address') ``` For full SDK docs (contracts, HD wallets, async) — see [xian-py](https://github.com/xian-network/xian-py). ## Resources - [xian-network/xian-stack](https://github.com/xian-network/xian-stack) — Docker setup - [xian-network/xian-core](https://github.com/xian-network/xian-core) — Core node - [xian-network/xian-py](https://github.com/xian-network/xian-py) — Python SDK - [CometBFT docs](https://docs.cometbft.com/) — Consensus engine - [xian.org](https://xian.org) — Project site

path: references/genesis-template.md
content: # Genesis File Template

When creating a new Xian network, you need a genesis file that defines:
- Chain ID and initial time
- Consensus parameters
- Initial validators
- Initial contract state (optional)

## Minimal Genesis

```json
{
  "genesis_time": "2024-01-01T00:00:00.000000000Z",
  "chain_id": "xian-custom-1",
  "initial_height": "1",
  "consensus_params": {
    "block": {
      "max_bytes": "22020096",
      "max_gas": "-1"
    },
    "evidence": {
      "max_age_num_blocks": "100000",
      "max_age_duration": "172800000000000",
      "max_bytes": "1048576"
    },
    "validator": {
      "pub_key_types": ["ed25519"]
    },
    "version": {
      "app": "0"
    },
    "abci": {
      "vote_extensions_enable_height": "0"
    }
  },
  "validators": [
    {
      "address": "<VALIDATOR_ADDRESS>",
      "pub_key": {
        "type": "tendermint/PubKeyEd25519",
        "value": "<BASE64_PUBKEY>"
      },
      "power": "10",
      "name": "genesis-validator"
    }
  ],
  "app_hash": "",
  "app_state": {}
}
```

## Fields Explanation

### Required Fields

| Field | Description |
|-------|-------------|
| `genesis_time` | ISO 8601 timestamp for chain start |
| `chain_id` | Unique network identifier (e.g., `xian-mainnet-1`) |
| `validators` | Initial validator set with their public keys and voting power |

### Validator Entry

```json
{
  "address": "hex-encoded validator address (first 20 bytes of pubkey sha256)",
  "pub_key": {
    "type": "tendermint/PubKeyEd25519",
    "value": "base64-encoded 32-byte ed25519 public key"
  },
  "power": "voting power (usually 10 for equal weight)",
  "name": "human-readable name"
}
```

### Generating Validator Address

```python
import hashlib
import base64

def pubkey_to_address(pubkey_hex: str) -> str:
    """Convert ed25519 pubkey to CometBFT address."""
    pubkey_bytes = bytes.fromhex(pubkey_hex)
    sha = hashlib.sha256(pubkey_bytes).digest()
    return sha[:20].hex().upper()

def pubkey_to_base64(pubkey_hex: str) -> str:
    """Convert hex pubkey to base64 for genesis."""
    return base64.b64encode(bytes.fromhex(pubkey_hex)).decode()
```

## Multi-Validator Genesis

For a network with multiple validators:

```json
{
  "validators": [
    {
      "address": "ADDR1",
      "pub_key": {"type": "tendermint/PubKeyEd25519", "value": "KEY1"},
      "power": "10",
      "name": "validator-1"
    },
    {
      "address": "ADDR2", 
      "pub_key": {"type": "tendermint/PubKeyEd25519", "value": "KEY2"},
      "power": "10",
      "name": "validator-2"
    },
    {
      "address": "ADDR3",
      "pub_key": {"type": "tendermint/PubKeyEd25519", "value": "KEY3"},
      "power": "10",
      "name": "validator-3"
    }
  ]
}
```

Equal voting power (`"power": "10"`) means each validator has equal influence. Adjust for weighted voting.

## Initial State (app_state)

For Xian, `app_state` can include initial contract deployments and balances. Leave empty `{}` for a fresh chain, or pre-populate with contracts.

## Deploying Genesis

1. Create your genesis file
2. Place at `.cometbft/config/genesis.json` (or use `--copy-genesis` with configure)
3. All validators must use identical genesis
4. Start nodes — first block created when 2/3+ validators online


path: scripts/generate_validator_key.py
content: #!/usr/bin/env python3
"""Generate a new Xian validator key pair."""

import argparse
import base64
import hashlib
import json
import secrets


def generate_keypair():
    """Generate ed25519 keypair using PyNaCl."""
    try:
        from nacl.signing import SigningKey
    except ImportError:
        print("Error: PyNaCl not installed. Run: pip install pynacl")
        raise SystemExit(1)
    
    sk = SigningKey(secrets.token_bytes(32))
    pk = sk.verify_key
    
    return sk.encode().hex(), pk.encode().hex()


def pubkey_to_address(pubkey_hex: str) -> str:
    """Convert ed25519 pubkey to CometBFT validator address."""
    pubkey_bytes = bytes.fromhex(pubkey_hex)
    sha = hashlib.sha256(pubkey_bytes).digest()
    return sha[:20].hex().upper()


def pubkey_to_base64(pubkey_hex: str) -> str:
    """Convert hex pubkey to base64 for genesis file."""
    return base64.b64encode(bytes.fromhex(pubkey_hex)).decode()


def main():
    parser = argparse.ArgumentParser(description="Generate Xian validator keys")
    parser.add_argument(
        "--name",
        default="validator",
        help="Validator name for genesis entry"
    )
    parser.add_argument(
        "--power",
        default="10",
        help="Voting power (default: 10)"
    )
    parser.add_argument(
        "--genesis-entry",
        action="store_true",
        help="Output genesis validator entry JSON"
    )
    args = parser.parse_args()

    private_key, public_key = generate_keypair()
    address = pubkey_to_address(public_key)
    pubkey_b64 = pubkey_to_base64(public_key)

    if args.genesis_entry:
        entry = {
            "address": address,
            "pub_key": {
                "type": "tendermint/PubKeyEd25519",
                "value": pubkey_b64
            },
            "power": args.power,
            "name": args.name
        }
        print("Genesis validator entry:")
        print(json.dumps(entry, indent=2))
        print(f"\nPrivate key (keep secret!): {private_key}")
    else:
        print(f"Private key: {private_key}")
        print(f"Public key:  {public_key}")
        print(f"Address:     {address}")
        print(f"Base64 key:  {pubkey_b64}")
        print(f"\nUse --genesis-entry for ready-to-use genesis JSON")


if __name__ == "__main__":
    main()


path: scripts/node_status.py
content: #!/usr/bin/env python3
"""Check Xian node sync status and display progress."""

import argparse
import json
import sys
from urllib.request import urlopen
from urllib.error import URLError


def get_status(rpc_url: str) -> dict:
    """Fetch node status from RPC endpoint."""
    try:
        with urlopen(f"{rpc_url}/status", timeout=5) as resp:
            return json.load(resp)["result"]
    except URLError as e:
        print(f"Error connecting to {rpc_url}: {e}")
        sys.exit(1)


def format_height(height: int) -> str:
    """Format block height with commas."""
    return f"{height:,}"


def main():
    parser = argparse.ArgumentParser(description="Check Xian node status")
    parser.add_argument(
        "--rpc", 
        default="http://localhost:26657",
        help="RPC endpoint (default: http://localhost:26657)"
    )
    parser.add_argument(
        "--json", 
        action="store_true",
        help="Output raw JSON"
    )
    args = parser.parse_args()

    status = get_status(args.rpc)
    
    if args.json:
        print(json.dumps(status, indent=2))
        return

    node_info = status["node_info"]
    sync_info = status["sync_info"]
    
    current = int(sync_info["latest_block_height"])
    catching_up = sync_info["catching_up"]
    
    print(f"Node:     {node_info['moniker']} ({node_info['id'][:8]}...)")
    print(f"Network:  {node_info['network']}")
    print(f"Height:   {format_height(current)}")
    print(f"Syncing:  {'Yes' if catching_up else 'No (fully synced)'}")
    print(f"Time:     {sync_info['latest_block_time']}")
    
    if catching_up:
        # Try to get peer info for max height estimation
        try:
            with urlopen(f"{args.rpc}/net_info", timeout=5) as resp:
                net = json.load(resp)["result"]
                peers = int(net["n_peers"])
                print(f"Peers:    {peers}")
        except:
            pass


if __name__ == "__main__":
    main()


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