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.
- package/README.md +417 -201
- package/build/agent.d.ts +371 -0
- package/build/agent.d.ts.map +1 -0
- package/build/agent.js +553 -0
- package/build/agent.js.map +1 -0
- package/build/agentActions.d.ts +841 -0
- package/build/agentActions.d.ts.map +1 -0
- package/build/agentActions.js +581 -0
- package/build/agentActions.js.map +1 -0
- package/build/agentRecord.d.ts +83 -0
- package/build/agentRecord.d.ts.map +1 -0
- package/build/agentRecord.js +69 -0
- package/build/agentRecord.js.map +1 -0
- package/build/agentVendor.d.ts +220 -0
- package/build/agentVendor.d.ts.map +1 -0
- package/build/agentVendor.js +306 -0
- package/build/agentVendor.js.map +1 -0
- package/build/appsLookup.d.ts +14 -0
- package/build/appsLookup.d.ts.map +1 -0
- package/build/appsLookup.js +12 -0
- package/build/appsLookup.js.map +1 -0
- package/build/archiveBootstrap.d.ts +31 -5
- package/build/archiveBootstrap.d.ts.map +1 -0
- package/build/archiveBootstrap.js +140 -47
- package/build/archiveBootstrap.js.map +1 -0
- package/build/atomicJson.d.ts +5 -0
- package/build/atomicJson.d.ts.map +1 -0
- package/build/atomicJson.js +55 -0
- package/build/atomicJson.js.map +1 -0
- package/build/bindClient.d.ts +85 -0
- package/build/bindClient.d.ts.map +1 -0
- package/build/bindClient.js +187 -0
- package/build/bindClient.js.map +1 -0
- package/build/buyClient.d.ts +27 -4
- package/build/buyClient.d.ts.map +1 -0
- package/build/buyClient.js +72 -26
- package/build/buyClient.js.map +1 -0
- package/build/chainHead.d.ts +61 -0
- package/build/chainHead.d.ts.map +1 -0
- package/build/chainHead.js +88 -0
- package/build/chainHead.js.map +1 -0
- package/build/compactMerkleMap.d.ts +72 -0
- package/build/compactMerkleMap.d.ts.map +1 -0
- package/build/compactMerkleMap.js +224 -0
- package/build/compactMerkleMap.js.map +1 -0
- package/build/contractInterface.d.ts +191 -51
- package/build/contractInterface.d.ts.map +1 -0
- package/build/contractInterface.js +234 -43
- package/build/contractInterface.js.map +1 -0
- package/build/contractTypes.d.ts +9 -0
- package/build/contractTypes.d.ts.map +1 -0
- package/build/contractTypes.js +28 -0
- package/build/contractTypes.js.map +1 -0
- package/build/countryStats.d.ts +12 -3
- package/build/countryStats.d.ts.map +1 -0
- package/build/countryStats.js +87 -23
- package/build/countryStats.js.map +1 -0
- package/build/deployClient.d.ts +138 -24
- package/build/deployClient.d.ts.map +1 -0
- package/build/deployClient.js +91 -31
- package/build/deployClient.js.map +1 -0
- package/build/deviceIdentity.d.ts +68 -0
- package/build/deviceIdentity.d.ts.map +1 -0
- package/build/deviceIdentity.js +209 -0
- package/build/deviceIdentity.js.map +1 -0
- package/build/freezeClient.d.ts +26 -6
- package/build/freezeClient.d.ts.map +1 -0
- package/build/freezeClient.js +30 -6
- package/build/freezeClient.js.map +1 -0
- package/build/generationClient.d.ts +6 -3
- package/build/generationClient.d.ts.map +1 -0
- package/build/generationClient.js +25 -38
- package/build/generationClient.js.map +1 -0
- package/build/holderSecret.d.ts +70 -0
- package/build/holderSecret.d.ts.map +1 -0
- package/build/holderSecret.js +76 -0
- package/build/holderSecret.js.map +1 -0
- package/build/index.d.ts +38 -38
- package/build/index.d.ts.map +1 -0
- package/build/index.js +97 -121
- package/build/index.js.map +1 -0
- package/build/keysClient.d.ts +142 -0
- package/build/keysClient.d.ts.map +1 -0
- package/build/keysClient.js +109 -0
- package/build/keysClient.js.map +1 -0
- package/build/licenseLeaf.d.ts +76 -0
- package/build/licenseLeaf.d.ts.map +1 -0
- package/build/licenseLeaf.js +189 -0
- package/build/licenseLeaf.js.map +1 -0
- package/build/licenseRecord.d.ts +68 -5
- package/build/licenseRecord.d.ts.map +1 -0
- package/build/licenseRecord.js +26 -1
- package/build/licenseRecord.js.map +1 -0
- package/build/licenseStore.d.ts +81 -4
- package/build/licenseStore.d.ts.map +1 -0
- package/build/licenseStore.js +400 -30
- package/build/licenseStore.js.map +1 -0
- package/build/licenseTrustless.d.ts +43 -0
- package/build/licenseTrustless.d.ts.map +1 -0
- package/build/licenseTrustless.js +180 -0
- package/build/licenseTrustless.js.map +1 -0
- package/build/manifestVerify.d.ts +29 -1
- package/build/manifestVerify.d.ts.map +1 -0
- package/build/manifestVerify.js +46 -4
- package/build/manifestVerify.js.map +1 -0
- package/build/minaPriceHistory.d.ts +9 -0
- package/build/minaPriceHistory.d.ts.map +1 -0
- package/build/minaPriceHistory.js +152 -0
- package/build/minaPriceHistory.js.map +1 -0
- package/build/onChainRoot.d.ts +60 -0
- package/build/onChainRoot.d.ts.map +1 -0
- package/build/onChainRoot.js +74 -0
- package/build/onChainRoot.js.map +1 -0
- package/build/ownershipLicenseHash.d.ts +25 -0
- package/build/ownershipLicenseHash.d.ts.map +1 -0
- package/build/ownershipLicenseHash.js +29 -0
- package/build/ownershipLicenseHash.js.map +1 -0
- package/build/ownershipSignature.d.ts +153 -0
- package/build/ownershipSignature.d.ts.map +1 -0
- package/build/ownershipSignature.js +297 -0
- package/build/ownershipSignature.js.map +1 -0
- package/build/permitCheckFlow.d.ts +155 -0
- package/build/permitCheckFlow.d.ts.map +1 -0
- package/build/permitCheckFlow.js +196 -0
- package/build/permitCheckFlow.js.map +1 -0
- package/build/permitDescribe.d.ts +124 -0
- package/build/permitDescribe.d.ts.map +1 -0
- package/build/permitDescribe.js +324 -0
- package/build/permitDescribe.js.map +1 -0
- package/build/permitInterface.d.ts +85 -0
- package/build/permitInterface.d.ts.map +1 -0
- package/build/permitInterface.js +121 -0
- package/build/permitInterface.js.map +1 -0
- package/build/permitMessages.d.ts +313 -0
- package/build/permitMessages.d.ts.map +1 -0
- package/build/permitMessages.js +462 -0
- package/build/permitMessages.js.map +1 -0
- package/build/permitProof.d.ts +108 -0
- package/build/permitProof.d.ts.map +1 -0
- package/build/permitProof.js +202 -0
- package/build/permitProof.js.map +1 -0
- package/build/permitRegistryRebuild.d.ts +166 -0
- package/build/permitRegistryRebuild.d.ts.map +1 -0
- package/build/permitRegistryRebuild.js +347 -0
- package/build/permitRegistryRebuild.js.map +1 -0
- package/build/permitTree.d.ts +88 -0
- package/build/permitTree.d.ts.map +1 -0
- package/build/permitTree.js +230 -0
- package/build/permitTree.js.map +1 -0
- package/build/permitsClient.d.ts +680 -0
- package/build/permitsClient.d.ts.map +1 -0
- package/build/permitsClient.js +597 -0
- package/build/permitsClient.js.map +1 -0
- package/build/proveErrors.d.ts +50 -0
- package/build/proveErrors.d.ts.map +1 -0
- package/build/proveErrors.js +76 -0
- package/build/proveErrors.js.map +1 -0
- package/build/receipt.d.ts +95 -0
- package/build/receipt.d.ts.map +1 -0
- package/build/receipt.js +84 -0
- package/build/receipt.js.map +1 -0
- package/build/refundClient.d.ts +7 -12
- package/build/refundClient.d.ts.map +1 -0
- package/build/refundClient.js +52 -21
- package/build/refundClient.js.map +1 -0
- package/build/registryTrustVerify.d.ts +78 -0
- package/build/registryTrustVerify.d.ts.map +1 -0
- package/build/registryTrustVerify.js +129 -0
- package/build/registryTrustVerify.js.map +1 -0
- package/build/releaseClient.d.ts +26 -0
- package/build/releaseClient.d.ts.map +1 -0
- package/build/releaseClient.js +65 -0
- package/build/releaseClient.js.map +1 -0
- package/build/renewClient.d.ts +28 -2
- package/build/renewClient.d.ts.map +1 -0
- package/build/renewClient.js +31 -27
- package/build/renewClient.js.map +1 -0
- package/build/seatName.d.ts +8 -0
- package/build/seatName.d.ts.map +1 -0
- package/build/seatName.js +136 -0
- package/build/seatName.js.map +1 -0
- package/build/slotConstants.d.ts +15 -0
- package/build/slotConstants.d.ts.map +1 -0
- package/build/slotConstants.js +17 -0
- package/build/slotConstants.js.map +1 -0
- package/build/statsClient.d.ts +61 -7
- package/build/statsClient.d.ts.map +1 -0
- package/build/statsClient.js +44 -5
- package/build/statsClient.js.map +1 -0
- package/build/successorChain.d.ts +35 -0
- package/build/successorChain.d.ts.map +1 -0
- package/build/successorChain.js +69 -0
- package/build/successorChain.js.map +1 -0
- package/build/taxId.d.ts +44 -0
- package/build/taxId.d.ts.map +1 -0
- package/build/taxId.js +212 -0
- package/build/taxId.js.map +1 -0
- package/build/trustAnchorVerify.d.ts +49 -0
- package/build/trustAnchorVerify.d.ts.map +1 -0
- package/build/trustAnchorVerify.js +432 -0
- package/build/trustAnchorVerify.js.map +1 -0
- package/build/trustAnchorVerifyCli.d.ts +45 -0
- package/build/trustAnchorVerifyCli.d.ts.map +1 -0
- package/build/trustAnchorVerifyCli.js +235 -0
- package/build/trustAnchorVerifyCli.js.map +1 -0
- package/build/txBroadcastClient.d.ts +26 -0
- package/build/txBroadcastClient.d.ts.map +1 -0
- package/build/txBroadcastClient.js +20 -0
- package/build/txBroadcastClient.js.map +1 -0
- package/build/unbindClient.d.ts +77 -0
- package/build/unbindClient.d.ts.map +1 -0
- package/build/unbindClient.js +169 -0
- package/build/unbindClient.js.map +1 -0
- package/build/units.d.ts +28 -0
- package/build/units.d.ts.map +1 -0
- package/build/units.js +65 -0
- package/build/units.js.map +1 -0
- package/build/vatRates.d.ts +54 -0
- package/build/vatRates.d.ts.map +1 -0
- package/build/vatRates.js +82 -0
- package/build/vatRates.js.map +1 -0
- package/build/vatRatesData.d.ts +5 -0
- package/build/vatRatesData.d.ts.map +1 -0
- package/build/vatRatesData.js +42 -0
- package/build/vatRatesData.js.map +1 -0
- package/build/vendorAccountClient.d.ts +66 -0
- package/build/vendorAccountClient.d.ts.map +1 -0
- package/build/vendorAccountClient.js +47 -0
- package/build/vendorAccountClient.js.map +1 -0
- package/build/verifyCore.d.ts +4 -1
- package/build/verifyCore.d.ts.map +1 -0
- package/build/verifyCore.js +21 -10
- package/build/verifyCore.js.map +1 -0
- package/build/verifyOffline.d.ts +85 -0
- package/build/verifyOffline.d.ts.map +1 -0
- package/build/verifyOffline.js +139 -0
- package/build/verifyOffline.js.map +1 -0
- package/build/verifyService.d.ts.map +1 -0
- package/build/verifyService.js +571 -501
- package/build/verifyService.js.map +1 -0
- package/build/verifyWithAncestors.d.ts +19 -0
- package/build/verifyWithAncestors.d.ts.map +1 -0
- package/build/verifyWithAncestors.js +127 -0
- package/build/verifyWithAncestors.js.map +1 -0
- package/build/zipToState.d.ts +2 -0
- package/build/zipToState.d.ts.map +1 -0
- package/build/zipToState.js +94 -0
- package/build/zipToState.js.map +1 -0
- package/package.json +141 -16
- package/src/agent.ts +682 -0
- package/src/agentActions.ts +637 -0
- package/src/agentRecord.ts +121 -0
- package/src/agentVendor.ts +356 -0
- package/src/appsLookup.ts +48 -0
- package/src/archiveBootstrap.ts +386 -0
- package/src/atomicJson.ts +53 -0
- package/src/bindClient.ts +297 -0
- package/src/buyClient.ts +344 -0
- package/src/chainHead.ts +106 -0
- package/src/compactMerkleMap.ts +222 -0
- package/src/contractInterface.ts +465 -0
- package/src/contractTypes.ts +31 -0
- package/src/countryStats.ts +200 -0
- package/src/deployClient.ts +517 -0
- package/src/deviceIdentity.ts +249 -0
- package/src/freezeClient.ts +162 -0
- package/src/generationClient.ts +145 -0
- package/src/holderSecret.ts +91 -0
- package/src/index.ts +362 -0
- package/src/keysClient.ts +135 -0
- package/src/licenseLeaf.ts +221 -0
- package/src/licenseRecord.ts +190 -0
- package/src/licenseStore.ts +767 -0
- package/src/licenseTrustless.ts +207 -0
- package/src/manifestVerify.ts +164 -0
- package/src/minaPriceHistory.ts +166 -0
- package/src/onChainRoot.ts +90 -0
- package/src/ownershipLicenseHash.ts +31 -0
- package/src/ownershipSignature.ts +421 -0
- package/src/permitCheckFlow.ts +227 -0
- package/src/permitDescribe.ts +362 -0
- package/src/permitInterface.ts +146 -0
- package/src/permitMessages.ts +572 -0
- package/src/permitProof.ts +230 -0
- package/src/permitRegistryRebuild.ts +423 -0
- package/src/permitTree.ts +232 -0
- package/src/permitsClient.ts +746 -0
- package/src/proveErrors.ts +98 -0
- package/src/receipt.ts +156 -0
- package/src/refundClient.ts +177 -0
- package/src/registryTrustVerify.ts +192 -0
- package/src/releaseClient.ts +101 -0
- package/src/renewClient.ts +234 -0
- package/src/seatName.ts +165 -0
- package/src/slotConstants.ts +18 -0
- package/src/statsClient.ts +249 -0
- package/src/successorChain.ts +87 -0
- package/src/taxId.ts +227 -0
- package/src/trustAnchorVerify.ts +550 -0
- package/src/trustAnchorVerifyCli.ts +234 -0
- package/src/txBroadcastClient.ts +48 -0
- package/src/unbindClient.ts +233 -0
- package/src/units.ts +74 -0
- package/src/vatRates.ts +100 -0
- package/src/vatRatesData.ts +43 -0
- package/src/vendorAccountClient.ts +120 -0
- package/src/verifyCore.ts +100 -0
- package/src/verifyOffline.ts +212 -0
- package/src/verifyService.ts +982 -0
- package/src/verifyWithAncestors.ts +163 -0
- package/src/zipToState.ts +94 -0
- package/build/licenseProof.d.ts +0 -26
- package/build/licenseProof.js +0 -41
- package/build/migrateClient.d.ts +0 -43
- package/build/migrateClient.js +0 -47
- package/build/ownership.d.ts +0 -34
- 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
|
+
}
|