@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
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
CheckpointNotReadyError,
|
|
3
|
+
ReclaimFailedError,
|
|
4
|
+
SettlementWindowOpenError,
|
|
5
|
+
StreamNotInCheckpointError,
|
|
6
|
+
} from "@absol-labs/sdk";
|
|
2
7
|
|
|
3
8
|
import { booleanFlag, integerFlag } from "../args.js";
|
|
4
9
|
import { formatUsdc } from "../amounts.js";
|
|
@@ -11,7 +16,14 @@ import {
|
|
|
11
16
|
} from "../escape-hatch.js";
|
|
12
17
|
import { signRecoveryMandate } from "../mandate.js";
|
|
13
18
|
import { assertCanPayGas } from "../preflight.js";
|
|
14
|
-
import {
|
|
19
|
+
import { writeClientOptions } from "../deps.js";
|
|
20
|
+
import {
|
|
21
|
+
describeAwaitingCheckpoint,
|
|
22
|
+
describeTerminalBlocker,
|
|
23
|
+
readReclaimBlocker,
|
|
24
|
+
type ReclaimBlocker,
|
|
25
|
+
} from "../reclaimability.js";
|
|
26
|
+
import { isPolicyEnforced, resolveCliWallet } from "../wallet.js";
|
|
15
27
|
import type { CommandContext, CommandSpec } from "../context.js";
|
|
16
28
|
import type { CliReadClient, CliWriteClient } from "../deps.js";
|
|
17
29
|
import type {
|
|
@@ -19,6 +31,9 @@ import type {
|
|
|
19
31
|
StreamProofTransactionResult,
|
|
20
32
|
} from "../../sdk/client.js";
|
|
21
33
|
import type { StreamV2 } from "@absol-labs/sdk";
|
|
34
|
+
import { streamEscrowV2Abi } from "@absol-labs/shared";
|
|
35
|
+
import { parseEventLogs, type Log } from "viem";
|
|
36
|
+
import { escrowGenerationFor } from "../../escrow-generation.js";
|
|
22
37
|
|
|
23
38
|
/**
|
|
24
39
|
* How long to keep retrying a reclaim that is blocked only by an open
|
|
@@ -48,12 +63,45 @@ export const reclaimCommand: CommandSpec = {
|
|
|
48
63
|
if (attempts < 1) throw usageError("--attempts must be at least 1");
|
|
49
64
|
if (interval < 1) throw usageError("--interval must be at least 1 second");
|
|
50
65
|
|
|
51
|
-
const
|
|
66
|
+
const wallet = await resolveCliWallet(env, {
|
|
67
|
+
note: out.note,
|
|
68
|
+
rpcUrl: config.rpcUrl,
|
|
69
|
+
});
|
|
70
|
+
const { account } = wallet;
|
|
52
71
|
const read = deps.createReadClient(config);
|
|
53
72
|
await assertTestnetRpc(config, () => read.getChainId());
|
|
54
|
-
await assertCanPayGas(read, account.address);
|
|
55
73
|
|
|
56
|
-
|
|
74
|
+
// Name the condition from chain state BEFORE anything is built or spent.
|
|
75
|
+
// The public endpoint's revert data is not dependable enough to tell
|
|
76
|
+
// "already reclaimed" from "still running" from "not checkpointed yet", and
|
|
77
|
+
// those three have nothing in common but the word "reverted". See
|
|
78
|
+
// `../reclaimability.ts`.
|
|
79
|
+
const blocker = await readReclaimBlocker(read, streamId, deps.nowSeconds());
|
|
80
|
+
// `--close-first` exists precisely to clear a running stream, so it is the
|
|
81
|
+
// one caller allowed past that refusal. Nothing gets past "already
|
|
82
|
+
// reclaimed": there is no money left to take, with or without a close.
|
|
83
|
+
if (blocker !== null && !(closeFirst && blocker.kind === "running")) {
|
|
84
|
+
throwIfTerminal(blocker, streamId);
|
|
85
|
+
if (blocker.kind === "awaiting-checkpoint") {
|
|
86
|
+
out.note(describeAwaitingCheckpoint(blocker));
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
await assertCanPayGas(read, account.address, config.network);
|
|
91
|
+
|
|
92
|
+
const write = deps.createWriteClient(
|
|
93
|
+
config,
|
|
94
|
+
account,
|
|
95
|
+
writeClientOptions(wallet),
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
// A policy-enforced wallet (Privy) signs no mandate of its own: these are
|
|
99
|
+
// buyer-recovery actions, gated by the escrow on the buyer address and by
|
|
100
|
+
// the broker on `stream.buyer == wallet`. See `../spend-policy.ts`.
|
|
101
|
+
const recoveryMandate = async () =>
|
|
102
|
+
isPolicyEnforced(wallet)
|
|
103
|
+
? null
|
|
104
|
+
: await signRecoveryMandate(account, config.chainId);
|
|
57
105
|
|
|
58
106
|
// `--close-first` is TWO transactions, and the second one only simulates
|
|
59
107
|
// successfully against post-close state. Sequencing them inside a single SDK
|
|
@@ -75,7 +123,7 @@ export const reclaimCommand: CommandSpec = {
|
|
|
75
123
|
} else {
|
|
76
124
|
closeResult = await closeBeforeReclaim(write, {
|
|
77
125
|
streamId,
|
|
78
|
-
signedMandate: await
|
|
126
|
+
signedMandate: await recoveryMandate(),
|
|
79
127
|
spentSoFarUsdc: 0n,
|
|
80
128
|
nowSeconds: deps.nowSeconds(),
|
|
81
129
|
});
|
|
@@ -89,6 +137,19 @@ export const reclaimCommand: CommandSpec = {
|
|
|
89
137
|
`closed in ${closeResult.txHash} — waiting for it to land …`,
|
|
90
138
|
);
|
|
91
139
|
await awaitClosed(read, streamId, deps.sleep, closeResult.txHash);
|
|
140
|
+
// A stream closed before any checkpoint covers its stop time is the
|
|
141
|
+
// ORDINARY outcome of a short trial run, not an exotic one — and it
|
|
142
|
+
// is the state `--close-first` leaves behind by construction. Say
|
|
143
|
+
// what is being waited for, so the retries below read as the
|
|
144
|
+
// advertised wait rather than as a command that has stalled.
|
|
145
|
+
const afterClose = await readReclaimBlocker(
|
|
146
|
+
read,
|
|
147
|
+
streamId,
|
|
148
|
+
deps.nowSeconds(),
|
|
149
|
+
);
|
|
150
|
+
if (afterClose?.kind === "awaiting-checkpoint") {
|
|
151
|
+
out.note(describeAwaitingCheckpoint(afterClose));
|
|
152
|
+
}
|
|
92
153
|
}
|
|
93
154
|
}
|
|
94
155
|
}
|
|
@@ -97,7 +158,7 @@ export const reclaimCommand: CommandSpec = {
|
|
|
97
158
|
write,
|
|
98
159
|
async () => ({
|
|
99
160
|
streamId,
|
|
100
|
-
signedMandate: await
|
|
161
|
+
signedMandate: await recoveryMandate(),
|
|
101
162
|
spentSoFarUsdc: 0n,
|
|
102
163
|
nowSeconds: deps.nowSeconds(),
|
|
103
164
|
}),
|
|
@@ -105,6 +166,15 @@ export const reclaimCommand: CommandSpec = {
|
|
|
105
166
|
attempts,
|
|
106
167
|
intervalMs: interval * 1_000,
|
|
107
168
|
sleep: deps.sleep,
|
|
169
|
+
isRetryable: async (error) =>
|
|
170
|
+
await isWaitingForCheckpoint(
|
|
171
|
+
error,
|
|
172
|
+
read,
|
|
173
|
+
streamId,
|
|
174
|
+
deps.nowSeconds(),
|
|
175
|
+
),
|
|
176
|
+
explainFailure: async (error) =>
|
|
177
|
+
await explainReclaimFailure(error, read, streamId, deps.nowSeconds()),
|
|
108
178
|
onRetry: (attempt, reason) =>
|
|
109
179
|
out.note(
|
|
110
180
|
`reclaim blocked: no checkpoint covers the stream's stop time yet (attempt ${attempt}/${attempts}) — ${reason}`,
|
|
@@ -114,18 +184,31 @@ export const reclaimCommand: CommandSpec = {
|
|
|
114
184
|
// give-up message names the path that needs no proof, and says whether
|
|
115
185
|
// it is open yet.
|
|
116
186
|
exhaustedHint: async () =>
|
|
117
|
-
await describeLastResort(
|
|
187
|
+
await describeLastResort(
|
|
188
|
+
read,
|
|
189
|
+
streamId,
|
|
190
|
+
deps.nowSeconds(),
|
|
191
|
+
escrowGenerationFor(config.chainId, config.escrow),
|
|
192
|
+
),
|
|
118
193
|
},
|
|
119
194
|
);
|
|
120
195
|
|
|
121
196
|
// Read the settled figure back only once the node we are reading from has
|
|
122
|
-
// actually applied the reclaim.
|
|
197
|
+
// actually applied the reclaim. The public RPC is load-balanced, so an
|
|
123
198
|
// immediate read can land on a lagged node and report `settledCumulative: 0`
|
|
124
199
|
// for a stream that just settled a non-zero amount — printing "settled to
|
|
125
200
|
// operator 0 USDC" when it was not zero. Poll for `reclaimed` (which the
|
|
126
201
|
// transaction we just landed sets) so the number beside it is from the same
|
|
127
202
|
// post-reclaim state. Bounded, and falls back to the last value read.
|
|
128
203
|
const stream = await readSettledStream(read, streamId, deps.sleep);
|
|
204
|
+
// The refund comes from the reclaim's own BuyerReclaimed event, never from
|
|
205
|
+
// a post-reclaim read: reclaim overwrites `deposit` with the operator's
|
|
206
|
+
// entitlement, so deposit − settled reads 0 after every reclaim.
|
|
207
|
+
const refunded = refundFromReceipt(
|
|
208
|
+
result.reclaimResult.receipt,
|
|
209
|
+
config.escrow,
|
|
210
|
+
streamId,
|
|
211
|
+
);
|
|
129
212
|
out.result({
|
|
130
213
|
streamId,
|
|
131
214
|
txHash: result.reclaimResult.txHash,
|
|
@@ -135,6 +218,14 @@ export const reclaimCommand: CommandSpec = {
|
|
|
135
218
|
// asked for".
|
|
136
219
|
closeSkippedAlreadyClosed: closeFirst ? alreadyClosed : null,
|
|
137
220
|
settledCumulative: stream?.settledCumulative ?? null,
|
|
221
|
+
// What the reclaim sent back: everything not settled to the operator.
|
|
222
|
+
// Only reported once the read reflects the reclaim, so a lagged node can
|
|
223
|
+
// never print a refund figure computed from pre-reclaim state.
|
|
224
|
+
refundedAmount: refunded,
|
|
225
|
+
// For an x402 stream the refund goes to the opener's payer, whoever sent
|
|
226
|
+
// the (permissionless) reclaim.
|
|
227
|
+
opener: stream?.opener ?? null,
|
|
228
|
+
refundedTo: stream === null ? null : (stream.payer ?? stream.buyer),
|
|
138
229
|
});
|
|
139
230
|
out.print("Reclaimed.");
|
|
140
231
|
out.table([
|
|
@@ -149,6 +240,22 @@ export const reclaimCommand: CommandSpec = {
|
|
|
149
240
|
? []
|
|
150
241
|
: ([
|
|
151
242
|
["settled to operator", formatUsdc(stream.settledCumulative)],
|
|
243
|
+
...(refunded === null
|
|
244
|
+
? []
|
|
245
|
+
: ([
|
|
246
|
+
[
|
|
247
|
+
(stream.opener ?? null) === null
|
|
248
|
+
? "returned to buyer"
|
|
249
|
+
: "returned to payer",
|
|
250
|
+
formatUsdc(refunded),
|
|
251
|
+
],
|
|
252
|
+
] as const)),
|
|
253
|
+
...((stream.opener ?? null) === null
|
|
254
|
+
? []
|
|
255
|
+
: ([
|
|
256
|
+
["via opener", stream.opener as string],
|
|
257
|
+
["refunded to payer", stream.payer],
|
|
258
|
+
] as const)),
|
|
152
259
|
] as const)),
|
|
153
260
|
]);
|
|
154
261
|
},
|
|
@@ -190,7 +297,7 @@ function isAlreadyClosed(error: unknown): boolean {
|
|
|
190
297
|
* Block until the node we read from reports the stream as closed.
|
|
191
298
|
*
|
|
192
299
|
* The close transaction's own receipt is awaited inside the SDK, but
|
|
193
|
-
*
|
|
300
|
+
* the public RPC is load-balanced: the receipt can come back from one node
|
|
194
301
|
* while the very next `simulateContract` lands on another that has not imported
|
|
195
302
|
* the block yet. That node simulates `reclaim` against PRE-close state and
|
|
196
303
|
* reverts `StreamNotReclaimable()`. Confirming the close through the same read
|
|
@@ -226,8 +333,16 @@ export async function awaitClosed(
|
|
|
226
333
|
* time. That is not an error condition — it is the normal ordering of a
|
|
227
334
|
* checkpoint rail, and it happens on the FIRST attempt essentially every time.
|
|
228
335
|
* A CLI that surfaced the raw revert would teach every user that reclaim is
|
|
229
|
-
* broken, so the wait is built in and narrated.
|
|
230
|
-
*
|
|
336
|
+
* broken, so the wait is built in and narrated.
|
|
337
|
+
*
|
|
338
|
+
* What counts as "wait for the next checkpoint" is decided by `isRetryable`,
|
|
339
|
+
* NOT by this function. It used to be `instanceof SettlementWindowOpenError`
|
|
340
|
+
* alone, which missed the two ways the wait actually shows up in practice: the
|
|
341
|
+
* ORACLE refusing a proof for a stream it has not checkpointed yet (the SDK
|
|
342
|
+
* fetches the proof before it ever simulates, so the escrow's revert never
|
|
343
|
+
* happens), and the endpoint returning revert data the ABI cannot decode. Both
|
|
344
|
+
* killed the advertised retry on its first attempt — the second one right
|
|
345
|
+
* after `--close-first` had already spent the buyer's close.
|
|
231
346
|
*/
|
|
232
347
|
export async function reclaimWithBoundedRetry(
|
|
233
348
|
write: CliWriteClient,
|
|
@@ -236,6 +351,10 @@ export async function reclaimWithBoundedRetry(
|
|
|
236
351
|
readonly attempts: number;
|
|
237
352
|
readonly intervalMs: number;
|
|
238
353
|
readonly sleep: (ms: number) => Promise<void>;
|
|
354
|
+
/** True when this failure is the checkpoint wait and will clear on its own. */
|
|
355
|
+
readonly isRetryable: (error: unknown) => Promise<boolean>;
|
|
356
|
+
/** Turn a failure that will NOT clear into something worth reading. */
|
|
357
|
+
readonly explainFailure: (error: unknown) => Promise<unknown>;
|
|
239
358
|
readonly onRetry: (attempt: number, reason: string) => void;
|
|
240
359
|
/** What to tell the buyer once the budget is spent. See `describeLastResort`. */
|
|
241
360
|
readonly exhaustedHint?: () => Promise<string>;
|
|
@@ -246,7 +365,8 @@ export async function reclaimWithBoundedRetry(
|
|
|
246
365
|
try {
|
|
247
366
|
return await write.reclaimStream(await buildInput());
|
|
248
367
|
} catch (error) {
|
|
249
|
-
if (!
|
|
368
|
+
if (!(await options.isRetryable(error)))
|
|
369
|
+
throw await options.explainFailure(error);
|
|
250
370
|
lastError = error;
|
|
251
371
|
if (attempt === options.attempts) break;
|
|
252
372
|
options.onRetry(attempt, describeError(error));
|
|
@@ -282,25 +402,123 @@ async function describeLastResort(
|
|
|
282
402
|
read: CliReadClient,
|
|
283
403
|
streamId: `0x${string}`,
|
|
284
404
|
fallbackNowSeconds: number,
|
|
405
|
+
generation: string,
|
|
285
406
|
): Promise<string> {
|
|
286
407
|
const [stream, grace] = await Promise.all([
|
|
287
408
|
read.getStreamV2(streamId),
|
|
288
409
|
read.escapeGraceSeconds(),
|
|
289
410
|
]);
|
|
290
411
|
const now = await readChainNow(read, fallbackNowSeconds);
|
|
291
|
-
const window = escapeHatchWindow(stream, grace, now.seconds);
|
|
412
|
+
const window = escapeHatchWindow(stream, grace, now.seconds, generation);
|
|
292
413
|
const escapeCommand = `metrik reclaim-unverified ${streamId} --forfeit-unclaimed`;
|
|
293
414
|
return window.ready
|
|
294
415
|
? `the funds are not lost. The no-proof escape hatch is OPEN (${describeEscapeHatch(window)}): \`${escapeCommand}\` takes them back without the oracle — at the cost of forfeiting whatever the operator earned but has not yet claimed. Retrying this command only helps if the oracle comes back.`
|
|
295
416
|
: `the funds are not lost, but retrying only helps if the oracle comes back — this path needs a proof from it. The no-proof escape hatch ${describeEscapeHatch(window)}; from then on \`${escapeCommand}\` takes them back without the oracle, forfeiting whatever the operator earned but has not yet claimed.`;
|
|
296
417
|
}
|
|
297
418
|
|
|
419
|
+
/** Raise a blocker that will not clear on its own as a named CliError. */
|
|
420
|
+
function throwIfTerminal(
|
|
421
|
+
blocker: ReclaimBlocker,
|
|
422
|
+
streamId: `0x${string}`,
|
|
423
|
+
cause?: unknown,
|
|
424
|
+
): void {
|
|
425
|
+
const terminal = describeTerminalBlocker(blocker, streamId);
|
|
426
|
+
if (terminal === null) return;
|
|
427
|
+
throw new CliError(terminal.message, {
|
|
428
|
+
exitCode: 4,
|
|
429
|
+
hint: terminal.hint,
|
|
430
|
+
...(cause === undefined ? {} : { cause }),
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* Is this failure the checkpoint wait, i.e. will it clear on its own?
|
|
436
|
+
*
|
|
437
|
+
* Three shapes, in increasing order of how little the endpoint told us:
|
|
438
|
+
*
|
|
439
|
+
* 1. `SettlementWindowOpenError` — the escrow said so and the ABI decoded it.
|
|
440
|
+
* 2. `StreamNotInCheckpointError` / `CheckpointNotReadyError` — the ORACLE has
|
|
441
|
+
* no proof for this stream yet, so the SDK never reached the escrow at all.
|
|
442
|
+
* This is the state `--close-first` creates by construction on a stream
|
|
443
|
+
* closed before its first covering checkpoint, and treating it as fatal is
|
|
444
|
+
* what stranded a buyer's deposit in escrow for three minutes of manual
|
|
445
|
+
* retries while the CLI's own help promised it would wait.
|
|
446
|
+
* 3. A reclaim revert the ABI could not decode. Nothing in the error names the
|
|
447
|
+
* condition, so ask the chain instead — the state is authoritative and the
|
|
448
|
+
* answer is the same one the pre-flight computes.
|
|
449
|
+
*
|
|
450
|
+
* Case 3 is deliberately narrowed to `ReclaimFailedError`: an unrelated failure
|
|
451
|
+
* (no gas, a dead RPC) must still fail on its first attempt rather than burn
|
|
452
|
+
* twelve of them.
|
|
453
|
+
*/
|
|
454
|
+
async function isWaitingForCheckpoint(
|
|
455
|
+
error: unknown,
|
|
456
|
+
read: Pick<
|
|
457
|
+
CliReadClient,
|
|
458
|
+
"getStreamV2" | "latestCheckpointCheckedAt" | "latestBlockTimestamp"
|
|
459
|
+
>,
|
|
460
|
+
streamId: `0x${string}`,
|
|
461
|
+
nowSeconds: number,
|
|
462
|
+
): Promise<boolean> {
|
|
463
|
+
if (isSettlementWindowOpen(error)) return true;
|
|
464
|
+
if (hasNoCheckpointProofYet(error)) return true;
|
|
465
|
+
if (!isReclaimRevert(error)) return false;
|
|
466
|
+
const blocker = await readReclaimBlocker(read, streamId, nowSeconds).catch(
|
|
467
|
+
() => null,
|
|
468
|
+
);
|
|
469
|
+
return blocker?.kind === "awaiting-checkpoint";
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* The oracle could not supply a proof yet: it has no leaf for this stream in
|
|
474
|
+
* the latest checkpoint (404), or no internally consistent checkpoint is ready
|
|
475
|
+
* (503). Both resolve when the next checkpoint lands, ~15-22s apart.
|
|
476
|
+
*/
|
|
477
|
+
function hasNoCheckpointProofYet(error: unknown): boolean {
|
|
478
|
+
for (let current: unknown = error, depth = 0; depth < 8; depth++) {
|
|
479
|
+
if (
|
|
480
|
+
current instanceof StreamNotInCheckpointError ||
|
|
481
|
+
current instanceof CheckpointNotReadyError
|
|
482
|
+
) {
|
|
483
|
+
return true;
|
|
484
|
+
}
|
|
485
|
+
if (!(current instanceof Error) || current.cause === undefined)
|
|
486
|
+
return false;
|
|
487
|
+
current = current.cause;
|
|
488
|
+
}
|
|
489
|
+
return false;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
/** True when the escrow rejected the reclaim, whatever the reason decoded to. */
|
|
493
|
+
function isReclaimRevert(error: unknown): boolean {
|
|
494
|
+
for (let current: unknown = error, depth = 0; depth < 8; depth++) {
|
|
495
|
+
if (current instanceof ReclaimFailedError) return true;
|
|
496
|
+
if (!(current instanceof Error) || current.cause === undefined)
|
|
497
|
+
return false;
|
|
498
|
+
current = current.cause;
|
|
499
|
+
}
|
|
500
|
+
return false;
|
|
501
|
+
}
|
|
502
|
+
|
|
298
503
|
/**
|
|
299
|
-
*
|
|
300
|
-
*
|
|
301
|
-
*
|
|
504
|
+
* Name a failure that will not clear.
|
|
505
|
+
*
|
|
506
|
+
* `StreamNotReclaimable()` means exactly one thing when the ABI decodes it: the
|
|
507
|
+
* stream is neither closed nor past its expiry. When it does NOT decode — the
|
|
508
|
+
* live failure mode on the public endpoint — the chain is asked the same
|
|
509
|
+
* question, so "already reclaimed" and "still running" are still told apart and
|
|
510
|
+
* still say which command fixes them. The raw error is kept as the `cause`
|
|
511
|
+
* either way.
|
|
302
512
|
*/
|
|
303
|
-
function explainReclaimFailure(
|
|
513
|
+
async function explainReclaimFailure(
|
|
514
|
+
error: unknown,
|
|
515
|
+
read: Pick<
|
|
516
|
+
CliReadClient,
|
|
517
|
+
"getStreamV2" | "latestCheckpointCheckedAt" | "latestBlockTimestamp"
|
|
518
|
+
>,
|
|
519
|
+
streamId: `0x${string}`,
|
|
520
|
+
nowSeconds: number,
|
|
521
|
+
): Promise<unknown> {
|
|
304
522
|
if (!(error instanceof Error)) return error;
|
|
305
523
|
for (let current: unknown = error, depth = 0; depth < 8; depth++) {
|
|
306
524
|
if (!(current instanceof Error)) break;
|
|
@@ -309,7 +527,7 @@ function explainReclaimFailure(error: unknown): unknown {
|
|
|
309
527
|
"the stream is still running, so there is nothing to reclaim yet",
|
|
310
528
|
{
|
|
311
529
|
exitCode: 4,
|
|
312
|
-
hint:
|
|
530
|
+
hint: `stop the clock first — \`metrik close ${streamId}\`, or re-run this command with --close-first — or wait for the stream to expire.`,
|
|
313
531
|
cause: error,
|
|
314
532
|
},
|
|
315
533
|
);
|
|
@@ -317,6 +535,18 @@ function explainReclaimFailure(error: unknown): unknown {
|
|
|
317
535
|
if (current.cause === undefined) break;
|
|
318
536
|
current = current.cause;
|
|
319
537
|
}
|
|
538
|
+
if (isReclaimRevert(error)) {
|
|
539
|
+
const blocker = await readReclaimBlocker(read, streamId, nowSeconds).catch(
|
|
540
|
+
() => null,
|
|
541
|
+
);
|
|
542
|
+
if (blocker !== null) {
|
|
543
|
+
try {
|
|
544
|
+
throwIfTerminal(blocker, streamId, error);
|
|
545
|
+
} catch (named) {
|
|
546
|
+
return named;
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
}
|
|
320
550
|
return error;
|
|
321
551
|
}
|
|
322
552
|
|
|
@@ -342,6 +572,31 @@ const SETTLED_READ_ATTEMPTS = 5;
|
|
|
342
572
|
// `--interval`), so the two waits are tellable apart in tests and in logs.
|
|
343
573
|
const SETTLED_READ_INTERVAL_MS = 500;
|
|
344
574
|
|
|
575
|
+
/**
|
|
576
|
+
* The amount the reclaim returned, decoded from its BuyerReclaimed event (same
|
|
577
|
+
* signature on V2 and V3). Null when the receipt carries no matching log, so
|
|
578
|
+
* the CLI prints nothing rather than a guessed figure.
|
|
579
|
+
*/
|
|
580
|
+
export function refundFromReceipt(
|
|
581
|
+
receipt: { logs?: readonly Log[] } | undefined,
|
|
582
|
+
escrow: string,
|
|
583
|
+
streamId: `0x${string}`,
|
|
584
|
+
): bigint | null {
|
|
585
|
+
const logs = receipt?.logs;
|
|
586
|
+
if (!Array.isArray(logs)) return null;
|
|
587
|
+
const events = parseEventLogs({
|
|
588
|
+
abi: streamEscrowV2Abi,
|
|
589
|
+
eventName: "BuyerReclaimed",
|
|
590
|
+
logs: logs.filter(
|
|
591
|
+
(log) => log.address?.toLowerCase() === escrow.toLowerCase(),
|
|
592
|
+
),
|
|
593
|
+
});
|
|
594
|
+
const match = events.find(
|
|
595
|
+
(event) => event.args.streamId.toLowerCase() === streamId.toLowerCase(),
|
|
596
|
+
);
|
|
597
|
+
return match === undefined ? null : match.args.amount;
|
|
598
|
+
}
|
|
599
|
+
|
|
345
600
|
async function readSettledStream(
|
|
346
601
|
read: { getStreamV2(streamId: `0x${string}`): Promise<StreamV2> },
|
|
347
602
|
streamId: `0x${string}`,
|