Skill: lokoweb3/x1-vault-memory (version 0.1.10)

--- name: x1-vault-memory description: Backup and restore OpenClaw agent memory to IPFS with AES-256-GCM encryption and X1 blockchain CID anchoring version: 0.2.0 author: Lokoweb3 homepage: https://github.com/Lokoweb3/x1-vault-memory metadata: clawdbot: emoji: "🦞" requires: env: ["PINATA_JWT"] primaryEnv: "PINATA_JWT" configPaths: ["x1_vault_cli/wallet.json"] files: ["src/*"] tags: - backup - memory - ipfs - encryption - x1 - blockchain - restore - vault --- # X1 Vault Memory Encrypted, decentralized memory backup for OpenClaw agents — powered by IPFS and X1 blockchain. ## Required Configuration | Variable | Required | Default | Description | |----------|----------|---------|-------------| | `PINATA_JWT` | ✅ Yes | — | Your Pinata API token for IPFS uploads. Get it at https://app.pinata.cloud | | `X1_RPC_URL` | ❌ No | `https://rpc.mainnet.x1.xyz` | The X1 RPC endpoint. Change only if using testnet or custom endpoint | | `x1_vault_cli/wallet.json` | ✅ Yes | — | Your X1 wallet keypair file. Used for encryption and blockchain anchoring. | > 🔴 **SECURITY WARNING:** Use a dedicated wallet with minimal XNT. Never use your primary wallet. --- ## What It Does Backs up your agent's brain (identity, personality, memories) with AES-256-GCM military-grade encryption, stores it on IPFS, and anchors the CID on the X1 blockchain. Only your wallet keypair can decrypt. Pipeline: Agent Files > tar.gz > AES-256-GCM Encrypt > IPFS Upload > X1 On-Chain Anchor ### Why This Exists Servers die. Containers get wiped. One bad rm -rf and your agent's identity is gone. X1 Vault Memory makes your agent's brain indestructible — encrypted, decentralized, and recoverable from anywhere. ### Key Features - AES-256-GCM encryption — uses your wallet keypair as the key. Only you can decrypt. - IPFS storage — your data lives on a decentralized network, not a single server. - X1 blockchain anchoring — every backup CID is recorded on-chain for permanent, verifiable proof. - Self-healing restore — one command to download, decrypt, and restore all agent files. - Negligible cost — about 0.03 USD per year for daily backups. ### New in v1.1.2 - ✅ Security fixes: removed "curl | sh" Solana CLI install suggestion - ✅ Declared environment variables clearly in skill metadata - ✅ Added opt-in note for heartbeat auto-restore - Updated package.json with required env vars in config section ### New in v1.1.0 - SHA-256 integrity verification on backup and restore - Selective restore with --only flag to restore specific files or directories - List command for viewing all backups with versioned rollback support --- ## Requirements - **Node.js v18+** — run all scripts without external dependencies - **Pinata Account** — free at https://app.pinata.cloud (500 files, 1GB included) - **Solana Wallet** — keypair JSON file (free to create via @solana/web3.js) - **XNT Tokens** — about 0.002 XNT per backup for on-chain fees ✅ **No Solana CLI required** — we use `@solana/web3.js` directly. > 🔴 **SECURITY WARNING:** Use a dedicated wallet with minimal XNT. Never use your primary wallet. --- ## How to Get XNT Tokens XNT is the native gas token of the X1 blockchain. You need a small amount for on-chain transaction fees. 1. Bridge from Solana (easiest) — https://app.bridge.x1.xyz — wrap SOL/USDC to X1, then swap for XNT on XDEX 2. Buy directly on XDEX — https://app.xdex.xyz/swap — native DEX on X1, connect your wallet and swap 3. OTC — https://otc.xonedex.xyz — peer-to-peer for larger amounts 4. Honey Badger Bot — https://t.me/HoneyBadgerCoreBot?start=ref_HEBCU2E3 — Telegram trading bot for instant swaps You will also need the X1 Wallet Chrome extension: https://chromewebstore.google.com/detail/x1-wallet/kcfmcpdmlchhbikbogddmgopmjbflnae Start with bridge + XDEX if you are coming from Solana. That is the smoothest path. Each backup costs approximately 0.002 XNT, so even a small amount goes a long way. --- ## How to Set Up Pinata and Get Your JWT Token Pinata is the IPFS pinning service that stores your encrypted backups. The free tier is more than enough. 1. Go to https://app.pinata.cloud and click Sign Up 2. Create an account with your email or sign in with GitHub/Google 3. After login, click your profile icon in the top right corner 4. Select API Keys from the dropdown menu 5. Click the New Key button 6. Enable only the pinFileToIPFS permission (Admin access is NOT required) 7. Give the key a name like "x1-vault-memory" 8. Click Create Key 9. You will see three values: API Key, API Secret, and JWT 10. Copy the JWT token — this is your PINATA_JWT value 11. Save it somewhere safe — you will not be able to see the JWT again after closing this page The JWT token does not expire unless you manually revoke it in the Pinata dashboard. Free tier includes 500 files and 1GB storage. Each encrypted backup is around 10-50KB, so you can store thousands of backups without paying anything. --- ## Setup > 🔴 **SECURITY WARNING:** Use a dedicated wallet with minimal XNT. Never use your primary wallet. ### 1. Install Node dependencies ```bash cd x1-vault-memory && npm install ``` ### 2. Configure environment variables **Option A: Using a .env file (recommended for Docker and production)** Create a `.env` file in your project or workspace root: ``` PINATA_JWT=your_pinata_jwt_token X1_RPC_URL=https://rpc.mainnet.x1.xyz ``` No quotes, no export keyword. Docker Compose and most Node.js apps read this format automatically. **Option B: Using shell environment (for manual or one-time use)** ```bash export PINATA_JWT="your_pinata_jwt_token" export X1_RPC_URL="https://rpc.mainnet.x1.xyz" ``` Note: These values only persist for the current terminal session. **Option C: Docker Compose environment block** If running inside Docker, add to your docker-compose.yml environment section: ```yaml environment: PINATA_JWT: ${PINATA_JWT} X1_RPC_URL: https://rpc.mainnet.x1.xyz ``` Then set `PINATA_JWT` in your Docker `.env` file as shown in Option A. ### 3. Create a wallet keypair (Node.js only, no CLI) The wallet can be created programmatically using `@solana/web3.js`. Here's a quick script: ```bash node -e " const { Keypair } = require('@solana/web3.js'); const fs = require('fs'); const kp = Keypair.generate(); fs.writeFileSync('x1_vault_cli/wallet.json', JSON.stringify([...kp.secretKey])); console.log('Wallet created:', kp.publicKey.toBase58()); console.log('Save the secretKey JSON array to x1_vault_cli/wallet.json'); " ``` Or use the X1 Wallet Chrome extension to generate a keypair and export the secretKey. Keep `wallet.json` safe. This is your encryption key AND your blockchain wallet. Never commit it to GitHub. ### 4. Fund the wallet Get your wallet address from `x1_vault_cli/wallet.json` (the public key derived from the secret key), then send XNT tokens to that address. ### 5. X1 RPC URL Set `X1_RPC_URL` to your preferred endpoint. Default: `https://rpc.mainnet.x1.xyz` --- ## Usage ### Backup ```bash node src/backup.js ``` Encrypts and uploads `IDENTITY.md`, `SOUL.md`, `USER.md`, `TOOLS.md`, and `memory/` directory. Records CID on X1 blockchain and logs to `vault-log.json`. ### Restore ```bash node src/restore.js <CID> ``` Downloads from IPFS, decrypts with your wallet key, and restores all agent files. ### Selective Restore ```bash node src/restore.js <CID> --only memory/ ``` Restore only specific files or directories from a backup. ### List All Backups ```bash node src/list.js ``` View all stored backups with timestamps, CIDs, and checksums for versioned rollback. ### Heartbeat Check ```bash node src/heartbeat.js ``` Monitors agent file integrity. If critical files are missing or corrupted, automatically triggers a restore from the latest backup. > ⚠️ **Opt-in Only** — Heartbeat auto-restore must be explicitly scheduled via cron. It is NOT automatic. > > **Add to crontab:** > ```bash > 0 */6 * * * cd /path/to/workspace && node x1-vault-memory/src/heartbeat.js >> /var/log/vault-heartbeat.log 2>&1 > ``` > > **Note:** Heartbeat auto-restore is disabled by default and must be explicitly enabled via cron. ### Dry Run ```bash node src/backup.js --dry-run ``` Shows which files would be backed up without uploading or spending tokens. --- ## Error Handling - Pinata is down — backup fails with connection error. Retry later, local files are untouched. - X1 RPC fails — IPFS upload succeeds but on-chain anchor fails. CID is still logged locally in `vault-log.json`. Re-anchor when RPC recovers. - Invalid wallet — encryption fails before upload. Check `wallet.json` path and format (must be JSON array of bytes). - Insufficient XNT — on-chain transaction rejected. Fund wallet with more XNT tokens. - CID not found on restore — check Pinata dashboard, re-pin if needed. - Checksum mismatch — SHA-256 verification failed. Backup may be corrupted. Try restoring from a previous version using `list.js`. --- ## Where Data Is Stored - **IPFS** — encrypted blob on Pinata IPFS network - **X1 Blockchain** — CID recorded as on-chain transaction (permanent, verifiable) - **vault-log.json** — local log of all backup CIDs with timestamps and checksums - **Only your wallet keypair can decrypt the data** --- ## Security - AES-256-GCM authenticated encryption derived from your wallet secret key - SHA-256 integrity checksums on every backup and restore - Only your keypair can decrypt — even if someone finds the CID, data stays private - Stored on IPFS, not a single server - CID anchored on X1 blockchain for tamper-proof records - Never share your `wallet.json` or `PINATA_JWT` > 🔴 **SECURITY WARNING:** Use a dedicated wallet with minimal XNT. Never use your primary wallet. --- ## Automation Weekly backup via cron: ```bash 0 2 * * 0 cd /path/to/workspace && node x1-vault-memory/src/backup.js >> /var/log/vault-backup.log 2>&1 ``` Heartbeat check every 6 hours (opt-in): ```bash 0 */6 * * * cd /path/to/workspace && node x1-vault-memory/src/heartbeat.js >> /var/log/vault-heartbeat.log 2>&1 ``` --- ## Files Backed Up - `IDENTITY.md` — agent name, persona, vibe - `SOUL.md` — personality, instructions, expertise - `USER.md` — user profile and preferences - `TOOLS.md` — environment-specific notes - `memory/*.md` — daily memory logs --- ## Tech Stack | Component | Technology | |-----------|-----------| | Encryption | AES-256-GCM (authenticated encryption) | | Integrity | SHA-256 checksums | | IPFS Storage | Pinata API (JWT auth) | | Blockchain | X1 Mainnet (SVM-compatible L1) | | Wallet | @solana/web3.js (Node.js, no CLI needed) | | Runtime | Node.js v18+ | --- ## Links - GitHub: https://github.com/Lokoweb3/x1-vault-memory - X1 Explorer: https://explorer.mainnet.x1.xyz - X1 Bridge: https://app.bridge.x1.xyz - XDEX: https://app.xdex.xyz/swap - X1 Wallet: https://chromewebstore.google.com/detail/x1-wallet/kcfmcpdmlchhbikbogddmgopmjbflnae - Honey Badger Bot: https://t.me/HoneyBadgerCoreBot?start=ref_HEBCU2E3 - Pinata: https://app.pinata.cloud Built by [Lokoweb3](https://github.com/Lokoweb3) --- [GitHub Repository](https://github.com/Lokoweb3/x1-vault-memory) | [OpenClaw Docs](https://docs.openclaw.ai)

