@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
package/src/cli/deps.ts
CHANGED
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
type StreamV2,
|
|
9
9
|
type StreamProofTransactionResult,
|
|
10
10
|
} from "@absol-labs/sdk";
|
|
11
|
-
import { erc20Abi, getAbiItem, http, type
|
|
11
|
+
import { erc20Abi, getAbiItem, http, type Transport } from "viem";
|
|
12
12
|
|
|
13
13
|
import {
|
|
14
14
|
discoverServicesDetailed,
|
|
@@ -30,8 +30,15 @@ import {
|
|
|
30
30
|
type ReclaimAuthorizedStreamInput,
|
|
31
31
|
type ReclaimVerifiedStreamResult,
|
|
32
32
|
} from "../sdk/client.js";
|
|
33
|
+
import type { SignedSpendMandate } from "../mandates/mandate.js";
|
|
34
|
+
import type {
|
|
35
|
+
PayX402WithMandateInput,
|
|
36
|
+
PayX402WithMandateResult,
|
|
37
|
+
} from "../x402-buyer/pay.js";
|
|
38
|
+
import type { RefundClient } from "@absol-labs/sdk/x402";
|
|
33
39
|
|
|
34
40
|
import type { CliConfig } from "./config.js";
|
|
41
|
+
import type { CliSignerAccount } from "./wallet.js";
|
|
35
42
|
import { RPC_RETRY_COUNT, RPC_TIMEOUT_MS, withReadTimeout } from "./timeout.js";
|
|
36
43
|
|
|
37
44
|
/**
|
|
@@ -149,17 +156,39 @@ export interface OpenedStream {
|
|
|
149
156
|
readonly blockNumber: bigint;
|
|
150
157
|
}
|
|
151
158
|
|
|
159
|
+
/**
|
|
160
|
+
* A fund-moving call, with the mandate made EXPLICITLY optional.
|
|
161
|
+
*
|
|
162
|
+
* `null` is not "no authorization" — it is "this wallet's ceiling is enforced
|
|
163
|
+
* by a broker policy the client cannot reach or raise, so a locally self-signed
|
|
164
|
+
* mandate would be the agent attesting to its own limits on top of a stronger
|
|
165
|
+
* limit". The CLI substitutes the real ceiling instead; see
|
|
166
|
+
* `./spend-policy.ts`, which also explains why re-adding the mandate here would
|
|
167
|
+
* be a regression rather than a tidy-up.
|
|
168
|
+
*
|
|
169
|
+
* A key-holding wallet (private key, CDP) always passes a real mandate, and its
|
|
170
|
+
* path through `VerifiedStreamAgentClient` is unchanged.
|
|
171
|
+
*/
|
|
172
|
+
type MandateOptional<T extends { readonly signedMandate: SignedSpendMandate }> =
|
|
173
|
+
Omit<T, "signedMandate"> & {
|
|
174
|
+
readonly signedMandate: SignedSpendMandate | null;
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
export type CliOpenStreamInput = MandateOptional<OpenVerifiedStreamInput>;
|
|
178
|
+
export type CliStreamActionInput =
|
|
179
|
+
MandateOptional<MandateAuthorizedStreamActionInput>;
|
|
180
|
+
export type CliReclaimStreamInput =
|
|
181
|
+
MandateOptional<ReclaimAuthorizedStreamInput>;
|
|
182
|
+
|
|
152
183
|
export interface CliWriteClient {
|
|
153
184
|
/** Build + best-effort simulate the hire WITHOUT broadcasting (`--dry-run`). */
|
|
154
185
|
buildHireTx(input: HireComputeInput): Promise<HireTxPreview>;
|
|
155
|
-
openVerifiedStream(
|
|
156
|
-
input: OpenVerifiedStreamInput,
|
|
157
|
-
): Promise<HireComputeResult>;
|
|
186
|
+
openVerifiedStream(input: CliOpenStreamInput): Promise<HireComputeResult>;
|
|
158
187
|
closeStream(
|
|
159
|
-
input:
|
|
188
|
+
input: CliStreamActionInput,
|
|
160
189
|
): Promise<StreamProofTransactionResult>;
|
|
161
190
|
reclaimStream(
|
|
162
|
-
input:
|
|
191
|
+
input: CliReclaimStreamInput,
|
|
163
192
|
): Promise<ReclaimVerifiedStreamResult>;
|
|
164
193
|
/**
|
|
165
194
|
* The no-proof escape hatch. Separate from {@link reclaimStream} because it is
|
|
@@ -167,14 +196,53 @@ export interface CliWriteClient {
|
|
|
167
196
|
* — see `VerifiedStreamAgentClient.reclaimUnverifiedStream`.
|
|
168
197
|
*/
|
|
169
198
|
reclaimUnverifiedStream(
|
|
170
|
-
input:
|
|
199
|
+
input: CliStreamActionInput,
|
|
171
200
|
): Promise<StreamProofTransactionResult>;
|
|
172
201
|
/** Operator-only: redeem the verified-earned balance for a stream. */
|
|
173
202
|
claimStream(
|
|
174
|
-
input:
|
|
203
|
+
input: CliStreamActionInput,
|
|
175
204
|
): Promise<StreamProofTransactionResult>;
|
|
176
205
|
}
|
|
177
206
|
|
|
207
|
+
/**
|
|
208
|
+
* Per-wallet write-path overrides.
|
|
209
|
+
*
|
|
210
|
+
* `transport` exists for exactly one reason: a Privy wallet has no local
|
|
211
|
+
* `signTransaction`. Its EIP-1193 provider performs `eth_sendTransaction`
|
|
212
|
+
* itself, so the provider must BE the wallet client's transport rather than the
|
|
213
|
+
* plain `http(--rpc-url)` every local signer uses. Left unset — which is every
|
|
214
|
+
* private-key and CDP run — the transport is unchanged, byte for byte.
|
|
215
|
+
*/
|
|
216
|
+
export interface CreateWriteClientOptions {
|
|
217
|
+
readonly transport?: Transport;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* The buyer side of x402 (`metrik pay` / `metrik refund`). Behind the injectable
|
|
222
|
+
* surface like every other side effect, and loaded lazily by default so a CLI
|
|
223
|
+
* run that never pays over x402 never imports `@x402/*`.
|
|
224
|
+
*/
|
|
225
|
+
export interface CliX402 {
|
|
226
|
+
/** Mandate-gated `payWithX402` (see `../x402-buyer/pay.ts`). */
|
|
227
|
+
readonly pay: (
|
|
228
|
+
input: PayX402WithMandateInput,
|
|
229
|
+
) => Promise<PayX402WithMandateResult>;
|
|
230
|
+
/**
|
|
231
|
+
* The SDK's `refundClient`, bound to ONE stream and verifying every relay
|
|
232
|
+
* "success" against the chain this CLI is configured for.
|
|
233
|
+
*/
|
|
234
|
+
readonly refundClient: (
|
|
235
|
+
config: CliConfig,
|
|
236
|
+
target: {
|
|
237
|
+
readonly refundUrl: string;
|
|
238
|
+
readonly streamId: `0x${string}`;
|
|
239
|
+
readonly opener?: `0x${string}`;
|
|
240
|
+
readonly maxAttempts?: number;
|
|
241
|
+
readonly maxDelayMs?: number;
|
|
242
|
+
},
|
|
243
|
+
) => Promise<RefundClient>;
|
|
244
|
+
}
|
|
245
|
+
|
|
178
246
|
export interface CliDependencies {
|
|
179
247
|
readonly createReadClient: (config: CliConfig) => CliReadClient;
|
|
180
248
|
/**
|
|
@@ -185,7 +253,8 @@ export interface CliDependencies {
|
|
|
185
253
|
*/
|
|
186
254
|
readonly createWriteClient: (
|
|
187
255
|
config: CliConfig,
|
|
188
|
-
account:
|
|
256
|
+
account: CliSignerAccount | null,
|
|
257
|
+
options?: CreateWriteClientOptions,
|
|
189
258
|
) => CliWriteClient;
|
|
190
259
|
readonly discover: (
|
|
191
260
|
options: DiscoverServicesOptions,
|
|
@@ -198,6 +267,8 @@ export interface CliDependencies {
|
|
|
198
267
|
readonly createInvokeStreamReader: (config: CliConfig) => InvokeStreamReader;
|
|
199
268
|
readonly nowSeconds: () => number;
|
|
200
269
|
readonly sleep: (ms: number) => Promise<void>;
|
|
270
|
+
/** Optional so an embedder's existing dependency object keeps compiling. */
|
|
271
|
+
readonly x402?: CliX402;
|
|
201
272
|
}
|
|
202
273
|
|
|
203
274
|
/**
|
|
@@ -218,21 +289,38 @@ const STREAM_OPENED_EVENT = getAbiItem({
|
|
|
218
289
|
* with nothing on screen. Bounding it here bounds every read and every
|
|
219
290
|
* simulation the SDK performs, in one place.
|
|
220
291
|
*/
|
|
221
|
-
function rpcTransport(config: CliConfig) {
|
|
292
|
+
export function rpcTransport(config: CliConfig) {
|
|
222
293
|
return http(config.rpcUrl, {
|
|
223
294
|
timeout: RPC_TIMEOUT_MS,
|
|
224
295
|
retryCount: RPC_RETRY_COUNT,
|
|
225
296
|
});
|
|
226
297
|
}
|
|
227
298
|
|
|
299
|
+
/**
|
|
300
|
+
* An `--escrow` the registry does not record was declared with
|
|
301
|
+
* `--escrow-generation`: the only case the SDK may skip its code-hash pin.
|
|
302
|
+
*/
|
|
303
|
+
function unregisteredEscrowOptions(config: CliConfig): {
|
|
304
|
+
readonly allowUnregisteredEscrow?: true;
|
|
305
|
+
readonly escrowGeneration?: "v2" | "v3";
|
|
306
|
+
} {
|
|
307
|
+
return config.unregisteredEscrowGeneration === undefined
|
|
308
|
+
? {}
|
|
309
|
+
: {
|
|
310
|
+
allowUnregisteredEscrow: true,
|
|
311
|
+
escrowGeneration: config.unregisteredEscrowGeneration,
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
|
|
228
315
|
/** Shared `MetrikClient` construction: one escrow, declared as V2 on both keys. */
|
|
229
316
|
function createMetrikClient(
|
|
230
317
|
config: CliConfig,
|
|
231
|
-
account?:
|
|
318
|
+
account?: CliSignerAccount,
|
|
319
|
+
transport?: Transport,
|
|
232
320
|
): MetrikClient {
|
|
233
321
|
return new MetrikClient({
|
|
234
322
|
chain: config.chain,
|
|
235
|
-
transport: rpcTransport(config),
|
|
323
|
+
transport: transport ?? rpcTransport(config),
|
|
236
324
|
escrow: config.escrow,
|
|
237
325
|
// Declared on BOTH keys: without `escrowV2` the SDK routes reads and
|
|
238
326
|
// settlement through the legacy V1 ABI, which reverts against
|
|
@@ -242,6 +330,7 @@ function createMetrikClient(
|
|
|
242
330
|
usdc: config.usdc,
|
|
243
331
|
oracleBaseUrl: config.oracleBaseUrl,
|
|
244
332
|
settlementTarget: { chainId: config.chainId, escrow: config.escrow },
|
|
333
|
+
...unregisteredEscrowOptions(config),
|
|
245
334
|
...(account === undefined ? {} : { account }),
|
|
246
335
|
});
|
|
247
336
|
}
|
|
@@ -365,15 +454,19 @@ export const defaultCliDependencies: CliDependencies = {
|
|
|
365
454
|
},
|
|
366
455
|
};
|
|
367
456
|
},
|
|
368
|
-
createWriteClient(config, account) {
|
|
457
|
+
createWriteClient(config, account, options) {
|
|
458
|
+
// A Privy wallet's provider IS the transport (see CreateWriteClientOptions);
|
|
459
|
+
// every other wallet keeps the plain bounded HTTP transport.
|
|
460
|
+
const transport = options?.transport ?? rpcTransport(config);
|
|
369
461
|
const clientConfig = {
|
|
370
462
|
chain: config.chain,
|
|
371
|
-
transport
|
|
463
|
+
transport,
|
|
372
464
|
escrow: config.escrow,
|
|
373
465
|
escrowV2: config.escrow,
|
|
374
466
|
usdc: config.usdc,
|
|
375
467
|
oracleBaseUrl: config.oracleBaseUrl,
|
|
376
468
|
settlementTarget: { chainId: config.chainId, escrow: config.escrow },
|
|
469
|
+
...unregisteredEscrowOptions(config),
|
|
377
470
|
...(account === null ? {} : { account }),
|
|
378
471
|
};
|
|
379
472
|
// The mandate-gated agent client owns every fund-moving call; the raw SDK
|
|
@@ -382,26 +475,61 @@ export const defaultCliDependencies: CliDependencies = {
|
|
|
382
475
|
const agentClient = new VerifiedStreamAgentClient(clientConfig);
|
|
383
476
|
const metrik = createMetrikClient(
|
|
384
477
|
config,
|
|
385
|
-
|
|
478
|
+
account === null ? undefined : account,
|
|
479
|
+
transport,
|
|
386
480
|
);
|
|
387
481
|
return {
|
|
388
482
|
async buildHireTx(input) {
|
|
389
483
|
return await metrik.buildHireTx(input);
|
|
390
484
|
},
|
|
391
|
-
async openVerifiedStream(input) {
|
|
392
|
-
|
|
485
|
+
async openVerifiedStream({ signedMandate, ...input }) {
|
|
486
|
+
// No mandate means a policy-enforced wallet: the ceiling was already
|
|
487
|
+
// checked against the broker's own `/v1/config` before we got here, and
|
|
488
|
+
// the broker checks it AGAIN server-side on the transaction it signs.
|
|
489
|
+
// Going straight at the SDK client is the same path the SDK-driven
|
|
490
|
+
// (non-CLI) flow has always used.
|
|
491
|
+
if (signedMandate === null) {
|
|
492
|
+
return await metrik.hireVerifiedService({
|
|
493
|
+
operator: input.operator,
|
|
494
|
+
serviceRef: input.serviceRef,
|
|
495
|
+
budgetUsdc: input.budgetUsdc,
|
|
496
|
+
ratePerSecond: input.ratePerSecondUsdc,
|
|
497
|
+
maxDurationSeconds: input.maxDurationSeconds,
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
return await agentClient.openVerifiedStream({
|
|
501
|
+
...input,
|
|
502
|
+
signedMandate,
|
|
503
|
+
});
|
|
393
504
|
},
|
|
394
|
-
async closeStream(input) {
|
|
395
|
-
return await
|
|
505
|
+
async closeStream({ signedMandate, ...input }) {
|
|
506
|
+
if (signedMandate === null) return await metrik.close(input.streamId);
|
|
507
|
+
return await agentClient.closeStream({ ...input, signedMandate });
|
|
396
508
|
},
|
|
397
|
-
async reclaimStream(input) {
|
|
398
|
-
|
|
509
|
+
async reclaimStream({ signedMandate, ...input }) {
|
|
510
|
+
if (signedMandate === null) {
|
|
511
|
+
const closeResult = input.closeFirst
|
|
512
|
+
? await metrik.close(input.streamId)
|
|
513
|
+
: null;
|
|
514
|
+
return {
|
|
515
|
+
closeResult,
|
|
516
|
+
reclaimResult: await metrik.reclaim(input.streamId),
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
return await agentClient.reclaimStream({ ...input, signedMandate });
|
|
399
520
|
},
|
|
400
|
-
async reclaimUnverifiedStream(input) {
|
|
401
|
-
|
|
521
|
+
async reclaimUnverifiedStream({ signedMandate, ...input }) {
|
|
522
|
+
if (signedMandate === null) {
|
|
523
|
+
return await metrik.reclaimUnverified(input.streamId);
|
|
524
|
+
}
|
|
525
|
+
return await agentClient.reclaimUnverifiedStream({
|
|
526
|
+
...input,
|
|
527
|
+
signedMandate,
|
|
528
|
+
});
|
|
402
529
|
},
|
|
403
|
-
async claimStream(input) {
|
|
404
|
-
return await
|
|
530
|
+
async claimStream({ signedMandate, ...input }) {
|
|
531
|
+
if (signedMandate === null) return await metrik.claim(input.streamId);
|
|
532
|
+
return await agentClient.claimStream({ ...input, signedMandate });
|
|
405
533
|
},
|
|
406
534
|
};
|
|
407
535
|
},
|
|
@@ -429,9 +557,45 @@ export const defaultCliDependencies: CliDependencies = {
|
|
|
429
557
|
nowSeconds() {
|
|
430
558
|
return Math.floor(Date.now() / 1000);
|
|
431
559
|
},
|
|
560
|
+
x402: {
|
|
561
|
+
async pay(input) {
|
|
562
|
+
const { payX402WithMandate } = await import("../x402-buyer/pay.js");
|
|
563
|
+
return await payX402WithMandate(input);
|
|
564
|
+
},
|
|
565
|
+
async refundClient(config, target) {
|
|
566
|
+
const { refundClient } = await import("@absol-labs/sdk/x402");
|
|
567
|
+
return refundClient(target.refundUrl, {
|
|
568
|
+
streamId: target.streamId,
|
|
569
|
+
...(target.opener === undefined ? {} : { opener: target.opener }),
|
|
570
|
+
// Every relay "success" is checked against the receipt on THIS chain.
|
|
571
|
+
verifyWith: createMetrikClient(config),
|
|
572
|
+
expectedChainId: config.chainId,
|
|
573
|
+
...(target.maxAttempts === undefined
|
|
574
|
+
? {}
|
|
575
|
+
: { maxAttempts: target.maxAttempts }),
|
|
576
|
+
...(target.maxDelayMs === undefined
|
|
577
|
+
? {}
|
|
578
|
+
: { maxDelayMs: target.maxDelayMs }),
|
|
579
|
+
});
|
|
580
|
+
},
|
|
581
|
+
},
|
|
432
582
|
async sleep(ms) {
|
|
433
583
|
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
434
584
|
},
|
|
435
585
|
};
|
|
436
586
|
|
|
587
|
+
/**
|
|
588
|
+
* The write-client options a resolved wallet implies.
|
|
589
|
+
*
|
|
590
|
+
* Kept next to {@link CreateWriteClientOptions} so every fund-moving command
|
|
591
|
+
* spells the transport decision the same way, and so a wallet that needs a
|
|
592
|
+
* provider transport cannot be broadcast through a plain RPC by a command that
|
|
593
|
+
* simply forgot to pass it.
|
|
594
|
+
*/
|
|
595
|
+
export function writeClientOptions(
|
|
596
|
+
wallet: { readonly transport?: Transport } | null,
|
|
597
|
+
): CreateWriteClientOptions {
|
|
598
|
+
return wallet?.transport === undefined ? {} : { transport: wallet.transport };
|
|
599
|
+
}
|
|
600
|
+
|
|
437
601
|
export type { DiscoverResult, ServiceListing };
|
package/src/cli/errors.ts
CHANGED
|
@@ -76,6 +76,28 @@ function namesTheRevert(line: string): boolean {
|
|
|
76
76
|
);
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
+
/**
|
|
80
|
+
* Links that state the OUTCOME rather than open a sentence the next link
|
|
81
|
+
* completes — so the trailing "…and it reverted" carries nothing.
|
|
82
|
+
*
|
|
83
|
+
* `metrik status <a stream id that does not exist>` used to print
|
|
84
|
+
*
|
|
85
|
+
* stream 0x…: does not exist on the escrow: The contract function
|
|
86
|
+
* "getStream" reverted.
|
|
87
|
+
*
|
|
88
|
+
* The first link is the whole answer; the tail is viem restating that a revert
|
|
89
|
+
* is how the escrow says so. Contrast "reclaim simulation failed", which is a
|
|
90
|
+
* PREFIX — dropping the boilerplate after it would leave a failure with no
|
|
91
|
+
* stated reason at all, which is why the one-boilerplate rule exists.
|
|
92
|
+
*/
|
|
93
|
+
const STATES_THE_OUTCOME: readonly RegExp[] = [
|
|
94
|
+
/\bdoes not exist on the escrow\b/,
|
|
95
|
+
];
|
|
96
|
+
|
|
97
|
+
function statesTheOutcome(line: string): boolean {
|
|
98
|
+
return STATES_THE_OUTCOME.some((pattern) => pattern.test(line));
|
|
99
|
+
}
|
|
100
|
+
|
|
79
101
|
function isBoilerplate(line: string): boolean {
|
|
80
102
|
return VIEM_BOILERPLATE.some((pattern) => pattern.test(line));
|
|
81
103
|
}
|
|
@@ -108,7 +130,8 @@ export function stripRedundantBoilerplate(
|
|
|
108
130
|
chain: readonly string[],
|
|
109
131
|
): readonly string[] {
|
|
110
132
|
const decoded = chain.some(
|
|
111
|
-
(line) =>
|
|
133
|
+
(line) =>
|
|
134
|
+
(namesTheRevert(line) || statesTheOutcome(line)) && !isBoilerplate(line),
|
|
112
135
|
);
|
|
113
136
|
let keptBoilerplate = false;
|
|
114
137
|
const kept = chain.filter((line, index) => {
|
package/src/cli/escape-hatch.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { StreamV2 } from "@absol-labs/sdk";
|
|
2
2
|
|
|
3
|
+
import { escapeHatchReadyAtFormula } from "../escrow-generation.js";
|
|
4
|
+
|
|
3
5
|
import { formatDuration } from "./amounts.js";
|
|
4
6
|
import type { CliReadClient } from "./deps.js";
|
|
5
7
|
|
|
@@ -17,9 +19,11 @@ import type { CliReadClient } from "./deps.js";
|
|
|
17
19
|
* — and `escapeGraceSeconds` is a public immutable on the escrow, so readiness
|
|
18
20
|
* is fully computable before a single wei of gas is spent.
|
|
19
21
|
*
|
|
20
|
-
* Note which timestamp the escrow uses
|
|
21
|
-
*
|
|
22
|
-
*
|
|
22
|
+
* Note which timestamp the escrow uses, which depends on its GENERATION (the
|
|
23
|
+
* SDK registry's `escrow.generation`; see `../escrow-generation.ts`): V2 uses
|
|
24
|
+
* `expiresAt`, NEVER `closedAt` — closing early does not bring the hatch
|
|
25
|
+
* forward, and the CLI must not imply that it does. V3 opens it at
|
|
26
|
+
* `min(expiresAt, closedAt) + escapeGraceSeconds`.
|
|
23
27
|
*/
|
|
24
28
|
export interface EscapeHatchWindow {
|
|
25
29
|
/** `escapeGraceSeconds` as read from the escrow. */
|
|
@@ -32,11 +36,21 @@ export interface EscapeHatchWindow {
|
|
|
32
36
|
}
|
|
33
37
|
|
|
34
38
|
export function escapeHatchWindow(
|
|
35
|
-
stream: Pick<StreamV2, "expiresAt"
|
|
39
|
+
stream: Pick<StreamV2, "expiresAt"> & Partial<Pick<StreamV2, "closedAt">>,
|
|
36
40
|
graceSeconds: number,
|
|
37
41
|
nowSeconds: number,
|
|
42
|
+
generation = "v2",
|
|
38
43
|
): EscapeHatchWindow {
|
|
39
|
-
const readyAt =
|
|
44
|
+
const readyAt = Number(
|
|
45
|
+
escapeHatchReadyAtFormula(
|
|
46
|
+
generation,
|
|
47
|
+
{
|
|
48
|
+
expiresAt: BigInt(stream.expiresAt),
|
|
49
|
+
closedAt: BigInt(stream.closedAt ?? 0),
|
|
50
|
+
},
|
|
51
|
+
BigInt(graceSeconds),
|
|
52
|
+
),
|
|
53
|
+
);
|
|
40
54
|
const secondsRemaining = readyAt - nowSeconds;
|
|
41
55
|
return {
|
|
42
56
|
graceSeconds,
|
package/src/cli/listings.ts
CHANGED
|
@@ -18,6 +18,30 @@ export async function resolveListing(
|
|
|
18
18
|
serviceRef: `0x${string}`,
|
|
19
19
|
options: { readonly required: boolean },
|
|
20
20
|
): Promise<ServiceListing | null> {
|
|
21
|
+
return (await resolveListingRecord(context, serviceRef, options)).listing;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* What {@link resolveListing} found, including the verified signed record of a
|
|
26
|
+
* matching row even when that row is not usable as a listing (no signed
|
|
27
|
+
* invocation URL) — `open` still has to read the operator-signed rate band off
|
|
28
|
+
* it — and whether the registry was unreachable (the only case in which a
|
|
29
|
+
* signed band may exist and be unknown).
|
|
30
|
+
*/
|
|
31
|
+
export interface ListingRecordResolution {
|
|
32
|
+
readonly listing: ServiceListing | null;
|
|
33
|
+
readonly record: {
|
|
34
|
+
readonly serviceRef: string;
|
|
35
|
+
readonly signed: ServiceListing["signed"];
|
|
36
|
+
} | null;
|
|
37
|
+
readonly registryUnreachable: boolean;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export async function resolveListingRecord(
|
|
41
|
+
context: CommandContext,
|
|
42
|
+
serviceRef: `0x${string}`,
|
|
43
|
+
options: { readonly required: boolean },
|
|
44
|
+
): Promise<ListingRecordResolution> {
|
|
21
45
|
const { config, out, deps } = context;
|
|
22
46
|
let services;
|
|
23
47
|
try {
|
|
@@ -32,7 +56,7 @@ export async function resolveListing(
|
|
|
32
56
|
out.note(
|
|
33
57
|
`warning: the registry at ${config.registryUrl} is unreachable; continuing without a verified listing.`,
|
|
34
58
|
);
|
|
35
|
-
return null;
|
|
59
|
+
return { listing: null, record: null, registryUnreachable: true };
|
|
36
60
|
}
|
|
37
61
|
throw new CliError(
|
|
38
62
|
`the service registry at ${config.registryUrl} could not be read`,
|
|
@@ -45,7 +69,9 @@ export async function resolveListing(
|
|
|
45
69
|
(service) => service.serviceRef.toLowerCase() === wanted,
|
|
46
70
|
);
|
|
47
71
|
if (match === undefined) {
|
|
48
|
-
if (!options.required)
|
|
72
|
+
if (!options.required) {
|
|
73
|
+
return { listing: null, record: null, registryUnreachable: false };
|
|
74
|
+
}
|
|
49
75
|
throw new CliError(
|
|
50
76
|
`no verified listing in the registry matches serviceRef ${serviceRef}`,
|
|
51
77
|
{
|
|
@@ -54,8 +80,11 @@ export async function resolveListing(
|
|
|
54
80
|
},
|
|
55
81
|
);
|
|
56
82
|
}
|
|
83
|
+
const record = { serviceRef: match.serviceRef, signed: match.signed };
|
|
57
84
|
if (match.accessUrl === null) {
|
|
58
|
-
if (!options.required)
|
|
85
|
+
if (!options.required) {
|
|
86
|
+
return { listing: null, record, registryUnreachable: false };
|
|
87
|
+
}
|
|
59
88
|
throw new CliError(
|
|
60
89
|
`listing ${serviceRef} carries no signed invocation URL`,
|
|
61
90
|
{
|
|
@@ -66,12 +95,16 @@ export async function resolveListing(
|
|
|
66
95
|
}
|
|
67
96
|
|
|
68
97
|
return {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
98
|
+
listing: {
|
|
99
|
+
serviceRef: match.serviceRef,
|
|
100
|
+
operator: match.operator,
|
|
101
|
+
publicUrl: match.publicUrl,
|
|
102
|
+
access: match.access,
|
|
103
|
+
accessUrl: match.accessUrl,
|
|
104
|
+
signed: match.signed,
|
|
105
|
+
category: match.category,
|
|
106
|
+
},
|
|
107
|
+
record,
|
|
108
|
+
registryUnreachable: false,
|
|
76
109
|
};
|
|
77
110
|
}
|
package/src/cli/preflight.ts
CHANGED
|
@@ -1,14 +1,10 @@
|
|
|
1
|
+
import type { MetrikNetwork } from "@absol-labs/sdk";
|
|
2
|
+
|
|
3
|
+
import { gasFundingHint, usdcFundingHint } from "../network.js";
|
|
1
4
|
import { formatUsdc } from "./amounts.js";
|
|
2
5
|
import { CliError } from "./errors.js";
|
|
3
6
|
import type { CliReadClient } from "./deps.js";
|
|
4
7
|
|
|
5
|
-
/** Where a Base Sepolia test wallet gets its gas. */
|
|
6
|
-
export const BASE_SEPOLIA_FAUCET_URL =
|
|
7
|
-
"https://www.alchemy.com/faucets/base-sepolia";
|
|
8
|
-
|
|
9
|
-
/** Where a Base Sepolia test wallet gets the USDC it escrows. */
|
|
10
|
-
export const CIRCLE_USDC_FAUCET_URL = "https://faucet.circle.com";
|
|
11
|
-
|
|
12
8
|
/**
|
|
13
9
|
* Refuse to broadcast from a wallet that cannot pay for gas.
|
|
14
10
|
*
|
|
@@ -16,20 +12,29 @@ export const CIRCLE_USDC_FAUCET_URL = "https://faucet.circle.com";
|
|
|
16
12
|
* phrasing — `USDC approve transaction failed: Execution reverted with reason:
|
|
17
13
|
* gas required exceeds allowance (0)` — which reads like a USDC allowance
|
|
18
14
|
* problem and sends the reader looking at approvals. The actual fact is "this
|
|
19
|
-
* address holds no
|
|
15
|
+
* address holds no gas", and it is knowable with one read before anything is
|
|
20
16
|
* signed.
|
|
21
17
|
*
|
|
18
|
+
* The gas token is read from the network's registry metadata, never assumed to
|
|
19
|
+
* be ETH. Where gas IS the settlement token (`gasToken.isSettlementToken`),
|
|
20
|
+
* there is only one balance: the USDC balance is the gas balance, so that is
|
|
21
|
+
* the one read.
|
|
22
|
+
*
|
|
22
23
|
* Deliberately only a ZERO check, not an estimate: predicting the gas a hire
|
|
23
|
-
* will cost means simulating it, and a wallet with a little
|
|
24
|
+
* will cost means simulating it, and a wallet with a little gas that still runs
|
|
24
25
|
* short should get the real revert rather than a guess from this CLI.
|
|
25
26
|
*/
|
|
26
27
|
export async function assertCanPayGas(
|
|
27
28
|
read: CliReadClient,
|
|
28
29
|
address: `0x${string}`,
|
|
30
|
+
network: MetrikNetwork,
|
|
29
31
|
): Promise<void> {
|
|
32
|
+
const gasIsUsdc = network.gasToken.isSettlementToken;
|
|
30
33
|
let balance: bigint;
|
|
31
34
|
try {
|
|
32
|
-
balance =
|
|
35
|
+
balance = gasIsUsdc
|
|
36
|
+
? await read.usdcBalance(address)
|
|
37
|
+
: await read.nativeBalance(address);
|
|
33
38
|
} catch {
|
|
34
39
|
// A balance read is a courtesy, not a gate. If the RPC will not answer it,
|
|
35
40
|
// let the command proceed and fail with the real error.
|
|
@@ -38,10 +43,12 @@ export async function assertCanPayGas(
|
|
|
38
43
|
if (balance > 0n) return;
|
|
39
44
|
|
|
40
45
|
throw new CliError(
|
|
41
|
-
`wallet ${address} has 0
|
|
46
|
+
`wallet ${address} has 0 ${network.gasToken.symbol} on ${network.name}, so it cannot pay for gas`,
|
|
42
47
|
{
|
|
43
48
|
exitCode: 3,
|
|
44
|
-
hint:
|
|
49
|
+
hint: gasIsUsdc
|
|
50
|
+
? `${gasFundingHint(network)}, then re-run. (On ${network.name} gas is paid in USDC, the same balance a stream is escrowed from.)`
|
|
51
|
+
: `${gasFundingHint(network)}, then re-run. (Gas is ${network.gasToken.symbol}; the stream itself is escrowed in USDC, which is a separate balance.)`,
|
|
45
52
|
},
|
|
46
53
|
);
|
|
47
54
|
}
|
|
@@ -49,12 +56,16 @@ export async function assertCanPayGas(
|
|
|
49
56
|
/**
|
|
50
57
|
* Refuse to broadcast a hire the wallet cannot fund.
|
|
51
58
|
*
|
|
52
|
-
* The sibling of `assertCanPayGas`, for the OTHER balance.
|
|
53
|
-
*
|
|
54
|
-
* that passes the gas check can still hold no USDC at all.
|
|
55
|
-
* `open` path sends the USDC approval FIRST — which succeeds,
|
|
56
|
-
* an amount you do not hold is legal — and then reverts
|
|
57
|
-
* having already spent gas to learn something that was
|
|
59
|
+
* The sibling of `assertCanPayGas`, for the OTHER balance. On a network whose
|
|
60
|
+
* gas token is not USDC, gas and deposit are separate balances on separate
|
|
61
|
+
* tokens, so a wallet that passes the gas check can still hold no USDC at all.
|
|
62
|
+
* Without this the `open` path sends the USDC approval FIRST — which succeeds,
|
|
63
|
+
* because approving an amount you do not hold is legal — and then reverts
|
|
64
|
+
* inside `transferFrom`, having already spent gas to learn something that was
|
|
65
|
+
* one `balanceOf` away.
|
|
66
|
+
*
|
|
67
|
+
* Where gas IS USDC there is one balance, and it must cover the deposit AND
|
|
68
|
+
* leave something for gas, so the check is strict (`balance > budget`).
|
|
58
69
|
*
|
|
59
70
|
* Deliberately NOT an allowance check. The CLI's own open path grants the
|
|
60
71
|
* bounded approval it needs (`approveTxHash` in the result), so a missing
|
|
@@ -66,7 +77,9 @@ export async function assertCanFundStream(
|
|
|
66
77
|
read: CliReadClient,
|
|
67
78
|
address: `0x${string}`,
|
|
68
79
|
budgetUsdc: bigint,
|
|
80
|
+
network: MetrikNetwork,
|
|
69
81
|
): Promise<void> {
|
|
82
|
+
const gasIsUsdc = network.gasToken.isSettlementToken;
|
|
70
83
|
let balance: bigint;
|
|
71
84
|
try {
|
|
72
85
|
balance = await read.usdcBalance(address);
|
|
@@ -76,13 +89,15 @@ export async function assertCanFundStream(
|
|
|
76
89
|
// the real error surface.
|
|
77
90
|
return;
|
|
78
91
|
}
|
|
79
|
-
if (balance >= budgetUsdc) return;
|
|
92
|
+
if (gasIsUsdc ? balance > budgetUsdc : balance >= budgetUsdc) return;
|
|
80
93
|
|
|
81
94
|
throw new CliError(
|
|
82
|
-
`wallet ${address} holds ${formatUsdc(balance)} but --budget is ${formatUsdc(budgetUsdc)}`,
|
|
95
|
+
`wallet ${address} holds ${formatUsdc(balance)} but --budget is ${formatUsdc(budgetUsdc)}${gasIsUsdc ? " (plus gas, which is also USDC here)" : ""}`,
|
|
83
96
|
{
|
|
84
97
|
exitCode: 3,
|
|
85
|
-
hint:
|
|
98
|
+
hint: gasIsUsdc
|
|
99
|
+
? `the deposit and the gas are both USDC on ${network.name} — ${usdcFundingHint(network)}, then re-run.`
|
|
100
|
+
: `the deposit is escrowed in USDC — ${usdcFundingHint(network)}, then re-run. (Gas is a separate ${network.gasToken.symbol} balance.)`,
|
|
86
101
|
},
|
|
87
102
|
);
|
|
88
103
|
}
|