enigma-memory 0.1.17 → 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 (290) hide show
  1. package/README.md +85 -27
  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 +4979 -3263
  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/SIMULATION.md +14 -9
  74. package/deploy/docker-compose.local-production-simulation.yml +54 -12
  75. package/docs/benchmark-attestation-network.md +487 -487
  76. package/docs/benchmark-reproducibility.md +289 -289
  77. package/docs/blockchain-only-mechanisms.md +400 -400
  78. package/docs/browser-extension-install.md +8 -6
  79. package/docs/client-connectors.md +16 -12
  80. package/docs/demo-proof-network.md +275 -275
  81. package/docs/developer-ecosystem.md +15 -13
  82. package/docs/developer-proof-quickstart.md +325 -325
  83. package/docs/enigma-memory-ready-conformance.md +378 -376
  84. package/docs/install-anywhere.md +64 -30
  85. package/docs/installers-and-desktop.md +8 -7
  86. package/docs/memory-benchmarks.md +1 -1
  87. package/docs/memory-drive-health-model.md +690 -690
  88. package/docs/novelty-invention-candidates.md +161 -161
  89. package/docs/proof-network-build-notes.md +240 -240
  90. package/docs/proof-network-claim-boundaries.md +320 -318
  91. package/docs/proof-network.md +339 -339
  92. package/docs/sdk-api.md +324 -324
  93. package/docs/solana-proof-rail.md +453 -453
  94. package/examples/01-quickstart-agent/index.mjs +49 -0
  95. package/examples/01_agent_memory_quickstart.mjs +57 -0
  96. package/examples/02-multi-agent-swarm/index.mjs +57 -0
  97. package/examples/02_cross_model_passport.mjs +64 -0
  98. package/examples/03-langchain-memory/index.mjs +41 -0
  99. package/examples/03_poseidon_commitment_verification.mjs +71 -0
  100. package/examples/04-python-trading-agent/trader.py +49 -0
  101. package/examples/README.md +27 -0
  102. package/examples/ci/github-actions.yml +7 -2
  103. package/package.json +410 -278
  104. package/packages/adapters/PACKAGE_CONTRACT.md +1 -1
  105. package/packages/connectors/src/index.js +196 -4
  106. package/packages/connectors/swarm-router.mjs +168 -0
  107. package/packages/core/src/index.js +249 -2
  108. package/packages/core/src/version.mjs +7 -0
  109. package/packages/dev-tools/package.json +19 -0
  110. package/packages/dev-tools/src/index.js +4 -0
  111. package/packages/dev-tools/src/memory-benchmark-suite.js +112 -0
  112. package/packages/dev-tools/src/swarm-simulator.js +101 -0
  113. package/packages/dev-tools/src/vault-inspector.js +114 -0
  114. package/packages/dev-tools/src/vector-benchmark.js +100 -0
  115. package/packages/developer-platform/src/access-credentials.js +341 -0
  116. package/packages/developer-platform/src/http.js +132 -0
  117. package/packages/developer-platform/src/index.js +4 -0
  118. package/packages/developer-platform/src/usage-http.js +60 -0
  119. package/packages/developer-platform/src/usage.js +295 -0
  120. package/packages/enclave-runtime/attestation.mjs +159 -0
  121. package/packages/enclave-runtime/index.mjs +47 -0
  122. package/packages/enclave-runtime/session-manager.mjs +253 -0
  123. package/packages/enclave-runtime/zeroization-proof.mjs +227 -0
  124. package/packages/enigma-reflex/package.json +14 -0
  125. package/packages/enigma-reflex/src/index.js +204 -0
  126. package/packages/enigma-reflex/training/generate-dataset.mjs +40 -0
  127. package/packages/enigma-reflex/training/requirements.txt +8 -0
  128. package/packages/enigma-reflex/training/train.py +314 -0
  129. package/packages/enigma-weave/LICENSE +22 -0
  130. package/packages/enigma-weave/UPSTREAM.json +21 -0
  131. package/packages/enigma-weave/package.json +14 -0
  132. package/packages/enigma-weave/src/index.js +286 -0
  133. package/packages/hosted-cloud/src/index.js +80 -5
  134. package/packages/importers/src/index.js +432 -0
  135. package/packages/inference-runtime/src/browser.js +401 -0
  136. package/packages/inference-runtime/src/chat.js +265 -0
  137. package/packages/inference-runtime/src/code.js +407 -0
  138. package/packages/inference-runtime/src/contracts.js +162 -0
  139. package/packages/inference-runtime/src/http.js +232 -0
  140. package/packages/inference-runtime/src/image.js +186 -0
  141. package/packages/inference-runtime/src/index.js +10 -0
  142. package/packages/inference-runtime/src/model-router.js +320 -0
  143. package/packages/inference-runtime/src/platform.js +125 -0
  144. package/packages/inference-runtime/src/privacy.js +400 -0
  145. package/packages/inference-runtime/src/video.js +253 -0
  146. package/packages/mcp-server/README.md +22 -6
  147. package/packages/mcp-server/bin/enigma-mcp.mjs +2 -1
  148. package/packages/mcp-server/src/index.js +2498 -1185
  149. package/packages/mcp-server/src/oauth.js +561 -0
  150. package/packages/mcp-server/src/private-handoff.js +84 -0
  151. package/packages/mcp-server/src/remote-http.js +273 -0
  152. package/packages/mcp-server/src/remote-policy.js +72 -0
  153. package/packages/mcp-server/swarm-bridge.mjs +361 -0
  154. package/packages/mesh/index.d.ts +283 -0
  155. package/packages/mesh/package.json +23 -0
  156. package/packages/mesh/src/crypto.js +189 -0
  157. package/packages/mesh/src/federation-packets.js +353 -0
  158. package/packages/mesh/src/gossip.js +311 -0
  159. package/packages/mesh/src/index.js +6 -0
  160. package/packages/mesh/src/protocol.js +255 -0
  161. package/packages/mesh/src/router.js +279 -0
  162. package/packages/mesh/src/transport.js +306 -0
  163. package/packages/passport/src/index.js +436 -7
  164. package/packages/private-economy/src/credits-http.js +100 -0
  165. package/packages/private-economy/src/credits.js +447 -0
  166. package/packages/private-economy/src/index.js +5 -0
  167. package/packages/private-economy/src/payments-http.js +120 -0
  168. package/packages/private-economy/src/payments.js +509 -0
  169. package/packages/private-economy/src/x402.js +346 -0
  170. package/packages/proof-network/PACKAGE_CONTRACT.md +21 -0
  171. package/packages/rag/index.d.ts +182 -0
  172. package/packages/rag/models/Xenova/all-MiniLM-L6-v2/THIRD_PARTY_LICENSES.txt +207 -0
  173. package/packages/rag/models/Xenova/all-MiniLM-L6-v2/config.json +25 -0
  174. package/packages/rag/models/Xenova/all-MiniLM-L6-v2/onnx/model_quantized.onnx +0 -0
  175. package/packages/rag/models/Xenova/all-MiniLM-L6-v2/sha256-manifest.json +28 -0
  176. package/packages/rag/models/Xenova/all-MiniLM-L6-v2/tokenizer.json +30686 -0
  177. package/packages/rag/models/Xenova/all-MiniLM-L6-v2/tokenizer_config.json +15 -0
  178. package/packages/rag/package.json +27 -0
  179. package/packages/rag/src/blinded-search.js +109 -0
  180. package/packages/rag/src/bm25.js +169 -0
  181. package/packages/rag/src/embeddings.js +459 -0
  182. package/packages/rag/src/hybrid.js +76 -0
  183. package/packages/rag/src/index.js +38 -0
  184. package/packages/rag/src/reranker.js +61 -0
  185. package/packages/rag/src/research.js +107 -0
  186. package/packages/rag/src/vector-store.js +430 -0
  187. package/packages/rag/src/verify-model-artifacts.mjs +4 -0
  188. package/packages/sdk/index.d.ts +760 -0
  189. package/packages/sdk/package.json +33 -0
  190. package/packages/sdk/python/README.md +24 -0
  191. package/packages/sdk/python/enigma_sdk.py +250 -0
  192. package/packages/sdk/python/pyproject.toml +34 -0
  193. package/packages/sdk/python/requirements.txt +1 -0
  194. package/packages/sdk/python/setup.py +20 -0
  195. package/packages/sdk/src/federation/capability-grant.js +389 -0
  196. package/packages/sdk/src/federation/federation-router.js +360 -0
  197. package/packages/sdk/src/federation/ghostmesh-bridge.js +497 -0
  198. package/packages/sdk/src/federation/index.js +3 -0
  199. package/packages/sdk/src/index.js +1796 -0
  200. package/packages/sdk/src/intelligence/contradiction.js +337 -0
  201. package/packages/sdk/src/intelligence/decision-engine.js +155 -0
  202. package/packages/sdk/src/intelligence/index.js +4 -0
  203. package/packages/sdk/src/intelligence/ontology.js +122 -0
  204. package/packages/sdk/src/intelligence/temporal.js +123 -0
  205. package/packages/sdk/src/market-client.js +142 -0
  206. package/packages/sdk/src/mesh-client.js +110 -0
  207. package/packages/sdk/src/middleware/index.js +3 -0
  208. package/packages/sdk/src/middleware/langchain.js +159 -0
  209. package/packages/sdk/src/middleware/llamaindex.js +101 -0
  210. package/packages/sdk/src/middleware/vercel-ai.js +112 -0
  211. package/packages/sdk/src/rag-client.js +85 -0
  212. package/packages/sdk/src/swarm-orchestrator.js +260 -0
  213. package/packages/settlement/PACKAGE_CONTRACT.md +1 -1
  214. package/packages/snapcompact/THIRD_PARTY_LICENSES.txt +40 -0
  215. package/packages/snapcompact/assets/8x13-latin1.bdf +3837 -0
  216. package/packages/snapcompact/index.d.ts +284 -0
  217. package/packages/snapcompact/package.json +25 -0
  218. package/packages/snapcompact/src/index.js +716 -0
  219. package/packages/storage/PACKAGE_CONTRACT.md +1 -1
  220. package/packages/terminal-console/animations.mjs +240 -0
  221. package/packages/terminal-console/auto-anchor.mjs +220 -0
  222. package/packages/terminal-console/banner.mjs +91 -0
  223. package/packages/terminal-console/commands.mjs +459 -0
  224. package/packages/terminal-console/delegation.mjs +152 -0
  225. package/packages/terminal-console/index.mjs +5 -0
  226. package/packages/terminal-console/outbox.mjs +143 -0
  227. package/packages/terminal-console/phantom-bridge.mjs +637 -0
  228. package/packages/terminal-console/repl.mjs +136 -0
  229. package/packages/terminal-console/signer-store.mjs +130 -0
  230. package/packages/terminal-console/solana-rpc.mjs +214 -0
  231. package/packages/terminal-console/solana-transport.mjs +189 -0
  232. package/packages/terminal-tui/dashboard.mjs +214 -0
  233. package/packages/terminal-tui/index.mjs +28 -0
  234. package/packages/terminal-tui/merkle-tree-renderer.mjs +268 -0
  235. package/packages/terminal-tui/telemetry-hud.mjs +137 -0
  236. package/packages/vault/index.d.ts +449 -0
  237. package/packages/vault/package.json +27 -0
  238. package/packages/vault/src/e2ee.mjs +393 -0
  239. package/packages/vault/src/enclave.js +481 -0
  240. package/packages/vault/src/erasure.js +207 -0
  241. package/packages/vault/src/index.js +1150 -125
  242. package/packages/vault/src/persistence.js +307 -0
  243. package/packages/vault/src/poseidon.js +354 -0
  244. package/packages/vault/src/receipt.js +459 -0
  245. package/scripts/benchmark-optical-context.mjs +166 -0
  246. package/scripts/bootstrap-enigma.mjs +502 -0
  247. package/scripts/build-edge-backend-workers.mjs +20 -5
  248. package/scripts/build-goal-completion-audit.mjs +72 -25
  249. package/scripts/build-hosted-api-key-lifecycle.mjs +292 -274
  250. package/scripts/build-hosted-customer-lifecycle.mjs +493 -476
  251. package/scripts/build-hosted-probe-worker.mjs +19 -4
  252. package/scripts/build-installer-assets.mjs +409 -389
  253. package/scripts/build-operator-evidence-starter.mjs +59 -1
  254. package/scripts/build-production-backend-env-kit.mjs +2 -0
  255. package/scripts/build-production-unblocker.mjs +4 -1
  256. package/scripts/build-proof-network-packet.mjs +213 -213
  257. package/scripts/check.mjs +25 -4
  258. package/scripts/collect-hosted-backend-live-evidence.mjs +49 -12
  259. package/scripts/install-enigma-local.mjs +18 -5
  260. package/scripts/release-audit.mjs +74 -115
  261. package/scripts/release-provenance.mjs +12 -2
  262. package/scripts/run-backend-readiness-smoke.mjs +112 -10
  263. package/scripts/run-standard-memory-benchmarks.mjs +1354 -1352
  264. package/scripts/scan-secrets.mjs +178 -0
  265. package/scripts/simulate-production-env.mjs +71 -10
  266. package/scripts/validate-hosted-backend-live.mjs +112 -1
  267. package/specs/antibody-pack-v1.schema.json +95 -0
  268. package/specs/antigen-envelope-v1.schema.json +81 -0
  269. package/specs/boundary-manifest-v1.schema.json +35 -35
  270. package/specs/capsule-v1.schema.json +55 -55
  271. package/specs/claim-boundary-manifest-v1.schema.json +22 -22
  272. package/specs/claim-ledger-v1.schema.json +291 -0
  273. package/specs/context-passport-v1.schema.json +59 -0
  274. package/specs/deletion-tombstone-v1.schema.json +26 -26
  275. package/specs/evidence-packet-v1.schema.json +177 -0
  276. package/specs/hosted-backend-live-evidence-v1.schema.json +72 -3
  277. package/specs/immune-scan-report-v1.schema.json +112 -0
  278. package/specs/lifecycle-receipt-log-v1.schema.json +67 -0
  279. package/specs/memory-atom-v1.schema.json +59 -0
  280. package/specs/memory-event-v1.schema.json +42 -42
  281. package/specs/passport-v1.schema.json +50 -50
  282. package/specs/proof-of-non-use-v1.schema.json +65 -0
  283. package/specs/quarantine-record-v1.schema.json +126 -0
  284. package/specs/receipt-v1.schema.json +61 -61
  285. package/specs/state-checkpoint-v1.schema.json +37 -37
  286. package/specs/trust-bundle-v1.schema.json +56 -56
  287. package/specs/trust-card-v1.schema.json +119 -0
  288. package/docs/proof-network-launch-plan.md +0 -421
  289. package/packages/metering/PACKAGE_CONTRACT.md +0 -20
  290. package/scripts/build-ai-orchestration-plan.mjs +0 -248
