npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

privily-plugin

v1.0.0

Published

Privily local MCP server — terminal-side Privily wallet integration for Claude Code.

Readme

privily-plugin

The Privily plugin for Claude Code — a private crypto wallet and payments tool. One package bundles a Skill and a local MCP server; the MCP is also usable standalone by any MCP client. Privily lives at app.privily.fi.

Status: 46 tools live, covering withdrawal, deposit, send, disperse, request, pay, exchange, approval, reads & discovery, and optional encryption-at-rest. Production-validated end-to-end across every flow.

The MCP runs as a Node 20+ process on stdio. It owns the Privily Access Key (~/.privily/access-key.json, chmod 0600). It mints backend session tokens lazily via the SDK's loginRPCV0 challenge-response (the stark sk never leaves the machine). For Privily-side operations (withdrawals + internal sends), it signs internally using the SDK's UnitsAccount. For external-chain operations (deposits), the user's external wallet signs and the MCP validates + broadcasts. A local capability-policy engine and a confirmation-gated Skill provide two independent safety layers.

Install

Privily ships as a Claude Code plugin — installing it adds the Privily Skill and the MCP server together. From inside Claude Code:

/plugin

Find privily in the Anthropic community marketplace and install it. The plugin's Skill then walks you through connecting your account — exporting the Access Key from the Privily web app, then a confirmed import to ~/.privily/access-key.json (chmod 0600).

Standalone MCP (any MCP client). The same package is a normal MCP server:

claude mcp add privily -- npx privily-plugin

Environment variables

All are optional — sensible defaults ship.

