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

wdk-wrapper-agentguard

v1.0.0-beta.1

Published

WDK middleware wrapper for self-custodial AI agent payments: deterministic local policy enforcement and signed receipts for any WDK wallet.

Readme

wdk-wrapper-agentguard

Local, deterministic policy enforcement and signed receipts for self-custodial AI agent payments — for any WDK wallet.

Every autonomous agent needs spending limits and an audit trail. AgentGuard is one registerMiddleware call that gives any WDK wallet both — same policy, every chain, every protocol module, every receipt cryptographically signed.

wdk-wrapper-agentguard is a WDK middleware module. It plugs into the official @tetherto/wdk orchestrator via registerMiddleware and decorates every wallet account with a deterministic policy engine. Every authorize / deny decision produces an append-only, ed25519-signed receipt that is independently verifiable.

It is chain-agnostic: works with EVM, TON, TRON, Solana, BTC, Spark, ERC-4337, or any future WDK wallet manager that exposes the standard IWalletAccount interface.

Status: beta. Built for the Tether WDK Module bounty (Mar 2026). Tested with brittle, Bare-runtime compatible.

Use cases in the agentic economy: see USE_CASES.md for the long form (agentic commerce / x402, A2A payments, delegated treasury, DeFi auto-pilot, subscription agents, kill-switch, receipts as a settlement primitive).


Why a wrapper / middleware module?

WDK ships five module types — wallet, swap, bridge, lending, fiat — and a registerMiddleware hook for cross-cutting concerns (e.g., the official @tetherto/wdk-wrapper-failover-cascade). AgentGuard fills a gap none of the existing modules cover: a portable authorization layer for autonomous agents. No core changes, no per-chain forks — one policy, every wallet.

Architecture

            ┌─────────────────────────────┐
            │   Agent loop / LLM caller   │
            │  (orchestrator, MCP, x402,  │
            │   yield bot, ops bot, …)    │
            └──────────────┬─────────────┘
                          │  account.transfer(…)
                          │  account.sendTransaction(…)
                          ▼
            ┌─────────────────────────────┐         ┌──────────────────┐
            │         AgentGuard          │◄─load──│  policy.json       │
            │  (wdk-wrapper-agentguard)   │         │  (deterministic,   │
            │                             │         │   hot-swappable)   │
            │  1. validateIntent          │         └──────────────────┘
            │     • caps / denylist /     │
            │       rate / chain          │         ┌──────────────────┐
            │  2. record signed receipt   │──sign─►│  Receipt store    │
            │     (ed25519 + policyHash)  │         │  (allow + deny,   │
            │  3. allow → forward         │         │   verifiable      │
            │     deny  → throw           │         │   off-chain)      │
            └──────────────┬─────────────┘         └──────────────────┘
                          │  forwards only on allow
                          ▼
            ┌─────────────────────────────┐
            │   @tetherto/wdk wallet      │    EVM │ TON │ SOL │ BTC │ …
            │   IWalletAccount            │
            └──────────────┬─────────────┘
                          │  signed tx → RPC
                          ▼
                     ┌────────────┐
                     │  blockchain  │
                     └────────────┘

Protocol composition. Every official WDK protocol module — @tetherto/wdk-protocol-bridge-usdt0-evm, @tetherto/wdk-protocol-lending-aave-evm, swaps, etc. — calls this._account.sendTransaction(…) under the hood. AgentGuard wraps sendTransaction on the account, so every protocol call flows through the same policy gate without any protocol-specific code:

  bridge.send(…)     ┐
  lending.deposit(…) ├─► account.sendTransaction(…) ─► AgentGuard ─► wallet ─► RPC
  swap.exec(…)       ┘

See examples/integration.composed.js for a live demo with a MockBridgeProtocol mirroring the real USDT0 bridge call shape.

