# Agent-Controlled Wallet

`402mob-agent-wallet` lets a Node.js agent create a Base EOA, enroll it as a 402Mob merchant, and operate with the resulting merchant credential. The wallet key stays with the agent runtime; enrollment submits an EIP-191 challenge signature. This client uses signature enrollment, which requires no Base ETH; transaction-proof enrollment is a separate merchant API flow.

Python agents start with the [Python wallet guide](/python-wallet.md), also available as MCP resource `python_wallet_guide`. Package names match across npm and PyPI, but releases and feature sets differ: Node is 0.1.3; Python is 0.2.0. Node examples and requirements below apply to the npm client.

Have an MCP host that can launch local tools? The [local wallet MCP bridge](/wallet-mcp.md) exposes readiness, status and bootstrap so the agent can enroll in one tool call. The host installs/runs the Node companion; the hosted service MCP cannot create the local wallet itself.

The [operations instructions and wallet enrollment prompt](/v1/skills/402mob-operations) are free to read. The same prompt is embedded in `/v1/capabilities` and exposed through MCP resources; no payment or wallet is required for those reads.

## Runtime requirements

- Node.js 22.13 or later.
- HTTPS access to `https://evidence.contact`.
- A persistent private directory for process locking and purchase recovery journals.
- A private local disk, or an encrypted secret store; managed signers require a secret store.
- Linux or macOS filesystem permissions (Windows is not supported).

The package is designed for headless use. It does not create wallet tools in an agent runtime that cannot execute code, call HTTP, or invoke a wallet signer.

## Preflight before creating an identity

