Skill: koba42corp/sage-wallet (version 1.0.0)

--- name: sage-wallet description: Interact with the Sage Chia blockchain wallet via RPC. Use for XCH transactions, CAT tokens, NFTs, DIDs, offers, options, coin management, and wallet configuration. Supports cross-platform setups (Mac/Linux/Windows) with configurable RPC endpoints and SSL certificates. Invoke with /sage commands or natural language like "send XCH", "check my NFTs", "create an offer", "mint a CAT token". --- # Sage Wallet Skill RPC interface to Sage wallet for Chia blockchain operations. ## Configuration User settings stored in `{workspace}/config/sage-wallet.json`: ```json { "platform": "auto", "rpc_url": "https://127.0.0.1:9257", "cert_path": null, "key_path": null, "fingerprint": null, "auto_login": false } ``` ### Platform Defaults | Platform | Cert Path | Key Path | |----------|-----------|----------| | mac | `~/Library/Application Support/com.rigidnetwork.sage/ssl/wallet.crt` | `...wallet.key` | | linux | `~/.local/share/sage/ssl/wallet.crt` | `...wallet.key` | | windows | `%APPDATA%\com.rigidnetwork.sage\ssl\wallet.crt` | `...wallet.key` | When `platform` is `"auto"`, detect via `uname -s`. ## Slash Commands ### Configuration | Command | Action | |---------|--------| | `/sage status` | Show config and test connection | | `/sage config` | Display current settings | | `/sage config platform <auto\|mac\|linux\|windows>` | Set platform | | `/sage config rpc <url>` | Set RPC URL | | `/sage config cert <path>` | Set SSL cert path | | `/sage config key <path>` | Set SSL key path | | `/sage config fingerprint <fp>` | Set default wallet fingerprint | | `/sage config autologin <on\|off>` | Toggle auto-login | | `/sage config reset` | Reset to defaults | ### Operations Route to appropriate sub-skill based on domain: | Domain | Sub-Skill | Example Commands | |--------|-----------|------------------| | Auth & Keys | `sage-auth` | `/sage login`, `/sage logout`, `/sage keys` | | XCH | `sage-xch` | `/sage send xch`, `/sage balance`, `/sage combine` | | CAT Tokens | `sage-cat` | `/sage cats`, `/sage send cat`, `/sage issue cat` | | NFTs | `sage-nft` | `/sage nfts`, `/sage mint nft`, `/sage transfer nft` | | DIDs | `sage-did` | `/sage dids`, `/sage create did` | | Offers | `sage-offers` | `/sage offers`, `/sage make offer`, `/sage take offer` | | Options | `sage-options` | `/sage options`, `/sage mint option` | | Coins | `sage-coins` | `/sage coins`, `/sage check address` | | Transactions | `sage-txn` | `/sage pending`, `/sage submit` | | Network | `sage-network` | `/sage peers`, `/sage network` | | System | `sage-system` | `/sage sync`, `/sage version` | | WalletConnect | `sage-walletconnect` | `/sage wc sign` | ### Global Parameters All commands accept optional overrides: - `--fingerprint <fp>` — Use specific wallet - `--rpc <url>` — Override RPC URL - `--cert <path>` — Override cert path - `--key <path>` — Override key path ## Scripts - `scripts/sage-config.sh` — Config management - `scripts/sage-rpc.sh` — RPC caller with mTLS ### Making RPC Calls ```bash # Source the RPC helper source scripts/sage-rpc.sh # Call an endpoint sage_rpc "get_sync_status" '{}' sage_rpc "send_xch" '{"address":"xch1...","amount":"1000000000000","fee":"100000000"}' ``` ## Sub-Skills Each sub-skill handles a specific domain. Load the appropriate one based on the operation: | Sub-Skill | When to Load | |-----------|--------------| | [sage-auth](sub-skills/sage-auth/SKILL.md) | Login, logout, key management, mnemonics | | [sage-xch](sub-skills/sage-xch/SKILL.md) | Send/receive XCH, combine, split coins | | [sage-cat](sub-skills/sage-cat/SKILL.md) | CAT token operations | | [sage-nft](sub-skills/sage-nft/SKILL.md) | NFT minting, transfers, collections | | [sage-did](sub-skills/sage-did/SKILL.md) | DID creation and management | | [sage-offers](sub-skills/sage-offers/SKILL.md) | Offer creation, acceptance, cancellation | | [sage-options](sub-skills/sage-options/SKILL.md) | Options protocol operations | | [sage-coins](sub-skills/sage-coins/SKILL.md) | Coin queries, address validation | | [sage-txn](sub-skills/sage-txn/SKILL.md) | Transaction signing, submission | | [sage-network](sub-skills/sage-network/SKILL.md) | Peer and network settings | | [sage-system](sub-skills/sage-system/SKILL.md) | Sync status, version, database | | [sage-walletconnect](sub-skills/sage-walletconnect/SKILL.md) | WalletConnect integration | ## First Run On first invocation: 1. Check for config file; create if missing 2. Detect platform if set to `"auto"` 3. Verify cert/key files exist 4. If missing, prompt user to configure paths 5. Test connection with `GetVersion` If certs not found, guide user: ``` Sage certificates not found. Configure with: /sage config cert /path/to/wallet.crt /sage config key /path/to/wallet.key ``` ## Error Handling RPC errors return HTTP status codes: - `400` — Bad request (invalid params) - `401` — Unauthorized (cert mismatch) - `404` — Not found - `500` — Internal error Always check response for error field before processing data. ## Amount Formatting Sage uses mojos (1 XCH = 1,000,000,000,000 mojos). The `Amount` type in requests/responses is a string representing mojos. Convert for display: ``` 1 XCH = "1000000000000" 0.001 XCH = "1000000000" ```

path: README.md
content: <p align="center">
  <h1 align="center">🌿 Sage Wallet</h1>
  <p align="center">
    <strong>Complete RPC interface to the Sage Chia blockchain wallet</strong>
  </p>
  <p align="center">
    <em>112 endpoints across 12 domain skills for XCH, CATs, NFTs, DIDs, Offers, and more</em>
  </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://www.chia.net/">
    <img src="https://img.shields.io/badge/Blockchain-Chia-3AAC59.svg" alt="Chia Blockchain">
  </a>
  <a href="https://github.com/xch-dev/sage">
    <img src="https://img.shields.io/badge/Wallet-Sage-blue.svg" alt="Sage Wallet">
  </a>
  <a href="https://clawd.bot">
    <img src="https://img.shields.io/badge/Framework-Clawdbot-orange.svg" alt="Built for Clawdbot">
  </a>
</p>

<p align="center">
  <img src="https://img.shields.io/badge/Endpoints-112-brightgreen.svg" alt="112 Endpoints">
  <img src="https://img.shields.io/badge/Sub--Skills-12-blue.svg" alt="12 Sub-Skills">
  <img src="https://img.shields.io/badge/Platforms-Mac%20%7C%20Linux%20%7C%20Windows-purple.svg" alt="Cross Platform">
  <img src="https://img.shields.io/badge/Version-1.0.0-blue.svg" alt="Version">
</p>

---

## 🎯 Overview

