@sorandomains/owner 0.10.1 → 0.12.0
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 +68 -3
- package/dist/funding-history.js +3 -3
- package/dist/funding-reader.js +4 -0
- package/dist/funding-service.js +14 -2
- package/dist/funding-types.d.ts +2 -0
- package/dist/funding-types.js +30 -0
- package/dist/index.d.ts +82 -2
- package/dist/index.js +131 -18
- package/dist/multichain.d.ts +99 -0
- package/dist/multichain.js +204 -0
- package/dist/names.d.ts +29 -0
- package/dist/names.js +83 -0
- package/dist/native-code-policy.d.ts +7 -0
- package/dist/native-code-policy.js +7 -6
- package/dist/native-codec.js +1 -1
- package/dist/native-transport.d.ts +2 -0
- package/dist/native-transport.js +18 -1
- package/dist/native-upgrades.d.ts +48 -0
- package/dist/native-upgrades.js +93 -0
- package/dist/subnames-client.d.ts +49 -0
- package/dist/subnames-client.js +63 -0
- package/package.json +11 -3
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { NativeClaimError, address, bool, bytes32, exactObject, hex, namespaceNode, sc, u32 } from "./native-codec.js";
|
|
2
|
+
import { expectedNativeCode } from "./native-code-policy.js";
|
|
3
|
+
/**
|
|
4
|
+
* Reviewed implementation lineage of the governed testnet Registry. Governance
|
|
5
|
+
* approval means "this hash may be installed on any namespace", not "this is a
|
|
6
|
+
* safe transition": superseded releases stay approved, so the chain accepts a
|
|
7
|
+
* downgrade. `latest` is the release owners are offered; `superseded` are the
|
|
8
|
+
* older releases it replaces. A test keeps this equal to the web catalog
|
|
9
|
+
* (web/lib/contractUpdates.ts) and the release record; adding a release means
|
|
10
|
+
* moving the old `latest` into `superseded` here and there.
|
|
11
|
+
*/
|
|
12
|
+
export const CONTRACT_RELEASES = {
|
|
13
|
+
registrar: {
|
|
14
|
+
latest: "4d6d18625f4d890028acb794b24871c5c93f96dc15d94cf3d52cbf9713b07fb1",
|
|
15
|
+
superseded: [
|
|
16
|
+
"42dc5f8b40932617d3f2667e1d26dad3e5e9a62c63de1fde7c016cb67a86c420",
|
|
17
|
+
"5c8ae72ea7370ccf19af52b9bbc0007362be94464651ccdf68a2bb1f478b6f2c",
|
|
18
|
+
"61ded153e6311075c1087c5639a5410222e23f7cade239ed2c946150af94b1c5",
|
|
19
|
+
"26e03cd1d7fbfa46fe442406fb1b88022ec8cd8bd9123bed570f971fce596a14",
|
|
20
|
+
],
|
|
21
|
+
},
|
|
22
|
+
resolver: {
|
|
23
|
+
latest: "941bf87f26cb817f3c6a52a326de855cca8a1dc4477e62a38ec4ffa5c7f43806",
|
|
24
|
+
superseded: [
|
|
25
|
+
"cb927ffa19d6b1f3f0b0187048d885e200adb6de3fddd11ee3cfeb59df9c5c48",
|
|
26
|
+
"351bf4e056b179b578fe2a4ea46f937bfafccc6538c7357ea70021b7038d91ea",
|
|
27
|
+
"c6dfcd7bbf35943a13406888678334d0938d0fea54939906b00c2dc37b4a9a71",
|
|
28
|
+
"5e7b4fb8d310d67ed6966a155e748d60be771422b3e8c34bbc14485f98525574",
|
|
29
|
+
],
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
export function contractUpgradeDirection(role, currentHash, targetHash) {
|
|
33
|
+
const { latest, superseded } = CONTRACT_RELEASES[role];
|
|
34
|
+
if (currentHash === targetHash)
|
|
35
|
+
return "same";
|
|
36
|
+
if (superseded.includes(targetHash))
|
|
37
|
+
return "backward";
|
|
38
|
+
if (targetHash !== latest)
|
|
39
|
+
return "unreviewed-target";
|
|
40
|
+
return superseded.includes(currentHash) ? "forward" : "unrecognized-current";
|
|
41
|
+
}
|
|
42
|
+
export function upgradeHash(hash) {
|
|
43
|
+
if (typeof hash !== "string" || !/^[a-f0-9]{64}$/.test(hash))
|
|
44
|
+
throw new NativeClaimError("Contract implementation hash must be lowercase hex32");
|
|
45
|
+
return sc.bytes(Uint8Array.from(hash.match(/../g), value => parseInt(value, 16)));
|
|
46
|
+
}
|
|
47
|
+
/** Independent of claim versions, claim configuration and fee-token policy. */
|
|
48
|
+
export async function contractUpgradeStatus(context, namespace, role, wasmHash) {
|
|
49
|
+
if (role !== "registrar" && role !== "resolver")
|
|
50
|
+
throw new NativeClaimError("Unknown namespace contract role");
|
|
51
|
+
const target = upgradeHash(wasmHash);
|
|
52
|
+
const node = namespaceNode(namespace), args = [sc.bytes(node)];
|
|
53
|
+
const [pair, owner, code, registrar, resolver, selected, registrarTainted, resolverTainted] = await Promise.all([
|
|
54
|
+
context.read(context.registryId, "native_contracts", args),
|
|
55
|
+
context.read(context.registryId, "owner_of", args),
|
|
56
|
+
expectedNativeCode(context, hex(node)),
|
|
57
|
+
context.read(context.registryId, "registrar_of", args),
|
|
58
|
+
context.read(context.registryId, "attested_resolver_of", args),
|
|
59
|
+
context.read(context.registryId, "resolver_of", args),
|
|
60
|
+
context.read(context.registryId, "registrar_tainted", args),
|
|
61
|
+
context.read(context.registryId, "resolver_tainted", args),
|
|
62
|
+
]);
|
|
63
|
+
const fail = (message) => { throw new NativeClaimError(message, "unavailable"); };
|
|
64
|
+
if (!Array.isArray(pair) || pair.length !== 2 || pair[0] !== registrar || pair[1] !== resolver || selected !== resolver || registrarTainted !== false || resolverTainted !== false)
|
|
65
|
+
fail("Namespace contracts are missing, tainted or differ from the Registry's native binding");
|
|
66
|
+
const registrarId = address(registrar, "contract", "namespace Registrar");
|
|
67
|
+
const resolverId = address(resolver, "contract", "namespace Resolver");
|
|
68
|
+
const contractId = role === "registrar" ? registrarId : resolverId;
|
|
69
|
+
const [registrarInstance, resolverInstance, anchors, provenanceRaw, authority, registry, version] = await Promise.all([
|
|
70
|
+
context.server.getContractInstance(registrarId),
|
|
71
|
+
context.server.getContractInstance(resolverId),
|
|
72
|
+
context.read(registrarId, "anchors", []),
|
|
73
|
+
context.read(resolverId, "provenance", []),
|
|
74
|
+
context.read(resolverId, "authority", []),
|
|
75
|
+
context.read(resolverId, "registry", []),
|
|
76
|
+
// A verified legacy Registry is safely reported as disallowed without probing a new child ABI.
|
|
77
|
+
code.governed ? context.read(contractId, "upgrade_policy_version", []) : 0,
|
|
78
|
+
]);
|
|
79
|
+
for (const [instance, expected] of [[registrarInstance, code.registrar], [resolverInstance, code.resolver]]) {
|
|
80
|
+
if (instance.executable.type !== "contractExecutableWasm" || hex(instance.executable.wasmHash.value) !== expected)
|
|
81
|
+
fail("Namespace contract executable differs from Registry-approved code");
|
|
82
|
+
}
|
|
83
|
+
if (!Array.isArray(anchors) || anchors.length !== 2 || anchors[0] !== context.registryId || hex(bytes32(anchors[1], "Registrar namespace")) !== hex(node))
|
|
84
|
+
fail("Registrar anchors differ from selected namespace");
|
|
85
|
+
const provenance = exactObject(provenanceRaw, ["registry", "node"], "Resolver provenance");
|
|
86
|
+
if (provenance.registry !== context.registryId || hex(bytes32(provenance.node, "Resolver namespace")) !== hex(node) || authority !== registrarId || registry !== context.registryId)
|
|
87
|
+
fail("Resolver provenance or authority differs from selected namespace");
|
|
88
|
+
const governed = code.governed && u32(version, "contract upgrade policy") === 1;
|
|
89
|
+
const approved = code.governed
|
|
90
|
+
? bool(await context.read(context.registryId, "implementation_approved", [sc.u32(role === "registrar" ? 0 : 1), target]), "implementation approval")
|
|
91
|
+
: false;
|
|
92
|
+
return { owner: address(owner, "identity", "namespace owner"), contractId, currentHash: code[role], approved, governed };
|
|
93
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { type xdr } from "@stellar/stellar-sdk";
|
|
2
|
+
import { type SubnameRecord } from "./names.js";
|
|
3
|
+
export declare const SUBNAME_REGISTRAR_ERRORS: Record<number, string>;
|
|
4
|
+
export declare const subnameLabelArg: (value: string) => xdr.ScVal;
|
|
5
|
+
export declare function subnameParent(input: string): import("./names.js").ParsedResolvableName;
|
|
6
|
+
export declare function subnameParts(input: string): import("./names.js").ParsedResolvableName;
|
|
7
|
+
export declare function createSubnameReader(config: {
|
|
8
|
+
registryId: string;
|
|
9
|
+
error?: (message: string) => Error;
|
|
10
|
+
read: (id: string, fn: string, args: xdr.ScVal[]) => Promise<unknown>;
|
|
11
|
+
}): {
|
|
12
|
+
context: (input: string) => Promise<{
|
|
13
|
+
namespace: string;
|
|
14
|
+
registrar: string;
|
|
15
|
+
owner: string;
|
|
16
|
+
policy: import("./names.js").SubnamePolicy;
|
|
17
|
+
}>;
|
|
18
|
+
creationContext: (input: string) => Promise<{
|
|
19
|
+
namespace: string;
|
|
20
|
+
registrar: string;
|
|
21
|
+
owner: string;
|
|
22
|
+
policy: import("./names.js").SubnamePolicy;
|
|
23
|
+
}>;
|
|
24
|
+
recordAt: (ctx: Awaited<ReturnType<(input: string) => Promise<{
|
|
25
|
+
namespace: string;
|
|
26
|
+
registrar: string;
|
|
27
|
+
owner: string;
|
|
28
|
+
policy: import("./names.js").SubnamePolicy;
|
|
29
|
+
}>>>, parent: string, label: string) => Promise<SubnameRecord | null>;
|
|
30
|
+
record: (name: string) => Promise<SubnameRecord | null>;
|
|
31
|
+
policy: (namespace: string) => Promise<import("./names.js").SubnamePolicy>;
|
|
32
|
+
list: (parent: string, options?: {
|
|
33
|
+
offset?: number;
|
|
34
|
+
limit?: number;
|
|
35
|
+
}) => Promise<{
|
|
36
|
+
parent: string;
|
|
37
|
+
policy: import("./names.js").SubnamePolicy;
|
|
38
|
+
records: {
|
|
39
|
+
parentNode: Uint8Array;
|
|
40
|
+
parentGeneration: bigint;
|
|
41
|
+
generation: bigint;
|
|
42
|
+
address: string;
|
|
43
|
+
active: boolean;
|
|
44
|
+
name: string;
|
|
45
|
+
}[];
|
|
46
|
+
offset: number;
|
|
47
|
+
nextOffset: number | null;
|
|
48
|
+
}>;
|
|
49
|
+
};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// Generated by sdk/names/sync.mjs; edit sdk/names/subnames-client.ts.
|
|
2
|
+
import { hash, nativeToScVal, StrKey } from "@stellar/stellar-sdk";
|
|
3
|
+
import { parseNamespace, parseResolvableName, resolvableNameNode, subnamePolicyFromNative, subnameRecordFromNative, subnameLabelsFromNative, subnamePageInput } from "./names.js";
|
|
4
|
+
export const SUBNAME_REGISTRAR_ERRORS = { 53: "SubnameCreationDisabled", 54: "SubnamesSuspended", 55: "SubnameGenerationMismatch", 56: "SubnameIndexUnavailable" };
|
|
5
|
+
const account = (value) => typeof value === "string" && (StrKey.isValidEd25519PublicKey(value) || StrKey.isValidContract(value));
|
|
6
|
+
const contract = (value) => typeof value === "string" && StrKey.isValidContract(value);
|
|
7
|
+
export const subnameLabelArg = (value) => nativeToScVal(new TextEncoder().encode(value), { type: "bytes" });
|
|
8
|
+
export function subnameParent(input) { const parsed = parseResolvableName(input); if (parsed.childLabel)
|
|
9
|
+
throw new Error("parent must be label.namespace"); return parsed; }
|
|
10
|
+
export function subnameParts(input) { const parsed = parseResolvableName(input); if (!parsed.childLabel)
|
|
11
|
+
throw new Error("subname must be child.label.namespace"); return parsed; }
|
|
12
|
+
export function createSubnameReader(config) {
|
|
13
|
+
const read = config.read;
|
|
14
|
+
const failure = (message) => config.error?.(message) ?? new Error(message);
|
|
15
|
+
async function context(input) {
|
|
16
|
+
const namespace = parseNamespace(input);
|
|
17
|
+
if (!contract(config.registryId))
|
|
18
|
+
throw failure("invalid Registry");
|
|
19
|
+
const joined = new Uint8Array(64);
|
|
20
|
+
joined.set(hash(new TextEncoder().encode(namespace)), 32);
|
|
21
|
+
const node = hash(joined);
|
|
22
|
+
const arg = nativeToScVal(node, { type: "bytes" });
|
|
23
|
+
const [registrar, owner] = await Promise.all([read(config.registryId, "registrar_of", [arg]), read(config.registryId, "owner_of", [arg])]);
|
|
24
|
+
if (!contract(registrar) || !account(owner))
|
|
25
|
+
throw failure("namespace has no current Registrar or owner");
|
|
26
|
+
const anchors = await read(registrar, "anchors", []);
|
|
27
|
+
if (!Array.isArray(anchors) || anchors.length !== 2 || anchors[0] !== config.registryId || !(anchors[1] instanceof Uint8Array) || anchors[1].length !== 32 || !node.every((b, i) => b === anchors[1][i]))
|
|
28
|
+
throw failure("subname Registrar context mismatch");
|
|
29
|
+
if (await read(registrar, "subname_version", []) !== 1)
|
|
30
|
+
throw failure("subnames unsupported");
|
|
31
|
+
const policy = subnamePolicyFromNative(await read(registrar, "subname_policy", []));
|
|
32
|
+
return { namespace, registrar, owner, policy };
|
|
33
|
+
}
|
|
34
|
+
async function creationContext(input) {
|
|
35
|
+
const ctx = await context(input), joined = new Uint8Array(64);
|
|
36
|
+
joined.set(hash(new TextEncoder().encode(ctx.namespace)), 32);
|
|
37
|
+
const resolver = await read(config.registryId, "resolver_of", [nativeToScVal(hash(joined), { type: "bytes" })]);
|
|
38
|
+
if (!contract(resolver))
|
|
39
|
+
throw failure("subnames unsupported: no Resolver");
|
|
40
|
+
const [anchor, authority, version] = await Promise.all([read(resolver, "registry", []), read(resolver, "authority", []), read(resolver, "subname_version", [])]);
|
|
41
|
+
if (anchor !== config.registryId || authority !== ctx.registrar)
|
|
42
|
+
throw failure("subname Resolver context mismatch");
|
|
43
|
+
if (version !== 1)
|
|
44
|
+
throw failure("subnames unsupported by Resolver");
|
|
45
|
+
return ctx;
|
|
46
|
+
}
|
|
47
|
+
async function recordAt(ctx, parent, label) {
|
|
48
|
+
const record = subnameRecordFromNative(await read(ctx.registrar, "subname_record", [subnameLabelArg(parent), subnameLabelArg(label)]), resolvableNameNode(`${parent}.${ctx.namespace}`));
|
|
49
|
+
if (record && !account(record.address))
|
|
50
|
+
throw failure("invalid subname destination");
|
|
51
|
+
return record;
|
|
52
|
+
}
|
|
53
|
+
async function record(name) { const parsed = subnameParts(name), ctx = await context(parsed.namespace); return recordAt(ctx, parsed.parentLabel, parsed.childLabel); }
|
|
54
|
+
async function policy(namespace) { return (await context(namespace)).policy; }
|
|
55
|
+
async function list(parent, options = {}) {
|
|
56
|
+
const parsed = subnameParent(parent), { offset, limit } = subnamePageInput(options.offset, options.limit), ctx = await context(parsed.namespace);
|
|
57
|
+
const labels = subnameLabelsFromNative(await read(ctx.registrar, "subname_labels", [subnameLabelArg(parsed.label), nativeToScVal(offset, { type: "u32" }), nativeToScVal(limit, { type: "u32" })]), limit);
|
|
58
|
+
const records = await Promise.all(labels.map(async (label) => { const record = await recordAt(ctx, parsed.label, label); if (!record)
|
|
59
|
+
throw failure("subname index references missing record"); return { name: `${label}.${parsed.name}`, ...record }; }));
|
|
60
|
+
return { parent: parsed.name, policy: ctx.policy, records, offset, nextOffset: labels.length === limit ? offset + labels.length : null };
|
|
61
|
+
}
|
|
62
|
+
return { context, creationContext, recordAt, record, policy, list };
|
|
63
|
+
}
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sorandomains/owner",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "Run your Soran namespace on Stellar: configure claims, issue names and manage namespace policies.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
7
|
-
"node": ">=
|
|
7
|
+
"node": ">=22.12.0"
|
|
8
8
|
},
|
|
9
9
|
"main": "./dist/index.js",
|
|
10
10
|
"types": "./dist/index.d.ts",
|
|
@@ -16,7 +16,8 @@
|
|
|
16
16
|
"typecheck": "tsc --noEmit",
|
|
17
17
|
"prepublishOnly": "tsc",
|
|
18
18
|
"check:browser": "esbuild dist/index.js --bundle --platform=browser --external:@stellar/stellar-sdk --outfile=browser-check.js --legal-comments=none --log-level=error && node -e \"const fs=require('fs');const s=fs.readFileSync('browser-check.js','utf8');fs.unlinkSync('browser-check.js');if(/\\bBuffer\\b/.test(s)){console.error('FAIL: bare Buffer reference in browser bundle');process.exit(1)}console.log('browser bundle clean')\"",
|
|
19
|
-
"test": "node --import tsx test/decode-failure.test.mts && node --import tsx --test test/integration.test.mts test/native-*.test.mts && node --import tsx --test test/funding.test.mts test/funding-history.test.mts"
|
|
19
|
+
"test": "node --import tsx test/decode-failure.test.mts && node --import tsx --test test/integration.test.mts test/hardening.test.mts test/error-codes.test.mts test/native-*.test.mts && node --import tsx --test test/funding.test.mts test/funding-history.test.mts test/funding-bounds.test.mts && node --import tsx --test test/multichain.test.mts && node --import tsx --test test/subnames.test.mts",
|
|
20
|
+
"test:hardening": "node --import tsx --test test/hardening.test.mts"
|
|
20
21
|
},
|
|
21
22
|
"license": "MIT",
|
|
22
23
|
"repository": {
|
|
@@ -51,5 +52,12 @@
|
|
|
51
52
|
"types": "./dist/index.d.ts",
|
|
52
53
|
"default": "./dist/index.js"
|
|
53
54
|
}
|
|
55
|
+
},
|
|
56
|
+
"dependencies": {
|
|
57
|
+
"@noble/hashes": "2.4.0",
|
|
58
|
+
"@scure/base": "2.4.0"
|
|
59
|
+
},
|
|
60
|
+
"overrides": {
|
|
61
|
+
"axios": "1.20.0"
|
|
54
62
|
}
|
|
55
63
|
}
|