@svsprotocol/solana 0.1.0

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 (38) hide show
  1. package/LICENSE +158 -0
  2. package/README.md +365 -0
  3. package/dist/action-production-proof-evidence.js +553 -0
  4. package/dist/adapter-catalog.d.ts +29 -0
  5. package/dist/adapter-catalog.js +146 -0
  6. package/dist/adapter-core.d.ts +48 -0
  7. package/dist/adapter-core.js +249 -0
  8. package/dist/approval-signature.js +197 -0
  9. package/dist/base58.js +69 -0
  10. package/dist/bot-auth.js +50 -0
  11. package/dist/bot-certification-evidence.js +342 -0
  12. package/dist/bot-first-action-runbook.js +299 -0
  13. package/dist/bot-integration-contract.js +41 -0
  14. package/dist/certified-submit-status.js +176 -0
  15. package/dist/common.d.ts +1135 -0
  16. package/dist/elizaos.d.ts +43 -0
  17. package/dist/elizaos.js +227 -0
  18. package/dist/goat.d.ts +47 -0
  19. package/dist/goat.js +261 -0
  20. package/dist/index.d.ts +330 -0
  21. package/dist/index.js +128 -0
  22. package/dist/protocol.d.ts +205 -0
  23. package/dist/protocol.js +900 -0
  24. package/dist/receipt.js +51 -0
  25. package/dist/signed-proof-read-protection.js +495 -0
  26. package/dist/solana-agent-kit.d.ts +35 -0
  27. package/dist/solana-agent-kit.js +151 -0
  28. package/dist/svs-client.js +1232 -0
  29. package/dist/vercel-ai.d.ts +47 -0
  30. package/dist/vercel-ai.js +266 -0
  31. package/dist/verified-agent-adoption-kit.js +471 -0
  32. package/dist/verified-agent-profile.js +329 -0
  33. package/dist/verified-agent-registry-consumer.js +421 -0
  34. package/dist/verified-agent-registry.d.ts +36 -0
  35. package/dist/verified-agent-registry.js +826 -0
  36. package/dist/verified-agent-trust-score.js +335 -0
  37. package/dist/webhooks.js +834 -0
  38. package/package.json +72 -0
