@sorandomains/holder 0.5.0 → 0.5.1
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 +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +11 -2
- package/dist/native-holder.d.ts +1 -1
- package/dist/native-holder.js +53 -22
- package/dist/native-transport.d.ts +8 -1
- package/dist/native-transport.js +39 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -143,7 +143,7 @@ supply the matching `primaryId` explicitly.
|
|
|
143
143
|
|
|
144
144
|
## Native username claiming
|
|
145
145
|
|
|
146
|
-
Native username claiming uses `claimQuote`, `createClaimIntent`, `buildClaim` and `claim`. The current namespace owner must first enable the public policy. The claimant's G wallet authorizes the exact username, complete receiving destination, owner price, policy version and deadline. `recoverClaim` reads the original immutable receipt; it never silently retries an uncertain transaction. `acceptNameTransferWithDestination` and `renewName` cover separately authorized holder lifecycle actions.
|
|
146
|
+
Native username claiming uses `claimQuote`, `createClaimIntent`, `buildClaim` and `claim`. The current namespace owner must first enable the public policy. The claimant's G wallet authorizes the exact username, complete receiving destination, owner price, policy version and deadline. `recoverClaim` reads the original immutable receipt; it never silently retries an uncertain transaction. Receipt reads, receipt absence and confirmed transaction results are accepted only after a clean Registrar attestation and executable check with RPC ledger context at least as recent as the receipt read or transaction inclusion. Missing or older context stops recovery; retain the original request and transaction hash. This check trusts the configured RPC to report its state and ledger honestly. Later namespace-owner, claim-policy or Resolver changes alone do not invalidate a historical receipt, while a tainted or upgraded Registrar cannot supply authoritative history. `acceptNameTransferWithDestination` and `renewName` cover separately authorized holder lifecycle actions.
|
|
147
147
|
|
|
148
148
|
Read the [native claim APIs, security boundaries and complete signup flow](https://github.com/SoranDomains/sdk/blob/main/NATIVE-CLAIMS.md). G/no memo, G with ID/Text/Hash, full M/no separate memo and C/no memo remain supported payment destinations. Current transaction-signing adapters use classic G accounts.
|
|
149
149
|
|
package/dist/index.d.ts
CHANGED
|
@@ -35,7 +35,7 @@ import { type PaymentDestination } from "./payment.js";
|
|
|
35
35
|
export { encodeMuxedAddress, decodeMuxedAddress, PAYMENT_RECORD_KEY, encodePaymentRecord, parsePaymentRecord, validatePaymentDestination, type PaymentMemo, type PaymentDestination } from "./payment.js";
|
|
36
36
|
import { type ClaimSubmitOptions } from "./native-holder.js";
|
|
37
37
|
import type { ClaimIntent, TransferIntent, RenewIntent } from "./native-types.js";
|
|
38
|
-
import type
|
|
38
|
+
import { type NativeWriteOptions } from "./native-transport.js";
|
|
39
39
|
export * from "./native-types.js";
|
|
40
40
|
export * from "./native-allowlist.js";
|
|
41
41
|
export * from "./native-approver.js";
|
|
@@ -244,6 +244,8 @@ export declare class SoranHolder {
|
|
|
244
244
|
private paymentResolverOf;
|
|
245
245
|
private assertMemoFree;
|
|
246
246
|
private read;
|
|
247
|
+
private readWithLedger;
|
|
248
|
+
private simulateRead;
|
|
247
249
|
private signEnvelope;
|
|
248
250
|
private serialize;
|
|
249
251
|
private sourceAccount;
|
package/dist/index.js
CHANGED
|
@@ -36,6 +36,7 @@ import { decodeMuxedAddress, destinationFromNative, paymentFromNative, paymentMe
|
|
|
36
36
|
export { encodeMuxedAddress, decodeMuxedAddress, PAYMENT_RECORD_KEY, encodePaymentRecord, parsePaymentRecord, validatePaymentDestination } from "./payment.js";
|
|
37
37
|
import { NativeHolderClient } from "./native-holder.js";
|
|
38
38
|
import { NativeClaimError } from "./native-codec.js";
|
|
39
|
+
import { requireReadLedger } from "./native-transport.js";
|
|
39
40
|
export * from "./native-types.js";
|
|
40
41
|
export * from "./native-allowlist.js";
|
|
41
42
|
export * from "./native-approver.js";
|
|
@@ -280,7 +281,8 @@ export class SoranHolder {
|
|
|
280
281
|
}
|
|
281
282
|
nativeClient() {
|
|
282
283
|
return new NativeHolderClient({ registryId: this.registryId, passphrase: this.passphrase, server: this.server, signer: this.signer, fee: this.fee, timeoutSecs: this.timeoutSecs, maxFeeStroops: this.maxNativeFeeStroops,
|
|
283
|
-
read: (id, method, args) => this.read(id, method, args),
|
|
284
|
+
read: (id, method, args) => this.read(id, method, args),
|
|
285
|
+
readWithLedger: (id, method, args) => this.readWithLedger(id, method, args), serialize: work => this.serialize(work) });
|
|
284
286
|
}
|
|
285
287
|
nativeClaimCapability(namespace) { return this.nativeClient().nativeClaimCapability(namespace); }
|
|
286
288
|
claimQuote(name, claimant) { return this.nativeClient().claimQuote(name, claimant); }
|
|
@@ -603,6 +605,13 @@ export class SoranHolder {
|
|
|
603
605
|
return registrar;
|
|
604
606
|
}
|
|
605
607
|
async read(contractId, fn, args) {
|
|
608
|
+
return (await this.simulateRead(contractId, fn, args)).value;
|
|
609
|
+
}
|
|
610
|
+
async readWithLedger(contractId, fn, args) {
|
|
611
|
+
const result = await this.simulateRead(contractId, fn, args);
|
|
612
|
+
return { value: result.value, ledger: requireReadLedger(result.ledger) };
|
|
613
|
+
}
|
|
614
|
+
async simulateRead(contractId, fn, args) {
|
|
606
615
|
const tx = new TransactionBuilder(new Account(SIM_SOURCE, "0"), {
|
|
607
616
|
fee: BASE_FEE,
|
|
608
617
|
networkPassphrase: this.passphrase,
|
|
@@ -619,7 +628,7 @@ export class SoranHolder {
|
|
|
619
628
|
if (!rpc.Api.isSimulationSuccess(sim) || !sim.result?.retval)
|
|
620
629
|
throw new HolderError(`${fn}: missing simulation return value`, contractId, fn);
|
|
621
630
|
const v = scValToNative(sim.result.retval);
|
|
622
|
-
return v === undefined ? null : v;
|
|
631
|
+
return { value: v === undefined ? null : v, ledger: sim.latestLedger };
|
|
623
632
|
}
|
|
624
633
|
async signEnvelope(xdrBase64) {
|
|
625
634
|
const signed = await this.signer.signTransaction(xdrBase64, {
|
package/dist/native-holder.d.ts
CHANGED
|
@@ -31,7 +31,7 @@ export declare class NativeHolderClient {
|
|
|
31
31
|
private checkQuote;
|
|
32
32
|
claimReceipt(namespace: string, claimant: string, requestId: string): Promise<ClaimReceipt | null>;
|
|
33
33
|
private receiptAt;
|
|
34
|
-
recoverClaim(
|
|
34
|
+
recoverClaim(input: ClaimIntent): Promise<ClaimReceipt | null>;
|
|
35
35
|
private planClaim;
|
|
36
36
|
buildClaim(intent: ClaimIntent, options?: ClaimSubmitOptions): Promise<PreparedClaim>;
|
|
37
37
|
claim(intent: ClaimIntent, options?: ClaimSubmitOptions): Promise<NativeClaimSubmission>;
|
package/dist/native-holder.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { hash, scValToNative, xdr } from "@stellar/stellar-sdk";
|
|
2
2
|
import { NativeClaimError, address, bytes32, exactObject, hex, hex32, label, namespaceNode, sc, u64, unhex, utf8 } from "./native-codec.js";
|
|
3
|
-
import { nativeCapability, requireNative, verifyRegistrarProvenance, prepareNative, sendNative } from "./native-transport.js";
|
|
3
|
+
import { nativeCapability, requireNative, requireReadLedger, verifyRegistrarProvenance, prepareNative, sendNative } from "./native-transport.js";
|
|
4
4
|
import { claimIntentToScVal, claimIntentFromNative, claimQuoteFromNative, claimReceiptFromNative, claimResultFromNative, destinationPreviewFromNative, transferIntentToScVal, renewIntentToScVal, nativeIntentHash, claimLabelToScVal } from "./native-types.js";
|
|
5
5
|
import { paymentDestinationToScVal } from "./native-codec.js";
|
|
6
6
|
import { validatePaymentDestination } from "./payment.js";
|
|
@@ -27,6 +27,8 @@ function receiptMatches(receipt, method, intent, holder) {
|
|
|
27
27
|
if (receipt.intentHash !== nativeIntentHash(method, intent) || receipt.holder !== holder)
|
|
28
28
|
throw new NativeClaimError("request ID already belongs to a different immutable intent");
|
|
29
29
|
const raw = scValToNative(intent), expectedOperation = { claim: "claim", issue_reserved_with_destination: "reserved", accept_transfer_with_destination: "transfer", renew_holder: "renew" }[method];
|
|
30
|
+
if (receipt.timestamp < raw.context.valid_after || receipt.timestamp > raw.context.deadline)
|
|
31
|
+
throw new NativeClaimError("receipt timestamp is outside the original request window");
|
|
30
32
|
const nodeBytes = new Uint8Array(64);
|
|
31
33
|
nodeBytes.set(raw.context.namespace);
|
|
32
34
|
nodeBytes.set(hash(raw.label), 32);
|
|
@@ -44,20 +46,34 @@ function receiptMatches(receipt, method, intent, holder) {
|
|
|
44
46
|
else {
|
|
45
47
|
if (receipt.feeAmount !== 0n || receipt.feeRecipient !== null)
|
|
46
48
|
throw new NativeClaimError("lifecycle receipt unexpectedly contains a username fee");
|
|
47
|
-
if (method === "accept_transfer_with_destination" && receipt.expiresAt !== raw.expected_expiry)
|
|
48
|
-
throw new NativeClaimError("transfer receipt changed the lease");
|
|
49
|
+
if (method === "accept_transfer_with_destination" && (receipt.expiresAt !== raw.expected_expiry || receipt.timestamp > raw.proposal_expires))
|
|
50
|
+
throw new NativeClaimError("transfer receipt changed the lease or exceeded the proposal window");
|
|
49
51
|
if (method === "renew_holder" && (receipt.expiresAt < raw.min_new_expiry || receipt.expiresAt > raw.max_new_expiry || receipt.expiresAt !== (receipt.timestamp > raw.expected_expiry ? receipt.timestamp : raw.expected_expiry) + raw.term_secs))
|
|
50
52
|
throw new NativeClaimError("renewal receipt differs from signed lease bounds");
|
|
51
53
|
}
|
|
52
54
|
}
|
|
53
|
-
function
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
55
|
+
function receiptReadMatches(receipt, holder, observedLedger) {
|
|
56
|
+
requireReadLedger(receipt.ledger);
|
|
57
|
+
if (receipt.holder !== holder || receipt.ledger > observedLedger)
|
|
58
|
+
throw new NativeClaimError("receipt holder or inclusion ledger differs from the requested snapshot", "unavailable");
|
|
59
|
+
}
|
|
60
|
+
async function confirmedResult(context, result, method, encoded, holder) {
|
|
61
|
+
try {
|
|
62
|
+
// Inclusion proves the reviewed transaction ran, not that an owner left the
|
|
63
|
+
// Registrar on its trusted implementation until it ran. Recheck after it.
|
|
64
|
+
const raw = scValToNative(encoded);
|
|
65
|
+
await verifyRegistrarProvenance(context, raw.context.registrar, hex(raw.context.namespace), result.ledger);
|
|
66
|
+
const decoded = claimResultFromNative(result.value);
|
|
67
|
+
receiptReadMatches(decoded.receipt, holder, result.ledger);
|
|
68
|
+
if (decoded.status === "fresh" && decoded.receipt.ledger !== result.ledger)
|
|
69
|
+
throw new NativeClaimError("fresh receipt does not belong to the confirmed ledger");
|
|
70
|
+
receiptMatches(decoded.receipt, method, encoded, holder);
|
|
71
|
+
return { ...decoded, transaction: { hash: result.hash, ledger: result.ledger } };
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
throw new NativeClaimError(`transaction confirmed but result could not be verified: ${String(error)}; recover the original request`, "pending", result.hash);
|
|
75
|
+
}
|
|
57
76
|
}
|
|
58
|
-
catch (error) {
|
|
59
|
-
throw new NativeClaimError(`transaction confirmed but result could not be verified: ${String(error)}; recover the original request`, "pending", result.hash);
|
|
60
|
-
} }
|
|
61
77
|
export class NativeHolderClient {
|
|
62
78
|
context;
|
|
63
79
|
constructor(context) {
|
|
@@ -100,14 +116,31 @@ export class NativeHolderClient {
|
|
|
100
116
|
if (quote.node !== hex(hash(bytes)))
|
|
101
117
|
throw new NativeClaimError("quote name node is invalid", "unavailable");
|
|
102
118
|
}
|
|
103
|
-
async claimReceipt(namespace, claimant, requestId) {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
119
|
+
async claimReceipt(namespace, claimant, requestId) {
|
|
120
|
+
const canonical = canonicalNamespace(namespace);
|
|
121
|
+
return this.receiptAt(await this.registrar(canonical), hex(namespaceNode(canonical)), claimant, requestId);
|
|
122
|
+
}
|
|
123
|
+
async receiptAt(registrar, namespace, claimant, requestId) {
|
|
124
|
+
const holder = address(claimant, "identity", "receipt holder");
|
|
125
|
+
const snapshot = await this.context.readWithLedger(registrar, "claim_receipt", [sc.address(holder), sc.bytes(unhex(hex32(requestId, "request ID")))]);
|
|
126
|
+
const ledger = requireReadLedger(snapshot?.ledger);
|
|
127
|
+
// Validate both Some and None before they can drive a recovery/replay decision.
|
|
128
|
+
await verifyRegistrarProvenance(this.context, registrar, namespace, ledger);
|
|
129
|
+
if (snapshot.value === null)
|
|
130
|
+
return null;
|
|
131
|
+
const receipt = claimReceiptFromNative(snapshot.value);
|
|
132
|
+
receiptReadMatches(receipt, holder, ledger);
|
|
133
|
+
return receipt;
|
|
134
|
+
}
|
|
135
|
+
async recoverClaim(input) {
|
|
136
|
+
const encoded = claimIntentToScVal(input), intent = claimIntentFromNative(scValToNative(encoded));
|
|
137
|
+
scoped(this.context, intent.context);
|
|
138
|
+
await verifyRegistrarProvenance(this.context, intent.context.registrar, intent.context.namespace);
|
|
139
|
+
const receipt = await this.receiptAt(intent.context.registrar, intent.context.namespace, intent.claimant, intent.context.requestId);
|
|
140
|
+
if (receipt)
|
|
141
|
+
receiptMatches(receipt, "claim", encoded, intent.claimant);
|
|
142
|
+
return receipt;
|
|
107
143
|
}
|
|
108
|
-
async recoverClaim(intent) { const encoded = claimIntentToScVal(intent); scoped(this.context, intent.context); await verifyRegistrarProvenance(this.context, intent.context.registrar, intent.context.namespace); if (await this.context.read(this.context.registryId, "registrar_of", [sc.bytes(unhex(intent.context.namespace))]) !== intent.context.registrar)
|
|
109
|
-
throw new NativeClaimError("recovery Registrar is not the Registry attestation", "unavailable"); const receipt = await this.receiptAt(intent.context.registrar, intent.claimant, intent.context.requestId); if (receipt)
|
|
110
|
-
receiptMatches(receipt, "claim", encoded, intent.claimant); return receipt; }
|
|
111
144
|
async planClaim(input, proof = []) {
|
|
112
145
|
const encoded = claimIntentToScVal(input);
|
|
113
146
|
const intent = claimIntentFromNative(scValToNative(encoded));
|
|
@@ -177,7 +210,7 @@ export class NativeHolderClient {
|
|
|
177
210
|
// Snapshot before any awaits: callers cannot mutate reviewed destination/amount mid-flow.
|
|
178
211
|
const snapshot = claimIntentFromNative(scValToNative(claimIntentToScVal(intent)));
|
|
179
212
|
return this.context.serialize(async () => { const old = await this.recoverClaim(snapshot); if (old)
|
|
180
|
-
return { status: "replayed", receipt: old, transaction: null }; const { plan } = await this.planClaim(snapshot, options.proof); const result = await sendNative(this.context, plan, options); return confirmedResult(result, "claim", claimIntentToScVal(snapshot), snapshot.claimant); });
|
|
213
|
+
return { status: "replayed", receipt: old, transaction: null }; const { plan } = await this.planClaim(snapshot, options.proof); const result = await sendNative(this.context, plan, options); return confirmedResult(this.context, result, "claim", claimIntentToScVal(snapshot), snapshot.claimant); });
|
|
181
214
|
}
|
|
182
215
|
async renewalPreview(name) {
|
|
183
216
|
const parsed = names(name), registrar = await this.registrar(parsed.namespace);
|
|
@@ -198,9 +231,7 @@ export class NativeHolderClient {
|
|
|
198
231
|
scoped(this.context, request);
|
|
199
232
|
return this.context.serialize(async () => {
|
|
200
233
|
await verifyRegistrarProvenance(this.context, request.registrar, request.namespace);
|
|
201
|
-
|
|
202
|
-
throw new NativeClaimError("recovery Registrar is not the Registry attestation", "unavailable");
|
|
203
|
-
const old = await this.receiptAt(request.registrar, holder, request.requestId);
|
|
234
|
+
const old = await this.receiptAt(request.registrar, request.namespace, holder, request.requestId);
|
|
204
235
|
if (old) {
|
|
205
236
|
receiptMatches(old, method, intent, holder);
|
|
206
237
|
return { status: "replayed", receipt: old, transaction: null };
|
|
@@ -215,7 +246,7 @@ export class NativeHolderClient {
|
|
|
215
246
|
if (source !== holder)
|
|
216
247
|
throw new NativeClaimError("connected wallet is not the intended holder/recipient", "authorization");
|
|
217
248
|
const result = await sendNative(this.context, { source, contract: request.registrar, method, args: [intent], sourceInvocation: { contract: request.registrar, method, args: [intent], children: child ? [child] : [] }, maxFeeStroops: this.context.maxFeeStroops }, options);
|
|
218
|
-
return confirmedResult(result, method, intent, holder);
|
|
249
|
+
return confirmedResult(this.context, result, method, intent, holder);
|
|
219
250
|
});
|
|
220
251
|
}
|
|
221
252
|
}
|
|
@@ -8,6 +8,10 @@ export type NativeSigner = {
|
|
|
8
8
|
signedTxXdr: string;
|
|
9
9
|
}>;
|
|
10
10
|
};
|
|
11
|
+
export type NativeRead = {
|
|
12
|
+
value: unknown;
|
|
13
|
+
ledger: number;
|
|
14
|
+
};
|
|
11
15
|
export type NativeContext = {
|
|
12
16
|
registryId: string;
|
|
13
17
|
passphrase: string;
|
|
@@ -17,6 +21,7 @@ export type NativeContext = {
|
|
|
17
21
|
timeoutSecs: number;
|
|
18
22
|
maxFeeStroops: bigint;
|
|
19
23
|
read(contract: string, method: string, args: xdr.ScVal[]): Promise<unknown>;
|
|
24
|
+
readWithLedger(contract: string, method: string, args: xdr.ScVal[]): Promise<NativeRead>;
|
|
20
25
|
serialize<T>(work: () => Promise<T>): Promise<T>;
|
|
21
26
|
};
|
|
22
27
|
export type NativeCapability = {
|
|
@@ -33,8 +38,10 @@ export type NativeCapability = {
|
|
|
33
38
|
owner: string;
|
|
34
39
|
ownerEpoch: bigint;
|
|
35
40
|
};
|
|
41
|
+
/** A missing context is not evidence that an RPC observation is fresh. */
|
|
42
|
+
export declare function requireReadLedger(value: unknown, minimum?: number): number;
|
|
36
43
|
/** Historical reads must not trust an upgraded Registrar that can fabricate receipts. Resolver changes do not block this Registrar-only check. */
|
|
37
|
-
export declare function verifyRegistrarProvenance(context: NativeContext, registrar: string, namespace: string): Promise<void>;
|
|
44
|
+
export declare function verifyRegistrarProvenance(context: NativeContext, registrar: string, namespace: string, minimumLedger?: number): Promise<void>;
|
|
38
45
|
export declare function nativeCapability(context: NativeContext, namespace: string): Promise<NativeCapability>;
|
|
39
46
|
export declare function requireNative(context: NativeContext, namespace: string): Promise<Extract<NativeCapability, {
|
|
40
47
|
supported: true;
|
package/dist/native-transport.js
CHANGED
|
@@ -3,8 +3,46 @@ import { NativeClaimError, address, bytes32, hex, namespaceNode, sc, unhex } fro
|
|
|
3
3
|
import { assertSignedBodyUnchanged, validateEligibilityAuthorization, validateNativeTransaction } from "./native-auth.js";
|
|
4
4
|
/** This published legacy code has owner-only issuance. Missing RPC data is never legacy detection. */
|
|
5
5
|
const LEGACY_REGISTRAR_HASH = "ed06b3374ff4342b4a2316fc546505132cd8fd35be6666571cf088710af9bbc6";
|
|
6
|
+
/** A missing context is not evidence that an RPC observation is fresh. */
|
|
7
|
+
export function requireReadLedger(value, minimum = 1) {
|
|
8
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value < minimum || value < 1 || value > 0xffff_ffff)
|
|
9
|
+
throw new NativeClaimError("RPC read has missing, invalid or stale ledger context", "unavailable");
|
|
10
|
+
return value;
|
|
11
|
+
}
|
|
12
|
+
/** Every post-receipt observation must be at least as new as that simulation.
|
|
13
|
+
* Registry taint is irreversible: clean at/after the receipt rules out an
|
|
14
|
+
* upgrade before it, even when these truthful RPC snapshots differ in ledger.
|
|
15
|
+
* The RPC remains trusted to report its ledger and returned state honestly.
|
|
16
|
+
*/
|
|
17
|
+
async function verifyProvenanceAfter(context, registrar, namespace, minimumLedger) {
|
|
18
|
+
requireReadLedger(minimumLedger);
|
|
19
|
+
const node = sc.bytes(unhex(namespace));
|
|
20
|
+
const key = new Contract(registrar).getFootprint();
|
|
21
|
+
const [attestation, taint, templates, executable] = await Promise.all([
|
|
22
|
+
context.readWithLedger(context.registryId, "registrar_of", [node]),
|
|
23
|
+
context.readWithLedger(context.registryId, "registrar_tainted", [node]),
|
|
24
|
+
context.readWithLedger(context.registryId, "template_hashes", []),
|
|
25
|
+
context.server.getLedgerEntries(key),
|
|
26
|
+
]);
|
|
27
|
+
for (const observation of [attestation, taint, templates])
|
|
28
|
+
requireReadLedger(observation?.ledger, minimumLedger);
|
|
29
|
+
requireReadLedger(executable?.latestLedger, minimumLedger);
|
|
30
|
+
if (attestation.value !== registrar || taint.value !== false || !Array.isArray(templates.value) || templates.value.length !== 2)
|
|
31
|
+
throw new NativeClaimError("Registrar provenance changed or is unavailable after receipt read", "unavailable");
|
|
32
|
+
// getContractInstance discards latestLedger; read and bind the exact entry.
|
|
33
|
+
const entry = executable.entries?.[0];
|
|
34
|
+
if (executable.entries?.length !== 1 || !entry || entry.key.toXDR("base64") !== key.toXDR("base64") || entry.val.type !== "contractData")
|
|
35
|
+
throw new NativeClaimError("Registrar executable proof is missing or mismatched", "unavailable");
|
|
36
|
+
const data = entry.val.value;
|
|
37
|
+
if (data.contract.toXDR("base64") !== new Contract(registrar).address().toScAddress().toXDR("base64") || data.key.type !== "scvLedgerKeyContractInstance" || data.durability !== xdr.ContractDataDurability.persistent || data.val.type !== "scvContractInstance" || data.val.value.executable.type !== "contractExecutableWasm")
|
|
38
|
+
throw new NativeClaimError("Registrar executable proof is not the requested Wasm instance", "unavailable");
|
|
39
|
+
if (hex(bytes32(templates.value[0], "Registry Registrar template")) !== hex(data.val.value.executable.wasmHash.value))
|
|
40
|
+
throw new NativeClaimError("Registrar executable differs from immutable Registry template after receipt read", "unavailable");
|
|
41
|
+
}
|
|
6
42
|
/** Historical reads must not trust an upgraded Registrar that can fabricate receipts. Resolver changes do not block this Registrar-only check. */
|
|
7
|
-
export async function verifyRegistrarProvenance(context, registrar, namespace) {
|
|
43
|
+
export async function verifyRegistrarProvenance(context, registrar, namespace, minimumLedger) {
|
|
44
|
+
if (minimumLedger !== undefined)
|
|
45
|
+
return verifyProvenanceAfter(context, registrar, namespace, minimumLedger);
|
|
8
46
|
const node = sc.bytes(unhex(namespace));
|
|
9
47
|
const [attested, tainted, templates, instance] = await Promise.all([
|
|
10
48
|
context.read(context.registryId, "registrar_of", [node]), context.read(context.registryId, "registrar_tainted", [node]),
|
package/package.json
CHANGED