@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/src/gateway/x402-mode.ts
DELETED
|
@@ -1,350 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
PaymentRequirements,
|
|
3
|
-
SettleResponse,
|
|
4
|
-
VerifyResponse,
|
|
5
|
-
} from "x402/types";
|
|
6
|
-
|
|
7
|
-
import {
|
|
8
|
-
buildMetrikPaymentRequirements,
|
|
9
|
-
buildX402PaymentRequiredBody,
|
|
10
|
-
type SignedListingPayee,
|
|
11
|
-
type X402AssetConfig,
|
|
12
|
-
type X402PaymentRequiredBody,
|
|
13
|
-
} from "../x402/payment-requirements.js";
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Standard x402 mode for the seller-side gateway (Tier 1).
|
|
17
|
-
*
|
|
18
|
-
* The gateway already authorizes requests that carry a Metrik
|
|
19
|
-
* `InvocationCapability` — a buyer with an open, funded, verified stream. That
|
|
20
|
-
* path is untouched. This module adds a SECOND, independent way in: a request
|
|
21
|
-
* with no capability gets an ordinary HTTP `402` carrying real x402
|
|
22
|
-
* `PaymentRequirements`, so an agent holding nothing but `x402-fetch` can pay
|
|
23
|
-
* per call without knowing Metrik exists.
|
|
24
|
-
*
|
|
25
|
-
* ## The two paths never overlap
|
|
26
|
-
*
|
|
27
|
-
* - capability header present -> stream path, exactly as before;
|
|
28
|
-
* - capability header absent, x402 mode on -> `402` + `PaymentRequirements`;
|
|
29
|
-
* - BOTH present -> rejected. A request that looks like both is ambiguous about
|
|
30
|
-
* which one is paying for it, and the failure mode of guessing is charging the
|
|
31
|
-
* caller twice for one call. Better a loud `400` than a silent double charge.
|
|
32
|
-
*
|
|
33
|
-
* ## The per-call price is the seller's, and is not signed
|
|
34
|
-
*
|
|
35
|
-
* `payTo` comes from the operator-signed descriptor (see
|
|
36
|
-
* `../x402/payment-requirements.ts`). The price does not — the descriptor has no
|
|
37
|
-
* per-call price field. It is the seller's own number on the seller's own
|
|
38
|
-
* gateway, which is legitimate, but a buyer cannot cross-check it against the
|
|
39
|
-
* registry the way they can cross-check the payee.
|
|
40
|
-
*/
|
|
41
|
-
|
|
42
|
-
/** Request header carrying the x402 payment payload (x402 v1). */
|
|
43
|
-
export const X402_PAYMENT_HEADER = "x-payment" as const;
|
|
44
|
-
|
|
45
|
-
/** Response header carrying the settlement result (x402 v1). */
|
|
46
|
-
export const X402_PAYMENT_RESPONSE_HEADER = "x-payment-response" as const;
|
|
47
|
-
|
|
48
|
-
/** The facilitator the gateway delegates verify/settle to. */
|
|
49
|
-
export interface X402FacilitatorClient {
|
|
50
|
-
verify(
|
|
51
|
-
payload: unknown,
|
|
52
|
-
requirements: PaymentRequirements,
|
|
53
|
-
): Promise<VerifyResponse>;
|
|
54
|
-
settle(
|
|
55
|
-
payload: unknown,
|
|
56
|
-
requirements: PaymentRequirements,
|
|
57
|
-
): Promise<SettleResponse>;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export interface HttpFacilitatorClientOptions {
|
|
61
|
-
/** Base URL of a facilitator exposing `/verify` and `/settle`. */
|
|
62
|
-
readonly url: string;
|
|
63
|
-
/**
|
|
64
|
-
* Shared secret for the facilitator's `/settle`. Sent as
|
|
65
|
-
* `Authorization: Bearer`. Metrik's own facilitator refuses to start a
|
|
66
|
-
* settling deployment without one, because `/settle` spends its gas.
|
|
67
|
-
*/
|
|
68
|
-
readonly token?: string;
|
|
69
|
-
readonly headers?: Readonly<Record<string, string>>;
|
|
70
|
-
readonly timeoutMs?: number;
|
|
71
|
-
readonly fetch?: typeof globalThis.fetch;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Talks to any x402 facilitator over HTTP — including Metrik's own
|
|
76
|
-
* (`../x402/facilitator-server.ts`), which is the point: a seller who wires this
|
|
77
|
-
* gateway to the Metrik facilitator gets a paywall whose payments stop when
|
|
78
|
-
* their own delivery stops being verifiable.
|
|
79
|
-
*/
|
|
80
|
-
export function createHttpFacilitatorClient(
|
|
81
|
-
options: HttpFacilitatorClientOptions,
|
|
82
|
-
): X402FacilitatorClient {
|
|
83
|
-
const base = options.url.replace(/\/+$/, "");
|
|
84
|
-
const timeoutMs = options.timeoutMs ?? 10_000;
|
|
85
|
-
const doFetch = options.fetch ?? globalThis.fetch;
|
|
86
|
-
|
|
87
|
-
async function post(path: string, body: unknown): Promise<unknown> {
|
|
88
|
-
const controller = new AbortController();
|
|
89
|
-
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
90
|
-
try {
|
|
91
|
-
const response = await doFetch(`${base}${path}`, {
|
|
92
|
-
method: "POST",
|
|
93
|
-
headers: {
|
|
94
|
-
"content-type": "application/json",
|
|
95
|
-
...(options.token === undefined
|
|
96
|
-
? {}
|
|
97
|
-
: { authorization: `Bearer ${options.token}` }),
|
|
98
|
-
...(options.headers ?? {}),
|
|
99
|
-
},
|
|
100
|
-
body: JSON.stringify(body),
|
|
101
|
-
signal: controller.signal,
|
|
102
|
-
});
|
|
103
|
-
return await response.json();
|
|
104
|
-
} finally {
|
|
105
|
-
clearTimeout(timer);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
return {
|
|
110
|
-
async verify(payload, requirements) {
|
|
111
|
-
const result = (await post("/verify", {
|
|
112
|
-
x402Version: 1,
|
|
113
|
-
paymentPayload: payload,
|
|
114
|
-
paymentRequirements: requirements,
|
|
115
|
-
})) as VerifyResponse;
|
|
116
|
-
// Fail closed: an unreadable facilitator answer is not a pass.
|
|
117
|
-
if (typeof result !== "object" || result === null) {
|
|
118
|
-
return { isValid: false, invalidReason: "unexpected_verify_error" };
|
|
119
|
-
}
|
|
120
|
-
return result;
|
|
121
|
-
},
|
|
122
|
-
async settle(payload, requirements) {
|
|
123
|
-
return (await post("/settle", {
|
|
124
|
-
x402Version: 1,
|
|
125
|
-
paymentPayload: payload,
|
|
126
|
-
paymentRequirements: requirements,
|
|
127
|
-
})) as SettleResponse;
|
|
128
|
-
},
|
|
129
|
-
};
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/** Builds the `PaymentRequirements` quoted for a given request. */
|
|
133
|
-
export type X402Quoter = (request: {
|
|
134
|
-
readonly method: string;
|
|
135
|
-
readonly path: string;
|
|
136
|
-
}) => readonly PaymentRequirements[] | Promise<readonly PaymentRequirements[]>;
|
|
137
|
-
|
|
138
|
-
export interface ListingPriceQuoterOptions {
|
|
139
|
-
/** Projected from the OPERATOR-SIGNED descriptor. `payTo` comes from here. */
|
|
140
|
-
readonly listing: SignedListingPayee;
|
|
141
|
-
/** Public origin buyers reach this gateway on, e.g. `https://svc.example.com`. */
|
|
142
|
-
readonly resourceBaseUrl: string;
|
|
143
|
-
/** Per-call price in the asset's base units (micro-USDC). Seller-set, unsigned. */
|
|
144
|
-
readonly pricePerCallBaseUnits: bigint;
|
|
145
|
-
readonly chainId: number;
|
|
146
|
-
readonly description?: string;
|
|
147
|
-
readonly mimeType?: string;
|
|
148
|
-
readonly maxTimeoutSeconds?: number;
|
|
149
|
-
readonly asset?: X402AssetConfig;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
/** One flat price per call, quoted against the request's own path. */
|
|
153
|
-
export function createListingPriceQuoter(
|
|
154
|
-
options: ListingPriceQuoterOptions,
|
|
155
|
-
): X402Quoter {
|
|
156
|
-
const base = new URL(options.resourceBaseUrl);
|
|
157
|
-
return (request) => [
|
|
158
|
-
buildMetrikPaymentRequirements({
|
|
159
|
-
listing: options.listing,
|
|
160
|
-
resource: new URL(request.path, base).toString(),
|
|
161
|
-
priceBaseUnits: options.pricePerCallBaseUnits,
|
|
162
|
-
chainId: options.chainId,
|
|
163
|
-
...(options.description === undefined
|
|
164
|
-
? {}
|
|
165
|
-
: { description: options.description }),
|
|
166
|
-
...(options.mimeType === undefined ? {} : { mimeType: options.mimeType }),
|
|
167
|
-
...(options.maxTimeoutSeconds === undefined
|
|
168
|
-
? {}
|
|
169
|
-
: { maxTimeoutSeconds: options.maxTimeoutSeconds }),
|
|
170
|
-
...(options.asset === undefined ? {} : { asset: options.asset }),
|
|
171
|
-
}),
|
|
172
|
-
];
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
export interface GatewayX402Mode {
|
|
176
|
-
readonly quote: X402Quoter;
|
|
177
|
-
readonly facilitator: X402FacilitatorClient;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
/** Decoded, still-untrusted `X-PAYMENT` payload plus the quote it answers. */
|
|
181
|
-
export interface AcceptedX402Payment {
|
|
182
|
-
readonly payload: unknown;
|
|
183
|
-
readonly requirements: PaymentRequirements;
|
|
184
|
-
readonly payer?: string;
|
|
185
|
-
/**
|
|
186
|
-
* Unix seconds after which the signed authorization is no longer settleable
|
|
187
|
-
* (`validBefore`), when the payload carried one.
|
|
188
|
-
*
|
|
189
|
-
* The upstream runs BEFORE settlement, so an upstream slower than this window
|
|
190
|
-
* would produce its side effects and then fail to be paid for. The server
|
|
191
|
-
* bounds the upstream call by this instead of letting that happen.
|
|
192
|
-
*/
|
|
193
|
-
readonly validBefore?: number;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
export type X402EvaluationOutcome =
|
|
197
|
-
| {
|
|
198
|
-
readonly outcome: "payment-required";
|
|
199
|
-
readonly body: X402PaymentRequiredBody;
|
|
200
|
-
}
|
|
201
|
-
| { readonly outcome: "accepted"; readonly payment: AcceptedX402Payment };
|
|
202
|
-
|
|
203
|
-
/**
|
|
204
|
-
* Evaluates the x402 half of a request. Returns either the `402` body to send or
|
|
205
|
-
* an accepted payment to settle after the upstream has been called.
|
|
206
|
-
*/
|
|
207
|
-
export async function evaluateX402Request(
|
|
208
|
-
request: { readonly method: string; readonly path: string },
|
|
209
|
-
paymentHeader: string | undefined,
|
|
210
|
-
mode: GatewayX402Mode,
|
|
211
|
-
): Promise<X402EvaluationOutcome> {
|
|
212
|
-
const accepts = await mode.quote(request);
|
|
213
|
-
if (paymentHeader === undefined || paymentHeader.trim().length === 0) {
|
|
214
|
-
return {
|
|
215
|
-
outcome: "payment-required",
|
|
216
|
-
body: buildX402PaymentRequiredBody(accepts),
|
|
217
|
-
};
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
const payload = decodePaymentHeader(paymentHeader);
|
|
221
|
-
if (payload === undefined) {
|
|
222
|
-
return {
|
|
223
|
-
outcome: "payment-required",
|
|
224
|
-
body: buildX402PaymentRequiredBody(
|
|
225
|
-
accepts,
|
|
226
|
-
"X-PAYMENT header is not base64-encoded JSON",
|
|
227
|
-
),
|
|
228
|
-
};
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
const requirements = selectRequirements(accepts, payload);
|
|
232
|
-
if (requirements === undefined) {
|
|
233
|
-
return {
|
|
234
|
-
outcome: "payment-required",
|
|
235
|
-
body: buildX402PaymentRequiredBody(
|
|
236
|
-
accepts,
|
|
237
|
-
"X-PAYMENT does not match any offered payment requirements",
|
|
238
|
-
),
|
|
239
|
-
};
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
let verified: VerifyResponse;
|
|
243
|
-
try {
|
|
244
|
-
verified = await mode.facilitator.verify(payload, requirements);
|
|
245
|
-
} catch (error) {
|
|
246
|
-
// An unreachable facilitator is a refusal, never a free call.
|
|
247
|
-
return {
|
|
248
|
-
outcome: "payment-required",
|
|
249
|
-
body: buildX402PaymentRequiredBody(
|
|
250
|
-
accepts,
|
|
251
|
-
`payment could not be verified: ${
|
|
252
|
-
error instanceof Error ? error.message : "facilitator unreachable"
|
|
253
|
-
}`,
|
|
254
|
-
),
|
|
255
|
-
};
|
|
256
|
-
}
|
|
257
|
-
if (!verified.isValid) {
|
|
258
|
-
return {
|
|
259
|
-
outcome: "payment-required",
|
|
260
|
-
body: buildX402PaymentRequiredBody(
|
|
261
|
-
accepts,
|
|
262
|
-
verified.invalidReason ?? "payment rejected by facilitator",
|
|
263
|
-
),
|
|
264
|
-
};
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
// A Metrik facilitator reports up front whether it would actually broadcast
|
|
268
|
-
// this payment (asset allowlist, settlement scope, ...). Refusing HERE — before
|
|
269
|
-
// the upstream runs — is the difference between "the caller gets nothing" and
|
|
270
|
-
// "the caller gets the upstream's side effects for free and we cannot charge
|
|
271
|
-
// for them". A non-Metrik facilitator omits the field and nothing changes.
|
|
272
|
-
const annotation = (
|
|
273
|
-
verified as { metrik?: { settleable?: boolean; detail?: string } }
|
|
274
|
-
).metrik;
|
|
275
|
-
if (annotation?.settleable === false) {
|
|
276
|
-
return {
|
|
277
|
-
outcome: "payment-required",
|
|
278
|
-
body: buildX402PaymentRequiredBody(
|
|
279
|
-
accepts,
|
|
280
|
-
`facilitator will not settle this payment: ${annotation.detail ?? "refused"}`,
|
|
281
|
-
),
|
|
282
|
-
};
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
const validBefore = readValidBefore(payload);
|
|
286
|
-
|
|
287
|
-
return {
|
|
288
|
-
outcome: "accepted",
|
|
289
|
-
payment: {
|
|
290
|
-
payload,
|
|
291
|
-
requirements,
|
|
292
|
-
...(verified.payer === undefined ? {} : { payer: verified.payer }),
|
|
293
|
-
...(validBefore === undefined ? {} : { validBefore }),
|
|
294
|
-
},
|
|
295
|
-
};
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
/** Reads the exact-EVM authorization deadline, if the payload has one. */
|
|
299
|
-
function readValidBefore(payload: unknown): number | undefined {
|
|
300
|
-
const authorization = (
|
|
301
|
-
payload as { payload?: { authorization?: { validBefore?: unknown } } }
|
|
302
|
-
)?.payload?.authorization;
|
|
303
|
-
const raw = authorization?.validBefore;
|
|
304
|
-
if (typeof raw !== "string" && typeof raw !== "number") {
|
|
305
|
-
return undefined;
|
|
306
|
-
}
|
|
307
|
-
const parsed = Number(raw);
|
|
308
|
-
return Number.isFinite(parsed) && parsed > 0 ? parsed : undefined;
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
/**
|
|
312
|
-
* Seconds of headroom the settlement leg needs after the upstream returns.
|
|
313
|
-
*
|
|
314
|
-
* The x402 `exact` verifier rejects an authorization whose `validBefore` is less
|
|
315
|
-
* than 6 s away, so an upstream allowed to run right up to the deadline would
|
|
316
|
-
* hand back a response that can never be settled. This leaves room for that
|
|
317
|
-
* check plus the settle round trip.
|
|
318
|
-
*/
|
|
319
|
-
export const X402_SETTLEMENT_HEADROOM_SECONDS = 10;
|
|
320
|
-
|
|
321
|
-
/** Matches the payload's scheme+network against the quotes actually offered. */
|
|
322
|
-
function selectRequirements(
|
|
323
|
-
accepts: readonly PaymentRequirements[],
|
|
324
|
-
payload: unknown,
|
|
325
|
-
): PaymentRequirements | undefined {
|
|
326
|
-
if (typeof payload !== "object" || payload === null) {
|
|
327
|
-
return undefined;
|
|
328
|
-
}
|
|
329
|
-
const { scheme, network } = payload as {
|
|
330
|
-
scheme?: unknown;
|
|
331
|
-
network?: unknown;
|
|
332
|
-
};
|
|
333
|
-
return accepts.find(
|
|
334
|
-
(candidate) => candidate.scheme === scheme && candidate.network === network,
|
|
335
|
-
);
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
export function decodePaymentHeader(header: string): unknown {
|
|
339
|
-
try {
|
|
340
|
-
const decoded = Buffer.from(header.trim(), "base64").toString("utf8");
|
|
341
|
-
const parsed: unknown = JSON.parse(decoded);
|
|
342
|
-
return typeof parsed === "object" && parsed !== null ? parsed : undefined;
|
|
343
|
-
} catch {
|
|
344
|
-
return undefined;
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
export function encodeSettleResponseHeader(response: SettleResponse): string {
|
|
349
|
-
return Buffer.from(JSON.stringify(response), "utf8").toString("base64");
|
|
350
|
-
}
|
|
@@ -1,285 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* The Metrik delivery signal, as consumed by the x402 facilitator.
|
|
5
|
-
*
|
|
6
|
-
* ## Where the signal comes from
|
|
7
|
-
*
|
|
8
|
-
* The oracle publishes `GET /services` — one row per `serviceRef`, reduced from
|
|
9
|
-
* the oracle's own settlement-path verdicts for the active streams of that
|
|
10
|
-
* service (`streamproof-oracle/src/service-status.ts`). Its three states are
|
|
11
|
-
* already buyer-favouring at the source: a FRESH failing stream beats a fresh
|
|
12
|
-
* delivering sibling, and absent/stale evidence is reported as `unverified`
|
|
13
|
-
* rather than folded into "fine".
|
|
14
|
-
*
|
|
15
|
-
* ## Why this module re-checks freshness the oracle already checked
|
|
16
|
-
*
|
|
17
|
-
* The oracle's own bound is 5 minutes (`DEFAULT_SERVICE_STATUS_TTL_MS`), which
|
|
18
|
-
* is a sensible bound for a dashboard and far too loose for a payment gate. On
|
|
19
|
-
* top of that the HTTP response itself can be cached or served by a stale
|
|
20
|
-
* replica, and `lastCheckedAt` is the only field that dates the EVIDENCE rather
|
|
21
|
-
* than the response. So the age of `lastCheckedAt` is re-derived here against
|
|
22
|
-
* this module's own, much tighter window.
|
|
23
|
-
*
|
|
24
|
-
* ## The freshness window: 90 seconds, and why
|
|
25
|
-
*
|
|
26
|
-
* The oracle checkpoints every ~15–22 s, and a service's recorded status is
|
|
27
|
-
* refreshed on that cadence. A window has to be wide enough that ordinary
|
|
28
|
-
* jitter does not flap the gate, and narrow enough that "verified" still means
|
|
29
|
-
* "now":
|
|
30
|
-
*
|
|
31
|
-
* - 3 worst-case probe intervals (3 x 22 s = 66 s) absorbs two consecutive
|
|
32
|
-
* missed or slow ticks, which is normal operational noise, not evidence of
|
|
33
|
-
* a dead service.
|
|
34
|
-
* - plus the oracle's own record-and-serve latency and this process's fetch
|
|
35
|
-
* round trip, rounded up: **90 s**.
|
|
36
|
-
*
|
|
37
|
-
* Tighter (e.g. 30 s) and a single slow tick turns a healthy service's gate
|
|
38
|
-
* indeterminate. Looser (e.g. the oracle's own 300 s) and a service that died
|
|
39
|
-
* four minutes ago still reads `delivering`, which is exactly the claim this
|
|
40
|
-
* gate exists to avoid making.
|
|
41
|
-
*
|
|
42
|
-
* Note this window is strictly stricter than the oracle's: a verdict aged
|
|
43
|
-
* between 90 s and 300 s is `delivering` at the source and `indeterminate`
|
|
44
|
-
* here.
|
|
45
|
-
*/
|
|
46
|
-
export const DEFAULT_DELIVERY_FRESHNESS_SECONDS = 90;
|
|
47
|
-
|
|
48
|
-
/** How long a fetched `/services` snapshot may be reused. See `verdictFor`. */
|
|
49
|
-
export const DEFAULT_DELIVERY_SNAPSHOT_TTL_MS = 10_000;
|
|
50
|
-
|
|
51
|
-
/** Request timeout for the oracle read. Short: this sits in a payment hot path. */
|
|
52
|
-
export const DEFAULT_DELIVERY_ORACLE_TIMEOUT_MS = 4_000;
|
|
53
|
-
|
|
54
|
-
export const DEFAULT_METRIK_ORACLE_URL = "https://oracle.metrik.live";
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Three states, never two.
|
|
58
|
-
*
|
|
59
|
-
* `indeterminate` is deliberately NOT collapsed into either of the others: the
|
|
60
|
-
* facilitator's policy for "no fresh evidence" is a separate, configurable
|
|
61
|
-
* decision (see `./verified-delivery-facilitator.ts`), and folding it into
|
|
62
|
-
* `failing` would make Metrik refuse every payment whenever its own oracle is
|
|
63
|
-
* quiet or unreachable.
|
|
64
|
-
*/
|
|
65
|
-
export type DeliveryVerdict = "delivering" | "failing" | "indeterminate";
|
|
66
|
-
|
|
67
|
-
/** Why the verdict is what it is — machine-readable, for logs and for tests. */
|
|
68
|
-
export type DeliveryVerdictReason =
|
|
69
|
-
| "fresh-delivering"
|
|
70
|
-
| "fresh-failing"
|
|
71
|
-
| "no-fresh-evidence"
|
|
72
|
-
| "verdict-stale"
|
|
73
|
-
| "service-not-listed"
|
|
74
|
-
| "oracle-unreachable"
|
|
75
|
-
| "oracle-malformed";
|
|
76
|
-
|
|
77
|
-
export interface DeliveryAssessment {
|
|
78
|
-
readonly serviceRef: string;
|
|
79
|
-
readonly verdict: DeliveryVerdict;
|
|
80
|
-
readonly reason: DeliveryVerdictReason;
|
|
81
|
-
/** Unix ms of the newest recorded check for this service, when known. */
|
|
82
|
-
readonly lastCheckedAt: number | null;
|
|
83
|
-
/** Age of `lastCheckedAt` in seconds, when known. */
|
|
84
|
-
readonly ageSeconds: number | null;
|
|
85
|
-
/** The freshness window applied, in seconds. */
|
|
86
|
-
readonly freshnessWindowSeconds: number;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
export interface MetrikDeliveryOracle {
|
|
90
|
-
/** Never throws: an unreachable oracle is an `indeterminate` verdict, not an exception. */
|
|
91
|
-
verdictFor(serviceRef: string): Promise<DeliveryAssessment>;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/** The subset of the oracle's `/services` row this module relies on. */
|
|
95
|
-
const serviceStatusRowSchema = z
|
|
96
|
-
.object({
|
|
97
|
-
serviceRef: z.string(),
|
|
98
|
-
status: z.enum(["delivering", "failing", "unverified"]),
|
|
99
|
-
lastCheckedAt: z.number().nullable().optional(),
|
|
100
|
-
})
|
|
101
|
-
.passthrough();
|
|
102
|
-
|
|
103
|
-
const servicesResponseSchema = z.object({
|
|
104
|
-
services: z.array(serviceStatusRowSchema),
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
export type FetchLike = (
|
|
108
|
-
input: string,
|
|
109
|
-
init?: { signal?: AbortSignal; headers?: Record<string, string> },
|
|
110
|
-
) => Promise<{
|
|
111
|
-
ok: boolean;
|
|
112
|
-
status: number;
|
|
113
|
-
json(): Promise<unknown>;
|
|
114
|
-
}>;
|
|
115
|
-
|
|
116
|
-
export interface OracleDeliveryOracleOptions {
|
|
117
|
-
/** Oracle base URL. Default {@link DEFAULT_METRIK_ORACLE_URL}. */
|
|
118
|
-
readonly oracleUrl?: string;
|
|
119
|
-
/** Default {@link DEFAULT_DELIVERY_FRESHNESS_SECONDS}. */
|
|
120
|
-
readonly freshnessWindowSeconds?: number;
|
|
121
|
-
/** Default {@link DEFAULT_DELIVERY_SNAPSHOT_TTL_MS}. */
|
|
122
|
-
readonly snapshotTtlMs?: number;
|
|
123
|
-
readonly timeoutMs?: number;
|
|
124
|
-
readonly fetch?: FetchLike;
|
|
125
|
-
/** Injectable clock (unix ms), for tests. */
|
|
126
|
-
readonly now?: () => number;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* Reads `GET {oracleUrl}/services` and projects one row onto a
|
|
131
|
-
* {@link DeliveryAssessment}.
|
|
132
|
-
*
|
|
133
|
-
* The snapshot is cached for {@link DEFAULT_DELIVERY_SNAPSHOT_TTL_MS} so a burst
|
|
134
|
-
* of payments does not become a burst of oracle reads. The cache is SHORTER than
|
|
135
|
-
* the freshness window by design, and caching cannot make a verdict look fresher
|
|
136
|
-
* than it is: freshness is computed from the row's own `lastCheckedAt`, never
|
|
137
|
-
* from when we fetched it.
|
|
138
|
-
*/
|
|
139
|
-
export function createOracleDeliveryOracle(
|
|
140
|
-
options: OracleDeliveryOracleOptions = {},
|
|
141
|
-
): MetrikDeliveryOracle {
|
|
142
|
-
const oracleUrl = (options.oracleUrl ?? DEFAULT_METRIK_ORACLE_URL).replace(
|
|
143
|
-
/\/+$/,
|
|
144
|
-
"",
|
|
145
|
-
);
|
|
146
|
-
const freshnessWindowSeconds =
|
|
147
|
-
options.freshnessWindowSeconds ?? DEFAULT_DELIVERY_FRESHNESS_SECONDS;
|
|
148
|
-
if (!Number.isFinite(freshnessWindowSeconds) || freshnessWindowSeconds <= 0) {
|
|
149
|
-
throw new Error("freshnessWindowSeconds must be a positive number");
|
|
150
|
-
}
|
|
151
|
-
const snapshotTtlMs =
|
|
152
|
-
options.snapshotTtlMs ?? DEFAULT_DELIVERY_SNAPSHOT_TTL_MS;
|
|
153
|
-
const timeoutMs = options.timeoutMs ?? DEFAULT_DELIVERY_ORACLE_TIMEOUT_MS;
|
|
154
|
-
const doFetch = (options.fetch ??
|
|
155
|
-
((input, init) => globalThis.fetch(input, init))) as FetchLike;
|
|
156
|
-
const now = options.now ?? (() => Date.now());
|
|
157
|
-
|
|
158
|
-
type Snapshot = {
|
|
159
|
-
readonly fetchedAtMs: number;
|
|
160
|
-
readonly rows: Map<string, z.infer<typeof serviceStatusRowSchema>> | null;
|
|
161
|
-
readonly failure: DeliveryVerdictReason | null;
|
|
162
|
-
};
|
|
163
|
-
let snapshot: Snapshot | undefined;
|
|
164
|
-
let inFlight: Promise<Snapshot> | undefined;
|
|
165
|
-
|
|
166
|
-
async function loadSnapshot(): Promise<Snapshot> {
|
|
167
|
-
const controller = new AbortController();
|
|
168
|
-
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
169
|
-
try {
|
|
170
|
-
const response = await doFetch(`${oracleUrl}/services`, {
|
|
171
|
-
signal: controller.signal,
|
|
172
|
-
headers: { accept: "application/json", "cache-control": "no-cache" },
|
|
173
|
-
});
|
|
174
|
-
if (!response.ok) {
|
|
175
|
-
return {
|
|
176
|
-
fetchedAtMs: now(),
|
|
177
|
-
rows: null,
|
|
178
|
-
failure: "oracle-unreachable",
|
|
179
|
-
};
|
|
180
|
-
}
|
|
181
|
-
const parsed = servicesResponseSchema.safeParse(await response.json());
|
|
182
|
-
if (!parsed.success) {
|
|
183
|
-
return { fetchedAtMs: now(), rows: null, failure: "oracle-malformed" };
|
|
184
|
-
}
|
|
185
|
-
const rows = new Map<string, z.infer<typeof serviceStatusRowSchema>>();
|
|
186
|
-
for (const row of parsed.data.services) {
|
|
187
|
-
rows.set(row.serviceRef.toLowerCase(), row);
|
|
188
|
-
}
|
|
189
|
-
return { fetchedAtMs: now(), rows, failure: null };
|
|
190
|
-
} catch {
|
|
191
|
-
return { fetchedAtMs: now(), rows: null, failure: "oracle-unreachable" };
|
|
192
|
-
} finally {
|
|
193
|
-
clearTimeout(timer);
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
async function currentSnapshot(): Promise<Snapshot> {
|
|
198
|
-
const cached = snapshot;
|
|
199
|
-
if (cached !== undefined && now() - cached.fetchedAtMs < snapshotTtlMs) {
|
|
200
|
-
return cached;
|
|
201
|
-
}
|
|
202
|
-
// Collapse concurrent misses onto one request.
|
|
203
|
-
inFlight ??= loadSnapshot().finally(() => {
|
|
204
|
-
inFlight = undefined;
|
|
205
|
-
});
|
|
206
|
-
const loaded = await inFlight;
|
|
207
|
-
snapshot = loaded;
|
|
208
|
-
return loaded;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
return {
|
|
212
|
-
async verdictFor(serviceRef) {
|
|
213
|
-
const key = serviceRef.toLowerCase();
|
|
214
|
-
const loaded = await currentSnapshot();
|
|
215
|
-
if (loaded.rows === null) {
|
|
216
|
-
return indeterminate(
|
|
217
|
-
serviceRef,
|
|
218
|
-
loaded.failure ?? "oracle-unreachable",
|
|
219
|
-
null,
|
|
220
|
-
freshnessWindowSeconds,
|
|
221
|
-
);
|
|
222
|
-
}
|
|
223
|
-
const row = loaded.rows.get(key);
|
|
224
|
-
if (row === undefined) {
|
|
225
|
-
return indeterminate(
|
|
226
|
-
serviceRef,
|
|
227
|
-
"service-not-listed",
|
|
228
|
-
null,
|
|
229
|
-
freshnessWindowSeconds,
|
|
230
|
-
);
|
|
231
|
-
}
|
|
232
|
-
const lastCheckedAt = row.lastCheckedAt ?? null;
|
|
233
|
-
const ageSeconds =
|
|
234
|
-
lastCheckedAt === null
|
|
235
|
-
? null
|
|
236
|
-
: Math.max(0, Math.round((now() - lastCheckedAt) / 1000));
|
|
237
|
-
|
|
238
|
-
if (row.status === "unverified") {
|
|
239
|
-
return indeterminate(
|
|
240
|
-
serviceRef,
|
|
241
|
-
"no-fresh-evidence",
|
|
242
|
-
lastCheckedAt,
|
|
243
|
-
freshnessWindowSeconds,
|
|
244
|
-
ageSeconds,
|
|
245
|
-
);
|
|
246
|
-
}
|
|
247
|
-
// The oracle dated its own verdict; if it cannot be shown to be recent it
|
|
248
|
-
// is not evidence of anything, in EITHER direction.
|
|
249
|
-
if (ageSeconds === null || ageSeconds > freshnessWindowSeconds) {
|
|
250
|
-
return indeterminate(
|
|
251
|
-
serviceRef,
|
|
252
|
-
"verdict-stale",
|
|
253
|
-
lastCheckedAt,
|
|
254
|
-
freshnessWindowSeconds,
|
|
255
|
-
ageSeconds,
|
|
256
|
-
);
|
|
257
|
-
}
|
|
258
|
-
return {
|
|
259
|
-
serviceRef,
|
|
260
|
-
verdict: row.status === "failing" ? "failing" : "delivering",
|
|
261
|
-
reason: row.status === "failing" ? "fresh-failing" : "fresh-delivering",
|
|
262
|
-
lastCheckedAt,
|
|
263
|
-
ageSeconds,
|
|
264
|
-
freshnessWindowSeconds,
|
|
265
|
-
};
|
|
266
|
-
},
|
|
267
|
-
};
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
function indeterminate(
|
|
271
|
-
serviceRef: string,
|
|
272
|
-
reason: DeliveryVerdictReason,
|
|
273
|
-
lastCheckedAt: number | null,
|
|
274
|
-
freshnessWindowSeconds: number,
|
|
275
|
-
ageSeconds: number | null = null,
|
|
276
|
-
): DeliveryAssessment {
|
|
277
|
-
return {
|
|
278
|
-
serviceRef,
|
|
279
|
-
verdict: "indeterminate",
|
|
280
|
-
reason,
|
|
281
|
-
lastCheckedAt,
|
|
282
|
-
ageSeconds,
|
|
283
|
-
freshnessWindowSeconds,
|
|
284
|
-
};
|
|
285
|
-
}
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { startMetrikFacilitatorServerFromEnv } from "./facilitator-server.js";
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Runnable entrypoint for Metrik's x402 facilitator (`metrik-x402-facilitator`).
|
|
6
|
-
* All configuration comes from the environment; see
|
|
7
|
-
* {@link parseMetrikFacilitatorEnvConfig}. Refuses to start without a
|
|
8
|
-
* settlement relayer unless `METRIK_X402_ALLOW_VERIFY_ONLY=true`.
|
|
9
|
-
*/
|
|
10
|
-
async function main(): Promise<void> {
|
|
11
|
-
const server = await startMetrikFacilitatorServerFromEnv();
|
|
12
|
-
const host = process.env.METRIK_X402_HOST ?? "0.0.0.0";
|
|
13
|
-
console.error(
|
|
14
|
-
`[metrik-x402-facilitator] listening on http://${host}:${server.boundPort}`,
|
|
15
|
-
);
|
|
16
|
-
|
|
17
|
-
const shutdown = (signal: string) => {
|
|
18
|
-
console.error(
|
|
19
|
-
`[metrik-x402-facilitator] ${signal} received — shutting down`,
|
|
20
|
-
);
|
|
21
|
-
server
|
|
22
|
-
.close()
|
|
23
|
-
.then(() => process.exit(0))
|
|
24
|
-
.catch(() => process.exit(1));
|
|
25
|
-
};
|
|
26
|
-
process.on("SIGINT", () => shutdown("SIGINT"));
|
|
27
|
-
process.on("SIGTERM", () => shutdown("SIGTERM"));
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
main().catch((error) => {
|
|
31
|
-
console.error(
|
|
32
|
-
error instanceof Error ? error.message : "failed to start x402 facilitator",
|
|
33
|
-
);
|
|
34
|
-
process.exit(1);
|
|
35
|
-
});
|