@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,191 @@
|
|
|
1
|
+
import { streamEscrowV2Abi } from "@absol-labs/shared";
|
|
2
|
+
import type { Address } from "viem";
|
|
3
|
+
import { readOnlyMetrikClient } from "../network.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Seller-side plumbing for oracle-signed probe verification.
|
|
7
|
+
*
|
|
8
|
+
* Two pieces, both deliberately small and both fail-closed:
|
|
9
|
+
*
|
|
10
|
+
* 1. {@link OracleSignerSetReader} — WHO may mint a `ProbeCapability`. Read
|
|
11
|
+
* straight off the escrow's `getOracleSigners()`, which is a public `view`.
|
|
12
|
+
* There is no registration step, no shared secret, and no trust in the
|
|
13
|
+
* oracle's own say-so: a third-party seller performs the identical read.
|
|
14
|
+
* 2. {@link ProbeRateLimiter} — how often a probe may be served for free.
|
|
15
|
+
* Without a bound, an oracle-signed capability is a licence to consume the
|
|
16
|
+
* seller's service indefinitely at no cost.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/** Resolves the escrow's current oracle signer set (lowercased). */
|
|
20
|
+
export interface OracleSignerSetReader {
|
|
21
|
+
getOracleSigners(): Promise<readonly Address[]>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Default reader: a read-only `getOracleSigners()` call. No wallet, no writes. */
|
|
25
|
+
export function createSdkOracleSignerSetReader(options: {
|
|
26
|
+
readonly escrowAddress: Address;
|
|
27
|
+
readonly rpcUrl: string;
|
|
28
|
+
/** Chain of the escrow; must be a registered network. Default: the registry default. */
|
|
29
|
+
readonly chainId?: number;
|
|
30
|
+
}): OracleSignerSetReader {
|
|
31
|
+
const metrik = readOnlyMetrikClient(options);
|
|
32
|
+
return {
|
|
33
|
+
async getOracleSigners() {
|
|
34
|
+
return (await metrik.publicClient.readContract({
|
|
35
|
+
address: options.escrowAddress,
|
|
36
|
+
abi: streamEscrowV2Abi,
|
|
37
|
+
functionName: "getOracleSigners",
|
|
38
|
+
})) as readonly Address[];
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Default freshness bound for the cached signer set, in seconds. The set only
|
|
45
|
+
* changes by an explicit escrow transaction, so a short cache costs nothing in
|
|
46
|
+
* correctness while keeping one RPC read off every probe.
|
|
47
|
+
*/
|
|
48
|
+
export const DEFAULT_ORACLE_SIGNER_CACHE_SECONDS = 60;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Bounded-staleness cache over {@link OracleSignerSetReader}.
|
|
52
|
+
*
|
|
53
|
+
* FAIL-CLOSED by construction: once the cached set is older than its TTL it is
|
|
54
|
+
* discarded, and a refresh that fails resolves to `undefined` rather than
|
|
55
|
+
* serving the stale set. A probe is a free call, so "the RPC is down" must deny
|
|
56
|
+
* — which the oracle then reports as unverifiable, never as passing.
|
|
57
|
+
*
|
|
58
|
+
* Concurrent callers share one in-flight refresh, so a burst of probes cannot
|
|
59
|
+
* fan out into a burst of RPC reads.
|
|
60
|
+
*/
|
|
61
|
+
export class CachedOracleSignerSet {
|
|
62
|
+
private cached:
|
|
63
|
+
| { readonly signers: ReadonlySet<string>; readonly at: number }
|
|
64
|
+
| undefined;
|
|
65
|
+
private inFlight: Promise<ReadonlySet<string> | undefined> | undefined;
|
|
66
|
+
|
|
67
|
+
constructor(
|
|
68
|
+
private readonly reader: OracleSignerSetReader,
|
|
69
|
+
private readonly ttlSeconds: number = DEFAULT_ORACLE_SIGNER_CACHE_SECONDS,
|
|
70
|
+
private readonly now: () => number = () => Math.floor(Date.now() / 1000),
|
|
71
|
+
) {
|
|
72
|
+
if (!Number.isFinite(ttlSeconds) || ttlSeconds <= 0) {
|
|
73
|
+
throw new Error(
|
|
74
|
+
"oracle signer cache ttlSeconds must be a positive number",
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** The current set, or `undefined` when it could not be read fresh enough. */
|
|
80
|
+
async resolve(): Promise<ReadonlySet<string> | undefined> {
|
|
81
|
+
const cached = this.cached;
|
|
82
|
+
if (cached !== undefined && this.now() - cached.at <= this.ttlSeconds) {
|
|
83
|
+
return cached.signers;
|
|
84
|
+
}
|
|
85
|
+
if (this.inFlight !== undefined) {
|
|
86
|
+
return this.inFlight;
|
|
87
|
+
}
|
|
88
|
+
const refresh = (async (): Promise<ReadonlySet<string> | undefined> => {
|
|
89
|
+
try {
|
|
90
|
+
const signers = await this.reader.getOracleSigners();
|
|
91
|
+
const set = new Set(signers.map((signer) => signer.toLowerCase()));
|
|
92
|
+
// An empty set is a real answer, and it means nobody may probe.
|
|
93
|
+
this.cached = { signers: set, at: this.now() };
|
|
94
|
+
return set;
|
|
95
|
+
} catch {
|
|
96
|
+
// Drop the stale entry rather than serving it: a signer REMOVED from
|
|
97
|
+
// the escrow must stop being able to probe within one TTL, not within
|
|
98
|
+
// "however long the RPC stays down".
|
|
99
|
+
this.cached = undefined;
|
|
100
|
+
return undefined;
|
|
101
|
+
} finally {
|
|
102
|
+
this.inFlight = undefined;
|
|
103
|
+
}
|
|
104
|
+
})();
|
|
105
|
+
this.inFlight = refresh;
|
|
106
|
+
return refresh;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** Bounds how many probes this gateway will serve for free. */
|
|
111
|
+
export interface ProbeRateLimiter {
|
|
112
|
+
/** Returns `true` (and consumes budget) IFF a probe may be served now. */
|
|
113
|
+
tryConsume(): boolean;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Default probe budget, in probes per minute.
|
|
118
|
+
*
|
|
119
|
+
* Derived from the real cadence, not picked round: the oracle checkpoints every
|
|
120
|
+
* ~15-22s, so a single vantage probes a given service at most ~4 times a
|
|
121
|
+
* minute. T1 multi-vantage fans that out across at most 9 vantages (the
|
|
122
|
+
* coordinator plus the 8-worker cap in the oracle's vantage config), giving a
|
|
123
|
+
* worst-case honest load of ~36 probes/minute. 60 leaves headroom for a
|
|
124
|
+
* re-execution sample and clock jitter while capping the free-service surface
|
|
125
|
+
* at one request per second — orders of magnitude below what would make this an
|
|
126
|
+
* economically interesting way to get the service for nothing.
|
|
127
|
+
*/
|
|
128
|
+
export const DEFAULT_MAX_PROBES_PER_MINUTE = 60;
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Fixed-window probe limiter. One counter for the whole gateway (which fronts
|
|
132
|
+
* one service), so the bound holds no matter how many oracle signers exist or
|
|
133
|
+
* how many vantages a coordinator runs.
|
|
134
|
+
*
|
|
135
|
+
* A useful secondary effect: because every probe capability is single-use and
|
|
136
|
+
* short-lived, this limiter is also what bounds the shared nonce replay cache —
|
|
137
|
+
* at most `maxPerMinute x ttl/60` probe nonces can be live at once.
|
|
138
|
+
*/
|
|
139
|
+
export class FixedWindowProbeRateLimiter implements ProbeRateLimiter {
|
|
140
|
+
private windowStart: number;
|
|
141
|
+
private count = 0;
|
|
142
|
+
|
|
143
|
+
constructor(
|
|
144
|
+
private readonly maxPerMinute: number = DEFAULT_MAX_PROBES_PER_MINUTE,
|
|
145
|
+
private readonly now: () => number = () => Math.floor(Date.now() / 1000),
|
|
146
|
+
) {
|
|
147
|
+
if (!Number.isInteger(maxPerMinute) || maxPerMinute <= 0) {
|
|
148
|
+
throw new Error("maxProbesPerMinute must be a positive integer");
|
|
149
|
+
}
|
|
150
|
+
this.windowStart = this.now();
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
tryConsume(): boolean {
|
|
154
|
+
const nowSeconds = this.now();
|
|
155
|
+
if (nowSeconds - this.windowStart >= 60) {
|
|
156
|
+
this.windowStart = nowSeconds;
|
|
157
|
+
this.count = 0;
|
|
158
|
+
}
|
|
159
|
+
if (this.count >= this.maxPerMinute) {
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
this.count += 1;
|
|
163
|
+
return true;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/** Opt-in probe verification mode for {@link CallerAuthGateway}. */
|
|
168
|
+
export interface GatewayProbeMode {
|
|
169
|
+
/**
|
|
170
|
+
* Who may mint. Defaults to a read-only `getOracleSigners()` reader built
|
|
171
|
+
* from the gateway's own escrow + RPC.
|
|
172
|
+
*/
|
|
173
|
+
readonly signerSetReader?: OracleSignerSetReader;
|
|
174
|
+
/** Freshness bound for the cached signer set. Default {@link DEFAULT_ORACLE_SIGNER_CACHE_SECONDS}. */
|
|
175
|
+
readonly signerCacheSeconds?: number;
|
|
176
|
+
/** Probe budget. Default {@link DEFAULT_MAX_PROBES_PER_MINUTE}. */
|
|
177
|
+
readonly maxProbesPerMinute?: number;
|
|
178
|
+
/** Override the limiter entirely (tests, or a shared multi-instance store). */
|
|
179
|
+
readonly rateLimiter?: ProbeRateLimiter;
|
|
180
|
+
/** Probe capability header name. Default `x-metrik-probe-capability`. */
|
|
181
|
+
readonly headerName?: string;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/** What an authorized probe tells the caller. Carries NO stream and NO payment. */
|
|
185
|
+
export interface ProbeAuthorization {
|
|
186
|
+
/** The oracle signer that minted the capability. */
|
|
187
|
+
readonly oracle: Address;
|
|
188
|
+
readonly serviceRef: `0x${string}`;
|
|
189
|
+
/** Unix seconds the capability expires at. */
|
|
190
|
+
readonly expiry: number;
|
|
191
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
deriveServiceRef,
|
|
5
|
+
recoverServiceDescriptorSigner,
|
|
6
|
+
signedServiceDescriptorSchema,
|
|
7
|
+
} from "@absol-labs/shared";
|
|
8
|
+
import { getAddress, type Address, type Hex } from "viem";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Operator-SIGNED rate bounds for the gateway's capability path
|
|
12
|
+
* (metrik-agent#121 item 5, metrik-contracts#41).
|
|
13
|
+
*
|
|
14
|
+
* `StreamEscrowV2.openStream` only requires `ratePerSecond != 0`; the seller's
|
|
15
|
+
* signed `minRatePerSecond` / `maxRatePerSecond` are checked by the buyer-side
|
|
16
|
+
* SDK and the marketplace, not by the contract. A buyer who calls the escrow
|
|
17
|
+
* directly can therefore open a stream at 1 base unit per second. The gateway
|
|
18
|
+
* is the seller's own trust boundary, so it refuses to SERVE such a stream:
|
|
19
|
+
* the on-chain `ratePerSecond` must fall inside the band the operator signed.
|
|
20
|
+
*
|
|
21
|
+
* Bounds are only ever taken from a signed descriptor that verifies here — the
|
|
22
|
+
* serviceRef re-derives from the signed content and the EIP-712 signature
|
|
23
|
+
* recovers to the descriptor's operator. `CallerAuthGateway` accepts only
|
|
24
|
+
* objects this module produced, so a hand-built `{ minRatePerSecond: 0n }`
|
|
25
|
+
* cannot slip through as "signed".
|
|
26
|
+
*/
|
|
27
|
+
export interface SignedRateBounds {
|
|
28
|
+
/** The serviceRef the bounds were signed for (re-derived, lower-case). */
|
|
29
|
+
readonly serviceRef: Hex;
|
|
30
|
+
/** The operator whose EIP-712 signature was recovered. */
|
|
31
|
+
readonly operator: Address;
|
|
32
|
+
/** Signed minimum rate, USDC base units per second; `null` = none signed. */
|
|
33
|
+
readonly minRatePerSecond: bigint | null;
|
|
34
|
+
/** Signed maximum rate, USDC base units per second; `null` = none signed. */
|
|
35
|
+
readonly maxRatePerSecond: bigint | null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export class SignedRateBoundsError extends Error {
|
|
39
|
+
constructor(message: string, options?: { readonly cause?: unknown }) {
|
|
40
|
+
super(message, options);
|
|
41
|
+
this.name = "SignedRateBoundsError";
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const verified = new WeakSet<object>();
|
|
46
|
+
|
|
47
|
+
/** Whether `bounds` came out of {@link verifySignedRateBounds} in this process. */
|
|
48
|
+
export function isVerifiedSignedRateBounds(
|
|
49
|
+
bounds: unknown,
|
|
50
|
+
): bounds is SignedRateBounds {
|
|
51
|
+
return typeof bounds === "object" && bounds !== null && verified.has(bounds);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Verify a signed service descriptor and extract its operator-signed rate band.
|
|
56
|
+
* Throws {@link SignedRateBoundsError} unless the schema parses, the serviceRef
|
|
57
|
+
* re-derives from the signed content, and the signature recovers to the
|
|
58
|
+
* descriptor's operator.
|
|
59
|
+
*/
|
|
60
|
+
export async function verifySignedRateBounds(
|
|
61
|
+
signedDescriptor: unknown,
|
|
62
|
+
): Promise<SignedRateBounds> {
|
|
63
|
+
const parsed = signedServiceDescriptorSchema.safeParse(signedDescriptor);
|
|
64
|
+
if (!parsed.success) {
|
|
65
|
+
throw new SignedRateBoundsError(
|
|
66
|
+
`not a signed service descriptor: ${parsed.error.issues
|
|
67
|
+
.map((issue) => `${issue.path.join(".")}: ${issue.message}`)
|
|
68
|
+
.join("; ")}`,
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
const signed = parsed.data;
|
|
72
|
+
const derived = deriveServiceRef(signed.descriptor).toLowerCase() as Hex;
|
|
73
|
+
if (signed.serviceRef.toLowerCase() !== derived) {
|
|
74
|
+
throw new SignedRateBoundsError(
|
|
75
|
+
`the signed descriptor's serviceRef ${signed.serviceRef} does not derive from its content (${derived}); it was tampered with or re-labelled`,
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
let signer: Address | null;
|
|
79
|
+
try {
|
|
80
|
+
signer = await recoverServiceDescriptorSigner(
|
|
81
|
+
signed.descriptor,
|
|
82
|
+
signed.signature as Hex,
|
|
83
|
+
);
|
|
84
|
+
} catch (cause) {
|
|
85
|
+
throw new SignedRateBoundsError(
|
|
86
|
+
"the signed descriptor's signature could not be recovered",
|
|
87
|
+
{ cause },
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
if (
|
|
91
|
+
signer === null ||
|
|
92
|
+
signer.toLowerCase() !== signed.descriptor.operator.toLowerCase()
|
|
93
|
+
) {
|
|
94
|
+
throw new SignedRateBoundsError(
|
|
95
|
+
`the descriptor for ${derived} is not signed by its operator ${signed.descriptor.operator}`,
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
const commercial = signed.descriptor.commercial;
|
|
99
|
+
const bounds: SignedRateBounds = Object.freeze({
|
|
100
|
+
serviceRef: derived,
|
|
101
|
+
operator: getAddress(signed.descriptor.operator),
|
|
102
|
+
minRatePerSecond:
|
|
103
|
+
commercial?.minRatePerSecond === undefined
|
|
104
|
+
? null
|
|
105
|
+
: BigInt(commercial.minRatePerSecond),
|
|
106
|
+
maxRatePerSecond:
|
|
107
|
+
commercial?.maxRatePerSecond === undefined
|
|
108
|
+
? null
|
|
109
|
+
: BigInt(commercial.maxRatePerSecond),
|
|
110
|
+
});
|
|
111
|
+
verified.add(bounds);
|
|
112
|
+
return bounds;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Load and verify the signed descriptor(s) in a JSON file: one signed
|
|
117
|
+
* descriptor object, or an array of them (one per served serviceRef, e.g. a
|
|
118
|
+
* migration alias). Fail closed on any read, parse or verification error.
|
|
119
|
+
*/
|
|
120
|
+
export async function loadSignedRateBoundsFile(
|
|
121
|
+
path: string,
|
|
122
|
+
): Promise<SignedRateBounds[]> {
|
|
123
|
+
let raw: string;
|
|
124
|
+
try {
|
|
125
|
+
raw = await readFile(path, "utf8");
|
|
126
|
+
} catch (cause) {
|
|
127
|
+
throw new SignedRateBoundsError(
|
|
128
|
+
`could not read the signed descriptor file ${path}`,
|
|
129
|
+
{ cause },
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
let json: unknown;
|
|
133
|
+
try {
|
|
134
|
+
json = JSON.parse(raw);
|
|
135
|
+
} catch (cause) {
|
|
136
|
+
throw new SignedRateBoundsError(
|
|
137
|
+
`the signed descriptor file ${path} is not valid JSON`,
|
|
138
|
+
{ cause },
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
const entries = Array.isArray(json) ? json : [json];
|
|
142
|
+
if (entries.length === 0) {
|
|
143
|
+
throw new SignedRateBoundsError(
|
|
144
|
+
`the signed descriptor file ${path} holds no descriptor`,
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
return await Promise.all(
|
|
148
|
+
entries.map((entry) => verifySignedRateBounds(entry)),
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** `undefined` when `rate` is inside the band, otherwise why it is not. */
|
|
153
|
+
export function rateOutsideBounds(
|
|
154
|
+
rate: bigint,
|
|
155
|
+
bounds: SignedRateBounds,
|
|
156
|
+
): string | undefined {
|
|
157
|
+
if (bounds.minRatePerSecond !== null && rate < bounds.minRatePerSecond) {
|
|
158
|
+
return `rate ${rate} is below the operator-signed minimum ${bounds.minRatePerSecond}`;
|
|
159
|
+
}
|
|
160
|
+
if (bounds.maxRatePerSecond !== null && rate > bounds.maxRatePerSecond) {
|
|
161
|
+
return `rate ${rate} is above the operator-signed maximum ${bounds.maxRatePerSecond}`;
|
|
162
|
+
}
|
|
163
|
+
return undefined;
|
|
164
|
+
}
|