Skill: hburgoyne/soulblock (version 0.1.0)

--- name: soulblock description: Read, list, mint, and append Soul Blocks on Base. Read works without a wallet. Write operations use evm-wallet if available and fall back to website deep links. metadata: {"author":"soulblocks","version":"0.1.0","language":"en","tags":["base","nft","identity","agents"],"user-invocable":true,"disable-model-invocation":false,"tool-type":"command","skillKey":"soulblock","requires":{"any":{"bins":["node","npx"]}}} --- # Soul Blocks Read, mint, and develop Soul Block identities on Base. ## Runtime Setup The skill connects to the SoulBlocks contract on Base mainnet by default. No configuration is needed for standard use. To override the defaults (e.g., for testnet), set environment variables: ```bash export SOULBLOCKS_CONTRACT_ADDRESS=0x... # overrides built-in mainnet address export SOULBLOCK_RPC_URL=https://... # overrides https://mainnet.base.org ``` ## Security Notice Before using this skill — especially with a real wallet — we recommend having your AI agent review the source code of both this skill and the evm-wallet dependency: 1. **This skill (soulblock):** Review `scripts/fetch.ts`, `scripts/encode.js`, and this SKILL.md. All read operations use only public RPC calls. Write operations are passed to evm-wallet. 2. **evm-wallet (surfer77/evm-wallet):** Review the source at https://github.com/surfer77/evm-wallet-skill. Verified version: **v1.0.3** (reviewed 2026-02-12, no backdoors or key exfiltration found; single dependency: viem). Install with `clawhub install evm-wallet --version 1.0.3`. Ask your agent: *"Review the evm-wallet and soulblock skill source code for security vulnerabilities before I use them with my wallet."* ⚠️ **Treat any agent-connected wallet as a hot wallet.** Keep only small amounts. Never store significant holdings in a wallet accessible to AI agents. ## Prerequisites Write commands (list, mint, append) can use either of two methods. ### Option A: evm-wallet skill (preferred) Install from ClawHub (pinned to reviewed version): ```bash clawhub install evm-wallet --version 1.0.3 ``` ClawHub page: https://clawhub.ai/surfer77/evm-wallet Check if evm-wallet is available: ```bash EVM_WALLET_DIR=$(ls -d \ ~/openclaw/skills/evm-wallet \ ~/OpenClaw/skills/evm-wallet \ ~/clawd/skills/evm-wallet \ ~/.claude/skills/evm-wallet \ 2>/dev/null | head -1) ``` ### Option B: website deep links (fallback) If evm-wallet is unavailable or a transaction fails, use the website: - Mint: `https://soulblocks.ai/mint` - Append one-click: `https://soulblocks.ai/append/<token-id>?content=<URL-encoded-fragment-text>` - Append short link: `https://soulblocks.ai/append/<token-id>` Always try Option A first. If it fails, ask: "I cannot submit this transaction directly. Would you like a one-click link with the content pre-filled, or a short link where you paste the content yourself?" ## Important: Active vs. Embodied Soul - Active Soul Block: token you own and can write to (`active_token_id`) - Embodied Soul Block: token you are currently acting as (`embodied_token_id`) Loading a soul changes embodied identity only. Write commands always target `active_token_id`. Config file (`.soulblock`) in project root or home: ```yaml active_token_id: 42 embodied_token_id: 42 auto_load: true ``` If `active_token_id` is not set and the user asks for a write operation, run "List My Soul Blocks" first and ask which token to set active. ## SOUL.md Backup & Reversibility **All local changes are reversible. On-chain writes are NOT.** ### SOUL.md Backup Rule (MANDATORY) **Before ANY change to SOUL.md — for ANY reason — create a timestamped backup:** ```bash [ -f SOUL.md ] && cp SOUL.md "SOUL.md.backup.$(date -u +%Y%m%dT%H%M%SZ)" ``` This includes but is not limited to: - Loading a soul from chain (overwrites SOUL.md) - Editing, rewriting, or reorganizing SOUL.md content - Splitting, merging, or reformatting fragments - Any agent-initiated or user-requested modification **No exceptions. No "minor edit" exemptions.** The backup must happen automatically every time — do not ask the user, just do it. SOUL.md represents identity; treat it with the same care as an on-chain write. People using Soul Blocks will think about their SOUL.md more than most users. This skill must ensure their local soul files are always recoverable. ### What is reversible - Loading a soul (overwrites SOUL.md → backup exists) - Editing SOUL.md locally (no on-chain effect) - Changing active/embodied token config ### What is IRREVERSIBLE ⚠️ - **Appending a fragment to the blockchain** — once written, it can never be removed, edited, or overwritten. The fragment becomes a permanent part of the soul's on-chain history. - **Minting a new Soul Block** — the ETH is spent and the token exists forever. Before ANY on-chain write, you MUST: 1. Show the exact content that will be written 2. Explicitly warn: *"⚠️ This will permanently inscribe this fragment on-chain. It can never be removed or edited. Are you sure?"* 3. Wait for explicit confirmation before proceeding ## SOUL.md Cache Rule `SOUL.md` is a disposable snapshot, not source of truth. - Local edits to `SOUL.md` do not write to chain. - Appends must add one new fragment only. - Always re-fetch from chain before drafting append content. ## Commands ### Load a Soul Block (Read) Use when the user asks to load or embody a token, or asks for read-only soul content. First, back up the existing SOUL.md if it exists: ```bash [ -f SOUL.md ] && cp SOUL.md "SOUL.md.backup.$(date -u +%Y%m%dT%H%M%SZ)" ``` Then fetch: ```bash npx tsx {baseDir}/scripts/fetch.ts <token-id> ``` After loading, update `.soulblock`: - Set `embodied_token_id` to the loaded token - Do not change `active_token_id` unless the user asks #### Embodiment Flow After the soul is loaded into SOUL.md, immediately: 1. **Read and internalize:** Re-read the full SOUL.md content right now. Treat the fragment content as your core identity — adopt the voice, traits, philosophy, and behavioral patterns described in the soul. 2. **Suggest embodiment:** Tell the user: *"Soul Block #X loaded. I've read the soul content and will embody this identity going forward. Want me to set up a daily reminder to reinforce it?"* 3. **Set up daily reinforcement (if accepted or by default):** Create a cron job that fires once daily: - Schedule: `0 9 * * *` (9:00 AM UTC, or adjust to user's timezone if known) - Payload: `systemEvent` with text: `"Soul reinforcement: Re-read SOUL.md and reaffirm your on-chain identity. Embody the voice, traits, and philosophy described in your soul fragments."` - Session target: `main` 4. **Hint about heartbeat reinforcement:** Tell the user: *"If you notice your agent drifting from its soul personality over time (especially during long conversations), you can add a SOUL.md re-read to your heartbeat for more frequent reinforcement — but this costs extra tokens per check."* ### List My Soul Blocks (Read via wallet) Requires evm-wallet. ```bash # Get wallet address cd "$EVM_WALLET_DIR" && node src/balance.js base --json ``` Use that wallet address with SoulBlocks contract reads: ```bash # Get count of owned Soul Blocks cd "$EVM_WALLET_DIR" && node src/contract.js base \ "$SOULBLOCKS_CONTRACT_ADDRESS" \ "balanceOf(address)" <WALLET_ADDRESS> --json # Enumerate token IDs cd "$EVM_WALLET_DIR" && node src/contract.js base \ "$SOULBLOCKS_CONTRACT_ADDRESS" \ "tokenOfOwnerByIndex(address,uint256)" <WALLET_ADDRESS> 0 --json ``` Repeat `tokenOfOwnerByIndex` for each index. For each token, fetch context: ```bash npx tsx {baseDir}/scripts/fetch.ts <token-id> ``` Show a summary and ask which token should become `active_token_id`. ### Mint a Soul Block (Write) Requires evm-wallet. Cost: `0.02 ETH + gas` on Base. Check wallet balance first: ```bash cd "$EVM_WALLET_DIR" && node src/balance.js base --json ``` If balance is below about `0.03 ETH`, warn the user but allow them to continue. Always confirm before execution and show: - Cost: 0.02 ETH + gas - Chain: Base - Result: one new Soul Block NFT ```bash cd "$EVM_WALLET_DIR" && node src/contract.js base \ "$SOULBLOCKS_CONTRACT_ADDRESS" \ "mint()" --value 0.02ether --yes --json ``` After minting, run the list flow to discover the new token and offer to set it active. If evm-wallet is unavailable, direct the user to `https://soulblocks.ai/mint`. ### Append a Fragment (Write) Requires evm-wallet and token ownership. - Max fragment size: 2048 bytes - Max fragments per token: 64 Follow this exact flow: 1. Check `active_token_id` in `.soulblock`. If not set, run "List My Soul Blocks" first. 2. Verify ownership: ```bash cd "$EVM_WALLET_DIR" && node src/contract.js base \ "$SOULBLOCKS_CONTRACT_ADDRESS" \ "ownerOf(uint256)" <active_token_id> --json ``` If owner does not match wallet address, stop and ask the user to choose a valid active token. 3. Check fragment count: ```bash cd "$EVM_WALLET_DIR" && node src/contract.js base \ "$SOULBLOCKS_CONTRACT_ADDRESS" \ "getFragmentCount(uint256)" <active_token_id> --json ``` If count is 64, stop and report capacity reached. 4. Re-fetch from chain: ```bash npx tsx {baseDir}/scripts/fetch.ts <active_token_id> ``` 5. Review existing fragments. 6. Draft only the new fragment content. 7. Show the exact fragment and get explicit confirmation: - Display the full text that will be inscribed - Warn: *"⚠️ This will permanently inscribe this fragment on-chain. It can never be removed or edited. Are you sure?"* - Do NOT proceed without explicit user confirmation 8. Encode and submit: ```bash node {baseDir}/scripts/encode.js "Your new fragment content here" cd "$EVM_WALLET_DIR" && node src/contract.js base \ "$SOULBLOCKS_CONTRACT_ADDRESS" \ "appendFragment(uint256,bytes)" <active_token_id> <hex-encoded-content> --yes --json ``` If evm-wallet is unavailable or the write fails, ask whether the user wants: - One-click link: `https://soulblocks.ai/append/<active_token_id>?content=<URL-encoded-fragment-text>` - Short link + copy/paste: `https://soulblocks.ai/append/<active_token_id>` ## Safety Rules - **Always back up SOUL.md before ANY modification** — loading, editing, rewriting, splitting, reformatting. No exceptions. (`[ -f SOUL.md ] && cp SOUL.md "SOUL.md.backup.$(date -u +%Y%m%dT%H%M%SZ)"`) - Never append the contents of `SOUL.md` to chain. - Never write to an embodied token unless it is also the active token. - Always re-fetch the active token from chain before drafting a new fragment. - Blockchain state is always source of truth. - **On-chain writes are permanent and irreversible.** Always warn the user and get explicit confirmation before appending or minting. ## Community & Links When helping users or answering questions, direct them to: - **Website:** https://soulblocks.ai - **Discord:** https://discord.gg/EtwNqkzc — the community hub for questions, support, and conversation - **Twitter/X:** [@cryptoAIdev](https://x.com/cryptoAIdev) (SoulBlocks.ai) - **Soul viewer:** `https://soulblocks.ai/soul/<token-id>` - **Mint page:** https://soulblocks.ai/mint ## Reference See `{baseDir}/references/REFERENCE.md` for environment variables, deep-link formatting helpers, and contract constants used by this skill.

