@absol-labs/agent 0.11.0 → 0.12.0-rc.2
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 +43 -51
- package/dist/capability/invocation-capability.d.ts +2 -2
- package/dist/capability/probe-capability.d.ts +225 -0
- package/dist/capability/probe-capability.d.ts.map +1 -0
- package/dist/capability/probe-capability.js +248 -0
- package/dist/capability/probe-capability.js.map +1 -0
- package/dist/cli/args.d.ts +11 -0
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +23 -0
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/commands/claim.d.ts +32 -0
- package/dist/cli/commands/claim.d.ts.map +1 -1
- package/dist/cli/commands/claim.js +116 -28
- package/dist/cli/commands/claim.js.map +1 -1
- package/dist/cli/commands/close.d.ts.map +1 -1
- package/dist/cli/commands/close.js +35 -3
- package/dist/cli/commands/close.js.map +1 -1
- package/dist/cli/commands/invoke.d.ts +2 -0
- package/dist/cli/commands/invoke.d.ts.map +1 -1
- package/dist/cli/commands/invoke.js +2 -2
- package/dist/cli/commands/invoke.js.map +1 -1
- package/dist/cli/commands/mcp.d.ts.map +1 -1
- package/dist/cli/commands/mcp.js +15 -2
- package/dist/cli/commands/mcp.js.map +1 -1
- package/dist/cli/commands/open.d.ts.map +1 -1
- package/dist/cli/commands/open.js +58 -4
- package/dist/cli/commands/open.js.map +1 -1
- package/dist/cli/commands/pay.d.ts +34 -0
- package/dist/cli/commands/pay.d.ts.map +1 -0
- package/dist/cli/commands/pay.js +479 -0
- package/dist/cli/commands/pay.js.map +1 -0
- package/dist/cli/commands/reclaim-unverified.d.ts.map +1 -1
- package/dist/cli/commands/reclaim-unverified.js +3 -2
- package/dist/cli/commands/reclaim-unverified.js.map +1 -1
- package/dist/cli/commands/reclaim.d.ts +23 -2
- package/dist/cli/commands/reclaim.d.ts.map +1 -1
- package/dist/cli/commands/reclaim.js +204 -20
- package/dist/cli/commands/reclaim.js.map +1 -1
- package/dist/cli/commands/refund.d.ts +31 -0
- package/dist/cli/commands/refund.d.ts.map +1 -0
- package/dist/cli/commands/refund.js +397 -0
- package/dist/cli/commands/refund.js.map +1 -0
- package/dist/cli/commands/status.d.ts.map +1 -1
- package/dist/cli/commands/status.js +21 -2
- package/dist/cli/commands/status.js.map +1 -1
- package/dist/cli/commands/streams.js +8 -12
- package/dist/cli/commands/streams.js.map +1 -1
- package/dist/cli/commands/watch.d.ts.map +1 -1
- package/dist/cli/commands/watch.js +4 -1
- package/dist/cli/commands/watch.js.map +1 -1
- package/dist/cli/config.d.ts +44 -26
- package/dist/cli/config.d.ts.map +1 -1
- package/dist/cli/config.js +88 -38
- package/dist/cli/config.js.map +1 -1
- package/dist/cli/context.d.ts +19 -0
- package/dist/cli/context.d.ts.map +1 -1
- package/dist/cli/deps.d.ts +53 -0
- package/dist/cli/deps.d.ts.map +1 -1
- package/dist/cli/deps.js +51 -1
- package/dist/cli/deps.js.map +1 -1
- package/dist/cli/errors.d.ts.map +1 -1
- package/dist/cli/errors.js +21 -1
- package/dist/cli/errors.js.map +1 -1
- package/dist/cli/escape-hatch.d.ts +6 -4
- package/dist/cli/escape-hatch.d.ts.map +1 -1
- package/dist/cli/escape-hatch.js +6 -2
- package/dist/cli/escape-hatch.js.map +1 -1
- package/dist/cli/listings.d.ts +18 -0
- package/dist/cli/listings.d.ts.map +1 -1
- package/dist/cli/listings.js +22 -12
- package/dist/cli/listings.js.map +1 -1
- package/dist/cli/preflight.d.ts +20 -14
- package/dist/cli/preflight.d.ts.map +1 -1
- package/dist/cli/preflight.js +34 -20
- package/dist/cli/preflight.js.map +1 -1
- package/dist/cli/privy.d.ts.map +1 -1
- package/dist/cli/privy.js +4 -2
- package/dist/cli/privy.js.map +1 -1
- package/dist/cli/progress.d.ts +13 -1
- package/dist/cli/progress.d.ts.map +1 -1
- package/dist/cli/progress.js +7 -1
- package/dist/cli/progress.js.map +1 -1
- package/dist/cli/reclaimability.d.ts +82 -0
- package/dist/cli/reclaimability.d.ts.map +1 -0
- package/dist/cli/reclaimability.js +80 -0
- package/dist/cli/reclaimability.js.map +1 -0
- package/dist/cli/run.d.ts +2 -1
- package/dist/cli/run.d.ts.map +1 -1
- package/dist/cli/run.js +62 -21
- package/dist/cli/run.js.map +1 -1
- package/dist/cli/scan-range.d.ts +2 -2
- package/dist/cli/scan-range.js +2 -2
- package/dist/cli/status.d.ts +106 -1
- package/dist/cli/status.d.ts.map +1 -1
- package/dist/cli/status.js +325 -44
- package/dist/cli/status.js.map +1 -1
- package/dist/cli/wallet.d.ts +2 -2
- package/dist/cli/wallet.d.ts.map +1 -1
- package/dist/cli/wallet.js +29 -5
- package/dist/cli/wallet.js.map +1 -1
- package/dist/discovery/rate-band.d.ts +74 -0
- package/dist/discovery/rate-band.d.ts.map +1 -0
- package/dist/discovery/rate-band.js +114 -0
- package/dist/discovery/rate-band.js.map +1 -0
- package/dist/escrow-generation.d.ts +81 -0
- package/dist/escrow-generation.d.ts.map +1 -0
- package/dist/escrow-generation.js +76 -0
- package/dist/escrow-generation.js.map +1 -0
- package/dist/frameworks/agentkit.d.ts +8 -4
- package/dist/frameworks/agentkit.d.ts.map +1 -1
- package/dist/frameworks/agentkit.js +27 -10
- package/dist/frameworks/agentkit.js.map +1 -1
- package/dist/frameworks/eliza.d.ts +3 -3
- package/dist/frameworks/eliza.d.ts.map +1 -1
- package/dist/frameworks/eliza.js +15 -6
- package/dist/frameworks/eliza.js.map +1 -1
- package/dist/frameworks/langchain.d.ts.map +1 -1
- package/dist/frameworks/langchain.js +8 -0
- package/dist/frameworks/langchain.js.map +1 -1
- package/dist/gateway/caller-auth-gateway.d.ts +83 -31
- package/dist/gateway/caller-auth-gateway.d.ts.map +1 -1
- package/dist/gateway/caller-auth-gateway.js +180 -27
- package/dist/gateway/caller-auth-gateway.js.map +1 -1
- package/dist/gateway/http-server.d.ts +128 -44
- package/dist/gateway/http-server.d.ts.map +1 -1
- package/dist/gateway/http-server.js +506 -155
- package/dist/gateway/http-server.js.map +1 -1
- package/dist/gateway/probe-auth.d.ts +112 -0
- package/dist/gateway/probe-auth.d.ts.map +1 -0
- package/dist/gateway/probe-auth.js +127 -0
- package/dist/gateway/probe-auth.js.map +1 -0
- package/dist/gateway/signed-rate-bounds.d.ts +51 -0
- package/dist/gateway/signed-rate-bounds.d.ts.map +1 -0
- package/dist/gateway/signed-rate-bounds.js +94 -0
- package/dist/gateway/signed-rate-bounds.js.map +1 -0
- package/dist/gateway/x402-opener-mode.d.ts +252 -0
- package/dist/gateway/x402-opener-mode.d.ts.map +1 -0
- package/dist/gateway/x402-opener-mode.js +772 -0
- package/dist/gateway/x402-opener-mode.js.map +1 -0
- package/dist/index.d.ts +6 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -5
- package/dist/index.js.map +1 -1
- package/dist/mcp/server.d.ts +64 -1
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +247 -37
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/x402-tools.d.ts +109 -0
- package/dist/mcp/x402-tools.d.ts.map +1 -0
- package/dist/mcp/x402-tools.js +554 -0
- package/dist/mcp/x402-tools.js.map +1 -0
- package/dist/network.d.ts +119 -0
- package/dist/network.d.ts.map +1 -0
- package/dist/network.js +199 -0
- package/dist/network.js.map +1 -0
- package/dist/sdk/client.d.ts +13 -0
- package/dist/sdk/client.d.ts.map +1 -1
- package/dist/sdk/client.js +15 -0
- package/dist/sdk/client.js.map +1 -1
- package/dist/sdk/invoke.d.ts +4 -0
- package/dist/sdk/invoke.d.ts.map +1 -1
- package/dist/sdk/invoke.js +2 -5
- package/dist/sdk/invoke.js.map +1 -1
- package/dist/wallet/autonomous-wallet-broker.d.ts +3 -0
- package/dist/wallet/autonomous-wallet-broker.d.ts.map +1 -1
- package/dist/wallet/autonomous-wallet-broker.js +27 -23
- package/dist/wallet/autonomous-wallet-broker.js.map +1 -1
- package/dist/wallet/autonomous-wallet-protocol.d.ts +8 -2
- package/dist/wallet/autonomous-wallet-protocol.d.ts.map +1 -1
- package/dist/wallet/autonomous-wallet-protocol.js +9 -2
- package/dist/wallet/autonomous-wallet-protocol.js.map +1 -1
- package/dist/wallet/autonomous-wallet.d.ts.map +1 -1
- package/dist/wallet/autonomous-wallet.js +3 -2
- package/dist/wallet/autonomous-wallet.js.map +1 -1
- package/dist/wallet/broker-escrows.d.ts +222 -0
- package/dist/wallet/broker-escrows.d.ts.map +1 -0
- package/dist/wallet/broker-escrows.js +36 -0
- package/dist/wallet/broker-escrows.js.map +1 -0
- package/dist/wallet/lifecycle.d.ts +21 -5
- package/dist/wallet/lifecycle.d.ts.map +1 -1
- package/dist/wallet/lifecycle.js +32 -12
- package/dist/wallet/lifecycle.js.map +1 -1
- package/dist/wallet/privy-broker-server.d.ts.map +1 -1
- package/dist/wallet/privy-broker-server.js +31 -26
- package/dist/wallet/privy-broker-server.js.map +1 -1
- package/dist/wallet/privy-session-broker.d.ts +14 -5
- package/dist/wallet/privy-session-broker.d.ts.map +1 -1
- package/dist/wallet/privy-session-broker.js +29 -17
- package/dist/wallet/privy-session-broker.js.map +1 -1
- package/dist/wallet/privy-session-provider.d.ts +3 -1
- package/dist/wallet/privy-session-provider.d.ts.map +1 -1
- package/dist/wallet/privy-session-provider.js +5 -3
- package/dist/wallet/privy-session-provider.js.map +1 -1
- package/dist/wallet/provider.d.ts.map +1 -1
- package/dist/wallet/provider.js +15 -8
- package/dist/wallet/provider.js.map +1 -1
- package/dist/x402/facilitator.d.ts +3 -30
- package/dist/x402/facilitator.d.ts.map +1 -1
- package/dist/x402/facilitator.js +0 -27
- package/dist/x402/facilitator.js.map +1 -1
- package/dist/x402-buyer/guarded-fetch.d.ts +51 -0
- package/dist/x402-buyer/guarded-fetch.d.ts.map +1 -0
- package/dist/x402-buyer/guarded-fetch.js +147 -0
- package/dist/x402-buyer/guarded-fetch.js.map +1 -0
- package/dist/x402-buyer/index.d.ts +10 -0
- package/dist/x402-buyer/index.d.ts.map +1 -0
- package/dist/x402-buyer/index.js +10 -0
- package/dist/x402-buyer/index.js.map +1 -0
- package/dist/x402-buyer/pay.d.ts +157 -0
- package/dist/x402-buyer/pay.d.ts.map +1 -0
- package/dist/x402-buyer/pay.js +366 -0
- package/dist/x402-buyer/pay.js.map +1 -0
- package/dist/x402-opener/abi.d.ts +684 -0
- package/dist/x402-opener/abi.d.ts.map +1 -0
- package/dist/x402-opener/abi.js +880 -0
- package/dist/x402-opener/abi.js.map +1 -0
- package/dist/x402-opener/facilitator-entry.d.ts +3 -0
- package/dist/x402-opener/facilitator-entry.d.ts.map +1 -0
- package/dist/x402-opener/facilitator-entry.js +40 -0
- package/dist/x402-opener/facilitator-entry.js.map +1 -0
- package/dist/x402-opener/facilitator-server.d.ts +209 -0
- package/dist/x402-opener/facilitator-server.d.ts.map +1 -0
- package/dist/x402-opener/facilitator-server.js +762 -0
- package/dist/x402-opener/facilitator-server.js.map +1 -0
- package/dist/x402-opener/index.d.ts +14 -0
- package/dist/x402-opener/index.d.ts.map +1 -0
- package/dist/x402-opener/index.js +14 -0
- package/dist/x402-opener/index.js.map +1 -0
- package/dist/x402-opener/listing-resolver.d.ts +55 -0
- package/dist/x402-opener/listing-resolver.d.ts.map +1 -0
- package/dist/x402-opener/listing-resolver.js +125 -0
- package/dist/x402-opener/listing-resolver.js.map +1 -0
- package/dist/x402-opener/refund-relay.d.ts +266 -0
- package/dist/x402-opener/refund-relay.d.ts.map +1 -0
- package/dist/x402-opener/refund-relay.js +1341 -0
- package/dist/x402-opener/refund-relay.js.map +1 -0
- package/dist/x402-opener/relayer-queue.d.ts +14 -0
- package/dist/x402-opener/relayer-queue.d.ts.map +1 -0
- package/dist/x402-opener/relayer-queue.js +18 -0
- package/dist/x402-opener/relayer-queue.js.map +1 -0
- package/dist/x402-opener/scheme.d.ts +296 -0
- package/dist/x402-opener/scheme.d.ts.map +1 -0
- package/dist/x402-opener/scheme.js +1035 -0
- package/dist/x402-opener/scheme.js.map +1 -0
- package/dist/x402-opener/terms.d.ts +165 -0
- package/dist/x402-opener/terms.d.ts.map +1 -0
- package/dist/x402-opener/terms.js +304 -0
- package/dist/x402-opener/terms.js.map +1 -0
- package/dist/zktls/reclaim.d.ts +2 -2
- package/dist/zktls/t2-delivery-proof.d.ts +6 -6
- package/docs/agent-layer.md +3 -2
- package/docs/autonomous-privy-wallet.md +188 -0
- package/docs/cli.md +265 -34
- package/docs/langchain.md +5 -0
- package/docs/mcp-reference.md +91 -5
- package/docs/probe-capability.md +191 -0
- package/docs/quickstart.md +39 -4
- package/docs/threat-model.md +20 -0
- package/docs/x402.md +766 -593
- package/package.json +19 -17
- package/src/capability/probe-capability.ts +355 -0
- package/src/cli/args.ts +29 -0
- package/src/cli/commands/claim.ts +168 -33
- package/src/cli/commands/close.ts +36 -3
- package/src/cli/commands/invoke.ts +5 -2
- package/src/cli/commands/mcp.ts +18 -2
- package/src/cli/commands/open.ts +75 -4
- package/src/cli/commands/pay.ts +584 -0
- package/src/cli/commands/reclaim-unverified.ts +8 -2
- package/src/cli/commands/reclaim.ts +277 -19
- package/src/cli/commands/refund.ts +475 -0
- package/src/cli/commands/status.ts +28 -2
- package/src/cli/commands/streams.ts +10 -14
- package/src/cli/commands/watch.ts +11 -1
- package/src/cli/config.ts +139 -52
- package/src/cli/context.ts +16 -0
- package/src/cli/deps.ts +111 -1
- package/src/cli/errors.ts +24 -1
- package/src/cli/escape-hatch.ts +19 -5
- package/src/cli/listings.ts +43 -10
- package/src/cli/preflight.ts +36 -21
- package/src/cli/privy.ts +4 -2
- package/src/cli/progress.ts +21 -2
- package/src/cli/reclaimability.ts +154 -0
- package/src/cli/run.ts +77 -21
- package/src/cli/scan-range.ts +2 -2
- package/src/cli/status.ts +454 -44
- package/src/cli/wallet.ts +45 -8
- package/src/discovery/rate-band.ts +172 -0
- package/src/escrow-generation.ts +135 -0
- package/src/frameworks/agentkit.ts +45 -11
- package/src/frameworks/eliza.ts +27 -5
- package/src/frameworks/langchain.ts +14 -0
- package/src/gateway/caller-auth-gateway.ts +320 -68
- package/src/gateway/http-server.ts +717 -220
- package/src/gateway/probe-auth.ts +191 -0
- package/src/gateway/signed-rate-bounds.ts +164 -0
- package/src/gateway/x402-opener-mode.ts +1171 -0
- package/src/index.ts +82 -19
- package/src/mcp/server.ts +351 -42
- package/src/mcp/x402-tools.ts +785 -0
- package/src/network.ts +302 -0
- package/src/sdk/client.ts +25 -0
- package/src/sdk/invoke.ts +6 -5
- package/src/wallet/autonomous-wallet-broker.ts +46 -20
- package/src/wallet/autonomous-wallet-protocol.ts +11 -2
- package/src/wallet/autonomous-wallet.ts +4 -2
- package/src/wallet/broker-escrows.ts +66 -0
- package/src/wallet/lifecycle.ts +53 -18
- package/src/wallet/privy-broker-server.ts +35 -25
- package/src/wallet/privy-session-broker.ts +58 -25
- package/src/wallet/privy-session-provider.ts +7 -3
- package/src/wallet/provider.ts +20 -15
- package/src/x402/facilitator.ts +3 -43
- package/src/x402-buyer/guarded-fetch.ts +213 -0
- package/src/x402-buyer/index.ts +9 -0
- package/src/x402-buyer/pay.ts +555 -0
- package/src/x402-opener/abi.ts +881 -0
- package/src/x402-opener/facilitator-entry.ts +48 -0
- package/src/x402-opener/facilitator-server.ts +1114 -0
- package/src/x402-opener/index.ts +13 -0
- package/src/x402-opener/listing-resolver.ts +202 -0
- package/src/x402-opener/refund-relay.ts +1873 -0
- package/src/x402-opener/relayer-queue.ts +18 -0
- package/src/x402-opener/scheme.ts +1551 -0
- package/src/x402-opener/terms.ts +453 -0
- package/dist/gateway/x402-mode.d.ts +0 -123
- package/dist/gateway/x402-mode.d.ts.map +0 -1
- package/dist/gateway/x402-mode.js +0 -212
- package/dist/gateway/x402-mode.js.map +0 -1
- package/dist/x402/delivery-oracle.d.ts +0 -108
- package/dist/x402/delivery-oracle.d.ts.map +0 -1
- package/dist/x402/delivery-oracle.js +0 -174
- package/dist/x402/delivery-oracle.js.map +0 -1
- package/dist/x402/facilitator-server-entry.d.ts +0 -3
- package/dist/x402/facilitator-server-entry.d.ts.map +0 -1
- package/dist/x402/facilitator-server-entry.js +0 -27
- package/dist/x402/facilitator-server-entry.js.map +0 -1
- package/dist/x402/facilitator-server.d.ts +0 -73
- package/dist/x402/facilitator-server.d.ts.map +0 -1
- package/dist/x402/facilitator-server.js +0 -305
- package/dist/x402/facilitator-server.js.map +0 -1
- package/dist/x402/payment-requirements.d.ts +0 -106
- package/dist/x402/payment-requirements.d.ts.map +0 -1
- package/dist/x402/payment-requirements.js +0 -138
- package/dist/x402/payment-requirements.js.map +0 -1
- package/dist/x402/relayer.d.ts +0 -41
- package/dist/x402/relayer.d.ts.map +0 -1
- package/dist/x402/relayer.js +0 -64
- package/dist/x402/relayer.js.map +0 -1
- package/dist/x402/resource-index.d.ts +0 -75
- package/dist/x402/resource-index.d.ts.map +0 -1
- package/dist/x402/resource-index.js +0 -126
- package/dist/x402/resource-index.js.map +0 -1
- package/dist/x402/verified-delivery-facilitator.d.ts +0 -285
- package/dist/x402/verified-delivery-facilitator.d.ts.map +0 -1
- package/dist/x402/verified-delivery-facilitator.js +0 -423
- package/dist/x402/verified-delivery-facilitator.js.map +0 -1
- package/src/gateway/x402-mode.ts +0 -350
- package/src/x402/delivery-oracle.ts +0 -285
- package/src/x402/facilitator-server-entry.ts +0 -35
- package/src/x402/facilitator-server.ts +0 -445
- package/src/x402/payment-requirements.ts +0 -231
- package/src/x402/relayer.ts +0 -122
- package/src/x402/resource-index.ts +0 -228
- package/src/x402/verified-delivery-facilitator.ts +0 -744
package/docs/mcp-reference.md
CHANGED
|
@@ -36,11 +36,16 @@ an economic question, not a cryptographic one.
|
|
|
36
36
|
| `reclaim_unspent` | **yes** | Closes the stream (**stops the meter**) and returns unspent USDC to the buyer. |
|
|
37
37
|
| `list_streams` | no | Lists the streams this server opened. |
|
|
38
38
|
| `prove_https_response` | no | Buyer-side zkTLS proof of a response. Registered only with Reclaim credentials. |
|
|
39
|
+
| `x402_pay` | **yes** | Pays an x402 endpoint (opener path, docs/x402.md) under the spend mandate. |
|
|
40
|
+
| `refund_status` | no | Refund state of an x402 stream, from the seller's gasless refund relay. |
|
|
41
|
+
| `refund` | **yes** | Gasless close (payer-signed) and reclaim of an opener stream via the relay. |
|
|
39
42
|
|
|
40
43
|
### `discover_services`
|
|
41
44
|
|
|
42
45
|
`category?`, `limit?`. Returns `serviceRef`, `operator`, `publicUrl`, `access`,
|
|
43
|
-
`accessUrl` per listing
|
|
46
|
+
`accessUrl` per listing, plus `ratePerSecondBand` (`minRatePerSecondUsdc` /
|
|
47
|
+
`maxRatePerSecondUsdc`, atomic units, `null` for an unsigned side) when the
|
|
48
|
+
operator's signed descriptor carries one.
|
|
44
49
|
|
|
45
50
|
The registry is an **untrusted transport**: every `serviceRef` is re-derived
|
|
46
51
|
from the signed record and the operator's signature recovered, so a compromised
|
|
@@ -80,6 +85,16 @@ warning on the result. It is operator-set input only — never a tool argument,
|
|
|
80
85
|
because the caller being asked to relax a fund-safety check is the LLM the check
|
|
81
86
|
exists to constrain — and it never overrides a positive contradiction.
|
|
82
87
|
|
|
88
|
+
`ratePerSecondUsdc` must sit inside the listing's operator-signed rate band
|
|
89
|
+
(inclusive). The descriptor is re-verified with the gateway's own check, and an
|
|
90
|
+
out-of-band rate is refused before any approval or open is sent: the escrow
|
|
91
|
+
accepts any non-zero rate, but the seller's gateway answers every request on
|
|
92
|
+
such a stream with `403 stream-rate-out-of-bounds`. A descriptor that does not
|
|
93
|
+
verify is a refusal. Under `METRIK_AGENT_ALLOW_UNVERIFIED_OPERATOR=true` an
|
|
94
|
+
unverifiable pairing has no listing to read a band from, and the result's
|
|
95
|
+
warning says the rate was not checked; the override never widens a band the
|
|
96
|
+
server can read.
|
|
97
|
+
|
|
83
98
|
The result carries a `metering` block: the rate, the budget, the maximum this
|
|
84
99
|
stream can ever spend, and what starts and stops the clock.
|
|
85
100
|
|
|
@@ -151,6 +166,72 @@ evidence of a delivery that did not happen here.
|
|
|
151
166
|
This is an L2 signal to be cross-checked with oracle/L1 evidence — not a
|
|
152
167
|
substitute for it.
|
|
153
168
|
|
|
169
|
+
### `x402_pay`
|
|
170
|
+
|
|
171
|
+
Arguments: `url` (https; http only for localhost), optional `method`, `headers`,
|
|
172
|
+
`body`, `maxAmountUsdc`, `maxRatePerSecondUsdc` (base units; these can only TIGHTEN
|
|
173
|
+
the server mandate), `minDurationSeconds` and `maxUpfrontAmountUsdc` (the cap on the
|
|
174
|
+
non-refundable part of an upfront quote, passed to the SDK as `maxUpfrontAmount`;
|
|
175
|
+
default 100000 = 0.10 USDC, `"0"` refuses any upfront charge). Pays through `@absol-labs/sdk/x402`'s
|
|
176
|
+
`payWithX402` with every SDK protection intact, and is gated exactly like
|
|
177
|
+
`hire_verified_service`: the same server mandate, the same cumulative spend, the
|
|
178
|
+
same per-owner serialization. The payment's opener terms are checked by
|
|
179
|
+
`checkMandate()` before anything is signed — `amount` (the deposit) against the
|
|
180
|
+
per-stream and total caps, `ratePerSecond`, `maxDuration` and `operator` against
|
|
181
|
+
theirs — and the stream it opens is recorded, so it counts against `maxTotalUsdc`.
|
|
182
|
+
A caller-supplied `PAYMENT-SIGNATURE` / `X-PAYMENT` header is refused. The result
|
|
183
|
+
carries the response (bounded), `streamId`, `opener`, `refundUrl`, the deposit and
|
|
184
|
+
rate, and `refundIsAutomatic: false`.
|
|
185
|
+
|
|
186
|
+
**Upfront ("minimum charge") quotes.** The price splits into a non-refundable per-call
|
|
187
|
+
charge (`upfrontAmount`, paid to the seller at settlement) and a metered, refundable
|
|
188
|
+
deposit (`price - upfrontAmount`). The mandate counts the WHOLE price as spend; the
|
|
189
|
+
upfront part is non-recoverable. The result describes the split (`openerKind`,
|
|
190
|
+
`priceUsdc`, `nonRefundableUsdc`, `refundableUsdc`, `upfrontFeeUsdc`, `upfrontOnly`) and
|
|
191
|
+
the stream record carries `nonRefundableUsdc`. An **upfront-only** payment
|
|
192
|
+
(`upfrontAmount == price`) opens no stream: `streamId` is `null`, no stream (and no
|
|
193
|
+
reservation) is recorded — only a non-stream charge that keeps counting against
|
|
194
|
+
`maxTotalUsdc` and shows in `list_streams` as such — and the payment is final.
|
|
195
|
+
|
|
196
|
+
### `refund_status` / `refund`
|
|
197
|
+
|
|
198
|
+
Arguments: `streamId`, optional `refundUrl` (not needed for a stream this server
|
|
199
|
+
paid for — it was recorded); `refund` also takes `close` (stop the stream first;
|
|
200
|
+
this wallet signs the opener's `Close`, authorized on the mandate owner's signature
|
|
201
|
+
alone like `reclaim_unspent`) and `reclaim` (default `true`; the permissionless
|
|
202
|
+
reclaim, the refund always goes to the payer). Every relay success is verified
|
|
203
|
+
against the transaction receipt. A settlement window that is still open is reported
|
|
204
|
+
with when to retry. Nothing is refunded automatically. For an upfront-only payment —
|
|
205
|
+
`streamId` 0, or the charge key `x402_pay` recorded — both tools answer "nothing to
|
|
206
|
+
refund" (not an error) without contacting a relay or signing anything. The default
|
|
207
|
+
refund client is given read-only chain access, so it can prove an opener's kind
|
|
208
|
+
on-chain and sign an upfront opener's `Close` under its own EIP-712 domain.
|
|
209
|
+
|
|
210
|
+
The three x402 tools are **opt-in**: set `METRIK_AGENT_X402=1` to register them
|
|
211
|
+
(any other value, or none, leaves them off).
|
|
212
|
+
|
|
213
|
+
**Spend is reserved before anything is signed.** When the mandate approves a
|
|
214
|
+
quote, `x402_pay` records a reservation for its amount in the stream registry
|
|
215
|
+
— inside the same per-owner queue as `hire_verified_service` — before the SDK
|
|
216
|
+
signs the EIP-3009 authorization. If anything fails after the signature has
|
|
217
|
+
been handed out (a malformed or missing `PAYMENT-RESPONSE`, an unconfirmed
|
|
218
|
+
settlement, a dropped connection), the outcome is established from the chain,
|
|
219
|
+
never from the seller: USDC `authorizationState(from, nonce)` and the opener's
|
|
220
|
+
`StreamOpenedFor(streamId, payer, authorizationNonce, …)` log. The reservation
|
|
221
|
+
is released only if the authorization was never used AND has expired; a stream
|
|
222
|
+
found on-chain replaces it under its real `streamId`; otherwise it stays
|
|
223
|
+
counted, and the tool says the money may have moved — never "not settled".
|
|
224
|
+
|
|
225
|
+
**Outbound requests are guarded.** The URLs the payment tool, `refund` and
|
|
226
|
+
`refund_status` contact come from the model, so every request must be `https:`
|
|
227
|
+
to a host whose every resolved address is public (loopback, private,
|
|
228
|
+
link-local, unique-local, CGNAT, multicast and reserved ranges are refused),
|
|
229
|
+
redirects are never followed, and `METRIK_AGENT_X402_ALLOWED_HOSTS`
|
|
230
|
+
(comma-separated, exact or `*.domain`) restricts it further — list the seller
|
|
231
|
+
AND its refund relay. A response that did not ask for payment is not returned
|
|
232
|
+
(status only). The address is checked at lookup time; use the allowlist where
|
|
233
|
+
DNS rebinding matters.
|
|
234
|
+
|
|
154
235
|
## Configuration
|
|
155
236
|
|
|
156
237
|
`metrik mcp --print-config` prints the `mcpServers` block to paste into an MCP
|
|
@@ -159,11 +240,15 @@ in it are placeholders by design.
|
|
|
159
240
|
|
|
160
241
|
| Variable | Required | Meaning |
|
|
161
242
|
| ---------------------------------------- | -------- | ----------------------------------------------------------------------------------------------- |
|
|
162
|
-
| `METRIK_AGENT_RPC_URL` | **yes** |
|
|
163
|
-
| `METRIK_AGENT_ESCROW` | **yes** | The **StreamEscrowV2
|
|
243
|
+
| `METRIK_AGENT_RPC_URL` | **yes** | RPC of the selected network. |
|
|
244
|
+
| `METRIK_AGENT_ESCROW` | **yes** | The network's registered **StreamEscrowV2**. Anything else is refused (see below). |
|
|
164
245
|
| `METRIK_AGENT_USDC` | **yes** | Settlement token. |
|
|
165
246
|
| `METRIK_AGENT_ESCROW_GENERATION` | no | `v2` (default, and the only supported value). Declared, not probed. |
|
|
166
|
-
| `
|
|
247
|
+
| `METRIK_NETWORK` | no | Registry id, chain id or CAIP-2 id; default the registry's default network. Testnets only. |
|
|
248
|
+
| `METRIK_AGENT_CHAIN_ID` | no | Alternative selector; must agree with `METRIK_NETWORK` when both are set. |
|
|
249
|
+
| `METRIK_AGENT_ALLOW_UNREGISTERED_ESCROW` | no | `true` accepts an escrow the registry does not record (a deliberate private deployment). |
|
|
250
|
+
| `METRIK_AGENT_X402` | no | `1` registers `x402_pay`, `refund_status`, `refund`. **Off by default.** |
|
|
251
|
+
| `METRIK_AGENT_X402_ALLOWED_HOSTS` | no | Comma-separated hosts (`*.domain` allowed) the x402 tools may contact. Unset = any public host. |
|
|
167
252
|
| `METRIK_AGENT_REGISTRY_URL` | no | Registry the invocation origin is verified against. |
|
|
168
253
|
| `METRIK_AGENT_ALLOW_UNVERIFIED_OPERATOR` | no | `true` downgrades an unverifiable operator pairing from a refusal to a warning. Off by default. |
|
|
169
254
|
| `METRIK_AGENT_MCP_STATE_FILE` | no | Persists the stream registry across restarts. |
|
|
@@ -171,5 +256,6 @@ in it are placeholders by design.
|
|
|
171
256
|
| wallet vars | **yes** | `METRIK_AGENT_PRIVATE_KEY`, or the CDP/Privy set. |
|
|
172
257
|
|
|
173
258
|
The escrow generation is a **declared fact, not an inference**: the server
|
|
174
|
-
refuses
|
|
259
|
+
refuses any escrow that is not the network's registered `StreamEscrowV2` (the
|
|
260
|
+
retired V1 escrow included) unless `METRIK_AGENT_ALLOW_UNREGISTERED_ESCROW=true`, because a wrong-generation read does not
|
|
175
261
|
revert — it returns confident nonsense about someone's money.
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
# `ProbeCapability` — letting the verifier check a gated paid route
|
|
2
|
+
|
|
3
|
+
> **What this changes for you as a seller:** if your listing is `gated`, the
|
|
4
|
+
> Metrik oracle currently cannot call your paid route at all. It can only reach
|
|
5
|
+
> your health route. Adopting `ProbeCapability` is what turns "the health
|
|
6
|
+
> endpoint answered" into "the paid route delivered". It is **opt-in**, and a
|
|
7
|
+
> gateway that does not adopt it is reported by the oracle as **unverifiable**
|
|
8
|
+
> for the paid route — never as passing.
|
|
9
|
+
|
|
10
|
+
## The problem
|
|
11
|
+
|
|
12
|
+
A gated listing's paid route requires an `InvocationCapability` (see
|
|
13
|
+
[`../src/capability/invocation-capability.ts`](../src/capability/invocation-capability.ts))
|
|
14
|
+
signed by **the stream's buyer**: 60s TTL, single-use nonce, bound to one method
|
|
15
|
+
and one path. The gateway recovers that signature and checks it against
|
|
16
|
+
`stream.buyer` on-chain.
|
|
17
|
+
|
|
18
|
+
The oracle is not a buyer. It holds no buyer key and has no stream. So for a
|
|
19
|
+
gated listing there is no credential a regional probe worker could safely
|
|
20
|
+
present, and the paid route goes unprobed by anybody — not remotely, and not
|
|
21
|
+
even by the coordinator.
|
|
22
|
+
|
|
23
|
+
That is a real gap in a claim the product makes. "The seller earns only for
|
|
24
|
+
intervals that verified" must not quietly mean "the health endpoint answered".
|
|
25
|
+
|
|
26
|
+
## The mechanism
|
|
27
|
+
|
|
28
|
+
A second EIP-712 type, `ProbeCapability`, signed by an **oracle signer**:
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
ProbeCapability {
|
|
32
|
+
bytes32 serviceRef; // the service being probed
|
|
33
|
+
address oracle; // the minting signer; must equal the recovered signer
|
|
34
|
+
string method; // exactly one HTTP method
|
|
35
|
+
bytes32 pathHash; // keccak256 of exactly one request path
|
|
36
|
+
bytes32 nonce; // single-use
|
|
37
|
+
uint64 expiry; // ~30s
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
EIP-712 domain: `name = "Metrik Probe Capability"`, `version = "1"`, the
|
|
42
|
+
gateway's `chainId`, and `verifyingContract` = the `StreamEscrowV2` the gateway
|
|
43
|
+
already checks streams against. The domain name is deliberately **different**
|
|
44
|
+
from `"Metrik Invocation Capability"`, so a buyer's invocation signature can
|
|
45
|
+
never be replayed as a free probe and vice versa.
|
|
46
|
+
|
|
47
|
+
Transport: base64url in the `x-metrik-probe-capability` request header.
|
|
48
|
+
|
|
49
|
+
### Authority is public, not a shared secret
|
|
50
|
+
|
|
51
|
+
The gateway authorizes the probe iff the recovered signer is in
|
|
52
|
+
`StreamEscrowV2.getOracleSigners()` — a **public `view`** on the escrow. On the
|
|
53
|
+
live Base Sepolia deployment (`0x0f09f36Ccc05A7c9882F438721C08De314dFd46C`) that
|
|
54
|
+
returns three addresses today.
|
|
55
|
+
|
|
56
|
+
This is the whole reason the design beats "give the workers a credential":
|
|
57
|
+
|
|
58
|
+
- **Nothing to distribute.** No API key, no registration, no onboarding call.
|
|
59
|
+
A third-party seller performs the identical on-chain read and reaches the
|
|
60
|
+
identical verdict, with no trust in the oracle's say-so.
|
|
61
|
+
- **Nothing to leak.** The only secret is the oracle coordinator's signing key,
|
|
62
|
+
which never leaves it.
|
|
63
|
+
- **Revocation is already solved.** Remove a signer on the escrow and it stops
|
|
64
|
+
being able to probe within one signer-cache TTL (60s by default).
|
|
65
|
+
|
|
66
|
+
### No signing key leaves the coordinator
|
|
67
|
+
|
|
68
|
+
The oracle coordinator mints **one fresh capability per probe, per vantage** and
|
|
69
|
+
hands the encoded header to the worker alongside the nonce. Workers hold a
|
|
70
|
+
bearer token for the coordinator and nothing else. Compromising every regional
|
|
71
|
+
worker yields no ability to mint a capability, and no ability to probe anything
|
|
72
|
+
the coordinator did not already ask for — the capability is bound to one
|
|
73
|
+
service, one method, one path, one nonce and ~30 seconds.
|
|
74
|
+
|
|
75
|
+
One capability **per vantage**, not one per probe, because the nonce is
|
|
76
|
+
single-use: two vantages sharing a capability would see the second rejected as a
|
|
77
|
+
replay.
|
|
78
|
+
|
|
79
|
+
### A probe never consumes a buyer's budget
|
|
80
|
+
|
|
81
|
+
Three independent reasons, all structural rather than conventional:
|
|
82
|
+
|
|
83
|
+
1. A `ProbeCapability` carries **no `streamId` and no `buyer`**. There is no
|
|
84
|
+
stream identified for it to be billed against.
|
|
85
|
+
2. Accrual is only ever produced by an **oracle checkpoint keyed to a stream**.
|
|
86
|
+
A gateway call produces none, ever — the gateway is an authorizer and proxy,
|
|
87
|
+
not a meter.
|
|
88
|
+
3. In the reference gateway the probe branch is evaluated **first and returns
|
|
89
|
+
unconditionally**, so it can never fall through into the stream branch. An
|
|
90
|
+
authorized probe decision carries only the probe — no `stream`, no
|
|
91
|
+
capability, nothing billable. A test asserts the stream reader is never even
|
|
92
|
+
called.
|
|
93
|
+
|
|
94
|
+
The gateway additionally sets `x-metrik-probe: 1` on the **upstream** request so
|
|
95
|
+
your own metering and analytics can exclude verification traffic. It is
|
|
96
|
+
gateway-asserted: any inbound copy from a client is stripped first, so a paying
|
|
97
|
+
caller cannot mark its own billable request as a probe.
|
|
98
|
+
|
|
99
|
+
### Rate limiting: why 60/minute
|
|
100
|
+
|
|
101
|
+
Without a bound, an oracle-signed capability is a licence to consume your
|
|
102
|
+
service indefinitely for free.
|
|
103
|
+
|
|
104
|
+
The oracle checkpoints every ~15–22 seconds, so **one** vantage probes a given
|
|
105
|
+
service at most ~4 times a minute. T1 multi-vantage fans that out across at most
|
|
106
|
+
9 vantages (the coordinator plus the oracle's 8-worker cap), giving a worst-case
|
|
107
|
+
honest load of **~36 probes/minute**. The default budget is **60 per minute**:
|
|
108
|
+
comfortable headroom for a re-execution sample and clock jitter, while capping
|
|
109
|
+
the free-service surface at one request per second — far below anything
|
|
110
|
+
economically interesting as a way to get the service for nothing.
|
|
111
|
+
|
|
112
|
+
Tune it with `METRIK_GATEWAY_PROBE_MAX_PER_MINUTE`. A useful side effect: since
|
|
113
|
+
every capability is single-use and ~30s-lived, the rate limit is also what
|
|
114
|
+
bounds the replay cache — at most `maxPerMinute × ttl/60` probe nonces live at
|
|
115
|
+
once.
|
|
116
|
+
|
|
117
|
+
### Replay protection reuses the existing cache
|
|
118
|
+
|
|
119
|
+
The probe path calls the **same** `NonceReplayCache` the buyer path uses, so a
|
|
120
|
+
nonce can never be spent once as a probe and once as an invocation. The nonce is
|
|
121
|
+
consumed _before_ the rate-limit check, so a captured capability replayed at
|
|
122
|
+
volume is rejected as a replay rather than eating the honest probe budget, and
|
|
123
|
+
_before_ the on-chain signer read, so a flood cannot amplify into RPC load.
|
|
124
|
+
|
|
125
|
+
## Enabling it
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
METRIK_GATEWAY_PROBE_CAPABILITY=true # off by default
|
|
129
|
+
METRIK_GATEWAY_PROBE_MAX_PER_MINUTE=60 # optional
|
|
130
|
+
METRIK_GATEWAY_PROBE_SIGNER_CACHE_SECONDS=60 # optional
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
There is **no credential to configure**. The signer set is read from the
|
|
134
|
+
`METRIK_GATEWAY_RPC_URL` and escrow the gateway already uses.
|
|
135
|
+
|
|
136
|
+
Not using the reference gateway? Implement the verification in this exact order
|
|
137
|
+
— cheapest and most local first, so an unsigned or replayed flood cannot amplify
|
|
138
|
+
into RPC load:
|
|
139
|
+
|
|
140
|
+
1. Decode the `x-metrik-probe-capability` header; malformed → `403`.
|
|
141
|
+
2. `now >= expiry` → `403`.
|
|
142
|
+
3. Recover the EIP-712 signer; it must equal `capability.oracle` → else `403`.
|
|
143
|
+
4. `capability.method` must equal the request method → else `403`.
|
|
144
|
+
5. `keccak256(path)` must equal `capability.pathHash` (path only — no query, no
|
|
145
|
+
fragment, no trailing slash except root) → else `403`.
|
|
146
|
+
6. `capability.serviceRef` must be one you front → else `403`.
|
|
147
|
+
7. Consume `capability.nonce` single-use → replay is `403`.
|
|
148
|
+
8. Rate-limit → `429`.
|
|
149
|
+
9. `capability.oracle` must be in `getOracleSigners()` → else `403`. If you
|
|
150
|
+
cannot read the set, **deny** (`403`); do not serve on a stale set.
|
|
151
|
+
|
|
152
|
+
Then proxy upstream and **do not meter, bill or settle the call**.
|
|
153
|
+
|
|
154
|
+
## Failure modes, and what each one means to the oracle
|
|
155
|
+
|
|
156
|
+
Every denial is a distinct machine-readable `reason` in the JSON body:
|
|
157
|
+
|
|
158
|
+
| Reason | Status | What it means |
|
|
159
|
+
| ----------------------------------------------- | ------ | -------------------------------------------------------------------------------------------------------- |
|
|
160
|
+
| `probe-not-enabled` | 403 | This gateway has not adopted `ProbeCapability`. The oracle reports the paid route **unverifiable**. |
|
|
161
|
+
| `malformed-probe-capability` | 403 | Header was not a decodable signed capability. |
|
|
162
|
+
| `probe-capability-expired` | 403 | Past `expiry`. |
|
|
163
|
+
| `probe-invalid-signature` | 403 | Signature does not recover to the claimed `oracle`. |
|
|
164
|
+
| `probe-method-mismatch` / `probe-path-mismatch` | 403 | Capability was not minted for this route. |
|
|
165
|
+
| `probe-service-ref-mismatch` | 403 | Minted for another service. |
|
|
166
|
+
| `probe-nonce-replayed` | 403 | Nonce already spent. |
|
|
167
|
+
| `probe-rate-limited` | 429 | Free-probe budget for this window is spent. |
|
|
168
|
+
| `probe-signer-not-authorized` | 403 | Signer is not in `getOracleSigners()`. |
|
|
169
|
+
| `probe-signer-set-unavailable` | 403 | The set could not be read fresh enough. Fail closed. |
|
|
170
|
+
| `ambiguous-probe-mode` | 400 | Request carried a probe capability _and_ a buyer capability or `X-PAYMENT`. Refused rather than guessed. |
|
|
171
|
+
|
|
172
|
+
## Honest limitations
|
|
173
|
+
|
|
174
|
+
- **This is an adoption ask.** We ship the reference implementation because we
|
|
175
|
+
run the reference gateway, but a third-party seller has to adopt it. Until
|
|
176
|
+
they do, their gated paid route stays health-route-only and the oracle says so
|
|
177
|
+
out loud rather than implying coverage it does not have.
|
|
178
|
+
- **A gated seller can stay unverifiable on purpose.** A hostile gateway can
|
|
179
|
+
simply answer `403` to every probe and remain in the "unverifiable" bucket.
|
|
180
|
+
That gains it nothing it does not already have today — under the oracle's
|
|
181
|
+
`strict` enforcement an unverifiable paid route accrues nothing, and under
|
|
182
|
+
`eligible` it is exactly today's health-route-only behaviour, counted and
|
|
183
|
+
surfaced. What it can never do is be counted as _passing_.
|
|
184
|
+
- **Delivery, not correctness.** A passing probe is evidence the paid route
|
|
185
|
+
produced a fresh, spec-conforming response for _this_ challenge. It is not a
|
|
186
|
+
proof the output is correct. See
|
|
187
|
+
[`../../streamproof-protocol/docs/verification-model.md`](https://github.com/Absol-Labs/metrik-protocol/blob/main/docs/verification-model.md).
|
|
188
|
+
- **The signer set is federated today.** All three addresses in
|
|
189
|
+
`getOracleSigners()` are keys held by one process, and one of them operates
|
|
190
|
+
the live listings. `ProbeCapability` inherits that posture exactly — it makes
|
|
191
|
+
the _paid route_ checkable, not the verifier independent.
|
package/docs/quickstart.md
CHANGED
|
@@ -111,7 +111,7 @@ const account = wallet.account;
|
|
|
111
111
|
const mandate = spendMandateSchema.parse({
|
|
112
112
|
maxPerStreamUsdc: 100_000n, // 0.1 USDC (6 decimals) — max deposit per stream
|
|
113
113
|
maxTotalUsdc: 100_000n, // cumulative cap across all streams
|
|
114
|
-
maxRatePerSecondUsdc:
|
|
114
|
+
maxRatePerSecondUsdc: 100n, // max accrual rate the agent may agree to (0.0001 USDC/s)
|
|
115
115
|
maxDurationSeconds: 3600, // max stream lifetime
|
|
116
116
|
allowedOperators: [account.address as `0x${string}`], // omit/empty ⇒ any operator
|
|
117
117
|
expiresAt: nowSeconds + 365 * 24 * 60 * 60,
|
|
@@ -228,7 +228,7 @@ const hire = await agent.openVerifiedStream({
|
|
|
228
228
|
operator: target.operator, // seller payout wallet from discovery
|
|
229
229
|
serviceRef: target.serviceRef, // bytes32 the oracle verifies delivery against
|
|
230
230
|
budgetUsdc: 100_000n,
|
|
231
|
-
ratePerSecondUsdc:
|
|
231
|
+
ratePerSecondUsdc: 100n, // must sit inside the listing's signed rate band
|
|
232
232
|
maxDurationSeconds: 3600,
|
|
233
233
|
signedMandate,
|
|
234
234
|
spentSoFarUsdc: 0n,
|
|
@@ -293,6 +293,40 @@ const { reclaimResult } = await agent.reclaimStream({
|
|
|
293
293
|
escape-window rules, and `claimed + fees + reclaimed == deposit` conserves the escrowed
|
|
294
294
|
funds exactly.
|
|
295
295
|
|
|
296
|
+
### 7b. Or pay a seller over x402 (no gas needed)
|
|
297
|
+
|
|
298
|
+
A Metrik x402 seller (an HTTP `402` with an x402 v2 `exact` quote) can be paid in one
|
|
299
|
+
call. The payment opens the same kind of stream — `deposit = price`, earned per verified
|
|
300
|
+
second — and the same spend mandate bounds it before anything is signed:
|
|
301
|
+
|
|
302
|
+
```ts
|
|
303
|
+
import { defaultNetwork } from "@absol-labs/agent";
|
|
304
|
+
import { payX402WithMandate } from "@absol-labs/agent/x402-buyer";
|
|
305
|
+
|
|
306
|
+
const paid = await payX402WithMandate({
|
|
307
|
+
url: "https://x402.137.23.50.249.sslip.io/transform",
|
|
308
|
+
init: {
|
|
309
|
+
method: "POST",
|
|
310
|
+
headers: { "content-type": "application/json" },
|
|
311
|
+
body: '{"text":"# hi"}',
|
|
312
|
+
},
|
|
313
|
+
signer: account, // USDC only, no ETH
|
|
314
|
+
network: defaultNetwork(),
|
|
315
|
+
signedMandate,
|
|
316
|
+
spentSoFarUsdc: 0n,
|
|
317
|
+
nowSeconds: Math.floor(Date.now() / 1000),
|
|
318
|
+
});
|
|
319
|
+
console.log(paid.metrikStreamOpener); // streamId, opener, refundUrl, deposit, rate
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
Getting the unearned part back is not automatic: close, then reclaim, through the seller's
|
|
323
|
+
gasless relay (the `metrik refund` command below, or the SDK's `refundClient`).
|
|
324
|
+
|
|
325
|
+
Full details, including the mandate mapping: [the buyer section](./x402.md).
|
|
326
|
+
|
|
327
|
+
From a terminal: `metrik pay <url> --json '{"text":"# hi"}' --max-amount 0.1`, then
|
|
328
|
+
`metrik refund <streamId> --refund-url <url> --close --reclaim`.
|
|
329
|
+
|
|
296
330
|
---
|
|
297
331
|
|
|
298
332
|
## 8. Five ways to integrate
|
|
@@ -306,7 +340,7 @@ loop (`discover -> open -> invoke -> watch -> close -> reclaim`):
|
|
|
306
340
|
```bash
|
|
307
341
|
npx --package @absol-labs/agent metrik discover --min-tier 2
|
|
308
342
|
npx --package @absol-labs/agent metrik open --service <serviceRef> \
|
|
309
|
-
--rate 0.
|
|
343
|
+
--rate 0.0001 --budget 0.36 --duration 1h --dry-run
|
|
310
344
|
```
|
|
311
345
|
|
|
312
346
|
Testnet-only, key read from `METRIK_AGENT_PRIVATE_KEY`, `--json` for scripting. Full
|
|
@@ -394,4 +428,5 @@ Metrik proves **verified delivery** (responded, correctly-shaped, within SLA to
|
|
|
394
428
|
traffic) — **not output correctness** (that the model's answer is right). Pitch and treat
|
|
395
429
|
it accordingly. The full journey, run live against the real deployed escrow on Base
|
|
396
430
|
Sepolia, is [`scripts/e2e-cdp.ts`](../scripts/e2e-cdp.ts) (`pnpm build:e2e && pnpm
|
|
397
|
-
e2e:cdp`) — the working reference for everything above
|
|
431
|
+
e2e:cdp`) — the working reference for everything above; its x402 leg is
|
|
432
|
+
[`scripts/e2e-x402.ts`](../scripts/e2e-x402.ts) (`pnpm e2e:x402`).
|
package/docs/threat-model.md
CHANGED
|
@@ -22,6 +22,9 @@ In scope:
|
|
|
22
22
|
- the mandate-gated SDK wrapper in [`src/sdk/client.ts`](../src/sdk/client.ts)
|
|
23
23
|
- Metrik `402` verified-stream opens in [`src/x402/facilitator.ts`](../src/x402/facilitator.ts)
|
|
24
24
|
(a Metrik-native challenge flow, not the x402 protocol — see [docs/x402.md](./x402.md))
|
|
25
|
+
- the x402 v2 opener facilitator in [`src/x402-opener/`](../src/x402-opener/) and the
|
|
26
|
+
gateway's x402 v2 mode in [`src/gateway/x402-opener-mode.ts`](../src/gateway/x402-opener-mode.ts)
|
|
27
|
+
(both off by default — see [docs/x402.md](./x402.md))
|
|
25
28
|
- MCP fund-moving tools in [`src/mcp/server.ts`](../src/mcp/server.ts)
|
|
26
29
|
- zkTLS consumer delivery proofs in [`src/zktls/reclaim.ts`](../src/zktls/reclaim.ts)
|
|
27
30
|
- wallet custody in [`src/wallet/provider.ts`](../src/wallet/provider.ts)
|
|
@@ -43,6 +46,23 @@ Out of scope:
|
|
|
43
46
|
| Mandate replay | A previously valid signed mandate is replayed after revocation or outside the intended lifetime. | Mandates are EIP-712 signed over `mandateId`, `owner`, `chainId`, `issuedAt`, expiry, and policy hashes. `checkMandate()` verifies signature, revocation, expiry, then policy. Integrators can revoke by `mandateId` and track cumulative spend. | [`src/mandates/mandate.ts`](../src/mandates/mandate.ts), [`test/mandate.test.ts`](../test/mandate.test.ts), [`test/mcp.test.ts`](../test/mcp.test.ts) | Replay protection is only as strong as revocation and cumulative-spend state freshness. The signed payload itself is bound to one chain and one owner, but revocation is local state today. |
|
|
44
47
|
| False or weak delivery evidence | A consumer submits a proof that is validly signed by the zkTLS system but not bound to the intended request or stream context. | The Reclaim adapter re-verifies every proof locally against the exact URL/method/body/match/redaction spec supplied by the caller, and binds the proof context to buyer/stream metadata before returning it. MCP only exposes the public-request subset so secrets do not leak through tool arguments. | [`src/zktls/reclaim.ts`](../src/zktls/reclaim.ts), [`src/x402/facilitator.ts`](../src/x402/facilitator.ts), [`src/mcp/server.ts`](../src/mcp/server.ts), [`test/reclaim.test.ts`](../test/reclaim.test.ts), [`test/x402.test.ts`](../test/x402.test.ts), [`test/mcp.test.ts`](../test/mcp.test.ts) | L2 is still consumer-supplied evidence. A malicious consumer can withhold proofs, and settlement must still treat L1/oracle evidence as authoritative on failure, ambiguity, and expiry. |
|
|
45
48
|
|
|
49
|
+
## x402 v2 opener threats
|
|
50
|
+
|
|
51
|
+
The opener path moves a buyer's USDC only under that buyer's own EIP-3009
|
|
52
|
+
signature, so no agent-held key spends and no spend mandate applies; the
|
|
53
|
+
threats are about where the signed payment can end up.
|
|
54
|
+
|
|
55
|
+
| Threat | Mitigation | Code / tests |
|
|
56
|
+
| ------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
|
|
57
|
+
| A stock x402 facilitator (or anyone holding the signed payload) submits `transferWithAuthorization` to the opener directly, stranding the payment | Only `MetrikOpenerExactScheme` settles, via `deployAndSettle`; its stock verifier gets a signer that cannot broadcast. The gateway refuses to start, and fails closed (503) later, unless the facilitator's `/supported` marker names its exact factory/escrow/token/chain. `PAYMENT-SIGNATURE` is never forwarded upstream. No discovery extension is ever declared. | `src/x402-opener/scheme.ts`, `src/gateway/x402-opener-mode.ts`; `test/x402-opener-gateway.test.ts`, `test/x402-opener.fork.int.test.ts` |
|
|
58
|
+
| A forged or tampered `payTo`, terms or listing redirects funds | `payTo` must equal `factory.predict(terms)` read on-chain (never code/codehash); `(operator, serviceRef)` must resolve to exactly one operator-signed listing via discovery signature verification; amount bounded by `rate × maxDuration` | `test/x402-opener-scheme.test.ts`, `test/x402-opener-config.test.ts` |
|
|
59
|
+
| Success reported without a stream | `settle` returns `success: true` only from a mined receipt carrying the escrow `StreamOpened` (buyer = opener) and the opener `StreamOpenedFor` for this payer/nonce/amount | `test/x402-opener-scheme.test.ts` |
|
|
60
|
+
| One payment buys several served responses (a replayed `PAYMENT-SIGNATURE`) | At most one successful settle per authorization (chain + asset + payer + nonce) per facilitator instance; the gateway refuses an in-flight or settled authorization with `409` before the upstream runs. In-memory, per instance: run one instance | `test/x402-opener-scheme.test.ts`, `test/x402-opener-gateway.test.ts`, `test/x402-opener-regressions.fork.int.test.ts` |
|
|
61
|
+
| Concurrent payers fail on the relayer nonce after being served | Relayer submissions are serialized | same |
|
|
62
|
+
| Dust payments drain relayer gas; unauthenticated `/verify` amplifies RPC load | Minimum amount (default 0.01 USDC, advertised; the gateway refuses a lower price), `/settle` rate limit per token, `/verify` requires the token by default | `test/x402-opener-config.test.ts`, fork regressions |
|
|
63
|
+
| A failed upstream still charges the buyer | The gateway settles only when the upstream status is `< 400`, and a failed settle sends no body | `test/x402-opener-gateway.test.ts`, fork E2E |
|
|
64
|
+
| Relayer key or facilitator token leaks | Env only, never logged or echoed in errors; `/settle` requires the bearer token (constant-time compared) | `src/x402-opener/facilitator-server.ts`; `test/x402-opener-config.test.ts` |
|
|
65
|
+
|
|
46
66
|
## Fund-Moving Paths
|
|
47
67
|
|
|
48
68
|
These are the autonomous fund-moving paths in the current agent layer:
|