# @metamask/fox-sdk

MetaMask SDK that powers agentic wallet flows for `agentic-cli` and `agentic-sdk`. Provides a chain-agnostic plugin runtime plus an EVM wallet client built on `@toruslabs/ethereum-controllers`, with optional remote signing via a policy-aware backend.

## Highlights

- **Single package** (`@metamask/fox-sdk`) — no monorepo, no per-plugin publishing.
- **EVM wallet client** backed by `@toruslabs/ethereum-controllers` (`TransactionController`, keyring hooks, `NonceTracker`, gas estimation via `prepareTransaction` with `low | medium | high` speed tiers, replacement-by-fee helpers on the client).
- **Two keyring modes** (discriminated by `keyring.kind`):
  - `byok` — mnemonic-backed signing with a CLI-owned seed. BYOK derives EVM wallets locally, registers them with the remote signing service, and submits signed payloads for policy/MFA evaluation and server-side broadcast.
  - `server` — wallet roster + HTTP client for the remote signing service. Every signing path (transaction / message / typed-data) submits an async job to the backend and polls for terminal status. The backend owns Shield + policies + MFA + sign + RPC broadcast.
- **Solana wallet client** (`SolanaWalletClient` and SPL helpers) for Solana-focused plugins.
- **Plugin model** (`PluginBase`, `ToolBase`, `@Tool` decorator, `WalletClientBase`) shared by every protocol plugin; tools are gathered with `getTools`.
- **TypeScript-first** with Vitest tests colocated as `*.test.ts`.

## Architecture

- **`src/core/`** — `WalletClientBase`, `PluginBase`, `ToolBase`, chain/token types, `getTools`, decorators, and shared utilities.
- **`src/wallets/keyring/`** — chain-agnostic keyring layer. `ByokKeyring` + `ServerKeyring` (concrete keyrings, namespace-aware; `ServerKeyring` is the mimir-proxied HSM, holds no key material), `WalletDirectory` (shared roster lookup), the `ByokChainAdapter` / `ServerChainAdapter` / `ChainSignerRegistry` interfaces, `ServerKeyringHttp` (shared auth+fetch+error mapping), `createKeyringController` factory (takes adapter factories), mnemonic validation, and the `IFoxKeyring` discriminated union. Never imports from any chain-specific module.
- **`src/wallets/evm/client/`** — `createEvmControllerStack` (wires `TransactionController` + `GasFeeController` (RPC-only, on-demand) + JSON-RPC provider + block tracker), `ControllerEVMWalletClient` (high-level send / read / sign paths; signing paths are branch-free via `EvmSigner` polymorphism), and the `EVMWalletClient` plugin contract.
- **`src/wallets/evm/keyring/`** — EVM-specific keyring code: `EvmByokAdapter` (derivation + Torus `KeyringController` wrap for in-process signing), `EvmServerAdapter` (EVM-typed submit/await/sign methods over the shared `signature-requests` / `transaction-requests` routes), and the `EvmSigner` interface that both implement so wallet clients reach signing methods polymorphically. Augments `ChainSignerRegistry` from the chain-agnostic keyring via module declaration.
- **`src/wallets/evm/remote-signing/`** — shared utilities for the server-mode flow: kind-aware polling (`pollUntilTerminal`, `isTerminalStatus`), error classes (`JobFailedError`, `RemoteSigningError`, `UnsupportedInRemoteMode`), and the `JobStatus` discriminated union for transaction / message / typed-data jobs.
- **`src/wallets/evm/tools/`** — shared Zod parameter schemas for wallet-exposed tools (balances, approvals, sends, typed data, …).
- **`src/wallets/evm/types/`** and **`src/wallets/evm/utils/`** — EVM transaction/read types, ERC-20 ABI, ERC-20 metadata reader, and the predefined token catalog. Callers can override or extend tokens via `EVMWalletClient`'s `tokens` constructor option and `addToken`.
- **`src/wallets/solana/`** — Solana wallet client, token metadata, and helpers for Solana plugins.
- **`src/plugins/*/`** — one directory per integration; each exports a `PluginBase` subclass and tools implemented with `@Tool` on service classes. Currently only `polymarket` ships in-tree.