1. Confirm that creating a new payout identity is intended. If the task names an existing wallet, use the [existing-wallet flow](/merchant-api.md#agent-enrollment-prompt).
2. Confirm Node.js 22.13+ and npm execution on Linux/macOS, plus HTTPS access to evidence.contact and registry.npmjs.org.
3. Choose a stable absolute private directory that survives restarts. Separate identities need separate directories and vault secret prefixes. If expected state is missing, restore it before bootstrap.
4. Use an encrypted disk or vault adapter for production secrets. The package's default file permissions do not provide encryption.

If a required capability is unavailable, report it before creating an identity. A browsing-only agent still needs an execution or wallet-proof capability.

## One command: create an EOA and enroll

On Linux or macOS with Node.js 22.13+, run:

```sh
npm exec --yes --package=402mob-agent-wallet@0.1.3 -- 402mob-wallet bootstrap \
  --dir "$HOME/.local/share/402mob/research-agent" --name "Research Agent"
```

This installs the published 0.1.3 package from npm, generates an EOA locally, validates and signs the server challenge, and stores the merchant credential. It needs no existing wallet, imported private key, browser, operator token, USDC, or Base ETH. Success requires exit code 0 and JSON with `enrolled: true`, a valid EVM `address`, a UUID `merchant.id`, and `merchant.payoutAddress` equal to `address` (ignoring case). Stdout contains no private key or API key. The package download is free. Its dependencies are installed from npm.

The default local file mode uses permissions 0700/0600 and is **not encrypted**. Use a private encrypted disk or configure the encrypted vault adapter below. Keep this same absolute directory on later calls. Back up its contents privately; the identity file, wallet secret, and purchase journals belong together. Never delete these files to retry enrollment.

Retry the same command after a timeout. It retains the wallet and reuses its credential, or rotates the credential if enrollment completed but its response was lost. Rotation invalidates the previous key. If a vault entry is missing, restore the backup; version 0.1.3 refuses to silently create a replacement identity. Identities previously opened by version 0.1.1 already have this protection and remain bound to their original storage and address.

Only `bootstrap` creates a wallet in the CLI. Other commands require existing state and fail if the directory is missing. `status` reads local saved state without contacting the service; `enrolled: true` there means a credential is stored. Repeat `bootstrap` to verify that credential with 402Mob.

Read local status without another enrollment:

```sh
npm exec --yes --package=402mob-agent-wallet@0.1.3 -- 402mob-wallet status \
  --dir "$HOME/.local/share/402mob/research-agent"
```

Version 0.1.3 is published on npm. The site's versioned download contains the same release bytes. Machine readers can obtain the download URL, checksum, runtime requirements, and success conditions from [`/agent-wallet.json`](/agent-wallet.json).

## Install as a library

```sh
npm install 402mob-agent-wallet@0.1.3
```

## Create a wallet and enroll

Python-only runtime? Use the [Python wallet guide](/python-wallet.md) and the recipe's `pythonBootstrap` field. Python 0.2.0 creates and enrolls a local EOA with separate storage from this Node client. Its library also supports prepared offers, worker discovery, signed public buyer requests and seller Work Tickets. Purchases and managed signers remain Node wallet features.

Adapt the two secret-store methods to the runtime's encrypted vault:

```js
import { AgentWallet } from "402mob-agent-wallet";
import { homedir } from "node:os";

const secretStore = {
  get: (key) => runtimeSecrets.get(key),       // JSON string or undefined
  set: (key, value) => runtimeSecrets.set(key, value),
};

const wallet = await AgentWallet.open({
  directory: process.env.A2A_WALLET_DIR ?? `${homedir()}/.local/share/402mob/research-agent`,
  origin: "https://evidence.contact",
  secretStore,
});

try {
  const status = await wallet.bootstrap("Research Agent");
  console.log(JSON.stringify(status)); // address and merchant profile; no secrets
} finally {
  await wallet.close();
}
```

On first open, the wallet generates an EOA with viem and saves the serialized wallet state in `secretStore` before contacting 402Mob. That stored value contains the private key and, after enrollment, the one-time merchant API key. The adapter must encrypt and restrict that value. Reuse the same directory and secret prefix for every process using this identity. Different agents must use separate directories and prefixes. The directory lock is local; distributed vault locking is not provided. Switching storage backends is not an automatic migration. The default secret name is `402mob/agent-wallet:<origin>`; use `secretPrefix` to separate agents.

The response status includes the public wallet address and merchant profile. The API key is not returned by `status()` or printed by the CLI; it is retained in the secret store. Enrollment uses the existing five-minute EIP-191 challenge and the exact message returned by the service. See the [merchant enrollment API](./merchant-api.md#1-enroll-a-merchant-wallet).

For CLI use, create a trusted adapter module using the [downloadable adapter template](/wallet-adapter.template.mjs), then run:

```sh
npm exec --yes --package=402mob-agent-wallet@0.1.3 -- 402mob-wallet bootstrap --adapter ./wallet-adapter.mjs \
  --dir "$HOME/.local/share/402mob/research-agent" --name "Research Agent"
```

The adapter is executable code, so load only a module supplied by the agent's runtime owner. It keeps secrets access outside command-line arguments and process logs.

## Use a managed signer

To keep key material inside a KMS, hardware wallet, or runtime signer, pass `signer` and `secretStore`:

```js
const wallet = await AgentWallet.open({
  directory: process.env.A2A_WALLET_DIR ?? `${homedir()}/.local/share/402mob/research-agent`,
  secretStore,
  signer: {
    address: signer.address,
    signMessage: ({ message }) => signer.signMessage(message),
    signTypedData: (typedData) => signer.signTypedData(typedData),
  },
});
```

`signMessage` is needed for enrollment. `signTypedData` enables x402 purchases. The signer address must remain the same when reopening the wallet. The package stores that public address and the merchant credential, not the signer’s private key.

## Local file mode

Without adapters, the wallet uses `.local/agent-wallet/wallet.json` with restrictive filesystem permissions. This file is not encrypted. Use the secret-store adapter for unattended production agents. The directory remains necessary in either mode for its process lock and durable purchase journals; keep it private and persistent so interrupted purchases can be resumed safely.

## Boundaries

- Creating a wallet and enrolling it does not fund it.
- EIP-191 enrollment is gas-free. The zero-USDC transaction proof alternative requires Base ETH for gas.
- A buyer purchase requires Base USDC and a signer that supports EIP-712 typed data.
- `maxUSDC` limits one purchase. The wallet does not enforce an aggregate daily or lifetime spending limit.
- The local wallet is noncustodial: 402Mob never receives the private key.

The package includes TypeScript declarations and adapter examples. This guide tracks the current npm release; the immutable archive README was prepared before npm publication.

## Publish a prepared resource

Keep the same directory and adapter on every call. Save an offer JSON file matching the [merchant API](/merchant-api.md), then use the versioned executable from the bootstrap command with `publish --file offer.json`. This uploads the prepared result; `discovery --enabled true` separately opts its metadata into the resolver. Merchant payments go directly to the enrolled wallet. Creating a wallet or enrolling alone does not publish content, enable discovery, fund the wallet, or promise buyer demand.

## Validation scope

Version 0.1.3 was installed from npm and passed local HTTP/CLI enrollment and recovery checks using real local EOA signatures and simulated settlement. Fresh production enrollment, live purchases, external vaults and transaction-proof enrollment remain unvalidated for this version. Historical fresh production enrollment applied to 0.1.2 and does not validate 0.1.3.

## Initial prompt for an external agent

```text
Create a new agent-controlled EOA and enroll it as a 402Mob merchant.
If the local 402mob-wallet MCP bridge is already connected, read its instructions,
call wallet_readiness, then wallet_bootstrap with my merchant name. Do not confuse
it with https://evidence.contact/mcp, which cannot create a local wallet itself.
Use the shell-based runtime paths below if no local wallet bridge is connected.
Read https://evidence.contact/agent-wallet.json
and https://evidence.contact/agent-wallet.md first.
If my task specifies an existing payout wallet, preserve that identity and use
https://evidence.contact/merchant-api.md#agent-enrollment-prompt
instead of generating a replacement.

Choose a supported runtime: Node.js 22.13+ with npm, or Python 3.11+ with pip and subprocess execution
on Linux/macOS. Python agents read https://evidence.contact/python-wallet.md
and use pythonBootstrap in the recipe; install 402mob-agent-wallet==0.2.0 from PyPI.
Verify HTTPS access and a stable absolute private storage directory that
survives restarts. Use an encrypted disk for the CLI or my runtime's encrypted
store through the library (Node secretStore or Python secret_store) for production.
Python vault adapters are synchronous and require the library bootstrap flow;
Python CLI does not accept Node's --adapter flag. Use a separate directory and vault prefix per identity.
If expected wallet state is missing, restore it; do not create a replacement.
If a capability is unavailable, report exactly which one and stop.

Run the chosen runtime's bootstrap command with my merchant name and directory.
Keep Python and Node wallet state in separate directories; do not migrate implicitly.
For shell-free execution, use bootstrap.invocation for Node or
pythonBootstrap.invocation for Python. Substitute every placeholder
and invoke its executable/args directly. Do not treat shell variables as expanded
when using an argument array. Keep the same directory and adapter on later calls.

Confirm exit code 0, enrolled=true, an EVM address, a UUID merchant.id, and
merchant.payoutAddress equal to address. Keep keys and recovery files private.
Report only the public address, merchant ID, and observed enrollment outcome.
Status is a local snapshot; bootstrap checks the credential with the service.
If bootstrap times out, preserve state and retry the same command. Recovery may
rotate the credential and invalidate the prior key. Never delete state to retry.
Enrollment needs no funding, browser or operator token. Do not fund, buy, publish
content or enable public discovery unless those actions are also authorized.
```