@@ -0,0 +1,509 @@
1
+ import { createHmac, randomUUID, timingSafeEqual } from 'node:crypto';
2
+ import { mkdir, open, readFile, rename } from 'node:fs/promises';
3
+ import { dirname, resolve } from 'node:path';
4
+
5
+ export const ENIGMA_PAYMENT_STORE_SCHEMA = 'enigma.private_payment_store.v1';
6
+ export const ENIGMA_PAYMENT_QUOTE_SCHEMA = 'enigma.private_credit_quote.v1';
7
+ export const SOLANA_MAINNET_USDC_MINT = 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v';
8
+ const SIGNATURE_PATTERN = /^[1-9A-HJ-NP-Za-km-z]{64,96}$/;
9
+ const ADDRESS_PATTERN = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;
10
+
11
+ function requiredString(value, name, maximum = 1024) {
12
+ if (typeof value !== 'string' || !value.trim()) throw new TypeError(`${name} is required`);
13
+ const normalized = value.trim();
14
+ if (normalized.length > maximum) throw new TypeError(`${name} exceeds ${maximum} characters`);
15
+ return normalized;
16
+ }
17
+
18
+ function positiveInteger(value, name) {
19
+ if (!Number.isSafeInteger(value) || value <= 0) throw new TypeError(`${name} must be a positive safe integer`);
20
+ return value;
21
+ }
22
+
23
+ function checkoutReturnUrl(value, name) {
24
+ const url = new URL(requiredString(value, name, 4096));
25
+ const loopback = ['127.0.0.1', 'localhost', '::1'].includes(url.hostname);
26
+ if (url.protocol !== 'https:' && !(url.protocol === 'http:' && loopback)) throw new TypeError(`${name} must use HTTPS outside localhost`);
27
+ if (url.username || url.password) throw new TypeError(`${name} must not contain credentials`);
28
+ return url.toString();
29
+ }
30
+
31
+ function boundedInteger(value, name, minimum, maximum, fallback) {
32
+ const resolved = value === undefined ? fallback : value;
33
+ if (!Number.isSafeInteger(resolved) || resolved < minimum || resolved > maximum) throw new TypeError(`${name} must be from ${minimum} through ${maximum}`);
34
+ return resolved;
35
+ }
36
+
37
+ function iso(clock) {
38
+ return new Date(clock()).toISOString();
39
+ }
40
+
41
+ function emptyState() {
42
+ return { schema: ENIGMA_PAYMENT_STORE_SCHEMA, revision: 0, quotes: {}, usedSignatures: {}, webhookEvents: {}, subscriptions: {} };
43
+ }
44
+
45
+ function publicQuote(quote) {
46
+ return {
47
+ schema: ENIGMA_PAYMENT_QUOTE_SCHEMA,
48
+ id: quote.id,
49
+ packageId: quote.packageId,
50
+ label: quote.label,
51
+ rail: quote.rail,
52
+ creditsMicrocredits: quote.creditsMicrocredits,
53
+ createdAt: quote.createdAt,
54
+ expiresAt: quote.expiresAt,
55
+ status: quote.status,
56
+ memo: quote.memo,
57
+ payment: structuredClone(quote.payment),
58
+ settlementReference: quote.creditReference || null,
59
+ };
60
+ }
61
+
62
+ function createStoreAdapter(load, save, clock) {
63
+ let queue = Promise.resolve();
64
+ const transaction = (operation) => {
65
+ const run = queue.then(async () => {
66
+ const state = await load();
67
+ const result = await operation(state);
68
+ state.revision += 1;
69
+ await save(state);
70
+ return result;
71
+ });
72
+ queue = run.catch(() => undefined);
73
+ return run;
74
+ };
75
+ const view = async (operation) => {
76
+ await queue;
77
+ return operation(await load());
78
+ };
79
+ return Object.freeze({
80
+ async insertQuote(quote) {
81
+ return transaction((state) => {
82
+ if (state.quotes[quote.id]) throw new Error('payment quote collision');
83
+ state.quotes[quote.id] = structuredClone(quote);
84
+ return structuredClone(quote);
85
+ });
86
+ },
87
+ async getQuote(id) {
88
+ return view((state) => state.quotes[id] ? structuredClone(state.quotes[id]) : null);
89
+ },
90
+ async markCheckout(id, sessionId) {
91
+ return transaction((state) => {
92
+ const quote = state.quotes[id];
93
+ if (!quote) return null;
94
+ if (quote.status === 'credited') return structuredClone(quote);
95
+ if (quote.checkoutSessionId && quote.checkoutSessionId !== sessionId) throw new Error('quote already has a different checkout session');
96
+ quote.checkoutSessionId = sessionId;
97
+ quote.status = 'checkout_created';
98
+ return structuredClone(quote);
99
+ });
100
+ },
101
+ async beginOnchain(id, signature, verified, graceMs) {
102
+ return transaction((state) => {
103
+ const quote = state.quotes[id];
104
+ if (!quote) return null;
105
+ const usedBy = state.usedSignatures[signature];
106
+ if (usedBy && usedBy !== id) throw new Error('transaction signature was already used for another quote');
107
+ if (quote.status === 'credited') {
108
+ if (quote.transactionSignature !== signature) throw new Error('quote was already settled by another transaction');
109
+ return { quote: structuredClone(quote), duplicate: true };
110
+ }
111
+ if (verified.blockTimeMs > Date.parse(quote.expiresAt) || clock() > Date.parse(quote.expiresAt) + graceMs) {
112
+ throw new Error('payment quote expired before settlement');
113
+ }
114
+ if (quote.transactionSignature && quote.transactionSignature !== signature) throw new Error('quote already has a different transaction');
115
+ state.usedSignatures[signature] = id;
116
+ quote.transactionSignature = signature;
117
+ quote.verifiedBlockTime = new Date(verified.blockTimeMs).toISOString();
118
+ quote.status = 'processing';
119
+ return { quote: structuredClone(quote), duplicate: false };
120
+ });
121
+ },
122
+ async finalizeQuote(id, reference) {
123
+ return transaction((state) => {
124
+ const quote = state.quotes[id];
125
+ if (!quote) return null;
126
+ quote.status = 'credited';
127
+ quote.creditedAt ||= iso(clock);
128
+ quote.creditReference ||= reference;
129
+ return structuredClone(quote);
130
+ });
131
+ },
132
+ async beginWebhook(eventId, type) {
133
+ return transaction((state) => {
134
+ const existing = state.webhookEvents[eventId];
135
+ if (existing?.status === 'done') return { duplicate: true, event: structuredClone(existing) };
136
+ const event = existing || { id: eventId, type, status: 'processing', receivedAt: iso(clock), completedAt: null };
137
+ if (event.type !== type) throw new Error('Stripe event identifier changed type');
138
+ state.webhookEvents[eventId] = event;
139
+ return { duplicate: false, event: structuredClone(event) };
140
+ });
141
+ },
142
+ async finishWebhook(eventId) {
143
+ return transaction((state) => {
144
+ const event = state.webhookEvents[eventId];
145
+ if (!event) return null;
146
+ event.status = 'done';
147
+ event.completedAt = iso(clock);
148
+ return structuredClone(event);
149
+ });
150
+ },
151
+ async putSubscription(subscriptionId, subscription) {
152
+ return transaction((state) => {
153
+ const existing = state.subscriptions[subscriptionId];
154
+ if (existing && existing.vaultId !== subscription.vaultId) throw new Error('subscription was already bound to another credits vault');
155
+ state.subscriptions[subscriptionId] = { ...existing, ...structuredClone(subscription), subscriptionId };
156
+ return structuredClone(state.subscriptions[subscriptionId]);
157
+ });
158
+ },
159
+ async getSubscription(subscriptionId) {
160
+ return view((state) => state.subscriptions[subscriptionId] ? structuredClone(state.subscriptions[subscriptionId]) : null);
161
+ },
162
+ });
163
+ }
164
+
165
+ export function createEphemeralPaymentStore(options = {}) {
166
+ const clock = options.clock || Date.now;
167
+ let state = emptyState();
168
+ return createStoreAdapter(async () => structuredClone(state), async (next) => { state = structuredClone(next); }, clock);
169
+ }
170
+
171
+ export function createFilePaymentStore(options = {}) {
172
+ const path = resolve(String(options.path || '.enigma/payments.json'));
173
+ const clock = options.clock || Date.now;
174
+ async function load() {
175
+ try {
176
+ const state = JSON.parse(await readFile(path, 'utf8'));
177
+ if (state?.schema !== ENIGMA_PAYMENT_STORE_SCHEMA) throw new Error('unsupported payment store schema');
178
+ return state;
179
+ } catch (error) {
180
+ if (error.code === 'ENOENT') return emptyState();
181
+ throw error;
182
+ }
183
+ }
184
+ async function save(state) {
185
+ await mkdir(dirname(path), { recursive: true, mode: 0o700 });
186
+ const temporary = `${path}.${process.pid}.${randomUUID()}.tmp`;
187
+ const handle = await open(temporary, 'wx', 0o600);
188
+ try {
189
+ await handle.writeFile(JSON.stringify(state));
190
+ await handle.sync();
191
+ } finally {
192
+ await handle.close();
193
+ }
194
+ await rename(temporary, path);
195
+ }
196
+ return createStoreAdapter(load, save, clock);
197
+ }
198
+
199
+ function normalizePackages(values = []) {
200
+ if (!Array.isArray(values)) throw new TypeError('payment packages must be an array');
201
+ const seen = new Set();
202
+ return values.map((value) => {
203
+ if (!value || typeof value !== 'object') throw new TypeError('payment package must be an object');
204
+ const id = requiredString(value.id, 'package id', 120);
205
+ if (seen.has(id)) throw new TypeError(`duplicate payment package: ${id}`);
206
+ seen.add(id);
207
+ const rails = {};
208
+ if (value.usdcAmountBaseUnits !== undefined) rails.usdcAmountBaseUnits = positiveInteger(value.usdcAmountBaseUnits, 'usdcAmountBaseUnits');
209
+ if (value.tokenBurnBaseUnits !== undefined) rails.tokenBurnBaseUnits = positiveInteger(value.tokenBurnBaseUnits, 'tokenBurnBaseUnits');
210
+ if (value.stripePriceId !== undefined) rails.stripePriceId = requiredString(value.stripePriceId, 'stripePriceId', 200);
211
+ if (!Object.keys(rails).length) throw new TypeError(`payment package ${id} has no configured rail`);
212
+ return Object.freeze({ id, label: requiredString(value.label || id, 'package label', 120), creditsMicrocredits: positiveInteger(value.creditsMicrocredits, 'creditsMicrocredits'), ...rails });
213
+ });
214
+ }
215
+
216
+ function accountSigners(transaction) {
217
+ return new Set((transaction?.transaction?.message?.accountKeys || [])
218
+ .filter((key) => key?.signer === true)
219
+ .map((key) => String(key.pubkey)));
220
+ }
221
+
222
+ function allInstructions(transaction) {
223
+ const top = transaction?.transaction?.message?.instructions || [];
224
+ const inner = (transaction?.meta?.innerInstructions || []).flatMap((entry) => entry.instructions || []);
225
+ return [...top, ...inner];
226
+ }
227
+
228
+ function instructionAmount(info) {
229
+ const value = info?.tokenAmount?.amount ?? info?.amount;
230
+ return typeof value === 'string' && /^\d+$/.test(value) ? value : String(value ?? '');
231
+ }
232
+
233
+ function hasMemo(transaction, memo) {
234
+ return allInstructions(transaction).some((instruction) => instruction?.program === 'spl-memo' && String(instruction.parsed) === memo);
235
+ }
236
+
237
+ export function createSolanaPaymentVerifier(options = {}) {
238
+ const rpcUrl = new URL(requiredString(options.rpcUrl, 'Solana RPC URL', 4096));
239
+ if (rpcUrl.protocol !== 'https:' && !(rpcUrl.protocol === 'http:' && ['127.0.0.1', 'localhost'].includes(rpcUrl.hostname))) {
240
+ throw new TypeError('Solana RPC must use HTTPS outside localhost');
241
+ }
242
+ const timeoutMs = boundedInteger(options.timeoutMs, 'timeoutMs', 1000, 120000, 20000);
243
+ return async function verifySolanaPayment({ signature, quote }) {
244
+ if (!SIGNATURE_PATTERN.test(signature)) throw new TypeError('Solana signature is invalid');
245
+ const controller = new AbortController();
246
+ const timer = setTimeout(() => controller.abort(new Error('Solana verification timed out')), timeoutMs);
247
+ try {
248
+ const response = await fetch(rpcUrl, {
249
+ method: 'POST',
250
+ headers: { 'Content-Type': 'application/json' },
251
+ body: JSON.stringify({ jsonrpc: '2.0', id: quote.id, method: 'getTransaction', params: [signature, { commitment: 'finalized', encoding: 'jsonParsed', maxSupportedTransactionVersion: 0 }] }),
252
+ signal: controller.signal,
253
+ });
254
+ const payload = await response.json();
255
+ if (!response.ok || payload.error || !payload.result) throw new Error('finalized Solana transaction was not found');
256
+ const transaction = payload.result;
257
+ if (transaction.meta?.err !== null) throw new Error('Solana transaction failed');
258
+ if (!Number.isInteger(transaction.blockTime)) throw new Error('Solana transaction has no block time');
259
+ if (!hasMemo(transaction, quote.memo)) throw new Error('Solana transaction does not contain the quote memo');
260
+ const signers = accountSigners(transaction);
261
+ const payment = quote.payment;
262
+ if (quote.rail === 'usdc') {
263
+ const match = allInstructions(transaction).find((instruction) => {
264
+ const info = instruction?.parsed?.info;
265
+ return instruction?.program === 'spl-token'
266
+ && ['transfer', 'transferChecked'].includes(instruction?.parsed?.type)
267
+ && info?.destination === payment.destinationTokenAccount
268
+ && info?.mint === payment.mint
269
+ && instructionAmount(info) === String(payment.amountBaseUnits)
270
+ && signers.has(String(info?.authority));
271
+ });
272
+ if (!match) throw new Error('USDC transfer does not match quote destination, mint, amount, and signer');
273
+ } else if (quote.rail === 'token_burn') {
274
+ const match = allInstructions(transaction).find((instruction) => {
275
+ const info = instruction?.parsed?.info;
276
+ return instruction?.program === 'spl-token'
277
+ && ['burn', 'burnChecked'].includes(instruction?.parsed?.type)
278
+ && info?.mint === payment.mint
279
+ && instructionAmount(info) === String(payment.amountBaseUnits)
280
+ && signers.has(String(info?.authority));
281
+ });
282
+ if (!match) throw new Error('token burn does not match quote mint, amount, and signer');
283
+ } else throw new Error('quote is not an on-chain rail');
284
+ return { signature, blockTimeMs: transaction.blockTime * 1000, slot: transaction.slot };
285
+ } finally {
286
+ clearTimeout(timer);
287
+ }
288
+ };
289
+ }
290
+
291
+ function stripeSignatureParts(value) {
292
+ const result = { timestamp: null, signatures: [] };
293
+ for (const part of String(value || '').split(',')) {
294
+ const [key, candidate] = part.split('=', 2);
295
+ if (key === 't') result.timestamp = Number(candidate);
296
+ if (key === 'v1') result.signatures.push(candidate);
297
+ }
298
+ return result;
299
+ }
300
+
301
+ export function createStripeSubscriptionProvider(options = {}) {
302
+ const apiKey = requiredString(options.apiKey, 'Stripe API key', 4096);
303
+ const webhookSecret = requiredString(options.webhookSecret, 'Stripe webhook secret', 4096);
304
+ const baseUrl = new URL(options.baseUrl || 'https://api.stripe.com/v1/');
305
+ if (baseUrl.protocol !== 'https:' && !(baseUrl.protocol === 'http:' && ['127.0.0.1', 'localhost'].includes(baseUrl.hostname))) throw new TypeError('Stripe API must use HTTPS outside localhost');
306
+ const toleranceSeconds = boundedInteger(options.toleranceSeconds, 'toleranceSeconds', 30, 900, 300);
307
+
308
+ async function request(path, init = {}) {
309
+ const response = await fetch(new URL(path, baseUrl), {
310
+ ...init,
311
+ headers: { Authorization: `Bearer ${apiKey}`, ...(init.body ? { 'Content-Type': 'application/x-www-form-urlencoded' } : {}), ...(init.headers || {}) },
312
+ });
313
+ const payload = await response.json();
314
+ if (!response.ok) throw new Error(`Stripe request failed with HTTP ${response.status}`);
315
+ return payload;
316
+ }
317
+
318
+ return Object.freeze({
319
+ async createCheckout({ quote, successUrl, cancelUrl }) {
320
+ const form = new URLSearchParams({
321
+ mode: 'subscription',
322
+ 'line_items[0][price]': quote.payment.stripePriceId,
323
+ 'line_items[0][quantity]': '1',
324
+ client_reference_id: quote.id,
325
+ success_url: successUrl,
326
+ cancel_url: cancelUrl,
327
+ 'metadata[enigma_quote_id]': quote.id,
328
+ 'subscription_data[metadata][enigma_quote_id]': quote.id,
329
+ });
330
+ const session = await request('checkout/sessions', { method: 'POST', headers: { 'Idempotency-Key': quote.id }, body: form });
331
+ if (!session.id || !session.url) throw new Error('Stripe returned no checkout session URL');
332
+ return { id: session.id, url: session.url };
333
+ },
334
+ async retrieveSubscription(id) {
335
+ return request(`subscriptions/${encodeURIComponent(requiredString(id, 'subscription id'))}`);
336
+ },
337
+ verifyWebhook(rawBody, signatureHeader, now = Date.now()) {
338
+ if (!Buffer.isBuffer(rawBody)) throw new TypeError('Stripe webhook body must be a Buffer');
339
+ const parts = stripeSignatureParts(signatureHeader);
340
+ if (!Number.isInteger(parts.timestamp) || Math.abs(Math.floor(now / 1000) - parts.timestamp) > toleranceSeconds) throw new Error('Stripe webhook timestamp is outside tolerance');
341
+ const expected = createHmac('sha256', webhookSecret).update(`${parts.timestamp}.`).update(rawBody).digest('hex');
342
+ const valid = parts.signatures.some((candidate) => {
343
+ const actualBytes = Buffer.from(String(candidate));
344
+ const expectedBytes = Buffer.from(expected);
345
+ return actualBytes.length === expectedBytes.length && timingSafeEqual(actualBytes, expectedBytes);
346
+ });
347
+ if (!valid) throw new Error('Stripe webhook signature is invalid');
348
+ const event = JSON.parse(rawBody.toString('utf8'));
349
+ if (!event?.id || !event?.type || !event?.data?.object) throw new Error('Stripe webhook event is malformed');
350
+ return event;
351
+ },
352
+ });
353
+ }
354
+
355
+ function stripeSubscriptionId(object) {
356
+ return typeof object?.subscription === 'string'
357
+ ? object.subscription
358
+ : typeof object?.parent?.subscription_details?.subscription === 'string'
359
+ ? object.parent.subscription_details.subscription
360
+ : null;
361
+ }
362
+
363
+ function stripePriceIds(invoice) {
364
+ return new Set((invoice?.lines?.data || []).map((line) => line?.pricing?.price_details?.price || line?.price?.id).filter(Boolean));
365
+ }
366
+
367
+ export function createCreditPaymentService(options = {}) {
368
+ const store = options.store;
369
+ const credits = options.credits;
370
+ if (!store || typeof store.insertQuote !== 'function') throw new TypeError('payment store is required');
371
+ if (!credits || typeof credits.authenticate !== 'function' || typeof credits.credit !== 'function') throw new TypeError('private credits service is required');
372
+ const clock = options.clock || Date.now;
373
+ const packages = normalizePackages(options.packages || []);
374
+ const packagesById = new Map(packages.map((entry) => [entry.id, entry]));
375
+ const usdcMint = options.usdcMint || SOLANA_MAINNET_USDC_MINT;
376
+ const usdcDestinationTokenAccount = options.usdcDestinationTokenAccount || null;
377
+ const tokenBurnMint = options.tokenBurnMint || null;
378
+ if (usdcDestinationTokenAccount && !ADDRESS_PATTERN.test(usdcDestinationTokenAccount)) throw new TypeError('USDC destination token account is invalid');
379
+ if (tokenBurnMint && !ADDRESS_PATTERN.test(tokenBurnMint)) throw new TypeError('token burn mint is invalid');
380
+ const verifySolanaPayment = options.verifySolanaPayment || null;
381
+ const stripe = options.stripe || null;
382
+ const quoteTtlMs = boundedInteger(options.quoteTtlSeconds, 'quoteTtlSeconds', 60, 3600, 600) * 1000;
383
+ const settlementGraceMs = boundedInteger(options.settlementGraceSeconds, 'settlementGraceSeconds', 60, 86400, 3600) * 1000;
384
+
385
+ function availableRails(entry) {
386
+ return [
387
+ ...(entry.usdcAmountBaseUnits && usdcDestinationTokenAccount && verifySolanaPayment ? ['usdc'] : []),
388
+ ...(entry.tokenBurnBaseUnits && tokenBurnMint && verifySolanaPayment ? ['token_burn'] : []),
389
+ ...(entry.stripePriceId && stripe ? ['subscription'] : []),
390
+ ];
391
+ }
392
+
393
+ async function authenticatedVault(accessToken) {
394
+ const principal = await credits.authenticate(accessToken);
395
+ if (!principal) throw new Error('private credits access token is invalid or expired');
396
+ return principal;
397
+ }
398
+
399
+ return Object.freeze({
400
+ listPackages() {
401
+ return packages.map((entry) => ({ id: entry.id, label: entry.label, creditsMicrocredits: entry.creditsMicrocredits, rails: availableRails(entry) }));
402
+ },
403
+ async quote(accessToken, input = {}) {
404
+ const principal = await authenticatedVault(accessToken);
405
+ const entry = packagesById.get(requiredString(input.packageId, 'packageId', 120));
406
+ if (!entry) throw new Error('payment package not found');
407
+ const rail = requiredString(input.rail, 'rail', 32);
408
+ if (!availableRails(entry).includes(rail)) throw new Error('payment rail is not configured for this package');
409
+ const id = randomUUID();
410
+ const createdAt = clock();
411
+ const memo = `enigma:${id}`;
412
+ const payment = rail === 'usdc'
413
+ ? { network: 'solana-mainnet', mint: usdcMint, destinationTokenAccount: usdcDestinationTokenAccount, amountBaseUnits: entry.usdcAmountBaseUnits }
414
+ : rail === 'token_burn'
415
+ ? { network: 'solana-mainnet', mint: tokenBurnMint, amountBaseUnits: entry.tokenBurnBaseUnits }
416
+ : { provider: 'stripe', mode: 'subscription', stripePriceId: entry.stripePriceId };
417
+ const stored = await store.insertQuote({
418
+ id, vaultId: principal.vaultId, packageId: entry.id, label: entry.label, rail,
419
+ creditsMicrocredits: entry.creditsMicrocredits, createdAt: new Date(createdAt).toISOString(),
420
+ expiresAt: new Date(createdAt + quoteTtlMs).toISOString(), status: 'quoted', memo, payment,
421
+ transactionSignature: null, checkoutSessionId: null, creditedAt: null, creditReference: null,
422
+ });
423
+ return publicQuote(stored);
424
+ },
425
+ async settleOnchain(accessToken, input = {}) {
426
+ const principal = await authenticatedVault(accessToken);
427
+ const quote = await store.getQuote(requiredString(input.quoteId, 'quoteId'));
428
+ if (!quote || quote.vaultId !== principal.vaultId) throw new Error('payment quote not found');
429
+ if (!['usdc', 'token_burn'].includes(quote.rail) || !verifySolanaPayment) throw new Error('quote is not payable through an on-chain rail');
430
+ const signature = requiredString(input.signature, 'signature', 128);
431
+ const verified = await verifySolanaPayment({ signature, quote: publicQuote(quote) });
432
+ const begun = await store.beginOnchain(quote.id, signature, verified, settlementGraceMs);
433
+ if (!begun) throw new Error('payment quote not found');
434
+ const credited = await credits.credit(quote.vaultId, {
435
+ amountMicrocredits: quote.creditsMicrocredits,
436
+ idempotencyKey: `solana:${signature}`,
437
+ reference: `${quote.rail}:${signature}`,
438
+ });
439
+ const finalized = await store.finalizeQuote(quote.id, `${quote.rail}:${signature}`);
440
+ return { quote: publicQuote(finalized), vault: credited.vault, duplicate: begun.duplicate || !credited.inserted };
441
+ },
442
+ async createSubscriptionCheckout(accessToken, input = {}) {
443
+ const principal = await authenticatedVault(accessToken);
444
+ const quote = await store.getQuote(requiredString(input.quoteId, 'quoteId'));
445
+ if (!quote || quote.vaultId !== principal.vaultId) throw new Error('payment quote not found');
446
+ if (quote.rail !== 'subscription' || !stripe) throw new Error('quote is not a Stripe subscription quote');
447
+ if (Date.parse(quote.expiresAt) <= clock()) throw new Error('payment quote expired');
448
+ const checkout = await stripe.createCheckout({
449
+ quote: publicQuote(quote),
450
+ successUrl: checkoutReturnUrl(input.successUrl, 'successUrl'),
451
+ cancelUrl: checkoutReturnUrl(input.cancelUrl, 'cancelUrl'),
452
+ });
453
+ await store.markCheckout(quote.id, checkout.id);
454
+ return { checkoutUrl: checkout.url, sessionId: checkout.id, quoteId: quote.id };
455
+ },
456
+ async processStripeWebhook(event) {
457
+ if (!stripe) throw new Error('Stripe subscription provider is not configured');
458
+ const claim = await store.beginWebhook(requiredString(event.id, 'Stripe event id'), requiredString(event.type, 'Stripe event type'));
459
+ if (claim.duplicate) return { duplicate: true, type: event.type };
460
+ const object = event.data.object;
461
+ if (event.type === 'checkout.session.completed') {
462
+ const quoteId = object.metadata?.enigma_quote_id || object.client_reference_id;
463
+ const subscriptionId = stripeSubscriptionId(object);
464
+ const quote = quoteId ? await store.getQuote(quoteId) : null;
465
+ if (!quote || quote.rail !== 'subscription' || !subscriptionId) throw new Error('Stripe checkout event is not bound to an Enigma subscription quote');
466
+ await store.putSubscription(subscriptionId, {
467
+ quoteId: quote.id,
468
+ vaultId: quote.vaultId,
469
+ creditsMicrocredits: quote.creditsMicrocredits,
470
+ stripePriceId: quote.payment.stripePriceId,
471
+ status: 'active',
472
+ createdAt: iso(clock),
473
+ });
474
+ } else if (event.type === 'invoice.paid') {
475
+ const subscriptionId = stripeSubscriptionId(object);
476
+ if (!subscriptionId || !positiveInteger(object.amount_paid, 'Stripe invoice amount_paid')) throw new Error('paid invoice has no funded subscription');
477
+ let subscription = await store.getSubscription(subscriptionId);
478
+ if (!subscription) {
479
+ const remote = await stripe.retrieveSubscription(subscriptionId);
480
+ const quoteId = remote.metadata?.enigma_quote_id;
481
+ const quote = quoteId ? await store.getQuote(quoteId) : null;
482
+ if (!quote || quote.rail !== 'subscription') throw new Error('Stripe subscription is not bound to an Enigma quote');
483
+ subscription = await store.putSubscription(subscriptionId, {
484
+ quoteId: quote.id,
485
+ vaultId: quote.vaultId,
486
+ creditsMicrocredits: quote.creditsMicrocredits,
487
+ stripePriceId: quote.payment.stripePriceId,
488
+ status: 'active',
489
+ createdAt: iso(clock),
490
+ });
491
+ }
492
+ if (!stripePriceIds(object).has(subscription.stripePriceId)) throw new Error('Stripe invoice price does not match subscription package');
493
+ await credits.credit(subscription.vaultId, {
494
+ amountMicrocredits: subscription.creditsMicrocredits,
495
+ idempotencyKey: `stripe-invoice:${object.id}`,
496
+ reference: `stripe:${object.id}`,
497
+ });
498
+ const quote = await store.getQuote(subscription.quoteId);
499
+ if (quote && quote.status !== 'credited') await store.finalizeQuote(quote.id, `stripe:${object.id}`);
500
+ } else if (event.type === 'customer.subscription.deleted') {
501
+ const subscriptionId = object.id;
502
+ const existing = await store.getSubscription(subscriptionId);
503
+ if (existing) await store.putSubscription(subscriptionId, { ...existing, status: 'cancelled', cancelledAt: iso(clock) });
504
+ }
505
+ await store.finishWebhook(event.id);
506
+ return { duplicate: false, type: event.type };
507
+ },
508
+ });
509
+ }