enigma-memory 0.1.18 → 0.1.22

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 (276) hide show
  1. package/README.md +76 -24
  2. package/apps/cli/bin/enigma-desktop.mjs +140 -0
  3. package/apps/cli/bin/enigma-terminal.mjs +78 -0
  4. package/apps/cli/bin/enigma.mjs +1923 -285
  5. package/apps/desktop/electron-main.cjs +217 -0
  6. package/apps/desktop/package.json +12 -0
  7. package/apps/desktop/src/app.js +264 -7
  8. package/apps/desktop/src/index.html +3514 -1373
  9. package/apps/desktop/src/launch-electron.mjs +51 -0
  10. package/apps/desktop/src/server.mjs +2914 -0
  11. package/apps/desktop/src/styles.css +2972 -260
  12. package/apps/desktop/src/zk-browser-prove.mjs +53 -0
  13. package/apps/desktop/src/zk-state.mjs +1789 -0
  14. package/apps/gateway/bin/enigma-gateway.mjs +102 -5
  15. package/apps/gateway/src/server.mjs +271 -8
  16. package/apps/ios/EnigmaCore/Package.swift +12 -0
  17. package/apps/ios/EnigmaCore/Sources/EnigmaCore/EnigmaAPIClient.swift +227 -0
  18. package/apps/ios/EnigmaCore/Sources/EnigmaCore/Models.swift +278 -0
  19. package/apps/ios/EnigmaCore/Sources/EnigmaCore/PKCE.swift +96 -0
  20. package/apps/ios/EnigmaCore/Sources/EnigmaCore/PrivacyMinimizer.swift +187 -0
  21. package/apps/ios/EnigmaCore/Sources/EnigmaCore/ToolModels.swift +129 -0
  22. package/apps/ios/EnigmaCore/Tests/EnigmaCoreTests/EnigmaCoreTests.swift +42 -0
  23. package/apps/ios/EnigmaIOS/Enigma/AppModel.swift +346 -0
  24. package/apps/ios/EnigmaIOS/Enigma/Assets.xcassets/AccentColor.colorset/Contents.json +12 -0
  25. package/apps/ios/EnigmaIOS/Enigma/Assets.xcassets/AppIcon.appiconset/Contents.json +11 -0
  26. package/apps/ios/EnigmaIOS/Enigma/Assets.xcassets/AppIcon.appiconset/EnigmaAppIcon.png +0 -0
  27. package/apps/ios/EnigmaIOS/Enigma/Assets.xcassets/Contents.json +3 -0
  28. package/apps/ios/EnigmaIOS/Enigma/Assets.xcassets/LaunchBackground.colorset/Contents.json +12 -0
  29. package/apps/ios/EnigmaIOS/Enigma/ChatView.swift +181 -0
  30. package/apps/ios/EnigmaIOS/Enigma/CouncilView.swift +78 -0
  31. package/apps/ios/EnigmaIOS/Enigma/CreateView.swift +152 -0
  32. package/apps/ios/EnigmaIOS/Enigma/EnigmaApp.swift +52 -0
  33. package/apps/ios/EnigmaIOS/Enigma/Info.plist +52 -0
  34. package/apps/ios/EnigmaIOS/Enigma/NaturalLanguagePrivacyTagger.swift +26 -0
  35. package/apps/ios/EnigmaIOS/Enigma/OAuthClient.swift +321 -0
  36. package/apps/ios/EnigmaIOS/Enigma/OnboardingView.swift +105 -0
  37. package/apps/ios/EnigmaIOS/Enigma/PrivateVaultView.swift +275 -0
  38. package/apps/ios/EnigmaIOS/Enigma/SecureStore.swift +76 -0
  39. package/apps/ios/EnigmaIOS/Enigma/SettingsView.swift +60 -0
  40. package/apps/ios/EnigmaIOS/Enigma/Theme.swift +80 -0
  41. package/apps/ios/EnigmaIOS/EnigmaIOS.xcodeproj/project.pbxproj +211 -0
  42. package/apps/ios/EnigmaIOS/EnigmaIOS.xcodeproj/xcshareddata/xcschemes/Enigma.xcscheme +23 -0
  43. package/apps/native-host/README.md +19 -8
  44. package/apps/native-host/bin/enigma-native-host.mjs +229 -13
  45. package/apps/relay/bin/enigma-relay.mjs +103 -5
  46. package/apps/relay/src/federation-runtime.mjs +618 -0
  47. package/apps/relay/src/server.mjs +310 -9
  48. package/apps/verifier/bin/enigma-verify.mjs +327 -11
  49. package/cortex-v3/circuits/build/intent_vk_bytes.json +35 -0
  50. package/cortex-v3/circuits/build/sale_vk_bytes.json +35 -0
  51. package/cortex-v3/circuits/build/vk_bytes.json +32 -0
  52. package/cortex-v3/proving-assets.json +64 -0
  53. package/cortex-v3/zk/BUILD-CONTRACT.md +87 -0
  54. package/cortex-v3/zk/action-transition-vk.json +119 -0
  55. package/cortex-v3/zk/alias-adversarial.test.mjs +220 -0
  56. package/cortex-v3/zk/groth16-verify-child.mjs +17 -0
  57. package/cortex-v3/zk/intent-witness.mjs +365 -0
  58. package/cortex-v3/zk/intent-witness.test.mjs +485 -0
  59. package/cortex-v3/zk/proving-assets.mjs +203 -0
  60. package/cortex-v3/zk/sale-witness.mjs +783 -0
  61. package/cortex-v3/zk/sale-witness.test.mjs +784 -0
  62. package/cortex-v3/zk/sealed-sale-release-vk.json +119 -0
  63. package/cortex-v3/zk/settlement-evidence.mjs +722 -0
  64. package/cortex-v3/zk/setup-intent.mjs +688 -0
  65. package/cortex-v3/zk/setup-sale.mjs +666 -0
  66. package/cortex-v3/zk/setup.mjs +594 -0
  67. package/cortex-v3/zk/witness.mjs +184 -0
  68. package/cortex-v3/zk/zk-codec.mjs +232 -0
  69. package/cortex-v3/zk/zk-codec.test.mjs +293 -0
  70. package/cortex-v3/zk/zk-settle.mjs +370 -0
  71. package/cortex-v3/zk/zk-tree.mjs +256 -0
  72. package/cortex-v3/zk/zk-tree.test.mjs +419 -0
  73. package/deploy/docker-compose.local-production-simulation.yml +36 -0
  74. package/docs/browser-extension-install.md +8 -6
  75. package/docs/client-connectors.md +15 -11
  76. package/docs/developer-ecosystem.md +15 -13
  77. package/docs/enigma-memory-ready-conformance.md +11 -9
  78. package/docs/install-anywhere.md +61 -28
  79. package/docs/installers-and-desktop.md +8 -7
  80. package/docs/novelty-invention-candidates.md +161 -161
  81. package/docs/proof-network-claim-boundaries.md +320 -318
  82. package/examples/01-quickstart-agent/index.mjs +49 -0
  83. package/examples/01_agent_memory_quickstart.mjs +57 -0
  84. package/examples/02-multi-agent-swarm/index.mjs +57 -0
  85. package/examples/02_cross_model_passport.mjs +64 -0
  86. package/examples/03-langchain-memory/index.mjs +41 -0
  87. package/examples/03_poseidon_commitment_verification.mjs +71 -0
  88. package/examples/04-python-trading-agent/trader.py +49 -0
  89. package/examples/README.md +27 -0
  90. package/examples/ci/github-actions.yml +7 -2
  91. package/package.json +142 -11
  92. package/packages/adapters/PACKAGE_CONTRACT.md +1 -1
  93. package/packages/connectors/src/index.js +196 -4
  94. package/packages/connectors/swarm-router.mjs +168 -0
  95. package/packages/core/src/index.js +248 -1
  96. package/packages/core/src/version.mjs +7 -0
  97. package/packages/dev-tools/package.json +19 -0
  98. package/packages/dev-tools/src/index.js +4 -0
  99. package/packages/dev-tools/src/memory-benchmark-suite.js +112 -0
  100. package/packages/dev-tools/src/swarm-simulator.js +101 -0
  101. package/packages/dev-tools/src/vault-inspector.js +114 -0
  102. package/packages/dev-tools/src/vector-benchmark.js +100 -0
  103. package/packages/developer-platform/src/access-credentials.js +341 -0
  104. package/packages/developer-platform/src/http.js +132 -0
  105. package/packages/developer-platform/src/index.js +4 -0
  106. package/packages/developer-platform/src/usage-http.js +60 -0
  107. package/packages/developer-platform/src/usage.js +295 -0
  108. package/packages/enclave-runtime/attestation.mjs +159 -0
  109. package/packages/enclave-runtime/index.mjs +47 -0
  110. package/packages/enclave-runtime/session-manager.mjs +253 -0
  111. package/packages/enclave-runtime/zeroization-proof.mjs +227 -0
  112. package/packages/enigma-reflex/package.json +14 -0
  113. package/packages/enigma-reflex/src/index.js +204 -0
  114. package/packages/enigma-reflex/training/generate-dataset.mjs +40 -0
  115. package/packages/enigma-reflex/training/requirements.txt +8 -0
  116. package/packages/enigma-reflex/training/train.py +314 -0
  117. package/packages/enigma-weave/LICENSE +22 -0
  118. package/packages/enigma-weave/UPSTREAM.json +21 -0
  119. package/packages/enigma-weave/package.json +14 -0
  120. package/packages/enigma-weave/src/index.js +286 -0
  121. package/packages/hosted-cloud/src/index.js +80 -5
  122. package/packages/importers/src/index.js +432 -0
  123. package/packages/inference-runtime/src/browser.js +401 -0
  124. package/packages/inference-runtime/src/chat.js +265 -0
  125. package/packages/inference-runtime/src/code.js +407 -0
  126. package/packages/inference-runtime/src/contracts.js +162 -0
  127. package/packages/inference-runtime/src/http.js +232 -0
  128. package/packages/inference-runtime/src/image.js +186 -0
  129. package/packages/inference-runtime/src/index.js +10 -0
  130. package/packages/inference-runtime/src/model-router.js +320 -0
  131. package/packages/inference-runtime/src/platform.js +125 -0
  132. package/packages/inference-runtime/src/privacy.js +400 -0
  133. package/packages/inference-runtime/src/video.js +253 -0
  134. package/packages/mcp-server/README.md +22 -6
  135. package/packages/mcp-server/bin/enigma-mcp.mjs +2 -1
  136. package/packages/mcp-server/src/index.js +1418 -105
  137. package/packages/mcp-server/src/oauth.js +561 -0
  138. package/packages/mcp-server/src/private-handoff.js +84 -0
  139. package/packages/mcp-server/src/remote-http.js +273 -0
  140. package/packages/mcp-server/src/remote-policy.js +72 -0
  141. package/packages/mcp-server/swarm-bridge.mjs +361 -0
  142. package/packages/mesh/index.d.ts +283 -0
  143. package/packages/mesh/package.json +23 -0
  144. package/packages/mesh/src/crypto.js +189 -0
  145. package/packages/mesh/src/federation-packets.js +353 -0
  146. package/packages/mesh/src/gossip.js +311 -0
  147. package/packages/mesh/src/index.js +6 -0
  148. package/packages/mesh/src/protocol.js +255 -0
  149. package/packages/mesh/src/router.js +279 -0
  150. package/packages/mesh/src/transport.js +306 -0
  151. package/packages/passport/src/index.js +426 -1
  152. package/packages/private-economy/src/credits-http.js +100 -0
  153. package/packages/private-economy/src/credits.js +447 -0
  154. package/packages/private-economy/src/index.js +5 -0
  155. package/packages/private-economy/src/payments-http.js +120 -0
  156. package/packages/private-economy/src/payments.js +509 -0
  157. package/packages/private-economy/src/x402.js +346 -0
  158. package/packages/proof-network/PACKAGE_CONTRACT.md +21 -0
  159. package/packages/rag/index.d.ts +182 -0
  160. package/packages/rag/models/Xenova/all-MiniLM-L6-v2/THIRD_PARTY_LICENSES.txt +207 -0
  161. package/packages/rag/models/Xenova/all-MiniLM-L6-v2/config.json +25 -0
  162. package/packages/rag/models/Xenova/all-MiniLM-L6-v2/onnx/model_quantized.onnx +0 -0
  163. package/packages/rag/models/Xenova/all-MiniLM-L6-v2/sha256-manifest.json +28 -0
  164. package/packages/rag/models/Xenova/all-MiniLM-L6-v2/tokenizer.json +30686 -0
  165. package/packages/rag/models/Xenova/all-MiniLM-L6-v2/tokenizer_config.json +15 -0
  166. package/packages/rag/package.json +27 -0
  167. package/packages/rag/src/blinded-search.js +109 -0
  168. package/packages/rag/src/bm25.js +169 -0
  169. package/packages/rag/src/embeddings.js +459 -0
  170. package/packages/rag/src/hybrid.js +76 -0
  171. package/packages/rag/src/index.js +38 -0
  172. package/packages/rag/src/reranker.js +61 -0
  173. package/packages/rag/src/research.js +107 -0
  174. package/packages/rag/src/vector-store.js +430 -0
  175. package/packages/rag/src/verify-model-artifacts.mjs +4 -0
  176. package/packages/sdk/index.d.ts +760 -0
  177. package/packages/sdk/package.json +33 -0
  178. package/packages/sdk/python/README.md +24 -0
  179. package/packages/sdk/python/enigma_sdk.py +250 -0
  180. package/packages/sdk/python/pyproject.toml +34 -0
  181. package/packages/sdk/python/requirements.txt +1 -0
  182. package/packages/sdk/python/setup.py +20 -0
  183. package/packages/sdk/src/federation/capability-grant.js +389 -0
  184. package/packages/sdk/src/federation/federation-router.js +360 -0
  185. package/packages/sdk/src/federation/ghostmesh-bridge.js +497 -0
  186. package/packages/sdk/src/federation/index.js +3 -0
  187. package/packages/sdk/src/index.js +1796 -0
  188. package/packages/sdk/src/intelligence/contradiction.js +337 -0
  189. package/packages/sdk/src/intelligence/decision-engine.js +155 -0
  190. package/packages/sdk/src/intelligence/index.js +4 -0
  191. package/packages/sdk/src/intelligence/ontology.js +122 -0
  192. package/packages/sdk/src/intelligence/temporal.js +123 -0
  193. package/packages/sdk/src/market-client.js +142 -0
  194. package/packages/sdk/src/mesh-client.js +110 -0
  195. package/packages/sdk/src/middleware/index.js +3 -0
  196. package/packages/sdk/src/middleware/langchain.js +159 -0
  197. package/packages/sdk/src/middleware/llamaindex.js +101 -0
  198. package/packages/sdk/src/middleware/vercel-ai.js +112 -0
  199. package/packages/sdk/src/rag-client.js +85 -0
  200. package/packages/sdk/src/swarm-orchestrator.js +260 -0
  201. package/packages/settlement/PACKAGE_CONTRACT.md +1 -1
  202. package/packages/snapcompact/THIRD_PARTY_LICENSES.txt +40 -0
  203. package/packages/snapcompact/assets/8x13-latin1.bdf +3837 -0
  204. package/packages/snapcompact/index.d.ts +284 -0
  205. package/packages/snapcompact/package.json +25 -0
  206. package/packages/snapcompact/src/index.js +716 -0
  207. package/packages/storage/PACKAGE_CONTRACT.md +1 -1
  208. package/packages/terminal-console/animations.mjs +240 -0
  209. package/packages/terminal-console/auto-anchor.mjs +220 -0
  210. package/packages/terminal-console/banner.mjs +91 -0
  211. package/packages/terminal-console/commands.mjs +459 -0
  212. package/packages/terminal-console/delegation.mjs +152 -0
  213. package/packages/terminal-console/index.mjs +5 -0
  214. package/packages/terminal-console/outbox.mjs +143 -0
  215. package/packages/terminal-console/phantom-bridge.mjs +637 -0
  216. package/packages/terminal-console/repl.mjs +136 -0
  217. package/packages/terminal-console/signer-store.mjs +130 -0
  218. package/packages/terminal-console/solana-rpc.mjs +214 -0
  219. package/packages/terminal-console/solana-transport.mjs +189 -0
  220. package/packages/terminal-tui/dashboard.mjs +214 -0
  221. package/packages/terminal-tui/index.mjs +28 -0
  222. package/packages/terminal-tui/merkle-tree-renderer.mjs +268 -0
  223. package/packages/terminal-tui/telemetry-hud.mjs +137 -0
  224. package/packages/vault/index.d.ts +449 -0
  225. package/packages/vault/package.json +27 -0
  226. package/packages/vault/src/e2ee.mjs +393 -0
  227. package/packages/vault/src/enclave.js +481 -0
  228. package/packages/vault/src/erasure.js +207 -0
  229. package/packages/vault/src/index.js +1018 -155
  230. package/packages/vault/src/persistence.js +307 -0
  231. package/packages/vault/src/poseidon.js +354 -0
  232. package/packages/vault/src/receipt.js +459 -0
  233. package/scripts/benchmark-optical-context.mjs +166 -0
  234. package/scripts/bootstrap-enigma.mjs +502 -0
  235. package/scripts/build-edge-backend-workers.mjs +20 -5
  236. package/scripts/build-goal-completion-audit.mjs +72 -25
  237. package/scripts/build-hosted-api-key-lifecycle.mjs +26 -8
  238. package/scripts/build-hosted-customer-lifecycle.mjs +20 -3
  239. package/scripts/build-hosted-probe-worker.mjs +19 -4
  240. package/scripts/build-installer-assets.mjs +41 -21
  241. package/scripts/build-operator-evidence-starter.mjs +59 -1
  242. package/scripts/build-production-backend-env-kit.mjs +2 -0
  243. package/scripts/build-production-unblocker.mjs +3 -0
  244. package/scripts/check.mjs +17 -3
  245. package/scripts/collect-hosted-backend-live-evidence.mjs +49 -12
  246. package/scripts/install-enigma-local.mjs +18 -5
  247. package/scripts/release-audit.mjs +65 -109
  248. package/scripts/release-provenance.mjs +6 -0
  249. package/scripts/run-backend-readiness-smoke.mjs +112 -10
  250. package/scripts/scan-secrets.mjs +1 -0
  251. package/scripts/simulate-production-env.mjs +7 -2
  252. package/scripts/validate-hosted-backend-live.mjs +112 -1
  253. package/specs/antibody-pack-v1.schema.json +95 -0
  254. package/specs/antigen-envelope-v1.schema.json +81 -0
  255. package/specs/boundary-manifest-v1.schema.json +35 -35
  256. package/specs/capsule-v1.schema.json +55 -55
  257. package/specs/claim-boundary-manifest-v1.schema.json +22 -22
  258. package/specs/claim-ledger-v1.schema.json +291 -0
  259. package/specs/context-passport-v1.schema.json +59 -0
  260. package/specs/deletion-tombstone-v1.schema.json +26 -26
  261. package/specs/evidence-packet-v1.schema.json +177 -0
  262. package/specs/hosted-backend-live-evidence-v1.schema.json +72 -3
  263. package/specs/immune-scan-report-v1.schema.json +112 -0
  264. package/specs/lifecycle-receipt-log-v1.schema.json +67 -0
  265. package/specs/memory-atom-v1.schema.json +59 -0
  266. package/specs/memory-event-v1.schema.json +42 -42
  267. package/specs/passport-v1.schema.json +50 -50
  268. package/specs/proof-of-non-use-v1.schema.json +65 -0
  269. package/specs/quarantine-record-v1.schema.json +126 -0
  270. package/specs/receipt-v1.schema.json +61 -61
  271. package/specs/state-checkpoint-v1.schema.json +37 -37
  272. package/specs/trust-bundle-v1.schema.json +56 -56
  273. package/specs/trust-card-v1.schema.json +119 -0
  274. package/docs/proof-network-launch-plan.md +0 -421
  275. package/packages/metering/PACKAGE_CONTRACT.md +0 -20
  276. package/scripts/build-ai-orchestration-plan.mjs +0 -248