@@ -0,0 +1,50 @@
1
+ import { createHmac, timingSafeEqual } from "node:crypto";
2
+
3
+ export const DEFAULT_BOT_REQUEST_TIMESTAMP_MAX_AGE_MS = 5 * 60 * 1000;
4
+ export const BOT_REQUEST_SIGNATURE_VERSION = "v1";
5
+
6
+ export function createBotRequestSignature({ body, timestamp, secret } = {}) {
7
+ if (!secret) {
8
+ throw new Error("secret is required.");
9
+ }
10
+
11
+ if (!timestamp) {
12
+ throw new Error("timestamp is required.");
13
+ }
14
+
15
+ const rawBody = body === undefined || body === null ? "" : String(body);
16
+ const digest = createHmac("sha256", secret)
17
+ .update(`${timestamp}.${rawBody}`)
18
+ .digest("hex");
19
+
20
+ return `${BOT_REQUEST_SIGNATURE_VERSION}=${digest}`;
21
+ }
22
+
23
+ export function verifyBotRequestSignature({
24
+ body,
25
+ timestamp,
26
+ signature,
27
+ secret,
28
+ now = new Date(),
29
+ maxAgeMs = DEFAULT_BOT_REQUEST_TIMESTAMP_MAX_AGE_MS
30
+ } = {}) {
31
+ if (!signature || !timestamp || !secret) {
32
+ return false;
33
+ }
34
+
35
+ const requestTime = new Date(timestamp);
36
+ if (Number.isNaN(requestTime.getTime())) {
37
+ return false;
38
+ }
39
+
40
+ if (Math.abs(now.getTime() - requestTime.getTime()) > maxAgeMs) {
41
+ return false;
42
+ }
43
+
44
+ const expected = createBotRequestSignature({ body, timestamp, secret });
45
+ const expectedBuffer = Buffer.from(expected);
46
+ const actualBuffer = Buffer.from(signature);
47
+
48
+ return expectedBuffer.length === actualBuffer.length &&
49
+ timingSafeEqual(expectedBuffer, actualBuffer);
50
+ }
@@ -0,0 +1,342 @@
1
+ import { mkdir, writeFile } from "node:fs/promises";
2
+ import { dirname } from "node:path";
3
+ import { hashObject } from "./receipt.js";
4
+
5
+ export const BOT_CERTIFICATION_EVIDENCE_VERSION = "svs.bot-certification-evidence.v1";
6
+ export const BOT_CERTIFICATION_EVIDENCE_VERIFICATION_VERSION = "svs.bot-certification-evidence-verification.v1";
7
+ export const DEFAULT_BOT_CERTIFICATION_EVIDENCE_PATH = "./data/security/bot-certification-evidence.json";
8
+
9
+ export function createBotCertificationEvidenceFromResult({
10
+ result,
11
+ generatedAt = new Date()
12
+ } = {}) {
13
+ const certification = result?.certification ?? {};
14
+ const unsigned = {
15
+ version: BOT_CERTIFICATION_EVIDENCE_VERSION,
16
+ generatedAt: generatedAt.toISOString(),
17
+ source: {
18
+ resultVersion: result?.version ?? null,
19
+ serverUrl: result?.serverUrl ?? null
20
+ },
21
+ reportSafety: {
22
+ secretsIncluded: false,
23
+ redactedFields: [
24
+ "SVS_BOT_API_KEY",
25
+ "SVS_BOT_REQUEST_SIGNING_SECRET",
26
+ "requestSigningSecret",
27
+ "apiKey",
28
+ "webhookSecret",
29
+ "svs-request-signature"
30
+ ]
31
+ },
32
+ certificationCheck: {
33
+ ok: result?.ok === true,
34
+ status: result?.status ?? null,
35
+ checkedAt: result?.checkedAt ?? null,
36
+ botId: result?.botId ?? certification?.botId ?? null,
37
+ integrationContract: summarizeIntegrationContractBinding(result?.integrationContract ?? result?.contract),
38
+ nextAction: result?.nextAction ?? certification?.nextAction ?? null
39
+ },
40
+ certification: {
41
+ version: certification?.version ?? null,
42
+ ok: certification?.ok === true,
43
+ status: certification?.status ?? null,
44
+ botId: certification?.botId ?? result?.botId ?? null,
45
+ certificationHash: certification?.certification?.certificationHash ??
46
+ certification?.certificationHash ??
47
+ null,
48
+ recordId: certification?.certification?.recordId ??
49
+ certification?.recordId ??
50
+ null,
51
+ found: certification?.certification?.found === true ||
52
+ certification?.found === true,
53
+ botMatches: certification?.certification?.botMatches === true ||
54
+ certification?.botMatches === true,
55
+ quality: summarizeBotIntegrationQuality(certification?.quality ?? result?.quality ?? certification?.certification?.quality)
56
+ },
57
+ qualityTarget: summarizeBotIntegrationQualityTarget(certification?.qualityTarget ?? result?.qualityTarget),
58
+ proofs: {
59
+ verificationSetHash: certification?.proofs?.verificationSetHash ??
60
+ certification?.evidence?.verificationSetHash ??
61
+ null,
62
+ botIntegrationReportHash: certification?.proofs?.botIntegrationReportHash ??
63
+ certification?.evidence?.botIntegrationReportHash ??
64
+ null,
65
+ integrationContractHash: result?.integrationContract?.certifiedContractHash ??
66
+ certification?.proofs?.integrationContractHash ??
67
+ certification?.evidence?.integrationContractHash ??
68
+ null,
69
+ currentIntegrationContractHash: result?.integrationContract?.currentContractHash ??
70
+ certification?.proofs?.currentIntegrationContractHash ??
71
+ result?.proofs?.currentIntegrationContractHash ??
72
+ null,
73
+ integrationContractCurrent: result?.integrationContract
74
+ ? result.integrationContract.ok === true
75
+ : certification?.proofs?.integrationContractCurrent === true ||
76
+ result?.proofs?.integrationContractCurrent === true,
77
+ actionRecordVerificationOk: certification?.proofs?.actionRecordVerificationOk === true ||
78
+ certification?.evidence?.actionRecordVerificationOk === true,
79
+ receiptRegistrySubmissionVerificationOk: certification?.proofs?.receiptRegistrySubmissionVerificationOk === true ||
80
+ certification?.evidence?.receiptRegistrySubmissionVerificationOk === true,
81
+ portableSetVerified: certification?.proofs?.portableSetVerified === true ||
82
+ certification?.evidence?.portableSetVerified === true,
83
+ secretsIncluded: certification?.proofs?.secretsIncluded === true ||
84
+ certification?.evidence?.secretsIncluded === true
85
+ },
86
+ freshness: {
87
+ fresh: certification?.freshness?.fresh === true,
88
+ stale: certification?.freshness?.stale === true,
89
+ ageMs: Number.isFinite(certification?.freshness?.ageMs) ? certification.freshness.ageMs : null,
90
+ staleAfterMs: Number.isFinite(certification?.freshness?.staleAfterMs)
91
+ ? certification.freshness.staleAfterMs
92
+ : null
93
+ },
94
+ nextAction: result?.nextAction ?? certification?.nextAction ?? null
95
+ };
96
+
97
+ return {
98
+ ...unsigned,
99
+ evidenceHash: hashBotCertificationEvidence(unsigned)
100
+ };
101
+ }
102
+
103
+ export async function persistBotCertificationEvidence({
104
+ result,
105
+ outputPath = DEFAULT_BOT_CERTIFICATION_EVIDENCE_PATH,
106
+ generatedAt = new Date()
107
+ } = {}) {
108
+ const evidence = createBotCertificationEvidenceFromResult({ result, generatedAt });
109
+ const text = `${JSON.stringify(evidence, null, 2)}\n`;
110
+
111
+ await mkdir(dirname(outputPath), { recursive: true });
112
+ await writeFile(outputPath, text);
113
+
114
+ return {
115
+ path: outputPath,
116
+ bytes: Buffer.byteLength(text, "utf8"),
117
+ evidence
118
+ };
119
+ }
120
+
121
+ export function verifyBotCertificationEvidence(evidence, {
122
+ requireOk = true,
123
+ staleAfterMs = null,
124
+ now = new Date()
125
+ } = {}) {
126
+ const freshness = checkBotCertificationEvidenceFreshness({
127
+ generatedAt: evidence?.generatedAt,
128
+ staleAfterMs,
129
+ now
130
+ });
131
+ const recomputedHash = hashBotCertificationEvidence(evidence);
132
+ const checks = [
133
+ check(
134
+ "Bot certification evidence version is supported",
135
+ evidence?.version === BOT_CERTIFICATION_EVIDENCE_VERSION,
136
+ evidence?.version ?? "missing"
137
+ ),
138
+ check(
139
+ "Bot certification evidence hash is valid",
140
+ evidence?.evidenceHash === recomputedHash,
141
+ `declared=${evidence?.evidenceHash ?? "missing"} recomputed=${recomputedHash ?? "missing"}`
142
+ ),
143
+ check(
144
+ "Bot certification evidence excludes secrets",
145
+ evidence?.reportSafety?.secretsIncluded === false && evidence?.proofs?.secretsIncluded !== true,
146
+ `reportSafety=${evidence?.reportSafety?.secretsIncluded ?? "missing"} proofs=${evidence?.proofs?.secretsIncluded ?? "missing"}`
147
+ ),
148
+ check(
149
+ "Bot certification command passed",
150
+ !requireOk || evidence?.certificationCheck?.ok === true,
151
+ `status=${evidence?.certificationCheck?.status ?? "missing"}`
152
+ ),
153
+ check(
154
+ "Bot integration certification is ready",
155
+ !requireOk ||
156
+ evidence?.certification?.ok === true &&
157
+ ["ready", "certified"].includes(evidence?.certification?.status),
158
+ `status=${evidence?.certification?.status ?? "missing"}`
159
+ ),
160
+ check(
161
+ "Bot certification references a matching certified bot",
162
+ !requireOk || evidence?.certification?.found === true && evidence?.certification?.botMatches === true,
163
+ `found=${evidence?.certification?.found ?? "missing"} botMatches=${evidence?.certification?.botMatches ?? "missing"}`
164
+ ),
165
+ check(
166
+ "Bot certification action record proof passed",
167
+ !requireOk || evidence?.proofs?.actionRecordVerificationOk === true,
168
+ `ok=${evidence?.proofs?.actionRecordVerificationOk ?? "missing"}`
169
+ ),
170
+ check(
171
+ "Bot certification custom registry proof passed",
172
+ !requireOk || evidence?.proofs?.receiptRegistrySubmissionVerificationOk === true,
173
+ `ok=${evidence?.proofs?.receiptRegistrySubmissionVerificationOk ?? "missing"}`
174
+ ),
175
+ check(
176
+ "Bot certification portable set proof passed",
177
+ !requireOk || evidence?.proofs?.portableSetVerified === true,
178
+ `ok=${evidence?.proofs?.portableSetVerified ?? "missing"}`
179
+ ),
180
+ check(
181
+ "Bot certification integration contract binding passed",
182
+ !requireOk ||
183
+ !evidence?.certificationCheck?.integrationContract ||
184
+ evidence.certificationCheck.integrationContract.ok === true,
185
+ `status=${evidence?.certificationCheck?.integrationContract?.status ?? "missing"} local=${evidence?.certificationCheck?.integrationContract?.localContractHash ?? "missing"} certified=${evidence?.certificationCheck?.integrationContract?.certifiedContractHash ?? "missing"}`
186
+ ),
187
+ check(
188
+ "Bot certification quality target passed",
189
+ !requireOk || evidence?.qualityTarget?.ready === true &&
190
+ evidence.qualityTarget.score === evidence.qualityTarget.targetScore &&
191
+ evidence.qualityTarget.status === "excellent",
192
+ `ready=${evidence?.qualityTarget?.ready ?? "missing"} score=${evidence?.qualityTarget?.score ?? "missing"} target=${evidence?.qualityTarget?.targetScore ?? "missing"} status=${evidence?.qualityTarget?.status ?? "missing"}`
193
+ )
194
+ ];
195
+
196
+ if (staleAfterMs !== null && staleAfterMs !== undefined) {
197
+ checks.push(check(
198
+ "Bot certification evidence is fresh",
199
+ !freshness.stale,
200
+ freshness.ageMs === null
201
+ ? `generatedAt=${evidence?.generatedAt ?? "missing"}`
202
+ : `ageMs=${freshness.ageMs} staleAfterMs=${staleAfterMs}`
203
+ ));
204
+ }
205
+
206
+ const failedChecks = checks.filter((item) => !item.ok);
207
+
208
+ return {
209
+ version: BOT_CERTIFICATION_EVIDENCE_VERIFICATION_VERSION,
210
+ ok: failedChecks.length === 0,
211
+ status: failedChecks.length === 0 ? "verified" : freshness.stale ? "stale" : "failed",
212
+ checkedAt: now.toISOString(),
213
+ found: Boolean(evidence),
214
+ generatedAt: evidence?.generatedAt ?? null,
215
+ stale: freshness.stale,
216
+ ageMs: freshness.ageMs,
217
+ staleAfterMs,
218
+ evidenceHash: evidence?.evidenceHash ?? null,
219
+ computedEvidenceHash: recomputedHash,
220
+ botId: evidence?.certificationCheck?.botId ?? evidence?.certification?.botId ?? null,
221
+ certificationHash: evidence?.certification?.certificationHash ?? null,
222
+ recordId: evidence?.certification?.recordId ?? null,
223
+ qualityTarget: evidence?.qualityTarget ?? null,
224
+ failedCheckCount: failedChecks.length,
225
+ failedChecks,
226
+ checks
227
+ };
228
+ }
229
+
230
+ function summarizeBotIntegrationQuality(quality) {
231
+ if (!quality || typeof quality !== "object") {
232
+ return null;
233
+ }
234
+
235
+ return {
236
+ version: quality.version ?? null,
237
+ score: Number.isInteger(quality.score) ? quality.score : null,
238
+ maxScore: Number.isInteger(quality.maxScore) ? quality.maxScore : null,
239
+ status: quality.status ?? null,
240
+ passedFactorCount: Number.isInteger(quality.passedFactorCount) ? quality.passedFactorCount : null,
241
+ failedFactorCount: Number.isInteger(quality.failedFactorCount) ? quality.failedFactorCount : null,
242
+ missingFactorCodes: Array.isArray(quality.factors)
243
+ ? quality.factors.filter((factor) => factor.ok !== true).map((factor) => factor.code ?? "unknown")
244
+ : [],
245
+ nextAction: quality.nextAction
246
+ ? {
247
+ code: quality.nextAction.code ?? null,
248
+ message: quality.nextAction.message ?? null
249
+ }
250
+ : null
251
+ };
252
+ }
253
+
254
+ function summarizeBotIntegrationQualityTarget(target) {
255
+ if (!target || typeof target !== "object") {
256
+ return null;
257
+ }
258
+
259
+ return {
260
+ version: target.version ?? null,
261
+ ready: target.ready === true,
262
+ score: Number.isInteger(target.score) ? target.score : null,
263
+ maxScore: Number.isInteger(target.maxScore) ? target.maxScore : null,
264
+ targetScore: Number.isInteger(target.targetScore) ? target.targetScore : null,
265
+ status: target.status ?? null,
266
+ missingFactorCount: Number.isInteger(target.missingFactorCount) ? target.missingFactorCount : null,
267
+ missingFactorCodes: Array.isArray(target.missingFactorCodes) ? target.missingFactorCodes : [],
268
+ nextAction: target.nextAction
269
+ ? {
270
+ code: target.nextAction.code ?? null,
271
+ message: target.nextAction.message ?? null
272
+ }
273
+ : null
274
+ };
275
+ }
276
+
277
+ function summarizeIntegrationContractBinding(binding) {
278
+ if (!binding || typeof binding !== "object") {
279
+ return null;
280
+ }
281
+
282
+ return {
283
+ version: binding.version ?? null,
284
+ required: binding.required === true,
285
+ ok: binding.ok === true || binding.status === "current",
286
+ status: binding.status ?? null,
287
+ localContractHash: binding.localContractHash ?? binding.currentHash ?? null,
288
+ dashboardContractHash: binding.dashboardContractHash ?? binding.currentHash ?? null,
289
+ certifiedContractHash: binding.certifiedContractHash ?? binding.certifiedHash ?? null,
290
+ currentContractHash: binding.currentContractHash ?? binding.currentHash ?? null,
291
+ drifted: binding.drifted === true,
292
+ contractVersion: binding.contractVersion ?? null
293
+ };
294
+ }
295
+
296
+ export function hashBotCertificationEvidence(evidence) {
297
+ if (!evidence || typeof evidence !== "object") {
298
+ return null;
299
+ }
300
+
301
+ const { evidenceHash: _evidenceHash, ...unsigned } = evidence;
302
+
303
+ return hashObject(unsigned);
304
+ }
305
+
306
+ export function checkBotCertificationEvidenceFreshness({
307
+ generatedAt,
308
+ staleAfterMs,
309
+ now = new Date()
310
+ } = {}) {
311
+ if (staleAfterMs === null || staleAfterMs === undefined) {
312
+ return {
313
+ stale: false,
314
+ ageMs: null
315
+ };
316
+ }
317
+
318
+ const generatedTime = Date.parse(generatedAt);
319
+ const nowTime = now instanceof Date ? now.getTime() : Date.parse(now);
320
+
321
+ if (!Number.isFinite(generatedTime) || !Number.isFinite(nowTime)) {
322
+ return {
323
+ stale: true,
324
+ ageMs: null
325
+ };
326
+ }
327
+
328
+ const ageMs = Math.max(0, nowTime - generatedTime);
329
+
330
+ return {
331
+ stale: ageMs > staleAfterMs,
332
+ ageMs
333
+ };
334
+ }
335
+
336
+ function check(name, ok, detail) {
337
+ return {
338
+ name,
339
+ ok: Boolean(ok),
340
+ detail
341
+ };
342
+ }
@@ -0,0 +1,299 @@
1
+ import { createHash } from "node:crypto";
2
+ import {
3
+ BOT_INTEGRATION_CONTRACT_VERSION,
4
+ BOT_INTEGRATION_QUICKSTART_VERSION,
5
+ hashBotIntegrationContract
6
+ } from "./bot-integration-contract.js";
7
+
8
+ export const BOT_FIRST_ACTION_RUNBOOK_VERSION = "svs.bot-first-action-runbook.v1";
9
+ export const BOT_FIRST_ACTION_STEP_VERSION = "svs.bot-first-action-runbook-step.v1";
10
+
11
+ const DEFAULT_ENV_PATH = "./examples/external-bots/signed-devnet-memo-bot.local.env";
12
+
13
+ export function createBotFirstActionRunbook(contract, {
14
+ serverUrl = null,
15
+ envPath = null,
16
+ generatedAt = new Date().toISOString()
17
+ } = {}) {
18
+ const quickstart = validateQuickstart(contract);
19
+ const contractHash = hashBotIntegrationContract(contract);
20
+ const commands = quickstart.commands ?? {};
21
+ const resolvedEnvPath = envPath ?? quickstart.envFile?.localPath ?? DEFAULT_ENV_PATH;
22
+ const resolvedServerUrl = normalizeBaseUrl(
23
+ serverUrl ?? quickstart.envFile?.defaults?.SVS_SERVER_URL ?? "http://127.0.0.1:4173"
24
+ );
25
+ const commandSet = createCommandSet({
26
+ commands,
27
+ resolvedServerUrl,
28
+ resolvedEnvPath,
29
+ quickstartEnvPath: quickstart.envFile?.localPath ?? null,
30
+ quickstartServerUrl: quickstart.envFile?.defaults?.SVS_SERVER_URL ?? null
31
+ });
32
+ const runbook = {
33
+ version: BOT_FIRST_ACTION_RUNBOOK_VERSION,
34
+ generatedAt,
35
+ objective: "Move a new bot from local env setup to one human-approved, production-verifiable SVS action with the fewest manual handoffs.",
36
+ audience: [
37
+ "bot-builders",
38
+ "operators"
39
+ ],
40
+ serverUrl: resolvedServerUrl,
41
+ envPath: resolvedEnvPath,
42
+ contract: {
43
+ version: contract.version,
44
+ quickstartVersion: quickstart.version,
45
+ hashAlgorithm: "sha256",
46
+ hash: contractHash,
47
+ source: quickstart.endpoints?.integrationContract ?? `${resolvedServerUrl}/api/bots/integration-contract`
48
+ },
49
+ noSecretGuarantees: [
50
+ "Does not include API keys.",
51
+ "Does not include request-signing secrets.",
52
+ "Does not include wallet private keys.",
53
+ "Only points to env fields and commands that the operator or bot runtime already controls."
54
+ ],
55
+ commandSet,
56
+ orderedSteps: createOrderedSteps(commandSet),
57
+ fallbackPath: {
58
+ when: "The proof-wait submit exits before the operator finishes approval, broadcast, custom-registry registration, and proof export.",
59
+ commands: [
60
+ commandSet.certifiedSubmitStatus,
61
+ commandSet.certifiedSubmitPromote
62
+ ].filter(Boolean),
63
+ operatorAction: "Approve, broadcast, verify action, register the custom receipt, then promote the certified-submit evidence."
64
+ },
65
+ successDefinition: {
66
+ botSide: "The proof-wait submit returns or later promotes a production-verified action with a fetched proof response.",
67
+ operatorSide: "Dashboard action lifecycle shows approved, broadcast, verified, custom registry registered, and exportable proof.",
68
+ auditorSide: "The resulting verification set includes action-production proof evidence and custom receipt-registry evidence."
69
+ },
70
+ nextAction: {
71
+ code: "run_first_action_path",
72
+ message: "Run the ordered steps; if the proof wait expires, use the fallback status/promote commands after the operator completes the dashboard path."
73
+ }
74
+ };
75
+
76
+ return {
77
+ ...runbook,
78
+ runbookHashAlgorithm: "sha256",
79
+ runbookHash: hashRunbook(runbook)
80
+ };
81
+ }
82
+
83
+ export function renderBotFirstActionRunbookText(runbook) {
84
+ const lines = [
85
+ "SVS first verified action runbook",
86
+ `Server: ${runbook.serverUrl}`,
87
+ `Env: ${runbook.envPath}`,
88
+ `Contract: ${runbook.contract.hash}`,
89
+ "",
90
+ "Ordered path:"
91
+ ];
92
+
93
+ for (const step of runbook.orderedSteps ?? []) {
94
+ lines.push(`${step.order}. [${step.actor}] ${step.title}`);
95
+ if (step.command) {
96
+ lines.push(` ${step.command}`);
97
+ }
98
+ if (step.dashboardAction) {
99
+ lines.push(` Dashboard: ${step.dashboardAction}`);
100
+ }
101
+ lines.push(` Success: ${step.successSignal}`);
102
+ }
103
+
104
+ if (runbook.fallbackPath?.commands?.length) {
105
+ lines.push("");
106
+ lines.push("If proof wait expires:");
107
+ for (const command of runbook.fallbackPath.commands) {
108
+ lines.push(` ${command}`);
109
+ }
110
+ lines.push(` Dashboard: ${runbook.fallbackPath.operatorAction}`);
111
+ }
112
+
113
+ lines.push("");
114
+ lines.push(`Runbook hash: ${runbook.runbookHash}`);
115
+ return `${lines.join("\n")}\n`;
116
+ }
117
+
118
+ function validateQuickstart(contract) {
119
+ if (contract?.version !== BOT_INTEGRATION_CONTRACT_VERSION) {
120
+ throw new Error(`Unsupported bot integration contract version: ${contract?.version ?? "missing"}.`);
121
+ }
122
+
123
+ if (contract?.quickstart?.version !== BOT_INTEGRATION_QUICKSTART_VERSION) {
124
+ throw new Error(`Unsupported bot quickstart version: ${contract?.quickstart?.version ?? "missing"}.`);
125
+ }
126
+
127
+ return contract.quickstart;
128
+ }
129
+
130
+ function createCommandSet({
131
+ commands,
132
+ resolvedServerUrl,
133
+ resolvedEnvPath,
134
+ quickstartEnvPath,
135
+ quickstartServerUrl
136
+ }) {
137
+ const envFlag = `--env ${resolvedEnvPath}`;
138
+ const normalizeCommand = (command) => rewriteCommand(command, {
139
+ resolvedEnvPath,
140
+ resolvedServerUrl,
141
+ quickstartEnvPath,
142
+ quickstartServerUrl
143
+ });
144
+
145
+ return {
146
+ quickstart: normalizeCommand(commands.quickstart) ?? `npm run bot:quickstart -- --server ${resolvedServerUrl} --write-env ${resolvedEnvPath}`,
147
+ preflight: normalizeCommand(commands.preflight) ?? `npm run bot:preflight -- ${envFlag}`,
148
+ doctor: normalizeCommand(commands.doctor) ?? `npm run bot:doctor -- ${envFlag}`,
149
+ report: normalizeCommand(commands.report) ?? `npm run bot:report -- ${envFlag}`,
150
+ certification: normalizeCommand(commands.certification) ?? `npm run bot:certification -- ${envFlag} --write-evidence`,
151
+ certifiedSubmitReadiness: normalizeCommand(commands.certifiedSubmitReadiness) ?? `npm run bot:certified-submit:readiness -- ${envFlag} --write-receipt`,
152
+ certifiedSubmitAndWaitForProof: normalizeCommand(commands.certifiedSubmitAndWaitForProof) ??
153
+ `npm run bot:certified-submit -- ${envFlag} --wait-production-proof true --production-proof-wait-attempts 24 --production-proof-wait-interval-ms 10000 --production-proof-check-receipt-registry-chain true`,
154
+ certifiedSubmitStatus: normalizeCommand(commands.certifiedSubmitStatus) ?? "npm run bot:certified-submit:status",
155
+ certifiedSubmitPromote: normalizeCommand(commands.certifiedSubmitPromote) ?? "npm run bot:certified-submit:promote"
156
+ };
157
+ }
158
+
159
+ function rewriteCommand(command, {
160
+ resolvedEnvPath,
161
+ resolvedServerUrl,
162
+ quickstartEnvPath,
163
+ quickstartServerUrl
164
+ }) {
165
+ if (!command) {
166
+ return null;
167
+ }
168
+
169
+ let text = String(command);
170
+
171
+ for (const path of new Set([quickstartEnvPath, DEFAULT_ENV_PATH])) {
172
+ if (path && path !== resolvedEnvPath) {
173
+ text = text.split(path).join(resolvedEnvPath);
174
+ }
175
+ }
176
+
177
+ if (quickstartServerUrl && quickstartServerUrl !== resolvedServerUrl) {
178
+ text = text.split(quickstartServerUrl).join(resolvedServerUrl);
179
+ }
180
+
181
+ return text;
182
+ }
183
+
184
+ function createOrderedSteps(commandSet) {
185
+ return [
186
+ {
187
+ version: BOT_FIRST_ACTION_STEP_VERSION,
188
+ order: 1,
189
+ actor: "operator",
190
+ title: "Generate the bot env handoff",
191
+ command: commandSet.quickstart,
192
+ successSignal: "A local env file exists with placeholders, current server URL, and pinned integration-contract hash."
193
+ },
194
+ {
195
+ version: BOT_FIRST_ACTION_STEP_VERSION,
196
+ order: 2,
197
+ actor: "operator",
198
+ title: "Fill credentials from the dashboard Bot onboarding panel",
199
+ dashboardAction: "Copy bot id, API key, request-signing secret, controller wallet, and policy into the env file.",
200
+ successSignal: "The bot runtime has the env values; no secret material is stored in this runbook."
201
+ },
202
+ {
203
+ version: BOT_FIRST_ACTION_STEP_VERSION,
204
+ order: 3,
205
+ actor: "bot",
206
+ title: "Run safe preflight",
207
+ command: commandSet.preflight,
208
+ successSignal: "Preflight exits 0 and reports the next action without queueing a transaction."
209
+ },
210
+ {
211
+ version: BOT_FIRST_ACTION_STEP_VERSION,
212
+ order: 4,
213
+ actor: "bot",
214
+ title: "Run doctor for compatibility and credential readiness",
215
+ command: commandSet.doctor,
216
+ successSignal: "Doctor exits 0 with the current quickstart version and no missing client capabilities."
217
+ },
218
+ {
219
+ version: BOT_FIRST_ACTION_STEP_VERSION,
220
+ order: 5,
221
+ actor: "bot",
222
+ title: "Generate a shareable integration report",
223
+ command: commandSet.report,
224
+ successSignal: "The report is non-secret and gives the operator one adoption score plus next action."
225
+ },
226
+ {
227
+ version: BOT_FIRST_ACTION_STEP_VERSION,
228
+ order: 6,
229
+ actor: "operator",
230
+ title: "Certify the bot integration path",
231
+ command: commandSet.certification,
232
+ successSignal: "Certification evidence is current, contract-bound, and qualityTarget is ready before production submit."
233
+ },
234
+ {
235
+ version: BOT_FIRST_ACTION_STEP_VERSION,
236
+ order: 7,
237
+ actor: "bot",
238
+ title: "Write the certified-submit readiness receipt",
239
+ command: commandSet.certifiedSubmitReadiness,
240
+ successSignal: "A no-secret readiness receipt proves the bot can submit through the certified path."
241
+ },
242
+ {
243
+ version: BOT_FIRST_ACTION_STEP_VERSION,
244
+ order: 8,
245
+ actor: "bot",
246
+ title: "Submit the first action and wait for production proof",
247
+ command: commandSet.certifiedSubmitAndWaitForProof,
248
+ successSignal: "The helper submits with HMAC request signing, polls signed proof reads, and fetches the action production proof when the operator path completes."
249
+ },
250
+ {
251
+ version: BOT_FIRST_ACTION_STEP_VERSION,
252
+ order: 9,
253
+ actor: "operator",
254
+ title: "Approve, broadcast, verify, and register the action",
255
+ dashboardAction: "Use the dashboard action queue to approve with wallet, broadcast, verify action, register the custom receipt, and export proof.",
256
+ successSignal: "The selected action has a custom registry PDA proof and exportable verification set."
257
+ }
258
+ ];
259
+ }
260
+
261
+ function hashRunbook(runbook) {
262
+ return createHash("sha256")
263
+ .update(stableStringify(normalizeRunbookForHash(runbook)))
264
+ .digest("hex");
265
+ }
266
+
267
+ function normalizeRunbookForHash(value) {
268
+ if (Array.isArray(value)) {
269
+ return value.map(normalizeRunbookForHash);
270
+ }
271
+
272
+ if (value && typeof value === "object") {
273
+ return Object.keys(value)
274
+ .filter((key) => !["generatedAt", "runbookHash", "runbookHashAlgorithm"].includes(key))
275
+ .sort()
276
+ .reduce((result, key) => {
277
+ result[key] = normalizeRunbookForHash(value[key]);
278
+ return result;
279
+ }, {});
280
+ }
281
+
282
+ return value;
283
+ }
284
+
285
+ function stableStringify(value) {
286
+ if (Array.isArray(value)) {
287
+ return `[${value.map(stableStringify).join(",")}]`;
288
+ }
289
+
290
+ if (value && typeof value === "object") {
291
+ return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${stableStringify(value[key])}`).join(",")}}`;
292
+ }
293
+
294
+ return JSON.stringify(value);
295
+ }
296
+
297
+ function normalizeBaseUrl(value) {
298
+ return String(value || "http://127.0.0.1:4173").replace(/\/+$/, "");
299
+ }