Full RPC integration with [Sage Wallet](https://github.com/xch-dev/sage) for Chia blockchain operations. Send XCH, manage CAT tokens, mint NFTs, create offers, and more — all through natural language or slash commands.

---

## ✨ Features

<table>
<tr>
<td width="50%">

### 💰 **XCH Transactions**
- Send to single or multiple addresses
- Combine and split coins
- Auto-combine dust
- Clawback support

### 🪙 **CAT Tokens**
- Issue new tokens
- Send and receive CATs
- Track balances
- Auto-combine CAT coins

### 🖼️ **NFTs**
- Mint single or bulk NFTs
- Transfer ownership
- Manage collections
- Add URIs and metadata

</td>
<td width="50%">

### 🆔 **DIDs**
- Create identities
- Transfer DIDs
- Link NFTs to DIDs
- Provenance tracking

### 🤝 **Offers**
- Create P2P offers
- Accept and combine offers
- Cancel on-chain
- View offer details

### ⚙️ **System**
- Sync status monitoring
- Database maintenance
- Network peer management
- WalletConnect integration

</td>
</tr>
</table>

---

## 📋 Requirements

| Requirement | Version | Notes |
|-------------|---------|-------|
| **Sage Wallet** | Latest | Running with RPC enabled |
| **Bash** | 4.0+ | Shell scripting |
| **curl** | Any | HTTP client |
| **jq** | Any | JSON processing |
| **Clawdbot** | Latest | Agent framework |

---

## 🖥️ Platform Support

| Platform | Default Cert Path |
|----------|-------------------|
| **macOS** | `~/Library/Application Support/com.rigidnetwork.sage/ssl/wallet.crt` |
| **Linux** | `~/.local/share/sage/ssl/wallet.crt` |
| **Windows** | `%APPDATA%\com.rigidnetwork.sage\ssl\wallet.crt` |

Platform auto-detected, or set manually with `/sage config platform`.

---

## 🚀 Installation

### Via ClawdHub (Recommended)

```bash
clawdhub install sage-wallet
```

### Manual Installation

1. Download and extract to `~/clawd/skills/sage-wallet/`
2. Make scripts executable:
   ```bash
   chmod +x ~/clawd/skills/sage-wallet/scripts/*.sh
   ```

---

## 🎬 Quick Start

### Step 1️⃣: Verify Sage is Running

Ensure Sage Wallet is running with RPC enabled (default port 9257).

### Step 2️⃣: Test Connection

```
/sage status
```

This auto-detects your platform, finds certificates, and tests the connection.

### Step 3️⃣: Login to Wallet

```
/sage login 1234567890
```

Replace with your wallet fingerprint.

### Step 4️⃣: Start Using

```
/sage balance
/sage nfts
/sage send xch xch1... 1.5
```

---

## 💬 Commands Reference

### ⚙️ Configuration

| Command | Description | Example |
|---------|-------------|---------|
| `/sage status` | Show config & test connection | `/sage status` |
| `/sage config` | Display settings | `/sage config` |
| `/sage config platform <p>` | Set platform | `/sage config platform mac` |
| `/sage config rpc <url>` | Set RPC URL | `/sage config rpc https://192.168.1.50:9257` |
| `/sage config cert <path>` | Set cert path | `/sage config cert /path/to/wallet.crt` |
| `/sage config key <path>` | Set key path | `/sage config key /path/to/wallet.key` |
| `/sage config fingerprint <fp>` | Set default fingerprint | `/sage config fingerprint 1234567890` |
| `/sage config autologin <on/off>` | Toggle auto-login | `/sage config autologin on` |
| `/sage config reset` | Reset to defaults | `/sage config reset` |

### 🔐 Authentication

| Command | Description |
|---------|-------------|
| `/sage login <fingerprint>` | Login to wallet |
| `/sage logout` | End session |
| `/sage keys` | List wallet keys |
| `/sage mnemonic generate` | Generate new mnemonic |

### 💰 XCH Transactions

| Command | Description |
|---------|-------------|
| `/sage balance` | Check XCH balance |
| `/sage send xch <addr> <amount>` | Send XCH |
| `/sage send xch <addr> <amount> --fee 0.0001` | Send with fee |
| `/sage combine` | Auto-combine dust |
| `/sage split <coin_id> <count>` | Split coin |

### 🪙 CAT Tokens

| Command | Description |
|---------|-------------|
| `/sage cats` | List CAT tokens |
| `/sage send cat <asset_id> <addr> <amount>` | Send CAT |
| `/sage issue cat <name> <ticker> <supply>` | Issue new CAT |

### 🖼️ NFTs

| Command | Description |
|---------|-------------|
| `/sage nfts` | List NFTs |
| `/sage nft <nft_id>` | Get NFT details |
| `/sage collections` | List collections |
| `/sage mint nft --did <did> --data <uri>` | Mint NFT |
| `/sage transfer nft <nft_id> <address>` | Transfer NFT |

### 🆔 DIDs

| Command | Description |
|---------|-------------|
| `/sage dids` | List DIDs |
| `/sage create did <name>` | Create DID |
| `/sage transfer did <did_id> <address>` | Transfer DID |

### 🤝 Offers

| Command | Description |
|---------|-------------|
| `/sage offers` | List offers |
| `/sage offer view <offer_string>` | View offer details |
| `/sage offer make --request <xch:1> --offer <cat:1000>` | Create offer |
| `/sage offer take <offer_string>` | Accept offer |
| `/sage offer cancel <offer_id>` | Cancel on-chain |

### 📊 System

| Command | Description |
|---------|-------------|
| `/sage sync` | Sync status |
| `/sage version` | Wallet version |
| `/sage peers` | Connected peers |
| `/sage network` | Current network |
| `/sage pending` | Pending transactions |

### 🔧 Global Options

All commands accept optional overrides:

```
--fingerprint <fp>    Use specific wallet
--rpc <url>           Override RPC URL
--cert <path>         Override cert path
--key <path>          Override key path
```

---

## 📁 Skill Structure

```
sage-wallet/
├── SKILL.md                    # Master skill (orchestration)
├── README.md                   # This file
├── references/
│   └── endpoints.md            # All 112 endpoints reference
├── scripts/
│   ├── sage-config.sh          # Configuration management
│   ├── sage-rpc.sh             # mTLS RPC caller
│   ├── test-config.sh          # Config tests
│   ├── test-rpc.sh             # RPC tests
│   └── test-integration.sh     # Full integration tests
└── sub-skills/
    ├── sage-auth/              # Authentication & keys
    ├── sage-xch/               # XCH transactions
    ├── sage-cat/               # CAT tokens
    ├── sage-nft/               # NFTs
    ├── sage-did/               # DIDs
    ├── sage-offers/            # Offers
    ├── sage-options/           # Options protocol
    ├── sage-coins/             # Coins & addresses
    ├── sage-txn/               # Transaction signing
    ├── sage-network/           # Network & peers
    ├── sage-system/            # System & sync
    └── sage-walletconnect/     # WalletConnect
```

---

## 🔌 Sub-Skills

Each domain has a dedicated sub-skill with full endpoint documentation:

| Sub-Skill | Endpoints | Description |
|-----------|-----------|-------------|
| **sage-auth** | 16 | Login, logout, keys, mnemonics, themes |
| **sage-xch** | 7 | Send, bulk send, combine, split, clawback |
| **sage-cat** | 9 | List, send, issue, combine CAT tokens |
| **sage-nft** | 14 | Mint, transfer, collections, URIs |
| **sage-did** | 6 | Create, transfer, normalize DIDs |
| **sage-offers** | 11 | Create, take, view, cancel offers |
| **sage-options** | 6 | Mint, exercise, transfer options |
| **sage-coins** | 8 | List coins, addresses, derivations |
| **sage-txn** | 6 | Sign, submit, pending transactions |
| **sage-network** | 12 | Peers, network settings, sync config |
| **sage-system** | 4 | Version, sync status, database |
| **sage-walletconnect** | 5 | dApp connectivity, message signing |

**Total: 112 endpoints**

---

## 🧪 Testing

### Run Config Tests

```bash
./scripts/test-config.sh
```

### Run RPC Tests (Dry Mode)

```bash
./scripts/test-rpc.sh
```

### Run RPC Tests (Live)

```bash
./scripts/test-rpc.sh --live
```

### Full Integration Test

```bash
./scripts/test-integration.sh --fingerprint 1234567890
```

---

## ⚙️ Configuration File

Location: `~/.config/sage-wallet/config.json`

```json
{
  "platform": "auto",
  "rpc_url": "https://127.0.0.1:9257",
  "cert_path": null,
  "key_path": null,
  "fingerprint": null,
  "auto_login": false
}
```

| Field | Type | Description |
|-------|------|-------------|
| `platform` | string | `"auto"`, `"mac"`, `"linux"`, or `"windows"` |
| `rpc_url` | string | Sage RPC endpoint |
| `cert_path` | string/null | Custom cert path (null = platform default) |
| `key_path` | string/null | Custom key path (null = platform default) |
| `fingerprint` | number/null | Default wallet fingerprint |
| `auto_login` | boolean | Auto-login on first RPC call |

---

## 💱 Amount Formatting

Sage uses **mojos** (smallest unit). Conversions:

| XCH | Mojos |
|-----|-------|
| 1 XCH | `1000000000000` |
| 0.1 XCH | `100000000000` |
| 0.001 XCH | `1000000000` |
| 0.000001 XCH | `1000000` |

CAT amounts are in the token's smallest unit (no decimals in RPC).

---

## 🔧 Troubleshooting

<details>
<summary><b>❌ "Certificate not found"</b></summary>

**Solution:**
1. Verify Sage is installed and has been run at least once
2. Check platform detection: `/sage config`
3. Set path manually:
   ```
   /sage config cert /path/to/wallet.crt
   /sage config key /path/to/wallet.key
   ```
</details>

<details>
<summary><b>❌ "Connection refused"</b></summary>

**Solution:**
1. Ensure Sage wallet is running
2. Check RPC is enabled in Sage settings
3. Verify port 9257 is accessible:
   ```bash
   curl -k https://127.0.0.1:9257/get_version
   ```
4. Check custom RPC URL if set: `/sage config`
</details>

<details>
<summary><b>❌ "Unauthorized" error</b></summary>

**Solution:**
1. Sage uses mutual TLS — both cert and key required
2. Ensure cert/key files are readable
3. Verify cert matches the Sage installation
4. Try regenerating certs in Sage settings
</details>

<details>
<summary><b>❌ "Not logged in"</b></summary>

**Solution:**
1. Login with fingerprint: `/sage login 1234567890`
2. Or set default fingerprint:
   ```
   /sage config fingerprint 1234567890
   /sage config autologin on
   ```
</details>

<details>
<summary><b>❌ Wrong platform detected</b></summary>

**Solution:**
```
/sage config platform linux
```
Options: `auto`, `mac`, `linux`, `windows`
</details>

---

## 🔐 Security Notes

> **Important:** This skill interacts with real cryptocurrency wallets.

### ✅ Best Practices

- Never share your mnemonic or wallet.key file
- Use testnet for development and testing
- Verify transaction details before submitting
- Keep Sage wallet software updated
- Use clawback for large transfers to new addresses

### 🔒 What This Skill Does

- Reads certificates from your local Sage installation
- Makes authenticated RPC calls to your local wallet
- Does **not** store or transmit mnemonics
- Does **not** access remote wallets (unless you configure a remote RPC)

---

## 📊 Use Cases

### 💼 **Portfolio Management**
Check balances, track NFTs, and monitor transactions across wallets.

### 🤖 **Automated Trading**
Create and manage offers programmatically through natural language.

### 🎨 **NFT Operations**
Bulk mint, transfer, and organize NFT collections.

### 🔄 **Token Distribution**
Bulk send CAT tokens or XCH to multiple addresses.

### 📈 **Wallet Monitoring**
Track sync status, pending transactions, and network health.

---

## 🗺️ Roadmap

### ✅ Completed (v1.0.0)
- [x] All 112 RPC endpoints mapped
- [x] 12 domain sub-skills
- [x] Cross-platform support (Mac/Linux/Windows)
- [x] Configuration management
- [x] Test suites
- [x] Comprehensive documentation

### 🚧 Planned
- [ ] Transaction builder with confirmation prompts
- [ ] Offer marketplace integration
- [ ] Price feed integration (XCH/USD)
- [ ] Portfolio analytics
- [ ] Multi-wallet dashboard
- [ ] Scheduled transactions
- [ ] Webhook notifications

---

## 🤝 Contributing

Contributions welcome! 

- **Bug Reports:** Open an issue with reproduction steps
- **Feature Requests:** Describe the use case
- **Pull Requests:** Fork, branch, test, submit

---

## 📄 License

MIT License — Koba42 Corp

---

## 🙏 Credits

Built with ❤️ by **Koba42 Corp**

### Powered By:
- 🌿 [Sage Wallet](https://github.com/xch-dev/sage) - Chia wallet
- 🌱 [Chia Blockchain](https://www.chia.net/) - Green cryptocurrency
- 🦾 [Clawdbot](https://clawd.bot) - Agent framework
- 🔧 [curl](https://curl.se/) - HTTP client
- 📋 [jq](https://jqlang.github.io/jq/) - JSON processor

### Resources:
- [Sage Wallet GitHub](https://github.com/xch-dev/sage)
- [Chia Developer Documentation](https://docs.chia.net/)
- [Clawdbot Documentation](https://docs.clawd.bot)
- [ClawdHub](https://clawdhub.com)

---

## 📬 Support

- 💬 **Discord:** [discord.com/invite/clawd](https://discord.com/invite/clawd)
- 📖 **Docs:** [docs.clawd.bot](https://docs.clawd.bot)
- 🐛 **Issues:** GitHub Issues

---

<p align="center">
  <strong>⭐ Star us on GitHub if this skill helped you!</strong>
</p>

<p align="center">
  <em>"The Chia blockchain: where sustainability meets innovation."</em>
</p>

<p align="center">
  <sub>Version 1.0.0 | January 2026</sub>
</p>


path: references/endpoints.md
content: # Sage RPC Endpoints Quick Reference

All 112 endpoints organized by category.

## Authentication & Keys (16)
| Endpoint | Description |
|----------|-------------|
| `login` | Login with fingerprint |
| `logout` | End session |
| `generate_mnemonic` | Generate 12/24-word mnemonic |
| `import_key` | Import wallet |
| `delete_key` | Delete wallet key |
| `rename_key` | Rename wallet |
| `set_wallet_emoji` | Set emoji |
| `get_keys` | List all keys |
| `get_key` | Get specific key |
| `get_secret_key` | Get mnemonic |
| `resync` | Resync wallet |
| `delete_database` | Delete wallet DB |
| `get_user_themes` | List themes |
| `get_user_theme` | Get theme |
| `save_user_theme` | Save theme |
| `delete_user_theme` | Delete theme |

## XCH Transactions (7)
| Endpoint | Description |
|----------|-------------|
| `send_xch` | Send XCH |
| `bulk_send_xch` | Multi-recipient send |
| `multi_send` | Multi-asset send |
| `combine` | Merge coins |
| `split` | Split coin |
| `auto_combine_xch` | Auto-merge |
| `finalize_clawback` | Complete clawback |

## CAT Tokens (9)
| Endpoint | Description |
|----------|-------------|
| `get_cats` | List wallet CATs |
| `get_all_cats` | List all known CATs |
| `get_token` | Get token details |
| `update_cat` | Update metadata |
| `resync_cat` | Resync balance |
| `issue_cat` | Mint new token |
| `send_cat` | Send CAT |
| `bulk_send_cat` | Multi-recipient CAT |
| `auto_combine_cat` | Auto-merge CAT coins |

## NFTs (14)
| Endpoint | Description |
|----------|-------------|
| `get_nfts` | List NFTs |
| `get_nft` | Get NFT details |
| `get_nft_icon` | Get icon |
| `get_nft_thumbnail` | Get thumbnail |
| `get_nft_data` | Get raw data |
| `get_nft_collections` | List collections |
| `get_nft_collection` | Get collection |
| `update_nft` | Update visibility |
| `update_nft_collection` | Update collection |
| `redownload_nft` | Re-fetch data |
| `bulk_mint_nfts` | Mint NFTs |
| `transfer_nfts` | Transfer NFTs |
| `add_nft_uri` | Add URI |
| `assign_nfts_to_did` | Assign to DID |

## DIDs (6)
| Endpoint | Description |
|----------|-------------|
| `get_dids` | List DIDs |
| `get_minter_did_ids` | List minter DIDs |
| `update_did` | Update DID |
| `create_did` | Create DID |
| `transfer_dids` | Transfer DIDs |
| `normalize_dids` | Normalize state |

## Offers (11)
| Endpoint | Description |
|----------|-------------|
| `make_offer` | Create offer |
| `take_offer` | Accept offer |
| `combine_offers` | Merge offers |
| `view_offer` | View offer |
| `import_offer` | Import offer |
| `get_offers` | List offers |
| `get_offers_for_asset` | Filter by asset |
| `get_offer` | Get specific offer |
| `delete_offer` | Delete local |
| `cancel_offer` | Cancel on-chain |
| `cancel_offers` | Bulk cancel |

## Options (5)
| Endpoint | Description |
|----------|-------------|
| `get_options` | List options |
| `get_option` | Get option |
| `update_option` | Update visibility |
| `mint_option` | Mint option |
| `exercise_options` | Exercise |
| `transfer_options` | Transfer |

## Coins & Addresses (8)
| Endpoint | Description |
|----------|-------------|
| `get_coins` | List coins |
| `get_coins_by_ids` | Get specific coins |
| `get_are_coins_spendable` | Check spendability |
| `get_spendable_coin_count` | Count spendable |
| `check_address` | Validate address |
| `get_derivations` | Get derivations |
| `increase_derivation_index` | Generate addresses |
| `is_asset_owned` | Check ownership |

## Transactions (6)
| Endpoint | Description |
|----------|-------------|
| `get_transactions` | List transactions |
| `get_transaction` | Get by height |
| `get_pending_transactions` | List pending |
| `sign_coin_spends` | Sign transaction |
| `view_coin_spends` | Preview |
| `submit_transaction` | Broadcast |

## Network & Peers (12)
| Endpoint | Description |
|----------|-------------|
| `get_peers` | List peers |
| `add_peer` | Add peer |
| `remove_peer` | Remove/ban peer |
| `set_discover_peers` | Toggle discovery |
| `set_target_peers` | Set target count |
| `get_network` | Get current network |
| `get_networks` | List networks |
| `set_network` | Switch network |
| `set_network_override` | Per-wallet override |
| `set_delta_sync` | Toggle delta sync |
| `set_delta_sync_override` | Per-wallet sync |
| `set_change_address` | Set change address |

## System (4)
| Endpoint | Description |
|----------|-------------|
| `get_sync_status` | Sync progress |
| `get_version` | Wallet version |
| `get_database_stats` | DB statistics |
| `perform_database_maintenance` | Optimize DB |

## WalletConnect (5)
| Endpoint | Description |
|----------|-------------|
| `filter_unlocked_coins` | Filter unlocked |
| `get_asset_coins` | Get spendable |
| `sign_message_with_public_key` | Sign with pubkey |
| `sign_message_by_address` | Sign with address |
| `send_transaction_immediately` | Direct broadcast |


path: scripts/sage-config.sh
content: #!/usr/bin/env bash
# sage-config.sh — Configuration management for Sage Wallet skill
# Cross-platform support for Mac, Linux, Windows

set -euo pipefail

# Default config location
SAGE_CONFIG_DIR="${SAGE_CONFIG_DIR:-$HOME/.config/sage-wallet}"
SAGE_CONFIG_FILE="${SAGE_CONFIG_DIR}/config.json"

# Platform detection
detect_platform() {
  case "$(uname -s)" in
    Darwin*)  echo "mac" ;;
    Linux*)   echo "linux" ;;
    MINGW*|MSYS*|CYGWIN*) echo "windows" ;;
    *)        echo "linux" ;;
  esac
}

# Get default cert path for platform
get_default_cert_path() {
  local platform="${1:-$(detect_platform)}"
  case "$platform" in
    mac)     echo "$HOME/Library/Application Support/com.rigidnetwork.sage/ssl/wallet.crt" ;;
    linux)   echo "$HOME/.local/share/sage/ssl/wallet.crt" ;;
    windows) echo "${APPDATA:-$HOME}/com.rigidnetwork.sage/ssl/wallet.crt" ;;
  esac
}

# Get default key path for platform
get_default_key_path() {
  local platform="${1:-$(detect_platform)}"
  case "$platform" in
    mac)     echo "$HOME/Library/Application Support/com.rigidnetwork.sage/ssl/wallet.key" ;;
    linux)   echo "$HOME/.local/share/sage/ssl/wallet.key" ;;
    windows) echo "${APPDATA:-$HOME}/com.rigidnetwork.sage/ssl/wallet.key" ;;
  esac
}

# Initialize config with defaults
init_config() {
  mkdir -p "$SAGE_CONFIG_DIR"
  if [[ ! -f "$SAGE_CONFIG_FILE" ]]; then
    cat > "$SAGE_CONFIG_FILE" << 'EOF'
{
  "platform": "auto",
  "rpc_url": "https://127.0.0.1:9257",
  "cert_path": null,
  "key_path": null,
  "fingerprint": null,
  "auto_login": false
}
EOF
    echo "Created config at $SAGE_CONFIG_FILE"
  fi
}

# Read config value
get_config() {
  local key="$1"
  local default="${2:-}"
  
  if [[ ! -f "$SAGE_CONFIG_FILE" ]]; then
    echo "$default"
    return
  fi
  
  local value
  value=$(jq -r ".$key // empty" "$SAGE_CONFIG_FILE" 2>/dev/null || echo "")
  
  if [[ -z "$value" || "$value" == "null" ]]; then
    echo "$default"
  else
    echo "$value"
  fi
}

# Set config value
set_config() {
  local key="$1"
  local value="$2"
  
  init_config
  
  local tmp_file
  tmp_file=$(mktemp)
  
  # Handle different value types
  if [[ "$value" == "null" || "$value" == "true" || "$value" == "false" || "$value" =~ ^[0-9]+$ ]]; then
    jq ".$key = $value" "$SAGE_CONFIG_FILE" > "$tmp_file"
  else
    jq ".$key = \"$value\"" "$SAGE_CONFIG_FILE" > "$tmp_file"
  fi
  
  mv "$tmp_file" "$SAGE_CONFIG_FILE"
  echo "Set $key = $value"
}

# Reset config to defaults
reset_config() {
  rm -f "$SAGE_CONFIG_FILE"
  init_config
  echo "Config reset to defaults"
}

# Show current config
show_config() {
  if [[ ! -f "$SAGE_CONFIG_FILE" ]]; then
    echo "No config file found. Run 'init' to create one."
    return 1
  fi
  
  local platform rpc_url cert_path key_path fingerprint auto_login
  
  platform=$(get_config "platform" "auto")
  rpc_url=$(get_config "rpc_url" "https://127.0.0.1:9257")
  cert_path=$(get_config "cert_path" "")
  key_path=$(get_config "key_path" "")
  fingerprint=$(get_config "fingerprint" "")
  auto_login=$(get_config "auto_login" "false")
  
  # Resolve effective paths
  local effective_platform="$platform"
  [[ "$platform" == "auto" ]] && effective_platform=$(detect_platform)
  
  local effective_cert="${cert_path:-$(get_default_cert_path "$effective_platform")}"
  local effective_key="${key_path:-$(get_default_key_path "$effective_platform")}"
  
  echo "Sage Wallet Configuration"
  echo "========================="
  echo "Config file: $SAGE_CONFIG_FILE"
  echo ""
  echo "Settings:"
  echo "  platform:    $platform (effective: $effective_platform)"
  echo "  rpc_url:     $rpc_url"
  echo "  cert_path:   ${cert_path:-<default>}"
  echo "  key_path:    ${key_path:-<default>}"
  echo "  fingerprint: ${fingerprint:-<not set>}"
  echo "  auto_login:  $auto_login"
  echo ""
  echo "Effective paths:"
  echo "  cert: $effective_cert"
  echo "  key:  $effective_key"
  echo ""
  
  # Check if files exist
  if [[ -f "$effective_cert" ]]; then
    echo "  ✓ Certificate found"
  else
    echo "  ✗ Certificate NOT found"
  fi
  
  if [[ -f "$effective_key" ]]; then
    echo "  ✓ Key found"
  else
    echo "  ✗ Key NOT found"
  fi
}

# Resolve effective config values (for use by other scripts)
resolve_config() {
  local platform rpc_url cert_path key_path
  
  platform=$(get_config "platform" "auto")
  [[ "$platform" == "auto" ]] && platform=$(detect_platform)
  
  rpc_url=$(get_config "rpc_url" "https://127.0.0.1:9257")
  cert_path=$(get_config "cert_path" "")
  key_path=$(get_config "key_path" "")
  
  [[ -z "$cert_path" ]] && cert_path=$(get_default_cert_path "$platform")
  [[ -z "$key_path" ]] && key_path=$(get_default_key_path "$platform")
  
  echo "SAGE_PLATFORM=$platform"
  echo "SAGE_RPC_URL=$rpc_url"
  echo "SAGE_CERT_PATH=$cert_path"
  echo "SAGE_KEY_PATH=$key_path"
  echo "SAGE_FINGERPRINT=$(get_config "fingerprint" "")"
  echo "SAGE_AUTO_LOGIN=$(get_config "auto_login" "false")"
}

# Main command handler
main() {
  local cmd="${1:-show}"
  shift || true
  
  case "$cmd" in
    init)
      init_config
      ;;
    show|config)
      show_config
      ;;
    get)
      get_config "$@"
      ;;
    set)
      set_config "$@"
      ;;
    reset)
      reset_config
      ;;
    resolve)
      resolve_config
      ;;
    platform)
      if [[ -n "${1:-}" ]]; then
        set_config "platform" "$1"
      else
        detect_platform
      fi
      ;;
    cert)
      set_config "cert_path" "$1"
      ;;
    key)
      set_config "key_path" "$1"
      ;;
    rpc)
      set_config "rpc_url" "$1"
      ;;
    fingerprint)
      set_config "fingerprint" "$1"
      ;;
    autologin)
      local val="false"
      [[ "$1" == "on" || "$1" == "true" || "$1" == "1" ]] && val="true"
      set_config "auto_login" "$val"
      ;;
    help|--help|-h)
      echo "Usage: sage-config.sh <command> [args]"
      echo ""
      echo "Commands:"
      echo "  init                  Initialize config file"
      echo "  show, config          Show current configuration"
      echo "  get <key>             Get config value"
      echo "  set <key> <value>     Set config value"
      echo "  reset                 Reset to defaults"
      echo "  resolve               Output resolved config as env vars"
      echo "  platform [value]      Get/set platform (auto|mac|linux|windows)"
      echo "  cert <path>           Set certificate path"
      echo "  key <path>            Set key path"
      echo "  rpc <url>             Set RPC URL"
      echo "  fingerprint <fp>      Set default fingerprint"
      echo "  autologin <on|off>    Set auto-login"
      ;;
    *)
      echo "Unknown command: $cmd"
      echo "Run 'sage-config.sh help' for usage"
      exit 1
      ;;
  esac
}

