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
package/src/index.ts
ADDED
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
// Copyright (c) 2025-2026 zkLicensing project developers
|
|
2
|
+
// SPDX-License-Identifier: BUSL-1.1
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* verifyLicense — client SDK.
|
|
6
|
+
*
|
|
7
|
+
* Calls the licensing service's GET /verify until the on-chain refund
|
|
8
|
+
* window for the license has closed; from that point on returns from a
|
|
9
|
+
* local anchor and never touches the network again. The refund-window
|
|
10
|
+
* decision is server-side: the service returns `refundWindowClosed:
|
|
11
|
+
* boolean` on its /verify response, computed from its own record of
|
|
12
|
+
* `purchaseSlot + REFUND_WINDOW_N` against the on-chain `currentSlot`.
|
|
13
|
+
* The SDK gates anchor writes on that boolean and never trusts a
|
|
14
|
+
* `purchaseSlot` supplied by the local license file, so a user who forges
|
|
15
|
+
* their proof metadata cannot force the offline branch prematurely.
|
|
16
|
+
*
|
|
17
|
+
* Anchor purity: every field persisted in the Anchor derives from the
|
|
18
|
+
* service's /verify response — `anchoredSlot`, `expirySlot`. The only
|
|
19
|
+
* client-supplied input to the offline branch is `proof.expirySlot`,
|
|
20
|
+
* used solely as the loadAnchor binding key. A tampered license file
|
|
21
|
+
* therefore never gets a wrong answer; it gets no offline branch at all
|
|
22
|
+
* and falls through to the licensing service on every call.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import {
|
|
26
|
+
REFUND_WINDOW_N,
|
|
27
|
+
GRACE_PERIOD_N,
|
|
28
|
+
MS_PER_SLOT_N,
|
|
29
|
+
SLOTS_PER_DAY_N,
|
|
30
|
+
} from './slotConstants.js';
|
|
31
|
+
|
|
32
|
+
const MS_PER_SLOT = MS_PER_SLOT_N;
|
|
33
|
+
const SLOTS_PER_DAY = SLOTS_PER_DAY_N;
|
|
34
|
+
|
|
35
|
+
// The one and only verify endpoint. Every issued license routes here
|
|
36
|
+
// regardless of network — the platform verifier resolves devnet vs
|
|
37
|
+
// mainnet from the zkAppAddress (each deployment is unique per network).
|
|
38
|
+
// Tests inject a mock via VerifyOptions.fetcher, not by rewriting this URL.
|
|
39
|
+
export const DEFAULT_VERIFIER_URL = 'https://zklicensing.com/api/verify';
|
|
40
|
+
|
|
41
|
+
// Anchor records are keyed by (zkAppAddress, licenseHash). zkAppAddress
|
|
42
|
+
// is generation-scoped by construction: each redeploy puts the
|
|
43
|
+
// LicensingApp at a fresh address (see backend's finalizeRedeploy and
|
|
44
|
+
// AppRecord.deploymentHistory[]), so keying on it implicitly captures
|
|
45
|
+
// generation. An anchor for a retired address is orphaned once the license
|
|
46
|
+
// file names the successor, and re-created cleanly on its next verify.
|
|
47
|
+
const ANCHOR_KEY_PREFIX = 'zklic_anchor_';
|
|
48
|
+
|
|
49
|
+
function anchorKey(zkAppAddress: string, licenseHash: string): string {
|
|
50
|
+
return `${ANCHOR_KEY_PREFIX}${zkAppAddress}_${licenseHash}`;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface Storage {
|
|
54
|
+
getItem(key: string): string | null;
|
|
55
|
+
setItem(key: string, value: string): void;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface ProofInput {
|
|
59
|
+
licenseHash: string;
|
|
60
|
+
zkAppAddress: string;
|
|
61
|
+
expirySlot: number;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface VerifyOptions {
|
|
65
|
+
storage?: Storage | null;
|
|
66
|
+
fetcher?: typeof fetch;
|
|
67
|
+
now?: () => number;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface VerifyResult {
|
|
71
|
+
valid: boolean;
|
|
72
|
+
expirySlot: number;
|
|
73
|
+
// ISO-8601 timestamp of estimated expiry. `null` when the license is
|
|
74
|
+
// unknown / refunded / unreachable. In the offline branch this is
|
|
75
|
+
// estimated from the anchor + local clock.
|
|
76
|
+
expiresAt: string | null;
|
|
77
|
+
// Estimated current Mina slot. From the licensing service in the
|
|
78
|
+
// chain branch; estimated from the anchor + elapsed local time in the
|
|
79
|
+
// offline branch.
|
|
80
|
+
currentSlot: number;
|
|
81
|
+
inGracePeriod: boolean;
|
|
82
|
+
// Whole days until expiry. Negative while in the grace period (down
|
|
83
|
+
// to ~-7) and below that once fully expired. 0 when expirySlot is
|
|
84
|
+
// unknown.
|
|
85
|
+
remainingDays: number;
|
|
86
|
+
reason: string | null;
|
|
87
|
+
source: 'offline' | 'chain';
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
interface Anchor {
|
|
91
|
+
anchoredSlot: number;
|
|
92
|
+
anchoredAtMs: number;
|
|
93
|
+
expirySlot: number;
|
|
94
|
+
// The service's effectiveExpirySlot when the license counts from the app's
|
|
95
|
+
// fork slot (it lapsed during the pre-fork sales freeze).
|
|
96
|
+
effectiveExpirySlot?: number;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function defaultStorage(): Storage | null {
|
|
100
|
+
if (typeof globalThis !== 'undefined' && (globalThis as any).localStorage) {
|
|
101
|
+
return (globalThis as any).localStorage as Storage;
|
|
102
|
+
}
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function loadAnchor(
|
|
107
|
+
storage: Storage,
|
|
108
|
+
zkAppAddress: string,
|
|
109
|
+
licenseHash: string,
|
|
110
|
+
expirySlot: number,
|
|
111
|
+
): Anchor | null {
|
|
112
|
+
try {
|
|
113
|
+
const raw = storage.getItem(anchorKey(zkAppAddress, licenseHash));
|
|
114
|
+
if (!raw) return null;
|
|
115
|
+
const a = JSON.parse(raw) as Anchor;
|
|
116
|
+
// `a.expirySlot` is the SERVICE's value captured at anchor time;
|
|
117
|
+
// the `expirySlot` argument is the LOCAL license file's value. Match
|
|
118
|
+
// ⇒ local file agrees with chain truth as of anchoring ⇒ offline is
|
|
119
|
+
// safe. Mismatch has two honest meanings:
|
|
120
|
+
// - renewal: chain moved, local file updated ⇒ re-anchor via chain check
|
|
121
|
+
// - forgery: local file edited ⇒ every call falls through to the
|
|
122
|
+
// licensing service, which reports truth. The forger gets no
|
|
123
|
+
// offline branch at all.
|
|
124
|
+
if (a.expirySlot !== expirySlot) return null;
|
|
125
|
+
return a;
|
|
126
|
+
} catch {
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function saveAnchor(
|
|
132
|
+
storage: Storage,
|
|
133
|
+
zkAppAddress: string,
|
|
134
|
+
licenseHash: string,
|
|
135
|
+
anchor: Anchor,
|
|
136
|
+
): void {
|
|
137
|
+
try {
|
|
138
|
+
storage.setItem(anchorKey(zkAppAddress, licenseHash), JSON.stringify(anchor));
|
|
139
|
+
} catch {
|
|
140
|
+
// Storage may be full or read-only; the offline path simply won't activate.
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function computeRemainingDays(expirySlot: number, currentSlot: number): number {
|
|
145
|
+
if (expirySlot === 0) return 0;
|
|
146
|
+
return Math.floor((expirySlot - currentSlot) / SLOTS_PER_DAY);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function offlineCheck(anchor: Anchor, nowMs: number): VerifyResult {
|
|
150
|
+
const elapsedSlots = Math.max(0, Math.floor((nowMs - anchor.anchoredAtMs) / MS_PER_SLOT));
|
|
151
|
+
const estCurrentSlot = anchor.anchoredSlot + elapsedSlots;
|
|
152
|
+
const until = anchor.effectiveExpirySlot ?? anchor.expirySlot;
|
|
153
|
+
const expired = estCurrentSlot > until + GRACE_PERIOD_N;
|
|
154
|
+
const inGracePeriod = !expired && estCurrentSlot > until;
|
|
155
|
+
const expiresAt = new Date(nowMs + (until - estCurrentSlot) * MS_PER_SLOT).toISOString();
|
|
156
|
+
return {
|
|
157
|
+
valid: !expired,
|
|
158
|
+
expirySlot: anchor.expirySlot,
|
|
159
|
+
expiresAt,
|
|
160
|
+
currentSlot: estCurrentSlot,
|
|
161
|
+
inGracePeriod,
|
|
162
|
+
remainingDays: computeRemainingDays(until, estCurrentSlot),
|
|
163
|
+
reason: expired
|
|
164
|
+
? 'License has expired'
|
|
165
|
+
: inGracePeriod
|
|
166
|
+
? 'License is in grace period — renew to avoid interruption'
|
|
167
|
+
: null,
|
|
168
|
+
source: 'offline',
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export async function verifyLicense(
|
|
173
|
+
proof: ProofInput,
|
|
174
|
+
options: VerifyOptions = {}
|
|
175
|
+
): Promise<VerifyResult> {
|
|
176
|
+
const storage = options.storage === null ? null : options.storage ?? defaultStorage();
|
|
177
|
+
const fetcher = options.fetcher ?? (typeof fetch !== 'undefined' ? fetch : undefined);
|
|
178
|
+
const nowFn = options.now ?? (() => Date.now());
|
|
179
|
+
|
|
180
|
+
if (storage) {
|
|
181
|
+
const anchor = loadAnchor(storage, proof.zkAppAddress, proof.licenseHash, proof.expirySlot);
|
|
182
|
+
if (anchor) return offlineCheck(anchor, nowFn());
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (!fetcher) {
|
|
186
|
+
return {
|
|
187
|
+
valid: false,
|
|
188
|
+
expirySlot: proof.expirySlot,
|
|
189
|
+
expiresAt: null,
|
|
190
|
+
currentSlot: 0,
|
|
191
|
+
inGracePeriod: false,
|
|
192
|
+
remainingDays: 0,
|
|
193
|
+
reason: 'No fetch implementation available; cannot verify',
|
|
194
|
+
source: 'chain',
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const url =
|
|
199
|
+
`${DEFAULT_VERIFIER_URL}` +
|
|
200
|
+
`?licenseHash=${encodeURIComponent(proof.licenseHash)}` +
|
|
201
|
+
`&zkAppAddress=${encodeURIComponent(proof.zkAppAddress)}`;
|
|
202
|
+
|
|
203
|
+
let data: any;
|
|
204
|
+
try {
|
|
205
|
+
const resp = await fetcher(url);
|
|
206
|
+
if (!resp.ok) {
|
|
207
|
+
return {
|
|
208
|
+
valid: false,
|
|
209
|
+
expirySlot: proof.expirySlot,
|
|
210
|
+
expiresAt: null,
|
|
211
|
+
currentSlot: 0,
|
|
212
|
+
inGracePeriod: false,
|
|
213
|
+
remainingDays: 0,
|
|
214
|
+
reason: `Verification request failed (HTTP ${resp.status})`,
|
|
215
|
+
source: 'chain',
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
data = await resp.json();
|
|
219
|
+
} catch (err: any) {
|
|
220
|
+
return {
|
|
221
|
+
valid: false,
|
|
222
|
+
expirySlot: proof.expirySlot,
|
|
223
|
+
expiresAt: null,
|
|
224
|
+
currentSlot: 0,
|
|
225
|
+
inGracePeriod: false,
|
|
226
|
+
remainingDays: 0,
|
|
227
|
+
reason: err?.message ?? 'Verification request errored',
|
|
228
|
+
source: 'chain',
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Service's authoritative expiry, or null if the response omits it.
|
|
233
|
+
// Only the null-safe form may be written into the anchor; the
|
|
234
|
+
// chain-result display value below falls back to the local license file
|
|
235
|
+
// for user-facing fields, which is safe (display only).
|
|
236
|
+
const respExpirySlotStrict: number | null =
|
|
237
|
+
typeof data.expirySlot === 'number' ? data.expirySlot : null;
|
|
238
|
+
const respExpirySlot: number = respExpirySlotStrict ?? proof.expirySlot;
|
|
239
|
+
const respCurrentSlot: number = typeof data.currentSlot === 'number' ? data.currentSlot : 0;
|
|
240
|
+
const result: VerifyResult = {
|
|
241
|
+
valid: !!data.valid,
|
|
242
|
+
expirySlot: respExpirySlot,
|
|
243
|
+
expiresAt: typeof data.expiresAt === 'string' ? data.expiresAt : null,
|
|
244
|
+
currentSlot: respCurrentSlot,
|
|
245
|
+
inGracePeriod: !!data.inGracePeriod,
|
|
246
|
+
remainingDays: typeof data.remainingDays === 'number'
|
|
247
|
+
? data.remainingDays
|
|
248
|
+
: computeRemainingDays(respExpirySlot, respCurrentSlot),
|
|
249
|
+
reason: data.reason ?? null,
|
|
250
|
+
source: 'chain',
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
// Anchor gate uses the service's own refund-window decision. The
|
|
254
|
+
// service knows the true purchaseSlot (stored in its LicenseRecord)
|
|
255
|
+
// and the true currentSlot; the SDK never trusts a purchaseSlot or
|
|
256
|
+
// expirySlot supplied by the local license file, since that file is
|
|
257
|
+
// client-writable and could be forged. If the service omits
|
|
258
|
+
// `expirySlot`, we fail-closed on the offline path — no anchor
|
|
259
|
+
// written, subsequent calls stay online.
|
|
260
|
+
if (
|
|
261
|
+
storage &&
|
|
262
|
+
result.valid &&
|
|
263
|
+
data.refundWindowClosed === true &&
|
|
264
|
+
typeof data.currentSlot === 'number' &&
|
|
265
|
+
respExpirySlotStrict !== null
|
|
266
|
+
) {
|
|
267
|
+
saveAnchor(storage, proof.zkAppAddress, proof.licenseHash, {
|
|
268
|
+
anchoredSlot: data.currentSlot,
|
|
269
|
+
anchoredAtMs: nowFn(),
|
|
270
|
+
expirySlot: respExpirySlotStrict,
|
|
271
|
+
...(typeof data.effectiveExpirySlot === 'number' ? { effectiveExpirySlot: data.effectiveExpirySlot } : {}),
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return result;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Re-exported so tests and downstream tooling can reference the same values
|
|
279
|
+
// the SDK uses internally without re-deriving them.
|
|
280
|
+
export { REFUND_WINDOW_N, GRACE_PERIOD_N, MS_PER_SLOT };
|
|
281
|
+
|
|
282
|
+
// Non-custodial device binding surface, here alongside buy / renew / refund
|
|
283
|
+
// so vendor integrations can pull the whole family from one import path.
|
|
284
|
+
// These load o1js (bindClient, unbindClient, deviceIdentity) only when
|
|
285
|
+
// called, so this entry imports without o1js (an optional peer): an app
|
|
286
|
+
// that only verifies licenses doesn't need it. Each also has its own
|
|
287
|
+
// subpath (`zklicensing/bind`, `zklicensing/unbind`,
|
|
288
|
+
// `zklicensing/device-identity`).
|
|
289
|
+
type BindClient = typeof import('./bindClient.js');
|
|
290
|
+
type UnbindClient = typeof import('./unbindClient.js');
|
|
291
|
+
type DeviceIdentityModule = typeof import('./deviceIdentity.js');
|
|
292
|
+
const bindClient = () => import('./bindClient.js');
|
|
293
|
+
const unbindClient = () => import('./unbindClient.js');
|
|
294
|
+
const deviceIdentityModule = () => import('./deviceIdentity.js');
|
|
295
|
+
export async function proveBind(...a: Parameters<BindClient['proveBind']>): ReturnType<BindClient['proveBind']> { return (await bindClient()).proveBind(...a); }
|
|
296
|
+
export async function confirmBind(...a: Parameters<BindClient['confirmBind']>): ReturnType<BindClient['confirmBind']> { return (await bindClient()).confirmBind(...a); }
|
|
297
|
+
export async function pollBindStatus(...a: Parameters<BindClient['pollBindStatus']>): ReturnType<BindClient['pollBindStatus']> { return (await bindClient()).pollBindStatus(...a); }
|
|
298
|
+
export async function fetchBindingState(...a: Parameters<BindClient['fetchBindingState']>): ReturnType<BindClient['fetchBindingState']> { return (await bindClient()).fetchBindingState(...a); }
|
|
299
|
+
export async function proveUnbind(...a: Parameters<UnbindClient['proveUnbind']>): ReturnType<UnbindClient['proveUnbind']> { return (await unbindClient()).proveUnbind(...a); }
|
|
300
|
+
export async function confirmUnbind(...a: Parameters<UnbindClient['confirmUnbind']>): ReturnType<UnbindClient['confirmUnbind']> { return (await unbindClient()).confirmUnbind(...a); }
|
|
301
|
+
export async function pollUnbindStatus(...a: Parameters<UnbindClient['pollUnbindStatus']>): ReturnType<UnbindClient['pollUnbindStatus']> { return (await unbindClient()).pollUnbindStatus(...a); }
|
|
302
|
+
export async function getOrCreateDeviceIdentity(...a: Parameters<DeviceIdentityModule['getOrCreateDeviceIdentity']>): ReturnType<DeviceIdentityModule['getOrCreateDeviceIdentity']> { return (await deviceIdentityModule()).getOrCreateDeviceIdentity(...a); }
|
|
303
|
+
export type {
|
|
304
|
+
ProveBindRequest,
|
|
305
|
+
ProveBindResponse,
|
|
306
|
+
ConfirmBindRequest,
|
|
307
|
+
BindStatus,
|
|
308
|
+
PollBindStatusOptions,
|
|
309
|
+
} from './bindClient.js';
|
|
310
|
+
export type {
|
|
311
|
+
ProveUnbindRequest,
|
|
312
|
+
ProveUnbindResponse,
|
|
313
|
+
ConfirmUnbindRequest,
|
|
314
|
+
UnbindStatus,
|
|
315
|
+
PollUnbindStatusOptions,
|
|
316
|
+
} from './unbindClient.js';
|
|
317
|
+
export type { DeviceIdentity } from './deviceIdentity.js';
|
|
318
|
+
|
|
319
|
+
// The synchronous o1js helpers are on their subpaths only (they can't load
|
|
320
|
+
// o1js lazily): `computeDeviceCommitment` from `zklicensing/device-identity`,
|
|
321
|
+
// `verifyOffline` and `deviceCommitmentFromFields` from
|
|
322
|
+
// `zklicensing/verify-offline`.
|
|
323
|
+
export type {
|
|
324
|
+
VerifyOfflineInput,
|
|
325
|
+
VerifyOfflineResult,
|
|
326
|
+
DeviceSlotSnapshot,
|
|
327
|
+
} from './verifyOffline.js';
|
|
328
|
+
|
|
329
|
+
// Non-custodial trust anchor for `verifyOffline`. Reads the live
|
|
330
|
+
// `licensesRoot` (zkapp.appState[0]) directly from Mina — pair it with
|
|
331
|
+
// `verifyOffline` when you do not want to trust the licensing service's
|
|
332
|
+
// reported root. Also available at the `zklicensing/on-chain-root`
|
|
333
|
+
// subpath. o1js is imported lazily so callers that skip this helper
|
|
334
|
+
// don't pay the o1js dep cost at load time.
|
|
335
|
+
export { fetchOnChainLicensesRoot, DEFAULT_MINA_GRAPHQL_URLS } from './onChainRoot.js';
|
|
336
|
+
export type { FetchOnChainLicensesRootInput, NetworkName } from './onChainRoot.js';
|
|
337
|
+
|
|
338
|
+
// The receipt file a buyer downloads: `license-<appId>.json`. Look for it
|
|
339
|
+
// under this name in your app's data folder. Also available at the
|
|
340
|
+
// `zklicensing/record` subpath.
|
|
341
|
+
export { licenseFileName, platformFeeNano, vendorShareNano } from './licenseRecord.js';
|
|
342
|
+
|
|
343
|
+
// Human-readable seat names for the device-binding UI ("Silent Otter",
|
|
344
|
+
// "Curious Panda"). Deterministic in `licenseHash + slotIndex + boundAtSlot`,
|
|
345
|
+
// so every viewer of the same license computes the same name per slot
|
|
346
|
+
// without a naming sidecar. Also available at the `zklicensing/seat-name`
|
|
347
|
+
// subpath.
|
|
348
|
+
export { seatNameFor, SEAT_NAME_COMBINATIONS } from './seatName.js';
|
|
349
|
+
export type { SeatNameInput } from './seatName.js';
|
|
350
|
+
|
|
351
|
+
// Vendor-signed escrow release. The licensing service proves the release tx
|
|
352
|
+
// with the vendor's address as fee payer and returns unsigned tx JSON; the
|
|
353
|
+
// vendor's wallet signs the fee-payer AU and broadcasts. Removes the
|
|
354
|
+
// service's own feepayer key from the release critical path — vendors can
|
|
355
|
+
// release even when the auto-release loop is disabled.
|
|
356
|
+
export { proveVendorRelease, confirmVendorRelease } from './releaseClient.js';
|
|
357
|
+
export type {
|
|
358
|
+
ProveVendorReleaseRequest,
|
|
359
|
+
ProveVendorReleaseResponse,
|
|
360
|
+
ConfirmVendorReleaseRequest,
|
|
361
|
+
ConfirmVendorReleaseResponse,
|
|
362
|
+
} from './releaseClient.js';
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
// Copyright (c) 2025-2026 zkLicensing project developers
|
|
2
|
+
// SPDX-License-Identifier: BUSL-1.1
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* keysClient.ts — key changes on a licensing app, through the keeper.
|
|
6
|
+
*
|
|
7
|
+
* - The keys an app holds on chain (vendor, payout address and a pending
|
|
8
|
+
* change, platform key and treasury).
|
|
9
|
+
* - Payout address: the vendor proposes a new one (their wallet sends it);
|
|
10
|
+
* it takes effect PAYOUT_DELAY_SLOTS_N slots (48 h) later unless
|
|
11
|
+
* cancelled. The keeper applies it then; anyone may (applyPayout).
|
|
12
|
+
* - Vendor account move: the current and the new wallet both sign a text
|
|
13
|
+
* and, for every live deployment, the contract's vendorKeyMessage.
|
|
14
|
+
* - License transfer (resale): the new owner opens it with the public key
|
|
15
|
+
* of their new passphrase (the passphrase never leaves their device), the
|
|
16
|
+
* current owner signs, the new owner signs, and the new owner's wallet
|
|
17
|
+
* sends it. Only once nothing of the purchase is in escrow.
|
|
18
|
+
*
|
|
19
|
+
* Signatures over fields are Mina Schnorr signatures (base58), as an
|
|
20
|
+
* o1js `Signature` or a wallet's `signFields` gives.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import { Field, PublicKey, Signature, UInt32, type PrivateKey } from 'o1js';
|
|
24
|
+
import { licenseTransferMessage, vendorKeyMessage } from './contractInterface.js';
|
|
25
|
+
import { networkField } from './permitMessages.js';
|
|
26
|
+
import { ownerPubKeyHash } from './licenseLeaf.js';
|
|
27
|
+
|
|
28
|
+
export type KeysClientOptions = { keeperUrl: string; fetcher?: typeof fetch };
|
|
29
|
+
|
|
30
|
+
export type AppKeys = {
|
|
31
|
+
appId: string; network: 'devnet' | 'mainnet'; zkAppAddress: string; slot: number;
|
|
32
|
+
vendor: string; vendorNonce: number; payout: string;
|
|
33
|
+
pendingPayout: { payout: string; effectiveSlot: number } | null;
|
|
34
|
+
platform: { key: string; treasury: string; epoch: number };
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export type LicenseTransfer = {
|
|
38
|
+
id: string; network: 'devnet' | 'mainnet'; zkAppAddress: string;
|
|
39
|
+
from: string; to: string; newOwner: string; leafHash: string; validUntil: number; message: string[];
|
|
40
|
+
oldOwner?: string; status: 'signing' | 'ready' | 'sent' | 'done'; createdAt: string; txHash?: string;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/** A wallet that signs texts and fields (Auro's signMessage / signFields shapes). */
|
|
44
|
+
export type MoveWallet = {
|
|
45
|
+
address: string;
|
|
46
|
+
signMessage(message: string): Promise<{ field: string; scalar: string }>;
|
|
47
|
+
signFields(fields: string[]): Promise<string>;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export class KeysError extends Error {
|
|
51
|
+
constructor(message: string, readonly status: number, readonly code?: string) { super(message); }
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function createKeysClient(opts: KeysClientOptions) {
|
|
55
|
+
const base = opts.keeperUrl.replace(/\/+$/, '');
|
|
56
|
+
const f = opts.fetcher ?? fetch;
|
|
57
|
+
async function call<T>(method: string, path: string, body?: unknown): Promise<T> {
|
|
58
|
+
const r = await f(base + path, { method, headers: { 'content-type': 'application/json' }, ...(body === undefined ? {} : { body: JSON.stringify(body) }) });
|
|
59
|
+
const json = await r.json().catch(() => null) as any;
|
|
60
|
+
if (!r.ok) throw new KeysError(json?.error ?? `${method} ${path} failed (${r.status})`, r.status, json?.code);
|
|
61
|
+
return json as T;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
/** The keys the app holds on chain. */
|
|
66
|
+
getAppKeys: (zkAppAddress: string) => call<AppKeys>('GET', `/app-keys?zkAppAddress=${encodeURIComponent(zkAppAddress)}`),
|
|
67
|
+
|
|
68
|
+
/** Proves a payout change for the vendor's wallet to sign and send (its `vendorAddress` must be the app's vendor key). */
|
|
69
|
+
provePayout: (a: { zkAppAddress: string; vendorAddress: string; payout: string }) =>
|
|
70
|
+
call<{ provenTxJson: string; payout: string }>('POST', '/prove/payout', a),
|
|
71
|
+
/** Proves the cancellation of a pending payout change (vendor's wallet). */
|
|
72
|
+
provePayoutCancel: (a: { zkAppAddress: string; vendorAddress: string }) =>
|
|
73
|
+
call<{ provenTxJson: string; cancelled: string }>('POST', '/prove/payout/cancel', a),
|
|
74
|
+
/** Proves applying a payout change past its delay, with any fee payer (the keeper does it itself). */
|
|
75
|
+
provePayoutApply: (a: { zkAppAddress: string; feePayer: string }) =>
|
|
76
|
+
call<{ provenTxJson: string; payout: string }>('POST', '/prove/payout/apply', a),
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Moves the vendor account from `from` to `to`: both wallets sign the
|
|
80
|
+
* move and every live deployment's key change. The keeper's records move
|
|
81
|
+
* at once; it sends the contract changes (or anyone, with /prove/vendor-key).
|
|
82
|
+
* `network` is the one the wallets' text signatures are made on.
|
|
83
|
+
*/
|
|
84
|
+
async moveVendorAccount(a: { network: 'devnet' | 'mainnet'; from: MoveWallet; to: MoveWallet }) {
|
|
85
|
+
const prep = await call<{ text: string; changes: Array<{ appId: string; network: 'devnet' | 'mainnet'; zkAppAddress: string; nonce: number; message: string[] }> }>(
|
|
86
|
+
'POST', '/vendors/move/prepare', { from: a.from.address, to: a.to.address });
|
|
87
|
+
// Check each message is the key change it says it is before signing.
|
|
88
|
+
for (const c of prep.changes) {
|
|
89
|
+
const expected = vendorKeyMessage({ network: networkField(c.network), app: PublicKey.fromBase58(c.zkAppAddress), nonce: UInt32.from(c.nonce), vendor: PublicKey.fromBase58(a.to.address) }).map(String);
|
|
90
|
+
if (expected.join() !== c.message.join()) throw new KeysError(`The key change for ${c.zkAppAddress} is not the one asked for: not signing`, 0, 'SIGNATURE_INVALID');
|
|
91
|
+
}
|
|
92
|
+
const changes = [];
|
|
93
|
+
for (const c of prep.changes) changes.push({ zkAppAddress: c.zkAppAddress, oldSignature: await a.from.signFields(c.message), newSignature: await a.to.signFields(c.message) });
|
|
94
|
+
return call<{ moved: { from: string; to: string }; changes: unknown[]; keeperSends: boolean }>('POST', '/vendors/move', {
|
|
95
|
+
network: a.network, from: a.from.address, to: a.to.address,
|
|
96
|
+
oldTextSignature: await a.from.signMessage(prep.text), newTextSignature: await a.to.signMessage(prep.text), changes,
|
|
97
|
+
});
|
|
98
|
+
},
|
|
99
|
+
|
|
100
|
+
// ── license transfer ────────────────────────────────────────────────────
|
|
101
|
+
|
|
102
|
+
/** The new owner opens a transfer with the public key of their new passphrase (deriveOwnershipKeypair). */
|
|
103
|
+
openTransfer: (a: { zkAppAddress: string; licenseHash: string; newOwnerPubKey: string }) =>
|
|
104
|
+
call<{ transfer: LicenseTransfer }>('POST', '/transfers', a).then(r => r.transfer),
|
|
105
|
+
getTransfer: (id: string) => call<{ transfer: LicenseTransfer }>('GET', `/transfers/${encodeURIComponent(id)}`).then(r => r.transfer),
|
|
106
|
+
/**
|
|
107
|
+
* Signs the transfer with an ownership key: the current owner's (`role:
|
|
108
|
+
* 'old'`; from findOwnershipKeypair, since a license carried over from an
|
|
109
|
+
* earlier generation is keyed to that one's address) or the new owner's
|
|
110
|
+
* (`'new'`; deriveOwnershipKeypair with the transfer's zkAppAddress). The message is checked first
|
|
111
|
+
* against the transfer's details.
|
|
112
|
+
*/
|
|
113
|
+
async signTransfer(id: string, role: 'old' | 'new', ownerKey: PrivateKey) {
|
|
114
|
+
const t = await call<{ transfer: LicenseTransfer }>('GET', `/transfers/${encodeURIComponent(id)}`).then(r => r.transfer);
|
|
115
|
+
const pub = ownerKey.toPublicKey();
|
|
116
|
+
if (role === 'old' && ownerPubKeyHash(pub).toString() !== t.from) throw new KeysError('This passphrase does not own the license', 0, 'FORBIDDEN');
|
|
117
|
+
if (role === 'new' && pub.toBase58() !== t.newOwner) throw new KeysError('This passphrase is not the one the transfer was opened for', 0, 'FORBIDDEN');
|
|
118
|
+
const expected = licenseTransferMessage({
|
|
119
|
+
network: networkField(t.network), app: PublicKey.fromBase58(t.zkAppAddress), leafHash: Field(t.leafHash), to: Field(t.to), validUntil: UInt32.from(t.validUntil),
|
|
120
|
+
}).map(String);
|
|
121
|
+
if (expected.join() !== t.message.join() || ownerPubKeyHash(PublicKey.fromBase58(t.newOwner)).toString() !== t.to) {
|
|
122
|
+
throw new KeysError('The transfer message does not match its details: not signing', 0, 'SIGNATURE_INVALID');
|
|
123
|
+
}
|
|
124
|
+
const signature = Signature.create(ownerKey, t.message.map(x => Field(x))).toBase58();
|
|
125
|
+
return call<{ transfer: LicenseTransfer }>('POST', `/transfers/${encodeURIComponent(id)}/signatures`, {
|
|
126
|
+
role, signature, ...(role === 'old' ? { ownerPubKey: pub.toBase58() } : {}),
|
|
127
|
+
}).then(r => r.transfer);
|
|
128
|
+
},
|
|
129
|
+
/** Both signed: proves the transfer for `feePayer` (the new owner's wallet) to sign and send. */
|
|
130
|
+
proveTransfer: (id: string, feePayer: string) => call<{ provenTxJson: string; transferId: string }>('POST', '/prove/transfer', { id, feePayer }),
|
|
131
|
+
confirmTransfer: (id: string, txHash: string) => call<{ ok: true }>('POST', '/confirm/transfer', { id, txHash }),
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export type KeysClient = ReturnType<typeof createKeysClient>;
|