zklicensing 0.1.0-beta.2 → 0.1.0-beta.21

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 (317) hide show
  1. package/README.md +417 -201
  2. package/build/agent.d.ts +371 -0
  3. package/build/agent.d.ts.map +1 -0
  4. package/build/agent.js +553 -0
  5. package/build/agent.js.map +1 -0
  6. package/build/agentActions.d.ts +841 -0
  7. package/build/agentActions.d.ts.map +1 -0
  8. package/build/agentActions.js +581 -0
  9. package/build/agentActions.js.map +1 -0
  10. package/build/agentRecord.d.ts +83 -0
  11. package/build/agentRecord.d.ts.map +1 -0
  12. package/build/agentRecord.js +69 -0
  13. package/build/agentRecord.js.map +1 -0
  14. package/build/agentVendor.d.ts +220 -0
  15. package/build/agentVendor.d.ts.map +1 -0
  16. package/build/agentVendor.js +306 -0
  17. package/build/agentVendor.js.map +1 -0
  18. package/build/appsLookup.d.ts +14 -0
  19. package/build/appsLookup.d.ts.map +1 -0
  20. package/build/appsLookup.js +12 -0
  21. package/build/appsLookup.js.map +1 -0
  22. package/build/archiveBootstrap.d.ts +31 -5
  23. package/build/archiveBootstrap.d.ts.map +1 -0
  24. package/build/archiveBootstrap.js +140 -47
  25. package/build/archiveBootstrap.js.map +1 -0
  26. package/build/atomicJson.d.ts +5 -0
  27. package/build/atomicJson.d.ts.map +1 -0
  28. package/build/atomicJson.js +55 -0
  29. package/build/atomicJson.js.map +1 -0
  30. package/build/bindClient.d.ts +85 -0
  31. package/build/bindClient.d.ts.map +1 -0
  32. package/build/bindClient.js +187 -0
  33. package/build/bindClient.js.map +1 -0
  34. package/build/buyClient.d.ts +27 -4
  35. package/build/buyClient.d.ts.map +1 -0
  36. package/build/buyClient.js +72 -26
  37. package/build/buyClient.js.map +1 -0
  38. package/build/chainHead.d.ts +61 -0
  39. package/build/chainHead.d.ts.map +1 -0
  40. package/build/chainHead.js +88 -0
  41. package/build/chainHead.js.map +1 -0
  42. package/build/compactMerkleMap.d.ts +72 -0
  43. package/build/compactMerkleMap.d.ts.map +1 -0
  44. package/build/compactMerkleMap.js +224 -0
  45. package/build/compactMerkleMap.js.map +1 -0
  46. package/build/contractInterface.d.ts +191 -51
  47. package/build/contractInterface.d.ts.map +1 -0
  48. package/build/contractInterface.js +234 -43
  49. package/build/contractInterface.js.map +1 -0
  50. package/build/contractTypes.d.ts +9 -0
  51. package/build/contractTypes.d.ts.map +1 -0
  52. package/build/contractTypes.js +28 -0
  53. package/build/contractTypes.js.map +1 -0
  54. package/build/countryStats.d.ts +12 -3
  55. package/build/countryStats.d.ts.map +1 -0
  56. package/build/countryStats.js +87 -23
  57. package/build/countryStats.js.map +1 -0
  58. package/build/deployClient.d.ts +138 -24
  59. package/build/deployClient.d.ts.map +1 -0
  60. package/build/deployClient.js +91 -31
  61. package/build/deployClient.js.map +1 -0
  62. package/build/deviceIdentity.d.ts +68 -0
  63. package/build/deviceIdentity.d.ts.map +1 -0
  64. package/build/deviceIdentity.js +209 -0
  65. package/build/deviceIdentity.js.map +1 -0
  66. package/build/freezeClient.d.ts +26 -6
  67. package/build/freezeClient.d.ts.map +1 -0
  68. package/build/freezeClient.js +30 -6
  69. package/build/freezeClient.js.map +1 -0
  70. package/build/generationClient.d.ts +6 -3
  71. package/build/generationClient.d.ts.map +1 -0
  72. package/build/generationClient.js +25 -38
  73. package/build/generationClient.js.map +1 -0
  74. package/build/holderSecret.d.ts +70 -0
  75. package/build/holderSecret.d.ts.map +1 -0
  76. package/build/holderSecret.js +76 -0
  77. package/build/holderSecret.js.map +1 -0
  78. package/build/index.d.ts +38 -38
  79. package/build/index.d.ts.map +1 -0
  80. package/build/index.js +97 -121
  81. package/build/index.js.map +1 -0
  82. package/build/keysClient.d.ts +142 -0
  83. package/build/keysClient.d.ts.map +1 -0
  84. package/build/keysClient.js +109 -0
  85. package/build/keysClient.js.map +1 -0
  86. package/build/licenseLeaf.d.ts +76 -0
  87. package/build/licenseLeaf.d.ts.map +1 -0
  88. package/build/licenseLeaf.js +189 -0
  89. package/build/licenseLeaf.js.map +1 -0
  90. package/build/licenseRecord.d.ts +68 -5
  91. package/build/licenseRecord.d.ts.map +1 -0
  92. package/build/licenseRecord.js +26 -1
  93. package/build/licenseRecord.js.map +1 -0
  94. package/build/licenseStore.d.ts +81 -4
  95. package/build/licenseStore.d.ts.map +1 -0
  96. package/build/licenseStore.js +400 -30
  97. package/build/licenseStore.js.map +1 -0
  98. package/build/licenseTrustless.d.ts +43 -0
  99. package/build/licenseTrustless.d.ts.map +1 -0
  100. package/build/licenseTrustless.js +180 -0
  101. package/build/licenseTrustless.js.map +1 -0
  102. package/build/manifestVerify.d.ts +29 -1
  103. package/build/manifestVerify.d.ts.map +1 -0
  104. package/build/manifestVerify.js +46 -4
  105. package/build/manifestVerify.js.map +1 -0
  106. package/build/minaPriceHistory.d.ts +9 -0
  107. package/build/minaPriceHistory.d.ts.map +1 -0
  108. package/build/minaPriceHistory.js +152 -0
  109. package/build/minaPriceHistory.js.map +1 -0
  110. package/build/onChainRoot.d.ts +60 -0
  111. package/build/onChainRoot.d.ts.map +1 -0
  112. package/build/onChainRoot.js +74 -0
  113. package/build/onChainRoot.js.map +1 -0
  114. package/build/ownershipLicenseHash.d.ts +25 -0
  115. package/build/ownershipLicenseHash.d.ts.map +1 -0
  116. package/build/ownershipLicenseHash.js +29 -0
  117. package/build/ownershipLicenseHash.js.map +1 -0
  118. package/build/ownershipSignature.d.ts +153 -0
  119. package/build/ownershipSignature.d.ts.map +1 -0
  120. package/build/ownershipSignature.js +297 -0
  121. package/build/ownershipSignature.js.map +1 -0
  122. package/build/permitCheckFlow.d.ts +155 -0
  123. package/build/permitCheckFlow.d.ts.map +1 -0
  124. package/build/permitCheckFlow.js +196 -0
  125. package/build/permitCheckFlow.js.map +1 -0
  126. package/build/permitDescribe.d.ts +124 -0
  127. package/build/permitDescribe.d.ts.map +1 -0
  128. package/build/permitDescribe.js +324 -0
  129. package/build/permitDescribe.js.map +1 -0
  130. package/build/permitInterface.d.ts +85 -0
  131. package/build/permitInterface.d.ts.map +1 -0
  132. package/build/permitInterface.js +121 -0
  133. package/build/permitInterface.js.map +1 -0
  134. package/build/permitMessages.d.ts +313 -0
  135. package/build/permitMessages.d.ts.map +1 -0
  136. package/build/permitMessages.js +462 -0
  137. package/build/permitMessages.js.map +1 -0
  138. package/build/permitProof.d.ts +108 -0
  139. package/build/permitProof.d.ts.map +1 -0
  140. package/build/permitProof.js +202 -0
  141. package/build/permitProof.js.map +1 -0
  142. package/build/permitRegistryRebuild.d.ts +166 -0
  143. package/build/permitRegistryRebuild.d.ts.map +1 -0
  144. package/build/permitRegistryRebuild.js +347 -0
  145. package/build/permitRegistryRebuild.js.map +1 -0
  146. package/build/permitTree.d.ts +88 -0
  147. package/build/permitTree.d.ts.map +1 -0
  148. package/build/permitTree.js +230 -0
  149. package/build/permitTree.js.map +1 -0
  150. package/build/permitsClient.d.ts +680 -0
  151. package/build/permitsClient.d.ts.map +1 -0
  152. package/build/permitsClient.js +597 -0
  153. package/build/permitsClient.js.map +1 -0
  154. package/build/proveErrors.d.ts +50 -0
  155. package/build/proveErrors.d.ts.map +1 -0
  156. package/build/proveErrors.js +76 -0
  157. package/build/proveErrors.js.map +1 -0
  158. package/build/receipt.d.ts +95 -0
  159. package/build/receipt.d.ts.map +1 -0
  160. package/build/receipt.js +84 -0
  161. package/build/receipt.js.map +1 -0
  162. package/build/refundClient.d.ts +7 -12
  163. package/build/refundClient.d.ts.map +1 -0
  164. package/build/refundClient.js +52 -21
  165. package/build/refundClient.js.map +1 -0
  166. package/build/registryTrustVerify.d.ts +78 -0
  167. package/build/registryTrustVerify.d.ts.map +1 -0
  168. package/build/registryTrustVerify.js +129 -0
  169. package/build/registryTrustVerify.js.map +1 -0
  170. package/build/releaseClient.d.ts +26 -0
  171. package/build/releaseClient.d.ts.map +1 -0
  172. package/build/releaseClient.js +65 -0
  173. package/build/releaseClient.js.map +1 -0
  174. package/build/renewClient.d.ts +28 -2
  175. package/build/renewClient.d.ts.map +1 -0
  176. package/build/renewClient.js +31 -27
  177. package/build/renewClient.js.map +1 -0
  178. package/build/seatName.d.ts +8 -0
  179. package/build/seatName.d.ts.map +1 -0
  180. package/build/seatName.js +136 -0
  181. package/build/seatName.js.map +1 -0
  182. package/build/slotConstants.d.ts +15 -0
  183. package/build/slotConstants.d.ts.map +1 -0
  184. package/build/slotConstants.js +17 -0
  185. package/build/slotConstants.js.map +1 -0
  186. package/build/statsClient.d.ts +61 -7
  187. package/build/statsClient.d.ts.map +1 -0
  188. package/build/statsClient.js +44 -5
  189. package/build/statsClient.js.map +1 -0
  190. package/build/successorChain.d.ts +35 -0
  191. package/build/successorChain.d.ts.map +1 -0
  192. package/build/successorChain.js +69 -0
  193. package/build/successorChain.js.map +1 -0
  194. package/build/taxId.d.ts +44 -0
  195. package/build/taxId.d.ts.map +1 -0
  196. package/build/taxId.js +212 -0
  197. package/build/taxId.js.map +1 -0
  198. package/build/trustAnchorVerify.d.ts +49 -0
  199. package/build/trustAnchorVerify.d.ts.map +1 -0
  200. package/build/trustAnchorVerify.js +432 -0
  201. package/build/trustAnchorVerify.js.map +1 -0
  202. package/build/trustAnchorVerifyCli.d.ts +45 -0
  203. package/build/trustAnchorVerifyCli.d.ts.map +1 -0
  204. package/build/trustAnchorVerifyCli.js +235 -0
  205. package/build/trustAnchorVerifyCli.js.map +1 -0
  206. package/build/txBroadcastClient.d.ts +26 -0
  207. package/build/txBroadcastClient.d.ts.map +1 -0
  208. package/build/txBroadcastClient.js +20 -0
  209. package/build/txBroadcastClient.js.map +1 -0
  210. package/build/unbindClient.d.ts +77 -0
  211. package/build/unbindClient.d.ts.map +1 -0
  212. package/build/unbindClient.js +169 -0
  213. package/build/unbindClient.js.map +1 -0
  214. package/build/units.d.ts +28 -0
  215. package/build/units.d.ts.map +1 -0
  216. package/build/units.js +65 -0
  217. package/build/units.js.map +1 -0
  218. package/build/vatRates.d.ts +54 -0
  219. package/build/vatRates.d.ts.map +1 -0
  220. package/build/vatRates.js +82 -0
  221. package/build/vatRates.js.map +1 -0
  222. package/build/vatRatesData.d.ts +5 -0
  223. package/build/vatRatesData.d.ts.map +1 -0
  224. package/build/vatRatesData.js +42 -0
  225. package/build/vatRatesData.js.map +1 -0
  226. package/build/vendorAccountClient.d.ts +66 -0
  227. package/build/vendorAccountClient.d.ts.map +1 -0
  228. package/build/vendorAccountClient.js +47 -0
  229. package/build/vendorAccountClient.js.map +1 -0
  230. package/build/verifyCore.d.ts +4 -1
  231. package/build/verifyCore.d.ts.map +1 -0
  232. package/build/verifyCore.js +21 -10
  233. package/build/verifyCore.js.map +1 -0
  234. package/build/verifyOffline.d.ts +85 -0
  235. package/build/verifyOffline.d.ts.map +1 -0
  236. package/build/verifyOffline.js +139 -0
  237. package/build/verifyOffline.js.map +1 -0
  238. package/build/verifyService.d.ts.map +1 -0
  239. package/build/verifyService.js +571 -501
  240. package/build/verifyService.js.map +1 -0
  241. package/build/verifyWithAncestors.d.ts +19 -0
  242. package/build/verifyWithAncestors.d.ts.map +1 -0
  243. package/build/verifyWithAncestors.js +127 -0
  244. package/build/verifyWithAncestors.js.map +1 -0
  245. package/build/zipToState.d.ts +2 -0
  246. package/build/zipToState.d.ts.map +1 -0
  247. package/build/zipToState.js +94 -0
  248. package/build/zipToState.js.map +1 -0
  249. package/package.json +141 -16
  250. package/src/agent.ts +682 -0
  251. package/src/agentActions.ts +637 -0
  252. package/src/agentRecord.ts +121 -0
  253. package/src/agentVendor.ts +356 -0
  254. package/src/appsLookup.ts +48 -0
  255. package/src/archiveBootstrap.ts +386 -0
  256. package/src/atomicJson.ts +53 -0
  257. package/src/bindClient.ts +297 -0
  258. package/src/buyClient.ts +344 -0
  259. package/src/chainHead.ts +106 -0
  260. package/src/compactMerkleMap.ts +222 -0
  261. package/src/contractInterface.ts +465 -0
  262. package/src/contractTypes.ts +31 -0
  263. package/src/countryStats.ts +200 -0
  264. package/src/deployClient.ts +517 -0
  265. package/src/deviceIdentity.ts +249 -0
  266. package/src/freezeClient.ts +162 -0
  267. package/src/generationClient.ts +145 -0
  268. package/src/holderSecret.ts +91 -0
  269. package/src/index.ts +362 -0
  270. package/src/keysClient.ts +135 -0
  271. package/src/licenseLeaf.ts +221 -0
  272. package/src/licenseRecord.ts +190 -0
  273. package/src/licenseStore.ts +767 -0
  274. package/src/licenseTrustless.ts +207 -0
  275. package/src/manifestVerify.ts +164 -0
  276. package/src/minaPriceHistory.ts +166 -0
  277. package/src/onChainRoot.ts +90 -0
  278. package/src/ownershipLicenseHash.ts +31 -0
  279. package/src/ownershipSignature.ts +421 -0
  280. package/src/permitCheckFlow.ts +227 -0
  281. package/src/permitDescribe.ts +362 -0
  282. package/src/permitInterface.ts +146 -0
  283. package/src/permitMessages.ts +572 -0
  284. package/src/permitProof.ts +230 -0
  285. package/src/permitRegistryRebuild.ts +423 -0
  286. package/src/permitTree.ts +232 -0
  287. package/src/permitsClient.ts +746 -0
  288. package/src/proveErrors.ts +98 -0
  289. package/src/receipt.ts +156 -0
  290. package/src/refundClient.ts +177 -0
  291. package/src/registryTrustVerify.ts +192 -0
  292. package/src/releaseClient.ts +101 -0
  293. package/src/renewClient.ts +234 -0
  294. package/src/seatName.ts +165 -0
  295. package/src/slotConstants.ts +18 -0
  296. package/src/statsClient.ts +249 -0
  297. package/src/successorChain.ts +87 -0
  298. package/src/taxId.ts +227 -0
  299. package/src/trustAnchorVerify.ts +550 -0
  300. package/src/trustAnchorVerifyCli.ts +234 -0
  301. package/src/txBroadcastClient.ts +48 -0
  302. package/src/unbindClient.ts +233 -0
  303. package/src/units.ts +74 -0
  304. package/src/vatRates.ts +100 -0
  305. package/src/vatRatesData.ts +43 -0
  306. package/src/vendorAccountClient.ts +120 -0
  307. package/src/verifyCore.ts +100 -0
  308. package/src/verifyOffline.ts +212 -0
  309. package/src/verifyService.ts +982 -0
  310. package/src/verifyWithAncestors.ts +163 -0
  311. package/src/zipToState.ts +94 -0
  312. package/build/licenseProof.d.ts +0 -26
  313. package/build/licenseProof.js +0 -41
  314. package/build/migrateClient.d.ts +0 -43
  315. package/build/migrateClient.js +0 -47
  316. package/build/ownership.d.ts +0 -34
  317. package/build/ownership.js +0 -147