| Var | Default | Purpose | |---|---|---| | PRIVILY_ENV | production | One of production / mock. | | PRIVILY_BACKEND_URL | per env (api.privily.fi / http://127.0.0.1:7777) | Auth + RPC reads. | | PRIVILY_APPCHAIN_RPC_URL | per env (privily.karnot.xyz for production; loopback for mock) | UnitsProvider broadcast for withdrawals. | | PRIVILY_APP_URL | per env (app.privily.fi for prod) | Web-app origin — used only to build the share_url for a payment Request (display string, never fetched). | | PRIVILY_EVM_RPC_URL_<CHAIN> | public RPCs (eth.merkle.io, mainnet.base.org, arb1.arbitrum.io/rpc, mainnet.optimism.io, polygon-rpc.com, bsc-dataseed.binance.org) | Per-chain JSON-RPC for the EVM external-source R4 flows. <CHAIN>{ETHEREUM, BASE, ARBITRUM, OPTIMISM, POLYGON, BNB}. | | PRIVILY_TRON_API_URL | https://api.trongrid.io | Tron API endpoint for the Tron external-source R4 flows. | | PRIVILY_SOLANA_RPC_URL | https://api.mainnet-beta.solana.com | Solana JSON-RPC for the Solana external-source R4 flows. | | PRIVILY_STARKNET_RPC_URL | https://rpc.starknet.lava.build | External Starknet-L2 JSON-RPC for the Starknet external-source R4 flows. Distinct from PRIVILY_APPCHAIN_RPC_URL (the Privily appchain). | | PRIVILY_DEBUG | 0 | Verbose stderr logging. | | PRIVILY_SDK_RPC_URL | (SDK default) | Override the SDK's hardcoded read-RPC URL (used when env=mock to point at the local mock-backend). | | PRIVILY_INSECURE_SKIP_ENVELOPE | off | Dev hatch for the pre-pivot envelope verifier — refused for env=mock. Largely obsolete. | | PRIVILY_PASSWORD | (unset) | The encryption-at-rest password. When set, an encrypted Access Key auto-unlocks at startup; when set before Privily_Import_Access_Key, the imported key is sealed. Read ONLY from the environment, never a tool argument. Set it transiently per session — persisting it in a dotfile defeats the encryption. | | PRIVILY_NEW_PASSWORD | (unset) | The target password for Privily_Change_Password (encrypt a plaintext key, or re-key an encrypted one). Environment-only, like PRIVILY_PASSWORD. |

Tools (46 total)

Plomberie (5):

  • Privily_Health — liveness probe, no auth.
  • Privily_Authentication — local session-state diagnostic.
  • Privily_Validate_Address — per-chain address sanity check.
  • Privily_Get_Operation_Status — poll operation status by bridge_id (= NewBridge.id / lpId).
  • Privily_Verify_Prepare_Envelope — diagnostic-only since Phase 5.1.

Admin (7):

  • Privily_Get_Account_Metadata — which account is loaded; JWT presence; encryption state (encryption + unlocked).
  • Privily_Disconnect — clear JWT cache and/or policies file; lock:true drops the in-memory decrypted key (never deletes the Access Key file).
  • Privily_Get_Capabilities — read the active safety policy + 24h spent.
  • Privily_Set_Capabilities — install or replace the safety policy (confirmation-gated).
  • Privily_Import_Access_Key — two-step Access Key import (preview → user-approved perform); seals the key when PRIVILY_PASSWORD is set.
  • Privily_Unlock — R0, decrypt an encrypted Access Key for the session (Phase 15). No arguments — the password is read from PRIVILY_PASSWORD only.
  • Privily_Change_Password — R0, encrypt a plaintext key or change an encrypted key's password (Phase 15). Confirmation-gated; passwords from PRIVILY_NEW_PASSWORD / PRIVILY_PASSWORD.

Portfolio (2):

  • Privily_Get_Account — suffix-masked account + chain_addr + supported_chains + beta_flags + ref_code.
  • Privily_Get_Balances — Privily-appchain aggregated balances + USD value.

Prepare (7):

  • Privily_Prepare_Withdrawal — preflight Privily → external chain. Calls SDK getBridgeData, runs verifySignedResponse, builds a LocalPrepareEnvelope cached 5 min. to_address is optional — omitted → defaults to the wallet-owner address. Optional delay ({value, unit}) schedules it for later (10 min – 72 h).
  • Privily_Prepare_Deposit — preflight external chain → Privily. Same shape; raw_bridge_tx holds the per-chain tx for the agent's wallet to sign.
  • Privily_Prepare_Send — preflight a Send (all 4 directions). from_chain/to_chain select the direction; getBridgeData with conf.isSend. to_address is always mandatory (the explicit recipient is what makes it a Send); from_address required for an external from_chain. from_token + optional to_token — a Send can swap in-flight (to_token defaults to from_token). isAnon:true hides the sender. Optional delay schedules it — external destination only.
  • Privily_Prepare_Disperse — preflight a Disperse: fan one source token out to up to 20 recipients. Always lands on an external chain — Privily→external or external→external (never to Privily). to_chain + optional to_token are batch-level (every recipient lands on the same chain, receives the same token); recipients is an array of {to_address, amount, delay_minutes?} legs. from_chain "privily" or external; getDisperseBridgeData with conf.isSend. isAnon is one passthrough flag for the whole batch. delay_minutes (per-leg, 10–60, all-or-nothing) schedules each recipient's payout.
  • Privily_Prepare_Pay — preflight a Pay: settle an existing Payment Request. A Pay is a Send whose destination is a request_id — the backend resolves the amount / receive chain / receive token / recipient FROM the request; the payer only chooses where to fund FROM (from_chain). from_chain "privily" → Privily-funded (R3); an external chain → external-funded (R4, from_address required). No swap-pay (funded with exactly the requested asset). Runs the FULL capability policy. isAnon:true hides the payer (Pay does NOT read anon_mode_default).
  • Privily_Prepare_Exchange — preflight an Exchange: a SELF-DIRECTED swap (token A → token B), optionally across chains. The destination is ALWAYS the user — to a Privily destination the funds return to the connected account; to an external chain, at the user's own to_address (to pay someone else, use Send). All 4 directions selected by from_chain/to_chain; getBridgeData with conf.isExchange. from_token + to_token both required; amount is the from_token amount (backend quotes the receive). to_address required for an external to_chain, omitted for "privily"; from_address required for an external from_chain. No Anonymous Mode (no counterparty). Optional delay schedules it — external destination only.
  • Privily_Prepare_Approval — preflight a standalone ERC-20 / TRC-20 approval: grant the Privily bridge contract an allowance to move a token WITHOUT immediately bridging. A standalone approval is leg 0 of a getBridgeData plan, broadcast on its own — the MCP isolates the approve leg and discards the bridge leg; the spender is always the Privily bridge contract. chain accepts the EVM chains plus tron (Phase 14); token must be an ERC-20 / TRC-20 (a native asset has no approve step → SCHEMA_INVALID). unlimited:true widens the allowance to max-uint256 (default false → an exact-amount approval). Runs a LIGHT capability check (operation/chain/token); the new allow_unlimited_approval policy field can forbid the unlimited variant.

Delayed Execution (Phase 13bis). Prepare_Withdrawal / Prepare_Send / Prepare_Exchange take an optional delay: {value, unit} (unitminutes|hours); Prepare_Disperse takes a per-leg delay_minutes (all-or-nothing across the batch). A delay is valid ONLY when the destination is external (a flow that lands on Privily cannot be scheduled). Bounds: 10 min – 72 h (60 min cap per Disperse leg) — out-of-bounds → DELAY_OUT_OF_BOUNDS. Two capability-policy fields gate it: allow_delayed (default true) and max_delay_seconds (default null). A delayed Privily-source Submit_* signs a wide-bounds SNIP-9 outside-execution and relays it via processDelayedWithdrawals — output status: "scheduled", tx_hash: null, exec_timestamp; the backend broadcasts at the scheduled time. Poll status only AFTER the delay: a Privily-source delayed flow has nothing on-chain until exec_timestamp; an external-source delayed flow is two-stage — poll the deposit (operation_id) immediately, then poll the destination leg (second_operation_id) only after the schedule.

Submit (11):

  • Privily_Submit_Withdrawal — R3, MCP signs internally with the stark sk. Output: {tx_hash, bridge_id, status, explorer_url}.
  • Privily_Submit_Deposit — R4, wallet-agnostic. MCP NEVER touches sk material; the agent's wallet signs and supplies a chain-specific signedPayload. Source chains: EVM (EOA / ERC-1271 / EIP-7702 — raw signed tx hex), Tron (signed-tx JSON string), Solana (base64 VersionedTransaction), Starknet (signed v3-invoke JSON) — Phase 14. Multi-leg support via leg_index for EVM ERC-20 / Tron TRC-20 (2-leg approve+bridge); Solana + Starknet are single-leg.
  • Privily_Submit_Send — R3, Privily-source Send. MCP signs internally. Branches on direction: Privily→Privily signs a SNIP-9 outside-execution relayed via processTransfer (a transfer); Privily→external signs + broadcasts a direct appchain invoke (a bridge). Output: {direction, operation_id, tx_hash, status, explorer_url, notes}.
  • Privily_Submit_Send_External — R4, external-source Send (external→Privily, external→external). Wallet-agnostic — reuses the Submit_Deposit machinery; MCP never touches sk material. Output: {tx_hash, operation_id, second_operation_id?, status, signer_type, …}. external→external is a two-stage settlement: poll operation_id to SUCCESS, then second_operation_id (30 polls each); external→Privily is single-stage.
  • Privily_Submit_Disperse — R3, Privily-source disperse. MCP signs internally — ONE appchain invoke covering every leg. Output: {direction, operation_ids, tx_hash, total_legs, status, …}operation_ids is the per-recipient status-handle array (poll each, 30 polls per leg).
  • Privily_Submit_Disperse_External — R4, external-source disperse. Wallet-agnostic — reuses the Submit_Deposit machinery. Output: {tx_hash, operation_id, second_operation_ids, total_recipients, …}. Always two-stage: poll operation_id (the deposit) to SUCCESS, then each second_operation_ids entry (30 polls each).
  • Privily_Submit_Pay — R3, Privily-funded Pay. MCP signs internally. Structurally Submit_Send: Privily→Privily signs a SNIP-9 outside-execution relayed via processTransfer with the requestId (so the backend marks the request EXECUTED); Privily→external signs + broadcasts a direct appchain invoke. Output: {direction, operation_id, tx_hash, status, explorer_url, notes}.
  • Privily_Submit_Pay_External — R4, external-funded Pay (external→Privily, external→external). Wallet-agnostic — reuses the Submit_Deposit machinery; MCP never touches sk material. Output: {tx_hash, operation_id, second_operation_id?, status, signer_type, …}. external→external is two-stage; external→Privily single-stage.
  • Privily_Submit_Exchange — R3, Privily-source Exchange (in-Privily swap, or Privily→external). MCP signs internally. An Exchange has no counterparty, so BOTH directions sign + broadcast a direct appchain invoke (the Submit_Withdrawal mechanism — never processTransfer). Output: {direction, operation_id, tx_hash, status, explorer_url, notes}tx_hash present for both directions.
  • Privily_Submit_Exchange_External — R4, external-source Exchange (external→Privily, external→external). Wallet-agnostic — reuses the Submit_Deposit machinery; MCP never touches sk material. Output: {tx_hash, operation_id, second_operation_id?, status, signer_type, …}. external→external is two-stage; external→Privily single-stage.
  • Privily_Submit_Approval — R4, standalone ERC-20 approval. Wallet-agnostic — reuses the Submit_Deposit machinery (parse → field-equality → verify → broadcast); MCP never touches sk material. Always a single transaction (no leg_index). Output: {tx_hash, chain, token, token_contract, spender, unlimited, allowance, signer_type, status, explorer_url, notes}no operation_id: an approval is a plain on-chain tx, not a Privily operation; confirm it via the explorer, not Get_Operation_Status.

Request (2):

  • Privily_Create_Request — R2, the simplest Privily flow: it signs NOTHING and broadcasts NOTHING, just creates a Payment Request record (an invoice / pay-link) on the backend via the SDK's requestPayment. Funds are requested into the connected Privily account by default, or onto an external chain (receive_chain + receive_address). payer_address omitted → an open public pay-link; set → restricted to one Privily account. isAnon:true hides the requester (the share page renders "Anonymous"); anon_mode_default applies when omitted. Output: {request_id, share_url, scope, …}. A payer settles the request LATER via the Pay flow (Privily_Prepare_Pay).
  • Privily_Cancel_Request — R2, the retract counterpart to Create_Request: cancel a pending Payment Request the user created. Even simpler — signs nothing, broadcasts nothing, moves no value, no capability-policy gate (cancelling is purely subtractive). Input {request_id} (what Create_Request returned). Once cancelled the pay-link is dead. Only a PENDING request is cancellable, and only by its creator; an already-paid/failed/cancelled request → BACKEND_REJECTED. Output: {request_id, status: "cancelled", message, notes}.

Reads & Discovery (12 — Phase 16):

  • Privily_Get_History — R1, paginated operation history (one type filter over the SDK's four history feeds: all / deposit / withdrawal / transfer).
  • Privily_Get_Prices — R1, oracle USD token prices. PUBLIC — answers before an account is connected.
  • Privily_List_Assets — R1, the supported-token registry + liquidity-provider conf (surfaces the supported chains). PUBLIC.
  • Privily_List_Requests — R1, the account's Payment Requests (sent / received) — the discovery counterpart of Create/Cancel_Request.
  • Privily_Get_Referral — R1, referral code + referees + trading rebates.
  • Privily_List_Addresses — R1, the saved address book (searchable by label / address).
  • Privily_Save_Address — R2, add or update an address-book entry (label max 20 chars — the backend's limit). Moves no value — no policy gate; Skill-confirmed.
  • Privily_Delete_Address — R2, delete an address-book entry by id. Skill-confirmed.
  • Privily_Get_Quote — R1, a read-only swap / bridge price estimate — calls getBridgeData but builds no envelope and commits nothing (NON-BINDING). route_available: false + null pay/receive when the backend has no route.
  • Privily_Verify_Signature — R0, verify an EVM (EIP-191) or Solana (Ed25519) message signature. Pure local — no network, no account.
  • Privily_Search — R1, fuzzy substring discovery across history + requests + addresses.
  • Privily_Fetch — R1, retrieve one entity by id (request / operation / address).

Security model

  • Stark sk never sent to the backend, never returned in tool output (regex-scrubbed at the output boundary via src/audit/redactor.ts), never logged, never accepted as tool input (root validator rejects fields named sk, private_key, secret_key, entropy, seed, mnemonic, master_key, passphrase, password).
  • Encryption-at-rest (Phase 15, optional) — when PRIVILY_PASSWORD is set the Access Key file is sealed with scrypt (KDF) + AES-256-GCM; with no password it stays plaintext at chmod 0600 (the v1 model, unchanged). Self-describing file format (an encryption header); LocalStorage decrypts transparently. The password is read ONLY from the environment — never a tool argument, never logged, never returned — and the leakage gate covers a fixture passphrase. A capability policy may set require_encrypted_storage to refuse R3/R4 ops on a plaintext key.
  • CI import-graph check (scripts/ci-import-graph-check.ts) gates which source files may import sk-handling SDK functions — only src/signing/, src/admin/, src/storage/, src/auth/jwt.ts, plus src/client/sdk-shim.ts as the gateway exemption.
  • CI forbidden-fields scan (scripts/ci-forbidden-fields-check.ts) walks every tool's jsonInputSchema and rejects any sk-shape field name.
  • Leakage gate (tests/leakage.test.ts + per-tool suites) asserts assertNoSkLeak against every sk-touching tool's output + error paths.
  • Backend response signature verified via SDK 1.1.0 verifySignedResponse (ed25519 over SHA-256 of canonicalized JSON; pubkey hardcoded in SDK constants).
  • Local Access Key file chmod 0600 (POSIX) or owner-only ACL (Windows, via icacls).
  • Lockfile at ~/.privily/mcp.lock prevents concurrent MCP processes.
  • No telemetry, no analytics, no auto-update. Outbound HTTPS limited to the configured Privily backend + per-chain RPCs.
  • Localhost-only invariant: refuses PRIVILY_HTTP_BIND=0.0.0.0.
  • Capability policy engine with confirmation-gated Set_Capabilities, per-tx + rolling-24h USD caps, allowed/denied lists for operations / chains / tokens / recipients.

Full audit: docs/SECURITY_AUDIT_PHASE7.md. v1 ships with 14 ✅ / 3 ⚠️ (follow-ups, not failures) / 0 ❌ against the 19-item §17 checklist.

Dev quickstart

npm install
npm run ci             # typecheck + import-graph + forbidden-fields + the vitest suite
npm run build
npm run dev            # start the MCP on stdio for local invocation

Smoke tests (each spawns the MCP child + a mock-backend on a random ephemeral port):

npm run smoke:health        # confirms the tool list + Privily_Health responds
npm run smoke:auth-prod     # exercises real backend login (needs network)
npm run smoke:policy        # exercises Set_Capabilities + enforcement
npm run smoke:prepare       # exercises Prepare_Withdrawal flow
npm run smoke:submit        # full Prepare → Submit_Withdrawal end-to-end
npm run smoke:send          # full Prepare → Submit_Send (Privily → Privily)
npm run smoke:send-external # full Prepare → Submit_Send_External (external → Privily)
npm run smoke:disperse           # full Prepare → Submit_Disperse (Privily-source batch)
npm run smoke:disperse-external  # full Prepare → Submit_Disperse_External (external-source)
npm run smoke:request       # Privily_Create_Request (R2 — creates a pay-link, no signing)
npm run smoke:cancel-request # Create_Request → Cancel_Request round-trip (R2)
npm run smoke:pay           # full Prepare_Pay → Submit_Pay (Privily-funded, R3)
npm run smoke:pay-external  # full Prepare_Pay → Submit_Pay_External (external-funded, R4)
npm run smoke:exchange      # full Prepare_Exchange → Submit_Exchange (in-Privily swap, R3)
npm run smoke:exchange-external # full Prepare_Exchange → Submit_Exchange_External (external-source, R4)
npm run smoke:delayed       # scheduled Prepare_Withdrawal → Submit_Withdrawal (Phase 13bis, processDelayedWithdrawals)
npm run smoke:approval      # full Prepare_Approval → Submit_Approval (exact + unlimited, Phase 12)
npm run smoke:tron-external     # Prepare_Deposit → Submit_Deposit, Tron 2-leg TRC-20 (Phase 14)
npm run smoke:solana-external   # Prepare_Deposit → Submit_Deposit, Solana VersionedTransaction (Phase 14)
npm run smoke:starknet-external # Prepare_Deposit → Submit_Deposit, Starknet v3 invoke (Phase 14)
npm run smoke:reads         # all 12 Phase-16 read / discovery tools end-to-end
npm run smoke:encryption    # Phase 15 — sealed-key auto-unlock + locked-key UNLOCK_FAILED
npm run smoke:deposit       # full Prepare → Submit_Deposit (mock viem + EVM stub)
npm run smoke:admin    # exercises Disconnect + Get_Account_Metadata
npm run smoke:perf     # cold-start <2s + read-tool RTT <200ms targets

Real-prod ops helper (run on your own machine; reads PK from env, never logged):

PRIVILY_EVM_PK=<your-test-wallet-pk> \
PRIVILY_EVM_FROM=<your-evm-address> \
PRIVILY_DEP_CHAIN=base \
PRIVILY_DEP_TOKEN=ETH \
PRIVILY_DEP_AMOUNT=0.001 \
PRIVILY_ENV=production \
npx tsx scripts/manual-deposit-helper.ts

Performance

npm run smoke:perf targets, measured on Windows 11 / Node 24:

| Metric | Target | Measured (mean of 5) | Notes | |---|---|---|---| | Cold-start (process boot → initialize response) | < 6000 ms | ~3.4 s | A one-time cost — paid once when the MCP process is launched, not per operation. ESM module-evaluation of the dependency tree: the Privily SDK + starknet + viem + tronweb (~1.1 s) + @solana/web3.js + @noble/* + zod + the MCP SDK, plus 46 tool modules. The target was raised from the original Phase-7 2000 ms (16 tools, 1 chain family) for today's 46-tool / 5-chain surface. Documented future optimization: lazy-load tronweb. | | Read-tool RTT (Privily_Authentication, warm process) | < 200 ms | ~6 ms | The per-operation latency — LocalStorage stat + zod validate + JSON serialize only. Tightly gated. |

Architecture

~/.privily/                              cli/dapp share this path
├── access-key.json     (chmod 0600)    {chainAddr, account, sk, version:"v0"} — plaintext,
│                                       OR a {encryption, ciphertext} sealed file (Phase 15)
├── jwt-cache.json      (chmod 0600)    {jwt, expiresAt, accountAddress}
├── policies.json       (chmod 0600)    Policy schema (see CAPABILITY_POLICIES.md)
├── audit.csv           (chmod 0644)    ts,tool,risk,duration,status,notes
└── mcp.lock            (chmod 0600)    PID-based exclusive lock

mcp/src/
├── server.ts                  stdio MCP server entry
├── env.ts                     resolveConfig + validateNodeVersion
├── errors.ts                  typed McpError catalog (~50 codes)
├── client/
│   ├── sdk-shim.ts            (PRIVILEGED) re-exports privily-sdk surface
│   ├── sdk-wrapper.ts         getAuthenticatedRpcWrapper(storage)
│   └── sdk-errors.ts          interpretSdkError → {code, message, details}
├── storage/                   (PRIVILEGED) lockfile + LocalStorage + perms + encryption (Phase 15)
├── auth/                      (PRIVILEGED) jwt.ts + session.ts
├── signing/                   (PRIVILEGED) privily-appchain.ts (sk-handling)
├── admin/                     (PRIVILEGED) capabilities, import-access-key, status, disconnect
├── audit/                     redactor + logger
├── policies/                  loader + enforcement + daily-counter
├── envelopes/                 legacy secp256k1 verifier (diagnostic only)
├── prepare/                   runner + withdrawal + deposit + send + disperse + pay + local-envelope
├── submit/                    withdrawal + deposit + send(+external) + disperse(+external) + pay(+external) + external-chain-tx
├── request/                   create.ts + cancel.ts — Request tools (R2, no signing)
├── chains/
│   ├── adapter.ts             generic ChainAdapter interface
│   ├── privily/               appchain-side verifier + broadcaster + intent
│   ├── evm/                   parser + verifier + broadcaster + adapter
│   ├── tvm/                   Tron — parser + verifier + broadcaster + adapter + approval + allowance
│   ├── svm/                   Solana — parser + verifier + broadcaster + adapter
│   └── snvm/                  Starknet (external L2) — parser + verifier + broadcaster + adapter
├── validators/                address, freshness, idempotency, ownership
├── portfolio/                 account + balances read tools
├── reads/                     Phase 16 — history + prices + assets + requests + referral + addresses + quote + verify-signature + search + fetch
├── utils/                     prepare-cache (5 min TTL, 256-entry LRU)
└── tools/                     tool registry + per-tool handlers

License

MIT.