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,386 @@
|
|
|
1
|
+
// Copyright (c) 2025-2026 zkLicensing project developers
|
|
2
|
+
// SPDX-License-Identifier: BUSL-1.1
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* archiveBootstrap.ts
|
|
6
|
+
*
|
|
7
|
+
* Two layers:
|
|
8
|
+
* - bootstrapFromArchive() fetches zkApp events and returns them normalized.
|
|
9
|
+
* No writes. Caller decides what to do with the events.
|
|
10
|
+
* - applyEventsToStore() writes the events to licenseStore.
|
|
11
|
+
*
|
|
12
|
+
* Events handled (write side):
|
|
13
|
+
* licenseIssued → applyLicenseIssued
|
|
14
|
+
* licenseIssuedNoEscrow → applyLicenseIssued (escrowless: true)
|
|
15
|
+
* licenseRenewed → updateExpiry
|
|
16
|
+
* appCreated (successor) → carryOverRecords from its predecessor (lazyBootstrap)
|
|
17
|
+
* appRetired → ignored (the app is frozen; its successor carries on)
|
|
18
|
+
* refundIssued → recordRefund
|
|
19
|
+
* fundsReleased → recordRelease
|
|
20
|
+
* deviceBound → applyDeviceBound (non-custodial binding)
|
|
21
|
+
* deviceUnbound → applyDeviceUnbound (non-custodial binding)
|
|
22
|
+
* priceUpdated → ignored (price-history tracking lives in the private
|
|
23
|
+
* zklicensing-app backend alongside vendor-stats)
|
|
24
|
+
*
|
|
25
|
+
* Uses LicensingAppEventsContract (from contractInterface) for fetchEvents —
|
|
26
|
+
* this lets the public verify service read events without depending on the
|
|
27
|
+
* private LicensingApp circuit source.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
import { PublicKey } from 'o1js';
|
|
31
|
+
import {
|
|
32
|
+
LicensingAppEventsContract,
|
|
33
|
+
ExpiryUpdateEvent,
|
|
34
|
+
LicenseIssuedEvent,
|
|
35
|
+
AppCreatedEvent,
|
|
36
|
+
PriceUpdatedEvent,
|
|
37
|
+
DeviceBoundEvent,
|
|
38
|
+
DeviceUnboundEvent,
|
|
39
|
+
MONTHLY_SLOTS_N,
|
|
40
|
+
ONE_YEAR_SLOTS_N,
|
|
41
|
+
FIVE_YEAR_SLOTS_N,
|
|
42
|
+
PERPETUAL_SLOTS_N,
|
|
43
|
+
} from './contractInterface.js';
|
|
44
|
+
import {
|
|
45
|
+
applyLicenseIssued,
|
|
46
|
+
applyLicenseRenewed,
|
|
47
|
+
applyDeviceBound,
|
|
48
|
+
applyDeviceUnbound,
|
|
49
|
+
applyLicenseTransferred,
|
|
50
|
+
recordRefund,
|
|
51
|
+
recordRelease,
|
|
52
|
+
readRecords,
|
|
53
|
+
carryOverRecords,
|
|
54
|
+
} from './licenseStore.js';
|
|
55
|
+
|
|
56
|
+
export const SYNC_CHUNK_SLOTS = 10_000;
|
|
57
|
+
export const MAX_LOOKBACK_SLOTS = 525_600;
|
|
58
|
+
|
|
59
|
+
export type Tier = 'monthly' | 'yearly' | 'fiveYear' | 'perpetual';
|
|
60
|
+
|
|
61
|
+
export type NormalizedEvent =
|
|
62
|
+
| { kind: 'licenseIssued'; slot: number; licenseHash: string; amount: bigint; issuedSlot: number; expirySlot: number; buyerAddress: string; cap: number }
|
|
63
|
+
| { kind: 'licenseIssuedNoEscrow'; slot: number; licenseHash: string; amount: bigint; issuedSlot: number; expirySlot: number; buyerAddress: string; cap: number }
|
|
64
|
+
| { kind: 'licenseRenewed'; slot: number; licenseHash: string; newExpiry: number }
|
|
65
|
+
| { kind: 'appCreated'; slot: number; predecessor: string | null }
|
|
66
|
+
| { kind: 'appRetired'; slot: number; licensesRoot: string }
|
|
67
|
+
| { kind: 'refundIssued'; slot: number; licenseHash: string }
|
|
68
|
+
| { kind: 'fundsReleased'; slot: number; licenseHash: string }
|
|
69
|
+
| { kind: 'deviceBound'; slot: number; licenseHash: string; slotIndex: number; deviceCommitment: string; boundAtSlot: number }
|
|
70
|
+
| { kind: 'deviceUnbound'; slot: number; licenseHash: string; slotIndex: number; freedAtSlot: number }
|
|
71
|
+
| { kind: 'priceUpdated'; slot: number; priceMonthly: bigint; priceYearly: bigint; priceFiveYear: bigint; pricePerpetual: bigint }
|
|
72
|
+
| { kind: 'licenseTransferred'; slot: number; from: string; to: string; wallet: string };
|
|
73
|
+
|
|
74
|
+
// Back-derive the duration tier from (expirySlot - purchaseSlot). Tolerance
|
|
75
|
+
// of ±2 slots matches the SLOT_TOLERANCE used in the circuit.
|
|
76
|
+
export function tierFromExpiryDelta(slots: number): Tier | null {
|
|
77
|
+
const within = (target: number) => Math.abs(slots - target) <= 2;
|
|
78
|
+
if (within(MONTHLY_SLOTS_N)) return 'monthly';
|
|
79
|
+
if (within(ONE_YEAR_SLOTS_N)) return 'yearly';
|
|
80
|
+
if (within(FIVE_YEAR_SLOTS_N)) return 'fiveYear';
|
|
81
|
+
if (within(PERPETUAL_SLOTS_N)) return 'perpetual';
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export type FetchRange = {
|
|
86
|
+
from?: number;
|
|
87
|
+
to?: number;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
export type BootstrapResult = {
|
|
91
|
+
maxSeenSlot: number;
|
|
92
|
+
events: NormalizedEvent[];
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
export async function bootstrapFromArchive(
|
|
96
|
+
zkAppAddress: string,
|
|
97
|
+
range: FetchRange = {},
|
|
98
|
+
): Promise<BootstrapResult> {
|
|
99
|
+
const pubKey = PublicKey.fromBase58(zkAppAddress);
|
|
100
|
+
const zkApp = new LicensingAppEventsContract(pubKey);
|
|
101
|
+
|
|
102
|
+
// o1js's fetchEvents(from, to) passes these directly to the archive-node
|
|
103
|
+
// GraphQL as `from`/`to`, which the reference Mina archive interprets as
|
|
104
|
+
// BLOCK HEIGHTS, not global slots. We track everything in slots, so the two
|
|
105
|
+
// never match and every query returned zero events. Fetch all events for
|
|
106
|
+
// the address and filter by globalSlot client-side — over-fetches per
|
|
107
|
+
// chunked call, but is the only correct behavior against this archive.
|
|
108
|
+
const allEvents = await zkApp.fetchEvents();
|
|
109
|
+
|
|
110
|
+
const fromSlot = range.from ?? 0;
|
|
111
|
+
const toSlot = range.to ?? Number.MAX_SAFE_INTEGER;
|
|
112
|
+
|
|
113
|
+
// Filter out only orphaned events; keep both 'canonical' and 'pending'.
|
|
114
|
+
// The devnet-archive-node-api.gcp.o1test.net archive returns everything
|
|
115
|
+
// with chainStatus='pending' (it doesn't finalize the flag), so a strict
|
|
116
|
+
// `=== 'canonical'` filter drops every event and the mirror stays empty.
|
|
117
|
+
// Mina's k-block finality means recently-landed events on the best chain
|
|
118
|
+
// are safe to replay; if one later reorgs out, the next sync sweep will
|
|
119
|
+
// re-derive state from the new canonical events (a no-op if none of them
|
|
120
|
+
// touch this record).
|
|
121
|
+
const canonical = [...allEvents]
|
|
122
|
+
.filter(e => e.chainStatus !== 'orphaned')
|
|
123
|
+
.filter(e => {
|
|
124
|
+
const slot = Number(e.globalSlot.toString());
|
|
125
|
+
return slot >= fromSlot && slot <= toSlot;
|
|
126
|
+
})
|
|
127
|
+
.sort((a, b) => Number(a.globalSlot.toString()) - Number(b.globalSlot.toString()));
|
|
128
|
+
|
|
129
|
+
const events: NormalizedEvent[] = [];
|
|
130
|
+
|
|
131
|
+
for (const ev of canonical) {
|
|
132
|
+
const slot = Number(ev.globalSlot.toString());
|
|
133
|
+
switch (ev.type) {
|
|
134
|
+
case 'priceUpdated': {
|
|
135
|
+
const data = ev.event.data as unknown as PriceUpdatedEvent;
|
|
136
|
+
events.push({
|
|
137
|
+
kind: 'priceUpdated',
|
|
138
|
+
slot,
|
|
139
|
+
priceMonthly: BigInt(data.priceMonthly.toString()),
|
|
140
|
+
priceYearly: BigInt(data.priceYearly.toString()),
|
|
141
|
+
priceFiveYear: BigInt(data.priceFiveYear.toString()),
|
|
142
|
+
pricePerpetual: BigInt(data.pricePerpetual.toString()),
|
|
143
|
+
});
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
146
|
+
case 'licenseIssued': {
|
|
147
|
+
const data = ev.event.data as unknown as LicenseIssuedEvent;
|
|
148
|
+
events.push({
|
|
149
|
+
kind: 'licenseIssued',
|
|
150
|
+
slot,
|
|
151
|
+
licenseHash: data.licenseHash.toString(),
|
|
152
|
+
amount: BigInt(data.amount.toString()),
|
|
153
|
+
issuedSlot: Number(data.slot.toString()),
|
|
154
|
+
expirySlot: Number(data.expirySlot.toString()),
|
|
155
|
+
buyerAddress: data.buyer.toBase58(),
|
|
156
|
+
cap: Number(data.cap.toString()),
|
|
157
|
+
});
|
|
158
|
+
break;
|
|
159
|
+
}
|
|
160
|
+
case 'licenseIssuedNoEscrow': {
|
|
161
|
+
const data = ev.event.data as unknown as LicenseIssuedEvent;
|
|
162
|
+
events.push({
|
|
163
|
+
kind: 'licenseIssuedNoEscrow',
|
|
164
|
+
slot,
|
|
165
|
+
licenseHash: data.licenseHash.toString(),
|
|
166
|
+
amount: BigInt(data.amount.toString()),
|
|
167
|
+
issuedSlot: Number(data.slot.toString()),
|
|
168
|
+
expirySlot: Number(data.expirySlot.toString()),
|
|
169
|
+
buyerAddress: data.buyer.toBase58(),
|
|
170
|
+
cap: Number(data.cap.toString()),
|
|
171
|
+
});
|
|
172
|
+
break;
|
|
173
|
+
}
|
|
174
|
+
case 'licenseRenewed': {
|
|
175
|
+
const data = ev.event.data as unknown as ExpiryUpdateEvent;
|
|
176
|
+
events.push({
|
|
177
|
+
kind: 'licenseRenewed',
|
|
178
|
+
slot,
|
|
179
|
+
licenseHash: data.licenseHash.toString(),
|
|
180
|
+
newExpiry: Number(data.newExpiry.toString()),
|
|
181
|
+
});
|
|
182
|
+
break;
|
|
183
|
+
}
|
|
184
|
+
case 'appCreated': {
|
|
185
|
+
const data = ev.event.data as unknown as AppCreatedEvent;
|
|
186
|
+
const pred = data.predecessor as PublicKey;
|
|
187
|
+
events.push({ kind: 'appCreated', slot, predecessor: pred.equals(PublicKey.empty()).toBoolean() ? null : pred.toBase58() });
|
|
188
|
+
break;
|
|
189
|
+
}
|
|
190
|
+
case 'appRetired':
|
|
191
|
+
events.push({ kind: 'appRetired', slot, licensesRoot: (ev.event.data as any).licensesRoot.toString() });
|
|
192
|
+
break;
|
|
193
|
+
case 'refundIssued':
|
|
194
|
+
events.push({ kind: 'refundIssued', slot, licenseHash: (ev.event.data as any).toString() });
|
|
195
|
+
break;
|
|
196
|
+
case 'fundsReleased':
|
|
197
|
+
events.push({ kind: 'fundsReleased', slot, licenseHash: (ev.event.data as any).toString() });
|
|
198
|
+
break;
|
|
199
|
+
case 'deviceBound': {
|
|
200
|
+
const data = ev.event.data as unknown as DeviceBoundEvent;
|
|
201
|
+
events.push({
|
|
202
|
+
kind: 'deviceBound',
|
|
203
|
+
slot,
|
|
204
|
+
licenseHash: data.licenseHash.toString(),
|
|
205
|
+
slotIndex: Number(data.slotIndex.toString()),
|
|
206
|
+
deviceCommitment: data.deviceCommitment.toString(),
|
|
207
|
+
// Proof-time slot, NOT the block slot. The circuit packs this into
|
|
208
|
+
// the leaf's freedAt field, so the mirror must use the same value
|
|
209
|
+
// or the reconstructed leaf hash won't match the on-chain root.
|
|
210
|
+
boundAtSlot: Number(data.boundAtSlot.toString()),
|
|
211
|
+
});
|
|
212
|
+
break;
|
|
213
|
+
}
|
|
214
|
+
case 'licenseTransferred': {
|
|
215
|
+
const data = ev.event.data as any;
|
|
216
|
+
events.push({ kind: 'licenseTransferred', slot, from: data.from.toString(), to: data.to.toString(), wallet: data.wallet.toBase58() });
|
|
217
|
+
break;
|
|
218
|
+
}
|
|
219
|
+
case 'deviceUnbound': {
|
|
220
|
+
const data = ev.event.data as unknown as DeviceUnboundEvent;
|
|
221
|
+
events.push({
|
|
222
|
+
kind: 'deviceUnbound',
|
|
223
|
+
slot,
|
|
224
|
+
licenseHash: data.licenseHash.toString(),
|
|
225
|
+
slotIndex: Number(data.slotIndex.toString()),
|
|
226
|
+
// Proof-time slot; see deviceBound comment above.
|
|
227
|
+
freedAtSlot: Number(data.freedAtSlot.toString()),
|
|
228
|
+
});
|
|
229
|
+
break;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const maxSeenSlot = canonical.length === 0
|
|
235
|
+
? -1
|
|
236
|
+
: Number(canonical[canonical.length - 1].globalSlot.toString());
|
|
237
|
+
|
|
238
|
+
return { maxSeenSlot, events };
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
export async function applyEventsToStore(
|
|
242
|
+
zkAppAddress: string,
|
|
243
|
+
events: NormalizedEvent[],
|
|
244
|
+
): Promise<{ count: number; warnings: string[] }> {
|
|
245
|
+
const warnings: string[] = [];
|
|
246
|
+
let count = 0;
|
|
247
|
+
|
|
248
|
+
for (const ev of events) {
|
|
249
|
+
switch (ev.kind) {
|
|
250
|
+
case 'priceUpdated':
|
|
251
|
+
break;
|
|
252
|
+
case 'licenseIssued': {
|
|
253
|
+
const tier = tierFromExpiryDelta(ev.expirySlot - ev.issuedSlot) ?? undefined;
|
|
254
|
+
await applyLicenseIssued(zkAppAddress, ev.licenseHash, {
|
|
255
|
+
expirySlot: ev.expirySlot,
|
|
256
|
+
purchaseSlot: ev.issuedSlot,
|
|
257
|
+
purchaseAmount: ev.amount.toString(),
|
|
258
|
+
buyerAddress: ev.buyerAddress,
|
|
259
|
+
deviceCap: ev.cap,
|
|
260
|
+
...(tier ? { tier } : {}),
|
|
261
|
+
});
|
|
262
|
+
count++;
|
|
263
|
+
break;
|
|
264
|
+
}
|
|
265
|
+
case 'licenseIssuedNoEscrow': {
|
|
266
|
+
const tier = tierFromExpiryDelta(ev.expirySlot - ev.issuedSlot) ?? undefined;
|
|
267
|
+
await applyLicenseIssued(zkAppAddress, ev.licenseHash, {
|
|
268
|
+
expirySlot: ev.expirySlot,
|
|
269
|
+
purchaseSlot: ev.issuedSlot,
|
|
270
|
+
purchaseAmount: ev.amount.toString(),
|
|
271
|
+
escrowless: true,
|
|
272
|
+
buyerAddress: ev.buyerAddress,
|
|
273
|
+
deviceCap: ev.cap,
|
|
274
|
+
...(tier ? { tier } : {}),
|
|
275
|
+
});
|
|
276
|
+
count++;
|
|
277
|
+
break;
|
|
278
|
+
}
|
|
279
|
+
case 'licenseRenewed': {
|
|
280
|
+
const ok = await applyLicenseRenewed(zkAppAddress, ev.licenseHash, { newExpiry: ev.newExpiry });
|
|
281
|
+
if (!ok) warnings.push(`renew for unknown license ${ev.licenseHash.slice(0, 16)}…`);
|
|
282
|
+
break;
|
|
283
|
+
}
|
|
284
|
+
case 'appCreated':
|
|
285
|
+
case 'appRetired':
|
|
286
|
+
// A successor's carried-over licenses are copied by lazyBootstrap
|
|
287
|
+
// (from its predecessor) before its own events apply.
|
|
288
|
+
break;
|
|
289
|
+
case 'refundIssued': {
|
|
290
|
+
const ok = await recordRefund(zkAppAddress, ev.licenseHash);
|
|
291
|
+
if (!ok) warnings.push(`refund for unknown license ${ev.licenseHash.slice(0, 16)}…`);
|
|
292
|
+
break;
|
|
293
|
+
}
|
|
294
|
+
case 'fundsReleased': {
|
|
295
|
+
const ok = await recordRelease(zkAppAddress, ev.licenseHash, ev.slot);
|
|
296
|
+
if (!ok) warnings.push(`release for unknown license ${ev.licenseHash.slice(0, 16)}…`);
|
|
297
|
+
break;
|
|
298
|
+
}
|
|
299
|
+
case 'deviceBound': {
|
|
300
|
+
const ok = await applyDeviceBound(zkAppAddress, ev.licenseHash, {
|
|
301
|
+
slotIndex: ev.slotIndex,
|
|
302
|
+
deviceCommitment: ev.deviceCommitment,
|
|
303
|
+
boundAtSlot: ev.boundAtSlot,
|
|
304
|
+
});
|
|
305
|
+
if (!ok) warnings.push(`bind for unknown license ${ev.licenseHash.slice(0, 16)}…`);
|
|
306
|
+
break;
|
|
307
|
+
}
|
|
308
|
+
case 'licenseTransferred': {
|
|
309
|
+
const ok = await applyLicenseTransferred(zkAppAddress, ev.from, ev.to, { wallet: ev.wallet });
|
|
310
|
+
if (!ok) warnings.push(`transfer of unknown license ${ev.from.slice(0, 16)}…`);
|
|
311
|
+
break;
|
|
312
|
+
}
|
|
313
|
+
case 'deviceUnbound': {
|
|
314
|
+
const ok = await applyDeviceUnbound(zkAppAddress, ev.licenseHash, {
|
|
315
|
+
slotIndex: ev.slotIndex,
|
|
316
|
+
freedAtSlot: ev.freedAtSlot,
|
|
317
|
+
});
|
|
318
|
+
if (!ok) warnings.push(`unbind for unknown license ${ev.licenseHash.slice(0, 16)}…`);
|
|
319
|
+
break;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
return { count, warnings };
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
export async function discoverAppCreatedSlot(
|
|
328
|
+
zkAppAddress: string,
|
|
329
|
+
nowSlot: number,
|
|
330
|
+
lookbackSlots: number = MAX_LOOKBACK_SLOTS,
|
|
331
|
+
): Promise<number | null> {
|
|
332
|
+
const pubKey = PublicKey.fromBase58(zkAppAddress);
|
|
333
|
+
const zkApp = new LicensingAppEventsContract(pubKey);
|
|
334
|
+
const floor = Math.max(1, nowSlot - lookbackSlots);
|
|
335
|
+
|
|
336
|
+
// See bootstrapFromArchive: fetchEvents(from, to) filters by BLOCK HEIGHT
|
|
337
|
+
// in the reference archive, not slot. One full fetch + client-side slot
|
|
338
|
+
// filter is the only correct approach. Also keeps chainStatus='pending'
|
|
339
|
+
// events — o1test.net's devnet archive doesn't finalize that flag; strict
|
|
340
|
+
// 'canonical' filtering leaves discovery empty on freshly-deployed apps.
|
|
341
|
+
const events = await zkApp.fetchEvents();
|
|
342
|
+
const created = events
|
|
343
|
+
.filter(e => e.chainStatus !== 'orphaned' && e.type === 'appCreated')
|
|
344
|
+
.filter(e => {
|
|
345
|
+
const slot = Number(e.globalSlot.toString());
|
|
346
|
+
return slot >= floor && slot <= nowSlot;
|
|
347
|
+
})
|
|
348
|
+
.sort((a, b) => Number(a.globalSlot.toString()) - Number(b.globalSlot.toString()));
|
|
349
|
+
if (created.length > 0) return Number(created[0].globalSlot.toString());
|
|
350
|
+
return null;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
const inFlightBootstraps = new Map<string, Promise<void>>();
|
|
354
|
+
|
|
355
|
+
export async function lazyBootstrap(zkAppAddress: string): Promise<void> {
|
|
356
|
+
const existing = inFlightBootstraps.get(zkAppAddress);
|
|
357
|
+
if (existing) return existing;
|
|
358
|
+
const short = zkAppAddress.slice(0, 12) + '…';
|
|
359
|
+
const p = (async () => {
|
|
360
|
+
const started = Date.now();
|
|
361
|
+
try {
|
|
362
|
+
const { events } = await bootstrapFromArchive(zkAppAddress);
|
|
363
|
+
// A redeployed app's successor starts with its predecessor's licenses:
|
|
364
|
+
// rebuild those first (recursively), then copy them across.
|
|
365
|
+
const created = events.find(e => e.kind === 'appCreated');
|
|
366
|
+
if (created?.kind === 'appCreated' && created.predecessor) {
|
|
367
|
+
if (!(await readRecords()).some(r => r.zkAppAddress === created.predecessor)) await lazyBootstrap(created.predecessor);
|
|
368
|
+
await carryOverRecords(created.predecessor, zkAppAddress);
|
|
369
|
+
}
|
|
370
|
+
const { count, warnings } = await applyEventsToStore(zkAppAddress, events);
|
|
371
|
+
for (const w of warnings) console.warn(`[lazy-bootstrap] ${short}: ${w}`);
|
|
372
|
+
// Always log the outcome — count=0 with a mismatch trigger is the
|
|
373
|
+
// interesting case: it means either the archive is behind Mina's own
|
|
374
|
+
// best chain (transient) or the local mirror silently drifted and the
|
|
375
|
+
// replay didn't fix it (a real bug — verify will keep looping through
|
|
376
|
+
// this until the next mutation event lands). Silence would hide both.
|
|
377
|
+
console.log(`[lazy-bootstrap] ${short}: replayed ${count} record(s) in ${Date.now() - started}ms`);
|
|
378
|
+
} catch (e: any) {
|
|
379
|
+
console.warn(`[lazy-bootstrap] ${short}: ${e?.message ?? e}`);
|
|
380
|
+
} finally {
|
|
381
|
+
inFlightBootstraps.delete(zkAppAddress);
|
|
382
|
+
}
|
|
383
|
+
})();
|
|
384
|
+
inFlightBootstraps.set(zkAppAddress, p);
|
|
385
|
+
return p;
|
|
386
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// Copyright (c) 2025-2026 zkLicensing project developers
|
|
2
|
+
// SPDX-License-Identifier: BUSL-1.1
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* atomicJson.ts — `zklicensing/internal/atomicJson`
|
|
6
|
+
*
|
|
7
|
+
* Crash-safe JSON files. A write goes to a temporary file in the same
|
|
8
|
+
* directory, is flushed to disk, and replaces the target with one rename, so
|
|
9
|
+
* a crash leaves either the old or the new file — never half of one. A read
|
|
10
|
+
* returns the fallback only when the file doesn't exist; a file that exists
|
|
11
|
+
* but doesn't parse is an error, so a damaged store is never mistaken for an
|
|
12
|
+
* empty one (and then overwritten with nothing).
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
// Only a default import of fs/promises (no named imports from Node modules):
|
|
16
|
+
// browser bundles that reach this file through licenseStore get Vite's empty
|
|
17
|
+
// stub for it instead of a build error.
|
|
18
|
+
import fs from 'fs/promises';
|
|
19
|
+
|
|
20
|
+
export async function readJsonOr<T>(path: string, fallback: T): Promise<T> {
|
|
21
|
+
let text: string;
|
|
22
|
+
try {
|
|
23
|
+
text = await fs.readFile(path, 'utf8');
|
|
24
|
+
} catch (e: any) {
|
|
25
|
+
if (e?.code === 'ENOENT') return fallback;
|
|
26
|
+
throw e;
|
|
27
|
+
}
|
|
28
|
+
try {
|
|
29
|
+
return JSON.parse(text) as T;
|
|
30
|
+
} catch (e: any) {
|
|
31
|
+
throw new Error(`${path} exists but is not valid JSON (${e?.message ?? e}); restore it from a backup — refusing to treat it as empty`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export async function writeJsonAtomic(path: string, value: unknown, opts: { mode?: number } = {}): Promise<void> {
|
|
36
|
+
// Same directory as the target (rename is only atomic within a file system).
|
|
37
|
+
const slash = Math.max(path.lastIndexOf('/'), path.lastIndexOf('\\'));
|
|
38
|
+
const suffix = [...crypto.getRandomValues(new Uint8Array(6))].map(b => b.toString(16).padStart(2, '0')).join('');
|
|
39
|
+
const tmp = `${path.slice(0, slash + 1)}.${path.slice(slash + 1)}.${suffix}.tmp`;
|
|
40
|
+
const fh = await fs.open(tmp, 'w', opts.mode ?? 0o600);
|
|
41
|
+
try {
|
|
42
|
+
await fh.writeFile(JSON.stringify(value, null, 2), 'utf8');
|
|
43
|
+
await fh.sync();
|
|
44
|
+
} finally {
|
|
45
|
+
await fh.close();
|
|
46
|
+
}
|
|
47
|
+
try {
|
|
48
|
+
await fs.rename(tmp, path);
|
|
49
|
+
} catch (e) {
|
|
50
|
+
await fs.rm(tmp, { force: true });
|
|
51
|
+
throw e;
|
|
52
|
+
}
|
|
53
|
+
}
|