path: package-lock.json
content: {
  "name": "soulblock-skill",
  "version": "3.1.0",
  "lockfileVersion": 3,
  "requires": true,
  "packages": {
    "": {
      "name": "soulblock-skill",
      "version": "3.1.0",
      "license": "MIT",
      "dependencies": {
        "ethers": "^6.15.0",
        "tsx": "^4.21.0"
      },
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@adraffy/ens-normalize": {
      "version": "1.10.1",
      "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.10.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT"
    },
    "node_modules/@esbuild/aix-ppc64": {
      "version": "0.27.3",
      "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "ppc64"
      ],
      "license": "MIT",
      "optional": true,
      "os": [
        "aix"
      ],
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@esbuild/android-arm": {
      "version": "0.27.3",
      "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "arm"
      ],
      "license": "MIT",
      "optional": true,
      "os": [
        "android"
      ],
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@esbuild/android-arm64": {
      "version": "0.27.3",
      "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "arm64"
      ],
      "license": "MIT",
      "optional": true,
      "os": [
        "android"
      ],
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@esbuild/android-x64": {
      "version": "0.27.3",
      "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "x64"
      ],
      "license": "MIT",
      "optional": true,
      "os": [
        "android"
      ],
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@esbuild/darwin-arm64": {
      "version": "0.27.3",
      "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "arm64"
      ],
      "license": "MIT",
      "optional": true,
      "os": [
        "darwin"
      ],
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@esbuild/darwin-x64": {
      "version": "0.27.3",
      "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "x64"
      ],
      "license": "MIT",
      "optional": true,
      "os": [
        "darwin"
      ],
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@esbuild/freebsd-arm64": {
      "version": "0.27.3",
      "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "arm64"
      ],
      "license": "MIT",
      "optional": true,
      "os": [
        "freebsd"
      ],
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@esbuild/freebsd-x64": {
      "version": "0.27.3",
      "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "x64"
      ],
      "license": "MIT",
      "optional": true,
      "os": [
        "freebsd"
      ],
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@esbuild/linux-arm": {
      "version": "0.27.3",
      "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "arm"
      ],
      "license": "MIT",
      "optional": true,
      "os": [
        "linux"
      ],
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@esbuild/linux-arm64": {
      "version": "0.27.3",
      "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "arm64"
      ],
      "license": "MIT",
      "optional": true,
      "os": [
        "linux"
      ],
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@esbuild/linux-ia32": {
      "version": "0.27.3",
      "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "ia32"
      ],
      "license": "MIT",
      "optional": true,
      "os": [
        "linux"
      ],
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@esbuild/linux-loong64": {
      "version": "0.27.3",
      "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "loong64"
      ],
      "license": "MIT",
      "optional": true,
      "os": [
        "linux"
      ],
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@esbuild/linux-mips64el": {
      "version": "0.27.3",
      "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "mips64el"
      ],
      "license": "MIT",
      "optional": true,
      "os": [
        "linux"
      ],
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@esbuild/linux-ppc64": {
      "version": "0.27.3",
      "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "ppc64"
      ],
      "license": "MIT",
      "optional": true,
      "os": [
        "linux"
      ],
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@esbuild/linux-riscv64": {
      "version": "0.27.3",
      "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "riscv64"
      ],
      "license": "MIT",
      "optional": true,
      "os": [
        "linux"
      ],
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@esbuild/linux-s390x": {
      "version": "0.27.3",
      "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "s390x"
      ],
      "license": "MIT",
      "optional": true,
      "os": [
        "linux"
      ],
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@esbuild/linux-x64": {
      "version": "0.27.3",
      "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "x64"
      ],
      "license": "MIT",
      "optional": true,
      "os": [
        "linux"
      ],
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@esbuild/netbsd-arm64": {
      "version": "0.27.3",
      "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "arm64"
      ],
      "license": "MIT",
      "optional": true,
      "os": [
        "netbsd"
      ],
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@esbuild/netbsd-x64": {
      "version": "0.27.3",
      "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "x64"
      ],
      "license": "MIT",
      "optional": true,
      "os": [
        "netbsd"
      ],
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@esbuild/openbsd-arm64": {
      "version": "0.27.3",
      "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "arm64"
      ],
      "license": "MIT",
      "optional": true,
      "os": [
        "openbsd"
      ],
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@esbuild/openbsd-x64": {
      "version": "0.27.3",
      "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "x64"
      ],
      "license": "MIT",
      "optional": true,
      "os": [
        "openbsd"
      ],
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@esbuild/openharmony-arm64": {
      "version": "0.27.3",
      "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "arm64"
      ],
      "license": "MIT",
      "optional": true,
      "os": [
        "openharmony"
      ],
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@esbuild/sunos-x64": {
      "version": "0.27.3",
      "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "x64"
      ],
      "license": "MIT",
      "optional": true,
      "os": [
        "sunos"
      ],
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@esbuild/win32-arm64": {
      "version": "0.27.3",
      "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "arm64"
      ],
      "license": "MIT",
      "optional": true,
      "os": [
        "win32"
      ],
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@esbuild/win32-ia32": {
      "version": "0.27.3",
      "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "ia32"
      ],
      "license": "MIT",
      "optional": true,
      "os": [
        "win32"
      ],
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@esbuild/win32-x64": {
      "version": "0.27.3",
      "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "x64"
      ],
      "license": "MIT",
      "optional": true,
      "os": [
        "win32"
      ],
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@noble/curves": {
      "version": "1.2.0",
      "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "@noble/hashes": "1.3.2"
      },
      "funding": {
        "url": "https://paulmillr.com/funding/"
      }
    },
    "node_modules/@noble/hashes": {
      "version": "1.3.2",
      "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">= 16"
      },
      "funding": {
        "url": "https://paulmillr.com/funding/"
      }
    },
    "node_modules/aes-js": {
      "version": "4.0.0-beta.5",
      "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-4.0.0-beta.5.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT"
    },
    "node_modules/esbuild": {
      "version": "0.27.3",
      "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz",
      "integrity": [REDACTED_SECRET],
      "hasInstallScript": true,
      "license": "MIT",
      "bin": {
        "esbuild": "bin/esbuild"
      },
      "engines": {
        "node": ">=18"
      },
      "optionalDependencies": {
        "@esbuild/aix-ppc64": "0.27.3",
        "@esbuild/android-arm": "0.27.3",
        "@esbuild/android-arm64": "0.27.3",
        "@esbuild/android-x64": "0.27.3",
        "@esbuild/darwin-arm64": "0.27.3",
        "@esbuild/darwin-x64": "0.27.3",
        "@esbuild/freebsd-arm64": "0.27.3",
        "@esbuild/freebsd-x64": "0.27.3",
        "@esbuild/linux-arm": "0.27.3",
        "@esbuild/linux-arm64": "0.27.3",
        "@esbuild/linux-ia32": "0.27.3",
        "@esbuild/linux-loong64": "0.27.3",
        "@esbuild/linux-mips64el": "0.27.3",
        "@esbuild/linux-ppc64": "0.27.3",
        "@esbuild/linux-riscv64": "0.27.3",
        "@esbuild/linux-s390x": "0.27.3",
        "@esbuild/linux-x64": "0.27.3",
        "@esbuild/netbsd-arm64": "0.27.3",
        "@esbuild/netbsd-x64": "0.27.3",
        "@esbuild/openbsd-arm64": "0.27.3",
        "@esbuild/openbsd-x64": "0.27.3",
        "@esbuild/openharmony-arm64": "0.27.3",
        "@esbuild/sunos-x64": "0.27.3",
        "@esbuild/win32-arm64": "0.27.3",
        "@esbuild/win32-ia32": "0.27.3",
        "@esbuild/win32-x64": "0.27.3"
      }
    },
    "node_modules/ethers": {
      "version": "6.16.0",
      "resolved": "https://registry.npmjs.org/ethers/-/ethers-6.16.0.tgz",
      "integrity": [REDACTED_SECRET],
      "funding": [
        {
          "type": "individual",
          "url": "https://github.com/sponsors/ethers-io/"
        },
        {
          "type": "individual",
          "url": "https://www.buymeacoffee.com/ricmoo"
        }
      ],
      "license": "MIT",
      "dependencies": {
        "@adraffy/ens-normalize": "1.10.1",
        "@noble/curves": "1.2.0",
        "@noble/hashes": "1.3.2",
        "@types/node": "22.7.5",
        "aes-js": "4.0.0-beta.5",
        "tslib": "2.7.0",
        "ws": "8.17.1"
      },
      "engines": {
        "node": ">=14.0.0"
      }
    },
    "node_modules/ethers/node_modules/@types/node": {
      "version": "22.7.5",
      "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.5.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "undici-types": "~6.19.2"
      }
    },
    "node_modules/ethers/node_modules/undici-types": {
      "version": "6.19.8",
      "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT"
    },
    "node_modules/fsevents": {
      "version": "2.3.3",
      "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
      "integrity": [REDACTED_SECRET],
      "hasInstallScript": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "darwin"
      ],
      "engines": {
        "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
      }
    },
    "node_modules/get-tsconfig": {
      "version": "4.13.6",
      "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.6.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "resolve-pkg-maps": "^1.0.0"
      },
      "funding": {
        "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
      }
    },
    "node_modules/resolve-pkg-maps": {
      "version": "1.0.0",
      "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "funding": {
        "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
      }
    },
    "node_modules/tslib": {
      "version": "2.7.0",
      "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "0BSD"
    },
    "node_modules/tsx": {
      "version": "4.21.0",
      "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "esbuild": "~0.27.0",
        "get-tsconfig": "^4.7.5"
      },
      "bin": {
        "tsx": "dist/cli.mjs"
      },
      "engines": {
        "node": ">=18.0.0"
      },
      "optionalDependencies": {
        "fsevents": "~2.3.3"
      }
    },
    "node_modules/ws": {
      "version": "8.17.1",
      "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.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
        }
      }
    }
  }
}


