@absol-labs/agent 0.10.0 → 0.12.0-rc.1
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 +106 -30
- package/dist/capability/invocation-capability.d.ts +20 -8
- package/dist/capability/invocation-capability.d.ts.map +1 -1
- package/dist/capability/invocation-capability.js.map +1 -1
- 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.map +1 -1
- package/dist/cli/commands/claim.js +21 -5
- 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 +51 -7
- 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 +3 -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 +121 -15
- package/dist/cli/commands/open.js.map +1 -1
- package/dist/cli/commands/pay.d.ts +19 -0
- package/dist/cli/commands/pay.d.ts.map +1 -0
- package/dist/cli/commands/pay.js +355 -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 +15 -6
- 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 +200 -21
- package/dist/cli/commands/reclaim.js.map +1 -1
- package/dist/cli/commands/refund.d.ts +13 -0
- package/dist/cli/commands/refund.d.ts.map +1 -0
- package/dist/cli/commands/refund.js +224 -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 +10 -1
- package/dist/cli/commands/status.js.map +1 -1
- package/dist/cli/commands/streams.js +9 -13
- 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 +2 -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/deps.d.ts +86 -7
- package/dist/cli/deps.d.ts.map +1 -1
- package/dist/cli/deps.js +102 -16
- 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 +83 -0
- package/dist/cli/privy.d.ts.map +1 -0
- package/dist/cli/privy.js +232 -0
- package/dist/cli/privy.js.map +1 -0
- 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 +40 -11
- 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/spend-policy.d.ts +60 -0
- package/dist/cli/spend-policy.d.ts.map +1 -0
- package/dist/cli/spend-policy.js +65 -0
- package/dist/cli/spend-policy.js.map +1 -0
- package/dist/cli/status.d.ts +3 -1
- package/dist/cli/status.d.ts.map +1 -1
- package/dist/cli/status.js +15 -3
- package/dist/cli/status.js.map +1 -1
- package/dist/cli/wallet.d.ts +109 -40
- package/dist/cli/wallet.d.ts.map +1 -1
- package/dist/cli/wallet.js +109 -44
- 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 +91 -4
- package/dist/gateway/caller-auth-gateway.d.ts.map +1 -1
- package/dist/gateway/caller-auth-gateway.js +184 -7
- package/dist/gateway/caller-auth-gateway.js.map +1 -1
- package/dist/gateway/http-server.d.ts +121 -1
- package/dist/gateway/http-server.d.ts.map +1 -1
- package/dist/gateway/http-server.js +474 -5
- 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 +215 -0
- package/dist/gateway/x402-opener-mode.d.ts.map +1 -0
- package/dist/gateway/x402-opener-mode.js +639 -0
- package/dist/gateway/x402-opener-mode.js.map +1 -0
- package/dist/index.d.ts +11 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +15 -5
- package/dist/index.js.map +1 -1
- package/dist/mandates/env.d.ts +3 -2
- package/dist/mandates/env.d.ts.map +1 -1
- package/dist/mandates/env.js +3 -2
- package/dist/mandates/env.js.map +1 -1
- package/dist/mcp/server.d.ts +100 -0
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +719 -39
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/service-invoker.d.ts +97 -0
- package/dist/mcp/service-invoker.d.ts.map +1 -0
- package/dist/mcp/service-invoker.js +167 -0
- package/dist/mcp/service-invoker.js.map +1 -0
- package/dist/mcp/x402-tools.d.ts +104 -0
- package/dist/mcp/x402-tools.d.ts.map +1 -0
- package/dist/mcp/x402-tools.js +434 -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/invoke.d.ts +23 -4
- package/dist/sdk/invoke.d.ts.map +1 -1
- package/dist/sdk/invoke.js +71 -10
- 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 +42 -17
- package/dist/x402/facilitator.d.ts.map +1 -1
- package/dist/x402/facilitator.js +49 -33
- 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 +135 -0
- package/dist/x402-buyer/pay.d.ts.map +1 -0
- package/dist/x402-buyer/pay.js +336 -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 +37 -0
- package/dist/x402-opener/facilitator-entry.js.map +1 -0
- package/dist/x402-opener/facilitator-server.d.ts +180 -0
- package/dist/x402-opener/facilitator-server.d.ts.map +1 -0
- package/dist/x402-opener/facilitator-server.js +696 -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 +240 -0
- package/dist/x402-opener/refund-relay.d.ts.map +1 -0
- package/dist/x402-opener/refund-relay.js +1240 -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 +217 -0
- package/dist/x402-opener/scheme.d.ts.map +1 -0
- package/dist/x402-opener/scheme.js +749 -0
- package/dist/x402-opener/scheme.js.map +1 -0
- package/dist/x402-opener/terms.d.ts +105 -0
- package/dist/x402-opener/terms.d.ts.map +1 -0
- package/dist/x402-opener/terms.js +212 -0
- package/dist/x402-opener/terms.js.map +1 -0
- package/dist/zktls/reclaim.d.ts +2 -2
- package/docs/agent-layer.md +13 -8
- package/docs/autonomous-privy-wallet.md +188 -0
- package/docs/cli.md +342 -53
- package/docs/langchain.md +5 -0
- package/docs/mcp-hosted.md +4 -3
- package/docs/mcp-reference.md +245 -0
- package/docs/probe-capability.md +191 -0
- package/docs/quickstart.md +54 -5
- package/docs/threat-model.md +24 -3
- package/docs/x402.md +681 -0
- package/package.json +19 -6
- package/src/capability/invocation-capability.ts +17 -1
- package/src/capability/probe-capability.ts +355 -0
- package/src/cli/args.ts +29 -0
- package/src/cli/commands/claim.ts +31 -5
- package/src/cli/commands/close.ts +56 -7
- package/src/cli/commands/invoke.ts +6 -2
- package/src/cli/commands/mcp.ts +18 -2
- package/src/cli/commands/open.ts +151 -14
- package/src/cli/commands/pay.ts +442 -0
- package/src/cli/commands/reclaim-unverified.ts +24 -6
- package/src/cli/commands/reclaim.ts +274 -19
- package/src/cli/commands/refund.ts +277 -0
- package/src/cli/commands/status.ts +17 -1
- package/src/cli/commands/streams.ts +11 -15
- package/src/cli/commands/watch.ts +7 -1
- package/src/cli/config.ts +139 -52
- package/src/cli/deps.ts +189 -25
- 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 +398 -0
- package/src/cli/reclaimability.ts +154 -0
- package/src/cli/run.ts +54 -11
- package/src/cli/scan-range.ts +2 -2
- package/src/cli/spend-policy.ts +139 -0
- package/src/cli/status.ts +14 -1
- package/src/cli/wallet.ts +189 -58
- 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 +336 -13
- package/src/gateway/http-server.ts +690 -7
- package/src/gateway/probe-auth.ts +191 -0
- package/src/gateway/signed-rate-bounds.ts +164 -0
- package/src/gateway/x402-opener-mode.ts +974 -0
- package/src/index.ts +112 -14
- package/src/mandates/env.ts +3 -2
- package/src/mcp/server.ts +973 -44
- package/src/mcp/service-invoker.ts +285 -0
- package/src/mcp/x402-tools.ts +621 -0
- package/src/network.ts +302 -0
- package/src/sdk/invoke.ts +96 -17
- 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 +85 -46
- package/src/x402-buyer/guarded-fetch.ts +213 -0
- package/src/x402-buyer/index.ts +9 -0
- package/src/x402-buyer/pay.ts +517 -0
- package/src/x402-opener/abi.ts +881 -0
- package/src/x402-opener/facilitator-entry.ts +45 -0
- package/src/x402-opener/facilitator-server.ts +1014 -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 +1743 -0
- package/src/x402-opener/relayer-queue.ts +18 -0
- package/src/x402-opener/scheme.ts +1133 -0
- package/src/x402-opener/terms.ts +300 -0
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
RefundClient,
|
|
3
|
+
RefundRelayResult,
|
|
4
|
+
RefundStatus,
|
|
5
|
+
X402PayerSigner,
|
|
6
|
+
} from "@absol-labs/sdk/x402";
|
|
7
|
+
|
|
8
|
+
import { verifyMandateOwnerSignature } from "../../mandates/mandate.js";
|
|
9
|
+
import { booleanFlag, integerFlag, stringFlag } from "../args.js";
|
|
10
|
+
import { formatUsdc } from "../amounts.js";
|
|
11
|
+
import { assertTestnetRpc, parseBytes32 } from "../config.js";
|
|
12
|
+
import type { CliX402 } from "../deps.js";
|
|
13
|
+
import { CliError, configError, usageError } from "../errors.js";
|
|
14
|
+
import { signRecoveryMandate } from "../mandate.js";
|
|
15
|
+
import { isPrivyWalletSource, resolveCliWallet } from "../wallet.js";
|
|
16
|
+
import type { CommandContext, CommandSpec } from "../context.js";
|
|
17
|
+
|
|
18
|
+
/** Environment fallback for `--refund-url`. */
|
|
19
|
+
export const REFUND_URL_ENV = "METRIK_AGENT_REFUND_URL";
|
|
20
|
+
|
|
21
|
+
const DEFAULT_ATTEMPTS = 5;
|
|
22
|
+
const DEFAULT_MAX_WAIT_SECONDS = 60;
|
|
23
|
+
|
|
24
|
+
export const refundCommand: CommandSpec = {
|
|
25
|
+
name: "refund",
|
|
26
|
+
summary:
|
|
27
|
+
"Recover the unearned part of an x402 payment through the gasless refund relay",
|
|
28
|
+
usage:
|
|
29
|
+
"metrik refund <streamId> --refund-url <url> [--close] [--reclaim] [--attempts 5] [--max-wait 60]",
|
|
30
|
+
flags: ["refund-url", "close", "reclaim", "attempts", "max-wait"],
|
|
31
|
+
// `--close` signs the payer's EIP-712 Close (off-chain); the RELAY broadcasts
|
|
32
|
+
// and pays the gas. `--reclaim` needs no wallet at all: `opener.reclaim` is
|
|
33
|
+
// permissionless and always pays `payerOf(streamId)`.
|
|
34
|
+
signs: false,
|
|
35
|
+
requiresKey: false,
|
|
36
|
+
async run(context: CommandContext): Promise<void> {
|
|
37
|
+
const { args, config, env, out, deps } = context;
|
|
38
|
+
if (deps.x402 === undefined) {
|
|
39
|
+
throw new CliError(
|
|
40
|
+
"this CLI build has no x402 dependency wired (CliDependencies.x402)",
|
|
41
|
+
{ exitCode: 4 },
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
const x402: CliX402 = deps.x402;
|
|
45
|
+
const [streamIdArg] = args.positionals;
|
|
46
|
+
if (streamIdArg === undefined) {
|
|
47
|
+
throw usageError("metrik refund requires a <streamId>");
|
|
48
|
+
}
|
|
49
|
+
const streamId = parseBytes32(streamIdArg, "<streamId>");
|
|
50
|
+
const refundUrl = resolveRefundUrl(
|
|
51
|
+
stringFlag(args, "refund-url") ?? env[REFUND_URL_ENV],
|
|
52
|
+
streamId,
|
|
53
|
+
);
|
|
54
|
+
const doClose = booleanFlag(args, "close");
|
|
55
|
+
const doReclaim = booleanFlag(args, "reclaim");
|
|
56
|
+
const attempts = integerFlag(args, "attempts") ?? DEFAULT_ATTEMPTS;
|
|
57
|
+
const maxWait = integerFlag(args, "max-wait") ?? DEFAULT_MAX_WAIT_SECONDS;
|
|
58
|
+
if (attempts < 1) throw usageError("--attempts must be at least 1");
|
|
59
|
+
|
|
60
|
+
const read = deps.createReadClient(config);
|
|
61
|
+
await assertTestnetRpc(config, () => read.getChainId());
|
|
62
|
+
// Bind the relay to the stream AS THE CHAIN SEES IT: the opener comes from
|
|
63
|
+
// `factory.isOpener(buyer)` on-chain, never from the relay's answer.
|
|
64
|
+
const stream = await read.getStreamV2(streamId);
|
|
65
|
+
const opener = stream.opener ?? null;
|
|
66
|
+
if (opener === null) {
|
|
67
|
+
throw configError(
|
|
68
|
+
`stream ${streamId} was not opened by an x402 opener, so the refund relay does not serve it`,
|
|
69
|
+
`the buyer is ${stream.buyer}, an ordinary wallet: use \`metrik close ${streamId}\` and \`metrik reclaim ${streamId}\` with that wallet.`,
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const refunds = await x402.refundClient(config, {
|
|
74
|
+
refundUrl,
|
|
75
|
+
streamId,
|
|
76
|
+
opener,
|
|
77
|
+
maxAttempts: attempts,
|
|
78
|
+
maxDelayMs: maxWait * 1_000,
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
let closeResult: RefundRelayResult | null = null;
|
|
82
|
+
if (doClose) {
|
|
83
|
+
const wallet = await resolveCliWallet(env, {
|
|
84
|
+
purpose: "capability",
|
|
85
|
+
note: out.note,
|
|
86
|
+
rpcUrl: config.rpcUrl,
|
|
87
|
+
});
|
|
88
|
+
if (isPrivyWalletSource(wallet.source)) {
|
|
89
|
+
throw configError(
|
|
90
|
+
"metrik refund --close cannot sign with a Privy wallet",
|
|
91
|
+
"the payer signs the opener's EIP-712 Close, which the Privy broker's typed-data policy does not allow. Use the key that paid.",
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
// Buyer recovery: authorized on the mandate OWNER's signature alone, like
|
|
95
|
+
// `metrik close` — never on caps or expiry, so nothing can strand the
|
|
96
|
+
// payer's own money. The SDK then refuses a signer that is not
|
|
97
|
+
// `payerOf(streamId)` before anything is signed for the relay.
|
|
98
|
+
const mandate = await signRecoveryMandate(wallet.account, config.chainId);
|
|
99
|
+
const decision = await verifyMandateOwnerSignature(mandate);
|
|
100
|
+
if (!decision.allowed) {
|
|
101
|
+
throw new CliError(`mandate denied: ${decision.reason}`, {
|
|
102
|
+
exitCode: 3,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
closeResult = await relay(() =>
|
|
106
|
+
refunds.close({
|
|
107
|
+
signer: wallet.account as unknown as X402PayerSigner,
|
|
108
|
+
}),
|
|
109
|
+
);
|
|
110
|
+
out.note(
|
|
111
|
+
`close relayed in ${closeResult.transaction}${closeResult.verified ? " (receipt verified on-chain)" : ""}`,
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
let reclaimResult: RefundRelayResult | null = null;
|
|
116
|
+
if (doReclaim) {
|
|
117
|
+
reclaimResult = await relay(() => refunds.reclaim());
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const status = await relay(() => refunds.status());
|
|
121
|
+
out.result({
|
|
122
|
+
streamId,
|
|
123
|
+
opener,
|
|
124
|
+
payer: status.payer,
|
|
125
|
+
status: status.status,
|
|
126
|
+
reclaimed: status.reclaimed,
|
|
127
|
+
reclaimable: status.reclaimable,
|
|
128
|
+
next: status.next,
|
|
129
|
+
close:
|
|
130
|
+
closeResult === null
|
|
131
|
+
? null
|
|
132
|
+
: {
|
|
133
|
+
transaction: closeResult.transaction,
|
|
134
|
+
verified: closeResult.verified,
|
|
135
|
+
},
|
|
136
|
+
reclaim:
|
|
137
|
+
reclaimResult === null
|
|
138
|
+
? null
|
|
139
|
+
: {
|
|
140
|
+
transaction: reclaimResult.transaction,
|
|
141
|
+
amount: reclaimResult.amount,
|
|
142
|
+
verified: reclaimResult.verified,
|
|
143
|
+
},
|
|
144
|
+
refundIsAutomatic: false,
|
|
145
|
+
});
|
|
146
|
+
out.print(`x402 stream ${streamId}`);
|
|
147
|
+
out.table(statusRows(status, closeResult, reclaimResult));
|
|
148
|
+
out.print("");
|
|
149
|
+
out.print(nextStep(status, streamId, refundUrl));
|
|
150
|
+
},
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Accepts the relay's full stream URL, a `{streamId}` template, or the relay's
|
|
155
|
+
* base origin (`https://relay.example` -> `https://relay.example/streams/<id>`).
|
|
156
|
+
*/
|
|
157
|
+
export function resolveRefundUrl(
|
|
158
|
+
raw: string | undefined,
|
|
159
|
+
streamId: `0x${string}`,
|
|
160
|
+
): string {
|
|
161
|
+
if (raw === undefined || raw.trim().length === 0) {
|
|
162
|
+
throw usageError(
|
|
163
|
+
`metrik refund needs the seller's refund relay: pass --refund-url (printed by \`metrik pay\`) or set ${REFUND_URL_ENV}`,
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
const value = raw.trim();
|
|
167
|
+
if (value.includes("{streamId}")) return value;
|
|
168
|
+
let url: URL;
|
|
169
|
+
try {
|
|
170
|
+
url = new URL(value);
|
|
171
|
+
} catch {
|
|
172
|
+
throw usageError(
|
|
173
|
+
`--refund-url must be an absolute http(s) URL (got "${value}")`,
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
if (/\/streams\/0x[0-9a-fA-F]{64}\/?$/.test(url.pathname)) return value;
|
|
177
|
+
url.pathname = `${url.pathname.replace(/\/+$/, "")}/streams/${streamId}`;
|
|
178
|
+
return url.toString();
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
async function relay<T>(task: () => Promise<T>): Promise<T> {
|
|
182
|
+
try {
|
|
183
|
+
return await task();
|
|
184
|
+
} catch (error) {
|
|
185
|
+
throw explainRefundFailure(error);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export function explainRefundFailure(error: unknown): unknown {
|
|
190
|
+
if (!(error instanceof Error)) return error;
|
|
191
|
+
switch (error.name) {
|
|
192
|
+
case "RefundNotReadyError": {
|
|
193
|
+
const retryAt = (error as { retryAt?: number | null }).retryAt;
|
|
194
|
+
return new CliError(error.message, {
|
|
195
|
+
exitCode: 3,
|
|
196
|
+
hint:
|
|
197
|
+
retryAt === undefined || retryAt === null
|
|
198
|
+
? "the relay is not ready to reclaim yet (the settlement window is open). Re-run later with --reclaim."
|
|
199
|
+
: `the relay can reclaim from ${new Date(retryAt * 1000).toISOString()}. Re-run then with --reclaim (or raise --max-wait to wait here).`,
|
|
200
|
+
cause: error,
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
case "RefundRelayError":
|
|
204
|
+
return new CliError(error.message, {
|
|
205
|
+
exitCode: 3,
|
|
206
|
+
hint: `relay refusal${(error as { code?: string }).code === undefined ? "" : ` (${(error as { code?: string }).code})`}. The payer can always recover on-chain without the relay: \`metrik close\` (as the payer, needs gas) and \`metrik reclaim\` (anyone).`,
|
|
207
|
+
cause: error,
|
|
208
|
+
});
|
|
209
|
+
case "NotStreamPayerError":
|
|
210
|
+
return new CliError(error.message, {
|
|
211
|
+
exitCode: 3,
|
|
212
|
+
hint: "only the address that paid (payerOf(streamId)) can sign the close. Nothing was signed.",
|
|
213
|
+
cause: error,
|
|
214
|
+
});
|
|
215
|
+
default:
|
|
216
|
+
return error;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function statusRows(
|
|
221
|
+
status: RefundStatus,
|
|
222
|
+
closeResult: RefundRelayResult | null,
|
|
223
|
+
reclaimResult: RefundRelayResult | null,
|
|
224
|
+
): (readonly [string, string])[] {
|
|
225
|
+
const rows: (readonly [string, string])[] = [
|
|
226
|
+
[
|
|
227
|
+
"status",
|
|
228
|
+
status.reclaimed ? `${status.status} (reclaimed)` : status.status,
|
|
229
|
+
],
|
|
230
|
+
["payer", status.payer],
|
|
231
|
+
["opener", status.opener],
|
|
232
|
+
[
|
|
233
|
+
"reclaimable",
|
|
234
|
+
status.reclaimable === null ? "-" : formatUsdc(status.reclaimable),
|
|
235
|
+
],
|
|
236
|
+
["next", status.next.action],
|
|
237
|
+
];
|
|
238
|
+
if (closeResult !== null) {
|
|
239
|
+
rows.push([
|
|
240
|
+
"close tx",
|
|
241
|
+
`${closeResult.transaction}${closeResult.verified ? " (verified)" : " (relay-reported)"}`,
|
|
242
|
+
]);
|
|
243
|
+
}
|
|
244
|
+
if (reclaimResult !== null) {
|
|
245
|
+
rows.push([
|
|
246
|
+
"reclaim tx",
|
|
247
|
+
`${reclaimResult.transaction}${reclaimResult.verified ? " (verified)" : " (relay-reported)"}`,
|
|
248
|
+
]);
|
|
249
|
+
if (reclaimResult.amount !== null) {
|
|
250
|
+
rows.push(["refunded to payer", formatUsdc(reclaimResult.amount)]);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
return rows;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function nextStep(
|
|
257
|
+
status: RefundStatus,
|
|
258
|
+
streamId: `0x${string}`,
|
|
259
|
+
refundUrl: string,
|
|
260
|
+
): string {
|
|
261
|
+
if (status.reclaimed) {
|
|
262
|
+
return "Reclaimed: the unearned remainder was returned to the payer.";
|
|
263
|
+
}
|
|
264
|
+
const base = `metrik refund ${streamId} --refund-url ${refundUrl}`;
|
|
265
|
+
switch (status.next.action) {
|
|
266
|
+
case "close":
|
|
267
|
+
return `Refunds are NOT automatic. The stream is still running: stop it with ${base} --close --reclaim`;
|
|
268
|
+
case "reclaim":
|
|
269
|
+
return `Refunds are NOT automatic. Reclaim the unearned remainder with ${base} --reclaim`;
|
|
270
|
+
case "wait":
|
|
271
|
+
return `Refunds are NOT automatic, and the settlement window is still open${status.next.retryAt === undefined ? "" : ` until ${new Date(status.next.retryAt * 1000).toISOString()}`}. Then run ${base} --reclaim`;
|
|
272
|
+
default:
|
|
273
|
+
return `Refunds are NOT automatic. The relay says: ${status.next.message ?? status.next.action}.`;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
export type { RefundClient };
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
streamReportRows,
|
|
8
8
|
} from "../status.js";
|
|
9
9
|
import type { CommandContext, CommandSpec } from "../context.js";
|
|
10
|
+
import { escrowGenerationFor } from "../../escrow-generation.js";
|
|
10
11
|
|
|
11
12
|
export const statusCommand: CommandSpec = {
|
|
12
13
|
name: "status",
|
|
@@ -28,7 +29,12 @@ export const statusCommand: CommandSpec = {
|
|
|
28
29
|
// diverge from index 5 onward, so a wrong-chain read does not revert — it
|
|
29
30
|
// silently decodes something else into fields with the right names.
|
|
30
31
|
await assertTestnetRpc(config, () => read.getChainId());
|
|
31
|
-
const report = await readStreamReport(
|
|
32
|
+
const report = await readStreamReport(
|
|
33
|
+
read,
|
|
34
|
+
streamId,
|
|
35
|
+
deps.nowSeconds(),
|
|
36
|
+
escrowGenerationFor(config.chainId, config.escrow),
|
|
37
|
+
);
|
|
32
38
|
|
|
33
39
|
out.result(streamReportJson(report));
|
|
34
40
|
// Who attests, before the numbers. Printed on stderr so it survives `--json`
|
|
@@ -40,6 +46,16 @@ export const statusCommand: CommandSpec = {
|
|
|
40
46
|
if (disclosure !== null) out.note(disclosure);
|
|
41
47
|
out.print(`Stream ${streamId}`);
|
|
42
48
|
out.table(streamReportRows(report));
|
|
49
|
+
const opener = report.stream.opener ?? null;
|
|
50
|
+
if (opener !== null) {
|
|
51
|
+
out.print("");
|
|
52
|
+
out.print(
|
|
53
|
+
`Bought over x402: the escrow accepts close/reclaim only from the opener ${opener}. ` +
|
|
54
|
+
`\`metrik close ${streamId}\` (signed by the payer ${report.stream.payer}) routes through it; ` +
|
|
55
|
+
`\`metrik reclaim ${streamId}\` sends the permissionless opener.reclaim, and the refund always goes to the payer. ` +
|
|
56
|
+
"A payer with no gas can use the seller's relay instead: `metrik refund <streamId> --refund-url <url> --close --reclaim`. Nothing refunds automatically.",
|
|
57
|
+
);
|
|
58
|
+
}
|
|
43
59
|
if (report.settlement.pending) {
|
|
44
60
|
out.print("");
|
|
45
61
|
out.print(
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { BASE_SEPOLIA_ESCROW_V2 } from "@absol-labs/sdk";
|
|
2
1
|
import { getAddress, isAddress } from "viem";
|
|
3
2
|
|
|
4
3
|
import { integerFlag, stringFlag } from "../args.js";
|
|
5
4
|
import { formatUsdc } from "../amounts.js";
|
|
6
5
|
import {
|
|
7
|
-
BASE_SEPOLIA_ESCROW_V2_DEPLOY_BLOCK,
|
|
8
6
|
assertTestnetRpc,
|
|
7
|
+
escrowDeployBlock,
|
|
8
|
+
type CliConfig,
|
|
9
9
|
} from "../config.js";
|
|
10
10
|
import { CliError, configError, usageError } from "../errors.js";
|
|
11
11
|
import { escrowScanRange } from "../scan-range.js";
|
|
@@ -32,7 +32,7 @@ const SCAN_CONCURRENCY = 6;
|
|
|
32
32
|
/**
|
|
33
33
|
* Attempts per window before it is reported as unscanned. A public RPC answers
|
|
34
34
|
* a burst of `eth_getLogs` with an occasional 429 — measured against
|
|
35
|
-
*
|
|
35
|
+
* the network's public RPC, 2 of 212 windows on a full-history scan — and a transient
|
|
36
36
|
* rate-limit is NOT a window that cannot be scanned. Declaring it one would
|
|
37
37
|
* make the "this scan is incomplete" warning routine, and a warning that fires
|
|
38
38
|
* on every healthy run is a warning nobody reads on the run that matters.
|
|
@@ -71,7 +71,7 @@ export const streamsCommand: CommandSpec = {
|
|
|
71
71
|
// else into fields with the right names.
|
|
72
72
|
await assertTestnetRpc(config, () => read.getChainId());
|
|
73
73
|
|
|
74
|
-
const anchor = resolveAnchorBlock(args, config
|
|
74
|
+
const anchor = resolveAnchorBlock(args, config);
|
|
75
75
|
const head = await read.blockNumber();
|
|
76
76
|
const range = escrowScanRange(head, anchor.block);
|
|
77
77
|
|
|
@@ -234,7 +234,7 @@ async function resolveScanAddress(
|
|
|
234
234
|
if (wallet === null) {
|
|
235
235
|
throw configError(
|
|
236
236
|
"metrik streams needs a wallet to know whose streams to list",
|
|
237
|
-
`pass --address 0x… to list any address's streams without one, or set ${PRIVATE_KEY_ENV} (or the CDP variables).`,
|
|
237
|
+
`pass --address 0x… to list any address's streams without one, or set ${PRIVATE_KEY_ENV} (or the CDP or Privy variables).`,
|
|
238
238
|
);
|
|
239
239
|
}
|
|
240
240
|
return wallet.account.address;
|
|
@@ -251,25 +251,21 @@ async function resolveScanAddress(
|
|
|
251
251
|
*/
|
|
252
252
|
function resolveAnchorBlock(
|
|
253
253
|
args: CommandContext["args"],
|
|
254
|
-
|
|
254
|
+
config: CliConfig,
|
|
255
255
|
): { readonly block: bigint; readonly canary: bigint | null } {
|
|
256
256
|
const override = integerFlag(args, "from-block");
|
|
257
|
-
const
|
|
258
|
-
escrow.toLowerCase() === BASE_SEPOLIA_ESCROW_V2.toLowerCase();
|
|
257
|
+
const deployBlock = escrowDeployBlock(config);
|
|
259
258
|
|
|
260
259
|
if (override !== undefined) {
|
|
261
260
|
return { block: BigInt(override), canary: null };
|
|
262
261
|
}
|
|
263
|
-
if (
|
|
262
|
+
if (deployBlock === undefined) {
|
|
264
263
|
throw configError(
|
|
265
|
-
`no scan anchor is known for escrow ${escrow}`,
|
|
266
|
-
|
|
264
|
+
`no scan anchor is known for escrow ${config.escrow}`,
|
|
265
|
+
`the scan is anchored at the escrow's deploy block, and the network registry only records it for ${config.network.name}'s registered escrow (${config.network.escrow.address}). Pass --from-block <deploy block> for a different escrow.`,
|
|
267
266
|
);
|
|
268
267
|
}
|
|
269
|
-
return {
|
|
270
|
-
block: BASE_SEPOLIA_ESCROW_V2_DEPLOY_BLOCK,
|
|
271
|
-
canary: BASE_SEPOLIA_ESCROW_V2_DEPLOY_BLOCK,
|
|
272
|
-
};
|
|
268
|
+
return { block: deployBlock, canary: deployBlock };
|
|
273
269
|
}
|
|
274
270
|
|
|
275
271
|
/**
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
streamReportRows,
|
|
8
8
|
} from "../status.js";
|
|
9
9
|
import type { CommandContext, CommandSpec } from "../context.js";
|
|
10
|
+
import { escrowGenerationFor } from "../../escrow-generation.js";
|
|
10
11
|
|
|
11
12
|
const DEFAULT_INTERVAL_SECONDS = 15;
|
|
12
13
|
|
|
@@ -43,7 +44,12 @@ export const watchCommand: CommandSpec = {
|
|
|
43
44
|
|
|
44
45
|
let report;
|
|
45
46
|
try {
|
|
46
|
-
report = await readStreamReport(
|
|
47
|
+
report = await readStreamReport(
|
|
48
|
+
read,
|
|
49
|
+
streamId,
|
|
50
|
+
deps.nowSeconds(),
|
|
51
|
+
escrowGenerationFor(config.chainId, config.escrow),
|
|
52
|
+
);
|
|
47
53
|
} catch (error) {
|
|
48
54
|
// A transient RPC blip must not end a long watch.
|
|
49
55
|
out.note(`read failed, retrying: ${describeError(error)}`);
|
package/src/cli/config.ts
CHANGED
|
@@ -1,76 +1,97 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
BASE_SEPOLIA_USDC,
|
|
4
|
-
DEFAULT_ORACLE_BASE_URL,
|
|
5
|
-
DEFAULT_PUBLIC_REGISTRY_URL,
|
|
6
|
-
} from "@absol-labs/sdk";
|
|
7
|
-
import { isAddress, getAddress } from "viem";
|
|
8
|
-
import { baseSepolia } from "viem/chains";
|
|
1
|
+
import type { MetrikNetwork } from "@absol-labs/sdk";
|
|
2
|
+
import { isAddress, getAddress, type Chain } from "viem";
|
|
9
3
|
|
|
4
|
+
import {
|
|
5
|
+
allowedNetworks,
|
|
6
|
+
describeAllowedNetworks,
|
|
7
|
+
registeredDeployment,
|
|
8
|
+
METRIK_NETWORK_ENV,
|
|
9
|
+
resolveAgentNetwork,
|
|
10
|
+
UnsupportedNetworkError,
|
|
11
|
+
} from "../network.js";
|
|
10
12
|
import { CliError, configError, usageError } from "./errors.js";
|
|
11
13
|
import { stringFlag, integerFlag, type ParsedArgs } from "./args.js";
|
|
12
14
|
|
|
13
15
|
/**
|
|
14
|
-
* TESTNET ONLY — the
|
|
16
|
+
* TESTNET ONLY — the CLI settles only on registered networks the SDK registry
|
|
17
|
+
* marks `testnet: true`.
|
|
15
18
|
*
|
|
16
19
|
* Metrik's escrow moves real USDC the moment it is pointed at a mainnet chain,
|
|
17
20
|
* and this CLI signs with a raw private key read from the environment. So the
|
|
18
|
-
* chain is an allowlist, not a default:
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
21
|
+
* chain is an allowlist, not a default: a chain the registry does not know, or
|
|
22
|
+
* one it knows as a mainnet, is refused outright, with no override flag. There
|
|
23
|
+
* is deliberately no `--allow-mainnet` — an escape hatch that re-enables real
|
|
24
|
+
* funds would defeat the guarantee.
|
|
22
25
|
*
|
|
23
|
-
* The allowlist is enforced TWICE: once against the configured/declared
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
26
|
+
* The allowlist is enforced TWICE: once against the configured/declared network,
|
|
27
|
+
* and again against the chain id the RPC endpoint actually reports before any
|
|
28
|
+
* fund-moving command signs anything (see `assertTestnetRpc`). The second check
|
|
29
|
+
* is what stops a testnet `--chain-id` paired with a mainnet `--rpc-url`.
|
|
27
30
|
*/
|
|
28
|
-
export const
|
|
31
|
+
export const CLI_NETWORK_POLICY = { testnetOnly: true } as const;
|
|
32
|
+
|
|
33
|
+
/** Chain ids of every registered testnet this CLI will settle against. */
|
|
34
|
+
export function testnetChainIds(): number[] {
|
|
35
|
+
return allowedNetworks(CLI_NETWORK_POLICY).map((network) => network.chain.id);
|
|
36
|
+
}
|
|
29
37
|
|
|
30
38
|
/**
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
* Verified against Base Sepolia on 2026-09-17: `eth_getLogs` for this single
|
|
35
|
-
* block returns 6 escrow logs (the deployment's own events). History is
|
|
36
|
-
* immutable, so that is a stable known answer forever, which is what lets the
|
|
37
|
-
* same block serve as the log-retention canary in `metrik streams`.
|
|
38
|
-
*
|
|
39
|
-
* It is tied to that ONE address. A run pointed at a different `--escrow` has
|
|
40
|
-
* no anchor and must be given `--from-block`, rather than silently scanning
|
|
41
|
-
* from a block that means nothing for it.
|
|
39
|
+
* @deprecated Snapshot of {@link testnetChainIds} at import time. A network
|
|
40
|
+
* registered later is not in it; call `testnetChainIds()` instead.
|
|
42
41
|
*/
|
|
43
|
-
export const
|
|
42
|
+
export const TESTNET_CHAIN_IDS: readonly number[] = testnetChainIds();
|
|
44
43
|
|
|
45
44
|
export interface CliConfig {
|
|
46
|
-
|
|
45
|
+
/** The registered network this run settles on (from the SDK registry). */
|
|
46
|
+
readonly network: MetrikNetwork;
|
|
47
|
+
readonly chain: Chain;
|
|
47
48
|
readonly chainId: number;
|
|
48
49
|
readonly rpcUrl: string;
|
|
49
50
|
readonly escrow: `0x${string}`;
|
|
50
51
|
readonly usdc: `0x${string}`;
|
|
51
52
|
readonly oracleBaseUrl: string;
|
|
52
53
|
readonly registryUrl: string;
|
|
54
|
+
/**
|
|
55
|
+
* Set ONLY for an `--escrow` the registry does not record, from the explicit
|
|
56
|
+
* `--escrow-generation`: the SDK then opts out of its code-hash pin
|
|
57
|
+
* (`allowUnregisteredEscrow`). A registered escrow's generation comes from
|
|
58
|
+
* the registry.
|
|
59
|
+
*/
|
|
60
|
+
readonly unregisteredEscrowGeneration?: "v2" | "v3";
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* The block the configured escrow was deployed at — the anchor of every escrow
|
|
65
|
+
* log scan — or `undefined` when the escrow is not the network's registered
|
|
66
|
+
* escrow (a run pointed at a different `--escrow` has no anchor and must be
|
|
67
|
+
* given `--from-block`, rather than silently scanning from a block that means
|
|
68
|
+
* nothing for it).
|
|
69
|
+
*/
|
|
70
|
+
export function escrowDeployBlock(config: CliConfig): bigint | undefined {
|
|
71
|
+
return registeredDeployment(config.network, config.escrow)?.escrow
|
|
72
|
+
.deployBlock;
|
|
53
73
|
}
|
|
54
74
|
|
|
55
75
|
/**
|
|
56
|
-
* Resolves settlement configuration from flags, then env, then the
|
|
57
|
-
*
|
|
76
|
+
* Resolves settlement configuration from flags, then env, then the network's
|
|
77
|
+
* registry defaults — so `metrik discover` works with nothing configured at
|
|
58
78
|
* all, while every value stays overridable.
|
|
79
|
+
*
|
|
80
|
+
* The network is selected by `--network` / `METRIK_NETWORK` (registry id,
|
|
81
|
+
* chain id or CAIP-2 id) or by `--chain-id` / `METRIK_AGENT_CHAIN_ID`; giving
|
|
82
|
+
* both is allowed only when they agree.
|
|
59
83
|
*/
|
|
60
84
|
export function resolveConfig(
|
|
61
85
|
args: ParsedArgs,
|
|
62
86
|
env: NodeJS.ProcessEnv,
|
|
63
87
|
): CliConfig {
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
parseEnvChainId(env.METRIK_AGENT_CHAIN_ID) ??
|
|
67
|
-
baseSepolia.id;
|
|
68
|
-
assertTestnetChainId(chainId, "the configured chain id");
|
|
88
|
+
const network = resolveCliNetwork(args, env);
|
|
89
|
+
const chainId = network.chain.id;
|
|
69
90
|
|
|
70
91
|
const rpcUrl =
|
|
71
92
|
stringFlag(args, "rpc-url") ??
|
|
72
93
|
env.METRIK_AGENT_RPC_URL ??
|
|
73
|
-
|
|
94
|
+
network.rpcUrls[0];
|
|
74
95
|
if (rpcUrl === undefined || !isHttpUrl(rpcUrl)) {
|
|
75
96
|
throw configError(
|
|
76
97
|
`RPC URL must be an http(s) URL (got "${rpcUrl ?? "undefined"}")`,
|
|
@@ -78,35 +99,101 @@ export function resolveConfig(
|
|
|
78
99
|
);
|
|
79
100
|
}
|
|
80
101
|
|
|
102
|
+
const escrow = resolveAddress(
|
|
103
|
+
stringFlag(args, "escrow") ?? env.METRIK_AGENT_ESCROW,
|
|
104
|
+
network.escrow.address,
|
|
105
|
+
"escrow",
|
|
106
|
+
);
|
|
107
|
+
const declared = (
|
|
108
|
+
stringFlag(args, "escrow-generation") ?? env.METRIK_AGENT_ESCROW_GENERATION
|
|
109
|
+
)
|
|
110
|
+
?.trim()
|
|
111
|
+
.toLowerCase();
|
|
112
|
+
if (declared !== undefined && declared !== "v2" && declared !== "v3") {
|
|
113
|
+
throw configError(
|
|
114
|
+
`--escrow-generation must be "v2" or "v3" (got "${declared}")`,
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
const deployment = registeredDeployment(network, escrow);
|
|
118
|
+
if (deployment === undefined && declared === undefined) {
|
|
119
|
+
throw configError(
|
|
120
|
+
`escrow ${escrow} is not a registered deployment of ${network.name}`,
|
|
121
|
+
`the SDK pins registered escrows by code hash; for your own deployment pass --escrow-generation v2|v3 (it then only checks the interface, which any contract can fake). Registered: ${[network.escrow, ...network.legacy.map((d) => d.escrow)].map((e) => `${e.address} (${e.generation})`).join(", ")}.`,
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
if (
|
|
125
|
+
deployment !== undefined &&
|
|
126
|
+
declared !== undefined &&
|
|
127
|
+
declared !== deployment.escrow.generation
|
|
128
|
+
) {
|
|
129
|
+
throw configError(
|
|
130
|
+
`--escrow-generation ${declared} contradicts the registry: ${escrow} is the ${deployment.escrow.generation} escrow`,
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
81
134
|
return {
|
|
82
|
-
|
|
135
|
+
network,
|
|
136
|
+
chain: network.chain,
|
|
83
137
|
chainId,
|
|
84
138
|
rpcUrl,
|
|
85
|
-
escrow
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
),
|
|
139
|
+
escrow,
|
|
140
|
+
...(deployment === undefined
|
|
141
|
+
? { unregisteredEscrowGeneration: declared as "v2" | "v3" }
|
|
142
|
+
: {}),
|
|
90
143
|
usdc: resolveAddress(
|
|
91
144
|
stringFlag(args, "usdc") ?? env.METRIK_AGENT_USDC,
|
|
92
|
-
|
|
145
|
+
network.usdc.address,
|
|
93
146
|
"usdc",
|
|
94
147
|
),
|
|
95
148
|
oracleBaseUrl: resolveHttpUrl(
|
|
96
149
|
stringFlag(args, "oracle-url") ?? env.METRIK_AGENT_ORACLE_URL,
|
|
97
|
-
|
|
150
|
+
network.oracleBaseUrl,
|
|
98
151
|
"oracle-url",
|
|
99
152
|
),
|
|
100
153
|
registryUrl: resolveHttpUrl(
|
|
101
154
|
stringFlag(args, "registry-url") ?? env.METRIK_AGENT_REGISTRY_URL,
|
|
102
|
-
|
|
155
|
+
network.publicRegistryUrl ?? `${network.oracleBaseUrl}/listings`,
|
|
103
156
|
"registry-url",
|
|
104
157
|
),
|
|
105
158
|
};
|
|
106
159
|
}
|
|
107
160
|
|
|
161
|
+
function resolveCliNetwork(
|
|
162
|
+
args: ParsedArgs,
|
|
163
|
+
env: NodeJS.ProcessEnv,
|
|
164
|
+
): MetrikNetwork {
|
|
165
|
+
const chainId =
|
|
166
|
+
integerFlag(args, "chain-id") ?? parseEnvChainId(env.METRIK_AGENT_CHAIN_ID);
|
|
167
|
+
const selector = stringFlag(args, "network") ?? env[METRIK_NETWORK_ENV];
|
|
168
|
+
if (chainId !== undefined)
|
|
169
|
+
assertTestnetChainId(chainId, "the configured chain id");
|
|
170
|
+
let network: MetrikNetwork;
|
|
171
|
+
try {
|
|
172
|
+
network = resolveAgentNetwork(
|
|
173
|
+
selector !== undefined && selector.trim().length > 0 ? selector : chainId,
|
|
174
|
+
CLI_NETWORK_POLICY,
|
|
175
|
+
);
|
|
176
|
+
} catch (error) {
|
|
177
|
+
if (error instanceof UnsupportedNetworkError) {
|
|
178
|
+
throw configError(
|
|
179
|
+
`refusing to run against network "${error.selector}": it is not a supported testnet`,
|
|
180
|
+
`${error.message}. There is no override flag.`,
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
throw error;
|
|
184
|
+
}
|
|
185
|
+
if (chainId !== undefined && chainId !== network.chain.id) {
|
|
186
|
+
throw configError(
|
|
187
|
+
`--chain-id ${chainId} does not match --network ${network.id} (chain ${network.chain.id})`,
|
|
188
|
+
"give one of them, or make them agree.",
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
return network;
|
|
192
|
+
}
|
|
108
193
|
/** Flags every command accepts for settlement configuration. */
|
|
109
194
|
export const CONFIG_FLAGS: readonly string[] = [
|
|
195
|
+
"network",
|
|
196
|
+
"escrow-generation",
|
|
110
197
|
"chain-id",
|
|
111
198
|
"rpc-url",
|
|
112
199
|
"escrow",
|
|
@@ -119,12 +206,12 @@ export const CONFIG_FLAGS: readonly string[] = [
|
|
|
119
206
|
export const GLOBAL_FLAGS: readonly string[] = ["json", "help"];
|
|
120
207
|
|
|
121
208
|
export function assertTestnetChainId(chainId: number, source: string): void {
|
|
122
|
-
if (
|
|
209
|
+
if (testnetChainIds().includes(chainId)) return;
|
|
123
210
|
throw configError(
|
|
124
211
|
`refusing to run against chain ${chainId}: ${source} is not a supported testnet`,
|
|
125
|
-
`this CLI signs with a raw private key and settles real value, so it is testnet-only. Supported: ${
|
|
126
|
-
|
|
127
|
-
)}
|
|
212
|
+
`this CLI signs with a raw private key and settles real value, so it is testnet-only. Supported (registered testnets): ${describeAllowedNetworks(
|
|
213
|
+
CLI_NETWORK_POLICY,
|
|
214
|
+
)}. There is no override flag.`,
|
|
128
215
|
);
|
|
129
216
|
}
|
|
130
217
|
|