@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.
Files changed (331) hide show
  1. package/README.md +106 -30
  2. package/dist/capability/invocation-capability.d.ts +20 -8
  3. package/dist/capability/invocation-capability.d.ts.map +1 -1
  4. package/dist/capability/invocation-capability.js.map +1 -1
  5. package/dist/capability/probe-capability.d.ts +225 -0
  6. package/dist/capability/probe-capability.d.ts.map +1 -0
  7. package/dist/capability/probe-capability.js +248 -0
  8. package/dist/capability/probe-capability.js.map +1 -0
  9. package/dist/cli/args.d.ts +11 -0
  10. package/dist/cli/args.d.ts.map +1 -1
  11. package/dist/cli/args.js +23 -0
  12. package/dist/cli/args.js.map +1 -1
  13. package/dist/cli/commands/claim.d.ts.map +1 -1
  14. package/dist/cli/commands/claim.js +21 -5
  15. package/dist/cli/commands/claim.js.map +1 -1
  16. package/dist/cli/commands/close.d.ts.map +1 -1
  17. package/dist/cli/commands/close.js +51 -7
  18. package/dist/cli/commands/close.js.map +1 -1
  19. package/dist/cli/commands/invoke.d.ts +2 -0
  20. package/dist/cli/commands/invoke.d.ts.map +1 -1
  21. package/dist/cli/commands/invoke.js +3 -2
  22. package/dist/cli/commands/invoke.js.map +1 -1
  23. package/dist/cli/commands/mcp.d.ts.map +1 -1
  24. package/dist/cli/commands/mcp.js +15 -2
  25. package/dist/cli/commands/mcp.js.map +1 -1
  26. package/dist/cli/commands/open.d.ts.map +1 -1
  27. package/dist/cli/commands/open.js +121 -15
  28. package/dist/cli/commands/open.js.map +1 -1
  29. package/dist/cli/commands/pay.d.ts +19 -0
  30. package/dist/cli/commands/pay.d.ts.map +1 -0
  31. package/dist/cli/commands/pay.js +355 -0
  32. package/dist/cli/commands/pay.js.map +1 -0
  33. package/dist/cli/commands/reclaim-unverified.d.ts.map +1 -1
  34. package/dist/cli/commands/reclaim-unverified.js +15 -6
  35. package/dist/cli/commands/reclaim-unverified.js.map +1 -1
  36. package/dist/cli/commands/reclaim.d.ts +23 -2
  37. package/dist/cli/commands/reclaim.d.ts.map +1 -1
  38. package/dist/cli/commands/reclaim.js +200 -21
  39. package/dist/cli/commands/reclaim.js.map +1 -1
  40. package/dist/cli/commands/refund.d.ts +13 -0
  41. package/dist/cli/commands/refund.d.ts.map +1 -0
  42. package/dist/cli/commands/refund.js +224 -0
  43. package/dist/cli/commands/refund.js.map +1 -0
  44. package/dist/cli/commands/status.d.ts.map +1 -1
  45. package/dist/cli/commands/status.js +10 -1
  46. package/dist/cli/commands/status.js.map +1 -1
  47. package/dist/cli/commands/streams.js +9 -13
  48. package/dist/cli/commands/streams.js.map +1 -1
  49. package/dist/cli/commands/watch.d.ts.map +1 -1
  50. package/dist/cli/commands/watch.js +2 -1
  51. package/dist/cli/commands/watch.js.map +1 -1
  52. package/dist/cli/config.d.ts +44 -26
  53. package/dist/cli/config.d.ts.map +1 -1
  54. package/dist/cli/config.js +88 -38
  55. package/dist/cli/config.js.map +1 -1
  56. package/dist/cli/deps.d.ts +86 -7
  57. package/dist/cli/deps.d.ts.map +1 -1
  58. package/dist/cli/deps.js +102 -16
  59. package/dist/cli/deps.js.map +1 -1
  60. package/dist/cli/errors.d.ts.map +1 -1
  61. package/dist/cli/errors.js +21 -1
  62. package/dist/cli/errors.js.map +1 -1
  63. package/dist/cli/escape-hatch.d.ts +6 -4
  64. package/dist/cli/escape-hatch.d.ts.map +1 -1
  65. package/dist/cli/escape-hatch.js +6 -2
  66. package/dist/cli/escape-hatch.js.map +1 -1
  67. package/dist/cli/listings.d.ts +18 -0
  68. package/dist/cli/listings.d.ts.map +1 -1
  69. package/dist/cli/listings.js +22 -12
  70. package/dist/cli/listings.js.map +1 -1
  71. package/dist/cli/preflight.d.ts +20 -14
  72. package/dist/cli/preflight.d.ts.map +1 -1
  73. package/dist/cli/preflight.js +34 -20
  74. package/dist/cli/preflight.js.map +1 -1
  75. package/dist/cli/privy.d.ts +83 -0
  76. package/dist/cli/privy.d.ts.map +1 -0
  77. package/dist/cli/privy.js +232 -0
  78. package/dist/cli/privy.js.map +1 -0
  79. package/dist/cli/reclaimability.d.ts +82 -0
  80. package/dist/cli/reclaimability.d.ts.map +1 -0
  81. package/dist/cli/reclaimability.js +80 -0
  82. package/dist/cli/reclaimability.js.map +1 -0
  83. package/dist/cli/run.d.ts +2 -1
  84. package/dist/cli/run.d.ts.map +1 -1
  85. package/dist/cli/run.js +40 -11
  86. package/dist/cli/run.js.map +1 -1
  87. package/dist/cli/scan-range.d.ts +2 -2
  88. package/dist/cli/scan-range.js +2 -2
  89. package/dist/cli/spend-policy.d.ts +60 -0
  90. package/dist/cli/spend-policy.d.ts.map +1 -0
  91. package/dist/cli/spend-policy.js +65 -0
  92. package/dist/cli/spend-policy.js.map +1 -0
  93. package/dist/cli/status.d.ts +3 -1
  94. package/dist/cli/status.d.ts.map +1 -1
  95. package/dist/cli/status.js +15 -3
  96. package/dist/cli/status.js.map +1 -1
  97. package/dist/cli/wallet.d.ts +109 -40
  98. package/dist/cli/wallet.d.ts.map +1 -1
  99. package/dist/cli/wallet.js +109 -44
  100. package/dist/cli/wallet.js.map +1 -1
  101. package/dist/discovery/rate-band.d.ts +74 -0
  102. package/dist/discovery/rate-band.d.ts.map +1 -0
  103. package/dist/discovery/rate-band.js +114 -0
  104. package/dist/discovery/rate-band.js.map +1 -0
  105. package/dist/escrow-generation.d.ts +81 -0
  106. package/dist/escrow-generation.d.ts.map +1 -0
  107. package/dist/escrow-generation.js +76 -0
  108. package/dist/escrow-generation.js.map +1 -0
  109. package/dist/frameworks/agentkit.d.ts +8 -4
  110. package/dist/frameworks/agentkit.d.ts.map +1 -1
  111. package/dist/frameworks/agentkit.js +27 -10
  112. package/dist/frameworks/agentkit.js.map +1 -1
  113. package/dist/frameworks/eliza.d.ts +3 -3
  114. package/dist/frameworks/eliza.d.ts.map +1 -1
  115. package/dist/frameworks/eliza.js +15 -6
  116. package/dist/frameworks/eliza.js.map +1 -1
  117. package/dist/frameworks/langchain.d.ts.map +1 -1
  118. package/dist/frameworks/langchain.js +8 -0
  119. package/dist/frameworks/langchain.js.map +1 -1
  120. package/dist/gateway/caller-auth-gateway.d.ts +91 -4
  121. package/dist/gateway/caller-auth-gateway.d.ts.map +1 -1
  122. package/dist/gateway/caller-auth-gateway.js +184 -7
  123. package/dist/gateway/caller-auth-gateway.js.map +1 -1
  124. package/dist/gateway/http-server.d.ts +121 -1
  125. package/dist/gateway/http-server.d.ts.map +1 -1
  126. package/dist/gateway/http-server.js +474 -5
  127. package/dist/gateway/http-server.js.map +1 -1
  128. package/dist/gateway/probe-auth.d.ts +112 -0
  129. package/dist/gateway/probe-auth.d.ts.map +1 -0
  130. package/dist/gateway/probe-auth.js +127 -0
  131. package/dist/gateway/probe-auth.js.map +1 -0
  132. package/dist/gateway/signed-rate-bounds.d.ts +51 -0
  133. package/dist/gateway/signed-rate-bounds.d.ts.map +1 -0
  134. package/dist/gateway/signed-rate-bounds.js +94 -0
  135. package/dist/gateway/signed-rate-bounds.js.map +1 -0
  136. package/dist/gateway/x402-opener-mode.d.ts +215 -0
  137. package/dist/gateway/x402-opener-mode.d.ts.map +1 -0
  138. package/dist/gateway/x402-opener-mode.js +639 -0
  139. package/dist/gateway/x402-opener-mode.js.map +1 -0
  140. package/dist/index.d.ts +11 -5
  141. package/dist/index.d.ts.map +1 -1
  142. package/dist/index.js +15 -5
  143. package/dist/index.js.map +1 -1
  144. package/dist/mandates/env.d.ts +3 -2
  145. package/dist/mandates/env.d.ts.map +1 -1
  146. package/dist/mandates/env.js +3 -2
  147. package/dist/mandates/env.js.map +1 -1
  148. package/dist/mcp/server.d.ts +100 -0
  149. package/dist/mcp/server.d.ts.map +1 -1
  150. package/dist/mcp/server.js +719 -39
  151. package/dist/mcp/server.js.map +1 -1
  152. package/dist/mcp/service-invoker.d.ts +97 -0
  153. package/dist/mcp/service-invoker.d.ts.map +1 -0
  154. package/dist/mcp/service-invoker.js +167 -0
  155. package/dist/mcp/service-invoker.js.map +1 -0
  156. package/dist/mcp/x402-tools.d.ts +104 -0
  157. package/dist/mcp/x402-tools.d.ts.map +1 -0
  158. package/dist/mcp/x402-tools.js +434 -0
  159. package/dist/mcp/x402-tools.js.map +1 -0
  160. package/dist/network.d.ts +119 -0
  161. package/dist/network.d.ts.map +1 -0
  162. package/dist/network.js +199 -0
  163. package/dist/network.js.map +1 -0
  164. package/dist/sdk/invoke.d.ts +23 -4
  165. package/dist/sdk/invoke.d.ts.map +1 -1
  166. package/dist/sdk/invoke.js +71 -10
  167. package/dist/sdk/invoke.js.map +1 -1
  168. package/dist/wallet/autonomous-wallet-broker.d.ts +3 -0
  169. package/dist/wallet/autonomous-wallet-broker.d.ts.map +1 -1
  170. package/dist/wallet/autonomous-wallet-broker.js +27 -23
  171. package/dist/wallet/autonomous-wallet-broker.js.map +1 -1
  172. package/dist/wallet/autonomous-wallet-protocol.d.ts +8 -2
  173. package/dist/wallet/autonomous-wallet-protocol.d.ts.map +1 -1
  174. package/dist/wallet/autonomous-wallet-protocol.js +9 -2
  175. package/dist/wallet/autonomous-wallet-protocol.js.map +1 -1
  176. package/dist/wallet/autonomous-wallet.d.ts.map +1 -1
  177. package/dist/wallet/autonomous-wallet.js +3 -2
  178. package/dist/wallet/autonomous-wallet.js.map +1 -1
  179. package/dist/wallet/broker-escrows.d.ts +222 -0
  180. package/dist/wallet/broker-escrows.d.ts.map +1 -0
  181. package/dist/wallet/broker-escrows.js +36 -0
  182. package/dist/wallet/broker-escrows.js.map +1 -0
  183. package/dist/wallet/lifecycle.d.ts +21 -5
  184. package/dist/wallet/lifecycle.d.ts.map +1 -1
  185. package/dist/wallet/lifecycle.js +32 -12
  186. package/dist/wallet/lifecycle.js.map +1 -1
  187. package/dist/wallet/privy-broker-server.d.ts.map +1 -1
  188. package/dist/wallet/privy-broker-server.js +31 -26
  189. package/dist/wallet/privy-broker-server.js.map +1 -1
  190. package/dist/wallet/privy-session-broker.d.ts +14 -5
  191. package/dist/wallet/privy-session-broker.d.ts.map +1 -1
  192. package/dist/wallet/privy-session-broker.js +29 -17
  193. package/dist/wallet/privy-session-broker.js.map +1 -1
  194. package/dist/wallet/privy-session-provider.d.ts +3 -1
  195. package/dist/wallet/privy-session-provider.d.ts.map +1 -1
  196. package/dist/wallet/privy-session-provider.js +5 -3
  197. package/dist/wallet/privy-session-provider.js.map +1 -1
  198. package/dist/wallet/provider.d.ts.map +1 -1
  199. package/dist/wallet/provider.js +15 -8
  200. package/dist/wallet/provider.js.map +1 -1
  201. package/dist/x402/facilitator.d.ts +42 -17
  202. package/dist/x402/facilitator.d.ts.map +1 -1
  203. package/dist/x402/facilitator.js +49 -33
  204. package/dist/x402/facilitator.js.map +1 -1
  205. package/dist/x402-buyer/guarded-fetch.d.ts +51 -0
  206. package/dist/x402-buyer/guarded-fetch.d.ts.map +1 -0
  207. package/dist/x402-buyer/guarded-fetch.js +147 -0
  208. package/dist/x402-buyer/guarded-fetch.js.map +1 -0
  209. package/dist/x402-buyer/index.d.ts +10 -0
  210. package/dist/x402-buyer/index.d.ts.map +1 -0
  211. package/dist/x402-buyer/index.js +10 -0
  212. package/dist/x402-buyer/index.js.map +1 -0
  213. package/dist/x402-buyer/pay.d.ts +135 -0
  214. package/dist/x402-buyer/pay.d.ts.map +1 -0
  215. package/dist/x402-buyer/pay.js +336 -0
  216. package/dist/x402-buyer/pay.js.map +1 -0
  217. package/dist/x402-opener/abi.d.ts +684 -0
  218. package/dist/x402-opener/abi.d.ts.map +1 -0
  219. package/dist/x402-opener/abi.js +880 -0
  220. package/dist/x402-opener/abi.js.map +1 -0
  221. package/dist/x402-opener/facilitator-entry.d.ts +3 -0
  222. package/dist/x402-opener/facilitator-entry.d.ts.map +1 -0
  223. package/dist/x402-opener/facilitator-entry.js +37 -0
  224. package/dist/x402-opener/facilitator-entry.js.map +1 -0
  225. package/dist/x402-opener/facilitator-server.d.ts +180 -0
  226. package/dist/x402-opener/facilitator-server.d.ts.map +1 -0
  227. package/dist/x402-opener/facilitator-server.js +696 -0
  228. package/dist/x402-opener/facilitator-server.js.map +1 -0
  229. package/dist/x402-opener/index.d.ts +14 -0
  230. package/dist/x402-opener/index.d.ts.map +1 -0
  231. package/dist/x402-opener/index.js +14 -0
  232. package/dist/x402-opener/index.js.map +1 -0
  233. package/dist/x402-opener/listing-resolver.d.ts +55 -0
  234. package/dist/x402-opener/listing-resolver.d.ts.map +1 -0
  235. package/dist/x402-opener/listing-resolver.js +125 -0
  236. package/dist/x402-opener/listing-resolver.js.map +1 -0
  237. package/dist/x402-opener/refund-relay.d.ts +240 -0
  238. package/dist/x402-opener/refund-relay.d.ts.map +1 -0
  239. package/dist/x402-opener/refund-relay.js +1240 -0
  240. package/dist/x402-opener/refund-relay.js.map +1 -0
  241. package/dist/x402-opener/relayer-queue.d.ts +14 -0
  242. package/dist/x402-opener/relayer-queue.d.ts.map +1 -0
  243. package/dist/x402-opener/relayer-queue.js +18 -0
  244. package/dist/x402-opener/relayer-queue.js.map +1 -0
  245. package/dist/x402-opener/scheme.d.ts +217 -0
  246. package/dist/x402-opener/scheme.d.ts.map +1 -0
  247. package/dist/x402-opener/scheme.js +749 -0
  248. package/dist/x402-opener/scheme.js.map +1 -0
  249. package/dist/x402-opener/terms.d.ts +105 -0
  250. package/dist/x402-opener/terms.d.ts.map +1 -0
  251. package/dist/x402-opener/terms.js +212 -0
  252. package/dist/x402-opener/terms.js.map +1 -0
  253. package/dist/zktls/reclaim.d.ts +2 -2
  254. package/docs/agent-layer.md +13 -8
  255. package/docs/autonomous-privy-wallet.md +188 -0
  256. package/docs/cli.md +342 -53
  257. package/docs/langchain.md +5 -0
  258. package/docs/mcp-hosted.md +4 -3
  259. package/docs/mcp-reference.md +245 -0
  260. package/docs/probe-capability.md +191 -0
  261. package/docs/quickstart.md +54 -5
  262. package/docs/threat-model.md +24 -3
  263. package/docs/x402.md +681 -0
  264. package/package.json +19 -6
  265. package/src/capability/invocation-capability.ts +17 -1
  266. package/src/capability/probe-capability.ts +355 -0
  267. package/src/cli/args.ts +29 -0
  268. package/src/cli/commands/claim.ts +31 -5
  269. package/src/cli/commands/close.ts +56 -7
  270. package/src/cli/commands/invoke.ts +6 -2
  271. package/src/cli/commands/mcp.ts +18 -2
  272. package/src/cli/commands/open.ts +151 -14
  273. package/src/cli/commands/pay.ts +442 -0
  274. package/src/cli/commands/reclaim-unverified.ts +24 -6
  275. package/src/cli/commands/reclaim.ts +274 -19
  276. package/src/cli/commands/refund.ts +277 -0
  277. package/src/cli/commands/status.ts +17 -1
  278. package/src/cli/commands/streams.ts +11 -15
  279. package/src/cli/commands/watch.ts +7 -1
  280. package/src/cli/config.ts +139 -52
  281. package/src/cli/deps.ts +189 -25
  282. package/src/cli/errors.ts +24 -1
  283. package/src/cli/escape-hatch.ts +19 -5
  284. package/src/cli/listings.ts +43 -10
  285. package/src/cli/preflight.ts +36 -21
  286. package/src/cli/privy.ts +398 -0
  287. package/src/cli/reclaimability.ts +154 -0
  288. package/src/cli/run.ts +54 -11
  289. package/src/cli/scan-range.ts +2 -2
  290. package/src/cli/spend-policy.ts +139 -0
  291. package/src/cli/status.ts +14 -1
  292. package/src/cli/wallet.ts +189 -58
  293. package/src/discovery/rate-band.ts +172 -0
  294. package/src/escrow-generation.ts +135 -0
  295. package/src/frameworks/agentkit.ts +45 -11
  296. package/src/frameworks/eliza.ts +27 -5
  297. package/src/frameworks/langchain.ts +14 -0
  298. package/src/gateway/caller-auth-gateway.ts +336 -13
  299. package/src/gateway/http-server.ts +690 -7
  300. package/src/gateway/probe-auth.ts +191 -0
  301. package/src/gateway/signed-rate-bounds.ts +164 -0
  302. package/src/gateway/x402-opener-mode.ts +974 -0
  303. package/src/index.ts +112 -14
  304. package/src/mandates/env.ts +3 -2
  305. package/src/mcp/server.ts +973 -44
  306. package/src/mcp/service-invoker.ts +285 -0
  307. package/src/mcp/x402-tools.ts +621 -0
  308. package/src/network.ts +302 -0
  309. package/src/sdk/invoke.ts +96 -17
  310. package/src/wallet/autonomous-wallet-broker.ts +46 -20
  311. package/src/wallet/autonomous-wallet-protocol.ts +11 -2
  312. package/src/wallet/autonomous-wallet.ts +4 -2
  313. package/src/wallet/broker-escrows.ts +66 -0
  314. package/src/wallet/lifecycle.ts +53 -18
  315. package/src/wallet/privy-broker-server.ts +35 -25
  316. package/src/wallet/privy-session-broker.ts +58 -25
  317. package/src/wallet/privy-session-provider.ts +7 -3
  318. package/src/wallet/provider.ts +20 -15
  319. package/src/x402/facilitator.ts +85 -46
  320. package/src/x402-buyer/guarded-fetch.ts +213 -0
  321. package/src/x402-buyer/index.ts +9 -0
  322. package/src/x402-buyer/pay.ts +517 -0
  323. package/src/x402-opener/abi.ts +881 -0
  324. package/src/x402-opener/facilitator-entry.ts +45 -0
  325. package/src/x402-opener/facilitator-server.ts +1014 -0
  326. package/src/x402-opener/index.ts +13 -0
  327. package/src/x402-opener/listing-resolver.ts +202 -0
  328. package/src/x402-opener/refund-relay.ts +1743 -0
  329. package/src/x402-opener/relayer-queue.ts +18 -0
  330. package/src/x402-opener/scheme.ts +1133 -0
  331. package/src/x402-opener/terms.ts +300 -0
