@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
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import type { MetrikNetwork } from "@absol-labs/sdk";
|
|
2
|
+
import type { RefundClient, X402PayerSigner } from "@absol-labs/sdk/x402";
|
|
3
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
4
|
+
import { type SignedSpendMandate } from "../mandates/mandate.js";
|
|
5
|
+
import { type LookupFn } from "../x402-buyer/guarded-fetch.js";
|
|
6
|
+
import type { PayX402WithMandateInput, PayX402WithMandateResult } from "../x402-buyer/pay.js";
|
|
7
|
+
/**
|
|
8
|
+
* The MCP surface for x402 (metrik-agent#121 item 4): `x402_pay`,
|
|
9
|
+
* `refund_status`, `refund`. Registered only when the server is given an
|
|
10
|
+
* {@link McpX402Options}; every existing tool is unchanged.
|
|
11
|
+
*
|
|
12
|
+
* `x402_pay` is gated exactly like `hire_verified_service`: the SAME server
|
|
13
|
+
* mandate, the SAME per-owner serialization, the SAME cumulative spend
|
|
14
|
+
* (`getSpentSoFarUsdc`, which counts the stream this payment opens once it is
|
|
15
|
+
* recorded), and `checkMandate()` on the payment's opener terms before the SDK
|
|
16
|
+
* signs anything (see `../x402-buyer/pay.ts`).
|
|
17
|
+
*/
|
|
18
|
+
export declare const X402_PAY_TOOL_DESCRIPTION = "Pay an x402 endpoint (HTTP 402 Payment Required, x402 v2 'exact') and return its response. A Metrik x402 seller quotes a payment that opens an escrow STREAM; the seller earns ratePerSecond x VERIFIED seconds, capped at the deposit, and the unearned remainder stays recoverable by this server's wallet. An UPFRONT ('minimum charge') quote splits the price: upfrontAmount is a NON-REFUNDABLE per-call charge paid to the seller at settlement, and only price - upfrontAmount is escrowed and refundable; when upfrontAmount == price no stream opens at all (streamId null, nothing to refund). Subject to the agent's spend mandate (the WHOLE price vs per-stream and total caps, rate, duration, operator) and a cap on the non-refundable part (maxUpfrontAmountUsdc, default 0.10 USDC), checked BEFORE anything is signed. Refunds are NOT automatic: use refund_status and refund with the returned streamId.";
|
|
19
|
+
export declare const REFUND_STATUS_TOOL_DESCRIPTION = "Read the refund state of a stream this server paid for over x402, from the seller's gasless refund relay: whether it is running, what is reclaimable, and the next step (close / wait / reclaim). An upfront-only payment (no stream) has nothing to refund. Moves no funds.";
|
|
20
|
+
export declare const REFUND_TOOL_DESCRIPTION = "Recover the unearned part of an x402 payment through the seller's gasless refund relay: close=true stops the stream (this wallet signs the opener's Close; the relay pays the gas), reclaim=true sends the permissionless reclaim (the refund always goes to the payer). Only the escrowed part can come back: an upfront charge is final, and an upfront-only payment has nothing to refund. Nothing is refunded automatically; reclaim waits for the settlement window and may say when to retry.";
|
|
21
|
+
export interface McpX402Options {
|
|
22
|
+
/** The payer: this server's own wallet (must own the server mandate). */
|
|
23
|
+
readonly signer: X402PayerSigner;
|
|
24
|
+
readonly network: MetrikNetwork;
|
|
25
|
+
/** Mandate-gated payer. Default: `payX402WithMandate` (lazily loaded). */
|
|
26
|
+
readonly pay?: (input: PayX402WithMandateInput) => Promise<PayX402WithMandateResult>;
|
|
27
|
+
/** Refund relay client factory. Default: the SDK's `refundClient`. */
|
|
28
|
+
readonly refundClient?: (target: {
|
|
29
|
+
readonly refundUrl: string;
|
|
30
|
+
readonly streamId: `0x${string}`;
|
|
31
|
+
readonly opener?: `0x${string}`;
|
|
32
|
+
/** The guarded fetch the relay client must use. */
|
|
33
|
+
readonly fetch?: typeof fetch;
|
|
34
|
+
}) => Promise<RefundClient>;
|
|
35
|
+
/** Passed to the SDK payer (e.g. a transport for the on-chain checks). */
|
|
36
|
+
readonly sdkOptions?: PayX402WithMandateInput["sdkOptions"];
|
|
37
|
+
/** Chain reads after a post-signature failure (see `payX402WithMandate`). */
|
|
38
|
+
readonly chainReader?: PayX402WithMandateInput["chainReader"];
|
|
39
|
+
/**
|
|
40
|
+
* `METRIK_AGENT_X402_ALLOWED_HOSTS`: the only hosts x402_pay / refund /
|
|
41
|
+
* refund_status may contact (exact, or `*.domain`). Unset = any PUBLIC host.
|
|
42
|
+
*/
|
|
43
|
+
readonly allowedHosts?: readonly string[];
|
|
44
|
+
/** Base fetch under the outbound guard (tests). Default `globalThis.fetch`. */
|
|
45
|
+
readonly fetch?: typeof fetch;
|
|
46
|
+
/** DNS resolution for the outbound guard (tests). */
|
|
47
|
+
readonly lookup?: LookupFn;
|
|
48
|
+
/**
|
|
49
|
+
* Local development / fork tests ONLY: let the outbound guard reach
|
|
50
|
+
* loopback and private addresses (and plain http). Never set from env.
|
|
51
|
+
*/
|
|
52
|
+
readonly allowPrivateNetworks?: boolean;
|
|
53
|
+
}
|
|
54
|
+
/** What the server shares with these tools (its mandate state and registry). */
|
|
55
|
+
export interface McpX402Context {
|
|
56
|
+
readonly x402: McpX402Options;
|
|
57
|
+
readonly resolveMandate: () => SignedSpendMandate | Promise<SignedSpendMandate>;
|
|
58
|
+
readonly getSpentSoFarUsdc: (owner: `0x${string}`) => Promise<bigint>;
|
|
59
|
+
readonly getRevokedMandateIds: (owner: `0x${string}`) => Promise<readonly `0x${string}`[]>;
|
|
60
|
+
readonly serialize: <T>(owner: `0x${string}`, task: () => Promise<T>) => Promise<T>;
|
|
61
|
+
readonly recordStream: (record: {
|
|
62
|
+
readonly streamId: `0x${string}`;
|
|
63
|
+
readonly buyer: `0x${string}`;
|
|
64
|
+
readonly operator: `0x${string}`;
|
|
65
|
+
readonly serviceRef: `0x${string}`;
|
|
66
|
+
readonly budgetUsdc: bigint;
|
|
67
|
+
readonly ratePerSecondUsdc: bigint;
|
|
68
|
+
readonly maxDurationSeconds: number;
|
|
69
|
+
readonly openedAtSeconds: number;
|
|
70
|
+
readonly mandateId: `0x${string}`;
|
|
71
|
+
readonly refundUrl?: string;
|
|
72
|
+
readonly opener?: `0x${string}`;
|
|
73
|
+
readonly x402Reservation?: boolean;
|
|
74
|
+
/** Not a stream: a settled upfront-only charge (counted spend, nothing to refund). */
|
|
75
|
+
readonly x402Charge?: "upfront-only";
|
|
76
|
+
/** The non-refundable part of the payment (the upfront charge), base units. */
|
|
77
|
+
readonly nonRefundableUsdc?: bigint;
|
|
78
|
+
}) => Promise<void>;
|
|
79
|
+
/** Drop a record (a released reservation). Absent = reservations are kept. */
|
|
80
|
+
readonly removeStream?: (streamId: `0x${string}`) => Promise<void>;
|
|
81
|
+
readonly findStream: (streamId: `0x${string}`) => Promise<{
|
|
82
|
+
readonly refundUrl?: string;
|
|
83
|
+
readonly opener?: `0x${string}`;
|
|
84
|
+
readonly x402Charge?: "upfront-only";
|
|
85
|
+
} | null>;
|
|
86
|
+
readonly now: () => number;
|
|
87
|
+
readonly ok: (text: string, structured?: Record<string, unknown>) => {
|
|
88
|
+
content: {
|
|
89
|
+
type: "text";
|
|
90
|
+
text: string;
|
|
91
|
+
}[];
|
|
92
|
+
};
|
|
93
|
+
readonly fail: (text: string) => {
|
|
94
|
+
content: {
|
|
95
|
+
type: "text";
|
|
96
|
+
text: string;
|
|
97
|
+
}[];
|
|
98
|
+
isError: boolean;
|
|
99
|
+
};
|
|
100
|
+
readonly unexpected: (error: unknown) => {
|
|
101
|
+
content: {
|
|
102
|
+
type: "text";
|
|
103
|
+
text: string;
|
|
104
|
+
}[];
|
|
105
|
+
isError: boolean;
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
export declare function registerX402Tools(mcpServer: McpServer, ctx: McpX402Context): void;
|
|
109
|
+
//# sourceMappingURL=x402-tools.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"x402-tools.d.ts","sourceRoot":"","sources":["../../src/mcp/x402-tools.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,554 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { verifyMandateOwnerSignature, } from "../mandates/mandate.js";
|
|
3
|
+
import { createPublicClient, http, keccak256, toHex, } from "viem";
|
|
4
|
+
import { assertOutboundUrlAllowed, createGuardedFetch, OutboundRequestRefusedError, } from "../x402-buyer/guarded-fetch.js";
|
|
5
|
+
/**
|
|
6
|
+
* The MCP surface for x402 (metrik-agent#121 item 4): `x402_pay`,
|
|
7
|
+
* `refund_status`, `refund`. Registered only when the server is given an
|
|
8
|
+
* {@link McpX402Options}; every existing tool is unchanged.
|
|
9
|
+
*
|
|
10
|
+
* `x402_pay` is gated exactly like `hire_verified_service`: the SAME server
|
|
11
|
+
* mandate, the SAME per-owner serialization, the SAME cumulative spend
|
|
12
|
+
* (`getSpentSoFarUsdc`, which counts the stream this payment opens once it is
|
|
13
|
+
* recorded), and `checkMandate()` on the payment's opener terms before the SDK
|
|
14
|
+
* signs anything (see `../x402-buyer/pay.ts`).
|
|
15
|
+
*/
|
|
16
|
+
export const X402_PAY_TOOL_DESCRIPTION = "Pay an x402 endpoint (HTTP 402 Payment Required, x402 v2 'exact') and return its response. A Metrik x402 seller quotes a payment that opens an escrow STREAM; the seller earns ratePerSecond x VERIFIED seconds, capped at the deposit, and the unearned remainder stays recoverable by this server's wallet. An UPFRONT ('minimum charge') quote splits the price: upfrontAmount is a NON-REFUNDABLE per-call charge paid to the seller at settlement, and only price - upfrontAmount is escrowed and refundable; when upfrontAmount == price no stream opens at all (streamId null, nothing to refund). Subject to the agent's spend mandate (the WHOLE price vs per-stream and total caps, rate, duration, operator) and a cap on the non-refundable part (maxUpfrontAmountUsdc, default 0.10 USDC), checked BEFORE anything is signed. Refunds are NOT automatic: use refund_status and refund with the returned streamId.";
|
|
17
|
+
export const REFUND_STATUS_TOOL_DESCRIPTION = "Read the refund state of a stream this server paid for over x402, from the seller's gasless refund relay: whether it is running, what is reclaimable, and the next step (close / wait / reclaim). An upfront-only payment (no stream) has nothing to refund. Moves no funds.";
|
|
18
|
+
export const REFUND_TOOL_DESCRIPTION = "Recover the unearned part of an x402 payment through the seller's gasless refund relay: close=true stops the stream (this wallet signs the opener's Close; the relay pays the gas), reclaim=true sends the permissionless reclaim (the refund always goes to the payer). Only the escrowed part can come back: an upfront charge is final, and an upfront-only payment has nothing to refund. Nothing is refunded automatically; reclaim waits for the settlement window and may say when to retry.";
|
|
19
|
+
/** Longest response body returned to the model. */
|
|
20
|
+
const MAX_BODY_CHARS = 32 * 1024;
|
|
21
|
+
const atomicSchema = z
|
|
22
|
+
.string()
|
|
23
|
+
.regex(/^[1-9]\d*$/, "a positive integer of USDC base units (6 decimals)");
|
|
24
|
+
const bytes32Schema = z
|
|
25
|
+
.string()
|
|
26
|
+
.regex(/^0x[0-9a-fA-F]{64}$/, "a 0x-prefixed 32-byte hex value");
|
|
27
|
+
const x402PaySchema = {
|
|
28
|
+
url: z
|
|
29
|
+
.string()
|
|
30
|
+
.url()
|
|
31
|
+
.describe("The x402 endpoint. https only (http only for localhost)."),
|
|
32
|
+
method: z
|
|
33
|
+
.enum(["GET", "POST", "PUT", "PATCH", "DELETE"])
|
|
34
|
+
.optional()
|
|
35
|
+
.describe("HTTP method. Default POST with a body, otherwise GET."),
|
|
36
|
+
headers: z.record(z.string()).optional(),
|
|
37
|
+
body: z.string().max(1_000_000).optional().describe("Request body."),
|
|
38
|
+
maxAmountUsdc: atomicSchema
|
|
39
|
+
.optional()
|
|
40
|
+
.describe("Optional price ceiling, base units. Can only TIGHTEN the server mandate's per-stream cap."),
|
|
41
|
+
maxRatePerSecondUsdc: atomicSchema
|
|
42
|
+
.optional()
|
|
43
|
+
.describe("Optional rate ceiling, base units/second. Tightens only."),
|
|
44
|
+
minDurationSeconds: z
|
|
45
|
+
.number()
|
|
46
|
+
.int()
|
|
47
|
+
.positive()
|
|
48
|
+
.optional()
|
|
49
|
+
.describe("Refuse a quote the seller can fully earn in fewer verified seconds."),
|
|
50
|
+
maxUpfrontAmountUsdc: z
|
|
51
|
+
.string()
|
|
52
|
+
.regex(/^(0|[1-9]\d*)$/, "a non-negative integer of USDC base units")
|
|
53
|
+
.optional()
|
|
54
|
+
.describe("Cap on the NON-REFUNDABLE per-call part of an upfront-opener quote, base units. Default 100000 (0.10 USDC); 0 refuses any upfront charge."),
|
|
55
|
+
};
|
|
56
|
+
const ZERO_STREAM_ID = `0x${"00".repeat(32)}`;
|
|
57
|
+
const refundSchema = {
|
|
58
|
+
streamId: bytes32Schema,
|
|
59
|
+
refundUrl: z
|
|
60
|
+
.string()
|
|
61
|
+
.url()
|
|
62
|
+
.optional()
|
|
63
|
+
.describe("The seller's refund relay URL. Optional for a stream this server paid for (it was recorded)."),
|
|
64
|
+
};
|
|
65
|
+
const refundActionSchema = {
|
|
66
|
+
...refundSchema,
|
|
67
|
+
close: z
|
|
68
|
+
.boolean()
|
|
69
|
+
.optional()
|
|
70
|
+
.describe("Stop the stream first (this wallet signs Close)."),
|
|
71
|
+
reclaim: z
|
|
72
|
+
.boolean()
|
|
73
|
+
.optional()
|
|
74
|
+
.describe("Send the permissionless reclaim. Default true."),
|
|
75
|
+
};
|
|
76
|
+
const PAYMENT_HEADERS = new Set(["payment-signature", "x-payment"]);
|
|
77
|
+
export function registerX402Tools(mcpServer, ctx) {
|
|
78
|
+
const pay = ctx.x402.pay ??
|
|
79
|
+
(async (input) => {
|
|
80
|
+
const { payX402WithMandate } = await import("../x402-buyer/pay.js");
|
|
81
|
+
return await payX402WithMandate(input);
|
|
82
|
+
});
|
|
83
|
+
// Every outbound request these tools make — the seller, and the refund
|
|
84
|
+
// relay — goes through the guard: https only, public addresses only, the
|
|
85
|
+
// optional host allowlist, and never a redirect.
|
|
86
|
+
const guard = {
|
|
87
|
+
...(ctx.x402.allowedHosts === undefined
|
|
88
|
+
? {}
|
|
89
|
+
: { allowedHosts: ctx.x402.allowedHosts }),
|
|
90
|
+
...(ctx.x402.allowPrivateNetworks === true
|
|
91
|
+
? { allowPrivateNetworks: true }
|
|
92
|
+
: {}),
|
|
93
|
+
...(ctx.x402.fetch === undefined ? {} : { fetch: ctx.x402.fetch }),
|
|
94
|
+
...(ctx.x402.lookup === undefined ? {} : { lookup: ctx.x402.lookup }),
|
|
95
|
+
};
|
|
96
|
+
const guardedFetch = createGuardedFetch(guard);
|
|
97
|
+
const openRefunds = ctx.x402.refundClient ??
|
|
98
|
+
(async (target) => {
|
|
99
|
+
const { refundClient } = await import("@absol-labs/sdk/x402");
|
|
100
|
+
return refundClient(target.refundUrl, {
|
|
101
|
+
streamId: target.streamId,
|
|
102
|
+
...(target.opener === undefined ? {} : { opener: target.opener }),
|
|
103
|
+
...(target.fetch === undefined ? {} : { fetch: target.fetch }),
|
|
104
|
+
expectedChainId: ctx.x402.network.chain.id,
|
|
105
|
+
// Chain access is what lets the SDK read the opener's KIND on-chain and
|
|
106
|
+
// sign an upfront opener's Close under its own EIP-712 domain; without
|
|
107
|
+
// it, such a Close is refused.
|
|
108
|
+
publicClient: refundReader(ctx.x402),
|
|
109
|
+
network: ctx.x402.network,
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
mcpServer.registerTool("x402_pay", {
|
|
113
|
+
description: X402_PAY_TOOL_DESCRIPTION,
|
|
114
|
+
inputSchema: x402PaySchema,
|
|
115
|
+
annotations: {
|
|
116
|
+
destructiveHint: true,
|
|
117
|
+
idempotentHint: false,
|
|
118
|
+
openWorldHint: true,
|
|
119
|
+
readOnlyHint: false,
|
|
120
|
+
title: "Pay x402 Endpoint",
|
|
121
|
+
},
|
|
122
|
+
}, async (args) => {
|
|
123
|
+
const headers = {};
|
|
124
|
+
for (const [name, value] of Object.entries(args.headers ?? {})) {
|
|
125
|
+
if (PAYMENT_HEADERS.has(name.toLowerCase())) {
|
|
126
|
+
return ctx.fail(`do not set the ${name} header: this server signs the payment itself, after the quote passes the spend mandate.`);
|
|
127
|
+
}
|
|
128
|
+
headers[name.toLowerCase()] = value;
|
|
129
|
+
}
|
|
130
|
+
if (args.body !== undefined && headers["content-type"] === undefined) {
|
|
131
|
+
headers["content-type"] = "application/json";
|
|
132
|
+
}
|
|
133
|
+
try {
|
|
134
|
+
// Refuse a forbidden target before the mandate, the registry or the
|
|
135
|
+
// seller is touched.
|
|
136
|
+
await assertOutboundUrlAllowed(args.url, guard);
|
|
137
|
+
const signedMandate = await ctx.resolveMandate();
|
|
138
|
+
const owner = signedMandate.owner;
|
|
139
|
+
return await ctx.serialize(owner, async () => {
|
|
140
|
+
const spentSoFarUsdc = await ctx.getSpentSoFarUsdc(owner);
|
|
141
|
+
const revokedMandateIds = await ctx.getRevokedMandateIds(owner);
|
|
142
|
+
const nowSeconds = ctx.now();
|
|
143
|
+
// The reservation: written BEFORE the SDK signs (onApproved), inside
|
|
144
|
+
// this owner's serialized queue, under a key of its own. It is
|
|
145
|
+
// released only when the authorization provably never moved money,
|
|
146
|
+
// and replaced by the real stream record once the stream is known.
|
|
147
|
+
let reservation = null;
|
|
148
|
+
const release = async () => {
|
|
149
|
+
if (reservation !== null)
|
|
150
|
+
await ctx.removeStream?.(reservation);
|
|
151
|
+
};
|
|
152
|
+
let result;
|
|
153
|
+
try {
|
|
154
|
+
result = await pay({
|
|
155
|
+
url: args.url,
|
|
156
|
+
init: {
|
|
157
|
+
method: args.method ?? (args.body === undefined ? "GET" : "POST"),
|
|
158
|
+
headers,
|
|
159
|
+
...(args.body === undefined ? {} : { body: args.body }),
|
|
160
|
+
},
|
|
161
|
+
signer: ctx.x402.signer,
|
|
162
|
+
network: ctx.x402.network,
|
|
163
|
+
signedMandate,
|
|
164
|
+
spentSoFarUsdc,
|
|
165
|
+
nowSeconds,
|
|
166
|
+
revokedMandateIds,
|
|
167
|
+
...(args.maxAmountUsdc === undefined
|
|
168
|
+
? {}
|
|
169
|
+
: { maxAmount: BigInt(args.maxAmountUsdc) }),
|
|
170
|
+
...(args.maxRatePerSecondUsdc === undefined
|
|
171
|
+
? {}
|
|
172
|
+
: { maxRatePerSecond: BigInt(args.maxRatePerSecondUsdc) }),
|
|
173
|
+
...(args.minDurationSeconds === undefined
|
|
174
|
+
? {}
|
|
175
|
+
: { minDurationSeconds: args.minDurationSeconds }),
|
|
176
|
+
...(args.maxUpfrontAmountUsdc === undefined
|
|
177
|
+
? {}
|
|
178
|
+
: { maxUpfrontAmount: BigInt(args.maxUpfrontAmountUsdc) }),
|
|
179
|
+
sdkOptions: { ...ctx.x402.sdkOptions, fetch: guardedFetch },
|
|
180
|
+
...(ctx.x402.chainReader === undefined
|
|
181
|
+
? {}
|
|
182
|
+
: { chainReader: ctx.x402.chainReader }),
|
|
183
|
+
onApproved: async ({ request, quote }) => {
|
|
184
|
+
reservation = keccak256(toHex(`x402-reservation:${owner}:${quote.payTo}:${nowSeconds}:${crypto.randomUUID()}`));
|
|
185
|
+
await ctx.recordStream({
|
|
186
|
+
streamId: reservation,
|
|
187
|
+
buyer: owner,
|
|
188
|
+
operator: request.operator,
|
|
189
|
+
serviceRef: quote.serviceRef ?? `0x${"00".repeat(32)}`,
|
|
190
|
+
budgetUsdc: request.budgetUsdc,
|
|
191
|
+
ratePerSecondUsdc: request.ratePerSecondUsdc,
|
|
192
|
+
maxDurationSeconds: request.maxDurationSeconds,
|
|
193
|
+
openedAtSeconds: nowSeconds,
|
|
194
|
+
mandateId: signedMandate.mandateId,
|
|
195
|
+
opener: quote.payTo,
|
|
196
|
+
x402Reservation: true,
|
|
197
|
+
...(quote.upfrontAmount === null || quote.upfrontAmount === 0n
|
|
198
|
+
? {}
|
|
199
|
+
: { nonRefundableUsdc: quote.upfrontAmount }),
|
|
200
|
+
});
|
|
201
|
+
},
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
catch (error) {
|
|
205
|
+
return await settleFailure(ctx, error, {
|
|
206
|
+
release,
|
|
207
|
+
reservation,
|
|
208
|
+
owner,
|
|
209
|
+
mandateId: signedMandate.mandateId,
|
|
210
|
+
nowSeconds,
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
const opener = result.metrikStreamOpener;
|
|
214
|
+
const upfrontAmount = result.quote?.upfrontAmount ?? null;
|
|
215
|
+
if (result.paid &&
|
|
216
|
+
opener !== null &&
|
|
217
|
+
opener.streamId === null &&
|
|
218
|
+
result.approved !== null) {
|
|
219
|
+
// Upfront-only: there is NO stream to record or refund. The spend
|
|
220
|
+
// still counts against the mandate (the whole price is gone), so
|
|
221
|
+
// the reservation is replaced by a permanent, non-stream charge.
|
|
222
|
+
await ctx.recordStream({
|
|
223
|
+
streamId: chargeKey(owner, opener.opener, result),
|
|
224
|
+
buyer: owner,
|
|
225
|
+
operator: result.approved.operator,
|
|
226
|
+
serviceRef: result.quote?.serviceRef ?? `0x${"00".repeat(32)}`,
|
|
227
|
+
budgetUsdc: result.approved.budgetUsdc,
|
|
228
|
+
ratePerSecondUsdc: result.approved.ratePerSecondUsdc,
|
|
229
|
+
maxDurationSeconds: result.approved.maxDurationSeconds,
|
|
230
|
+
openedAtSeconds: nowSeconds,
|
|
231
|
+
mandateId: signedMandate.mandateId,
|
|
232
|
+
opener: opener.opener,
|
|
233
|
+
x402Charge: "upfront-only",
|
|
234
|
+
nonRefundableUsdc: result.approved.budgetUsdc,
|
|
235
|
+
});
|
|
236
|
+
await release();
|
|
237
|
+
}
|
|
238
|
+
else if (result.paid &&
|
|
239
|
+
opener !== null &&
|
|
240
|
+
opener.streamId !== null &&
|
|
241
|
+
result.approved !== null) {
|
|
242
|
+
// Replace the reservation with the real stream (same amount).
|
|
243
|
+
await ctx.recordStream({
|
|
244
|
+
streamId: opener.streamId,
|
|
245
|
+
buyer: owner,
|
|
246
|
+
operator: result.approved.operator,
|
|
247
|
+
serviceRef: result.quote?.serviceRef ?? `0x${"00".repeat(32)}`,
|
|
248
|
+
budgetUsdc: result.approved.budgetUsdc,
|
|
249
|
+
ratePerSecondUsdc: result.approved.ratePerSecondUsdc,
|
|
250
|
+
maxDurationSeconds: result.approved.maxDurationSeconds,
|
|
251
|
+
openedAtSeconds: nowSeconds,
|
|
252
|
+
mandateId: signedMandate.mandateId,
|
|
253
|
+
...(opener.refundUrl === null
|
|
254
|
+
? {}
|
|
255
|
+
: { refundUrl: opener.refundUrl }),
|
|
256
|
+
opener: opener.opener,
|
|
257
|
+
...(upfrontAmount === null || upfrontAmount === 0n
|
|
258
|
+
? {}
|
|
259
|
+
: { nonRefundableUsdc: upfrontAmount }),
|
|
260
|
+
});
|
|
261
|
+
await release();
|
|
262
|
+
}
|
|
263
|
+
else if (!result.paid) {
|
|
264
|
+
// No 402 was answered, so nothing was approved or signed.
|
|
265
|
+
await release();
|
|
266
|
+
}
|
|
267
|
+
if (!result.paid) {
|
|
268
|
+
// Not a payment: do not relay a body this server fetched on the
|
|
269
|
+
// model's behalf (it would make the tool a general-purpose
|
|
270
|
+
// fetcher of whatever the URL serves).
|
|
271
|
+
return ctx.fail(`the endpoint answered HTTP ${result.response.status} without asking for an x402 payment; nothing was signed or paid, and its body is not returned.`);
|
|
272
|
+
}
|
|
273
|
+
const body = await result.response.text();
|
|
274
|
+
const truncated = body.length > MAX_BODY_CHARS;
|
|
275
|
+
const structured = {
|
|
276
|
+
paid: result.paid,
|
|
277
|
+
httpStatus: result.response.status,
|
|
278
|
+
contentType: result.response.headers.get("content-type"),
|
|
279
|
+
body: truncated ? body.slice(0, MAX_BODY_CHARS) : body,
|
|
280
|
+
bodyTruncated: truncated,
|
|
281
|
+
streamId: opener?.streamId ?? null,
|
|
282
|
+
opener: opener?.opener ?? null,
|
|
283
|
+
refundUrl: opener?.refundUrl ?? null,
|
|
284
|
+
depositUsdc: opener?.deposit?.toString() ?? null,
|
|
285
|
+
ratePerSecondUsdc: opener?.ratePerSecond?.toString() ?? null,
|
|
286
|
+
expiresAt: opener?.expiresAt ?? null,
|
|
287
|
+
minEarnSeconds: result.quote?.minEarnSeconds?.toString() ?? null,
|
|
288
|
+
openerKind: result.quote?.openerKind ?? null,
|
|
289
|
+
priceUsdc: result.quote?.amount.toString() ?? null,
|
|
290
|
+
nonRefundableUsdc: upfrontAmount?.toString() ?? null,
|
|
291
|
+
refundableUsdc: result.quote?.refundableAmount?.toString() ?? null,
|
|
292
|
+
upfrontFeeUsdc: opener?.upfrontFee?.toString() ?? null,
|
|
293
|
+
upfrontOnly: opener?.upfrontOnly ?? false,
|
|
294
|
+
refundIsAutomatic: false,
|
|
295
|
+
};
|
|
296
|
+
if (opener !== null && opener.streamId === null) {
|
|
297
|
+
return ctx.ok(`paid ${result.quote?.amount.toString() ?? "?"} USDC base units as an UPFRONT-ONLY charge; HTTP ${result.response.status}. The whole price was a non-refundable per-call charge paid to the seller at settlement: no stream was opened, the payment is final and there is nothing to refund.`, structured);
|
|
298
|
+
}
|
|
299
|
+
const split = upfrontAmount !== null && upfrontAmount > 0n
|
|
300
|
+
? ` Of the ${result.quote?.amount.toString() ?? "?"} paid, ${upfrontAmount.toString()} was a NON-REFUNDABLE per-call charge (final); only the ${opener?.deposit?.toString() ?? "?"} escrowed can come back.`
|
|
301
|
+
: "";
|
|
302
|
+
return ctx.ok(`paid ${opener?.deposit?.toString() ?? "?"} USDC base units into stream ${opener?.streamId ?? "?"}; HTTP ${result.response.status}.${split} The seller earns only for VERIFIED seconds; the unearned remainder is NOT refunded automatically — call refund (or refund_status) with this streamId.`, structured);
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
catch (error) {
|
|
306
|
+
if (error instanceof OutboundRequestRefusedError) {
|
|
307
|
+
return ctx.fail(error.message);
|
|
308
|
+
}
|
|
309
|
+
return ctx.unexpected(error);
|
|
310
|
+
}
|
|
311
|
+
});
|
|
312
|
+
const bind = async (args) => {
|
|
313
|
+
const streamId = args.streamId.toLowerCase();
|
|
314
|
+
// Stream id 0 is an upfront-only payment: nothing was escrowed. Never
|
|
315
|
+
// contact a relay or sign anything for it.
|
|
316
|
+
if (streamId === ZERO_STREAM_ID) {
|
|
317
|
+
return { nothingToRefund: streamId };
|
|
318
|
+
}
|
|
319
|
+
const record = await ctx.findStream(streamId);
|
|
320
|
+
if (record?.x402Charge === "upfront-only") {
|
|
321
|
+
return { nothingToRefund: streamId };
|
|
322
|
+
}
|
|
323
|
+
const refundUrl = args.refundUrl ?? record?.refundUrl;
|
|
324
|
+
if (refundUrl === undefined) {
|
|
325
|
+
return {
|
|
326
|
+
error: `no refund relay is known for stream ${streamId}: pass refundUrl (x402_pay returned it).`,
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
try {
|
|
330
|
+
await assertOutboundUrlAllowed(refundUrl.replace("{streamId}", streamId), guard);
|
|
331
|
+
}
|
|
332
|
+
catch (error) {
|
|
333
|
+
if (error instanceof OutboundRequestRefusedError) {
|
|
334
|
+
return { error: error.message };
|
|
335
|
+
}
|
|
336
|
+
throw error;
|
|
337
|
+
}
|
|
338
|
+
return await openRefunds({
|
|
339
|
+
refundUrl,
|
|
340
|
+
streamId,
|
|
341
|
+
...(record?.opener === undefined ? {} : { opener: record.opener }),
|
|
342
|
+
fetch: guardedFetch,
|
|
343
|
+
});
|
|
344
|
+
};
|
|
345
|
+
mcpServer.registerTool("refund_status", {
|
|
346
|
+
description: REFUND_STATUS_TOOL_DESCRIPTION,
|
|
347
|
+
inputSchema: refundSchema,
|
|
348
|
+
annotations: {
|
|
349
|
+
idempotentHint: true,
|
|
350
|
+
openWorldHint: true,
|
|
351
|
+
readOnlyHint: true,
|
|
352
|
+
title: "x402 Refund Status",
|
|
353
|
+
},
|
|
354
|
+
}, async (args) => {
|
|
355
|
+
try {
|
|
356
|
+
const refunds = await bind(args);
|
|
357
|
+
if ("error" in refunds)
|
|
358
|
+
return ctx.fail(refunds.error);
|
|
359
|
+
if ("nothingToRefund" in refunds)
|
|
360
|
+
return nothingToRefund(ctx, args);
|
|
361
|
+
const status = await refunds.status();
|
|
362
|
+
return ctx.ok(describeStatus(status), serializeStatus(status));
|
|
363
|
+
}
|
|
364
|
+
catch (error) {
|
|
365
|
+
if (error instanceof Error && error.name === "NothingToRefundError") {
|
|
366
|
+
return nothingToRefund(ctx, args);
|
|
367
|
+
}
|
|
368
|
+
return explainRefundError(ctx, error);
|
|
369
|
+
}
|
|
370
|
+
});
|
|
371
|
+
mcpServer.registerTool("refund", {
|
|
372
|
+
description: REFUND_TOOL_DESCRIPTION,
|
|
373
|
+
inputSchema: refundActionSchema,
|
|
374
|
+
annotations: {
|
|
375
|
+
destructiveHint: true,
|
|
376
|
+
idempotentHint: false,
|
|
377
|
+
openWorldHint: true,
|
|
378
|
+
readOnlyHint: false,
|
|
379
|
+
title: "x402 Refund",
|
|
380
|
+
},
|
|
381
|
+
}, async (args) => {
|
|
382
|
+
try {
|
|
383
|
+
const refunds = await bind(args);
|
|
384
|
+
if ("error" in refunds)
|
|
385
|
+
return ctx.fail(refunds.error);
|
|
386
|
+
if ("nothingToRefund" in refunds)
|
|
387
|
+
return nothingToRefund(ctx, args);
|
|
388
|
+
let closeResult = null;
|
|
389
|
+
if (args.close === true) {
|
|
390
|
+
// Buyer recovery, authorized on the mandate owner's signature alone
|
|
391
|
+
// (never on caps or expiry, so a lapsed mandate cannot strand the
|
|
392
|
+
// payer's own money) — the same rule as reclaim_unspent.
|
|
393
|
+
const signedMandate = await ctx.resolveMandate();
|
|
394
|
+
const decision = await verifyMandateOwnerSignature(signedMandate);
|
|
395
|
+
if (!decision.allowed) {
|
|
396
|
+
return ctx.fail(`mandate denied: ${decision.reason ?? "unknown"}`);
|
|
397
|
+
}
|
|
398
|
+
closeResult = await refunds.close({ signer: ctx.x402.signer });
|
|
399
|
+
}
|
|
400
|
+
let reclaimResult = null;
|
|
401
|
+
if (args.reclaim !== false) {
|
|
402
|
+
reclaimResult = await refunds.reclaim();
|
|
403
|
+
}
|
|
404
|
+
const status = await refunds.status();
|
|
405
|
+
return ctx.ok(`${closeResult === null ? "" : `closed (${closeResult.transaction}). `}${reclaimResult === null ? "" : `reclaimed ${reclaimResult.amount?.toString() ?? "?"} USDC base units to the payer (${reclaimResult.transaction}). `}${describeStatus(status)}`, {
|
|
406
|
+
...serializeStatus(status),
|
|
407
|
+
close: closeResult === null
|
|
408
|
+
? null
|
|
409
|
+
: {
|
|
410
|
+
transaction: closeResult.transaction,
|
|
411
|
+
verified: closeResult.verified,
|
|
412
|
+
},
|
|
413
|
+
reclaim: reclaimResult === null
|
|
414
|
+
? null
|
|
415
|
+
: {
|
|
416
|
+
transaction: reclaimResult.transaction,
|
|
417
|
+
amountUsdc: reclaimResult.amount?.toString() ?? null,
|
|
418
|
+
verified: reclaimResult.verified,
|
|
419
|
+
},
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
catch (error) {
|
|
423
|
+
if (error instanceof Error && error.name === "NothingToRefundError") {
|
|
424
|
+
return nothingToRefund(ctx, args);
|
|
425
|
+
}
|
|
426
|
+
return explainRefundError(ctx, error);
|
|
427
|
+
}
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
/** Upfront-only payments: a finished, successful answer — not an error. */
|
|
431
|
+
function nothingToRefund(ctx, args) {
|
|
432
|
+
return ctx.ok("nothing to refund: this was an upfront-only payment — the whole price was a non-refundable per-call charge paid to the seller at settlement, no stream was opened and nothing was escrowed. Nothing was requested, signed or sent.", {
|
|
433
|
+
streamId: args.streamId.toLowerCase(),
|
|
434
|
+
nothingToRefund: true,
|
|
435
|
+
upfrontOnly: true,
|
|
436
|
+
refundIsAutomatic: false,
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
/** Registry key of a settled upfront-only charge (not an escrow stream id). */
|
|
440
|
+
function chargeKey(owner, opener, result) {
|
|
441
|
+
return keccak256(toHex(`x402-upfront-charge:${owner}:${opener}:${result.authorization?.nonce ?? crypto.randomUUID()}`));
|
|
442
|
+
}
|
|
443
|
+
/** Read-only chain access for the SDK refund client's opener-kind check. */
|
|
444
|
+
function refundReader(options) {
|
|
445
|
+
return (options.sdkOptions?.publicClient ??
|
|
446
|
+
createPublicClient({
|
|
447
|
+
chain: options.network.chain,
|
|
448
|
+
transport: options.sdkOptions?.transport ?? http(options.network.rpcUrls[0]),
|
|
449
|
+
}));
|
|
450
|
+
}
|
|
451
|
+
function serializeStatus(status) {
|
|
452
|
+
return {
|
|
453
|
+
streamId: status.streamId,
|
|
454
|
+
opener: status.opener,
|
|
455
|
+
payer: status.payer,
|
|
456
|
+
status: status.status,
|
|
457
|
+
reclaimed: status.reclaimed,
|
|
458
|
+
reclaimableUsdc: status.reclaimable?.toString() ?? null,
|
|
459
|
+
next: status.next,
|
|
460
|
+
refundIsAutomatic: false,
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
function describeStatus(status) {
|
|
464
|
+
if (status.reclaimed) {
|
|
465
|
+
return `stream ${status.streamId} is reclaimed: the unearned remainder went back to ${status.payer}.`;
|
|
466
|
+
}
|
|
467
|
+
return `stream ${status.streamId} is ${status.status}; next step: ${status.next.action}${status.next.retryAt === undefined ? "" : ` (from ${status.next.retryAt})`}. Nothing is refunded automatically.`;
|
|
468
|
+
}
|
|
469
|
+
/**
|
|
470
|
+
* A failed x402_pay. Before the signature was handed out (mandate or SDK
|
|
471
|
+
* refusal) the reservation is released. After it, the reservation is KEPT
|
|
472
|
+
* unless the authorization provably can never move funds, the real stream is
|
|
473
|
+
* recorded when it can be identified on-chain, and the model is never told
|
|
474
|
+
* "not settled" when the money may have moved.
|
|
475
|
+
*/
|
|
476
|
+
async function settleFailure(ctx, error, state) {
|
|
477
|
+
if (error instanceof Error && error.name === "X402PostSignatureError") {
|
|
478
|
+
const post = error;
|
|
479
|
+
if (post.releasable) {
|
|
480
|
+
await state.release();
|
|
481
|
+
}
|
|
482
|
+
else if (post.upfrontOnly && post.state === "used") {
|
|
483
|
+
// Settled as an upfront-only charge: no stream to record, but the spend
|
|
484
|
+
// is real and stays counted — as a non-stream charge.
|
|
485
|
+
await ctx.recordStream({
|
|
486
|
+
streamId: keccak256(toHex(`x402-upfront-charge:${state.owner}:${post.opener}:${post.authorization?.nonce ?? crypto.randomUUID()}`)),
|
|
487
|
+
buyer: state.owner,
|
|
488
|
+
operator: post.approved.operator,
|
|
489
|
+
serviceRef: post.quote.serviceRef ?? `0x${"00".repeat(32)}`,
|
|
490
|
+
budgetUsdc: post.approved.budgetUsdc,
|
|
491
|
+
ratePerSecondUsdc: post.approved.ratePerSecondUsdc,
|
|
492
|
+
maxDurationSeconds: post.approved.maxDurationSeconds,
|
|
493
|
+
openedAtSeconds: state.nowSeconds,
|
|
494
|
+
mandateId: state.mandateId,
|
|
495
|
+
opener: post.opener,
|
|
496
|
+
x402Charge: "upfront-only",
|
|
497
|
+
nonRefundableUsdc: post.approved.budgetUsdc,
|
|
498
|
+
});
|
|
499
|
+
await state.release();
|
|
500
|
+
}
|
|
501
|
+
else if (post.streamId !== null) {
|
|
502
|
+
await ctx.recordStream({
|
|
503
|
+
streamId: post.streamId,
|
|
504
|
+
buyer: state.owner,
|
|
505
|
+
operator: post.approved.operator,
|
|
506
|
+
serviceRef: post.quote.serviceRef ?? `0x${"00".repeat(32)}`,
|
|
507
|
+
budgetUsdc: post.approved.budgetUsdc,
|
|
508
|
+
ratePerSecondUsdc: post.approved.ratePerSecondUsdc,
|
|
509
|
+
maxDurationSeconds: post.approved.maxDurationSeconds,
|
|
510
|
+
openedAtSeconds: state.nowSeconds,
|
|
511
|
+
mandateId: state.mandateId,
|
|
512
|
+
opener: post.opener,
|
|
513
|
+
...(post.quote.upfrontAmount === null || post.quote.upfrontAmount === 0n
|
|
514
|
+
? {}
|
|
515
|
+
: { nonRefundableUsdc: post.quote.upfrontAmount }),
|
|
516
|
+
});
|
|
517
|
+
await state.release();
|
|
518
|
+
}
|
|
519
|
+
return ctx.fail(`${post.message} ${post.releasable ? "The spend reservation was released." : `The ${post.approved.budgetUsdc.toString()} USDC base units stay COUNTED against the spend mandate.`}${post.streamId === null ? "" : ` streamId: ${post.streamId} (opener ${post.opener}); recover the unearned part with refund / refund_status.`} Do not retry this payment blindly.`);
|
|
520
|
+
}
|
|
521
|
+
// Anything else was raised before a signature existed: nothing moved.
|
|
522
|
+
await state.release();
|
|
523
|
+
if (error instanceof OutboundRequestRefusedError)
|
|
524
|
+
return ctx.fail(error.message);
|
|
525
|
+
return explainPayError(ctx, error);
|
|
526
|
+
}
|
|
527
|
+
function explainPayError(ctx, error) {
|
|
528
|
+
if (error instanceof Error) {
|
|
529
|
+
switch (error.name) {
|
|
530
|
+
case "X402MandateDeniedError":
|
|
531
|
+
return ctx.fail(`${error.message} Raise the server's METRIK_AGENT_MANDATE_* caps only if that payment is intended.`);
|
|
532
|
+
case "X402PaymentRefusedError":
|
|
533
|
+
return ctx.fail(error.message);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
return ctx.unexpected(error);
|
|
537
|
+
}
|
|
538
|
+
function explainRefundError(ctx, error) {
|
|
539
|
+
if (error instanceof Error) {
|
|
540
|
+
switch (error.name) {
|
|
541
|
+
case "RefundNotReadyError": {
|
|
542
|
+
const retryAt = error.retryAt;
|
|
543
|
+
return ctx.fail(`${error.message}${retryAt === null || retryAt === undefined ? "" : ` Retry at ${retryAt} (unix seconds).`} Nothing is refunded automatically.`);
|
|
544
|
+
}
|
|
545
|
+
case "OutboundRequestRefusedError":
|
|
546
|
+
case "RefundRelayError":
|
|
547
|
+
case "NotStreamPayerError":
|
|
548
|
+
case "InvalidInputError":
|
|
549
|
+
return ctx.fail(error.message);
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
return ctx.unexpected(error);
|
|
553
|
+
}
|
|
554
|
+
//# sourceMappingURL=x402-tools.js.map
|