@@ -0,0 +1,136 @@
1
+ import readline from 'node:readline';
2
+ import { renderTerminalBanner } from './banner.mjs';
3
+ import { TerminalSession } from './commands.mjs';
4
+
5
+ const KNOWN_COMMANDS = [
6
+ 'quickstart',
7
+ 'remember',
8
+ 'context',
9
+ 'doctor',
10
+ 'tree',
11
+ 'tui',
12
+ 'dashboard',
13
+ 'monitor',
14
+ 'constellation',
15
+ 'matrix',
16
+ 'enclave cycle',
17
+ 'enclave attest',
18
+ 'enclave verify',
19
+ 'swarm status',
20
+ 'swarm graph',
21
+ 'wallet',
22
+ 'wallet connect',
23
+ 'wallet status',
24
+ 'wallet disconnect',
25
+ 'solana status',
26
+ 'solana stream',
27
+ 'solana pulse',
28
+ 'solana anchor-bundle',
29
+ 'solana submit',
30
+ 'solana verify-anchor',
31
+ 'export',
32
+ 'verify',
33
+ 'token',
34
+ 'ca',
35
+ 'clear',
36
+ 'cls',
37
+ 'help',
38
+ 'exit',
39
+ 'quit',
40
+ ];
41
+
42
+ export function createCompleter(session) {
43
+ return function completer(linePartial) {
44
+ const trimmed = linePartial.trimStart();
45
+ const hits = KNOWN_COMMANDS.filter((cmd) => cmd.startsWith(trimmed));
46
+ return [hits.length ? hits : KNOWN_COMMANDS, linePartial];
47
+ };
48
+ }
49
+
50
+ export async function runTerminalConsole({
51
+ bundlePath = '.enigma/bundle.json',
52
+ color = true,
53
+ execCommand = undefined,
54
+ interactive = true,
55
+ io = { stdin: process.stdin, stdout: process.stdout, stderr: process.stderr },
56
+ } = {}) {
57
+ const session = new TerminalSession({ bundlePath, color, io });
58
+
59
+ // If a one-shot command was provided via --exec, run it and return
60
+ if (execCommand) {
61
+ const exitCode = await session.executeCommand(execCommand);
62
+ return exitCode === 'EXIT' ? 0 : (exitCode || 0);
63
+ }
64
+
65
+ // If not interactive (e.g. CI without TTY), print banner and exit
66
+ if (!interactive || !io.stdin?.isTTY) {
67
+ io.stdout.write(renderTerminalBanner({ color }) + '\n');
68
+ return 0;
69
+ }
70
+
71
+ // Interactive REPL Loop
72
+ io.stdout.write('\x1b[2J\x1b[0;0H');
73
+ io.stdout.write(renderTerminalBanner({ color }) + '\n\n');
74
+
75
+ const rl = readline.createInterface({
76
+ input: io.stdin,
77
+ output: io.stdout,
78
+ completer: createCompleter(session),
79
+ terminal: true,
80
+ });
81
+
82
+ return new Promise((resolve) => {
83
+ let closed = false;
84
+ let commandQueue = Promise.resolve();
85
+
86
+ async function promptLoop() {
87
+ if (closed) return;
88
+ const promptStr = await session.renderPrompt();
89
+ rl.setPrompt(promptStr);
90
+ rl.prompt();
91
+ }
92
+
93
+ rl.on('line', (line) => {
94
+ commandQueue = commandQueue.then(async () => {
95
+ if (closed) return;
96
+ session.activeAbortController = new AbortController();
97
+ try {
98
+ const result = await session.executeCommand(line, { signal: session.activeAbortController.signal });
99
+ if (result === 'EXIT') {
100
+ closed = true;
101
+ io.stdout.write('\nExiting Enigma Terminal Console. Session closed.\n');
102
+ rl.close();
103
+ resolve(0);
104
+ return;
105
+ }
106
+ } catch (err) {
107
+ if (!session.activeAbortController?.signal?.aborted) {
108
+ io.stderr.write(`\nError: ${err.message}\n`);
109
+ }
110
+ } finally {
111
+ session.activeAbortController = null;
112
+ }
113
+ io.stdout.write('\n');
114
+ await promptLoop();
115
+ });
116
+ });
117
+
118
+ rl.on('SIGINT', () => {
119
+ if (session.activeAbortController) {
120
+ session.activeAbortController.abort();
121
+ io.stdout.write('\n^C\n');
122
+ } else {
123
+ closed = true;
124
+ io.stdout.write('\nExiting Enigma Terminal Console.\n');
125
+ rl.close();
126
+ resolve(0);
127
+ }
128
+ });
129
+ rl.on('close', () => {
130
+ closed = true;
131
+ resolve(0);
132
+ });
133
+
134
+ promptLoop();
135
+ });
136
+ }
@@ -0,0 +1,130 @@
1
+ // Enigma Sovereign AI Memory — SignerStore Interface
2
+ // Manages secure automation signer key custody with strict zero-plaintext boundaries.
3
+ // Integrates with Electron safeStorage (OS DPAPI on Windows / Keychain on macOS) and host-bound AES-256-GCM.
4
+
5
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
6
+ import { resolve, dirname } from 'node:path';
7
+ import crypto from 'node:crypto';
8
+ import os from 'node:os';
9
+ import { encodeBase58 } from './phantom-bridge.mjs';
10
+
11
+ function deriveHostBoundKey(salt = 'enigma-auto-anchor-dpapi-salt-v1') {
12
+ const hostIdentity = [
13
+ os.hostname() || 'localhost',
14
+ os.userInfo()?.username || 'user',
15
+ os.platform() || 'win32',
16
+ os.arch() || 'x64',
17
+ ].join(':');
18
+ return crypto.scryptSync(hostIdentity, salt, 32);
19
+ }
20
+
21
+ export function zeroizeBuffer(buf) {
22
+ if (Buffer.isBuffer(buf)) {
23
+ try {
24
+ buf.fill(0);
25
+ } catch {}
26
+ }
27
+ }
28
+
29
+ export class SignerStore {
30
+ constructor({ signerPath = '.enigma/auto-anchor/signer.enc.json', safeStorage = undefined, customKey = undefined } = {}) {
31
+ this.signerPath = resolve(signerPath);
32
+ this.safeStorage = safeStorage;
33
+ this.customKey = customKey;
34
+ }
35
+
36
+ hasSigner() {
37
+ return existsSync(this.signerPath);
38
+ }
39
+
40
+ getPublicKey() {
41
+ if (!this.hasSigner()) return null;
42
+ try {
43
+ const data = JSON.parse(readFileSync(this.signerPath, 'utf8'));
44
+ return data.publicKeyBase58 || null;
45
+ } catch {
46
+ return null;
47
+ }
48
+ }
49
+
50
+ generateKeypair() {
51
+ const { publicKey, privateKey } = crypto.generateKeyPairSync('ed25519');
52
+ const rawPubBytes = publicKey.export({ type: 'spki', format: 'der' }).subarray(-32);
53
+ const rawPrivBytes = privateKey.export({ type: 'pkcs8', format: 'der' }).subarray(-32);
54
+ const fullSecretKey = Buffer.concat([rawPrivBytes, rawPubBytes]);
55
+ const publicKeyBase58 = encodeBase58(rawPubBytes);
56
+ return {
57
+ publicKeyBase58,
58
+ publicKeyBuffer: rawPubBytes,
59
+ secretKeyBuffer: fullSecretKey,
60
+ };
61
+ }
62
+
63
+ saveSignerSecretKey(secretKeyBuffer, { publicKeyBase58 }) {
64
+ if (!Buffer.isBuffer(secretKeyBuffer) || secretKeyBuffer.length !== 64) {
65
+ throw new Error('Signer secret key must be a valid 64-byte Buffer');
66
+ }
67
+ if (!publicKeyBase58 || typeof publicKeyBase58 !== 'string') {
68
+ throw new Error('Missing valid publicKeyBase58');
69
+ }
70
+
71
+ const dir = dirname(this.signerPath);
72
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
73
+
74
+ if (this.safeStorage && typeof this.safeStorage.encryptString === 'function') {
75
+ const encryptedBuffer = this.safeStorage.encryptString(secretKeyBuffer.toString('hex'));
76
+ const manifest = {
77
+ schema: 'enigma.encrypted_signer_safestorage.v1',
78
+ publicKeyBase58,
79
+ encryptedHex: encryptedBuffer.toString('hex'),
80
+ storage: 'electron-safestorage-dpapi',
81
+ created_at: new Date().toISOString(),
82
+ };
83
+ writeFileSync(this.signerPath, JSON.stringify(manifest, null, 2), 'utf8');
84
+ return true;
85
+ }
86
+
87
+ // Host-bound AES-256-GCM encryption fallback
88
+ const key = this.customKey || deriveHostBoundKey();
89
+ const iv = crypto.randomBytes(12);
90
+ const cipher = crypto.createCipheriv('aes-256-gcm', key, iv);
91
+ const encrypted = Buffer.concat([cipher.update(secretKeyBuffer), cipher.final()]);
92
+ const tag = cipher.getAuthTag();
93
+
94
+ const manifest = {
95
+ schema: 'enigma.encrypted_signer_hostbound.v1',
96
+ publicKeyBase58,
97
+ iv: iv.toString('hex'),
98
+ tag: tag.toString('hex'),
99
+ data: encrypted.toString('hex'),
100
+ storage: 'host-bound-aes-256-gcm',
101
+ created_at: new Date().toISOString(),
102
+ };
103
+ writeFileSync(this.signerPath, JSON.stringify(manifest, null, 2), 'utf8');
104
+ return true;
105
+ }
106
+
107
+ loadSignerSecretKey() {
108
+ if (!this.hasSigner()) throw new Error('Signer store is empty');
109
+ const data = JSON.parse(readFileSync(this.signerPath, 'utf8'));
110
+
111
+ if (data.schema === 'enigma.encrypted_signer_safestorage.v1') {
112
+ if (!this.safeStorage || typeof this.safeStorage.decryptString !== 'function') {
113
+ throw new Error('Cannot decrypt safeStorage payload outside active Electron session');
114
+ }
115
+ const decryptedHex = this.safeStorage.decryptString(Buffer.from(data.encryptedHex, 'hex'));
116
+ return Buffer.from(decryptedHex, 'hex');
117
+ }
118
+
119
+ if (data.schema === 'enigma.encrypted_signer_hostbound.v1') {
120
+ const key = this.customKey || deriveHostBoundKey();
121
+ const iv = Buffer.from(data.iv, 'hex');
122
+ const tag = Buffer.from(data.tag, 'hex');
123
+ const decipher = crypto.createDecipheriv('aes-256-gcm', key, iv);
124
+ decipher.setAuthTag(tag);
125
+ return Buffer.concat([decipher.update(Buffer.from(data.data, 'hex')), decipher.final()]);
126
+ }
127
+
128
+ throw new Error(`Unsupported signer storage schema: ${data.schema}`);
129
+ }
130
+ }
@@ -0,0 +1,214 @@
1
+ import { OFFICIAL_TOKEN_CA } from './banner.mjs';
2
+ import { ANSI } from '../terminal-tui/merkle-tree-renderer.mjs';
3
+
4
+ export const SOLANA_RPC_CLUSTERS = Object.freeze({
5
+ 'mainnet-beta': 'https://api.mainnet-beta.solana.com',
6
+ 'devnet': 'https://api.devnet.solana.com',
7
+ 'testnet': 'https://api.testnet.solana.com',
8
+ 'localnet': 'http://127.0.0.1:8899',
9
+ });
10
+
11
+ function isCustomRpcUrl(rpcUrl) {
12
+ return typeof rpcUrl === 'string' && rpcUrl.length > 0;
13
+ }
14
+
15
+ function redactRpcText(value, resolvedUrl) {
16
+ let text = String(value ?? '');
17
+ if (resolvedUrl) text = text.split(resolvedUrl).join('<custom-rpc>');
18
+ return text.replace(/[a-z][a-z0-9+.-]*:\/\/[^\s/@]+:[^\s/@]+@[^\s)]+/giu, '<custom-rpc>');
19
+ }
20
+
21
+ export async function probeSolanaRpc({
22
+ cluster = 'mainnet-beta',
23
+ rpcUrl = undefined,
24
+ timeoutMs = 4000,
25
+ fetchFn = globalThis.fetch,
26
+ } = {}) {
27
+ const customRpc = isCustomRpcUrl(rpcUrl);
28
+ const resolvedUrl = customRpc ? rpcUrl : SOLANA_RPC_CLUSTERS[cluster] || SOLANA_RPC_CLUSTERS['mainnet-beta'];
29
+ const displayedUrl = customRpc ? '<custom-rpc>' : resolvedUrl;
30
+ const started = Date.now();
31
+
32
+ const payload = {
33
+ jsonrpc: '2.0',
34
+ id: 'enigma-probe',
35
+ method: 'getHealth',
36
+ };
37
+
38
+ try {
39
+ let timer;
40
+ const controller = typeof AbortController !== 'undefined' ? new AbortController() : null;
41
+ if (controller) {
42
+ timer = setTimeout(() => controller.abort(), timeoutMs);
43
+ }
44
+
45
+ const response = await fetchFn(resolvedUrl, {
46
+ method: 'POST',
47
+ headers: { 'Content-Type': 'application/json' },
48
+ body: JSON.stringify(payload),
49
+ signal: controller?.signal,
50
+ });
51
+
52
+ clearTimeout(timer);
53
+ const latencyMs = Date.now() - started;
54
+
55
+ if (!response.ok) {
56
+ return {
57
+ ok: false,
58
+ cluster,
59
+ rpc_url: displayedUrl,
60
+ error: redactRpcText(`HTTP ${response.status}: ${response.statusText}`, customRpc ? resolvedUrl : undefined),
61
+ latency_ms: latencyMs,
62
+ };
63
+ }
64
+
65
+ const data = await response.json();
66
+ const isHealthy = data?.result === 'ok' || data?.result === true || (!data?.error && data?.result !== undefined);
67
+
68
+ return {
69
+ ok: isHealthy,
70
+ cluster,
71
+ rpc_url: displayedUrl,
72
+ health: data?.result ?? (data?.error ? 'error' : 'ok'),
73
+ latency_ms: latencyMs,
74
+ error: data?.error?.message ? redactRpcText(data.error.message, customRpc ? resolvedUrl : undefined) : null,
75
+ };
76
+ } catch (err) {
77
+ return {
78
+ ok: false,
79
+ cluster,
80
+ rpc_url: displayedUrl,
81
+ error: err.name === 'AbortError' ? `RPC probe timed out after ${timeoutMs}ms` : redactRpcText(err.message, customRpc ? resolvedUrl : undefined),
82
+ latency_ms: Date.now() - started,
83
+ };
84
+ }
85
+ }
86
+
87
+ export async function fetchSolanaStreamMetrics({
88
+ cluster = 'mainnet-beta',
89
+ rpcUrl = undefined,
90
+ timeoutMs = 4000,
91
+ fetchFn = globalThis.fetch,
92
+ } = {}) {
93
+ const customRpc = isCustomRpcUrl(rpcUrl);
94
+ const resolvedUrl = customRpc ? rpcUrl : SOLANA_RPC_CLUSTERS[cluster] || SOLANA_RPC_CLUSTERS['mainnet-beta'];
95
+ const displayedUrl = customRpc ? '<custom-rpc>' : resolvedUrl;
96
+ const started = Date.now();
97
+
98
+ const payload = [
99
+ { jsonrpc: '2.0', id: 'probe-health', method: 'getHealth' },
100
+ { jsonrpc: '2.0', id: 'probe-slot', method: 'getSlot' },
101
+ { jsonrpc: '2.0', id: 'probe-epoch', method: 'getEpochInfo' },
102
+ { jsonrpc: '2.0', id: 'probe-version', method: 'getVersion' },
103
+ ];
104
+
105
+ try {
106
+ let timer;
107
+ const controller = typeof AbortController !== 'undefined' ? new AbortController() : null;
108
+ if (controller) {
109
+ timer = setTimeout(() => controller.abort(), timeoutMs);
110
+ }
111
+
112
+ const response = await fetchFn(resolvedUrl, {
113
+ method: 'POST',
114
+ headers: { 'Content-Type': 'application/json' },
115
+ body: JSON.stringify(payload),
116
+ signal: controller?.signal,
117
+ });
118
+
119
+ clearTimeout(timer);
120
+ const latencyMs = Date.now() - started;
121
+
122
+ if (!response.ok) {
123
+ return {
124
+ ok: false,
125
+ cluster,
126
+ rpc_url: displayedUrl,
127
+ latency_ms: latencyMs,
128
+ error: redactRpcText(`HTTP ${response.status}: ${response.statusText}`, customRpc ? resolvedUrl : undefined),
129
+ };
130
+ }
131
+
132
+ const data = await response.json();
133
+ const results = Array.isArray(data) ? data : [data];
134
+ const healthRes = results.find((r) => r.id === 'probe-health')?.result ?? data?.result;
135
+ const slotRes = results.find((r) => r.id === 'probe-slot')?.result;
136
+ const epochRes = results.find((r) => r.id === 'probe-epoch')?.result;
137
+ const versionRes = results.find((r) => r.id === 'probe-version')?.result;
138
+
139
+ const isHealthy = healthRes === 'ok' || healthRes === true || (slotRes !== undefined && slotRes !== null) || data?.result === 'ok' || data?.result === true;
140
+
141
+ return {
142
+ ok: isHealthy,
143
+ cluster,
144
+ rpc_url: displayedUrl,
145
+ latency_ms: latencyMs,
146
+ slot: typeof slotRes === 'number' ? slotRes : (typeof epochRes?.absoluteSlot === 'number' ? epochRes.absoluteSlot : null),
147
+ block_height: typeof epochRes?.blockHeight === 'number' ? epochRes.blockHeight : null,
148
+ epoch: typeof epochRes?.epoch === 'number' ? epochRes.epoch : null,
149
+ slot_index: typeof epochRes?.slotIndex === 'number' ? epochRes.slotIndex : null,
150
+ slots_in_epoch: typeof epochRes?.slotsInEpoch === 'number' ? epochRes.slotsInEpoch : null,
151
+ transaction_count: typeof epochRes?.transactionCount === 'number' ? epochRes.transactionCount : null,
152
+ solana_core_version: versionRes?.['solana-core'] ?? (typeof versionRes === 'string' ? versionRes : 'N/A'),
153
+ error: null,
154
+ };
155
+ } catch (err) {
156
+ return {
157
+ ok: false,
158
+ cluster,
159
+ rpc_url: displayedUrl,
160
+ latency_ms: Date.now() - started,
161
+ slot: null,
162
+ block_height: null,
163
+ epoch: null,
164
+ error: err.name === 'AbortError' ? `RPC probe timed out after ${timeoutMs}ms` : redactRpcText(err.message, customRpc ? resolvedUrl : undefined),
165
+ };
166
+ }
167
+ }
168
+
169
+ export function formatSolanaStatusReport(probeResult, { color = true } = {}) {
170
+ const c = (text, code) => (color ? `${code}${text}${ANSI.reset}` : text);
171
+
172
+ const statusBadge = probeResult.ok
173
+ ? `${c('● CONNECTED', `${ANSI.bold}${ANSI.green}`)} (${probeResult.latency_ms}ms)`
174
+ : `${c('○ UNREACHABLE / OFFLINE', `${ANSI.bold}${ANSI.yellow}`)} (${probeResult.error || 'no response'})`;
175
+
176
+ const lines = [
177
+ ` ${c('ENIGMA SOLANA CONTEXT LAYER & RPC STATUS', `${ANSI.bold}${ANSI.amber}`)}`,
178
+ ` ${c('Cluster:', ANSI.dimText)} ${probeResult.cluster} (${probeResult.rpc_url})`,
179
+ ` ${c('RPC Connection:', ANSI.dimText)} ${statusBadge}`,
180
+ ` ${c('Token Mint CA:', ANSI.dimText)} ${c(OFFICIAL_TOKEN_CA, `${ANSI.bold}${ANSI.white}`)}`,
181
+ ` ${c('Proof Anchoring:', ANSI.dimText)} Solana Memo Program (MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr)`,
182
+ ` ${c('Anchor Contracts:', ANSI.dimText)} enigma_nullifier, enigma_context_escrow (in development)`,
183
+ ` ${c('Settlement Model:', ANSI.dimText)} M2M context micro-escrows with receipt-hash release`,
184
+ ` ${c('Architecture:', ANSI.dimText)} 100% offline standalone vaults; optional on-chain proof anchoring`,
185
+ ];
186
+
187
+ return lines.join('\n') + '\n';
188
+ }
189
+
190
+ export function formatSolanaAnchorVerificationReport(report, { color = true } = {}) {
191
+ const c = (text, code) => (color ? `${code}${text}${ANSI.reset}` : text);
192
+ const verified = report?.ok === true && report?.verified_on_chain === true;
193
+ const status = verified
194
+ ? c('● VERIFIED', `${ANSI.bold}${ANSI.green}`)
195
+ : c('○ NOT VERIFIED', `${ANSI.bold}${ANSI.yellow}`);
196
+ const confirmation = report?.confirmation_status
197
+ ? `${report.confirmation_status}${report.confirmation_depth === null || report.confirmation_depth === undefined ? '' : ` (${report.confirmation_depth} confirmations)`}`
198
+ : 'unknown';
199
+ const lines = [
200
+ ` ${c('ENIGMA SOLANA ANCHOR VERIFICATION', `${ANSI.bold}${ANSI.amber}`)}`,
201
+ ` ${c('Status:', ANSI.dimText)} ${status}`,
202
+ ` ${c('Cluster:', ANSI.dimText)} ${report?.cluster ?? 'unknown'}`,
203
+ ` ${c('Signature:', ANSI.dimText)} ${report?.signature ?? 'unknown'}`,
204
+ ` ${c('Slot / block time:', ANSI.dimText)} ${report?.slot ?? 'unknown'} / ${report?.block_time ?? 'unknown'}`,
205
+ ` ${c('Confirmation:', ANSI.dimText)} ${confirmation}`,
206
+ ` ${c('Slot depth:', ANSI.dimText)} ${report?.slot_depth ?? 'unknown'}`,
207
+ ` ${c('Artifact hash:', ANSI.dimText)} ${report?.artifact_hash ?? 'unknown'}`,
208
+ ` ${c('Proof commitment:', ANSI.dimText)} ${report?.proof_commitment ?? 'unknown'}`,
209
+ ` ${c('Verification state:', ANSI.dimText)} ${report?.verification_status ?? 'unknown'}`,
210
+ ...(report?.error?.message ? [` ${c('Error:', ANSI.dimText)} ${report.error.message}`] : []),
211
+ ` ${c('Boundary:', ANSI.dimText)} public commitment only; no factual-truth, hallucination, provider-deletion, or physical-deletion claim`,
212
+ ];
213
+ return lines.join('\n') + '\n';
214
+ }
@@ -0,0 +1,189 @@
1
+ // Enigma Sovereign AI Memory — Solana Transport Layer
2
+ // Enforces Mainnet genesis verification, dynamic fee computation with 5,000 lamport clamp,
3
+ // SPL Memo program allowlisting, parsed transaction confirmation, and secret key zeroization.
4
+
5
+ import { SOLANA_MAINNET_RPC } from './phantom-bridge.mjs';
6
+ import { sha256Json, PROOF_NETWORK_ANCHOR_BATCH_SCHEMA } from '../proof-network/src/index.js';
7
+
8
+ export const SOLANA_MEMO_PROGRAM_ID = 'MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr';
9
+ export const SOLANA_MAINNET_GENESIS_HASH = '5eykt4UsFv8P8NJdTREpY1vzqKqZKvdpKuc147dw2N9d';
10
+ export const MAX_AUTO_ANCHOR_FEE_LAMPORTS = 5000;
11
+
12
+ export function createSolanaProofMemoRef(schema, artifact, cluster = 'mainnet-beta') {
13
+ const artifactHash = sha256Json(artifact);
14
+ const proofCommitment = sha256Json({
15
+ rail: 'solana-memo-v1',
16
+ cluster,
17
+ artifact_type: schema,
18
+ artifact_hash: artifactHash,
19
+ });
20
+ return {
21
+ v: 1,
22
+ protocol: 'enigma-proof-network',
23
+ rail: 'solana-memo',
24
+ cluster,
25
+ artifact_type: schema,
26
+ artifact_hash: artifactHash,
27
+ proof_commitment: proofCommitment,
28
+ };
29
+ }
30
+
31
+ export class SolanaTransport {
32
+ constructor({
33
+ rpcUrl = SOLANA_MAINNET_RPC,
34
+ cluster = 'mainnet-beta',
35
+ fetchFn = globalThis.fetch,
36
+ maxFeeLamports = MAX_AUTO_ANCHOR_FEE_LAMPORTS,
37
+ } = {}) {
38
+ this.rpcUrl = rpcUrl;
39
+ this.cluster = cluster;
40
+ this.fetchFn = fetchFn;
41
+ this.maxFeeLamports = maxFeeLamports;
42
+ }
43
+
44
+ async verifyGenesisHash() {
45
+ const res = await this.fetchFn(this.rpcUrl, {
46
+ method: 'POST',
47
+ headers: { 'Content-Type': 'application/json' },
48
+ body: JSON.stringify({ jsonrpc: '2.0', id: 'genesis-check', method: 'getGenesisHash' }),
49
+ });
50
+ const data = await res.json();
51
+ const hash = data?.result;
52
+ if (this.cluster === 'mainnet-beta' && hash !== SOLANA_MAINNET_GENESIS_HASH) {
53
+ throw new Error(`Solana RPC genesis mismatch: got ${hash}, expected ${SOLANA_MAINNET_GENESIS_HASH}`);
54
+ }
55
+ return hash;
56
+ }
57
+
58
+ async getBalance(publicKeyBase58) {
59
+ const res = await this.fetchFn(this.rpcUrl, {
60
+ method: 'POST',
61
+ headers: { 'Content-Type': 'application/json' },
62
+ body: JSON.stringify({ jsonrpc: '2.0', id: 'bal-check', method: 'getBalance', params: [publicKeyBase58] }),
63
+ });
64
+ const data = await res.json();
65
+ const lamports = Number(data?.result?.value ?? 0);
66
+ return { lamports, balanceSol: (lamports / 1e9).toFixed(4) };
67
+ }
68
+
69
+ async broadcastMemo({ payerSecretKey, memoRef }) {
70
+ if (!Buffer.isBuffer(payerSecretKey) || payerSecretKey.length !== 64) {
71
+ throw new Error('payerSecretKey must be a 64-byte Buffer');
72
+ }
73
+ if (!memoRef || typeof memoRef !== 'object') {
74
+ throw new Error('Invalid memoRef payload');
75
+ }
76
+
77
+ await this.verifyGenesisHash();
78
+
79
+ // Dynamically load Solana web3 SDK
80
+ const web3 = await import('@solana/web3.js');
81
+ const { Connection, Keypair, PublicKey, Transaction, TransactionInstruction, sendAndConfirmTransaction } = web3;
82
+
83
+ let payer;
84
+ try {
85
+ payer = Keypair.fromSecretKey(payerSecretKey);
86
+ } finally {
87
+ // Memory safety: Zeroize volatile secret key buffer copy after keypair creation
88
+ try {
89
+ payerSecretKey.fill(0);
90
+ } catch {}
91
+ }
92
+
93
+ const connection = new Connection(this.rpcUrl, 'confirmed');
94
+ const memoBytes = Buffer.from(JSON.stringify(memoRef), 'utf8');
95
+
96
+ const instruction = new TransactionInstruction({
97
+ keys: [],
98
+ programId: new PublicKey(SOLANA_MEMO_PROGRAM_ID),
99
+ data: memoBytes,
100
+ });
101
+
102
+ const { blockhash } = await connection.getLatestBlockhash('confirmed');
103
+ const transaction = new Transaction({ feePayer: payer.publicKey, recentBlockhash: blockhash }).add(instruction);
104
+
105
+ // Dynamic Fee Query & Clamp Verification
106
+ const message = transaction.compileMessage();
107
+ const feeRes = await connection.getFeeForMessage(message, 'confirmed');
108
+ const estimatedFee = Number(feeRes?.value ?? 5000);
109
+
110
+ if (estimatedFee > this.maxFeeLamports) {
111
+ throw new Error(`Estimated transaction fee (${estimatedFee} lamports) exceeds maximum auto-anchor fee policy (${this.maxFeeLamports} lamports)`);
112
+ }
113
+
114
+ // Submit and confirm
115
+ const signature = await sendAndConfirmTransaction(connection, transaction, [payer], { commitment: 'confirmed' });
116
+
117
+ return await this.verifyTransactionOnChain(connection, signature, memoRef, estimatedFee);
118
+ }
119
+
120
+ async verifyTransactionOnChain(connection, signature, expectedMemoRef, fee = 5000) {
121
+ if (!expectedMemoRef || typeof expectedMemoRef !== 'object') {
122
+ throw new Error('expectedMemoRef must be a canonical memoRef object');
123
+ }
124
+
125
+ const parsedTx = await connection.getParsedTransaction(signature, {
126
+ commitment: 'confirmed',
127
+ maxSupportedTransactionVersion: 0,
128
+ });
129
+
130
+ if (!parsedTx || parsedTx.meta?.err !== null) {
131
+ throw new Error(`On-chain transaction execution failed: ${signature}`);
132
+ }
133
+
134
+ // Verify SPL Memo program ID and instruction presence
135
+ const memoIx = parsedTx.transaction?.message?.instructions?.find((ix) => {
136
+ const progId = ix.programId?.toBase58 ? ix.programId.toBase58() : String(ix.programId || '');
137
+ return progId === SOLANA_MEMO_PROGRAM_ID;
138
+ });
139
+
140
+ if (!memoIx) {
141
+ throw new Error(`Transaction ${signature} missing verified SPL Memo program instruction`);
142
+ }
143
+
144
+ // Extract memo text from parsed or data field
145
+ const rawMemoText = typeof memoIx.parsed === 'string'
146
+ ? memoIx.parsed
147
+ : (typeof memoIx.data === 'string' ? Buffer.from(memoIx.data, 'base64').toString('utf8') : '');
148
+
149
+ let observed;
150
+ try {
151
+ observed = JSON.parse(rawMemoText);
152
+ } catch {
153
+ throw new Error('On-chain memo payload is not valid JSON');
154
+ }
155
+
156
+ // Exact field-by-field equality checks against canonical memoRef contract
157
+ if (observed.v !== expectedMemoRef.v) {
158
+ throw new Error(`Memo version mismatch: got ${observed.v}, expected ${expectedMemoRef.v}`);
159
+ }
160
+ if (observed.protocol !== expectedMemoRef.protocol) {
161
+ throw new Error(`Memo protocol mismatch: got ${observed.protocol}, expected ${expectedMemoRef.protocol}`);
162
+ }
163
+ if (observed.rail !== expectedMemoRef.rail) {
164
+ throw new Error(`Memo rail mismatch: got ${observed.rail}, expected ${expectedMemoRef.rail}`);
165
+ }
166
+ if (observed.cluster !== expectedMemoRef.cluster) {
167
+ throw new Error(`Memo cluster mismatch: got ${observed.cluster}, expected ${expectedMemoRef.cluster}`);
168
+ }
169
+ if (observed.artifact_type !== expectedMemoRef.artifact_type) {
170
+ throw new Error(`Memo artifact_type mismatch: got ${observed.artifact_type}, expected ${expectedMemoRef.artifact_type}`);
171
+ }
172
+ if (observed.artifact_hash !== expectedMemoRef.artifact_hash) {
173
+ throw new Error(`Memo artifact_hash mismatch: got ${observed.artifact_hash}, expected ${expectedMemoRef.artifact_hash}`);
174
+ }
175
+ if (observed.proof_commitment !== expectedMemoRef.proof_commitment) {
176
+ throw new Error(`Memo proof_commitment mismatch: got ${observed.proof_commitment}, expected ${expectedMemoRef.proof_commitment}`);
177
+ }
178
+
179
+ return {
180
+ signature,
181
+ slot: parsedTx.slot,
182
+ blockTime: parsedTx.blockTime,
183
+ cluster: this.cluster,
184
+ solscanUrl: `https://solscan.io/tx/${signature}`,
185
+ feePaidLamports: parsedTx.meta?.fee ?? fee,
186
+ observedMemo: observed,
187
+ };
188
+ }
189
+ }