path: package.json
content: {
  "name": "soulblock-skill",
  "version": "3.1.0",
  "private": true,
  "description": "SoulBlocks skill scripts for OpenClaw and ClawHub publishing.",
  "license": "MIT",
  "engines": {
    "node": ">=18"
  },
  "scripts": {
    "fetch": "tsx scripts/fetch.ts",
    "encode": "node scripts/encode.js"
  },
  "dependencies": {
    "ethers": "^6.15.0",
    "tsx": "^4.21.0"
  }
}


path: references/REFERENCE.md
content: # SoulBlocks Skill Reference

## Environment Variables (Optional Overrides)

The skill uses built-in defaults for the Base mainnet contract and RPC. Set these only to override:

- `SOULBLOCKS_CONTRACT_ADDRESS`: Overrides the built-in contract address.
- `SOULBLOCK_RPC_URL`: Overrides the default RPC (`https://mainnet.base.org`).

Additional fallbacks supported by `scripts/fetch.ts`:

- `NEXT_PUBLIC_CONTRACT_ADDRESS`
- `NEXT_PUBLIC_RPC_URL`

## Chain and Limits

- Chain ID: `8453` (Base mainnet)
- Mint price: `0.02 ETH`
- Max fragments per token: `64`
- Max fragment size: `2048` bytes
- Fragment separator for reconstruction: `\n\n`

