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,465 @@
1
+ // Copyright (c) 2025-2026 zkLicensing project developers
2
+ // SPDX-License-Identifier: BUSL-1.1
3
+
4
+ /**
5
+ * contractInterface.ts
6
+ *
7
+ * Public contract interface: event Structs, slot/grace constants, duration
8
+ * helpers, and a minimal `LicensingAppEventsContract` SmartContract subclass
9
+ * that has the events declaration but no @method bodies.
10
+ *
11
+ * The full `LicensingApp` (with provable method bodies) lives in the private
12
+ * zklicensing-app package; only the things needed to decode events and reason
13
+ * about durations are published here so a public verify service can replay
14
+ * the archive without pulling in the contract source.
15
+ *
16
+ * `CANONICAL_VK_HASH` is the verification-key hash of the published
17
+ * `LicensingApp` circuit. Bump it whenever the on-chain zkApp changes
18
+ * and rerun the backend's boot assertion (see zklicensing-app/src/keeperService.ts)
19
+ * / the verify service's `ensureCompiled()` check to catch drift.
20
+ */
21
+
22
+ import { Bool, declareState, fetchAccount, Field, PublicKey, SmartContract, Struct, UInt32, UInt64, MerkleMap } from 'o1js';
23
+
24
+ // ------------------------------------------------------------
25
+ // EVENT STRUCTS
26
+ // ------------------------------------------------------------
27
+
28
+ // Struct() returns a class whose type references o1js internals that fall
29
+ // outside this package's rootDir (npm hoists o1js to the workspace root).
30
+ // Annotating each export as `any` keeps tsc from inferring (and therefore
31
+ // emitting) those internal type references, so the generated declarations
32
+ // stay self-contained.
33
+ //
34
+ // The clean alternative — `export class X extends Struct({...}) {}` as the
35
+ // mina-fungible-token repo does — only works when o1js sits inside rootDir,
36
+ // which a workspace member can't guarantee.
37
+
38
+ // `cap` is carried so that a backend reconstructing the license from archive
39
+ // events (e.g. after losing its in-memory intent map to a restart) can
40
+ // rehydrate the exact LicenseLeaf that was committed on-chain. Without it,
41
+ // leafFromRecord() would default cap to UInt32.zero and every follow-up
42
+ // root-mutating proof (renew, refund, bind, unbind) would fail the
43
+ // in-circuit hash check.
44
+ //
45
+ // `deviceSlotsRoot` / `deviceSlotsFilled` are omitted deliberately: at issue
46
+ // time they are always EMPTY_DEVICE_SLOTS_ROOT / 0. The backend's rehydration
47
+ // uses those defaults, matching the leaf the circuit wrote.
48
+ //
49
+ // The leaf carries no separate ownerPubKeyHash — `licenseHash` IS the owner-
50
+ // pubkey commitment (buyLicense asserts them equal), so it doesn't need to
51
+ // ride in the event either.
52
+ export const LicenseIssuedEvent: any = Struct({
53
+ licenseHash: Field,
54
+ buyer: PublicKey,
55
+ amount: UInt64,
56
+ slot: UInt32,
57
+ expirySlot: UInt32,
58
+ cap: UInt32,
59
+ });
60
+ export type LicenseIssuedEvent = any;
61
+
62
+ export const ExpiryUpdateEvent: any = Struct({
63
+ licenseHash: Field,
64
+ newExpiry: UInt32,
65
+ });
66
+ export type ExpiryUpdateEvent = any;
67
+
68
+ export const PriceUpdatedEvent: any = Struct({
69
+ priceMonthly: UInt64,
70
+ priceYearly: UInt64,
71
+ priceFiveYear: UInt64,
72
+ pricePerpetual: UInt64,
73
+ });
74
+ export type PriceUpdatedEvent = any;
75
+
76
+ export const AppCreatedEvent: any = Struct({
77
+ vendor: PublicKey,
78
+ /** The app this one continues (a redeploy: it starts with that app's licenses); PublicKey.empty() for a first generation. */
79
+ predecessor: PublicKey,
80
+ /** The licenses tree it starts with: empty, or its predecessor's frozen root. */
81
+ licensesRoot: Field,
82
+ });
83
+ export type AppCreatedEvent = any;
84
+
85
+ // Emitted by `retire`: the app is frozen at `licensesRoot` for a redeploy;
86
+ // its successor starts with that tree (initializeSuccessor). `by` is the key
87
+ // that signed it (the vendor's or the platform's).
88
+ // `forced`: the platform retired it with purchases still in escrow (their
89
+ // funds stay on the retired contract, which settles nothing any more).
90
+ export const AppRetiredEvent: any = Struct({ licensesRoot: Field, by: PublicKey, successor: PublicKey, forced: Bool });
91
+ export type AppRetiredEvent = any;
92
+
93
+ /**
94
+ * What the vendor or the platform signs to retire an app: bound to the app,
95
+ * its frozen root, the one successor that may start from it, and `force`
96
+ * (only the platform may: retire with purchases in escrow, whose funds then
97
+ * stay on the retired contract).
98
+ */
99
+ export const APP_RETIRE_TAG: Field = Field(0x7a6b726574697265n); // "zkretire"
100
+ export function appRetireMessage(app: PublicKey, licensesRoot: Field, successor: PublicKey, force: Bool): Field[] {
101
+ return [APP_RETIRE_TAG, ...app.toFields(), licensesRoot, ...successor.toFields(), force.toField()];
102
+ }
103
+
104
+ /**
105
+ * LicensingApp's on-chain state, in declaration order (appState index of
106
+ * each field; a PublicKey takes two). A successor's initializeSuccessor
107
+ * reads its predecessor's through account preconditions.
108
+ */
109
+ export const LICENSING_APP_STATE = {
110
+ licensesRoot: 0, vendorX: 1, vendorIsOdd: 2, escrowRoot: 3, pricesPacked: 4,
111
+ version: 5, ancestorsRoot: 6, forkActivationSlot: 7, deviceCap: 8, retired: 9,
112
+ successorX: 10, successorIsOdd: 11,
113
+ payoutX: 12, payoutIsOdd: 13, pendingPayoutX: 14, pendingPayoutIsOdd: 15, pendingPayoutSlot: 16,
114
+ platformX: 17, platformIsOdd: 18, treasuryX: 19, treasuryIsOdd: 20, platformEpoch: 21,
115
+ vendorNonce: 22, networkId: 23,
116
+ } as const;
117
+
118
+ // ------------------------------------------------------------
119
+ // KEYS: the platform key, payout addresses, the vendor key, license owners
120
+ // ------------------------------------------------------------
121
+
122
+ /** Deterministic Field from a short ASCII label (≤ 31 bytes). */
123
+ function tagField(label: string): Field {
124
+ let v = 0n;
125
+ for (const ch of label) v = (v << 8n) | BigInt(ch.charCodeAt(0));
126
+ return Field(v);
127
+ }
128
+
129
+ export const KEY_TAGS: Readonly<Record<'PLATFORM_KEY' | 'PLATFORM_RECOVER' | 'VENDOR_KEY' | 'LICENSE_TRANSFER', Field>> = {
130
+ PLATFORM_KEY: tagField('zkLicensing/v1/platformKey'),
131
+ PLATFORM_RECOVER: tagField('zkLicensing/v1/platformRecover'),
132
+ VENDOR_KEY: tagField('zkLicensing/v1/vendorKey'),
133
+ LICENSE_TRANSFER: tagField('zkLicensing/v1/licenseMove'),
134
+ };
135
+
136
+ /**
137
+ * The platform's key (signs platform retires and cancels), its treasury
138
+ * (receives the 2%) and how many times they changed, as every app and
139
+ * registry holds them. A new contract starts with the current ones (the
140
+ * keeper checks them before listing it); a successor carries its
141
+ * predecessor's over, checked on chain.
142
+ */
143
+ export const PlatformState: any = Struct({ key: PublicKey, treasury: PublicKey, epoch: UInt32 });
144
+ export type PlatformState = any;
145
+
146
+ /**
147
+ * A change of the platform key and treasury: signed by the current platform
148
+ * key and the new one. Bound to the network and the next epoch, not to a
149
+ * contract, so one pair of signatures updates every app and registry on the
150
+ * network (anyone may send them; each contract takes it once).
151
+ */
152
+ export function platformKeyMessage(a: { network: Field; epoch: UInt32; key: PublicKey; treasury: PublicKey }): Field[] {
153
+ return [KEY_TAGS.PLATFORM_KEY, a.network, a.epoch.value, ...a.key.toFields(), ...a.treasury.toFields()];
154
+ }
155
+
156
+ /**
157
+ * The same, signed by the offline recovery key alone (compiled into the
158
+ * circuits): it can always set a new platform key and treasury, lost or
159
+ * stolen, at any epoch above a contract's current one.
160
+ */
161
+ export function platformRecoverMessage(a: { network: Field; epoch: UInt32; key: PublicKey; treasury: PublicKey }): Field[] {
162
+ return [KEY_TAGS.PLATFORM_RECOVER, a.network, a.epoch.value, ...a.key.toFields(), ...a.treasury.toFields()];
163
+ }
164
+
165
+ /** A new vendor key for an app: signed by the current vendor key and the new one; `nonce` is the app's vendorNonce. */
166
+ export function vendorKeyMessage(a: { network: Field; app: PublicKey; nonce: UInt32; vendor: PublicKey }): Field[] {
167
+ return [KEY_TAGS.VENDOR_KEY, a.network, ...a.app.toFields(), a.nonce.value, ...a.vendor.toFields()];
168
+ }
169
+
170
+ /**
171
+ * A license moving to a new owner key (`to` = ownerPubKeyHash of the new
172
+ * key): signed by the current owner key and the new one, over the license
173
+ * as it is now (its leaf hash) and a last slot it may land by.
174
+ */
175
+ export function licenseTransferMessage(a: { network: Field; app: PublicKey; leafHash: Field; to: Field; validUntil: UInt32 }): Field[] {
176
+ return [KEY_TAGS.LICENSE_TRANSFER, a.network, ...a.app.toFields(), a.leafHash, a.to, a.validUntil.value];
177
+ }
178
+
179
+ /**
180
+ * How long a new payout address waits before it takes effect: 48 hours at
181
+ * 90 s per slot. Payments keep going to the current one until it is
182
+ * applied, and the owner can cancel it meanwhile.
183
+ */
184
+ export const PAYOUT_DELAY_SLOTS_N = 1_920;
185
+
186
+ /** A payout address was proposed; anyone may apply it from `effectiveSlot` on. */
187
+ export const PayoutProposedEvent: any = Struct({ payout: PublicKey, effectiveSlot: UInt32 });
188
+ export type PayoutProposedEvent = any;
189
+ /** The payout address now in use (applied), or the pending one cancelled (`applied` false). */
190
+ export const PayoutChangedEvent: any = Struct({ payout: PublicKey, applied: Bool });
191
+ export type PayoutChangedEvent = any;
192
+ export const PlatformChangedEvent: any = Struct({ platform: PlatformState, byRecovery: Bool });
193
+ export type PlatformChangedEvent = any;
194
+ export const VendorChangedEvent: any = Struct({ vendor: PublicKey, nonce: UInt32 });
195
+ export type VendorChangedEvent = any;
196
+ /**
197
+ * A license moved to a new owner key: the same leaf under `to`. `wallet` is
198
+ * the transaction's sender, the new owner's wallet that paid for it (as
199
+ * LicenseIssuedEvent's `buyer`), so a replay lists it under that wallet.
200
+ */
201
+ export const LicenseTransferredEvent: any = Struct({ from: Field, to: Field, wallet: PublicKey });
202
+ export type LicenseTransferredEvent = any;
203
+
204
+ // Emitted by `bind` when the buyer's device commitment lands in a slot on
205
+ // their license leaf. `deviceCommitment = Poseidon(devicePubKeyHash)` — the
206
+ // raw pubkey is never revealed. `slotIndex` is 0-based within the license's
207
+ // fixed-size slot array (see DEVICE_SLOT_CAP).
208
+ //
209
+ // `boundAtSlot` carries the circuit's proof-time `globalSlotSinceGenesis` —
210
+ // the exact value packed into the leaf's `freedAt` via `packSlotTimes(
211
+ // currentSlot, 0)`. Off-chain backends rebuilding `deviceSlotsRoot` MUST use
212
+ // this, not the block's globalSlot: the tx's precondition allows any block
213
+ // slot within TOLERANCE_WINDOW of proof time, so block-slot != proof-slot in
214
+ // the general case, and any mismatch flips the packed field, the leaf hash,
215
+ // and the aggregate `licensesRoot`. Follows the same pattern as
216
+ // `LicenseIssuedEvent.slot`.
217
+ export const DeviceBoundEvent: any = Struct({
218
+ licenseHash: Field,
219
+ slotIndex: UInt32,
220
+ deviceCommitment: Field,
221
+ boundAtSlot: UInt32,
222
+ });
223
+ export type DeviceBoundEvent = any;
224
+
225
+ // Emitted by `unbind` when a slot is freed. `slotIndex` stays occupied in the
226
+ // array (`slotFreedAt` stamp stays set) so the cooldown timer starts; the
227
+ // slot's `occupied` field goes to Field(0). Any device holding the license's
228
+ // ownership key can trigger this — remote log-off is by design.
229
+ //
230
+ // `freedAtSlot` carries the circuit's proof-time `globalSlotSinceGenesis` —
231
+ // the value packed into the leaf's `freedAt` via `packSlotTimes(preservedBound,
232
+ // currentSlot)`. Same off-chain reconstruction reason as `DeviceBoundEvent.
233
+ // boundAtSlot`. The preserved `boundAtSlot` is not re-emitted; the backend
234
+ // reads it from its own record (which still has the earlier bind's stamp).
235
+ export const DeviceUnboundEvent: any = Struct({
236
+ licenseHash: Field,
237
+ slotIndex: UInt32,
238
+ freedAtSlot: UInt32,
239
+ });
240
+ export type DeviceUnboundEvent = any;
241
+
242
+ // ------------------------------------------------------------
243
+ // CONSTANTS (slot math — 90 s/slot after Mesa upgrade)
244
+ // ------------------------------------------------------------
245
+
246
+ // Plain numbers, in an o1js-free module so the package root (verifyLicense)
247
+ // can use them without o1js installed; re-exported here unchanged.
248
+ import {
249
+ MONTHLY_SLOTS_N, ONE_YEAR_SLOTS_N, FIVE_YEAR_SLOTS_N, PERPETUAL_SLOTS_N,
250
+ REFUND_WINDOW_N, GRACE_PERIOD_N, MS_PER_SLOT_N, SLOTS_PER_DAY_N,
251
+ } from './slotConstants.js';
252
+ export {
253
+ MONTHLY_SLOTS_N, ONE_YEAR_SLOTS_N, FIVE_YEAR_SLOTS_N, PERPETUAL_SLOTS_N,
254
+ REFUND_WINDOW_N, GRACE_PERIOD_N, MS_PER_SLOT_N, SLOTS_PER_DAY_N,
255
+ };
256
+
257
+ // Slot tolerance for the globalSlotSinceGenesis precondition. Applied uniformly to
258
+ // every method that reads currentSlot. 40 slots × 90 s = 1 hour — sized to
259
+ // clear the honest-user prove+wallet+mempool latency envelope. The refund vs
260
+ // release windows stay disjoint by construction: requestRefund asserts against
261
+ // the lower bound (currentSlot ≤ purchaseSlot + REFUND_WINDOW), releaseFunds
262
+ // asserts against a threshold shifted by +TOLERANCE_WINDOW so neither can
263
+ // land on the same real slot as the other. See AUDIT_BRIEF.md §7.
264
+ export const TOLERANCE_WINDOW_N = 40;
265
+
266
+ // How far before a successor's fork activation slot a license's grace period
267
+ // may have ended and still qualify for the fork bump (effectiveExpirySlot).
268
+ // The bump exists for licenses that lapsed because the pre-fork sales freeze
269
+ // blocked renewal; the freeze starts at hardforkSlot - REFUND_WINDOW_N -
270
+ // margin (~15_500 slots). 20_160 slots = 21 days covers that with slack.
271
+ // Licenses whose grace ran out earlier lapsed normally and keep their expiry.
272
+ // The keeper warns at startup if FREEZE_AFTER_SLOT sits further back.
273
+ export const MIGRATION_BUMP_LOOKBACK_N = 20_160;
274
+
275
+ /**
276
+ * A license's expiry as the app's contract treats it: a successor deployed
277
+ * across a Mina hardfork (forkActivationSlot > 0) starts with its
278
+ * predecessor's licenses, and a license that lapsed during the pre-fork
279
+ * freeze (expiry before the fork slot, but within grace + lookback of it)
280
+ * counts as expiring at the fork slot, so the 7-day grace runs from there.
281
+ * renewLicense applies the same rule in-circuit; verification uses this.
282
+ */
283
+ export function effectiveExpirySlot(expirySlot: number, forkActivationSlot = 0): number {
284
+ return forkBumpedExpiry(expirySlot, forkActivationSlot, GRACE_PERIOD_N);
285
+ }
286
+
287
+ /**
288
+ * The fork bump shared by licenses and permits: an expiry before the fork
289
+ * slot, but within `grace` + MIGRATION_BUMP_LOOKBACK_N of it (it lapsed
290
+ * during the pre-fork freeze, when it couldn't be renewed), counts as
291
+ * expiring at the fork slot. Licenses use their 7-day grace, permits their
292
+ * registry's grace period.
293
+ */
294
+ export function forkBumpedExpiry(expirySlot: number, forkActivationSlot: number, grace: number): number {
295
+ return expirySlot < forkActivationSlot && expirySlot + grace + MIGRATION_BUMP_LOOKBACK_N >= forkActivationSlot
296
+ ? forkActivationSlot : expirySlot;
297
+ }
298
+
299
+ export const EMPTY: Field = Field(0);
300
+ export const EMPTY_ROOT: Field = new MerkleMap().getRoot();
301
+
302
+ // ------------------------------------------------------------
303
+ // DURATION HELPERS
304
+ // ------------------------------------------------------------
305
+
306
+ export const DURATION_LABELS = ['1 Month', '1 Year', '5 Years', 'Perpetual'] as const;
307
+ export type DurationLabel = typeof DURATION_LABELS[number];
308
+
309
+ export const DURATION_INDEX = { '1 Month': 0, '1 Year': 1, '5 Years': 2, 'Perpetual': 3 } as const;
310
+
311
+ export function durationSlotsFromLabel(label?: string | null): number | null {
312
+ if (label === '1 Month') return MONTHLY_SLOTS_N;
313
+ if (label === '1 Year') return ONE_YEAR_SLOTS_N;
314
+ if (label === '5 Years') return FIVE_YEAR_SLOTS_N;
315
+ if (label === 'Perpetual') return PERPETUAL_SLOTS_N;
316
+ return null;
317
+ }
318
+
319
+ export function durationIndexFromLabel(label?: string | null): number | null {
320
+ if (label === '1 Month') return 0;
321
+ if (label === '1 Year') return 1;
322
+ if (label === '5 Years') return 2;
323
+ if (label === 'Perpetual') return 3;
324
+ return null;
325
+ }
326
+
327
+ export function durationLabelFromIndex(idx: number): DurationLabel | null {
328
+ return DURATION_LABELS[idx] ?? null;
329
+ }
330
+
331
+ // ------------------------------------------------------------
332
+ // EVENTS-ONLY CONTRACT
333
+ //
334
+ // A SmartContract subclass with the same `events: { ... }` declaration as
335
+ // LicensingApp but no `@method` bodies. Used by archive-replay code to call
336
+ // `.fetchEvents()` without depending on the private circuit source.
337
+ // ------------------------------------------------------------
338
+
339
+ export class LicensingAppEventsContract extends SmartContract {
340
+ events: any = {
341
+ appCreated: AppCreatedEvent,
342
+ licenseIssued: LicenseIssuedEvent,
343
+ licenseIssuedNoEscrow: LicenseIssuedEvent,
344
+ licenseRenewed: ExpiryUpdateEvent,
345
+ appRetired: AppRetiredEvent,
346
+ refundIssued: Field,
347
+ fundsReleased: Field,
348
+ priceUpdated: PriceUpdatedEvent,
349
+ deviceBound: DeviceBoundEvent,
350
+ deviceUnbound: DeviceUnboundEvent,
351
+ licenseTransferred: LicenseTransferredEvent,
352
+ vendorChanged: VendorChangedEvent,
353
+ payoutProposed: PayoutProposedEvent,
354
+ payoutChanged: PayoutChangedEvent,
355
+ platformChanged: PlatformChangedEvent,
356
+ };
357
+ }
358
+
359
+ // State-slot layout mirrors LicensingApp's declareState(). Read-only from here
360
+ // — no @method bodies exist. Consumers use `.version.get()`, `.licensesRoot.get()`
361
+ // etc. after fetchAccount(). `pricesPacked` is a single Field with the four
362
+ // 63-bit tier prices packed as [pM | pY<<63 | pF<<126 | pP<<189]; unpack with
363
+ // `unpackPrices` if you need the individual tiers off-chain.
364
+ declareState(LicensingAppEventsContract, {
365
+ licensesRoot: Field,
366
+ vendor: PublicKey,
367
+ escrowRoot: Field,
368
+ pricesPacked: Field,
369
+ version: UInt32,
370
+ ancestorsRoot: Field,
371
+ forkActivationSlot: UInt32,
372
+ deviceCap: UInt32,
373
+ retired: Field,
374
+ successor: PublicKey,
375
+ payout: PublicKey,
376
+ pendingPayout: PublicKey,
377
+ pendingPayoutSlot: UInt32,
378
+ platform: PublicKey,
379
+ treasury: PublicKey,
380
+ platformEpoch: UInt32,
381
+ vendorNonce: UInt32,
382
+ networkId: Field,
383
+ } as any);
384
+
385
+ // Off-chain mirror of the in-circuit price packing (see LicensingApp.ts). Kept
386
+ // here so the events service can decode `pricesPacked` slots without pulling
387
+ // the private contract source. Layout: four 63-bit tiers = 252 bits, fits in
388
+ // the ~254-bit Field. Each tier's high bit is unused — a UInt64 written by
389
+ // packPrices is range-checked to 63 bits in circuit.
390
+ export function unpackPrices(packed: Field): {
391
+ priceMonthly: UInt64;
392
+ priceYearly: UInt64;
393
+ priceFiveYear: UInt64;
394
+ pricePerpetual: UInt64;
395
+ } {
396
+ const raw = packed.toBigInt();
397
+ const mask = (1n << 63n) - 1n;
398
+ return {
399
+ priceMonthly: UInt64.from(raw & mask),
400
+ priceYearly: UInt64.from((raw >> 63n) & mask),
401
+ priceFiveYear: UInt64.from((raw >> 126n) & mask),
402
+ pricePerpetual: UInt64.from((raw >> 189n) & mask),
403
+ };
404
+ }
405
+
406
+ // ------------------------------------------------------------
407
+ // SUPPORTED SOURCE VERSIONS
408
+ //
409
+ // On-chain `version` slot is set at init() and can never change (setPermissions
410
+ // is impossible; setVerificationKey is impossibleDuringCurrentVersion). A
411
+ // backend / verifier that only understands v1 state decodes must refuse to serve
412
+ // a zkApp deployed from a different source revision — otherwise it could
413
+ // mis-decode events or feed a stale state format to the verifier.
414
+ //
415
+ // Add new versions here once the corresponding decoder / verifier code path
416
+ // lands. Never remove: dropping a version is a support-removal decision that
417
+ // belongs in a release note, not silent breakage.
418
+ // ------------------------------------------------------------
419
+
420
+ export const SUPPORTED_LICENSING_APP_VERSIONS: readonly number[] = [1];
421
+
422
+ // Read the immutable `version` slot from a deployed LicensingApp. Returns null
423
+ // when the account is not (yet) a zkApp — caller decides whether to retry
424
+ // (deploy still pending) or reject (wrong address).
425
+ export async function fetchLicensingAppVersion(zkAppAddress: string): Promise<number | null> {
426
+ const pub = PublicKey.fromBase58(zkAppAddress);
427
+ await fetchAccount({ publicKey: pub });
428
+ const contract: any = new LicensingAppEventsContract(pub);
429
+ try {
430
+ const v = contract.version.get();
431
+ return Number(v.toString());
432
+ } catch {
433
+ return null;
434
+ }
435
+ }
436
+
437
+ // ------------------------------------------------------------
438
+ // CANONICAL VK HASHES
439
+ //
440
+ // Per-generation, append-only. The backend boots with `LicensingApp.compile()`
441
+ // and asserts the resulting VK hash matches `CANONICAL_VK_HASH` (the current
442
+ // generation's row); the verify service resolves an address's generation via
443
+ // AppRecord.deploymentHistory and asserts the on-chain VK hash matches
444
+ // `CANONICAL_VK_HASHES[generation]`. Retired-generation licenses (e.g. a
445
+ // 5-year buyer whose license lives on v1 after the platform bumped to v2/v3)
446
+ // continue to verify because the historical row is still present. A genuine
447
+ // old circuit deployed at a fresh address fails: the address is not in any
448
+ // `deploymentHistory[]`, so the resolver returns null and verify rejects.
449
+ //
450
+ // Rules:
451
+ // - Append the new generation's row at each circuit release; never
452
+ // delete historical rows.
453
+ // - Bump `CURRENT_GENERATION` to the new key alongside the row.
454
+ // - `CANONICAL_VK_HASH` is derived from the record — drift test and backend
455
+ // boot-assert are unchanged.
456
+ // ------------------------------------------------------------
457
+
458
+ export const CANONICAL_VK_HASHES: Readonly<Record<number, string>> = {
459
+ 1: '24356937757056509770089463595975375774248178850824042790271400407790662393035',
460
+ // 2: '…' — append at each circuit release, never delete.
461
+ };
462
+
463
+ export const CURRENT_GENERATION: number = 1;
464
+
465
+ export const CANONICAL_VK_HASH: string = CANONICAL_VK_HASHES[CURRENT_GENERATION];
@@ -0,0 +1,31 @@
1
+ // Copyright (c) 2025-2026 zkLicensing project developers
2
+ // SPDX-License-Identifier: BUSL-1.1
3
+
4
+ /**
5
+ * contractTypes.ts — `zklicensing/contract-types`
6
+ *
7
+ * The two contract types the platform runs and where they meet: the
8
+ * verification-key table by type. A missing type anywhere (manifest,
9
+ * well-known file, record) means `licensing`, so everything published
10
+ * before permits existed keeps its meaning.
11
+ */
12
+
13
+ import { CANONICAL_VK_HASHES } from './contractInterface.js';
14
+ import { CANONICAL_PERMIT_VK_HASHES } from './permitInterface.js';
15
+
16
+ export type ContractType = 'licensing' | 'permits';
17
+ export const CONTRACT_TYPES: readonly ContractType[] = ['licensing', 'permits'];
18
+
19
+ /** Canonical VK hashes by contract type, each keyed by generation. */
20
+ export const CANONICAL_VK_HASHES_BY_TYPE: Readonly<Record<ContractType, Readonly<Record<number, string>>>> = {
21
+ licensing: CANONICAL_VK_HASHES,
22
+ permits: CANONICAL_PERMIT_VK_HASHES,
23
+ };
24
+
25
+ /** Reads an optional `contractType` field; absent or unknown-but-empty → licensing. */
26
+ export function contractTypeOf(value: { contractType?: unknown } | null | undefined): ContractType {
27
+ const t = value?.contractType;
28
+ if (t === undefined || t === null || t === '') return 'licensing';
29
+ if (t === 'licensing' || t === 'permits') return t;
30
+ throw new Error(`unknown contractType: ${String(t)}`);
31
+ }