@decentnetwork/beagle 0.1.118 → 0.1.119

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.
@@ -1,4 +1,4 @@
1
- window.__DK_UI_VERSION="0.1.118";
1
+ window.__DK_UI_VERSION="0.1.119";
2
2
  "use strict";
3
3
  (() => {
4
4
  // packages/beagle-ui/dist/beagle-ui.js
@@ -123,6 +123,8 @@ window.__DK_UI_VERSION="0.1.118";
123
123
  var hostPrefersRtcFile = false;
124
124
  var hostHasKeyBackup = false;
125
125
  var hostEnsWrite = true;
126
+ var hostWalletBind = true;
127
+ var hostAssistant = true;
126
128
  var hostBrandHref = "";
127
129
  var hostTabs = null;
128
130
  var hostHideApps = null;
@@ -142,6 +144,8 @@ window.__DK_UI_VERSION="0.1.118";
142
144
  hostPrefersRtcFile = !!H.hostPrefersRtcFile;
143
145
  hostHasKeyBackup = !!H.hostHasKeyBackup;
144
146
  hostEnsWrite = H.hostEnsWrite !== void 0 ? !!H.hostEnsWrite : !H.hostNeedsWelcome;
147
+ hostWalletBind = H.hostWalletBind !== void 0 ? !!H.hostWalletBind : hostEnsWrite;
148
+ hostAssistant = H.hostAssistant !== void 0 ? !!H.hostAssistant : true;
145
149
  hostBrandHref = typeof H.hostBrandHref === "string" ? H.hostBrandHref : "";
146
150
  }
147
151
  function fileUrl(name) {
@@ -3484,6 +3488,65 @@ ${peer.address}`
3484
3488
  return /* @__PURE__ */ React.createElement("div", { style: { flex: 1, display: "flex", minWidth: 0, minHeight: 0 } }, /* @__PURE__ */ React.createElement(PeerSidebar, { T, peers, requests, activeId, onSelect, onAct, onAdd, prefillAddr, onPrefillConsumed, muted, isProtocol, protocolFix }), peer ? /* @__PURE__ */ React.createElement(Conversation, { T, peer, lang, peers, onOpenChat: onSelect, thread, isProtocol, onSend, onSendFile, onSendRtcFile, onAlias, onRemove, onOpenNet, onCall, onReloadThread, hasEarlier, onLoadEarlier, onAssistantRemove, onAssistantAdopt, muted: !!muted && muted.includes(peer.id), onToggleMute }) : /* @__PURE__ */ React.createElement(ChatEmpty, { T }));
3485
3489
  }
3486
3490
  Object.assign(window, { ChatTab });
3491
+ var PROOF_KEY = { eth: "proof.eth", sol: "proof.sol" };
3492
+ var HEAD = "Beagle wallet binding";
3493
+ var LINE = "I bind this wallet to my Beagle identity.";
3494
+ var ADDRESS_RE = { eth: /^0x[0-9a-fA-F]{40}$/, sol: /^[1-9A-HJ-NP-Za-km-z]{32,44}$/ };
3495
+ var USERID_RE = /^[1-9A-HJ-NP-Za-km-z]{32,48}$/;
3496
+ var ISSUED_RE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$/;
3497
+ function isWalletAddress(chain, address) {
3498
+ const re = ADDRESS_RE[chain];
3499
+ return !!re && typeof address === "string" && re.test(address);
3500
+ }
3501
+ function proofTime(date = /* @__PURE__ */ new Date()) {
3502
+ return new Date(Math.floor(date.getTime() / 1e3) * 1e3).toISOString().replace(".000Z", "Z");
3503
+ }
3504
+ function walletProofMessage({ userid, chain, address, issued }) {
3505
+ if (!USERID_RE.test(String(userid || "")))
3506
+ throw new Error("wallet proof: bad userid");
3507
+ if (!isWalletAddress(chain, address))
3508
+ throw new Error("wallet proof: bad chain or address");
3509
+ if (!ISSUED_RE.test(String(issued || "")))
3510
+ throw new Error("wallet proof: bad issued time");
3511
+ return [HEAD, "", LINE, "", `userid: ${userid}`, `chain: ${chain}`, `address: ${address}`, `issued: ${issued}`].join("\n");
3512
+ }
3513
+ var B58 = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
3514
+ function base58Encode(bytes) {
3515
+ const digits = [];
3516
+ for (const b of bytes) {
3517
+ let carry = b;
3518
+ for (let j = 0; j < digits.length; j++) {
3519
+ carry += digits[j] << 8;
3520
+ digits[j] = carry % 58;
3521
+ carry = carry / 58 | 0;
3522
+ }
3523
+ while (carry > 0) {
3524
+ digits.push(carry % 58);
3525
+ carry = carry / 58 | 0;
3526
+ }
3527
+ }
3528
+ let out = "";
3529
+ for (const b of bytes) {
3530
+ if (b !== 0)
3531
+ break;
3532
+ out += "1";
3533
+ }
3534
+ for (let i = digits.length - 1; i >= 0; i--)
3535
+ out += B58[digits[i]];
3536
+ return out;
3537
+ }
3538
+ var utf8Hex = (s) => "0x" + Array.from(new TextEncoder().encode(s), (b) => b.toString(16).padStart(2, "0")).join("");
3539
+ async function signEthProof(provider, { userid, address, issued = proofTime() }) {
3540
+ const message = walletProofMessage({ userid, chain: "eth", address, issued });
3541
+ const signature = await provider.request({ method: "personal_sign", params: [utf8Hex(message), address] });
3542
+ return { message, signature: String(signature) };
3543
+ }
3544
+ async function signSolProof(wallet, { userid, address, issued = proofTime() }) {
3545
+ const message = walletProofMessage({ userid, chain: "sol", address, issued });
3546
+ const r = await wallet.signMessage(new TextEncoder().encode(message), "utf8");
3547
+ const sig = r && r.signature ? r.signature : r;
3548
+ return { message, signature: base58Encode(Uint8Array.from(sig)) };
3549
+ }
3487
3550
  function CopyBtn({ value, copiedText = "Copied", copyFailedText = "Copy failed", copyTitle = "Copy" }) {
3488
3551
  const [status, setStatus] = React.useState(null);
3489
3552
  const timer = React.useRef(null);
@@ -3897,7 +3960,7 @@ ${peer.address}`
3897
3960
  const [msg, setMsg] = React.useState(null);
3898
3961
  const reload = () => {
3899
3962
  fetch("/api/ens-name").then((r) => r.json()).then((d) => {
3900
- setSt({ loading: false, registered: !!d.registered, record: d.record || null, mineOwned: !!d.mineOwned });
3963
+ setSt({ loading: false, registered: !!d.registered, record: d.record || null, mineOwned: !!d.mineOwned, writeBlocked: d.writeBlocked || "" });
3901
3964
  if (d.registered && d.record && d.record.name)
3902
3965
  setLabel(d.record.name.replace(/\.beagles\.eth$/i, ""));
3903
3966
  if (onRecord)
@@ -3924,7 +3987,15 @@ ${peer.address}`
3924
3987
  if (l)
3925
3988
  post("/api/ens-register", { name: l, displayName: me.name, description: me.description || "" });
3926
3989
  };
3927
- const bind = (chain, address) => post("/api/ens-bind-wallet", { chain, address });
3990
+ const bind = (chain, address, proof) => post("/api/ens-bind-wallet", proof ? { chain, address, proof } : { chain, address });
3991
+ const signing = () => {
3992
+ setBusy(true);
3993
+ setMsg({ tone: "ok", text: T.ensSigning || "approve the signature in your wallet\u2026" });
3994
+ };
3995
+ const walletErr = (e) => {
3996
+ setBusy(false);
3997
+ setMsg({ tone: "err", text: String(e && e.message || e) });
3998
+ };
3928
3999
  const bindEth = async () => {
3929
4000
  const eth = window.ethereum;
3930
4001
  if (!eth) {
@@ -3933,10 +4004,13 @@ ${peer.address}`
3933
4004
  }
3934
4005
  try {
3935
4006
  const a = await eth.request({ method: "eth_requestAccounts" });
3936
- if (a && a[0])
3937
- bind("eth", a[0]);
4007
+ if (!a || !a[0])
4008
+ return;
4009
+ signing();
4010
+ const proof = await signEthProof(eth, { userid: me.userId, address: a[0] });
4011
+ bind("eth", a[0], proof);
3938
4012
  } catch (e) {
3939
- setMsg({ tone: "err", text: String(e && e.message || e) });
4013
+ walletErr(e);
3940
4014
  }
3941
4015
  };
3942
4016
  const bindSol = async () => {
@@ -3948,10 +4022,13 @@ ${peer.address}`
3948
4022
  try {
3949
4023
  const r = await sol.connect();
3950
4024
  const a = r && r.publicKey && r.publicKey.toString();
3951
- if (a)
3952
- bind("sol", a);
4025
+ if (!a)
4026
+ return;
4027
+ signing();
4028
+ const proof = await signSolProof(sol, { userid: me.userId, address: a });
4029
+ bind("sol", a, proof);
3953
4030
  } catch (e) {
3954
- setMsg({ tone: "err", text: String(e && e.message || e) });
4031
+ walletErr(e);
3955
4032
  }
3956
4033
  };
3957
4034
  const field = { flex: 1, minWidth: 0, height: 34, borderRadius: 8, border: "1px solid var(--line)", background: "var(--panel-2)", color: "var(--text)", fontFamily: "var(--mono)", fontSize: 13, padding: "0 10px", outline: "none" };
@@ -4000,6 +4077,7 @@ ${peer.address}`
4000
4077
  const [picking, setPicking] = React.useState(false);
4001
4078
  const fileRef = React.useRef(null);
4002
4079
  const canWrite = hostEnsWrite && !!st.mineOwned;
4080
+ const canBind = hostWalletBind && !!st.mineOwned && !st.writeBlocked;
4003
4081
  const rec = st.record;
4004
4082
  const boundEth = rec && rec.addresses && rec.addresses["60"];
4005
4083
  const boundSol = rec && rec.addresses && rec.addresses["501"];
@@ -4022,7 +4100,16 @@ ${peer.address}`
4022
4100
  setMsg({ tone: "err", text: m === "too-big" ? T.ensTooBig || "image too large even after scaling" : m === "bad-image" ? T.ensBadImage || "cannot read that image" : String(m || err) });
4023
4101
  });
4024
4102
  };
4025
- const walletRow = (lbl, chain, bound, onBind, last) => /* @__PURE__ */ React.createElement("div", { className: "dk-row", style: { ...row, borderBottom: last ? "none" : row.borderBottom } }, /* @__PURE__ */ React.createElement("span", { className: "dk-lbl", style: { width: 130, flexShrink: 0, fontFamily: "var(--mono)", fontSize: 11.5, fontWeight: 600, color: "var(--faint)", textTransform: "uppercase", letterSpacing: 0.5 } }, lbl), bound ? /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("span", { style: { flex: 1, minWidth: 0, fontFamily: "var(--mono)", fontSize: 12.5, color: "var(--text)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" } }, bound), /* @__PURE__ */ React.createElement(CopyBtn, { value: bound, copiedText: T.copied, copyFailedText: T.copyFailed, copyTitle: T.copy }), /* @__PURE__ */ React.createElement(Btn, { size: "sm", tone: "danger", disabled: busy || !canWrite, onClick: () => bind(chain, "") }, T.ensUnbind || "unbind")) : /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("span", { style: { flex: 1, fontFamily: "var(--ui)", fontSize: 12, color: "var(--faint)" } }, T.ensNotBound || "not bound"), /* @__PURE__ */ React.createElement(Btn, { size: "sm", disabled: busy || !canWrite, onClick: onBind }, T.ensBind || "bind")));
4103
+ const proven = (chain, bound) => {
4104
+ if (rec && rec.owner && String(rec.owner).toLowerCase() === String(bound).toLowerCase())
4105
+ return true;
4106
+ try {
4107
+ return JSON.parse((rec && rec.texts || {})[PROOF_KEY[chain]]).msg.indexOf("address: " + bound + "\n") >= 0;
4108
+ } catch {
4109
+ return false;
4110
+ }
4111
+ };
4112
+ const walletRow = (lbl, chain, bound, onBind, last) => /* @__PURE__ */ React.createElement("div", { className: "dk-row", style: { ...row, borderBottom: last ? "none" : row.borderBottom } }, /* @__PURE__ */ React.createElement("span", { className: "dk-lbl", style: { width: 130, flexShrink: 0, fontFamily: "var(--mono)", fontSize: 11.5, fontWeight: 600, color: "var(--faint)", textTransform: "uppercase", letterSpacing: 0.5 } }, lbl), bound ? /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("span", { style: { flex: 1, minWidth: 0, display: "flex", flexDirection: "column", gap: 2 } }, /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 12.5, color: "var(--text)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" } }, bound), /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--ui)", fontSize: 11, color: proven(chain, bound) ? "var(--online)" : "var(--faint)" } }, proven(chain, bound) ? T.ensProven || "signed by this wallet" : T.ensUnproven || "no wallet signature yet")), /* @__PURE__ */ React.createElement(CopyBtn, { value: bound, copiedText: T.copied, copyFailedText: T.copyFailed, copyTitle: T.copy }), !proven(chain, bound) && /* @__PURE__ */ React.createElement(Btn, { size: "sm", disabled: busy || !canBind, onClick: onBind }, T.ensProve || "sign"), /* @__PURE__ */ React.createElement(Btn, { size: "sm", tone: "danger", disabled: busy || !canBind, onClick: () => bind(chain, "") }, T.ensUnbind || "unbind")) : /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("span", { style: { flex: 1, fontFamily: "var(--ui)", fontSize: 12, color: "var(--faint)" } }, T.ensNotBound || "not bound"), /* @__PURE__ */ React.createElement(Btn, { size: "sm", disabled: busy || !canBind, onClick: onBind }, T.ensBind || "bind")));
4026
4113
  return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Card, { label: T.ensCard || "Name \xB7 beagles.eth" }, st.loading ? /* @__PURE__ */ React.createElement("div", { style: { padding: "14px 16px", fontFamily: "var(--ui)", fontSize: 12.5, color: "var(--faint)" } }, T.dirLoading || "loading\u2026") : /* @__PURE__ */ React.createElement(React.Fragment, null, !hostEnsWrite ? /* @__PURE__ */ React.createElement("div", { className: "dk-row dk-wrap", style: row }, /* @__PURE__ */ React.createElement("span", { className: "dk-lbl", style: { width: 130, flexShrink: 0, fontFamily: "var(--mono)", fontSize: 11.5, fontWeight: 600, color: "var(--faint)", textTransform: "uppercase", letterSpacing: 0.5 } }, T.ensName || "name"), st.registered && rec && rec.name ? /* @__PURE__ */ React.createElement("span", { style: { flex: 1, minWidth: 0, fontFamily: "var(--mono)", fontSize: 13, color: "var(--text)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" } }, rec.name) : /* @__PURE__ */ React.createElement("span", { style: { flex: 1, minWidth: 0, fontFamily: "var(--ui)", fontSize: 12, lineHeight: 1.5, color: "var(--faint)" } }, T.ensWebOnly || "Registering a beagles.eth name needs the Beagle phone or desktop app. Your avatar can be set above without one.")) : /* @__PURE__ */ React.createElement("div", { className: "dk-row dk-wrap", style: row }, /* @__PURE__ */ React.createElement("span", { className: "dk-lbl", style: { width: 130, flexShrink: 0, fontFamily: "var(--mono)", fontSize: 11.5, fontWeight: 600, color: "var(--faint)", textTransform: "uppercase", letterSpacing: 0.5 } }, T.ensName || "name"), /* @__PURE__ */ React.createElement(
4027
4114
  "input",
4028
4115
  {
@@ -4036,7 +4123,7 @@ ${peer.address}`
4036
4123
  },
4037
4124
  style: { ...field, minWidth: 110 }
4038
4125
  }
4039
- ), /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 12.5, color: "var(--dim)", flexShrink: 0 } }, ".beagles.eth"), /* @__PURE__ */ React.createElement(Btn, { tone: "accent", size: "sm", disabled: busy || !label.trim(), onClick: register }, busy ? "\u2026" : st.registered && st.mineOwned ? T.ensUpdate || "update" : T.ensRegister || "register")), st.registered && !st.mineOwned && /* @__PURE__ */ React.createElement("div", { style: { padding: "10px 16px", borderBottom: "1px solid var(--line)", fontFamily: "var(--ui)", fontSize: 11.5, color: "var(--faint)" } }, (T.ensWalletOwned || "registered via your mobile wallet as") + " ", /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)" } }, rec && rec.name)), st.registered && /* @__PURE__ */ React.createElement("div", { className: "dk-row dk-wrap", style: row }, /* @__PURE__ */ React.createElement("span", { className: "dk-lbl", style: { width: 130, flexShrink: 0, fontFamily: "var(--mono)", fontSize: 11.5, fontWeight: 600, color: "var(--faint)", textTransform: "uppercase", letterSpacing: 0.5 } }, T.ensAvatar || "avatar"), upAvatar ? /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("img", { src: upAvatar, alt: "", width: 34, height: 34, style: { width: 34, height: 34, borderRadius: 9, objectFit: "cover", flexShrink: 0, background: "var(--panel-2)" } }), /* @__PURE__ */ React.createElement("span", { style: { flex: 1, minWidth: 0, fontFamily: "var(--ui)", fontSize: 12.5, color: "var(--text)" } }, T.ensCustom || "custom image"), /* @__PURE__ */ React.createElement(Btn, { size: "sm", tone: "danger", disabled: busy || !canWrite, onClick: () => post("/api/ens-avatar", { nftid: null }) }, T.ensClear || "clear")) : punkId != null ? /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(DkPunkAvatar, { id: punkId, size: 34, radius: 9, fallbackSeed: me.userId }), /* @__PURE__ */ React.createElement("span", { style: { flex: 1, minWidth: 0, fontFamily: "var(--mono)", fontSize: 12.5, color: "var(--text)" } }, `CryptoPunk #${punkId}`), /* @__PURE__ */ React.createElement(Btn, { size: "sm", tone: "danger", disabled: busy || !canWrite, onClick: () => post("/api/ens-avatar", { nftid: null }) }, T.ensClear || "clear")) : /* @__PURE__ */ React.createElement("span", { style: { flex: 1, fontFamily: "var(--ui)", fontSize: 12, color: "var(--faint)" } }, T.ensNotSet || "not set"), /* @__PURE__ */ React.createElement(Btn, { size: "sm", disabled: busy || !canWrite, onClick: () => fileRef.current && fileRef.current.click() }, T.ensUpload || "upload"), /* @__PURE__ */ React.createElement(Btn, { size: "sm", disabled: busy || !canWrite, onClick: () => setPicking(true) }, T.ensChoose || "choose"), /* @__PURE__ */ React.createElement("input", { ref: fileRef, type: "file", accept: "image/*", onChange: onUploadFile, style: { display: "none" } })), walletRow(T.ensEth || "ethereum", "eth", boundEth, bindEth, false), walletRow(T.ensSol || "solana", "sol", boundSol, bindSol, !msg), msg && /* @__PURE__ */ React.createElement("div", { style: { padding: "10px 16px", fontFamily: "var(--ui)", fontSize: 12, color: msg.tone === "ok" ? "var(--online)" : "var(--danger)" } }, msg.text)), picking && /* @__PURE__ */ React.createElement(DkPunkPicker, { T, onPick: pickPunk, onClose: () => setPicking(false) })), !st.loading && st.registered && /* @__PURE__ */ React.createElement(Card, { label: T.linksCard || "Links" }, SOCIALS.map((s, i) => socialRow(s, i === SOCIALS.length - 1))));
4126
+ ), /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 12.5, color: "var(--dim)", flexShrink: 0 } }, ".beagles.eth"), /* @__PURE__ */ React.createElement(Btn, { tone: "accent", size: "sm", disabled: busy || !label.trim(), onClick: register }, busy ? "\u2026" : st.registered && st.mineOwned ? T.ensUpdate || "update" : T.ensRegister || "register")), st.registered && !st.mineOwned && /* @__PURE__ */ React.createElement("div", { style: { padding: "10px 16px", borderBottom: "1px solid var(--line)", fontFamily: "var(--ui)", fontSize: 11.5, color: "var(--faint)" } }, (T.ensWalletOwned || "registered via your mobile wallet as") + " ", /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)" } }, rec && rec.name)), st.registered && /* @__PURE__ */ React.createElement("div", { className: "dk-row dk-wrap", style: row }, /* @__PURE__ */ React.createElement("span", { className: "dk-lbl", style: { width: 130, flexShrink: 0, fontFamily: "var(--mono)", fontSize: 11.5, fontWeight: 600, color: "var(--faint)", textTransform: "uppercase", letterSpacing: 0.5 } }, T.ensAvatar || "avatar"), upAvatar ? /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("img", { src: upAvatar, alt: "", width: 34, height: 34, style: { width: 34, height: 34, borderRadius: 9, objectFit: "cover", flexShrink: 0, background: "var(--panel-2)" } }), /* @__PURE__ */ React.createElement("span", { style: { flex: 1, minWidth: 0, fontFamily: "var(--ui)", fontSize: 12.5, color: "var(--text)" } }, T.ensCustom || "custom image"), /* @__PURE__ */ React.createElement(Btn, { size: "sm", tone: "danger", disabled: busy || !canWrite, onClick: () => post("/api/ens-avatar", { nftid: null }) }, T.ensClear || "clear")) : punkId != null ? /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(DkPunkAvatar, { id: punkId, size: 34, radius: 9, fallbackSeed: me.userId }), /* @__PURE__ */ React.createElement("span", { style: { flex: 1, minWidth: 0, fontFamily: "var(--mono)", fontSize: 12.5, color: "var(--text)" } }, `CryptoPunk #${punkId}`), /* @__PURE__ */ React.createElement(Btn, { size: "sm", tone: "danger", disabled: busy || !canWrite, onClick: () => post("/api/ens-avatar", { nftid: null }) }, T.ensClear || "clear")) : /* @__PURE__ */ React.createElement("span", { style: { flex: 1, fontFamily: "var(--ui)", fontSize: 12, color: "var(--faint)" } }, T.ensNotSet || "not set"), /* @__PURE__ */ React.createElement(Btn, { size: "sm", disabled: busy || !canWrite, onClick: () => fileRef.current && fileRef.current.click() }, T.ensUpload || "upload"), /* @__PURE__ */ React.createElement(Btn, { size: "sm", disabled: busy || !canWrite, onClick: () => setPicking(true) }, T.ensChoose || "choose"), /* @__PURE__ */ React.createElement("input", { ref: fileRef, type: "file", accept: "image/*", onChange: onUploadFile, style: { display: "none" } })), walletRow(T.ensEth || "ethereum", "eth", boundEth, bindEth, false), walletRow(T.ensSol || "solana", "sol", boundSol, bindSol, !msg && !st.writeBlocked), st.writeBlocked && hostWalletBind && /* @__PURE__ */ React.createElement("div", { style: { padding: "10px 16px", borderBottom: msg ? "1px solid var(--line)" : "none", fontFamily: "var(--ui)", fontSize: 12, lineHeight: 1.5, color: "var(--faint)" } }, st.writeBlocked), msg && /* @__PURE__ */ React.createElement("div", { style: { padding: "10px 16px", fontFamily: "var(--ui)", fontSize: 12, color: msg.tone === "ok" ? "var(--online)" : "var(--danger)" } }, msg.text)), picking && /* @__PURE__ */ React.createElement(DkPunkPicker, { T, onPick: pickPunk, onClose: () => setPicking(false) })), !st.loading && st.registered && /* @__PURE__ */ React.createElement(Card, { label: T.linksCard || "Links" }, SOCIALS.map((s, i) => socialRow(s, i === SOCIALS.length - 1))));
4040
4127
  }
