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

@kuiralabs/arc-mandate

v0.1.8

Published

Let a coding agent spend a bounded allowance, and call the functions an app names, granted from your phone and enforced by the chain. Arc and Monad.

Readme

Give your coding agent an allowance

An MCP server, so the agent you already run — Claude Code, Cursor, Codex — can spend from your wallet inside limits you set on your phone and take back with your face.

claude mcp add arc-mandate -s user -- npx -y @kuiralabs/arc-mandate

The wallet it spends from opens in your phone's browser at https://kuiralabs.github.io/mandate/, on Arc testnet, Monad testnet and Arc mainnet alike. The connector names it when it shows its code. Then, in the agent:

you: what's your payment address? agent: (calls get_pairing_address) → a QR code, with 0x1f94…F107 under it

— you grant it $10 in the app, with Face ID —

you: buy me the ACME filings agent: (calls pay) → paid $2. Remaining: $8.

How pairing works, and why it is one step

The agent generates its own key on first run and never sends it anywhere. A mandate is granted to an address, so only a public value moves, and only from the agent to you.

Granting needs nothing but that address, and the address is public, so the agent does not take the first grant it finds. Its code also carries a one-time pairing code, and the app writes a hash of it into the grant. After you grant, the agent asks the chain for the SessionKeyAdded event naming its own address and carrying its code. Both are indexed in the event, which is what makes a single scan enough: no second round trip, no config file, and no copy-paste back.

A grant made any other way, by a stranger or from an address typed on its own, carries no code the agent is waiting for, and is not used. The first grant carrying a code wins, so a copy made after yours cannot take its place. Every answer about spending names the wallet it comes from, so you can match it to the app. To move the agent to another wallet, ask it for its code again and scan it from that wallet. The code is also saved as pairing-code.png beside the agent's key.

Tools

| | | |---|---| | get_pairing_address | the code to grant to: a QR carrying the agent's address and a one-time pairing code, for the app to scan, also saved as an image. Says which wallet the agent spends from, if one is paired | | check_allowance | limit, spent, remaining and what is spendable now, read from the chain, and the agent's ERC-8004 identity, which it sets up the first time for each wallet | | pay | send USDC to an address, within the allowance | | call | call one function an allowance names, on an app's contract, sending the coin with it when the function takes payment | | sign_statement | sign an app's own structure, so its door can see which agent is knocking, for an app whose contract the allowance reaches | | buy | fetch a URL and pay if it answers 402 Payment Required (x402), topping up the agent's balance at Circle's Gateway from the allowance when it has to | | top_up | move money into the agent's balance ahead of a run of small purchases; it counts against the same allowance |

The agent cannot exceed the allowance or keep spending after you revoke, and an allowance that names payees cannot pay anyone else. Nor can it touch anything else in your wallet: every allowance is an allowlist naming the three things an agent does, paying in USDC, filling its balance at Circle's Gateway, and setting up its own identity, so your other tokens and NFTs are out of its reach. None of that is enforced here: the account enforces it. A revoked or expired allowance is refused during validation, before anything runs. On the one-meter allowance the app grants, an over-limit payment is refused when it runs instead: no money moves, and the gas is Circle's sponsorship. This connector checks the limit before sending, so only a connector that skipped the check reaches that refusal.

Proving who the agent is, to an app

A seller that charges learns who the agent is by being paid: the payment carries a signature, and the payer's address is the agent. An app that charges for nothing, and only wants to know whose agent is at its door, needs the same proof without the money. That is sign_statement.

What it signs is the app's own structure, in the app's own domain, never a sentence and never a bare hash. That is not a style choice. An operation is authorised by a plain signature over its hash, so a key that signs whatever text it is handed hands out something that can be submitted as an operation. A structure carries its domain's separator, so the signature is unusable anywhere but that app, cannot be read as an operation, and is never the wallet's: the plugin validates operations and nothing else, so a session key's signature is only ever the agent's own, and the agent owns nothing.

An agent may sign only for an app whose contract its allowance reaches, read from the chain, so an allowance for one app is not a licence to sign in another's name. The app publishes its structure; the agent writes it exactly as published, the way it writes a function for call.

The agent's identity

A seller that rewards agents, like the maze, credits an ERC-8004 identity and gives its badge to the identity's owner. So the first time check_allowance runs for a wallet, the agent sets one up: it registers an identity from your wallet, which therefore owns it, and links its own key to it with its own signature. That is two operations, sponsored like its payments, and neither moves money. The allowance names exactly those two calls on the registry, and not the ones that would move an identity.

check_allowance then reports the identity's number, and the agent gives it wherever a seller asks for an agent id: for the maze, POST /game?agent=<id>. What the agent earns is written to that identity, and the badge goes to your wallet. Pair the agent with another wallet and it sets up another identity, owned by that one.

Configuration