Highlights

  • WDK-native integration. Single registerMiddleware(chain, fn) call.
  • Deterministic local policy engine. No network, no oracles, no LLMs in the decision path.
  • Signed receipts. Every allow/deny is recorded as an ed25519-signed JSON receipt with a stable SHA-256 policy hash. Verifiable offline, after the fact, by anyone who has the public key.
  • Rolling-window rules. Daily caps and per-hour transaction rate limits are evaluated against the in-memory history of approved operations.
  • Fail-closed. Any thrown error blocks the underlying transaction. The wrapped account never reaches the wire on a denial.
  • Bare-runtime compatible. Tests under brittle-bare, runtime entry point in bare.js.
  • Zero coupling to a chain. Works with @tetherto/wdk-wallet-evm, @tetherto/wdk-wallet-ton, @tetherto/wdk-wallet-solana, @tetherto/wdk-wallet-spark, ERC-4337 smart accounts, gasless TON, or any future WDK wallet manager.

Live proof on Polygon Amoy

This module is exercised end-to-end against real @tetherto/wdk + @tetherto/wdk-wallet-evm on Polygon Amoy testnet (chainId 80002).

  • Funded test wallet: 0x46Ca9120Ea33E7AF921Db0a230831CB08AeB2910 (derived through @tetherto/wdk-wallet-evm from a BIP-39 mnemonic in .env)
  • Outbound recipient: 0xde5797A9C474D4480f0769c9a1361001ED4f8038 (configurable via RECIPIENT=0x...)
  • Live signed broadcasts (status 0x1):
  • Three signed receipts captured under submission/live-amoy-receipts.json (one allow + two deny). All share the same policyHash c5e78fdef9aea3c91c7a66061ce45f6ffa235dbd68d020b87e3c0edbac4aa2bc.
  • Independent verification:
    npm run verify:receipts
    # OK  ... deny  sendTransaction → 0xdead…  amount=1
    # OK  ... deny  sendTransaction → 0x...0001 amount=10¹18
    # OK  ... allow sendTransaction → 0xde57…  amount=1
    # Verified 3 receipts, 0 failed.
    The verifier depends only on @noble/ed25519 + @noble/hashes, can be pasted into any environment, and rejects any tampered field.

See submission/LIVE-AMOY-PROOF.md for the full breakdown.

Installation

npm install wdk-wrapper-agentguard

Peer dependency: @tetherto/wdk-wallet >= 1.0.0-beta.4.

Quick start

import WDK from '@tetherto/wdk'
import WalletManagerEvm from '@tetherto/wdk-wallet-evm'
import getAgentGuardMiddleware, { loadPolicy } from 'wdk-wrapper-agentguard'

const policy = await loadPolicy('./policy.json')

const { middleware, signer, stores } = getAgentGuardMiddleware({ policy })

const wdk = new WDK(seedPhrase)
  .registerWallet('ethereum', WalletManagerEvm, { provider: process.env.RPC_URL })
  .registerMiddleware('ethereum', middleware)

const account = await wdk.getAccount('ethereum')

// Allowed: transfer goes through, signed receipt recorded.
await account.transfer({ token: USDT, recipient: ALICE, amount: 1_000_000n })

// Denied: throws PolicyDeniedError, transaction never sent.
try {
  await account.transfer({ token: USDT, recipient: BAD_ACTOR, amount: 1n })
} catch (err) {
  console.log(err.reasons) // [{ code: 'RECIPIENT_DENIED', message: ... }]
  console.log(err.receipt) // signed deny receipt
}

// Receipts for any account by address:
const myReceipts = stores.get(await account.getAddress()).list()

Try the demo

Three examples ship with the repo, from zero-setup stub to real testnet integration.

1. Zero-setup stub demo

npm install
npm run example

Uses a stub IWalletAccount (no RPC, no real keys). Walks through allow / deny / cap-exceeded / denylisted scenarios and prints the signed receipt trail. Best for a quick first look.

2. Real @tetherto/wdk on Polygon Amoy testnet

