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,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
|
+
}
|