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

@metalntwx/metal-agent

v0.3.3

Published

The `metal-agent` CLI and MCP server: create or import the agent's key, fund it on Metal devnet or testnet, pay x402 offers in *USDZ, look up a settlement, and set spending limits for an agent that Metal enforces.

Readme

@metalntwx/metal-agent

The metal-agent CLI an agent pays x402 payees on Metal with: create or import a key, fund it, pay a URL, look up a settlement, or serve pay, fund and status as MCP tools, and under a mandate its payouts, status and verification.

npm install -g @metalntwx/metal-agent

metal-agent key create
metal-agent key import <file|hex>
metal-agent fund <address> --network testnet
metal-agent pay https://x402-demo-testnet.metalntwx.com/quote --network testnet
metal-agent status <txHash> --network testnet --rpc-url <endpoint>
metal-agent mcp

npx -y @metalntwx/metal-agent <command> runs one command without installing.

Every command that touches the chain names --network devnet or --network testnet; there is no default. Each network's facilitator URL, its RPC (devnet's default endpoint, or the console that issues testnet endpoints) and the *USDZ address come from @metalntwx/metal-pay, and networks.json holds its demo payee URL and, on testnet, its faucet and explorer URLs. METAL_RPC_URL and METAL_FAUCET_URL override those defaults, and --rpc-url and --faucet-url override both; METAL_FACILITATOR_URL and --facilitator-url do the same for mandate pull.

On testnet, pay and fund need no RPC. status, account create and every mandate command but credential create need an endpoint of your own: sign up in the Metal console, https://console-testnet.metalntwx.com, create an endpoint, and pass it with --rpc-url or METAL_RPC_URL. On testnet, pay and fund print an explorer line with the transaction's page on https://testnet.metalntwx.com.

The key file defaults to ~/.metal-agent/key.json and holds the private key in the clear, owner-readable only. key import takes a hex key (with or without 0x), a file holding one, or a key file, and prints only the address.

fund --network testnet asks the public faucet's POST /api/fund, and the faucet sets the amount. An address that already holds a full grant counts as funded. A rate-limited request fails with what to do next: retry after the stated wait, try tomorrow, or import a key someone funded for you. fund --network devnet [--amount <n>] spends from Foundry test account #0, which the devnet funds at genesis.

pay refuses, before it signs, an offer in any token but *USDZ and any offer above --max, 1 *USDZ per payment by default. A refusal at verification, by the facilitator or the seller's own check, prints payment refused: <reason>. A refusal at settlement for a reason that moves nothing prints payment refused at settlement: <reason>, with the transaction hash when there is one; both refusals end nothing was paid. Any other failed settlement, such as one whose answer the seller lost, prints payment in doubt at settlement: <reason>: it may have settled.

pay sends --method (GET by default, POST with a body), --body (as application/json unless a --header names a content type) and each repeated --header 'name: value', and sends them again with the payment. A JSON-RPC call to an RPC endpoint sold over x402, for example:

metal-agent pay <rpc url> --network testnet --method POST \
  --body '{"jsonrpc":"2.0","id":1,"method":"eth_blockNumber","params":[]}'

pay prints the response headers before the body, as header <name>: <value> lines, leaving out transport, caching and CORS headers and the payment it reports above. An RPC seller such as Metal's answers with rpc-aas-endpoint, the endpoint the payment bought: send further JSON-RPC calls there, without paying again, until its credit runs out.

Spending limits

A mandate lets an agent pay from its owner's account with a P-256 key of its own, within limits Metal enforces on chain: the most per payment, a budget for the mandate's whole life, an expiry and, when --payee names any, the addresses it may pay; with none named it may pay any address.

metal-agent mandate credential create --principal <MetalAccount address> [--payee <address>[,<name>[,<website>]]]... \
  --cap 5 --budget 10 [--description <text>] --key <owner key file> --out ./mandate.json --network testnet
metal-agent mandate bind --credential ./mandate.json --key <owner key file> --network testnet --rpc-url <endpoint>
metal-agent pay <url> --mandate ./mandate.json --network testnet
metal-agent mandate status --credential ./mandate.json --network testnet --rpc-url <endpoint>
metal-agent mandate pull --credential ./mandate.json --to <address> --amount 2.50 --network testnet --rpc-url <endpoint>
metal-agent mandate verify --credential ./mandate.json --issuer <issuer public key file> --network testnet --rpc-url <endpoint>
metal-agent mandate verify --credential ./mandate.json --issuer <issuer public key file> --tx <hash> --signed-offer <text> --network testnet --rpc-url <endpoint>
metal-agent mandate replace --credential ./wider.json --replaces ./mandate.json --key <owner key file> --network testnet --rpc-url <endpoint>
metal-agent mandate renew --credential ./next.json --replaces ./mandate.json --key <owner key file> --network testnet --rpc-url <endpoint>
metal-agent mandate revoke --credential ./wider.json --key <owner key file> --network testnet --rpc-url <endpoint>

credential create writes the limits into a credential file, a Verifiable Intent mandate, and beside it the mandate key the agent signs with: mandate.key.json for mandate.json. --mandate-key-file <file> names a mandate key that exists instead, as a replacement does. The keys that sign the credential are the owner's: they live beside the owner's key file, created on first use (owner-key.issuer.json, with its public half in owner-key.issuer.public.json), unless --issuer-key names their file. --description says what the mandate is for, and --payee takes a name and a website after the address. Every other command takes that credential file: the owner's account, the mandate's id and its terms derive from it. Amounts are in *USDZ, to the cent; a mandate lasts 30 days unless --expires-in says otherwise, and its budget never resets.