cp .env.example .env
# Generate a fresh test mnemonic — never reuse a real seed:
node -e "import('@tetherto/wdk').then(m => console.log(m.default.getRandomSeedPhrase(12)))"
# Paste the mnemonic into .env as MNEMONIC=...
npm run example:integration

What this proves:

  1. AgentGuard registers via the real wdk.registerMiddleware hook on @tetherto/[email protected].
  2. Real account derivation through @tetherto/[email protected].
  3. Live RPC calls (getBalance) work against https://rpc-amoy.polygon.technology.
  4. Denied transactions are blocked locally — they never reach the RPC.
  5. Allowed transactions are signed and broadcast for real when you set SEND_REAL_TX=1 and fund the derived address from https://faucet.polygon.technology.

Sample run (without funding, allowed-broadcast skipped):

Derived account address:  0x8c1ecCb1e0EBD9167A8Ad41DfbcdCE8DCb5a714e
Live native balance:      0 wei

>> Scenario 1: send 1 wei to a denylisted address
   ✓ DENIED locally (no RPC call) reasons=[RECIPIENT_DENIED]

>> Scenario 2: send 1 MATIC (over cap) to allowed address
   ✓ DENIED locally (no RPC call) reasons=[AMOUNT_EXCEEDS_PER_TX, AMOUNT_EXCEEDS_DAILY_CAP]

3. Composition with WDK protocol modules (bridge / lending / swap)

npm run example:composed

The killer feature: every official protocol module — including @tetherto/wdk-protocol-bridge-usdt0-evm and @tetherto/wdk-protocol-lending-aave-evm — funnels its on-chain calls through account.sendTransaction(...). AgentGuard wraps that method on the WDK account itself, so the moment a protocol object holds a wrapped account, every call it makes goes through the policy gate. No protocol-specific code in AgentGuard.

This script demonstrates that with a tiny MockBridgeProtocol whose call shape mirrors the real bridge module (this._account.sendTransaction(oftTx)):

Wrapped WDK account:  0x0184Eb87...

>> Bridge attempt 1: 1 wei, allowed recipient
   policy allowed (broadcast attempted, fails only on insufficient funds — expected)

>> Bridge attempt 2: 1 wei, DENIED recipient
   ✓ Bridge call BLOCKED by AgentGuard at the wallet layer reasons=[RECIPIENT_DENIED]

>> Bridge attempt 3: amount over per-tx cap
   ✓ Bridge call BLOCKED reasons=[AMOUNT_EXCEEDS_PER_TX, AMOUNT_EXCEEDS_DAILY_CAP]

Same wrap, every protocol — no per-protocol integration code in AgentGuard.

Policy schema

Policies are versioned JSON. All amount strings are decimal integers in the token base unit (e.g., "1000000" for 1 USDT, since USDT has 6 decimals).

{
  "version": "1.0.0",
  "agentId": "agent-001",

  "rules": {
    // Per-transaction maximum (decimal string, base units).
    "maxAmountPerTx": "10000000",

    // Rolling 24h cumulative cap, per (chain, token).
    "dailyCap": "100000000",

    // Rolling 1h transaction rate limit (count of approved tx).
    "maxTxPerHour": 5,

    // Allowed/denied addresses (case-insensitive). deniedRecipients always wins.
    "allowedRecipients": ["0xabc..."],
    "deniedRecipients":  ["0xdead..."],

    // Allowed token contracts. Empty/omitted = no constraint.
    "allowedTokens": ["0xdAC17F958D2ee523a2206206994597C13D831ec7"],

    // Allowed chain identifiers. Empty/omitted = all chains allowed.
    "allowedChains": ["ethereum", "arbitrum"]
  }
}

The policy is hashed (canonical-JSON SHA-256) at registration time, and the hash is embedded in every receipt. Any change to the policy produces a new hash, so receipts are bound to the exact rule set that was active when the decision was made.

API reference

getAgentGuardMiddleware(config) (default export)

Creates a WDK middleware function that wraps every derived account.

