diff --git a/.github/workflows/validate-versions.yml b/.github/workflows/validate-versions.yml new file mode 100644 index 0000000..89f052d --- /dev/null +++ b/.github/workflows/validate-versions.yml @@ -0,0 +1,19 @@ +name: Validate versions + +on: + pull_request: + push: + +permissions: + contents: read + +jobs: + versions: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 20 + - name: Check repository and skill versions + run: sh scripts/check_versions.sh diff --git a/AGENTS.md b/AGENTS.md index 11b1db7..7d151d3 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -76,7 +76,7 @@ tags: |-------|----------|-------------| | `name` | ✅ | Human-readable skill name | | `description` | ✅ | Brief description (1-2 sentences) | -| `version` | ✅ | Semantic version (e.g., 1.0.0) | +| `version` | ✅ | Repository release version from the root `VERSION` file | | `dependencies` | ⚠️ | List of required tools/servers (e.g., mcp-server-tron) | | `tags` | ⚠️ | Searchable tags for skill discovery | @@ -84,6 +84,12 @@ tags: ## 🗂 Skill Directory Structure +### Versioning + +All skills are released together and must use the exact version in the root `VERSION` file. Skill +versions do not track their external CLI or SDK dependencies. See [VERSIONING.md](./VERSIONING.md) +and run `sh scripts/check_versions.sh` after changing a version. + ### Required Files - **SKILL.md** - Main skill definition (required) @@ -119,7 +125,7 @@ cat > skills/my-skill/SKILL.md << 'EOF' --- name: My Skill description: What this skill does -version: 1.0.0 +version: tags: - category --- @@ -240,6 +246,7 @@ tags: ### Manual Testing Checklist - [ ] SKILL.md has valid YAML frontmatter +- [ ] SKILL.md version matches the root VERSION file - [ ] All dependencies are documented - [ ] Instructions are clear and step-by-step - [ ] Examples run without errors @@ -350,7 +357,8 @@ Future skills to consider: A: Yes, list it in `dependencies` and reference it in instructions. ### Q: How do I version a skill? -A: Use semantic versioning (MAJOR.MINOR.PATCH) in frontmatter. +A: Use the repository release in the root `VERSION` file. All skills advance together; dependency +versions remain independent. ### Q: Can I use external APIs in a skill? A: Yes, but document them clearly in prerequisites. @@ -369,5 +377,5 @@ A: Use resource files (JSON) with network-specific configs. --- -**Last Updated**: 2026-02-09 +**Last Updated**: 2026-08-26 **Maintainer**: Bank of AI Team diff --git a/CHANGELOG.md b/CHANGELOG.md index 933dc86..77e111c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,25 @@ # Changelog +## [2.0.0] - 2026-08-26 + +### Breaking Changes + +- Removed `multisig-permissions`, `trc20-toolkit-skill`, `trx-staking-skill`, `twitter-digest`, and + `twitter-mcp`. Generic TRON workflows are now handled by `wallet-cli`; X/Twitter workflows no + longer belong to this DeFi-focused Skills collection. +- Unified every retained Skill version with the repository release version. + +### Features + +- Added the `wallet-cli` skill for machine-readable TRON wallet, transfer, staking, governance, + contract, signing, permission, and chain-query operations. +- Added a root version source, repository versioning policy, and CI consistency check. + +### Security + +- Preserved exact-token-approval, staking/unfreezing, voting, and permission-lockout guidance in + the consolidated `wallet-cli` safety reference. + ## [1.5.9] - 2026-07-09 ### Improvements diff --git a/README.md b/README.md index 2eaea88..6f2de69 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,9 @@ A curated collection of practical, DeFi-focused utility skills developed by the BANK OF AI Skills are reusable, task-oriented capabilities that teach AI agents how to perform specific blockchain workflows. Each skill encapsulates domain knowledge (like SunSwap pathfinding) and provides step-by-step instructions for the agent to follow. +All skills share the repository release version in [`VERSION`](./VERSION). External CLI and SDK +versions remain independent dependencies. See [`VERSIONING.md`](./VERSIONING.md) for the policy. + ## Installation Use the standard installer and follow the `npx` prompts to select the skills you want and the agentic platform you use. This installs the selected Skill definitions but does not install their external CLI dependencies. @@ -78,13 +81,10 @@ Some skills require wallet signature operations and are built on Agent Wallet. B ### Security & Permissions - [**agent-wallet**](./agent-wallet) - Wallet management and signing skill for creating wallets, switching active wallets, resolving addresses, and signing transactions or messages on EVM and TRON. See [README](./agent-wallet/README.md). -- [**multisig-permissions**](./multisig-permissions) - TRON native account permission and multi-signature workflow skill. Supports permission inspection, template-based updates, proposal creation, co-signing, review, and execution. See [README](./multisig-permissions/README.md). ### Data & Analytics - [**tronscan-skill**](./tronscan-skill) - Comprehensive TRON blockchain data lookup via TronScan API. Supports accounts, transactions, tokens, blocks, and network-wide statistics. See [README](./tronscan-skill/README.md). -- [**trc20-toolkit-skill**](./trc20-toolkit-skill) - Universal TRC20 token toolkit for TRON. Supports metadata lookup, balance checks, transfers, and allowance management. See [README](./trc20-toolkit-skill/README.md). -- [**trx-staking-skill**](./trx-staking-skill) - TRX staking workflow skill for Tron Power, vote delegation, reward queries, and SR operations. See [README](./trx-staking-skill/README.md). - [**usdd-skill**](./usdd-skill) - USDD toolkit for balance checks, PSM inspection, swaps, and vault information. See [README](./usdd-skill/README.md). ### Guides & Onboarding @@ -97,13 +97,10 @@ Each current skill now has its own README for quick discovery: - [agent-wallet/README.md](./agent-wallet/README.md) - [bankofai-guide/README.md](./bankofai-guide/README.md) -- [multisig-permissions/README.md](./multisig-permissions/README.md) - [recharge-skill/README.md](./recharge-skill/README.md) - [sunperp-skill/README.md](./sunperp-skill/README.md) - [sunswap/README.md](./sunswap/README.md) -- [trc20-toolkit-skill/README.md](./trc20-toolkit-skill/README.md) - [tronscan-skill/README.md](./tronscan-skill/README.md) -- [trx-staking-skill/README.md](./trx-staking-skill/README.md) - [usdd-skill/README.md](./usdd-skill/README.md) - [wallet-cli/README.md](./wallet-cli/README.md) - [x402-payment/README.md](./x402-payment/README.md) diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..227cea2 --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +2.0.0 diff --git a/VERSIONING.md b/VERSIONING.md new file mode 100644 index 0000000..b5d6197 --- /dev/null +++ b/VERSIONING.md @@ -0,0 +1,49 @@ +# Versioning + +This repository uses one release version for the complete Skills collection. + +## Source of truth + +The root [`VERSION`](./VERSION) file is the source of truth. Its value uses Semantic Versioning +without a leading `v`. + +The same version must appear in: + +- every top-level `*/SKILL.md` frontmatter `version` field; +- every top-level skill `package.json` that declares a version; +- the newest entry in `CHANGELOG.md`. + +Release tags add the conventional prefix: version `2.0.0` is tagged `v2.0.0`. Do not create +independent per-skill release tags. A change to any skill is released as a new repository version, +and all skill frontmatter versions advance together even when a particular skill did not change. + +## Dependency versions + +External CLI, SDK, runtime, and service versions are independent of the repository release. Keep +them in each skill's `dependencies`, prerequisites, and installer. Financial execution CLIs should +normally use an exact tested version; use a range only when the skill intentionally supports and +tests that complete range. + +For example: + +```yaml +version: 2.0.0 +dependencies: + - "@tron-walletcli/wallet-cli@4.12.0" +``` + +Here `2.0.0` is the BofAI Skills release and `4.12.0` is the independently maintained CLI version. + +## Release rules + +- Patch: documentation corrections and backward-compatible fixes. +- Minor: backward-compatible skill additions or workflow improvements. +- Major: removed skills, renamed skills, incompatible trigger or workflow changes, or changed + authorization/security contracts. + +Before opening a release, update `VERSION`, all skill and package mirrors, and `CHANGELOG.md`, then +run: + +```bash +sh scripts/check_versions.sh +``` diff --git a/agent-wallet/SKILL.md b/agent-wallet/SKILL.md index cab503a..35d1125 100644 --- a/agent-wallet/SKILL.md +++ b/agent-wallet/SKILL.md @@ -1,6 +1,7 @@ --- name: agent-wallet description: 'Use when the user asks to create a wallet, check wallet addresses, sign transactions or messages, switch active wallets, or perform any blockchain signing operation using the agent-wallet CLI. Supports EVM and TRON networks.' +version: 2.0.0 compatibility: Requires Node.js 20+. metadata: author: BofAI diff --git a/bankofai-guide/SKILL.md b/bankofai-guide/SKILL.md index 334ce5e..b0a5ac8 100644 --- a/bankofai-guide/SKILL.md +++ b/bankofai-guide/SKILL.md @@ -1,7 +1,7 @@ --- name: bankofai-guide description: "Onboarding guide for BofAI skills — handles post-install setup, AgentWallet creation, and wallet guard when no wallet is configured." -version: 1.0.0 +version: 2.0.0 tags: - bankofai - onboarding diff --git a/multisig-permissions/.gitignore b/multisig-permissions/.gitignore deleted file mode 100644 index 504afef..0000000 --- a/multisig-permissions/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -node_modules/ -package-lock.json diff --git a/multisig-permissions/README.md b/multisig-permissions/README.md deleted file mode 100644 index 013c313..0000000 --- a/multisig-permissions/README.md +++ /dev/null @@ -1,235 +0,0 @@ -# Multi-Sig & Account Permissions - -🔐 Configure TRON's native account permission system and coordinate multi-signature transactions. - -## Quick Start - -```bash -cd multisig-permissions && npm install -export TRON_PRIVATE_KEY="" -export TRON_NETWORK="mainnet" - -# Check current permissions -node scripts/status.js - -# Set up 2-of-3 multi-sig -node scripts/update.js from-template basic-2of3 \ - --key1 TKey1... --key2 TKey2... --key3 TKey3... --dry-run - -# If the current owner is already multi-sig, permission updates create a -# pending owner proposal for co-signers instead of broadcasting immediately. -node scripts/approve.js prop_xxxxx_xxxx -node scripts/execute.js prop_xxxxx_xxxx - -# Multi-sig transaction flow -node scripts/propose.js transfer TRecipient... 10000 --memo "Payment" -node scripts/approve.js prop_xxxxx_xxxx -node scripts/execute.js prop_xxxxx_xxxx -``` - -Always derive the address from each private key and confirm it matches the intended owner / active role on-chain before signing. Environment variable names are only labels; permission authority comes from the derived address in the account configuration. - -## Scripts - -- **status.js** — View account permission configuration and security analysis -- **update.js** — Modify permissions (add/remove keys, set thresholds, scope operations, apply templates) -- **propose.js** — Create multi-sig transaction proposals (TRX transfer, TRC20 transfer, contract call) -- **approve.js** — Add your signature to a pending proposal -- **execute.js** — Broadcast a fully-signed transaction to the network -- **pending.js** — List and filter pending multi-sig proposals -- **review.js** — Human CLI: list, inspect, co-sign, and execute proposals in one tool - -For `propose.js`, use `--account ` when the signer key is acting through another account's active permission. For owner multi-sig permission changes, `update.js` will create a pending owner proposal that must be completed via `approve.js` and `execute.js`. - -## Templates - -Keys are numbered positionally across all roles in the template (owner first, then active-only roles): - -| Template | Key mapping | Description | -|----------|-------------|-------------| -| `basic-2of3` | `--key1`..`--key3` (owner keys) | Standard 2-of-3 multi-sig | -| `agent-restricted` | `--key1` HUMAN, `--key2` BACKUP (owner), `--key3` AGENT (active) | Agent limited to smart contract calls only | -| `team-tiered` | `--key1`..`--key5` (owner keys, first 3 reused in active) | 3-of-5 owner, 2-of-3 active for daily ops | -| `weighted-authority` | `--key1` PRIMARY (wt 2), `--key2`..`--key3` SECONDARY (wt 1) | Primary key has extra weight | - -## Demo: Hybrid Signature Workflow (Human + Agent) - -This walkthrough demonstrates a 2-of-2 multi-sig setup where **Key A** is held by a human and **Key B** is held by the agent. The agent initiates a TRX transfer proposal, and the human reviews and co-signs it before broadcast. - -**Setup:** Account `TJf2n7Wq...` is configured with 2-of-2 owner permission: -- Key A (human): `THumanKey8xR2mVp...` (weight 1) -- Key B (agent): `TAgentKeyQ9nZw3...` (weight 1) -- Owner threshold: 2 - ---- - -### Step 1 — Agent proposes a transfer - -The agent builds the transaction and signs it with Key B. - -```bash -# Agent's environment -$ export TRON_PRIVATE_KEY="" -$ node scripts/propose.js transfer TRecipientAddr... 500 --memo "Q1 vendor payment" -``` -``` -Building transfer transaction (permission: owner, threshold: 2) ... -Signing with caller key ... -Proposal saved to ~/.clawdbot/multisig/pending/prop_1710345600_b7e4.json -``` -```json -{ - "proposalId": "prop_1710345600_b7e4", - "description": "Transfer 500 TRX to TRecipientAddr...", - "memo": "Q1 vendor payment", - "permission": "owner", - "threshold": 2, - "signatures": { - "collected": 1, - "collected_weight": 1, - "required_weight": 2, - "threshold_met": false - }, - "expires": "2026-03-14T12:00:00.000Z", - "saved_to": "~/.clawdbot/multisig/pending/prop_1710345600_b7e4.json", - "next_step": "Share proposal ID with co-signers: node approve.js prop_1710345600_b7e4" -} -``` - -The agent shares `prop_1710345600_b7e4` with the human for review. - ---- - -### Step 2 — Human reviews the pending proposal - -The human inspects the proposal before signing. - -```bash -# Human's environment (no private key needed for read-only review) -$ node scripts/review.js -``` -``` -Found 1 pending proposal(s): - - [AWAITING] prop_1710345600_b7e4 - Transfer 500 TRX to TRecipientAddr... - Signatures: 1/2 | Time left: 23h 58m - -> Review: node review.js prop_1710345600_b7e4 --sign -``` - -```bash -# Inspect details (still no key needed) -$ node scripts/review.js prop_1710345600_b7e4 -``` -``` -=== PROPOSAL DETAILS === - - ID: prop_1710345600_b7e4 - Description: Transfer 500 TRX to TRecipientAddr... - Memo: Q1 vendor payment - Permission: owner (id=0) - Created: 2026-03-13T12:00:00.000Z - Expires: 2026-03-14T12:00:00.000Z (23h 58m) - ---- Signatures --- - Threshold: 2 (weight required) - Collected: 1/2 weight from 1 signer(s) - Status: AWAITING SIGNATURES - - Signers so far: - - TAgentKeyQ9nZw3... (weight 1) - - All authorized keys: - - THumanKey8xR2mVp... (weight 1) [pending] - - TAgentKeyQ9nZw3... (weight 1) [SIGNED] -``` - -The human verifies: correct recipient, correct amount, correct memo. - ---- - -### Step 3 — Human co-signs the proposal - -```bash -$ export TRON_HUMAN_PRIVATE_KEY="" -$ node scripts/review.js prop_1710345600_b7e4 --sign -``` -``` -=== PROPOSAL DETAILS === - ... -Signing proposal with THumanKey8xR2mVp... ... -Signature added (weight 1). Total weight: 2/2 -``` -```json -{ - "proposalId": "prop_1710345600_b7e4", - "action": "signed", - "signer": "THumanKey8xR2mVp...", - "signatures": { - "collected": 2, - "collected_weight": 2, - "required_weight": 2, - "threshold_met": true - }, - "next_step": "Ready! Execute: node review.js prop_1710345600_b7e4 --sign --execute" -} -``` - ---- - -### Step 4 — Either party executes (broadcasts) the transaction - -```bash -$ node scripts/review.js prop_1710345600_b7e4 --sign --execute -``` -``` -Broadcasting transaction ... -Transaction submitted: 9c4a2f8e1d3b5a7096e8f2c4d6a8b0e2...abcdef1234567890 -Proposal archived to ~/.clawdbot/multisig/executed/prop_1710345600_b7e4.json -``` -```json -{ - "proposalId": "prop_1710345600_b7e4", - "action": "signed_and_executed", - "signer": "THumanKey8xR2mVp...", - "signatures": 2, - "threshold": 2, - "status": "submitted", - "tx_id": "9c4a2f8e1d3b5a7096e8f2c4d6a8b0e2...abcdef1234567890", - "archived_to": "~/.clawdbot/multisig/executed/prop_1710345600_b7e4.json" -} -``` - ---- - -### Step 5 — Verify no pending proposals remain - -```bash -$ node scripts/review.js -``` -``` -No pending proposals. -The agent can create one with: node propose.js transfer -``` - -### Summary - -| Step | Actor | Script | What happens | -|------|-------|--------|-------------| -| 1 | Agent (Key B) | `propose.js` | Builds tx, signs with Key B, saves proposal | -| 2 | Human (Key A) | `review.js` | Reviews proposal details (no key needed) | -| 3 | Human (Key A) | `review.js --sign` | Co-signs with Key A, threshold met (2/2) | -| 4 | Either | `review.js --sign --execute` | Broadcasts fully-signed tx to TRON network | - -## Dependencies - -- Node.js >= 18.0.0 -- tronweb ^6.0.0 - -## Version - -1.0.0 (March 2026) - -## License - -MIT — see LICENSE for details. diff --git a/multisig-permissions/SKILL.md b/multisig-permissions/SKILL.md deleted file mode 100644 index 0a9608d..0000000 --- a/multisig-permissions/SKILL.md +++ /dev/null @@ -1,251 +0,0 @@ ---- -name: Multi-Sig & Account Permissions -description: Manage TRON multi-sig permissions — configure keys, thresholds, and co-signed proposals. -version: 1.0.0 -dependencies: - - node >= 18.0.0 - - tronweb -tags: - - tron - - security - - multisig - - permissions - - account-management ---- - -# Multi-Sig & Account Permissions - -Manage TRON's native three-tier permission model (Owner, Active, Witness) with built-in multi-sig support at the protocol level — no external contracts needed. Configure keys, thresholds, and operation scopes, then coordinate multi-signature transactions through a propose → approve → execute flow. - ---- - -## Quick Start - -> **Wallet required:** Run `agent-wallet list` first. -> If no wallets exist, invoke `bankofai-guide` (Section C — Wallet Guard) before proceeding. - -```bash -cd multisig-permissions && npm install -export TRON_PRIVATE_KEY="" -export TRON_NETWORK="mainnet" -``` - -Before using any signer-specific command, derive the address from the configured key and confirm it matches the intended owner / active role on-chain. Variable names like `TRON_PRIVATE_KEY` and `TRON_HUMAN_PRIVATE_KEY` are only labels; the actual signer role is determined by the derived address in the current account permission set. - -> [!CAUTION] -> Misconfiguring **owner permissions** can permanently lock an account with no recovery. Always use `--dry-run` first and verify you control enough keys to meet the new threshold. - ---- - -## Available Scripts - -| Script | Purpose | Reads/Writes | -|--------|---------|-------------| -| `status.js` | View current permission configuration | Read-only | -| `update.js` | Modify account permissions (add/remove keys, thresholds, scope) | **Write** | -| `propose.js` | Create a multi-sig transaction proposal | **Write** | -| `approve.js` | Add your signature to a pending proposal | **Write** | -| `execute.js` | Broadcast a fully-signed transaction | **Write** | -| `pending.js` | List pending multi-sig proposals | Read-only | -| `review.js` | Human CLI: list, inspect, co-sign, and execute proposals in one tool | **Write** | - ---- - -## TRON Permission Model - -Every TRON account has three permission tiers: - -| Permission | Type | Purpose | Default | -|-----------|------|---------|---------| -| **Owner** | 0 | Full control. Only permission that can modify other permissions. | Account key, threshold 1 | -| **Active** | 2 | Day-to-day operations. Can be scoped to specific transaction types. | Account key, threshold 1, all ops | -| **Witness** | 1 | Block production (Super Representatives only). | Not set | - -Each permission has **keys** (address + weight pairs) and a **threshold** (minimum total weight to authorize). Example: 3 keys with weight 1 each and threshold 2 = 2-of-3 multi-sig. - ---- - -## Usage Patterns - -### Pattern 1: Inspect Account Security - -```bash -# Check your own account -node scripts/status.js - -# Check any account -node scripts/status.js TXk8rQSAvPvBBNtqSoY3UkFdpMTMbqRMKU -``` - -### Pattern 2: Set Up Multi-Sig (2-of-3) - -```bash -# Step 1: Preview changes (always dry-run first!) -node scripts/update.js add-key TKey2Address... --permission owner --weight 1 --dry-run -node scripts/update.js add-key TKey3Address... --permission owner --weight 1 --dry-run -node scripts/update.js set-threshold 2 --permission owner --dry-run - -# Step 2: Or use a template for all at once -node scripts/update.js from-template basic-2of3 \ - --key1 TKey1... --key2 TKey2... --key3 TKey3... --dry-run - -# Step 3: Execute when satisfied -node scripts/update.js from-template basic-2of3 \ - --key1 TKey1... --key2 TKey2... --key3 TKey3... -``` - -### Pattern 3: Restrict Agent to DeFi Only - -```bash -# Preview the restricted owner + active configuration -node scripts/update.js from-template agent-restricted \ - --key1 THumanKey... --key2 TBackupKey... --key3 TAgentKey... --dry-run - -# Apply it from a single-owner account -node scripts/update.js from-template agent-restricted \ - --key1 THumanKey... --key2 TBackupKey... --key3 TAgentKey... - -# If the current owner is already multi-sig, update.js creates a pending owner -# proposal instead of broadcasting directly. Finish it through the normal flow. -node scripts/approve.js prop_xxxxx_xxxx -node scripts/execute.js prop_xxxxx_xxxx -``` - -### Pattern 4: Multi-Sig Transaction Flow - -```bash -# Signer 1: Propose a transfer -node scripts/propose.js transfer TRecipient... 10000 --memo "Q1 budget" - -# Signer 2: Review and approve -node scripts/approve.js prop_1709312400_a3f2 - -# Any signer: Execute when threshold is met -node scripts/execute.js prop_1709312400_a3f2 - -# Check all pending proposals -node scripts/pending.js -``` - -### Pattern 5: Hybrid Signature (Human + Agent) - -The `review.js` script is a single CLI tool for humans to list, inspect, co-sign, and execute agent-created proposals. It uses `TRON_HUMAN_PRIVATE_KEY` (not `TRON_PRIVATE_KEY`) to avoid mixing up human and agent keys. - -```bash -# Agent (Key B) proposes a transfer from its own account (uses TRON_PRIVATE_KEY): -node scripts/propose.js transfer TRecipient... 500 --memo "Q1 vendor payment" -# → Outputs proposal ID: prop_1710345600_b7e4 - -# Agent (Key B) proposes an active-scoped contract call for a controlled -# multi-sig account. --account identifies the controlled account. -node scripts/propose.js contract-call TXLAQ63Xg1NAzckPwKHvzw7CSEmLMEqcdj \ - "approve(address,uint256)" '["TSpender...","1000000"]' \ - --permission active \ - --account TControlledMultisig... - -# Human (Key A) reviews all pending proposals (no key needed): -node scripts/review.js - -# Human inspects a specific proposal (read-only, no key needed): -node scripts/review.js prop_1710345600_b7e4 - -# Human co-signs after verifying details (uses TRON_HUMAN_PRIVATE_KEY): -export TRON_HUMAN_PRIVATE_KEY="" -node scripts/review.js prop_1710345600_b7e4 --sign - -# Human co-signs AND broadcasts in one step: -node scripts/review.js prop_1710345600_b7e4 --sign --execute -``` - -### Pattern 6: Scope Active Permission Operations - -```bash -# Restrict active permission to only TRX transfers and smart contract calls -node scripts/update.js scope-active --id 2 \ - --operations TransferContract,TriggerSmartContract --dry-run -``` - ---- - -## Permission Templates - -Templates use `--keyN` flags to assign addresses to named roles. Keys are numbered **positionally across owner and active roles** (deduplicated, owner first). You can also use `--role_name` directly (e.g. `--agent_key TAddr...`). - -| Template | Config | Key mapping | Description | -|----------|--------|-------------|-------------| -| `basic-2of3` | 2-of-3 owner, 1-of-1 active (all ops) | `--key1`=KEY_1, `--key2`=KEY_2, `--key3`=KEY_3 | Standard multi-sig | -| `agent-restricted` | 2-of-2 owner, 1-of-1 active (TriggerSmartContract only) | `--key1`=HUMAN_KEY, `--key2`=BACKUP_KEY, `--key3`=AGENT_KEY | Agent can only call contracts | -| `team-tiered` | 3-of-5 owner, 2-of-3 active (transfers + contracts) | `--key1`..`--key5`=KEY_1..KEY_5 | Team with tiered access | -| `weighted-authority` | Threshold 3: primary (wt 2) + secondaries (wt 1) | `--key1`=PRIMARY_KEY, `--key2`=SECONDARY_1, `--key3`=SECONDARY_2 | Weighted key authority | - ---- - -## Transaction Types (Operations) - -Used with `scope-active --operations`: - -| Operation | Description | -|-----------|-------------| -| `TransferContract` | Transfer TRX | -| `TransferAssetContract` | Transfer TRC10 tokens | -| `TriggerSmartContract` | Call any smart contract (DeFi, tokens, etc.) | -| `FreezeBalanceV2Contract` | Stake TRX for energy/bandwidth | -| `UnfreezeBalanceV2Contract` | Unstake TRX | -| `DelegateResourceContract` | Delegate energy/bandwidth | -| `VoteWitnessContract` | Vote for Super Representatives | -| `AccountPermissionUpdateContract` | Modify permissions (very dangerous in Active) | - -See `resources/permission_config.json` for the full list. - ---- - -## Security Rules - -> [!WARNING] -> **Lockout prevention**: The `update.js` script refuses to execute if the new threshold exceeds the total key weight. However, you MUST verify you actually control the keys — the script cannot check this. - -1. **Always dry-run first**: Every write operation supports `--dry-run`. -2. **Owner changes are irreversible**: Only the owner key set can modify permissions. If you lock yourself out, the account is permanently lost. -3. **Verify key control**: Before changing owner permissions, confirm you have access to enough keys to meet the new threshold. -4. **Scope active permissions**: Don't give agents all-operations access. Use `scope-active` to limit to `TriggerSmartContract`. -5. **Proposals expire**: Default expiry is 24 hours. Expired proposals cannot be executed. -6. **Check signer roles explicitly**: Before `approve.js`, `review.js --sign`, or any active-scoped `propose.js --account` call, confirm the configured private key derives to an address that actually appears in that permission block. - ---- - -## Multi-Sig Proposal Storage - -Proposals are stored locally at `~/.clawdbot/multisig/pending/` as JSON files. After execution, they are archived to `~/.clawdbot/multisig/executed/`. - -For multi-party setups, share the proposal ID with co-signers who have access to the same file system, or copy the proposal JSON file to them. - ---- - -## Common Issues - -### "LOCKOUT DANGER: threshold exceeds total key weight" -The script prevented a dangerous permission change. Reduce the threshold or add more keys. - -### "Account not found or not activated" -The address has no on-chain account. Send at least 0.1 TRX to activate it. - -### "Proposal has expired" -The proposal's 24-hour window has passed. Create a new proposal with `propose.js`. - -### "Threshold not met" -The proposal needs more signatures. Check `pending.js` to see remaining weight needed. - ---- - -## Environment Variables - -| Variable | Required | Description | -|----------|----------|-------------| -| `TRON_PRIVATE_KEY` | Yes (write scripts) | Private key used by `propose.js`, `approve.js`, `execute.js`, and `update.js`. The derived address must match the intended owner or active signer role. | -| `TRON_HUMAN_PRIVATE_KEY` | Yes (`review.js --sign`) | Private key used by `review.js --sign`. This should be set to the human reviewer's key and validated by derived address, not by variable name alone. | -| `TRON_NETWORK` | No (default: mainnet) | `mainnet`, `nile`, or `shasta` | -| `TRONGRID_API_KEY` | No | TronGrid API key for higher rate limits | - ---- - -*Version 1.0.0 — Created by [M2M Agent Registry](https://m2mregistry.io) for Bank of AI* diff --git a/multisig-permissions/USE_CASES.md b/multisig-permissions/USE_CASES.md deleted file mode 100644 index ef7b436..0000000 --- a/multisig-permissions/USE_CASES.md +++ /dev/null @@ -1,184 +0,0 @@ -# Multi-Sig & Account Permissions — Use Cases & Ecosystem Benefits - -## Use Cases - -### 1. Securing an Autonomous Agent Wallet - -**Scenario**: An operator deploys an AI agent on TRON that trades on SunSwap, lends on JustLend, and earns yield. The agent controls a wallet with $50,000 in assets. A single compromised key would mean total loss. - -**How it works**: -- The operator starts with a standard single-key wallet and runs `status.js` to confirm the current (insecure) configuration -- Using the `agent-restricted` template via `update.js from-template`, the operator sets up: - - **Owner**: 2-of-2 multi-sig with the operator's cold wallet and a hardware wallet backup — neither key is on a hot server - - **Active**: 1-of-1 with the agent's hot key, scoped to `TriggerSmartContract` only -- The agent can call DeFi contracts freely (swap, lend, stake) but **cannot** transfer TRX, change permissions, or do anything outside smart contract calls -- If the agent's hot key is compromised, the attacker can interact with DeFi contracts but cannot drain TRX or change the wallet's security configuration -- The operator retains full control through the 2-of-2 owner permission using offline keys -- If the account is already owner-multisig when this change is applied, `update.js` creates a pending owner proposal and the operator completes it through `approve.js` and `execute.js` - -**Who benefits**: -- **The agent operator** sleeps at night knowing a compromised agent key has limited blast radius -- **The agent** operates autonomously within its scoped permissions without needing operator approval for every DeFi transaction -- **The ecosystem** has fewer catastrophic key compromises, which reduces negative sentiment and contagion risk - ---- - -### 2. Multi-Agent Team Treasury - -**Scenario**: A team of three AI agents manages a shared treasury. Each agent specializes in a different domain (trading, lending, staking). No single agent should be able to unilaterally move treasury funds. - -**How it works**: -- The treasury wallet is configured with `update.js from-template team-tiered`: - - **Owner**: 3-of-5 multi-sig across team members' cold wallets (maximum security for permission changes) - - **Active (operations)**: 2-of-3 multi-sig across the three agents (any two must agree to execute) -- When the trading agent wants to move 10,000 TRX to a new pool: - 1. It creates a proposal: `propose.js transfer TPoolAddr... 10000 --permission active --memo "Rebalance to new USDD pool"` - 2. The lending agent reviews and signs: `approve.js prop_xxxxx` - 3. With 2-of-3 threshold met, either agent executes: `execute.js prop_xxxxx` -- The staking agent can review pending proposals via `pending.js` at any time -- All proposals have a 24-hour expiry, preventing stale transactions from being executed in changed market conditions - -**Who benefits**: -- **The treasury** is protected from any single agent going rogue or being compromised -- **Each agent** maintains autonomy in its domain while requiring peer approval for treasury movements -- **The team** has a complete audit trail of every proposal, approval, and execution - ---- - -### 3. Human-Agent Hybrid Operations - -**Scenario**: A DeFi power user wants an AI agent to manage their daily operations (swapping, claiming rewards, compounding yield) while retaining personal control over large transfers and security settings. - -**How it works**: -- `update.js` configures two active permissions: - - **Active:2 (agent-ops)**: Agent's key, threshold 1, scoped to `TriggerSmartContract` + `FreezeBalanceV2Contract` + `UnfreezeBalanceV2Contract` — the agent can trade, stake, and unstake - - **Active:3 (transfers)**: Human's key, threshold 1, scoped to `TransferContract` + `TransferAssetContract` — only the human can send TRX or TRC10 tokens - - **Owner**: Human's cold key, threshold 1 — full control reserved for the human -- The agent handles daily yield optimization without needing the human's key -- When the agent needs to move profits to cold storage, the current repository scripts do not yet expose a permission-id selector beyond the default active id=2. In this layout, the human should create the transfer proposal with the transfer-scoped key, or the team should extend the tooling before relying on multi-active-id routing. -- The human reviews the proposal, approves if correct, and the transfer executes through the standard proposal flow. - -**Who benefits**: -- **The user** gets automated DeFi management without giving up control over fund movements -- **The agent** operates efficiently within defined boundaries -- **The relationship** between human and agent has clear, enforceable trust boundaries — no ambiguity about what the agent can and cannot do - ---- - -### 4. Key Rotation Without Downtime - -**Scenario**: An agent's private key has been in use for 6 months. Best practice is to rotate keys periodically, but the agent can't stop operating and the wallet address must stay the same (it's referenced in contracts, has reputation on-chain, holds positions). - -**How it works**: -- The operator generates a new key pair for the agent -- `update.js add-key TNewAgentKey... --permission active --weight 1` adds the new key alongside the old one -- The agent switches to using the new key for all operations -- After confirming the new key works: `update.js remove-key TOldAgentKey... --permission active` removes the old key -- The wallet address never changes — all positions, approvals, and on-chain reputation are preserved -- If owner is already multi-sig, these update operations become owner proposals and must go through `approve.js` and `execute.js` - -**Who benefits**: -- **Security** improves through regular key rotation without operational disruption -- **The agent** maintains continuous uptime and all its on-chain state -- **The operator** follows security best practices that are impossible with single-key accounts on most chains - ---- - -### 5. Progressive Decentralization of Agent Control - -**Scenario**: A startup launches an AI agent service with centralized control initially, then progressively decentralizes control to the community as trust is established. - -**How it works**: -- **Phase 1 (Launch)**: Owner is a 2-of-3 multi-sig among the founding team. Active is a single agent key with all operations. -- **Phase 2 (Growth)**: `update.js set-threshold 3 --permission owner` creates an owner proposal that raises the owner threshold to 3-of-3 after co-sign + execute. Agent key gets scoped via `scope-active` to only DeFi operations. -- **Phase 3 (Community)**: `update.js add-key TCommunityMultisig... --permission owner --weight 2` adds a community-controlled multi-sig to the owner permission with higher weight. Team members' weights stay at 1. Threshold increases to 3, meaning the community multi-sig (weight 2) plus any one team member (weight 1) can approve changes. -- **Phase 4 (Full Decentralization)**: Team keys are removed. Owner permission is entirely controlled by community governance. -- Each phase transition is documented through the proposal system, creating a verifiable decentralization timeline. - -**Who benefits**: -- **The startup** launches quickly without complex governance, then decentralizes as appropriate -- **The community** gains verifiable, increasing control over time -- **The TRON ecosystem** gets a template for progressive decentralization that other projects can follow - ---- - -### 6. Emergency Recovery Setup - -**Scenario**: An agent operator wants a dead-man's-switch style recovery mechanism — if their primary key is lost or compromised, a pre-configured backup can recover the account. - -**How it works**: -- `update.js from-template basic-2of3` configures: - - Key 1: Operator's daily-use key (weight 1) - - Key 2: Hardware wallet stored in a safe (weight 1) - - Key 3: Trusted third party or time-locked social recovery key (weight 1) - - Threshold: 2 -- In normal operation, the operator uses Key 1 + Key 2 (hardware wallet) for sensitive operations -- If Key 1 is compromised: Key 2 + Key 3 can remove Key 1 and add a replacement -- If Key 1 is lost: Same recovery path via Key 2 + Key 3 -- The compromised/lost key never had unilateral control, so funds were never at risk -- `status.js` lets anyone verify the recovery configuration is in place - -**Who benefits**: -- **Operators** have a clear recovery path for lost or compromised keys -- **The agent** continues operating even through key loss events (after recovery) -- **The ecosystem** sees fewer permanently lost accounts, maintaining active TVL - ---- - -### 7. Auditable Multi-Party DeFi Operations - -**Scenario**: An investment club of 5 members pools funds into a TRON wallet. An AI agent manages the portfolio, but every significant action requires member approval. - -**How it works**: -- Owner: 4-of-5 multi-sig across all members (for security changes) -- Active: 3-of-5 multi-sig across all members (for fund movements) -- The AI agent has an advisory role — it analyzes opportunities and creates proposals: - - `propose.js contract-call TSunSwapRouter... "swapExactTokensForTokens(...)" '[...]' --permission owner --memo "Rebalance: sell 20% ETH position for USDD at current price $3,200"` -- Members review proposals via `pending.js`, discuss the agent's reasoning, and sign with `approve.js` -- When 3 members approve, any member or the agent can execute -- Complete history is maintained in `~/.clawdbot/multisig/executed/` — every proposal, every signature, every execution - -**Who benefits**: -- **Club members** have equal control and complete transparency over shared funds -- **The AI agent** contributes analysis and execution without having unilateral authority -- **Accountability** is built-in — every action has a paper trail of who proposed, who approved, and when - ---- - -## Ecosystem Benefits - -### For the TRON Network - -**Institutional readiness**: Institutions and funds evaluating TRON need multi-sig capabilities. TRON's native permission system is more powerful than Ethereum's (which requires separate smart contract wallets like Safe), but it's been underutilized because tooling was limited. This skill unlocks TRON's built-in advantage, making it an attractive chain for institutional capital. - -**Reduced key compromise impact**: Every compromised single-key wallet on TRON is a negative headline. Multi-sig adoption across agent wallets dramatically reduces the blast radius of key compromises. A compromised key in a 2-of-3 setup means zero funds lost — the attacker can't do anything alone. - -**On-chain governance alignment**: Agents with multi-sig wallets holding governance tokens can coordinate votes more securely. The propose → approve → execute flow for governance voting ensures that delegated voting power is exercised with proper authorization. - -**Network value preservation**: Permanently lost wallets (lost keys) represent locked, unusable value on the network. Multi-sig with recovery setups means fewer wallets are permanently lost, keeping more value active and circulating on TRON. - -### For AI Agents - -**Trust foundation**: The entire agent economy depends on trust. An agent that can prove its wallet is secured by multi-sig, with its key scoped to only smart contract calls, is fundamentally more trustworthy than one with an unrestricted single-key wallet. This verifiable security configuration (readable by any agent via `status.js`) becomes a trust signal — complementing the ERC-8004 identity and reputation system. - -**Operational boundaries**: The active permission scoping system gives agents well-defined operational boundaries. An agent authorized only for `TriggerSmartContract` literally cannot drain TRX or change permissions at the protocol level. This constraint is not a policy the agent promises to follow — it's enforced by the TRON blockchain itself. - -**Inter-agent coordination**: The propose → approve → execute pattern is the foundation for multi-agent workflows. Two agents managing a shared portfolio can coordinate actions without trusting each other with full key access. This unlocks collaborative agent strategies that are impossible with single-key wallets. - -**Graceful key management**: Agents have finite lifetimes — they get upgraded, retired, or replaced. The ability to add a new agent's key and remove the old one without changing the wallet address means agent succession is seamless. Positions, approvals, reputation, and on-chain history all carry over. - -### For DeFi Protocols on TRON - -**Reduced bad debt from hacks**: When a single-key wallet gets hacked and the attacker drains funds from lending protocols, the protocol may absorb bad debt. Multi-sig wallets are orders of magnitude harder to compromise, directly reducing bad debt risk for protocols. - -**Smarter TVL**: Multi-sig wallets tend to hold larger balances (they're used for treasuries and institutional funds). Attracting multi-sig wallets to TRON DeFi protocols means attracting stickier, larger capital that contributes more to TVL stability. - -**Composable authorization**: The scoped permission model means protocols can reason about what a wallet is authorized to do. A protocol could offer better terms to wallets that prove their agent key is scoped (verified via `status.js`), creating an on-chain credit tier system. - -### For the Broader Agent Economy - -**Standard for agent authorization**: The skill establishes a pattern that other chains and agent frameworks can adopt. "Agent keys should be scoped to minimum necessary operations, with human/cold keys retaining owner control" becomes a best practice for the entire autonomous agent industry. - -**Verifiable security posture**: Any participant in the agent economy can check any wallet's permission configuration on-chain. This transparency creates market pressure for better security — agents with weak permission setups will be trusted less, creating a race to the top for security standards. - -**Foundation for agent insurance**: Future agent insurance protocols need to assess risk. A wallet with 3-of-5 multi-sig and scoped agent keys is quantifiably lower risk than a single-key wallet. The permission configuration becomes an input to risk pricing, enabling an agent insurance market to emerge. diff --git a/multisig-permissions/package.json b/multisig-permissions/package.json deleted file mode 100644 index f4f6a35..0000000 --- a/multisig-permissions/package.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "name": "@bankofai/multisig-permissions-skill", - "version": "1.0.0", - "description": "TRON native multi-sig and account permission management for AI agents", - "license": "MIT", - "private": true, - "engines": { "node": ">=18.0.0" }, - "dependencies": { "tronweb": "^6.0.0" } -} diff --git a/multisig-permissions/resources/permission_config.json b/multisig-permissions/resources/permission_config.json deleted file mode 100644 index 5953784..0000000 --- a/multisig-permissions/resources/permission_config.json +++ /dev/null @@ -1,103 +0,0 @@ -{ - "operation_codes": { - "1": { "name": "TransferContract", "description": "Transfer TRX" }, - "2": { "name": "TransferAssetContract", "description": "Transfer TRC10 tokens" }, - "4": { "name": "VoteWitnessContract", "description": "Vote for Super Representatives" }, - "6": { "name": "AssetIssueContract", "description": "Issue TRC10 token" }, - "8": { "name": "AccountUpdateContract", "description": "Update account name" }, - "9": { "name": "FreezeBalanceContract", "description": "Freeze balance (v1, deprecated)" }, - "11": { "name": "FreezeBalanceV2Contract", "description": "Stake TRX for energy/bandwidth" }, - "12": { "name": "UnfreezeBalanceV2Contract", "description": "Unstake TRX" }, - "13": { "name": "WithdrawExpireUnfreezeContract", "description": "Withdraw expired unstaked TRX" }, - "30": { "name": "CreateSmartContract", "description": "Deploy a smart contract" }, - "31": { "name": "TriggerSmartContract", "description": "Call a smart contract" }, - "33": { "name": "UpdateSettingContract", "description": "Update smart contract settings" }, - "41": { "name": "ExchangeCreateContract", "description": "Create Bancor exchange" }, - "42": { "name": "ExchangeInjectContract", "description": "Inject into Bancor exchange" }, - "43": { "name": "ExchangeWithdrawContract", "description": "Withdraw from Bancor exchange" }, - "44": { "name": "ExchangeTransactionContract", "description": "Bancor exchange transaction" }, - "45": { "name": "UpdateEnergyLimitContract", "description": "Update smart contract energy limit" }, - "46": { "name": "AccountPermissionUpdateContract", "description": "Update account permissions" }, - "48": { "name": "DelegateResourceContract", "description": "Delegate energy/bandwidth" }, - "49": { "name": "UnDelegateResourceContract", "description": "Undelegate energy/bandwidth" }, - "51": { "name": "CancelAllUnfreezeV2Contract", "description": "Cancel all pending unstakes" } - }, - "templates": { - "basic-2of3": { - "description": "Standard 2-of-3 multi-sig owner, single-key active with all operations", - "owner": { - "threshold": 2, - "key_roles": ["KEY_1", "KEY_2", "KEY_3"], - "weights": [1, 1, 1] - }, - "active": [ - { - "name": "active0", - "threshold": 1, - "key_roles": ["KEY_1"], - "weights": [1], - "operations": "all" - } - ] - }, - "agent-restricted": { - "description": "Agent can only call smart contracts; owner retains 2-of-2 control", - "owner": { - "threshold": 2, - "key_roles": ["HUMAN_KEY", "BACKUP_KEY"], - "weights": [1, 1] - }, - "active": [ - { - "name": "agent-defi", - "threshold": 1, - "key_roles": ["AGENT_KEY"], - "weights": [1], - "operations": ["TriggerSmartContract"] - } - ] - }, - "team-tiered": { - "description": "3-of-5 owner, 2-of-3 active for daily operations", - "owner": { - "threshold": 3, - "key_roles": ["KEY_1", "KEY_2", "KEY_3", "KEY_4", "KEY_5"], - "weights": [1, 1, 1, 1, 1] - }, - "active": [ - { - "name": "operations", - "threshold": 2, - "key_roles": ["KEY_1", "KEY_2", "KEY_3"], - "weights": [1, 1, 1], - "operations": ["TransferContract", "TransferAssetContract", "TriggerSmartContract"] - } - ] - }, - "weighted-authority": { - "description": "Primary key has weight 2, secondaries weight 1, threshold 3", - "owner": { - "threshold": 3, - "key_roles": ["PRIMARY_KEY", "SECONDARY_1", "SECONDARY_2"], - "weights": [2, 1, 1] - }, - "active": [ - { - "name": "default", - "threshold": 1, - "key_roles": ["PRIMARY_KEY"], - "weights": [1], - "operations": "all" - } - ] - } - }, - "notes": { - "permission_types": "TRON accounts have three permission tiers: Owner (type 0), Witness (type 1, SR only), Active (type 2). Owner can change all permissions. Active can be scoped.", - "operations_bitmask": "Active permissions use a hex-encoded bitmask where bit N controls whether transaction type N is allowed. A 32-byte (256-bit) field.", - "default_permissions": "New accounts start with a single key on both owner and active (id=2) with threshold 1 and all operations enabled.", - "lockout_warning": "Setting threshold higher than the total weight of keys you control permanently locks the account. There is NO recovery mechanism.", - "permission_ids": "Owner permission has implicit id=0. Active permissions start at id=2. The id is assigned when the permission is created.", - "multi_sig_flow": "Build tx -> set Permission_id on the raw tx -> sign with first key -> addSign with more keys -> broadcast when threshold met." - } -} diff --git a/multisig-permissions/scripts/approve.js b/multisig-permissions/scripts/approve.js deleted file mode 100644 index b3ba679..0000000 --- a/multisig-permissions/scripts/approve.js +++ /dev/null @@ -1,109 +0,0 @@ -#!/usr/bin/env node - -/** - * approve.js — Add a signature to a pending multi-sig proposal. - * - * Usage: - * node approve.js - * node approve.js - * - * Examples: - * node approve.js prop_1709312400_a3f2 - * node approve.js ~/.clawdbot/multisig/pending/prop_1709312400_a3f2.json - */ - -const { - getTronWeb, loadProposal, saveProposal, outputJSON, log, -} = require("./utils"); - -async function main() { - const args = process.argv.slice(2); - if (args.length < 1) { - console.error("Usage: node approve.js "); - process.exit(1); - } - - const tronWeb = getTronWeb(); - const walletAddress = tronWeb.defaultAddress.base58; - const proposalRef = args[0]; - - log(`Loading proposal "${proposalRef}" ...`); - const proposal = loadProposal(proposalRef); - - // Display proposal details - log(""); - log(` Proposal: ${proposal.proposalId}`); - log(` Description: ${proposal.description}`); - if (proposal.memo) log(` Memo: ${proposal.memo}`); - log(` Permission: ${proposal.permission} (id=${proposal.permissionId})`); - log(` Threshold: ${proposal.threshold}`); - log(` Signatures: ${proposal.signaturesCollected}/${proposal.threshold}`); - log(` Expires: ${proposal.expiresAt}`); - log(""); - - // Check expiry - if (new Date(proposal.expiresAt) < new Date()) { - outputJSON({ error: "Proposal has expired", proposalId: proposal.proposalId, expiredAt: proposal.expiresAt }); - process.exit(1); - } - - // Check caller is an authorized signer - const isAuthorized = proposal.allKeys.some(k => k.address === walletAddress); - if (!isAuthorized) { - outputJSON({ - error: `Your address ${walletAddress} is not an authorized signer for this proposal`, - authorized_keys: proposal.allKeys.map(k => k.address), - }); - process.exit(1); - } - - // Check if already signed - const alreadySigned = proposal.signers.some(s => s.address === walletAddress); - if (alreadySigned) { - outputJSON({ - error: `You have already signed this proposal`, - proposalId: proposal.proposalId, - your_address: walletAddress, - }); - process.exit(1); - } - - // Add signature (TronWeb v6 uses multiSign instead of addSign) - log(`Signing with ${walletAddress} ...`); - const privateKey = process.env.TRON_PRIVATE_KEY; - const signed = await tronWeb.trx.multiSign(proposal.transaction, privateKey, proposal.permissionId); - - // Update proposal - const signerWeight = proposal.allKeys.find(k => k.address === walletAddress)?.weight || 1; - proposal.transaction = signed; - proposal.signaturesCollected += 1; - proposal.signers.push({ address: walletAddress, weight: signerWeight }); - - // Calculate total collected weight - const collectedWeight = proposal.signers.reduce((s, k) => s + k.weight, 0); - const thresholdMet = collectedWeight >= proposal.threshold; - - // Save updated proposal - saveProposal(proposal); - log("Proposal updated and saved."); - - outputJSON({ - proposalId: proposal.proposalId, - description: proposal.description, - approved: true, - signer: walletAddress, - signer_weight: signerWeight, - signatures: { - collected: proposal.signaturesCollected, - collected_weight: collectedWeight, - required_weight: proposal.threshold, - threshold_met: thresholdMet, - }, - ready_to_execute: thresholdMet, - next_step: thresholdMet - ? `Execute now: node execute.js ${proposal.proposalId}` - : `Need more signatures (weight ${proposal.threshold - collectedWeight} remaining)`, - }); -} - -main().catch((e) => { outputJSON({ error: e.message }); process.exit(1); }); diff --git a/multisig-permissions/scripts/execute.js b/multisig-permissions/scripts/execute.js deleted file mode 100644 index 8d30517..0000000 --- a/multisig-permissions/scripts/execute.js +++ /dev/null @@ -1,105 +0,0 @@ -#!/usr/bin/env node - -/** - * execute.js — Broadcast a fully-signed multi-sig transaction. - * - * Usage: - * node execute.js [--dry-run] - * - * Examples: - * node execute.js prop_1709312400_a3f2 --dry-run - * node execute.js prop_1709312400_a3f2 - */ - -const { - getTronWeb, loadProposal, waitForProposalThreshold, archiveProposal, outputJSON, log, -} = require("./utils"); - -async function main() { - const args = process.argv.slice(2); - if (args.length < 1) { - console.error("Usage: node execute.js [--dry-run]"); - process.exit(1); - } - - const proposalRef = args[0]; - const dryRun = args.includes("--dry-run"); - - const tronWeb = getTronWeb(); - - log(`Loading proposal "${proposalRef}" ...`); - const initialProposal = loadProposal(proposalRef); - const proposal = dryRun ? initialProposal : await waitForProposalThreshold(proposalRef); - - // Display summary - log(""); - log(` Proposal: ${proposal.proposalId}`); - log(` Description: ${proposal.description}`); - log(` Signatures: ${proposal.signaturesCollected}`); - log(` Threshold: ${proposal.threshold}`); - log(` Expires: ${proposal.expiresAt}`); - log(""); - - // Validate threshold - const collectedWeight = proposal.signers.reduce((s, k) => s + k.weight, 0); - if (collectedWeight < proposal.threshold) { - outputJSON({ - error: `Threshold not met. Have weight ${collectedWeight}, need ${proposal.threshold}`, - proposalId: proposal.proposalId, - signatures: proposal.signers.map(s => s.address), - }); - process.exit(1); - } - - // Validate not expired - if (new Date(proposal.expiresAt) < new Date()) { - outputJSON({ - error: "Proposal has expired", - proposalId: proposal.proposalId, - expiredAt: proposal.expiresAt, - }); - process.exit(1); - } - - const result = { - action: "execute", - proposalId: proposal.proposalId, - description: proposal.description, - signatures: proposal.signaturesCollected, - threshold: proposal.threshold, - dry_run: dryRun, - }; - - if (dryRun) { - result.status = "dry_run"; - result.message = "Transaction is valid and ready to broadcast"; - result.signers = proposal.signers.map(s => s.address); - outputJSON(result); - return; - } - - // Broadcast - log("Broadcasting transaction ..."); - try { - const broadcast = await tronWeb.trx.sendRawTransaction(proposal.transaction); - result.status = broadcast.result ? "submitted" : "failed"; - result.tx_id = broadcast.txid; - - if (broadcast.result) { - const archivedTo = archiveProposal(proposal.proposalId); - result.archived_to = archivedTo; - log(`Transaction: ${broadcast.txid}`); - log(`Proposal archived to ${archivedTo}`); - } else { - result.broadcast_error = broadcast.message || broadcast.code || "Unknown error"; - log(`Broadcast failed: ${result.broadcast_error}`); - } - } catch (e) { - result.status = "failed"; - result.error = e.message || String(e); - } - - outputJSON(result); -} - -main().catch((e) => { outputJSON({ error: e.message }); process.exit(1); }); diff --git a/multisig-permissions/scripts/pending.js b/multisig-permissions/scripts/pending.js deleted file mode 100644 index c16e422..0000000 --- a/multisig-permissions/scripts/pending.js +++ /dev/null @@ -1,94 +0,0 @@ -#!/usr/bin/env node - -/** - * pending.js — List pending multi-sig proposals. - * - * Usage: - * node pending.js [--status awaiting|ready|expired|all] - * - * Examples: - * node pending.js - * node pending.js --status ready - * node pending.js --status all - */ - -const { listProposals, outputJSON, log } = require("./utils"); - -async function main() { - const args = process.argv.slice(2); - let statusFilter = "all"; - - for (let i = 0; i < args.length; i++) { - if (args[i] === "--status" && args[i + 1]) { statusFilter = args[++i].toLowerCase(); } - } - - log("Loading pending proposals ..."); - const proposals = listProposals(); - - if (proposals.length === 0) { - outputJSON({ - pending: [], - summary: { awaiting: 0, ready: 0, expired: 0, total: 0 }, - message: "No pending proposals. Create one with: node propose.js transfer ", - }); - return; - } - - const now = new Date(); - const enriched = proposals.map(p => { - const collectedWeight = p.signers.reduce((s, k) => s + k.weight, 0); - const thresholdMet = collectedWeight >= p.threshold; - const isExpired = new Date(p.expiresAt) < now; - - let status; - if (isExpired) status = "expired"; - else if (thresholdMet) status = "ready"; - else status = "awaiting"; - - const remainingWeight = Math.max(0, p.threshold - collectedWeight); - const timeRemaining = isExpired ? "expired" : formatTimeRemaining(new Date(p.expiresAt) - now); - - return { - proposalId: p.proposalId, - description: p.description, - memo: p.memo || "", - permission: p.permission, - created: p.createdAt, - expires: p.expiresAt, - time_remaining: timeRemaining, - signatures: `${p.signaturesCollected}/${p.threshold}`, - collected_weight: collectedWeight, - required_weight: p.threshold, - status, - signers: p.signers.map(s => s.address), - remaining_weight: remainingWeight, - }; - }); - - // Apply filter - const filtered = statusFilter === "all" ? enriched : enriched.filter(p => p.status === statusFilter); - - // Compute summary - const summary = { - awaiting: enriched.filter(p => p.status === "awaiting").length, - ready: enriched.filter(p => p.status === "ready").length, - expired: enriched.filter(p => p.status === "expired").length, - total: enriched.length, - }; - - outputJSON({ - filter: statusFilter, - pending: filtered, - summary, - }); -} - -function formatTimeRemaining(ms) { - if (ms <= 0) return "expired"; - const hours = Math.floor(ms / (1000 * 60 * 60)); - const minutes = Math.floor((ms % (1000 * 60 * 60)) / (1000 * 60)); - if (hours > 24) return `${Math.floor(hours / 24)}d ${hours % 24}h`; - return `${hours}h ${minutes}m`; -} - -main().catch((e) => { outputJSON({ error: e.message }); process.exit(1); }); diff --git a/multisig-permissions/scripts/propose.js b/multisig-permissions/scripts/propose.js deleted file mode 100644 index 4474c90..0000000 --- a/multisig-permissions/scripts/propose.js +++ /dev/null @@ -1,215 +0,0 @@ -#!/usr/bin/env node - -/** - * propose.js — Create a multi-sig transaction proposal. - * - * Builds a transaction, partially signs it with the caller's key, - * and saves it to ~/.clawdbot/multisig/pending/ for co-signers. - * - * Usage: - * node propose.js transfer [--permission owner|active] [--account ] [--memo "..."] - * node propose.js trc20-transfer [--permission owner|active] [--account ] [--memo "..."] - * node propose.js contract-call [--permission owner|active] [--account ] [--memo "..."] - * - * Examples: - * node propose.js transfer TRecipient... 10000 --memo "Monthly budget" - * node propose.js trc20-transfer TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t TRecipient... 500 --memo "USDT payment" - * node propose.js contract-call TContractAddr... "transfer(address,uint256)" '["TTo...", "1000000"]' --permission active - */ - -const { TronWeb } = require("tronweb"); -const { - getTronWeb, getAccountInfo, toSun, fromSun, generateProposalId, saveProposal, - outputJSON, log, -} = require("./utils"); - -function toBase58(addr) { - try { return TronWeb.address.fromHex(addr); } catch { return addr; } -} - -function parseArgs() { - const args = process.argv.slice(2); - if (args.length < 2) { - console.error("Usage: node propose.js [--options]"); - process.exit(1); - } - - const txType = args[0]; - const positional = []; - const flags = { permission: "owner", memo: "" }; - - for (let i = 1; i < args.length; i++) { - if (args[i] === "--permission" && args[i + 1]) { flags.permission = args[++i]; continue; } - if (args[i] === "--account" && args[i + 1]) { flags.account = args[++i]; continue; } - if (args[i] === "--memo" && args[i + 1]) { flags.memo = args[++i]; continue; } - if (args[i] === "--expiry" && args[i + 1]) { flags.expiryHours = Number(args[++i]); continue; } - if (!args[i].startsWith("--")) positional.push(args[i]); - } - - return { txType, positional, ...flags }; -} - -async function main() { - const opts = parseArgs(); - const tronWeb = getTronWeb(); - const signerAddress = tronWeb.defaultAddress.base58; - const targetAccount = opts.account || signerAddress; - - // Determine permission ID - const permissionId = opts.permission === "active" ? 2 : 0; - const permLabel = permissionId === 0 ? "owner" : "active"; - - // Fetch account to know threshold - const account = await getAccountInfo(tronWeb, targetAccount); - if (!account || !account.address) { - outputJSON({ error: `Account not found or not activated: ${targetAccount}` }); - process.exit(1); - } - - let threshold; - let permKeys; - if (permissionId === 0) { - const perm = account.owner_permission || { threshold: 1, keys: [{ address: walletAddress, weight: 1 }] }; - threshold = perm.threshold || 1; - permKeys = perm.keys || []; - } else { - const actives = account.active_permission || []; - const perm = actives.find(p => p.id === permissionId); - if (!perm) { outputJSON({ error: `Active permission id=${permissionId} not found` }); process.exit(1); } - threshold = perm.threshold || 1; - permKeys = perm.keys || []; - } - - log(`Building ${opts.txType} transaction for ${targetAccount} (signer: ${signerAddress}, permission: ${permLabel}, threshold: ${threshold}) ...`); - - let tx; - let description; - - switch (opts.txType) { - case "transfer": { - const to = opts.positional[0]; - const amountTrx = opts.positional[1]; - if (!to || !amountTrx) { outputJSON({ error: "Usage: node propose.js transfer " }); process.exit(1); } - const amountSun = Number(toSun(amountTrx)); - tx = await tronWeb.transactionBuilder.sendTrx(to, amountSun, targetAccount); - description = `Transfer ${amountTrx} TRX to ${to}`; - break; - } - - case "trc20-transfer": { - const tokenAddr = opts.positional[0]; - const to = opts.positional[1]; - const amount = opts.positional[2]; - if (!tokenAddr || !to || !amount) { - outputJSON({ error: "Usage: node propose.js trc20-transfer " }); - process.exit(1); - } - // Encode transfer(address,uint256) call - const parameter = [ - { type: "address", value: to }, - { type: "uint256", value: amount }, - ]; - tx = await tronWeb.transactionBuilder.triggerSmartContract( - tokenAddr, "transfer(address,uint256)", {}, - parameter, targetAccount - ); - tx = tx.transaction; - description = `TRC20 transfer ${amount} of ${tokenAddr} to ${to}`; - break; - } - - case "contract-call": { - const contract = opts.positional[0]; - const functionSig = opts.positional[1]; - const argsJson = opts.positional[2] || "[]"; - if (!contract || !functionSig) { - outputJSON({ error: "Usage: node propose.js contract-call [args-json]" }); - process.exit(1); - } - let callArgs; - try { callArgs = JSON.parse(argsJson); } catch { callArgs = []; } - - // Parse function signature to extract parameter types - const paramMatch = functionSig.match(/\(([^)]*)\)/); - const paramTypes = paramMatch && paramMatch[1] ? paramMatch[1].split(",") : []; - const parameter = paramTypes.map((type, i) => ({ - type: type.trim(), - value: callArgs[i], - })); - - tx = await tronWeb.transactionBuilder.triggerSmartContract( - contract, functionSig, {}, - parameter, targetAccount - ); - tx = tx.transaction; - description = `Call ${functionSig} on ${contract}`; - break; - } - - default: - outputJSON({ error: `Unknown tx type: "${opts.txType}". Use: transfer, trc20-transfer, contract-call` }); - process.exit(1); - } - - // Extend transaction expiration for multi-sig (default ~60s is too short). - // TronWeb builds transactions with expiration = now + 60s, and extendExpiration - // ADDS to that value. TRON's node enforces expiration <= head_block_time + 24h, - // so we subtract the default 60s to stay within the limit. - const TRON_DEFAULT_EXPIRY_S = 60; - const expiryMs = (opts.expiryHours || 24) * 60 * 60 * 1000; - const expirySeconds = Math.max(0, Math.floor(expiryMs / 1000) - TRON_DEFAULT_EXPIRY_S); - tx = await tronWeb.transactionBuilder.extendExpiration(tx, expirySeconds); - - // Sign with caller's key using multiSign (which handles Permission_id internally) - log("Signing with caller key ..."); - const signed = await tronWeb.trx.multiSign(tx, undefined, permissionId); - - // Build proposal - const proposalId = generateProposalId(); - const now = Date.now(); - - const proposal = { - proposalId, - description, - memo: opts.memo || "", - permission: permLabel, - permissionId, - account: targetAccount, - threshold, - signaturesCollected: 1, - createdAt: new Date(now).toISOString(), - expiresAt: new Date(now + expiryMs).toISOString(), - signers: [{ address: signerAddress, weight: permKeys.find(k => toBase58(k.address) === signerAddress)?.weight || 1 }], - allKeys: permKeys.map(k => ({ address: toBase58(k.address), weight: k.weight || 1 })), - transaction: signed, - }; - - const filePath = saveProposal(proposal); - log(`Proposal saved to ${filePath}`); - - // Calculate collected weight - const collectedWeight = proposal.signers.reduce((s, k) => s + k.weight, 0); - - outputJSON({ - proposalId, - description, - memo: opts.memo || "", - permission: permLabel, - account: targetAccount, - signer: signerAddress, - threshold, - signatures: { - collected: 1, - collected_weight: collectedWeight, - required_weight: threshold, - threshold_met: collectedWeight >= threshold, - }, - expires: proposal.expiresAt, - saved_to: filePath, - next_step: collectedWeight >= threshold - ? `Ready to execute: node execute.js ${proposalId}` - : `Share proposal ID with co-signers: node approve.js ${proposalId}`, - }); -} - -main().catch((e) => { outputJSON({ error: e.message }); process.exit(1); }); diff --git a/multisig-permissions/scripts/review.js b/multisig-permissions/scripts/review.js deleted file mode 100644 index b8d3dc1..0000000 --- a/multisig-permissions/scripts/review.js +++ /dev/null @@ -1,282 +0,0 @@ -#!/usr/bin/env node - -/** - * review.js — Human-facing CLI for reviewing and co-signing agent proposals. - * - * Usage: - * node review.js # List all pending proposals - * node review.js # Inspect a proposal (read-only) - * node review.js --sign # Inspect + co-sign - * node review.js --sign --execute # Inspect + co-sign + broadcast - * - * Examples: - * node review.js - * node review.js prop_1710345600_b7e4 - * node review.js prop_1710345600_b7e4 --sign - * node review.js prop_1710345600_b7e4 --sign --execute - */ - -const { TronWeb } = require("tronweb"); -const { - loadProposal, saveProposal, archiveProposal, - listProposals, getNetwork, outputJSON, log, -} = require("./utils"); - -function formatTimeRemaining(ms) { - if (ms <= 0) return "EXPIRED"; - const hours = Math.floor(ms / (1000 * 60 * 60)); - const minutes = Math.floor((ms % (1000 * 60 * 60)) / (1000 * 60)); - if (hours > 24) return `${Math.floor(hours / 24)}d ${hours % 24}h`; - return `${hours}h ${minutes}m`; -} - -function printProposalDetails(p) { - const now = new Date(); - const expiresAt = new Date(p.expiresAt); - const isExpired = expiresAt < now; - const collectedWeight = p.signers.reduce((s, k) => s + k.weight, 0); - const thresholdMet = collectedWeight >= p.threshold; - - log(""); - log("=== PROPOSAL DETAILS ==="); - log(""); - log(` ID: ${p.proposalId}`); - log(` Description: ${p.description}`); - if (p.memo) log(` Memo: ${p.memo}`); - log(` Permission: ${p.permission} (id=${p.permissionId})`); - log(` Created: ${p.createdAt}`); - log(` Expires: ${p.expiresAt} (${formatTimeRemaining(expiresAt - now)})`); - log(""); - log("--- Signatures ---"); - log(` Threshold: ${p.threshold} (weight required)`); - log(` Collected: ${collectedWeight}/${p.threshold} weight from ${p.signaturesCollected} signer(s)`); - log(` Status: ${isExpired ? "EXPIRED" : thresholdMet ? "READY TO EXECUTE" : "AWAITING SIGNATURES"}`); - log(""); - log(" Signers so far:"); - for (const s of p.signers) { - log(` - ${s.address} (weight ${s.weight})`); - } - log(""); - log(" All authorized keys:"); - for (const k of p.allKeys) { - const signed = p.signers.some(s => s.address === k.address); - log(` - ${k.address} (weight ${k.weight}) ${signed ? "[SIGNED]" : "[pending]"}`); - } - log(""); - - return { isExpired, thresholdMet, collectedWeight }; -} - -async function listMode() { - const proposals = listProposals(); - - if (proposals.length === 0) { - log("No pending proposals."); - log("The agent can create one with: node propose.js transfer "); - outputJSON({ pending: [], total: 0 }); - return; - } - - const now = new Date(); - const items = proposals.map(p => { - const collectedWeight = p.signers.reduce((s, k) => s + k.weight, 0); - const expiresAt = new Date(p.expiresAt); - const isExpired = expiresAt < now; - const thresholdMet = collectedWeight >= p.threshold; - - return { - proposalId: p.proposalId, - description: p.description, - memo: p.memo || "", - signatures: `${p.signaturesCollected}/${p.threshold}`, - collected_weight: collectedWeight, - required_weight: p.threshold, - time_remaining: formatTimeRemaining(expiresAt - now), - status: isExpired ? "expired" : thresholdMet ? "ready" : "awaiting", - next_step: isExpired - ? "Expired — create a new proposal" - : thresholdMet - ? `Execute: node review.js ${p.proposalId} --sign --execute` - : `Review: node review.js ${p.proposalId} --sign`, - }; - }); - - log(`Found ${items.length} pending proposal(s):\n`); - for (const item of items) { - log(` [${item.status.toUpperCase()}] ${item.proposalId}`); - log(` ${item.description}`); - log(` Signatures: ${item.signatures} | Time left: ${item.time_remaining}`); - log(` -> ${item.next_step}`); - log(""); - } - - outputJSON({ pending: items, total: items.length }); -} - -async function reviewMode(proposalRef, doSign, doExecute) { - const proposal = loadProposal(proposalRef); - - // Always show details first - let { isExpired, thresholdMet, collectedWeight } = printProposalDetails(proposal); - - if (isExpired) { - outputJSON({ error: "Proposal has expired. Create a new one.", proposalId: proposal.proposalId }); - process.exit(1); - } - - if (!doSign) { - // Read-only mode — no private key required - const result = { - proposalId: proposal.proposalId, - description: proposal.description, - threshold_met: thresholdMet, - }; - - if (thresholdMet) { - result.next_step = `Ready to execute: node review.js ${proposal.proposalId} --sign --execute`; - } else { - result.next_step = `To co-sign: node review.js ${proposal.proposalId} --sign`; - } - - outputJSON(result); - return; - } - - // --- Sign mode (requires TRON_HUMAN_PRIVATE_KEY or TRON_PRIVATE_KEY) --- - - const humanKey = process.env.TRON_HUMAN_PRIVATE_KEY; - if (!humanKey) { - outputJSON({ error: "TRON_HUMAN_PRIVATE_KEY is required to sign. This must be the human co-signer's key, not the agent's TRON_PRIVATE_KEY." }); - process.exit(1); - } - const network = getNetwork(); - const mainnetHost = process.env.TRONGRID_API_KEY ? "https://api.trongrid.io" : "https://hptg.bankofai.io"; - const hosts = { mainnet: mainnetHost, nile: "https://nile.trongrid.io", shasta: "https://api.shasta.trongrid.io" }; - const opts = { fullHost: hosts[network], privateKey: humanKey }; - if (process.env.TRONGRID_API_KEY) opts.headers = { "TRON-PRO-API-KEY": process.env.TRONGRID_API_KEY }; - const tronWeb = new TronWeb(opts); - const walletAddress = tronWeb.defaultAddress.base58; - - const isAuthorized = proposal.allKeys.some(k => k.address === walletAddress); - if (!isAuthorized) { - outputJSON({ - error: `Your address ${walletAddress} is not an authorized signer`, - authorized_keys: proposal.allKeys.map(k => k.address), - }); - process.exit(1); - } - - const alreadySigned = proposal.signers.some(s => s.address === walletAddress); - if (alreadySigned) { - log(`You (${walletAddress}) have already signed this proposal.`); - } else { - log(`Signing proposal with ${walletAddress} ...`); - const signed = await tronWeb.trx.multiSign(proposal.transaction, humanKey, proposal.permissionId); - - const signerWeight = proposal.allKeys.find(k => k.address === walletAddress)?.weight || 1; - proposal.transaction = signed; - proposal.signaturesCollected += 1; - proposal.signers.push({ address: walletAddress, weight: signerWeight }); - saveProposal(proposal); - - collectedWeight = proposal.signers.reduce((s, k) => s + k.weight, 0); - thresholdMet = collectedWeight >= proposal.threshold; - - log(`Signature added (weight ${signerWeight}). Total weight: ${collectedWeight}/${proposal.threshold}`); - log(""); - } - - // --- Execute mode --- - - if (doExecute) { - if (!thresholdMet) { - outputJSON({ - error: `Cannot execute — threshold not met. Have weight ${collectedWeight}, need ${proposal.threshold}`, - proposalId: proposal.proposalId, - remaining_weight: proposal.threshold - collectedWeight, - }); - process.exit(1); - } - - log("Broadcasting transaction ..."); - try { - const broadcast = await tronWeb.trx.sendRawTransaction(proposal.transaction); - if (broadcast.result) { - const archivedTo = archiveProposal(proposal.proposalId); - log(`Transaction submitted: ${broadcast.txid}`); - log(`Proposal archived to ${archivedTo}`); - - outputJSON({ - proposalId: proposal.proposalId, - action: "signed_and_executed", - signer: walletAddress, - signatures: proposal.signaturesCollected, - threshold: proposal.threshold, - status: "submitted", - tx_id: broadcast.txid, - archived_to: archivedTo, - }); - } else { - outputJSON({ - proposalId: proposal.proposalId, - status: "broadcast_failed", - error: broadcast.message || broadcast.code || "Unknown error", - }); - process.exit(1); - } - } catch (e) { - outputJSON({ proposalId: proposal.proposalId, status: "failed", error: e.message || String(e) }); - process.exit(1); - } - return; - } - - // Signed but not executing - const result = { - proposalId: proposal.proposalId, - action: "signed", - signer: walletAddress, - signatures: { - collected: proposal.signaturesCollected, - collected_weight: collectedWeight, - required_weight: proposal.threshold, - threshold_met: thresholdMet, - }, - }; - - if (thresholdMet) { - result.next_step = `Ready! Execute: node review.js ${proposal.proposalId} --sign --execute`; - } else { - result.next_step = `Need ${proposal.threshold - collectedWeight} more weight from other signers.`; - } - - outputJSON(result); -} - -async function main() { - const args = process.argv.slice(2); - - // No args → list mode - if (args.length === 0) { - await listMode(); - return; - } - - // Parse flags - const doSign = args.includes("--sign"); - const doExecute = args.includes("--execute"); - const proposalRef = args.find(a => !a.startsWith("--")); - - if (!proposalRef) { - await listMode(); - return; - } - - if (doExecute && !doSign) { - log("Warning: --execute requires --sign. Adding --sign automatically."); - } - - await reviewMode(proposalRef, doSign || doExecute, doExecute); -} - -main().catch((e) => { outputJSON({ error: e.message }); process.exit(1); }); diff --git a/multisig-permissions/scripts/status.js b/multisig-permissions/scripts/status.js deleted file mode 100644 index 40dbedb..0000000 --- a/multisig-permissions/scripts/status.js +++ /dev/null @@ -1,117 +0,0 @@ -#!/usr/bin/env node - -/** - * status.js — View current account permission configuration. - * - * Usage: - * node status.js [address] - * - * If no address is given, inspects the caller's wallet. - * - * Examples: - * node status.js - * node status.js TXk8rQSAvPvBBNtqSoY3UkFdpMTMbqRMKU - */ - -const { TronWeb } = require("tronweb"); -const { - getTronWeb, getTronWebReadOnly, getAccountInfo, decodeOperations, getEnabledOperationBitCount, classifySecurity, - formatPermConfig, outputJSON, log, -} = require("./utils"); - -function toBase58(addr) { - try { return TronWeb.address.fromHex(addr); } catch { return addr; } -} - -function formatKeys(keys) { - return (keys || []).map(k => ({ - address: toBase58(k.address), - weight: k.weight || 1, - })); -} - -async function main() { - const targetAddress = process.argv[2]; - - let tronWeb; - let address; - - if (targetAddress) { - tronWeb = getTronWebReadOnly(); - address = targetAddress; - } else { - tronWeb = getTronWeb(); - address = tronWeb.defaultAddress.base58; - } - - log(`Checking permissions for ${address} ...`); - - const account = await getAccountInfo(tronWeb, address); - if (!account || !account.address) { - outputJSON({ error: `Account not found or not activated: ${address}` }); - process.exit(1); - } - - // --- Owner permission --- - const ownerPerm = account.owner_permission || { - type: 0, - permission_name: "owner", - threshold: 1, - keys: [{ address, weight: 1 }], - }; - const ownerKeys = formatKeys(ownerPerm.keys); - const ownerThreshold = ownerPerm.threshold || 1; - - // --- Active permissions --- - const rawActive = account.active_permission || []; - const activePerms = rawActive.map(p => { - const keys = formatKeys(p.keys); - const threshold = p.threshold || 1; - const ops = decodeOperations(p.operations); - const enabledBits = getEnabledOperationBitCount(p.operations); - return { - id: p.id, - name: p.permission_name || `active:${p.id}`, - threshold, - keys, - is_multisig: threshold > 1 || keys.length > 1, - config: formatPermConfig(keys, threshold), - operations: ops, - operations_recognized: ops.length, - operations_enabled_bits: enabledBits, - operations_hex: p.operations || null, - }; - }); - - // --- Witness permission --- - const witnessPerm = account.witness_permission || null; - let witnessOut = null; - if (witnessPerm) { - const wKeys = formatKeys(witnessPerm.keys); - witnessOut = { - threshold: witnessPerm.threshold || 1, - keys: wKeys, - url: witnessPerm.url || null, - }; - } - - // --- Security analysis --- - const { level, notes } = classifySecurity(ownerPerm, rawActive); - - outputJSON({ - address, - network: (process.env.TRON_NETWORK || "mainnet").toLowerCase(), - owner: { - threshold: ownerThreshold, - keys: ownerKeys, - is_multisig: ownerThreshold > 1 || ownerKeys.length > 1, - config: formatPermConfig(ownerKeys, ownerThreshold), - }, - active: activePerms, - witness: witnessOut, - security_level: level, - analysis: notes, - }); -} - -main().catch((e) => { outputJSON({ error: e.message }); process.exit(1); }); diff --git a/multisig-permissions/scripts/update.js b/multisig-permissions/scripts/update.js deleted file mode 100644 index bf84258..0000000 --- a/multisig-permissions/scripts/update.js +++ /dev/null @@ -1,409 +0,0 @@ -#!/usr/bin/env node - -/** - * update.js — Modify account permissions (owner, active, witness). - * - * ⚠️ THIS IS THE MOST DANGEROUS SCRIPT IN THE REPO. - * Misconfiguring owner permissions can permanently lock an account. - * - * Usage: - * node update.js add-key
[--permission owner|active] [--weight 1] [--dry-run] - * node update.js remove-key
[--permission owner|active] [--dry-run] - * node update.js set-threshold [--permission owner|active] [--dry-run] - * node update.js scope-active [--id 2] [--operations Op1,Op2,...] [--dry-run] - * node update.js from-template