docs: align public copy with the brand voice - no em dashes, AP3X vocabulary, research-themed examples
This commit is contained in:
14
README.md
14
README.md
@@ -79,8 +79,8 @@ Self-hosting instructions are below.
|
||||
## Local signer (non-custodial path)
|
||||
|
||||
`packages/signer` is a local MCP server that holds your key and signs transactions locally
|
||||
instead of handing your mnemonic to a shared host. It has **no network access at all** —
|
||||
stdio transport, no Provider, no egress — so a key given to it never reaches a shared server
|
||||
instead of handing your mnemonic to a shared host. It has **no network access at all**:
|
||||
stdio transport only, no Provider, no egress, so a key given to it never reaches a shared server
|
||||
or your model provider. Four tools: `vector_signer_get_address`, `vector_signer_decode_transaction`,
|
||||
`vector_signer_sign`, `vector_signer_get_spend_limits`.
|
||||
|
||||
@@ -124,7 +124,7 @@ limitations.
|
||||
|
||||
| Tool | Description |
|
||||
|------|-------------|
|
||||
| `vector_build_send_apex` | Build an unsigned AP3X payment (keyless — sign with the local signer) |
|
||||
| `vector_build_send_apex` | Build an unsigned AP3X transfer (keyless - sign with the local signer) |
|
||||
| `vector_build_send_tokens` | Build an unsigned native-token transfer (keyless) |
|
||||
| `vector_build_transaction` | Build an unsigned multi-output transaction (keyless, never submits) |
|
||||
| `vector_dry_run` | Simulate a transaction without submitting - estimate fees and validate |
|
||||
@@ -267,7 +267,7 @@ If this instance will be reachable by anyone but you, set `MCP_AUTH_TOKENS` firs
|
||||
| `VECTOR_MCP_SESSION_IDLE_MS` | `/mcp` session idle timeout before the reaper closes it, in ms (min `100`) | `600000` (10 min) |
|
||||
| `VECTOR_MCP_SESSION_SWEEP_MS` | How often the `/mcp` idle reaper sweeps, in ms (min `100`) | `60000` (1 min) |
|
||||
| `VECTOR_MCP_MAX_SESSIONS_PER_IDENTITY` | Max concurrent `/mcp` sessions per identity before the oldest is evicted (min `1`) | `32` |
|
||||
| `MCP_AUTH_TOKENS` | Bearer tokens that may call this server. Comma-separated; each entry is `label:token` or a bare token. **When unset, the server is open to anyone who can reach it.** | _(unset — auth disabled)_ |
|
||||
| `MCP_AUTH_TOKENS` | Bearer tokens that may call this server. Comma-separated; each entry is `label:token` or a bare token. **When unset, the server is open to anyone who can reach it.** | _(unset - auth disabled)_ |
|
||||
|
||||
The four numeric knobs above fail loudly at startup on a malformed value: anything that is not
|
||||
a plain integer, or is below its listed minimum, raises a startup error naming the variable and
|
||||
@@ -292,10 +292,10 @@ it, lives in your local signer instead - see `VECTOR_SIGNER_SPEND_LIMIT_PER_TX`
|
||||
> (`VECTOR_MCP_MAX_SESSIONS_PER_IDENTITY`) - see the Configuration table above for defaults and
|
||||
> minimums.
|
||||
|
||||
> **Malformed values fail loudly, not silently.** The server refuses to start if
|
||||
> **Malformed values fail loudly.** The server refuses to start if
|
||||
> `MCP_AUTH_TOKENS` contains an empty token, a token with embedded whitespace, a
|
||||
> duplicate token, or a value where every comma-separated entry is blank (e.g. a
|
||||
> stray `,,,`) — each raises a startup error that names the problem but never
|
||||
> stray `,,,`). Each case raises a startup error that names the problem but never
|
||||
> echoes a token value. Commas delimit entries and cannot be escaped, so generate
|
||||
> tokens from a comma-free charset (hex / base64url / alphanumeric).
|
||||
|
||||
@@ -339,7 +339,7 @@ it, lives in your local signer instead - see `VECTOR_SIGNER_SPEND_LIMIT_PER_TX`
|
||||
npm run test:unit
|
||||
```
|
||||
|
||||
No wallet, no network — pure logic only (CBOR encode/decode assertions).
|
||||
No wallet, no network - pure logic only (CBOR encode/decode assertions).
|
||||
|
||||
```bash
|
||||
npm run test:smoke
|
||||
|
||||
150
docs/VECTOR.md
150
docs/VECTOR.md
@@ -1,4 +1,4 @@
|
||||
# Vector MCP Server — Tool Reference
|
||||
# Vector MCP Server - Tool Reference
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -6,7 +6,7 @@ The Vector MCP Server exposes blockchain tools that AI agents can use to interac
|
||||
|
||||
## Prerequisites
|
||||
|
||||
1. A Vector wallet address (source of funds) — the tools below are keyless and take no mnemonic; signing happens separately via your local signer
|
||||
1. A Vector wallet address that holds your AP3X - the tools below are keyless and take no mnemonic; signing happens separately via your local signer
|
||||
2. Access to Vector network endpoints (Ogmios, submit API, Koios)
|
||||
3. Node.js >= 14.0.0
|
||||
|
||||
@@ -14,10 +14,10 @@ The Vector MCP Server exposes blockchain tools that AI agents can use to interac
|
||||
|
||||
### vector_get_balance
|
||||
|
||||
Get ADA and token balances for any Vector address.
|
||||
Get AP3X and token balances for any Vector address.
|
||||
|
||||
**Parameters:**
|
||||
- `address` (string, required) — Vector address to check (addr1...)
|
||||
- `address` (string, required) - Vector address to check (addr1...)
|
||||
|
||||
**Example prompt:** "What's the balance of addr1qx..."
|
||||
|
||||
@@ -26,33 +26,33 @@ Get ADA and token balances for any Vector address.
|
||||
List unspent transaction outputs for an address.
|
||||
|
||||
**Parameters:**
|
||||
- `address` (string, required) — Address to query (addr1...)
|
||||
- `address` (string, required) - Address to query (addr1...)
|
||||
|
||||
**Example prompt:** "Show me the UTxOs at addr1qx..."
|
||||
|
||||
### vector_build_send_apex
|
||||
|
||||
Build an unsigned transaction sending AP3X to a recipient (keyless — takes no key material). Sign the returned CBOR with your local signer, then broadcast with `vector_submit_transaction`.
|
||||
Build an unsigned transaction sending AP3X to a recipient (keyless, takes no key material). Sign the returned CBOR with your local signer, then broadcast with `vector_submit_transaction`.
|
||||
|
||||
**Parameters:**
|
||||
- `changeAddress` (string, required) — Your wallet address; source of funds and change (addr1...)
|
||||
- `recipientAddress` (string, required) — Recipient address (addr1...)
|
||||
- `amount` (number, required) — Amount of AP3X to send (minimum 1)
|
||||
- `metadata` (string, optional) — Transaction metadata in JSON format
|
||||
- `changeAddress` (string, required) - Your wallet address; holds the AP3X and receives change (addr1...)
|
||||
- `recipientAddress` (string, required) - Recipient address (addr1...)
|
||||
- `amount` (number, required) - Amount of AP3X to send (minimum 1)
|
||||
- `metadata` (string, optional) - Transaction metadata in JSON format
|
||||
|
||||
**Example prompt:** "Send 5 AP3X to addr1qy..."
|
||||
|
||||
### vector_build_send_tokens
|
||||
|
||||
Build an unsigned transaction sending Vector native tokens (keyless — takes no key material). Sign the returned CBOR with your local signer, then broadcast with `vector_submit_transaction`.
|
||||
Build an unsigned transaction sending Vector native tokens (keyless, takes no key material). Sign the returned CBOR with your local signer, then broadcast with `vector_submit_transaction`.
|
||||
|
||||
**Parameters:**
|
||||
- `changeAddress` (string, required) — Your wallet address; source of funds and change (addr1...)
|
||||
- `recipientAddress` (string, required) — Recipient address (addr1...)
|
||||
- `policyId` (string, required) — Token policy ID
|
||||
- `assetName` (string, required) — Asset name (can be empty)
|
||||
- `amount` (string, required) — Amount of tokens to send
|
||||
- `adaAmount` (number, optional) — AP3X to include with tokens
|
||||
- `changeAddress` (string, required) - Your wallet address; holds the AP3X and receives change (addr1...)
|
||||
- `recipientAddress` (string, required) - Recipient address (addr1...)
|
||||
- `policyId` (string, required) - Token policy ID
|
||||
- `assetName` (string, required) - Asset name (can be empty)
|
||||
- `amount` (string, required) - Amount of tokens to send
|
||||
- `adaAmount` (number, optional) - AP3X to include with tokens
|
||||
|
||||
**Example prompt:** "Send 100 of token with policy abc123... to addr1qy..."
|
||||
|
||||
@@ -61,21 +61,21 @@ Build an unsigned transaction sending Vector native tokens (keyless — takes no
|
||||
Build an unsigned multi-output transaction with metadata (keyless, never submits). Sign the returned CBOR with your local signer, then broadcast with `vector_submit_transaction`.
|
||||
|
||||
**Parameters:**
|
||||
- `changeAddress` (string, required) — Your wallet address; source of funds and change (addr1...)
|
||||
- `outputs` (array, required) — Array of `{ address, lovelace, assets? }` objects
|
||||
- `metadata` (string, optional) — JSON metadata (attached under label 674)
|
||||
- `changeAddress` (string, required) - Your wallet address; holds the AP3X and receives change (addr1...)
|
||||
- `outputs` (array, required) - Array of `{ address, lovelace, assets? }` objects
|
||||
- `metadata` (string, optional) - JSON metadata (attached under label 674)
|
||||
|
||||
**Example prompt:** "Build a transaction sending 5 AP3X to addr1qx... and 3 AP3X to addr1qy..."
|
||||
|
||||
### vector_dry_run
|
||||
|
||||
Simulate a transaction without submitting — returns fee estimate and validation (keyless).
|
||||
Simulate a transaction without submitting - returns fee estimate and validation (keyless).
|
||||
|
||||
**Parameters:**
|
||||
- `txCbor` (string, optional) — Hex CBOR of a built transaction to evaluate
|
||||
- `outputs` (array, optional) — If no txCbor, build a TX from these outputs and evaluate
|
||||
- `changeAddress` (string, optional) — Your wallet address; required when building from outputs
|
||||
- `metadata` (string, optional) — JSON metadata when building from outputs
|
||||
- `txCbor` (string, optional) - Hex CBOR of a built transaction to evaluate
|
||||
- `outputs` (array, optional) - If no txCbor, build a TX from these outputs and evaluate
|
||||
- `changeAddress` (string, optional) - Your wallet address; required when building from outputs
|
||||
- `metadata` (string, optional) - JSON metadata when building from outputs
|
||||
|
||||
**Example prompt:** "How much would it cost to send 10 AP3X to addr1qx...?"
|
||||
|
||||
@@ -84,89 +84,89 @@ Simulate a transaction without submitting — returns fee estimate and validatio
|
||||
Get transaction history for an address via Koios indexed queries.
|
||||
|
||||
**Parameters:**
|
||||
- `address` (string, required) — Address to query (addr1...)
|
||||
- `limit` (number, optional) — Number of transactions (default: 20, max: 50)
|
||||
- `offset` (number, optional) — Pagination offset (default: 0)
|
||||
- `address` (string, required) - Address to query (addr1...)
|
||||
- `limit` (number, optional) - Number of transactions (default: 20, max: 50)
|
||||
- `offset` (number, optional) - Pagination offset (default: 0)
|
||||
|
||||
**Example prompt:** "Show me recent transactions for addr1qx..."
|
||||
|
||||
### vector_build_deploy_contract
|
||||
|
||||
Build an unsigned transaction deploying a Plutus/Aiken smart contract by locking funds at its script address (keyless). Sign the returned CBOR with your local signer, then broadcast with `vector_submit_transaction`.
|
||||
Build an unsigned transaction deploying a Plutus/Aiken smart contract by locking AP3X at its script address (keyless). Sign the returned CBOR with your local signer, then broadcast with `vector_submit_transaction`.
|
||||
|
||||
**Parameters:**
|
||||
- `changeAddress` (string, required) — Your wallet address; source of funds and change (addr1...)
|
||||
- `scriptCbor` (string, required) — Compiled script CBOR hex
|
||||
- `scriptType` (string, required) — "PlutusV1", "PlutusV2", or "PlutusV3"
|
||||
- `initialDatum` (string, optional) — Datum CBOR hex (default: void `d87980`)
|
||||
- `lovelaceAmount` (number, optional) — AP3X to lock in lovelace (default: 2 AP3X)
|
||||
- `changeAddress` (string, required) - Your wallet address; holds the AP3X and receives change (addr1...)
|
||||
- `scriptCbor` (string, required) - Compiled script CBOR hex
|
||||
- `scriptType` (string, required) - "PlutusV1", "PlutusV2", or "PlutusV3"
|
||||
- `initialDatum` (string, optional) - Datum CBOR hex (default: void `d87980`)
|
||||
- `lovelaceAmount` (number, optional) - AP3X to lock in lovelace (default: 2 AP3X)
|
||||
|
||||
**Example prompt:** "Deploy this smart contract with 5 AP3X locked"
|
||||
|
||||
### vector_build_interact_contract
|
||||
|
||||
Build an unsigned transaction interacting with a deployed smart contract — lock funds or spend from it (keyless). Sign the returned CBOR with your local signer, then broadcast with `vector_submit_transaction`.
|
||||
Build an unsigned transaction interacting with a deployed smart contract: lock AP3X or spend from it (keyless). Sign the returned CBOR with your local signer, then broadcast with `vector_submit_transaction`.
|
||||
|
||||
**Parameters:**
|
||||
- `changeAddress` (string, required) — Your wallet address; source of funds, change, and the required signer for spends (addr1...)
|
||||
- `scriptCbor` (string, required) — Compiled script CBOR hex
|
||||
- `scriptType` (string, required) — "PlutusV1", "PlutusV2", or "PlutusV3"
|
||||
- `action` (string, required) — "spend" or "lock"
|
||||
- `redeemer` (string, optional) — Redeemer CBOR hex (for spend, default: void)
|
||||
- `datum` (string, optional) — Datum CBOR hex (for lock, default: void)
|
||||
- `lovelaceAmount` (number, optional) — Lovelace to lock (for lock)
|
||||
- `utxoRef` (object, optional) — `{ txHash, outputIndex }` for specific UTxO (for spend)
|
||||
- `assets` (object, optional) — Native assets for lock: `{ "unit": "quantity" }`
|
||||
- `changeAddress` (string, required) - Your wallet address; holds the AP3X, receives change, and provides the required signer for spends (addr1...)
|
||||
- `scriptCbor` (string, required) - Compiled script CBOR hex
|
||||
- `scriptType` (string, required) - "PlutusV1", "PlutusV2", or "PlutusV3"
|
||||
- `action` (string, required) - "spend" or "lock"
|
||||
- `redeemer` (string, optional) - Redeemer CBOR hex (for spend, default: void)
|
||||
- `datum` (string, optional) - Datum CBOR hex (for lock, default: void)
|
||||
- `lovelaceAmount` (number, optional) - Lovelace to lock (for lock)
|
||||
- `utxoRef` (object, optional) - `{ txHash, outputIndex }` for specific UTxO (for spend)
|
||||
- `assets` (object, optional) - Native assets for lock: `{ "unit": "quantity" }`
|
||||
|
||||
**Example prompt:** "Collect funds from the escrow contract at addr1..."
|
||||
**Example prompt:** "Collect the locked AP3X from the escrow contract at addr1..."
|
||||
|
||||
### vector_build_register_agent
|
||||
|
||||
Build an unsigned transaction that registers an agent in the Vector on-chain registry — mints a soulbound identity NFT and locks a 10 AP3X deposit (keyless). Sign the returned CBOR with your local signer, then broadcast with `vector_submit_transaction`.
|
||||
Build an unsigned transaction that registers an agent in the Vector on-chain registry: mints a soulbound identity NFT and locks a 10 AP3X deposit (keyless). Sign the returned CBOR with your local signer, then broadcast with `vector_submit_transaction`.
|
||||
|
||||
**Parameters:**
|
||||
- `changeAddress` (string, required) — Your wallet address; source of the deposit and change (addr1...)
|
||||
- `name` (string, required) — Agent name (max 64 chars)
|
||||
- `description` (string, required) — Short description of the agent's purpose (max 256 chars)
|
||||
- `capabilities` (array of strings, required) — Capability tags (e.g. `["investing", "research"]`)
|
||||
- `framework` (string, required) — Framework used (e.g. "OpenClaw", "LangChain", "CrewAI", "custom")
|
||||
- `endpoint` (string, required) — A2A communication endpoint URL (or empty string if not applicable)
|
||||
- `changeAddress` (string, required) - Your wallet address; source of the deposit and change (addr1...)
|
||||
- `name` (string, required) - Agent name (max 64 chars)
|
||||
- `description` (string, required) - Short description of the agent's purpose (max 256 chars)
|
||||
- `capabilities` (array of strings, required) - Capability tags (e.g. `["research", "data-extraction"]`)
|
||||
- `framework` (string, required) - Framework used (e.g. "OpenClaw", "LangChain", "CrewAI", "custom")
|
||||
- `endpoint` (string, required) - A2A communication endpoint URL (or empty string if not applicable)
|
||||
|
||||
**Example prompt:** "Register an agent named TradingBot with investing and research capabilities"
|
||||
**Example prompt:** "Register an agent named ResearchAgent with research and data-extraction capabilities"
|
||||
|
||||
### vector_build_update_agent
|
||||
|
||||
Build an unsigned transaction that updates a registered agent's profile fields — only the specified fields change, the rest are preserved (keyless). Sign the returned CBOR with your local signer, then broadcast with `vector_submit_transaction`.
|
||||
Build an unsigned transaction that updates a registered agent's profile fields: only the specified fields change, the rest are preserved (keyless). Sign the returned CBOR with your local signer, then broadcast with `vector_submit_transaction`.
|
||||
|
||||
**Parameters:**
|
||||
- `changeAddress` (string, required) — The agent owner's wallet address (addr1...)
|
||||
- `agent_id` (string, required) — Agent DID to update: `did:vector:agent:{policyId}:{nftAssetName}`
|
||||
- `name` (string, optional) — New agent name
|
||||
- `description` (string, optional) — New description
|
||||
- `capabilities` (array of strings, optional) — New capability tags (replaces the existing list)
|
||||
- `framework` (string, optional) — New framework identifier
|
||||
- `endpoint` (string, optional) — New A2A endpoint URL (or empty string to clear)
|
||||
- `changeAddress` (string, required) - The agent owner's wallet address (addr1...)
|
||||
- `agent_id` (string, required) - Agent DID to update: `did:vector:agent:{policyId}:{nftAssetName}`
|
||||
- `name` (string, optional) - New agent name
|
||||
- `description` (string, optional) - New description
|
||||
- `capabilities` (array of strings, optional) - New capability tags (replaces the existing list)
|
||||
- `framework` (string, optional) - New framework identifier
|
||||
- `endpoint` (string, optional) - New A2A endpoint URL (or empty string to clear)
|
||||
|
||||
**Example prompt:** "Update my agent's description to mention DeFi support"
|
||||
**Example prompt:** "Update my agent's description to mention archive-extraction support"
|
||||
|
||||
### vector_build_transfer_agent
|
||||
|
||||
Build an unsigned transaction that transfers agent ownership to a new address (keyless). Sign the returned CBOR with your local signer, then broadcast with `vector_submit_transaction`.
|
||||
|
||||
**Parameters:**
|
||||
- `changeAddress` (string, required) — The CURRENT owner's wallet address (addr1...)
|
||||
- `agent_id` (string, required) — Agent DID to transfer: `did:vector:agent:{policyId}:{nftAssetName}`
|
||||
- `new_owner_address` (string, required) — Bech32 address of the new owner (must be a verification-key address, not a script address)
|
||||
- `changeAddress` (string, required) - The CURRENT owner's wallet address (addr1...)
|
||||
- `agent_id` (string, required) - Agent DID to transfer: `did:vector:agent:{policyId}:{nftAssetName}`
|
||||
- `new_owner_address` (string, required) - Bech32 address of the new owner (must be a verification-key address, not a script address)
|
||||
|
||||
**Example prompt:** "Transfer my agent to addr1qy..."
|
||||
|
||||
### vector_build_deregister_agent
|
||||
|
||||
Build an unsigned transaction that deregisters an agent — burns the identity NFT and returns the 10 AP3X deposit to the owner wallet (keyless). Sign the returned CBOR with your local signer, then broadcast with `vector_submit_transaction`.
|
||||
Build an unsigned transaction that deregisters an agent: burns the identity NFT and returns the 10 AP3X deposit to the owner wallet (keyless). Sign the returned CBOR with your local signer, then broadcast with `vector_submit_transaction`.
|
||||
|
||||
**Parameters:**
|
||||
- `changeAddress` (string, required) — The agent owner's wallet address; receives the returned deposit (addr1...)
|
||||
- `agent_id` (string, required) — Agent DID to deregister: `did:vector:agent:{policyId}:{nftAssetName}`
|
||||
- `changeAddress` (string, required) - The agent owner's wallet address; receives the returned deposit (addr1...)
|
||||
- `agent_id` (string, required) - Agent DID to deregister: `did:vector:agent:{policyId}:{nftAssetName}`
|
||||
|
||||
**Example prompt:** "Deregister my agent and reclaim the deposit"
|
||||
|
||||
@@ -175,10 +175,10 @@ Build an unsigned transaction that deregisters an agent — burns the identity N
|
||||
Build an unsigned transaction that sends an on-chain message to a registered agent via TX metadata (label 674), delivering 2 AP3X to the agent's owner address (keyless). Sign the returned CBOR with your local signer, then broadcast with `vector_submit_transaction`.
|
||||
|
||||
**Parameters:**
|
||||
- `changeAddress` (string, required) — Your wallet address; pays the 2 AP3X delivery and fee (addr1...)
|
||||
- `agent_id` (string, required) — Recipient agent DID: `did:vector:agent:{policyId}:{nftAssetName}`
|
||||
- `message_type` (string, required) — Type of message: "inquiry", "proposal", or "result"
|
||||
- `payload` (string, required) — Message payload (max 512 chars)
|
||||
- `changeAddress` (string, required) - Your wallet address; covers the 2 AP3X delivery and fee (addr1...)
|
||||
- `agent_id` (string, required) - Recipient agent DID: `did:vector:agent:{policyId}:{nftAssetName}`
|
||||
- `message_type` (string, required) - Type of message: "inquiry", "proposal", or "result"
|
||||
- `payload` (string, required) - Message payload (max 512 chars)
|
||||
|
||||
**Example prompt:** "Send an inquiry message to did:vector:agent:... asking about pricing"
|
||||
|
||||
@@ -261,6 +261,6 @@ and [docs/architecture/non-custodial-split.md](architecture/non-custodial-split.
|
||||
|
||||
All tools return helpful error messages with troubleshooting tips. Common issues:
|
||||
|
||||
- **Insufficient balance** — Ensure the wallet at `changeAddress` has enough AP3X for the transaction + fees
|
||||
- **Connection failed** — Verify Ogmios endpoint is reachable
|
||||
- **Invalid address** — Vector addresses start with `addr1`
|
||||
- **Insufficient balance** - Ensure the wallet at `changeAddress` has enough AP3X for the transaction + fees
|
||||
- **Connection failed** - Verify Ogmios endpoint is reachable
|
||||
- **Invalid address** - Vector addresses start with `addr1`
|
||||
|
||||
@@ -20,7 +20,7 @@ The server's spend limits and rate limits are app-layer only, so they do not con
|
||||
Success criteria:
|
||||
1. No hosted tool accepts a mnemonic or private key. Grep-able: zero `mnemonic` params in the builder package.
|
||||
2. The signing key never enters the model context, never crosses the network.
|
||||
3. Spend policy is enforced where it is actually enforceable — locally, per user.
|
||||
3. Spend policy is enforced where it is actually enforceable: locally, per user.
|
||||
4. Every PR in the series is independently reviewable and leaves `main` coherent.
|
||||
|
||||
## 3. Decisions (locked)
|
||||
@@ -30,14 +30,14 @@ Success criteria:
|
||||
| Deployment reality | Hosted multi-tenant is flagship → seed-to-server is P0 |
|
||||
| Architecture | Keyless hosted **builder** + local companion **signer** |
|
||||
| Repo topology | Single repo, **npm workspaces** monorepo |
|
||||
| Migration | **Hard cut per family** — no dual-mode, no deprecation window |
|
||||
| Migration | **Hard cut per family** - no dual-mode, no deprecation window |
|
||||
| Key storage | Pluggable `KeySource`; v1 = env var / `0600` file |
|
||||
| Signer policy | Decode tx → **net outflow** → limits → audit → sign. Fully autonomous |
|
||||
| Tool flow | 4 calls: `get_address` → `build_*` → `sign` → `submit` |
|
||||
| Tool naming | Explicit `build_*` / `signer_*` / `submit_*` prefixes |
|
||||
| Cross-cutting fixes | Entangled ones inline; independent ones as separate small PRs |
|
||||
| Rollout | Testnet auto-deploys and may break; mainnet held until complete |
|
||||
| Testing | **TDD** — tests written first |
|
||||
| Testing | **TDD** - tests written first |
|
||||
| Auth | Enforced when `MCP_AUTH_TOKENS` is configured |
|
||||
|
||||
## 4. Architecture
|
||||
@@ -46,17 +46,17 @@ Success criteria:
|
||||
mcp-server/ npm workspaces root
|
||||
├── packages/
|
||||
│ ├── shared/ @apexfusion/vector-mcp-shared
|
||||
│ │ exports "./tx" pure CBOR, types, formatting — NO network
|
||||
│ │ exports "./provider" OgmiosProvider, endpoints — network
|
||||
│ │ exports "./tx" pure CBOR, types, formatting - NO network
|
||||
│ │ exports "./provider" OgmiosProvider, endpoints - network
|
||||
│ ├── builder/ @apexfusion/vector-mcp-builder HOSTED · keyless · network
|
||||
│ └── signer/ @apexfusion/vector-mcp-signer LOCAL · holds key · no network
|
||||
```
|
||||
|
||||
Two properties carry the security argument:
|
||||
|
||||
**The signer has no network interface, inbound or outbound.** It speaks **stdio** (the natural MCP local transport) — no port, no listener. Submission lives on the builder, so it has no egress either. The signer is a pure function: `CBOR in → signed CBOR out`, gated by policy.
|
||||
**The signer has no network interface, inbound or outbound.** It speaks **stdio** (the natural MCP local transport): no port, no listener. Submission lives on the builder, so it has no egress either. The signer is a pure function: `CBOR in → signed CBOR out`, gated by policy.
|
||||
|
||||
**The boundary is enforced by the module graph, not convention.** `shared` uses subpath exports; the signer imports `@apexfusion/vector-mcp-shared/tx` and *cannot* reach `/provider`. A reviewer can verify the signer is network-free by reading its imports.
|
||||
**The module graph enforces the boundary.** `shared` uses subpath exports; the signer imports `@apexfusion/vector-mcp-shared/tx` and *cannot* reach `/provider`. A reviewer can verify the signer is network-free by reading its imports.
|
||||
|
||||
### Why zero-network is possible
|
||||
|
||||
@@ -119,7 +119,7 @@ interface Policy {
|
||||
|
||||
`vector_signer_decode_transaction` is a read-only preview so an agent can show a human what it is about to sign.
|
||||
|
||||
**Accepted trade-off of the 4-call shape:** the signer records **intent to spend** at signing time, not confirmed submission. If a submit later fails, the audit log over-counts. Conservative, therefore acceptable — and it is the price of the signer having no network access. Documented, not hidden.
|
||||
**Accepted trade-off of the 4-call shape:** the signer records **intent to spend** at signing time, not confirmed submission. If a submit later fails, the audit log over-counts. Conservative, therefore acceptable, and it is the price of the signer having no network access.
|
||||
|
||||
### Two-step self-improvement flow
|
||||
|
||||
@@ -134,18 +134,18 @@ This is why self-improvement is the last family: the LLM must drive a stateful,
|
||||
|
||||
## 7. Tool inventory
|
||||
|
||||
**Builder — 24 tools at full migration, zero keys.**
|
||||
**Builder - 24 tools at full migration, zero keys.**
|
||||
|
||||
| Group | Tools |
|
||||
|---|---|
|
||||
| Read-only (8, unchanged) | `get_balance`, `get_utxos`, `get_transaction_history`, `dry_run`, `discover_agents`, `get_agent_profile`, `self_improvement_browse`, `self_improvement_analyze_metrics` |
|
||||
| Build — tx (3) | `build_send_apex`, `build_send_tokens`, `build_transaction` |
|
||||
| Build — contracts (2) | `build_deploy_contract`, `build_interact_contract` |
|
||||
| Build — registry (5) | `build_register_agent`, `build_update_agent`, `build_transfer_agent`, `build_deregister_agent`, `build_message_agent` |
|
||||
| Build — self-improvement (4) | `build_self_improvement_proposal_lock`, `build_self_improvement_proposal_spend`, `build_self_improvement_critique`, `build_self_improvement_endorse` |
|
||||
| Build - tx (3) | `build_send_apex`, `build_send_tokens`, `build_transaction` |
|
||||
| Build - contracts (2) | `build_deploy_contract`, `build_interact_contract` |
|
||||
| Build - registry (5) | `build_register_agent`, `build_update_agent`, `build_transfer_agent`, `build_deregister_agent`, `build_message_agent` |
|
||||
| Build - self-improvement (4) | `build_self_improvement_proposal_lock`, `build_self_improvement_proposal_spend`, `build_self_improvement_critique`, `build_self_improvement_endorse` |
|
||||
| Chain ops (2, new) | `submit_transaction`, `await_transaction` |
|
||||
|
||||
**Signer — 4 tools, holds the key.**
|
||||
**Signer - 4 tools, holds the key.**
|
||||
|
||||
`signer_get_address` · `signer_decode_transaction` · `signer_sign` · `signer_get_spend_limits`
|
||||
|
||||
@@ -168,7 +168,7 @@ TDD throughout: failing test before implementation, every PR.
|
||||
| `signer` | **Adversarial** (below) | throwaway key | no |
|
||||
| integration | build→sign→submit round trip | **funded testnet** | yes |
|
||||
|
||||
**Mandatory adversarial cases for the signer** — these are the security boundary:
|
||||
**Mandatory adversarial cases for the signer** - these are the security boundary:
|
||||
|
||||
- Drain to a foreign address exceeding the limit → **refused**
|
||||
- Change returning to own address → **excluded** from net outflow
|
||||
@@ -185,13 +185,13 @@ TDD throughout: failing test before implementation, every PR.
|
||||
|---|---|---|
|
||||
| **1** | CI harness: build + unit tests. Ships this design doc into `docs/architecture/` so reviewers have the rationale | no |
|
||||
| **2** | Fix the 8 type errors (import Zod via `zod/v4`) + **typecheck becomes a hard gate** | no |
|
||||
| **3** | Workspace scaffolding — pure `git mv`, zero logic change, validated by PR 1–2 CI | no |
|
||||
| **3** | Workspace scaffolding: pure `git mv`, zero logic change, validated by PR 1–2 CI | no |
|
||||
| **4** | **Signer package** (purely additive): `KeySource`, decode, `PolicyEngine`, audit, 4 tools. Heavy TDD | no |
|
||||
| **5** | Builder hardening: auth (enforced when configured), per-identity rate limits, `submit_transaction`, `await_transaction` | no |
|
||||
| **6** | **Family 1 — wallet/tx keyless.** Mnemonics removed; `build_*` tools land ✅ **(landed — this PR)** | **yes** |
|
||||
| **7** | **Family 2 — agent registry keyless.** Drops `@ts-nocheck` from `agent-network.ts` ✅ **(landed — this PR)** | yes |
|
||||
| **8** | **Family 3 — self-improvement keyless.** Ref-UTxO env config carried over from the custodial code; the suspected CBOR encoding issue closed as no-bug (audit #10, golden-pinned against a live on-chain token) ✅ **(landed — this PR)** | yes |
|
||||
| **9** | Mainnet cutover: README, security-page correction, deliberate `workflow_dispatch` ✅ **(landed — deployed 2026-07-31)** | — |
|
||||
| **6** | **Family 1: wallet/tx keyless.** Mnemonics removed; `build_*` tools land ✅ **(landed - this PR)** | **yes** |
|
||||
| **7** | **Family 2: agent registry keyless.** Drops `@ts-nocheck` from `agent-network.ts` ✅ **(landed - this PR)** | yes |
|
||||
| **8** | **Family 3: self-improvement keyless.** Ref-UTxO env config carried over from the custodial code; the suspected CBOR encoding issue closed as no-bug (audit #10, golden-pinned against a live on-chain token) ✅ **(landed - this PR)** | yes |
|
||||
| **9** | Mainnet cutover: README, security-page correction, deliberate `workflow_dispatch` ✅ **(landed - deployed 2026-07-31)** | - |
|
||||
|
||||
**PR 4 is the review that matters.** It is additive, self-contained, and *is* the security boundary. It breaks nothing while it is reviewed.
|
||||
|
||||
@@ -216,14 +216,14 @@ TDD throughout: failing test before implementation, every PR.
|
||||
PR1 ─→ PR2 ─→ PR3 ─┬─→ PR4 (signer) ─┐
|
||||
└─→ PR5 (builder hardening) ─┴─→ PR6 ─→ PR7 ─→ PR8 ─→ PR9
|
||||
|
||||
A…H independent of the spine and of each other — land any time
|
||||
A…H independent of the spine and of each other - land any time
|
||||
```
|
||||
|
||||
PR 4 and PR 5 both depend on the workspace split (PR 3) and can proceed in parallel; PR 6 needs both, because the first keyless family requires a signer to sign with and a `submit_transaction` to submit through.
|
||||
|
||||
## 11. Rollout
|
||||
|
||||
Testnet auto-deploys on push to `main` (`deploy.yml`) and **is the proving ground** — breaking it from PR 6 onward is expected and useful signal. Mainnet deploys only by manual `workflow_dispatch` and **stayed on the last custodial image until PR 9** ✅ **(cutover deployed 2026-07-31, both networks now run the same image)**. This uses the existing CI split as designed; no new infrastructure.
|
||||
Testnet auto-deploys on push to `main` (`deploy.yml`) and **is the proving ground**: breaking it from PR 6 onward is expected and useful signal. Mainnet deploys only by manual `workflow_dispatch` and **stayed on the last custodial image until PR 9** ✅ **(cutover deployed 2026-07-31, both networks now run the same image)**. This uses the existing CI split as designed; no new infrastructure.
|
||||
|
||||
## 12. Findings from local verification
|
||||
|
||||
@@ -231,30 +231,30 @@ Verified locally on Windows 11, **Node v25.8.0 / npm 11.11.0**: `npm ci` clean i
|
||||
|
||||
> **Verification caveat:** local Node is **25.8.0**; the Dockerfile deploys on **node:22-alpine**, and `package.json` claims `>=14`. Local green does not prove green on the deploy runtime. PR 1's CI must pin the **Node 22** matrix so the gate matches production, and PR B corrects the declared floor.
|
||||
|
||||
**`npm run typecheck` fails on `main` — 8 errors, all `src/vector/vector.ts`.**
|
||||
**`npm run typecheck` fails on `main`: 8 errors, all `src/vector/vector.ts`.**
|
||||
|
||||
```
|
||||
512,3 · 702,3 · 782,3 · 920,3 · 990,3 · 1192,3 · 1250,3 TS2589 instantiation excessively deep
|
||||
1250,10 TS2769 no overload — z.record(z.string()).optional()
|
||||
1250,10 TS2769 no overload - z.record(z.string()).optional()
|
||||
```
|
||||
|
||||
CI only runs `npm run build`, and `tsup`/esbuild strips types without checking them, so this has never been caught.
|
||||
|
||||
**This corrects an earlier misattribution.** The `@ts-nocheck` headers are attributed in-code to "Cardano WASM imports," but the real cause is `server.tool()` generic inference exceeding TypeScript's instantiation depth on rich Zod schemas — the same failure leaking out of the one file lacking the escape hatch.
|
||||
**This corrects an earlier misattribution.** The `@ts-nocheck` headers are attributed in-code to "Cardano WASM imports," but the real cause is `server.tool()` generic inference exceeding TypeScript's instantiation depth on rich Zod schemas: the same failure leaking out of the one file lacking the escape hatch.
|
||||
|
||||
**Root cause, resolved (PR 2).** The initial hypothesis — a stale `@modelcontextprotocol/sdk@^1.4.1` — was wrong: `^1.4.1` is the declared *range*, and npm resolves it to 1.27.1. The actual cause is that the app imported Zod 3 (`from "zod"`) while the SDK expects Zod 4; its `ZodRawShapeCompat` dual-compat type degrades on Zod 3 schemas, and anything richer than a bare `z.string()` exceeds the instantiation limit. The TS2769 is a downstream symptom, not a second defect. Ruled out by experiment before landing the fix: upgrading the SDK to 1.29.0, a duplicate Zod in the tree, pinning Zod 3.24.4, `moduleResolution: bundler`/`nodenext`, the newer `registerTool` API, and hoisting schemas to a `const`. The fix is importing via the `zod/v4` subpath — already shipped inside `zod@3.25.76` — so no dependency change was needed.
|
||||
**Root cause, resolved (PR 2).** The initial hypothesis, a stale `@modelcontextprotocol/sdk@^1.4.1`, was wrong: `^1.4.1` is the declared *range*, and npm resolves it to 1.27.1. The actual cause is that the app imported Zod 3 (`from "zod"`) while the SDK expects Zod 4; its `ZodRawShapeCompat` dual-compat type degrades on Zod 3 schemas, and anything richer than a bare `z.string()` exceeds the instantiation limit. The TS2769 is a downstream symptom, not a second defect. Ruled out by experiment before landing the fix: upgrading the SDK to 1.29.0, a duplicate Zod in the tree, pinning Zod 3.24.4, `moduleResolution: bundler`/`nodenext`, the newer `registerTool` API, and hoisting schemas to a `const`. The fix is importing via the `zod/v4` subpath, already shipped inside `zod@3.25.76`, so no dependency change was needed.
|
||||
|
||||
**Accepted trade-off.** `zod/v4` routes schemas through a different JSON Schema serializer than v3 (the SDK uses the vendored `zod-to-json-schema` for v3, Zod's native `toJSONSchema` for v4). Consequently **no tool advertises `additionalProperties: false` any more**, where the v3 path emitted it. All three tool modules were converted together so the 23-tool surface stays uniform rather than split. Runtime enforcement is unaffected — missing required arguments are still rejected, and extra keys were already stripped under v3. `test/smoke/tool-schemas.snapshot.json` pins the resulting surface so any further drift is deliberate.
|
||||
**Accepted trade-off.** `zod/v4` routes schemas through a different JSON Schema serializer than v3 (the SDK uses the vendored `zod-to-json-schema` for v3, Zod's native `toJSONSchema` for v4). Consequently **no tool advertises `additionalProperties: false` any more**, where the v3 path emitted it. All three tool modules were converted together so the 23-tool surface stays uniform rather than split. Runtime enforcement is unaffected: missing required arguments are still rejected, and extra keys were already stripped under v3. `test/smoke/tool-schemas.snapshot.json` pins the resulting surface so any further drift is deliberate.
|
||||
|
||||
## 13. Residual risks
|
||||
|
||||
**Native-asset outflow is not limited in v1.** Spend limits apply to lovelace. A transaction moving ~2 AP3X plus a valuable NFT to a foreign address passes the lovelace check. Blanket-denying asset outflow is not viable because legitimate registry flows mint an NFT to the registry script address.
|
||||
|
||||
v1 mitigation: all asset movements are decoded, logged, and returned in the policy decision, and `assetOutflowPolicy` is configurable (`allow` default | `deny` | `allowlist`) for high-value wallets. **Stated as a known limitation, not glossed.** Post-v1: per-policy asset rules.
|
||||
v1 mitigation: all asset movements are decoded, logged, and returned in the policy decision, and `assetOutflowPolicy` is configurable (`allow` default | `deny` | `allowlist`) for high-value wallets. Post-v1: per-policy asset rules.
|
||||
|
||||
**Key at rest is plaintext in v1** (env var or `0600` file). The win is that the key is *local and never transits the LLM or a shared host* — not that it is in an HSM. `KeySource` exists so keychain/HSM/hardware land later without touching the sign path.
|
||||
**Key at rest is plaintext in v1** (env var or `0600` file). The win is that the key is *local and never transits the LLM or a shared host*, not that it is in an HSM. `KeySource` exists so keychain/HSM/hardware land later without touching the sign path.
|
||||
|
||||
**Prompt injection is mitigated, not eliminated.** The signer refuses transactions violating policy, but an injected LLM can still cause any transaction *within* policy. Recipient allowlisting is the next lever.
|
||||
**Prompt injection is a residual risk.** The signer refuses transactions violating policy, but an injected LLM can still cause any transaction *within* policy. Recipient allowlisting is the next lever.
|
||||
|
||||
## 14. Open items
|
||||
|
||||
|
||||
@@ -8,29 +8,29 @@ provider.
|
||||
|
||||
## Why it has no network
|
||||
|
||||
- **stdio transport only** — no port, no HTTP listener, nothing to connect to
|
||||
- **no Provider and no Lucid instance** — signing uses CML directly
|
||||
- **stdio transport only** - no port, no HTTP listener, nothing to connect to
|
||||
- **no Provider and no Lucid instance** - signing uses CML directly
|
||||
- **submission lives on the builder**, so the signer needs no egress either
|
||||
|
||||
`test/boundary.test.ts` checks this mechanically — it walks every file under `src/` for a
|
||||
`test/boundary.test.ts` checks this mechanically: it walks every file under `src/` for a
|
||||
network-capable import or an ambient network global (`fetch`, `WebSocket`, ...), and checks
|
||||
`package.json` for a network-capable dependency. That check is a text scan, not a sandbox, and
|
||||
it is scoped accordingly: it reliably catches *accidental* regressions and *casual* misuse — an
|
||||
it is scoped accordingly: it reliably catches *accidental* regressions and *casual* misuse, an
|
||||
added dependency, a forgotten forbidden import, an unthinking `fetch(...)` call, including one
|
||||
routed around the bare identifier via `globalThis['fetch']` or `globalThis.fetch`. It does not
|
||||
catch *deliberate* obfuscation. Verified directly against the hardened check: an ambient global
|
||||
reached through a plain alias (`const f = fetch; f(...)`) or a forbidden module specifier
|
||||
assembled at runtime from concatenated strings (`import('node:' + 'dns')`) both still pass.
|
||||
Closing gaps like those is a code-review responsibility, not something a fixed set of text
|
||||
patterns can guarantee — what this check actually verifies is that today's shipped `src/` has no
|
||||
patterns can guarantee: what this check actually verifies is that today's shipped `src/` has no
|
||||
*accidental or careless* network capability, not a proof that none could ever be smuggled in.
|
||||
|
||||
`test/integration/roundtrip.test.ts` imports `@lucid-evolution/lucid` and the shared package's
|
||||
`/provider` — that does **not** weaken the check above. That file lives under `test/`,
|
||||
`/provider` - that does **not** weaken the check above. That file lives under `test/`,
|
||||
which `boundary.test.ts` deliberately scans only as far as `src/`, and which the build never
|
||||
bundles (`tsup`'s only entry point is `src/index.ts`). Those imports exist so the test can
|
||||
build a real transaction the way the hosted *builder* does (no key) before handing it to the
|
||||
signer's actual decode/policy/sign code path — the network calls belong to the test's
|
||||
signer's actual decode/policy/sign code path: the network calls belong to the test's
|
||||
stand-in builder, never to code that ships.
|
||||
|
||||
## Defense in depth
|
||||
@@ -43,13 +43,13 @@ expose:
|
||||
by one function whose parameter list never includes the key source. That's structural, not a
|
||||
matter of discipline: reintroducing signing capability there means visibly changing that
|
||||
function's signature, not adding a line inside a handler.
|
||||
- **Key material resists accidental logging.** TypeScript `private` is compile-time only — at
|
||||
- **Key material resists accidental logging.** TypeScript `private` is compile-time only: at
|
||||
runtime the field is an ordinary enumerable property, fully visible to `JSON.stringify`,
|
||||
`console.log`, and `util.inspect`. Mnemonics and derived private keys are therefore held in
|
||||
native JavaScript `#private` fields instead, and both `KeySource` classes override
|
||||
`toJSON()` and Node's inspect symbol so that even a stray `console.log(keySource)` prints a
|
||||
safe description instead of the secret. This defends against *accidental* disclosure (a
|
||||
debugging log line, a crash reporter serializing local state) — it is not a defense against
|
||||
debugging log line, a crash reporter serializing local state); it is not a defense against
|
||||
a compromised process, which can still read the key out of memory directly.
|
||||
- **The audit log is crash-safe.** Every entry is written to a temp file and renamed into
|
||||
place, so a process kill mid-write costs at most the one entry in flight, never the rest of
|
||||
@@ -63,11 +63,11 @@ Pick one key source:
|
||||
|---|---|
|
||||
| `VECTOR_SIGNER_MNEMONIC_FILE` | Path to a file containing your mnemonic. Use `chmod 600`. Recommended. |
|
||||
| `VECTOR_SIGNER_MNEMONIC` | The mnemonic inline. Convenient, but ends up in your shell history and process environment. |
|
||||
| `VECTOR_SIGNER_PRIVATE_KEY` + `VECTOR_SIGNER_ADDRESS` | A derived bech32 signing key. **Safest in terms of blast radius** — it controls one account rather than your whole HD tree, so a leak is bounded. See the mismatch caveat under Known limitations. |
|
||||
| `VECTOR_SIGNER_PRIVATE_KEY` + `VECTOR_SIGNER_ADDRESS` | A derived bech32 signing key. **Safest in terms of blast radius**: it controls one account rather than your whole HD tree, so a leak is bounded. See the mismatch caveat under Known limitations. |
|
||||
|
||||
If more than one is set, `config.ts` checks them in this order: `VECTOR_SIGNER_PRIVATE_KEY` +
|
||||
`VECTOR_SIGNER_ADDRESS`, then `VECTOR_SIGNER_MNEMONIC`, then `VECTOR_SIGNER_MNEMONIC_FILE`.
|
||||
Unset the ones you are not using — a leftover `VECTOR_SIGNER_MNEMONIC` in your shell profile
|
||||
Unset the ones you are not using: a leftover `VECTOR_SIGNER_MNEMONIC` in your shell profile
|
||||
silently wins over a `VECTOR_SIGNER_MNEMONIC_FILE` you just configured.
|
||||
|
||||
Optional:
|
||||
@@ -81,12 +81,12 @@ Optional:
|
||||
|
||||
**`VECTOR_SIGNER_AUDIT_LOG_PATH`, precisely:** if unset, the log is written to
|
||||
`vector-signer-audit-log.json` resolved against the *package's own install location* (three
|
||||
directories up from `build/index.js` — the monorepo root, in this repo), **not** against
|
||||
directories up from `build/index.js`, the monorepo root, in this repo), **not** against
|
||||
whatever directory the process happened to be started from. That makes the location
|
||||
predictable regardless of your MCP client's working directory, but it also means the file is
|
||||
easy to forget about. Like the builder's `vector-audit-log.json`, this exact filename is listed
|
||||
in this repo's `.gitignore`, so a default-configured signer run from a checkout will not get its
|
||||
audit log accidentally staged. That entry only matches the default filename, though — if you
|
||||
audit log accidentally staged. That entry only matches the default filename, though: if you
|
||||
point `VECTOR_SIGNER_AUDIT_LOG_PATH` at some other path inside the repo, check before you
|
||||
`git add`, or just set the variable to somewhere outside the repo entirely.
|
||||
|
||||
@@ -99,7 +99,7 @@ point `VECTOR_SIGNER_AUDIT_LOG_PATH` at some other path inside the repo, check b
|
||||
| `vector_signer_sign` | Policy-check and sign. Returns signed CBOR. |
|
||||
| `vector_signer_get_spend_limits` | Limits, today's committed total, recent decisions. |
|
||||
|
||||
No tool accepts key material as a parameter — that is asserted by the smoke test.
|
||||
No tool accepts key material as a parameter; that is asserted by the smoke test.
|
||||
|
||||
## What "net outflow" means
|
||||
|
||||
@@ -116,32 +116,31 @@ over-counts, which refuses too much rather than too little.
|
||||
lovelace. A transaction moving ~2 AP3X plus a valuable NFT passes the lovelace check.
|
||||
Asset movements appear in `decode_transaction` output and the audit log so they are
|
||||
visible, but they are not yet a policy input.
|
||||
- **The audit log records intent, not confirmation.** The signer has no network and cannot
|
||||
- **The audit log records intent to spend, at signing time.** The signer has no network and cannot
|
||||
observe whether a submit succeeded, so a failed submit still counts against the daily
|
||||
budget. Conservative by design.
|
||||
- **A durable audit record is mandatory for signing — which means an unwritable audit log
|
||||
stops all signing, not just accounting.** `AuditLog.append()` throws if the write fails, and
|
||||
- **A durable audit record is mandatory for signing: an unwritable audit log halts every
|
||||
signature.** `AuditLog.append()` throws if the write fails, and
|
||||
`vector_signer_sign` withholds a signature it already produced rather than return one with
|
||||
no durable record of it — the alternative would let a restarted process silently under-count
|
||||
no durable record of it. The alternative would let a restarted process silently under-count
|
||||
today's spend, which quietly raises the effective daily limit. The practical effect: if the
|
||||
audit log's directory disappears, the disk fills, or the path loses write permission, every
|
||||
`vector_signer_sign` call starts returning `REFUSED` — including transactions well inside
|
||||
policy — until it is writable again. That refusal names the audit failure explicitly rather
|
||||
than reading like an ordinary policy refusal, but it is still a full stop on signing, not a
|
||||
warning.
|
||||
`vector_signer_sign` call starts returning `REFUSED`, including transactions well inside
|
||||
policy, until it is writable again. That refusal names the audit failure explicitly, unlike
|
||||
an ordinary policy refusal. Signing stops outright until the log is writable again.
|
||||
- **The key is plaintext at rest** in v1 (env var or file). The win is that it is *local*
|
||||
and never transits your model provider or a shared host — not that it is in an HSM.
|
||||
and never transits your model provider or a shared host, not that it is in an HSM.
|
||||
`KeySource` exists so keychain and hardware backends can be added without touching the
|
||||
signing path.
|
||||
- **`VECTOR_SIGNER_PRIVATE_KEY` and `VECTOR_SIGNER_ADDRESS` are not cross-checked against each
|
||||
other.** Each is validated as well-formed independently, but nothing confirms the address is
|
||||
actually the one the private key controls — that check is possible entirely offline (this
|
||||
actually the one the private key controls: that check is possible entirely offline (this
|
||||
package already depends on the CML library that could do it) but is not wired up yet. A
|
||||
mismatched pair still constructs and signs without error. The failure surfaces later: either
|
||||
as a chain-level rejection at submission, or as the policy engine failing to recognise your
|
||||
own change as change and over-counting it as outflow — the safe direction, but confusing if
|
||||
you hit it without knowing why.
|
||||
- **Prompt injection is bounded, not eliminated.** Policy refuses out-of-limit
|
||||
own change as change and over-counting it as outflow (safe, but confusing if
|
||||
you hit it without knowing why).
|
||||
- **Prompt injection is a residual risk.** Policy refuses out-of-limit
|
||||
transactions, but an injected agent can still cause any transaction *within* policy.
|
||||
Recipient allowlisting is the next lever.
|
||||
- **Pairing this signer with the builder in this codebase now removes the custody exposure it
|
||||
@@ -218,5 +217,5 @@ npm run test:integration:signer
|
||||
|
||||
Requires a **funded Vector testnet** wallet. Builds a real unsigned transaction the way the
|
||||
hosted builder does (no key), then decodes, policy-checks, and signs it with the real signer
|
||||
code path against live chain data. **Never submits.** Not run in CI — see
|
||||
code path against live chain data. **Never submits.** Not run in CI; see
|
||||
`test/integration/roundtrip.test.ts`.
|
||||
|
||||
Reference in New Issue
Block a user