@metalntwx/metal-agent 0.1.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +78 -52
- package/dist/chain.js +1 -1
- package/dist/commands/mandate.js +564 -131
- package/dist/commands/pay.js +16 -7
- package/dist/help.js +119 -0
- package/dist/index.js +52 -96
- package/dist/keystore.js +6 -10
- package/dist/mcp.js +59 -39
- package/package.json +7 -7
- package/skills/metal-agent/SKILL.md +53 -27
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# @metalntwx/metal-agent
|
|
2
2
|
|
|
3
3
|
The `metal-agent` CLI an agent pays x402 payees on Metal with: create or
|
|
4
|
-
import a key, fund it, pay a URL, look up a settlement, or serve
|
|
5
|
-
|
|
4
|
+
import a key, fund it, pay a URL, look up a settlement, or serve `pay`,
|
|
5
|
+
`fund` and `status` as MCP tools.
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
8
|
npm install -g @metalntwx/metal-agent
|
|
@@ -25,10 +25,12 @@ endpoints) and the `*USDZ` address come from `@metalntwx/metal-pay`, and
|
|
|
25
25
|
`networks.json` holds its demo payee URL and, on testnet, its faucet and
|
|
26
26
|
explorer URLs.
|
|
27
27
|
`METAL_RPC_URL` and `METAL_FAUCET_URL` override those defaults, and
|
|
28
|
-
`--rpc-url` and `--faucet-url` override both
|
|
28
|
+
`--rpc-url` and `--faucet-url` override both; `METAL_FACILITATOR_URL` and
|
|
29
|
+
`--facilitator-url` do the same for `mandate pull`.
|
|
29
30
|
|
|
30
|
-
On testnet, `pay` and `fund` need no RPC. `status
|
|
31
|
-
|
|
31
|
+
On testnet, `pay` and `fund` need no RPC. `status`, `account create` and
|
|
32
|
+
every `mandate` command but `credential create` need an endpoint of your
|
|
33
|
+
own: sign up in the Metal console,
|
|
32
34
|
https://console-testnet.metalntwx.com, create an endpoint, and pass it with
|
|
33
35
|
`--rpc-url` or `METAL_RPC_URL`. On testnet, `pay` and `fund` print
|
|
34
36
|
an `explorer` line with the transaction's page on
|
|
@@ -48,7 +50,8 @@ which the devnet funds at genesis.
|
|
|
48
50
|
|
|
49
51
|
`pay` refuses, before it signs, an offer in any token but `*USDZ` and any
|
|
50
52
|
offer above `--max`, 1 `*USDZ` per payment by default. A refusal at
|
|
51
|
-
verification
|
|
53
|
+
verification, by the facilitator or the seller's own check, prints
|
|
54
|
+
`payment refused: <reason>`.
|
|
52
55
|
A refusal at settlement prints `payment refused at settlement: <reason>`,
|
|
53
56
|
with the transaction hash when there is one.
|
|
54
57
|
|
|
@@ -77,68 +80,83 @@ for the mandate's whole life, an expiry and, when `--payee` names any, the
|
|
|
77
80
|
addresses it may pay; with none named it may pay any address.
|
|
78
81
|
|
|
79
82
|
```bash
|
|
80
|
-
metal-agent mandate credential create --principal <MetalAccount address> [--payee <address>]... \
|
|
81
|
-
--cap 5 --budget 10 --out ./mandate.json --network testnet
|
|
83
|
+
metal-agent mandate credential create --principal <MetalAccount address> [--payee <address>[,<name>[,<website>]]]... \
|
|
84
|
+
--cap 5 --budget 10 [--description <text>] --key <owner key file> --out ./mandate.json --network testnet
|
|
82
85
|
metal-agent mandate bind --credential ./mandate.json --key <owner key file> --network testnet --rpc-url <endpoint>
|
|
83
86
|
metal-agent pay <url> --mandate ./mandate.json --network testnet
|
|
84
87
|
metal-agent mandate status --credential ./mandate.json --network testnet --rpc-url <endpoint>
|
|
88
|
+
metal-agent mandate pull --credential ./mandate.json --to <address> --amount 2.50 --network testnet --rpc-url <endpoint>
|
|
89
|
+
metal-agent mandate verify --credential ./mandate.json --issuer <issuer public key file> --network testnet --rpc-url <endpoint>
|
|
90
|
+
metal-agent mandate verify --credential ./mandate.json --issuer <issuer public key file> --tx <hash> --signed-offer <text> --network testnet --rpc-url <endpoint>
|
|
85
91
|
metal-agent mandate replace --credential ./wider.json --replaces ./mandate.json --key <owner key file> --network testnet --rpc-url <endpoint>
|
|
92
|
+
metal-agent mandate renew --credential ./next.json --replaces ./mandate.json --key <owner key file> --network testnet --rpc-url <endpoint>
|
|
86
93
|
metal-agent mandate revoke --credential ./wider.json --key <owner key file> --network testnet --rpc-url <endpoint>
|
|
87
|
-
metal-agent mandate pull --credential ./mandate.json --to <address> --amount 2.50 --network testnet --rpc-url <endpoint>
|
|
88
|
-
metal-agent mandate verify --credential ./mandate.json --network testnet --rpc-url <endpoint>
|
|
89
|
-
metal-agent mandate verify --credential ./mandate.json --tx <hash> --signed-offer <text> --network testnet --rpc-url <endpoint>
|
|
90
94
|
```
|
|
91
95
|
|
|
92
96
|
`credential create` writes the limits into a credential file, a Verifiable
|
|
93
|
-
Intent mandate, and
|
|
94
|
-
`mandate.
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
97
|
+
Intent mandate, and beside it the mandate key the agent signs with:
|
|
98
|
+
`mandate.key.json` for `mandate.json`. `--mandate-key-file <file>` names a
|
|
99
|
+
mandate key that exists instead, as a replacement does. The keys
|
|
100
|
+
that sign the credential are the owner's: they live beside the owner's key file, created
|
|
101
|
+
on first use (`owner-key.issuer.json`, with its public half in
|
|
102
|
+
`owner-key.issuer.public.json`), unless `--issuer-key` names their file.
|
|
103
|
+
`--description` says what the mandate is for, and `--payee` takes a name
|
|
104
|
+
and a website after the address. Every other command takes
|
|
105
|
+
that credential file: the owner's account, the mandate's id and its
|
|
106
|
+
terms derive from it. Amounts are in `*USDZ`, to the cent; a
|
|
107
|
+
mandate lasts 30 days unless `--expires-in` says otherwise, and its budget
|
|
108
|
+
never resets.
|
|
109
|
+
|
|
110
|
+
`account create --out <owner key file>` writes the owner's key and creates the
|
|
111
|
+
owner's Metal account on chain, a smart account that key controls with
|
|
112
|
+
threshold one; `--key <file>` uses a key that exists. The owner's key pays the
|
|
113
|
+
gas, and one that holds none is funded first. Fund the account and name it as
|
|
114
|
+
the credential's `--principal`; `bind` refuses an address with no account.
|
|
107
115
|
The account factory is the one `@metalntwx/metal-pay` names for the network;
|
|
108
116
|
on a devnet `just deploy` deploys it there, and `--factory <address>` names
|
|
109
|
-
another.
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
117
|
+
another. `@metalntwx/metal-pay` names the testnet's.
|
|
118
|
+
|
|
119
|
+
`bind`, `replace` and `revoke` are signed by the account's owners, as many as
|
|
120
|
+
its threshold needs. `--key` names the gas sponsor and defaults as the sole
|
|
121
|
+
signer; repeat `--signer-key <file>` to supply the owners' keys of an account
|
|
122
|
+
with several instead. The SDK's `authorizeMandateCall` accepts a signing
|
|
123
|
+
callback for mixed keys and passkeys. Keep owner keys outside the agent's
|
|
124
|
+
reach: an owner key moves the account's funds outside any mandate's limits. `replace` revokes the mandate
|
|
125
|
+
`--replaces` names and carries its spend into the new one; `renew` binds the
|
|
126
|
+
new one and revokes the old in one batch, starting its spend at nothing, which
|
|
127
|
+
is how a budget starts over.
|
|
128
|
+
`bind`, `replace` and `renew` trust the issuer beside `--key` unless
|
|
129
|
+
`--issuer` names others.
|
|
119
130
|
|
|
120
131
|
Verification trusts that factory, never one a payer names: it checks the
|
|
121
|
-
account's immutable clone code against the factory's
|
|
122
|
-
|
|
132
|
+
account's immutable clone code against the factory's current
|
|
133
|
+
implementation, and checks successful token-transfer receipts.
|
|
123
134
|
|
|
124
135
|
`pay --mandate <credential>` pays from the owner's account, signed by the
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
the
|
|
136
|
+
mandate key beside the credential, or the one `--mandate-key-file` names.
|
|
137
|
+
It takes neither `--key` nor `--max`. A payment
|
|
138
|
+
outside the mandate is refused and moves nothing, usually with a
|
|
139
|
+
`mandate_*` reason: `payment refused: mandate_cap_exceeded (…)`. `pay` prints the
|
|
128
140
|
payment's `nonce` as soon as it signs; the facilitator's
|
|
129
141
|
`/settlements?payTo=<payee>` lists a settled payment by an id ending in it.
|
|
130
142
|
|
|
131
143
|
`mandate pull` pays an address the mandate allows directly, with no seller:
|
|
132
|
-
the agent's mandate key signs, and the
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
144
|
+
the agent's mandate key signs, and the facilitator submits the transaction
|
|
145
|
+
and pays its gas; `--key` names a funded key to submit it instead. A pull
|
|
146
|
+
the mandate refuses when checked is not sent: it reports the reason and
|
|
147
|
+
exits 1. With `--key`, `--record-refusal` sends it, so the refusal is
|
|
148
|
+
recorded on chain in a transaction that succeeds and moves nothing. The
|
|
149
|
+
outcome is read from the chain: `paid`, `refused`, `held` when the payee's
|
|
150
|
+
receive policy sends it to escrow, or `pending`. A pending pull is kept in
|
|
151
|
+
the directory beside the credential, `mandate.pulls` for `mandate.json`, and
|
|
152
|
+
running the same command again sends that same pull, which pays at most once.
|
|
136
153
|
|
|
137
154
|
`mandate verify` checks the mandate and every payment under it from the
|
|
138
155
|
chain alone, through any RPC endpoint: the binding on the owner's account,
|
|
139
156
|
the credential against it and under its issuer, and each payment and
|
|
140
|
-
refusal as the
|
|
141
|
-
transaction. Anyone holding the credential
|
|
157
|
+
refusal as the mandate key's own signed per-payment mandate, rebuilt from its
|
|
158
|
+
transaction. Anyone holding the credential and the issuer's public file,
|
|
159
|
+
passed as `--issuer`, can run it; `--tx <hash>`
|
|
142
160
|
verifies one transaction, with `--signed-offer <text>` the offer the agent
|
|
143
161
|
signed for it, and a check that fails exits 1. A seller that asks for the
|
|
144
162
|
agent's signed order (`requireSignedOffer` in `@metalntwx/metal-pay/mandates`,
|
|
@@ -149,17 +167,25 @@ the same, the latter for a payee that holds no credential.
|
|
|
149
167
|
`skills/metal-agent/SKILL.md` is an installable skill: the setup steps for
|
|
150
168
|
an agent, then the commands.
|
|
151
169
|
|
|
152
|
-
`metal-agent mcp` serves `pay`, `fund` and `status` over stdio,
|
|
153
|
-
|
|
154
|
-
`
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
no
|
|
170
|
+
`metal-agent mcp` serves `pay`, `fund` and `status` over stdio, each taking
|
|
171
|
+
a `network`, and `pay` the request as optional `method`, `body` and
|
|
172
|
+
`headers`. What `pay` spends is fixed when the server starts: its key and
|
|
173
|
+
`--max` (the default key file and 1 `*USDZ` when omitted), or one mandate
|
|
174
|
+
with `--mandate <credential>`. The model calling the tools chooses no key,
|
|
175
|
+
no mandate and no higher limit; under a key, a `max` it passes can only
|
|
176
|
+
lower the server's. The mandate commands enter only through the CLI. An MCP host
|
|
177
|
+
starts it with:
|
|
158
178
|
|
|
159
179
|
```json
|
|
160
180
|
{ "command": "npx", "args": ["-y", "@metalntwx/metal-agent", "mcp"] }
|
|
161
181
|
```
|
|
162
182
|
|
|
183
|
+
or, under one mandate:
|
|
184
|
+
|
|
185
|
+
```json
|
|
186
|
+
{ "command": "npx", "args": ["-y", "@metalntwx/metal-agent", "mcp", "--mandate", "/path/to/mandate.json"] }
|
|
187
|
+
```
|
|
188
|
+
|
|
163
189
|
## Develop
|
|
164
190
|
|
|
165
191
|
In the `mx56db/metal` repository, `just agent <command>` runs the CLI from
|
package/dist/chain.js
CHANGED
|
@@ -22,7 +22,7 @@ export function resolveSettings(network, file, flags, env) {
|
|
|
22
22
|
const faucetUrl = flags.faucetUrl ?? env["METAL_FAUCET_URL"] ?? file.faucetUrl;
|
|
23
23
|
return {
|
|
24
24
|
...(rpcUrl !== undefined && { rpcUrl }),
|
|
25
|
-
facilitatorUrl: env["METAL_FACILITATOR_URL"] ?? network.facilitatorUrl,
|
|
25
|
+
facilitatorUrl: flags.facilitatorUrl ?? env["METAL_FACILITATOR_URL"] ?? network.facilitatorUrl,
|
|
26
26
|
demoPayeeUrl: env["METAL_DEMO_PAYEE_URL"] ?? file.demoPayeeUrl,
|
|
27
27
|
...(faucetUrl !== undefined && { faucetUrl }),
|
|
28
28
|
...(file.explorerUrl !== undefined && { explorerUrl: file.explorerUrl }),
|