# Only run main if executed directly (not sourced)
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
  main "$@"
fi


path: scripts/sage-rpc.sh
content: #!/usr/bin/env bash
# sage-rpc.sh — RPC caller for Sage Wallet with mTLS support
# Source this file to use sage_rpc function, or run directly

set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

# Load config if not already set
load_config() {
  if [[ -z "${SAGE_RPC_URL:-}" ]]; then
    if [[ -f "$SCRIPT_DIR/sage-config.sh" ]]; then
      eval "$("$SCRIPT_DIR/sage-config.sh" resolve)"
    else
      # Fallback defaults
      SAGE_RPC_URL="https://127.0.0.1:9257"
      SAGE_CERT_PATH="$HOME/.local/share/sage/ssl/wallet.crt"
      SAGE_KEY_PATH="$HOME/.local/share/sage/ssl/wallet.key"
    fi
  fi
}

# Make an RPC call to Sage wallet
# Usage: sage_rpc <endpoint> [payload] [--fingerprint fp] [--rpc url] [--cert path] [--key path]
sage_rpc() {
  load_config
  
  local endpoint=""
  local payload="{}"
  local rpc_url="$SAGE_RPC_URL"
  local cert_path="$SAGE_CERT_PATH"
  local key_path="$SAGE_KEY_PATH"
  
  # Parse arguments
  while [[ $# -gt 0 ]]; do
    case "$1" in
      --rpc)
        rpc_url="$2"
        shift 2
        ;;
      --cert)
        cert_path="$2"
        shift 2
        ;;
      --key)
        key_path="$2"
        shift 2
        ;;
      --fingerprint)
        # Inject fingerprint into payload if needed
        shift 2
        ;;
      -*)
        echo "Unknown option: $1" >&2
        return 1
        ;;
      *)
        if [[ -z "$endpoint" ]]; then
          endpoint="$1"
        else
          payload="$1"
        fi
        shift
        ;;
    esac
  done
  
  if [[ -z "$endpoint" ]]; then
    echo "Usage: sage_rpc <endpoint> [payload] [options]" >&2
    return 1
  fi
  
  # Validate cert/key exist
  if [[ ! -f "$cert_path" ]]; then
    echo "Error: Certificate not found at $cert_path" >&2
    echo "Configure with: sage-config.sh cert /path/to/wallet.crt" >&2
    return 1
  fi
  
  if [[ ! -f "$key_path" ]]; then
    echo "Error: Key not found at $key_path" >&2
    echo "Configure with: sage-config.sh key /path/to/wallet.key" >&2
    return 1
  fi
  
  # Make the RPC call
  local response
  response=$(curl -s -X POST \
    --cert "$cert_path" \
    --key "$key_path" \
    --cacert "$cert_path" \
    -H "Content-Type: application/json" \
    -d "$payload" \
    "${rpc_url}/${endpoint}" 2>&1) || {
      echo "Error: RPC call failed" >&2
      echo "$response" >&2
      return 1
    }
  
  echo "$response"
}

