@sorandomains/owner 0.4.0 → 0.5.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 +17 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +20 -14
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# @sorandomains/owner
|
|
2
2
|
|
|
3
|
+
Version 0.5.0 targets Stellar SDK17 (`>=17 <18`). ASCII names
|
|
4
|
+
and labels are validated before lowercase normalization; Unicode lookalikes are
|
|
5
|
+
rejected. Writes continue to target the owning Registry/Registrar/Resolver. Universal
|
|
6
|
+
Lookup is the read entry point in `@sorandomains/lookup` 0.5.0.
|
|
7
|
+
|
|
3
8
|
Run your Soran namespace on Stellar, programmatically. Issue names to your
|
|
4
9
|
users, manage their lifecycle, transfer the namespace, and — when you are
|
|
5
10
|
ready — walk the one-way permanence door. Every operation is a transaction
|
|
@@ -68,3 +73,15 @@ trustless resolve, reverse lookup, and ownership assurance, with no signing
|
|
|
68
73
|
code at all.
|
|
69
74
|
|
|
70
75
|
Docs: <https://github.com/SoranDomains/docs> · License: MIT
|
|
76
|
+
|
|
77
|
+
## Verified testnet deployment
|
|
78
|
+
|
|
79
|
+
Verified on 2026-09-05 at ledger 4515471. Network passphrase: `Test SDF Network ; September 2015`.
|
|
80
|
+
|
|
81
|
+
| Contract | Address |
|
|
82
|
+
|---|---|
|
|
83
|
+
| Registry | `CBSORANXTUFKBZK74AAM2ZM5OX2V7PIXUADM3HGP6WU3IDN7M3YEEDLU` |
|
|
84
|
+
|
|
85
|
+
Mainnet has no deployment preset. Custom networks must supply their own verified
|
|
86
|
+
addresses. Universal Lookup upgrades remain immediately executable; an address
|
|
87
|
+
and ABI version do not pin the code that will execute after a governance upgrade.
|
package/dist/index.d.ts
CHANGED
|
@@ -40,7 +40,7 @@ export declare const DEPLOYMENTS: {
|
|
|
40
40
|
readonly testnet: {
|
|
41
41
|
readonly rpcUrl: "https://soroban-testnet.stellar.org";
|
|
42
42
|
readonly passphrase: string;
|
|
43
|
-
readonly registryId: "
|
|
43
|
+
readonly registryId: "CBSORANXTUFKBZK74AAM2ZM5OX2V7PIXUADM3HGP6WU3IDN7M3YEEDLU";
|
|
44
44
|
};
|
|
45
45
|
};
|
|
46
46
|
/**
|
|
@@ -75,6 +75,8 @@ export declare class OwnerError extends Error {
|
|
|
75
75
|
readonly txHash: string | null;
|
|
76
76
|
constructor(message: string, contractId?: string | null, fn?: string | null, code?: number | null, codeName?: string | null, txHash?: string | null);
|
|
77
77
|
}
|
|
78
|
+
/** Contract label rules: 1–63 bytes of a-z 0-9, `-` neither first nor last. */
|
|
79
|
+
export declare function normalizeLabel(value: string): string;
|
|
78
80
|
/** One transaction issues at most this many names (contract MAX_BATCH). */
|
|
79
81
|
export declare const MAX_BATCH = 23;
|
|
80
82
|
export type Submitted = {
|
package/dist/index.js
CHANGED
|
@@ -94,7 +94,7 @@ export const DEPLOYMENTS = {
|
|
|
94
94
|
passphrase: Networks.TESTNET,
|
|
95
95
|
// The immutable Registry. The Registrar for a namespace is discovered on
|
|
96
96
|
// chain via `registrar_of(node)` — never configured by hand.
|
|
97
|
-
registryId: "
|
|
97
|
+
registryId: "CBSORANXTUFKBZK74AAM2ZM5OX2V7PIXUADM3HGP6WU3IDN7M3YEEDLU",
|
|
98
98
|
},
|
|
99
99
|
};
|
|
100
100
|
/** A TxSigner over a raw secret key — for backends and scripts. */
|
|
@@ -205,6 +205,13 @@ function typedError(contractId, fn, raw, names, txHash = null) {
|
|
|
205
205
|
// ---------------------------------------------------------------------------
|
|
206
206
|
const LABEL_RE = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/;
|
|
207
207
|
/** Contract label rules: 1–63 bytes of a-z 0-9, `-` neither first nor last. */
|
|
208
|
+
export function normalizeLabel(value) {
|
|
209
|
+
if (typeof value !== "string" || /[^\x00-\x7f]/.test(value))
|
|
210
|
+
throw new OwnerError("label must contain ASCII characters only");
|
|
211
|
+
const normalized = value.toLowerCase();
|
|
212
|
+
assertLabel(normalized);
|
|
213
|
+
return normalized;
|
|
214
|
+
}
|
|
208
215
|
function assertLabel(label) {
|
|
209
216
|
if (label.length < 1 || label.length > 63 || !LABEL_RE.test(label)) {
|
|
210
217
|
throw new OwnerError(`invalid label "${label}" — 1-63 chars of a-z, 0-9, and non-edge hyphens`);
|
|
@@ -280,7 +287,7 @@ export class SoranOwner {
|
|
|
280
287
|
// ---- discovery -----------------------------------------------------------
|
|
281
288
|
/** The namespace's on-chain attested Registrar id. Cached per instance. */
|
|
282
289
|
async registrarOf(namespace) {
|
|
283
|
-
|
|
290
|
+
namespace = normalizeLabel(namespace);
|
|
284
291
|
const hit = this.registrars.get(namespace);
|
|
285
292
|
if (hit && Date.now() - hit.at < SoranOwner.REGISTRAR_TTL_MS)
|
|
286
293
|
return hit.value;
|
|
@@ -295,7 +302,7 @@ export class SoranOwner {
|
|
|
295
302
|
}
|
|
296
303
|
/** The namespace's current owner on the Registry, or null if unallocated. */
|
|
297
304
|
async namespaceOwner(namespace) {
|
|
298
|
-
|
|
305
|
+
namespace = normalizeLabel(namespace);
|
|
299
306
|
return (await this.read(this.registryId, "owner_of", [
|
|
300
307
|
nodeArg(namehash(namespace)),
|
|
301
308
|
]));
|
|
@@ -319,7 +326,7 @@ export class SoranOwner {
|
|
|
319
326
|
// ---- registrar: issuance -------------------------------------------------
|
|
320
327
|
/** Issue `label.namespace` to `holder`. Owner-signed; term set by policy. */
|
|
321
328
|
async issue(namespace, label, holder) {
|
|
322
|
-
|
|
329
|
+
label = normalizeLabel(label);
|
|
323
330
|
const registrarId = await this.registrarOf(namespace);
|
|
324
331
|
const r = await this.invoke(registrarId, "issue", [labelArg(label), addrArg(holder)], REGISTRAR_ERRORS);
|
|
325
332
|
return { hash: r.hash, ledger: r.ledger, node: toHex(r.returnValue) };
|
|
@@ -338,8 +345,7 @@ export class SoranOwner {
|
|
|
338
345
|
if (entries.length > MAX_BATCH) {
|
|
339
346
|
throw new OwnerError(`issueBatch: ${entries.length} entries exceeds the contract cap of ${MAX_BATCH} per transaction — split the batch`);
|
|
340
347
|
}
|
|
341
|
-
|
|
342
|
-
assertLabel(e.label);
|
|
348
|
+
entries = entries.map(e => ({ ...e, label: normalizeLabel(e.label) }));
|
|
343
349
|
const registrarId = await this.registrarOf(namespace);
|
|
344
350
|
const r = await this.invoke(registrarId, "issue_batch", [
|
|
345
351
|
xdr.ScVal.scvVec(entries.map((e) => labelArg(e.label))),
|
|
@@ -424,14 +430,14 @@ export class SoranOwner {
|
|
|
424
430
|
* policy never allowed it and after `make_permanent` removed it forever.
|
|
425
431
|
*/
|
|
426
432
|
async reclaim(namespace, label) {
|
|
427
|
-
|
|
433
|
+
label = normalizeLabel(label);
|
|
428
434
|
const registrarId = await this.registrarOf(namespace);
|
|
429
435
|
const r = await this.invoke(registrarId, "reclaim", [labelArg(label)], REGISTRAR_ERRORS);
|
|
430
436
|
return { hash: r.hash, ledger: r.ledger };
|
|
431
437
|
}
|
|
432
438
|
/** Extend a finite-term name. Returns the new expiry (unix seconds). */
|
|
433
439
|
async renew(namespace, label, extendSecs) {
|
|
434
|
-
|
|
440
|
+
label = normalizeLabel(label);
|
|
435
441
|
const registrarId = await this.registrarOf(namespace);
|
|
436
442
|
const r = await this.invoke(registrarId, "renew", [labelArg(label), nativeToScVal(extendSecs, { type: "u64" })], REGISTRAR_ERRORS);
|
|
437
443
|
return { hash: r.hash, ledger: r.ledger, expiresAt: BigInt(r.returnValue) };
|
|
@@ -463,19 +469,19 @@ export class SoranOwner {
|
|
|
463
469
|
* accepts; re-proposing replaces the pending offer, cancel withdraws it.
|
|
464
470
|
*/
|
|
465
471
|
async proposeNamespaceTransfer(namespace, to) {
|
|
466
|
-
|
|
472
|
+
namespace = normalizeLabel(namespace);
|
|
467
473
|
const r = await this.invoke(this.registryId, "propose_transfer", [nodeArg(namehash(namespace)), addrArg(to)], REGISTRY_ERRORS);
|
|
468
474
|
return { hash: r.hash, ledger: r.ledger };
|
|
469
475
|
}
|
|
470
476
|
/** Accept a namespace offered to you. Signer must be the PROPOSED owner. */
|
|
471
477
|
async acceptNamespaceTransfer(namespace) {
|
|
472
|
-
|
|
478
|
+
namespace = normalizeLabel(namespace);
|
|
473
479
|
const r = await this.invoke(this.registryId, "accept_transfer", [nodeArg(namehash(namespace))], REGISTRY_ERRORS);
|
|
474
480
|
return { hash: r.hash, ledger: r.ledger };
|
|
475
481
|
}
|
|
476
482
|
/** Withdraw a pending namespace transfer. Current owner only. */
|
|
477
483
|
async cancelNamespaceTransfer(namespace) {
|
|
478
|
-
|
|
484
|
+
namespace = normalizeLabel(namespace);
|
|
479
485
|
const r = await this.invoke(this.registryId, "cancel_transfer", [nodeArg(namehash(namespace))], REGISTRY_ERRORS);
|
|
480
486
|
return { hash: r.hash, ledger: r.ledger };
|
|
481
487
|
}
|
|
@@ -484,7 +490,7 @@ export class SoranOwner {
|
|
|
484
490
|
* Refused (ResolverFrozen) once the namespace is permanent.
|
|
485
491
|
*/
|
|
486
492
|
async setResolver(namespace, resolver) {
|
|
487
|
-
|
|
493
|
+
namespace = normalizeLabel(namespace);
|
|
488
494
|
const r = await this.invoke(this.registryId, "set_resolver", [nodeArg(namehash(namespace)), resolver === null ? xdr.ScVal.scvVoid() : addrArg(resolver)], REGISTRY_ERRORS);
|
|
489
495
|
return { hash: r.hash, ledger: r.ledger };
|
|
490
496
|
}
|
|
@@ -508,13 +514,13 @@ export class SoranOwner {
|
|
|
508
514
|
}
|
|
509
515
|
/** Current state of `label.namespace`, or null if never issued. */
|
|
510
516
|
async nameState(namespace, label) {
|
|
511
|
-
|
|
517
|
+
label = normalizeLabel(label);
|
|
512
518
|
const registrarId = await this.registrarOf(namespace);
|
|
513
519
|
return this.readRecord(registrarId, label);
|
|
514
520
|
}
|
|
515
521
|
/** The pending namespace transfer on the Registry, or null. */
|
|
516
522
|
async pendingNamespaceTransfer(namespace) {
|
|
517
|
-
|
|
523
|
+
namespace = normalizeLabel(namespace);
|
|
518
524
|
return this.read(this.registryId, "pending_transfer", [nodeArg(namehash(namespace))]);
|
|
519
525
|
}
|
|
520
526
|
// ---- internals -----------------------------------------------------------
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sorandomains/owner",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Run your Soran namespace on Stellar
|
|
3
|
+
"version": "0.5.0",
|
|
4
|
+
"description": "Run your Soran namespace on Stellar \u2014 issue names, manage lifecycle, transfer, and walk the one-way permanence door.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"types": "./dist/index.d.ts",
|
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
"build": "tsc",
|
|
13
13
|
"typecheck": "tsc --noEmit",
|
|
14
14
|
"prepublishOnly": "tsc",
|
|
15
|
-
"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')\""
|
|
15
|
+
"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')\"",
|
|
16
|
+
"test": "node --import tsx test/decode-failure.test.mts && node --import tsx --test test/integration.test.mts"
|
|
16
17
|
},
|
|
17
18
|
"license": "MIT",
|
|
18
19
|
"repository": {
|