@sorandomains/owner 0.4.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 CHANGED
@@ -1,5 +1,10 @@
1
1
  # @sorandomains/owner
2
2
 
3
+ Version 0.5.1 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.1.
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 4520986. Network passphrase: `Test SDF Network ; September 2015`.
80
+
81
+ | Contract | Address |
82
+ |---|---|
83
+ | Registry | `CDSORANCV3IFF3MKHJ7KI4MKEJOJZFMTDVAZCD5XFOR4WTGNXJJNOKQE` |
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: "CAUEHYVLLNNDZ4H5QWCPBDWEONRI44SI3XYSEACB4U3HYILIVQGQAMNI";
43
+ readonly registryId: "CDSORANCV3IFF3MKHJ7KI4MKEJOJZFMTDVAZCD5XFOR4WTGNXJJNOKQE";
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: "CAUEHYVLLNNDZ4H5QWCPBDWEONRI44SI3XYSEACB4U3HYILIVQGQAMNI",
97
+ registryId: "CDSORANCV3IFF3MKHJ7KI4MKEJOJZFMTDVAZCD5XFOR4WTGNXJJNOKQE",
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
- assertLabel(namespace);
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
- assertLabel(namespace);
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
- assertLabel(label);
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
- for (const e of entries)
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
- assertLabel(label);
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
- assertLabel(label);
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
- assertLabel(namespace);
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
- assertLabel(namespace);
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
- assertLabel(namespace);
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
- assertLabel(namespace);
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
- assertLabel(label);
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
- assertLabel(namespace);
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.0",
4
- "description": "Run your Soran namespace on Stellar — issue names, manage lifecycle, transfer, and walk the one-way permanence door.",
3
+ "version": "0.5.1",
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": {