path: README.md
content: # 🦞 X1 Vault Memory

**Decentralized, encrypted memory backup for OpenClaw AI agents — powered by X1 blockchain and IPFS.**

---

## Required Configuration

| Variable | Required | Details |
|----------|----------|---------|
| `PINATA_JWT` | ✅ Yes | Set in container `.env` - Your Pinata API token for IPFS uploads |
| `x1_vault_cli/wallet.json` | ✅ Yes | Dedicated wallet only, NOT your main wallet |
| `X1_RPC_URL` | ❌ No | Defaults to `https://rpc.mainnet.x1.xyz` |

> 🔴 **SECURITY WARNING:** Use a dedicated wallet with minimal XNT. Never use your primary wallet.

---

## What Is This?

An OpenClaw skill that backs up your AI agent's identity and memory files to IPFS with encrypted storage and on-chain CID references on the X1 blockchain.

Your agent's brain — personality, knowledge, memories — encrypted with your wallet key, stored on decentralized infrastructure, and recoverable from anywhere.

**No more losing your agent when a server dies.**

---

## How It Works
```
Agent Files → tar.gz → Encrypt (AES-256-GCM) → Upload (IPFS/Pinata) → Record CID (X1 Blockchain)
```

1. **Bundle** — Compresses agent files (IDENTITY.md, SOUL.md, USER.md, TOOLS.md, memory/) into a tar.gz
2. **Encrypt** — Encrypts the archive with your wallet's secret key using AES-256-GCM
3. **Upload** — Pushes the encrypted blob to IPFS via Pinata's API
4. **Record** — Stores the IPFS CID on the X1 blockchain via Memo Program
5. **Track** — Logs the CID and timestamp to vault-log.json

Only your wallet keypair can decrypt. Even if someone finds the CID, your data stays private.

---

## Requirements