| Field | Type | Description | |---|---|---| | policy | Policy | The policy to enforce. | | chain | string (optional, recommended) | The blockchain identifier — pass the same value used in wdk.registerMiddleware(chain, fn). Falls back to reading account._config.chain if available, then "unknown". | | signerSeed | Uint8Array (optional) | 32-byte ed25519 seed. Persist this if you need cross-session receipt verification. If omitted, a random seed is generated. | | now | () => number (optional) | Clock function returning unix ms. Defaults to Date.now. Useful for tests. |

Returns: { middleware, signer, stores }

  • middleware — pass to wdk.registerMiddleware(chain, ...).
  • signer — the ed25519 signer (public key available via signer.getPublicKeyHex()).
  • storesMap<address, ReceiptStore> populated as accounts are derived.

wrapAccount(account, options)

Lower-level helper. Wraps an existing IWalletAccount directly. Useful when you are not using @tetherto/wdk but still have a WDK-shaped account.

| Field | Type | Description | |---|---|---| | policy | Policy | The policy to enforce. | | chain | string | The blockchain identifier. | | receiptStore | ReceiptStore (optional) | Use an existing store. | | signer | Signer (optional) | Use an existing signer. | | now | () => number (optional) | Clock function. |

Returns: { account, receiptStore, signer } — the wrapped account is the same reference passed in (its sendTransaction / transfer are replaced with guarded versions).

loadPolicy(filePath) / parsePolicy(text)

Load a policy from disk (Node + Bare via bare-fs) or parse one in-memory. Throws PolicyError on malformed input.

hashPolicy(policy)

Returns a stable lowercase hex SHA-256 of the canonical-JSON serialisation of the policy.

validateIntent(policy, intent, history?)

Pure function. Evaluate an intent against a policy and a history of approved operations. Returns { allow: boolean, reasons: PolicyReason[] }. Used internally by the proxy; exported for advanced use cases (dry-run, simulation, custom integrations).

createReceiptStore(signer) / createEd25519Signer(seed?)

Compose your own decision pipeline. The proxy uses these under the hood.

Errors

  • PolicyError — thrown by loadPolicy / parsePolicy / hashPolicy for malformed policies.
  • PolicyDeniedError — thrown by guarded sendTransaction / transfer. Carries reasons: PolicyReason[] and receipt: Receipt.

Receipt format

{
  "id":         "1700000000000-1a2b3c4d",
  "timestamp":  1700000000000,
  "chain":      "ethereum",
  "action":     "transfer",
  "recipient":  "0xabc...",
  "amount":     "1000000",         // decimal string, base units
  "token":      "0xdAC17F...",     // omitted for native transfers
  "decision":   "allow",           // "allow" | "deny"
  "reasons":    [],                // PolicyReason[] — non-empty on deny
  "policyHash": "f1c4...e9",       // SHA-256 of canonical policy JSON
  "agentId":    "agent-001",
  "publicKey":  "ab12...",         // ed25519 public key, hex
  "signature":  "9f44...12"        // ed25519 signature over canonical body, hex
}

To verify a receipt: re-canonicalise the body (everything except signature), and verify the ed25519 signature against publicKey.

Development

npm install
npm run lint           # standard
npm test               # brittle (Node)
npm run test:bare      # brittle-bare (requires bare runtime installed)
npm run build:types    # tsc → ./types
npm run example        # zero-setup stub demo
npm run example:integration   # real WDK + Polygon Amoy testnet (needs MNEMONIC)
npm run example:composed      # AgentGuard wrapping a WDK protocol-shaped object

Conventions

This module follows the Tether WDK module conventions:

  • ES modules, JSDoc-typed, tsc-generated .d.ts.
  • standard lint, brittle tests.
  • Per-file Apache-2.0 copyright headers.
  • bare.js entry via bare-node-runtime/global + import attributes.
  • AGENTS.md describing project structure for AI assistants.

License

Apache-2.0 © Hebx