@sorandomains/holder 0.1.0 → 0.2.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 CHANGED
@@ -28,11 +28,12 @@ await me.setProfile("alice.nova", { // the standard keys every wallet reads
28
28
  | --- | --- |
29
29
  | `setRecord` | Point your name's explicit resolver record at any address (generation-gated — stops resolving the moment the name changes hands) |
30
30
  | `setAddress` | Re-point the built-in (Registrar) resolution target |
31
- | `setText` / `setProfile` | Publish text records; `setProfile` writes the standard `PROFILE_KEYS` (one transaction per key) |
31
+ | `setText` / `setProfile` / `clearText` | Publish text records; `setProfile` writes the standard `PROFILE_KEYS` (one transaction per key); records are overwrite-only on chain — `clearText` retracts by writing the empty value standard readers treat as unset |
32
32
  | `setReverse` / `clearReverse` | Claim your address→name reverse record — the contract refuses names that don't already resolve to you (`ForwardMismatch`) |
33
33
  | `setPrimary` / `clearPrimary` | Your one cross-namespace display name, re-verified on chain at every read |
34
34
  | `proposeNameTransfer` / `acceptNameTransfer` / `cancelNameTransfer` | Two-step, accept-to-move name transfers (policy-gated) |
35
35
  | `pendingNameTransfer` | Read the pending proposal |
36
+ | `registrarOf` / `resolverOf` | Discover the namespace's attested Registrar / resolver pointer |
36
37
 
37
38
  Everything is holder-authorized **on chain** — the Resolver checks you hold
38
39
  the name right now, reverse and primary claims are authorized by the address
package/dist/index.d.ts CHANGED
@@ -158,6 +158,11 @@ export declare class SoranHolder {
158
158
  * and the contract enforces that the name's forward record already
159
159
  * resolves to you (ForwardMismatch otherwise): a reverse claim on a name
160
160
  * that doesn't point back at you is spoofing, and the chain refuses it.
161
+ *
162
+ * NOTE the forward proof must be the RESOLVER's OWN record — the
163
+ * Registrar's built-in target (which `lookup.resolve` also honors for
164
+ * fresh names) does not count. On a freshly issued name, call
165
+ * `setRecord(name, yourAddress)` first, then `setReverse(name)`.
161
166
  */
162
167
  setReverse(name: string): Promise<Submitted>;
163
168
  /** Remove your reverse record on a namespace's resolver. */
package/dist/index.js CHANGED
@@ -30,7 +30,7 @@
30
30
  * before signing. Operations on one instance are serialized so concurrent
31
31
  * calls cannot race the account sequence number.
32
32
  */
33
- import { Account, Address, BASE_FEE, Contract, Keypair, Networks, Operation, TransactionBuilder, hash, nativeToScVal, rpc, scValToNative, xdr, } from "@stellar/stellar-sdk";
33
+ import { Account, Address, BASE_FEE, Contract, Keypair, Networks, Operation, TransactionBuilder, hash, nativeToScVal, rpc, scValToNative, } from "@stellar/stellar-sdk";
34
34
  // ---------------------------------------------------------------------------
35
35
  // Deployments
36
36
  // ---------------------------------------------------------------------------
@@ -167,6 +167,11 @@ function concatBytes(...parts) {
167
167
  }
168
168
  return out;
169
169
  }
170
+ /** Browser-safe hex (SDK17: hash()/XDR bytes are Uint8Array, whose toString()
171
+ * ignores a radix — and this package forbids a bare `Buffer` in the bundle). */
172
+ function toHex(b) {
173
+ return Array.from(b, (x) => x.toString(16).padStart(2, "0")).join("");
174
+ }
170
175
  function namehash(namespace) {
171
176
  const labelHash = new Uint8Array(hash(utf8(namespace)));
172
177
  return new Uint8Array(hash(concatBytes(new Uint8Array(32), labelHash)));
@@ -322,12 +327,26 @@ export class SoranHolder {
322
327
  * and the contract enforces that the name's forward record already
323
328
  * resolves to you (ForwardMismatch otherwise): a reverse claim on a name
324
329
  * that doesn't point back at you is spoofing, and the chain refuses it.
330
+ *
331
+ * NOTE the forward proof must be the RESOLVER's OWN record — the
332
+ * Registrar's built-in target (which `lookup.resolve` also honors for
333
+ * fresh names) does not count. On a freshly issued name, call
334
+ * `setRecord(name, yourAddress)` first, then `setReverse(name)`.
325
335
  */
326
336
  async setReverse(name) {
327
337
  const { namespace } = parseName(name);
328
338
  const resolverId = await this.resolverOf(namespace);
329
339
  const pub = await this.signer.publicKey();
330
- const r = await this.invoke(resolverId, "set_reverse", [addrArg(pub), nativeToScVal(name.toLowerCase(), { type: "string" })], RESOLVER_ERRORS);
340
+ let r;
341
+ try {
342
+ r = await this.invoke(resolverId, "set_reverse", [addrArg(pub), nativeToScVal(name.toLowerCase(), { type: "string" })], RESOLVER_ERRORS);
343
+ }
344
+ catch (e) {
345
+ if (e instanceof HolderError && e.codeName === "ForwardMismatch") {
346
+ throw new HolderError(`${e.message} — the resolver only accepts its OWN forward record as proof; if this name still resolves via the Registrar's built-in target, call setRecord(name, yourAddress) first, then retry setReverse`, e.contractId, e.fn, e.code, e.codeName, e.txHash);
347
+ }
348
+ throw e;
349
+ }
331
350
  return { hash: r.hash, ledger: r.ledger };
332
351
  }
333
352
  /** Remove your reverse record on a namespace's resolver. */
@@ -473,12 +492,12 @@ export class SoranHolder {
473
492
  assertSatisfiableAuth(prepared, pub, contractId, fn) {
474
493
  const op = prepared.operations[0];
475
494
  for (const entry of op?.auth ?? []) {
476
- const cred = entry.credentials();
477
- if (cred.switch() !== xdr.SorobanCredentialsType.sorobanCredentialsAddress())
495
+ const cred = entry.credentials;
496
+ if (cred.type !== "sorobanCredentialsAddress")
478
497
  continue;
479
498
  let required;
480
499
  try {
481
- required = Address.fromScAddress(cred.address().address()).toString();
500
+ required = Address.fromScAddress(cred.address.address).toString();
482
501
  }
483
502
  catch {
484
503
  continue;
@@ -509,21 +528,24 @@ export class SoranHolder {
509
528
  .setTimeout(this.timeoutSecs)
510
529
  .build();
511
530
  let tx = build(await this.sourceAccount(pub, fn));
512
- let sim = await this.server.simulateTransaction(tx);
531
+ // (SDK17/P28) useUpgradedAuth=false keeps legacy V1 SorobanCredentials rather
532
+ // than CAP-71 address-bound V2 — valid against P27 (pre-vote) and P28 (post),
533
+ // and keeps assertSatisfiableAuth's `sorobanCredentialsAddress` check exact.
534
+ let sim = await this.server.simulateTransaction(tx, undefined, undefined, false);
513
535
  for (let round = 0; rpc.Api.isSimulationRestore(sim); round++) {
514
536
  if (round >= 2) {
515
537
  throw new HolderError(`${fn}: entries still need restoring after ${round} restore transactions — retry later`, contractId, fn);
516
538
  }
517
539
  await this.restore(sim, pub);
518
540
  tx = build(await this.sourceAccount(pub, fn));
519
- sim = await this.server.simulateTransaction(tx);
541
+ sim = await this.server.simulateTransaction(tx, undefined, undefined, false);
520
542
  }
521
543
  if (rpc.Api.isSimulationError(sim)) {
522
544
  throw typedError(contractId, fn, sim.error, errNames);
523
545
  }
524
546
  const prepared = rpc.assembleTransaction(tx, sim).build();
525
547
  this.assertSatisfiableAuth(prepared, pub, contractId, fn);
526
- const txHash = prepared.hash().toString("hex");
548
+ const txHash = toHex(prepared.hash()); // (SDK17) hash() is Uint8Array
527
549
  const signed = await this.signEnvelope(prepared.toXDR());
528
550
  const envelope = TransactionBuilder.fromXDR(signed, this.passphrase);
529
551
  let sent;
@@ -595,18 +617,18 @@ export class SoranHolder {
595
617
  try {
596
618
  const meta = got.resultMetaXdr;
597
619
  const diags = got.diagnosticEventsXdr ??
598
- (meta && meta.switch() === 3
599
- ? (meta.v3().sorobanMeta()?.diagnosticEvents() ?? [])
600
- : meta && meta.switch() === 4
601
- ? meta.v4().diagnosticEvents()
620
+ (meta && meta.type === "v3"
621
+ ? (meta.value.sorobanMeta?.diagnosticEvents ?? [])
622
+ : meta && meta.type === "v4"
623
+ ? meta.value.diagnosticEvents
602
624
  : []);
603
625
  outer: for (const d of diags) {
604
- const body = d.event().body().v0();
605
- for (const v of [...body.topics(), body.data()]) {
606
- if (v.switch() === xdr.ScValType.scvError()) {
607
- const err = v.error();
608
- if (err.switch() === xdr.ScErrorType.sceContract()) {
609
- code = err.contractCode();
626
+ const body = d.event.body.value;
627
+ for (const v of [...body.topics, body.data]) {
628
+ if (v.type === "scvError") {
629
+ const err = v.error;
630
+ if (err.type === "sceContract") {
631
+ code = err.contractCode;
610
632
  break outer;
611
633
  }
612
634
  }
@@ -618,7 +640,7 @@ export class SoranHolder {
618
640
  }
619
641
  let resultCode = `tx status ${got.status}`;
620
642
  try {
621
- resultCode = got.resultXdr?.result().switch().name ?? resultCode;
643
+ resultCode = got.resultXdr?.result.type ?? resultCode;
622
644
  }
623
645
  catch {
624
646
  /* keep plain status */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sorandomains/holder",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Manage your own Soran name on Stellar — records, profile, reverse, primary, and transfers, signed by your key.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -39,10 +39,10 @@
39
39
  "reverse-lookup"
40
40
  ],
41
41
  "peerDependencies": {
42
- "@stellar/stellar-sdk": ">=13 <17"
42
+ "@stellar/stellar-sdk": ">=17 <18"
43
43
  },
44
44
  "devDependencies": {
45
- "@stellar/stellar-sdk": "^16.2.0",
45
+ "@stellar/stellar-sdk": "^17.0.1",
46
46
  "@types/node": "^22.20.1",
47
47
  "esbuild": "^0.25.0",
48
48
  "tsx": "^4.19.2",