EVM transaction lifecycle relies on **`@toruslabs/ethereum-controllers`** for local signing and tx preparation; the remote-signing layer handles policy evaluation, MFA, and broadcast. Both modes route the caller's `EVMTransaction` through `TransactionController.prepareTransaction({ speed, fillNonce })` to populate `gas`, EIP-1559 fees, and the nonce. BYOK mode signs locally via `addNewUnapprovedTransaction` (sign-only), then submits the signed raw tx to the remote service; server mode submits the unsigned payload and polls until terminal.

## Install

Use Node **≥ 20.12.2** and **Yarn 4** (see `packageManager` in `package.json`). Enable Corepack if needed, then install from the repo root:

```bash
corepack enable
yarn install
```

## Usage

Sketch aligned with the current API (adjust imports to your bundler’s resolution of subpath exports):

```typescript
import { getTools } from "@metamask/fox-sdk";
import { mainnet } from "viem/chains";

import {
  ControllerEVMWalletClient,
  createEvmControllerStack,
  createKeyringController,
  EvmByokAdapter,
  KEYRING_KIND,
  ChainNamespace,
} from "@metamask/fox-sdk/wallets/evm";
import { polymarket } from "@metamask/fox-sdk/plugins/polymarket";

// 1. Keyring — chain-agnostic. Register one adapter per namespace you want
//    to support; add SolanaByokAdapter, BitcoinByokAdapter, etc. as they land.
const { keyring, wallets } = await createKeyringController({
  mode: KEYRING_KIND.BYOK,
  input: {
    mnemonic: process.env.WALLET_MNEMONIC!,
    wallets: cachedByokWallets, // optional metadata previously returned by keyring.snapshot()
  },
  adapters: [() => new EvmByokAdapter()],
  registration: {
    baseUrl: "https://signing.example.com",
    authToken: "…",
    projectId: "…",
  },
});

// 2. Controller stack for one chain (repeat per chainId/rpcUrl as needed)
const stack = createEvmControllerStack({
  keyring,
  address: wallets[0].address,
  rpcUrl: "https://…",
  chainId: mainnet.id,
});

const chain = {
  type: "evm" as const,
  id: mainnet.id,
  name: mainnet.name,
  nativeCurrency: mainnet.nativeCurrency,
};

// 3. Wallet client — common signing paths reach the adapter polymorphically
//    via the EvmSigner contract (no `keyring.kind` branching for sign methods).
const wallet = new ControllerEVMWalletClient({ stack, chain });

// 4. Tools = core wallet tools + plugin tools
const tools = await getTools({
  wallet,
  plugins: [polymarket({ credentials: { key: "…", secret: "…", passphrase: "…" } })],
});
```

### Logging

Fox SDK uses a named `loglevel` logger (`fox-sdk`) and defaults to `error`.
Consumers can opt into more verbose diagnostics either globally:

```ts
import { setLoggerLevel } from "@metamask/fox-sdk";

setLoggerLevel("debug");
```

or through the top-level Fox construction helpers used by consumers:

```ts
const { keyring } = await createKeyringController({ ...spec, logLevel: "warn" });
const stack = createEvmControllerStack({ ...stackOpts, logLevel: "warn" });
const wallet = new ControllerEVMWalletClient({ ...walletOpts, logLevel: "warn" });
const tools = await getTools({ wallet, plugins, logLevel: "warn" });
```

The logger setting is module-wide for Fox SDK; the last explicit `logLevel`
wins, and omitting `logLevel` leaves the current setting unchanged. Fox logs
only SDK-owned diagnostics and does not configure upstream Web3Auth/Torus
loggers.

In BYOK mode, call `keyring.createWallet({ namespace: ChainNamespace.Evm, name: "…" })`, `keyring.getWallets()`, and `keyring.snapshot()` to manage CLI-persisted wallet metadata. `ChainNamespace.Solana` is typed for the future but currently throws `UnsupportedNamespaceError`.

For the direct CLI integration recipe, see [docs/cli-sdk-integration-guide.md](docs/cli-sdk-integration-guide.md).

For **server keyring** mode (mimir-proxied HSM), pass adapter factories that the keyring instantiates with its shared HTTP client + project id:

```typescript
import { EvmServerAdapter } from "@metamask/fox-sdk/wallets/evm";

const { keyring, wallets } = await createKeyringController({
  mode: KEYRING_KIND.SERVER,
  input: {
    baseUrl: "https://signing.example.com",
    authToken: "…",
    projectId: "…",
    wallets: cachedWallets, // e.g. from a previous listWalletsRemote() call cached on disk
  },
  adapters: [(deps) => new EvmServerAdapter(deps)],
});
```