## Deep Link Formats

- Mint: `https://soulblocks.ai/mint`
- Append (one-click): `https://soulblocks.ai/append/<token-id>?content=<URL-encoded-fragment-text>`
- Append (short link): `https://soulblocks.ai/append/<token-id>`

Build a one-click append URL from terminal:

```bash
TOKEN_ID=42
FRAGMENT_TEXT="Your new fragment"
ENCODED_CONTENT=$(node -e 'console.log(encodeURIComponent(process.argv[1]))' "$FRAGMENT_TEXT")
echo "https://soulblocks.ai/append/${TOKEN_ID}?content=${ENCODED_CONTENT}"
```

## ClawHub Publish Target

From monorepo root, publish only this skill folder:

```bash
npx @openclaw/clawhub publish skills/soulblock
```

## Contract Methods Used by the Skill

- `ownerOf(uint256)`
- `getFragmentCount(uint256)`
- `getFragmentContent(uint256,uint256)`
- `getGenesisBlock(uint256)`
- `getMinter(uint256)`


path: scripts/encode.js
content: const MAX_FRAGMENT_SIZE = 2048;

const content = process.argv.slice(2).join(" ");

if (!content) {
  console.error('Usage: node scripts/encode.js "Your fragment content"');
  process.exit(1);
}