# Test connection to Sage wallet
sage_test_connection() {
  load_config
  
  echo "Testing connection to $SAGE_RPC_URL..."
  echo "  Cert: $SAGE_CERT_PATH"
  echo "  Key:  $SAGE_KEY_PATH"
  echo ""
  
  local response
  if response=$(sage_rpc "get_version" '{}' 2>&1); then
    local version
    version=$(echo "$response" | jq -r '.version // empty' 2>/dev/null || echo "")
    if [[ -n "$version" ]]; then
      echo "✓ Connected! Sage version $version"
      return 0
    else
      echo "✗ Unexpected response: $response"
      return 1
    fi
  else
    echo "✗ Connection failed: $response"
    return 1
  fi
}

# Get sync status
sage_sync_status() {
  local response
  response=$(sage_rpc "get_sync_status" '{}')
  echo "$response" | jq '.'
}

# Login to wallet
sage_login() {
  local fingerprint="${1:-${SAGE_FINGERPRINT:-}}"
  
  if [[ -z "$fingerprint" ]]; then
    echo "Error: Fingerprint required" >&2
    echo "Usage: sage_login <fingerprint>" >&2
    return 1
  fi
  
  sage_rpc "login" "{\"fingerprint\": $fingerprint}"
}

# Logout from wallet
sage_logout() {
  sage_rpc "logout" '{}'
}

