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