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,43 @@
1
+ // Copyright (c) 2025-2026 zkLicensing project developers
2
+ // SPDX-License-Identifier: BUSL-1.1
3
+ //
4
+ // GENERATED by scripts/build-vat-rates.mjs — do not edit by hand.
5
+ //
6
+ // Standard VAT rates (percent) with the date each took effect, per country.
7
+ // Source: ibericode/vat-rates, https://raw.githubusercontent.com/ibericode/vat-rates/master/vat-rates.json
8
+ // (MIT License, Copyright 2019 Danny van Kooten). Fetched 2026-10-13.
9
+
10
+ export const VAT_RATES_SOURCE = 'ibericode/vat-rates (MIT)';
11
+ export const VAT_RATES_FETCHED = '2026-10-13';
12
+
13
+ /** country → [effective-from date, standard rate %][], oldest first. */
14
+ export const STANDARD_VAT_RATES: Readonly<Record<string, ReadonlyArray<readonly [string, number]>>> = {
15
+ AT: [['1970-01-01', 20], ['2016-01-01', 20]],
16
+ BE: [['1970-01-01', 21]],
17
+ BG: [['1970-01-01', 20]],
18
+ CY: [['1970-01-01', 19]],
19
+ CZ: [['1970-01-01', 21], ['2024-01-01', 21]],
20
+ DE: [['1970-01-01', 19], ['2020-07-01', 16], ['2021-01-01', 19]],
21
+ DK: [['1970-01-01', 25]],
22
+ EE: [['1970-01-01', 20], ['2024-01-01', 22], ['2025-01-01', 22], ['2025-07-01', 24]],
23
+ ES: [['1970-01-01', 21]],
24
+ FI: [['1970-01-01', 24], ['2024-09-01', 25.5], ['2026-01-01', 25.5]],
25
+ FR: [['1970-01-01', 19.6], ['2012-01-01', 19.6], ['2014-01-01', 20]],
26
+ GB: [['2011-01-04', 20]],
27
+ GR: [['1970-01-01', 23], ['2016-01-01', 23], ['2016-06-01', 24]],
28
+ HR: [['1970-01-01', 25]],
29
+ HU: [['1970-01-01', 27]],
30
+ IE: [['1970-01-01', 23], ['2020-09-01', 21], ['2021-03-01', 23]],
31
+ IT: [['1970-01-01', 22]],
32
+ LT: [['1970-01-01', 21]],
33
+ LU: [['1970-01-01', 15], ['2015-01-01', 17], ['2016-01-01', 17], ['2023-01-01', 16], ['2024-01-01', 17]],
34
+ LV: [['1970-01-01', 21]],
35
+ MT: [['1970-01-01', 18]],
36
+ NL: [['1970-01-01', 19], ['2012-10-01', 21], ['2019-01-01', 21]],
37
+ PL: [['1970-01-01', 23]],
38
+ PT: [['1970-01-01', 23]],
39
+ RO: [['1970-01-01', 24], ['2016-01-01', 20], ['2017-01-01', 19], ['2025-08-01', 21]],
40
+ SE: [['1970-01-01', 25]],
41
+ SI: [['1970-01-01', 22]],
42
+ SK: [['1970-01-01', 19], ['2011-01-01', 20], ['2025-01-01', 23]],
43
+ };
@@ -0,0 +1,120 @@
1
+ // Copyright (c) 2025-2026 zkLicensing project developers
2
+ // SPDX-License-Identifier: BUSL-1.1
3
+
4
+ /**
5
+ * vendorAccountClient.ts
6
+ *
7
+ * Browser-safe client for the vendor's shared legal/billing block —
8
+ * the fields that must stay consistent across every app registered by
9
+ * one vendor wallet (legal entity name, billing country and address,
10
+ * VAT id, company registry). One PATCH updates all of the vendor's
11
+ * apps atomically on the backend, so a legal-entity rename or address
12
+ * move doesn't leave old apps advertising stale CRD Art. 6 disclosures.
13
+ *
14
+ * Signature covers the vendor address, mirroring the "sign the
15
+ * resource id" pattern PUT /apps/:appId uses. The backend verifies
16
+ * against the on-chain vendor recorded on the app records.
17
+ */
18
+
19
+ export type VendorLegalBlock = {
20
+ country?: string;
21
+ legalName?: string;
22
+ billingAddress?: {
23
+ line1: string;
24
+ line2?: string;
25
+ city: string;
26
+ postalCode: string;
27
+ };
28
+ invoiceEmail?: string;
29
+ supportEmail?: string;
30
+ vatId?: string;
31
+ companyRegistrationRegistry?: string;
32
+ companyRegistrationNumber?: string;
33
+ termsOfServiceUrl?: string;
34
+ privacyPolicyUrl?: string;
35
+ refundPolicyUrl?: string;
36
+ };
37
+
38
+ export type GetVendorLegalRequest = {
39
+ keeperUrl: string;
40
+ vendorAddress: string;
41
+ vendorSessionToken: string;
42
+ fetcher?: typeof fetch;
43
+ signal?: AbortSignal;
44
+ };
45
+
46
+ export type GetVendorLegalResponse = {
47
+ // null when the wallet has no apps yet — register form should prompt
48
+ // for the block instead of treating first-app registration as an error.
49
+ legal: VendorLegalBlock | null;
50
+ };
51
+
52
+ export type UpdateVendorLegalRequest = {
53
+ keeperUrl: string;
54
+ vendorAddress: string;
55
+ payload: VendorLegalBlock;
56
+ /** The vendor's session (`VendorSession` token) for the network its apps
57
+ * are on — mainnet if any app is on mainnet. Or give a `fetcher` that
58
+ * adds it. */
59
+ sessionToken?: string;
60
+ /** Ignored by the keeper (the old signature over the vendor's own address,
61
+ * which could be replayed); kept so existing callers compile. */
62
+ signature?: { field: string; scalar: string };
63
+ fetcher?: typeof fetch;
64
+ signal?: AbortSignal;
65
+ };
66
+
67
+ export type UpdateVendorLegalResponse = {
68
+ ok: true;
69
+ appsUpdated: number;
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
+ // GET /vendors/:address/legal — fetch the shared legal block for a wallet.
85
+ // Returns `{ legal: null }` for wallets with no registered apps.
86
+ export async function getVendorLegal(req: GetVendorLegalRequest): Promise<GetVendorLegalResponse> {
87
+ const fetcher = req.fetcher ?? fetch;
88
+ const resp = await fetcher(
89
+ `${baseUrl(req.keeperUrl)}/vendors/${encodeURIComponent(req.vendorAddress)}/legal`,
90
+ {
91
+ signal: req.signal,
92
+ headers: { Authorization: `VendorSession ${req.vendorSessionToken}` },
93
+ },
94
+ );
95
+ if (!resp.ok) throw new Error(await readJsonError(resp));
96
+ return await resp.json() as GetVendorLegalResponse;
97
+ }
98
+
99
+ // PATCH /vendors/:address/legal — updates the block on EVERY app the vendor
100
+ // owns in a single atomic sweep. Authorised by the vendor's session. Bypasses the pendingEdit staging that
101
+ // per-app metadata edits go through; legal fields are factual (not
102
+ // marketing copy) so admin review doesn't gain anything and staleness
103
+ // hurts CRD Art. 6 compliance.
104
+ export async function updateVendorLegal(req: UpdateVendorLegalRequest): Promise<UpdateVendorLegalResponse> {
105
+ const fetcher = req.fetcher ?? fetch;
106
+ const resp = await fetcher(
107
+ `${baseUrl(req.keeperUrl)}/vendors/${encodeURIComponent(req.vendorAddress)}/legal`,
108
+ {
109
+ method: 'PATCH',
110
+ headers: {
111
+ 'Content-Type': 'application/json',
112
+ ...(req.sessionToken ? { Authorization: `VendorSession ${req.sessionToken}` } : {}),
113
+ },
114
+ signal: req.signal,
115
+ body: JSON.stringify({ payload: req.payload }),
116
+ },
117
+ );
118
+ if (!resp.ok) throw new Error(await readJsonError(resp));
119
+ return await resp.json() as UpdateVendorLegalResponse;
120
+ }
@@ -0,0 +1,100 @@
1
+ // Copyright (c) 2025-2026 zkLicensing project developers
2
+ // SPDX-License-Identifier: BUSL-1.1
3
+
4
+ /**
5
+ * verifyCore.ts
6
+ *
7
+ * Pure verification logic shared by keeperService and verifyService.
8
+ * No HTTP, no network — takes already-fetched on-chain state and
9
+ * performs Merkle inclusion check + expiry logic.
10
+ */
11
+
12
+ import { Field } from 'o1js';
13
+ import { buildMap, type LicenseRecord } from './licenseStore.js';
14
+ import { GRACE_PERIOD_N, MS_PER_SLOT_N, SLOTS_PER_DAY_N, effectiveExpirySlot } from './contractInterface.js';
15
+
16
+ export type VerifyResponse = {
17
+ valid: boolean;
18
+ expiresAt: string | null;
19
+ expirySlot: number;
20
+ // Set when a license that lapsed during the pre-fork sales freeze counts
21
+ // from the app's fork slot (see effectiveExpirySlot); validity uses it.
22
+ effectiveExpirySlot?: number;
23
+ inGracePeriod: boolean;
24
+ // Whole days until expiry. Negative while in the grace period (down to ~-7
25
+ // — GRACE_PERIOD_N is 6 720 slots = 7 days) and below that once expired.
26
+ // 0 when expirySlot is unknown (unknown/refunded/root-mismatch).
27
+ remainingDays: number;
28
+ reason: string | null;
29
+ };
30
+
31
+ function computeRemainingDays(expirySlot: number, currentSlot: number): number {
32
+ if (expirySlot === 0) return 0;
33
+ return Math.floor((expirySlot - currentSlot) / SLOTS_PER_DAY_N);
34
+ }
35
+
36
+ /**
37
+ * Core license verification.
38
+ *
39
+ * @param zkAppAddress - app address (composite key with licenseHash)
40
+ * @param licenseHash - Poseidon hash of the buyer's Ed25519 pubkey (see ownershipLicenseHash.ts) as a field string
41
+ * @param records - current off-chain license records (from licenseStore)
42
+ * @param onChainRoot - zkApp appState[0] fetched from Mina
43
+ * @param currentSlot - current blockchain slot (globalSlotSinceGenesis)
44
+ * @param forkActivationSlot - the app's forkActivationSlot state (0: none)
45
+ */
46
+ export function verifyLicenseCore(params: {
47
+ zkAppAddress: string;
48
+ licenseHash: string;
49
+ records: LicenseRecord[];
50
+ onChainRoot: string;
51
+ currentSlot: number;
52
+ forkActivationSlot?: number;
53
+ }): VerifyResponse {
54
+ const { zkAppAddress, licenseHash, records, onChainRoot, currentSlot } = params;
55
+
56
+ const map = buildMap(records, zkAppAddress);
57
+ const key = Field(licenseHash);
58
+ const currentValue = map.get(key).toString();
59
+ const witness = map.getWitness(key);
60
+
61
+ const [computedRoot] = witness.computeRootAndKey(Field(currentValue));
62
+
63
+ if (computedRoot.toString() !== onChainRoot) {
64
+ return { valid: false, expiresAt: null, expirySlot: 0, inGracePeriod: false, remainingDays: 0, reason: 'License state does not match on-chain root' };
65
+ }
66
+
67
+ // Post-LicenseLeaf: the map value is `leaf.hash()`, not raw expirySlot.
68
+ // Empty slots still hash to Field(0). Look up the record to recover
69
+ // expirySlot; `buildMap` excludes refunded/empty entries, so a missing
70
+ // record here means either "never bought" or "refunded" (record kept but
71
+ // expirySlot cleared to 0).
72
+ const record = records.find(r => r.zkAppAddress === zkAppAddress && r.licenseHash === licenseHash);
73
+
74
+ if (currentValue === '0') {
75
+ const reason = record?.refunded ? 'License has been refunded' : 'License not found';
76
+ return { valid: false, expiresAt: null, expirySlot: 0, inGracePeriod: false, remainingDays: 0, reason };
77
+ }
78
+
79
+ const expirySlot = record?.expirySlot ?? 0;
80
+ const effective = effectiveExpirySlot(expirySlot, params.forkActivationSlot ?? 0);
81
+ const bumped = effective !== expirySlot ? { effectiveExpirySlot: effective } : {};
82
+ const inGracePeriod = currentSlot > effective && currentSlot <= effective + GRACE_PERIOD_N;
83
+ const expired = currentSlot > effective + GRACE_PERIOD_N;
84
+ const expiresAt = new Date(Date.now() + (effective - currentSlot) * MS_PER_SLOT_N).toISOString();
85
+ const remainingDays = computeRemainingDays(effective, currentSlot);
86
+
87
+ if (expired) {
88
+ return { valid: false, expiresAt, expirySlot, ...bumped, inGracePeriod: false, remainingDays, reason: 'License has expired' };
89
+ }
90
+
91
+ return {
92
+ valid: true,
93
+ expiresAt,
94
+ expirySlot,
95
+ ...bumped,
96
+ inGracePeriod,
97
+ remainingDays,
98
+ reason: inGracePeriod ? 'License is in grace period — renew to avoid interruption' : null,
99
+ };
100
+ }
@@ -0,0 +1,212 @@
1
+ // Copyright (c) 2025-2026 zkLicensing project developers
2
+ // SPDX-License-Identifier: BUSL-1.1
3
+
4
+ /**
5
+ * verifyOffline.ts
6
+ *
7
+ * Non-custodial device-binding offline verifier. Given:
8
+ * - a serialized MerkleMapWitness for the license entry
9
+ * - a `licensesRoot` value the caller has anchored to (see "Trust anchoring")
10
+ * - the full LicenseLeaf field set (licenseHash, expirySlot, deviceCap,
11
+ * deviceSlots[]) as of that sync — the leaf carries no separate
12
+ * owner-pubkey commitment because `licenseHash` IS it (buy circuit
13
+ * asserts equality)
14
+ * - the caller's device public key
15
+ *
16
+ * this module:
17
+ * 1. Reconstructs the LicenseLeaf hash from its fields.
18
+ * 2. Rebuilds the on-chain root through the witness and asserts it matches
19
+ * the passed `licensesRoot` — proves the leaf field set is authentic
20
+ * relative to that root.
21
+ * 3. If `deviceCap > 0`: computes the device commitment for the caller's
22
+ * pubkey and asserts it appears in `occupied[]`.
23
+ * 4. Asserts `expirySlot > currentSlot` (grace-period handling stays in
24
+ * the online verifier; the device-bound offline path is pass/fail).
25
+ * 5. If `deviceCap === 0`: skips the device check entirely — the app has
26
+ * binding disabled at the vendor level.
27
+ *
28
+ * Trust anchoring — where does `licensesRoot` come from?
29
+ * -------------------------------------------------------
30
+ * This module treats the passed root as ground truth. It does NOT fetch
31
+ * it. Two ways to source it:
32
+ *
33
+ * • Non-custodial (recommended). Fetch it yourself from Mina via
34
+ * `fetchOnChainLicensesRoot({ zkAppAddress, network })` from
35
+ * `zklicensing/on-chain-root`. That reads `zkapp.appState[0]` on
36
+ * the deployed LicensingApp — the same field the circuit updates on
37
+ * every buy/renew/bind/unbind/refund. No trust in the licensing
38
+ * service required.
39
+ *
40
+ * • Custodial (trusted-service). Use whatever root the licensing
41
+ * service returned on its `/verify` or `/witness` response. Faster
42
+ * (no o1js + Mina RPC round-trip) but relies on the service to
43
+ * report the true chain state. If you go this route, cross-check
44
+ * the reported root against the on-chain value on some cadence.
45
+ *
46
+ * The caller fetches the device slot state (occupied[] + freedAt[] + cap)
47
+ * from the vendor SDK or platform verifier alongside the licensesRoot on
48
+ * the sync cadence of their choice. The parallel arrays fold to
49
+ * `deviceSlotsRoot`, so any tampering with slot contents changes the leaf
50
+ * hash and fails the witness check — provided `licensesRoot` itself is
51
+ * trustworthy (see above), verifyOffline needs no trust in whoever
52
+ * served the slot arrays.
53
+ *
54
+ * Not exported at the top-level barrel by default; vendors who ship a
55
+ * device-cap app import from `zklicensing/verify-offline` explicitly.
56
+ */
57
+
58
+ import { Field, Poseidon, PublicKey, UInt32 } from 'o1js';
59
+ import { deserializeWitness, type serializeWitness } from './licenseStore.js';
60
+ import {
61
+ computeDeviceSlotsRoot,
62
+ DEVICE_SLOT_CAP_N,
63
+ leafHash,
64
+ LicenseLeaf,
65
+ packSlotTimes,
66
+ } from './licenseLeaf.js';
67
+ import { computeDeviceCommitment } from './deviceIdentity.js';
68
+
69
+ // Serialized shape callers pass in for each of the DEVICE_SLOT_CAP_N slots.
70
+ // Empty slots use `deviceCommitment: '0'` (matches the record mirror). The
71
+ // two timestamps are Mina slot numbers (UInt32-range); freedAt=0 means the
72
+ // slot has never been unbound since its bind.
73
+ export type DeviceSlotSnapshot = {
74
+ slotIndex: number;
75
+ deviceCommitment: string;
76
+ boundAtSlot: number;
77
+ freedAtSlot: number;
78
+ };
79
+
80
+ export type VerifyOfflineInput = {
81
+ // The on-chain root the caller has anchored to for this verification.
82
+ // See the "Trust anchoring" section of the module-level docstring for
83
+ // sourcing — non-custodial callers should pass the value returned by
84
+ // `fetchOnChainLicensesRoot` from `zklicensing/on-chain-root`; custodial
85
+ // callers can pass a service-reported root but forfeit the "service
86
+ // doesn't need to be trusted" property.
87
+ licensesRoot: string;
88
+ currentSlot: number;
89
+
90
+ // ── LicenseLeaf field set as of the sync ─────────────────────────────
91
+ // Together these reconstruct `leafHash(leaf)` and are matched against the
92
+ // witness. Any tampering with any field flips the reconstructed hash and
93
+ // fails the root check.
94
+ licenseHash: string; // Also serves as the on-chain owner-pubkey commitment (buy circuit asserts equality)
95
+ expirySlot: number;
96
+ deviceCap: number; // 0 disables the device-commitment check entirely
97
+ devices: DeviceSlotSnapshot[]; // exactly DEVICE_SLOT_CAP_N entries; caller pads empties
98
+
99
+ // MerkleMap witness for `licenseHash` in the licensesRoot tree, serialized
100
+ // by `serializeWitness` (same shape POST /prove/buy and the /verify handler
101
+ // both emit).
102
+ witness: ReturnType<typeof serializeWitness>;
103
+
104
+ // Caller's device pubkey — only consulted when deviceCap > 0. Omit for
105
+ // uncapped apps; the module rejects a caller-supplied pubkey with cap=0
106
+ // so a misconfigured integration surfaces loudly instead of silently
107
+ // trusting whatever was passed.
108
+ devicePubKey?: PublicKey;
109
+ };
110
+
111
+ export type VerifyOfflineResult = {
112
+ valid: boolean;
113
+ reason: string | null;
114
+ };
115
+
116
+ // Pad or trim the caller-supplied slot list to exactly DEVICE_SLOT_CAP_N and
117
+ // order by slotIndex. `computeDeviceSlotsRoot` requires the exact layout
118
+ // (occupied[i], freedAt[i], ...) that the circuit folds — mismatched length
119
+ // or ordering yields a different root and (correctly) fails the witness
120
+ // check downstream. Doing the normalization here keeps the caller free to
121
+ // pass only the occupied slots.
122
+ function normalizeSlots(devices: DeviceSlotSnapshot[]): { occupied: Field[]; freedAt: Field[]; occupiedCommitments: string[] } {
123
+ const occupied: Field[] = [];
124
+ const freedAt: Field[] = [];
125
+ const occupiedCommitments: string[] = [];
126
+ for (let i = 0; i < DEVICE_SLOT_CAP_N; i++) {
127
+ const slot = devices.find(d => d.slotIndex === i);
128
+ const occ = slot?.deviceCommitment ?? '0';
129
+ occupied.push(Field(occ));
130
+ occupiedCommitments.push(occ);
131
+ const boundU32 = UInt32.from(slot?.boundAtSlot ?? 0);
132
+ const freedU32 = UInt32.from(slot?.freedAtSlot ?? 0);
133
+ freedAt.push(packSlotTimes(boundU32, freedU32));
134
+ }
135
+ return { occupied, freedAt, occupiedCommitments };
136
+ }
137
+
138
+ /**
139
+ * Pure offline device-binding verifier. See module-level docstring.
140
+ *
141
+ * All errors are returned via `{ valid: false, reason }` rather than thrown
142
+ * — callers integrate this into a boot-time gate and don't want to catch
143
+ * around every call. `reason` is stable enough to key error UX off of.
144
+ */
145
+ export function verifyOffline(input: VerifyOfflineInput): VerifyOfflineResult {
146
+ const {
147
+ licensesRoot,
148
+ currentSlot,
149
+ licenseHash,
150
+ expirySlot,
151
+ deviceCap,
152
+ devices,
153
+ witness,
154
+ devicePubKey,
155
+ } = input;
156
+
157
+ const { occupied, freedAt, occupiedCommitments } = normalizeSlots(devices);
158
+ const deviceSlotsRoot = computeDeviceSlotsRoot(occupied, freedAt);
159
+ const deviceSlotsFilled = occupiedCommitments.filter(c => c !== '0').length;
160
+
161
+ const leaf = new LicenseLeaf({
162
+ licenseHash: Field(licenseHash),
163
+ expirySlot: UInt32.from(expirySlot),
164
+ cap: UInt32.from(deviceCap),
165
+ deviceSlotsRoot,
166
+ deviceSlotsFilled: UInt32.from(deviceSlotsFilled),
167
+ });
168
+ const expectedLeafHash = leafHash(leaf);
169
+
170
+ const restoredWitness = deserializeWitness(witness);
171
+ const [computedRoot, computedKey] = restoredWitness.computeRootAndKey(expectedLeafHash);
172
+ if (computedKey.toString() !== Field(licenseHash).toString()) {
173
+ return { valid: false, reason: 'Witness key does not match licenseHash' };
174
+ }
175
+ if (computedRoot.toString() !== licensesRoot) {
176
+ return { valid: false, reason: 'Reconstructed leaf does not match synced licensesRoot' };
177
+ }
178
+
179
+ if (expirySlot <= currentSlot) {
180
+ return { valid: false, reason: 'License has expired' };
181
+ }
182
+
183
+ if (deviceCap === 0) {
184
+ if (devicePubKey !== undefined) {
185
+ // Fail loud rather than silently accept — a caller wiring devicePubKey
186
+ // into an app that turns out to have cap=0 is almost certainly a
187
+ // misconfiguration (they think the app enforces bindings when it
188
+ // doesn't).
189
+ return { valid: false, reason: 'deviceCap is 0 for this license; do not pass devicePubKey' };
190
+ }
191
+ return { valid: true, reason: null };
192
+ }
193
+
194
+ if (!devicePubKey) {
195
+ return { valid: false, reason: 'deviceCap > 0 requires devicePubKey' };
196
+ }
197
+ const commitment = computeDeviceCommitment(devicePubKey);
198
+ if (!occupiedCommitments.includes(commitment)) {
199
+ return { valid: false, reason: 'Device is not bound to this license' };
200
+ }
201
+
202
+ return { valid: true, reason: null };
203
+ }
204
+
205
+ // Re-export the shared Poseidon fold formula so callers that want to compute
206
+ // `deviceCommitment` for a pubkey they only have as (x, isOdd) can reuse
207
+ // the identical shape the circuit uses. Preferred entry point is still
208
+ // `computeDeviceCommitment(devicePubKey)` in ./deviceIdentity.ts — this is
209
+ // for callers who only hold raw fields.
210
+ export function deviceCommitmentFromFields(x: Field, isOdd: Field): Field {
211
+ return Poseidon.hash([x, isOdd]);
212
+ }