# List keys
sage_keys() {
  sage_rpc "get_keys" '{}'
}

# If run directly (not sourced), execute command
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
  cmd="${1:-help}"
  shift || true
  
  case "$cmd" in
    test)
      sage_test_connection
      ;;
    sync)
      sage_sync_status
      ;;
    login)
      sage_login "$@"
      ;;
    logout)
      sage_logout
      ;;
    keys)
      sage_keys
      ;;
    call)
      sage_rpc "$@"
      ;;
    help|--help|-h)
      echo "Usage: sage-rpc.sh <command> [args]"
      echo ""
      echo "Commands:"
      echo "  test              Test connection to Sage wallet"
      echo "  sync              Get sync status"
      echo "  login <fp>        Login with fingerprint"
      echo "  logout            Logout"
      echo "  keys              List wallet keys"
      echo "  call <ep> [json]  Call arbitrary endpoint"
      echo ""
      echo "Source this file to use sage_rpc function:"
      echo "  source sage-rpc.sh"
      echo "  sage_rpc get_version '{}'"
      ;;
    *)
      echo "Unknown command: $cmd"
      exit 1
      ;;
  esac
fi


path: scripts/test-config.sh
content: #!/usr/bin/env bash
# test-config.sh — Tests for sage-config.sh
# Run: ./test-config.sh