| Requirement | Details |
|-------------|---------|
| **Node.js** | v18+ |
| **Pinata Account** | Free at [app.pinata.cloud](https://app.pinata.cloud) |
| **XNT Tokens** | ~0.002 XNT per backup for on-chain fees |
| **OpenClaw** | Running instance with workspace access |

✅ **No Solana CLI required** — we use `@solana/web3.js` directly.

---

## Installation

### For OpenClaw Agents

1. Clone into your OpenClaw workspace:
```bash
cd /home/node/.openclaw/workspace
git clone https://github.com/Lokoweb3/x1-vault-memory.git
cd x1-vault-memory
npm install
```

2. Add environment variables to your `.env` file:
```bash
echo "PINATA_JWT=your_token_here" >> ~/openclaw/.env
echo "X1_RPC_URL=https://rpc.mainnet.x1.xyz" >> ~/openclaw/.env
```

3. Add to `docker-compose.yml` environment:
```yaml
environment:
  PINATA_JWT: ${PINATA_JWT}
  X1_RPC_URL: ${X1_RPC_URL}
```

4. Restart the container:
```bash
cd ~/openclaw
docker compose down && docker compose up -d
```

5. Tell your agent about the skill:
> "You have a new skill called x1-vault-memory. You can backup your memory with node x1-vault-memory/src/backup.js and restore with node x1-vault-memory/src/restore.js CID. Save this to your memory."

---

## Setup

### 1. Create a Wallet Keypair

**Note:** Use Node.js only. No Solana CLI needed.

```bash
node -e "
const { Keypair } = require('@solana/web3.js');
const fs = require('fs');
const kp = Keypair.generate();
fs.writeFileSync('x1_vault_cli/wallet.json', JSON.stringify([...kp.secretKey]));
console.log('Wallet created:', kp.publicKey.toBase58());
"
```

**Or use X1 Wallet Chrome extension** to generate a keypair and export the secretKey JSON.

Keep `wallet.json` safe. This is your encryption key. Never commit it to GitHub.

> 🔴 **SECURITY WARNING:** Use a dedicated wallet with minimal XNT. Never use your primary wallet.

### 2. Fund Your Wallet

Send XNT tokens to your wallet address. You need ~0.002 XNT per backup transaction.

> 🔴 **SECURITY WARNING:** Use a dedicated wallet with minimal XNT. Never use your primary wallet.

---

## Usage

### Backup
```bash
node x1-vault-memory/src/backup.js
```

Output:
```
Archive checksum: a1b2c3d4e5f6...
Backup uploaded, CID: QmExampleCID123456789abcdefghijklmnopqrstuv
X1 Transaction: 5h1XWikXsqVoDEnK54DbG5Jurxnwqf5puVD5FL28JByCBhatCk7X2mnMCyipLvYmsNDjdrvvmDtQZpPRZuwWqccV
Explorer: https://explorer.mainnet.x1.xyz/tx/...
Logged backup CID to vault-log.json
```

**Features:**
- ✅ SHA-256 checksum generated before encryption
- ✅ Checksum stored in encrypted payload for integrity verification
- ✅ CID anchored to X1 blockchain with transaction hash

### Restore
```bash
# Full restore
node x1-vault-memory/src/restore.js <CID>

# Selective restore (only memory folder)
node x1-vault-memory/src/restore.js <CID> --only memory/
```

**Features:**
- ✅ Automatic integrity verification (checksum must match)
- ✅ Aborts with error if archive is corrupted
- ✅ Selective restore — restore specific paths without overwriting identity files

### List Backups
```bash
node x1-vault-memory/src/list.js
```

Shows numbered list of all backups with timestamps and anchor status.

### Heartbeat Check
```bash
node x1-vault-memory/src/heartbeat.js
```

Self-healing check:
- Verifies SOUL.md and memory/ exist and aren't empty
- Auto-restores from latest backup if issues detected

> ⚠️ **Opt-in Only** — Heartbeat auto-restore must be explicitly scheduled via cron. It is NOT automatic.
> 
> **Add to crontab:**
> ```bash
> 0 */6 * * * cd /path/to/workspace && node x1-vault-memory/src/heartbeat.js >> /var/log/vault-heartbeat.log 2>&1
> ```
> 
> **Note:** Heartbeat auto-restore is disabled by default and must be explicitly enabled via cron.

### Shell Wrappers
```bash
bash x1-vault-memory/scripts/backup.sh
bash x1-vault-memory/scripts/restore.sh <CID>
```

---

## CID Tracking

Every backup is logged to vault-log.json:
```json
[
  {
    "timestamp": "2026-02-16T09:48:38.207Z",
    "cid": [REDACTED_SECRET]
  }
]
```

CIDs are also recorded on-chain. Check your wallet's transaction history on the [X1 Explorer](https://explorer.mainnet.x1.xyz).

---

## Automation

### Weekly Backup (Sundays at 2am)
```bash
0 2 * * 0 cd /path/to/workspace && node x1-vault-memory/src/backup.js >> /var/log/vault-backup.log 2>&1
```

### Heartbeat Check (Opt-in)
```bash
# Must be explicitly added to crontab - NOT automatic
0 */6 * * * cd /path/to/workspace && node x1-vault-memory/src/heartbeat.js >> /var/log/vault-heartbeat.log 2>&1
```

---

## Files Backed Up

| File | Purpose |
|------|---------|
| IDENTITY.md | Agent name, persona, vibe |
| SOUL.md | Personality, instructions, expertise |
| USER.md | User profile and preferences |
| TOOLS.md | Environment-specific notes |
| memory/*.md | Daily memory logs |

---

## Security

- 🔐 Encrypted with AES-256-GCM using your wallet's secret key
- 🔑 Only your keypair can decrypt
- 📡 Stored on IPFS, not a single server
- ⛓️ CID recorded on X1 blockchain via Memo Program for permanence
- 🚫 Never share your wallet.json or PINATA_JWT

> 🔴 **SECURITY WARNING:** Use a dedicated wallet with minimal XNT. Never use your primary wallet.

---

## Tech Stack

| Component | Technology |
|-----------|-----------|
| Encryption | AES-256-GCM |
| IPFS Storage | Pinata API (JWT auth) |
| Blockchain | X1 (SVM-compatible L1) |
| Runtime | Node.js |
| Archiving | tar (npm) |
| Wallet | @solana/web3.js (Node.js, no CLI needed) |

---

## About X1

[X1](https://x1.xyz) is a high-performance, SVM-compatible Layer-1 blockchain.

- **Docs:** [docs.x1.xyz](https://docs.x1.xyz)
- **Explorer:** [explorer.mainnet.x1.xyz](https://explorer.mainnet.x1.xyz)
- **Testnet RPC:** https://rpc.testnet.x1.xyz
- **Mainnet RPC:** https://rpc.mainnet.x1.xyz

---

## License

MIT

---

**Built by [Lokoweb3](https://github.com/Lokoweb3) with Loko_AI 🦞**

---

[**GitHub Repository**](https://github.com/Lokoweb3/x1-vault-memory) | [**Documentation**](https://docs.openclaw.ai)


path: package-lock.json
content: {
  "name": "vault-backup",
  "version": "1.0.0",
  "lockfileVersion": 3,
  "requires": true,
  "packages": {
    "": {
      "name": "vault-backup",
      "version": "1.0.0",
      "license": "ISC",
      "dependencies": {
        "@solana/spl-memo": "^0.2.5",
        "@solana/web3.js": "^1.95.0",
        "bs58": "^6.0.0",
        "form-data": "^4.0.0",
        "tar": "^7.5.9"
      }
    },
    "node_modules/@babel/runtime": {
      "version": "7.28.6",
      "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">=6.9.0"
      }
    },
    "node_modules/@isaacs/fs-minipass": {
      "version": "4.0.1",
      "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "ISC",
      "dependencies": {
        "minipass": "^7.0.4"
      },
      "engines": {
        "node": ">=18.0.0"
      }
    },
    "node_modules/@noble/curves": {
      "version": "1.9.7",
      "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.7.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "@noble/hashes": "1.8.0"
      },
      "engines": {
        "node": "^14.21.3 || >=16"
      },
      "funding": {
        "url": "https://paulmillr.com/funding/"
      }
    },
    "node_modules/@noble/hashes": {
      "version": "1.8.0",
      "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": "^14.21.3 || >=16"
      },
      "funding": {
        "url": "https://paulmillr.com/funding/"
      }
    },
    "node_modules/@solana/buffer-layout": {
      "version": "4.0.1",
      "resolved": "https://registry.npmjs.org/@solana/buffer-layout/-/buffer-layout-4.0.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "buffer": "~6.0.3"
      },
      "engines": {
        "node": ">=5.10"
      }
    },
    "node_modules/@solana/codecs-core": {
      "version": "2.3.0",
      "resolved": "https://registry.npmjs.org/@solana/codecs-core/-/codecs-core-2.3.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "@solana/errors": "2.3.0"
      },
      "engines": {
        "node": ">=20.18.0"
      },
      "peerDependencies": {
        "typescript": ">=5.3.3"
      }
    },
    "node_modules/@solana/codecs-numbers": {
      "version": "2.3.0",
      "resolved": "https://registry.npmjs.org/@solana/codecs-numbers/-/codecs-numbers-2.3.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "@solana/codecs-core": "2.3.0",
        "@solana/errors": "2.3.0"
      },
      "engines": {
        "node": ">=20.18.0"
      },
      "peerDependencies": {
        "typescript": ">=5.3.3"
      }
    },
    "node_modules/@solana/errors": {
      "version": "2.3.0",
      "resolved": "https://registry.npmjs.org/@solana/errors/-/errors-2.3.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "chalk": "^5.4.1",
        "commander": "^14.0.0"
      },
      "bin": {
        "errors": "bin/cli.mjs"
      },
      "engines": {
        "node": ">=20.18.0"
      },
      "peerDependencies": {
        "typescript": ">=5.3.3"
      }
    },
    "node_modules/@solana/spl-memo": {
      "version": "0.2.5",
      "resolved": "https://registry.npmjs.org/@solana/spl-memo/-/spl-memo-0.2.5.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "Apache-2.0",
      "dependencies": {
        "buffer": "^6.0.3"
      },
      "engines": {
        "node": ">=16"
      },
      "peerDependencies": {
        "@solana/web3.js": "^1.91.6"
      }
    },
    "node_modules/@solana/web3.js": {
      "version": "1.98.4",
      "resolved": "https://registry.npmjs.org/@solana/web3.js/-/web3.js-1.98.4.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "@babel/runtime": "^7.25.0",
        "@noble/curves": "^1.4.2",
        "@noble/hashes": "^1.4.0",
        "@solana/buffer-layout": "^4.0.1",
        "@solana/codecs-numbers": "^2.1.0",
        "agentkeepalive": "^4.5.0",
        "bn.js": "^5.2.1",
        "borsh": "^0.7.0",
        "bs58": "^4.0.1",
        "buffer": "6.0.3",
        "fast-stable-stringify": "^1.0.0",
        "jayson": "^4.1.1",
        "node-fetch": "^2.7.0",
        "rpc-websockets": "^9.0.2",
        "superstruct": "^2.0.2"
      }
    },
    "node_modules/@solana/web3.js/node_modules/base-x": {
      "version": "3.0.11",
      "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.11.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "safe-buffer": "^5.0.1"
      }
    },
    "node_modules/@solana/web3.js/node_modules/bs58": {
      "version": "4.0.1",
      "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "base-x": "^3.0.2"
      }
    },
    "node_modules/@swc/helpers": {
      "version": "0.5.18",
      "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.18.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "Apache-2.0",
      "dependencies": {
        "tslib": "^2.8.0"
      }
    },
    "node_modules/@types/connect": {
      "version": "3.4.38",
      "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "@types/node": "*"
      }
    },
    "node_modules/@types/node": {
      "version": "12.20.55",
      "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT"
    },
    "node_modules/@types/uuid": {
      "version": "8.3.4",
      "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT"
    },
    "node_modules/@types/ws": {
      "version": "7.4.7",
      "resolved": "https://registry.npmjs.org/@types/ws/-/ws-7.4.7.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "@types/node": "*"
      }
    },
    "node_modules/agentkeepalive": {
      "version": "4.6.0",
      "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "humanize-ms": "^1.2.1"
      },
      "engines": {
        "node": ">= 8.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/base-x": {
      "version": "5.0.1",
      "resolved": "https://registry.npmjs.org/base-x/-/base-x-5.0.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT"
    },
    "node_modules/base64-js": {
      "version": "1.5.1",
      "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
      "integrity": [REDACTED_SECRET],
      "funding": [
        {
          "type": "github",
          "url": "https://github.com/sponsors/feross"
        },
        {
          "type": "patreon",
          "url": "https://www.patreon.com/feross"
        },
        {
          "type": "consulting",
          "url": "https://feross.org/support"
        }
      ],
      "license": "MIT"
    },
    "node_modules/bn.js": {
      "version": "5.2.2",
      "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT"
    },
    "node_modules/borsh": {
      "version": "0.7.0",
      "resolved": "https://registry.npmjs.org/borsh/-/borsh-0.7.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "Apache-2.0",
      "dependencies": {
        "bn.js": "^5.2.0",
        "bs58": "^4.0.0",
        "text-encoding-utf-8": "^1.0.2"
      }
    },
    "node_modules/borsh/node_modules/base-x": {
      "version": "3.0.11",
      "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.11.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "safe-buffer": "^5.0.1"
      }
    },
    "node_modules/borsh/node_modules/bs58": {
      "version": "4.0.1",
      "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "base-x": "^3.0.2"
      }
    },
    "node_modules/bs58": {
      "version": "6.0.0",
      "resolved": "https://registry.npmjs.org/bs58/-/bs58-6.0.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "base-x": "^5.0.0"
      }
    },
    "node_modules/buffer": {
      "version": "6.0.3",
      "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
      "integrity": [REDACTED_SECRET],
      "funding": [
        {
          "type": "github",
          "url": "https://github.com/sponsors/feross"
        },
        {
          "type": "patreon",
          "url": "https://www.patreon.com/feross"
        },
        {
          "type": "consulting",
          "url": "https://feross.org/support"
        }
      ],
      "license": "MIT",
      "dependencies": {
        "base64-js": "^1.3.1",
        "ieee754": "^1.2.1"
      }
    },
    "node_modules/bufferutil": {
      "version": "4.1.0",
      "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.1.0.tgz",
      "integrity": [REDACTED_SECRET],
      "hasInstallScript": true,
      "license": "MIT",
      "optional": true,
      "dependencies": {
        "node-gyp-build": "^4.3.0"
      },
      "engines": {
        "node": ">=6.14.2"
      }
    },
    "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/chalk": {
      "version": "5.6.2",
      "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": "^12.17.0 || ^14.13 || >=16.0.0"
      },
      "funding": {
        "url": "https://github.com/chalk/chalk?sponsor=1"
      }
    },
    "node_modules/chownr": {
      "version": "3.0.0",
      "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "BlueOak-1.0.0",
      "engines": {
        "node": ">=18"
      }
    },
    "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/commander": {
      "version": "14.0.3",
      "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">=20"
      }
    },
    "node_modules/delay": {
      "version": "5.0.0",
      "resolved": "https://registry.npmjs.org/delay/-/delay-5.0.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">=10"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "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/es6-promise": {
      "version": "4.2.8",
      "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT"
    },
    "node_modules/es6-promisify": {
      "version": "5.0.0",
      "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "es6-promise": "^4.0.3"
      }
    },
    "node_modules/eventemitter3": {
      "version": "5.0.4",
      "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT"
    },
    "node_modules/eyes": {
      "version": "0.1.8",
      "resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz",
      "integrity": [REDACTED_SECRET],
      "engines": {
        "node": "> 0.1.90"
      }
    },
    "node_modules/fast-stable-stringify": {
      "version": "1.0.0",
      "resolved": "https://registry.npmjs.org/fast-stable-stringify/-/fast-stable-stringify-1.0.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT"
    },
    "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/humanize-ms": {
      "version": "1.2.1",
      "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "ms": "^2.0.0"
      }
    },
    "node_modules/ieee754": {
      "version": "1.2.1",
      "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
      "integrity": [REDACTED_SECRET],
      "funding": [
        {
          "type": "github",
          "url": "https://github.com/sponsors/feross"
        },
        {
          "type": "patreon",
          "url": "https://www.patreon.com/feross"
        },
        {
          "type": "consulting",
          "url": "https://feross.org/support"
        }
      ],
      "license": "BSD-3-Clause"
    },
    "node_modules/isomorphic-ws": {
      "version": "4.0.1",
      "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "peerDependencies": {
        "ws": "*"
      }
    },
    "node_modules/jayson": {
      "version": "4.3.0",
      "resolved": "https://registry.npmjs.org/jayson/-/jayson-4.3.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "@types/connect": "^3.4.33",
        "@types/node": "^12.12.54",
        "@types/ws": "^7.4.4",
        "commander": "^2.20.3",
        "delay": "^5.0.0",
        "es6-promisify": "^5.0.0",
        "eyes": "^0.1.8",
        "isomorphic-ws": "^4.0.1",
        "json-stringify-safe": "^5.0.1",
        "stream-json": "^1.9.1",
        "uuid": "^8.3.2",
        "ws": "^7.5.10"
      },
      "bin": {
        "jayson": "bin/jayson.js"
      },
      "engines": {
        "node": ">=8"
      }
    },
    "node_modules/jayson/node_modules/commander": {
      "version": "2.20.3",
      "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT"
    },
    "node_modules/json-stringify-safe": {
      "version": "5.0.1",
      "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "ISC"
    },
    "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/minipass": {
      "version": "7.1.2",
      "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "ISC",
      "engines": {
        "node": ">=16 || 14 >=14.17"
      }
    },
    "node_modules/minizlib": {
      "version": "3.1.0",
      "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "minipass": "^7.1.2"
      },
      "engines": {
        "node": ">= 18"
      }
    },
    "node_modules/ms": {
      "version": "2.1.3",
      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT"
    },
    "node_modules/node-fetch": {
      "version": "2.7.0",
      "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "whatwg-url": "^5.0.0"
      },
      "engines": {
        "node": "4.x || >=6.0.0"
      },
      "peerDependencies": {
        "encoding": "^0.1.0"
      },
      "peerDependenciesMeta": {
        "encoding": {
          "optional": true
        }
      }
    },
    "node_modules/node-gyp-build": {
      "version": "4.8.4",
      "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "optional": true,
      "bin": {
        "node-gyp-build": "bin.js",
        "node-gyp-build-optional": "optional.js",
        "node-gyp-build-test": "build-test.js"
      }
    },
    "node_modules/rpc-websockets": {
      "version": "9.3.3",
      "resolved": "https://registry.npmjs.org/rpc-websockets/-/rpc-websockets-9.3.3.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "LGPL-3.0-only",
      "dependencies": {
        "@swc/helpers": "^0.5.11",
        "@types/uuid": "^8.3.4",
        "@types/ws": "^8.2.2",
        "buffer": "^6.0.3",
        "eventemitter3": "^5.0.1",
        "uuid": "^8.3.2",
        "ws": "^8.5.0"
      },
      "funding": {
        "type": "paypal",
        "url": "https://paypal.me/kozjak"
      },
      "optionalDependencies": {
        "bufferutil": "^4.0.1",
        "utf-8-validate": "^5.0.2"
      }
    },
    "node_modules/rpc-websockets/node_modules/@types/ws": {
      "version": "8.18.1",
      "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "@types/node": "*"
      }
    },
    "node_modules/rpc-websockets/node_modules/ws": {
      "version": "8.19.0",
      "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">=10.0.0"
      },
      "peerDependencies": {
        "bufferutil": "^4.0.1",
        "utf-8-validate": ">=5.0.2"
      },
      "peerDependenciesMeta": {
        "bufferutil": {
          "optional": true
        },
        "utf-8-validate": {
          "optional": true
        }
      }
    },
    "node_modules/safe-buffer": {
      "version": "5.2.1",
      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
      "integrity": [REDACTED_SECRET],
      "funding": [
        {
          "type": "github",
          "url": "https://github.com/sponsors/feross"
        },
        {
          "type": "patreon",
          "url": "https://www.patreon.com/feross"
        },
        {
          "type": "consulting",
          "url": "https://feross.org/support"
        }
      ],
      "license": "MIT"
    },
    "node_modules/stream-chain": {
      "version": "2.2.5",
      "resolved": "https://registry.npmjs.org/stream-chain/-/stream-chain-2.2.5.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "BSD-3-Clause"
    },
    "node_modules/stream-json": {
      "version": "1.9.1",
      "resolved": "https://registry.npmjs.org/stream-json/-/stream-json-1.9.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "BSD-3-Clause",
      "dependencies": {
        "stream-chain": "^2.2.5"
      }
    },
    "node_modules/superstruct": {
      "version": "2.0.2",
      "resolved": "https://registry.npmjs.org/superstruct/-/superstruct-2.0.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">=14.0.0"
      }
    },
    "node_modules/tar": {
      "version": "7.5.9",
      "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.9.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "BlueOak-1.0.0",
      "dependencies": {
        "@isaacs/fs-minipass": "^4.0.0",
        "chownr": "^3.0.0",
        "minipass": "^7.1.2",
        "minizlib": "^3.1.0",
        "yallist": "^5.0.0"
      },
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/text-encoding-utf-8": {
      "version": "1.0.2",
      "resolved": "https://registry.npmjs.org/text-encoding-utf-8/-/text-encoding-utf-8-1.0.2.tgz",
      "integrity": [REDACTED_SECRET]
    },
    "node_modules/tr46": {
      "version": "0.0.3",
      "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT"
    },
    "node_modules/tslib": {
      "version": "2.8.1",
      "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "0BSD"
    },
    "node_modules/typescript": {
      "version": "5.9.3",
      "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "Apache-2.0",
      "peer": true,
      "bin": {
        "tsc": "bin/tsc",
        "tsserver": "bin/tsserver"
      },
      "engines": {
        "node": ">=14.17"
      }
    },
    "node_modules/utf-8-validate": {
      "version": "5.0.10",
      "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz",
      "integrity": [REDACTED_SECRET],
      "hasInstallScript": true,
      "license": "MIT",
      "optional": true,
      "dependencies": {
        "node-gyp-build": "^4.3.0"
      },
      "engines": {
        "node": ">=6.14.2"
      }
    },
    "node_modules/uuid": {
      "version": "8.3.2",
      "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "bin": {
        "uuid": "dist/bin/uuid"
      }
    },
    "node_modules/webidl-conversions": {
      "version": "3.0.1",
      "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "BSD-2-Clause"
    },
    "node_modules/whatwg-url": {
      "version": "5.0.0",
      "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "tr46": "~0.0.3",
        "webidl-conversions": "^3.0.0"
      }
    },
    "node_modules/ws": {
      "version": "7.5.10",
      "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">=8.3.0"
      },
      "peerDependencies": {
        "bufferutil": "^4.0.1",
        "utf-8-validate": "^5.0.2"
      },
      "peerDependenciesMeta": {
        "bufferutil": {
          "optional": true
        },
        "utf-8-validate": {
          "optional": true
        }
      }
    },
    "node_modules/yallist": {
      "version": "5.0.0",
      "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "BlueOak-1.0.0",
      "engines": {
        "node": ">=18"
      }
    }
  }
}


path: package.json
content: {
  "name": "vault-backup",
  "version": "0.2.0",
  "description": "Encrypted memory backup to IPFS with X1 blockchain CID anchoring",
  "main": "src/backup.js",
  "scripts": {
    "backup": "node src/backup.js",
    "restore": "node src/restore.js",
    "list": "node src/list.js",
    "heartbeat": "node src/heartbeat.js",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [
    "backup",
    "memory",
    "ipfs",
    "encryption",
    "x1",
    "blockchain"
  ],
  "author": "Lokoweb3",
  "license": "ISC",
  "config": {
    "requiredEnv": ["PINATA_JWT"],
    "optionalEnv": ["X1_RPC_URL"],
    "configPaths": ["x1_vault_cli/wallet.json"]
  },
  "engines": {
    "node": ">=18.0.0"
  },
  "dependencies": {
    "tar": "^7.5.9",
    "form-data": "^4.0.0",
    "@solana/web3.js": "^1.95.0",
    "@solana/spl-memo": "^0.2.5",
    "bs58": "^6.0.0"
  }
}


path: scripts/backup.sh
content: set -euo pipefail
cd /home/node/.openclaw/workspace
node x1-vault-memory/src/backup.js "$@"


path: scripts/restore.sh
content: set -euo pipefail
cd /home/node/.openclaw/workspace
node x1-vault-memory/src/restore.js "$@"


path: src/anchor.js
content: const { Connection, Keypair, Transaction, SystemProgram, PublicKey } = require('@solana/web3.js');
const fs = require('fs');

// X1 Mainnet RPC
const RPC_URL = process.env.X1_RPC_URL || 'https://rpc.mainnet.x1.xyz';

// Known memo program IDs to try
const MEMO_PROGRAM_IDS = [
  [REDACTED_SECRET], // Solana Memo v2
  [REDACTED_SECRET], // Solana Memo v1
];

async function findWorkingMemoProgram(connection) {
  for (const programId of MEMO_PROGRAM_IDS) {
    try {
      const pubkey = new PublicKey(programId);
      const accountInfo = await connection.getAccountInfo(pubkey);
      if (accountInfo && accountInfo.executable) {
        console.log('Found working memo program:', programId);
        return pubkey;
      }
    } catch {
      // Continue to next
    }
  }
  return null;
}

async function anchorCID(cid, walletPath) {
  try {
    // Load wallet
    const walletData = JSON.parse(fs.readFileSync(walletPath, 'utf8'));
    const secretKey = Uint8Array.from(walletData.secretKey);
    const keypair = Keypair.fromSecretKey(secretKey);
    
    console.log('Using wallet:', keypair.publicKey.toBase58());
    
    // Connect to X1 mainnet
    const connection = new Connection(RPC_URL, 'confirmed');
    console.log('Connected to', RPC_URL);
    
    // Check balance
    const balance = await connection.getBalance(keypair.publicKey);
    console.log('Wallet balance:', (balance / 1e9).toFixed(4), 'XN');
    
    if (balance < 0.0001 * 1e9) {
      throw new Error('Insufficient balance. Need at least 0.0001 XN for transaction fee.');
    }
    
    // Try to find a working memo program
    const memoProgram = await findWorkingMemoProgram(connection);
    
    const transaction = new Transaction();
    let method = 'transfer-only';
    
    if (memoProgram) {
      // Use memo program if available
      console.log('Using memo program for CID anchoring');
      const { TransactionInstruction } = require('@solana/web3.js');
      const memoInstruction = new TransactionInstruction({
        programId: memoProgram,
        keys: [],
        data: Buffer.from(`IPFS:${cid}`, 'utf8'),
      });
      transaction.add(memoInstruction);
      method = 'memo-program';
    }
    
    // Add a minimal self-transfer (0 XN) to make transaction valid
    // This is required even with memo program
    const transferInstruction = SystemProgram.transfer({
      fromPubkey: keypair.publicKey,
      toPubkey: keypair.publicKey,
      lamports: 0,
    });
    transaction.add(transferInstruction);
    
    console.log('Transaction created with CID:', cid);
    console.log('Anchoring method:', method);
    
    // Send transaction
    console.log('Submitting transaction to X1 mainnet...');
    const signature = await connection.sendTransaction(transaction, [keypair], {
      skipPreflight: false,
      preflightCommitment: 'confirmed',
    });
    
    // Wait for confirmation
    console.log('Waiting for confirmation...');
    await connection.confirmTransaction({ signature }, 'confirmed');
    
    console.log('✓ Transaction confirmed:', signature);
    
    return {
      signature,
      explorerUrl: `https://explorer.mainnet.x1.xyz/tx/${signature}`,
      cid,
      method,
    };
  } catch (err) {
    console.error('Detailed error:', err.message);
    if (err.logs) {
      console.error('Transaction logs:', err.logs);
    }
    throw err;
  }
}

module.exports = { anchorCID };


path: src/backup.js
content: const fs = require('fs');
const path = require('path');
const crypto = require('crypto');
const tar = require('tar');
const { uploadToIPFS } = require('./upload');
const { anchorCID } = require('./anchor');

// Constants for encryption
const IV_SIZE = 12;
const AUTH_TAG_SIZE = 16;
const SALT_SIZE = 32;
const PBKDF2_ITERATIONS = 100000;
const KEY_SIZE = 32;

// Paths to backup (go up 2 levels to reach workspace root)
const filesToBackup = [
  'IDENTITY.md',
  'SOUL.md',
  'USER.md',
  'TOOLS.md',
];
const memoryDir = path.resolve(__dirname, '../..', 'memory');

/**
 * Derive encryption key using PBKDF2 with random salt
 * @param {Buffer} secretKey - Source secret key
 * @param {Buffer} salt - Random salt
 * @returns {Buffer} 32-byte derived key
 */
function deriveKey(secretKey, salt) {
  return crypto.pbkdf2Sync(secretKey, salt, PBKDF2_ITERATIONS, KEY_SIZE, 'sha256');
}

/**
 * Clean up temporary files
 * @param {string[]} files - Array of file paths to delete
 */
function cleanupFiles(files) {
  for (const file of files) {
    try {
      if (fs.existsSync(file)) {
        fs.unlinkSync(file);
        console.log('Cleaned up:', path.basename(file));
      }
    } catch (err) {
      console.error('Failed to clean up', file, err.message);
    }
  }
}

/**
 * Validate CID format to prevent SSRF
 * @param {string} cid - Content Identifier to validate
 * @returns {boolean} - True if valid
 */
function validateCID(cid) {
  // CIDv0: Qm + 44 base58 chars
  // CIDv1: starts with bafy, bafk, bafz, etc + base32 chars
  if (!cid || typeof cid !== 'string') return false;
  
  // Basic length check
  if (cid.length < 32 || cid.length > 128) return false;
  
  // Check for valid characters (base58 for CIDv0, base32 for CIDv1)
  const validPattern = /^[A-Za-z0-9]+$/;
  if (!validPattern.test(cid)) return false;
  
  // CIDv0 check
  if (cid.startsWith('Qm') && cid.length === 46) return true;
  
  // CIDv1 check (various codecs)
  if (cid.startsWith('baf') && cid.length >= 59) return true;
  
  return false;
}

async function createBackup() {
  const tempFiles = [];
  const entry = { timestamp: new Date().toISOString() };
  
  try {
    // Create a temporary tar.gz archive
    const archivePath = path.resolve(__dirname, 'backup.tar.gz');
    const tarFiles = filesToBackup.map(f => path.resolve(__dirname, '..', f));
    const cwd = path.resolve(__dirname, '../..');
    
    await tar.c(
      {
        gzip: true,
        file: archivePath,
        cwd,
      },
      [...filesToBackup, 'memory']
    );
    tempFiles.push(archivePath);

    // Generate SHA-256 hash of archive before encryption
    const archiveBuffer = fs.readFileSync(archivePath);
    const checksum = crypto.createHash('sha256').update(archiveBuffer).digest('hex');
    const checksumPath = path.resolve(__dirname, 'checksum.txt');
    fs.writeFileSync(checksumPath, checksum);
    tempFiles.push(checksumPath);
    console.log('Archive checksum:', checksum);

    // Create payload with archive + checksum
    const payloadPath = path.resolve(__dirname, 'payload.tar');
    await tar.c(
      {
        file: payloadPath,
        cwd: __dirname,
      },
      ['backup.tar.gz', 'checksum.txt']
    );
    tempFiles.push(payloadPath);

    // Load wallet secret key
    const walletPath = path.resolve(__dirname, '../..', 'x1_vault_cli', 'wallet.json');
    if (!fs.existsSync(walletPath)) {
      throw new Error(`Wallet not found at ${walletPath}`);
    }
    const wallet = JSON.parse(fs.readFileSync(walletPath, 'utf8'));
    const secretKey = Buffer.from(wallet.secretKey);
    
    // Generate random salt and derive key using PBKDF2
    const salt = crypto.randomBytes(SALT_SIZE);
    const key = deriveKey(secretKey, salt);
    
    // Encrypt the payload with AES-256-GCM
    const iv = crypto.randomBytes(IV_SIZE);
    const cipher = crypto.createCipheriv('aes-256-gcm', key, iv);
    const payloadBuffer = fs.readFileSync(payloadPath);
    const encrypted = Buffer.concat([cipher.update(payloadBuffer), cipher.final()]);
    const authTag = cipher.getAuthTag(); // AUTH_TAG_SIZE bytes
    
    // Structure: salt (32) + iv (12) + ciphertext + authTag (16)
    const encryptedData = Buffer.concat([salt, iv, encrypted, authTag]);
    const encryptedPath = archivePath + '.enc';
    fs.writeFileSync(encryptedPath, encryptedData);
    tempFiles.push(encryptedPath);

    // Upload encrypted backup to Pinata
    const cid = await uploadToIPFS(encryptedData);
    
    if (!validateCID(cid)) {
      throw new Error('Invalid CID returned from Pinata');
    }
    
    entry.cid = cid;
    console.log('Backup uploaded, CID:', cid);

    // Anchor CID to X1 blockchain
    try {
      const { signature, explorerUrl } = await anchorCID(cid, walletPath);
      console.log('X1 Transaction:', signature);
      console.log('Explorer:', explorerUrl);
      entry.txHash = signature;
      entry.explorerUrl = explorerUrl;
    } catch (err) {
      console.error('X1 anchoring failed (backup still saved to IPFS):', err.message);
    }

    // Log backup
    const logPath = path.resolve(__dirname, '../..', 'vault-log.json');
    let log = [];
    if (fs.existsSync(logPath)) {
      log = JSON.parse(fs.readFileSync(logPath, 'utf8'));
    }
    log.push(entry);
    fs.writeFileSync(logPath, JSON.stringify(log, null, 2));
    console.log('Logged backup CID to vault-log.json');

  } finally {
    // Always clean up temp files
    cleanupFiles(tempFiles);
  }
}

createBackup().catch(err => {
  console.error('Backup failed:', err);
  process.exit(1);
});


path: src/heartbeat.js
content: const fs = require('fs');
const path = require('path');
const { execFileSync } = require('child_process');

// Validate CID - only allow alphanumeric characters, reject empty, max 128 chars
function validateCID(cid) {
  if (!cid || typeof cid !== 'string') return false;
  if (cid.length === 0 || cid.length > 128) return false;
  return /^[a-zA-Z0-9]+$/.test(cid);
}

// Heartbeat check — verify memory files exist and aren't empty
// Auto-restore if missing or corrupted

const WORKSPACE_DIR = path.resolve(__dirname, '../..');
const SOUL_PATH = path.join(WORKSPACE_DIR, 'SOUL.md');
const MEMORY_DIR = path.join(WORKSPACE_DIR, 'memory');
const LOG_PATH = path.join(WORKSPACE_DIR, 'vault-log.json');

function getFileSize(filePath) {
  try {
    const stats = fs.statSync(filePath);
    return stats.size;
  } catch {
    return 0;
  }
}

function getDirSize(dirPath) {
  try {
    const files = fs.readdirSync(dirPath);
    return files.length;
  } catch {
    return 0;
  }
}

function getLatestCID() {
  try {
    if (!fs.existsSync(LOG_PATH)) return null;
    const log = JSON.parse(fs.readFileSync(LOG_PATH, 'utf8'));
    if (log.length === 0) return null;
    return log[log.length - 1].cid;
  } catch {
    return null;
  }
}

async function heartbeat() {
  console.log('Running X1 Vault heartbeat check...\n');
  
  let issues = [];
  
  // Check SOUL.md
  const soulSize = getFileSize(SOUL_PATH);
  console.log(`SOUL.md: ${soulSize} bytes`);
  if (soulSize < 10) {
    issues.push('SOUL.md missing or too small (< 10 bytes)');
  }
  
  // Check memory/ directory
  const memoryFiles = getDirSize(MEMORY_DIR);
  console.log(`memory/ directory: ${memoryFiles} files`);
  if (memoryFiles === 0) {
    issues.push('memory/ directory empty');
  }
  
  // Report status
  console.log('\n--- Heartbeat Status ---');
  
  if (issues.length === 0) {
    console.log('✓ All checks passed — memory files healthy');
    console.log('No action needed.');
    return;
  }
  
  // Issues found — attempt auto-restore
  console.log('✗ Issues detected:');
  issues.forEach(issue => console.log(`  - ${issue}`));
  
  console.log('\nAttempting auto-restore from latest backup...');
  
  const latestCID = getLatestCID();
  if (!latestCID) {
    console.error('ERROR: No backups found in vault-log.json');
    console.error('Cannot auto-restore — manual intervention required');
    process.exit(1);
  }
  
  // Validate CID before using in shell command
  if (!validateCID(latestCID)) {
    console.error('ERROR: Invalid CID format. CID must only contain letters and numbers.');
    console.error('CID received:', latestCID);
    process.exit(1);
  }

  console.log(`Latest backup CID: ${latestCID}`);
  console.log('Triggering restore...\n');

  try {
    execFileSync('node', [path.join(__dirname, 'restore.js'), latestCID], {
      stdio: 'inherit',
      cwd: __dirname
    });
    console.log('\n✓ Auto-restore completed successfully');
  } catch (err) {
    console.error('\n✗ Auto-restore failed:', err.message);
    console.error('Manual restore required:');
    console.error(`  node src/restore.js ${latestCID}`);
    process.exit(1);
  }
}

heartbeat().catch(err => {
  console.error('Heartbeat failed:', err);
  process.exit(1);
});


path: src/list.js
content: const fs = require('fs');
const path = require('path');

// Read vault-log.json and display numbered list of backups
async function listBackups() {
  const logPath = path.resolve(__dirname, '../..', 'vault-log.json');
  
  if (!fs.existsSync(logPath)) {
    console.error('No vault-log.json found. No backups recorded.');
    process.exit(1);
  }
  
  const log = JSON.parse(fs.readFileSync(logPath, 'utf8'));
  
  if (log.length === 0) {
    console.log('No backups found.');
    return;
  }
  
  console.log('Available backups:\n');
  
  log.forEach((entry, index) => {
    const num = index + 1;
    const date = new Date(entry.timestamp).toLocaleString();
    const cid = entry.cid ? entry.cid.slice(0, 16) + '...' : 'N/A';
    const txStatus = entry.txHash ? '✓ anchored' : 'IPFS only';
    
    console.log(`${num}. ${date} — ${cid} (${txStatus})`);
  });
  
  console.log('\nTo restore a specific backup:');
  console.log('  node restore.js <CID>');
  console.log('\nTo restore only memory folder:');
  console.log('  node restore.js <CID> --only memory/');
}

listBackups().catch(err => {
  console.error('Failed to list backups:', err);
  process.exit(1);
});


path: src/restore.js
content: const fs = require('fs');
const path = require('path');
const crypto = require('crypto');
const tar = require('tar');
const https = require('https');

// Constants for encryption (must match backup.js)
const IV_SIZE = 12;
const AUTH_TAG_SIZE = 16;
const SALT_SIZE = 32;
const PBKDF2_ITERATIONS = 100000;
const KEY_SIZE = 32;

/**
 * Derive encryption key using PBKDF2 with salt
 * @param {Buffer} secretKey - Source secret key
 * @param {Buffer} salt - Random salt from encrypted data
 * @returns {Buffer} 32-byte derived key
 */
function deriveKey(secretKey, salt) {
  return crypto.pbkdf2Sync(secretKey, salt, PBKDF2_ITERATIONS, KEY_SIZE, 'sha256');
}

/**
 * Validate CID format to prevent SSRF
 * @param {string} cid - Content Identifier to validate
 * @returns {boolean} - True if valid
 */
function validateCID(cid) {
  if (!cid || typeof cid !== 'string') return false;
  if (cid.length < 32 || cid.length > 128) return false;
  const validPattern = /^[A-Za-z0-9]+$/;
  if (!validPattern.test(cid)) return false;
  if (cid.startsWith('Qm') && cid.length === 46) return true;
  if (cid.startsWith('baf') && cid.length >= 59) return true;
  return false;
}

/**
 * Clean up temporary file
 * @param {string} filePath - File to delete
 */
function cleanupFile(filePath) {
  try {
    if (fs.existsSync(filePath)) {
      const stat = fs.statSync(filePath);
      if (stat.isDirectory()) {
        fs.rmSync(filePath, { recursive: true });
      } else {
        fs.unlinkSync(filePath);
      }
    }
  } catch (err) {
    console.error('Failed to clean up', filePath, err.message);
  }
}

/**
 * Download file from Pinata gateway with retry logic
 * @param {string} cid - Content identifier
 * @param {string} destPath - Destination path
 * @param {number} retries - Number of retry attempts
 * @returns {Promise}
 */
function downloadFromPinata(cid, destPath, retries = 3) {
  return new Promise((resolve, reject) => {
    const attempt = (left) => {
      const url = `https://gateway.pinata.cloud/ipfs/${cid}`;
      const file = fs.createWriteStream(destPath);
      
      https.get(url, response => {
        if (response.statusCode === 429 && left > 0) {
          console.log(`Rate limited, retrying in ${1000 * (4 - left)}ms`);
          file.close();
          setTimeout(() => attempt(left - 1), 1000 * (4 - left));
          return;
        }
        
        if (response.statusCode !== 200) {
          file.close();
          cleanupFile(destPath);
          reject(new Error(`Failed to download CID ${cid}, status ${response.statusCode}`));
          return;
        }
        
        response.pipe(file);
        file.on('finish', () => {
          file.close(() => resolve());
        });
      }).on('error', err => {
        cleanupFile(destPath);
        if (left > 0) {
          console.log(`Download error, retrying ${left} more times`);
          setTimeout(() => attempt(left - 1), 1000);
        } else {
          reject(err);
        }
      });
    };
    
    attempt(retries);
  });
}

async function restoreBackup(cid, onlyPath = null) {
  if (!validateCID(cid)) {
    throw new Error(`Invalid CID format: ${cid}`);
  }

  const tempFiles = [];
  const encryptedPath = path.resolve(__dirname, 'downloaded.enc');
  tempFiles.push(encryptedPath);

  try {
    await downloadFromPinata(cid, encryptedPath);
    console.log('Downloaded from Pinata');

    // Load wallet secret key
    const walletPath = path.resolve(__dirname, '../..', 'x1_vault_cli', 'wallet.json');
    if (!fs.existsSync(walletPath)) {
      throw new Error(`Wallet not found at ${walletPath}`);
    }
    const wallet = JSON.parse(fs.readFileSync(walletPath, 'utf8'));
    const secretKey = Buffer.from(wallet.secretKey);
    
    // Read encrypted file
    const data = fs.readFileSync(encryptedPath);
    
    // Extract components: salt + iv + ciphertext + authTag
    if (data.length < SALT_SIZE + IV_SIZE + AUTH_TAG_SIZE + 1) {
      throw new Error('Encrypted file too small to be valid');
    }
    
    const salt = data.slice(0, SALT_SIZE);
    const iv = data.slice(SALT_SIZE, SALT_SIZE + IV_SIZE);
    const authTag = data.slice(data.length - AUTH_TAG_SIZE);
    const ciphertext = data.slice(SALT_SIZE + IV_SIZE, data.length - AUTH_TAG_SIZE);
    
    // Derive key using PBKDF2
    const key = deriveKey(secretKey, salt);
    
    // Decrypt
    const decipher = crypto.createDecipheriv('aes-256-gcm', key, iv);
    decipher.setAuthTag(authTag);
    const decrypted = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
    
    // Write decrypted payload
    const payloadPath = path.resolve(__dirname, 'payload.tar');
    tempFiles.push(payloadPath);
    fs.writeFileSync(payloadPath, decrypted);
    console.log('Decrypted payload extracted');

    // Extract payload
    const tempDir = path.resolve(__dirname, 'temp_restore');
    if (!fs.existsSync(tempDir)) {
      fs.mkdirSync(tempDir, { recursive: true });
    }
    tempFiles.push(tempDir);
    
    await tar.x({ file: payloadPath, cwd: tempDir });
    console.log('Payload extracted');

    // Verify checksum
    const checksumPath = path.join(tempDir, 'checksum.txt');
    const archivePath = path.join(tempDir, 'backup.tar.gz');
    
    if (!fs.existsSync(checksumPath) || !fs.existsSync(archivePath)) {
      throw new Error('Payload missing checksum.txt or backup.tar.gz');
    }
    
    const expectedChecksum = fs.readFileSync(checksumPath, 'utf8').trim();
    const archiveBuffer = fs.readFileSync(archivePath);
    const actualChecksum = crypto.createHash('sha256').update(archiveBuffer).digest('hex');
    
    console.log('Expected checksum:', expectedChecksum);
    console.log('Actual checksum:', actualChecksum);
    
    if (expectedChecksum !== actualChecksum) {
      throw new Error(`Checksum mismatch! Expected ${expectedChecksum}, got ${actualChecksum}`);
    }
    
    console.log('✓ Checksum verified — archive integrity confirmed');

    // Extract archive to workspace
    const cwd = path.resolve(__dirname, '../..');
    
    if (onlyPath) {
      console.log(`Restoring only: ${onlyPath}`);
      const files = await tar.list({ file: archivePath });
      const matchingFiles = files.filter(f => f.startsWith(onlyPath));
      
      if (matchingFiles.length === 0) {
        throw new Error(`No files found matching: ${onlyPath}`);
      }
      
      console.log(`Found ${matchingFiles.length} matching files`);
      await tar.x({ file: archivePath, cwd, filter: (p) => p.startsWith(onlyPath) });
      console.log(`Restored ${onlyPath} to workspace`);
    } else {
      await tar.x({ file: archivePath, cwd });
      console.log('Backup restored to workspace');
    }
    
  } finally {
    // Always clean up temp files
    for (const file of tempFiles) {
      cleanupFile(file);
    }
  }
}

// Parse arguments
const cid = process.argv[2];
const onlyFlag = process.argv.indexOf('--only');
const onlyPath = onlyFlag !== -1 && process.argv[onlyFlag + 1] 
  ? process.argv[onlyFlag + 1] 
  : null;

if (!cid) {
  console.error('Usage: node restore.js <CID> [--only <path>]');
  console.error('Example: node restore.js QmAbC123 --only memory/');
  process.exit(1);
}

restoreBackup(cid, onlyPath).catch(err => {
  console.error('Restore failed:', err);
  process.exit(1);
});


path: src/upload.js
content: const FormData = require('form-data');
const https = require('https');
const fs = require('fs');

function uploadToIPFS(filePathOrBuffer) {
  const jwt = process.env.PINATA_JWT;
  if (!jwt) {
    throw new Error('PINATA_JWT environment variable not set');
  }

  return new Promise((resolve, reject) => {
    // Read file if path is provided, otherwise use buffer directly
    const buffer = Buffer.isBuffer(filePathOrBuffer) 
      ? filePathOrBuffer 
      : fs.readFileSync(filePathOrBuffer);
    
    const form = new FormData();
    form.append('file', buffer, {
      filename: 'encrypted.bin',
      contentType: 'application/octet-stream',
    });

    const options = {
      hostname: 'api.pinata.cloud',
      path: '/pinning/pinFileToIPFS',
      method: 'POST',
      headers: {
        Authorization: 'Bearer ' + jwt,
        ...form.getHeaders(),
      },
    };

    const req = https.request(options, (res) => {
      let body = '';
      res.on('data', (chunk) => { body += chunk; });
      res.on('end', () => {
        if (res.statusCode !== 200) {
          reject(new Error('Pinata upload failed: ' + res.statusCode + ' - ' + body));
          return;
        }
        resolve(JSON.parse(body).IpfsHash);
      });
    });

    req.on('error', reject);
    form.pipe(req);
  });
}

module.exports = { uploadToIPFS };


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