@stellar-agent-services/provider-kit 0.0.0-stage → 0.1.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 (220) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +508 -2
  3. package/dist/approve/assets/index-BazIJAI2.css +1 -0
  4. package/dist/approve/assets/index-CL28cSJy.js +420 -0
  5. package/dist/approve/favicon.svg +1 -0
  6. package/dist/approve/index.html +18 -0
  7. package/dist/cli/approval-view.d.ts +80 -0
  8. package/dist/cli/approval-view.d.ts.map +1 -0
  9. package/dist/cli/approval-view.js +5 -0
  10. package/dist/cli/approval-view.js.map +1 -0
  11. package/dist/cli/approval.d.ts +132 -0
  12. package/dist/cli/approval.d.ts.map +1 -0
  13. package/dist/cli/approval.js +542 -0
  14. package/dist/cli/approval.js.map +1 -0
  15. package/dist/cli/check.d.ts +3 -0
  16. package/dist/cli/check.d.ts.map +1 -0
  17. package/dist/cli/check.js +137 -0
  18. package/dist/cli/check.js.map +1 -0
  19. package/dist/cli/dev-account.d.ts +28 -0
  20. package/dist/cli/dev-account.d.ts.map +1 -0
  21. package/dist/cli/dev-account.js +62 -0
  22. package/dist/cli/dev-account.js.map +1 -0
  23. package/dist/cli/dev.d.ts +27 -0
  24. package/dist/cli/dev.d.ts.map +1 -0
  25. package/dist/cli/dev.js +226 -0
  26. package/dist/cli/dev.js.map +1 -0
  27. package/dist/cli/discovery.d.ts +20 -0
  28. package/dist/cli/discovery.d.ts.map +1 -0
  29. package/dist/cli/discovery.js +202 -0
  30. package/dist/cli/discovery.js.map +1 -0
  31. package/dist/cli/env-file.d.ts +16 -0
  32. package/dist/cli/env-file.d.ts.map +1 -0
  33. package/dist/cli/env-file.js +77 -0
  34. package/dist/cli/env-file.js.map +1 -0
  35. package/dist/cli/fly.d.ts +28 -0
  36. package/dist/cli/fly.d.ts.map +1 -0
  37. package/dist/cli/fly.js +92 -0
  38. package/dist/cli/fly.js.map +1 -0
  39. package/dist/cli/index.d.ts +3 -0
  40. package/dist/cli/index.d.ts.map +1 -0
  41. package/dist/cli/index.js +11 -0
  42. package/dist/cli/index.js.map +1 -0
  43. package/dist/cli/init.d.ts +3 -0
  44. package/dist/cli/init.d.ts.map +1 -0
  45. package/dist/cli/init.js +170 -0
  46. package/dist/cli/init.js.map +1 -0
  47. package/dist/cli/launch.d.ts +21 -0
  48. package/dist/cli/launch.d.ts.map +1 -0
  49. package/dist/cli/launch.js +627 -0
  50. package/dist/cli/launch.js.map +1 -0
  51. package/dist/cli/main.d.ts +2 -0
  52. package/dist/cli/main.d.ts.map +1 -0
  53. package/dist/cli/main.js +158 -0
  54. package/dist/cli/main.js.map +1 -0
  55. package/dist/cli/owner-chain.d.ts +88 -0
  56. package/dist/cli/owner-chain.d.ts.map +1 -0
  57. package/dist/cli/owner-chain.js +284 -0
  58. package/dist/cli/owner-chain.js.map +1 -0
  59. package/dist/cli/owner-steps.d.ts +33 -0
  60. package/dist/cli/owner-steps.d.ts.map +1 -0
  61. package/dist/cli/owner-steps.js +121 -0
  62. package/dist/cli/owner-steps.js.map +1 -0
  63. package/dist/cli/processes.d.ts +47 -0
  64. package/dist/cli/processes.d.ts.map +1 -0
  65. package/dist/cli/processes.js +129 -0
  66. package/dist/cli/processes.js.map +1 -0
  67. package/dist/cli/reconcile.d.ts +3 -0
  68. package/dist/cli/reconcile.d.ts.map +1 -0
  69. package/dist/cli/reconcile.js +76 -0
  70. package/dist/cli/reconcile.js.map +1 -0
  71. package/dist/cli/register.d.ts +9 -0
  72. package/dist/cli/register.d.ts.map +1 -0
  73. package/dist/cli/register.js +209 -0
  74. package/dist/cli/register.js.map +1 -0
  75. package/dist/cli/request-validation.d.ts +5 -0
  76. package/dist/cli/request-validation.d.ts.map +1 -0
  77. package/dist/cli/request-validation.js +144 -0
  78. package/dist/cli/request-validation.js.map +1 -0
  79. package/dist/cli/serve.d.ts +25 -0
  80. package/dist/cli/serve.d.ts.map +1 -0
  81. package/dist/cli/serve.js +73 -0
  82. package/dist/cli/serve.js.map +1 -0
  83. package/dist/cli/shared.d.ts +142 -0
  84. package/dist/cli/shared.d.ts.map +1 -0
  85. package/dist/cli/shared.js +110 -0
  86. package/dist/cli/shared.js.map +1 -0
  87. package/dist/cli/status.d.ts +32 -0
  88. package/dist/cli/status.d.ts.map +1 -0
  89. package/dist/cli/status.js +248 -0
  90. package/dist/cli/status.js.map +1 -0
  91. package/dist/cli/target.d.ts +14 -0
  92. package/dist/cli/target.d.ts.map +1 -0
  93. package/dist/cli/target.js +43 -0
  94. package/dist/cli/target.js.map +1 -0
  95. package/dist/cli/trustline.d.ts +3 -0
  96. package/dist/cli/trustline.d.ts.map +1 -0
  97. package/dist/cli/trustline.js +114 -0
  98. package/dist/cli/trustline.js.map +1 -0
  99. package/dist/cli/try.d.ts +54 -0
  100. package/dist/cli/try.d.ts.map +1 -0
  101. package/dist/cli/try.js +450 -0
  102. package/dist/cli/try.js.map +1 -0
  103. package/dist/cli/tunnel.d.ts +13 -0
  104. package/dist/cli/tunnel.d.ts.map +1 -0
  105. package/dist/cli/tunnel.js +43 -0
  106. package/dist/cli/tunnel.js.map +1 -0
  107. package/dist/cli/validators.d.ts +6 -0
  108. package/dist/cli/validators.d.ts.map +1 -0
  109. package/dist/cli/validators.js +14 -0
  110. package/dist/cli/validators.js.map +1 -0
  111. package/dist/define-service.d.ts +71 -0
  112. package/dist/define-service.d.ts.map +1 -0
  113. package/dist/define-service.js +86 -0
  114. package/dist/define-service.js.map +1 -0
  115. package/dist/gateway/config.d.ts +88 -0
  116. package/dist/gateway/config.d.ts.map +1 -0
  117. package/dist/gateway/config.js +201 -0
  118. package/dist/gateway/config.js.map +1 -0
  119. package/dist/gateway/openapi.d.ts +69 -0
  120. package/dist/gateway/openapi.d.ts.map +1 -0
  121. package/dist/gateway/openapi.js +677 -0
  122. package/dist/gateway/openapi.js.map +1 -0
  123. package/dist/gateway/upstream.d.ts +15 -0
  124. package/dist/gateway/upstream.d.ts.map +1 -0
  125. package/dist/gateway/upstream.js +95 -0
  126. package/dist/gateway/upstream.js.map +1 -0
  127. package/dist/http/bounded.d.ts +3 -0
  128. package/dist/http/bounded.d.ts.map +1 -0
  129. package/dist/http/bounded.js +23 -0
  130. package/dist/http/bounded.js.map +1 -0
  131. package/dist/http/node-server.d.ts +15 -0
  132. package/dist/http/node-server.d.ts.map +1 -0
  133. package/dist/http/node-server.js +120 -0
  134. package/dist/http/node-server.js.map +1 -0
  135. package/dist/http/rate-limit.d.ts +11 -0
  136. package/dist/http/rate-limit.d.ts.map +1 -0
  137. package/dist/http/rate-limit.js +35 -0
  138. package/dist/http/rate-limit.js.map +1 -0
  139. package/dist/http/router.d.ts +29 -0
  140. package/dist/http/router.d.ts.map +1 -0
  141. package/dist/http/router.js +329 -0
  142. package/dist/http/router.js.map +1 -0
  143. package/dist/index.d.ts +20 -0
  144. package/dist/index.d.ts.map +1 -0
  145. package/dist/index.js +31 -0
  146. package/dist/index.js.map +1 -0
  147. package/dist/jobs/memory-store.d.ts +3 -0
  148. package/dist/jobs/memory-store.d.ts.map +1 -0
  149. package/dist/jobs/memory-store.js +121 -0
  150. package/dist/jobs/memory-store.js.map +1 -0
  151. package/dist/jobs/reconcile.d.ts +22 -0
  152. package/dist/jobs/reconcile.d.ts.map +1 -0
  153. package/dist/jobs/reconcile.js +82 -0
  154. package/dist/jobs/reconcile.js.map +1 -0
  155. package/dist/jobs/recovery.d.ts +19 -0
  156. package/dist/jobs/recovery.d.ts.map +1 -0
  157. package/dist/jobs/recovery.js +65 -0
  158. package/dist/jobs/recovery.js.map +1 -0
  159. package/dist/jobs/runner.d.ts +38 -0
  160. package/dist/jobs/runner.d.ts.map +1 -0
  161. package/dist/jobs/runner.js +194 -0
  162. package/dist/jobs/runner.js.map +1 -0
  163. package/dist/jobs/scheduler.d.ts +66 -0
  164. package/dist/jobs/scheduler.d.ts.map +1 -0
  165. package/dist/jobs/scheduler.js +197 -0
  166. package/dist/jobs/scheduler.js.map +1 -0
  167. package/dist/jobs/sqlite-store.d.ts +8 -0
  168. package/dist/jobs/sqlite-store.d.ts.map +1 -0
  169. package/dist/jobs/sqlite-store.js +231 -0
  170. package/dist/jobs/sqlite-store.js.map +1 -0
  171. package/dist/jobs/store.d.ts +118 -0
  172. package/dist/jobs/store.d.ts.map +1 -0
  173. package/dist/jobs/store.js +36 -0
  174. package/dist/jobs/store.js.map +1 -0
  175. package/dist/manifest.d.ts +11 -0
  176. package/dist/manifest.d.ts.map +1 -0
  177. package/dist/manifest.js +99 -0
  178. package/dist/manifest.js.map +1 -0
  179. package/dist/payments/chain.d.ts +23 -0
  180. package/dist/payments/chain.d.ts.map +1 -0
  181. package/dist/payments/chain.js +108 -0
  182. package/dist/payments/chain.js.map +1 -0
  183. package/dist/payments/facilitator.d.ts +44 -0
  184. package/dist/payments/facilitator.d.ts.map +1 -0
  185. package/dist/payments/facilitator.js +125 -0
  186. package/dist/payments/facilitator.js.map +1 -0
  187. package/dist/payments/onchain.d.ts +36 -0
  188. package/dist/payments/onchain.d.ts.map +1 -0
  189. package/dist/payments/onchain.js +191 -0
  190. package/dist/payments/onchain.js.map +1 -0
  191. package/dist/payments/payload-checks.d.ts +22 -0
  192. package/dist/payments/payload-checks.d.ts.map +1 -0
  193. package/dist/payments/payload-checks.js +113 -0
  194. package/dist/payments/payload-checks.js.map +1 -0
  195. package/dist/payments/settlement.d.ts +22 -0
  196. package/dist/payments/settlement.d.ts.map +1 -0
  197. package/dist/payments/settlement.js +65 -0
  198. package/dist/payments/settlement.js.map +1 -0
  199. package/dist/payments/x402-server.d.ts +8 -0
  200. package/dist/payments/x402-server.d.ts.map +1 -0
  201. package/dist/payments/x402-server.js +74 -0
  202. package/dist/payments/x402-server.js.map +1 -0
  203. package/dist/provider.d.ts +63 -0
  204. package/dist/provider.d.ts.map +1 -0
  205. package/dist/provider.js +177 -0
  206. package/dist/provider.js.map +1 -0
  207. package/dist/registration.d.ts +86 -0
  208. package/dist/registration.d.ts.map +1 -0
  209. package/dist/registration.js +162 -0
  210. package/dist/registration.js.map +1 -0
  211. package/dist/testing.d.ts +13 -0
  212. package/dist/testing.d.ts.map +1 -0
  213. package/dist/testing.js +48 -0
  214. package/dist/testing.js.map +1 -0
  215. package/dist/validation-request.d.ts +36 -0
  216. package/dist/validation-request.d.ts.map +1 -0
  217. package/dist/validation-request.js +101 -0
  218. package/dist/validation-request.js.map +1 -0
  219. package/package.json +55 -4
  220. package/schemas/gateway-config.schema.json +140 -0