set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

# Source the config functions
source "$SCRIPT_DIR/sage-config.sh"

# Test temp directory
TEST_DIR=$(mktemp -d)
export SAGE_CONFIG_DIR="$TEST_DIR"
export SAGE_CONFIG_FILE="$TEST_DIR/config.json"

# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
NC='\033[0m'

PASSED=0
FAILED=0

pass() {
  echo -e "${GREEN}✓${NC} $1"
  PASSED=$((PASSED + 1))
}

fail() {
  echo -e "${RED}✗${NC} $1"
  FAILED=$((FAILED + 1))
}

cleanup() {
  rm -rf "$TEST_DIR"
}
trap cleanup EXIT

echo "=== Sage Config Tests ==="
echo "Test dir: $TEST_DIR"
echo ""

# Test 1: Platform detection
echo "--- Platform Detection ---"
platform=$(detect_platform)
if [[ "$platform" =~ ^(mac|linux|windows)$ ]]; then
  pass "detect_platform returns valid platform: $platform"
else
  fail "detect_platform returned invalid: $platform"
fi

# Test 2: Default paths
echo ""
echo "--- Default Paths ---"
cert_mac=$(get_default_cert_path "mac")
if [[ "$cert_mac" == *"Library/Application Support"*"wallet.crt" ]]; then
  pass "Mac cert path correct"
else
  fail "Mac cert path wrong: $cert_mac"
fi

cert_linux=$(get_default_cert_path "linux")
if [[ "$cert_linux" == *".local/share/sage"*"wallet.crt" ]]; then
  pass "Linux cert path correct"
else
  fail "Linux cert path wrong: $cert_linux"
fi

key_linux=$(get_default_key_path "linux")
if [[ "$key_linux" == *".local/share/sage"*"wallet.key" ]]; then
  pass "Linux key path correct"
else
  fail "Linux key path wrong: $key_linux"
fi

# Test 3: Config initialization
echo ""
echo "--- Config Init ---"
init_config > /dev/null
if [[ -f "$SAGE_CONFIG_FILE" ]]; then
  pass "Config file created"
else
  fail "Config file not created"
fi

# Verify JSON structure
if jq -e '.platform' "$SAGE_CONFIG_FILE" > /dev/null 2>&1; then
  pass "Config has valid JSON with platform field"
else
  fail "Config JSON invalid"
fi

# Test 4: Get config defaults
echo ""
echo "--- Get Config ---"
rpc_url=$(get_config "rpc_url")
if [[ "$rpc_url" == "https://127.0.0.1:9257" ]]; then
  pass "Default rpc_url correct"
else
  fail "Default rpc_url wrong: $rpc_url"
fi

platform_val=$(get_config "platform")
if [[ "$platform_val" == "auto" ]]; then
  pass "Default platform correct"
else
  fail "Default platform wrong: $platform_val"
fi

# Test 5: Set config
echo ""
echo "--- Set Config ---"
set_config "rpc_url" "https://custom:9999" > /dev/null
new_rpc=$(get_config "rpc_url")
if [[ "$new_rpc" == "https://custom:9999" ]]; then
  pass "Set rpc_url works"
else
  fail "Set rpc_url failed: $new_rpc"
fi

set_config "fingerprint" "1234567890" > /dev/null
fp=$(get_config "fingerprint")
if [[ "$fp" == "1234567890" ]]; then
  pass "Set fingerprint works"
else
  fail "Set fingerprint failed: $fp"
fi

set_config "auto_login" "true" > /dev/null
al=$(get_config "auto_login")
if [[ "$al" == "true" ]]; then
  pass "Set boolean works"
else
  fail "Set boolean failed: $al"
fi

# Test 6: Resolve config
echo ""
echo "--- Resolve Config ---"
resolved=$(resolve_config)
if echo "$resolved" | grep -q "SAGE_RPC_URL=https://custom:9999"; then
  pass "Resolve includes custom rpc_url"
else
  fail "Resolve missing custom rpc_url"
fi

if echo "$resolved" | grep -q "SAGE_FINGERPRINT=1234567890"; then
  pass "Resolve includes fingerprint"
else
  fail "Resolve missing fingerprint"
fi

# Test 7: Reset config
echo ""
echo "--- Reset Config ---"
reset_config > /dev/null
rpc_after_reset=$(get_config "rpc_url")
if [[ "$rpc_after_reset" == "https://127.0.0.1:9257" ]]; then
  pass "Reset restores defaults"
else
  fail "Reset failed: $rpc_after_reset"
fi

# Test 8: Null handling
echo ""
echo "--- Null Handling ---"
cert_null=$(get_config "cert_path" "")
if [[ -z "$cert_null" || "$cert_null" == "null" ]]; then
  pass "Null cert_path handled correctly"
