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

mm-plugin-grain

v0.1.4

Published

Grain for the MetaMask Agent Wallet: find out who made an image, then license it or pay them

Readme

mm-plugin-grain

Grain for the MetaMask Agent Wallet. Before an agent reposts, publishes or pays for an image, it can ask who made it, from the image itself, even after screenshots, crops and re-encoding. Then it can license the image at the creator's price, or pay them directly. And an agent that generates images can register them, credited to its own ERC-8004 identity.

mm grain verify <image>          who made this image?
mm grain license <image|record>  license it at the creator's price, on chain
mm grain pay <image|record> <n>  pay them any amount, through your MetaMask wallet
mm grain record <id>             look up a record
mm grain creator <handle>        everything a creator has registered
mm grain agent <name>            give this agent an ERC-8004 identity
mm grain register <image>        register an image it generated, credited to that identity

Why an agent needs this

Agents increasingly pick images to post, buy or build on, and they have no way to tell where an image came from. Content credentials (C2PA) are stripped by the first screenshot. Grain is an open onchain registry that finds them again: an invisible TrustMark watermark and a perceptual fingerprint, cross-checked so that a watermark copied onto someone else's picture is reported as forged rather than believed.

This plugin gives an agent the same answer the website gives, as JSON, and closes the loop with MetaMask: find the human who made it, then license it from them.

Commands

mm grain verify <image>

A local PNG/JPEG path or an http(s) URL. No sign-in or wallet needed.

$ mm grain verify https://grain-on-monad.vercel.app/samples/forged.jpg --json
{
  "ok": true,
  "data": {
    "state": "TAMPERED",
    "summary": "Forged credential: the image carries @grain-studio's watermark, but the picture does not match what they registered. Do not attribute it to them.",
    "watermark": "found",
    "candidatesFrom": "indexer",
    "onChainDistance": 32,
    ...
  }
}

| state | Meaning | What an agent should do | |---|---|---| | RESOLVED | Made by the creator in record | attribute it, or pay them | | UNCERTAIN | Heavily edited; probably that creator | attribute with care | | TAMPERED | A watermark copied onto a different picture | do not attribute it | | NOT_FOUND | Not registered, or edited past recognition | treat as unknown |

onChainDistance comes from FingerprintIndex.verify() on the contract itself, not from the indexer, so the agent never has to trust Grain's servers for the answer.

The first run downloads the 45 MB watermark decoder once into ~/.cache/grain/models. --fast skips it and matches by fingerprint alone, which also means it cannot detect forged watermarks.

mm grain license <image|record>

Licenses the image through Grain's LicenseRegistry on Monad testnet, at the price the creator set when they registered. The contract forwards the full amount to the creator (there is no protocol fee) and records the licence on chain, where Grain's Envio indexer picks it up. Needs mm login, wallet-submit and wallet-read.

