decane-connect-kit 1.0.1 → 1.0.3

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/dist/index.js CHANGED
@@ -1670,6 +1670,7 @@ function startSolanaDiscovery(registry) {
1670
1670
  var TRONLINK_RDNS = "link.tron.tronlink";
1671
1671
  var TRONLINK_ICON = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAzMiAzMiI+PGNpcmNsZSBjeD0iMTYiIGN5PSIxNiIgcj0iMTYiIGZpbGw9IiNGRjAwMTMiLz48cG9seWdvbiBwb2ludHM9IjEyLDkgMjMsMTYgMTIsMjMiIGZpbGw9IndoaXRlIi8+PC9zdmc+";
1672
1672
  function startTronDiscovery(registry) {
1673
+ if (typeof window === "undefined") return;
1673
1674
  let registered = false;
1674
1675
  function tryRegister() {
1675
1676
  if (registered || !window.tronLink) return;
@@ -1694,6 +1695,7 @@ function startTronDiscovery(registry) {
1694
1695
  // src/bitcoin/discovery.ts
1695
1696
  var BTC_ICON = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAzMiAzMiI+PGNpcmNsZSBjeD0iMTYiIGN5PSIxNiIgcj0iMTYiIGZpbGw9IiNGNzkzMUEiLz48dGV4dCB4PSIxNi41IiB5PSIyMiIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZm9udC1zaXplPSIxNiIgZm9udC13ZWlnaHQ9IjcwMCIgZmlsbD0id2hpdGUiIGZvbnQtZmFtaWx5PSJBcmlhbCxzeXN0ZW0tdWkiPuKmojwvdGV4dD48L3N2Zz4=";
1696
1697
  function startBitcoinDiscovery(registry) {
1698
+ if (typeof window === "undefined") return;
1697
1699
  setTimeout(() => {
1698
1700
  const candidates = [
1699
1701
  { name: "Unisat", rdns: "io.unisat.wallet", provider: window.unisat },
@@ -1731,6 +1733,1171 @@ function createDecane(config = {}) {
1731
1733
  return createProvider({ config, registry, session, chainStore, router, emitter });
1732
1734
  }
1733
1735
 
1736
+ // src/social/client.ts
1737
+ import { Wallet } from "ethers";
1738
+ import { Transaction as Transaction2, VersionedTransaction, Keypair } from "@solana/web3.js";
1739
+
1740
+ // src/social/errors.ts
1741
+ var DecaneConnectError = class extends Error {
1742
+ constructor(code, message, retryable = false) {
1743
+ super(message);
1744
+ this.name = "DecaneConnectError";
1745
+ this.code = code;
1746
+ this.retryable = retryable;
1747
+ }
1748
+ };
1749
+ var InvalidApiKeyError = class extends DecaneConnectError {
1750
+ constructor() {
1751
+ super("INVALID_API_KEY", "API key is invalid or revoked");
1752
+ }
1753
+ };
1754
+ var OriginNotAllowedError = class extends DecaneConnectError {
1755
+ constructor(origin) {
1756
+ super("ORIGIN_NOT_ALLOWED", `Origin ${origin} not in API key allowlist`);
1757
+ }
1758
+ };
1759
+ var SessionExpiredError = class extends DecaneConnectError {
1760
+ constructor() {
1761
+ super("SESSION_EXPIRED", "Session has expired \u2014 call unlock()", true);
1762
+ }
1763
+ };
1764
+ var UnlockRequiredError = class extends DecaneConnectError {
1765
+ constructor() {
1766
+ super("UNLOCK_REQUIRED", "Wallet must be unlocked before signing", true);
1767
+ }
1768
+ };
1769
+ var UserCancelledError = class extends DecaneConnectError {
1770
+ constructor() {
1771
+ super("USER_CANCELLED", "User cancelled the operation", true);
1772
+ }
1773
+ };
1774
+ var WebAuthnNotSupportedError = class extends DecaneConnectError {
1775
+ constructor() {
1776
+ super("WEBAUTHN_UNSUPPORTED", "This browser does not support WebAuthn");
1777
+ }
1778
+ };
1779
+ var InvalidPinError = class extends DecaneConnectError {
1780
+ constructor() {
1781
+ super("INVALID_PIN", "Incorrect PIN", true);
1782
+ }
1783
+ };
1784
+ var CallSequenceError = class extends DecaneConnectError {
1785
+ constructor(msg) {
1786
+ super("CALL_SEQUENCE", msg);
1787
+ }
1788
+ };
1789
+ var NetworkError = class extends DecaneConnectError {
1790
+ constructor() {
1791
+ super("NETWORK", "Network request failed", true);
1792
+ }
1793
+ };
1794
+ var ShareCorruptedError = class extends DecaneConnectError {
1795
+ constructor() {
1796
+ super("SHARE_CORRUPTED", "Stored device share is corrupted");
1797
+ }
1798
+ };
1799
+ var NewDeviceError = class extends DecaneConnectError {
1800
+ constructor() {
1801
+ super("NEW_DEVICE", "No device share found. Re-authenticate to set up this device.");
1802
+ }
1803
+ };
1804
+ var PromptPinNotConfiguredError = class extends DecaneConnectError {
1805
+ constructor() {
1806
+ super("PROMPT_PIN_NOT_CONFIGURED", "Passkey unavailable and no promptPin callback was provided in config");
1807
+ }
1808
+ };
1809
+ var AttestationError = class extends DecaneConnectError {
1810
+ constructor(msg) {
1811
+ super("ATTESTATION_FAILED", msg);
1812
+ }
1813
+ };
1814
+ var RecoveryFailedError = class extends DecaneConnectError {
1815
+ constructor(reason) {
1816
+ super("RECOVERY_FAILED", `Recovery failed: ${reason}`, true);
1817
+ }
1818
+ };
1819
+ var InvalidPhraseError = class extends DecaneConnectError {
1820
+ constructor() {
1821
+ super("INVALID_PHRASE", "Recovery phrase is invalid or corrupted", true);
1822
+ }
1823
+ };
1824
+
1825
+ // src/social/events.ts
1826
+ var EventEmitter = class {
1827
+ constructor() {
1828
+ this._listeners = /* @__PURE__ */ new Map();
1829
+ }
1830
+ emit(event, ...args) {
1831
+ this._listeners.get(event)?.forEach((h) => h(...args));
1832
+ }
1833
+ on(event, handler) {
1834
+ if (!this._listeners.has(event)) this._listeners.set(event, /* @__PURE__ */ new Set());
1835
+ this._listeners.get(event).add(handler);
1836
+ }
1837
+ off(event, handler) {
1838
+ this._listeners.get(event)?.delete(handler);
1839
+ }
1840
+ };
1841
+
1842
+ // src/social/api.ts
1843
+ var ERROR_MAP = {
1844
+ INVALID_API_KEY: InvalidApiKeyError,
1845
+ ORIGIN_NOT_ALLOWED: OriginNotAllowedError
1846
+ };
1847
+ var ApiClient = class {
1848
+ constructor(baseUrl, apiKey) {
1849
+ this.baseUrl = baseUrl;
1850
+ this.apiKey = apiKey;
1851
+ }
1852
+ async _request(path, init) {
1853
+ let res;
1854
+ try {
1855
+ res = await fetch(`${this.baseUrl}${path}`, {
1856
+ ...init,
1857
+ headers: {
1858
+ "Content-Type": "application/json",
1859
+ "X-API-Key": this.apiKey,
1860
+ ...init?.headers
1861
+ }
1862
+ });
1863
+ } catch {
1864
+ throw new NetworkError();
1865
+ }
1866
+ if (res.status === 404 && init?.allow404) return null;
1867
+ if (!res.ok) {
1868
+ const body = await res.json().catch(() => ({}));
1869
+ const code = body?.error?.code ?? "UNKNOWN";
1870
+ const msg = body?.error?.message ?? `Request failed: ${res.status}`;
1871
+ const Ctor = ERROR_MAP[code];
1872
+ if (Ctor) throw new Ctor(msg);
1873
+ throw new Error(msg);
1874
+ }
1875
+ return res.json();
1876
+ }
1877
+ async getGoogleInitUrl() {
1878
+ return this._request("/auth/google/init", { method: "GET" });
1879
+ }
1880
+ async authEmailStart(email) {
1881
+ await this._request("/auth/email/start", {
1882
+ method: "POST",
1883
+ body: JSON.stringify({ email })
1884
+ });
1885
+ }
1886
+ async authEmailVerify(email, code) {
1887
+ return this._request("/auth/email/verify", {
1888
+ method: "POST",
1889
+ body: JSON.stringify({ email, code })
1890
+ });
1891
+ }
1892
+ // GET /share (the server share itself) was removed as part of the TEE
1893
+ // migration — the browser must never reconstruct the private key anymore.
1894
+ // Only public addresses are fetched from the backend now.
1895
+ async getAddresses(jwt) {
1896
+ return this._request("/share/addresses", {
1897
+ headers: { Authorization: `Bearer ${jwt}` }
1898
+ });
1899
+ }
1900
+ async putShare(jwt, share) {
1901
+ await this._request("/share", {
1902
+ method: "POST",
1903
+ headers: { Authorization: `Bearer ${jwt}` },
1904
+ body: JSON.stringify({
1905
+ ciphertext: bytesToB64(share.ciphertext),
1906
+ iv: bytesToB64(share.iv),
1907
+ wrappedShareMeta: btoa(JSON.stringify(share.wrappedShareMeta)),
1908
+ evmAddress: share.evmAddress,
1909
+ solanaAddress: share.solanaAddress
1910
+ })
1911
+ });
1912
+ }
1913
+ async revoke(jwt) {
1914
+ await this._request("/auth/revoke", {
1915
+ method: "POST",
1916
+ headers: { Authorization: `Bearer ${jwt}` }
1917
+ });
1918
+ }
1919
+ // ── Recovery (passkey-sync backup + phrase-saved flag) ────────────────────
1920
+ async hasRecoveryBackup(jwt) {
1921
+ const data = await this._request("/recovery/backup/exists", {
1922
+ headers: { Authorization: `Bearer ${jwt}` }
1923
+ });
1924
+ return data.exists;
1925
+ }
1926
+ async getRecoveryBackup(jwt) {
1927
+ const data = await this._request("/recovery/backup", {
1928
+ headers: { Authorization: `Bearer ${jwt}` },
1929
+ allow404: true
1930
+ });
1931
+ if (!data) return null;
1932
+ return {
1933
+ wrappedDeviceShare: b64ToBytes(data.wrappedDeviceShare),
1934
+ iv: b64ToBytes(data.iv),
1935
+ unlockMethod: data.unlockMethod,
1936
+ credentialId: b64ToBytes(data.credentialId),
1937
+ prfSalt: b64ToBytes(data.prfSalt),
1938
+ formatVersion: data.formatVersion
1939
+ };
1940
+ }
1941
+ async putRecoveryBackup(jwt, backup) {
1942
+ await this._request("/recovery/backup", {
1943
+ method: "PUT",
1944
+ headers: { Authorization: `Bearer ${jwt}` },
1945
+ body: JSON.stringify({
1946
+ wrappedDeviceShare: bytesToB64(backup.wrappedDeviceShare),
1947
+ iv: bytesToB64(backup.iv),
1948
+ unlockMethod: backup.unlockMethod,
1949
+ credentialId: bytesToB64(backup.credentialId),
1950
+ prfSalt: bytesToB64(backup.prfSalt)
1951
+ })
1952
+ });
1953
+ }
1954
+ async deleteRecoveryBackup(jwt) {
1955
+ await this._request("/recovery/backup", {
1956
+ method: "DELETE",
1957
+ headers: { Authorization: `Bearer ${jwt}` }
1958
+ });
1959
+ }
1960
+ async markPhraseSaved(jwt) {
1961
+ await this._request("/recovery/phrase-saved", {
1962
+ method: "POST",
1963
+ headers: { Authorization: `Bearer ${jwt}` }
1964
+ });
1965
+ }
1966
+ };
1967
+ function bytesToB64(bytes) {
1968
+ return btoa(String.fromCharCode(...bytes));
1969
+ }
1970
+ function b64ToBytes(b642) {
1971
+ return Uint8Array.from(atob(b642), (c) => c.charCodeAt(0));
1972
+ }
1973
+
1974
+ // src/social/storage.ts
1975
+ import { openDB } from "idb";
1976
+ var DB_NAME = "decane-connect-kit";
1977
+ var STORE = "shares";
1978
+ var Storage = class {
1979
+ constructor() {
1980
+ this._db = openDB(DB_NAME, 1, {
1981
+ upgrade(db) {
1982
+ db.createObjectStore(STORE, { keyPath: "userId" });
1983
+ }
1984
+ });
1985
+ }
1986
+ async getShare(userId) {
1987
+ return (await this._db).get(STORE, userId) ?? null;
1988
+ }
1989
+ async putShare(share) {
1990
+ await (await this._db).put(STORE, share);
1991
+ }
1992
+ async deleteShare(userId) {
1993
+ await (await this._db).delete(STORE, userId);
1994
+ }
1995
+ };
1996
+
1997
+ // src/social/attestation.ts
1998
+ import { getCollateralAndVerify } from "@phala/dcap-qvl";
1999
+
2000
+ // ../../infra/tee-measurement.json
2001
+ var tee_measurement_default = {
2002
+ signerVersion: "0.1.3",
2003
+ acceptedMrtd: [
2004
+ '["68102e7b524af310f7b7d426ce75481e36c40f5d513a9009c046e9d37e31551f0134d954b496a3357fd61d03f07ffe96","07e6f51aa763abfe75c3ddfbf4f425fe3f0ceff66d807a75e049303dce9addf68e7218729bd419638af63a370f65878c","a2a58c9a959a4fa44bd6da0c97a2270c051faf12084cfe91ae900e4fdff6cdd4f69a82005e04ee920f231497894d677f","a76a81147b7f733cb400914c8791ec149bb3a31f3727b1c1e12ec22e9c1a183b6e617e78636dca37075f5a695afd46c2"]'
2005
+ ],
2006
+ primaryMrtd: '["68102e7b524af310f7b7d426ce75481e36c40f5d513a9009c046e9d37e31551f0134d954b496a3357fd61d03f07ffe96","07e6f51aa763abfe75c3ddfbf4f425fe3f0ceff66d807a75e049303dce9addf68e7218729bd419638af63a370f65878c","a2a58c9a959a4fa44bd6da0c97a2270c051faf12084cfe91ae900e4fdff6cdd4f69a82005e04ee920f231497894d677f","a76a81147b7f733cb400914c8791ec149bb3a31f3727b1c1e12ec22e9c1a183b6e617e78636dca37075f5a695afd46c2"]',
2007
+ capturedAt: "2026-07-20T10:08:58.717Z",
2008
+ tcbStatus: "UpToDate",
2009
+ _note: "Captured from a live, signature-verified TDX quote (getCollateralAndVerify -> UpToDate) against decane-tee-signer v0.1.3 running on Phala Cloud CVM 786e714d-5042-4bf1-8060-0bc358f6a72f. measurement is the JSON.stringify(replayRtmrs()) output, compared as an opaque string (not parsed per-RTMR), matching the actual comparison logic in social/attestation.ts."
2010
+ };
2011
+
2012
+ // src/social/attestation.ts
2013
+ function hexToBytes(hex) {
2014
+ const clean = hex.startsWith("0x") ? hex.slice(2) : hex;
2015
+ const bytes = new Uint8Array(Math.floor(clean.length / 2));
2016
+ for (let i = 0; i < bytes.length; i++) {
2017
+ bytes[i] = parseInt(clean.substring(i * 2, i * 2 + 2), 16);
2018
+ }
2019
+ return bytes;
2020
+ }
2021
+ function bytesToHex(bytes) {
2022
+ return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
2023
+ }
2024
+ async function verifyTeeAttestation(teeUrl, nonce) {
2025
+ const res = await fetch(`${teeUrl}/attestation`, {
2026
+ method: "POST",
2027
+ headers: { "Content-Type": "application/json" },
2028
+ body: JSON.stringify({ nonce })
2029
+ });
2030
+ if (!res.ok) throw new AttestationError("quote fetch failed");
2031
+ const { quote, measurement } = await res.json();
2032
+ let verified;
2033
+ try {
2034
+ verified = await getCollateralAndVerify(hexToBytes(quote));
2035
+ } catch {
2036
+ throw new AttestationError("quote signature invalid");
2037
+ }
2038
+ if (verified.status === "Revoked") {
2039
+ throw new AttestationError("TEE platform TCB status is Revoked");
2040
+ }
2041
+ const td = verified.report.asTd10() ?? verified.report.asTd15()?.base;
2042
+ if (!td) throw new AttestationError("quote is not a TDX report");
2043
+ const reportDataHex = bytesToHex(td.reportData);
2044
+ if (!reportDataHex.toLowerCase().startsWith(nonce.toLowerCase())) {
2045
+ throw new AttestationError("quote nonce mismatch");
2046
+ }
2047
+ const accepted = tee_measurement_default.acceptedMrtd.map((m) => m.toLowerCase());
2048
+ if (!accepted.includes(String(measurement).toLowerCase())) {
2049
+ throw new AttestationError(
2050
+ `TEE measurement not accepted. Got ${measurement}, expected one of ${accepted.join(", ") || "(none configured yet)"}`
2051
+ );
2052
+ }
2053
+ }
2054
+
2055
+ // src/social/teeClient.ts
2056
+ function bytesToBase64(b) {
2057
+ let s = "";
2058
+ for (const byte of b) s += String.fromCharCode(byte);
2059
+ return btoa(s);
2060
+ }
2061
+ var TeeClient = class {
2062
+ constructor(teeUrl) {
2063
+ this.teeUrl = teeUrl;
2064
+ this.handle = null;
2065
+ this.attested = false;
2066
+ }
2067
+ async ensureAttested() {
2068
+ if (this.attested) return;
2069
+ const nonce = crypto.getRandomValues(new Uint8Array(16));
2070
+ const nonceHex = Array.from(nonce).map((b) => b.toString(16).padStart(2, "0")).join("");
2071
+ await verifyTeeAttestation(this.teeUrl, nonceHex);
2072
+ this.attested = true;
2073
+ }
2074
+ async openSession(deviceShare, jwt) {
2075
+ await this.ensureAttested();
2076
+ const res = await fetch(`${this.teeUrl}/signer/session`, {
2077
+ method: "POST",
2078
+ headers: { Authorization: `Bearer ${jwt}`, "Content-Type": "application/json" },
2079
+ body: JSON.stringify({ deviceShare: bytesToBase64(deviceShare) })
2080
+ });
2081
+ if (!res.ok) throw new Error("session open failed");
2082
+ this.handle = (await res.json()).handle;
2083
+ }
2084
+ /** Verify a candidate device share (from a recovery phrase) without exposing shares. */
2085
+ async verifyRecoveryCandidate(deviceShare, jwt) {
2086
+ await this.ensureAttested();
2087
+ const res = await fetch(`${this.teeUrl}/signer/verify-recovery`, {
2088
+ method: "POST",
2089
+ headers: { Authorization: `Bearer ${jwt}`, "Content-Type": "application/json" },
2090
+ body: JSON.stringify({ deviceShare: bytesToBase64(deviceShare) })
2091
+ });
2092
+ if (!res.ok) return false;
2093
+ return (await res.json()).verified === true;
2094
+ }
2095
+ hasSession() {
2096
+ return this.handle !== null;
2097
+ }
2098
+ async sign(req) {
2099
+ if (!this.handle) throw new Error("no session");
2100
+ const res = await fetch(`${this.teeUrl}/signer/sign`, {
2101
+ method: "POST",
2102
+ headers: { "Content-Type": "application/json" },
2103
+ body: JSON.stringify({ handle: this.handle, ...req })
2104
+ });
2105
+ if (res.status === 401) {
2106
+ this.handle = null;
2107
+ throw new SessionExpiredError();
2108
+ }
2109
+ if (!res.ok) throw new Error("sign failed");
2110
+ return (await res.json()).signature;
2111
+ }
2112
+ async closeSession() {
2113
+ if (!this.handle) return;
2114
+ const handle = this.handle;
2115
+ this.handle = null;
2116
+ void fetch(`${this.teeUrl}/signer/close`, {
2117
+ method: "POST",
2118
+ headers: { "Content-Type": "application/json" },
2119
+ body: JSON.stringify({ handle })
2120
+ }).catch(() => {
2121
+ });
2122
+ }
2123
+ };
2124
+
2125
+ // src/social/session.ts
2126
+ var SessionManager = class {
2127
+ constructor(events, durationMs, teeUrl) {
2128
+ this.events = events;
2129
+ this.durationMs = durationMs;
2130
+ this.teeUrl = teeUrl;
2131
+ this.tee = null;
2132
+ this.evmAddress = null;
2133
+ this.solanaAddress = null;
2134
+ this.expiresAt = null;
2135
+ this.timeoutId = null;
2136
+ if (typeof window !== "undefined") {
2137
+ window.addEventListener("pagehide", () => this.end());
2138
+ }
2139
+ }
2140
+ async start(deviceShare, jwt, addresses) {
2141
+ this.tee = new TeeClient(this.teeUrl);
2142
+ await this.tee.openSession(deviceShare, jwt);
2143
+ this.evmAddress = addresses.evm;
2144
+ this.solanaAddress = addresses.solana;
2145
+ this.expiresAt = Date.now() + this.durationMs;
2146
+ if (this.timeoutId) clearTimeout(this.timeoutId);
2147
+ this.timeoutId = setTimeout(() => {
2148
+ this.end();
2149
+ this.events.emit("session-expired");
2150
+ }, this.durationMs);
2151
+ this.events.emit("session-started");
2152
+ }
2153
+ end() {
2154
+ void this.tee?.closeSession();
2155
+ this.tee = null;
2156
+ this.evmAddress = null;
2157
+ this.solanaAddress = null;
2158
+ this.expiresAt = null;
2159
+ if (this.timeoutId) {
2160
+ clearTimeout(this.timeoutId);
2161
+ this.timeoutId = null;
2162
+ }
2163
+ }
2164
+ isActive() {
2165
+ return this.tee !== null && Date.now() < (this.expiresAt ?? 0);
2166
+ }
2167
+ expiresAtTimestamp() {
2168
+ return this.expiresAt;
2169
+ }
2170
+ requireActive() {
2171
+ if (!this.isActive()) throw new UnlockRequiredError();
2172
+ return this.tee;
2173
+ }
2174
+ addresses() {
2175
+ if (!this.evmAddress || !this.solanaAddress) return null;
2176
+ return { evm: this.evmAddress, solana: this.solanaAddress };
2177
+ }
2178
+ };
2179
+
2180
+ // src/social/crypto/shares.ts
2181
+ function xorBytes(a, b) {
2182
+ if (a.length !== b.length) throw new Error("xorBytes: length mismatch");
2183
+ const out = new Uint8Array(a.length);
2184
+ for (let i = 0; i < a.length; i++) out[i] = a[i] ^ b[i];
2185
+ return out;
2186
+ }
2187
+ function generateRandomBytes(n) {
2188
+ return crypto.getRandomValues(new Uint8Array(n));
2189
+ }
2190
+ function generateWalletShares() {
2191
+ const privateKey = generateRandomBytes(32);
2192
+ const deviceShare = generateRandomBytes(32);
2193
+ const serverShare = xorBytes(privateKey, deviceShare);
2194
+ return { privateKey, deviceShare, serverShare };
2195
+ }
2196
+ function zeroize(bytes) {
2197
+ bytes.fill(0);
2198
+ }
2199
+
2200
+ // src/social/crypto/wrap.ts
2201
+ function buf(u) {
2202
+ return u.buffer.slice(u.byteOffset, u.byteOffset + u.byteLength);
2203
+ }
2204
+ async function importAesKey(rawKey) {
2205
+ return crypto.subtle.importKey(
2206
+ "raw",
2207
+ buf(rawKey),
2208
+ { name: "AES-GCM", length: 256 },
2209
+ false,
2210
+ ["encrypt", "decrypt"]
2211
+ );
2212
+ }
2213
+ async function aesGcmEncrypt(key, plaintext, iv) {
2214
+ const ct = await crypto.subtle.encrypt({ name: "AES-GCM", iv: buf(iv) }, key, buf(plaintext));
2215
+ return new Uint8Array(ct);
2216
+ }
2217
+ async function aesGcmDecrypt(key, ciphertext, iv) {
2218
+ const pt = await crypto.subtle.decrypt({ name: "AES-GCM", iv: buf(iv) }, key, buf(ciphertext));
2219
+ return new Uint8Array(pt);
2220
+ }
2221
+
2222
+ // src/social/crypto/pin.ts
2223
+ var PBKDF2_ITERATIONS = 6e5;
2224
+ function buf2(u) {
2225
+ return u.buffer.slice(u.byteOffset, u.byteOffset + u.byteLength);
2226
+ }
2227
+ async function deriveKeyFromPin(pin, salt) {
2228
+ const pinBytes = new TextEncoder().encode(pin);
2229
+ const base = await crypto.subtle.importKey("raw", buf2(pinBytes), "PBKDF2", false, ["deriveKey"]);
2230
+ return crypto.subtle.deriveKey(
2231
+ { name: "PBKDF2", salt: buf2(salt), iterations: PBKDF2_ITERATIONS, hash: "SHA-256" },
2232
+ base,
2233
+ { name: "AES-GCM", length: 256 },
2234
+ false,
2235
+ ["encrypt", "decrypt"]
2236
+ );
2237
+ }
2238
+ function validatePin(pin) {
2239
+ if (!/^\d{4,8}$/.test(pin)) throw new Error("PIN must be 4\u20138 digits");
2240
+ }
2241
+
2242
+ // src/social/crypto/passkey.ts
2243
+ function buf3(u) {
2244
+ return u.buffer.slice(u.byteOffset, u.byteOffset + u.byteLength);
2245
+ }
2246
+ function isPasskeySupported() {
2247
+ return typeof window !== "undefined" && !!window.PublicKeyCredential && "isUserVerifyingPlatformAuthenticatorAvailable" in window.PublicKeyCredential;
2248
+ }
2249
+ async function registerPasskey(opts) {
2250
+ const challenge = crypto.getRandomValues(new Uint8Array(32));
2251
+ let cred;
2252
+ try {
2253
+ cred = await navigator.credentials.create({
2254
+ publicKey: {
2255
+ challenge: buf3(challenge),
2256
+ rp: { id: opts.rpId, name: opts.rpName },
2257
+ user: { id: buf3(opts.userId), name: opts.userName, displayName: opts.userName },
2258
+ pubKeyCredParams: [
2259
+ { type: "public-key", alg: -7 },
2260
+ { type: "public-key", alg: -257 }
2261
+ ],
2262
+ authenticatorSelection: { userVerification: "required", residentKey: "preferred" },
2263
+ extensions: { prf: { eval: { first: buf3(opts.prfSalt) } } }
2264
+ }
2265
+ });
2266
+ } catch (e) {
2267
+ if (e.name === "NotAllowedError") throw new UserCancelledError();
2268
+ throw e;
2269
+ }
2270
+ const ext = cred.getClientExtensionResults();
2271
+ const prfResults = ext?.prf?.results;
2272
+ const prfOutput = prfResults?.first ? new Uint8Array(prfResults.first) : void 0;
2273
+ return { credentialId: new Uint8Array(cred.rawId), prfOutput };
2274
+ }
2275
+ async function getPrfOutput(opts) {
2276
+ const challenge = crypto.getRandomValues(new Uint8Array(32));
2277
+ let cred;
2278
+ try {
2279
+ cred = await navigator.credentials.get({
2280
+ publicKey: {
2281
+ challenge: buf3(challenge),
2282
+ rpId: opts.rpId,
2283
+ allowCredentials: [{ id: buf3(opts.credentialId), type: "public-key" }],
2284
+ userVerification: "required",
2285
+ extensions: { prf: { eval: { first: buf3(opts.prfSalt) } } }
2286
+ }
2287
+ });
2288
+ } catch (e) {
2289
+ if (e.name === "NotAllowedError") throw new UserCancelledError();
2290
+ throw e;
2291
+ }
2292
+ const ext = cred.getClientExtensionResults();
2293
+ const prfResults = ext?.prf?.results;
2294
+ if (!prfResults?.first) throw new WebAuthnNotSupportedError();
2295
+ return new Uint8Array(prfResults.first);
2296
+ }
2297
+
2298
+ // src/social/auth/email.ts
2299
+ async function startEmailAuth(api, email) {
2300
+ await api.authEmailStart(email);
2301
+ }
2302
+ async function verifyEmailCode(api, email, code) {
2303
+ return api.authEmailVerify(email, code);
2304
+ }
2305
+
2306
+ // src/social/signers/evm.ts
2307
+ import { keccak256, Transaction, resolveProperties } from "ethers";
2308
+ async function signEvmTransaction(tee, chain, tx) {
2309
+ const resolved = await resolveProperties(tx);
2310
+ const unsigned = Transaction.from(resolved).unsignedSerialized;
2311
+ const payload = unsigned.startsWith("0x") ? unsigned.slice(2) : unsigned;
2312
+ const txHash = keccak256("0x" + payload);
2313
+ return tee.sign({ chain, kind: "transaction", payload, txHash });
2314
+ }
2315
+ async function signEvmMessage(tee, chain, message) {
2316
+ return tee.sign({ chain, kind: "message", payload: message });
2317
+ }
2318
+
2319
+ // src/social/signers/solana.ts
2320
+ import { VersionedTransaction as VTx } from "@solana/web3.js";
2321
+ function b64(bytes) {
2322
+ let s = "";
2323
+ for (const b of bytes) s += String.fromCharCode(b);
2324
+ return btoa(s);
2325
+ }
2326
+ function unb64(s) {
2327
+ return Uint8Array.from(atob(s), (c) => c.charCodeAt(0));
2328
+ }
2329
+ async function signSolanaTransaction(tee, chain, tx) {
2330
+ const bytes = tx instanceof VTx ? tx.serialize() : tx.serialize({ requireAllSignatures: false });
2331
+ const signed = await tee.sign({ chain, kind: "transaction", payload: b64(bytes) });
2332
+ return unb64(signed);
2333
+ }
2334
+ async function signSolanaMessage(tee, chain, message) {
2335
+ const sig = await tee.sign({ chain, kind: "message", payload: b64(message) });
2336
+ return unb64(sig);
2337
+ }
2338
+
2339
+ // src/social/recovery/wrappedShareBackup.ts
2340
+ async function fetchWrappedShareBackup(api, jwt) {
2341
+ return api.getRecoveryBackup(jwt);
2342
+ }
2343
+
2344
+ // src/social/recovery/phrase.ts
2345
+ import { entropyToMnemonic, mnemonicToEntropy, validateMnemonic } from "@scure/bip39";
2346
+ import { wordlist } from "@scure/bip39/wordlists/english.js";
2347
+ function shareToPhrase(deviceShare) {
2348
+ if (deviceShare.length !== 32) throw new Error("deviceShare must be 32 bytes");
2349
+ return entropyToMnemonic(deviceShare, wordlist);
2350
+ }
2351
+ function phraseToShare(phrase) {
2352
+ const normalized = phrase.trim().toLowerCase().split(/\s+/).join(" ");
2353
+ if (!validateMnemonic(normalized, wordlist)) throw new InvalidPhraseError();
2354
+ const entropy = mnemonicToEntropy(normalized, wordlist);
2355
+ if (entropy.length !== 32) throw new InvalidPhraseError();
2356
+ return entropy;
2357
+ }
2358
+ function phraseConfirmationChallenge(phrase) {
2359
+ const words = phrase.trim().split(/\s+/);
2360
+ const chosen = /* @__PURE__ */ new Set();
2361
+ while (chosen.size < 3) chosen.add(Math.floor(Math.random() * words.length));
2362
+ return [...chosen].sort((a, b) => a - b).map((i) => ({ position: i + 1, expected: words[i] }));
2363
+ }
2364
+
2365
+ // src/social/recovery/recoveryFlow.ts
2366
+ async function recoverDeviceShare(input) {
2367
+ const backup = await fetchWrappedShareBackup(input.api, input.jwt);
2368
+ if (backup) {
2369
+ input.onProgress?.({ type: "passkey-attempt" });
2370
+ try {
2371
+ const prfOutput = await getPrfOutput({
2372
+ rpId: input.rpId,
2373
+ credentialId: backup.credentialId,
2374
+ prfSalt: backup.prfSalt
2375
+ });
2376
+ const wrappingKey = await importAesKey(prfOutput);
2377
+ const deviceShare = await aesGcmDecrypt(
2378
+ wrappingKey,
2379
+ backup.wrappedDeviceShare,
2380
+ backup.iv
2381
+ );
2382
+ await input.storage.putShare({
2383
+ userId: input.userId,
2384
+ wrappedDeviceShare: backup.wrappedDeviceShare,
2385
+ iv: backup.iv,
2386
+ unlockMethod: "passkey",
2387
+ credentialId: backup.credentialId,
2388
+ prfSalt: backup.prfSalt,
2389
+ version: 1
2390
+ });
2391
+ return deviceShare;
2392
+ } catch (e) {
2393
+ input.onProgress?.({
2394
+ type: "passkey-failed",
2395
+ reason: e instanceof Error ? e.message : "unknown"
2396
+ });
2397
+ }
2398
+ }
2399
+ if (!input.promptForPhrase) throw new NewDeviceError();
2400
+ input.onProgress?.({ type: "phrase-prompt" });
2401
+ const phrase = await input.promptForPhrase();
2402
+ if (!phrase) throw new NewDeviceError();
2403
+ let candidate;
2404
+ try {
2405
+ candidate = phraseToShare(phrase);
2406
+ } catch {
2407
+ throw new InvalidPhraseError();
2408
+ }
2409
+ input.onProgress?.({ type: "phrase-verifying" });
2410
+ const tee = new TeeClient(input.teeUrl);
2411
+ const verified = await tee.verifyRecoveryCandidate(candidate, input.jwt);
2412
+ if (!verified) {
2413
+ zeroize(candidate);
2414
+ throw new RecoveryFailedError("phrase-mismatch");
2415
+ }
2416
+ return candidate;
2417
+ }
2418
+
2419
+ // src/social/recovery/phraseUi.ts
2420
+ function buildPhraseResult(deviceShare) {
2421
+ const phrase = shareToPhrase(deviceShare);
2422
+ return { phrase, challenge: phraseConfirmationChallenge(phrase) };
2423
+ }
2424
+ function verifyConfirmationChallenge(challenge, userAnswers) {
2425
+ if (userAnswers.length !== challenge.length) return false;
2426
+ return challenge.every(({ position, expected }) => {
2427
+ const answer = userAnswers.find((a) => a.position === position);
2428
+ return !!answer && answer.word.trim().toLowerCase() === expected.toLowerCase();
2429
+ });
2430
+ }
2431
+
2432
+ // src/social/client.ts
2433
+ var DEFAULT_API_BASE = "https://backend.decane.app";
2434
+ var DEFAULT_TEE_URL = "https://4405742df08d19abc549a05ade2a7154d98bd87f-8443.dstack-pha-prod9.phala.network";
2435
+ var DEFAULT_SESSION_MINUTES = 30;
2436
+ var DEFAULT_RPCS = {
2437
+ "evm:1": "https://ethereum.publicnode.com",
2438
+ "evm:137": "https://polygon.publicnode.com",
2439
+ "evm:8453": "https://base.publicnode.com",
2440
+ "evm:10": "https://optimism.publicnode.com",
2441
+ "evm:42161": "https://arbitrum-one.publicnode.com",
2442
+ "evm:56": "https://bsc.publicnode.com",
2443
+ "evm:11155111": "https://ethereum-sepolia.publicnode.com",
2444
+ "evm:84532": "https://base-sepolia.publicnode.com",
2445
+ "evm:31337": "http://localhost:8545"
2446
+ };
2447
+ function makePersistKey(apiKey) {
2448
+ return `decane:social:${apiKey.slice(0, 20)}`;
2449
+ }
2450
+ function loadPersisted(key) {
2451
+ try {
2452
+ const raw = typeof localStorage !== "undefined" ? localStorage.getItem(key) : null;
2453
+ return raw ? JSON.parse(raw) : null;
2454
+ } catch {
2455
+ return null;
2456
+ }
2457
+ }
2458
+ function savePersisted(key, state) {
2459
+ try {
2460
+ localStorage.setItem(key, JSON.stringify(state));
2461
+ } catch {
2462
+ }
2463
+ }
2464
+ function clearPersisted(key) {
2465
+ try {
2466
+ localStorage.removeItem(key);
2467
+ } catch {
2468
+ }
2469
+ }
2470
+ async function createDecaneConnect(config) {
2471
+ if (!config.apiKey) throw new Error("decane-connect-kit: apiKey is required");
2472
+ if (!config.chains?.length)
2473
+ throw new Error("decane-connect-kit: at least one chain is required");
2474
+ const baseUrl = config.apiBaseUrl ?? DEFAULT_API_BASE;
2475
+ const durationMs = (config.sessionDurationMinutes ?? DEFAULT_SESSION_MINUTES) * 6e4;
2476
+ const rpId = config.rpId ?? (typeof window !== "undefined" ? window.location.hostname : "localhost");
2477
+ const rpName = config.rpName ?? "Decane Connect";
2478
+ const PERSIST_KEY = makePersistKey(config.apiKey);
2479
+ const teeUrl = config.teeUrl ?? DEFAULT_TEE_URL;
2480
+ const events = new EventEmitter();
2481
+ const api = new ApiClient(baseUrl, config.apiKey);
2482
+ const storage = new Storage();
2483
+ const session = new SessionManager(events, durationMs, teeUrl);
2484
+ let auth = null;
2485
+ let persisted = loadPersisted(PERSIST_KEY);
2486
+ async function pinSetup() {
2487
+ if (!config.promptPin) throw new PromptPinNotConfiguredError();
2488
+ const pin = await config.promptPin();
2489
+ validatePin(pin);
2490
+ const pinSalt = generateRandomBytes(32);
2491
+ const wrappingKey = await deriveKeyFromPin(pin, pinSalt);
2492
+ return {
2493
+ wrappingKey,
2494
+ meta: {
2495
+ unlockMethod: "pin",
2496
+ pinSalt: bufferToB64u(pinSalt),
2497
+ pinIterations: 6e5
2498
+ }
2499
+ };
2500
+ }
2501
+ async function getWrappingKey(stored) {
2502
+ if (stored.unlockMethod === "passkey") {
2503
+ const prfOutput = await getPrfOutput({
2504
+ rpId,
2505
+ credentialId: stored.credentialId,
2506
+ prfSalt: stored.prfSalt
2507
+ });
2508
+ return importAesKey(prfOutput);
2509
+ }
2510
+ if (!config.promptPin) throw new PromptPinNotConfiguredError();
2511
+ const pin = await config.promptPin();
2512
+ return deriveKeyFromPin(pin, stored.pinSalt);
2513
+ }
2514
+ async function registerPasskeyAndBackup(userId, jwt, deviceShare) {
2515
+ if (!isPasskeySupported()) return;
2516
+ const prfSalt = generateRandomBytes(32);
2517
+ let credentialId;
2518
+ let prfOutput;
2519
+ try {
2520
+ const authType = config.authMethods[0] ?? "social";
2521
+ const dateStr = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
2522
+ const result = await registerPasskey({
2523
+ rpId,
2524
+ rpName,
2525
+ userId: new TextEncoder().encode(userId),
2526
+ userName: `decane-wallet-${authType}-${dateStr}`,
2527
+ prfSalt
2528
+ });
2529
+ credentialId = result.credentialId;
2530
+ prfOutput = result.prfOutput;
2531
+ } catch (e) {
2532
+ console.warn("decane-connect-kit: passkey provisioning after recovery failed; user can set this up later.", e);
2533
+ return;
2534
+ }
2535
+ if (!prfOutput) return;
2536
+ const wrappingKey = await importAesKey(prfOutput);
2537
+ const iv = generateRandomBytes(12);
2538
+ const wrappedDeviceShare = await aesGcmEncrypt(wrappingKey, deviceShare, iv);
2539
+ await storage.putShare({
2540
+ userId,
2541
+ wrappedDeviceShare,
2542
+ iv,
2543
+ unlockMethod: "passkey",
2544
+ credentialId,
2545
+ prfSalt,
2546
+ version: 1
2547
+ });
2548
+ try {
2549
+ await api.putRecoveryBackup(jwt, {
2550
+ wrappedDeviceShare,
2551
+ iv,
2552
+ unlockMethod: "passkey",
2553
+ credentialId,
2554
+ prfSalt
2555
+ });
2556
+ } catch (e) {
2557
+ console.warn("decane-connect-kit: wrapped share backup upload failed; user can retry from settings.", e);
2558
+ }
2559
+ }
2560
+ async function firstTimeSetup(userId, jwt) {
2561
+ let unlockMethod;
2562
+ let wrappingKey;
2563
+ let meta = {};
2564
+ if (isPasskeySupported()) {
2565
+ const prfSalt = generateRandomBytes(32);
2566
+ try {
2567
+ const authType = config.authMethods[0] ?? "social";
2568
+ const dateStr = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
2569
+ const { credentialId, prfOutput } = await registerPasskey({
2570
+ rpId,
2571
+ rpName,
2572
+ userId: new TextEncoder().encode(userId),
2573
+ userName: `decane-wallet-${authType}-${dateStr}`,
2574
+ prfSalt
2575
+ });
2576
+ if (prfOutput) {
2577
+ unlockMethod = "passkey";
2578
+ wrappingKey = await importAesKey(prfOutput);
2579
+ meta = {
2580
+ unlockMethod: "passkey",
2581
+ credentialId: bufferToB64u(credentialId),
2582
+ prfSalt: bufferToB64u(prfSalt)
2583
+ };
2584
+ } else {
2585
+ ({ wrappingKey, meta } = await pinSetup());
2586
+ unlockMethod = "pin";
2587
+ }
2588
+ } catch (e) {
2589
+ if (e.name === "UserCancelledError")
2590
+ throw new UserCancelledError();
2591
+ ({ wrappingKey, meta } = await pinSetup());
2592
+ unlockMethod = "pin";
2593
+ }
2594
+ } else {
2595
+ ({ wrappingKey, meta } = await pinSetup());
2596
+ unlockMethod = "pin";
2597
+ }
2598
+ const { privateKey, deviceShare, serverShare } = generateWalletShares();
2599
+ let evmAddress;
2600
+ let solanaAddress;
2601
+ try {
2602
+ evmAddress = new Wallet(toHex(privateKey)).address;
2603
+ solanaAddress = Keypair.fromSeed(privateKey).publicKey.toBase58();
2604
+ } finally {
2605
+ zeroize(privateKey);
2606
+ }
2607
+ const iv = generateRandomBytes(12);
2608
+ const wrappedDeviceShare = await aesGcmEncrypt(
2609
+ wrappingKey,
2610
+ deviceShare,
2611
+ iv
2612
+ );
2613
+ await storage.putShare({
2614
+ userId,
2615
+ wrappedDeviceShare,
2616
+ iv,
2617
+ unlockMethod,
2618
+ credentialId: meta.credentialId ? b64uToBuffer(meta.credentialId) : void 0,
2619
+ prfSalt: meta.prfSalt ? b64uToBuffer(meta.prfSalt) : void 0,
2620
+ pinSalt: meta.pinSalt ? b64uToBuffer(meta.pinSalt) : void 0,
2621
+ pinIterations: meta.pinIterations,
2622
+ version: 1
2623
+ });
2624
+ const serverIv = generateRandomBytes(12);
2625
+ await api.putShare(jwt, {
2626
+ ciphertext: serverShare,
2627
+ iv: serverIv,
2628
+ wrappedShareMeta: {
2629
+ unlockMethod
2630
+ // credential IDs, prfSalt, pinSalt stay on-device — never sent to server
2631
+ },
2632
+ evmAddress,
2633
+ solanaAddress
2634
+ });
2635
+ zeroize(serverShare);
2636
+ const addresses = { evm: evmAddress, solana: solanaAddress };
2637
+ if (unlockMethod === "passkey" && meta.credentialId && meta.prfSalt) {
2638
+ try {
2639
+ await api.putRecoveryBackup(jwt, {
2640
+ wrappedDeviceShare,
2641
+ iv,
2642
+ unlockMethod: "passkey",
2643
+ credentialId: b64uToBuffer(meta.credentialId),
2644
+ prfSalt: b64uToBuffer(meta.prfSalt)
2645
+ });
2646
+ } catch (e) {
2647
+ console.warn("decane-connect-kit: wrapped share backup failed; user can retry from settings.", e);
2648
+ }
2649
+ }
2650
+ await session.start(deviceShare, jwt, addresses);
2651
+ if (config.offerPhraseAtSignup !== false && config.onPhraseGenerated) {
2652
+ try {
2653
+ const result = buildPhraseResult(deviceShare);
2654
+ const { userConfirmed, userAnswers } = await config.onPhraseGenerated(result);
2655
+ if (userConfirmed && verifyConfirmationChallenge(result.challenge, userAnswers)) {
2656
+ await api.markPhraseSaved(jwt);
2657
+ }
2658
+ } catch (e) {
2659
+ console.warn("decane-connect-kit: recovery phrase offer failed or was skipped.", e);
2660
+ }
2661
+ }
2662
+ zeroize(deviceShare);
2663
+ return addresses;
2664
+ }
2665
+ async function returningUserUnlock(userId, jwt) {
2666
+ const rawAddresses = await api.getAddresses(jwt);
2667
+ if (!rawAddresses.evm || !rawAddresses.solana) {
2668
+ throw new CallSequenceError(
2669
+ "No addresses are recorded for this user \u2014 inconsistent server state"
2670
+ );
2671
+ }
2672
+ const addresses = { evm: rawAddresses.evm, solana: rawAddresses.solana };
2673
+ const stored = await storage.getShare(userId);
2674
+ let deviceShare;
2675
+ if (!stored) {
2676
+ deviceShare = await recoverDeviceShare({
2677
+ userId,
2678
+ jwt,
2679
+ rpId,
2680
+ teeUrl,
2681
+ api,
2682
+ storage,
2683
+ onProgress: config.onRecoveryProgress,
2684
+ promptForPhrase: config.promptForRecoveryPhrase
2685
+ });
2686
+ if (!await storage.getShare(userId) && isPasskeySupported()) {
2687
+ await registerPasskeyAndBackup(userId, jwt, deviceShare);
2688
+ }
2689
+ } else {
2690
+ const wrappingKey = await getWrappingKey(stored);
2691
+ try {
2692
+ deviceShare = await aesGcmDecrypt(
2693
+ wrappingKey,
2694
+ stored.wrappedDeviceShare,
2695
+ stored.iv
2696
+ );
2697
+ } catch {
2698
+ throw stored.unlockMethod === "pin" ? new InvalidPinError() : new ShareCorruptedError();
2699
+ }
2700
+ }
2701
+ await session.start(deviceShare, jwt, addresses);
2702
+ zeroize(deviceShare);
2703
+ return addresses;
2704
+ }
2705
+ async function handleAuthResult(result) {
2706
+ let addresses;
2707
+ if (!result.hasShare) {
2708
+ addresses = await firstTimeSetup(result.userId, result.jwt);
2709
+ } else {
2710
+ addresses = await returningUserUnlock(result.userId, result.jwt);
2711
+ }
2712
+ auth = { jwt: result.jwt, userId: result.userId, addresses };
2713
+ persisted = { userId: result.userId, addresses };
2714
+ savePersisted(PERSIST_KEY, persisted);
2715
+ events.emit("connected");
2716
+ return { addresses, isNewUser: result.isNewUser };
2717
+ }
2718
+ const client = {
2719
+ async connect() {
2720
+ if (auth && session.isActive()) {
2721
+ return { addresses: auth.addresses, isNewUser: false };
2722
+ }
2723
+ const method = config.authMethods[0];
2724
+ if (!method) throw new CallSequenceError("No authMethods configured");
2725
+ if (method === "google") return client.connectWithGoogle();
2726
+ if (method === "email")
2727
+ throw new CallSequenceError(
2728
+ "Use connectWithEmail(email) for email auth"
2729
+ );
2730
+ throw new CallSequenceError(`Unknown auth method: ${method}`);
2731
+ },
2732
+ async connectWithGoogle() {
2733
+ const params = new URLSearchParams(window.location.search);
2734
+ const decaneJwt = params.get("decane_jwt");
2735
+ const decaneError = params.get("decane_error");
2736
+ if (decaneError) {
2737
+ window.history.replaceState({}, "", window.location.pathname);
2738
+ throw new CallSequenceError(`Google sign-in failed: ${decaneError}`);
2739
+ }
2740
+ if (decaneJwt) {
2741
+ const result = {
2742
+ jwt: decaneJwt,
2743
+ userId: params.get("decane_user_id") ?? "",
2744
+ isNewUser: params.get("decane_is_new_user") === "true",
2745
+ hasShare: params.get("decane_has_share") === "true"
2746
+ };
2747
+ window.history.replaceState({}, "", window.location.pathname);
2748
+ return handleAuthResult(result);
2749
+ }
2750
+ const { url } = await api.getGoogleInitUrl();
2751
+ window.location.href = url;
2752
+ return new Promise(() => {
2753
+ });
2754
+ },
2755
+ async connectWithEmail(email) {
2756
+ await startEmailAuth(api, email);
2757
+ return { codeRequired: true };
2758
+ },
2759
+ async verifyEmailCode(email, code) {
2760
+ const result = await verifyEmailCode(api, email, code);
2761
+ return handleAuthResult(result);
2762
+ },
2763
+ async unlock() {
2764
+ if (!auth)
2765
+ throw new CallSequenceError("Not connected \u2014 call connect() first");
2766
+ if (session.isActive()) return;
2767
+ await returningUserUnlock(auth.userId, auth.jwt);
2768
+ },
2769
+ isUnlocked() {
2770
+ return session.isActive();
2771
+ },
2772
+ sessionExpiresAt() {
2773
+ return session.expiresAtTimestamp();
2774
+ },
2775
+ async signMessage(req) {
2776
+ const tee = session.requireActive();
2777
+ if (req.chain.startsWith("evm:")) {
2778
+ return signEvmMessage(tee, req.chain, req.message);
2779
+ }
2780
+ if (req.chain.startsWith("solana:")) {
2781
+ const msg = typeof req.message === "string" ? new TextEncoder().encode(req.message) : req.message;
2782
+ const sig = await signSolanaMessage(tee, req.chain, msg);
2783
+ return bufferToB64u(sig);
2784
+ }
2785
+ throw new CallSequenceError(`Unsupported chain: ${req.chain}`);
2786
+ },
2787
+ async sendTransaction(req) {
2788
+ const tee = session.requireActive();
2789
+ if (!req.chain.startsWith("evm:")) {
2790
+ throw new CallSequenceError(
2791
+ "sendTransaction is EVM-only. Use signSolanaTransaction for Solana."
2792
+ );
2793
+ }
2794
+ if (!auth?.addresses.evm) {
2795
+ throw new CallSequenceError("Not connected");
2796
+ }
2797
+ const evmAddr = auth.addresses.evm;
2798
+ const rpcUrl = config.rpcUrls?.[req.chain] ?? DEFAULT_RPCS[req.chain];
2799
+ if (!rpcUrl) {
2800
+ throw new CallSequenceError(
2801
+ `No RPC URL for ${req.chain}. Pass rpcUrls: { '${req.chain}': 'https://...' } in social config.`
2802
+ );
2803
+ }
2804
+ const { JsonRpcProvider } = await import("ethers");
2805
+ const provider = new JsonRpcProvider(rpcUrl);
2806
+ const chainId = parseInt(req.chain.slice(4), 10);
2807
+ const [nonce, feeData] = await Promise.all([
2808
+ provider.getTransactionCount(evmAddr, "pending"),
2809
+ provider.getFeeData()
2810
+ ]);
2811
+ const gasLimit = req.gasLimit ?? await provider.estimateGas({
2812
+ from: evmAddr,
2813
+ to: req.to,
2814
+ value: req.value ?? 0n,
2815
+ data: req.data
2816
+ });
2817
+ const tx = {
2818
+ to: req.to,
2819
+ value: req.value ?? 0n,
2820
+ data: req.data,
2821
+ gasLimit,
2822
+ chainId,
2823
+ nonce,
2824
+ // EIP-1559 fees — fall back to legacy gasPrice if unavailable
2825
+ ...feeData.maxFeePerGas != null ? {
2826
+ maxFeePerGas: feeData.maxFeePerGas,
2827
+ maxPriorityFeePerGas: feeData.maxPriorityFeePerGas ?? 0n
2828
+ } : { gasPrice: req.gasPrice ?? feeData.gasPrice ?? void 0 }
2829
+ };
2830
+ const signedTx = await signEvmTransaction(tee, req.chain, tx);
2831
+ const txResponse = await provider.broadcastTransaction(signedTx);
2832
+ return txResponse.hash;
2833
+ },
2834
+ async signSolanaTransaction(serializedTx) {
2835
+ const tee = session.requireActive();
2836
+ let tx;
2837
+ try {
2838
+ tx = VersionedTransaction.deserialize(serializedTx);
2839
+ } catch {
2840
+ tx = Transaction2.from(serializedTx);
2841
+ }
2842
+ const solChain = config.chains.find((c) => c.startsWith("solana:")) ?? "solana:mainnet";
2843
+ return signSolanaTransaction(tee, solChain, tx);
2844
+ },
2845
+ async disconnect() {
2846
+ session.end();
2847
+ if (auth?.jwt) {
2848
+ await api.revoke(auth.jwt).catch(() => {
2849
+ });
2850
+ }
2851
+ auth = null;
2852
+ persisted = null;
2853
+ clearPersisted(PERSIST_KEY);
2854
+ events.emit("disconnected");
2855
+ },
2856
+ getAddresses() {
2857
+ return auth?.addresses ?? persisted?.addresses ?? null;
2858
+ },
2859
+ async generateRecoveryPhrase() {
2860
+ if (!auth) throw new CallSequenceError("Not connected \u2014 call connect() first");
2861
+ const stored = await storage.getShare(auth.userId);
2862
+ if (!stored) throw new NewDeviceError();
2863
+ const wrappingKey = await getWrappingKey(stored);
2864
+ const deviceShare = await aesGcmDecrypt(wrappingKey, stored.wrappedDeviceShare, stored.iv);
2865
+ try {
2866
+ return buildPhraseResult(deviceShare);
2867
+ } finally {
2868
+ zeroize(deviceShare);
2869
+ }
2870
+ },
2871
+ async markPhraseSaved(challenge, userAnswers) {
2872
+ if (!auth) throw new CallSequenceError("Not connected \u2014 call connect() first");
2873
+ if (!verifyConfirmationChallenge(challenge, userAnswers)) return false;
2874
+ await api.markPhraseSaved(auth.jwt);
2875
+ return true;
2876
+ },
2877
+ async hasBackup() {
2878
+ if (!auth) throw new CallSequenceError("Not connected \u2014 call connect() first");
2879
+ return api.hasRecoveryBackup(auth.jwt);
2880
+ },
2881
+ async deleteBackup() {
2882
+ if (!auth) throw new CallSequenceError("Not connected \u2014 call connect() first");
2883
+ await api.deleteRecoveryBackup(auth.jwt);
2884
+ },
2885
+ on: events.on.bind(events),
2886
+ off: events.off.bind(events)
2887
+ };
2888
+ return client;
2889
+ }
2890
+ function bufferToB64u(buf4) {
2891
+ return btoa(String.fromCharCode(...buf4)).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
2892
+ }
2893
+ function b64uToBuffer(b64u) {
2894
+ const b642 = b64u.replace(/-/g, "+").replace(/_/g, "/");
2895
+ return Uint8Array.from(atob(b642), (c) => c.charCodeAt(0));
2896
+ }
2897
+ function toHex(bytes) {
2898
+ return "0x" + Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
2899
+ }
2900
+
1734
2901
  // src/ui/WalletSelector.tsx
1735
2902
  import React2, { useEffect, useCallback, useState } from "react";
1736
2903
  import { createPortal } from "react-dom";
@@ -2083,17 +3250,33 @@ function ChainOption({ label, description, badges, onClick }) {
2083
3250
  /* @__PURE__ */ React2.createElement("span", { style: { fontSize: 11, color: "var(--dc-text-muted)", lineHeight: 1.4 } }, description)
2084
3251
  );
2085
3252
  }
3253
+ function hasSocialMode(mode) {
3254
+ return mode === "all" || mode === "social" || mode === "evm+social" || mode === "solana+social" || mode === "tron+social" || mode === "evm+tron+social";
3255
+ }
2086
3256
  function WalletSelector({
2087
3257
  provider,
2088
3258
  open,
2089
3259
  onClose,
2090
3260
  onConnect,
2091
3261
  theme = "auto",
2092
- networks = "all"
3262
+ networks = "all",
3263
+ mode = "wallets",
3264
+ socialAuthMethods = ["google", "email"],
3265
+ onGoogleSignIn,
3266
+ onEmailStart,
3267
+ onEmailVerify
2093
3268
  }) {
3269
+ const showSocial = hasSocialMode(mode);
3270
+ const showWallets = mode !== "social";
3271
+ const [view, setView] = useState(showWallets ? "wallets" : "social");
2094
3272
  const [search, setSearch] = useState("");
2095
3273
  const [filter, setFilter] = useState("all");
2096
3274
  const [pendingWallet, setPendingWallet] = useState(null);
3275
+ const [emailStep, setEmailStep] = useState("input");
3276
+ const [emailVal, setEmailVal] = useState("");
3277
+ const [codeVal, setCodeVal] = useState("");
3278
+ const [socialLoad, setSocialLoad] = useState(false);
3279
+ const [socialErr, setSocialErr] = useState("");
2097
3280
  useEffect(() => {
2098
3281
  if (!open) return;
2099
3282
  injectFont();
@@ -2109,12 +3292,55 @@ function WalletSelector({
2109
3292
  setSearch("");
2110
3293
  setFilter("all");
2111
3294
  setPendingWallet(null);
3295
+ setView(showWallets ? "wallets" : "social");
3296
+ setEmailStep("input");
3297
+ setEmailVal("");
3298
+ setCodeVal("");
3299
+ setSocialErr("");
2112
3300
  return;
2113
3301
  }
2114
3302
  return () => {
2115
3303
  document.getElementById("decane-tokens")?.remove();
2116
3304
  };
2117
- }, [open]);
3305
+ }, [open, showWallets]);
3306
+ async function handleGoogleClick() {
3307
+ if (!onGoogleSignIn) return;
3308
+ setSocialLoad(true);
3309
+ setSocialErr("");
3310
+ try {
3311
+ await onGoogleSignIn();
3312
+ } catch (e) {
3313
+ setSocialErr(e.message);
3314
+ setSocialLoad(false);
3315
+ }
3316
+ }
3317
+ async function handleEmailStart(e) {
3318
+ e.preventDefault();
3319
+ if (!onEmailStart) return;
3320
+ setSocialLoad(true);
3321
+ setSocialErr("");
3322
+ try {
3323
+ await onEmailStart(emailVal);
3324
+ setEmailStep("code");
3325
+ } catch (e2) {
3326
+ setSocialErr(e2.message);
3327
+ } finally {
3328
+ setSocialLoad(false);
3329
+ }
3330
+ }
3331
+ async function handleEmailVerify(e) {
3332
+ e.preventDefault();
3333
+ if (!onEmailVerify) return;
3334
+ setSocialLoad(true);
3335
+ setSocialErr("");
3336
+ try {
3337
+ await onEmailVerify(emailVal, codeVal);
3338
+ } catch (e2) {
3339
+ setSocialErr(e2.message);
3340
+ } finally {
3341
+ setSocialLoad(false);
3342
+ }
3343
+ }
2118
3344
  const handleKey = useCallback(
2119
3345
  (e) => {
2120
3346
  if (e.key === "Escape") {
@@ -2327,8 +3553,8 @@ function WalletSelector({
2327
3553
  }
2328
3554
  )))
2329
3555
  ) : (
2330
- /* ── Wallet list step ────────────────────────────────────────── */
2331
- /* @__PURE__ */ React2.createElement(React2.Fragment, null, /* @__PURE__ */ React2.createElement("div", { style: { padding: "18px 20px 0" } }, /* @__PURE__ */ React2.createElement("div", { style: { display: "flex", alignItems: "flex-start", justifyContent: "space-between", marginBottom: 14 } }, /* @__PURE__ */ React2.createElement("div", null, /* @__PURE__ */ React2.createElement("h2", { style: { margin: 0, fontSize: 17, fontWeight: 800, color: "var(--dc-text)", letterSpacing: "-0.02em", lineHeight: 1.2 } }, "Connect wallet"), networkWallets.length > 0 && /* @__PURE__ */ React2.createElement("p", { style: { margin: "3px 0 0", fontSize: 12, color: "var(--dc-text-muted)" } }, networkWallets.length, " wallet", networkWallets.length !== 1 ? "s" : "", " detected")), /* @__PURE__ */ React2.createElement(
3556
+ /* ── Wallet list / Social sign-in ────────────────────────────── */
3557
+ /* @__PURE__ */ React2.createElement(React2.Fragment, null, /* @__PURE__ */ React2.createElement("div", { style: { padding: "18px 20px 0" } }, /* @__PURE__ */ React2.createElement("div", { style: { display: "flex", alignItems: "flex-start", justifyContent: "space-between", marginBottom: 14 } }, /* @__PURE__ */ React2.createElement("div", null, /* @__PURE__ */ React2.createElement("h2", { style: { margin: 0, fontSize: 17, fontWeight: 800, color: "var(--dc-text)", letterSpacing: "-0.02em", lineHeight: 1.2 } }, view === "social" ? "Sign in" : "Connect wallet"), view === "wallets" && networkWallets.length > 0 && /* @__PURE__ */ React2.createElement("p", { style: { margin: "3px 0 0", fontSize: 12, color: "var(--dc-text-muted)" } }, networkWallets.length, " wallet", networkWallets.length !== 1 ? "s" : "", " detected")), /* @__PURE__ */ React2.createElement(
2332
3558
  "button",
2333
3559
  {
2334
3560
  onClick: onClose,
@@ -2359,7 +3585,28 @@ function WalletSelector({
2359
3585
  }
2360
3586
  },
2361
3587
  /* @__PURE__ */ React2.createElement("svg", { width: "12", height: "12", viewBox: "0 0 12 12", fill: "none" }, /* @__PURE__ */ React2.createElement("path", { d: "M1.5 1.5L10.5 10.5M10.5 1.5L1.5 10.5", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" }))
2362
- )), /* @__PURE__ */ React2.createElement("div", { style: { position: "relative", marginBottom: 10 } }, /* @__PURE__ */ React2.createElement(
3588
+ )), showWallets && showSocial && /* @__PURE__ */ React2.createElement("div", { style: { display: "flex", gap: 4, marginBottom: 14, background: "var(--dc-surface)", borderRadius: 10, padding: 3 } }, ["wallets", "social"].map((v) => /* @__PURE__ */ React2.createElement(
3589
+ "button",
3590
+ {
3591
+ key: v,
3592
+ onClick: () => setView(v),
3593
+ style: {
3594
+ flex: 1,
3595
+ padding: "7px 0",
3596
+ borderRadius: 8,
3597
+ border: "none",
3598
+ background: view === v ? "var(--dc-bg)" : "transparent",
3599
+ color: view === v ? "var(--dc-text)" : "var(--dc-text-muted)",
3600
+ fontSize: 13,
3601
+ fontWeight: view === v ? 700 : 500,
3602
+ cursor: "pointer",
3603
+ fontFamily: "inherit",
3604
+ boxShadow: view === v ? "0 1px 4px rgba(0,0,0,0.2)" : "none",
3605
+ transition: "all 0.15s"
3606
+ }
3607
+ },
3608
+ v === "wallets" ? "Wallet" : "Sign in"
3609
+ ))), view === "wallets" && networks === "all" && /* @__PURE__ */ React2.createElement("div", { style: { display: "flex", gap: 4, marginBottom: 10 } }, /* @__PURE__ */ React2.createElement(FilterTab, { label: "All", active: filter === "all", onClick: () => setFilter("all") }), /* @__PURE__ */ React2.createElement(FilterTab, { label: "EVM", active: filter === "evm", onClick: () => setFilter("evm") }), /* @__PURE__ */ React2.createElement(FilterTab, { label: "Solana", active: filter === "solana", onClick: () => setFilter("solana") }), provider.tronWallets && provider.tronWallets.length > 0 && /* @__PURE__ */ React2.createElement(FilterTab, { label: "Tron", active: filter === "tron", onClick: () => setFilter("tron") }), provider.bitcoinWallets && provider.bitcoinWallets.length > 0 && /* @__PURE__ */ React2.createElement(FilterTab, { label: "Bitcoin", active: filter === "bitcoin", onClick: () => setFilter("bitcoin") })), view === "wallets" && /* @__PURE__ */ React2.createElement("div", { style: { position: "relative", marginBottom: 10 } }, /* @__PURE__ */ React2.createElement(
2363
3610
  "svg",
2364
3611
  {
2365
3612
  width: "14",
@@ -2397,52 +3644,628 @@ function WalletSelector({
2397
3644
  e.currentTarget.style.borderColor = "var(--dc-border)";
2398
3645
  }
2399
3646
  }
2400
- )), networks === "all" && /* @__PURE__ */ React2.createElement("div", { style: { display: "flex", gap: 4, marginBottom: 10 } }, /* @__PURE__ */ React2.createElement(FilterTab, { label: "All", active: filter === "all", onClick: () => setFilter("all") }), /* @__PURE__ */ React2.createElement(FilterTab, { label: "EVM", active: filter === "evm", onClick: () => setFilter("evm") }), /* @__PURE__ */ React2.createElement(FilterTab, { label: "Solana", active: filter === "solana", onClick: () => setFilter("solana") }), provider.tronWallets && provider.tronWallets.length > 0 && /* @__PURE__ */ React2.createElement(FilterTab, { label: "Tron", active: filter === "tron", onClick: () => setFilter("tron") }), provider.bitcoinWallets && provider.bitcoinWallets.length > 0 && /* @__PURE__ */ React2.createElement(FilterTab, { label: "Bitcoin", active: filter === "bitcoin", onClick: () => setFilter("bitcoin") }))), /* @__PURE__ */ React2.createElement("div", { style: { overflowY: "auto", flex: 1, padding: "0 12px 20px" } }, filtered.length > 0 ? /* @__PURE__ */ React2.createElement("div", { style: { display: "flex", flexDirection: "column", gap: 2 } }, filtered.map((w) => /* @__PURE__ */ React2.createElement(WalletRow, { key: w.id, wallet: w, onClick: () => handleWalletClick(w) }))) : /* @__PURE__ */ React2.createElement("div", { style: { textAlign: "center", padding: "32px 16px", color: "var(--dc-text-muted)", fontSize: 13 } }, q ? `No wallets matching "${q}"` : "No wallets detected")), /* @__PURE__ */ React2.createElement("div", { style: { padding: "10px 20px 14px", borderTop: "1px solid var(--dc-border)", textAlign: "center", fontSize: 11, color: "var(--dc-text-muted)" } }, "Don't see your wallet? Make sure it's installed and unlocked"))
3647
+ ))), view === "social" && /* @__PURE__ */ React2.createElement("div", { style: { padding: "4px 20px 24px", display: "flex", flexDirection: "column", gap: 10 } }, socialErr && /* @__PURE__ */ React2.createElement("div", { style: { background: "rgba(204,51,0,0.1)", border: "1px solid var(--dc-danger)", borderRadius: 8, padding: "9px 12px", fontSize: 12, color: "var(--dc-danger)" } }, socialErr), socialAuthMethods.includes("google") && /* @__PURE__ */ React2.createElement(
3648
+ "button",
3649
+ {
3650
+ onClick: handleGoogleClick,
3651
+ disabled: socialLoad,
3652
+ style: {
3653
+ display: "flex",
3654
+ alignItems: "center",
3655
+ justifyContent: "center",
3656
+ gap: 10,
3657
+ width: "100%",
3658
+ padding: "11px 16px",
3659
+ background: "var(--dc-surface)",
3660
+ border: "1px solid var(--dc-border)",
3661
+ borderRadius: 10,
3662
+ cursor: socialLoad ? "not-allowed" : "pointer",
3663
+ fontSize: 14,
3664
+ fontWeight: 600,
3665
+ color: "var(--dc-text)",
3666
+ fontFamily: "inherit",
3667
+ transition: "border-color 0.12s, background 0.12s",
3668
+ opacity: socialLoad ? 0.6 : 1
3669
+ },
3670
+ onMouseEnter: (e) => {
3671
+ if (!socialLoad) e.currentTarget.style.borderColor = "var(--dc-border-accent)";
3672
+ },
3673
+ onMouseLeave: (e) => {
3674
+ e.currentTarget.style.borderColor = "var(--dc-border)";
3675
+ }
3676
+ },
3677
+ /* @__PURE__ */ React2.createElement("svg", { width: "18", height: "18", viewBox: "0 0 18 18", fill: "none" }, /* @__PURE__ */ React2.createElement("path", { d: "M17.64 9.2c0-.637-.057-1.251-.164-1.84H9v3.481h4.844c-.209 1.125-.843 2.078-1.796 2.717v2.258h2.908c1.702-1.567 2.684-3.875 2.684-6.615z", fill: "#4285F4" }), /* @__PURE__ */ React2.createElement("path", { d: "M9 18c2.43 0 4.467-.806 5.956-2.18l-2.908-2.259c-.806.54-1.837.86-3.048.86-2.344 0-4.328-1.584-5.036-3.711H.957v2.332A8.997 8.997 0 0 0 9 18z", fill: "#34A853" }), /* @__PURE__ */ React2.createElement("path", { d: "M3.964 10.71A5.41 5.41 0 0 1 3.682 9c0-.593.102-1.17.282-1.71V4.958H.957A8.996 8.996 0 0 0 0 9c0 1.452.348 2.827.957 4.042l3.007-2.332z", fill: "#FBBC05" }), /* @__PURE__ */ React2.createElement("path", { d: "M9 3.58c1.321 0 2.508.454 3.44 1.345l2.582-2.58C13.463.891 11.426 0 9 0A8.997 8.997 0 0 0 .957 4.958L3.964 6.29C4.672 4.163 6.656 3.58 9 3.58z", fill: "#EA4335" })),
3678
+ socialLoad ? "Redirecting\u2026" : "Continue with Google"
3679
+ ), socialAuthMethods.includes("google") && socialAuthMethods.includes("email") && /* @__PURE__ */ React2.createElement("div", { style: { display: "flex", alignItems: "center", gap: 10 } }, /* @__PURE__ */ React2.createElement("div", { style: { flex: 1, height: 1, background: "var(--dc-border)" } }), /* @__PURE__ */ React2.createElement("span", { style: { fontSize: 11, color: "var(--dc-text-muted)", whiteSpace: "nowrap" } }, "or continue with email"), /* @__PURE__ */ React2.createElement("div", { style: { flex: 1, height: 1, background: "var(--dc-border)" } })), socialAuthMethods.includes("email") && (emailStep === "input" ? /* @__PURE__ */ React2.createElement("form", { onSubmit: handleEmailStart, style: { display: "flex", flexDirection: "column", gap: 8 } }, /* @__PURE__ */ React2.createElement(
3680
+ "input",
3681
+ {
3682
+ type: "email",
3683
+ placeholder: "your@email.com",
3684
+ value: emailVal,
3685
+ onChange: (e) => setEmailVal(e.target.value),
3686
+ required: true,
3687
+ style: {
3688
+ width: "100%",
3689
+ padding: "10px 12px",
3690
+ background: "var(--dc-surface)",
3691
+ border: "1px solid var(--dc-border)",
3692
+ borderRadius: 10,
3693
+ color: "var(--dc-text)",
3694
+ fontSize: 13,
3695
+ fontFamily: "inherit",
3696
+ outline: "none",
3697
+ boxSizing: "border-box"
3698
+ },
3699
+ onFocus: (e) => {
3700
+ e.currentTarget.style.borderColor = "var(--dc-border-accent)";
3701
+ },
3702
+ onBlur: (e) => {
3703
+ e.currentTarget.style.borderColor = "var(--dc-border)";
3704
+ }
3705
+ }
3706
+ ), /* @__PURE__ */ React2.createElement(
3707
+ "button",
3708
+ {
3709
+ type: "submit",
3710
+ disabled: socialLoad || !emailVal,
3711
+ style: {
3712
+ width: "100%",
3713
+ padding: "10px 16px",
3714
+ background: "var(--dc-accent)",
3715
+ color: "var(--dc-accent-text)",
3716
+ border: "none",
3717
+ borderRadius: 10,
3718
+ fontSize: 14,
3719
+ fontWeight: 700,
3720
+ cursor: socialLoad ? "not-allowed" : "pointer",
3721
+ fontFamily: "inherit",
3722
+ opacity: socialLoad || !emailVal ? 0.6 : 1
3723
+ }
3724
+ },
3725
+ socialLoad ? "Sending\u2026" : "Send code"
3726
+ )) : /* @__PURE__ */ React2.createElement("form", { onSubmit: handleEmailVerify, style: { display: "flex", flexDirection: "column", gap: 8 } }, /* @__PURE__ */ React2.createElement("p", { style: { margin: 0, fontSize: 12, color: "var(--dc-text-muted)", textAlign: "center" } }, "Code sent to ", /* @__PURE__ */ React2.createElement("strong", { style: { color: "var(--dc-text)" } }, emailVal)), /* @__PURE__ */ React2.createElement(
3727
+ "input",
3728
+ {
3729
+ type: "text",
3730
+ placeholder: "6-digit code",
3731
+ value: codeVal,
3732
+ onChange: (e) => setCodeVal(e.target.value.replace(/\D/g, "").slice(0, 6)),
3733
+ autoFocus: true,
3734
+ required: true,
3735
+ style: {
3736
+ width: "100%",
3737
+ padding: "10px 12px",
3738
+ background: "var(--dc-surface)",
3739
+ border: "1px solid var(--dc-border)",
3740
+ borderRadius: 10,
3741
+ color: "var(--dc-text)",
3742
+ fontSize: 20,
3743
+ fontFamily: "monospace",
3744
+ letterSpacing: 8,
3745
+ textAlign: "center",
3746
+ outline: "none",
3747
+ boxSizing: "border-box"
3748
+ },
3749
+ onFocus: (e) => {
3750
+ e.currentTarget.style.borderColor = "var(--dc-border-accent)";
3751
+ },
3752
+ onBlur: (e) => {
3753
+ e.currentTarget.style.borderColor = "var(--dc-border)";
3754
+ }
3755
+ }
3756
+ ), /* @__PURE__ */ React2.createElement("div", { style: { display: "flex", gap: 8 } }, /* @__PURE__ */ React2.createElement(
3757
+ "button",
3758
+ {
3759
+ type: "button",
3760
+ onClick: () => {
3761
+ setEmailStep("input");
3762
+ setCodeVal("");
3763
+ setSocialErr("");
3764
+ },
3765
+ style: {
3766
+ flex: 1,
3767
+ padding: "9px 0",
3768
+ background: "transparent",
3769
+ border: "1px solid var(--dc-border)",
3770
+ borderRadius: 10,
3771
+ color: "var(--dc-text-muted)",
3772
+ fontSize: 13,
3773
+ cursor: "pointer",
3774
+ fontFamily: "inherit"
3775
+ }
3776
+ },
3777
+ "Back"
3778
+ ), /* @__PURE__ */ React2.createElement(
3779
+ "button",
3780
+ {
3781
+ type: "submit",
3782
+ disabled: socialLoad || codeVal.length < 6,
3783
+ style: {
3784
+ flex: 2,
3785
+ padding: "9px 0",
3786
+ background: "var(--dc-accent)",
3787
+ color: "var(--dc-accent-text)",
3788
+ border: "none",
3789
+ borderRadius: 10,
3790
+ fontSize: 13,
3791
+ fontWeight: 700,
3792
+ cursor: socialLoad || codeVal.length < 6 ? "not-allowed" : "pointer",
3793
+ fontFamily: "inherit",
3794
+ opacity: socialLoad || codeVal.length < 6 ? 0.6 : 1
3795
+ }
3796
+ },
3797
+ socialLoad ? "Verifying\u2026" : "Verify"
3798
+ ))))), view === "wallets" && /* @__PURE__ */ React2.createElement("div", { style: { overflowY: "auto", flex: 1, padding: "0 12px 20px" } }, filtered.length > 0 ? /* @__PURE__ */ React2.createElement("div", { style: { display: "flex", flexDirection: "column", gap: 2 } }, filtered.map((w) => /* @__PURE__ */ React2.createElement(WalletRow, { key: w.id, wallet: w, onClick: () => handleWalletClick(w) }))) : /* @__PURE__ */ React2.createElement("div", { style: { textAlign: "center", padding: "32px 16px", color: "var(--dc-text-muted)", fontSize: 13 } }, q ? `No wallets matching "${q}"` : "No wallets detected")), view === "wallets" && /* @__PURE__ */ React2.createElement("div", { style: { padding: "10px 20px 14px", borderTop: "1px solid var(--dc-border)", textAlign: "center", fontSize: 11, color: "var(--dc-text-muted)" } }, "Don't see your wallet? Make sure it's installed and unlocked"))
2401
3799
  )
2402
3800
  )
2403
3801
  );
2404
3802
  return createPortal(modal, document.body);
2405
3803
  }
2406
3804
 
3805
+ // src/ui/SocialWalletModal.tsx
3806
+ import React3, { useCallback as useCallback2, useEffect as useEffect2, useState as useState2 } from "react";
3807
+ import { createPortal as createPortal2 } from "react-dom";
3808
+ function injectTokens2(theme) {
3809
+ const resolved = theme === "auto" ? window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light" : theme;
3810
+ let el = document.getElementById("decane-tokens");
3811
+ if (!el) {
3812
+ el = document.createElement("style");
3813
+ el.id = "decane-tokens";
3814
+ document.head.appendChild(el);
3815
+ }
3816
+ el.textContent = `:root { ${CSS_VARS[resolved]} }`;
3817
+ }
3818
+ function countdown(expiresAt) {
3819
+ const secs = Math.max(0, Math.round((expiresAt - Date.now()) / 1e3));
3820
+ return `${Math.floor(secs / 60)}m ${String(secs % 60).padStart(2, "0")}s`;
3821
+ }
3822
+ function CopyButton({ value }) {
3823
+ const [copied, setCopied] = useState2(false);
3824
+ async function copy() {
3825
+ await navigator.clipboard.writeText(value).catch(() => {
3826
+ });
3827
+ setCopied(true);
3828
+ setTimeout(() => setCopied(false), 1500);
3829
+ }
3830
+ return /* @__PURE__ */ React3.createElement(
3831
+ "button",
3832
+ {
3833
+ onClick: copy,
3834
+ style: {
3835
+ background: "none",
3836
+ border: "none",
3837
+ cursor: "pointer",
3838
+ color: copied ? "var(--dc-accent)" : "var(--dc-text-muted)",
3839
+ fontSize: 11,
3840
+ fontFamily: "inherit",
3841
+ padding: "2px 6px",
3842
+ borderRadius: 5,
3843
+ transition: "color 0.15s"
3844
+ }
3845
+ },
3846
+ copied ? "\u2713" : "copy"
3847
+ );
3848
+ }
3849
+ function AddressRow({ label, address }) {
3850
+ const short = `${address.slice(0, 8)}\u2026${address.slice(-6)}`;
3851
+ return /* @__PURE__ */ React3.createElement("div", { style: {
3852
+ background: "var(--dc-surface)",
3853
+ border: "1px solid var(--dc-border)",
3854
+ borderRadius: 10,
3855
+ padding: "10px 14px",
3856
+ marginBottom: 8
3857
+ } }, /* @__PURE__ */ React3.createElement("div", { style: { fontSize: 10, fontWeight: 700, color: "var(--dc-text-muted)", letterSpacing: "0.06em", textTransform: "uppercase", marginBottom: 5 } }, label), /* @__PURE__ */ React3.createElement("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between", gap: 8 } }, /* @__PURE__ */ React3.createElement("span", { style: { fontSize: 13, fontFamily: "monospace", color: "var(--dc-text)", letterSpacing: "0.02em" } }, short), /* @__PURE__ */ React3.createElement(CopyButton, { value: address })));
3858
+ }
3859
+ function SocialWalletModal({
3860
+ open,
3861
+ onClose,
3862
+ theme = "auto",
3863
+ socialAuth,
3864
+ onUnlock,
3865
+ onDisconnect
3866
+ }) {
3867
+ const [timer, setTimer] = useState2("");
3868
+ const [unlocking, setUnlocking] = useState2(false);
3869
+ const [disconnecting, setDisconnecting] = useState2(false);
3870
+ useEffect2(() => {
3871
+ if (!open) return;
3872
+ injectTokens2(theme);
3873
+ if (theme !== "auto") return;
3874
+ const mq = window.matchMedia("(prefers-color-scheme: dark)");
3875
+ const h = () => injectTokens2("auto");
3876
+ mq.addEventListener("change", h);
3877
+ return () => mq.removeEventListener("change", h);
3878
+ }, [open, theme]);
3879
+ useEffect2(() => {
3880
+ if (!socialAuth.sessionExpiresAt) {
3881
+ setTimer("");
3882
+ return;
3883
+ }
3884
+ setTimer(countdown(socialAuth.sessionExpiresAt));
3885
+ const id = setInterval(() => setTimer(countdown(socialAuth.sessionExpiresAt)), 1e3);
3886
+ return () => clearInterval(id);
3887
+ }, [socialAuth.sessionExpiresAt]);
3888
+ const handleKey = useCallback2((e) => {
3889
+ if (e.key === "Escape") onClose();
3890
+ }, [onClose]);
3891
+ useEffect2(() => {
3892
+ if (!open) return;
3893
+ document.addEventListener("keydown", handleKey);
3894
+ return () => document.removeEventListener("keydown", handleKey);
3895
+ }, [open, handleKey]);
3896
+ if (!open) return null;
3897
+ async function handleUnlock() {
3898
+ setUnlocking(true);
3899
+ try {
3900
+ await onUnlock();
3901
+ } finally {
3902
+ setUnlocking(false);
3903
+ }
3904
+ }
3905
+ async function handleDisconnect() {
3906
+ setDisconnecting(true);
3907
+ try {
3908
+ await onDisconnect();
3909
+ onClose();
3910
+ } finally {
3911
+ setDisconnecting(false);
3912
+ }
3913
+ }
3914
+ const modal = /* @__PURE__ */ React3.createElement(
3915
+ "div",
3916
+ {
3917
+ role: "dialog",
3918
+ "aria-modal": "true",
3919
+ "aria-label": "Social wallet",
3920
+ style: {
3921
+ position: "fixed",
3922
+ inset: 0,
3923
+ zIndex: 9999,
3924
+ display: "flex",
3925
+ alignItems: "center",
3926
+ justifyContent: "center",
3927
+ padding: 16,
3928
+ fontFamily: "var(--dc-font-sans, system-ui, sans-serif)"
3929
+ }
3930
+ },
3931
+ /* @__PURE__ */ React3.createElement(
3932
+ "div",
3933
+ {
3934
+ onClick: onClose,
3935
+ style: {
3936
+ position: "absolute",
3937
+ inset: 0,
3938
+ background: "var(--dc-bg-overlay)",
3939
+ backdropFilter: "blur(8px)",
3940
+ WebkitBackdropFilter: "blur(8px)"
3941
+ }
3942
+ }
3943
+ ),
3944
+ /* @__PURE__ */ React3.createElement("div", { style: {
3945
+ position: "relative",
3946
+ background: "var(--dc-bg)",
3947
+ border: "1px solid var(--dc-border)",
3948
+ borderRadius: 20,
3949
+ width: "100%",
3950
+ maxWidth: 380,
3951
+ boxShadow: "0 24px 64px rgba(0,0,0,0.6), 0 0 0 1px rgba(255,255,255,0.04)",
3952
+ overflow: "hidden"
3953
+ } }, /* @__PURE__ */ React3.createElement("div", { style: {
3954
+ height: 2,
3955
+ background: "linear-gradient(90deg, transparent, var(--dc-accent) 30%, var(--dc-accent) 70%, transparent)"
3956
+ } }), /* @__PURE__ */ React3.createElement("div", { style: { padding: "18px 20px 16px", display: "flex", alignItems: "center", gap: 10 } }, /* @__PURE__ */ React3.createElement("div", { style: {
3957
+ width: 34,
3958
+ height: 34,
3959
+ borderRadius: 10,
3960
+ background: "var(--dc-surface)",
3961
+ border: "1px solid var(--dc-border)",
3962
+ display: "flex",
3963
+ alignItems: "center",
3964
+ justifyContent: "center",
3965
+ flexShrink: 0
3966
+ } }, /* @__PURE__ */ React3.createElement("svg", { width: "16", height: "18", viewBox: "0 0 16 18", fill: "none" }, /* @__PURE__ */ React3.createElement(
3967
+ "path",
3968
+ {
3969
+ d: "M8 1L1.5 3.5V8.5C1.5 12.09 4.35 15.4 8 16.5C11.65 15.4 14.5 12.09 14.5 8.5V3.5L8 1Z",
3970
+ stroke: "var(--dc-accent)",
3971
+ strokeWidth: "1.4",
3972
+ strokeLinejoin: "round"
3973
+ }
3974
+ ), /* @__PURE__ */ React3.createElement(
3975
+ "path",
3976
+ {
3977
+ d: "M5.5 9L7 10.5L10.5 7",
3978
+ stroke: "var(--dc-accent)",
3979
+ strokeWidth: "1.4",
3980
+ strokeLinecap: "round",
3981
+ strokeLinejoin: "round"
3982
+ }
3983
+ ))), /* @__PURE__ */ React3.createElement("div", { style: { flex: 1 } }, /* @__PURE__ */ React3.createElement("div", { style: { fontSize: 15, fontWeight: 800, color: "var(--dc-text)", letterSpacing: "-0.02em" } }, "Social Wallet"), /* @__PURE__ */ React3.createElement("div", { style: { fontSize: 11, color: "var(--dc-text-muted)", marginTop: 1 } }, "Decane non-custodial \xB7 key split between device & server")), /* @__PURE__ */ React3.createElement(
3984
+ "button",
3985
+ {
3986
+ onClick: onClose,
3987
+ style: {
3988
+ background: "var(--dc-surface)",
3989
+ border: "1px solid var(--dc-border)",
3990
+ borderRadius: 8,
3991
+ width: 28,
3992
+ height: 28,
3993
+ cursor: "pointer",
3994
+ display: "flex",
3995
+ alignItems: "center",
3996
+ justifyContent: "center",
3997
+ color: "var(--dc-text-muted)",
3998
+ flexShrink: 0,
3999
+ padding: 0
4000
+ }
4001
+ },
4002
+ /* @__PURE__ */ React3.createElement("svg", { width: "12", height: "12", viewBox: "0 0 12 12", fill: "none" }, /* @__PURE__ */ React3.createElement("path", { d: "M1.5 1.5L10.5 10.5M10.5 1.5L1.5 10.5", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" }))
4003
+ )), /* @__PURE__ */ React3.createElement("div", { style: {
4004
+ margin: "0 16px 14px",
4005
+ padding: "8px 14px",
4006
+ background: socialAuth.isUnlocked ? "rgba(18,183,106,0.08)" : "rgba(245,200,0,0.08)",
4007
+ border: `1px solid ${socialAuth.isUnlocked ? "rgba(18,183,106,0.2)" : "rgba(245,200,0,0.2)"}`,
4008
+ borderRadius: 10,
4009
+ display: "flex",
4010
+ alignItems: "center",
4011
+ justifyContent: "space-between"
4012
+ } }, /* @__PURE__ */ React3.createElement("div", { style: { display: "flex", alignItems: "center", gap: 8 } }, /* @__PURE__ */ React3.createElement("span", { style: {
4013
+ width: 7,
4014
+ height: 7,
4015
+ borderRadius: "50%",
4016
+ flexShrink: 0,
4017
+ background: socialAuth.isUnlocked ? "#12b76a" : "var(--dc-accent)",
4018
+ boxShadow: `0 0 6px ${socialAuth.isUnlocked ? "#12b76a" : "var(--dc-accent)"}`
4019
+ } }), /* @__PURE__ */ React3.createElement("span", { style: {
4020
+ fontSize: 12,
4021
+ fontWeight: 700,
4022
+ color: socialAuth.isUnlocked ? "#12b76a" : "var(--dc-accent)"
4023
+ } }, socialAuth.isUnlocked ? "Unlocked" : "Locked")), socialAuth.isUnlocked && timer && /* @__PURE__ */ React3.createElement("span", { style: { fontSize: 11, color: "var(--dc-text-muted)", fontFamily: "monospace" } }, "expires ", timer)), /* @__PURE__ */ React3.createElement("div", { style: { padding: "0 16px 16px" } }, socialAuth.addresses?.evm && /* @__PURE__ */ React3.createElement(AddressRow, { label: "EVM (Ethereum, Base, \u2026)", address: socialAuth.addresses.evm }), socialAuth.addresses?.solana && /* @__PURE__ */ React3.createElement(AddressRow, { label: "Solana", address: socialAuth.addresses.solana }), !socialAuth.addresses?.evm && !socialAuth.addresses?.solana && /* @__PURE__ */ React3.createElement("div", { style: { textAlign: "center", padding: "16px 0", color: "var(--dc-text-muted)", fontSize: 13 } }, "Unlock wallet to reveal addresses")), /* @__PURE__ */ React3.createElement("div", { style: {
4024
+ padding: "14px 16px",
4025
+ borderTop: "1px solid var(--dc-border)",
4026
+ display: "flex",
4027
+ gap: 8
4028
+ } }, !socialAuth.isUnlocked && /* @__PURE__ */ React3.createElement(
4029
+ "button",
4030
+ {
4031
+ onClick: handleUnlock,
4032
+ disabled: unlocking,
4033
+ style: {
4034
+ flex: 1,
4035
+ padding: "10px 0",
4036
+ background: "var(--dc-accent)",
4037
+ color: "var(--dc-accent-text)",
4038
+ border: "none",
4039
+ borderRadius: 10,
4040
+ fontSize: 13,
4041
+ fontWeight: 700,
4042
+ cursor: unlocking ? "not-allowed" : "pointer",
4043
+ fontFamily: "inherit",
4044
+ opacity: unlocking ? 0.7 : 1
4045
+ }
4046
+ },
4047
+ unlocking ? "Unlocking\u2026" : "\u{1F513} Unlock wallet"
4048
+ ), /* @__PURE__ */ React3.createElement(
4049
+ "button",
4050
+ {
4051
+ onClick: handleDisconnect,
4052
+ disabled: disconnecting,
4053
+ style: {
4054
+ flex: socialAuth.isUnlocked ? 1 : 0,
4055
+ padding: "10px 16px",
4056
+ background: "transparent",
4057
+ border: "1px solid var(--dc-border)",
4058
+ borderRadius: 10,
4059
+ color: "var(--dc-text-muted)",
4060
+ fontSize: 13,
4061
+ cursor: disconnecting ? "not-allowed" : "pointer",
4062
+ fontFamily: "inherit",
4063
+ opacity: disconnecting ? 0.7 : 1,
4064
+ whiteSpace: "nowrap"
4065
+ }
4066
+ },
4067
+ disconnecting ? "Signing out\u2026" : "Sign out"
4068
+ )))
4069
+ );
4070
+ return createPortal2(modal, document.body);
4071
+ }
4072
+
2407
4073
  // src/ui/context.tsx
2408
- import React3, {
4074
+ import React4, {
2409
4075
  createContext,
2410
- useCallback as useCallback2,
4076
+ useCallback as useCallback3,
2411
4077
  useContext,
4078
+ useEffect as useEffect3,
2412
4079
  useRef,
2413
- useState as useState2
4080
+ useState as useState3
2414
4081
  } from "react";
4082
+ var DEFAULT_SOCIAL = {
4083
+ addresses: null,
4084
+ isNewUser: false,
4085
+ isUnlocked: false,
4086
+ sessionExpiresAt: null
4087
+ };
2415
4088
  var DecaneContext = createContext(null);
4089
+ function hasSocial(mode) {
4090
+ return mode === "all" || mode === "social" || mode === "evm+social" || mode === "solana+social" || mode === "tron+social" || mode === "evm+tron+social";
4091
+ }
4092
+ function deriveNetworks(mode, explicit) {
4093
+ if (explicit) return explicit;
4094
+ if (mode === "evm" || mode === "evm+social") return "evm";
4095
+ if (mode === "solana" || mode === "solana+social") return "solana";
4096
+ if (mode === "tron" || mode === "tron+social") return "tron";
4097
+ return "all";
4098
+ }
2416
4099
  function DecaneKit({ config, children }) {
2417
- const ref = useRef(null);
2418
- if (!ref.current) {
2419
- ref.current = createDecane(config);
2420
- }
2421
- const [selectorOpen, setSelectorOpen] = useState2(false);
2422
- const [theme, setTheme] = useState2(config?.theme ?? "auto");
2423
- const openSelector = useCallback2(() => setSelectorOpen(true), []);
2424
- const closeSelector = useCallback2(() => setSelectorOpen(false), []);
2425
- const handleConnect = useCallback2(
4100
+ const mode = config?.mode ?? "wallets";
4101
+ const walletRef = useRef(null);
4102
+ if (!walletRef.current) walletRef.current = createDecane(config);
4103
+ const socialRef = useRef(null);
4104
+ const socialInitialized = useRef(false);
4105
+ const [selectorOpen, setSelectorOpen] = useState3(false);
4106
+ const [socialModalOpen, setSocialModalOpen] = useState3(false);
4107
+ const [theme, setTheme] = useState3(config?.theme ?? "auto");
4108
+ const [socialAuth, setSocialAuth] = useState3(DEFAULT_SOCIAL);
4109
+ const openSelector = useCallback3(() => setSelectorOpen(true), []);
4110
+ const closeSelector = useCallback3(() => setSelectorOpen(false), []);
4111
+ const openSocialModal = useCallback3(() => setSocialModalOpen(true), []);
4112
+ const closeSocialModal = useCallback3(() => setSocialModalOpen(false), []);
4113
+ useEffect3(() => {
4114
+ if (!socialAuth.isUnlocked) return;
4115
+ const id = setInterval(() => {
4116
+ const sdk = socialRef.current;
4117
+ if (!sdk) return;
4118
+ setSocialAuth((p) => ({
4119
+ ...p,
4120
+ isUnlocked: sdk.isUnlocked(),
4121
+ sessionExpiresAt: sdk.sessionExpiresAt()
4122
+ }));
4123
+ }, 1e4);
4124
+ return () => clearInterval(id);
4125
+ }, [socialAuth.isUnlocked]);
4126
+ useEffect3(() => {
4127
+ if (!hasSocial(mode) || !config?.social || socialInitialized.current) return;
4128
+ socialInitialized.current = true;
4129
+ const { apiKey, authMethods, promptPin, apiBaseUrl, chains, rpcUrls } = config.social;
4130
+ createDecaneConnect({
4131
+ apiKey,
4132
+ apiBaseUrl,
4133
+ authMethods: authMethods ?? ["google", "email"],
4134
+ chains: chains ?? ["evm:1", "solana:mainnet"],
4135
+ rpcUrls,
4136
+ promptPin
4137
+ }).then((sdk) => {
4138
+ socialRef.current = sdk;
4139
+ sdk.on(
4140
+ "session-expired",
4141
+ () => setSocialAuth((p) => ({ ...p, isUnlocked: false, sessionExpiresAt: null }))
4142
+ );
4143
+ sdk.on("disconnected", () => setSocialAuth(DEFAULT_SOCIAL));
4144
+ const saved = sdk.getAddresses();
4145
+ if (saved) {
4146
+ setSocialAuth((p) => ({ ...p, addresses: saved, isUnlocked: false }));
4147
+ }
4148
+ const params = new URLSearchParams(window.location.search);
4149
+ if (params.has("decane_jwt") || params.has("decane_error")) {
4150
+ sdk.connectWithGoogle().then(applyResult).catch(console.error);
4151
+ }
4152
+ }).catch(console.error);
4153
+ }, []);
4154
+ function applyResult(result) {
4155
+ const sdk = socialRef.current;
4156
+ setSocialAuth({
4157
+ addresses: result.addresses,
4158
+ isNewUser: result.isNewUser,
4159
+ isUnlocked: sdk?.isUnlocked() ?? false,
4160
+ sessionExpiresAt: sdk?.sessionExpiresAt() ?? null
4161
+ });
4162
+ }
4163
+ const connectWithGoogle = useCallback3(async () => {
4164
+ const sdk = socialRef.current;
4165
+ if (!sdk) throw new Error("Social sign-in not configured");
4166
+ await sdk.connectWithGoogle();
4167
+ }, []);
4168
+ const connectWithEmail = useCallback3(async (email) => {
4169
+ const sdk = socialRef.current;
4170
+ if (!sdk) throw new Error("Social sign-in not configured");
4171
+ return sdk.connectWithEmail(email);
4172
+ }, []);
4173
+ const verifyEmailCode2 = useCallback3(async (email, code) => {
4174
+ const sdk = socialRef.current;
4175
+ if (!sdk) throw new Error("Social sign-in not configured");
4176
+ const result = await sdk.verifyEmailCode(email, code);
4177
+ applyResult(result);
4178
+ setSelectorOpen(false);
4179
+ }, []);
4180
+ const socialUnlock = useCallback3(async () => {
4181
+ const sdk = socialRef.current;
4182
+ if (!sdk) return;
4183
+ await sdk.unlock();
4184
+ setSocialAuth((p) => ({
4185
+ ...p,
4186
+ isUnlocked: sdk.isUnlocked(),
4187
+ sessionExpiresAt: sdk.sessionExpiresAt()
4188
+ }));
4189
+ }, []);
4190
+ const socialDisconnect = useCallback3(async () => {
4191
+ const sdk = socialRef.current;
4192
+ if (!sdk) return;
4193
+ await sdk.disconnect();
4194
+ setSocialAuth(DEFAULT_SOCIAL);
4195
+ }, []);
4196
+ const socialSignMessage = useCallback3(async (chain, message) => {
4197
+ const sdk = socialRef.current;
4198
+ if (!sdk) throw new Error("Social wallet not connected");
4199
+ return sdk.signMessage({ chain, message });
4200
+ }, []);
4201
+ const socialSendTransaction = useCallback3(async (req) => {
4202
+ const sdk = socialRef.current;
4203
+ if (!sdk) throw new Error("Social wallet not connected");
4204
+ return sdk.sendTransaction({ ...req, chain: req.chain });
4205
+ }, []);
4206
+ const socialSignSolanaTransaction = useCallback3(async (tx) => {
4207
+ const sdk = socialRef.current;
4208
+ if (!sdk) throw new Error("Social wallet not connected");
4209
+ return sdk.signSolanaTransaction(tx);
4210
+ }, []);
4211
+ const handleWalletConnect = useCallback3(
2426
4212
  (wallet, preference) => {
2427
- ref.current.connect(wallet, preference).catch(console.error);
4213
+ walletRef.current.connect(wallet, preference).catch(console.error);
2428
4214
  },
2429
4215
  []
2430
4216
  );
2431
- return /* @__PURE__ */ React3.createElement(
4217
+ const networks = deriveNetworks(mode, config?.networks);
4218
+ return /* @__PURE__ */ React4.createElement(
2432
4219
  DecaneContext.Provider,
2433
4220
  {
2434
- value: { provider: ref.current, selectorOpen, openSelector, closeSelector, theme, setTheme }
4221
+ value: {
4222
+ provider: walletRef.current,
4223
+ selectorOpen,
4224
+ openSelector,
4225
+ closeSelector,
4226
+ theme,
4227
+ setTheme,
4228
+ mode,
4229
+ socialAuth,
4230
+ connectWithGoogle,
4231
+ connectWithEmail,
4232
+ verifyEmailCode: verifyEmailCode2,
4233
+ socialUnlock,
4234
+ socialDisconnect,
4235
+ socialSignMessage,
4236
+ socialSendTransaction,
4237
+ socialSignSolanaTransaction,
4238
+ socialModalOpen,
4239
+ openSocialModal,
4240
+ closeSocialModal
4241
+ }
2435
4242
  },
2436
4243
  children,
2437
- /* @__PURE__ */ React3.createElement(
4244
+ /* @__PURE__ */ React4.createElement(
2438
4245
  WalletSelector,
2439
4246
  {
2440
- provider: ref.current,
4247
+ provider: walletRef.current,
2441
4248
  open: selectorOpen,
2442
4249
  onClose: closeSelector,
2443
- onConnect: handleConnect,
4250
+ onConnect: handleWalletConnect,
4251
+ theme,
4252
+ networks,
4253
+ mode,
4254
+ socialAuthMethods: config?.social?.authMethods ?? ["google", "email"],
4255
+ onGoogleSignIn: connectWithGoogle,
4256
+ onEmailStart: connectWithEmail,
4257
+ onEmailVerify: verifyEmailCode2
4258
+ }
4259
+ ),
4260
+ /* @__PURE__ */ React4.createElement(
4261
+ SocialWalletModal,
4262
+ {
4263
+ open: socialModalOpen,
4264
+ onClose: closeSocialModal,
2444
4265
  theme,
2445
- networks: config?.networks ?? "all"
4266
+ socialAuth,
4267
+ onUnlock: socialUnlock,
4268
+ onDisconnect: socialDisconnect
2446
4269
  }
2447
4270
  )
2448
4271
  );
@@ -2458,7 +4281,169 @@ function useDecaneContext() {
2458
4281
  }
2459
4282
 
2460
4283
  // src/ui/hooks.ts
2461
- import { useState as useState3, useEffect as useEffect2, useCallback as useCallback3 } from "react";
4284
+ import { useState as useState4, useEffect as useEffect4, useCallback as useCallback4 } from "react";
4285
+ function useSocialAuth() {
4286
+ const {
4287
+ socialAuth,
4288
+ connectWithGoogle,
4289
+ connectWithEmail,
4290
+ verifyEmailCode: verifyEmailCode2,
4291
+ socialUnlock,
4292
+ socialDisconnect
4293
+ } = useDecaneContext();
4294
+ const [googleLoading, setGoogleLoading] = useState4(false);
4295
+ const [emailLoading, setEmailLoading] = useState4(false);
4296
+ const [error, setError] = useState4(null);
4297
+ const signInWithGoogle = useCallback4(async () => {
4298
+ setGoogleLoading(true);
4299
+ setError(null);
4300
+ try {
4301
+ await connectWithGoogle();
4302
+ } catch (e) {
4303
+ setError(e.message);
4304
+ setGoogleLoading(false);
4305
+ }
4306
+ }, [connectWithGoogle]);
4307
+ const sendEmailCode = useCallback4(async (email) => {
4308
+ setEmailLoading(true);
4309
+ setError(null);
4310
+ try {
4311
+ await connectWithEmail(email);
4312
+ return { codeRequired: true };
4313
+ } catch (e) {
4314
+ setError(e.message);
4315
+ throw e;
4316
+ } finally {
4317
+ setEmailLoading(false);
4318
+ }
4319
+ }, [connectWithEmail]);
4320
+ const confirmEmailCode = useCallback4(async (email, code) => {
4321
+ setEmailLoading(true);
4322
+ setError(null);
4323
+ try {
4324
+ await verifyEmailCode2(email, code);
4325
+ } catch (e) {
4326
+ setError(e.message);
4327
+ throw e;
4328
+ } finally {
4329
+ setEmailLoading(false);
4330
+ }
4331
+ }, [verifyEmailCode2]);
4332
+ const unlock = useCallback4(async () => {
4333
+ setError(null);
4334
+ try {
4335
+ await socialUnlock();
4336
+ } catch (e) {
4337
+ setError(e.message);
4338
+ throw e;
4339
+ }
4340
+ }, [socialUnlock]);
4341
+ const disconnect = useCallback4(async () => {
4342
+ setError(null);
4343
+ try {
4344
+ await socialDisconnect();
4345
+ } catch (e) {
4346
+ setError(e.message);
4347
+ throw e;
4348
+ }
4349
+ }, [socialDisconnect]);
4350
+ return {
4351
+ isConnected: !!socialAuth.addresses,
4352
+ isUnlocked: socialAuth.isUnlocked,
4353
+ isNewUser: socialAuth.isNewUser,
4354
+ addresses: socialAuth.addresses,
4355
+ sessionExpiresAt: socialAuth.sessionExpiresAt,
4356
+ googleLoading,
4357
+ emailLoading,
4358
+ error,
4359
+ clearError: () => setError(null),
4360
+ signInWithGoogle,
4361
+ sendEmailCode,
4362
+ confirmEmailCode,
4363
+ unlock,
4364
+ disconnect
4365
+ };
4366
+ }
4367
+ function useSocialWallet() {
4368
+ const {
4369
+ socialAuth,
4370
+ socialUnlock,
4371
+ socialDisconnect,
4372
+ socialSignMessage,
4373
+ socialSendTransaction,
4374
+ socialSignSolanaTransaction,
4375
+ openSocialModal,
4376
+ closeSocialModal,
4377
+ socialModalOpen
4378
+ } = useDecaneContext();
4379
+ const [loading, setLoading] = useState4(false);
4380
+ const [error, setError] = useState4(null);
4381
+ async function run(fn) {
4382
+ setLoading(true);
4383
+ setError(null);
4384
+ try {
4385
+ return await fn();
4386
+ } catch (e) {
4387
+ const msg = e.message;
4388
+ setError(msg);
4389
+ throw e;
4390
+ } finally {
4391
+ setLoading(false);
4392
+ }
4393
+ }
4394
+ const signMessage = useCallback4(
4395
+ (chain, message) => run(() => socialSignMessage(chain, message)),
4396
+ [socialSignMessage]
4397
+ );
4398
+ const sendTransaction = useCallback4(
4399
+ (req) => run(() => socialSendTransaction(req)),
4400
+ [socialSendTransaction]
4401
+ );
4402
+ const signSolanaTransaction2 = useCallback4(
4403
+ (tx) => run(() => socialSignSolanaTransaction(tx)),
4404
+ [socialSignSolanaTransaction]
4405
+ );
4406
+ const unlock = useCallback4(async () => {
4407
+ setError(null);
4408
+ try {
4409
+ await socialUnlock();
4410
+ } catch (e) {
4411
+ setError(e.message);
4412
+ throw e;
4413
+ }
4414
+ }, [socialUnlock]);
4415
+ const disconnect = useCallback4(async () => {
4416
+ setError(null);
4417
+ try {
4418
+ await socialDisconnect();
4419
+ } catch (e) {
4420
+ setError(e.message);
4421
+ throw e;
4422
+ }
4423
+ }, [socialDisconnect]);
4424
+ return {
4425
+ // State
4426
+ isConnected: !!socialAuth.addresses,
4427
+ isUnlocked: socialAuth.isUnlocked,
4428
+ isNewUser: socialAuth.isNewUser,
4429
+ addresses: socialAuth.addresses,
4430
+ sessionExpiresAt: socialAuth.sessionExpiresAt,
4431
+ // Modal
4432
+ modalOpen: socialModalOpen,
4433
+ openModal: openSocialModal,
4434
+ closeModal: closeSocialModal,
4435
+ // Auth
4436
+ unlock,
4437
+ disconnect,
4438
+ // Signing
4439
+ signMessage,
4440
+ sendTransaction,
4441
+ signSolanaTransaction: signSolanaTransaction2,
4442
+ loading,
4443
+ error,
4444
+ clearError: () => setError(null)
4445
+ };
4446
+ }
2462
4447
  function toDecaneError(err) {
2463
4448
  if (err instanceof DecaneError) return err;
2464
4449
  const msg = err instanceof Error ? err.message : err?.message != null ? String(err.message) : String(err);
@@ -2466,8 +4451,8 @@ function toDecaneError(err) {
2466
4451
  }
2467
4452
  function useDecane() {
2468
4453
  const { provider, theme, setTheme } = useDecaneContext();
2469
- const [, forceUpdate] = useState3(0);
2470
- useEffect2(() => {
4454
+ const [, forceUpdate] = useState4(0);
4455
+ useEffect4(() => {
2471
4456
  const refresh = () => forceUpdate((n) => n + 1);
2472
4457
  const events = [
2473
4458
  "connect",
@@ -2506,10 +4491,10 @@ function useWalletSelector() {
2506
4491
  }
2507
4492
  function useChainSwitch() {
2508
4493
  const { provider } = useDecaneContext();
2509
- const [, forceUpdate] = useState3(0);
2510
- const [loading, setLoading] = useState3(false);
2511
- const [error, setError] = useState3(null);
2512
- useEffect2(() => {
4494
+ const [, forceUpdate] = useState4(0);
4495
+ const [loading, setLoading] = useState4(false);
4496
+ const [error, setError] = useState4(null);
4497
+ useEffect4(() => {
2513
4498
  const refresh = () => forceUpdate((n) => n + 1);
2514
4499
  provider.on("connect", refresh);
2515
4500
  provider.on("disconnect", refresh);
@@ -2518,7 +4503,7 @@ function useChainSwitch() {
2518
4503
  provider.off("disconnect", refresh);
2519
4504
  };
2520
4505
  }, [provider]);
2521
- const switchChain = useCallback3(
4506
+ const switchChain = useCallback4(
2522
4507
  async (chainId) => {
2523
4508
  setLoading(true);
2524
4509
  setError(null);
@@ -2572,10 +4557,10 @@ function resolveScope(provider, chainId, account) {
2572
4557
  }
2573
4558
  function useSignMessage() {
2574
4559
  const { provider } = useDecaneContext();
2575
- const [signature, setSignature] = useState3(null);
2576
- const [loading, setLoading] = useState3(false);
2577
- const [error, setError] = useState3(null);
2578
- const signMessage = useCallback3(
4560
+ const [signature, setSignature] = useState4(null);
4561
+ const [loading, setLoading] = useState4(false);
4562
+ const [error, setError] = useState4(null);
4563
+ const signMessage = useCallback4(
2579
4564
  async (message, options) => {
2580
4565
  const { chain, account } = resolveScope(
2581
4566
  provider,
@@ -2601,7 +4586,7 @@ function useSignMessage() {
2601
4586
  },
2602
4587
  [provider]
2603
4588
  );
2604
- const reset = useCallback3(() => {
4589
+ const reset = useCallback4(() => {
2605
4590
  setSignature(null);
2606
4591
  setError(null);
2607
4592
  }, []);
@@ -2609,10 +4594,10 @@ function useSignMessage() {
2609
4594
  }
2610
4595
  function useSendTransaction() {
2611
4596
  const { provider } = useDecaneContext();
2612
- const [txHash, setTxHash] = useState3(null);
2613
- const [loading, setLoading] = useState3(false);
2614
- const [error, setError] = useState3(null);
2615
- const sendTransaction = useCallback3(
4597
+ const [txHash, setTxHash] = useState4(null);
4598
+ const [loading, setLoading] = useState4(false);
4599
+ const [error, setError] = useState4(null);
4600
+ const sendTransaction = useCallback4(
2616
4601
  async (tx, options) => {
2617
4602
  const { chain, account } = resolveScope(
2618
4603
  provider,
@@ -2639,7 +4624,7 @@ function useSendTransaction() {
2639
4624
  },
2640
4625
  [provider]
2641
4626
  );
2642
- const reset = useCallback3(() => {
4627
+ const reset = useCallback4(() => {
2643
4628
  setTxHash(null);
2644
4629
  setError(null);
2645
4630
  }, []);
@@ -2647,10 +4632,10 @@ function useSendTransaction() {
2647
4632
  }
2648
4633
  function useSignTypedData() {
2649
4634
  const { provider } = useDecaneContext();
2650
- const [signature, setSignature] = useState3(null);
2651
- const [loading, setLoading] = useState3(false);
2652
- const [error, setError] = useState3(null);
2653
- const signTypedData = useCallback3(
4635
+ const [signature, setSignature] = useState4(null);
4636
+ const [loading, setLoading] = useState4(false);
4637
+ const [error, setError] = useState4(null);
4638
+ const signTypedData = useCallback4(
2654
4639
  async (typedData, options) => {
2655
4640
  const { chain, account } = resolveScope(
2656
4641
  provider,
@@ -2676,25 +4661,47 @@ function useSignTypedData() {
2676
4661
  },
2677
4662
  [provider]
2678
4663
  );
2679
- const reset = useCallback3(() => {
4664
+ const reset = useCallback4(() => {
2680
4665
  setSignature(null);
2681
4666
  setError(null);
2682
4667
  }, []);
2683
4668
  return { signTypedData, signature, loading, error, reset };
2684
4669
  }
2685
4670
  export {
4671
+ AttestationError,
2686
4672
  BUILT_IN_CHAINS,
2687
4673
  CHAINS,
4674
+ CallSequenceError,
4675
+ DecaneConnectError,
2688
4676
  DecaneError,
2689
4677
  DecaneErrorCode,
2690
4678
  DecaneKit,
4679
+ InvalidApiKeyError,
4680
+ InvalidPhraseError,
4681
+ InvalidPinError,
4682
+ NetworkError,
4683
+ NewDeviceError,
4684
+ OriginNotAllowedError,
4685
+ PromptPinNotConfiguredError,
4686
+ RecoveryFailedError,
4687
+ SessionExpiredError,
4688
+ ShareCorruptedError,
4689
+ SocialWalletModal,
4690
+ UnlockRequiredError,
4691
+ UserCancelledError,
2691
4692
  WalletSelector,
4693
+ WebAuthnNotSupportedError,
2692
4694
  createDecane,
4695
+ createDecaneConnect,
4696
+ startEmailAuth,
2693
4697
  useChainSwitch,
2694
4698
  useDecane,
2695
4699
  useSendTransaction,
2696
4700
  useSignMessage,
2697
4701
  useSignTypedData,
2698
- useWalletSelector
4702
+ useSocialAuth,
4703
+ useSocialWallet,
4704
+ useWalletSelector,
4705
+ verifyEmailCode
2699
4706
  };
2700
4707
  //# sourceMappingURL=index.js.map