else
  fail "Null handling wrong: $cert_null"
fi

# Summary
echo ""
echo "=== Results ==="
echo -e "${GREEN}Passed: $PASSED${NC}"
echo -e "${RED}Failed: $FAILED${NC}"

if [[ $FAILED -gt 0 ]]; then
  exit 1
fi


path: scripts/test-integration.sh
content: #!/usr/bin/env bash
# test-integration.sh — Full integration tests against live Sage wallet
# 
# WARNING: These tests interact with a real wallet!
# Only run on testnet or with a test wallet.
#
# Usage: ./test-integration.sh [--testnet] [--fingerprint FP]

set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/sage-rpc.sh"

FINGERPRINT=""
NETWORK="mainnet"

# Parse args
while [[ $# -gt 0 ]]; do
  case "$1" in
    --testnet) NETWORK="testnet11"; shift ;;
    --fingerprint) FINGERPRINT="$2"; shift 2 ;;
    *) shift ;;
  esac
done

# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
BLUE='\033[0;34m'
NC='\033[0m'

PASSED=0
FAILED=0
SKIPPED=0

pass() { echo -e "${GREEN}✓${NC} $1"; PASSED=$((PASSED + 1)); }
fail() { echo -e "${RED}✗${NC} $1"; FAILED=$((FAILED + 1)); }
skip() { echo -e "${YELLOW}○${NC} $1"; SKIPPED=$((SKIPPED + 1)); }
info() { echo -e "${BLUE}ℹ${NC} $1"; }

echo "=========================================="
echo "  Sage Wallet Integration Tests"
echo "=========================================="
echo ""

# Pre-flight checks
echo "--- Pre-flight Checks ---"

load_config

if [[ ! -f "$SAGE_CERT_PATH" ]]; then
  echo -e "${RED}Certificate not found: $SAGE_CERT_PATH${NC}"
  echo "Configure with: sage-config.sh cert /path/to/wallet.crt"
  exit 1
fi

if [[ ! -f "$SAGE_KEY_PATH" ]]; then
  echo -e "${RED}Key not found: $SAGE_KEY_PATH${NC}"
  echo "Configure with: sage-config.sh key /path/to/wallet.key"
  exit 1
fi

pass "Certificates found"

# Test connection
version_resp=$(sage_rpc "get_version" '{}' 2>&1) || true
if ! echo "$version_resp" | jq -e '.version' > /dev/null 2>&1; then
  echo -e "${RED}Cannot connect to Sage wallet${NC}"
  echo "Response: $version_resp"
  exit 1
fi
VERSION=$(echo "$version_resp" | jq -r '.version')
pass "Connected to Sage $VERSION"

echo ""
echo "--- System Tests ---"

# Get sync status
sync_resp=$(sage_rpc "get_sync_status" '{}')
if echo "$sync_resp" | jq -e '.balance' > /dev/null 2>&1; then
  balance=$(echo "$sync_resp" | jq -r '.balance')
  synced=$(echo "$sync_resp" | jq -r '.synced_coins')
  total=$(echo "$sync_resp" | jq -r '.total_coins')
  pass "Sync status: $synced/$total coins synced"
  info "Balance: $balance mojos"
else
  fail "Sync status failed"
fi

# Database stats
db_resp=$(sage_rpc "get_database_stats" '{}')
if echo "$db_resp" | jq -e '.database_size_bytes' > /dev/null 2>&1; then
  size=$(echo "$db_resp" | jq -r '.database_size_bytes')
  size_mb=$((size / 1024 / 1024))
  pass "Database stats: ${size_mb}MB"
else
  fail "Database stats failed"
fi

echo ""
echo "--- Key Management Tests ---"

# List keys
keys_resp=$(sage_rpc "get_keys" '{}')
if echo "$keys_resp" | jq -e '.keys' > /dev/null 2>&1; then
  key_count=$(echo "$keys_resp" | jq '.keys | length')
  pass "Found $key_count wallet key(s)"
  
  if [[ $key_count -gt 0 ]]; then
    # Get first key fingerprint if not provided
    if [[ -z "$FINGERPRINT" ]]; then
      FINGERPRINT=$(echo "$keys_resp" | jq -r '.keys[0].fingerprint')
      info "Using first wallet: $FINGERPRINT"
    fi
  fi
else
  fail "List keys failed"
fi

# Login test (if we have a fingerprint)
if [[ -n "$FINGERPRINT" ]]; then
  login_resp=$(sage_rpc "login" "{\"fingerprint\": $FINGERPRINT}")
  if [[ "$login_resp" == "{}" ]] || echo "$login_resp" | jq -e '.' > /dev/null 2>&1; then
    pass "Login successful: $FINGERPRINT"
  else
    fail "Login failed: $login_resp"
  fi
else
  skip "Login test (no fingerprint)"
fi

echo ""
echo "--- Network Tests ---"

# Get network
network_resp=$(sage_rpc "get_network" '{}')
if echo "$network_resp" | jq -e '.network' > /dev/null 2>&1; then
  net_name=$(echo "$network_resp" | jq -r '.network.name')
  net_kind=$(echo "$network_resp" | jq -r '.kind')
  pass "Network: $net_name ($net_kind)"
else
  fail "Get network failed"
fi

# Get peers
peers_resp=$(sage_rpc "get_peers" '{}')
if echo "$peers_resp" | jq -e '.peers' > /dev/null 2>&1; then
  peer_count=$(echo "$peers_resp" | jq '.peers | length')
  pass "Connected to $peer_count peer(s)"
else
  fail "Get peers failed"
fi

echo ""
echo "--- Data Query Tests ---"

# Get coins (limited)
coins_resp=$(sage_rpc "get_coins" '{"offset": 0, "limit": 5, "filter_mode": "selectable"}')
if echo "$coins_resp" | jq -e '.coins' > /dev/null 2>&1; then
  coin_count=$(echo "$coins_resp" | jq '.coins | length')
  total_coins=$(echo "$coins_resp" | jq -r '.total')
  pass "Coins: showing $coin_count of $total_coins"
else
  fail "Get coins failed"
fi

# Get CATs
cats_resp=$(sage_rpc "get_cats" '{}')
if echo "$cats_resp" | jq -e '.cats' > /dev/null 2>&1; then
  cat_count=$(echo "$cats_resp" | jq '.cats | length')
  pass "CAT tokens: $cat_count"
else
  fail "Get CATs failed"
fi

# Get NFTs (limited)
nfts_resp=$(sage_rpc "get_nfts" '{"offset": 0, "limit": 5, "sort_mode": "recent", "include_hidden": false}')
if echo "$nfts_resp" | jq -e '.nfts' > /dev/null 2>&1; then
  nft_count=$(echo "$nfts_resp" | jq '.nfts | length')
  total_nfts=$(echo "$nfts_resp" | jq -r '.total')
  pass "NFTs: showing $nft_count of $total_nfts"
else
  fail "Get NFTs failed"
fi

# Get DIDs
dids_resp=$(sage_rpc "get_dids" '{}')
if echo "$dids_resp" | jq -e '.dids' > /dev/null 2>&1; then
  did_count=$(echo "$dids_resp" | jq '.dids | length')
  pass "DIDs: $did_count"
else
  fail "Get DIDs failed"
fi

# Get offers
offers_resp=$(sage_rpc "get_offers" '{}')
if echo "$offers_resp" | jq -e '.offers' > /dev/null 2>&1; then
  offer_count=$(echo "$offers_resp" | jq '.offers | length')
  pass "Offers: $offer_count"
else
  fail "Get offers failed"
fi