package/src/network.ts ADDED
@@ -0,0 +1,302 @@
1
+ import {
2
+ findNetwork,
3
+ MetrikClient,
4
+ networkDeployments,
5
+ type MetrikNetworkDeployment,
6
+ getNetwork,
7
+ listNetworks,
8
+ networkForCaip2,
9
+ networkForChainId,
10
+ type MetrikNetwork,
11
+ } from "@absol-labs/sdk";
12
+ import { defineChain, isAddress, isAddressEqual, type Chain } from "viem";
13
+
14
+ /**
15
+ * The agent layer's ONE view of where Metrik is deployed.
16
+ *
17
+ * Every chain id, CAIP-2 id, contract address, deploy block, default RPC, gas
18
+ * token and oracle origin this package uses comes from the `@absol-labs/sdk`
19
+ * network registry through this module. Nothing under `src/` may hardcode them
20
+ * (`test/no-hardcoded-chain.test.ts` fails the build if anything does), so a new
21
+ * deployment — the V3 escrow and factory included — is a registry/config change,
22
+ * not a code change here.
23
+ *
24
+ * What the agent adds on top of the registry is only what the registry does not
25
+ * describe: which network is the DEFAULT for a surface that is given none, and
26
+ * where a test wallet gets funded (a faucet is an off-chain courtesy, not a
27
+ * deployment fact).
28
+ */
29
+
30
+ /**
31
+ * The registry id every surface defaults to when no network is selected. An
32
+ * id, not a chain id: the registry resolves it to the chain, addresses and RPC.
33
+ */
34
+ export const DEFAULT_METRIK_NETWORK_ID = "baseSepolia";
35
+
36
+ /** Environment variable that selects the network by id, chain id or CAIP-2 id. */
37
+ export const METRIK_NETWORK_ENV = "METRIK_NETWORK";
38
+
39
+ /** What a surface is allowed to settle on, beyond "the registry knows it". */
40
+ export interface NetworkPolicy {
41
+ /**
42
+ * Refuse a registered network that is not a testnet. The CLI and the agent
43
+ * frameworks sign with keys read from the environment, so they keep the
44
+ * testnet-only rule; the registry's `testnet` flag is the source of truth.
45
+ */
46
+ readonly testnetOnly?: boolean;
47
+ }
48
+
49
+ /** A network the registry does not know, or one the build does not allow. */
50
+ export class UnsupportedNetworkError extends Error {
51
+ readonly selector: string;
52
+
53
+ constructor(selector: string, message: string) {
54
+ super(message);
55
+ this.name = "UnsupportedNetworkError";
56
+ this.selector = selector;
57
+ }
58
+ }
59
+
60
+ /** The network a surface uses when none is selected. */
61
+ export function defaultNetwork(): MetrikNetwork {
62
+ return getNetwork(DEFAULT_METRIK_NETWORK_ID);
63
+ }
64
+
65
+ /** Every registered network the policy allows, in registry order. */
66
+ export function allowedNetworks(policy: NetworkPolicy = {}): MetrikNetwork[] {
67
+ return listNetworks().filter(
68
+ (network) => policy.testnetOnly !== true || network.testnet,
69
+ );
70
+ }
71
+
72
+ /** `"<chainId> (<name>)"` for every allowed network, for error messages. */
73
+ export function describeAllowedNetworks(policy: NetworkPolicy = {}): string {
74
+ const allowed = allowedNetworks(policy);
75
+ if (allowed.length === 0) return "(none registered)";
76
+ return allowed
77
+ .map((network) => `${network.chain.id} (${network.name}, "${network.id}")`)
78
+ .join(", ");
79
+ }
80
+
81
+ /**
82
+ * Resolve a network selector — a registry id (e.g. the default
83
+ * {@link DEFAULT_METRIK_NETWORK_ID}), an EVM chain id (as a number or a decimal
84
+ * string), or a CAIP-2 id (`eip155:<chainId>`) — to a registered
85
+ * network the policy allows. `undefined` (or empty) selects the default.
86
+ *
87
+ * Fail closed: an unknown selector, or a known network the policy forbids, is an
88
+ * {@link UnsupportedNetworkError}, never a silent fallback to the default.
89
+ */
90
+ export function resolveAgentNetwork(
91
+ selector: string | number | undefined,
92
+ policy: NetworkPolicy = {},
93
+ ): MetrikNetwork {
94
+ const raw =
95
+ typeof selector === "number" ? String(selector) : selector?.trim();
96
+ const network =
97
+ raw === undefined || raw.length === 0
98
+ ? defaultNetwork()
99
+ : lookupNetwork(raw);
100
+ if (network === undefined) {
101
+ throw new UnsupportedNetworkError(
102
+ raw ?? "",
103
+ `unknown Metrik network "${raw}": the SDK network registry knows ${describeAllowedNetworks()}`,
104
+ );
105
+ }
106
+ assertNetworkAllowed(network, policy, raw ?? network.id);
107
+ return network;
108
+ }
109
+
110
+ /** The registered network on `chainId` the policy allows; throws otherwise. */
111
+ export function requireNetworkForChainId(
112
+ chainId: number,
113
+ policy: NetworkPolicy = {},
114
+ source = "the configured chain id",
115
+ ): MetrikNetwork {
116
+ const network = networkForChainId(chainId);
117
+ if (network === undefined) {
118
+ throw new UnsupportedNetworkError(
119
+ String(chainId),
120
+ `${source} ${chainId} is not a network the Metrik registry knows (registered: ${describeAllowedNetworks()})`,
121
+ );
122
+ }
123
+ assertNetworkAllowed(network, policy, source);
124
+ return network;
125
+ }
126
+
127
+ /** The registered network with this CAIP-2 id the policy allows; throws otherwise. */
128
+ export function requireNetworkForCaip2(
129
+ caip2: string,
130
+ policy: NetworkPolicy = {},
131
+ source = "the configured network",
132
+ ): MetrikNetwork {
133
+ const network = networkForCaip2(caip2);
134
+ if (network === undefined) {
135
+ throw new UnsupportedNetworkError(
136
+ caip2,
137
+ `${source} ${caip2} is not a network the Metrik registry knows (registered: ${describeAllowedNetworks()})`,
138
+ );
139
+ }
140
+ assertNetworkAllowed(network, policy, source);
141
+ return network;
142
+ }
143
+
144
+ function lookupNetwork(raw: string): MetrikNetwork | undefined {
145
+ if (/^\d+$/.test(raw)) return networkForChainId(Number(raw));
146
+ if (raw.startsWith("eip155:")) return networkForCaip2(raw);
147
+ return findNetwork(raw);
148
+ }
149
+
150
+ function assertNetworkAllowed(
151
+ network: MetrikNetwork,
152
+ policy: NetworkPolicy,
153
+ source: string,
154
+ ): void {
155
+ if (policy.testnetOnly === true && !network.testnet) {
156
+ throw new UnsupportedNetworkError(
157
+ source,
158
+ `refusing ${network.name} (chain ${network.chain.id}): this surface is testnet-only. Allowed: ${describeAllowedNetworks(policy)}`,
159
+ );
160
+ }
161
+ }
162
+
163
+ /**
164
+ * The network's registered deployment (the CURRENT one, or a `legacy` one that
165
+ * still holds streams) whose escrow is `escrow`, or `undefined`.
166
+ */
167
+ export function registeredDeployment(
168
+ network: MetrikNetwork,
169
+ escrow: string,
170
+ ): (MetrikNetworkDeployment & { readonly legacy: boolean }) | undefined {
171
+ if (!isAddress(escrow)) return undefined;
172
+ const deployments = networkDeployments(network);
173
+ const index = deployments.findIndex((entry) =>
174
+ isAddressEqual(entry.escrow.address, escrow),
175
+ );
176
+ return index < 0
177
+ ? undefined
178
+ : { ...(deployments[index] as MetrikNetworkDeployment), legacy: index > 0 };
179
+ }
180
+
181
+ /**
182
+ * Whether `escrow` is one of the network's registered escrows — the current
183
+ * one or a legacy one (whose streams can still be read, closed and
184
+ * reclaimed; the SDK refuses NEW opens on it unless explicitly opted in).
185
+ */
186
+ export function isRegisteredEscrow(
187
+ network: MetrikNetwork,
188
+ escrow: string,
189
+ ): boolean {
190
+ return registeredDeployment(network, escrow) !== undefined;
191
+ }
192
+
193
+ /**
194
+ * The viem chain for a chain id: the registered network's chain when there is
195
+ * one, otherwise a minimal chain definition around the given RPC (so a custom,
196
+ * explicitly configured deployment still signs with the right chain id).
197
+ */
198
+ export function chainForChainId(chainId: number, rpcUrl: string): Chain {
199
+ const registered = networkForChainId(chainId);
200
+ if (registered !== undefined) return registered.chain;
201
+ return defineChain({
202
+ id: chainId,
203
+ name: `eip155:${String(chainId)}`,
204
+ nativeCurrency: { name: "Ether", symbol: "ETH", decimals: 18 },
205
+ rpcUrls: { default: { http: [rpcUrl] } },
206
+ });
207
+ }
208
+
209
+ /**
210
+ * A read-only `MetrikClient` for one escrow on a REGISTERED network (default:
211
+ * the registry's default network). The chain, the default USDC and oracle, and
212
+ * opener-factory detection all come from the registry; the escrow and RPC are
213
+ * the caller's. A chain id the registry does not know is refused — register the
214
+ * network with the SDK's `registerNetwork()` first.
215
+ */
216
+ export function readOnlyMetrikClient(options: {
217
+ readonly escrowAddress: `0x${string}`;
218
+ readonly rpcUrl: string;
219
+ readonly chainId?: number;
220
+ /**
221
+ * Only for an escrow the registry does NOT record (a deliberate private
222
+ * deployment): its declared generation. The SDK then skips its code-hash
223
+ * pin. Ignored for a registered escrow, whose generation is the registry's.
224
+ */
225
+ readonly unregisteredEscrowGeneration?: "v2" | "v3";
226
+ }): MetrikClient {
227
+ const network =
228
+ options.chainId === undefined
229
+ ? defaultNetwork()
230
+ : requireNetworkForChainId(options.chainId);
231
+ const registered = registeredDeployment(network, options.escrowAddress);
232
+ return MetrikClient.forNetwork(network, {
233
+ escrow: options.escrowAddress,
234
+ rpcUrl: options.rpcUrl,
235
+ ...(registered === undefined &&
236
+ options.unregisteredEscrowGeneration !== undefined
237
+ ? {
238
+ allowUnregisteredEscrow: true,
239
+ generation: options.unregisteredEscrowGeneration,
240
+ }
241
+ : {}),
242
+ });
243
+ }
244
+
245
+ // ── Funding hints ────────────────────────────────────────────────────────────
246
+
247
+ /**
248
+ * Where a test wallet on a network gets its gas and its USDC. Off-chain
249
+ * courtesy data the registry does not carry; keyed by registry id, never by
250
+ * chain id.
251
+ */
252
+ export interface NetworkFunding {
253
+ /** A faucet for the network's GAS token, or `null` if none is known. */
254
+ readonly gasFaucetUrl: string | null;
255
+ /** A faucet for the network's settlement USDC, or `null` if none is known. */
256
+ readonly usdcFaucetUrl: string | null;
257
+ /** The Coinbase CDP faucet's name for this network, or `null` if CDP has none. */
258
+ readonly cdpNetwork: string | null;
259
+ }
260
+
261
+ const NETWORK_FUNDING: Readonly<Record<string, NetworkFunding>> = {
262
+ baseSepolia: {
263
+ gasFaucetUrl: "https://www.alchemy.com/faucets/base-sepolia",
264
+ usdcFaucetUrl: "https://faucet.circle.com",
265
+ cdpNetwork: "base-sepolia",
266
+ },
267
+ };
268
+
269
+ /** Funding hints for a network; all `null` for a network with none known. */
270
+ export function networkFunding(network: MetrikNetwork): NetworkFunding {
271
+ return (
272
+ NETWORK_FUNDING[network.id] ?? {
273
+ gasFaucetUrl: null,
274
+ usdcFaucetUrl: null,
275
+ cdpNetwork: null,
276
+ }
277
+ );
278
+ }
279
+
280
+ /**
281
+ * One sentence telling a user how to fund GAS on this network, read from the
282
+ * network's gas-token metadata. Where gas is the settlement token (USDC), the
283
+ * USDC faucet is the gas faucet.
284
+ */
285
+ export function gasFundingHint(network: MetrikNetwork): string {
286
+ const funding = networkFunding(network);
287
+ const symbol = network.gasToken.symbol;
288
+ const url = network.gasToken.isSettlementToken
289
+ ? funding.usdcFaucetUrl
290
+ : funding.gasFaucetUrl;
291
+ return url === null
292
+ ? `acquire ${symbol} on ${network.name}`
293
+ : `fund it with ${symbol} at ${url}`;
294
+ }
295
+
296
+ /** One sentence telling a user how to fund the settlement USDC on this network. */
297
+ export function usdcFundingHint(network: MetrikNetwork): string {
298
+ const url = networkFunding(network).usdcFaucetUrl;
299
+ return url === null
300
+ ? `acquire USDC on ${network.name}`
301
+ : `fund it with USDC at ${url} (${network.name})`;
302
+ }
package/src/sdk/invoke.ts CHANGED
@@ -6,7 +6,6 @@ import {
6
6
  signedServiceDescriptorSchema,
7
7
  type DeliveryReceiptDomainInput,
8
8
  } from "@absol-labs/shared";
