npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

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

About

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

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

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

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

Open Software & Tools

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

© 2026 – Pkg Stats / Ryan Hefner

@human.tech/plugin-waap

v0.1.2

Published

WaaP wallet plugin for ElizaOS — 2PC-MPC signing for EVM and Sui

Readme

@humantech/plugin-waap

ElizaOS plugin for the WaaP wallet — 2-of-2 MPC signing for EVM and Sui transactions, with native 2FA support, server-enforced spending policies, and zero private-key exposure to the agent process.

Status: Full EVM + Sui support. 14 actions, 204 tests.

Why this plugin (vs. Coinbase AgentKit)

| Property | Coinbase AgentKit | @humantech/plugin-waap | | --------------------------------------- | ---------------------- | -------------------------------------------------------------------------------------- | | Wallet model | Operator-held API keys | 2-of-2 MPC, no single party holds the key | | Credential surface in agent process | API keys in env vars | None — only a session file path | | Approval flow for high-risk txs | None | Native 2FA via Telegram/email/external wallet, surfaced to the user via Eliza callback | | Spend limit enforcement | Application-level | Server-side policy engine | | Plugin can sign without user permission | Yes (holds keys) | No (2FA required unless explicitly disabled or permission token issued) |

Installation

# Option A: Add to your character.json plugins array (auto-installed on agent start)
# "plugins": ["@humantech/plugin-waap"]

# Option B: Install explicitly
pnpm add @humantech/plugin-waap

Monorepo note: Inside this monorepo the plugin depends on @human.tech/waap-cli via workspace:^, so it always picks up the local CLI build (including in-progress prereq work for 0.2.0). At publish time, pnpm publish automatically rewrites workspace:^ → a caret range against the actual published version (e.g. ^0.2.0), so the artifact on npm depends on a real semver range that allows compatible upgrades.

Dev prerequisite: Because workspace:^ symlinks to the CLI's source directory (not its dist/), you must build the CLI once before the plugin's WaapService.start() can resolve the binary at runtime:

pnpm --filter @human.tech/waap-cli build