| | | |---|---| | ARC_MANDATE_NETWORK | which network the agent spends on: arcTestnet (the default), monadTestnet, or arc for Arc mainnet, which is real money. On mainnet the owner's wallet is a separate one, made with its own passkey | | ARC_MANDATE_RPC_URL | an endpoint for whichever network is chosen, ahead of every other setting | | ARC_TESTNET_RPC_URL | the endpoint on Arc testnet, and the one that wins there: the connector reads ARC_TESTNET_RPC_URL ?? ARC_RPC_URL ?? Arc testnet. This is the name .env.example uses, so it is the one already set if you copied that file | | ARC_RPC_URL | the same knob under an older name, used only when ARC_TESTNET_RPC_URL is unset. Setting this one while the other is set changes nothing, silently | | ARC_SESSION_KEY_PLUGIN | the mandate plugin | | CIRCLE_CLIENT_URL, CIRCLE_CLIENT_KEY, CIRCLE_PASSKEY_DOMAIN | the bundler the agent submits through. Arc has no public bundler. Leave all three unset and the connector uses the shared key for its network, on Arc testnet and on Arc mainnet; set all three to use your own. Set only some and nothing is filled in: the missing ones are named | | ARC_MANDATE_KEY_PATH | where the agent's key lives (default ~/.arc-mandate/agent.key). The code image is saved beside it | | ARC_MANDATE_ACCOUNT_PATH | where the paired wallet is remembered. Move or delete this and the agent forgets which wallet it spends from, and pairing starts over | | ARC_ACCOUNT | a wallet to spend from without pairing, for a connector you point at your own wallet by hand. Still checked against the chain before every use | | ARC_CHAIN_ID | overrides the chain id the connector expects. Set only if you are pointing it at something other than Arc testnet |

Installing it

One line, with Node 22 or later:

claude mcp add arc-mandate -s user -- npx -y @kuiralabs/arc-mandate

On Arc testnet that is all: nothing to clone and nothing to configure. For Arc mainnet, add a second connector beside it, since each one spends on its own network only:

claude mcp add arc-mandate-mainnet -s user -- npx -y @kuiralabs/arc-mandate mainnet
``` Restart the client afterwards;
MCP servers are launched at startup. Without `-s user` this lands in local scope, which is bound to
the directory you ran it in and shadows every other scope.

The package is the compiled JavaScript of this folder, one file per source file, neither minified
nor bundled: it holds a key and signs payments, so what it does should be readable where it is
installed. Values of your own, such as a Circle key of your own, go in the client's config as an
`env` block.

### From a clone

For working on the connector itself:

```bash
cd /path/to/arc-agent-mandate
claude mcp add arc-mandate -s user -- node "$PWD/mcp/server.ts"
claude mcp list | grep arc-mandate      # confirm the path; $PWD is your shell's, not ours

This runs the TypeScript directly, which needs Node 22.18 or later. The server reads this project's .env if there is one; do not copy .env.example just to have one, since its values are placeholders and a placeholder endpoint is worse than none.

Publishing

From this folder, npm publish; the package says it is public, since npm makes a scoped package private unless told. Packing builds dist/ first (prepack), and package.test.ts builds it the same way and starts it as an MCP client would, so a change that would break the package fails the gate instead of a stranger's install.

Whose key pays the gas

Arc has no public bundler. Circle's is the only way to get a user operation on chain, and their Gas Station is what pays the gas so that neither you nor the agent has to. Somebody's account is behind that.

On testnet it is ours. With none of the three Circle values set, the connector uses the client key the wallet's web build already publishes at https://kuiralabs.github.io/mandate/, bound to that domain. Nothing about it is secret: it is a TEST key, which Circle refuses on mainnet, and it authorises nothing on its own, since a spend still needs a signature your allowance permits. What it shares is the gas, which on testnet is test money. That was chosen on 25 September, so that connecting an agent needs no Circle account. It reversed the earlier "bring your own key", at first for testnet only.

On Arc mainnet it is ours too, with a cap. Chosen on 14 October, for the same reason: so an owner's agent pays on mainnet with nothing to set up. The key is a LIVE one, and public all the same, since the wallet's page has to carry it to work. What it shares is the gas, which there is real money on our Circle account, so the Gas Station policy behind it limits each operation and each day. A shared key with no cap would be a bill rather than a design; with one, it is a convenience with a known ceiling. Set your own three values and the connector uses them instead.

That is the whole developer setup. Everything after it is scanning.

Using it

Ask the agent for its address. It prints a code, and saves it as an image beside its key.

Scan the code in the app, choose an amount and how long it lasts, confirm with Face ID.

That is the whole thing. From then on the agent spends inside the allowance without asking, and cannot spend outside it however it is asked — not because it is well behaved, but because the account refuses.

Why the agent holds nothing

An allowance is authority, not a balance. An agent that keeps even a fraction of a dollar has broken that promise, and a person who sees money trickle out to agents will not trust the mechanism however small the trickle is.

That rules out having the agent submit for itself. A user operation travels inside an ordinary transaction, and a transaction's sender must hold a balance before it can send one — so a self-submitting agent has to be funded first and is left with dust afterwards, which nothing ever returns. The account has no claim on it; only the agent's own key can send it back.

So the agent hands its operations to a bundler. On Arc there is exactly one — Circle's — and that is checked rather than assumed: Arc's public RPC answers eth_supportedEntryPoints with "method not supported", while Circle's returns EntryPoint v0.7.

The credential that needs is not a secret. It is the same client key the mobile app already ships inside its bundle, bound to a passkey domain, and it authorises nothing by itself: a spend still requires a session-key signature the mandate permits. Handing it to the agent gives away no authority, which is what makes this a better trade than leaving money behind.

The paymaster then covers the operation, so the account pays no gas either. That closes a second problem which is easy to miss: submitting directly, the account was billed for everything the agent did and the mandate never counted it, because the spend limit bounds call.value and gas is not call.value. An agent could send a trivial amount inside an expensive operation and cost the account far more than its allowance. Sponsored, there is nothing to bill.

The security shape is unchanged and simpler to state: a compromised agent gets whatever the mandate still allows, and nothing else — no float, no wallet.