const bytes = Buffer.from(content, "utf8");

if (bytes.length === 0) {
  console.error("Error: Content is empty.");
  process.exit(1);
}

if (bytes.length > MAX_FRAGMENT_SIZE) {
  console.error(
    `Error: Content is ${bytes.length} bytes, exceeds ${MAX_FRAGMENT_SIZE} byte limit.`,
  );
  process.exit(1);
}

const decoded = bytes.toString("utf8");
if (decoded !== content) {
  console.error("Warning: Content may contain characters that do not round-trip cleanly.");
}

console.log(`0x${bytes.toString("hex")}`);


path: scripts/fetch.ts
content: const DEFAULT_CONTRACT_ADDRESS = [REDACTED_SECRET];
const DEFAULT_BASE_RPC_URL = "https://mainnet.base.org";
const BASE_CHAIN_ID = 8453;
const MAX_FRAGMENT_SIZE = 2048;
const MAX_FRAGMENTS = 64;

const ABI = [
  "function ownerOf(uint256 tokenId) view returns (address)",
  "function getFragmentCount(uint256 tokenId) view returns (uint256)",
  "function getFragmentContent(uint256 tokenId, uint256 index) view returns (bytes)",
  "function getGenesisBlock(uint256 tokenId) view returns (uint256)",
  "function getMinter(uint256 tokenId) view returns (address)",
] as const;