Re-run after pulling new CLI changes. (Plugin unit tests don't need this — they spawn a fake-cli.ts fixture via tsx. Only the production runtime path needs the real built binary.)

Getting Started

No provisioning needed. The plugin starts in unauthenticated mode and lets users create or connect wallets through chat:

  1. Add @humantech/plugin-waap to your agent's plugins (via character.json or the web UI)
  2. Start your agent
  3. Chat: "Create a wallet" — the agent will ask for email/password and create a WaaP account
  4. Or chat: "Log in with my email" — to connect an existing account

All settings are optional and auto-configured:

  • Session storage: ~/.eliza/<agentId>/waap/ (auto-generated per agent)
  • Chain: Ethereum mainnet (switchable via "switch to polygon")
  • RPC: Auto-resolved from chainid.network

Environment variables

| Variable | Required | Default | Description | | --------------------------------- | -------- | ---------------------------- | ------------------------------------------------------------------------------------------------------------------ | | WAAP_CLI_SESSION_DIR | no | ~/.eliza/<agentId>/waap | Per-agent session directory. Operator runs waap-cli login here. | | WAAP_DEFAULT_CHAIN | no | 1 (Ethereum mainnet) | Default chain. Accepts evm:137, sui:mainnet, polygon, 1, etc. Switchable via WAAP_SWITCH_CHAIN. | | WAAP_DEFAULT_CHAIN_ID | no | (deprecated) | Fallback for WAAP_DEFAULT_CHAIN. Use WAAP_DEFAULT_CHAIN instead. | | WAAP_DEFAULT_RPC_URL | no | (CLI's default) | RPC URL for the configured chain. | | WAAP_PERMISSION_TOKEN_<chainId> | no | — | Pre-issued permission token to bypass 2FA for a specific chain. Bearer credential — treat as a secret. Rotate. | | WAAP_CLI_BINARY | no | resolved from node_modules | Override path to the waap-cli binary (escape hatch). | | SILK_NODE_ENV | no | production | WaaP backend target — development or production. |

Security model

  1. Password handling in signup/login only. During WAAP_SIGNUP and WAAP_LOGIN, the password is passed directly to the CLI subprocess and never stored in ElizaOS memory, database, or response callbacks. After authentication, only the session token is retained.
  2. No private key exposure. WaaP uses 2-of-2 MPC; the encrypted keyshare lives in the session directory and is never read by the plugin. This is the headline differentiator vs. plugins that hold raw keys.
  3. Per-agent isolation. WAAP_CLI_SESSION_DIR is derived from runtime.agentId so two agents on one host get two independent session files.
  4. No shell: true. All CLI invocations spawn via argv arrays — zero command-injection surface.
  5. Trust-gated financial actions. Per @elizaos/plugin-trust componentDefaults, all signing/sending/policy actions are disabled by default. Operators must explicitly enable them per character.

Actions (14 total)

| Action | Default | Permissions | Chains | What it does | | ---------------------- | -------- | ----------- | -------- | --------------------------------------------------------- | | WAAP_SIGNUP | enabled | (none) | N/A | Create a new WaaP wallet account with email/password | | WAAP_LOGIN | enabled | (none) | N/A | Log in to an existing WaaP account | | WAAP_LOGOUT | enabled | (none) | N/A | Log out and clear session | | WAAP_SWITCH_CHAIN | enabled | (none) | EVM, Sui | Switch active chain (e.g. "switch to polygon", "use sui") | | WAAP_GET_BALANCE | enabled | (none) | EVM, Sui | Read native balance (ETH or SUI) — no 2FA | | WAAP_2FA_STATUS | enabled | (none) | N/A | Check current 2FA method | | WAAP_REQUEST | enabled | (none) | EVM only | Generic EIP-1193 JSON-RPC request — rejects on Sui | | WAAP_SIGN_MESSAGE | disabled | financial | EVM, Sui | EIP-191 personal_sign (EVM) / native sign (Sui) | | WAAP_SIGN_TYPED_DATA | disabled | financial | EVM only | EIP-712 structured data sign — rejects on Sui | | WAAP_SIGN_TX | disabled | financial | EVM, Sui | Sign transaction without broadcasting | | WAAP_SEND_TX | disabled | financial | EVM, Sui | Sign + broadcast transaction (ETH for EVM, MIST for Sui) | | WAAP_SET_POLICY | disabled | admin | N/A | Update wallet's daily spend limit | | WAAP_ENABLE_2FA | disabled | admin | N/A | Enable 2FA (email, telegram, or external wallet) | | WAAP_DISABLE_2FA | disabled | admin | N/A | Disable 2FA (requires current-method approval) |

To enable financial actions for a specific character, override componentDefaults in the character config — see character.json for an example.

Provider

waapWallet injects both wallet addresses, the active chain, 2FA method, and daily spend limit into the agent's prompt context every turn. ~80 tokens. Cached — does not call the CLI per turn.

The provider outputs text like:

Wallet: authenticated
EVM address: 0xabc123...
Sui address: 0x7f8e9d... (64 hex chars)
Active chain: sui:mainnet

And exposes structured values:

{
  waapAddress: string,          // active address (EVM or Sui depending on chain)
  waapEvmAddress: string,       // always available
  waapSuiAddress: string,       // always available
  waapChainCanonical: 'evm:137' | 'sui:mainnet',
  waapChainFamily: 'evm' | 'sui',
  waapEvmChainId: number | undefined,
  waapSuiNetwork: string | undefined,
  waap2faMethod: 'email' | 'telegram' | 'external_wallet' | 'phone' | 'disabled',
  waapDailyLimitUsd: number | undefined
}

2FA flow

When a wallet has 2FA enabled and the agent attempts a signing operation, the plugin:

  1. Submits the request to the policy engine
  2. Receives awaiting_2fa event from the CLI
  3. Sends a callback message to the user: "Approve this transaction in Telegram. I'll wait up to 5 minutes."
  4. Waits up to 5 minutes for approval (the CLI handles the WebSocket + HTTP poll fallback internally)
  5. On approval, completes the 2PC signature and reports the result

Phone 2FA is not supported. The plugin refuses to start if phone_authz is the configured method (the CLI's stdin OTP prompt can't be proxied through a non-TTY subprocess). Switch to telegram, email, or external_wallet via waap-cli 2fa enable --telegram <chatId>.

Troubleshooting

| Error code | Cause | Fix | | ---------------------------------------- | ----------------------------------------------- | ----------------------------------------------------------------------------------------------- | | NO_SESSION | No logged-in session exists | Chat "create a wallet" or "log in" to authenticate. Or provision manually via waap-cli login. | | PHONE_2FA_UNSUPPORTED | Wallet has phone 2FA enabled | Switch to telegram/email/external_wallet via waap-cli 2fa enable | | POLICY_REJECTED | Daily spend limit exceeded or other policy rule | Use WAAP_SET_POLICY to raise the limit (with operator approval) | | INSUFFICIENT_FUNDS | Wallet doesn't have enough balance + gas | Top up the wallet | | TWO_FA_TIMEOUT | User didn't approve within 5 minutes | Try again | | NETWORK | Backend or RPC unreachable | Check connectivity, retry | | CLI_NOT_FOUND / CLI_VERSION_MISMATCH | waap-cli binary missing or too old | pnpm install to refresh; or set WAAP_CLI_BINARY env var |

The PHONE_2FA_UNSUPPORTED error throws at agent boot — the agent will refuse to start until it's fixed. NO_SESSION no longer throws at boot; the plugin starts in unauthenticated mode and prompts the user to sign up or log in. Other errors are reported via the action's callback to the user.

Developing and testing locally

This section walks through running the plugin end-to-end on your machine. There are four testing layers — start with (1) and only move to (4) when you have real WaaP credentials.

Do I need to install ElizaOS?

Short answer: only for Layer 4. Layers 1–3 validate the plugin without booting an ElizaOS agent at all.

| Layer | Needs ElizaOS installed? | Why | | ------------------------- | ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | 1. Unit tests | ❌ No | vitest runs the tests against mocked IAgentRuntime objects. @elizaos/core is installed as a dev/runtime dep of the plugin (so the package's tsconfig can resolve its types), but nothing boots the real ElizaOS runtime. Pure pnpm install && pnpm test. | | 2. Type-check + build | ❌ No | tsc --noEmit and tsup only need the TypeScript definitions from @elizaos/core, which come in as a normal dep. No runtime, no agent. | | 3. Runtime smoke | ❌ No | Uses the WaapService.startWithRunner(runtime, runner) test helper to inject a mock runner and a plain object runtime. The built dist/index.cjs is loaded via node -e. No ElizaOS agent ever starts. | | 4. Full integration | ✅ Yes | This is the only layer that boots a real agent with the plugin loaded. You need either (a) @elizaos/cli to run elizaos start against a character.json, or (b) a standalone Node script that instantiates AgentRuntime from @elizaos/core directly. See Layer 4 setup below. |

Why you don't need ElizaOS for dev: the plugin's architecture deliberately isolates runtime dependencies behind interfaces. WaapService extends Eliza's Service class but can be instantiated and exercised with a minimal fake runtime that implements only agentId, getSetting, getService, and (for extractor-using actions) useModel + composeState. The test files in test/unit/ demonstrate the fake-runtime pattern — read them as a reference if you want to write your own integration harness without installing @elizaos/cli.

Prerequisites

  • Node.js ≥ 20 (run node --version to check)
  • pnpm ≥ 8.15.5 (the monorepo is pnpm-based; see root package.json packageManager field for the pinned version)
  • This monorepo checked out with the feature branch (feature/eliza-plugin-waap or wherever the plugin work lives)
  • macOS/Linux — the CLI has been tested primarily on these. Windows via WSL should work but isn't validated.
  • For Layer 4 only: either @elizaos/cli globally installed (npm i -g @elizaos/cli) OR a standalone Node harness script

1. Install dependencies (one-time per clone)

From the monorepo root (not the plugin directory):

cd /path/to/monorepo
pnpm install

This installs deps for every workspace package and creates the symlink packages/eliza-plugin-waap/node_modules/@human.tech/waap-clipackages/waap-cli (because the plugin depends on workspace:^).

2. Build @human.tech/waap-cli first (required for runtime — optional for unit tests)

Because the plugin's @human.tech/waap-cli dep is a workspace symlink pointing at source, you must build the CLI once so that dist/index.js (the bin entry) actually exists on disk:

pnpm --filter @human.tech/waap-cli build

Re-run after pulling new CLI changes. If you skip this step, WaapService.start() will throw CLI_NOT_FOUND at runtime (plugin unit tests don't need it — they use a fake-cli.ts fixture instead).

3. Layer 1 — Unit tests (fastest, no backend needed)

Runs the full test suite (204 tests across 19 files) against mocks. No network, no real WaaP backend, no credentials. Completes in ~3 seconds.

# Plugin package tests (204 tests)
pnpm --filter @humantech/plugin-waap test

# Or run everything at once from the monorepo root
pnpm -r test

What these cover (204 tests across 19 files):

  • errors.test.ts, types.test.ts — error taxonomy + discriminated types (ChainFamily, WaapChainState, ChainId)
  • chains.test.ts (19 cases) — resolveChain() for EVM names, numeric IDs, Sui networks
  • cliRunner.test.ts (16 cases) — subprocess spawning, NDJSON parsing, timeout, AbortSignal, stderr tolerance
  • WaapService.test.ts (19 cases) — init sequence, dual-address parsing, Sui chain switching, --chain flag, signTypedData/request Sui rejection, policy caching
  • serviceLifecycle.test.ts (23 cases) — unauthenticated mode, login→balance flow, Sui lifecycle, logout
  • actions/*.test.ts (9 files, ~80 cases) — all 14 actions: validate(), happy path, 2FA flow, Sui chains, param extraction
  • provider.test.ts — dual-address rendering, active chain display
  • eventRendering.test.ts — 2FA prompt rendering including external_wallet confirmUrl
  • paramExtraction*.test.ts — zod schemas, LLM extractor (EVM + Sui templates), regex extractor for EIP-712

Expect: 204/204 passing.

4. Layer 2 — Type-check + build (catches regressions tests can miss)

pnpm --filter @humantech/plugin-waap type-check    # tsc --noEmit
pnpm --filter @humantech/plugin-waap build         # tsup → dist/{index.js, index.cjs, index.d.ts, index.d.mts}

If type-check fails, the plugin has a real type-safety issue that must be fixed before shipping. If build fails, the dist won't be publishable.

5. Layer 3 — Runtime smoke test (no WaaP backend, no credentials)

This exercises the full plugin boot + action handler flow using a mock runtime and a mock CliRunner. No real subprocess, no real WaaP backend — but it verifies:

  • The built dist/index.cjs loads correctly from outside the plugin directory
  • resolveBinary() finds the workspace-linked CLI
  • Service initialization sequence runs (whoami → policy → 2fa)
  • Action handler() returns proper ActionResult objects
  • Eliza's Content callback shape is correct
# Run from any directory
cd /tmp
WAAP_CLI_BINARY= node -e "
const m = require('/absolute/path/to/packages/eliza-plugin-waap/dist/index.cjs');

// 1. Manifest shape
const p = m.waapPlugin;
console.log('plugin:', p.name);
console.log('actions:', p.actions.map(a => a.name));
console.log('service type:', m.WaapService.serviceType);

// 2. Resolve binary
m.createCliRunner().resolveBinary()
  .then(b => console.log('binary:', b))
  .catch(e => console.log('FAIL:', e.code, e.message));

// 3. Instantiate service via test helper + mock runner
const mockRunner = {
  async resolveBinary() { return '/fake'; },
  async run(opts) {
    if (opts.cmd === 'whoami') return { ok: true, result: { address: '0xabc' } };
    if (opts.cmd === 'policy') return { ok: true, result: { policy: { authorization_method: { Disabled: null } } } };
    if (opts.cmd === '2fa') return { ok: true, result: { method: 'disabled' } };
    if (opts.cmd === 'sign-message') return { ok: true, result: { signature: '0xdead' } };
    throw new Error('unexpected: ' + opts.cmd);
  }
};
const mockRuntime = {
  agentId: 'smoke-test',
  getSetting: k => k === 'WAAP_DEFAULT_CHAIN_ID' ? '137' : undefined,
  getService: () => null
};

m.WaapService.startWithRunner(mockRuntime, mockRunner).then(async svc => {
  console.log('isReady:', svc.isReady());
  console.log('address:', svc.getAddress());
  console.log('chain:', svc.getCanonicalChain());
  const sig = await svc.signMessage({ message: 'hi' });
  console.log('sig:', sig.signature);
  console.log('=== SMOKE PASSED ===');
});
"

Expected output (adjust /absolute/path/to/):

plugin: @humantech/plugin-waap
actions: [ 'WAAP_SEND_TX', 'WAAP_SIGN_MESSAGE', 'WAAP_SIGN_TYPED_DATA', 'WAAP_GET_BALANCE', 'WAAP_SET_POLICY', 'WAAP_SIGNUP', 'WAAP_LOGIN', 'WAAP_SWITCH_CHAIN' ]
service type: wallet
binary: /.../packages/waap-cli/dist/index.js
isReady: true
address: 0xabc
chain: evm:137
sig: 0xdead
=== SMOKE PASSED ===

If any of these print FAIL: or don't print, something is wrong with the built artifact. Rebuild and re-run.

6. Layer 4 — Full integration against a real WaaP backend (requires credentials)

This is the only layer that actually talks to the WaaP backend, signs with a real MPC keyshare, and broadcasts transactions. Requires a real test wallet with funds on whichever chain you're exercising.

Setup:

# 1. Pick a temp session directory (per-agent isolation — CLI creates it automatically on login)
export WAAP_CLI_SESSION_DIR=/tmp/waap-integration-$(date +%s)

# 2. Log the CLI in (one-time — this is the only place your password is entered)
./packages/waap-cli/dist/index.js login \
  -e [email protected] \
  -p 'your-password'

# 3. Verify the session works
./packages/waap-cli/dist/index.js whoami
# Expected: Wallet address: 0x...

# 4. Verify JSON mode works (this is what the plugin uses)
./packages/waap-cli/dist/index.js --json whoami
# Expected: one line of NDJSON:
# {"event":"result","ok":true,"address":"0x..."}

# 5. Verify NO_SESSION error path (point at an empty dir)
WAAP_CLI_SESSION_DIR=/tmp/waap-empty ./packages/waap-cli/dist/index.js --json whoami
# Expected:
# {"event":"error","message":"No session found...","code":"NO_SESSION"}

Exercise the plugin end-to-end:

Create a minimal Eliza character config (test-character.json) that loads the plugin:

{
  "name": "waap-smoke",
  "plugins": ["@humantech/plugin-waap"],
  "settings": {
    "WAAP_CLI_SESSION_DIR": "/tmp/waap-integration-...",
    "WAAP_DEFAULT_CHAIN_ID": "1"
  },
  "bio": ["Test agent for WaaP plugin smoke testing"]
}

Then boot an Eliza agent with this character and interact via your normal Eliza frontend. Try:

  1. "What's my wallet address?" → should hit waapWalletProvider cache, return address in context (no action call)
  2. "What's my balance?" → invokes WAAP_GET_BALANCE, returns ETH amount
  3. "Sign the message 'hello'" → invokes WAAP_SIGN_MESSAGE, returns signature
  4. "Send 0.001 ETH to 0x..." → invokes WAAP_SEND_TX; if 2FA is enabled, you should see the approval callback prompting you to approve in Telegram/email/hardware wallet, then the tx hash

What to watch for:

  • The provider context should include waapAddress, waapChainCanonical, waap2faMethod, waapDailyLimitUsd in every agent turn
  • WAAP_GET_BALANCE should never trigger 2FA (it's read-only, enabled by default)
  • WAAP_SEND_TX / WAAP_SIGN_MESSAGE / WAAP_SIGN_TYPED_DATA / WAAP_SET_POLICY are disabled by default — you must either enable them in character.actionPermissions or load @elizaos/plugin-trust and configure the trust layer
  • 2FA flow timing: the plugin waits up to 5 minutes for approval (inherited from the CLI's internal WebSocket + poll fallback)

7. Running the existing CLI integration test suite (optional, bash)

The CLI ships its own bash test runner at packages/waap-cli/test-cli.sh that exercises every CLI command (including the new --json mode). If you have a logged-in session:

cd packages/waap-cli

# Run the human-mode test suite (default)
./test-cli.sh

# Run in opt-in JSON mode (exercises the plugin's contract surface)
WAAP_CLI_JSON_MODE=1 ./test-cli.sh

The script verifies every signature with viem.recoverMessageAddress / viem.recoverTypedDataAddress / viem.recoverTransactionAddress to catch any regression in the 2PC signing pipeline.

8. Debugging tips

  • Tests hang or flake on cliRunner timeout cases: the fake-cli.ts fixture uses tsx at runtime. First run can be slow; the test suite has generous timeouts but hardTimeoutMs: 500 tests may flake on cold caches. Re-run.
  • CLI_NOT_FOUND at runtime: you skipped step 2 (build the CLI). Run pnpm --filter @human.tech/waap-cli build and retry.
  • NO_SESSION even though you ran login: the session dir the plugin computes (~/.eliza/<agentId>/waap by default) probably differs from where you logged in. Either set WAAP_CLI_SESSION_DIR explicitly on both the login and the agent boot, or log in to the auto-derived path.
  • Phone 2FA errors with PHONE_2FA_UNSUPPORTED: the plugin refuses to boot with phone 2FA because stdin OTP can't be proxied through a non-TTY subprocess. Switch the account's 2FA method: waap-cli 2fa enable --telegram <chatId> (or email / external_wallet).
  • Need verbose CLI output in JSON mode: you can't — --json suppresses decorated stdout on purpose. Tail stderr instead (2>&1 | grep -v '^{').
  • Type errors about @types/chai / @types/node Buffer conflicts: these are a known root-level tsc context issue in this monorepo, unrelated to the plugin. Use pnpm --filter @humantech/plugin-waap type-check (the package-local tsc) which has skipLibCheck: true and passes cleanly.

Why vitest instead of bun:test?

The official ElizaOS plugin docs recommend tsup for build + bun test for tests. We use tsup for build + vitest for tests — a deliberate, narrow divergence on the test runner only. The build tool matches the docs exactly.

Why:

  1. This plugin lives in a pnpm-based monorepo. Every other package under packages/ here uses vitest or jest. Adding bun would make this the one outlier and force every developer who works across packages to install and learn a second test runner.
  2. The 132 existing tests use vitest-specific patterns that don't translate 1:1 to bun:test's mock.module() API — particularly the action tests' vi.mock(..., async () => { const actual = await vi.importActual(...); return { ...actual, extractFn: vi.fn() } }) pattern and the storage tests' dynamic-import cache-busting trick.
  3. Test runner choice has zero impact on the published artifact. End users on Node, on bun-powered ElizaOS Cloud, or anywhere else see an identical dist/index.cjs. Test infrastructure never crosses the package boundary.
  4. The Eliza docs themselves are inconsistent — they recommend tsup (a Node-ecosystem bundler) for the build step, which signals that mixing tool ecosystems within a plugin is expected and acceptable.

Note for contributors: if you're copying test patterns from upstream Eliza plugin examples (which use bun:test), you'll need to translate the mock API. The structural shape (describe/it/expect/beforeEach) is identical; only the mock helpers differ (mock()vi.fn(), mock.module()vi.mock(), spyOn()vi.spyOn()).

Quick "everything still works" check

# From monorepo root
pnpm install && \
pnpm --filter @human.tech/waap-cli build && \
pnpm --filter @human.tech/waap-cli test && \
pnpm --filter @humantech/plugin-waap type-check && \
pnpm --filter @humantech/plugin-waap test && \
pnpm --filter @humantech/plugin-waap build && \
echo "✅ all green"

If that prints ✅ all green, the plugin is healthy at Layers 1-3. Only Layer 4 requires real credentials.

Testing in production (after publish to npm)

Once @humantech/plugin-waap is published to npm, you need a separate testing path to verify the published artifact works — not the local workspace checkout. Production testing catches a different class of bugs: missing files in the published tarball, broken dependency resolution in a fresh install, version drift between what you built locally and what npm actually got.

1. Pre-publish: dry-run the published tarball

Before pnpm publish (or via changesets), inspect exactly what will ship to npm:

cd packages/eliza-plugin-waap
pnpm pack
# → Creates humantech-plugin-waap-0.1.0.tgz in the current directory

# Inspect contents
tar -tzf humantech-plugin-waap-0.1.0.tgz | sort

Expected contents (matches the "files" field in package.json):

package/LICENSE
package/README.md
package/dist/index.cjs         # CJS bundle
package/dist/index.cjs.map
package/dist/index.d.mts       # ESM type declarations
package/dist/index.d.ts        # CJS type declarations
package/dist/index.js          # ESM bundle
package/dist/index.js.map
package/package.json

Tarball size should be ~70 KB. If it's substantially larger (e.g. 500 KB+) and you see extra chunks like chunk-*.js, _esm-*.js, secp256k1-*.js, that means viem (or another runtime dep) is being inlined. Check tsup.config.ts — every runtime dep must be in the external: [...] array so tsup doesn't bundle it.

Things to verify in the unpacked package.json:

mkdir -p /tmp/waap-unpack && tar -xzf humantech-plugin-waap-0.1.0.tgz -C /tmp/waap-unpack
cat /tmp/waap-unpack/package/package.json | grep -A 6 '"dependencies"'
  • "@human.tech/waap-cli" must be a real semver range (e.g. "^0.2.0"), not "workspace:^". pnpm publish rewrites it automatically at publish time, but verify — if you see workspace: in the published tarball, the publish pipeline is broken and npm installers will fail.
  • "@elizaos/core", "@elizaos/plugin-trust", "zod" all have concrete versions.
  • No dev deps bleeding into dependencies.

2. Fresh-install smoke test in a scratch directory

Simulate what a real end user sees when they install the plugin cold:

# Scratch directory outside the monorepo
mkdir -p /tmp/waap-published-smoke && cd /tmp/waap-published-smoke

# Initialize a minimal package
npm init -y
npm pkg set type=module

# Install the published plugin (or pack file for pre-release testing)
# Option A: real published version
npm install @humantech/plugin-waap

# Option B: locally-packed tarball (pre-release)
npm install /path/to/humantech-plugin-waap-0.1.0.tgz

Then verify the install produced a working artifact:

# 1. Can you require it at all?
node -e "const m = require('@humantech/plugin-waap'); console.log('name:', m.waapPlugin.name); console.log('actions:', m.waapPlugin.actions.map(a => a.name))"

Expected:

name: @humantech/plugin-waap
actions: [ 'WAAP_SEND_TX', 'WAAP_SIGN_MESSAGE', 'WAAP_SIGN_TYPED_DATA', 'WAAP_GET_BALANCE', 'WAAP_SET_POLICY', 'WAAP_SIGNUP', 'WAAP_LOGIN', 'WAAP_SWITCH_CHAIN' ]
# 2. Can the bundled cliRunner resolve @human.tech/waap-cli from the fresh node_modules?
node -e "const m = require('@humantech/plugin-waap'); m.createCliRunner().resolveBinary().then(p => console.log('binary:', p)).catch(e => console.log('FAIL:', e.code, e.message))"

Expected:

binary: /tmp/waap-published-smoke/node_modules/@human.tech/waap-cli/dist/index.js

If this prints FAIL: CLI_NOT_FOUND, the published @human.tech/waap-cli tarball is missing its dist/ directory (its "files" field or build-to-publish script is broken).

# 3. Can the CLI actually run in JSON mode?
./node_modules/.bin/waap-cli --json --help 2>&1 | head -5

Expected: the commander help output (not necessarily JSON-formatted since --help is a commander built-in, but the command should exit 0).

3. Canary smoke test against a real WaaP backend

Same setup as Layer 4 above, but against the published plugin version and against the production WaaP backend (or a dedicated staging backend if you have one). This is the only test that confirms real MPC signing + real policy engine + real 2FA flow work end-to-end with the published artifact.

cd /tmp/waap-published-smoke

# Login against production (use a dedicated canary test wallet, not a real user's wallet)
export WAAP_CLI_SESSION_DIR=/tmp/waap-canary-$(date +%s)

./node_modules/.bin/waap-cli login \
  -e [email protected] \
  -p 'your-canary-password'

# Verify session + JSON mode
./node_modules/.bin/waap-cli --json whoami

Then boot a minimal ElizaOS agent (see Layer 4) pointing at this published install. Run the same prompt battery:

  1. "What's my wallet address?" — provider context only
  2. "What's my balance?"WAAP_GET_BALANCE
  3. "Sign the message 'canary test'"WAAP_SIGN_MESSAGE (with 2FA flow if enabled)
  4. "Send 0.0001 ETH to "WAAP_SEND_TX (low-value canary tx)

Record the tx hash from the last step. Verify on a block explorer that it actually landed on-chain and the from address matches the canary wallet.

4. Monitoring after deploy (whoever hosts the agents)

Once the plugin is deployed in production agents, monitor for:

  • NO_SESSION errors at agent startup → operator didn't provision correctly, or the session dir got wiped
  • TWO_FA_TIMEOUT errors → users aren't approving within 5 minutes (UX problem — possibly need to surface the approval prompt more prominently)
  • POLICY_REJECTED errors → spend-limit tuning needed, or legitimate policy enforcement working as designed
  • CLI_PROTOCOL errors → the CLI is emitting something the plugin can't parse (e.g. after a CLI upgrade introduces a new event shape). This is the main signal that a CLI/plugin version mismatch happened.
  • CLI_NOT_FOUND / CLI_VERSION_MISMATCH → install pipeline problem
  • Telegram/email approval flows not surfacing to users → check that renderEvent output is actually making it through Eliza's callback() into the user-visible chat; the plugin can't tell by itself if the user saw the prompt

5. Rollback strategy

If a canary or production smoke fails catastrophically:

  • For the plugin: publish a patch release reverting to the last known-good behavior. npm has no "unpublish" for versions older than 72 hours, so always roll forward rather than try to delete.
  • For the CLI: waap-cli is pinned by semver range in the plugin's package.json. If a bad CLI version ships, either (a) publish a plugin patch that tightens the range ("^0.2.0""0.2.3"), or (b) publish a fixed CLI patch release. The second is usually faster because the plugin doesn't need to re-release.
  • For the backend: unchanged by this plugin — WaaP backend incidents are handled in the backend team's ops runbook.

6. What NOT to test in production

  • Do not test with your real personal wallet. Always use a dedicated canary account with minimal funds.
  • Do not test WAAP_SET_POLICY against a real wallet in production — it permanently changes the wallet's spend limit (reversible, but via another signed policy mutation).
  • Do not disable 2FA on a real wallet to speed up testing. Canary account only.
  • Do not run automated production tests that call real sign/send actions on a loop — even small value txs compound into gas spam.

Quick production smoke checklist

[ ] `pnpm pack` inspection — tarball contains expected files
[ ] package.json dependencies rewritten (no workspace: references)
[ ] Fresh npm install in scratch dir succeeds
[ ] `require('@humantech/plugin-waap').waapPlugin` loads cleanly
[ ] `createCliRunner().resolveBinary()` finds the CLI binary
[ ] Canary wallet logs in via published CLI
[ ] `--json whoami` emits expected NDJSON
[ ] Eliza agent boots with plugin loaded (no errors at startup)
[ ] Provider context includes wallet address
[ ] `WAAP_GET_BALANCE` returns a balance
[ ] `WAAP_SIGN_MESSAGE` returns a valid signature (verify with viem)
[ ] `WAAP_SEND_TX` produces a real tx hash confirmed on-chain
[ ] 2FA flow surfaces approval prompt to user via callback
[ ] No `CLI_PROTOCOL` / `CLI_VERSION_MISMATCH` errors in logs

Architecture notes

  • Consumes @human.tech/waap-cli via workspace:^ in this monorepo (on npm publish, rewritten to a caret range).
  • LLM-driven param extractors use the canonical Eliza 1.x pattern (composePromptFromState + runtime.useModel(ModelType.TEXT_SMALL) + parseJSONObjectFromText + zod validation), with regex-based fallback for WAAP_SIGN_TYPED_DATA (EIP-712 JSON blobs are too complex for reliable LLM round-trip).
  • Dual-address model: a single keyshare derives both an EVM and Sui address. Both stored in state, shown in provider context. getAddress() returns the one matching the active chain.
  • In-memory chain state with explicit --chain flag on every CLI call — avoids side effects on the CLI session file.

Design docs

The full design specs and implementation plans live in docs/superpowers/specs/ and docs/superpowers/plans/:

  • 001-eliza-plugin-waap-design-2026-04-07.md — original EVM-only design
  • 002-eliza-plugin-waap-production-2026-04-10.md — production readiness spec (audit fixes, new actions)
  • 003-eliza-plugin-waap-sui-2026-04-13.md — Sui integration spec
  • 004-sui-branch-audit-2026-04-14.md — 3-branch comparison audit
  • 005-cli-vs-plugin-actions-2026-04-14.md — CLI vs plugin action mapping