diff --git a/agent-wallet-sidebar.js b/agent-wallet-sidebar.js
index 2ff7b3d1789..f45f196dab7 100644
--- a/agent-wallet-sidebar.js
+++ b/agent-wallet-sidebar.js
@@ -29,11 +29,16 @@ const sidebar = {
'guides/trade-prediction-markets',
'guides/earn-yield-vaults',
'guides/pay-for-apis-x402',
- 'guides/lend-and-borrow-aave',
'guides/check-balances-and-prices',
'guides/sign-messages-and-transactions',
],
},
+ {
+ type: 'category',
+ label: 'Plugins',
+ collapsed: false,
+ items: ['plugins/index', 'plugins/install-a-plugin', 'plugins/build-a-plugin'],
+ },
{
type: 'category',
label: 'Reference',
@@ -43,6 +48,7 @@ const sidebar = {
'reference/trading-modes',
'reference/outflow-policy',
'reference/commands',
+ 'reference/plugins',
'reference/error-codes',
'reference/supported-chains',
],
diff --git a/agent-wallet/README.mdx b/agent-wallet/README.mdx
index f6415471192..68347da195d 100644
--- a/agent-wallet/README.mdx
+++ b/agent-wallet/README.mdx
@@ -78,8 +78,8 @@ Your agent routes natural-language requests to these commands through installed
| [Prediction markets](guides/trade-prediction-markets.md) | Search, quote, trade, and redeem on Polymarket |
| [Earn (yield vaults)](guides/earn-yield-vaults.md) | Supply and withdraw across chains and protocols, including Aave |
| [x402 payments](guides/pay-for-apis-x402.md) | Pay for HTTP 402 paywalled APIs and endpoints |
-| [Aave V3 lending](guides/lend-and-borrow-aave.md) | Borrow, repay, and manage collateral through the Aave API |
| [Market data](guides/check-balances-and-prices.md) | Spot prices, token discovery, supported chains |
+| [Plugins](plugins/index.md) | Extend Agent Wallet with custom commands from npm packages |
@@ -117,11 +117,6 @@ See [Supported chains](reference/supported-chains.md) for typical networks.
title: 'Earn with yield vaults',
description: 'Supply and withdraw from yield vaults across chains and protocols.',
},
- {
- href: '/agent-wallet/guides/lend-and-borrow-aave',
- title: 'Lend and borrow with Aave V3',
- description: 'Supply, borrow, repay, and manage Aave V3 positions through your agent.',
- },
{
href: '/agent-wallet/reference/trading-modes',
title: 'Trading modes',
diff --git a/agent-wallet/guides/check-balances-and-prices.md b/agent-wallet/guides/check-balances-and-prices.md
index 77a05d64817..052808f36f7 100644
--- a/agent-wallet/guides/check-balances-and-prices.md
+++ b/agent-wallet/guides/check-balances-and-prices.md
@@ -52,8 +52,24 @@ mm price currencies
mm price networks
```
+`--asset-ids` takes comma-separated CAIP-19 asset IDs. To price a chain's native asset, pass the
+bare CAIP-2 chain ID and the CLI resolves it for you, so `eip155:1` becomes `eip155:1/slip44:60`:
+
+```bash
+mm price spot --asset-ids eip155:1,eip155:8453 --vs USD
+```
+
Use `mm token assets` to resolve asset identifiers for tokens you care about.
+## Historical prices
+
+```bash
+mm price history --chain-id eip155:1 --time-period 7d
+mm price history --chain-id eip155:1 --asset-type erc20:0x
--time-period 30d
+```
+
+`--asset-type` is optional and defaults to the chain's native asset.
+
## Token discovery
```bash
diff --git a/agent-wallet/guides/lend-and-borrow-aave.md b/agent-wallet/guides/lend-and-borrow-aave.md
deleted file mode 100644
index 13ec3a6f173..00000000000
--- a/agent-wallet/guides/lend-and-borrow-aave.md
+++ /dev/null
@@ -1,202 +0,0 @@
----
-description: Supply, borrow, and manage Aave V3 positions using mm wallet commands and the Aave GraphQL API.
-keywords: [MetaMask, Agent Wallet, Aave, DeFi, lending, borrowing, mm]
----
-
-# Lend and borrow with Aave V3
-
-Supply assets, borrow against collateral, and manage Aave V3 positions through your agent.
-Agent Wallet has no dedicated `mm aave` command.
-
-For supply and withdraw, use [`mm earn`](earn-yield-vaults.md) with `--protocol aave`.
-The CLI handles vault selection, ERC-20 approval, and cross-chain routing for you.
-
-For borrow, repay, and collateral toggles, which `mm earn` does not cover, your agent uses
-`mm wallet send-transaction` together with the
-[Aave V3 GraphQL API](https://api.v3.aave.com/graphql) to build and submit transactions.
-
-:::note
-
-The `metamask-agent-wallet` skill dropped its dedicated Aave workflow templates in skill v6.0.0.
-Supply and withdraw are covered by the generic earn workflows.
-The GraphQL patterns on this page are a manual fallback for the operations earn does not support.
-
-:::
-
-## Ask your agent
-
-```text
-You (to your agent): "Supply 100 USDC to Aave on Base"
-```
-
-```text
-You (to your agent): "What's my Aave health factor on Ethereum?"
-```
-
-```text
-You (to your agent): "Borrow 0.5 ETH against my USDC collateral on Arbitrum"
-```
-
-Your agent resolves chain and token addresses, queries the Aave API for transaction payloads,
-confirms with you, then submits transactions with `mm wallet send-transaction`.
-
-## Prerequisites
-
-- [Quickstart](../quickstart.md) completed
-- Sufficient token and native-gas balances on the target chain
-- For borrows: supplied collateral with collateral enabled on at least one asset
-
-## Supported chains
-
-| Chain | Chain ID | Pool address |
-| --------- | -------- | -------------------------------------------- |
-| Ethereum | 1 | `0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2` |
-| Polygon | 137 | `0x794a61358D6845594F94dc1DB02A252b5b4814aD` |
-| Arbitrum | 42161 | `0x794a61358D6845594F94dc1DB02A252b5b4814aD` |
-| Optimism | 10 | `0x794a61358D6845594F94dc1DB02A252b5b4814aD` |
-| Avalanche | 43114 | `0x794a61358D6845594F94dc1DB02A252b5b4814aD` |
-| Base | 8453 | `0x794a61358D6845594F94dc1DB02A252b5b4814aD` |
-
-Resolve token contract addresses with `mm token list search --chain-ids `.
-
-## Common pattern
-
-Each Aave operation follows the same steps:
-
-1. Get your wallet address: `mm wallet address`
-2. Check balances or positions on the target chain
-3. Query the Aave V3 GraphQL API for the operation (supply, withdraw, borrow, repay, or collateral
- toggle)
-4. Handle the response:
- - **`TransactionRequest`**: confirm with the user, then send with `mm wallet send-transaction`
- - **`ApprovalRequired`**: send the approval transaction first, then retry the original operation
- - **`InsufficientBalanceError`**: fund the wallet or bridge tokens to the target chain
-5. Use `--wait` on server-wallet mode and include a descriptive `--intent` when supported
-
-Example transaction submission:
-
-```bash
-mm wallet send-transaction --chain-id --payload '{"to":"","value":"0x0","data":""}' --wait
-```
-
-For ERC-20 operations, the transaction `value` is typically `"0x0"`.
-For native token supplies, convert the amount to hex wei in the payload.
-
-Before signing unfamiliar calldata, run `mm decode --payload <0x-calldata>` and confirm the decoded
-intent.
-
-## Supply assets
-
-Use `mm earn supply --protocol aave`, which resolves the vault and handles approval:
-
-```bash
-mm earn markets --chain-id --protocol aave --token
-mm earn supply --token --amount --chain-id --protocol aave --wait
-```
-
-If you need to build the supply transaction yourself:
-
-1. Confirm you hold enough of the supply token and native gas on the chain:
-
- ```bash
- mm wallet balance --chain-ids
- ```
-
-2. Query the Aave API for a supply execution plan (replace placeholders with your values):
-
- ```bash
- curl -s -X POST https://api.v3.aave.com/graphql \
- -H 'Content-Type: application/json' \
- -d '{
- "query": "{ supply(request: { market: \"\", amount: { erc20: { currency: \"\", value: \"\" } }, sender: \"\", chainId: }) { __typename ... on TransactionRequest { to from data value chainId } ... on ApprovalRequired { approval { to from data value chainId } originalTransaction { to from data value chainId } } } }"
- }'
- ```
-
-3. If the response is `ApprovalRequired`, send the approval transaction, then retry supply.
-4. Confirm recipient, amount, token, and chain before executing.
-
-## Withdraw assets
-
-Use `mm earn withdraw --protocol aave`:
-
-```bash
-mm earn positions --chain-id --protocol aave
-mm earn withdraw --token --chain-id --protocol aave --amount
-```
-
-If you need to build the withdraw transaction yourself:
-
-1. Query your positions (see [Check positions](#check-positions)).
-2. Before a full withdrawal, confirm no outstanding debt remains.
- Full collateral withdrawal reverts if any debt exists.
-3. Query the Aave API for a withdraw execution plan and submit the transaction after confirmation.
-
-For partial withdrawals with outstanding borrows, preview the health factor impact through the Aave
-API `healthFactorPreview` query before proceeding.
-
-## Borrow assets
-
-1. Confirm supplied collateral with collateral enabled.
-2. Check available borrow capacity and that `borrowCapReached` is not `true` for the target asset.
-3. Preview health factor impact with the Aave `healthFactorPreview` query.
-4. Query the borrow execution plan and submit after confirmation.
-
-## Repay debt
-
-1. Query outstanding borrows from your positions.
-2. Query the Aave API for a repay execution plan.
-3. Handle `ApprovalRequired` if the debt asset is an ERC-20, then submit the repay transaction.
-
-## Toggle collateral
-
-Enable or disable an asset as collateral for borrowing.
-When disabling collateral with outstanding borrows, preview the health factor impact.
-Do not disable collateral if the health factor would drop below 1.0.
-
-## Check positions
-
-Query supply and borrow positions in one request:
-
-```bash
-curl -s -X POST https://api.v3.aave.com/graphql \
- -H 'Content-Type: application/json' \
- -d '{
- "query": "{ userSupplies(request: { markets: [{ address: \"\", chainId: }], user: \"\" }) { currency { symbol decimals } balance { amount { value } usd } apy { formatted } isCollateral } userBorrows(request: { markets: [{ address: \"\", chainId: }], user: \"\" }) { currency { symbol decimals } debt { amount { value } usd } apy { formatted } } }"
- }'
-```
-
-Present supply balances, borrow balances, collateral status, and APY rates to the user.
-
-## Discover markets
-
-List available reserves, supply APY, borrow APY, and liquidity on a chain:
-
-```bash
-curl -s -X POST https://api.v3.aave.com/graphql \
- -H 'Content-Type: application/json' \
- -d '{
- "query": "{ markets(request: { chainIds: [] }) { reserves { underlyingToken { symbol decimals } supplyInfo { apy { formatted } } borrowInfo { apy { formatted } availableLiquidity { amount { value } usd } borrowCapReached } isFrozen isPaused } } }"
- }'
-```
-
-Skip reserves where `isFrozen` or `isPaused` is `true`.
-
-## Common pitfalls
-
-:::caution Confirm before submitting
-Always confirm operation type, token, amount, chain, and health factor impact (for borrows and
-collateral changes) before calling `mm wallet send-transaction`.
-:::
-
-:::caution Guard Mode allowlists
-Aave pool contracts must be permitted by your server-wallet policy in Guard Mode.
-Transactions to untrusted contracts require 2-factor authentication approval.
-:::
-
-## Related commands
-
-- [`mm earn`](../reference/commands.md#mm-earn)
-- [`mm wallet send-transaction`](../reference/commands.md#mm-wallet-send-transaction)
-- [`mm decode`](../reference/commands.md#mm-decode)
-- [Earn with yield vaults](earn-yield-vaults.md)
-- [Check balances and prices](check-balances-and-prices.md)
diff --git a/agent-wallet/plugins/build-a-plugin.md b/agent-wallet/plugins/build-a-plugin.md
new file mode 100644
index 00000000000..cfd2129e2fb
--- /dev/null
+++ b/agent-wallet/plugins/build-a-plugin.md
@@ -0,0 +1,160 @@
+---
+description: Build a MetaMask Agent Wallet plugin from the official template and publish it to npm.
+keywords: [MetaMask, Agent Wallet, plugin, build, template, PluginCommand, capabilities]
+---
+
+# Build a plugin
+
+This guide helps you build your own MetaMask Agent Wallet plugin, starting from the
+[plugin template](https://github.com/MetaMask/agent-wallet-plugin-template).
+The template ships a working `mm hello ping` command that you can rename and extend, so you have
+something running before you write any code.
+
+By the end of this guide, you have a plugin that adds your own command to Agent Wallet, tested
+locally and ready to publish.
+
+## Prerequisites
+
+- Install [Node.js](https://nodejs.org/en/download) v22 or later.
+
+## 1. Clone the template
+
+Clone the [plugin template](https://github.com/MetaMask/agent-wallet-plugin-template) into a
+directory named after your plugin.
+
+```bash
+git clone https://github.com/MetaMask/agent-wallet-plugin-template my-plugin
+cd my-plugin
+```
+
+## 2. Install the dependencies
+
+Use your preferred package manager to install the dependencies.
+
+```bash npm2yarn
+npm install
+```
+
+## 3. Update the manifest
+
+The `package.json` file declares your plugin's identity and the `mm` block declares what each
+command does and needs.
+Replace the template's placeholder values with your own:
+
+- Set `name` and `description`.
+- In the `mm` block, update each `commands[].id` to your command id.
+- If a command reads wallet data or signs anything, list the capabilities and data access it needs.
+
+Users consent to these capabilities at install time.
+Keep the plugin-wide `capabilities` list empty because it is merged into every command.
+See the [plugins reference](../reference/plugins.md) for every manifest field, capability, and
+data access category.
+
+```json
+"mm": {
+ "schemaVersion": 1,
+ "minCliVersion": "^6.2.0",
+ "capabilities": [],
+ "commands": [
+ {
+ "id": "hello:balance",
+ "capabilities": ["wallet-read"],
+ "dataAccess": ["balances"]
+ }
+ ]
+}
+```
+
+## 4. Write your command
+
+Rename the template's command file to match the id you declared in the manifest.
+The file path defines the command.
+For example, `src/commands/hello/ping.ts` becomes `mm hello ping` with id `hello:ping`.
+
+To write the command, extend `PluginCommand` from `@metamask/agent-wallet/plugin` and implement
+`execute`.
+The template's command shows the full pattern.
+
+Keep the following rules in mind as you write your command:
+
+- You implement only `execute`, plus static configuration and the optional hooks `afterExecute`,
+ `successHint`, and `analyticsOutcome`.
+ The rest of the lifecycle is sealed by the host.
+- `pluginCommandId` must match the command's `id` in the plugin manifest.
+- `requiresAuth` and `requiresInit` gate sign-in and wallet setup.
+ Both default to `true`.
+- Declare inputs once as a schema.
+ `schemaToFlags` and `schemaToArgs` generate the command surface, and `io.resolveInputs` resolves
+ flags, positionals, and interactive prompts.
+- The base flags `--json`, `--format`, `--toon`, and `--verbose` are inherited automatically.
+
+```ts
+import {
+ type CommandIO,
+ InputFieldType,
+ type InputSchema,
+ PluginCommand,
+ schemaToArgs,
+ schemaToFlags,
+} from '@metamask/agent-wallet/plugin'
+
+const inputs = {
+ name: {
+ type: InputFieldType.Text,
+ flag: 'name',
+ message: 'Name to greet',
+ required: false,
+ prompt: false,
+ index: 0,
+ },
+} satisfies InputSchema
+
+export default class HelloPing extends PluginCommand<{ message: string }> {
+ static override description = 'Say hello from the plugin template.'
+ static override requiresAuth = false
+ static override requiresInit = false
+ static override flags = schemaToFlags(inputs)
+ static override args = schemaToArgs(inputs)
+
+ protected readonly pluginCommandId = 'hello:ping'
+
+ async execute(io: CommandIO) {
+ const { name } = await io.resolveInputs(inputs)
+ return { message: name ? `pong, ${name}!` : 'pong' }
+ }
+}
+```
+
+## 5. Build and test locally
+
+Now build your plugin and install it into your local Agent Wallet to try it out. Install from the
+directory rather than a packed tarball, so Agent Wallet can read your manifest and show the full
+capability consent screen.
+
+As you iterate, run `mm plugins uninstall ` to remove the plugin before you install it again.
+
+```bash npm2yarn
+npm run build
+mm config set experimentalPlugins true
+mm config set experimentalAllowUnverifiedInstalls true
+mm plugins install "file:$PWD" --accept-permissions
+mm hello ping Alice
+```
+
+## 6. Publish
+
+When your plugin works the way you want, publish the package to npm.
+
+Users then install it with `mm plugins install ` and review a consent screen listing
+your commands, data access, and requested capabilities.
+
+```bash npm2yarn
+npm publish
+```
+
+## Next steps
+
+- Browse the [plugin examples repository](https://github.com/MetaMask/agent-wallet-plugin-examples)
+ for complete working plugins, including an ENS resolver built on the authenticated RPC client.
+- See the [plugins reference](../reference/plugins.md) for the manifest schema, capability types,
+ and the published SDK surface.
diff --git a/agent-wallet/plugins/index.md b/agent-wallet/plugins/index.md
new file mode 100644
index 00000000000..7167d14316d
--- /dev/null
+++ b/agent-wallet/plugins/index.md
@@ -0,0 +1,49 @@
+---
+title: Plugins overview
+sidebar_label: Overview
+description: Extend MetaMask Agent Wallet with plugins that add custom commands.
+keywords: [MetaMask, Agent Wallet, plugin, extend, capabilities]
+---
+
+# Plugins overview
+
+Plugins extend MetaMask Agent Wallet with custom commands.
+A plugin is an npm package that adds native commands, discoverable in `mm help` and the REPL.
+For example, a plugin can add `mm ens resolve` to resolve ENS names, or `mm x402` to pay for
+paywalled APIs.
+
+:::note Beta Notice
+
+Plugins are a beta feature and are off by default.
+Installing and running plugins are both blocked until you enable the feature.
+:::
+
+## What plugins can do
+
+Plugins run inside Agent Wallet and use a curated context the host provides.
+Each command declares the capabilities it needs, and you consent to them at install time.
+
+| Capability | Grants |
+| ---------------- | ---------------------------------------------------------------------------------------- |
+| `wallet-read` | Read services such as balances, prices, and tokens, plus an authenticated EVM RPC client |
+| `wallet-submit` | Signing and transaction submission, still policy-gated by Agent Wallet |
+| `network-manage` | Reserved for future network management |
+
+A command with no capabilities can still run pure logic and prompt for inputs.
+The session, CLI token, and Secret Recovery Phrase are host-only and never exposed to plugins.
+See the [plugins reference](../reference/plugins.md) for the full list of what each capability
+unlocks.
+
+## Trust model
+
+Plugins run in-process and unsandboxed.
+Install-time consent plus MetaMask backend policy signing are the real trust boundaries.
+Only install plugins from publishers you trust.
+
+Installs from npm are consent-gated and fail closed if the package can't be verified.
+Plugin lifecycle scripts such as `postinstall` never run.
+
+## Get started
+
+- [Install a plugin](install-a-plugin.md) to add commands published by others.
+- [Build a plugin](build-a-plugin.md) to create your own, starting from the official template.
diff --git a/agent-wallet/plugins/install-a-plugin.md b/agent-wallet/plugins/install-a-plugin.md
new file mode 100644
index 00000000000..61263df7c28
--- /dev/null
+++ b/agent-wallet/plugins/install-a-plugin.md
@@ -0,0 +1,79 @@
+---
+description: Install, review, update, and remove MetaMask Agent Wallet plugins.
+keywords: [MetaMask, Agent Wallet, plugin, install, consent, capabilities]
+---
+
+# Install a plugin
+
+Install plugins to add new commands to MetaMask Agent Wallet.
+Every install shows a consent screen listing the plugin's commands, data access, and requested
+capabilities before anything runs.
+
+## Enable plugins
+
+Plugins are a beta feature and are off by default.
+Installing and running plugins are both blocked until you enable the feature.
+
+```bash
+mm config set experimentalPlugins true
+```
+
+## Install from npm
+
+Agent Wallet fetches the package metadata, shows the consent screen, and installs after you
+approve. Installs fail closed when the registry is unreachable or the package can't be verified.
+
+```bash
+mm plugins install
+
+# Pass `--accept-permissions` to skip the interactive prompt in scripts and CI
+mm plugins install --accept-permissions
+```
+
+## Install a local plugin
+
+Local `file:` paths and git sources can't be verified, so you need to opt in first.
+This is intended for development only.
+Without the opt-in, these installs are refused with `PLUGIN_UNVERIFIED_SOURCE`.
+
+```bash
+mm config set experimentalAllowUnverifiedInstalls true
+mm plugins install file:/path/to/plugin --accept-permissions
+```
+
+Prefer installing from the plugin directory rather than a packed tarball.
+Agent Wallet reads the manifest from the directory, so it can show the full capability consent
+screen before installing. A tarball shows only the unverified-source banner, because the manifest
+isn't readable until the package is unpacked. Either way, Agent Wallet persists the approved
+capabilities after the install completes.
+
+To develop against a directory without copying it, use `mm plugins link`, which shows the same
+consent screen:
+
+```bash
+mm plugins link /path/to/plugin
+```
+
+## Manage installed plugins
+
+`mm plugins update` shows the consent screen again for any plugin whose version or manifest
+changed.
+
+```bash
+mm plugins # list installed plugins
+mm plugins inspect # show plugin details
+mm plugins update # update all and re-consent changed manifests
+mm plugins uninstall # remove a plugin
+```
+
+## Disable plugins
+
+Turn the whole plugin system off at any time. Installed plugin commands then fail with `PLUGIN_BETA_DISABLED` until you re-enable the feature.
+
+```bash
+mm config set experimentalPlugins false
+```
+
+## Next steps
+
+- [Build a plugin](build-a-plugin.md)
diff --git a/agent-wallet/reference/commands.md b/agent-wallet/reference/commands.md
index 5e288b008bd..6671ce284b3 100644
--- a/agent-wallet/reference/commands.md
+++ b/agent-wallet/reference/commands.md
@@ -570,19 +570,40 @@ mm decode <0x-calldata>
## `mm price`
-| Command | Usage |
-| --------------------- | --------------------------------------------------------------------------------------------------------------------------- |
-| `mm price spot` | `--asset-ids [--vs ] [--market-data]` |
-| `mm price history` | `--chain-id --asset-type [--time-period ] [--interval ] [--from] [--to] [--vs ]` |
-| `mm price currencies` | Supported quote currencies |
-| `mm price networks` | Supported price networks |
+| Command | Usage |
+| --------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
+| `mm price spot` | `--asset-ids [--vs ] [--market-data]` |
+| `mm price history` | `--chain-id [--asset-type ] [--time-period ] [--interval ] [--from] [--to] [--vs ]` |
+| `mm price currencies` | Supported quote currencies |
+| `mm price networks` | Supported price networks |
+
+### `mm price spot`
+
+```bash
+mm price spot --asset-ids [--vs ] [--market-data]
+```
+
+`--asset-ids` accepts comma-separated CAIP-19 asset IDs, such as `eip155:1/slip44:60`.
+A bare CAIP-2 chain ID auto-completes to that chain's native asset, so `eip155:1` resolves to
+`eip155:1/slip44:60`:
+
+```bash
+mm price spot --asset-ids eip155:1,eip155:137
+```
+
+A malformed ID returns `INVALID_ASSET_ID` with a hint, and passing no IDs returns
+`MISSING_ASSET_IDS`. `mm token assets` does not auto-complete chain IDs and requires full CAIP-19
+asset IDs.
### `mm price history`
```bash
-mm price history --chain-id --asset-type [--time-period ] [--interval ] [--from ] [--to ] [--vs ]
+mm price history --chain-id [--asset-type ] [--time-period ] [--interval ] [--from ] [--to ] [--vs ]
```
+`--asset-type` is optional and defaults to the chain's native asset, so `--chain-id eip155:1`
+resolves to `slip44:60`. An invalid asset type fails fast with `INVALID_ASSET_ID`.
+
Use `--from` and `--to` for a custom range instead of `--time-period`.
`--time-period` accepts Price API values such as `1d`, `7d`, `30d`, `2M`, `1y`, and `3y`.
Supported `--interval` values include `5m`, `15m`, `30m`, `hourly`, and `daily`.
@@ -677,12 +698,14 @@ mm config get
mm config set
```
-| Key | Accepted values | Description |
-| ---------------------- | ---------------------- | ------------------------------------------------------------------------- |
-| `env` | `prod`, `dev`, `uat` | Backend environment |
-| `verbose` | `true`, `false` | Default for the global `--verbose` flag |
-| `format` | `text`, `json`, `toon` | Default for the global `--format` flag |
-| `walletTimeoutSeconds` | Integer (max 600) | Default for `--wallet-timeout` on server-wallet signing and swap commands |
+| Key | Accepted values | Description |
+| ------------------------------------- | ---------------------- | ------------------------------------------------------------------------- |
+| `env` | `prod`, `dev`, `uat` | Backend environment |
+| `verbose` | `true`, `false` | Default for the global `--verbose` flag |
+| `format` | `text`, `json`, `toon` | Default for the global `--format` flag |
+| `walletTimeoutSeconds` | Integer (max 600) | Default for `--wallet-timeout` on server-wallet signing and swap commands |
+| `experimentalPlugins` | `true`, `false` | Enable the [plugin system](#mm-plugins). Beta, off by default |
+| `experimentalAllowUnverifiedInstalls` | `true`, `false` | Allow installing plugins from local or git sources. Development only |
Run `mm config get` with no key to show all values.
Persisted `format` and `verbose` apply when you do not pass the corresponding global flags.
@@ -691,6 +714,29 @@ Non-prod sessions are stored in environment-scoped files under `~/.metamask/`, s
`session.dev.json`; prod uses `session.json`.
`walletTimeoutSeconds` is the stored default for `--wallet-timeout` (see [Troubleshooting](../troubleshooting.md)).
+## `mm plugins`
+
+Manage plugins, npm packages that add custom commands to Agent Wallet.
+Plugins are a beta feature and are off by default.
+Enable them with `mm config set experimentalPlugins true`.
+See the [plugins overview](../plugins/index.md).
+
+```bash
+mm plugins # list installed plugins
+mm plugins install # interactive consent per package
+mm plugins install --accept-permissions # non-interactive / CI
+mm plugins inspect
+mm plugins update # re-consents changed manifests
+mm plugins uninstall
+```
+
+Installs from npm are consent-gated.
+Agent Wallet shows the plugin's commands, data access, and requested capabilities before
+installing, and fails closed if the package can't be verified.
+Plugin lifecycle scripts such as `postinstall` never run.
+Local `file:` and git sources are refused unless you opt in with
+`mm config set experimentalAllowUnverifiedInstalls true`, which is intended for development only.
+
## `mm tx`
### `mm tx history`
diff --git a/agent-wallet/reference/error-codes.md b/agent-wallet/reference/error-codes.md
index dfd253cf919..fe745089e49 100644
--- a/agent-wallet/reference/error-codes.md
+++ b/agent-wallet/reference/error-codes.md
@@ -46,6 +46,9 @@ Run `mm --help` for command-specific validation rules.
| `NOT_INITIALIZED` | Project not initialized; run `mm init` |
| `INVALID_LIMIT` | Invalid `--limit` value; `mm tx history` accepts 1–50 and `mm token list search` accepts 1–500 |
| `INVALID_DATA` | The Price API returned an empty or malformed response for `mm price history`; retry, or verify the asset and chain with `mm price spot` |
+| `MISSING_ASSET_IDS` | No asset IDs passed to `mm price spot` or `mm token assets`; pass `--asset-ids` as comma-separated CAIP-19 IDs |
+| `MISSING_ASSET_TYPE` | `--asset-type` is missing on `mm price history` and the chain has no native asset to fall back to |
+| `INVALID_ASSET_ID` | Malformed CAIP-19 asset ID or `--asset-type`. `mm price spot` also accepts a bare CAIP-2 chain ID and resolves it to that chain's native asset |
| `MISSING_QUERY` | No search term passed to `mm token list search`; pass it as a positional argument |
| `INVALID_POLICY_YAML` | Policy YAML passed to `mm wallet policy set` is not a valid policy object; use `mm wallet policy get` or `mm wallet policy template` as a starting point |
| `INVALID_CONFIG_KEY` | Unknown CLI config key |
@@ -137,7 +140,8 @@ All expected predict failures return actionable per-code hints. Inspect the `hin
| `PREDICT_INSUFFICIENT_ALLOWANCE` | Insufficient allowance; run `mm predict approve` |
| `PREDICT_ORDER_SIZE_TOO_SMALL` | Order size below exchange minimum; raise `--size` |
| `PREDICT_ORDER_NOT_FILLED` | FOK order could not be fully filled; adjust `--size`/`--price` or use GTC |
-| `PREDICT_GEOBLOCKED` | Polymarket is unavailable in this region |
+| `PREDICT_GEOBLOCKED` | Polymarket is unavailable in this region, per the geoblock API |
+| `PREDICT_UNAVAILABLE_FOR_LEGAL_REASONS` | Polymarket returned HTTP 451 for a legal restriction in this region |
| `PREDICT_WITHDRAW_ZERO` | Withdraw amount must be greater than zero |
| `PREDICT_WITHDRAW_INSUFFICIENT_BALANCE` | Insufficient balance for withdrawal |
| `PREDICT_REDEEM_NONE` | No redeemable positions found |
@@ -182,6 +186,26 @@ All expected predict failures return actionable per-code hints. Inspect the `hin
| --------------------- | ------------------- |
| `NETWORK_UNREACHABLE` | Network unreachable |
+## Plugin errors
+
+Returned by `mm plugins` and installed plugin commands.
+See the [plugins overview](../plugins/index.md).
+
+| Code | Meaning |
+| ------------------------------ | ---------------------------------------------------------------------------------------------- |
+| `PLUGIN_BETA_DISABLED` | Plugins are disabled. Run `mm config set experimentalPlugins true` |
+| `PLUGIN_UNVERIFIED_SOURCE` | Local or git source refused. Enable `experimentalAllowUnverifiedInstalls` for development |
+| `PLUGIN_NOT_FOUND` | npm package could not be resolved |
+| `PLUGIN_METADATA_UNAVAILABLE` | npm metadata could not be fetched. The install fails closed |
+| `PLUGIN_MANIFEST_INVALID` | The package's `package.json#mm` manifest is missing or invalid |
+| `PLUGIN_MANIFEST_FILE_MISSING` | The package did not ship a prebuilt `oclif.manifest.json`. It was not approved and was removed |
+| `PLUGIN_CLI_VERSION` | The plugin requires a newer Agent Wallet version than the one running |
+| `PLUGIN_ID_COLLISION` | A plugin command ID collides with a built-in command |
+| `PLUGIN_HOOKS_FORBIDDEN` | The package declares `oclif.hooks` or `oclif.plugins`, which are not allowed |
+| `PLUGIN_INVALID_BASE` | A plugin command does not extend `PluginCommand` |
+| `PLUGIN_SEALED_OVERRIDE` | A plugin command overrides a sealed lifecycle member |
+| `PERMISSION_DENIED` | Install consent was declined, or a command used a capability it was not granted |
+
## Related pages
- [Troubleshooting](../troubleshooting.md)
diff --git a/agent-wallet/reference/plugins.md b/agent-wallet/reference/plugins.md
new file mode 100644
index 00000000000..45816564604
--- /dev/null
+++ b/agent-wallet/reference/plugins.md
@@ -0,0 +1,172 @@
+---
+description: MetaMask Agent Wallet plugin reference including the manifest schema, capability types, data access categories, and the plugin SDK surface.
+keywords: [MetaMask, Agent Wallet, plugin, manifest, capabilities, reference, PluginCommand]
+---
+
+# Plugins
+
+Reference for MetaMask Agent Wallet plugin authors.
+See the [plugins overview](../plugins/index.md) for concepts and the
+[build a plugin](../plugins/build-a-plugin.md) guide.
+
+## Plugin manifest
+
+Every plugin declares an `mm` block in its `package.json`.
+Agent Wallet validates the manifest at install time and shows its contents on the consent screen.
+
+The package must also declare the `oclif-plugin` keyword, a `@metamask/agent-wallet`
+peer dependency, an `oclif` block pointing at the compiled commands, and a generated
+`oclif.manifest.json` shipped in the package.
+
+Packages that declare `oclif.hooks` or `oclif.plugins` are rejected because hooks run outside the
+plugin boundary.
+
+```json
+"mm": {
+ "schemaVersion": 1,
+ "minCliVersion": "^6.2.0",
+ "capabilities": [],
+ "commands": [
+ {
+ "id": "hello:balance",
+ "capabilities": ["wallet-read"],
+ "dataAccess": ["balances"],
+ "targetChains": "any"
+ }
+ ]
+}
+```
+
+| Field | Required | Description |
+| ------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------ |
+| `schemaVersion` | Yes | Manifest schema version. Must be `1`. |
+| `minCliVersion` | Yes | Semver range of Agent Wallet versions the plugin supports. The plugin system shipped in 6.2.0, so use `^6.2.0` or later. |
+| `capabilities` | No | Plugin-wide capabilities merged into every command. Keep this empty to avoid over-granting. |
+| `commands` | Yes | One entry per command. At least one command is required. |
+| `commands[].id` | Yes | Command id matching the command's `pluginCommandId`, such as `hello:balance`. |
+| `commands[].capabilities` | No | Capabilities this command needs. Defaults to none. |
+| `commands[].dataAccess` | No | Data categories the command reads, shown on the consent screen. Defaults to none. |
+| `commands[].targetChains` | No | `"any"` or a list of EVM chain ids the command targets. Defaults to `"any"`. |
+
+## Capability types
+
+Capabilities gate what a command can reach on the plugin context.
+Users consent to them at install time.
+
+The capabilities `mnemonic-read` and `config-write` are reserved.
+Manifests that declare them are rejected.
+
+A command that uses a gated member without declaring the matching capability fails at runtime with
+`PERMISSION_DENIED`.
+
+| Capability | Grants |
+| ---------------- | -------------------------------------------------------------------------------------------- |
+| `wallet-read` | Read services and the authenticated per-chain EVM RPC client. See the table below. |
+| `wallet-submit` | `ctx.walletExecutor` for signing and transaction submission, still policy-gated by MetaMask. |
+| `network-manage` | `ctx.networkRegistry`. Reserved for future network management. |
+
+## Plugin context
+
+Commands access the host through `this.ctx`, a curated context the host restricts per command
+based on its granted capabilities.
+
+The session, CLI token, and mnemonic store are host-only.
+Accessing them from a plugin always fails with `PERMISSION_DENIED`.
+
+| Context member | Requires | Description |
+| --------------------------------- | ---------------- | ------------------------------------------------------------------- |
+| `logger`, `args`, `flags`, `argv` | None | Always available. |
+| `accountService` | `wallet-read` | Account and balance queries. |
+| `authService` | `wallet-read` | Authentication state queries. |
+| `priceService` | `wallet-read` | Spot and historical prices. |
+| `tokenService` | `wallet-read` | Token metadata and discovery. |
+| `walletStateManager` | `wallet-read` | Local wallet state snapshot, including wallets and the selection. |
+| `feesService` | `wallet-read` | Fee estimates. |
+| `swapQuoteStore` | `wallet-read` | Persisted swap quotes. |
+| `publicClient(chainId)` | `wallet-read` | Authenticated per-chain viem public client for raw EVM reads. |
+| `walletExecutor(io, source)` | `wallet-submit` | Executor for transactions, message signing, and typed-data signing. |
+| `networkRegistry` | `network-manage` | Supported network registry. |
+
+### Raw EVM reads
+
+With `wallet-read`, call `ctx.publicClient(chainId)` for an authenticated
+[viem](https://viem.sh) public client backed by the same RPC gateway the host uses:
+
+```ts
+const client = this.ctx.publicClient(1)
+const balance = await client.getBalance({ address })
+```
+
+### Signing and submission
+
+With `wallet-submit`, call `ctx.walletExecutor(io, "")` to get an executor.
+The executor accepts three request kinds and routes every request through MetaMask policy:
+
+| Request kind | Description | Result |
+| ------------- | ------------------------------------ | --------------------------- |
+| `transaction` | Submit an EVM transaction on a chain | Transaction hash and status |
+| `message` | Sign a plaintext message | Signature |
+| `typed-data` | Sign an EIP-712 typed-data payload | Signature |
+
+## PluginCommand class
+
+A plugin command extends `PluginCommand` and implements `execute`. The security-critical lifecycle is sealed.
+A subclass that overrides `run`, `runLifecycle`, `beforeExecute`, `init`, `prepareForRepl`,
+`withPluginIsolation`, or the `requiresAuth`, `requiresInit`, and `requiresFees` getters throws
+`PLUGIN_SEALED_OVERRIDE` before it can run.
+Fee-cache warmup is host-only and always off for plugin commands.
+
+| Member | Role |
+| ------------------------------------------------- | ------------------------------------------------------------------------ |
+| `execute(io)` | Required. The command's logic. Its return value is rendered by the host. |
+| `pluginCommandId` | Required. Must match the command's manifest `id`. |
+| `description`, `examples`, `flags`, `args` | Static configuration shown in help output. |
+| `requiresAuth` | Static. Gates the sign-in check. Defaults to `true`. |
+| `requiresInit` | Static. Gates the wallet setup check. Defaults to `true`. |
+| `afterExecute`, `successHint`, `analyticsOutcome` | Optional hooks. |
+
+## Plugin SDK surface
+
+Import from `@metamask/agent-wallet/plugin`:
+
+| Export | Description |
+| --------------------------------------------------------------------------------------------- | ------------------------------------------------------- |
+| `PluginCommand` | Base class for plugin commands. |
+| `PluginCommandContext` | Type of the curated context available as `this.ctx`. |
+| `PluginManifest`, `definePluginManifest`, `PluginManifestSchema` | Manifest type, authoring helper, and schema. |
+| `CommandIO` | Interaction surface passed to `execute`. |
+| `CommandError`, `ok`, result helpers | Error and result envelope helpers. |
+| `schemaToFlags`, `schemaToArgs`, `resolveInputs`, `mergeArgsIntoFlags`, `enumFlag`, `trimKey` | Declarative input engine. |
+| `InputFieldType`, `InputSchema`, `InputField`, `ResolvedInputs`, `SelectOption`, `Asker` | Input types. |
+| `PublicClient` | viem public client type returned by `ctx.publicClient`. |
+
+## Data access categories
+
+`commands[].dataAccess` labels the data a command reads.
+The categories appear on the consent screen and are informational.
+
+`accounts`, `balances`, `prices`, `tokens`, `network`, `fees`, `swap-quotes`, `session`,
+`mnemonic`
+
+## Storage locations
+
+| Data | Location |
+| --------------------------- | ------------------------------------------------------------------------------------------------------ |
+| Plugin code | The oclif data directory. On macOS `~/Library/Application Support/mm/`, on Linux `~/.local/share/mm/`. |
+| Approvals and configuration | `~/.metamask/config.json` under the `plugins` key. |
+
+Each approval record stores the approved version, package integrity, manifest hash, approved
+capabilities, approved command IDs, and an approval timestamp.
+
+The data directory is outside the running CLI's module tree, so Agent Wallet symlinks itself into
+the data directory's `node_modules` when user plugins are installed. That way a plugin's
+`@metamask/agent-wallet/plugin` import resolves to the same running instance rather than loading a
+second copy of the CLI. Declare `@metamask/agent-wallet` as a peer dependency, not a regular
+dependency, so the symlink is what resolves.
+
+## Related pages
+
+- [Plugins overview](../plugins/index.md)
+- [Install a plugin](../plugins/install-a-plugin.md)
+- [Build a plugin](../plugins/build-a-plugin.md)
+- [Error codes](error-codes.md#plugin-errors)
diff --git a/agent-wallet/troubleshooting.md b/agent-wallet/troubleshooting.md
index 93ac6602164..5130dba2ae7 100644
--- a/agent-wallet/troubleshooting.md
+++ b/agent-wallet/troubleshooting.md
@@ -237,6 +237,17 @@ A fill-or-kill (FOK) order could not be fully filled at the requested price. Adj
`--price`, check liquidity with `mm predict book`, or use a GTC (good-till-cancelled) order type
instead of FOK.
+### `PREDICT_UNAVAILABLE_FOR_LEGAL_REASONS` or `PREDICT_GEOBLOCKED`
+
+Polymarket is unavailable from your location. The two codes come from different signals:
+
+- `PREDICT_GEOBLOCKED` comes from the dedicated geoblock API.
+- `PREDICT_UNAVAILABLE_FOR_LEGAL_REASONS` comes from an HTTP 451 response on a Polymarket
+ request, which indicates a legal restriction.
+
+Neither is a bug. Confirm the restriction with `mm predict geoblock`, and
+retry from an allowed location.
+
## Swaps
### `NO_QUOTES` or unavailable quote from `mm swap quote`