account create --out <owner key file> writes the owner's key and creates the owner's Metal account on chain, a smart account that key controls with threshold one; --key <file> uses a key that exists. The owner's key pays the gas, and one that holds too little is funded first. When a later step fails, such as a faucet refusal, the error says why and names the key file; the same command continues from it. Fund the account and name it as the credential's --principal; bind refuses an address with no account. The account factory is the one @metalntwx/metal-pay names for the network; on a devnet just deploy deploys it there, and --factory <address> names another. @metalntwx/metal-pay names the testnet's.

bind, replace and revoke are signed by the account's owners, as many as its threshold needs. --key names the gas sponsor and defaults as the sole signer; repeat --signer-key <file> to supply the owners' keys of an account with several instead. The SDK's authorizeMandateCall accepts a signing callback for mixed keys and passkeys; the CLI's signatures expire fifteen minutes after signing. Keep owner keys outside the agent's reach: an owner key moves the account's funds outside any mandate's limits. replace revokes the mandate --replaces names and carries its spend into the new one; renew binds the new one and revokes the old in one batch, starting its spend at nothing, which is how a budget starts over. bind, replace and renew trust the issuer beside --key unless --issuer names others.

Verification trusts that factory, never one a payer names: it checks the account's immutable clone code against the factory's current implementation, and checks successful token-transfer receipts.

pay --mandate <credential> pays from the owner's account, signed by the mandate key beside the credential, or the one --mandate-key-file names. It takes neither --key nor --max. A payment outside the mandate is refused and moves nothing, usually with a mandate_* reason: payment refused: mandate_cap_exceeded (…). pay prints the payment's nonce as soon as it signs; the facilitator's /settlements?payTo=<payee> lists a settled payment by an id ending in it.

mandate pull pays an address the mandate allows directly, with no seller: the agent's mandate key signs, and the facilitator submits the transaction and pays its gas; --key names a funded key to submit it instead. A pull the mandate refuses when checked is not sent: it reports the reason and exits 1. With --key, --record-refusal sends it, so the refusal is recorded on chain in a transaction that succeeds and moves nothing. The outcome is read from the chain: paid (exit 0), refused (exit 1), held (exit 4) when the payee's receive policy sends it to escrow, or pending (exit 3); an error: line also exits 1. Each pull is kept under ~/.metal-agent/pulls/<mandate>/, and running the same command again sends a pending pull again, which pays at most once. --payment-id <id> names the payment: two payouts of one amount to one payee are two payments under two ids, an id whose payout was paid or held reports it again, after an already paid or already held line, and sends nothing, and one the account refused may be sent with other values. Without it, a payout is known by its payee and amount. A retry from another home directory, or under a credential that renewed or replaced this one, signs a new payout.

mandate verify checks the mandate and every payment under it from the chain alone, through any RPC endpoint: the binding on the owner's account, the credential against it and under its issuer, and each payment and refusal as the mandate key's own signed per-payment mandate, rebuilt from its transaction. Anyone holding the credential and the issuer's public file, passed as --issuer, can run it; --tx <hash> verifies one transaction, with --signed-offer <text> the offer the agent signed for it, and a check that fails exits 1, as does an event under the mandate that the SDK does not decode, as an account of another version emits. A seller that asks for the agent's signed order (requireSignedOffer in @metalntwx/metal-pay/mandates, with extra.signedOffer: "required" on the route) is paid with it, and pay prints it. From code, verifyMandate and verifyMandatePayment do the same, the latter for a payee that holds no credential.

skills/metal-agent/SKILL.md is an installable skill: the setup steps for an agent, then the commands.

metal-agent mcp serves pay, fund and status over stdio, each taking a network, and pay the request as optional method, body and headers. What pay spends is set when the server starts, by whoever starts it: a key file and --max (the default key file and 1 *USDZ when omitted), or one mandate's credential file with --mandate <credential>, which each call reads. Under a mandate it also serves mandate_pull, mandate_status and mandate_verify, which run those commands on the server's mandate and read the chain through its METAL_RPC_URL, or devnet's default endpoint. The model calling the tools chooses no key, no mandate and no higher limit; under a key, a max it passes can only lower the server's. The other mandate commands enter only through the CLI. An MCP host starts it with:

{ "command": "npx", "args": ["-y", "@metalntwx/metal-agent", "mcp"] }

or, under one mandate, with the endpoint the mandate tools read on testnet:

{
  "command": "npx",
  "args": ["-y", "@metalntwx/metal-agent", "mcp", "--mandate", "/path/to/mandate.json"],
  "env": { "METAL_RPC_URL": "<endpoint>" }
}

Develop

In the mx56db/metal repository, just agent <command> runs the CLI from source. Devnet fund signs through metal-ts's wasm codec; build it once with just metal-ts-wasm.

METAL_RPC_URL=<endpoint> just agent-testnet-smoke (scripts/testnet-smoke.ts) runs the pay quickstart against the testnet services: it checks the facilitator's /supported, funds a throwaway key through the faucet (or uses METAL_SMOKE_PRIVATE_KEY), pays the demo payee, confirms the MIP20Transfer on chain, and exits non-zero on any failure. METAL_FACILITATOR_URL and METAL_DEMO_PAYEE_URL point it elsewhere.

Run pnpm test and pnpm typecheck.