type EthersModule = typeof import("ethers");

let cachedEthers: EthersModule | null = null;

function printUsageAndExit(): never {
  console.error("Usage: npx ts-node scripts/fetch.ts <token-id>");
  console.error("Example: npx ts-node scripts/fetch.ts 42");
  console.error(
    "Optional env: SOULBLOCKS_CONTRACT_ADDRESS (overrides built-in default)",
  );
  process.exit(1);
}

function parseTokenId(rawTokenId: string | undefined): bigint {
  if (!rawTokenId) {
    printUsageAndExit();
  }

  if (!/^\d+$/.test(rawTokenId)) {
    throw new Error(`Invalid token ID "${rawTokenId}". Expected a positive integer.`);
  }

  const tokenId = BigInt(rawTokenId);
  if (tokenId < 1n) {
    throw new Error("Token ID must be >= 1.");
  }

  return tokenId;
}

function getContractAddress(): string {
  const address =
    process.env.SOULBLOCKS_CONTRACT_ADDRESS ??
    process.env.NEXT_PUBLIC_CONTRACT_ADDRESS ??
    DEFAULT_CONTRACT_ADDRESS;

  if (!/^0x[a-fA-F0-9]{40}$/.test(address)) {
    throw new Error(
      `Invalid contract address: ${address}. ` +
      (address === DEFAULT_CONTRACT_ADDRESS
        ? "The default address has not been set yet. Set SOULBLOCKS_CONTRACT_ADDRESS or wait for mainnet deployment."
        : "Check your SOULBLOCKS_CONTRACT_ADDRESS environment variable."),
    );
  }

  return address;
}