4041
4128
  function DkUpdateCheck({ T }) {
4042
4129
  const [st, setSt] = React.useState("idle");
@@ -6414,6 +6501,10 @@ ${peer.address}`
6414
6501
  ensUnbind: "unbind",
6415
6502
  ensNotBound: "not bound",
6416
6503
  ensWebOnly: "Registering a beagles.eth name needs the Beagle phone or desktop app. Your avatar can be set above without one.",
6504
+ ensProven: "signed by this wallet",
6505
+ ensUnproven: "no wallet signature yet",
6506
+ ensProve: "sign",
6507
+ ensSigning: "approve the signature in your wallet\u2026",
6417
6508
  ensAvatar: "avatar",
6418
6509
  ensChoose: "choose",
6419
6510
  ensClear: "clear",
@@ -6609,6 +6700,10 @@ ${peer.address}`
6609
6700
  ensUnbind: "\u89E3\u7ED1",
6610
6701
  ensNotBound: "\u672A\u7ED1\u5B9A",
6611
6702
  ensWebOnly: "\u6CE8\u518C beagles.eth \u540D\u5B57\u9700\u8981 Beagle \u624B\u673A\u6216\u684C\u9762\u5E94\u7528\u3002\u5934\u50CF\u4E0D\u9700\u8981\u540D\u5B57\uFF0C\u53EF\u5728\u4E0A\u65B9\u76F4\u63A5\u8BBE\u7F6E\u3002",
6703
+ ensProven: "\u5DF2\u7531\u8BE5\u94B1\u5305\u7B7E\u540D",
6704
+ ensUnproven: "\u5C1A\u65E0\u94B1\u5305\u7B7E\u540D",
6705
+ ensProve: "\u7B7E\u540D",
6706
+ ensSigning: "\u8BF7\u5728\u94B1\u5305\u4E2D\u786E\u8BA4\u7B7E\u540D\u2026",
6612
6707
  ensAvatar: "\u5934\u50CF",
6613
6708
  ensChoose: "\u9009\u62E9",
6614
6709
  ensClear: "\u6E05\u9664",
@@ -6943,10 +7038,10 @@ ${peer.address}`
6943
7038
  window.location.reload();
6944
7039
  }, [me && me.beagleVer]);
6945
7040
  const [assistantTs, setAssistantTs] = React.useState(loadAssistantTs);
6946
- const peers = React.useMemo(() => (data.peers || []).concat([Object.assign({}, ASSISTANT_PEER, {
7041
+ const peers = React.useMemo(() => (data.peers || []).concat(hostAssistant ? [Object.assign({}, ASSISTANT_PEER, {
6947
7042
  lastTs: assistantTs,
6948
7043
  lastTime: assistantTs ? dkClock(assistantTs) : ""
6949
- })]), [data.peers, assistantTs]);
7044
+ })] : []), [data.peers, assistantTs]);
6950
7045
  const [pendingAddr, setPendingAddr] = React.useState(() => dkParseDeepLinkAddress());
6951
7046
  React.useEffect(() => {
6952
7047
  const onHash = () => {
package/dist/server.js CHANGED
@@ -26,6 +26,7 @@ import { bridgeRoster } from "./bridge-roster.js";
26
26
  import { isSupervised } from "./relaunch.js";
27
27
  import { historyStats } from "./assistant-stats.js";
28
28
  import { createDiscoverFetch } from "./discover-cache.js";
29
+ import { applyWalletBind } from "@decentnetwork/beagle-ui/wallet-proof";
29
30
  // Directory holding the built desktop UI bundle (index.html, app.js, vendor/).
30
31
  // scripts/build-ui.mjs emits it next to this compiled module at dist/ui/desktop/.
31
32
  const DESKTOP_DIR = join(dirname(fileURLToPath(import.meta.url)), "desktop");
@@ -1354,13 +1355,11 @@ export function startBeagleServer(opts) {
1354
1355
  }
1355
1356
  if (req.method === "POST" && url === "/api/ens-bind-wallet") {
1356
1357
  const body = await readBody(req);
1357
- const coinType = body.chain === "eth" ? "60" : body.chain === "sol" ? "501" : null;
1358
- const addr = typeof body.address === "string" ? body.address.trim() : "";
1359
- const remove = addr === "";
1360
- const okAddr = remove
1361
- || (coinType === "60" ? /^0x[0-9a-fA-F]{40}$/.test(addr) : /^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(addr));
1362
- if (!coinType || !okAddr) {
1363
- sendJson(res, 400, { ok: false, error: "invalid chain or address" });
1358
+ // Format check up front, before any identity or gateway round trip;
1359
+ // applyWalletBind repeats it with the record in hand.
1360
+ const pre = applyWalletBind(null, { chain: String(body.chain ?? ""), address: body.address, userid: "" });
1361
+ if (!pre.ok) {
1362
+ sendJson(res, pre.status, { ok: false, error: pre.error });
1364
1363
  return;
1365
1364
  }
1366
1365
  const diag = await opts.call({ op: "diag" });
@@ -1378,16 +1377,22 @@ export function startBeagleServer(opts) {
1378
1377
  sendJson(res, 403, { ok: false, error: "this name was registered by your mobile wallet — bind addresses there" });
1379
1378
  return;
1380
1379
  }
1381
- const addresses = { ...(mine.addresses ?? {}) };
1382
- if (remove)
1383
- delete addresses[coinType];
1384
- else
1385
- addresses[coinType] = addr;
1380
+ // The wallet's signed proof (wallet-proof.js) is checked here and
1381
+ // stored as texts["proof.eth"|"proof.sol"] beside the address.
1382
+ const bound = applyWalletBind(mine, {
1383
+ chain: String(body.chain), address: body.address, userid: identity.userid,
1384
+ proof: (body.proof ?? null),
1385
+ });
1386
+ if (!bound.ok) {
1387
+ sendJson(res, bound.status, { ok: false, error: bound.error });
1388
+ return;
1389
+ }
1390
+ const { addresses, texts } = bound;
1386
1391
  const r = await ensSignAndSet(opts.call, {
1387
1392
  name: mine.name,
1388
1393
  owner: identity.userid,
1389
1394
  addresses,
1390
- texts: mine.texts ?? {},
1395
+ texts,
1391
1396
  referee: mine.referee ?? "",
1392
1397
  nft: mine.nft ?? "",
1393
1398
  nftid: mine.nftid ?? 0,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decentnetwork/beagle",
3
- "version": "0.1.118",
3
+ "version": "0.1.119",
4
4
  "description": "Beagle — P2P chat, file transfer and calls for regular users, on the Decent Network. No admin privilege required.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -26,6 +26,7 @@
26
26
  "start": "node dist/cli.js"
27
27
  },
28
28
  "dependencies": {
29
+ "@decentnetwork/beagle-ui": "^0.2.33",
29
30
  "@decentnetwork/chat-components": "^0.1.3",
30
31
  "@decentnetwork/lan": "^0.1.283",
31
32
  "@decentnetwork/peer": "^0.1.168",
@@ -35,7 +36,6 @@
35
36
  "yargs": "^17.7.2"
36
37
  },
37
38
  "devDependencies": {
38
- "@decentnetwork/beagle-ui": "^0.2.32",
39
39
  "@types/js-yaml": "^4.0.9",
40
40
  "@types/node": "^20.11.0",
41
41
  "@types/yargs": "^17.0.32",