@youam/sdk 0.2.1 → 0.4.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.
@@ -60,11 +60,11 @@ function extractPublicKey(tags) {
60
60
  }
61
61
  return null;
62
62
  }
63
- async function queryUamTxt(domain, timeout) {
63
+ async function queryUamTxt(domain2, timeout) {
64
64
  const controller = new AbortController();
65
65
  const timer = timeout != null ? setTimeout(() => controller.abort(), timeout) : void 0;
66
66
  try {
67
- const records = await (0, import_promises.resolveTxt)(`_uam.${domain}`);
67
+ const records = await (0, import_promises.resolveTxt)(`_uam.${domain2}`);
68
68
  const results = [];
69
69
  for (const parts of records) {
70
70
  const txtValue = parts.join("");
@@ -79,8 +79,8 @@ async function queryUamTxt(domain, timeout) {
79
79
  if (timer != null) clearTimeout(timer);
80
80
  }
81
81
  }
82
- async function resolveKeyViaHttps(agentName, domain, timeout) {
83
- const url = `https://${domain}/.well-known/uam.json`;
82
+ async function resolveKeyViaHttps(agentName, domain2, timeout) {
83
+ const url = `https://${domain2}/.well-known/uam.json`;
84
84
  try {
85
85
  const resp = await fetch(url, {
86
86
  signal: AbortSignal.timeout(timeout ?? 1e4)
@@ -115,7 +115,7 @@ var init_dns_verifier = __esm({
115
115
  var import_commander = require("commander");
116
116
 
117
117
  // src/version.ts
118
- var SDK_VERSION = "0.2.1";
118
+ var SDK_VERSION = "0.4.0";
119
119
 
120
120
  // src/cli/commands/init.ts
121
121
  var import_node_os5 = require("os");
@@ -199,6 +199,18 @@ var KeyPinningError = class _KeyPinningError extends UAMError {
199
199
  Object.setPrototypeOf(this, _KeyPinningError.prototype);
200
200
  }
201
201
  };
202
+ var SendRefusedError = class extends UAMError {
203
+ messageId;
204
+ code;
205
+ detail;
206
+ constructor(messageId, code, detail) {
207
+ super(`Relay refused message ${messageId}: [${code}] ${detail ?? ""}`.trimEnd());
208
+ this.name = "SendRefusedError";
209
+ this.messageId = messageId;
210
+ this.code = code;
211
+ this.detail = detail;
212
+ }
213
+ };
202
214
 
203
215
  // src/protocol/address.ts
204
216
  var ADDRESS_RE = /^(?<agent>[a-z0-9][a-z0-9_-]{0,62}[a-z0-9]|[a-z0-9])::(?<domain>[a-z0-9](?:[a-z0-9.-]{0,253}[a-z0-9])?)$/;
@@ -325,11 +337,11 @@ function encryptPayload(plaintext, senderSigningKey, recipientVerifyKey) {
325
337
  try {
326
338
  const senderCurve = import_libsodium_wrappers.default.crypto_sign_ed25519_sk_to_curve25519(senderSigningKey);
327
339
  const recipientCurve = import_libsodium_wrappers.default.crypto_sign_ed25519_pk_to_curve25519(recipientVerifyKey);
328
- const nonce = import_libsodium_wrappers.default.randombytes_buf(import_libsodium_wrappers.default.crypto_box_NONCEBYTES);
329
- const encrypted = import_libsodium_wrappers.default.crypto_box_easy(plaintext, nonce, recipientCurve, senderCurve);
330
- const combined = new Uint8Array(nonce.length + encrypted.length);
331
- combined.set(nonce, 0);
332
- combined.set(encrypted, nonce.length);
340
+ const nonce2 = import_libsodium_wrappers.default.randombytes_buf(import_libsodium_wrappers.default.crypto_box_NONCEBYTES);
341
+ const encrypted = import_libsodium_wrappers.default.crypto_box_easy(plaintext, nonce2, recipientCurve, senderCurve);
342
+ const combined = new Uint8Array(nonce2.length + encrypted.length);
343
+ combined.set(nonce2, 0);
344
+ combined.set(encrypted, nonce2.length);
333
345
  return b64Encode(combined);
334
346
  } catch (err) {
335
347
  if (err instanceof EncryptionError) throw err;
@@ -339,11 +351,11 @@ function encryptPayload(plaintext, senderSigningKey, recipientVerifyKey) {
339
351
  function decryptPayload(ciphertextB64, recipientSigningKey, senderVerifyKey) {
340
352
  try {
341
353
  const combined = b64Decode(ciphertextB64);
342
- const nonce = combined.slice(0, import_libsodium_wrappers.default.crypto_box_NONCEBYTES);
354
+ const nonce2 = combined.slice(0, import_libsodium_wrappers.default.crypto_box_NONCEBYTES);
343
355
  const ciphertext = combined.slice(import_libsodium_wrappers.default.crypto_box_NONCEBYTES);
344
356
  const recipientCurve = import_libsodium_wrappers.default.crypto_sign_ed25519_sk_to_curve25519(recipientSigningKey);
345
357
  const senderCurve = import_libsodium_wrappers.default.crypto_sign_ed25519_pk_to_curve25519(senderVerifyKey);
346
- return import_libsodium_wrappers.default.crypto_box_open_easy(ciphertext, nonce, senderCurve, recipientCurve);
358
+ return import_libsodium_wrappers.default.crypto_box_open_easy(ciphertext, nonce2, senderCurve, recipientCurve);
347
359
  } catch (err) {
348
360
  if (err instanceof DecryptionError) throw err;
349
361
  throw new DecryptionError(err instanceof Error ? err.message : String(err));
@@ -451,7 +463,7 @@ async function createEnvelope(fromAddress, toAddress, messageType, payloadPlaint
451
463
  parseAddress(fromAddress);
452
464
  parseAddress(toAddress);
453
465
  const messageId = (0, import_node_crypto2.randomUUID)();
454
- const nonce = generateNonce();
466
+ const nonce2 = generateNonce();
455
467
  const timestamp = utcTimestamp();
456
468
  let encryptedPayload;
457
469
  if (typeValue === "handshake.request" /* HANDSHAKE_REQUEST */) {
@@ -470,7 +482,7 @@ async function createEnvelope(fromAddress, toAddress, messageType, payloadPlaint
470
482
  toAddress,
471
483
  timestamp,
472
484
  type: typeValue,
473
- nonce,
485
+ nonce: nonce2,
474
486
  payload: encryptedPayload,
475
487
  signature: "",
476
488
  threadId: options?.threadId,
@@ -489,7 +501,7 @@ async function createEnvelope(fromAddress, toAddress, messageType, payloadPlaint
489
501
  toAddress,
490
502
  timestamp,
491
503
  type: typeValue,
492
- nonce,
504
+ nonce: nonce2,
493
505
  payload: encryptedPayload,
494
506
  signature,
495
507
  threadId: options?.threadId,
@@ -581,8 +593,8 @@ function contactCardFromDict(d, options) {
581
593
  }
582
594
  return card;
583
595
  }
584
- async function createContactCard(address, displayName, relay, signingKey, options) {
585
- parseAddress(address);
596
+ async function createContactCard(address2, displayName, relay, signingKey, options) {
597
+ parseAddress(address2);
586
598
  const verifyKey = signingKey.slice(32);
587
599
  const pubKeyB64 = serializeVerifyKey(verifyKey);
588
600
  const fp = publicKeyFingerprint(verifyKey);
@@ -592,7 +604,7 @@ async function createContactCard(address, displayName, relay, signingKey, option
592
604
  ];
593
605
  const tempCard = {
594
606
  version: UAM_VERSION,
595
- address,
607
+ address: address2,
596
608
  displayName,
597
609
  description: options?.description,
598
610
  system: options?.system,
@@ -609,7 +621,7 @@ async function createContactCard(address, displayName, relay, signingKey, option
609
621
  const signature = signMessage(canonicalize(signable), signingKey);
610
622
  return Object.freeze({
611
623
  version: UAM_VERSION,
612
- address,
624
+ address: address2,
613
625
  displayName,
614
626
  description: options?.description,
615
627
  system: options?.system,
@@ -636,6 +648,156 @@ function verifyContactCard(card) {
636
648
  verifySignature(canonicalize(signable), card.signature, vk);
637
649
  }
638
650
 
651
+ // src/protocol/signed.ts
652
+ var import_node_crypto3 = require("crypto");
653
+
654
+ // src/protocol/didkey.ts
655
+ var B58 = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
656
+ var PREFIX = {
657
+ ed25519: new Uint8Array([237, 1]),
658
+ p256: new Uint8Array([128, 36])
659
+ };
660
+ var LENGTH = { ed25519: 32, p256: 33 };
661
+ var InvalidDidKeyError = class extends UAMError {
662
+ constructor(message) {
663
+ super(message);
664
+ this.name = "InvalidDidKeyError";
665
+ }
666
+ };
667
+ function b58decode(text) {
668
+ let n = 0n;
669
+ for (const c of text) {
670
+ const i = B58.indexOf(c);
671
+ if (i < 0) throw new InvalidDidKeyError(`not base58btc: ${JSON.stringify(c)}`);
672
+ n = n * 58n + BigInt(i);
673
+ }
674
+ const body = [];
675
+ while (n > 0n) {
676
+ body.unshift(Number(n & 0xffn));
677
+ n >>= 8n;
678
+ }
679
+ let zeros = 0;
680
+ while (zeros < text.length && text[zeros] === "1") zeros += 1;
681
+ return new Uint8Array([...new Array(zeros).fill(0), ...body]);
682
+ }
683
+ function checkKey(kind, key) {
684
+ if (key.length !== LENGTH[kind]) {
685
+ throw new InvalidDidKeyError(`${kind} key must be ${LENGTH[kind]} bytes`);
686
+ }
687
+ if (kind === "p256" && key[0] !== 2 && key[0] !== 3) {
688
+ throw new InvalidDidKeyError("P-256 key must be a compressed point");
689
+ }
690
+ }
691
+ function decodeDidKey(did2) {
692
+ if (typeof did2 !== "string" || !did2.startsWith("did:key:z")) {
693
+ throw new InvalidDidKeyError("not a did:key with base58btc multibase");
694
+ }
695
+ const raw = b58decode(did2.slice("did:key:z".length));
696
+ for (const kind of Object.keys(PREFIX)) {
697
+ const prefix = PREFIX[kind];
698
+ if (raw.length >= prefix.length && prefix.every((b, i) => raw[i] === b)) {
699
+ const key = raw.slice(prefix.length);
700
+ checkKey(kind, key);
701
+ return { kind, key };
702
+ }
703
+ }
704
+ throw new InvalidDidKeyError("unsupported key type");
705
+ }
706
+
707
+ // src/protocol/signed.ts
708
+ var MAX_INT = 2 ** 53 - 1;
709
+ var SignedObjectError = class extends UAMError {
710
+ constructor(message) {
711
+ super(message);
712
+ this.name = "SignedObjectError";
713
+ }
714
+ };
715
+ var has = (obj, key) => Object.prototype.hasOwnProperty.call(obj, key);
716
+ function isPlainObject(value) {
717
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
718
+ const proto = Object.getPrototypeOf(value);
719
+ return proto === null || proto === Object.prototype;
720
+ }
721
+ var HASH_RE = /^[A-Za-z0-9_-]{43}$/;
722
+ var OWNER_ID_RE = /^[A-Za-z0-9_-]{22}$/;
723
+ var NONCE_RE = /^[A-Za-z0-9_-]{16,64}$/;
724
+ var SECRET_RE = /^[A-Za-z0-9_-]{22}$/;
725
+ var DOMAIN_RE = /^(?=.{1,253}$)[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?(\.[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)+$/;
726
+ function fail(path, why) {
727
+ throw new SignedObjectError(`${path}: ${why}`);
728
+ }
729
+ var matching = (re, what) => (v, p) => {
730
+ if (typeof v !== "string" || !re.test(v)) fail(p, `must be ${what}`);
731
+ };
732
+ var hash = matching(HASH_RE, "a hash (43 base64url characters)");
733
+ var ownerId = matching(OWNER_ID_RE, "an owner id (22 base64url characters)");
734
+ var nonce = matching(NONCE_RE, "a nonce (16-64 base64url characters)");
735
+ var secret = matching(SECRET_RE, "a secret (22 base64url characters)");
736
+ var domain = matching(DOMAIN_RE, "a lowercase domain");
737
+ var constant = (expected) => (v, p) => {
738
+ if (v !== expected) fail(p, `must be ${JSON.stringify(expected)}`);
739
+ };
740
+ var oneOf = (...options) => (v, p) => {
741
+ if (typeof v !== "string" || !options.includes(v)) fail(p, `must be one of ${options.join(", ")}`);
742
+ };
743
+ var address = (v, p) => {
744
+ if (typeof v !== "string") fail(p, "must be an address");
745
+ let full;
746
+ try {
747
+ full = parseAddress(v).full;
748
+ } catch {
749
+ return fail(p, "must be an address");
750
+ }
751
+ if (full !== v) fail(p, "must be an address in canonical (lowercase) form");
752
+ };
753
+ var did = (kind) => (v, p) => {
754
+ let found;
755
+ try {
756
+ found = decodeDidKey(v).kind;
757
+ } catch (err) {
758
+ return fail(p, err instanceof Error ? err.message : String(err));
759
+ }
760
+ if (found !== kind) fail(p, `must be a ${kind} did:key`);
761
+ };
762
+ var list = (item, minLen = 1, maxLen = 64) => (v, p) => {
763
+ if (!Array.isArray(v) || v.length < minLen || v.length > maxLen) {
764
+ fail(p, `must be a list of ${minLen}-${maxLen} items`);
765
+ }
766
+ v.forEach((entry, i) => item(entry, `${p}[${i}]`));
767
+ };
768
+ var object = (required, optional = {}) => (v, p) => {
769
+ if (!isPlainObject(v)) fail(p, "must be an object");
770
+ for (const key of Object.keys(v)) {
771
+ if (!has(required, key) && !has(optional, key)) fail(`${p}.${key}`, "unknown member");
772
+ }
773
+ for (const [key, member] of Object.entries(required)) {
774
+ if (!has(v, key)) fail(`${p}.${key}`, "missing");
775
+ member(v[key], `${p}.${key}`);
776
+ }
777
+ for (const [key, member] of Object.entries(optional)) {
778
+ if (has(v, key)) member(v[key], `${p}.${key}`);
779
+ }
780
+ };
781
+ var location = (v, p) => {
782
+ if (v !== "shared") address(v, p);
783
+ };
784
+ var access = object({ actions: list(oneOf("message"), 1, 1), locations: list(location) });
785
+ var ownerRef = object({ relay: domain, ownerId, keysHash: hash });
786
+ var ownerRefNoKeys = object({ relay: domain, ownerId });
787
+ var passkey = did("p256");
788
+ var agentKey = did("ed25519");
789
+ var keysOwner = object({ relay: domain }, { ownerId });
790
+ var DAY = 24 * 60 * 60 * 1e3;
791
+ var SUBJECTS = {
792
+ owner: object({ kind: constant("owner"), owner: ownerRef }),
793
+ invitee: object({ kind: constant("invitee"), invite: hash }),
794
+ agent: object({ kind: constant("agent"), id: agentKey, address })
795
+ };
796
+
797
+ // src/protocol/webauthn.ts
798
+ var import_node_crypto4 = require("crypto");
799
+ var import_p256 = require("@noble/curves/p256");
800
+
639
801
  // src/sdk/config.ts
640
802
  var import_node_os = require("os");
641
803
  var import_node_path = require("path");
@@ -783,11 +945,11 @@ var KeyManager = class {
783
945
  /**
784
946
  * Store the address `relayUrl` gave this agent.
785
947
  */
786
- saveAddress(name, address, relayUrl) {
948
+ saveAddress(name, address2, relayUrl) {
787
949
  (0, import_node_fs.mkdirSync)(this._keyDir, { recursive: true });
788
950
  (0, import_node_fs.writeFileSync)(
789
951
  (0, import_node_path2.join)(this._keyDir, `${name}.address`),
790
- JSON.stringify({ address, relay_url: relayUrl })
952
+ JSON.stringify({ address: address2, relay_url: relayUrl })
791
953
  );
792
954
  }
793
955
  /**
@@ -800,14 +962,14 @@ var KeyManager = class {
800
962
  try {
801
963
  const path = (0, import_node_path2.join)(this._keyDir, `${name}.address`);
802
964
  const saved = JSON.parse((0, import_node_fs.readFileSync)(path, "utf-8"));
803
- const address = saved?.address;
965
+ const address2 = saved?.address;
804
966
  const savedRelay = saved?.relay_url;
805
- if (typeof address !== "string" || typeof savedRelay !== "string") {
967
+ if (typeof address2 !== "string" || typeof savedRelay !== "string") {
806
968
  return null;
807
969
  }
808
970
  if (trimRelayUrl(savedRelay) !== trimRelayUrl(relayUrl)) return null;
809
- parseAddress(address);
810
- return address;
971
+ parseAddress(address2);
972
+ return address2;
811
973
  } catch {
812
974
  return null;
813
975
  }
@@ -899,6 +1061,31 @@ function relayVouches(raw, signedAt, now, relayDomain) {
899
1061
  return Math.abs(receivedAt - signedAt) <= window && age >= 0 && age <= STORED_MAIL_MAX_AGE * 1e3 + window;
900
1062
  }
901
1063
 
1064
+ // src/sdk/message.ts
1065
+ var SEND_STATUSES = [
1066
+ "delivered",
1067
+ "stored",
1068
+ "relayed",
1069
+ "queued",
1070
+ "duplicate",
1071
+ "dropped",
1072
+ "pending",
1073
+ "accepted",
1074
+ "unknown"
1075
+ ];
1076
+ function sendStatus(answer) {
1077
+ if (answer === null) return "unknown";
1078
+ const status = answer["status"];
1079
+ if (typeof status === "string" && SEND_STATUSES.includes(status) && status !== "accepted" && status !== "unknown") {
1080
+ return status;
1081
+ }
1082
+ return "accepted";
1083
+ }
1084
+ var PENDING_NOTE = "Not delivered: {to} only accepts messages from approved contacts. Do not resend it.";
1085
+ function pendingNote(to) {
1086
+ return PENDING_NOTE.replace("{to}", to);
1087
+ }
1088
+
902
1089
  // src/sdk/contact-book.ts
903
1090
  var import_node_fs3 = require("fs");
904
1091
  var import_node_path3 = require("path");
@@ -996,36 +1183,36 @@ var ContactBook = class {
996
1183
  /**
997
1184
  * Check if an address is in the contact book (in-memory, no I/O).
998
1185
  */
999
- isKnown(address) {
1000
- return this._knownAddresses.has(address);
1186
+ isKnown(address2) {
1187
+ return this._knownAddresses.has(address2);
1001
1188
  }
1002
1189
  /**
1003
1190
  * Check if an address matches any blocked pattern (O(1) set lookup).
1004
1191
  */
1005
- isBlocked(address) {
1006
- if (this._blockedExact.has(address)) {
1192
+ isBlocked(address2) {
1193
+ if (this._blockedExact.has(address2)) {
1007
1194
  return true;
1008
1195
  }
1009
- if (address.includes("::")) {
1010
- const domain = address.split("::")[1];
1011
- return this._blockedDomains.has(domain);
1196
+ if (address2.includes("::")) {
1197
+ const domain2 = address2.split("::")[1];
1198
+ return this._blockedDomains.has(domain2);
1012
1199
  }
1013
1200
  return false;
1014
1201
  }
1015
1202
  /**
1016
1203
  * Look up the public key for a known contact.
1017
1204
  */
1018
- getPublicKey(address) {
1205
+ getPublicKey(address2) {
1019
1206
  if (this._db === null) return null;
1020
- const row = this._db.prepare("SELECT public_key FROM contacts WHERE address = ?").get(address);
1207
+ const row = this._db.prepare("SELECT public_key FROM contacts WHERE address = ?").get(address2);
1021
1208
  return row?.public_key ?? null;
1022
1209
  }
1023
1210
  /**
1024
1211
  * Return the relay URLs for a known contact (CARD-04).
1025
1212
  */
1026
- getRelayUrls(address) {
1213
+ getRelayUrls(address2) {
1027
1214
  if (this._db === null) return null;
1028
- const row = this._db.prepare("SELECT relay, relays_json FROM contacts WHERE address = ?").get(address);
1215
+ const row = this._db.prepare("SELECT relay, relays_json FROM contacts WHERE address = ?").get(address2);
1029
1216
  if (!row) return null;
1030
1217
  if (row.relays_json !== null) {
1031
1218
  return JSON.parse(row.relays_json);
@@ -1046,19 +1233,19 @@ var ContactBook = class {
1046
1233
  * @throws {KeyPinningError} If a pinned/verified contact's key would be
1047
1234
  * replaced by a different key without `force`.
1048
1235
  */
1049
- addContact(address, publicKey, options) {
1236
+ addContact(address2, publicKey, options) {
1050
1237
  if (this._db === null) {
1051
1238
  throw new Error("ContactBook not open. Call open() first.");
1052
1239
  }
1053
- const existing = this._db.prepare("SELECT public_key, trust_state FROM contacts WHERE address = ?").get(address);
1240
+ const existing = this._db.prepare("SELECT public_key, trust_state FROM contacts WHERE address = ?").get(address2);
1054
1241
  if (existing !== void 0 && (existing.trust_state === "pinned" || existing.trust_state === "verified") && existing.public_key !== publicKey) {
1055
1242
  if (!options?.force) {
1056
1243
  throw new KeyPinningError(
1057
- `Refusing to overwrite ${existing.trust_state} contact ${JSON.stringify(address)}: stored public key does not match the new key. Pass force: true to rotate.`
1244
+ `Refusing to overwrite ${existing.trust_state} contact ${JSON.stringify(address2)}: stored public key does not match the new key. Pass force: true to rotate.`
1058
1245
  );
1059
1246
  }
1060
1247
  log.warn(
1061
- `Force-replacing the key of ${existing.trust_state} contact ${address} (key rotation)`
1248
+ `Force-replacing the key of ${existing.trust_state} contact ${address2} (key rotation)`
1062
1249
  );
1063
1250
  }
1064
1251
  const displayName = options?.displayName ?? null;
@@ -1077,8 +1264,8 @@ var ContactBook = class {
1077
1264
  relay = COALESCE(excluded.relay, contacts.relay),
1078
1265
  relays_json = COALESCE(excluded.relays_json, contacts.relays_json),
1079
1266
  last_seen = datetime('now')`
1080
- ).run(address, publicKey, displayName, trustState, trustSource, relay, relaysJson);
1081
- this._knownAddresses.add(address);
1267
+ ).run(address2, publicKey, displayName, trustState, trustSource, relay, relaysJson);
1268
+ this._knownAddresses.add(address2);
1082
1269
  }
1083
1270
  /**
1084
1271
  * Return all contacts sorted by last_seen DESC.
@@ -1099,13 +1286,13 @@ var ContactBook = class {
1099
1286
  /**
1100
1287
  * Store a pending handshake request.
1101
1288
  */
1102
- addPending(address, contactCardJson) {
1289
+ addPending(address2, contactCardJson) {
1103
1290
  if (this._db === null) {
1104
1291
  throw new Error("ContactBook not open. Call open() first.");
1105
1292
  }
1106
1293
  this._db.prepare(
1107
1294
  `INSERT OR REPLACE INTO pending_handshakes (address, contact_card) VALUES (?, ?)`
1108
- ).run(address, contactCardJson);
1295
+ ).run(address2, contactCardJson);
1109
1296
  }
1110
1297
  /**
1111
1298
  * Retrieve all pending handshake requests.
@@ -1124,9 +1311,9 @@ var ContactBook = class {
1124
1311
  /**
1125
1312
  * Remove a pending handshake request.
1126
1313
  */
1127
- removePending(address) {
1314
+ removePending(address2) {
1128
1315
  if (this._db === null) return;
1129
- this._db.prepare("DELETE FROM pending_handshakes WHERE address = ?").run(address);
1316
+ this._db.prepare("DELETE FROM pending_handshakes WHERE address = ?").run(address2);
1130
1317
  }
1131
1318
  /**
1132
1319
  * Return pending handshakes older than N days.
@@ -1146,31 +1333,31 @@ var ContactBook = class {
1146
1333
  /**
1147
1334
  * Return the trust_state for an address, or null if unknown.
1148
1335
  */
1149
- getTrustState(address) {
1336
+ getTrustState(address2) {
1150
1337
  if (this._db === null) return null;
1151
- const row = this._db.prepare("SELECT trust_state FROM contacts WHERE address = ?").get(address);
1338
+ const row = this._db.prepare("SELECT trust_state FROM contacts WHERE address = ?").get(address2);
1152
1339
  return row?.trust_state ?? null;
1153
1340
  }
1154
1341
  /**
1155
1342
  * Set the pinned_at timestamp and trust_state='pinned' for a contact (TOFU).
1156
1343
  */
1157
- setPinnedAt(address) {
1344
+ setPinnedAt(address2) {
1158
1345
  if (this._db === null) {
1159
1346
  throw new Error("ContactBook not open. Call open() first.");
1160
1347
  }
1161
1348
  this._db.prepare(
1162
1349
  "UPDATE contacts SET pinned_at = datetime('now'), trust_state = 'pinned' WHERE address = ?"
1163
- ).run(address);
1350
+ ).run(address2);
1164
1351
  }
1165
1352
  /**
1166
1353
  * Remove a contact by address. Returns true if a contact was deleted.
1167
1354
  */
1168
- removeContact(address) {
1355
+ removeContact(address2) {
1169
1356
  if (this._db === null) {
1170
1357
  throw new Error("ContactBook not open. Call open() first.");
1171
1358
  }
1172
- const result = this._db.prepare("DELETE FROM contacts WHERE address = ?").run(address);
1173
- this._knownAddresses.delete(address);
1359
+ const result = this._db.prepare("DELETE FROM contacts WHERE address = ?").run(address2);
1360
+ this._knownAddresses.delete(address2);
1174
1361
  return result.changes > 0;
1175
1362
  }
1176
1363
  /**
@@ -1178,8 +1365,8 @@ var ContactBook = class {
1178
1365
  *
1179
1366
  * Convenience helper for inbound message filtering (CARD-05, TOFU-02).
1180
1367
  */
1181
- isTrustedOrVerified(address) {
1182
- const state = this.getTrustState(address);
1368
+ isTrustedOrVerified(address2) {
1369
+ const state = this.getTrustState(address2);
1183
1370
  return state === "trusted" || state === "verified" || state === "pinned";
1184
1371
  }
1185
1372
  /**
@@ -1476,8 +1663,8 @@ var Tier3Resolver = class extends AddressResolver {
1476
1663
  transport: (0, import_viem.http)(rpcUrl)
1477
1664
  });
1478
1665
  }
1479
- async resolvePublicKey(address, _token, _relayUrl) {
1480
- const parsed = parseAddress(address);
1666
+ async resolvePublicKey(address2, _token, _relayUrl) {
1667
+ const parsed = parseAddress(address2);
1481
1668
  const name = parsed.domain;
1482
1669
  const cached = this._cache.get(name);
1483
1670
  if (cached && cached.expiresAt > Date.now()) {
@@ -1541,12 +1728,12 @@ var Tier3Resolver = class extends AddressResolver {
1541
1728
  // src/sdk/resolver.ts
1542
1729
  init_dns_verifier();
1543
1730
  var Tier1Resolver = class extends AddressResolver {
1544
- async resolvePublicKey(address, _token, relayUrl) {
1731
+ async resolvePublicKey(address2, _token, relayUrl) {
1545
1732
  const resp = await fetch(
1546
- `${relayUrl}/api/v1/agents/${address}/public-key`
1733
+ `${relayUrl}/api/v1/agents/${address2}/public-key`
1547
1734
  );
1548
1735
  if (resp.status === 404) {
1549
- throw new UAMError(`Agent not found: ${address}`);
1736
+ throw new UAMError(`Agent not found: ${address2}`);
1550
1737
  }
1551
1738
  if (!resp.ok) {
1552
1739
  throw new UAMError(
@@ -1558,8 +1745,8 @@ var Tier1Resolver = class extends AddressResolver {
1558
1745
  }
1559
1746
  };
1560
1747
  var Tier2Resolver = class extends AddressResolver {
1561
- async resolvePublicKey(address, _token, _relayUrl) {
1562
- const parsed = parseAddress(address);
1748
+ async resolvePublicKey(address2, _token, _relayUrl) {
1749
+ const parsed = parseAddress(address2);
1563
1750
  const txtRecords = await queryUamTxt(parsed.domain);
1564
1751
  for (const txt of txtRecords) {
1565
1752
  const tags = parseUamTxt(txt);
@@ -1571,7 +1758,7 @@ var Tier2Resolver = class extends AddressResolver {
1571
1758
  const httpsKey = await resolveKeyViaHttps(parsed.agent, parsed.domain);
1572
1759
  if (httpsKey) return httpsKey;
1573
1760
  throw new UAMError(
1574
- `Could not resolve public key for ${address} via DNS or HTTPS`
1761
+ `Could not resolve public key for ${address2} via DNS or HTTPS`
1575
1762
  );
1576
1763
  }
1577
1764
  };
@@ -1587,21 +1774,21 @@ var SmartResolver = class extends AddressResolver {
1587
1774
  this._tier2 = new Tier2Resolver();
1588
1775
  this._tier3 = tier3Config ? new Tier3Resolver(tier3Config) : null;
1589
1776
  }
1590
- async resolvePublicKey(address, token, relayUrl) {
1591
- const parsed = parseAddress(address);
1592
- const domain = parsed.domain;
1593
- if (domain === this.relayDomain) {
1594
- return this._tier1.resolvePublicKey(address, token, relayUrl);
1777
+ async resolvePublicKey(address2, token, relayUrl) {
1778
+ const parsed = parseAddress(address2);
1779
+ const domain2 = parsed.domain;
1780
+ if (domain2 === this.relayDomain) {
1781
+ return this._tier1.resolvePublicKey(address2, token, relayUrl);
1595
1782
  }
1596
- if (domain.includes(".")) {
1597
- return this._tier2.resolvePublicKey(address, token, relayUrl);
1783
+ if (domain2.includes(".")) {
1784
+ return this._tier2.resolvePublicKey(address2, token, relayUrl);
1598
1785
  }
1599
1786
  if (!this._tier3) {
1600
1787
  throw new UAMError(
1601
- `Tier 3 resolution requires contract config. Cannot resolve dot-free domain: '${domain}'`
1788
+ `Tier 3 resolution requires contract config. Cannot resolve dot-free domain: '${domain2}'`
1602
1789
  );
1603
1790
  }
1604
- return this._tier3.resolvePublicKey(address, token, relayUrl);
1791
+ return this._tier3.resolvePublicKey(address2, token, relayUrl);
1605
1792
  }
1606
1793
  };
1607
1794
 
@@ -1610,15 +1797,23 @@ var TransportBase = class {
1610
1797
  };
1611
1798
 
1612
1799
  // src/sdk/transport/http.ts
1800
+ async function readAnswer(resp) {
1801
+ try {
1802
+ const body = await resp.json();
1803
+ return body !== null && typeof body === "object" && !Array.isArray(body) ? body : {};
1804
+ } catch {
1805
+ return {};
1806
+ }
1807
+ }
1613
1808
  var HTTPTransport = class extends TransportBase {
1614
1809
  _relayUrl;
1615
1810
  _token;
1616
1811
  _address;
1617
- constructor(relayUrl, token, address) {
1812
+ constructor(relayUrl, token, address2) {
1618
1813
  super();
1619
1814
  this._relayUrl = relayUrl.replace(/\/+$/, "");
1620
1815
  this._token = token;
1621
- this._address = address;
1816
+ this._address = address2;
1622
1817
  }
1623
1818
  async connect() {
1624
1819
  }
@@ -1638,6 +1833,7 @@ var HTTPTransport = class extends TransportBase {
1638
1833
  `HTTP send failed: ${resp.status} ${resp.statusText}`
1639
1834
  );
1640
1835
  }
1836
+ return readAnswer(resp);
1641
1837
  }
1642
1838
  async receive(limit = 50) {
1643
1839
  const url = new URL(
@@ -1670,6 +1866,7 @@ var BASE_DELAY = 1;
1670
1866
  var MAX_DELAY = 60;
1671
1867
  var JITTER_RANGE = 1;
1672
1868
  var SEND_SETTLE_SECONDS = 5;
1869
+ var ACK_TIMEOUT_SECONDS = 5;
1673
1870
  var WebSocketTransport = class extends TransportBase {
1674
1871
  _url;
1675
1872
  _token;
@@ -1685,8 +1882,12 @@ var WebSocketTransport = class extends TransportBase {
1685
1882
  _unanswered = 0;
1686
1883
  _settling = null;
1687
1884
  _settled = null;
1885
+ /** Sends waiting for the relay's answer, by message_id. */
1886
+ _waiting = /* @__PURE__ */ new Map();
1688
1887
  /** @internal How long a close waits, in ms. Replaceable in tests. */
1689
1888
  _settleMs = SEND_SETTLE_SECONDS * 1e3;
1889
+ /** @internal How long send() waits for an answer, in ms. Replaceable in tests. */
1890
+ _ackMs = ACK_TIMEOUT_SECONDS * 1e3;
1690
1891
  constructor(wsUrl, token, onMessage) {
1691
1892
  super();
1692
1893
  this._url = wsUrl;
@@ -1730,13 +1931,42 @@ var WebSocketTransport = class extends TransportBase {
1730
1931
  this._ws = null;
1731
1932
  }
1732
1933
  this._connected = false;
1934
+ this._abandonWaiting();
1733
1935
  }
1936
+ /**
1937
+ * Send an envelope and wait for the relay's answer.
1938
+ *
1939
+ * Resolves with the ack that names the message, or null when none came
1940
+ * within _ackMs or the socket went: the message may still have arrived.
1941
+ * Rejects with SendRefusedError when the relay refuses it by message_id
1942
+ * (relays before 1.1 don't name it, so their refusals end as null).
1943
+ */
1734
1944
  async send(envelope) {
1735
1945
  if (!this._ws || this._ws.readyState !== import_ws.default.OPEN) {
1736
1946
  throw new Error("WebSocket not connected");
1737
1947
  }
1948
+ const messageId = envelope["message_id"];
1949
+ let answer = null;
1950
+ let timer;
1951
+ if (typeof messageId === "string" && !this._waiting.has(messageId)) {
1952
+ answer = new Promise((resolve, reject) => {
1953
+ this._waiting.set(messageId, { resolve, reject });
1954
+ timer = setTimeout(() => {
1955
+ log.warn(
1956
+ `No answer from the relay for ${messageId} within ${this._ackMs / 1e3}s`
1957
+ );
1958
+ this._settle(messageId, null);
1959
+ }, this._ackMs);
1960
+ });
1961
+ }
1738
1962
  this._ws.send(JSON.stringify(envelope));
1739
1963
  this._unanswered += 1;
1964
+ if (answer === null) return null;
1965
+ try {
1966
+ return await answer;
1967
+ } finally {
1968
+ clearTimeout(timer);
1969
+ }
1740
1970
  }
1741
1971
  async receive(limit = 50) {
1742
1972
  const result = this._pending.slice(0, limit);
@@ -1763,6 +1993,21 @@ var WebSocketTransport = class extends TransportBase {
1763
1993
  });
1764
1994
  return this._settling;
1765
1995
  }
1996
+ /** Hand the relay's answer (or a refusal) to the send waiting for it. */
1997
+ _settle(messageId, answer, error) {
1998
+ if (typeof messageId !== "string") return;
1999
+ const waiting = this._waiting.get(messageId);
2000
+ if (!waiting) return;
2001
+ this._waiting.delete(messageId);
2002
+ if (error) waiting.reject(error);
2003
+ else waiting.resolve(answer);
2004
+ }
2005
+ /** The socket went: no answers will come for what it carried. */
2006
+ _abandonWaiting() {
2007
+ const waiting = [...this._waiting.values()];
2008
+ this._waiting.clear();
2009
+ for (const w of waiting) w.resolve(null);
2010
+ }
1766
2011
  _answered() {
1767
2012
  this._unanswered = Math.max(0, this._unanswered - 1);
1768
2013
  if (this._unanswered === 0) this._settled?.();
@@ -1794,6 +2039,7 @@ var WebSocketTransport = class extends TransportBase {
1794
2039
  this._ws = null;
1795
2040
  this._unanswered = 0;
1796
2041
  this._settled?.();
2042
+ this._abandonWaiting();
1797
2043
  if (!this._running) return;
1798
2044
  attempt += 1;
1799
2045
  const delay = Math.min(BASE_DELAY * Math.pow(2, attempt), MAX_DELAY);
@@ -1823,16 +2069,28 @@ var WebSocketTransport = class extends TransportBase {
1823
2069
  }
1824
2070
  } else if (msgType === "ack") {
1825
2071
  this._answered();
2072
+ this._settle(msg["message_id"], msg);
2073
+ } else if (msgType === "relay.notice") {
2074
+ log.warn(
2075
+ `Relay notice: message ${String(msg["message_id"])} is ${String(msg["status"])}`
2076
+ );
1826
2077
  } else if (msgType === "error" || "error" in msg) {
1827
2078
  this._answered();
2079
+ const code = String(msg["error"] ?? msg["code"] ?? "error");
2080
+ const detail = typeof msg["detail"] === "string" ? msg["detail"] : void 0;
2081
+ log.warn(`Relay error: [${code}] ${detail ?? ""}`.trimEnd());
2082
+ const messageId = msg["message_id"];
2083
+ if (typeof messageId === "string") {
2084
+ this._settle(messageId, null, new SendRefusedError(messageId, code, detail));
2085
+ }
1828
2086
  }
1829
2087
  }
1830
2088
  };
1831
2089
 
1832
2090
  // src/sdk/transport/index.ts
1833
- function createTransport(config, token, address, onMessage) {
2091
+ function createTransport(config, token, address2, onMessage) {
1834
2092
  if (config.transportType === "http") {
1835
- return new HTTPTransport(config.relayUrl, token, address);
2093
+ return new HTTPTransport(config.relayUrl, token, address2);
1836
2094
  } else {
1837
2095
  return new WebSocketTransport(config.relayWsUrl, token, onMessage);
1838
2096
  }
@@ -2225,7 +2483,12 @@ var Agent = class {
2225
2483
  }
2226
2484
  // -- Messaging -----------------------------------------------------------
2227
2485
  /**
2228
- * Send an encrypted, signed message. Returns the message_id.
2486
+ * Send an encrypted, signed message.
2487
+ *
2488
+ * Returns the message id with what the relay did with it: `status`
2489
+ * "pending" means the recipient only takes approved contacts, and the
2490
+ * message was not kept (don't resend it). `delivered` is true only when
2491
+ * the relay says the recipient has it.
2229
2492
  */
2230
2493
  async send(toAddress, message, options) {
2231
2494
  await this._ensureConnected();
@@ -2257,14 +2520,20 @@ var Agent = class {
2257
2520
  );
2258
2521
  const wire = toWireDict(envelope);
2259
2522
  const relayUrls = await this._getRelayUrls(toAddress);
2523
+ let answer;
2260
2524
  if (relayUrls !== null && relayUrls.length === 1 && relayUrls[0] === this._config.relayUrl) {
2261
- await this._transport.send(wire);
2525
+ answer = await this._transport.send(wire);
2262
2526
  } else if (relayUrls !== null && relayUrls.length > 0) {
2263
- await this._trySendWithFailover(wire, relayUrls);
2527
+ answer = await this._trySendWithFailover(wire, relayUrls);
2264
2528
  } else {
2265
- await this._transport.send(wire);
2529
+ answer = await this._transport.send(wire);
2266
2530
  }
2267
- return envelope.messageId;
2531
+ const status = sendStatus(answer);
2532
+ return {
2533
+ messageId: envelope.messageId,
2534
+ status,
2535
+ delivered: status === "delivered"
2536
+ };
2268
2537
  }
2269
2538
  /**
2270
2539
  * Retrieve, decrypt, and verify pending messages.
@@ -2309,43 +2578,43 @@ var Agent = class {
2309
2578
  * Adds the sender to contacts with trust_source='explicit-approval'
2310
2579
  * and sends handshake.accept back.
2311
2580
  */
2312
- async approve(address) {
2581
+ async approve(address2) {
2313
2582
  await this._ensureConnected();
2314
2583
  const pendingList = this._contactBook.getPending();
2315
- const entry = pendingList.find((p) => p.address === address);
2584
+ const entry = pendingList.find((p) => p.address === address2);
2316
2585
  if (!entry) {
2317
- throw new UAMError(`No pending handshake from ${address}`);
2586
+ throw new UAMError(`No pending handshake from ${address2}`);
2318
2587
  }
2319
2588
  const cardDict = JSON.parse(entry.contactCard);
2320
2589
  const card = contactCardFromDict(cardDict);
2321
2590
  verifyContactCard(card);
2322
2591
  const senderVk = deserializeVerifyKey(card.publicKey);
2323
- bindCardToEnvelope(card, { fromAddress: address }, senderVk);
2592
+ bindCardToEnvelope(card, { fromAddress: address2 }, senderVk);
2324
2593
  this._contactBook.addContact(card.address, card.publicKey, {
2325
2594
  displayName: card.displayName,
2326
2595
  trustState: "trusted",
2327
2596
  trustSource: "explicit-approval"
2328
2597
  });
2329
- this._contactBook.removePending(address);
2330
- await this._handshake._sendAccept(this, address, senderVk);
2598
+ this._contactBook.removePending(address2);
2599
+ await this._handshake._sendAccept(this, address2, senderVk);
2331
2600
  }
2332
2601
  /**
2333
2602
  * Deny a pending handshake request.
2334
2603
  *
2335
2604
  * Removes the entry from pending and sends handshake.deny.
2336
2605
  */
2337
- async deny(address) {
2606
+ async deny(address2) {
2338
2607
  await this._ensureConnected();
2339
2608
  const pendingList = this._contactBook.getPending();
2340
- const entry = pendingList.find((p) => p.address === address);
2609
+ const entry = pendingList.find((p) => p.address === address2);
2341
2610
  if (!entry) {
2342
- throw new UAMError(`No pending handshake from ${address}`);
2611
+ throw new UAMError(`No pending handshake from ${address2}`);
2343
2612
  }
2344
2613
  const cardDict = JSON.parse(entry.contactCard);
2345
2614
  const card = contactCardFromDict(cardDict);
2346
2615
  const senderVk = deserializeVerifyKey(card.publicKey);
2347
- this._contactBook.removePending(address);
2348
- await this._handshake._sendDeny(this, address, senderVk);
2616
+ this._contactBook.removePending(address2);
2617
+ await this._handshake._sendDeny(this, address2, senderVk);
2349
2618
  }
2350
2619
  /**
2351
2620
  * Block an address or domain pattern (HAND-04).
@@ -2371,7 +2640,7 @@ var Agent = class {
2371
2640
  *
2372
2641
  * Returns `true` if verified, `false` on timeout.
2373
2642
  */
2374
- async verifyDomain(domain, options) {
2643
+ async verifyDomain(domain2, options) {
2375
2644
  const { generateTxtRecord: generateTxtRecord2 } = await Promise.resolve().then(() => (init_dns_verifier(), dns_verifier_exports));
2376
2645
  await this._ensureConnected();
2377
2646
  const timeout = options?.timeout ?? 3e5;
@@ -2392,7 +2661,7 @@ var Agent = class {
2392
2661
  "Content-Type": "application/json",
2393
2662
  Authorization: `Bearer ${this._token}`
2394
2663
  },
2395
- body: JSON.stringify({ domain }),
2664
+ body: JSON.stringify({ domain: domain2 }),
2396
2665
  signal: AbortSignal.timeout(3e4)
2397
2666
  }
2398
2667
  );
@@ -2445,7 +2714,7 @@ var Agent = class {
2445
2714
  if (!resp.ok) {
2446
2715
  throw new Error(`HTTP ${resp.status}`);
2447
2716
  }
2448
- return;
2717
+ return await readAnswer(resp);
2449
2718
  } catch (err) {
2450
2719
  lastError = err instanceof Error ? err : new Error(String(err));
2451
2720
  }
@@ -2743,13 +3012,13 @@ var Agent = class {
2743
3012
  };
2744
3013
  let resp = await this._postRegistration(body);
2745
3014
  if (resp.status === 409) {
2746
- const address2 = await this._addressOnRelay();
3015
+ const address3 = await this._addressOnRelay();
2747
3016
  resp = await this._postRegistration({
2748
3017
  ...body,
2749
- proof: this._registrationProof(address2)
3018
+ proof: this._registrationProof(address3)
2750
3019
  });
2751
3020
  if (resp.status === 409 || resp.status === 403) {
2752
- throw this._ownershipNotProven(address2);
3021
+ throw this._ownershipNotProven(address3);
2753
3022
  }
2754
3023
  }
2755
3024
  if (!resp.ok) {
@@ -2759,26 +3028,26 @@ var Agent = class {
2759
3028
  );
2760
3029
  }
2761
3030
  const data = await resp.json();
2762
- const address = data["address"];
2763
- this._useAddress(address);
3031
+ const address2 = data["address"];
3032
+ this._useAddress(address2);
2764
3033
  this._token = data["token"];
2765
3034
  const { name } = this._config;
2766
3035
  const token = this._token;
2767
3036
  this._tryToSave("token", () => this._keyManager.saveToken(name, token));
2768
- this._saveAddress(address);
3037
+ this._saveAddress(address2);
2769
3038
  }
2770
3039
  /** This agent's address on the relay, for signing a proof. */
2771
3040
  async _addressOnRelay() {
2772
3041
  const { name, explicitRelayDomain, relayDomain, relayUrl } = this._config;
2773
3042
  if (explicitRelayDomain !== null) return `${name}::${explicitRelayDomain}`;
2774
- const domain = await this._fetchRelayDomain();
2775
- if (domain === null) return `${name}::${relayDomain}`;
2776
- if (!this._relayMayUse(domain)) {
3043
+ const domain2 = await this._fetchRelayDomain();
3044
+ if (domain2 === null) return `${name}::${relayDomain}`;
3045
+ if (!this._relayMayUse(domain2)) {
2777
3046
  throw new UAMError(
2778
- `The relay at ${relayUrl} says its domain is ${domain}. Pass the domain option as ${JSON.stringify(domain)} if that is right.`
3047
+ `The relay at ${relayUrl} says its domain is ${domain2}. Pass the domain option as ${JSON.stringify(domain2)} if that is right.`
2779
3048
  );
2780
3049
  }
2781
- return `${name}::${domain}`;
3050
+ return `${name}::${domain2}`;
2782
3051
  }
2783
3052
  /**
2784
3053
  * True if the relay's URL fits `domain`, so a proof can be signed for it.
@@ -2786,14 +3055,14 @@ var Agent = class {
2786
3055
  * A proof names an address on one relay, so it is only signed for a domain
2787
3056
  * this URL belongs to. A relay on this machine may use any domain.
2788
3057
  */
2789
- _relayMayUse(domain) {
3058
+ _relayMayUse(domain2) {
2790
3059
  let host;
2791
3060
  try {
2792
3061
  host = new URL(this._config.relayUrl).hostname.toLowerCase();
2793
3062
  } catch {
2794
3063
  return false;
2795
3064
  }
2796
- const wanted = domain.toLowerCase();
3065
+ const wanted = domain2.toLowerCase();
2797
3066
  return host === wanted || host.endsWith(`.${wanted}`) || host === "localhost" || host === "[::1]" || /^127\.\d+\.\d+\.\d+$/.test(host);
2798
3067
  }
2799
3068
  _postRegistration(body) {
@@ -2808,27 +3077,27 @@ var Agent = class {
2808
3077
  *
2809
3078
  * The signed string must stay byte-identical to the Python SDK's.
2810
3079
  */
2811
- _registrationProof(address) {
3080
+ _registrationProof(address2) {
2812
3081
  const timestamp = utcTimestamp();
2813
3082
  const signature = signMessage(
2814
3083
  new TextEncoder().encode(`uam-register-v1
2815
- ${address}
3084
+ ${address2}
2816
3085
  ${timestamp}`),
2817
3086
  this._keyManager.signingKey
2818
3087
  );
2819
3088
  return { timestamp, signature };
2820
3089
  }
2821
- _ownershipNotProven(address) {
3090
+ _ownershipNotProven(address2) {
2822
3091
  return new UAMError(
2823
- `Address already registered, and this key could not prove ownership: ` + address
3092
+ `Address already registered, and this key could not prove ownership: ` + address2
2824
3093
  );
2825
3094
  }
2826
3095
  /** Remember `address` for this relay, if the key folder allows it. */
2827
- _saveAddress(address) {
3096
+ _saveAddress(address2) {
2828
3097
  const { name, relayUrl } = this._config;
2829
3098
  this._tryToSave(
2830
3099
  "address",
2831
- () => this._keyManager.saveAddress(name, address, relayUrl)
3100
+ () => this._keyManager.saveAddress(name, address2, relayUrl)
2832
3101
  );
2833
3102
  }
2834
3103
  /** Saving is best effort: a key folder we can't write doesn't stop us. */
@@ -2841,12 +3110,12 @@ ${timestamp}`),
2841
3110
  }
2842
3111
  }
2843
3112
  /** Take `address` as ours; its domain is this relay's real domain. */
2844
- _useAddress(address) {
2845
- this._address = address;
2846
- const domain = parseAddress(address).domain;
2847
- this._config.relayDomain = domain;
3113
+ _useAddress(address2) {
3114
+ this._address = address2;
3115
+ const domain2 = parseAddress(address2).domain;
3116
+ this._config.relayDomain = domain2;
2848
3117
  if (this._resolver instanceof SmartResolver) {
2849
- this._resolver.relayDomain = domain;
3118
+ this._resolver.relayDomain = domain2;
2850
3119
  }
2851
3120
  }
2852
3121
  /**
@@ -2861,11 +3130,11 @@ ${timestamp}`),
2861
3130
  if (explicitRelayDomain !== null) return `${name}::${explicitRelayDomain}`;
2862
3131
  const saved = this._keyManager.loadAddress(name, this._config.relayUrl);
2863
3132
  if (saved) return saved;
2864
- const domain = await this._fetchRelayDomain();
2865
- if (domain === null) return `${name}::${this._config.relayDomain}`;
2866
- const address = `${name}::${domain}`;
2867
- this._saveAddress(address);
2868
- return address;
3133
+ const domain2 = await this._fetchRelayDomain();
3134
+ if (domain2 === null) return `${name}::${this._config.relayDomain}`;
3135
+ const address2 = `${name}::${domain2}`;
3136
+ this._saveAddress(address2);
3137
+ return address2;
2869
3138
  }
2870
3139
  /** Ask the relay for its domain; null if it cannot say. */
2871
3140
  async _fetchRelayDomain() {
@@ -2874,9 +3143,9 @@ ${timestamp}`),
2874
3143
  const resp = await fetch(url, { signal: AbortSignal.timeout(1e4) });
2875
3144
  if (!resp.ok) return null;
2876
3145
  const data = await resp.json();
2877
- const domain = data["relay_domain"];
2878
- if (typeof domain !== "string" || domain === "") return null;
2879
- return parseAddress(`${this._config.name}::${domain}`).domain;
3146
+ const domain2 = data["relay_domain"];
3147
+ if (typeof domain2 !== "string" || domain2 === "") return null;
3148
+ return parseAddress(`${this._config.name}::${domain2}`).domain;
2880
3149
  } catch {
2881
3150
  return null;
2882
3151
  }
@@ -2915,18 +3184,18 @@ async function initCommand(options) {
2915
3184
  const keyPath = (0, import_node_path7.join)(cfg.keyDir, `${agentName}.key`);
2916
3185
  if ((0, import_node_fs6.existsSync)(keyPath)) {
2917
3186
  km.loadOrGenerate(agentName);
2918
- const address2 = km.loadAddress(agentName, cfg.relayUrl) ?? `${agentName}::${cfg.relayDomain}`;
3187
+ const address3 = km.loadAddress(agentName, cfg.relayUrl) ?? `${agentName}::${cfg.relayDomain}`;
2919
3188
  const fp2 = publicKeyFingerprint(km.verifyKey);
2920
- console.log(`Agent already initialized: ${address2}`);
3189
+ console.log(`Agent already initialized: ${address3}`);
2921
3190
  console.log(`Fingerprint: ${fp2}`);
2922
3191
  return;
2923
3192
  }
2924
3193
  const agent = new Agent(agentName, { relay: options.relay });
2925
3194
  await agent.connect();
2926
- const address = agent.address;
3195
+ const address2 = agent.address;
2927
3196
  const fp = publicKeyFingerprint(agent._keyManager.verifyKey);
2928
3197
  await agent.close();
2929
- console.log(`Initialized agent: ${address}`);
3198
+ console.log(`Initialized agent: ${address2}`);
2930
3199
  console.log(`Fingerprint: ${fp}`);
2931
3200
  } catch (err) {
2932
3201
  cliError(`Error: ${err instanceof Error ? err.message : String(err)}`);
@@ -2934,7 +3203,7 @@ async function initCommand(options) {
2934
3203
  }
2935
3204
 
2936
3205
  // src/cli/commands/send.ts
2937
- async function sendCommand(address, message, options) {
3206
+ async function sendCommand(address2, message, options) {
2938
3207
  const agentName = options.name ?? findAgentName();
2939
3208
  if (!agentName) {
2940
3209
  cliError("No agent initialized. Run `uam init` first.");
@@ -2942,9 +3211,12 @@ async function sendCommand(address, message, options) {
2942
3211
  try {
2943
3212
  const agent = new Agent(agentName);
2944
3213
  await agent.connect();
2945
- const msgId = await agent.send(address, message);
3214
+ const result = await agent.send(address2, message);
2946
3215
  await agent.close();
2947
- console.log(`Message sent to ${address} (id: ${msgId})`);
3216
+ console.log(`Message to ${address2}: ${result.status} (id: ${result.messageId})`);
3217
+ if (result.status === "pending") {
3218
+ console.log(pendingNote(address2));
3219
+ }
2948
3220
  } catch (err) {
2949
3221
  cliError(`Error: ${err instanceof Error ? err.message : String(err)}`);
2950
3222
  }
@@ -3011,9 +3283,9 @@ async function whoamiCommand(options) {
3011
3283
  await sodiumReady;
3012
3284
  const km = new KeyManager(cfg.keyDir);
3013
3285
  km.loadOrGenerate(agentName);
3014
- const address = km.loadAddress(agentName, cfg.relayUrl) ?? `${agentName}::${cfg.relayDomain}`;
3286
+ const address2 = km.loadAddress(agentName, cfg.relayUrl) ?? `${agentName}::${cfg.relayDomain}`;
3015
3287
  const fp = publicKeyFingerprint(km.verifyKey);
3016
- console.log(`Address: ${address}`);
3288
+ console.log(`Address: ${address2}`);
3017
3289
  console.log(`Fingerprint: ${fp}`);
3018
3290
  console.log(`Key file: ${keyPath}`);
3019
3291
  }
@@ -3084,7 +3356,7 @@ async function pendingCommand(options) {
3084
3356
  }
3085
3357
 
3086
3358
  // src/cli/commands/approve.ts
3087
- async function approveCommand(address, options) {
3359
+ async function approveCommand(address2, options) {
3088
3360
  const agentName = options.name ?? findAgentName();
3089
3361
  if (!agentName) {
3090
3362
  cliError("No agent initialized. Run `uam init` first.");
@@ -3092,16 +3364,16 @@ async function approveCommand(address, options) {
3092
3364
  try {
3093
3365
  const agent = new Agent(agentName);
3094
3366
  await agent.connect();
3095
- await agent.approve(address);
3367
+ await agent.approve(address2);
3096
3368
  await agent.close();
3097
- console.log(`Approved: ${address}`);
3369
+ console.log(`Approved: ${address2}`);
3098
3370
  } catch (err) {
3099
3371
  cliError(`Error: ${err instanceof Error ? err.message : err}`);
3100
3372
  }
3101
3373
  }
3102
3374
 
3103
3375
  // src/cli/commands/deny.ts
3104
- async function denyCommand(address, options) {
3376
+ async function denyCommand(address2, options) {
3105
3377
  const agentName = options.name ?? findAgentName();
3106
3378
  if (!agentName) {
3107
3379
  cliError("No agent initialized. Run `uam init` first.");
@@ -3109,9 +3381,9 @@ async function denyCommand(address, options) {
3109
3381
  try {
3110
3382
  const agent = new Agent(agentName);
3111
3383
  await agent.connect();
3112
- await agent.deny(address);
3384
+ await agent.deny(address2);
3113
3385
  await agent.close();
3114
- console.log(`Denied: ${address}`);
3386
+ console.log(`Denied: ${address2}`);
3115
3387
  } catch (err) {
3116
3388
  cliError(`Error: ${err instanceof Error ? err.message : err}`);
3117
3389
  }
@@ -3153,21 +3425,21 @@ async function unblockCommand(pattern, options) {
3153
3425
  }
3154
3426
 
3155
3427
  // src/cli/commands/contact.ts
3156
- async function contactFingerprintCommand(address, options) {
3428
+ async function contactFingerprintCommand(address2, options) {
3157
3429
  const agentName = options.name ?? findAgentName();
3158
3430
  const cfg = new SDKConfig({ name: agentName ?? "_probe" });
3159
3431
  const book = new ContactBook(cfg.dataDir);
3160
3432
  try {
3161
3433
  book.open();
3162
- const pkStr = book.getPublicKey(address);
3434
+ const pkStr = book.getPublicKey(address2);
3163
3435
  book.close();
3164
3436
  if (pkStr === null) {
3165
- cliError(`Contact not found: ${address}`);
3437
+ cliError(`Contact not found: ${address2}`);
3166
3438
  }
3167
3439
  const vk = deserializeVerifyKey(pkStr);
3168
3440
  const fp = publicKeyFingerprint(vk);
3169
3441
  const shortFp = fp.slice(0, 16);
3170
- console.log(`Address: ${address}`);
3442
+ console.log(`Address: ${address2}`);
3171
3443
  console.log(`Fingerprint: ${shortFp}`);
3172
3444
  console.log(`Full: ${fp}`);
3173
3445
  } catch (err) {
@@ -3177,43 +3449,43 @@ async function contactFingerprintCommand(address, options) {
3177
3449
  cliError(`Error: ${err instanceof Error ? err.message : err}`);
3178
3450
  }
3179
3451
  }
3180
- async function contactVerifyCommand(address, options) {
3452
+ async function contactVerifyCommand(address2, options) {
3181
3453
  const agentName = options.name ?? findAgentName();
3182
3454
  const cfg = new SDKConfig({ name: agentName ?? "_probe" });
3183
3455
  const book = new ContactBook(cfg.dataDir);
3184
3456
  try {
3185
3457
  book.open();
3186
- const pkStr = book.getPublicKey(address);
3458
+ const pkStr = book.getPublicKey(address2);
3187
3459
  if (pkStr === null) {
3188
3460
  book.close();
3189
- cliError(`Contact not found: ${address}`);
3461
+ cliError(`Contact not found: ${address2}`);
3190
3462
  }
3191
3463
  const vk = deserializeVerifyKey(pkStr);
3192
3464
  const fp = publicKeyFingerprint(vk);
3193
- console.log(`Fingerprint for ${address}: ${fp.slice(0, 16)}`);
3194
- book.addContact(address, pkStr, {
3465
+ console.log(`Fingerprint for ${address2}: ${fp.slice(0, 16)}`);
3466
+ book.addContact(address2, pkStr, {
3195
3467
  trustState: "verified",
3196
3468
  trustSource: "manual-verify"
3197
3469
  });
3198
3470
  book.close();
3199
- console.log(`Contact ${address} verified. Trust state: verified`);
3471
+ console.log(`Contact ${address2} verified. Trust state: verified`);
3200
3472
  } catch (err) {
3201
3473
  cliError(`Error: ${err instanceof Error ? err.message : err}`);
3202
3474
  }
3203
3475
  }
3204
- async function contactRemoveCommand(address, options) {
3476
+ async function contactRemoveCommand(address2, options) {
3205
3477
  const agentName = options.name ?? findAgentName();
3206
3478
  const cfg = new SDKConfig({ name: agentName ?? "_probe" });
3207
3479
  const book = new ContactBook(cfg.dataDir);
3208
3480
  try {
3209
3481
  book.open();
3210
- const removed = book.removeContact(address);
3482
+ const removed = book.removeContact(address2);
3211
3483
  book.close();
3212
3484
  if (!removed) {
3213
- cliError(`Contact not found: ${address}`);
3485
+ cliError(`Contact not found: ${address2}`);
3214
3486
  }
3215
3487
  console.log(
3216
- `Contact ${address} removed. Future messages will re-resolve the public key.`
3488
+ `Contact ${address2} removed. Future messages will re-resolve the public key.`
3217
3489
  );
3218
3490
  } catch (err) {
3219
3491
  cliError(`Error: ${err instanceof Error ? err.message : err}`);
@@ -3222,7 +3494,7 @@ async function contactRemoveCommand(address, options) {
3222
3494
 
3223
3495
  // src/cli/commands/verify-domain.ts
3224
3496
  init_dns_verifier();
3225
- async function verifyDomainCommand(domain, options) {
3497
+ async function verifyDomainCommand(domain2, options) {
3226
3498
  const agentName = options.name ?? findAgentName();
3227
3499
  if (!agentName) {
3228
3500
  cliError("No agent initialized. Run `uam init` first.");
@@ -3235,27 +3507,27 @@ async function verifyDomainCommand(domain, options) {
3235
3507
  const pubkey = agent.publicKey;
3236
3508
  const relayUrl = agent._config.relayUrl;
3237
3509
  const txtValue = generateTxtRecord(pubkey, relayUrl);
3238
- console.log(`Add this DNS TXT record to verify ${domain}:`);
3510
+ console.log(`Add this DNS TXT record to verify ${domain2}:`);
3239
3511
  console.log();
3240
- console.log(` Host: _uam.${domain}`);
3512
+ console.log(` Host: _uam.${domain2}`);
3241
3513
  console.log(` Type: TXT`);
3242
3514
  console.log(` Value: ${txtValue}`);
3243
3515
  console.log();
3244
3516
  console.log(`Or serve this HTTPS fallback:`);
3245
3517
  console.log();
3246
- console.log(` URL: https://${domain}/.well-known/uam.json`);
3518
+ console.log(` URL: https://${domain2}/.well-known/uam.json`);
3247
3519
  console.log();
3248
3520
  console.log(`See documentation for .well-known/uam.json format.`);
3249
3521
  console.log();
3250
3522
  console.log("Polling for verification...");
3251
- const verified = await agent.verifyDomain(domain, {
3523
+ const verified = await agent.verifyDomain(domain2, {
3252
3524
  timeout: timeout * 1e3,
3253
3525
  pollInterval: pollInterval * 1e3
3254
3526
  });
3255
3527
  await agent.close();
3256
3528
  if (verified) {
3257
3529
  console.log(
3258
- `Verified! ${agent.address} is now Tier 2 via ${domain}.`
3530
+ `Domain checked by ${agent.address.split("::")[1] ?? "your relay"}: ${agent.address} is now Tier 2 via ${domain2}.`
3259
3531
  );
3260
3532
  } else {
3261
3533
  console.log(
@@ -3278,9 +3550,9 @@ program.command("init").description("Initialize a new agent: generate keys and r
3278
3550
  relay: opts.relay
3279
3551
  });
3280
3552
  });
3281
- program.command("send <address> <message>").description("Send a message to another agent").action(async (address, message) => {
3553
+ program.command("send <address> <message>").description("Send a message to another agent").action(async (address2, message) => {
3282
3554
  const globalName = program.opts().name;
3283
- await sendCommand(address, message, { name: globalName });
3555
+ await sendCommand(address2, message, { name: globalName });
3284
3556
  });
3285
3557
  program.command("inbox").description("Check your inbox for pending messages").option("-l, --limit <number>", "Max messages to retrieve", "20").action(async (opts) => {
3286
3558
  const globalName = program.opts().name;
@@ -3302,21 +3574,21 @@ program.command("contacts").description("List known contacts from the local cont
3302
3574
  await contactsCommand({ name: globalName });
3303
3575
  });
3304
3576
  var contactCmd = program.command("contact").description("Contact management commands (fingerprint, verify, remove)");
3305
- contactCmd.command("fingerprint <address>").description("Display the fingerprint for a known contact's public key").action(async (address) => {
3577
+ contactCmd.command("fingerprint <address>").description("Display the fingerprint for a known contact's public key").action(async (address2) => {
3306
3578
  const globalName = program.opts().name;
3307
- await contactFingerprintCommand(address, { name: globalName });
3579
+ await contactFingerprintCommand(address2, { name: globalName });
3308
3580
  });
3309
- contactCmd.command("verify <address>").description("Manually verify a contact, upgrading trust state to verified").option("-y, --yes", "Skip confirmation prompt").action(async (address, opts) => {
3581
+ contactCmd.command("verify <address>").description("Manually verify a contact, upgrading trust state to verified").option("-y, --yes", "Skip confirmation prompt").action(async (address2, opts) => {
3310
3582
  const globalName = program.opts().name;
3311
- await contactVerifyCommand(address, { name: globalName, yes: opts.yes });
3583
+ await contactVerifyCommand(address2, { name: globalName, yes: opts.yes });
3312
3584
  });
3313
- contactCmd.command("remove <address>").description("Remove a contact from the contact book").action(async (address) => {
3585
+ contactCmd.command("remove <address>").description("Remove a contact from the contact book").action(async (address2) => {
3314
3586
  const globalName = program.opts().name;
3315
- await contactRemoveCommand(address, { name: globalName });
3587
+ await contactRemoveCommand(address2, { name: globalName });
3316
3588
  });
3317
- program.command("verify-domain <domain>").description("Verify domain ownership for Tier 2 DNS-verified status").option("-t, --timeout <seconds>", "Polling timeout in seconds", "300").option("--poll-interval <seconds>", "Polling interval in seconds", "10").action(async (domain, opts) => {
3589
+ program.command("verify-domain <domain>").description("Verify domain ownership for Tier 2 DNS-verified status").option("-t, --timeout <seconds>", "Polling timeout in seconds", "300").option("--poll-interval <seconds>", "Polling interval in seconds", "10").action(async (domain2, opts) => {
3318
3590
  const globalName = program.opts().name;
3319
- await verifyDomainCommand(domain, {
3591
+ await verifyDomainCommand(domain2, {
3320
3592
  name: globalName,
3321
3593
  timeout: parseInt(opts.timeout, 10),
3322
3594
  pollInterval: parseInt(opts.pollInterval, 10)
@@ -3326,13 +3598,13 @@ program.command("pending").description("List pending handshake requests awaiting
3326
3598
  const globalName = program.opts().name;
3327
3599
  await pendingCommand({ name: globalName });
3328
3600
  });
3329
- program.command("approve <address>").description("Approve a pending handshake request").action(async (address) => {
3601
+ program.command("approve <address>").description("Approve a pending handshake request").action(async (address2) => {
3330
3602
  const globalName = program.opts().name;
3331
- await approveCommand(address, { name: globalName });
3603
+ await approveCommand(address2, { name: globalName });
3332
3604
  });
3333
- program.command("deny <address>").description("Deny a pending handshake request").action(async (address) => {
3605
+ program.command("deny <address>").description("Deny a pending handshake request").action(async (address2) => {
3334
3606
  const globalName = program.opts().name;
3335
- await denyCommand(address, { name: globalName });
3607
+ await denyCommand(address2, { name: globalName });
3336
3608
  });
3337
3609
  program.command("block <pattern>").description("Block an address or domain pattern (e.g., spammer::evil.com or *::evil.com)").action(async (pattern) => {
3338
3610
  const globalName = program.opts().name;