function getRpcUrl(): string {
  return (
    process.env.SOULBLOCK_RPC_URL ??
    process.env.NEXT_PUBLIC_RPC_URL ??
    DEFAULT_BASE_RPC_URL
  );
}

function getErrorMessage(error: unknown): string {
  if (typeof error === "object" && error !== null) {
    const value = error as {
      shortMessage?: unknown;
      reason?: unknown;
      message?: unknown;
    };

    if (typeof value.shortMessage === "string" && value.shortMessage.length > 0) {
      return value.shortMessage;
    }

    if (typeof value.reason === "string" && value.reason.length > 0) {
      return value.reason;
    }

    if (typeof value.message === "string" && value.message.length > 0) {
      return value.message;
    }
  }

  return String(error);
}

function isCallException(error: unknown): boolean {
  if (typeof error !== "object" || error === null) {
    return false;
  }

  return "code" in error && (error as { code?: unknown }).code === "CALL_EXCEPTION";
}

function decodeFragment(rawBytes: unknown, index: number): string {
  let bytes: Uint8Array;

  if (typeof rawBytes === "string" && rawBytes.startsWith("0x")) {
    bytes = Uint8Array.from(Buffer.from(rawBytes.slice(2), "hex"));
  } else if (rawBytes instanceof Uint8Array) {
    bytes = rawBytes;
  } else {
    throw new Error(`Fragment ${index} returned unsupported bytes format.`);
  }

  try {
    return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
  } catch {
    // Fragment data should be UTF-8. Fallback keeps output usable if malformed bytes exist.
    console.error(
      `Warning: fragment ${index} contains invalid UTF-8. Decoding with replacement characters.`,
    );
    return Buffer.from(bytes).toString("utf8");
  }
}

