metal-agent is the CLI and MCP server of the @metalntwx/metal-agent
npm package:
name value lines, one per line. A failure prints
error: <message> to stderr and exits 1; pull also exits 1 on a refusal,
3 on pending and 4 on held, and verify exits 1 on a failed check. Amounts are decimal
*USDZ; amounts under a mandate are in *USDZ, to the cent.
metal-agent --version prints the installed version alone on one line.
CLI
Every command that reads or writes the chain takes these flags:Keys and funds
key create
Generates the agent’s key and writes its key file. Refuses to overwrite one.
Prints
address and keyfile.
key import
Writes a key you already hold to the key file. Takes a hex key, with or without0x, a file holding one, or a key file.
address and keyfile, never the key. Like key create, it does
not overwrite a key file.
fund
Asks the testnet faucet to fund an address; the faucet sets the amount. The faucet limits every client behind one IP address together, so a refusal of this client names the Faucet page in the Metal console, which grants per signed-in user.funded, transaction and explorer, or reports an address that
already holds a full grant. An IP address gets ten grants a UTC day, one a
minute, out of the faucet’s 1,000 a day for every client; a
client_rate_limited error names the seconds to wait.
Paying
pay
Requests a URL. If it answers402 with an exact offer in *USDZ, signs
the payment with the key file, retries, and prints the settlement, the
response headers and the body.
Prints
nonce as soon as the payment is signed, then status, payer,
payee, amount, network, transaction and explorer, then one
header <name>: <value> line per response header, except those it hides
such as content-type, date and CORS headers. body comes last. Under a mandate, mandate <mandate id>
comes first. A refusal exits with one of three prefixes:
An offer with no
exact option on the network named fails before signing
with Failed to create payment payload: …; one on another network names it,
as the offer is on testnet (eip155:63825), not devnet (eip155:1337). These
errors, and a seller that cannot be reached, end nothing was signed.
If the answer to a signed payment is lost, or the seller fails or answers
409 after it, the error says a payment was signed and may have settled.
payment in doubt at settlement: <reason> means the same: any failed
settlement whose reason is not one that moves nothing, such as
settlement_in_doubt, already_settled or a seller’s own fetch failed
when the facilitator’s answer to it was lost. Before paying again, look for
it in the facilitator’s /settlements?payTo=<payee>, by an id ending in the
nonce line printed first; <payee> is the payTo of the seller’s 402
offer, in its base64 JSON payment-required header. A seller that answers
without a settlement prints payment unconfirmed; one that answers with a
settlement prints it, whatever its status. A paid request redirected to
another origin is not followed, so no other site receives the payment;
status and header location show where it points.
pay under a mandate
Pays from the owner’s account under a mandate, signed by the mandate key. The mandate’s limits apply in place of--max and --key, which the
command refuses.
Prints
mandate, then the same lines as pay, with payer the owner’s
account. When the seller’s 402 asks for a signed order, prints
signed-offer <text>. An offer the mandate cannot pay, such as one in
another token or not a whole number of cents, fails before signing with
Failed to create payment payload: …. A payment
outside the mandate exits with
payment refused: mandate_…; the reasons are
below.
status
Shows a settlement from chain data.transaction, status, block, submitter, gas and one
transfer <amount> from <address> to <address> line per transfer the
transaction carried.
Spending limits
account create
Creates the owner’s Metal account on chain, the one a mandate’s--principal is. The owner key pays the gas; one that holds too little is
funded first, from the faucet on testnet. An account that exists is named, and
nothing is sent.
Prints
account, owner and keyfile, funded when it funds the owner
key, then deployed and the deployment’s transaction, explorer and gas.
When a later step fails, such as a faucet refusal, the error says why and
names the key file, which is kept; the same command continues from it.
mandate credential create
Writes a credential stating a mandate’s limits, and beside it the mandate key the agent signs with.
Prints
credential and mandate, the terms (description,
principal, mandate-key, token, per-payment-cap, budget, starts, expiry,
max-occurrences when it is set, and payee per address, with its name
and website, or payees any), then mandate-key-file, issuer-key-file
and issuer-public-file.
mandate bind
Records the mandate on the owner’s account, signed by the account’s owners. The account must exist already;account create creates a
one-owner one.
Prints
bound, transaction, explorer and gas.
mandate replace
Binds a new credential in place of an old one. The spend carries into the new budget, and the old mandate is revoked in the same transaction. Issue the new credential with--mandate-key-file naming the old one’s key, in
the same directory, and the agent’s commands find that key.
--replaces names the credential being replaced; the other flags are
bind’s. Prints replaced, bound, spent, transaction, explorer
and gas.
mandate renew
Binds a new credential as a fresh mandate and revokes the one it replaces, in one transaction. The new budget starts with nothing spent; a budget never resets on its own.replace’s. Prints revoked, bound, spent,
transaction, explorer and gas.
mandate revoke
Revokes a mandate. Payments under it are refused from this transaction on.revoked, transaction, explorer and gas.
mandate status
Shows a mandate’s terms and where it stands on the owner’s account.mandate, the terms as credential create prints them,
account-type, bound, and when bound revoked, spent <amount> of <budget> and payments. After a replace, spent and payments include
what the mandates it replaced spent and paid; mandate verify lists this
credential’s payments only.
mandate pull
Pays an address the mandate allows, with no seller. The facilitator submits it and pays the gas, unless--key names a key to submit it.
Checks the payout against the mandate first. One the mandate allows prints
transaction, then outcome paid with spent <amount> of <budget>,
payer, payee, amount, explorer and gas. One it refuses prints
outcome refused, not sent with reason and recorded no, and exits 1,
spending no gas; with --record-refusal it prints outcome refused with
the transaction that recorded it. One that passes the check and is refused
when its transaction runs prints outcome refused the same way. One the
receive policy contract holds in escrow prints outcome held: the payee was
not paid, and the amount counts against the budget; the payee, or the
authority its policy names, releases it. paid exits 0, a refusal 1,
pending 3 and held 4. A payout the facilitator refuses without sending a
transaction fails with the facilitator’s reason; it is kept, and the same
command sends it again, which is refused again until what the reason names
changes. A run that sends a kept payout again and is refused, cannot reach
the facilitator, has a facilitator URL that is not http or https, or finds
the payout rejected by its check, fails saying
the pull kept before may have been sent and can land until a time: it can
still land until then.
The outcome is read from the chain. A sent payout with none yet prints
outcome pending, a reason, can-land-until, the time it can still land
until, and kept, the payment id it is kept under, or its amount and payee,
in ~/.metal-agent/pulls/<mandate>/. Running the same command again sends
that same payout, which pays at most once, or reports how it ended; runs at
once send the same one. Once its outcome is reported, the same command makes
a new payment; under a --payment-id, a payout that was paid or held is
reported again, and nothing is sent, or fails, sending nothing, when the
chain does not hold it, as after a chain reset. The report starts already paid, nothing sent, or already held, nothing sent, and its spent line
is the mandate’s current spend. One the account refused paid nothing: the
same --payment-id may be sent with other values. An outcome that cannot be
recorded prints kept …: the outcome was not recorded after it, and the
same command reports it again. A kept payout that can no longer land is
followed by a new one, signed and checked in the same run, and a payout this
run sent that can no longer land prints outcome refused, not paid with its
reason.
Without --payment-id, a payout is known by its --to and --amount: a
second payout of the same amount to the same payee, while the first has no
reported outcome, sends the first again. Pass a --payment-id for each
payment that can repeat, such as refunds; a retry passes it, or leaves it
out, as the first run did. A payout is kept under this home directory and
this credential: a retry from another machine, or under a credential that
renewed or replaced it, signs a new payout. An event under the mandate that
the SDK does not decode fails the command with its topic and the account:
the payout may have paid, so do not pay it under another payment id.
mandate verify
Checks the mandate and every payment under it from the chain alone, or one transaction. It reads the chain through an RPC endpoint and nothing else.
Prints
mandate, four step <n> pass|fail|skipped: <title> lines each
with a detail line, one line per payment or refusal found, and
verified yes|no. With --tx, step 4’s detail says whether a signed order
was checked, and names an order of another payment as such. The payments
listed are this credential’s only: after a replace, verify each credential.
A failed check exits 1. An event under the mandate that
the SDK does not decode, as an account of another version emits, fails the
verification with its topic and the account.
MCP
Servespay, fund and status as MCP tools over stdio, and under a
mandate mandate_pull, mandate_status and mandate_verify. What pay
and mandate_pull spend is set when the server starts, by whoever starts
it: a key file and a per-payment limit, or one mandate’s credential file,
which each call reads. The model calling the tools chooses no key, no
mandate and no higher limit. A server started with a mandate keeps that
credential file, so restart it with the new credential after
mandate replace or mandate renew. Under a mandate on testnet, set
METAL_RPC_URL in the server’s environment, such as an env block in the
host’s configuration.
Every tool takes
network, with no default; fund and status also
take rpcUrl. The mandate tools read the chain through the server’s
METAL_RPC_URL, or devnet’s default endpoint, take no rpcUrl, and fail
naming the endpoint’s chain when it is not the network’s. mandate_pull
submits pulls to the server’s METAL_FACILITATOR_URL, or the network’s
facilitator.
Each tool returns the lines its command prints, with the
next: lines
above. Keys are created and imported through the CLI only, and no tool
sets or changes a mandate.
What a mandate enforces
A mandate’s limits are checked by the owner’s account in the transaction that moves the money, so no client, seller or facilitator can settle a payment outside them.
A payment under a mandate can also be refused for these:
Pay under spending limits
The mandate commands in the order the owner and the agent run them.
Facilitator
Endpoints, limits, refusal reasons and retries.