@@ -0,0 +1,101 @@
1
+ // Copyright (c) 2025-2026 zkLicensing project developers
2
+ // SPDX-License-Identifier: BUSL-1.1
3
+
4
+ /**
5
+ * releaseClient.ts
6
+ *
7
+ * Vendor-signed release path — call POST /prove/release-vendor to get an
8
+ * unsigned proved tx, then broadcast it through the vendor's wallet
9
+ * (`provider.sendTransaction`). The vendor pays the transaction fee and
10
+ * signs the fee-payer AU themselves — the licensing service never needs
11
+ * its own feepayer key for this path.
12
+ *
13
+ * `releaseFunds` is permissionless on-chain, so anyone willing to pay gas
14
+ * may call this. There is no server-side identity check — the client's
15
+ * wallet holds full authority via the fee-payer signature.
16
+ *
17
+ * On-chain settlement is observed by the licensing service's event-sync
18
+ * loop. That sweep runs every 30 minutes, so callers who want the
19
+ * `released` flag to flip within seconds of inclusion should call
20
+ * `confirmVendorRelease` with the wallet's returned tx hash — the service
21
+ * then watches bestChain and mirrors the fundsReleased event optimistically.
22
+ * The event-sync sweep is still authoritative; the confirm/watch path is a
23
+ * latency shortcut and is idempotent.
24
+ */
25
+
26
+ import { readErrorBody, keeperError } from './proveErrors.js';
27
+
28
+ export type ProveVendorReleaseRequest = {
29
+ keeperUrl: string;
30
+ licenseHash: string;
31
+ zkAppAddress: string;
32
+ senderPublicKey: string;
33
+ // Optional — the service will resolve the network from the zkAppAddress
34
+ // when omitted. Set explicitly when the caller already knows which
35
+ // network the deployment lives on.
36
+ network?: 'devnet' | 'mainnet';
37
+ fetcher?: typeof fetch;
38
+ signal?: AbortSignal;
39
+ };
40
+
41
+ export type ProveVendorReleaseResponse = {
42
+ // Serialized Mina.transaction JSON, proved but with an unsigned fee-payer
43
+ // AU. Pass verbatim to `provider.sendTransaction({ transaction })`.
44
+ provenTxJson: string;
45
+ };
46
+
47
+ function baseUrl(url: string): string {
48
+ return url.replace(/\/+$/, '');
49
+ }
50
+
51
+ export async function proveVendorRelease(req: ProveVendorReleaseRequest): Promise<ProveVendorReleaseResponse> {
52
+ const fetcher = req.fetcher ?? fetch;
53
+ const resp = await fetcher(`${baseUrl(req.keeperUrl)}/prove/release-vendor`, {
54
+ method: 'POST',
55
+ headers: { 'Content-Type': 'application/json' },
56
+ signal: req.signal,
57
+ body: JSON.stringify({
58
+ licenseHash: req.licenseHash,
59
+ zkAppAddress: req.zkAppAddress,
60
+ senderPublicKey: req.senderPublicKey,
61
+ ...(req.network ? { network: req.network } : {}),
62
+ }),
63
+ });
64
+ if (!resp.ok) {
65
+ const parsed = await readErrorBody(resp);
66
+ throw keeperError(parsed);
67
+ }
68
+ return await resp.json() as ProveVendorReleaseResponse;
69
+ }
70
+
71
+ export type ConfirmVendorReleaseRequest = {
72
+ keeperUrl: string;
73
+ licenseHash: string;
74
+ zkAppAddress: string;
75
+ // Tx hash returned by the wallet after broadcast. The service polls
76
+ // bestChain for this hash and flips `record.released` on sight.
77
+ txHash: string;
78
+ fetcher?: typeof fetch;
79
+ signal?: AbortSignal;
80
+ };
81
+
82
+ export type ConfirmVendorReleaseResponse = { ok: true };
83
+
84
+ export async function confirmVendorRelease(req: ConfirmVendorReleaseRequest): Promise<ConfirmVendorReleaseResponse> {
85
+ const fetcher = req.fetcher ?? fetch;
86
+ const resp = await fetcher(`${baseUrl(req.keeperUrl)}/confirm/release-vendor`, {
87
+ method: 'POST',
88
+ headers: { 'Content-Type': 'application/json' },
89
+ signal: req.signal,
90
+ body: JSON.stringify({
91
+ zkAppAddress: req.zkAppAddress,
92
+ licenseHash: req.licenseHash,
93
+ txHash: req.txHash,
94
+ }),
95
+ });
96
+ if (!resp.ok) {
97
+ const parsed = await readErrorBody(resp);
98
+ throw keeperError(parsed);
99
+ }
100
+ return await resp.json() as ConfirmVendorReleaseResponse;
101
+ }
@@ -0,0 +1,234 @@
1
+ // Copyright (c) 2025-2026 zkLicensing project developers
2
+ // SPDX-License-Identifier: BUSL-1.1
3
+
4
+ /**
5
+ * renewClient.ts
6
+ *
7
+ * Browser-safe client for the renew flow. Mirrors `buyClient.ts` in shape:
8
+ * the caller drives the wallet step between `proveRenew` and `confirmRenew`
9
+ * so the SDK stays wallet-agnostic.
10
+ *
11
+ * Unlike buy, renew carries a legally load-bearing `waiverAccepted` flag —
12
+ * EU/UK CRD Article 16(m) extinguishes the 14-day right of withdrawal only
13
+ * on the buyer's express affirmative act (consent to immediate delivery +
14
+ * acknowledgment of loss of withdrawal). We refuse the /prove/renew call
15
+ * when the flag is false to keep third-party integrators from silently
16
+ * shipping a UI that omits the waiver gate.
17
+ *
18
+ * Also refused pre-network when the backend's sales-freeze schedule has
19
+ * fired (salesFrozen === true) — the backend would 423 the request anyway,
20
+ * so surfacing the specific message client-side avoids a wasted round-trip.
21
+ */
22
+
23
+ import type { PurchaseReceipt } from './receipt.js';
24
+ import { resolveFreezeState } from './freezeClient.js';
25
+ import { readErrorBody, tryQueueBusyFromBody, keeperError } from './proveErrors.js';
26
+
27
+ export type ProveRenewRequest = {
28
+ // Standing consent (zklicensing/agent/actions) when an AI agent gives the
29
+ // withdrawal waiver for its owner.
30
+ agentConsent?: unknown;
31
+ keeperUrl: string;
32
+ licenseHash: string;
33
+ buyerAddress: string;
34
+ zkAppAddress: string;
35
+ // Mina network this renewal targets. Pull from your persisted ProofFile
36
+ // so it always matches the network the original buy was proved against.
37
+ network: 'devnet' | 'mainnet';
38
+ duration: 0 | 1 | 2 | 3; // 0 = Monthly, 1 = Yearly, 2 = 5 Years, 3 = Perpetual
39
+ // Set from the UI's Article 16(m) consent gate. Missing/false raises
40
+ // before the network call — the SDK never proves a renew without it.
41
+ waiverAccepted: boolean;
42
+ // Optional freeze-slot override. When omitted the SDK reads
43
+ // `process.env.FREEZE_AFTER_SLOT` (Node only), else GET /health.
44
+ freezeAfterSlot?: number | null;
45
+ fetcher?: typeof fetch;
46
+ signal?: AbortSignal;
47
+ };
48
+
49
+ export type ProveRenewResponse = {
50
+ provenTxJson: string;
51
+ newExpirySlot: number;
52
+ licenseHash: string;
53
+ renewAmount: string; // nanomina
54
+ // Deployment generation the backend proved against. If the buyer's stored
55
+ // ProofFile.generation is smaller, renew.ts walks ancestors and calls
56
+ // migrateLicense on each hop before submitting the renew.
57
+ generation: number;
58
+ // Key the backend stored the per-app in-flight slot under. Passed back to
59
+ // POST /tx-broadcast after the wallet returns a hash. See ProveBuyResponse.
60
+ appKey: string;
61
+ };
62
+
63
+ export type ConfirmRenewRequest = {
64
+ keeperUrl: string;
65
+ zkAppAddress: string;
66
+ licenseHash: string;
67
+ txHash: string;
68
+ fetcher?: typeof fetch;
69
+ signal?: AbortSignal;
70
+ };
71
+
72
+ function baseUrl(url: string): string {
73
+ return url.replace(/\/+$/, '');
74
+ }
75
+
76
+ async function readJsonError(resp: Response): Promise<string> {
77
+ try {
78
+ const body = await resp.json() as Record<string, unknown>;
79
+ if (typeof body.error === 'string') return body.error;
80
+ } catch { /* body wasn't JSON */ }
81
+ return `HTTP ${resp.status}`;
82
+ }
83
+
84
+ // POST /prove/renew — the backend compiles + proves the renew tx.
85
+ // Rejects locally when `waiverAccepted` is false; a network call in that
86
+ // state would be wasted work and would give the caller no signal that the
87
+ // legal precondition was missing. Also rejects when the backend's freeze
88
+ // has fired — same reasoning.
89
+ export async function proveRenew(req: ProveRenewRequest): Promise<ProveRenewResponse> {
90
+ if (req.waiverAccepted !== true) {
91
+ throw new Error('waiverAccepted must be true — Article 16(m) waiver required to renew');
92
+ }
93
+ const fetcher = req.fetcher ?? fetch;
94
+ const freeze = await resolveFreezeState({
95
+ keeperUrl: req.keeperUrl,
96
+ network: req.network,
97
+ freezeAfterSlot: req.freezeAfterSlot,
98
+ fetcher,
99
+ signal: req.signal,
100
+ });
101
+ if (freeze.salesFrozen) {
102
+ throw new Error('Sales frozen — new renewals refused ahead of scheduled hardfork migration');
103
+ }
104
+ const resp = await fetcher(`${baseUrl(req.keeperUrl)}/prove/renew`, {
105
+ method: 'POST',
106
+ headers: { 'Content-Type': 'application/json' },
107
+ signal: req.signal,
108
+ body: JSON.stringify({
109
+ licenseHash: req.licenseHash,
110
+ buyerAddress: req.buyerAddress,
111
+ zkAppAddress: req.zkAppAddress,
112
+ network: req.network,
113
+ duration: req.duration,
114
+ ...(req.agentConsent ? { agentConsent: req.agentConsent } : {}),
115
+ }),
116
+ });
117
+ if (!resp.ok) {
118
+ const parsed = await readErrorBody(resp);
119
+ const queueBusy = tryQueueBusyFromBody(parsed);
120
+ if (queueBusy) throw queueBusy;
121
+ throw keeperError(parsed);
122
+ }
123
+ return await resp.json() as ProveRenewResponse;
124
+ }
125
+
126
+ // POST /confirm/renew — promotes the in-memory renew intent to a pending
127
+ // LicenseRecord. Call this only after the wallet returns a txHash.
128
+ /** Returns the buyer's receipt for the renewal when the keeper issued one (put it in the license file as `receipt`). */
129
+ export async function confirmRenew(req: ConfirmRenewRequest): Promise<{ ok: true; receipt?: PurchaseReceipt }> {
130
+ const fetcher = req.fetcher ?? fetch;
131
+ const resp = await fetcher(`${baseUrl(req.keeperUrl)}/confirm/renew`, {
132
+ method: 'POST',
133
+ headers: { 'Content-Type': 'application/json' },
134
+ signal: req.signal,
135
+ body: JSON.stringify({
136
+ zkAppAddress: req.zkAppAddress,
137
+ licenseHash: req.licenseHash,
138
+ txHash: req.txHash,
139
+ }),
140
+ });
141
+ if (!resp.ok) throw new Error(await readJsonError(resp));
142
+ const body = await resp.json().catch(() => ({})) as { receipt?: PurchaseReceipt };
143
+ return { ok: true, ...(body?.receipt ? { receipt: body.receipt } : {}) };
144
+ }
145
+
146
+ export type RenewStatus = {
147
+ // 'confirmed' once the eventSync sweep has observed the licenseRenewed event
148
+ // on-chain and cleared the pending flag with the new expiry in place.
149
+ // 'pending' while the tx is in flight. 'unknown' if the license record
150
+ // hasn't been persisted yet (confirmRenew hasn't run or the intent expired).
151
+ // 'failed' if the keeper saw the transaction included but failed on chain
152
+ // (failureReason says why): the license is as it was; terminal.
153
+ status: 'confirmed' | 'pending' | 'unknown' | 'failed';
154
+ failureReason?: string;
155
+ expirySlot?: number;
156
+ txHash?: string | null;
157
+ elapsedMs?: number;
158
+ };
159
+
160
+ export type PollRenewStatusOptions = {
161
+ keeperUrl: string;
162
+ network: 'devnet' | 'mainnet';
163
+ zkAppAddress: string;
164
+ licenseHash: string;
165
+ // The expected new expiry (from ProveRenewResponse). Confirmation requires
166
+ // BOTH pending=false AND the on-chain record's expirySlot matching or
167
+ // exceeding this — the pending flag alone flips before the sweep, so
168
+ // matching against the expected slot avoids reporting confirmed on the
169
+ // stale pre-renew record.
170
+ newExpirySlot: number;
171
+ fetcher?: typeof fetch;
172
+ // Mina slots are ~90 s and a tx usually lands within a few slots, so a
173
+ // 10 s tick keeps the UI live without hammering the backend.
174
+ intervalMs?: number; // default 10 000
175
+ timeoutMs?: number; // default 20 min
176
+ signal?: AbortSignal;
177
+ onTick?: (status: RenewStatus) => void;
178
+ };
179
+
180
+ // Polls GET /license/:zkAppAddress/:licenseHash until the renew has landed
181
+ // on-chain (pending flipped and new expiry recorded) or the timeout fires.
182
+ // Mirrors pollBuyStatus, except the terminal condition is Mina-side rather
183
+ // than a backend-side buy-watch — renew has no dedicated watch map.
184
+ export async function pollRenewStatus(opts: PollRenewStatusOptions): Promise<RenewStatus> {
185
+ const fetcher = opts.fetcher ?? fetch;
186
+ const interval = opts.intervalMs ?? 10_000;
187
+ const timeout = opts.timeoutMs ?? 20 * 60 * 1000;
188
+ const startedAt = Date.now();
189
+ const deadline = startedAt + timeout;
190
+ const url = `${baseUrl(opts.keeperUrl)}/license/${encodeURIComponent(opts.zkAppAddress)}/${encodeURIComponent(opts.licenseHash)}?network=${opts.network}`;
191
+
192
+ while (true) {
193
+ if (opts.signal?.aborted) throw new Error('pollRenewStatus aborted');
194
+ let status: RenewStatus = { status: 'unknown', elapsedMs: Date.now() - startedAt };
195
+ try {
196
+ const resp = await fetcher(url, { signal: opts.signal });
197
+ if (resp.ok) {
198
+ const body = await resp.json() as {
199
+ found?: boolean; expirySlot?: number; pending?: boolean; txHash?: string | null;
200
+ };
201
+ if (!body.found) {
202
+ status = { status: 'unknown', elapsedMs: Date.now() - startedAt };
203
+ } else if (body.pending === false && (body.expirySlot ?? 0) >= opts.newExpirySlot) {
204
+ status = {
205
+ status: 'confirmed',
206
+ expirySlot: body.expirySlot,
207
+ txHash: body.txHash ?? null,
208
+ elapsedMs: Date.now() - startedAt,
209
+ };
210
+ } else {
211
+ status = {
212
+ status: 'pending',
213
+ expirySlot: body.expirySlot,
214
+ txHash: body.txHash ?? null,
215
+ elapsedMs: Date.now() - startedAt,
216
+ };
217
+ }
218
+ }
219
+ } catch { /* transient network — try again on next tick */ }
220
+ // Not confirmed yet: the keeper's landing watch says whether the
221
+ // transaction failed on chain (a keeper without the route: 404, ignored).
222
+ if (status.status !== 'confirmed') {
223
+ try {
224
+ const w = await fetcher(`${baseUrl(opts.keeperUrl)}/renew/status?licenseHash=${encodeURIComponent(opts.licenseHash)}`, { signal: opts.signal });
225
+ const watch = w.ok ? await w.json() as { status?: string; failureReason?: string } : null;
226
+ if (watch?.status === 'failed') status = { ...status, status: 'failed', ...(watch.failureReason ? { failureReason: watch.failureReason } : {}) };
227
+ } catch { /* transient — try again on next tick */ }
228
+ }
229
+ opts.onTick?.(status);
230
+ if (status.status === 'confirmed' || status.status === 'failed') return status;
231
+ if (Date.now() >= deadline) return status;
232
+ await new Promise<void>((r) => setTimeout(r, interval));
233
+ }
234
+ }
@@ -0,0 +1,165 @@
1
+ // Copyright (c) 2025-2026 zkLicensing project developers
2
+ // SPDX-License-Identifier: BUSL-1.1
3
+
4
+ /**
5
+ * seatName.ts
6
+ *
7
+ * Human-readable device names for the device-binding slot list
8
+ * ("Silent Otter", "Curious Panda"). Meant for the SDK UI so a buyer
9
+ * looking at their bound-devices panel sees "the laptop I left in the
10
+ * office" instead of an opaque device commitment.
11
+ *
12
+ * The name is purely cosmetic — the on-chain circuit never gates on it,
13
+ * verifyOffline never inspects it, and unbind targets `slotIndex`, not
14
+ * name. It exists solely so the SDK's device-manager UI has something
15
+ * readable to render next to each slot.
16
+ *
17
+ * Names are NOT persisted on-chain and NOT persisted off-chain either —
18
+ * `seatNameFor` derives the label deterministically from three on-chain
19
+ * fields (`licenseHash`, `slotIndex`, `boundAtSlot`), so every viewer of
20
+ * the same license computes the same name for a given occupied slot with
21
+ * no naming sidecar and no stored state.
22
+ *
23
+ * The wordlists are intentionally boring: no scary animals, no negative
24
+ * adjectives, no cultural landmines, no words with awkward secondary
25
+ * meanings. Order and word choice are stable — changing them shifts every
26
+ * slot's name on the next mint, which would be jarring for users looking
27
+ * at their device list.
28
+ */
29
+
30
+ // 250 friendly adjectives. Short, common, unambiguously positive or
31
+ // neutral. Ordering is alphabetical for maintainability — the derivation
32
+ // is index-based so order does not affect behavior.
33
+ const ADJECTIVES: readonly string[] = [
34
+ 'Agile', 'Alert', 'Amber', 'Ample', 'Ancient', 'Apricot', 'Arctic', 'Ashen',
35
+ 'Aspen', 'Autumn', 'Azure', 'Balmy', 'Bashful', 'Beaming', 'Beige', 'Bold',
36
+ 'Bouncy', 'Brave', 'Breezy', 'Bright', 'Brisk', 'Bronze', 'Bubbly', 'Busy',
37
+ 'Calm', 'Candid', 'Cedar', 'Cheery', 'Chic', 'Chipper', 'Chunky', 'Classic',
38
+ 'Clean', 'Clever', 'Cloudy', 'Clumsy', 'Coastal', 'Comet', 'Cool', 'Copper',
39
+ 'Coral', 'Cosmic', 'Cosy', 'Crafty', 'Creamy', 'Crimson', 'Crisp', 'Cuddly',
40
+ 'Curious', 'Daisy', 'Dandy', 'Dapper', 'Daring', 'Dawn', 'Deft', 'Delta',
41
+ 'Devout', 'Dewy', 'Diamond', 'Doughty', 'Dreamy', 'Dusky', 'Dusty', 'Eager',
42
+ 'Earnest', 'Earthy', 'Easy', 'Ebony', 'Elder', 'Elm', 'Ember', 'Emerald',
43
+ 'Epic', 'Fabled', 'Faint', 'Fair', 'Fancy', 'Fearless', 'Feisty', 'Fern',
44
+ 'Fiery', 'Fine', 'Firm', 'Flaxen', 'Fleet', 'Floral', 'Fluffy', 'Foggy',
45
+ 'Fond', 'Forest', 'Frank', 'Freckled', 'Friendly', 'Frosty', 'Frugal', 'Furry',
46
+ 'Fuzzy', 'Gallant', 'Genial', 'Gentle', 'Gilded', 'Ginger', 'Glad', 'Glassy',
47
+ 'Gleaming', 'Glossy', 'Glowing', 'Golden', 'Graceful', 'Grand', 'Grassy',
48
+ 'Green', 'Handy', 'Happy', 'Hardy', 'Hazel', 'Hearty', 'Helpful', 'Honest',
49
+ 'Humble', 'Icy', 'Indigo', 'Inky', 'Iron', 'Ivory', 'Jade', 'Jaunty', 'Jazzy',
50
+ 'Jolly', 'Jovial', 'Joyful', 'Juniper', 'Keen', 'Kind', 'Lanky', 'Lark',
51
+ 'Lavender', 'Lazy', 'Leaf', 'Lilac', 'Limber', 'Lively', 'Lofty', 'Loyal',
52
+ 'Lucky', 'Lush', 'Marble', 'Matte', 'Meadow', 'Mellow', 'Merry', 'Mighty',
53
+ 'Milky', 'Mindful', 'Minty', 'Misty', 'Modest', 'Mossy', 'Nautical', 'Nectar',
54
+ 'Nifty', 'Nimble', 'Noble', 'Nutmeg', 'Oaken', 'Ochre', 'Olive', 'Onyx',
55
+ 'Opal', 'Orange', 'Orchid', 'Pale', 'Patient', 'Peachy', 'Pearl', 'Pebble',
56
+ 'Perky', 'Piney', 'Placid', 'Playful', 'Plucky', 'Plum', 'Plush', 'Polar',
57
+ 'Polite', 'Prairie', 'Proud', 'Prudent', 'Punchy', 'Purple', 'Quaint', 'Quick',
58
+ 'Quiet', 'Radiant', 'Rainy', 'Rapid', 'Rare', 'Ready', 'Ripe', 'River',
59
+ 'Rocky', 'Rosy', 'Royal', 'Ruby', 'Rugged', 'Rustic', 'Saffron', 'Sage',
60
+ 'Sandy', 'Sapphire', 'Scarlet', 'Serene', 'Shady', 'Sharp', 'Shy', 'Silent',
61
+ 'Silken', 'Silky', 'Silver', 'Sincere', 'Sleek', 'Sleepy', 'Slender', 'Small',
62
+ 'Smart', 'Smiling', 'Snowy', 'Solar', 'Solid', 'Sonic', 'Soulful', 'Speedy',
63
+ 'Spirited', 'Splendid', 'Sprightly', 'Spry', 'Starry', 'Steady', 'Stellar',
64
+ 'Sterling', 'Stormy', 'Stout', 'Sunny', 'Sunset', 'Swift', 'Tangy', 'Teal',
65
+ 'Tender', 'Thankful', 'Thoughtful', 'Thrifty', 'Tidy', 'Timber', 'Tiny',
66
+ 'Topaz', 'Tranquil', 'Trusty', 'Turquoise', 'Twilight', 'Upright', 'Valiant',
67
+ 'Velvet', 'Verdant', 'Violet', 'Vivid', 'Warm', 'Whimsical', 'Willow',
68
+ 'Windy', 'Winsome', 'Wise', 'Witty', 'Woodland', 'Woolly', 'Zealous', 'Zesty',
69
+ ];
70
+
71
+ // 250 friendly animals. Common, recognisable, unambiguously non-threatening
72
+ // (no predators-of-humans, no venomous ones, no polarising taxidermy). Two-
73
+ // word species (mountain goat, sea otter) are collapsed to the base name so
74
+ // the final "Adjective Animal" reads cleanly. Ordering is alphabetical.
75
+ const ANIMALS: readonly string[] = [
76
+ 'Alpaca', 'Anteater', 'Antelope', 'Ape', 'Armadillo', 'Auk', 'Aurochs',
77
+ 'Axolotl', 'Baboon', 'Badger', 'Barnacle', 'Bat', 'Beagle', 'Bear', 'Beaver',
78
+ 'Bee', 'Beetle', 'Bison', 'Blackbird', 'Bluebird', 'Boar', 'Bobcat', 'Bonobo',
79
+ 'Booby', 'Buffalo', 'Bulldog', 'Bullfinch', 'Bumblebee', 'Butterfly', 'Camel',
80
+ 'Capybara', 'Cardinal', 'Caribou', 'Cassowary', 'Cat', 'Caterpillar',
81
+ 'Catfish', 'Chameleon', 'Chamois', 'Cheetah', 'Chickadee', 'Chinchilla',
82
+ 'Chipmunk', 'Clam', 'Clownfish', 'Coati', 'Cobra', 'Cockatoo', 'Codfish',
83
+ 'Colt', 'Condor', 'Coot', 'Cormorant', 'Corgi', 'Cougar', 'Cow', 'Coyote',
84
+ 'Crab', 'Crane', 'Cricket', 'Crow', 'Cub', 'Cuckoo', 'Curlew', 'Deer',
85
+ 'Dingo', 'Dolphin', 'Donkey', 'Dormouse', 'Dove', 'Dragonfly', 'Drake',
86
+ 'Duck', 'Duckling', 'Eagle', 'Echidna', 'Eel', 'Egret', 'Elephant', 'Elk',
87
+ 'Emu', 'Falcon', 'Fawn', 'Ferret', 'Finch', 'Firefly', 'Fish', 'Flamingo',
88
+ 'Fox', 'Frog', 'Gecko', 'Gerbil', 'Gibbon', 'Giraffe', 'Gnu', 'Goat',
89
+ 'Godwit', 'Goldfinch', 'Goldfish', 'Goose', 'Gorilla', 'Grebe', 'Griffon',
90
+ 'Grouse', 'Guanaco', 'Guineafowl', 'Gull', 'Guppy', 'Hamster', 'Hare',
91
+ 'Harrier', 'Hawk', 'Hedgehog', 'Heron', 'Herring', 'Hippo', 'Hoatzin',
92
+ 'Hoopoe', 'Hornbill', 'Horse', 'Hound', 'Hummingbird', 'Ibex', 'Ibis',
93
+ 'Iguana', 'Impala', 'Jackal', 'Jackrabbit', 'Jaguar', 'Jay', 'Jellyfish',
94
+ 'Kakapo', 'Kangaroo', 'Kestrel', 'Kingfisher', 'Kinkajou', 'Kite', 'Kitten',
95
+ 'Kiwi', 'Koala', 'Kookaburra', 'Ladybug', 'Lamb', 'Lapwing', 'Lark',
96
+ 'Lemming', 'Lemur', 'Leopard', 'Lion', 'Lizard', 'Llama', 'Lobster',
97
+ 'Lorikeet', 'Loris', 'Lynx', 'Macaque', 'Macaw', 'Magpie', 'Mallard',
98
+ 'Manatee', 'Mandrill', 'Mantis', 'Marmoset', 'Marmot', 'Marten', 'Meerkat',
99
+ 'Mink', 'Minnow', 'Mole', 'Mongoose', 'Monkey', 'Moose', 'Moth', 'Mouse',
100
+ 'Mule', 'Muntjac', 'Muskrat', 'Narwhal', 'Newt', 'Nightingale', 'Numbat',
101
+ 'Nuthatch', 'Ocelot', 'Octopus', 'Okapi', 'Opossum', 'Orangutan', 'Oriole',
102
+ 'Ostrich', 'Otter', 'Owl', 'Ox', 'Oyster', 'Panda', 'Pangolin', 'Panther',
103
+ 'Parakeet', 'Parrot', 'Partridge', 'Peacock', 'Pelican', 'Penguin', 'Perch',
104
+ 'Pheasant', 'Pig', 'Pigeon', 'Piglet', 'Pika', 'Pipit', 'Platypus', 'Plover',
105
+ 'Pointer', 'Polecat', 'Pony', 'Porcupine', 'Porpoise', 'Possum', 'Prawn',
106
+ 'Ptarmigan', 'Puffin', 'Puma', 'Pup', 'Puppy', 'Quail', 'Quokka', 'Rabbit',
107
+ 'Raccoon', 'Ram', 'Rat', 'Raven', 'Reindeer', 'Rhino', 'Roadrunner', 'Robin',
108
+ 'Rook', 'Salamander', 'Salmon', 'Sandpiper', 'Sardine', 'Scallop', 'Seahorse',
109
+ 'Seal', 'Serval', 'Shrew', 'Shrimp', 'Skunk', 'Sloth', 'Snail', 'Snake',
110
+ 'Snipe', 'Sparrow', 'Spider', 'Squid', 'Squirrel', 'Starling', 'Stoat',
111
+ 'Stork', 'Sturgeon', 'Swallow', 'Swan', 'Tamarin', 'Tanager', 'Tapir',
112
+ 'Tarsier', 'Teal', 'Tern', 'Terrier', 'Thrush', 'Tiger', 'Toad', 'Toucan',
113
+ 'Trout', 'Tuna', 'Turkey', 'Turtle', 'Vole', 'Wagtail', 'Walrus', 'Wapiti',
114
+ 'Warbler', 'Wasp', 'Weasel', 'Whale', 'Whippet', 'Wolf', 'Wolverine',
115
+ 'Wombat', 'Woodchuck', 'Woodpecker', 'Wren', 'Yak', 'Zebra',
116
+ ];
117
+
118
+ // Total combinations = ADJECTIVES.length * ANIMALS.length. Exposed for
119
+ // diagnostics and for tests that want to assert the name space is large
120
+ // enough to cover the expected ceiling of licenses × cap.
121
+ export const SEAT_NAME_COMBINATIONS: number = ADJECTIVES.length * ANIMALS.length;
122
+
123
+ // FNV-1a — a fast, non-cryptographic 32-bit hash. Uniform enough over 250
124
+ // buckets that the modulo bias (~1 in 17M) is invisible in practice. Not
125
+ // used for security; just to derive a stable pair of small indices from
126
+ // the seat's on-chain identity fields.
127
+ function fnv1a(s: string, seed: number): number {
128
+ let h = seed >>> 0;
129
+ for (let i = 0; i < s.length; i++) {
130
+ h ^= s.charCodeAt(i);
131
+ h = Math.imul(h, 0x01000193);
132
+ }
133
+ return h >>> 0;
134
+ }
135
+
136
+ // Deterministic seat-name derivation. Two viewers holding the same
137
+ // `LicenseLeaf` compute the same name for a given occupied slot, without
138
+ // a naming sidecar or any persisted state — the function is a pure fold
139
+ // over three on-chain fields.
140
+ //
141
+ // Key composition — `licenseHash + slotIndex + boundAtSlot`:
142
+ // - `licenseHash` scopes names to a single license, so slot 0 on
143
+ // license A doesn't collide with slot 0 on license B.
144
+ // - `slotIndex` separates concurrent occupants of the same license.
145
+ // - `boundAtSlot` binds the name to the device-instance in that slot at
146
+ // that bind moment. Unbind + rebind at the same slotIndex produces
147
+ // a fresh name, matching the mental model of "the laptop I bound in
148
+ // March" rather than "seat 0 forever".
149
+ //
150
+ // Empty slots (`boundAtSlot === 0`) are the caller's responsibility to
151
+ // filter — the function returns a valid string for any input rather than
152
+ // throwing, to keep the surface pure. Match on `deviceCommitment !== '0'`
153
+ // or `boundAtSlot > 0` before calling.
154
+ export type SeatNameInput = {
155
+ licenseHash: string; // Poseidon-hash decimal string from ProofFile / LicenseRecord
156
+ slotIndex: number; // 0..deviceCap-1
157
+ boundAtSlot: number; // Mina slot the bind() tx landed at
158
+ };
159
+
160
+ export function seatNameFor(input: SeatNameInput): string {
161
+ const key = `${input.licenseHash}|${input.slotIndex}|${input.boundAtSlot}`;
162
+ const adj = ADJECTIVES[fnv1a(key, 0x811c9dc5) % ADJECTIVES.length];
163
+ const animal = ANIMALS[ fnv1a(key, 0xdeadbeef) % ANIMALS.length];
164
+ return `${adj} ${animal}`;
165
+ }
@@ -0,0 +1,18 @@
1
+ // Copyright (c) 2025-2026 zkLicensing project developers
2
+ // SPDX-License-Identifier: BUSL-1.1
3
+
4
+ /**
5
+ * Slot math of the licensing contract (90 s per slot after the Mesa
6
+ * upgrade), as plain numbers. No o1js: the package root imports these, and
7
+ * o1js is an optional peer dependency (README, Publishing to npm).
8
+ * contractInterface re-exports them.
9
+ */
10
+
11
+ export const MONTHLY_SLOTS_N = 28_800; // 30 days × 24 × 40
12
+ export const ONE_YEAR_SLOTS_N = 350_640; // 365.25 days × 24 × 40 (Julian year)
13
+ export const FIVE_YEAR_SLOTS_N = 1_753_200; // 5 × ONE_YEAR_SLOTS_N
14
+ export const PERPETUAL_SLOTS_N = 350_640_000; // 1000 × ONE_YEAR_SLOTS_N ("perpetual — until vendor sunset")
15
+ export const REFUND_WINDOW_N = 13_440;
16
+ export const GRACE_PERIOD_N = 6_720;
17
+ export const MS_PER_SLOT_N = 90_000; // Mesa slot duration in ms
18
+ export const SLOTS_PER_DAY_N = 960; // 86_400_000 / MS_PER_SLOT_N