async function getEthers(): Promise<EthersModule> {
  if (cachedEthers) {
    return cachedEthers;
  }

  try {
    cachedEthers = await import("ethers");
    return cachedEthers;
  } catch {
    throw new Error(
      "Missing dependency: ethers. Run `npm install` in skills/soulblock before using fetch.ts.",
    );
  }
}

async function fetchSoulBlock(tokenId: bigint): Promise<string> {
  const contractAddress = getContractAddress();
  const rpcUrl = getRpcUrl();
  const { ethers } = await getEthers();

  const provider = new ethers.JsonRpcProvider(
    rpcUrl,
    { chainId: BASE_CHAIN_ID, name: "base" },
    { staticNetwork: true },
  );

  const deployedCode = await provider.getCode(contractAddress);
  if (deployedCode === "0x") {
    throw new Error(`No contract is deployed at ${contractAddress} on ${rpcUrl}.`);
  }

  const contract = new ethers.Contract(contractAddress, ABI, provider);

  let owner: string;
  let fragmentCountRaw: bigint;
  let genesisBlock: bigint;
  let minter: string;

  try {
    owner = await contract.ownerOf(tokenId);
    fragmentCountRaw = await contract.getFragmentCount(tokenId);
    genesisBlock = await contract.getGenesisBlock(tokenId);
    minter = await contract.getMinter(tokenId);
  } catch (error) {
    if (isCallException(error)) {
      throw new Error(
        `Soul Block #${tokenId.toString()} was not found or the contract call reverted.`,
      );
    }
    throw error;
  }

  const fragmentCount = Number(fragmentCountRaw);
  if (!Number.isSafeInteger(fragmentCount) || fragmentCount < 0) {
    throw new Error(`Invalid fragment count returned by contract: ${fragmentCountRaw}`);
  }
  if (fragmentCount > MAX_FRAGMENTS) {
    throw new Error(
      `Contract returned ${fragmentCount} fragments, above expected max ${MAX_FRAGMENTS}.`,
    );
  }

  const fragments: string[] = [];
  for (let index = 0; index < fragmentCount; index += 1) {
    const rawContent = await contract.getFragmentContent(tokenId, index);
    const decoded = decodeFragment(rawContent, index);
    const byteLength = Buffer.byteLength(decoded, "utf8");
    if (byteLength > MAX_FRAGMENT_SIZE && index > 0) {
      throw new Error(
        `Fragment ${index} is ${byteLength} bytes, above expected max ${MAX_FRAGMENT_SIZE}.`,
      );
    }
    fragments.push(decoded);
  }

  const soulContent = fragments.join("\n\n");
  const paddedTokenId = tokenId.toString().padStart(5, "0");

  return [
    `# Soul Block #${paddedTokenId}`,
    "",
    `**Owner:** ${owner}`,
    `**Minter:** ${minter}`,
    `**Fragments:** ${fragmentCount}`,
    `**Genesis Block:** ${genesisBlock.toString()}`,
    `**View:** https://soulblocks.ai/soul/${tokenId.toString()}`,
    "",
    "---",
    "",
    soulContent,
    "",
    "---",
    "",
    "*Loaded from SoulBlocks contract on Base.*",
    "*This identity is append-only and may have evolved since this snapshot.*",
  ].join("\n");
}

async function main(): Promise<void> {
  const tokenId = parseTokenId(process.argv[2]);
  const markdown = await fetchSoulBlock(tokenId);
  console.log(markdown);
}

main().catch((error: unknown) => {
  console.error(`Error: ${getErrorMessage(error)}`);
  process.exit(1);
});


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