`ServerKeyring` owns the wallet roster + shared HTTP client; the
`EvmServerAdapter` owns EVM-typed submit + status methods
(`submitTransaction` / `submitPersonalSign` / `submitSignTypedData` /
`getJobStatus` / `awaitJob`) plus the high-level `EvmSigner` surface
(`signMessage` / `signTypedData` / etc., which internally submit + poll).
`ControllerEVMWalletClient` reaches the adapter via
`keyring.getAdapter("evm")` for both BYOK and server modes — common
signing paths are branch-free. Both BYOK and server modes submit transactions
to the remote signing service (BYOK embeds a locally signed raw tx; server
submits unsigned params) and poll until terminal.

### Multi-chain

A `ControllerStack` is bound to exactly one `chainId` + `rpcUrl`. To transact on multiple chains, build one stack per chain and share the same keyring across them — the keyring is chain-agnostic, and each stack carries its own nonce tracker, pending-tx tracker, and block tracker (the correct isolation):

```ts
const { keyring } = await createKeyringController({ mode: "server", input: { ... } });

const baseStack = createEvmControllerStack({ keyring, address, chainId: 8453, rpcUrl: BASE_RPC });
const opStack   = createEvmControllerStack({ keyring, address, chainId: 10,   rpcUrl: OP_RPC });

const baseWallet = new ControllerEVMWalletClient({ stack: baseStack, chain: BASE_CHAIN });
const opWallet   = new ControllerEVMWalletClient({ stack: opStack,   chain: OP_CHAIN });
```

Two notes for consumers:

- **Read-only access to a different chain** (e.g. a bridge plugin verifying a destination-chain balance) should use a viem `PublicClient` directly rather than spinning up another stack — stacks carry tx-lifecycle machinery that read-only callers don't need.
- **Lazy + cached construction** belongs in the consumer. `agentic-cli`'s `WalletRegistry` is the recommended place to build stacks on first use per `chainId` and reuse them across commands; this SDK intentionally stays at the per-stack primitive level.

## Scripts

From `package.json`:

| Command                                       | Purpose                                               |
| --------------------------------------------- | ----------------------------------------------------- |
| `yarn build`                                  | Production library build (`tsdown` + declaration emit). |
| `yarn build:prod`                             | Build with `NODE_ENV=production`.                     |
| `yarn dev`                                    | `tsdown --watch`.                                       |
| `yarn typecheck`                              | `tsc --noEmit`.                                       |
| `yarn lint` / `yarn lint:fix`                 | ESLint.                                               |
| `yarn test`                                   | Vitest (`vitest run --passWithNoTests`).              |
| `yarn format` / `yarn format:check`           | Prettier write / check.                               |
| `yarn clean:dist` / `yarn clean:node_modules` | Remove `dist` or `node_modules`.                      |

## Plugins

Protocol plugins live under [`src/plugins/`](src/plugins/README.md). Each plugin exports a small factory that returns a `PluginBase` plus any reusable protocol primitives.

In-tree plugins:

- **polymarket** ([README](src/plugins/polymarket/README.md)) — Polymarket V2 read tools and stateless primitives for Polygon/Amoy CLOB reads, auth, deposit wallets, pUSD funding, allowance preflight, and quote math. Higher layers compose these into wallet setup and order flows.
- **calldata-decoder** — read-only calldata decoding helpers.

## Package Status

This package is published to npm as **`@metamask/fox-sdk`**. It is currently intended for MetaMask agentic consumers, including **`agentic-cli`** and **`agentic-sdk`**. Higher-level concerns — lazy plugin loading, `WalletRegistry` caching, CLI UX, and environment wiring — belong in those consumers; see internal docs and those repositories for integration patterns.

## Release & Publishing

This project follows the MetaMask module release process:

1. Choose the release version according to SemVer.
2. Run the `Create Release Pull Request` workflow from GitHub Actions.
3. Review and QA the generated release PR, including changelog cleanup.
4. Squash and merge the release PR into `main`.
5. Wait for the `Main` workflow to detect the release commit and call `Publish Release`.
6. Approve the `publish-npm` job in the `npm-publish` environment, or ask a member of the npm publishers team to approve it.
7. Verify the GitHub release and npm package after the workflow completes.