@@ -0,0 +1,627 @@
1
+ // provider-kit launch: takes a provider live in one go (docs/provider-setup-guide.md §4).
2
+ //
3
+ // provider-kit launch [--config <file> | --start '<command>' | --manifest <url>] [--url <https origin>]
4
+ // [--validator <G...> | --no-validation] [--deploy | --no-deploy] [--no-wait]
5
+ //
6
+ // 1. The public address: --url, else the app in fly.toml (https://<app>.fly.dev), else an https:// PUBLIC_ORIGIN. It is
7
+ // known before the first deploy, so the service is registered first and deployed once, with its agent id.
8
+ // 2. The manifest as that address will serve it, checked against the standard: built from the gateway config, from the
9
+ // provider's own command started here with PUBLIC_ORIGIN set (--start), or read from a deployed provider (--manifest).
10
+ // 3. One approval page and one wallet for every owner step: test XLM for a new testnet account, the USDC trustline [P2],
11
+ // the Stellar 8004 registration [I1]–[I3] (or its update, when the manifest moved), and a validation request to a
12
+ // verifier (docs/verification.md), which `verifier watch` answers by itself. The account that connects becomes
13
+ // payTo, unless PAY_TO already names an account of yours. Nothing is registered twice: a new agent's id is saved
14
+ // even when a later step stops.
15
+ // 4. Saves what it settled: PAY_TO and AGENT_ID in .env, the agent id in the gateway config (or its env:AGENT_ID), and
16
+ // the public settings in fly.toml's [env], with a command-run provider's STELLAR_NETWORK.
17
+ // 5. With fly.toml and your yes (--deploy), stages the variables that may hold keys as Fly secrets (the ones a gateway
18
+ // config reads; FACILITATOR_AUTH and SOROBAN_RPC_URL for a command), deploys with flyctl, waits until the public
19
+ // manifest serves the agent id on the network it registered on, and checks the registration from outside.
20
+ // No key reaches provider-kit: the wallet signs on a page on this computer (approval.ts). Scripts and CI use `register`
21
+ // and `request-validation` with --source instead.
22
+ import { existsSync } from "node:fs";
23
+ import { readFile, rm } from "node:fs/promises";
24
+ import { tmpdir } from "node:os";
25
+ import { dirname, join } from "node:path";
26
+ import { createInterface } from "node:readline/promises";
27
+ import { DEFAULT_MANIFEST_PATH, stellarAddressKind } from "@stellar-agent-services/protocol";
28
+ import { Keypair } from "@stellar/stellar-sdk";
29
+ import { loadGatewayConfig } from "../gateway/config.js";
30
+ import { checkManifest } from "../manifest.js";
31
+ import { createProvider } from "../provider.js";
32
+ import { inspectRegistration, mayActFor, registrationDataUri, registrationFile, registryConfig } from "../registration.js";
33
+ import { prepareValidationRequest, ValidationRequestError } from "../validation-request.js";
34
+ import { ApprovalError, describeOutcome } from "./approval.js";
35
+ import { DEV_PAY_TO, payToKind } from "./dev-account.js";
36
+ import { ENV_FILE, loadEnvFile, updateEnvFile } from "./env-file.js";
37
+ import { FLY_TOML, flyApp, flyDeploy, flySecrets, setFlyEnv } from "./fly.js";
38
+ import { explorerUrl, networkName, shortAddress } from "./owner-chain.js";
39
+ import { accountSteps, registerStep, validationRequestStep } from "./owner-steps.js";
40
+ import { freePort, runProgram, startCommand, waitForManifest } from "./processes.js";
41
+ import { saveAgentId } from "./register.js";
42
+ import { DEFAULT_CONFIG, nextCommand, ownerIo, plain, UsageError } from "./shared.js";
43
+ import { isTemporaryUrl } from "./tunnel.js";
44
+ import { defaultValidator } from "./validators.js";
45
+ /** Settings the gateway config and the deploy read from the environment, which launch settles itself. */
46
+ const SETTINGS = new Set(["PAY_TO", "PUBLIC_ORIGIN", "JOB_DB_PATH", "AGENT_ID", DEV_PAY_TO, "PORT"]);
47
+ /**
48
+ * What a provider run by its own command may read that holds a key, as the TypeScript template does: the facilitator's
49
+ * Authorization header (mainnet's facilitator wants an API key) and a Soroban RPC URL, which often carries one.
50
+ */
51
+ const COMMAND_SECRETS = ["FACILITATOR_AUTH", "SOROBAN_RPC_URL"];
52
+ /** How long launch waits for a deploy to serve the new manifest. */
53
+ const LIVE_WAIT_MS = 5 * 60_000;
54
+ const LIVE_POLL_MS = 5000;
55
+ export function launchIo(flags) {
56
+ return {
57
+ ...ownerIo(flags),
58
+ log: (line) => console.log(line),
59
+ interactive: process.stdin.isTTY === true && process.stdout.isTTY === true,
60
+ async confirm(question) {
61
+ const readline = createInterface({ input: process.stdin, output: process.stdout });
62
+ try {
63
+ const answer = await new Promise((resolve) => {
64
+ readline.once("SIGINT", () => resolve(undefined));
65
+ readline.question(question).then(resolve, () => resolve(undefined));
66
+ });
67
+ return answer !== undefined && /^(y|yes)?$/i.test(answer.trim());
68
+ }
69
+ finally {
70
+ readline.close();
71
+ }
72
+ },
73
+ fetch: (input, init) => fetch(input, init),
74
+ start: (command, env) => startCommand(command, { env }),
75
+ freePort,
76
+ fly: (args, options = {}) => runProgram("fly", args, options),
77
+ sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
78
+ env: process.env,
79
+ };
80
+ }
81
+ export async function launch(flags, io = launchIo(flags)) {
82
+ if ([flags.config, flags.start, flags.manifest].filter((source) => source !== undefined).length > 1) {
83
+ throw new UsageError("launch takes the provider from one of --config, --start or --manifest");
84
+ }
85
+ if (flags.deploy && flags["no-deploy"])
86
+ throw new UsageError("--deploy or --no-deploy, not both");
87
+ if (flags.validator !== undefined && flags["no-validation"])
88
+ throw new UsageError("--validator or --no-validation, not both");
89
+ if (flags.source !== undefined)
90
+ throw new UsageError("launch signs in your wallet. With --source, use provider-kit register and request-validation");
91
+ if (flags.validator !== undefined && stellarAddressKind(flags.validator) !== "account")
92
+ throw new UsageError("--validator must be a G... account address");
93
+ const envFile = flags["env-file"] ?? ENV_FILE;
94
+ if (flags["env-file"] !== undefined && !existsSync(envFile))
95
+ throw new UsageError(`${envFile} doesn't exist`);
96
+ await loadEnvFile(envFile, io.env);
97
+ const fly = await flyApp(FLY_TOML);
98
+ const config = flags.start === undefined && flags.manifest === undefined ? (flags.config ?? (existsSync(DEFAULT_CONFIG) ? DEFAULT_CONFIG : undefined)) : undefined;
99
+ if (config === undefined && flags.start === undefined && flags.manifest === undefined) {
100
+ throw new UsageError(`launch needs your provider: --config <file> (a gateway; ./${DEFAULT_CONFIG} by default), --start '<command>' (any provider this computer runs, such as 'node dist/server.js'), or --manifest <url> (one already deployed)`);
101
+ }
102
+ // 1 and 2: the public address, and the manifest as it will serve it.
103
+ const kind = payToKind(io.env);
104
+ const yours = kind === "yours" ? io.env["PAY_TO"] : undefined;
105
+ // Until a wallet connects, the manifest is built with a stand-in payTo: the registration doesn't name payTo, and the
106
+ // registry makes the registering account the agent's wallet [I3].
107
+ const standIn = yours ?? Keypair.random().publicKey();
108
+ const knownAgentId = io.env["AGENT_ID"] === undefined || io.env["AGENT_ID"] === "" ? undefined : io.env["AGENT_ID"];
109
+ let target;
110
+ let origin;
111
+ if (flags.manifest !== undefined) {
112
+ const loaded = await deployedTarget(flags.manifest, io.fetch);
113
+ if ("problems" in loaded)
114
+ return refuse(io, loaded.problems);
115
+ target = loaded;
116
+ origin = new URL(loaded.manifestUrl).origin;
117
+ }
118
+ else {
119
+ origin = publicOrigin(flags.url, fly, io.env);
120
+ try {
121
+ target =
122
+ config !== undefined
123
+ ? await gatewayTarget(config, origin, standIn, io.env)
124
+ : await commandTarget(flags.start, origin, standIn, knownAgentId, io);
125
+ }
126
+ catch (error) {
127
+ if (error instanceof ManifestProblems)
128
+ return refuse(io, error.problems);
129
+ throw error;
130
+ }
131
+ }
132
+ const { manifest, manifestUrl } = target;
133
+ const { network } = manifest.payment.accepts[0];
134
+ // With --manifest, payTo is what the provider serves.
135
+ const servedPayTo = flags.manifest !== undefined ? manifest.payment.accepts[0].payTo : yours;
136
+ const name = plain(manifest.provider.name);
137
+ io.log(`Launching ${name} on ${networkName(network)}`);
138
+ io.log(` manifest ${manifestUrl}`);
139
+ io.log(` services ${manifest.services.map((service) => service.name).join(", ")}`);
140
+ // 3: what the chain has already.
141
+ const registry = io.registry(network, flags.rpc);
142
+ const chain = io.chain(network, flags.rpc);
143
+ let agentUri;
144
+ try {
145
+ agentUri = registrationDataUri(registrationFile(manifest, manifestUrl));
146
+ }
147
+ catch {
148
+ throw new Error("the registration file is over 8 KiB: shorten the provider's description, or host the file and use provider-kit register --agent-uri");
149
+ }
150
+ const existing = manifest.provider.agentId ?? (knownAgentId === undefined ? undefined : Number(knownAgentId));
151
+ if (existing !== undefined && !(Number.isInteger(existing) && existing >= 0))
152
+ throw new UsageError(`AGENT_ID must be an agent id, not "${knownAgentId}"`);
153
+ let owner;
154
+ let payTo = servedPayTo;
155
+ let registration = "new";
156
+ if (existing !== undefined) {
157
+ owner = await registry.agentOwner(existing);
158
+ if (owner === undefined)
159
+ return refuse(io, [`agent ${existing} doesn't exist in the ${network} identity registry [I2]. To register a new agent, empty AGENT_ID (or provider.agentId).`]);
160
+ const wallet = await registry.agentWallet(existing);
161
+ if (payTo !== undefined && wallet !== undefined && wallet !== payTo) {
162
+ return refuse(io, [`agent ${existing}'s wallet is ${wallet}, but PAY_TO is ${payTo} [I3]. Set PAY_TO to the agent's wallet, or empty AGENT_ID to register anew.`]);
163
+ }
164
+ payTo ??= wallet ?? owner;
165
+ registration = (await registry.agentUri(existing)) === agentUri ? "current" : "update";
166
+ }
167
+ const validator = flags["no-validation"] ? undefined : (flags.validator ?? defaultValidator(network, io.env));
168
+ // A new or moved registration gets a new request. One already registered gets one when this verifier has none yet.
169
+ const validating = validator !== undefined &&
170
+ (registration !== "current" || flags.validate === true || !(await registry.agentValidations(existing)).some((record) => record.validator === validator));
171
+ if (validator === undefined && !flags["no-validation"]) {
172
+ io.log(` verifier none named, so no validation is requested: --validator <G...> names one (or STELLAR_VALIDATOR)`);
173
+ }
174
+ // A payout account that won't connect here, such as an agent's wallet other than its owner, needs USDC already [P2].
175
+ if (payTo !== undefined && registration !== "new" && payTo !== owner && stellarAddressKind(payTo) === "account" && !(await chain.account(payTo)).usdcTrustline) {
176
+ return refuse(io, [`payTo ${payTo} has no USDC trustline, so it can't be paid [P2]: provider-kit trustline --network ${network === "stellar:pubnet" ? "mainnet" : "testnet"} adds one`]);
177
+ }
178
+ if (flags["dry-run"]) {
179
+ const what = { new: "register a new agent", update: `update agent ${existing}'s registration`, current: `keep agent ${existing}'s registration, which is up to date` }[registration];
180
+ io.log(`\nDry run. Without --dry-run, a page opens where your wallet would ${what}${validating ? `, and ask ${shortAddress(validator)} for validation` : ""}. Nothing was signed.`);
181
+ return 0;
182
+ }
183
+ // A deployed provider's settings are wherever it runs, not in this .env or fly.toml.
184
+ const savedKind = flags.manifest !== undefined ? "yours" : kind;
185
+ const savedFly = flags.manifest !== undefined ? undefined : fly;
186
+ // 3: one approval page, for everything to sign.
187
+ let connected;
188
+ let registered;
189
+ let sentRegistration = false;
190
+ let requested;
191
+ let skipValidation = !validating;
192
+ const agentIdNow = () => registered ?? existing;
193
+ const needsSigning = registration !== "current" || validating || payTo === undefined || (stellarAddressKind(payTo) === "account" && !(await chain.account(payTo)).usdcTrustline);
194
+ if (needsSigning) {
195
+ const identity = registryConfig(network).contracts.identity;
196
+ const outline = [{ id: "trustline", title: "Receive USDC" }];
197
+ if (registration !== "current")
198
+ outline.push({ id: "register", title: registration === "update" ? "Update registration" : "Register" });
199
+ if (validating)
200
+ outline.push({ id: "request-validation", title: "Ask for verification" });
201
+ const plan = {
202
+ command: "provider-kit launch",
203
+ title: registration === "new" ? "Launch your service" : "Update your service",
204
+ intro: registration === "new"
205
+ ? `Get paid for ${name} in your own wallet, list it in the Stellar 8004 registry so agents find it${validating ? ", and ask a verifier to check it" : ""}.`
206
+ : `Point agent ${existing}'s registration at ${manifestUrl}${validating ? ", and ask a verifier to check it" : ""}.`,
207
+ network,
208
+ ...accountToConnect(registration, payTo, owner),
209
+ outline,
210
+ async steps(address) {
211
+ if (registration === "new" && payTo !== undefined && address !== payTo) {
212
+ throw new ApprovalError("Connect your payout account", `Your wallet connected ${shortAddress(address)}. Switch it to ${shortAddress(payTo)}, PAY_TO: the account that registers becomes the agent's wallet, which must be payTo [I3].`, "connect");
213
+ }
214
+ if (registration !== "new" && !(await mayActFor(registry, existing, owner, address))) {
215
+ throw new ApprovalError("This account can't change the registration", `Your wallet connected ${shortAddress(address)}. Switch it to agent ${existing}'s owner, ${shortAddress(owner)}, or an address it approved.`, "connect");
216
+ }
217
+ connected = address;
218
+ const paidTo = payTo ?? address;
219
+ const steps = await accountSteps(chain, address, { trustline: paidTo === address });
220
+ // Connecting again after a later step failed: what already landed isn't done twice.
221
+ if (registration !== "current" && !sentRegistration) {
222
+ const step = registerStep(chain, {
223
+ identity,
224
+ caller: address,
225
+ agentUri,
226
+ manifestUrl,
227
+ providerName: manifest.provider.name,
228
+ ...(registration === "update" ? { agentId: existing } : {}),
229
+ onRegistered: (id) => (registered = id),
230
+ });
231
+ steps.push(afterDone(step, () => (sentRegistration = true)));
232
+ }
233
+ if (!skipValidation && requested === undefined) {
234
+ if ([paidTo, owner, address].includes(validator)) {
235
+ skipValidation = true;
236
+ io.log(` ! ${shortAddress(validator)} is this provider's own account, so it can't validate it: no validation is requested`);
237
+ }
238
+ else {
239
+ const prepare = async () => {
240
+ const ready = structuredClone(manifest);
241
+ ready.payment.accepts[0].payTo = paidTo;
242
+ ready.provider.agentId = agentIdNow();
243
+ try {
244
+ return await prepareValidationRequest(ready, manifestUrl, { validator: validator, registry, signer: address, now: () => io.now() });
245
+ }
246
+ catch (error) {
247
+ if (error instanceof ValidationRequestError)
248
+ throw new ApprovalError("Verification can't be requested yet", error.problems.join(" "), "rebuild");
249
+ throw error;
250
+ }
251
+ };
252
+ steps.push(validationRequestStep(chain, address, prepare, (prepared) => (requested = prepared)));
253
+ }
254
+ }
255
+ return steps;
256
+ },
257
+ result() {
258
+ const agentId = agentIdNow();
259
+ return {
260
+ title: registration === "new" ? "Your service is launched" : "Your service is updated",
261
+ message: `${agentId === undefined ? "" : `It is agent ${agentId} on ${networkName(network)}. `}provider-kit saves the settings and goes on in your terminal.`,
262
+ details: [
263
+ ...(agentId === undefined ? [] : [{ label: "Agent", value: String(agentId) }]),
264
+ ...(connected === undefined ? [] : [{ label: "Paid to", value: payTo ?? connected, mono: true, href: explorerUrl(network, "account", payTo ?? connected) }]),
265
+ ...(requested === undefined ? [] : [{ label: "Verification request", value: requested.requestHash, mono: true }]),
266
+ ],
267
+ };
268
+ },
269
+ };
270
+ const outcome = await io.approve(plan, chain);
271
+ payTo ??= connected;
272
+ // A new agent's id is saved even when a later step stopped: registering again would make a second agent.
273
+ if (registered !== undefined || outcome.status === "done")
274
+ await save(io, { target, envFile, fly: savedFly, origin, payTo, kind: savedKind, agentId: agentIdNow(), registered: registered !== undefined });
275
+ if (outcome.status !== "done") {
276
+ io.log(describeOutcome(outcome));
277
+ if (registered !== undefined)
278
+ io.log(`Agent ${registered} is registered and saved. Run ${nextCommand("launch")} again to finish.`);
279
+ return 1;
280
+ }
281
+ }
282
+ else {
283
+ io.log(` ✓ agent ${existing} is registered, and its registration names this manifest: nothing to sign`);
284
+ await save(io, { target, envFile, fly: savedFly, origin, payTo: payTo, kind: savedKind, agentId: existing, registered: false });
285
+ }
286
+ const agentId = agentIdNow();
287
+ io.log(`\n✓ ${name} is agent ${agentId} on ${networkName(network)}, paid to ${payTo}`);
288
+ if (requested !== undefined) {
289
+ io.log(`✓ Validation requested from ${requested.request.validator} (request ${requested.requestHash.slice(0, 16)}…): its answer lands in the Validation Registry`);
290
+ }
291
+ // 5: deploy, then wait for the public manifest.
292
+ if (flags.manifest !== undefined) {
293
+ if (registered !== undefined)
294
+ io.log(`\nNext: set AGENT_ID=${agentId} (or provider.agentId) where the provider runs, and restart it [I2]. ${nextCommand("status")} then shows it live.`);
295
+ return 0;
296
+ }
297
+ // Inside a deployed container, 127.0.0.1 is the container: every paid call would fail, after an upfront charge.
298
+ const unreachable = config === undefined ? [] : await loopbackUpstreams(config);
299
+ if (unreachable.length > 0) {
300
+ io.log(`\n ! ${unreachable.join(", ")} ${unreachable.length === 1 ? "calls its API" : "call their API"} at 127.0.0.1, which in a deployed container is the container itself.`);
301
+ io.log(` Before deploying, set each upstream.url in ${config} to an address the container reaches, such as your API's public URL.`);
302
+ }
303
+ const deploying = fly !== undefined && unreachable.length === 0 && (await shouldDeploy(flags, io, fly));
304
+ if (fly !== undefined && unreachable.length > 0 && flags.deploy) {
305
+ io.log(` ✗ Not deployed. Once upstream.url is set, ${nextCommand("deploy", "fly deploy --ha=false")}.`);
306
+ return 1;
307
+ }
308
+ if (deploying) {
309
+ await stageSecrets(io, fly, target);
310
+ const problem = await flyDeploy(io.fly, fly, io.log);
311
+ if (problem !== undefined) {
312
+ io.log(` ✗ ${problem}`);
313
+ io.log(`Everything else is saved: deploy again with ${nextCommand("deploy", "fly deploy --ha=false")}, then ${nextCommand("status")}.`);
314
+ return 1;
315
+ }
316
+ }
317
+ else {
318
+ printDeploy(io, { fly, origin, payTo: payTo, agentId, target });
319
+ }
320
+ if (!deploying || flags["no-wait"]) {
321
+ io.log(`\n${nextCommand("status")} shows where it stands: live, registered, verified.`);
322
+ return 0;
323
+ }
324
+ return checkLive(io, { manifestUrl, agentId, payTo: payTo, network, rpc: flags.rpc });
325
+ }
326
+ /** The public origin: --url, fly.toml's app, or an https:// PUBLIC_ORIGIN. */
327
+ function publicOrigin(url, fly, env) {
328
+ const fromEnv = env["PUBLIC_ORIGIN"]?.startsWith("https://") ? env["PUBLIC_ORIGIN"] : undefined;
329
+ const candidate = url ?? fly?.origin ?? fromEnv;
330
+ if (candidate === undefined) {
331
+ throw new UsageError("launch needs the HTTPS address your service will have: --url https://api.example.com, an app in fly.toml, or an https:// PUBLIC_ORIGIN");
332
+ }
333
+ let parsed;
334
+ try {
335
+ parsed = new URL(candidate);
336
+ }
337
+ catch {
338
+ throw new UsageError(`${candidate} is not a URL`);
339
+ }
340
+ if (parsed.protocol !== "https:" || parsed.username !== "" || parsed.password !== "")
341
+ throw new UsageError(`the public address must be https://, with no user name: ${candidate} [M6]`);
342
+ if (isTemporaryUrl(candidate)) {
343
+ throw new UsageError(`${parsed.host} is a quick tunnel's address, which changes every run: the registry would soon point nowhere. Deploy the service, then launch with its address`);
344
+ }
345
+ return candidate.replace(/\/+$/, "");
346
+ }
347
+ class ManifestProblems extends Error {
348
+ problems;
349
+ constructor(problems) {
350
+ super(problems.join("\n"));
351
+ this.problems = problems;
352
+ }
353
+ }
354
+ /** A gateway's manifest at `origin`, built from its config as `serve` builds it. Variables it needs only to run get stand-ins. */
355
+ async function gatewayTarget(config, origin, payTo, env) {
356
+ const parsed = JSON.parse(await readFile(config, "utf8"));
357
+ const local = { ...env, PAY_TO: payTo };
358
+ const agentIdVariable = typeof parsed.provider?.agentId === "string" ? parsed.provider.agentId.slice("env:".length) : undefined;
359
+ for (const { name, field } of variablesRead(parsed)) {
360
+ if (name === agentIdVariable || (local[name] !== undefined && local[name] !== ""))
361
+ continue;
362
+ // Only the manifest is built here: what the gateway needs only to run, such as the API's key, gets a stand-in.
363
+ local[name] = /url|origin|facilitator/i.test(field) ? "https://stand-in.invalid" : "stand-in";
364
+ }
365
+ let built;
366
+ try {
367
+ const { config: providerConfig } = await loadGatewayConfig(config, { env: local, openStore: false, overrides: { baseUrl: origin, dev: false } });
368
+ const provider = createProvider(providerConfig);
369
+ await provider.close();
370
+ built = provider;
371
+ }
372
+ catch (error) {
373
+ throw new ManifestProblems([error.message]);
374
+ }
375
+ const agentId = parsed.provider?.agentId;
376
+ const secrets = new Set(variablesRead(parsed).map(({ name }) => name).filter((name) => !SETTINGS.has(name) && name !== agentIdVariable));
377
+ return {
378
+ manifest: built.manifest,
379
+ manifestUrl: built.manifestUrl,
380
+ agentIdGoes: typeof agentId === "string" && agentId.startsWith("env:") ? { variable: agentId.slice("env:".length) } : { config },
381
+ // The image carries the config, network and all.
382
+ settings: {},
383
+ secrets: { names: [...secrets], required: true },
384
+ };
385
+ }
386
+ /** Any provider's manifest at `origin`: its own command, started here on a free port with PUBLIC_ORIGIN set, then stopped. */
387
+ async function commandTarget(command, origin, payTo, agentId, io) {
388
+ const port = await io.freePort();
389
+ // The command's job database, used for a few seconds and then thrown away.
390
+ const database = join(tmpdir(), `provider-kit-launch-${process.pid}-${port}.sqlite`);
391
+ io.log(` … Starting ${command} to read its manifest as ${origin} will serve it`);
392
+ const started = io.start(command, { PUBLIC_ORIGIN: origin, PAY_TO: payTo, PORT: String(port), JOB_DB_PATH: database, AGENT_ID: agentId ?? "" });
393
+ try {
394
+ const manifest = await waitForManifest(`http://127.0.0.1:${port}${DEFAULT_MANIFEST_PATH}`, { exited: started.exited, timeoutMs: 90_000, fetch: io.fetch });
395
+ const manifestUrl = new URL(DEFAULT_MANIFEST_PATH, new URL(origin).origin).toString();
396
+ const problems = checkManifest(manifest, manifestUrl, false).map((violation) => `[${violation.rule}] ${violation.pointer || "/"}: ${violation.message}`);
397
+ if (typeof manifest.baseUrl === "string" && !manifest.baseUrl.startsWith(origin)) {
398
+ problems.push(`its baseUrl is ${manifest.baseUrl}, not under ${origin}: have it read PUBLIC_ORIGIN, as the template's src/server.ts does`);
399
+ }
400
+ if (problems.length > 0)
401
+ throw new ManifestProblems(problems);
402
+ const facilitatorUrl = io.env["FACILITATOR_URL"];
403
+ return {
404
+ manifest,
405
+ manifestUrl,
406
+ agentIdGoes: { variable: "AGENT_ID" },
407
+ // The deployed provider must take payments on the network registered here: it reads STELLAR_NETWORK, as this one
408
+ // did from .env (the TypeScript template's default is testnet).
409
+ settings: { STELLAR_NETWORK: manifest.payment.accepts[0].network, ...(facilitatorUrl ? { FACILITATOR_URL: facilitatorUrl } : {}) },
410
+ secrets: { names: COMMAND_SECRETS, required: false },
411
+ };
412
+ }
413
+ catch (error) {
414
+ if (error instanceof ManifestProblems)
415
+ throw error;
416
+ const output = started.output().trim().split("\n").slice(-12).join("\n");
417
+ throw new Error(`${command} didn't serve its manifest: ${error.message}${output === "" ? "" : `\nIts output:\n${output}`}`);
418
+ }
419
+ finally {
420
+ await started.stop();
421
+ await Promise.all(["", "-wal", "-shm"].map((suffix) => rm(`${database}${suffix}`, { force: true })));
422
+ }
423
+ }
424
+ /**
425
+ * The services of a gateway config that call an API on 127.0.0.1 or localhost, which a container can't reach unless
426
+ * the API runs inside it too, as the Python kind's does (start.sh starts both).
427
+ */
428
+ async function loopbackUpstreams(config) {
429
+ if (existsSync(join(dirname(config), "start.sh")))
430
+ return [];
431
+ const { services } = JSON.parse(await readFile(config, "utf8"));
432
+ return (services ?? []).flatMap((service) => {
433
+ const url = service.upstream?.url;
434
+ if (typeof url !== "string" || url.startsWith("env:"))
435
+ return [];
436
+ try {
437
+ return ["127.0.0.1", "localhost", "[::1]"].includes(new URL(url).hostname) ? [service.name ?? "a service"] : [];
438
+ }
439
+ catch {
440
+ return [];
441
+ }
442
+ });
443
+ }
444
+ /** A deployed provider's manifest, read and checked as a client reads it. */
445
+ async function deployedTarget(url, fetcher) {
446
+ if (!url.startsWith("https://"))
447
+ return { problems: [`${url} isn't https://: a deployed provider's manifest is served over HTTPS [M6]`] };
448
+ const response = await fetcher(url, { redirect: "manual", headers: { Accept: "application/json" }, signal: AbortSignal.timeout(15_000) });
449
+ if (response.status !== 200)
450
+ return { problems: [`[M1] the manifest answered ${response.status}; redirects are not followed [M3]`] };
451
+ let manifest;
452
+ try {
453
+ manifest = (await response.json());
454
+ }
455
+ catch {
456
+ return { problems: ["the manifest is not valid JSON"] };
457
+ }
458
+ const violations = checkManifest(manifest, url, false);
459
+ if (violations.length > 0)
460
+ return { problems: violations.map((violation) => `[${violation.rule}] ${violation.pointer || "/"}: ${violation.message}`) };
461
+ return { manifest, manifestUrl: url, agentIdGoes: { variable: "AGENT_ID" }, settings: {}, secrets: { names: [], required: false } };
462
+ }
463
+ /** Every "env:NAME" a gateway config reads, with the field it fills. */
464
+ function variablesRead(value, field = "") {
465
+ if (typeof value === "string")
466
+ return value.startsWith("env:") ? [{ name: value.slice("env:".length), field }] : [];
467
+ if (Array.isArray(value))
468
+ return value.flatMap((item) => variablesRead(item, field));
469
+ if (typeof value === "object" && value !== null)
470
+ return Object.entries(value).flatMap(([key, item]) => variablesRead(item, key));
471
+ return [];
472
+ }
473
+ /** The account the page asks for: payTo for a new agent when PAY_TO names one, the owner for an update, else any. */
474
+ function accountToConnect(registration, payTo, owner) {
475
+ if (registration === "new") {
476
+ return payTo === undefined ? {} : { account: { address: payTo, label: "Payout account", reason: "PAY_TO: the account clients pay. Registering from it makes it the agent's owner and wallet." } };
477
+ }
478
+ return { account: { address: owner, label: "Agent owner", reason: "Only the agent's owner, the address approved for it, or an operator of the owner's can change it." } };
479
+ }
480
+ /** A step that also runs `then` once it has gone through. */
481
+ function afterDone(step, then) {
482
+ if (step.kind !== "sign")
483
+ return step;
484
+ return {
485
+ ...step,
486
+ async done(sent) {
487
+ const note = await step.done(sent);
488
+ then();
489
+ return note;
490
+ },
491
+ };
492
+ }
493
+ function refuse(io, problems) {
494
+ io.log(`Not launched. Nothing was signed:\n${problems.map((problem) => ` ✗ ${problem}`).join("\n")}`);
495
+ return 1;
496
+ }
497
+ /** Saves what launch settled: PAY_TO and the agent id where the provider reads them, and fly.toml's public settings. */
498
+ async function save(io, options) {
499
+ const { target, envFile, fly, payTo, agentId } = options;
500
+ const values = {};
501
+ if (payTo !== undefined && options.kind !== "yours")
502
+ Object.assign(values, { PAY_TO: payTo, [DEV_PAY_TO]: undefined });
503
+ let savedTo;
504
+ if (agentId !== undefined && "variable" in target.agentIdGoes)
505
+ values[target.agentIdGoes.variable] = String(agentId);
506
+ else if (agentId !== undefined && options.registered && "config" in target.agentIdGoes) {
507
+ savedTo = (await saveAgentId(target.agentIdGoes.config, agentId)) ? target.agentIdGoes.config : undefined;
508
+ }
509
+ if (Object.keys(values).length > 0) {
510
+ const written = await updateEnvFile(envFile, values, { comments: { AGENT_ID: "Your Stellar 8004 agent id: provider-kit launch saved it." } });
511
+ const shown = Object.entries(values).filter(([, value]) => value !== undefined).map(([name, value]) => `${name}=${value}`);
512
+ if (written)
513
+ io.log(` ✓ Saved ${shown.join(", ")} in ${envFile}`);
514
+ else
515
+ io.log(` ! No ${envFile} here: set ${shown.join(" ")} where the provider runs`);
516
+ }
517
+ if (savedTo !== undefined)
518
+ io.log(` ✓ Saved "agentId": ${agentId} to ${savedTo} [I2]`);
519
+ // fly.toml gets the address launch registered, its own app's or a domain of yours that points at it.
520
+ if (fly !== undefined) {
521
+ const settings = { PUBLIC_ORIGIN: options.origin };
522
+ if (payTo !== undefined)
523
+ settings["PAY_TO"] = payTo;
524
+ if (agentId !== undefined && "variable" in target.agentIdGoes)
525
+ settings[target.agentIdGoes.variable] = String(agentId);
526
+ Object.assign(settings, target.settings);
527
+ await setFlyEnv(fly.path, settings);
528
+ io.log(` ✓ Saved ${Object.keys(settings).join(", ")} in ${fly.path}'s [env], for every deploy`);
529
+ }
530
+ }
531
+ async function shouldDeploy(flags, io, fly) {
532
+ if (flags["no-deploy"])
533
+ return false;
534
+ if (flags.deploy)
535
+ return true;
536
+ if (!io.interactive) {
537
+ io.log(`\nNot deployed: --deploy deploys to Fly.io (fly deploy, app ${fly.app}) without asking.`);
538
+ return false;
539
+ }
540
+ return io.confirm(`\nDeploy to Fly.io now (fly deploy, app ${fly.app})? [Y/n] `);
541
+ }
542
+ /**
543
+ * The provider's variables that may hold keys, such as its API's key or the facilitator's, staged as Fly secrets from
544
+ * .env for the deploy. A required one that isn't set here is named: the deployed provider can't start without it.
545
+ */
546
+ async function stageSecrets(io, fly, target) {
547
+ const secrets = {};
548
+ const unset = [];
549
+ for (const name of target.secrets.names) {
550
+ const value = io.env[name];
551
+ if (value !== undefined && value !== "")
552
+ secrets[name] = value;
553
+ else if (target.secrets.required)
554
+ unset.push(name);
555
+ }
556
+ const names = Object.keys(secrets);
557
+ if (names.length > 0) {
558
+ if (await flySecrets(io.fly, fly.app, secrets))
559
+ io.log(` ✓ Staged ${names.join(", ")} as Fly secrets, from your environment`);
560
+ else
561
+ io.log(` ! ${names.join(", ")} couldn't be staged as Fly secrets: fly secrets set them, or the service can't start`);
562
+ }
563
+ if (unset.length > 0) {
564
+ io.log(` ! ${unset.join(", ")} ${unset.length === 1 ? "isn't" : "aren't"} set here, so not staged: unless Fly has ${unset.length === 1 ? "it" : "them"} already, the service can't start. fly secrets set ${unset[0]}=...`);
565
+ }
566
+ }
567
+ function printDeploy(io, options) {
568
+ if (options.fly !== undefined) {
569
+ io.log(`\nDeploy when you're ready: ${nextCommand("deploy", "fly deploy --ha=false")}. fly.toml has the settings.`);
570
+ return;
571
+ }
572
+ const { agentIdGoes, settings: more, secrets } = options.target;
573
+ const settings = [
574
+ `PUBLIC_ORIGIN=${options.origin}`,
575
+ `PAY_TO=${options.payTo}`,
576
+ ...("variable" in agentIdGoes ? [`${agentIdGoes.variable}=${options.agentId}`] : []),
577
+ ...Object.entries(more).map(([name, value]) => `${name}=${value}`),
578
+ ];
579
+ io.log(`\nDeploy it at ${options.origin} with these settings, and a persistent volume for its job database (JOB_DB_PATH):`);
580
+ io.log(` ${settings.join(" ")}`);
581
+ // Only the names: their values stay in .env, to be set as secrets where it runs.
582
+ const kept = secrets.names.filter((name) => io.env[name] !== undefined && io.env[name] !== "");
583
+ if (kept.length > 0)
584
+ io.log(` and, as secrets there, ${kept.join(", ")} from .env`);
585
+ }
586
+ /** Waits for the public manifest to serve the agent id, then checks the registration from outside [I1]–[I3]. */
587
+ async function checkLive(io, options) {
588
+ io.log(`\n … Waiting for ${options.manifestUrl} to serve agent ${options.agentId}`);
589
+ const deadline = Date.now() + LIVE_WAIT_MS;
590
+ let served;
591
+ while (Date.now() < deadline) {
592
+ try {
593
+ const response = await io.fetch(options.manifestUrl, { redirect: "manual", headers: { Accept: "application/json" }, signal: AbortSignal.timeout(10_000) });
594
+ if (response.status === 200) {
595
+ const manifest = (await response.json());
596
+ if (manifest.provider?.agentId === options.agentId && manifest.payment?.accepts?.[0]?.payTo === options.payTo) {
597
+ served = manifest;
598
+ break;
599
+ }
600
+ }
601
+ else {
602
+ await response.body?.cancel();
603
+ }
604
+ }
605
+ catch {
606
+ // Not up yet.
607
+ }
608
+ await io.sleep(LIVE_POLL_MS);
609
+ }
610
+ if (served === undefined) {
611
+ io.log(` ! It doesn't serve agent ${options.agentId} yet. ${nextCommand("status")} checks again later.`);
612
+ return 1;
613
+ }
614
+ // Testnet and mainnet never mix: an agent registered on one network must take payments on that network.
615
+ const servedNetwork = served.payment.accepts[0]?.network;
616
+ if (servedNetwork !== options.network) {
617
+ io.log(` ✗ It serves agent ${options.agentId} with payments on ${JSON.stringify(servedNetwork)}, but the agent is registered on ${options.network}.`);
618
+ io.log(` Its deployed settings must name ${options.network}: fix them, deploy again, then ${nextCommand("status")}.`);
619
+ return 1;
620
+ }
621
+ io.log(` ✓ Live: ${options.manifestUrl}`);
622
+ const { findings } = await inspectRegistration(served, options.manifestUrl, { registry: io.registry(options.network, options.rpc), fetch: io.fetch });
623
+ for (const finding of findings)
624
+ io.log(` ${finding.ok ? "✓" : "✗"} 8004 [${finding.rule}]: ${finding.message}`);
625
+ return findings.every((finding) => finding.ok) ? 0 : 1;
626
+ }
627
+ //# sourceMappingURL=launch.js.map