# Get transactions (limited)
txns_resp=$(sage_rpc "get_transactions" '{"offset": 0, "limit": 5, "ascending": false}')
if echo "$txns_resp" | jq -e '.transactions' > /dev/null 2>&1; then
  txn_count=$(echo "$txns_resp" | jq '.transactions | length')
  total_txns=$(echo "$txns_resp" | jq -r '.total')
  pass "Transactions: showing $txn_count of $total_txns"
else
  fail "Get transactions failed"
fi

# Pending transactions
pending_resp=$(sage_rpc "get_pending_transactions" '{}')
if echo "$pending_resp" | jq -e '.transactions' > /dev/null 2>&1; then
  pending_count=$(echo "$pending_resp" | jq '.transactions | length')
  pass "Pending transactions: $pending_count"
else
  fail "Get pending transactions failed"
fi

echo ""
echo "--- Address Tests ---"

# Check a known good address format
check_resp=$(sage_rpc "check_address" '{"address": "xch1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqm6ksh7qddh"}')
if echo "$check_resp" | jq -e '.valid' > /dev/null 2>&1; then
  valid=$(echo "$check_resp" | jq -r '.valid')
  pass "Address validation works (valid=$valid)"
else
  fail "Address check failed"
fi

# Get derivations
deriv_resp=$(sage_rpc "get_derivations" '{"hardened": false, "offset": 0, "limit": 3}')
if echo "$deriv_resp" | jq -e '.derivations' > /dev/null 2>&1; then
  deriv_count=$(echo "$deriv_resp" | jq '.derivations | length')
  pass "Derivations: $deriv_count addresses"
else
  fail "Get derivations failed"
fi

# Logout
if [[ -n "$FINGERPRINT" ]]; then
  logout_resp=$(sage_rpc "logout" '{}')
  pass "Logout completed"
fi

# Summary
echo ""
echo "=========================================="
echo "  Results"
echo "=========================================="
echo -e "${GREEN}Passed: $PASSED${NC}"
echo -e "${RED}Failed: $FAILED${NC}"
echo -e "${YELLOW}Skipped: $SKIPPED${NC}"
echo ""

if [[ $FAILED -gt 0 ]]; then
  echo -e "${RED}Some tests failed!${NC}"
  exit 1
else
  echo -e "${GREEN}All tests passed!${NC}"
fi


path: scripts/test-rpc.sh
content: #!/usr/bin/env bash
# test-rpc.sh — Tests for sage-rpc.sh
# Run: ./test-rpc.sh [--live]
#
# By default runs dry/mock tests only.
# Use --live to test against a running Sage instance.

set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
LIVE_MODE=false

for arg in "$@"; do
  case "$arg" in
    --live) LIVE_MODE=true ;;
  esac
done

# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
NC='\033[0m'

PASSED=0
FAILED=0
SKIPPED=0

pass() {
  echo -e "${GREEN}✓${NC} $1"
  PASSED=$((PASSED + 1))
}

fail() {
  echo -e "${RED}✗${NC} $1"
  FAILED=$((FAILED + 1))
}

skip() {
  echo -e "${YELLOW}○${NC} $1 (skipped)"
  SKIPPED=$((SKIPPED + 1))
}

echo "=== Sage RPC Tests ==="
echo "Mode: $([ "$LIVE_MODE" = true ] && echo "LIVE" || echo "DRY")"
echo ""

# Test 1: Script sources without error
echo "--- Script Loading ---"
if source "$SCRIPT_DIR/sage-rpc.sh" 2>/dev/null; then
  pass "sage-rpc.sh sources without error"
else
  fail "sage-rpc.sh failed to source"
  exit 1
fi

# Test 2: load_config function exists
if declare -f load_config > /dev/null; then
  pass "load_config function exists"
else
  fail "load_config function missing"
fi

# Test 3: sage_rpc function exists
if declare -f sage_rpc > /dev/null; then
  pass "sage_rpc function exists"
else
  fail "sage_rpc function missing"
fi

# Test 4: sage_test_connection function exists
if declare -f sage_test_connection > /dev/null; then
  pass "sage_test_connection function exists"
else
  fail "sage_test_connection function missing"
fi

# Test 5: Config resolution
echo ""
echo "--- Config Resolution ---"

# Create temp config
TEST_DIR=$(mktemp -d)
export SAGE_CONFIG_DIR="$TEST_DIR"
mkdir -p "$TEST_DIR"
cat > "$TEST_DIR/config.json" << 'EOF'
{
  "platform": "linux",
  "rpc_url": "https://127.0.0.1:9257",
  "cert_path": "/tmp/test.crt",
  "key_path": "/tmp/test.key",
  "fingerprint": null,
  "auto_login": false
}
EOF

# Force reload
unset SAGE_RPC_URL SAGE_CERT_PATH SAGE_KEY_PATH 2>/dev/null || true
load_config

if [[ "$SAGE_RPC_URL" == "https://127.0.0.1:9257" ]]; then
  pass "RPC URL loaded from config"
else
  fail "RPC URL not loaded: $SAGE_RPC_URL"
fi

if [[ "$SAGE_CERT_PATH" == "/tmp/test.crt" ]]; then
  pass "Cert path loaded from config"
else
  fail "Cert path not loaded: $SAGE_CERT_PATH"
fi

rm -rf "$TEST_DIR"

# Live tests
echo ""
echo "--- Live Connection Tests ---"

if [[ "$LIVE_MODE" != true ]]; then
  skip "Connection test (use --live)"
  skip "Version check (use --live)"
  skip "Sync status (use --live)"
  skip "Keys list (use --live)"
else
  # Reset to real config
  unset SAGE_CONFIG_DIR SAGE_RPC_URL SAGE_CERT_PATH SAGE_KEY_PATH
  load_config
  
  # Test connection
  echo "Testing connection to $SAGE_RPC_URL..."
  
  if [[ ! -f "$SAGE_CERT_PATH" ]]; then
    fail "Certificate not found at $SAGE_CERT_PATH"
  elif [[ ! -f "$SAGE_KEY_PATH" ]]; then
    fail "Key not found at $SAGE_KEY_PATH"
  else
    pass "Certificates found"
    
    # Version check
    version_resp=$(sage_rpc "get_version" '{}' 2>&1) || true
    if echo "$version_resp" | jq -e '.version' > /dev/null 2>&1; then
      version=$(echo "$version_resp" | jq -r '.version')
      pass "Version check: Sage $version"
    else
      fail "Version check failed: $version_resp"
    fi
    
    # Sync status
    sync_resp=$(sage_rpc "get_sync_status" '{}' 2>&1) || true
    if echo "$sync_resp" | jq -e '.balance' > /dev/null 2>&1; then
      pass "Sync status returns valid response"
    else
      fail "Sync status failed: $sync_resp"
    fi
    
    # Keys list (may require login)
    keys_resp=$(sage_rpc "get_keys" '{}' 2>&1) || true
    if echo "$keys_resp" | jq -e '.keys' > /dev/null 2>&1; then
      key_count=$(echo "$keys_resp" | jq '.keys | length')
      pass "Keys list: $key_count wallet(s) found"
    else
      # May fail if not logged in - that's ok
      skip "Keys list (may need login)"
    fi
  fi
fi

# Summary
echo ""
echo "=== Results ==="
echo -e "${GREEN}Passed: $PASSED${NC}"
echo -e "${RED}Failed: $FAILED${NC}"
echo -e "${YELLOW}Skipped: $SKIPPED${NC}"

if [[ $FAILED -gt 0 ]]; then
  exit 1
fi


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