$ mm grain license https://grain-on-monad.vercel.app/samples/reposted.jpg --max-price 0.05 --dry-run
Licensing record 511 costs 0.01 MON under the Grain Licence v1 (https://grain-on-monad.vercel.app/licence), paid in full to @grain-studio. Nothing was sent.
  • What it buys: the Grain Licence v1: worldwide, non-exclusive use in any medium, personal or commercial; credit the creator where practical; don't resell the file or claim you made it. The approval MetaMask sends names the licence, and the result carries terms so the agent knows exactly what it bought.
  • --max-price refuses if the creator asks more. Use it for any unattended agent.
  • Same strictness as pay: forged, uncertain and withdrawn matches are refused, and so is a creator with no price set (the error suggests pay instead).
  • --rate 1-5: if the image was generated by an ERC-8004 agent whose identity checks out, leave that agent feedback on the standard ReputationRegistry once the licence confirms. A second approval. An agent can't rate itself.
  • mm grain record <id> shows the price and how many licences have been granted.

mm grain pay <image|record> <amount>

Finds the creator, then sends them the amount through MetaMask's policy-gated wallet executor. Needs mm login, and the wallet-submit and wallet-read capabilities, which you approve at install (wallet-read lets it add the Monad testnet RPC entry described below).

$ mm grain pay ./downloaded.jpg 0.5 --dry-run
Would send 0.5 on chain 10143 to @grain-studio (0x5c71…d7C8), the creator of record 511. Nothing was sent.
  • Strict on purpose. It pays only on a clean RESOLVED match or an explicit record number. A forged or uncertain match is refused with the reason, and so is a record its creator has withdrawn.
  • Any chain your wallet supports. A Grain creator's account is an ordinary EOA derived from their passkey, so the same address receives on every EVM chain. Payments default to Monad testnet (10143), where the registry lives; --chain-id 143 pays on Monad mainnet, or any other chain your wallet supports.
  • --dry-run shows the exact transaction without sending it.

mm grain agent <name> and mm grain register <image>

For agents that generate images. agent mints an ERC-8004 identity on Monad testnet's standard IdentityRegistry (0x8004A818…BD9e), owned by this MetaMask wallet, with its registration file embedded as a data: URI. register does what the website does: embeds an invisible watermark carrying the next record id, fingerprints the marked image, has the wallet sign the manifest, and registers it, declared AI-generated (C2PA trainedAlgorithmicMedia) and naming the agent. It saves the marked copy, which is the one to publish.

$ mm grain agent "Grain Demo Agent" --description "Generates illustrations"
Created ERC-8004 agent #2105 "Grain Demo Agent", owned by 0x6bA5…fF60.

$ mm grain register ./generated.png --agent-id 2105 --title "Provenance Generation"
Registered record 535, credited to ERC-8004 agent #2105.
Watermarked copy: generated-grain.png (publish this one, not the original)
  • register refuses an agent id this wallet doesn't control, so an agent can't credit its work to someone else's identity. Anyone checking the image sees "AI-generated by Grain Demo Agent · ERC-8004 agent #2105 · identity checked on chain" (record 535), and the site re-checks that on every view.
  • The first run downloads the 17 MB watermark encoder once. Two approvals: signing the manifest, then the transaction.

mm grain record <id> and mm grain creator <handle>

Lookups. creator lists a creator's records through Grain's Envio indexer, because the contracts alone cannot list them.

Install

mm config set experimentalPlugins true
mm plugins install mm-plugin-grain

On npm 12, mm plugins install fails with PLUGIN_NOT_FOUND for every npm package, not just this one: mm reads npm view <package> --json, which npm 12 now returns as an array where mm expects an object. Run the install with npm 11 (only the install needs it):

PATH="$(dirname "$(npx -y -p npm@11 which npm)"):$PATH" mm plugins install mm-plugin-grain
# or simply: npm install -g npm@11

From this repository:

cd plugins/mm-grain && npm install && npm run build
npm run link-host        # use the installed mm's own CLI package, not a copy (see link-host.mjs)
mm config set experimentalAllowUnverifiedInstalls true
mm plugins install "file:$PWD" --accept-permissions

Skip link-host and the plugin loads a second copy of the CLI, which crashes with window.addEventListener is not a function. Installs from npm don't need it; mm links its own copy for those.

Monad testnet and MetaMask's wallet

mm lists Monad Testnet (10143) as a supported chain, but two MetaMask services it relies on reject it with Invalid chainId (HTTP 400): the gas-fee estimate API, and the Infura proxy it uses as the chain's RPC. So out of the box, no transaction can be sent on 10143. Grain works around both using mm's own mechanisms:

  • Fees: on testnet the plugin sets gas, maxFeePerGas and maxPriorityFeePerGas itself, read from Monad's RPC (gas estimate plus 25%; Monad charges the whole limit). mm skips its fee API when these are present.
  • RPC: before the first real submission on testnet, the plugin adds one entry to the wallet's customEvmChains, Monad Testnet with rpcTarget: https://testnet-rpc.monad.xyz. mm checks custom chains before its default proxy. It's added once and only if missing, and the command says when it does it. A dry run never changes anything.

First licence through this path: 0xc9e76b7b…6b17, approved by email through MetaMask's server wallet.

How it works

The plugin bundles Grain's own resolver (packages/grain-core) and the website's browser TrustMark port, run on ONNX Runtime's WASM backend in Node. The verdict logic is the same code the website runs, so the CLI and the site cannot disagree about an image. Candidate lookups go to the Envio indexer and fall back to Monad's RPC.

| Variable | Default | |---|---| | GRAIN_INDEXER_URL | Grain's Envio Cloud endpoint | | GRAIN_RPC_URL | https://testnet-rpc.monad.xyz | | GRAIN_SITE | https://grain-on-monad.vercel.app (model downloads, record links) |

MIT