9
- import { MetrikClient } from "@absol-labs/sdk";
10
9
  import type { Address, Hex, LocalAccount } from "viem";
11
10
 
12
11
  import type { ServiceListing } from "../discovery/registry.js";
@@ -24,11 +23,13 @@ import {
24
23
  hashInvocationPath,
25
24
  signInvocationCapability,
26
25
  type HttpMethod,
26
+ type InvocationCapabilitySigner,
27
27
  type InvocationCapability,
28
28
  type InvocationCapabilityDomainInput,
29
29
  type SignedInvocationCapability,
30
30
  DEFAULT_CAPABILITY_TTL_SECONDS,
31
31
  } from "../capability/invocation-capability.js";
32
+ import { readOnlyMetrikClient } from "../network.js";
32
33
 
33
34
  /**
34
35
  * Buyer half of the pay -> use loop (metrik-agent#64, impl of #62 part 2/2).
@@ -56,11 +57,12 @@ export interface InvokeStreamReader {
56
57
  export function createSdkInvokeStreamReader(options: {
57
58
  readonly escrowAddress: Address;
58
59
  readonly rpcUrl: string;
60
+ /** Chain of the escrow; must be a registered network. Default: the registry default. */
61
+ readonly chainId?: number;
62
+ /** See `readOnlyMetrikClient`: only for an escrow the registry does not record. */
63
+ readonly unregisteredEscrowGeneration?: "v2" | "v3";
59
64
  }): InvokeStreamReader {
60
- const metrik = MetrikClient.baseSepolia({
61
- escrow: options.escrowAddress,
62
- rpcUrl: options.rpcUrl,
63
- });
65
+ const metrik = readOnlyMetrikClient(options);
64
66
  return {
65
67
  async getStreamV2(streamId) {
66
68
  const stream = await metrik.getStreamV2(streamId);
@@ -115,16 +117,14 @@ export class InvokeAccessUrlError extends Error {
115
117
  }
116
118
 
117
119
  export class InvokePathError extends Error {
118
- constructor(path: string) {
119
- super(
120
- `invocation path must be origin-relative and start with one slash: ${path}`,
121
- );
120
+ constructor(message: string) {
121
+ super(message);
122
122
  this.name = "InvokePathError";
123
123
  }
124
124
  }
125
125
 
126
126
  export interface CapabilityForOptions {
127
- readonly buyer: LocalAccount;
127
+ readonly buyer: InvocationCapabilitySigner;
128
128
  readonly domain: InvocationCapabilityDomainInput;
129
129
  readonly serviceRef: Hex;
130
130
  /** Default {@link DEFAULT_CAPABILITY_TTL_SECONDS}. */
@@ -173,7 +173,7 @@ export interface InvokeOptions {
173
173
  /** Reads the stream's current serviceRef/operator/status/expiry. Real default: a `MetrikClient` instance. */
174
174
  readonly streamReader: InvokeStreamReader;
175
175
  /** The buyer's own wallet - signs the capability, never broadcasts a transaction. */
176
- readonly buyer: LocalAccount;
176
+ readonly buyer: InvocationCapabilitySigner;
177
177
  /** `{chainId, verifyingContract}` - the `StreamEscrowV2` the stream settles on. */
178
178
  readonly domain: InvocationCapabilityDomainInput;
179
179
  /** Preferred: a cryptographically verified result from discoverServices(). */
@@ -226,6 +226,10 @@ export async function invoke(
226
226
  stream.serviceRef,
227
227
  options,
228
228
  );
229
+ // Resolve BEFORE signing. A path that escapes the signed base must never
230
+ // cause a buyer-signed capability to exist at all, let alone be sent
231
+ // somewhere the operator did not sign.
232
+ const url = resolveInvocationUrl(serviceBaseUrl, request.path);
229
233
 
230
234
  const capability = await capabilityFor(streamId, request, {
231
235
  buyer: options.buyer,
@@ -239,10 +243,6 @@ export async function invoke(
239
243
  });
240
244
 
241
245
  const header = encodeCapabilityHeader(capability);
242
- const url = new URL(
243
- request.path,
244
- ensureTrailingSlash(serviceBaseUrl),
245
- ).toString();
246
246
  const fetchImpl = options.fetchImpl ?? fetch;
247
247
  const response = await fetchImpl(url, {
248
248
  method: request.method,
@@ -484,10 +484,89 @@ function requireHttpUrl(value: string, label: string): string {
484
484
  return parsed.toString();
485
485
  }
486
486
 
487
+ /**
488
+ * Characters that must never appear in an invocation path.
489
+ *
490
+ * The backslash is the important one. The WHATWG URL parser treats `\` as `/`
491
+ * for special schemes, so `/\evil.com/x` resolved against `https://good/`
492
+ * yields `https://evil.com/x` — a prefix check for `//` does not see it. C0
493
+ * controls and DEL are rejected alongside it because a header- or log-splitting
494
+ * character has no business in a path either.
495
+ */
496
+ const FORBIDDEN_PATH_CHARACTERS = /[\\\u0000-\u001f\u007f]/;
497
+
487
498
  function assertOriginRelativePath(path: string): void {
488
- if (!path.startsWith("/") || path.startsWith("//") || path.includes("#")) {
489
- throw new InvokePathError(path);
499
+ if (!path.startsWith("/") || path.startsWith("//")) {
500
+ throw new InvokePathError(
501
+ `invocation path must be origin-relative and start with one slash: ${JSON.stringify(path)}`,
502
+ );
503
+ }
504
+ if (path.includes("#")) {
505
+ throw new InvokePathError(
506
+ `invocation path must not contain a fragment: ${JSON.stringify(path)}`,
507
+ );
508
+ }
509
+ if (FORBIDDEN_PATH_CHARACTERS.test(path)) {
510
+ throw new InvokePathError(
511
+ `invocation path must not contain a backslash or a control character: ${JSON.stringify(path)}`,
512
+ );
513
+ }
514
+ }
515
+
516
+ /**
517
+ * Resolves `path` against the operator-signed base URL and PROVES the result
518
+ * did not leave it.
519
+ *
520
+ * Prefix checks on the raw path are not sufficient and never were: the URL
521
+ * parser decides where a string points, so the only trustworthy check is to
522
+ * resolve first and then compare what came out. `/\evil.com/x` and
523
+ * `/a/../../b` both look origin-relative and both leave the signed base.
524
+ *
525
+ * What escaping would cost, concretely: the buyer's signed EIP-712 capability
526
+ * is handed to an attacker-controlled host, the attacker's response is
527
+ * returned to the caller as if it were the paid service's answer, and the
528
+ * MCP host's own network becomes reachable from a tool argument.
529
+ */
530
+ export function resolveInvocationUrl(
531
+ serviceBaseUrl: string,
532
+ path: string,
533
+ ): string {
534
+ assertOriginRelativePath(path);
535
+ const base = new URL(ensureTrailingSlash(serviceBaseUrl));
536
+ let resolved: URL;
537
+ try {
538
+ resolved = new URL(path, base);
539
+ } catch {
540
+ throw new InvokePathError(
541
+ `invocation path does not resolve against the signed service URL: ${JSON.stringify(path)}`,
542
+ );
543
+ }
544
+ if (resolved.origin !== base.origin || resolved.origin === "null") {
545
+ throw new InvokePathError(
546
+ `invocation path resolves to ${resolved.origin}, which is not the operator-signed origin ${base.origin}`,
547
+ );
548
+ }
549
+ if (resolved.username !== "" || resolved.password !== "") {
550
+ throw new InvokePathError(
551
+ "invocation path must not carry credentials in the resolved URL",
552
+ );
553
+ }
554
+ // `base.pathname` always ends in `/` (ensureTrailingSlash), so a prefix match
555
+ // on the RESOLVED pathname is a true containment check — `..` segments have
556
+ // already been collapsed by the parser.
557
+ //
558
+ // An absolute-path reference replaces the whole path, so this ALSO rejects a
559
+ // signed base URL carrying a sub-path (`https://host/base/`). That is
560
+ // deliberate: the previous behaviour silently discarded the operator's signed
561
+ // `/base/` and called the origin root instead — an origin path the operator
562
+ // never signed, and one the gateway's own pathHash check could not match.
563
+ // Refusing says so instead of quietly calling the wrong place.
564
+ if (!resolved.pathname.startsWith(base.pathname)) {
565
+ throw new InvokePathError(
566
+ `invocation path ${JSON.stringify(path)} resolves to ${resolved.pathname}, which is outside the operator-signed base path ${base.pathname}. An origin-relative path replaces the whole path, so a signed base URL with a sub-path cannot be addressed this way.`,
567
+ );
490
568
  }
569
+ return resolved.toString();
491
570
  }
492
571
 
493
572
  function ensureTrailingSlash(value: string): string {
@@ -4,7 +4,6 @@ import {
4
4
  formatRequestForAuthorizationSignature,
5
5
  type PrivyClient,
6
6
  } from "@privy-io/node";
7
- import { streamEscrowV2Abi } from "@absol-labs/shared";
8
7
  import {
9
8
  decodeFunctionData,
10
9
  getAddress,
@@ -16,6 +15,7 @@ import { z } from "zod";
16
15
 
17
16
  import {
18
17
  autonomousProofBytes,
18
+ AUTONOMOUS_WALLET_CAIP2,
19
19
  AUTONOMOUS_WALLET_CHAIN_ID,
20
20
  AUTONOMOUS_WALLET_VERSION,
21
21
  type AutonomousWalletBroker as AutonomousWalletClientBroker,
@@ -35,6 +35,14 @@ import {
35
35
  PRIVY_BROKER_USDC,
36
36
  type BrokerStreamReader,
37
37
  } from "./privy-session-broker.js";
38
+ import {
39
+ BROKER_EXIT_METHODS,
40
+ brokerEscrowPolicy,
41
+ decodeEscrowCall,
42
+ escrowRole,
43
+ type BrokerEscrowPolicy,
44
+ } from "./broker-escrows.js";
45
+ import { defaultNetwork } from "../network.js";
38
46
 
39
47
  const transactionSchema = z.object({
40
48
  from: z.string().refine(isAddress),
@@ -137,6 +145,8 @@ export interface AutonomousWalletBrokerOptions {
137
145
  readonly requestTtlSeconds?: number;
138
146
  /** Must match the base URL used by the Privy client, without a trailing slash. */
139
147
  readonly privyApiBaseUrl?: string;
148
+ /** Current + legacy escrows. Default: from the registries (see `./broker-escrows.ts`). */
149
+ readonly escrows?: BrokerEscrowPolicy;
140
150
  }
141
151
 
142
152
  /**
@@ -288,6 +298,7 @@ export class AutonomousWalletBroker implements AutonomousWalletClientBroker {
288
298
  provision.address,
289
299
  this.options.streamReader,
290
300
  this.nowSeconds(),
301
+ this.options.escrows ?? brokerEscrowPolicy(defaultNetwork()),
291
302
  );
292
303
  const requestExpiryMs = (this.nowSeconds() + this.requestTtl) * 1_000;
293
304
  const idempotencyKey = randomUUID();
@@ -558,6 +569,7 @@ async function buildRpcBody(
558
569
  address: Address,
559
570
  streamReader: BrokerStreamReader,
560
571
  now: number,
572
+ escrows: BrokerEscrowPolicy,
561
573
  ): Promise<Record<string, unknown>> {
562
574
  if (method === "eth_sendTransaction") {
563
575
  const tx = transactionSchema.parse(params[0]);
@@ -568,11 +580,19 @@ async function buildRpcBody(
568
580
  );
569
581
  assertChain(tx.chainId);
570
582
  assertZeroValue(tx.value);
571
- const target = getAddress(tx.to).toLowerCase();
572
- if (target === PRIVY_BROKER_USDC.toLowerCase()) {
573
- assertUsdcApproval(tx.data as Hex);
574
- } else if (target === PRIVY_BROKER_ESCROW.toLowerCase()) {
575
- await assertEscrowMethod(tx.data as Hex, address, streamReader);
583
+ const target = getAddress(tx.to);
584
+ const role = escrowRole(escrows, target);
585
+ if (target.toLowerCase() === PRIVY_BROKER_USDC.toLowerCase()) {
586
+ // The approval spender must be the CURRENT escrow, never a legacy one.
587
+ assertUsdcApproval(tx.data as Hex, escrows.current);
588
+ } else if (role !== null) {
589
+ await assertEscrowMethod(
590
+ tx.data as Hex,
591
+ address,
592
+ streamReader,
593
+ target,
594
+ role,
595
+ );
576
596
  } else {
577
597
  throw new AutonomousWalletBrokerError(
578
598
  "policy-denied",
@@ -583,7 +603,7 @@ async function buildRpcBody(
583
603
  return {
584
604
  method,
585
605
  chain_type: "ethereum",
586
- caip2: "eip155:84532",
606
+ caip2: AUTONOMOUS_WALLET_CAIP2,
587
607
  params: { transaction: normalizeJson(transaction) },
588
608
  };
589
609
  }
@@ -613,7 +633,7 @@ async function buildRpcBody(
613
633
  parsed.domain.name !== invocationCapabilityDomainName ||
614
634
  parsed.domain.version !== invocationCapabilityDomainVersion ||
615
635
  parsed.domain.verifyingContract.toLowerCase() !==
616
- PRIVY_BROKER_ESCROW.toLowerCase()
636
+ escrows.current.toLowerCase()
617
637
  )
618
638
  throw new AutonomousWalletBrokerError(
619
639
  "policy-denied",
@@ -655,7 +675,7 @@ async function buildRpcBody(
655
675
  return {
656
676
  method,
657
677
  chain_type: "ethereum",
658
- caip2: "eip155:84532",
678
+ caip2: AUTONOMOUS_WALLET_CAIP2,
659
679
  params: {
660
680
  typed_data: normalizeJson({
661
681
  ...wireTypedData,
@@ -704,7 +724,7 @@ function assertZeroValue(value: string | number | bigint | undefined): void {
704
724
  }
705
725
  }
706
726
 
707
- function assertUsdcApproval(data: Hex): void {
727
+ function assertUsdcApproval(data: Hex, currentEscrow: Address): void {
708
728
  try {
709
729
  const decoded = decodeFunctionData({
710
730
  abi: [
@@ -723,7 +743,7 @@ function assertUsdcApproval(data: Hex): void {
723
743
  });
724
744
  const [spender, amount] = decoded.args;
725
745
  if (
726
- spender.toLowerCase() !== PRIVY_BROKER_ESCROW.toLowerCase() ||
746
+ spender.toLowerCase() !== currentEscrow.toLowerCase() ||
727
747
  amount > 1_000_000n
728
748
  )
729
749
  throw new Error("approval outside Metrik policy");
@@ -740,10 +760,13 @@ async function assertEscrowMethod(
740
760
  data: Hex,
741
761
  address: Address,
742
762
  streamReader: BrokerStreamReader,
763
+ escrow: Address,
764
+ role: "current" | "legacy",
743
765
  ): Promise<void> {
744
766
  try {
745
- const decoded = decodeFunctionData({ abi: streamEscrowV2Abi, data });
746
- if (decoded.functionName === "openStream") {
767
+ const decoded = decodeEscrowCall(data);
768
+ // A legacy escrow is exit-only: openStream there falls through and fails.
769
+ if (decoded.functionName === "openStream" && role === "current") {
747
770
  const [, , deposit, rate, duration] = decoded.args;
748
771
  if (
749
772
  deposit > PRIVY_POLICY_MAX_TOTAL_USDC ||
@@ -754,14 +777,17 @@ async function assertEscrowMethod(
754
777
  }
755
778
  return;
756
779
  }
757
- if (
758
- decoded.functionName !== "close" &&
759
- decoded.functionName !== "reclaim" &&
760
- decoded.functionName !== "reclaimUnverified"
761
- ) {
762
- throw new Error("escrow method is not allowlisted");
780
+ if (!BROKER_EXIT_METHODS.has(decoded.functionName)) {
781
+ throw new Error(
782
+ role === "legacy"
783
+ ? "only close, reclaim and reclaimUnverified are allowed on a legacy escrow"
784
+ : "escrow method is not allowlisted",
785
+ );
763
786
  }
764
- const stream = await streamReader.getStream(decoded.args[0]);
787
+ const stream = await streamReader.getStream(
788
+ (decoded.args as readonly unknown[])[0] as Hex,
789
+ escrow,
790
+ );
765
791
  if (stream.buyer.toLowerCase() !== address.toLowerCase())
766
792
  throw new Error("autonomous wallet is not the stream buyer");
767
793
  } catch (error) {
@@ -1,8 +1,17 @@
1
1
  import type { Address, Hex } from "viem";
2
2
 
3
+ import { defaultNetwork } from "../network.js";
4
+
3
5
  export const AUTONOMOUS_WALLET_VERSION = 1 as const;
4
- export const AUTONOMOUS_WALLET_CHAIN_ID = 84532 as const;
5
- export const AUTONOMOUS_WALLET_CAIP2 = "eip155:84532" as const;
6
+ /**
7
+ * The network an agent-owned Privy wallet is provisioned and policed on: the
8
+ * registry's default network. A broker's Privy policy allowlists ONE escrow and
9
+ * ONE USDC, so the wallet is single-network by construction; the values come
10
+ * from the `@absol-labs/sdk` registry rather than literals.
11
+ */
12
+ export const AUTONOMOUS_WALLET_CHAIN_ID: number = defaultNetwork().chain.id;
13
+ export const AUTONOMOUS_WALLET_CAIP2: `eip155:${number}` =
14
+ defaultNetwork().caip2;
6
15
 
7
16
  export interface PreparedPrivyRequest {
8
17
  readonly requestId: string;
@@ -27,6 +27,7 @@ import {
27
27
  type AutonomousWalletCredentialStore,
28
28
  type AutonomousWalletRecord,
29
29
  } from "./autonomous-wallet-store.js";
30
+ import { defaultNetwork } from "../network.js";
30
31
 
31
32
  export {
32
33
  AUTONOMOUS_WALLET_CAIP2,
@@ -366,7 +367,8 @@ export function createAutonomousWalletProvider(
366
367
  let rpcId = 0;
367
368
  return {
368
369
  async request({ method, params = [] }) {
369
- if (method === "eth_chainId") return "0x14a34";
370
+ if (method === "eth_chainId")
371
+ return `0x${AUTONOMOUS_WALLET_CHAIN_ID.toString(16)}`;
370
372
  if (method === "eth_accounts" || method === "eth_requestAccounts")
371
373
  return [address];
372
374
  if (
@@ -410,7 +412,7 @@ export function createAutonomousWalletProvider(
410
412
  }
411
413
  const response = await (
412
414
  options.fetchImpl ?? globalThis.fetch.bind(globalThis)
413
- )(options.rpcUrl ?? "https://base-sepolia-rpc.publicnode.com", {
415
+ )(options.rpcUrl ?? defaultNetwork().rpcUrls[0], {
414
416
  method: "POST",
415
417
  headers: { "content-type": "application/json" },
416
418
  body: JSON.stringify(