decane-connect-kit 1.0.2 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +2600 -196
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +399 -2
- package/dist/index.d.ts +399 -2
- package/dist/index.js +2582 -195
- package/dist/index.js.map +1 -1
- package/package.json +15 -7
package/dist/index.cjs
CHANGED
|
@@ -5,6 +5,9 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
|
5
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
6
|
var __getProtoOf = Object.getPrototypeOf;
|
|
7
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __esm = (fn, res) => function __init() {
|
|
9
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
10
|
+
};
|
|
8
11
|
var __export = (target, all) => {
|
|
9
12
|
for (var name in all)
|
|
10
13
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
@@ -27,22 +30,179 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
27
30
|
));
|
|
28
31
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
32
|
|
|
33
|
+
// src/social/errors.ts
|
|
34
|
+
var errors_exports = {};
|
|
35
|
+
__export(errors_exports, {
|
|
36
|
+
AttestationError: () => AttestationError,
|
|
37
|
+
CallSequenceError: () => CallSequenceError,
|
|
38
|
+
DecaneConnectError: () => DecaneConnectError,
|
|
39
|
+
ExportNotAuthorizedError: () => ExportNotAuthorizedError,
|
|
40
|
+
InvalidApiKeyError: () => InvalidApiKeyError,
|
|
41
|
+
InvalidPinError: () => InvalidPinError,
|
|
42
|
+
InvalidRecoveryFileError: () => InvalidRecoveryFileError,
|
|
43
|
+
NetworkError: () => NetworkError,
|
|
44
|
+
NewDeviceError: () => NewDeviceError,
|
|
45
|
+
OriginNotAllowedError: () => OriginNotAllowedError,
|
|
46
|
+
PromptPinNotConfiguredError: () => PromptPinNotConfiguredError,
|
|
47
|
+
RecoveryFailedError: () => RecoveryFailedError,
|
|
48
|
+
RecoveryWalletMismatchError: () => RecoveryWalletMismatchError,
|
|
49
|
+
SessionExpiredError: () => SessionExpiredError,
|
|
50
|
+
ShareCorruptedError: () => ShareCorruptedError,
|
|
51
|
+
UnlockRequiredError: () => UnlockRequiredError,
|
|
52
|
+
UserCancelledError: () => UserCancelledError,
|
|
53
|
+
WebAuthnNotSupportedError: () => WebAuthnNotSupportedError,
|
|
54
|
+
WrongPasswordError: () => WrongPasswordError
|
|
55
|
+
});
|
|
56
|
+
var DecaneConnectError, InvalidApiKeyError, OriginNotAllowedError, SessionExpiredError, UnlockRequiredError, UserCancelledError, WebAuthnNotSupportedError, InvalidPinError, CallSequenceError, NetworkError, ShareCorruptedError, NewDeviceError, PromptPinNotConfiguredError, AttestationError, RecoveryFailedError, InvalidRecoveryFileError, WrongPasswordError, RecoveryWalletMismatchError, ExportNotAuthorizedError;
|
|
57
|
+
var init_errors = __esm({
|
|
58
|
+
"src/social/errors.ts"() {
|
|
59
|
+
"use strict";
|
|
60
|
+
DecaneConnectError = class extends Error {
|
|
61
|
+
constructor(code, message, retryable = false) {
|
|
62
|
+
super(message);
|
|
63
|
+
this.name = "DecaneConnectError";
|
|
64
|
+
this.code = code;
|
|
65
|
+
this.retryable = retryable;
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
InvalidApiKeyError = class extends DecaneConnectError {
|
|
69
|
+
constructor() {
|
|
70
|
+
super("INVALID_API_KEY", "API key is invalid or revoked");
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
OriginNotAllowedError = class extends DecaneConnectError {
|
|
74
|
+
constructor(origin) {
|
|
75
|
+
super("ORIGIN_NOT_ALLOWED", `Origin ${origin} not in API key allowlist`);
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
SessionExpiredError = class extends DecaneConnectError {
|
|
79
|
+
constructor() {
|
|
80
|
+
super("SESSION_EXPIRED", "Session has expired \u2014 call unlock()", true);
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
UnlockRequiredError = class extends DecaneConnectError {
|
|
84
|
+
constructor() {
|
|
85
|
+
super("UNLOCK_REQUIRED", "Wallet must be unlocked before signing", true);
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
UserCancelledError = class extends DecaneConnectError {
|
|
89
|
+
constructor() {
|
|
90
|
+
super("USER_CANCELLED", "User cancelled the operation", true);
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
WebAuthnNotSupportedError = class extends DecaneConnectError {
|
|
94
|
+
constructor() {
|
|
95
|
+
super("WEBAUTHN_UNSUPPORTED", "This browser does not support WebAuthn");
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
InvalidPinError = class extends DecaneConnectError {
|
|
99
|
+
constructor() {
|
|
100
|
+
super("INVALID_PIN", "Incorrect PIN", true);
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
CallSequenceError = class extends DecaneConnectError {
|
|
104
|
+
constructor(msg) {
|
|
105
|
+
super("CALL_SEQUENCE", msg);
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
NetworkError = class extends DecaneConnectError {
|
|
109
|
+
constructor() {
|
|
110
|
+
super("NETWORK", "Network request failed", true);
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
ShareCorruptedError = class extends DecaneConnectError {
|
|
114
|
+
constructor() {
|
|
115
|
+
super("SHARE_CORRUPTED", "Stored device share is corrupted");
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
NewDeviceError = class extends DecaneConnectError {
|
|
119
|
+
constructor() {
|
|
120
|
+
super("NEW_DEVICE", "No device share found. Re-authenticate to set up this device.");
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
PromptPinNotConfiguredError = class extends DecaneConnectError {
|
|
124
|
+
constructor() {
|
|
125
|
+
super("PROMPT_PIN_NOT_CONFIGURED", "Passkey unavailable and no promptPin callback was provided in config");
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
AttestationError = class extends DecaneConnectError {
|
|
129
|
+
constructor(msg) {
|
|
130
|
+
super("ATTESTATION_FAILED", msg);
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
RecoveryFailedError = class extends DecaneConnectError {
|
|
134
|
+
constructor(reason) {
|
|
135
|
+
super("RECOVERY_FAILED", `Recovery failed: ${reason}`, true);
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
InvalidRecoveryFileError = class extends DecaneConnectError {
|
|
139
|
+
constructor() {
|
|
140
|
+
super("INVALID_RECOVERY_FILE", "This does not look like a valid recovery file", false);
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
WrongPasswordError = class extends DecaneConnectError {
|
|
144
|
+
constructor() {
|
|
145
|
+
super("WRONG_PASSWORD", "Incorrect password for this recovery file", true);
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
RecoveryWalletMismatchError = class extends DecaneConnectError {
|
|
149
|
+
constructor() {
|
|
150
|
+
super(
|
|
151
|
+
"RECOVERY_WALLET_MISMATCH",
|
|
152
|
+
"This recovery file is for a different wallet than the account you are signed into",
|
|
153
|
+
false
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
ExportNotAuthorizedError = class extends DecaneConnectError {
|
|
158
|
+
constructor() {
|
|
159
|
+
super("EXPORT_NOT_AUTHORIZED", "Export requires a fresh security check", true);
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
|
|
30
165
|
// src/index.ts
|
|
31
166
|
var index_exports = {};
|
|
32
167
|
__export(index_exports, {
|
|
168
|
+
AttestationError: () => AttestationError,
|
|
33
169
|
BUILT_IN_CHAINS: () => BUILT_IN_CHAINS,
|
|
34
170
|
CHAINS: () => CHAINS,
|
|
171
|
+
CallSequenceError: () => CallSequenceError,
|
|
172
|
+
DecaneConnectError: () => DecaneConnectError,
|
|
35
173
|
DecaneError: () => DecaneError,
|
|
36
174
|
DecaneErrorCode: () => DecaneErrorCode,
|
|
37
175
|
DecaneKit: () => DecaneKit,
|
|
176
|
+
ExportNotAuthorizedError: () => ExportNotAuthorizedError,
|
|
177
|
+
InvalidApiKeyError: () => InvalidApiKeyError,
|
|
178
|
+
InvalidPinError: () => InvalidPinError,
|
|
179
|
+
InvalidRecoveryFileError: () => InvalidRecoveryFileError,
|
|
180
|
+
NetworkError: () => NetworkError,
|
|
181
|
+
NewDeviceError: () => NewDeviceError,
|
|
182
|
+
OriginNotAllowedError: () => OriginNotAllowedError,
|
|
183
|
+
PromptPinNotConfiguredError: () => PromptPinNotConfiguredError,
|
|
184
|
+
RecoveryFailedError: () => RecoveryFailedError,
|
|
185
|
+
RecoveryWalletMismatchError: () => RecoveryWalletMismatchError,
|
|
186
|
+
SessionExpiredError: () => SessionExpiredError,
|
|
187
|
+
ShareCorruptedError: () => ShareCorruptedError,
|
|
188
|
+
SocialWalletModal: () => SocialWalletModal,
|
|
189
|
+
UnlockRequiredError: () => UnlockRequiredError,
|
|
190
|
+
UserCancelledError: () => UserCancelledError,
|
|
38
191
|
WalletSelector: () => WalletSelector,
|
|
192
|
+
WebAuthnNotSupportedError: () => WebAuthnNotSupportedError,
|
|
193
|
+
WrongPasswordError: () => WrongPasswordError,
|
|
39
194
|
createDecane: () => createDecane,
|
|
195
|
+
createDecaneConnect: () => createDecaneConnect,
|
|
196
|
+
startEmailAuth: () => startEmailAuth,
|
|
40
197
|
useChainSwitch: () => useChainSwitch,
|
|
41
198
|
useDecane: () => useDecane,
|
|
42
199
|
useSendTransaction: () => useSendTransaction,
|
|
43
200
|
useSignMessage: () => useSignMessage,
|
|
44
201
|
useSignTypedData: () => useSignTypedData,
|
|
45
|
-
|
|
202
|
+
useSocialAuth: () => useSocialAuth,
|
|
203
|
+
useSocialWallet: () => useSocialWallet,
|
|
204
|
+
useWalletSelector: () => useWalletSelector,
|
|
205
|
+
verifyEmailCode: () => verifyEmailCode
|
|
46
206
|
});
|
|
47
207
|
module.exports = __toCommonJS(index_exports);
|
|
48
208
|
|
|
@@ -1718,6 +1878,7 @@ function startSolanaDiscovery(registry) {
|
|
|
1718
1878
|
var TRONLINK_RDNS = "link.tron.tronlink";
|
|
1719
1879
|
var TRONLINK_ICON = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAzMiAzMiI+PGNpcmNsZSBjeD0iMTYiIGN5PSIxNiIgcj0iMTYiIGZpbGw9IiNGRjAwMTMiLz48cG9seWdvbiBwb2ludHM9IjEyLDkgMjMsMTYgMTIsMjMiIGZpbGw9IndoaXRlIi8+PC9zdmc+";
|
|
1720
1880
|
function startTronDiscovery(registry) {
|
|
1881
|
+
if (typeof window === "undefined") return;
|
|
1721
1882
|
let registered = false;
|
|
1722
1883
|
function tryRegister() {
|
|
1723
1884
|
if (registered || !window.tronLink) return;
|
|
@@ -1742,6 +1903,7 @@ function startTronDiscovery(registry) {
|
|
|
1742
1903
|
// src/bitcoin/discovery.ts
|
|
1743
1904
|
var BTC_ICON = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAzMiAzMiI+PGNpcmNsZSBjeD0iMTYiIGN5PSIxNiIgcj0iMTYiIGZpbGw9IiNGNzkzMUEiLz48dGV4dCB4PSIxNi41IiB5PSIyMiIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZm9udC1zaXplPSIxNiIgZm9udC13ZWlnaHQ9IjcwMCIgZmlsbD0id2hpdGUiIGZvbnQtZmFtaWx5PSJBcmlhbCxzeXN0ZW0tdWkiPuKmojwvdGV4dD48L3N2Zz4=";
|
|
1744
1905
|
function startBitcoinDiscovery(registry) {
|
|
1906
|
+
if (typeof window === "undefined") return;
|
|
1745
1907
|
setTimeout(() => {
|
|
1746
1908
|
const candidates = [
|
|
1747
1909
|
{ name: "Unisat", rdns: "io.unisat.wallet", provider: window.unisat },
|
|
@@ -1779,160 +1941,1560 @@ function createDecane(config = {}) {
|
|
|
1779
1941
|
return createProvider({ config, registry, session, chainStore, router, emitter });
|
|
1780
1942
|
}
|
|
1781
1943
|
|
|
1782
|
-
// src/
|
|
1783
|
-
var
|
|
1784
|
-
var
|
|
1944
|
+
// src/social/client.ts
|
|
1945
|
+
var import_ethers2 = require("ethers");
|
|
1946
|
+
var import_web32 = require("@solana/web3.js");
|
|
1947
|
+
init_errors();
|
|
1785
1948
|
|
|
1786
|
-
// src/
|
|
1787
|
-
var
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
700: "#7A5E00",
|
|
1801
|
-
800: "#4D3B00",
|
|
1802
|
-
900: "#261D00"
|
|
1803
|
-
},
|
|
1804
|
-
// ── Black / dark palette ──────────────────────────────────────────────────
|
|
1805
|
-
black: {
|
|
1806
|
-
0: "#FFFFFF",
|
|
1807
|
-
50: "#F9F9F7",
|
|
1808
|
-
100: "#F0EFEB",
|
|
1809
|
-
200: "#DDDCDA",
|
|
1810
|
-
300: "#BDBBB8",
|
|
1811
|
-
400: "#8A8884",
|
|
1812
|
-
500: "#5C5A57",
|
|
1813
|
-
600: "#3D3C39",
|
|
1814
|
-
700: "#2A2927",
|
|
1815
|
-
800: "#1A1917",
|
|
1816
|
-
900: "#0D0D0B",
|
|
1817
|
-
950: "#070706"
|
|
1818
|
-
},
|
|
1819
|
-
// ── Semantic tokens — light mode ──────────────────────────────────────────
|
|
1820
|
-
light: {
|
|
1821
|
-
bg: "#FFFFFF",
|
|
1822
|
-
bgOverlay: "rgba(0, 0, 0, 0.55)",
|
|
1823
|
-
surface: "#F9F9F7",
|
|
1824
|
-
surfaceHover: "#F0EFEB",
|
|
1825
|
-
border: "#DDDCDA",
|
|
1826
|
-
borderAccent: "#F5C800",
|
|
1827
|
-
text: "#0D0D0B",
|
|
1828
|
-
textSecondary: "#5C5A57",
|
|
1829
|
-
textMuted: "#8A8884",
|
|
1830
|
-
accent: "#F5C800",
|
|
1831
|
-
accentHover: "#D4A800",
|
|
1832
|
-
accentText: "#0D0D0B",
|
|
1833
|
-
badgeEvm: "#1A1917",
|
|
1834
|
-
badgeEvmText: "#F5C800",
|
|
1835
|
-
badgeSolana: "#F5C800",
|
|
1836
|
-
badgeSolanaText: "#0D0D0B",
|
|
1837
|
-
danger: "#CC3300"
|
|
1838
|
-
},
|
|
1839
|
-
// ── Semantic tokens — dark mode ───────────────────────────────────────────
|
|
1840
|
-
dark: {
|
|
1841
|
-
bg: "#0D0D0B",
|
|
1842
|
-
bgOverlay: "rgba(0, 0, 0, 0.75)",
|
|
1843
|
-
surface: "#1A1917",
|
|
1844
|
-
surfaceHover: "#2A2927",
|
|
1845
|
-
border: "#3D3C39",
|
|
1846
|
-
borderAccent: "#F5C800",
|
|
1847
|
-
text: "#F9F9F7",
|
|
1848
|
-
textSecondary: "#BDBBB8",
|
|
1849
|
-
textMuted: "#8A8884",
|
|
1850
|
-
accent: "#F5C800",
|
|
1851
|
-
accentHover: "#FFE04D",
|
|
1852
|
-
accentText: "#0D0D0B",
|
|
1853
|
-
badgeEvm: "#F5C800",
|
|
1854
|
-
badgeEvmText: "#0D0D0B",
|
|
1855
|
-
badgeSolana: "#1A1917",
|
|
1856
|
-
badgeSolanaText: "#F5C800",
|
|
1857
|
-
danger: "#FF5533"
|
|
1949
|
+
// src/social/events.ts
|
|
1950
|
+
var EventEmitter = class {
|
|
1951
|
+
constructor() {
|
|
1952
|
+
this._listeners = /* @__PURE__ */ new Map();
|
|
1953
|
+
}
|
|
1954
|
+
emit(event, ...args) {
|
|
1955
|
+
this._listeners.get(event)?.forEach((h) => h(...args));
|
|
1956
|
+
}
|
|
1957
|
+
on(event, handler) {
|
|
1958
|
+
if (!this._listeners.has(event)) this._listeners.set(event, /* @__PURE__ */ new Set());
|
|
1959
|
+
this._listeners.get(event).add(handler);
|
|
1960
|
+
}
|
|
1961
|
+
off(event, handler) {
|
|
1962
|
+
this._listeners.get(event)?.delete(handler);
|
|
1858
1963
|
}
|
|
1859
1964
|
};
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1965
|
+
|
|
1966
|
+
// src/social/api.ts
|
|
1967
|
+
init_errors();
|
|
1968
|
+
var ERROR_MAP = {
|
|
1969
|
+
INVALID_API_KEY: InvalidApiKeyError,
|
|
1970
|
+
ORIGIN_NOT_ALLOWED: OriginNotAllowedError
|
|
1971
|
+
};
|
|
1972
|
+
var ApiClient = class {
|
|
1973
|
+
constructor(baseUrl, apiKey) {
|
|
1974
|
+
this.baseUrl = baseUrl;
|
|
1975
|
+
this.apiKey = apiKey;
|
|
1976
|
+
}
|
|
1977
|
+
async _request(path, init) {
|
|
1978
|
+
let res;
|
|
1979
|
+
try {
|
|
1980
|
+
res = await fetch(`${this.baseUrl}${path}`, {
|
|
1981
|
+
...init,
|
|
1982
|
+
headers: {
|
|
1983
|
+
"Content-Type": "application/json",
|
|
1984
|
+
"X-API-Key": this.apiKey,
|
|
1985
|
+
...init?.headers
|
|
1986
|
+
}
|
|
1987
|
+
});
|
|
1988
|
+
} catch {
|
|
1989
|
+
throw new NetworkError();
|
|
1990
|
+
}
|
|
1991
|
+
if (res.status === 404 && init?.allow404) return null;
|
|
1992
|
+
if (!res.ok) {
|
|
1993
|
+
const body = await res.json().catch(() => ({}));
|
|
1994
|
+
const code = body?.error?.code ?? "UNKNOWN";
|
|
1995
|
+
const msg = body?.error?.message ?? `Request failed: ${res.status}`;
|
|
1996
|
+
const Ctor = ERROR_MAP[code];
|
|
1997
|
+
if (Ctor) throw new Ctor(msg);
|
|
1998
|
+
throw new Error(msg);
|
|
1999
|
+
}
|
|
2000
|
+
return res.json();
|
|
2001
|
+
}
|
|
2002
|
+
async getGoogleInitUrl() {
|
|
2003
|
+
return this._request("/auth/google/init", { method: "GET" });
|
|
2004
|
+
}
|
|
2005
|
+
async authEmailStart(email) {
|
|
2006
|
+
await this._request("/auth/email/start", {
|
|
2007
|
+
method: "POST",
|
|
2008
|
+
body: JSON.stringify({ email })
|
|
2009
|
+
});
|
|
2010
|
+
}
|
|
2011
|
+
async authEmailVerify(email, code) {
|
|
2012
|
+
return this._request("/auth/email/verify", {
|
|
2013
|
+
method: "POST",
|
|
2014
|
+
body: JSON.stringify({ email, code })
|
|
2015
|
+
});
|
|
2016
|
+
}
|
|
2017
|
+
// GET /share (the server share itself) was removed as part of the TEE
|
|
2018
|
+
// migration — the browser must never reconstruct the private key anymore.
|
|
2019
|
+
// Only public addresses are fetched from the backend now.
|
|
2020
|
+
async getAddresses(jwt) {
|
|
2021
|
+
return this._request("/share/addresses", {
|
|
2022
|
+
headers: { Authorization: `Bearer ${jwt}` }
|
|
2023
|
+
});
|
|
2024
|
+
}
|
|
2025
|
+
async putShare(jwt, share) {
|
|
2026
|
+
await this._request("/share", {
|
|
2027
|
+
method: "POST",
|
|
2028
|
+
headers: { Authorization: `Bearer ${jwt}` },
|
|
2029
|
+
body: JSON.stringify({
|
|
2030
|
+
ciphertext: bytesToB64(share.ciphertext),
|
|
2031
|
+
iv: bytesToB64(share.iv),
|
|
2032
|
+
wrappedShareMeta: btoa(JSON.stringify(share.wrappedShareMeta)),
|
|
2033
|
+
evmAddress: share.evmAddress,
|
|
2034
|
+
solanaAddress: share.solanaAddress
|
|
2035
|
+
})
|
|
2036
|
+
});
|
|
2037
|
+
}
|
|
2038
|
+
// ── WebAuthn registration (additive to the PRF-only passkey flow) ─────────
|
|
2039
|
+
async getWebAuthnRegisterOptions(jwt, rpId, rpName) {
|
|
2040
|
+
return this._request(`/webauthn/register-options?rpId=${encodeURIComponent(rpId)}&rpName=${encodeURIComponent(rpName)}`, {
|
|
2041
|
+
headers: { Authorization: `Bearer ${jwt}` }
|
|
2042
|
+
});
|
|
2043
|
+
}
|
|
2044
|
+
async postWebAuthnRegister(jwt, response, origin) {
|
|
2045
|
+
await this._request("/webauthn/register", {
|
|
2046
|
+
method: "POST",
|
|
2047
|
+
headers: { Authorization: `Bearer ${jwt}` },
|
|
2048
|
+
body: JSON.stringify({ response, origin })
|
|
2049
|
+
});
|
|
2050
|
+
}
|
|
2051
|
+
async revoke(jwt) {
|
|
2052
|
+
await this._request("/auth/revoke", {
|
|
2053
|
+
method: "POST",
|
|
2054
|
+
headers: { Authorization: `Bearer ${jwt}` }
|
|
2055
|
+
});
|
|
2056
|
+
}
|
|
2057
|
+
// ── Recovery (passkey-sync backup + recovery-share-saved flag) ────────────
|
|
2058
|
+
async hasRecoveryBackup(jwt) {
|
|
2059
|
+
const data = await this._request("/recovery/backup/exists", {
|
|
2060
|
+
headers: { Authorization: `Bearer ${jwt}` }
|
|
2061
|
+
});
|
|
2062
|
+
return data.exists;
|
|
2063
|
+
}
|
|
2064
|
+
async getRecoveryBackup(jwt) {
|
|
2065
|
+
const data = await this._request("/recovery/backup", {
|
|
2066
|
+
headers: { Authorization: `Bearer ${jwt}` },
|
|
2067
|
+
allow404: true
|
|
2068
|
+
});
|
|
2069
|
+
if (!data) return null;
|
|
2070
|
+
return {
|
|
2071
|
+
wrappedDeviceShare: b64ToBytes(data.wrappedDeviceShare),
|
|
2072
|
+
iv: b64ToBytes(data.iv),
|
|
2073
|
+
unlockMethod: data.unlockMethod,
|
|
2074
|
+
credentialId: b64ToBytes(data.credentialId),
|
|
2075
|
+
prfSalt: b64ToBytes(data.prfSalt),
|
|
2076
|
+
formatVersion: data.formatVersion
|
|
2077
|
+
};
|
|
2078
|
+
}
|
|
2079
|
+
async putRecoveryBackup(jwt, backup) {
|
|
2080
|
+
await this._request("/recovery/backup", {
|
|
2081
|
+
method: "PUT",
|
|
2082
|
+
headers: { Authorization: `Bearer ${jwt}` },
|
|
2083
|
+
body: JSON.stringify({
|
|
2084
|
+
wrappedDeviceShare: bytesToB64(backup.wrappedDeviceShare),
|
|
2085
|
+
iv: bytesToB64(backup.iv),
|
|
2086
|
+
unlockMethod: backup.unlockMethod,
|
|
2087
|
+
credentialId: bytesToB64(backup.credentialId),
|
|
2088
|
+
prfSalt: bytesToB64(backup.prfSalt)
|
|
2089
|
+
})
|
|
2090
|
+
});
|
|
2091
|
+
}
|
|
2092
|
+
async deleteRecoveryBackup(jwt) {
|
|
2093
|
+
await this._request("/recovery/backup", {
|
|
2094
|
+
method: "DELETE",
|
|
2095
|
+
headers: { Authorization: `Bearer ${jwt}` }
|
|
2096
|
+
});
|
|
2097
|
+
}
|
|
2098
|
+
async markRecoveryShareSaved(jwt) {
|
|
2099
|
+
await this._request("/recovery/share-saved", {
|
|
2100
|
+
method: "POST",
|
|
2101
|
+
headers: { Authorization: `Bearer ${jwt}` }
|
|
2102
|
+
});
|
|
2103
|
+
}
|
|
2104
|
+
async getRecoveryStatus(jwt) {
|
|
2105
|
+
return this._request("/recovery/status", {
|
|
2106
|
+
headers: { Authorization: `Bearer ${jwt}` }
|
|
2107
|
+
});
|
|
2108
|
+
}
|
|
2109
|
+
async dismissRecoveryNag(jwt) {
|
|
2110
|
+
await this._request("/recovery/nag-dismissed", {
|
|
2111
|
+
method: "POST",
|
|
2112
|
+
headers: { Authorization: `Bearer ${jwt}` }
|
|
2113
|
+
});
|
|
2114
|
+
}
|
|
1905
2115
|
};
|
|
2116
|
+
function bytesToB64(bytes) {
|
|
2117
|
+
return btoa(String.fromCharCode(...bytes));
|
|
2118
|
+
}
|
|
2119
|
+
function b64ToBytes(b644) {
|
|
2120
|
+
return Uint8Array.from(atob(b644), (c) => c.charCodeAt(0));
|
|
2121
|
+
}
|
|
1906
2122
|
|
|
1907
|
-
// src/
|
|
1908
|
-
var
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
width: size,
|
|
1917
|
-
height: size,
|
|
1918
|
-
style: { borderRadius: size * 0.25, objectFit: "cover", flexShrink: 0 }
|
|
2123
|
+
// src/social/storage.ts
|
|
2124
|
+
var import_idb = require("idb");
|
|
2125
|
+
var DB_NAME = "decane-connect-kit";
|
|
2126
|
+
var STORE = "shares";
|
|
2127
|
+
var Storage = class {
|
|
2128
|
+
constructor() {
|
|
2129
|
+
this._db = (0, import_idb.openDB)(DB_NAME, 1, {
|
|
2130
|
+
upgrade(db) {
|
|
2131
|
+
db.createObjectStore(STORE, { keyPath: "userId" });
|
|
1919
2132
|
}
|
|
2133
|
+
});
|
|
2134
|
+
}
|
|
2135
|
+
async getShare(userId) {
|
|
2136
|
+
return (await this._db).get(STORE, userId) ?? null;
|
|
2137
|
+
}
|
|
2138
|
+
async putShare(share) {
|
|
2139
|
+
await (await this._db).put(STORE, share);
|
|
2140
|
+
}
|
|
2141
|
+
async deleteShare(userId) {
|
|
2142
|
+
await (await this._db).delete(STORE, userId);
|
|
2143
|
+
}
|
|
2144
|
+
};
|
|
2145
|
+
|
|
2146
|
+
// src/social/teeClient.ts
|
|
2147
|
+
var import_browser = require("@simplewebauthn/browser");
|
|
2148
|
+
|
|
2149
|
+
// src/social/attestation.ts
|
|
2150
|
+
var import_dcap_qvl = require("@phala/dcap-qvl");
|
|
2151
|
+
init_errors();
|
|
2152
|
+
|
|
2153
|
+
// ../../infra/tee-measurement.json
|
|
2154
|
+
var tee_measurement_default = {
|
|
2155
|
+
signerVersion: "0.1.3",
|
|
2156
|
+
acceptedMrtd: [
|
|
2157
|
+
'["68102e7b524af310f7b7d426ce75481e36c40f5d513a9009c046e9d37e31551f0134d954b496a3357fd61d03f07ffe96","07e6f51aa763abfe75c3ddfbf4f425fe3f0ceff66d807a75e049303dce9addf68e7218729bd419638af63a370f65878c","a2a58c9a959a4fa44bd6da0c97a2270c051faf12084cfe91ae900e4fdff6cdd4f69a82005e04ee920f231497894d677f","a76a81147b7f733cb400914c8791ec149bb3a31f3727b1c1e12ec22e9c1a183b6e617e78636dca37075f5a695afd46c2"]'
|
|
2158
|
+
],
|
|
2159
|
+
primaryMrtd: '["68102e7b524af310f7b7d426ce75481e36c40f5d513a9009c046e9d37e31551f0134d954b496a3357fd61d03f07ffe96","07e6f51aa763abfe75c3ddfbf4f425fe3f0ceff66d807a75e049303dce9addf68e7218729bd419638af63a370f65878c","a2a58c9a959a4fa44bd6da0c97a2270c051faf12084cfe91ae900e4fdff6cdd4f69a82005e04ee920f231497894d677f","a76a81147b7f733cb400914c8791ec149bb3a31f3727b1c1e12ec22e9c1a183b6e617e78636dca37075f5a695afd46c2"]',
|
|
2160
|
+
capturedAt: "2026-07-20T10:08:58.717Z",
|
|
2161
|
+
tcbStatus: "UpToDate",
|
|
2162
|
+
_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."
|
|
2163
|
+
};
|
|
2164
|
+
|
|
2165
|
+
// src/social/attestation.ts
|
|
2166
|
+
function hexToBytes(hex) {
|
|
2167
|
+
const clean = hex.startsWith("0x") ? hex.slice(2) : hex;
|
|
2168
|
+
const bytes = new Uint8Array(Math.floor(clean.length / 2));
|
|
2169
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
2170
|
+
bytes[i] = parseInt(clean.substring(i * 2, i * 2 + 2), 16);
|
|
2171
|
+
}
|
|
2172
|
+
return bytes;
|
|
2173
|
+
}
|
|
2174
|
+
function bytesToHex(bytes) {
|
|
2175
|
+
return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
2176
|
+
}
|
|
2177
|
+
async function verifyTeeAttestation(teeUrl, nonce) {
|
|
2178
|
+
const res = await fetch(`${teeUrl}/attestation`, {
|
|
2179
|
+
method: "POST",
|
|
2180
|
+
headers: { "Content-Type": "application/json" },
|
|
2181
|
+
body: JSON.stringify({ nonce })
|
|
2182
|
+
});
|
|
2183
|
+
if (!res.ok) throw new AttestationError("quote fetch failed");
|
|
2184
|
+
const { quote, measurement } = await res.json();
|
|
2185
|
+
let verified;
|
|
2186
|
+
try {
|
|
2187
|
+
verified = await (0, import_dcap_qvl.getCollateralAndVerify)(hexToBytes(quote));
|
|
2188
|
+
} catch {
|
|
2189
|
+
throw new AttestationError("quote signature invalid");
|
|
2190
|
+
}
|
|
2191
|
+
if (verified.status === "Revoked") {
|
|
2192
|
+
throw new AttestationError("TEE platform TCB status is Revoked");
|
|
2193
|
+
}
|
|
2194
|
+
const td = verified.report.asTd10() ?? verified.report.asTd15()?.base;
|
|
2195
|
+
if (!td) throw new AttestationError("quote is not a TDX report");
|
|
2196
|
+
const reportDataHex = bytesToHex(td.reportData);
|
|
2197
|
+
if (!reportDataHex.toLowerCase().startsWith(nonce.toLowerCase())) {
|
|
2198
|
+
throw new AttestationError("quote nonce mismatch");
|
|
2199
|
+
}
|
|
2200
|
+
const accepted = tee_measurement_default.acceptedMrtd.map((m) => m.toLowerCase());
|
|
2201
|
+
if (!accepted.includes(String(measurement).toLowerCase())) {
|
|
2202
|
+
throw new AttestationError(
|
|
2203
|
+
`TEE measurement not accepted. Got ${measurement}, expected one of ${accepted.join(", ") || "(none configured yet)"}`
|
|
1920
2204
|
);
|
|
1921
2205
|
}
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
2206
|
+
}
|
|
2207
|
+
|
|
2208
|
+
// src/social/teeClient.ts
|
|
2209
|
+
init_errors();
|
|
2210
|
+
function bytesToBase64(b) {
|
|
2211
|
+
let s = "";
|
|
2212
|
+
for (const byte of b) s += String.fromCharCode(byte);
|
|
2213
|
+
return btoa(s);
|
|
2214
|
+
}
|
|
2215
|
+
function base64ToBytes(s) {
|
|
2216
|
+
return Uint8Array.from(atob(s), (c) => c.charCodeAt(0));
|
|
2217
|
+
}
|
|
2218
|
+
var TeeClient = class {
|
|
2219
|
+
constructor(teeUrl) {
|
|
2220
|
+
this.teeUrl = teeUrl;
|
|
2221
|
+
this.handle = null;
|
|
2222
|
+
this.attested = false;
|
|
2223
|
+
}
|
|
2224
|
+
async ensureAttested() {
|
|
2225
|
+
if (this.attested) return;
|
|
2226
|
+
const nonce = crypto.getRandomValues(new Uint8Array(16));
|
|
2227
|
+
const nonceHex = Array.from(nonce).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
2228
|
+
await verifyTeeAttestation(this.teeUrl, nonceHex);
|
|
2229
|
+
this.attested = true;
|
|
2230
|
+
}
|
|
2231
|
+
async openSession(deviceShare, jwt) {
|
|
2232
|
+
await this.ensureAttested();
|
|
2233
|
+
const res = await fetch(`${this.teeUrl}/signer/session`, {
|
|
2234
|
+
method: "POST",
|
|
2235
|
+
headers: { Authorization: `Bearer ${jwt}`, "Content-Type": "application/json" },
|
|
2236
|
+
body: JSON.stringify({ deviceShare: bytesToBase64(deviceShare) })
|
|
2237
|
+
});
|
|
2238
|
+
if (!res.ok) throw new Error("session open failed");
|
|
2239
|
+
this.handle = (await res.json()).handle;
|
|
2240
|
+
}
|
|
2241
|
+
/**
|
|
2242
|
+
* Flow B: exchange a recovery share (from a recovery-share file) for a
|
|
2243
|
+
* fresh device share. The server verifies the reconstructed master
|
|
2244
|
+
* actually matches this account's addresses before returning anything —
|
|
2245
|
+
* see the security note in the TEE's /signer/recover handler. Rotates the
|
|
2246
|
+
* whole share set: the caller MUST persist the returned recoveryShare as a
|
|
2247
|
+
* new recovery-share file, since the old one stops working immediately.
|
|
2248
|
+
*/
|
|
2249
|
+
async recoverWithShare(jwt, recoveryShare) {
|
|
2250
|
+
await this.ensureAttested();
|
|
2251
|
+
const res = await fetch(`${this.teeUrl}/signer/recover`, {
|
|
2252
|
+
method: "POST",
|
|
2253
|
+
headers: { Authorization: `Bearer ${jwt}`, "Content-Type": "application/json" },
|
|
2254
|
+
body: JSON.stringify({ recoveryShare: bytesToBase64(recoveryShare) })
|
|
2255
|
+
});
|
|
2256
|
+
if (res.status === 403) {
|
|
2257
|
+
const { RecoveryWalletMismatchError: RecoveryWalletMismatchError2 } = await Promise.resolve().then(() => (init_errors(), errors_exports));
|
|
2258
|
+
throw new RecoveryWalletMismatchError2();
|
|
2259
|
+
}
|
|
2260
|
+
if (!res.ok) throw new Error("recovery failed");
|
|
2261
|
+
const body = await res.json();
|
|
2262
|
+
return {
|
|
2263
|
+
deviceShare: base64ToBytes(body.deviceShare),
|
|
2264
|
+
recoveryShare: base64ToBytes(body.recoveryShare),
|
|
2265
|
+
shareSetVersion: body.shareSetVersion
|
|
2266
|
+
};
|
|
2267
|
+
}
|
|
2268
|
+
/**
|
|
2269
|
+
* Request a fresh WebAuthn assertion challenge and complete it in the
|
|
2270
|
+
* browser via @simplewebauthn/browser's startAuthentication(), which
|
|
2271
|
+
* handles all the base64url (de)serialization the raw navigator.credentials
|
|
2272
|
+
* API requires. Shared by rotateShares() and exportPortableBackup() — both
|
|
2273
|
+
* require proof of fresh authenticator possession, not just an active session.
|
|
2274
|
+
*/
|
|
2275
|
+
async completeAssertion(jwt) {
|
|
2276
|
+
const res = await fetch(`${this.teeUrl}/signer/assertion-challenge`, {
|
|
2277
|
+
method: "POST",
|
|
2278
|
+
headers: { Authorization: `Bearer ${jwt}`, "Content-Type": "application/json" },
|
|
2279
|
+
body: JSON.stringify({ rpId: window.location.hostname })
|
|
2280
|
+
});
|
|
2281
|
+
if (!res.ok) throw new ExportNotAuthorizedError();
|
|
2282
|
+
const { challengeId, options } = await res.json();
|
|
2283
|
+
let assertion;
|
|
2284
|
+
try {
|
|
2285
|
+
assertion = await (0, import_browser.startAuthentication)({ optionsJSON: options });
|
|
2286
|
+
} catch {
|
|
2287
|
+
const { UserCancelledError: UserCancelledError2 } = await Promise.resolve().then(() => (init_errors(), errors_exports));
|
|
2288
|
+
throw new UserCancelledError2();
|
|
2289
|
+
}
|
|
2290
|
+
return { challengeId, assertion };
|
|
2291
|
+
}
|
|
2292
|
+
/** Voluntary rotation — e.g. after a suspected device compromise, or to invalidate an old recovery file. */
|
|
2293
|
+
async rotateShares(jwt) {
|
|
2294
|
+
if (!this.handle) throw new Error("no session");
|
|
2295
|
+
const { challengeId, assertion } = await this.completeAssertion(jwt);
|
|
2296
|
+
const res = await fetch(`${this.teeUrl}/signer/rotate`, {
|
|
2297
|
+
method: "POST",
|
|
2298
|
+
headers: { Authorization: `Bearer ${jwt}`, "Content-Type": "application/json" },
|
|
2299
|
+
body: JSON.stringify({ handle: this.handle, challengeId, assertion, origin: window.location.origin })
|
|
2300
|
+
});
|
|
2301
|
+
if (!res.ok) throw new ExportNotAuthorizedError();
|
|
2302
|
+
const body = await res.json();
|
|
2303
|
+
return {
|
|
2304
|
+
deviceShare: base64ToBytes(body.deviceShare),
|
|
2305
|
+
recoveryShare: base64ToBytes(body.recoveryShare),
|
|
2306
|
+
shareSetVersion: body.shareSetVersion
|
|
2307
|
+
};
|
|
2308
|
+
}
|
|
2309
|
+
/** The two-share bearer portable backup — see recovery/portableBackup.ts. Rotates the set on export. */
|
|
2310
|
+
async exportPortableBackup(jwt) {
|
|
2311
|
+
if (!this.handle) throw new Error("no session");
|
|
2312
|
+
const { challengeId, assertion } = await this.completeAssertion(jwt);
|
|
2313
|
+
const res = await fetch(`${this.teeUrl}/signer/export-portable`, {
|
|
2314
|
+
method: "POST",
|
|
2315
|
+
headers: { Authorization: `Bearer ${jwt}`, "Content-Type": "application/json" },
|
|
2316
|
+
body: JSON.stringify({ handle: this.handle, challengeId, assertion, origin: window.location.origin })
|
|
2317
|
+
});
|
|
2318
|
+
if (!res.ok) throw new ExportNotAuthorizedError();
|
|
2319
|
+
const body = await res.json();
|
|
2320
|
+
return {
|
|
2321
|
+
deviceShare: base64ToBytes(body.deviceShare),
|
|
2322
|
+
recoveryShare: base64ToBytes(body.recoveryShare),
|
|
2323
|
+
shareSetVersion: body.shareSetVersion
|
|
2324
|
+
};
|
|
2325
|
+
}
|
|
2326
|
+
hasSession() {
|
|
2327
|
+
return this.handle !== null;
|
|
2328
|
+
}
|
|
2329
|
+
async sign(req) {
|
|
2330
|
+
if (!this.handle) throw new Error("no session");
|
|
2331
|
+
const res = await fetch(`${this.teeUrl}/signer/sign`, {
|
|
2332
|
+
method: "POST",
|
|
2333
|
+
headers: { "Content-Type": "application/json" },
|
|
2334
|
+
body: JSON.stringify({ handle: this.handle, ...req })
|
|
2335
|
+
});
|
|
2336
|
+
if (res.status === 401) {
|
|
2337
|
+
this.handle = null;
|
|
2338
|
+
throw new SessionExpiredError();
|
|
2339
|
+
}
|
|
2340
|
+
if (!res.ok) throw new Error("sign failed");
|
|
2341
|
+
return (await res.json()).signature;
|
|
2342
|
+
}
|
|
2343
|
+
async closeSession() {
|
|
2344
|
+
if (!this.handle) return;
|
|
2345
|
+
const handle = this.handle;
|
|
2346
|
+
this.handle = null;
|
|
2347
|
+
void fetch(`${this.teeUrl}/signer/close`, {
|
|
2348
|
+
method: "POST",
|
|
2349
|
+
headers: { "Content-Type": "application/json" },
|
|
2350
|
+
body: JSON.stringify({ handle })
|
|
2351
|
+
}).catch(() => {
|
|
2352
|
+
});
|
|
2353
|
+
}
|
|
2354
|
+
};
|
|
2355
|
+
|
|
2356
|
+
// src/social/session.ts
|
|
2357
|
+
init_errors();
|
|
2358
|
+
var SessionManager = class {
|
|
2359
|
+
constructor(events, durationMs, teeUrl) {
|
|
2360
|
+
this.events = events;
|
|
2361
|
+
this.durationMs = durationMs;
|
|
2362
|
+
this.teeUrl = teeUrl;
|
|
2363
|
+
this.tee = null;
|
|
2364
|
+
this.evmAddress = null;
|
|
2365
|
+
this.solanaAddress = null;
|
|
2366
|
+
this.expiresAt = null;
|
|
2367
|
+
this.timeoutId = null;
|
|
2368
|
+
if (typeof window !== "undefined") {
|
|
2369
|
+
window.addEventListener("pagehide", () => this.end());
|
|
2370
|
+
}
|
|
2371
|
+
}
|
|
2372
|
+
async start(deviceShare, jwt, addresses) {
|
|
2373
|
+
this.tee = new TeeClient(this.teeUrl);
|
|
2374
|
+
await this.tee.openSession(deviceShare, jwt);
|
|
2375
|
+
this.evmAddress = addresses.evm;
|
|
2376
|
+
this.solanaAddress = addresses.solana;
|
|
2377
|
+
this.expiresAt = Date.now() + this.durationMs;
|
|
2378
|
+
if (this.timeoutId) clearTimeout(this.timeoutId);
|
|
2379
|
+
this.timeoutId = setTimeout(() => {
|
|
2380
|
+
this.end();
|
|
2381
|
+
this.events.emit("session-expired");
|
|
2382
|
+
}, this.durationMs);
|
|
2383
|
+
this.events.emit("session-started");
|
|
2384
|
+
}
|
|
2385
|
+
end() {
|
|
2386
|
+
void this.tee?.closeSession();
|
|
2387
|
+
this.tee = null;
|
|
2388
|
+
this.evmAddress = null;
|
|
2389
|
+
this.solanaAddress = null;
|
|
2390
|
+
this.expiresAt = null;
|
|
2391
|
+
if (this.timeoutId) {
|
|
2392
|
+
clearTimeout(this.timeoutId);
|
|
2393
|
+
this.timeoutId = null;
|
|
2394
|
+
}
|
|
2395
|
+
}
|
|
2396
|
+
isActive() {
|
|
2397
|
+
return this.tee !== null && Date.now() < (this.expiresAt ?? 0);
|
|
2398
|
+
}
|
|
2399
|
+
expiresAtTimestamp() {
|
|
2400
|
+
return this.expiresAt;
|
|
2401
|
+
}
|
|
2402
|
+
requireActive() {
|
|
2403
|
+
if (!this.isActive()) throw new UnlockRequiredError();
|
|
2404
|
+
return this.tee;
|
|
2405
|
+
}
|
|
2406
|
+
addresses() {
|
|
2407
|
+
if (!this.evmAddress || !this.solanaAddress) return null;
|
|
2408
|
+
return { evm: this.evmAddress, solana: this.solanaAddress };
|
|
2409
|
+
}
|
|
2410
|
+
};
|
|
2411
|
+
|
|
2412
|
+
// src/social/crypto/shares.ts
|
|
2413
|
+
var import_secp256k1 = require("@noble/curves/secp256k1");
|
|
2414
|
+
var import_shamir_secret_sharing = require("shamir-secret-sharing");
|
|
2415
|
+
function generateRandomBytes(n) {
|
|
2416
|
+
return crypto.getRandomValues(new Uint8Array(n));
|
|
2417
|
+
}
|
|
2418
|
+
function zeroize(bytes) {
|
|
2419
|
+
bytes.fill(0);
|
|
2420
|
+
}
|
|
2421
|
+
function generateMaster() {
|
|
2422
|
+
let master;
|
|
2423
|
+
do {
|
|
2424
|
+
master = generateRandomBytes(32);
|
|
2425
|
+
} while (!import_secp256k1.secp256k1.utils.isValidPrivateKey(master));
|
|
2426
|
+
return master;
|
|
2427
|
+
}
|
|
2428
|
+
async function splitMaster(master, version = 1) {
|
|
2429
|
+
if (master.length !== 32) throw new Error("master must be 32 bytes");
|
|
2430
|
+
const parts = await (0, import_shamir_secret_sharing.split)(master, 3, 2);
|
|
2431
|
+
if (parts.length !== 3) throw new Error("unexpected share count");
|
|
2432
|
+
return { device: parts[0], server: parts[1], recovery: parts[2], version };
|
|
2433
|
+
}
|
|
2434
|
+
|
|
2435
|
+
// src/social/crypto/wrap.ts
|
|
2436
|
+
function buf(u) {
|
|
2437
|
+
return u.buffer.slice(u.byteOffset, u.byteOffset + u.byteLength);
|
|
2438
|
+
}
|
|
2439
|
+
async function importAesKey(rawKey) {
|
|
2440
|
+
return crypto.subtle.importKey(
|
|
2441
|
+
"raw",
|
|
2442
|
+
buf(rawKey),
|
|
2443
|
+
{ name: "AES-GCM", length: 256 },
|
|
2444
|
+
false,
|
|
2445
|
+
["encrypt", "decrypt"]
|
|
2446
|
+
);
|
|
2447
|
+
}
|
|
2448
|
+
async function aesGcmEncrypt(key, plaintext, iv) {
|
|
2449
|
+
const ct = await crypto.subtle.encrypt({ name: "AES-GCM", iv: buf(iv) }, key, buf(plaintext));
|
|
2450
|
+
return new Uint8Array(ct);
|
|
2451
|
+
}
|
|
2452
|
+
async function aesGcmDecrypt(key, ciphertext, iv) {
|
|
2453
|
+
const pt = await crypto.subtle.decrypt({ name: "AES-GCM", iv: buf(iv) }, key, buf(ciphertext));
|
|
2454
|
+
return new Uint8Array(pt);
|
|
2455
|
+
}
|
|
2456
|
+
|
|
2457
|
+
// src/social/crypto/pin.ts
|
|
2458
|
+
var PBKDF2_ITERATIONS = 6e5;
|
|
2459
|
+
function buf2(u) {
|
|
2460
|
+
return u.buffer.slice(u.byteOffset, u.byteOffset + u.byteLength);
|
|
2461
|
+
}
|
|
2462
|
+
async function deriveKeyFromPin(pin, salt) {
|
|
2463
|
+
const pinBytes = new TextEncoder().encode(pin);
|
|
2464
|
+
const base = await crypto.subtle.importKey("raw", buf2(pinBytes), "PBKDF2", false, ["deriveKey"]);
|
|
2465
|
+
return crypto.subtle.deriveKey(
|
|
2466
|
+
{ name: "PBKDF2", salt: buf2(salt), iterations: PBKDF2_ITERATIONS, hash: "SHA-256" },
|
|
2467
|
+
base,
|
|
2468
|
+
{ name: "AES-GCM", length: 256 },
|
|
2469
|
+
false,
|
|
2470
|
+
["encrypt", "decrypt"]
|
|
2471
|
+
);
|
|
2472
|
+
}
|
|
2473
|
+
function validatePin(pin) {
|
|
2474
|
+
if (!/^\d{4,8}$/.test(pin)) throw new Error("PIN must be 4\u20138 digits");
|
|
2475
|
+
}
|
|
2476
|
+
|
|
2477
|
+
// src/social/crypto/passkey.ts
|
|
2478
|
+
var import_browser2 = require("@simplewebauthn/browser");
|
|
2479
|
+
init_errors();
|
|
2480
|
+
function buf3(u) {
|
|
2481
|
+
return u.buffer.slice(u.byteOffset, u.byteOffset + u.byteLength);
|
|
2482
|
+
}
|
|
2483
|
+
function isPasskeySupported() {
|
|
2484
|
+
return typeof window !== "undefined" && !!window.PublicKeyCredential && "isUserVerifyingPlatformAuthenticatorAvailable" in window.PublicKeyCredential;
|
|
2485
|
+
}
|
|
2486
|
+
async function registerPasskey(opts) {
|
|
2487
|
+
let response;
|
|
2488
|
+
try {
|
|
2489
|
+
response = await (0, import_browser2.startRegistration)({ optionsJSON: opts.optionsJSON });
|
|
2490
|
+
} catch (e) {
|
|
2491
|
+
if (e.name === "NotAllowedError") throw new UserCancelledError();
|
|
2492
|
+
throw e;
|
|
2493
|
+
}
|
|
2494
|
+
const extResults = response.clientExtensionResults;
|
|
2495
|
+
const prfResults = extResults?.prf?.results;
|
|
2496
|
+
let prfOutput;
|
|
2497
|
+
if (prfResults?.first) {
|
|
2498
|
+
prfOutput = typeof prfResults.first === "string" ? Uint8Array.from(atob(prfResults.first.replace(/-/g, "+").replace(/_/g, "/")), (c) => c.charCodeAt(0)) : new Uint8Array(prfResults.first);
|
|
2499
|
+
}
|
|
2500
|
+
const credentialId = Uint8Array.from(
|
|
2501
|
+
atob(response.id.replace(/-/g, "+").replace(/_/g, "/")),
|
|
2502
|
+
(c) => c.charCodeAt(0)
|
|
2503
|
+
);
|
|
2504
|
+
return { credentialId, prfOutput, response };
|
|
2505
|
+
}
|
|
2506
|
+
async function getPrfOutput(opts) {
|
|
2507
|
+
const challenge = crypto.getRandomValues(new Uint8Array(32));
|
|
2508
|
+
let cred;
|
|
2509
|
+
try {
|
|
2510
|
+
cred = await navigator.credentials.get({
|
|
2511
|
+
publicKey: {
|
|
2512
|
+
challenge: buf3(challenge),
|
|
2513
|
+
rpId: opts.rpId,
|
|
2514
|
+
allowCredentials: [{ id: buf3(opts.credentialId), type: "public-key" }],
|
|
2515
|
+
userVerification: "required",
|
|
2516
|
+
extensions: { prf: { eval: { first: buf3(opts.prfSalt) } } }
|
|
2517
|
+
}
|
|
2518
|
+
});
|
|
2519
|
+
} catch (e) {
|
|
2520
|
+
if (e.name === "NotAllowedError") throw new UserCancelledError();
|
|
2521
|
+
throw e;
|
|
2522
|
+
}
|
|
2523
|
+
const ext = cred.getClientExtensionResults();
|
|
2524
|
+
const prfResults = ext?.prf?.results;
|
|
2525
|
+
if (!prfResults?.first) throw new WebAuthnNotSupportedError();
|
|
2526
|
+
return new Uint8Array(prfResults.first);
|
|
2527
|
+
}
|
|
2528
|
+
|
|
2529
|
+
// src/social/webauthnRegistration.ts
|
|
2530
|
+
function bufFromBytes(u) {
|
|
2531
|
+
return u.buffer.slice(u.byteOffset, u.byteOffset + u.byteLength);
|
|
2532
|
+
}
|
|
2533
|
+
async function registerWebAuthnCredential(opts) {
|
|
2534
|
+
const baseOptions = await opts.api.getWebAuthnRegisterOptions(opts.jwt, opts.rpId, opts.rpName);
|
|
2535
|
+
const optionsJSON = {
|
|
2536
|
+
...baseOptions,
|
|
2537
|
+
extensions: {
|
|
2538
|
+
...baseOptions.extensions,
|
|
2539
|
+
prf: { eval: { first: bufFromBytes(opts.prfSalt) } }
|
|
2540
|
+
}
|
|
2541
|
+
};
|
|
2542
|
+
const { credentialId, prfOutput, response } = await registerPasskey({ optionsJSON });
|
|
2543
|
+
try {
|
|
2544
|
+
await opts.api.postWebAuthnRegister(opts.jwt, response, window.location.origin);
|
|
2545
|
+
} catch (e) {
|
|
2546
|
+
console.warn("decane-connect-kit: WebAuthn server-side registration failed; PRF-based unlock still works, but rotateShares()/exportPortableBackup() will not until this is retried.", e);
|
|
2547
|
+
}
|
|
2548
|
+
return { credentialId, prfOutput };
|
|
2549
|
+
}
|
|
2550
|
+
|
|
2551
|
+
// src/social/auth/email.ts
|
|
2552
|
+
async function startEmailAuth(api, email) {
|
|
2553
|
+
await api.authEmailStart(email);
|
|
2554
|
+
}
|
|
2555
|
+
async function verifyEmailCode(api, email, code) {
|
|
2556
|
+
return api.authEmailVerify(email, code);
|
|
2557
|
+
}
|
|
2558
|
+
|
|
2559
|
+
// src/social/signers/evm.ts
|
|
2560
|
+
var import_ethers = require("ethers");
|
|
2561
|
+
async function signEvmTransaction(tee, chain, tx) {
|
|
2562
|
+
const resolved = await (0, import_ethers.resolveProperties)(tx);
|
|
2563
|
+
const unsigned = import_ethers.Transaction.from(resolved).unsignedSerialized;
|
|
2564
|
+
const payload = unsigned.startsWith("0x") ? unsigned.slice(2) : unsigned;
|
|
2565
|
+
const txHash = (0, import_ethers.keccak256)("0x" + payload);
|
|
2566
|
+
return tee.sign({ chain, kind: "transaction", payload, txHash });
|
|
2567
|
+
}
|
|
2568
|
+
async function signEvmMessage(tee, chain, message) {
|
|
2569
|
+
return tee.sign({ chain, kind: "message", payload: message });
|
|
2570
|
+
}
|
|
2571
|
+
|
|
2572
|
+
// src/social/signers/solana.ts
|
|
2573
|
+
var import_web3 = require("@solana/web3.js");
|
|
2574
|
+
function b64(bytes) {
|
|
2575
|
+
let s = "";
|
|
2576
|
+
for (const b of bytes) s += String.fromCharCode(b);
|
|
2577
|
+
return btoa(s);
|
|
2578
|
+
}
|
|
2579
|
+
function unb64(s) {
|
|
2580
|
+
return Uint8Array.from(atob(s), (c) => c.charCodeAt(0));
|
|
2581
|
+
}
|
|
2582
|
+
async function signSolanaTransaction(tee, chain, tx) {
|
|
2583
|
+
const bytes = tx instanceof import_web3.VersionedTransaction ? tx.serialize() : tx.serialize({ requireAllSignatures: false });
|
|
2584
|
+
const signed = await tee.sign({ chain, kind: "transaction", payload: b64(bytes) });
|
|
2585
|
+
return unb64(signed);
|
|
2586
|
+
}
|
|
2587
|
+
async function signSolanaMessage(tee, chain, message) {
|
|
2588
|
+
const sig = await tee.sign({ chain, kind: "message", payload: b64(message) });
|
|
2589
|
+
return unb64(sig);
|
|
2590
|
+
}
|
|
2591
|
+
|
|
2592
|
+
// src/social/recovery/wrappedShareBackup.ts
|
|
2593
|
+
async function fetchWrappedShareBackup(api, jwt) {
|
|
2594
|
+
return api.getRecoveryBackup(jwt);
|
|
2595
|
+
}
|
|
2596
|
+
|
|
2597
|
+
// src/social/recovery/recoveryShareFile.ts
|
|
2598
|
+
init_errors();
|
|
2599
|
+
var PBKDF2_ITERATIONS2 = 6e5;
|
|
2600
|
+
var FILE_VERSION = 2;
|
|
2601
|
+
function b642(u) {
|
|
2602
|
+
let s = "";
|
|
2603
|
+
for (const b of u) s += String.fromCharCode(b);
|
|
2604
|
+
return btoa(s);
|
|
2605
|
+
}
|
|
2606
|
+
function unb642(s) {
|
|
2607
|
+
return Uint8Array.from(atob(s), (c) => c.charCodeAt(0));
|
|
2608
|
+
}
|
|
2609
|
+
function buf4(u) {
|
|
2610
|
+
return u.buffer.slice(u.byteOffset, u.byteOffset + u.byteLength);
|
|
2611
|
+
}
|
|
2612
|
+
async function deriveFileKey(password, salt, iterations) {
|
|
2613
|
+
const base = await crypto.subtle.importKey(
|
|
2614
|
+
"raw",
|
|
2615
|
+
new TextEncoder().encode(password),
|
|
2616
|
+
"PBKDF2",
|
|
2617
|
+
false,
|
|
2618
|
+
["deriveKey"]
|
|
2619
|
+
);
|
|
2620
|
+
return crypto.subtle.deriveKey(
|
|
2621
|
+
{ name: "PBKDF2", salt: buf4(salt), iterations, hash: "SHA-256" },
|
|
2622
|
+
base,
|
|
2623
|
+
{ name: "AES-GCM", length: 256 },
|
|
2624
|
+
false,
|
|
2625
|
+
["encrypt", "decrypt"]
|
|
2626
|
+
);
|
|
2627
|
+
}
|
|
2628
|
+
async function buildRecoveryShareFile(share, password, addresses, shareSetVersion, passwordHint) {
|
|
2629
|
+
if (share.length !== 33) throw new Error("share must be 33 bytes");
|
|
2630
|
+
if (password.length < 8) throw new Error("password must be at least 8 characters");
|
|
2631
|
+
if (passwordHint && passwordHint.trim() === password) {
|
|
2632
|
+
throw new Error("password hint must not be the password");
|
|
2633
|
+
}
|
|
2634
|
+
const salt = crypto.getRandomValues(new Uint8Array(16));
|
|
2635
|
+
const iv = crypto.getRandomValues(new Uint8Array(12));
|
|
2636
|
+
const key = await deriveFileKey(password, salt, PBKDF2_ITERATIONS2);
|
|
2637
|
+
const ct = new Uint8Array(
|
|
2638
|
+
await crypto.subtle.encrypt({ name: "AES-GCM", iv: buf4(iv) }, key, buf4(share))
|
|
2639
|
+
);
|
|
2640
|
+
return {
|
|
2641
|
+
version: FILE_VERSION,
|
|
2642
|
+
type: "decane-recovery-share",
|
|
2643
|
+
shareSetVersion,
|
|
2644
|
+
kdf: "pbkdf2-sha256",
|
|
2645
|
+
kdfParams: { iterations: PBKDF2_ITERATIONS2, salt: b642(salt) },
|
|
2646
|
+
cipher: "aes-256-gcm",
|
|
2647
|
+
iv: b642(iv),
|
|
2648
|
+
ciphertext: b642(ct),
|
|
2649
|
+
addresses,
|
|
2650
|
+
passwordHint: passwordHint?.trim() || null,
|
|
2651
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2652
|
+
};
|
|
2653
|
+
}
|
|
2654
|
+
function parseRecoveryShareFile(json) {
|
|
2655
|
+
const raw = json;
|
|
2656
|
+
if (raw?.type === "decane-recovery") {
|
|
2657
|
+
throw new InvalidRecoveryFileError();
|
|
2658
|
+
}
|
|
2659
|
+
const f = json;
|
|
2660
|
+
if (!f || f.type !== "decane-recovery-share" || f.version !== FILE_VERSION || f.kdf !== "pbkdf2-sha256" || f.cipher !== "aes-256-gcm" || typeof f.iv !== "string" || typeof f.ciphertext !== "string" || typeof f.kdfParams?.salt !== "string" || typeof f.kdfParams?.iterations !== "number" || typeof f.shareSetVersion !== "number" || typeof f.addresses?.evm !== "string" || typeof f.addresses?.solana !== "string") {
|
|
2661
|
+
throw new InvalidRecoveryFileError();
|
|
2662
|
+
}
|
|
2663
|
+
if (f.kdfParams.iterations < 1e5 || f.kdfParams.iterations > 5e6) {
|
|
2664
|
+
throw new InvalidRecoveryFileError();
|
|
2665
|
+
}
|
|
2666
|
+
return f;
|
|
2667
|
+
}
|
|
2668
|
+
async function decryptRecoveryShareFile(file, password) {
|
|
2669
|
+
const salt = unb642(file.kdfParams.salt);
|
|
2670
|
+
const iv = unb642(file.iv);
|
|
2671
|
+
const ct = unb642(file.ciphertext);
|
|
2672
|
+
const key = await deriveFileKey(password, salt, file.kdfParams.iterations);
|
|
2673
|
+
let share;
|
|
2674
|
+
try {
|
|
2675
|
+
share = new Uint8Array(
|
|
2676
|
+
await crypto.subtle.decrypt({ name: "AES-GCM", iv: buf4(iv) }, key, buf4(ct))
|
|
2677
|
+
);
|
|
2678
|
+
} catch {
|
|
2679
|
+
throw new WrongPasswordError();
|
|
2680
|
+
}
|
|
2681
|
+
if (share.length !== 33) {
|
|
2682
|
+
share.fill(0);
|
|
2683
|
+
throw new InvalidRecoveryFileError();
|
|
2684
|
+
}
|
|
2685
|
+
return share;
|
|
2686
|
+
}
|
|
2687
|
+
function recoveryShareFileName(evmAddress) {
|
|
2688
|
+
return `decane-recovery-${evmAddress.slice(0, 8)}.json`;
|
|
2689
|
+
}
|
|
2690
|
+
|
|
2691
|
+
// src/social/recovery/recoveryFlow.ts
|
|
2692
|
+
init_errors();
|
|
2693
|
+
async function recoverDeviceShare(input) {
|
|
2694
|
+
const backup = await fetchWrappedShareBackup(input.api, input.jwt);
|
|
2695
|
+
if (backup) {
|
|
2696
|
+
input.onProgress?.({ type: "passkey-attempt" });
|
|
2697
|
+
try {
|
|
2698
|
+
const prfOutput = await getPrfOutput({
|
|
2699
|
+
rpId: input.rpId,
|
|
2700
|
+
credentialId: backup.credentialId,
|
|
2701
|
+
prfSalt: backup.prfSalt
|
|
2702
|
+
});
|
|
2703
|
+
const wrappingKey = await importAesKey(prfOutput);
|
|
2704
|
+
const deviceShare = await aesGcmDecrypt(
|
|
2705
|
+
wrappingKey,
|
|
2706
|
+
backup.wrappedDeviceShare,
|
|
2707
|
+
backup.iv
|
|
2708
|
+
);
|
|
2709
|
+
await input.storage.putShare({
|
|
2710
|
+
userId: input.userId,
|
|
2711
|
+
wrappedDeviceShare: backup.wrappedDeviceShare,
|
|
2712
|
+
iv: backup.iv,
|
|
2713
|
+
unlockMethod: "passkey",
|
|
2714
|
+
credentialId: backup.credentialId,
|
|
2715
|
+
prfSalt: backup.prfSalt,
|
|
2716
|
+
version: 1
|
|
2717
|
+
});
|
|
2718
|
+
return deviceShare;
|
|
2719
|
+
} catch (e) {
|
|
2720
|
+
input.onProgress?.({
|
|
2721
|
+
type: "passkey-failed",
|
|
2722
|
+
reason: e instanceof Error ? e.message : "unknown"
|
|
2723
|
+
});
|
|
2724
|
+
}
|
|
2725
|
+
}
|
|
2726
|
+
if (!input.promptForRecoveryFile) throw new NewDeviceError();
|
|
2727
|
+
input.onProgress?.({ type: "recovery-file-prompt" });
|
|
2728
|
+
const supplied = await input.promptForRecoveryFile();
|
|
2729
|
+
if (!supplied) throw new NewDeviceError();
|
|
2730
|
+
const file = parseRecoveryShareFile(supplied.value);
|
|
2731
|
+
input.onProgress?.({ type: "password-prompt", passwordHint: file.passwordHint });
|
|
2732
|
+
const password = await supplied.getPassword();
|
|
2733
|
+
if (!password) throw new NewDeviceError();
|
|
2734
|
+
const recoveryShare = await decryptRecoveryShareFile(file, password);
|
|
2735
|
+
input.onProgress?.({ type: "recovery-verifying" });
|
|
2736
|
+
const tee = new TeeClient(input.teeUrl);
|
|
2737
|
+
let result;
|
|
2738
|
+
try {
|
|
2739
|
+
result = await tee.recoverWithShare(input.jwt, recoveryShare);
|
|
2740
|
+
} finally {
|
|
2741
|
+
zeroize(recoveryShare);
|
|
2742
|
+
}
|
|
2743
|
+
input.onProgress?.({ type: "rotated", shareSetVersion: result.shareSetVersion });
|
|
2744
|
+
await input.onNewRecoveryShare?.(result.recoveryShare, result.shareSetVersion);
|
|
2745
|
+
zeroize(result.recoveryShare);
|
|
2746
|
+
return result.deviceShare;
|
|
2747
|
+
}
|
|
2748
|
+
|
|
2749
|
+
// src/social/recovery/portableBackup.ts
|
|
2750
|
+
init_errors();
|
|
2751
|
+
var PBKDF2_ITERATIONS3 = 6e5;
|
|
2752
|
+
var FILE_VERSION2 = 1;
|
|
2753
|
+
function b643(u) {
|
|
2754
|
+
let s = "";
|
|
2755
|
+
for (const b of u) s += String.fromCharCode(b);
|
|
2756
|
+
return btoa(s);
|
|
2757
|
+
}
|
|
2758
|
+
function buf5(u) {
|
|
2759
|
+
return u.buffer.slice(u.byteOffset, u.byteOffset + u.byteLength);
|
|
2760
|
+
}
|
|
2761
|
+
async function deriveFileKey2(password, salt, iterations) {
|
|
2762
|
+
const base = await crypto.subtle.importKey(
|
|
2763
|
+
"raw",
|
|
2764
|
+
new TextEncoder().encode(password),
|
|
2765
|
+
"PBKDF2",
|
|
2766
|
+
false,
|
|
2767
|
+
["deriveKey"]
|
|
2768
|
+
);
|
|
2769
|
+
return crypto.subtle.deriveKey(
|
|
2770
|
+
{ name: "PBKDF2", salt: buf5(salt), iterations, hash: "SHA-256" },
|
|
2771
|
+
base,
|
|
2772
|
+
{ name: "AES-GCM", length: 256 },
|
|
2773
|
+
false,
|
|
2774
|
+
["encrypt", "decrypt"]
|
|
2775
|
+
);
|
|
2776
|
+
}
|
|
2777
|
+
async function buildPortableBackup(deviceShare, recoveryShare, password, addresses, shareSetVersion, passwordHint) {
|
|
2778
|
+
if (deviceShare.length !== 33 || recoveryShare.length !== 33) {
|
|
2779
|
+
throw new Error("shares must be 33 bytes");
|
|
2780
|
+
}
|
|
2781
|
+
if (password.length < 8) throw new Error("password must be at least 8 characters");
|
|
2782
|
+
if (passwordHint && passwordHint.trim() === password) {
|
|
2783
|
+
throw new Error("password hint must not be the password");
|
|
2784
|
+
}
|
|
2785
|
+
const plaintext = new TextEncoder().encode(
|
|
2786
|
+
JSON.stringify({ deviceShare: b643(deviceShare), recoveryShare: b643(recoveryShare) })
|
|
2787
|
+
);
|
|
2788
|
+
const salt = crypto.getRandomValues(new Uint8Array(16));
|
|
2789
|
+
const iv = crypto.getRandomValues(new Uint8Array(12));
|
|
2790
|
+
const key = await deriveFileKey2(password, salt, PBKDF2_ITERATIONS3);
|
|
2791
|
+
const ct = new Uint8Array(
|
|
2792
|
+
await crypto.subtle.encrypt({ name: "AES-GCM", iv: buf5(iv) }, key, buf5(plaintext))
|
|
2793
|
+
);
|
|
2794
|
+
return {
|
|
2795
|
+
version: FILE_VERSION2,
|
|
2796
|
+
type: "decane-portable-backup",
|
|
2797
|
+
shareSetVersion,
|
|
2798
|
+
kdf: "pbkdf2-sha256",
|
|
2799
|
+
kdfParams: { iterations: PBKDF2_ITERATIONS3, salt: b643(salt) },
|
|
2800
|
+
cipher: "aes-256-gcm",
|
|
2801
|
+
iv: b643(iv),
|
|
2802
|
+
ciphertext: b643(ct),
|
|
2803
|
+
addresses,
|
|
2804
|
+
passwordHint: passwordHint?.trim() || null,
|
|
2805
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2806
|
+
};
|
|
2807
|
+
}
|
|
2808
|
+
|
|
2809
|
+
// src/social/client.ts
|
|
2810
|
+
var DEFAULT_API_BASE = "https://backend.decane.app";
|
|
2811
|
+
var DEFAULT_TEE_URL = "https://4405742df08d19abc549a05ade2a7154d98bd87f-8443.dstack-pha-prod9.phala.network";
|
|
2812
|
+
var DEFAULT_SESSION_MINUTES = 30;
|
|
2813
|
+
var DEFAULT_RPCS = {
|
|
2814
|
+
"evm:1": "https://ethereum.publicnode.com",
|
|
2815
|
+
"evm:137": "https://polygon.publicnode.com",
|
|
2816
|
+
"evm:8453": "https://base.publicnode.com",
|
|
2817
|
+
"evm:10": "https://optimism.publicnode.com",
|
|
2818
|
+
"evm:42161": "https://arbitrum-one.publicnode.com",
|
|
2819
|
+
"evm:56": "https://bsc.publicnode.com",
|
|
2820
|
+
"evm:11155111": "https://ethereum-sepolia.publicnode.com",
|
|
2821
|
+
"evm:84532": "https://base-sepolia.publicnode.com",
|
|
2822
|
+
"evm:31337": "http://localhost:8545"
|
|
2823
|
+
};
|
|
2824
|
+
function makePersistKey(apiKey) {
|
|
2825
|
+
return `decane:social:${apiKey.slice(0, 20)}`;
|
|
2826
|
+
}
|
|
2827
|
+
function loadPersisted(key) {
|
|
2828
|
+
try {
|
|
2829
|
+
const raw = typeof localStorage !== "undefined" ? localStorage.getItem(key) : null;
|
|
2830
|
+
return raw ? JSON.parse(raw) : null;
|
|
2831
|
+
} catch {
|
|
2832
|
+
return null;
|
|
2833
|
+
}
|
|
2834
|
+
}
|
|
2835
|
+
function savePersisted(key, state) {
|
|
2836
|
+
try {
|
|
2837
|
+
localStorage.setItem(key, JSON.stringify(state));
|
|
2838
|
+
} catch {
|
|
2839
|
+
}
|
|
2840
|
+
}
|
|
2841
|
+
function clearPersisted(key) {
|
|
2842
|
+
try {
|
|
2843
|
+
localStorage.removeItem(key);
|
|
2844
|
+
} catch {
|
|
2845
|
+
}
|
|
2846
|
+
}
|
|
2847
|
+
async function createDecaneConnect(config) {
|
|
2848
|
+
if (!config.apiKey) throw new Error("decane-connect-kit: apiKey is required");
|
|
2849
|
+
if (!config.chains?.length)
|
|
2850
|
+
throw new Error("decane-connect-kit: at least one chain is required");
|
|
2851
|
+
const baseUrl = config.apiBaseUrl ?? DEFAULT_API_BASE;
|
|
2852
|
+
const durationMs = (config.sessionDurationMinutes ?? DEFAULT_SESSION_MINUTES) * 6e4;
|
|
2853
|
+
const rpId = config.rpId ?? (typeof window !== "undefined" ? window.location.hostname : "localhost");
|
|
2854
|
+
const rpName = config.rpName ?? "Decane Connect";
|
|
2855
|
+
const PERSIST_KEY = makePersistKey(config.apiKey);
|
|
2856
|
+
const teeUrl = config.teeUrl ?? DEFAULT_TEE_URL;
|
|
2857
|
+
const events = new EventEmitter();
|
|
2858
|
+
const api = new ApiClient(baseUrl, config.apiKey);
|
|
2859
|
+
const storage = new Storage();
|
|
2860
|
+
const session = new SessionManager(events, durationMs, teeUrl);
|
|
2861
|
+
let auth = null;
|
|
2862
|
+
let persisted = loadPersisted(PERSIST_KEY);
|
|
2863
|
+
async function pinSetup() {
|
|
2864
|
+
if (!config.promptPin) throw new PromptPinNotConfiguredError();
|
|
2865
|
+
const pin = await config.promptPin();
|
|
2866
|
+
validatePin(pin);
|
|
2867
|
+
const pinSalt = generateRandomBytes(32);
|
|
2868
|
+
const wrappingKey = await deriveKeyFromPin(pin, pinSalt);
|
|
2869
|
+
return {
|
|
2870
|
+
wrappingKey,
|
|
2871
|
+
meta: {
|
|
2872
|
+
unlockMethod: "pin",
|
|
2873
|
+
pinSalt: bufferToB64u(pinSalt),
|
|
2874
|
+
pinIterations: 6e5
|
|
2875
|
+
}
|
|
2876
|
+
};
|
|
2877
|
+
}
|
|
2878
|
+
async function getWrappingKey(stored) {
|
|
2879
|
+
if (stored.unlockMethod === "passkey") {
|
|
2880
|
+
const prfOutput = await getPrfOutput({
|
|
2881
|
+
rpId,
|
|
2882
|
+
credentialId: stored.credentialId,
|
|
2883
|
+
prfSalt: stored.prfSalt
|
|
2884
|
+
});
|
|
2885
|
+
return importAesKey(prfOutput);
|
|
2886
|
+
}
|
|
2887
|
+
if (!config.promptPin) throw new PromptPinNotConfiguredError();
|
|
2888
|
+
const pin = await config.promptPin();
|
|
2889
|
+
return deriveKeyFromPin(pin, stored.pinSalt);
|
|
2890
|
+
}
|
|
2891
|
+
async function registerPasskeyAndBackup(userId, jwt, deviceShare) {
|
|
2892
|
+
if (!isPasskeySupported()) return;
|
|
2893
|
+
const prfSalt = generateRandomBytes(32);
|
|
2894
|
+
let credentialId;
|
|
2895
|
+
let prfOutput;
|
|
2896
|
+
try {
|
|
2897
|
+
const result = await registerWebAuthnCredential({ api, jwt, rpId, rpName, prfSalt });
|
|
2898
|
+
credentialId = result.credentialId;
|
|
2899
|
+
prfOutput = result.prfOutput;
|
|
2900
|
+
} catch (e) {
|
|
2901
|
+
console.warn("decane-connect-kit: passkey provisioning after recovery failed; user can set this up later.", e);
|
|
2902
|
+
return;
|
|
2903
|
+
}
|
|
2904
|
+
if (!prfOutput) return;
|
|
2905
|
+
const wrappingKey = await importAesKey(prfOutput);
|
|
2906
|
+
const iv = generateRandomBytes(12);
|
|
2907
|
+
const wrappedDeviceShare = await aesGcmEncrypt(wrappingKey, deviceShare, iv);
|
|
2908
|
+
await storage.putShare({
|
|
2909
|
+
userId,
|
|
2910
|
+
wrappedDeviceShare,
|
|
2911
|
+
iv,
|
|
2912
|
+
unlockMethod: "passkey",
|
|
2913
|
+
credentialId,
|
|
2914
|
+
prfSalt,
|
|
2915
|
+
version: 1
|
|
2916
|
+
});
|
|
2917
|
+
try {
|
|
2918
|
+
await api.putRecoveryBackup(jwt, {
|
|
2919
|
+
wrappedDeviceShare,
|
|
2920
|
+
iv,
|
|
2921
|
+
unlockMethod: "passkey",
|
|
2922
|
+
credentialId,
|
|
2923
|
+
prfSalt
|
|
2924
|
+
});
|
|
2925
|
+
} catch (e) {
|
|
2926
|
+
console.warn("decane-connect-kit: wrapped share backup upload failed; user can retry from settings.", e);
|
|
2927
|
+
}
|
|
2928
|
+
}
|
|
2929
|
+
async function firstTimeSetup(userId, jwt) {
|
|
2930
|
+
let unlockMethod;
|
|
2931
|
+
let wrappingKey;
|
|
2932
|
+
let meta = {};
|
|
2933
|
+
if (isPasskeySupported()) {
|
|
2934
|
+
const prfSalt = generateRandomBytes(32);
|
|
2935
|
+
try {
|
|
2936
|
+
const { credentialId, prfOutput } = await registerWebAuthnCredential({ api, jwt, rpId, rpName, prfSalt });
|
|
2937
|
+
if (prfOutput) {
|
|
2938
|
+
unlockMethod = "passkey";
|
|
2939
|
+
wrappingKey = await importAesKey(prfOutput);
|
|
2940
|
+
meta = {
|
|
2941
|
+
unlockMethod: "passkey",
|
|
2942
|
+
credentialId: bufferToB64u(credentialId),
|
|
2943
|
+
prfSalt: bufferToB64u(prfSalt)
|
|
2944
|
+
};
|
|
2945
|
+
} else {
|
|
2946
|
+
({ wrappingKey, meta } = await pinSetup());
|
|
2947
|
+
unlockMethod = "pin";
|
|
2948
|
+
}
|
|
2949
|
+
} catch (e) {
|
|
2950
|
+
if (e.name === "UserCancelledError")
|
|
2951
|
+
throw new UserCancelledError();
|
|
2952
|
+
({ wrappingKey, meta } = await pinSetup());
|
|
2953
|
+
unlockMethod = "pin";
|
|
2954
|
+
}
|
|
2955
|
+
} else {
|
|
2956
|
+
({ wrappingKey, meta } = await pinSetup());
|
|
2957
|
+
unlockMethod = "pin";
|
|
2958
|
+
}
|
|
2959
|
+
const master = generateMaster();
|
|
2960
|
+
let shareSet;
|
|
2961
|
+
let evmAddress;
|
|
2962
|
+
let solanaAddress;
|
|
2963
|
+
try {
|
|
2964
|
+
evmAddress = new import_ethers2.Wallet(toHex(master)).address;
|
|
2965
|
+
solanaAddress = import_web32.Keypair.fromSeed(master).publicKey.toBase58();
|
|
2966
|
+
shareSet = await splitMaster(master, 1);
|
|
2967
|
+
} finally {
|
|
2968
|
+
zeroize(master);
|
|
2969
|
+
}
|
|
2970
|
+
const iv = generateRandomBytes(12);
|
|
2971
|
+
const wrappedDeviceShare = await aesGcmEncrypt(
|
|
2972
|
+
wrappingKey,
|
|
2973
|
+
shareSet.device,
|
|
2974
|
+
iv
|
|
2975
|
+
);
|
|
2976
|
+
await storage.putShare({
|
|
2977
|
+
userId,
|
|
2978
|
+
wrappedDeviceShare,
|
|
2979
|
+
iv,
|
|
2980
|
+
unlockMethod,
|
|
2981
|
+
credentialId: meta.credentialId ? b64uToBuffer(meta.credentialId) : void 0,
|
|
2982
|
+
prfSalt: meta.prfSalt ? b64uToBuffer(meta.prfSalt) : void 0,
|
|
2983
|
+
pinSalt: meta.pinSalt ? b64uToBuffer(meta.pinSalt) : void 0,
|
|
2984
|
+
pinIterations: meta.pinIterations,
|
|
2985
|
+
version: 1
|
|
2986
|
+
});
|
|
2987
|
+
const serverIv = generateRandomBytes(12);
|
|
2988
|
+
await api.putShare(jwt, {
|
|
2989
|
+
ciphertext: shareSet.server,
|
|
2990
|
+
iv: serverIv,
|
|
2991
|
+
wrappedShareMeta: {
|
|
2992
|
+
unlockMethod
|
|
2993
|
+
// credential IDs, prfSalt, pinSalt stay on-device — never sent to server
|
|
2994
|
+
},
|
|
2995
|
+
evmAddress,
|
|
2996
|
+
solanaAddress
|
|
2997
|
+
});
|
|
2998
|
+
zeroize(shareSet.server);
|
|
2999
|
+
const addresses = { evm: evmAddress, solana: solanaAddress };
|
|
3000
|
+
if (unlockMethod === "passkey" && meta.credentialId && meta.prfSalt) {
|
|
3001
|
+
try {
|
|
3002
|
+
await api.putRecoveryBackup(jwt, {
|
|
3003
|
+
wrappedDeviceShare,
|
|
3004
|
+
iv,
|
|
3005
|
+
unlockMethod: "passkey",
|
|
3006
|
+
credentialId: b64uToBuffer(meta.credentialId),
|
|
3007
|
+
prfSalt: b64uToBuffer(meta.prfSalt)
|
|
3008
|
+
});
|
|
3009
|
+
} catch (e) {
|
|
3010
|
+
console.warn("decane-connect-kit: wrapped share backup failed; user can retry from settings.", e);
|
|
3011
|
+
}
|
|
3012
|
+
}
|
|
3013
|
+
await session.start(shareSet.device, jwt, addresses);
|
|
3014
|
+
zeroize(shareSet.device);
|
|
3015
|
+
if (config.offerRecoveryAtSignup !== false && config.onRecoveryShareOffer) {
|
|
3016
|
+
try {
|
|
3017
|
+
const choice = await config.onRecoveryShareOffer({ evmAddress, solanaAddress });
|
|
3018
|
+
if (choice.wants) {
|
|
3019
|
+
const file = await buildRecoveryShareFile(
|
|
3020
|
+
shareSet.recovery,
|
|
3021
|
+
choice.password,
|
|
3022
|
+
addresses,
|
|
3023
|
+
shareSet.version,
|
|
3024
|
+
choice.passwordHint
|
|
3025
|
+
);
|
|
3026
|
+
await config.onRecoveryFileReady?.(file, recoveryShareFileName(evmAddress));
|
|
3027
|
+
await api.markRecoveryShareSaved(jwt);
|
|
3028
|
+
}
|
|
3029
|
+
} catch (e) {
|
|
3030
|
+
console.warn("decane-connect-kit: recovery share offer failed or was skipped.", e);
|
|
3031
|
+
}
|
|
3032
|
+
}
|
|
3033
|
+
zeroize(shareSet.recovery);
|
|
3034
|
+
return addresses;
|
|
3035
|
+
}
|
|
3036
|
+
async function returningUserUnlock(userId, jwt) {
|
|
3037
|
+
const rawAddresses = await api.getAddresses(jwt);
|
|
3038
|
+
if (!rawAddresses.evm || !rawAddresses.solana) {
|
|
3039
|
+
throw new CallSequenceError(
|
|
3040
|
+
"No addresses are recorded for this user \u2014 inconsistent server state"
|
|
3041
|
+
);
|
|
3042
|
+
}
|
|
3043
|
+
const addresses = { evm: rawAddresses.evm, solana: rawAddresses.solana };
|
|
3044
|
+
const stored = await storage.getShare(userId);
|
|
3045
|
+
let deviceShare;
|
|
3046
|
+
if (!stored) {
|
|
3047
|
+
deviceShare = await recoverDeviceShare({
|
|
3048
|
+
userId,
|
|
3049
|
+
jwt,
|
|
3050
|
+
rpId,
|
|
3051
|
+
teeUrl,
|
|
3052
|
+
api,
|
|
3053
|
+
storage,
|
|
3054
|
+
onProgress: config.onRecoveryProgress,
|
|
3055
|
+
promptForRecoveryFile: config.promptForRecoveryFile,
|
|
3056
|
+
onNewRecoveryShare: async (recoveryShare, shareSetVersion) => {
|
|
3057
|
+
if (!config.onRecoveryRotated) {
|
|
3058
|
+
console.warn("decane-connect-kit: share set rotated after recovery, but no onRecoveryRotated callback is configured \u2014 the user has no way to save a new recovery-share file.");
|
|
3059
|
+
return;
|
|
3060
|
+
}
|
|
3061
|
+
const { password, passwordHint } = await config.onRecoveryRotated({ evmAddress: addresses.evm, solanaAddress: addresses.solana });
|
|
3062
|
+
const file = await buildRecoveryShareFile(recoveryShare, password, addresses, shareSetVersion, passwordHint);
|
|
3063
|
+
await config.onRecoveryFileReady?.(file, recoveryShareFileName(addresses.evm));
|
|
3064
|
+
await api.markRecoveryShareSaved(jwt);
|
|
3065
|
+
}
|
|
3066
|
+
});
|
|
3067
|
+
if (!await storage.getShare(userId) && isPasskeySupported()) {
|
|
3068
|
+
await registerPasskeyAndBackup(userId, jwt, deviceShare);
|
|
3069
|
+
}
|
|
3070
|
+
} else {
|
|
3071
|
+
const wrappingKey = await getWrappingKey(stored);
|
|
3072
|
+
try {
|
|
3073
|
+
deviceShare = await aesGcmDecrypt(
|
|
3074
|
+
wrappingKey,
|
|
3075
|
+
stored.wrappedDeviceShare,
|
|
3076
|
+
stored.iv
|
|
3077
|
+
);
|
|
3078
|
+
} catch {
|
|
3079
|
+
throw stored.unlockMethod === "pin" ? new InvalidPinError() : new ShareCorruptedError();
|
|
3080
|
+
}
|
|
3081
|
+
}
|
|
3082
|
+
await session.start(deviceShare, jwt, addresses);
|
|
3083
|
+
zeroize(deviceShare);
|
|
3084
|
+
return addresses;
|
|
3085
|
+
}
|
|
3086
|
+
async function handleAuthResult(result) {
|
|
3087
|
+
let addresses;
|
|
3088
|
+
if (!result.hasShare) {
|
|
3089
|
+
addresses = await firstTimeSetup(result.userId, result.jwt);
|
|
3090
|
+
} else {
|
|
3091
|
+
addresses = await returningUserUnlock(result.userId, result.jwt);
|
|
3092
|
+
}
|
|
3093
|
+
auth = { jwt: result.jwt, userId: result.userId, addresses };
|
|
3094
|
+
persisted = { userId: result.userId, addresses };
|
|
3095
|
+
savePersisted(PERSIST_KEY, persisted);
|
|
3096
|
+
events.emit("connected");
|
|
3097
|
+
return { addresses, isNewUser: result.isNewUser };
|
|
3098
|
+
}
|
|
3099
|
+
const client = {
|
|
3100
|
+
async connect() {
|
|
3101
|
+
if (auth && session.isActive()) {
|
|
3102
|
+
return { addresses: auth.addresses, isNewUser: false };
|
|
3103
|
+
}
|
|
3104
|
+
const method = config.authMethods[0];
|
|
3105
|
+
if (!method) throw new CallSequenceError("No authMethods configured");
|
|
3106
|
+
if (method === "google") return client.connectWithGoogle();
|
|
3107
|
+
if (method === "email")
|
|
3108
|
+
throw new CallSequenceError(
|
|
3109
|
+
"Use connectWithEmail(email) for email auth"
|
|
3110
|
+
);
|
|
3111
|
+
throw new CallSequenceError(`Unknown auth method: ${method}`);
|
|
3112
|
+
},
|
|
3113
|
+
async connectWithGoogle() {
|
|
3114
|
+
const params = new URLSearchParams(window.location.search);
|
|
3115
|
+
const decaneJwt = params.get("decane_jwt");
|
|
3116
|
+
const decaneError = params.get("decane_error");
|
|
3117
|
+
if (decaneError) {
|
|
3118
|
+
window.history.replaceState({}, "", window.location.pathname);
|
|
3119
|
+
throw new CallSequenceError(`Google sign-in failed: ${decaneError}`);
|
|
3120
|
+
}
|
|
3121
|
+
if (decaneJwt) {
|
|
3122
|
+
const result = {
|
|
3123
|
+
jwt: decaneJwt,
|
|
3124
|
+
userId: params.get("decane_user_id") ?? "",
|
|
3125
|
+
isNewUser: params.get("decane_is_new_user") === "true",
|
|
3126
|
+
hasShare: params.get("decane_has_share") === "true"
|
|
3127
|
+
};
|
|
3128
|
+
window.history.replaceState({}, "", window.location.pathname);
|
|
3129
|
+
return handleAuthResult(result);
|
|
3130
|
+
}
|
|
3131
|
+
const { url } = await api.getGoogleInitUrl();
|
|
3132
|
+
window.location.href = url;
|
|
3133
|
+
return new Promise(() => {
|
|
3134
|
+
});
|
|
3135
|
+
},
|
|
3136
|
+
async connectWithEmail(email) {
|
|
3137
|
+
await startEmailAuth(api, email);
|
|
3138
|
+
return { codeRequired: true };
|
|
3139
|
+
},
|
|
3140
|
+
async verifyEmailCode(email, code) {
|
|
3141
|
+
const result = await verifyEmailCode(api, email, code);
|
|
3142
|
+
return handleAuthResult(result);
|
|
3143
|
+
},
|
|
3144
|
+
async unlock() {
|
|
3145
|
+
if (!auth)
|
|
3146
|
+
throw new CallSequenceError("Not connected \u2014 call connect() first");
|
|
3147
|
+
if (session.isActive()) return;
|
|
3148
|
+
await returningUserUnlock(auth.userId, auth.jwt);
|
|
3149
|
+
},
|
|
3150
|
+
isUnlocked() {
|
|
3151
|
+
return session.isActive();
|
|
3152
|
+
},
|
|
3153
|
+
sessionExpiresAt() {
|
|
3154
|
+
return session.expiresAtTimestamp();
|
|
3155
|
+
},
|
|
3156
|
+
async signMessage(req) {
|
|
3157
|
+
const tee = session.requireActive();
|
|
3158
|
+
if (req.chain.startsWith("evm:")) {
|
|
3159
|
+
return signEvmMessage(tee, req.chain, req.message);
|
|
3160
|
+
}
|
|
3161
|
+
if (req.chain.startsWith("solana:")) {
|
|
3162
|
+
const msg = typeof req.message === "string" ? new TextEncoder().encode(req.message) : req.message;
|
|
3163
|
+
const sig = await signSolanaMessage(tee, req.chain, msg);
|
|
3164
|
+
return bufferToB64u(sig);
|
|
3165
|
+
}
|
|
3166
|
+
throw new CallSequenceError(`Unsupported chain: ${req.chain}`);
|
|
3167
|
+
},
|
|
3168
|
+
async sendTransaction(req) {
|
|
3169
|
+
const tee = session.requireActive();
|
|
3170
|
+
if (!req.chain.startsWith("evm:")) {
|
|
3171
|
+
throw new CallSequenceError(
|
|
3172
|
+
"sendTransaction is EVM-only. Use signSolanaTransaction for Solana."
|
|
3173
|
+
);
|
|
3174
|
+
}
|
|
3175
|
+
if (!auth?.addresses.evm) {
|
|
3176
|
+
throw new CallSequenceError("Not connected");
|
|
3177
|
+
}
|
|
3178
|
+
const evmAddr = auth.addresses.evm;
|
|
3179
|
+
const rpcUrl = config.rpcUrls?.[req.chain] ?? DEFAULT_RPCS[req.chain];
|
|
3180
|
+
if (!rpcUrl) {
|
|
3181
|
+
throw new CallSequenceError(
|
|
3182
|
+
`No RPC URL for ${req.chain}. Pass rpcUrls: { '${req.chain}': 'https://...' } in social config.`
|
|
3183
|
+
);
|
|
3184
|
+
}
|
|
3185
|
+
const { JsonRpcProvider } = await import("ethers");
|
|
3186
|
+
const provider = new JsonRpcProvider(rpcUrl);
|
|
3187
|
+
const chainId = parseInt(req.chain.slice(4), 10);
|
|
3188
|
+
const [nonce, feeData] = await Promise.all([
|
|
3189
|
+
provider.getTransactionCount(evmAddr, "pending"),
|
|
3190
|
+
provider.getFeeData()
|
|
3191
|
+
]);
|
|
3192
|
+
const gasLimit = req.gasLimit ?? await provider.estimateGas({
|
|
3193
|
+
from: evmAddr,
|
|
3194
|
+
to: req.to,
|
|
3195
|
+
value: req.value ?? 0n,
|
|
3196
|
+
data: req.data
|
|
3197
|
+
});
|
|
3198
|
+
const tx = {
|
|
3199
|
+
to: req.to,
|
|
3200
|
+
value: req.value ?? 0n,
|
|
3201
|
+
data: req.data,
|
|
3202
|
+
gasLimit,
|
|
3203
|
+
chainId,
|
|
3204
|
+
nonce,
|
|
3205
|
+
// EIP-1559 fees — fall back to legacy gasPrice if unavailable
|
|
3206
|
+
...feeData.maxFeePerGas != null ? {
|
|
3207
|
+
maxFeePerGas: feeData.maxFeePerGas,
|
|
3208
|
+
maxPriorityFeePerGas: feeData.maxPriorityFeePerGas ?? 0n
|
|
3209
|
+
} : { gasPrice: req.gasPrice ?? feeData.gasPrice ?? void 0 }
|
|
3210
|
+
};
|
|
3211
|
+
const signedTx = await signEvmTransaction(tee, req.chain, tx);
|
|
3212
|
+
const txResponse = await provider.broadcastTransaction(signedTx);
|
|
3213
|
+
return txResponse.hash;
|
|
3214
|
+
},
|
|
3215
|
+
async signSolanaTransaction(serializedTx) {
|
|
3216
|
+
const tee = session.requireActive();
|
|
3217
|
+
let tx;
|
|
3218
|
+
try {
|
|
3219
|
+
tx = import_web32.VersionedTransaction.deserialize(serializedTx);
|
|
3220
|
+
} catch {
|
|
3221
|
+
tx = import_web32.Transaction.from(serializedTx);
|
|
3222
|
+
}
|
|
3223
|
+
const solChain = config.chains.find((c) => c.startsWith("solana:")) ?? "solana:mainnet";
|
|
3224
|
+
return signSolanaTransaction(tee, solChain, tx);
|
|
3225
|
+
},
|
|
3226
|
+
async disconnect() {
|
|
3227
|
+
session.end();
|
|
3228
|
+
if (auth?.jwt) {
|
|
3229
|
+
await api.revoke(auth.jwt).catch(() => {
|
|
3230
|
+
});
|
|
3231
|
+
}
|
|
3232
|
+
auth = null;
|
|
3233
|
+
persisted = null;
|
|
3234
|
+
clearPersisted(PERSIST_KEY);
|
|
3235
|
+
events.emit("disconnected");
|
|
3236
|
+
},
|
|
3237
|
+
getAddresses() {
|
|
3238
|
+
return auth?.addresses ?? persisted?.addresses ?? null;
|
|
3239
|
+
},
|
|
3240
|
+
async rotateShares(opts) {
|
|
3241
|
+
if (!auth) throw new CallSequenceError("Not connected \u2014 call connect() first");
|
|
3242
|
+
const tee = session.requireActive();
|
|
3243
|
+
const result = await tee.rotateShares(auth.jwt);
|
|
3244
|
+
try {
|
|
3245
|
+
const file = await buildRecoveryShareFile(
|
|
3246
|
+
result.recoveryShare,
|
|
3247
|
+
opts.password,
|
|
3248
|
+
auth.addresses,
|
|
3249
|
+
result.shareSetVersion,
|
|
3250
|
+
opts.passwordHint
|
|
3251
|
+
);
|
|
3252
|
+
await api.markRecoveryShareSaved(auth.jwt);
|
|
3253
|
+
return file;
|
|
3254
|
+
} finally {
|
|
3255
|
+
zeroize(result.deviceShare);
|
|
3256
|
+
zeroize(result.recoveryShare);
|
|
3257
|
+
}
|
|
3258
|
+
},
|
|
3259
|
+
async exportPortableBackup(opts) {
|
|
3260
|
+
if (!auth) throw new CallSequenceError("Not connected \u2014 call connect() first");
|
|
3261
|
+
const tee = session.requireActive();
|
|
3262
|
+
const result = await tee.exportPortableBackup(auth.jwt);
|
|
3263
|
+
try {
|
|
3264
|
+
return await buildPortableBackup(
|
|
3265
|
+
result.deviceShare,
|
|
3266
|
+
result.recoveryShare,
|
|
3267
|
+
opts.password,
|
|
3268
|
+
auth.addresses,
|
|
3269
|
+
result.shareSetVersion,
|
|
3270
|
+
opts.passwordHint
|
|
3271
|
+
);
|
|
3272
|
+
} finally {
|
|
3273
|
+
zeroize(result.deviceShare);
|
|
3274
|
+
zeroize(result.recoveryShare);
|
|
3275
|
+
}
|
|
3276
|
+
},
|
|
3277
|
+
async hasRecoveryShare() {
|
|
3278
|
+
if (!auth) throw new CallSequenceError("Not connected \u2014 call connect() first");
|
|
3279
|
+
const status = await api.getRecoveryStatus(auth.jwt);
|
|
3280
|
+
return status.hasRecoveryShare;
|
|
3281
|
+
},
|
|
3282
|
+
async shouldPromptRecovery() {
|
|
3283
|
+
if (!auth) throw new CallSequenceError("Not connected \u2014 call connect() first");
|
|
3284
|
+
const status = await api.getRecoveryStatus(auth.jwt);
|
|
3285
|
+
if (status.hasRecoveryShare || status.nagged) return false;
|
|
3286
|
+
try {
|
|
3287
|
+
const checks = [];
|
|
3288
|
+
if (auth.addresses.evm) {
|
|
3289
|
+
const { JsonRpcProvider } = await import("ethers");
|
|
3290
|
+
for (const chain of config.chains.filter((c) => c.startsWith("evm:"))) {
|
|
3291
|
+
const rpcUrl = config.rpcUrls?.[chain] ?? DEFAULT_RPCS[chain];
|
|
3292
|
+
if (!rpcUrl) continue;
|
|
3293
|
+
checks.push(
|
|
3294
|
+
new JsonRpcProvider(rpcUrl).getBalance(auth.addresses.evm).then((b) => b > 0n).catch(() => false)
|
|
3295
|
+
);
|
|
3296
|
+
}
|
|
3297
|
+
}
|
|
3298
|
+
if (auth.addresses.solana && config.chains.some((c) => c.startsWith("solana:"))) {
|
|
3299
|
+
const { Connection, PublicKey } = await import("@solana/web3.js");
|
|
3300
|
+
const solChain = config.chains.find((c) => c.startsWith("solana:")) ?? "solana:mainnet";
|
|
3301
|
+
const cluster = solChain.slice("solana:".length);
|
|
3302
|
+
const endpoint = cluster === "devnet" ? "https://api.devnet.solana.com" : "https://api.mainnet-beta.solana.com";
|
|
3303
|
+
checks.push(
|
|
3304
|
+
new Connection(endpoint).getBalance(new PublicKey(auth.addresses.solana)).then((b) => b > 0).catch(() => false)
|
|
3305
|
+
);
|
|
3306
|
+
}
|
|
3307
|
+
const results = await Promise.all(checks);
|
|
3308
|
+
return results.some(Boolean);
|
|
3309
|
+
} catch {
|
|
3310
|
+
return false;
|
|
3311
|
+
}
|
|
3312
|
+
},
|
|
3313
|
+
async dismissRecoveryPrompt() {
|
|
3314
|
+
if (!auth) throw new CallSequenceError("Not connected \u2014 call connect() first");
|
|
3315
|
+
await api.dismissRecoveryNag(auth.jwt);
|
|
3316
|
+
},
|
|
3317
|
+
async hasBackup() {
|
|
3318
|
+
if (!auth) throw new CallSequenceError("Not connected \u2014 call connect() first");
|
|
3319
|
+
return api.hasRecoveryBackup(auth.jwt);
|
|
3320
|
+
},
|
|
3321
|
+
async deleteBackup() {
|
|
3322
|
+
if (!auth) throw new CallSequenceError("Not connected \u2014 call connect() first");
|
|
3323
|
+
await api.deleteRecoveryBackup(auth.jwt);
|
|
3324
|
+
},
|
|
3325
|
+
on: events.on.bind(events),
|
|
3326
|
+
off: events.off.bind(events)
|
|
3327
|
+
};
|
|
3328
|
+
return client;
|
|
3329
|
+
}
|
|
3330
|
+
function bufferToB64u(buf6) {
|
|
3331
|
+
return btoa(String.fromCharCode(...buf6)).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
|
|
3332
|
+
}
|
|
3333
|
+
function b64uToBuffer(b64u) {
|
|
3334
|
+
const b644 = b64u.replace(/-/g, "+").replace(/_/g, "/");
|
|
3335
|
+
return Uint8Array.from(atob(b644), (c) => c.charCodeAt(0));
|
|
3336
|
+
}
|
|
3337
|
+
function toHex(bytes) {
|
|
3338
|
+
return "0x" + Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
3339
|
+
}
|
|
3340
|
+
|
|
3341
|
+
// src/social/index.ts
|
|
3342
|
+
init_errors();
|
|
3343
|
+
|
|
3344
|
+
// src/ui/WalletSelector.tsx
|
|
3345
|
+
var import_react2 = __toESM(require("react"), 1);
|
|
3346
|
+
var import_react_dom = require("react-dom");
|
|
3347
|
+
|
|
3348
|
+
// src/ui/styles.ts
|
|
3349
|
+
var tokens = {
|
|
3350
|
+
// ── Gold palette ──────────────────────────────────────────────────────────
|
|
3351
|
+
gold: {
|
|
3352
|
+
50: "#FFFDF0",
|
|
3353
|
+
100: "#FFF8D6",
|
|
3354
|
+
200: "#FFEF99",
|
|
3355
|
+
300: "#FFE04D",
|
|
3356
|
+
400: "#F5C800",
|
|
3357
|
+
// primary gold — buttons, accents
|
|
3358
|
+
500: "#D4A800",
|
|
3359
|
+
// darker gold — hover states
|
|
3360
|
+
600: "#A37F00",
|
|
3361
|
+
// deep gold — borders, active
|
|
3362
|
+
700: "#7A5E00",
|
|
3363
|
+
800: "#4D3B00",
|
|
3364
|
+
900: "#261D00"
|
|
3365
|
+
},
|
|
3366
|
+
// ── Black / dark palette ──────────────────────────────────────────────────
|
|
3367
|
+
black: {
|
|
3368
|
+
0: "#FFFFFF",
|
|
3369
|
+
50: "#F9F9F7",
|
|
3370
|
+
100: "#F0EFEB",
|
|
3371
|
+
200: "#DDDCDA",
|
|
3372
|
+
300: "#BDBBB8",
|
|
3373
|
+
400: "#8A8884",
|
|
3374
|
+
500: "#5C5A57",
|
|
3375
|
+
600: "#3D3C39",
|
|
3376
|
+
700: "#2A2927",
|
|
3377
|
+
800: "#1A1917",
|
|
3378
|
+
900: "#0D0D0B",
|
|
3379
|
+
950: "#070706"
|
|
3380
|
+
},
|
|
3381
|
+
// ── Semantic tokens — light mode ──────────────────────────────────────────
|
|
3382
|
+
light: {
|
|
3383
|
+
bg: "#FFFFFF",
|
|
3384
|
+
bgOverlay: "rgba(0, 0, 0, 0.55)",
|
|
3385
|
+
surface: "#F9F9F7",
|
|
3386
|
+
surfaceHover: "#F0EFEB",
|
|
3387
|
+
border: "#DDDCDA",
|
|
3388
|
+
borderAccent: "#F5C800",
|
|
3389
|
+
text: "#0D0D0B",
|
|
3390
|
+
textSecondary: "#5C5A57",
|
|
3391
|
+
textMuted: "#8A8884",
|
|
3392
|
+
accent: "#F5C800",
|
|
3393
|
+
accentHover: "#D4A800",
|
|
3394
|
+
accentText: "#0D0D0B",
|
|
3395
|
+
badgeEvm: "#1A1917",
|
|
3396
|
+
badgeEvmText: "#F5C800",
|
|
3397
|
+
badgeSolana: "#F5C800",
|
|
3398
|
+
badgeSolanaText: "#0D0D0B",
|
|
3399
|
+
danger: "#CC3300"
|
|
3400
|
+
},
|
|
3401
|
+
// ── Semantic tokens — dark mode ───────────────────────────────────────────
|
|
3402
|
+
dark: {
|
|
3403
|
+
bg: "#0D0D0B",
|
|
3404
|
+
bgOverlay: "rgba(0, 0, 0, 0.75)",
|
|
3405
|
+
surface: "#1A1917",
|
|
3406
|
+
surfaceHover: "#2A2927",
|
|
3407
|
+
border: "#3D3C39",
|
|
3408
|
+
borderAccent: "#F5C800",
|
|
3409
|
+
text: "#F9F9F7",
|
|
3410
|
+
textSecondary: "#BDBBB8",
|
|
3411
|
+
textMuted: "#8A8884",
|
|
3412
|
+
accent: "#F5C800",
|
|
3413
|
+
accentHover: "#FFE04D",
|
|
3414
|
+
accentText: "#0D0D0B",
|
|
3415
|
+
badgeEvm: "#F5C800",
|
|
3416
|
+
badgeEvmText: "#0D0D0B",
|
|
3417
|
+
badgeSolana: "#1A1917",
|
|
3418
|
+
badgeSolanaText: "#F5C800",
|
|
3419
|
+
danger: "#FF5533"
|
|
3420
|
+
}
|
|
3421
|
+
};
|
|
3422
|
+
var FONT_SANS = "'Plus Jakarta Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif";
|
|
3423
|
+
var FONT_MONO = "'JetBrains Mono', 'Fira Code', ui-monospace, monospace";
|
|
3424
|
+
var CSS_VARS = {
|
|
3425
|
+
light: `
|
|
3426
|
+
--dc-font-sans: ${FONT_SANS};
|
|
3427
|
+
--dc-font-mono: ${FONT_MONO};
|
|
3428
|
+
--dc-bg: ${tokens.light.bg};
|
|
3429
|
+
--dc-bg-overlay: ${tokens.light.bgOverlay};
|
|
3430
|
+
--dc-surface: ${tokens.light.surface};
|
|
3431
|
+
--dc-surface-hover: ${tokens.light.surfaceHover};
|
|
3432
|
+
--dc-border: ${tokens.light.border};
|
|
3433
|
+
--dc-border-accent: ${tokens.light.borderAccent};
|
|
3434
|
+
--dc-text: ${tokens.light.text};
|
|
3435
|
+
--dc-text-secondary: ${tokens.light.textSecondary};
|
|
3436
|
+
--dc-text-muted: ${tokens.light.textMuted};
|
|
3437
|
+
--dc-accent: ${tokens.light.accent};
|
|
3438
|
+
--dc-accent-hover: ${tokens.light.accentHover};
|
|
3439
|
+
--dc-accent-text: ${tokens.light.accentText};
|
|
3440
|
+
--dc-badge-evm: ${tokens.light.badgeEvm};
|
|
3441
|
+
--dc-badge-evm-text: ${tokens.light.badgeEvmText};
|
|
3442
|
+
--dc-badge-sol: ${tokens.light.badgeSolana};
|
|
3443
|
+
--dc-badge-sol-text: ${tokens.light.badgeSolanaText};
|
|
3444
|
+
--dc-danger: ${tokens.light.danger};
|
|
3445
|
+
`,
|
|
3446
|
+
dark: `
|
|
3447
|
+
--dc-font-sans: ${FONT_SANS};
|
|
3448
|
+
--dc-font-mono: ${FONT_MONO};
|
|
3449
|
+
--dc-bg: ${tokens.dark.bg};
|
|
3450
|
+
--dc-bg-overlay: ${tokens.dark.bgOverlay};
|
|
3451
|
+
--dc-surface: ${tokens.dark.surface};
|
|
3452
|
+
--dc-surface-hover: ${tokens.dark.surfaceHover};
|
|
3453
|
+
--dc-border: ${tokens.dark.border};
|
|
3454
|
+
--dc-border-accent: ${tokens.dark.borderAccent};
|
|
3455
|
+
--dc-text: ${tokens.dark.text};
|
|
3456
|
+
--dc-text-secondary: ${tokens.dark.textSecondary};
|
|
3457
|
+
--dc-text-muted: ${tokens.dark.textMuted};
|
|
3458
|
+
--dc-accent: ${tokens.dark.accent};
|
|
3459
|
+
--dc-accent-hover: ${tokens.dark.accentHover};
|
|
3460
|
+
--dc-accent-text: ${tokens.dark.accentText};
|
|
3461
|
+
--dc-badge-evm: ${tokens.dark.badgeEvm};
|
|
3462
|
+
--dc-badge-evm-text: ${tokens.dark.badgeEvmText};
|
|
3463
|
+
--dc-badge-sol: ${tokens.dark.badgeSolana};
|
|
3464
|
+
--dc-badge-sol-text: ${tokens.dark.badgeSolanaText};
|
|
3465
|
+
--dc-danger: ${tokens.dark.danger};
|
|
3466
|
+
`
|
|
3467
|
+
};
|
|
3468
|
+
|
|
3469
|
+
// src/ui/icons.tsx
|
|
3470
|
+
var import_react = __toESM(require("react"), 1);
|
|
3471
|
+
function WalletIcon({ src, name, size = 36 }) {
|
|
3472
|
+
if (src) {
|
|
3473
|
+
return /* @__PURE__ */ import_react.default.createElement(
|
|
3474
|
+
"img",
|
|
3475
|
+
{
|
|
3476
|
+
src,
|
|
3477
|
+
alt: name,
|
|
3478
|
+
width: size,
|
|
3479
|
+
height: size,
|
|
3480
|
+
style: { borderRadius: size * 0.25, objectFit: "cover", flexShrink: 0 }
|
|
3481
|
+
}
|
|
3482
|
+
);
|
|
3483
|
+
}
|
|
3484
|
+
const initial = name.charAt(0).toUpperCase();
|
|
3485
|
+
return /* @__PURE__ */ import_react.default.createElement(
|
|
3486
|
+
"div",
|
|
3487
|
+
{
|
|
3488
|
+
style: {
|
|
3489
|
+
width: size,
|
|
3490
|
+
height: size,
|
|
3491
|
+
borderRadius: size * 0.25,
|
|
3492
|
+
background: "linear-gradient(135deg, var(--dc-surface-hover), var(--dc-surface-active, var(--dc-surface-hover)))",
|
|
3493
|
+
border: "1px solid var(--dc-border)",
|
|
3494
|
+
display: "flex",
|
|
3495
|
+
alignItems: "center",
|
|
3496
|
+
justifyContent: "center",
|
|
3497
|
+
fontSize: size * 0.38,
|
|
1936
3498
|
fontWeight: 800,
|
|
1937
3499
|
color: "var(--dc-accent)",
|
|
1938
3500
|
flexShrink: 0,
|
|
@@ -2131,17 +3693,33 @@ function ChainOption({ label, description, badges, onClick }) {
|
|
|
2131
3693
|
/* @__PURE__ */ import_react2.default.createElement("span", { style: { fontSize: 11, color: "var(--dc-text-muted)", lineHeight: 1.4 } }, description)
|
|
2132
3694
|
);
|
|
2133
3695
|
}
|
|
3696
|
+
function hasSocialMode(mode) {
|
|
3697
|
+
return mode === "all" || mode === "social" || mode === "evm+social" || mode === "solana+social" || mode === "tron+social" || mode === "evm+tron+social";
|
|
3698
|
+
}
|
|
2134
3699
|
function WalletSelector({
|
|
2135
3700
|
provider,
|
|
2136
3701
|
open,
|
|
2137
3702
|
onClose,
|
|
2138
3703
|
onConnect,
|
|
2139
3704
|
theme = "auto",
|
|
2140
|
-
networks = "all"
|
|
3705
|
+
networks = "all",
|
|
3706
|
+
mode = "wallets",
|
|
3707
|
+
socialAuthMethods = ["google", "email"],
|
|
3708
|
+
onGoogleSignIn,
|
|
3709
|
+
onEmailStart,
|
|
3710
|
+
onEmailVerify
|
|
2141
3711
|
}) {
|
|
3712
|
+
const showSocial = hasSocialMode(mode);
|
|
3713
|
+
const showWallets = mode !== "social";
|
|
3714
|
+
const [view, setView] = (0, import_react2.useState)(showWallets ? "wallets" : "social");
|
|
2142
3715
|
const [search, setSearch] = (0, import_react2.useState)("");
|
|
2143
3716
|
const [filter, setFilter] = (0, import_react2.useState)("all");
|
|
2144
3717
|
const [pendingWallet, setPendingWallet] = (0, import_react2.useState)(null);
|
|
3718
|
+
const [emailStep, setEmailStep] = (0, import_react2.useState)("input");
|
|
3719
|
+
const [emailVal, setEmailVal] = (0, import_react2.useState)("");
|
|
3720
|
+
const [codeVal, setCodeVal] = (0, import_react2.useState)("");
|
|
3721
|
+
const [socialLoad, setSocialLoad] = (0, import_react2.useState)(false);
|
|
3722
|
+
const [socialErr, setSocialErr] = (0, import_react2.useState)("");
|
|
2145
3723
|
(0, import_react2.useEffect)(() => {
|
|
2146
3724
|
if (!open) return;
|
|
2147
3725
|
injectFont();
|
|
@@ -2157,12 +3735,55 @@ function WalletSelector({
|
|
|
2157
3735
|
setSearch("");
|
|
2158
3736
|
setFilter("all");
|
|
2159
3737
|
setPendingWallet(null);
|
|
3738
|
+
setView(showWallets ? "wallets" : "social");
|
|
3739
|
+
setEmailStep("input");
|
|
3740
|
+
setEmailVal("");
|
|
3741
|
+
setCodeVal("");
|
|
3742
|
+
setSocialErr("");
|
|
2160
3743
|
return;
|
|
2161
3744
|
}
|
|
2162
3745
|
return () => {
|
|
2163
3746
|
document.getElementById("decane-tokens")?.remove();
|
|
2164
3747
|
};
|
|
2165
|
-
}, [open]);
|
|
3748
|
+
}, [open, showWallets]);
|
|
3749
|
+
async function handleGoogleClick() {
|
|
3750
|
+
if (!onGoogleSignIn) return;
|
|
3751
|
+
setSocialLoad(true);
|
|
3752
|
+
setSocialErr("");
|
|
3753
|
+
try {
|
|
3754
|
+
await onGoogleSignIn();
|
|
3755
|
+
} catch (e) {
|
|
3756
|
+
setSocialErr(e.message);
|
|
3757
|
+
setSocialLoad(false);
|
|
3758
|
+
}
|
|
3759
|
+
}
|
|
3760
|
+
async function handleEmailStart(e) {
|
|
3761
|
+
e.preventDefault();
|
|
3762
|
+
if (!onEmailStart) return;
|
|
3763
|
+
setSocialLoad(true);
|
|
3764
|
+
setSocialErr("");
|
|
3765
|
+
try {
|
|
3766
|
+
await onEmailStart(emailVal);
|
|
3767
|
+
setEmailStep("code");
|
|
3768
|
+
} catch (e2) {
|
|
3769
|
+
setSocialErr(e2.message);
|
|
3770
|
+
} finally {
|
|
3771
|
+
setSocialLoad(false);
|
|
3772
|
+
}
|
|
3773
|
+
}
|
|
3774
|
+
async function handleEmailVerify(e) {
|
|
3775
|
+
e.preventDefault();
|
|
3776
|
+
if (!onEmailVerify) return;
|
|
3777
|
+
setSocialLoad(true);
|
|
3778
|
+
setSocialErr("");
|
|
3779
|
+
try {
|
|
3780
|
+
await onEmailVerify(emailVal, codeVal);
|
|
3781
|
+
} catch (e2) {
|
|
3782
|
+
setSocialErr(e2.message);
|
|
3783
|
+
} finally {
|
|
3784
|
+
setSocialLoad(false);
|
|
3785
|
+
}
|
|
3786
|
+
}
|
|
2166
3787
|
const handleKey = (0, import_react2.useCallback)(
|
|
2167
3788
|
(e) => {
|
|
2168
3789
|
if (e.key === "Escape") {
|
|
@@ -2375,8 +3996,8 @@ function WalletSelector({
|
|
|
2375
3996
|
}
|
|
2376
3997
|
)))
|
|
2377
3998
|
) : (
|
|
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(
|
|
3999
|
+
/* ── Wallet list / Social sign-in ────────────────────────────── */
|
|
4000
|
+
/* @__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
4001
|
"button",
|
|
2381
4002
|
{
|
|
2382
4003
|
onClick: onClose,
|
|
@@ -2407,7 +4028,28 @@ function WalletSelector({
|
|
|
2407
4028
|
}
|
|
2408
4029
|
},
|
|
2409
4030
|
/* @__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: {
|
|
4031
|
+
)), 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(
|
|
4032
|
+
"button",
|
|
4033
|
+
{
|
|
4034
|
+
key: v,
|
|
4035
|
+
onClick: () => setView(v),
|
|
4036
|
+
style: {
|
|
4037
|
+
flex: 1,
|
|
4038
|
+
padding: "7px 0",
|
|
4039
|
+
borderRadius: 8,
|
|
4040
|
+
border: "none",
|
|
4041
|
+
background: view === v ? "var(--dc-bg)" : "transparent",
|
|
4042
|
+
color: view === v ? "var(--dc-text)" : "var(--dc-text-muted)",
|
|
4043
|
+
fontSize: 13,
|
|
4044
|
+
fontWeight: view === v ? 700 : 500,
|
|
4045
|
+
cursor: "pointer",
|
|
4046
|
+
fontFamily: "inherit",
|
|
4047
|
+
boxShadow: view === v ? "0 1px 4px rgba(0,0,0,0.2)" : "none",
|
|
4048
|
+
transition: "all 0.15s"
|
|
4049
|
+
}
|
|
4050
|
+
},
|
|
4051
|
+
v === "wallets" ? "Wallet" : "Sign in"
|
|
4052
|
+
))), 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
4053
|
"svg",
|
|
2412
4054
|
{
|
|
2413
4055
|
width: "14",
|
|
@@ -2445,52 +4087,627 @@ function WalletSelector({
|
|
|
2445
4087
|
e.currentTarget.style.borderColor = "var(--dc-border)";
|
|
2446
4088
|
}
|
|
2447
4089
|
}
|
|
2448
|
-
)),
|
|
4090
|
+
))), 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(
|
|
4091
|
+
"button",
|
|
4092
|
+
{
|
|
4093
|
+
onClick: handleGoogleClick,
|
|
4094
|
+
disabled: socialLoad,
|
|
4095
|
+
style: {
|
|
4096
|
+
display: "flex",
|
|
4097
|
+
alignItems: "center",
|
|
4098
|
+
justifyContent: "center",
|
|
4099
|
+
gap: 10,
|
|
4100
|
+
width: "100%",
|
|
4101
|
+
padding: "11px 16px",
|
|
4102
|
+
background: "var(--dc-surface)",
|
|
4103
|
+
border: "1px solid var(--dc-border)",
|
|
4104
|
+
borderRadius: 10,
|
|
4105
|
+
cursor: socialLoad ? "not-allowed" : "pointer",
|
|
4106
|
+
fontSize: 14,
|
|
4107
|
+
fontWeight: 600,
|
|
4108
|
+
color: "var(--dc-text)",
|
|
4109
|
+
fontFamily: "inherit",
|
|
4110
|
+
transition: "border-color 0.12s, background 0.12s",
|
|
4111
|
+
opacity: socialLoad ? 0.6 : 1
|
|
4112
|
+
},
|
|
4113
|
+
onMouseEnter: (e) => {
|
|
4114
|
+
if (!socialLoad) e.currentTarget.style.borderColor = "var(--dc-border-accent)";
|
|
4115
|
+
},
|
|
4116
|
+
onMouseLeave: (e) => {
|
|
4117
|
+
e.currentTarget.style.borderColor = "var(--dc-border)";
|
|
4118
|
+
}
|
|
4119
|
+
},
|
|
4120
|
+
/* @__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" })),
|
|
4121
|
+
socialLoad ? "Redirecting\u2026" : "Continue with Google"
|
|
4122
|
+
), 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(
|
|
4123
|
+
"input",
|
|
4124
|
+
{
|
|
4125
|
+
type: "email",
|
|
4126
|
+
placeholder: "your@email.com",
|
|
4127
|
+
value: emailVal,
|
|
4128
|
+
onChange: (e) => setEmailVal(e.target.value),
|
|
4129
|
+
required: true,
|
|
4130
|
+
style: {
|
|
4131
|
+
width: "100%",
|
|
4132
|
+
padding: "10px 12px",
|
|
4133
|
+
background: "var(--dc-surface)",
|
|
4134
|
+
border: "1px solid var(--dc-border)",
|
|
4135
|
+
borderRadius: 10,
|
|
4136
|
+
color: "var(--dc-text)",
|
|
4137
|
+
fontSize: 13,
|
|
4138
|
+
fontFamily: "inherit",
|
|
4139
|
+
outline: "none",
|
|
4140
|
+
boxSizing: "border-box"
|
|
4141
|
+
},
|
|
4142
|
+
onFocus: (e) => {
|
|
4143
|
+
e.currentTarget.style.borderColor = "var(--dc-border-accent)";
|
|
4144
|
+
},
|
|
4145
|
+
onBlur: (e) => {
|
|
4146
|
+
e.currentTarget.style.borderColor = "var(--dc-border)";
|
|
4147
|
+
}
|
|
4148
|
+
}
|
|
4149
|
+
), /* @__PURE__ */ import_react2.default.createElement(
|
|
4150
|
+
"button",
|
|
4151
|
+
{
|
|
4152
|
+
type: "submit",
|
|
4153
|
+
disabled: socialLoad || !emailVal,
|
|
4154
|
+
style: {
|
|
4155
|
+
width: "100%",
|
|
4156
|
+
padding: "10px 16px",
|
|
4157
|
+
background: "var(--dc-accent)",
|
|
4158
|
+
color: "var(--dc-accent-text)",
|
|
4159
|
+
border: "none",
|
|
4160
|
+
borderRadius: 10,
|
|
4161
|
+
fontSize: 14,
|
|
4162
|
+
fontWeight: 700,
|
|
4163
|
+
cursor: socialLoad ? "not-allowed" : "pointer",
|
|
4164
|
+
fontFamily: "inherit",
|
|
4165
|
+
opacity: socialLoad || !emailVal ? 0.6 : 1
|
|
4166
|
+
}
|
|
4167
|
+
},
|
|
4168
|
+
socialLoad ? "Sending\u2026" : "Send code"
|
|
4169
|
+
)) : /* @__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(
|
|
4170
|
+
"input",
|
|
4171
|
+
{
|
|
4172
|
+
type: "text",
|
|
4173
|
+
placeholder: "6-digit code",
|
|
4174
|
+
value: codeVal,
|
|
4175
|
+
onChange: (e) => setCodeVal(e.target.value.replace(/\D/g, "").slice(0, 6)),
|
|
4176
|
+
autoFocus: true,
|
|
4177
|
+
required: true,
|
|
4178
|
+
style: {
|
|
4179
|
+
width: "100%",
|
|
4180
|
+
padding: "10px 12px",
|
|
4181
|
+
background: "var(--dc-surface)",
|
|
4182
|
+
border: "1px solid var(--dc-border)",
|
|
4183
|
+
borderRadius: 10,
|
|
4184
|
+
color: "var(--dc-text)",
|
|
4185
|
+
fontSize: 20,
|
|
4186
|
+
fontFamily: "monospace",
|
|
4187
|
+
letterSpacing: 8,
|
|
4188
|
+
textAlign: "center",
|
|
4189
|
+
outline: "none",
|
|
4190
|
+
boxSizing: "border-box"
|
|
4191
|
+
},
|
|
4192
|
+
onFocus: (e) => {
|
|
4193
|
+
e.currentTarget.style.borderColor = "var(--dc-border-accent)";
|
|
4194
|
+
},
|
|
4195
|
+
onBlur: (e) => {
|
|
4196
|
+
e.currentTarget.style.borderColor = "var(--dc-border)";
|
|
4197
|
+
}
|
|
4198
|
+
}
|
|
4199
|
+
), /* @__PURE__ */ import_react2.default.createElement("div", { style: { display: "flex", gap: 8 } }, /* @__PURE__ */ import_react2.default.createElement(
|
|
4200
|
+
"button",
|
|
4201
|
+
{
|
|
4202
|
+
type: "button",
|
|
4203
|
+
onClick: () => {
|
|
4204
|
+
setEmailStep("input");
|
|
4205
|
+
setCodeVal("");
|
|
4206
|
+
setSocialErr("");
|
|
4207
|
+
},
|
|
4208
|
+
style: {
|
|
4209
|
+
flex: 1,
|
|
4210
|
+
padding: "9px 0",
|
|
4211
|
+
background: "transparent",
|
|
4212
|
+
border: "1px solid var(--dc-border)",
|
|
4213
|
+
borderRadius: 10,
|
|
4214
|
+
color: "var(--dc-text-muted)",
|
|
4215
|
+
fontSize: 13,
|
|
4216
|
+
cursor: "pointer",
|
|
4217
|
+
fontFamily: "inherit"
|
|
4218
|
+
}
|
|
4219
|
+
},
|
|
4220
|
+
"Back"
|
|
4221
|
+
), /* @__PURE__ */ import_react2.default.createElement(
|
|
4222
|
+
"button",
|
|
4223
|
+
{
|
|
4224
|
+
type: "submit",
|
|
4225
|
+
disabled: socialLoad || codeVal.length < 6,
|
|
4226
|
+
style: {
|
|
4227
|
+
flex: 2,
|
|
4228
|
+
padding: "9px 0",
|
|
4229
|
+
background: "var(--dc-accent)",
|
|
4230
|
+
color: "var(--dc-accent-text)",
|
|
4231
|
+
border: "none",
|
|
4232
|
+
borderRadius: 10,
|
|
4233
|
+
fontSize: 13,
|
|
4234
|
+
fontWeight: 700,
|
|
4235
|
+
cursor: socialLoad || codeVal.length < 6 ? "not-allowed" : "pointer",
|
|
4236
|
+
fontFamily: "inherit",
|
|
4237
|
+
opacity: socialLoad || codeVal.length < 6 ? 0.6 : 1
|
|
4238
|
+
}
|
|
4239
|
+
},
|
|
4240
|
+
socialLoad ? "Verifying\u2026" : "Verify"
|
|
4241
|
+
))))), 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
4242
|
)
|
|
2450
4243
|
)
|
|
2451
4244
|
);
|
|
2452
4245
|
return (0, import_react_dom.createPortal)(modal, document.body);
|
|
2453
4246
|
}
|
|
2454
4247
|
|
|
2455
|
-
// src/ui/
|
|
4248
|
+
// src/ui/SocialWalletModal.tsx
|
|
2456
4249
|
var import_react3 = __toESM(require("react"), 1);
|
|
2457
|
-
var
|
|
4250
|
+
var import_react_dom2 = require("react-dom");
|
|
4251
|
+
function injectTokens2(theme) {
|
|
4252
|
+
const resolved = theme === "auto" ? window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light" : theme;
|
|
4253
|
+
let el = document.getElementById("decane-tokens");
|
|
4254
|
+
if (!el) {
|
|
4255
|
+
el = document.createElement("style");
|
|
4256
|
+
el.id = "decane-tokens";
|
|
4257
|
+
document.head.appendChild(el);
|
|
4258
|
+
}
|
|
4259
|
+
el.textContent = `:root { ${CSS_VARS[resolved]} }`;
|
|
4260
|
+
}
|
|
4261
|
+
function countdown(expiresAt) {
|
|
4262
|
+
const secs = Math.max(0, Math.round((expiresAt - Date.now()) / 1e3));
|
|
4263
|
+
return `${Math.floor(secs / 60)}m ${String(secs % 60).padStart(2, "0")}s`;
|
|
4264
|
+
}
|
|
4265
|
+
function CopyButton({ value }) {
|
|
4266
|
+
const [copied, setCopied] = (0, import_react3.useState)(false);
|
|
4267
|
+
async function copy() {
|
|
4268
|
+
await navigator.clipboard.writeText(value).catch(() => {
|
|
4269
|
+
});
|
|
4270
|
+
setCopied(true);
|
|
4271
|
+
setTimeout(() => setCopied(false), 1500);
|
|
4272
|
+
}
|
|
4273
|
+
return /* @__PURE__ */ import_react3.default.createElement(
|
|
4274
|
+
"button",
|
|
4275
|
+
{
|
|
4276
|
+
onClick: copy,
|
|
4277
|
+
style: {
|
|
4278
|
+
background: "none",
|
|
4279
|
+
border: "none",
|
|
4280
|
+
cursor: "pointer",
|
|
4281
|
+
color: copied ? "var(--dc-accent)" : "var(--dc-text-muted)",
|
|
4282
|
+
fontSize: 11,
|
|
4283
|
+
fontFamily: "inherit",
|
|
4284
|
+
padding: "2px 6px",
|
|
4285
|
+
borderRadius: 5,
|
|
4286
|
+
transition: "color 0.15s"
|
|
4287
|
+
}
|
|
4288
|
+
},
|
|
4289
|
+
copied ? "\u2713" : "copy"
|
|
4290
|
+
);
|
|
4291
|
+
}
|
|
4292
|
+
function AddressRow({ label, address }) {
|
|
4293
|
+
const short = `${address.slice(0, 8)}\u2026${address.slice(-6)}`;
|
|
4294
|
+
return /* @__PURE__ */ import_react3.default.createElement("div", { style: {
|
|
4295
|
+
background: "var(--dc-surface)",
|
|
4296
|
+
border: "1px solid var(--dc-border)",
|
|
4297
|
+
borderRadius: 10,
|
|
4298
|
+
padding: "10px 14px",
|
|
4299
|
+
marginBottom: 8
|
|
4300
|
+
} }, /* @__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 })));
|
|
4301
|
+
}
|
|
4302
|
+
function SocialWalletModal({
|
|
4303
|
+
open,
|
|
4304
|
+
onClose,
|
|
4305
|
+
theme = "auto",
|
|
4306
|
+
socialAuth,
|
|
4307
|
+
onUnlock,
|
|
4308
|
+
onDisconnect
|
|
4309
|
+
}) {
|
|
4310
|
+
const [timer, setTimer] = (0, import_react3.useState)("");
|
|
4311
|
+
const [unlocking, setUnlocking] = (0, import_react3.useState)(false);
|
|
4312
|
+
const [disconnecting, setDisconnecting] = (0, import_react3.useState)(false);
|
|
4313
|
+
(0, import_react3.useEffect)(() => {
|
|
4314
|
+
if (!open) return;
|
|
4315
|
+
injectTokens2(theme);
|
|
4316
|
+
if (theme !== "auto") return;
|
|
4317
|
+
const mq = window.matchMedia("(prefers-color-scheme: dark)");
|
|
4318
|
+
const h = () => injectTokens2("auto");
|
|
4319
|
+
mq.addEventListener("change", h);
|
|
4320
|
+
return () => mq.removeEventListener("change", h);
|
|
4321
|
+
}, [open, theme]);
|
|
4322
|
+
(0, import_react3.useEffect)(() => {
|
|
4323
|
+
if (!socialAuth.sessionExpiresAt) {
|
|
4324
|
+
setTimer("");
|
|
4325
|
+
return;
|
|
4326
|
+
}
|
|
4327
|
+
setTimer(countdown(socialAuth.sessionExpiresAt));
|
|
4328
|
+
const id = setInterval(() => setTimer(countdown(socialAuth.sessionExpiresAt)), 1e3);
|
|
4329
|
+
return () => clearInterval(id);
|
|
4330
|
+
}, [socialAuth.sessionExpiresAt]);
|
|
4331
|
+
const handleKey = (0, import_react3.useCallback)((e) => {
|
|
4332
|
+
if (e.key === "Escape") onClose();
|
|
4333
|
+
}, [onClose]);
|
|
4334
|
+
(0, import_react3.useEffect)(() => {
|
|
4335
|
+
if (!open) return;
|
|
4336
|
+
document.addEventListener("keydown", handleKey);
|
|
4337
|
+
return () => document.removeEventListener("keydown", handleKey);
|
|
4338
|
+
}, [open, handleKey]);
|
|
4339
|
+
if (!open) return null;
|
|
4340
|
+
async function handleUnlock() {
|
|
4341
|
+
setUnlocking(true);
|
|
4342
|
+
try {
|
|
4343
|
+
await onUnlock();
|
|
4344
|
+
} finally {
|
|
4345
|
+
setUnlocking(false);
|
|
4346
|
+
}
|
|
4347
|
+
}
|
|
4348
|
+
async function handleDisconnect() {
|
|
4349
|
+
setDisconnecting(true);
|
|
4350
|
+
try {
|
|
4351
|
+
await onDisconnect();
|
|
4352
|
+
onClose();
|
|
4353
|
+
} finally {
|
|
4354
|
+
setDisconnecting(false);
|
|
4355
|
+
}
|
|
4356
|
+
}
|
|
4357
|
+
const modal = /* @__PURE__ */ import_react3.default.createElement(
|
|
4358
|
+
"div",
|
|
4359
|
+
{
|
|
4360
|
+
role: "dialog",
|
|
4361
|
+
"aria-modal": "true",
|
|
4362
|
+
"aria-label": "Social wallet",
|
|
4363
|
+
style: {
|
|
4364
|
+
position: "fixed",
|
|
4365
|
+
inset: 0,
|
|
4366
|
+
zIndex: 9999,
|
|
4367
|
+
display: "flex",
|
|
4368
|
+
alignItems: "center",
|
|
4369
|
+
justifyContent: "center",
|
|
4370
|
+
padding: 16,
|
|
4371
|
+
fontFamily: "var(--dc-font-sans, system-ui, sans-serif)"
|
|
4372
|
+
}
|
|
4373
|
+
},
|
|
4374
|
+
/* @__PURE__ */ import_react3.default.createElement(
|
|
4375
|
+
"div",
|
|
4376
|
+
{
|
|
4377
|
+
onClick: onClose,
|
|
4378
|
+
style: {
|
|
4379
|
+
position: "absolute",
|
|
4380
|
+
inset: 0,
|
|
4381
|
+
background: "var(--dc-bg-overlay)",
|
|
4382
|
+
backdropFilter: "blur(8px)",
|
|
4383
|
+
WebkitBackdropFilter: "blur(8px)"
|
|
4384
|
+
}
|
|
4385
|
+
}
|
|
4386
|
+
),
|
|
4387
|
+
/* @__PURE__ */ import_react3.default.createElement("div", { style: {
|
|
4388
|
+
position: "relative",
|
|
4389
|
+
background: "var(--dc-bg)",
|
|
4390
|
+
border: "1px solid var(--dc-border)",
|
|
4391
|
+
borderRadius: 20,
|
|
4392
|
+
width: "100%",
|
|
4393
|
+
maxWidth: 380,
|
|
4394
|
+
boxShadow: "0 24px 64px rgba(0,0,0,0.6), 0 0 0 1px rgba(255,255,255,0.04)",
|
|
4395
|
+
overflow: "hidden"
|
|
4396
|
+
} }, /* @__PURE__ */ import_react3.default.createElement("div", { style: {
|
|
4397
|
+
height: 2,
|
|
4398
|
+
background: "linear-gradient(90deg, transparent, var(--dc-accent) 30%, var(--dc-accent) 70%, transparent)"
|
|
4399
|
+
} }), /* @__PURE__ */ import_react3.default.createElement("div", { style: { padding: "18px 20px 16px", display: "flex", alignItems: "center", gap: 10 } }, /* @__PURE__ */ import_react3.default.createElement("div", { style: {
|
|
4400
|
+
width: 34,
|
|
4401
|
+
height: 34,
|
|
4402
|
+
borderRadius: 10,
|
|
4403
|
+
background: "var(--dc-surface)",
|
|
4404
|
+
border: "1px solid var(--dc-border)",
|
|
4405
|
+
display: "flex",
|
|
4406
|
+
alignItems: "center",
|
|
4407
|
+
justifyContent: "center",
|
|
4408
|
+
flexShrink: 0
|
|
4409
|
+
} }, /* @__PURE__ */ import_react3.default.createElement("svg", { width: "16", height: "18", viewBox: "0 0 16 18", fill: "none" }, /* @__PURE__ */ import_react3.default.createElement(
|
|
4410
|
+
"path",
|
|
4411
|
+
{
|
|
4412
|
+
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",
|
|
4413
|
+
stroke: "var(--dc-accent)",
|
|
4414
|
+
strokeWidth: "1.4",
|
|
4415
|
+
strokeLinejoin: "round"
|
|
4416
|
+
}
|
|
4417
|
+
), /* @__PURE__ */ import_react3.default.createElement(
|
|
4418
|
+
"path",
|
|
4419
|
+
{
|
|
4420
|
+
d: "M5.5 9L7 10.5L10.5 7",
|
|
4421
|
+
stroke: "var(--dc-accent)",
|
|
4422
|
+
strokeWidth: "1.4",
|
|
4423
|
+
strokeLinecap: "round",
|
|
4424
|
+
strokeLinejoin: "round"
|
|
4425
|
+
}
|
|
4426
|
+
))), /* @__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(
|
|
4427
|
+
"button",
|
|
4428
|
+
{
|
|
4429
|
+
onClick: onClose,
|
|
4430
|
+
style: {
|
|
4431
|
+
background: "var(--dc-surface)",
|
|
4432
|
+
border: "1px solid var(--dc-border)",
|
|
4433
|
+
borderRadius: 8,
|
|
4434
|
+
width: 28,
|
|
4435
|
+
height: 28,
|
|
4436
|
+
cursor: "pointer",
|
|
4437
|
+
display: "flex",
|
|
4438
|
+
alignItems: "center",
|
|
4439
|
+
justifyContent: "center",
|
|
4440
|
+
color: "var(--dc-text-muted)",
|
|
4441
|
+
flexShrink: 0,
|
|
4442
|
+
padding: 0
|
|
4443
|
+
}
|
|
4444
|
+
},
|
|
4445
|
+
/* @__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" }))
|
|
4446
|
+
)), /* @__PURE__ */ import_react3.default.createElement("div", { style: {
|
|
4447
|
+
margin: "0 16px 14px",
|
|
4448
|
+
padding: "8px 14px",
|
|
4449
|
+
background: socialAuth.isUnlocked ? "rgba(18,183,106,0.08)" : "rgba(245,200,0,0.08)",
|
|
4450
|
+
border: `1px solid ${socialAuth.isUnlocked ? "rgba(18,183,106,0.2)" : "rgba(245,200,0,0.2)"}`,
|
|
4451
|
+
borderRadius: 10,
|
|
4452
|
+
display: "flex",
|
|
4453
|
+
alignItems: "center",
|
|
4454
|
+
justifyContent: "space-between"
|
|
4455
|
+
} }, /* @__PURE__ */ import_react3.default.createElement("div", { style: { display: "flex", alignItems: "center", gap: 8 } }, /* @__PURE__ */ import_react3.default.createElement("span", { style: {
|
|
4456
|
+
width: 7,
|
|
4457
|
+
height: 7,
|
|
4458
|
+
borderRadius: "50%",
|
|
4459
|
+
flexShrink: 0,
|
|
4460
|
+
background: socialAuth.isUnlocked ? "#12b76a" : "var(--dc-accent)",
|
|
4461
|
+
boxShadow: `0 0 6px ${socialAuth.isUnlocked ? "#12b76a" : "var(--dc-accent)"}`
|
|
4462
|
+
} }), /* @__PURE__ */ import_react3.default.createElement("span", { style: {
|
|
4463
|
+
fontSize: 12,
|
|
4464
|
+
fontWeight: 700,
|
|
4465
|
+
color: socialAuth.isUnlocked ? "#12b76a" : "var(--dc-accent)"
|
|
4466
|
+
} }, 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: {
|
|
4467
|
+
padding: "14px 16px",
|
|
4468
|
+
borderTop: "1px solid var(--dc-border)",
|
|
4469
|
+
display: "flex",
|
|
4470
|
+
gap: 8
|
|
4471
|
+
} }, !socialAuth.isUnlocked && /* @__PURE__ */ import_react3.default.createElement(
|
|
4472
|
+
"button",
|
|
4473
|
+
{
|
|
4474
|
+
onClick: handleUnlock,
|
|
4475
|
+
disabled: unlocking,
|
|
4476
|
+
style: {
|
|
4477
|
+
flex: 1,
|
|
4478
|
+
padding: "10px 0",
|
|
4479
|
+
background: "var(--dc-accent)",
|
|
4480
|
+
color: "var(--dc-accent-text)",
|
|
4481
|
+
border: "none",
|
|
4482
|
+
borderRadius: 10,
|
|
4483
|
+
fontSize: 13,
|
|
4484
|
+
fontWeight: 700,
|
|
4485
|
+
cursor: unlocking ? "not-allowed" : "pointer",
|
|
4486
|
+
fontFamily: "inherit",
|
|
4487
|
+
opacity: unlocking ? 0.7 : 1
|
|
4488
|
+
}
|
|
4489
|
+
},
|
|
4490
|
+
unlocking ? "Unlocking\u2026" : "\u{1F513} Unlock wallet"
|
|
4491
|
+
), /* @__PURE__ */ import_react3.default.createElement(
|
|
4492
|
+
"button",
|
|
4493
|
+
{
|
|
4494
|
+
onClick: handleDisconnect,
|
|
4495
|
+
disabled: disconnecting,
|
|
4496
|
+
style: {
|
|
4497
|
+
flex: socialAuth.isUnlocked ? 1 : 0,
|
|
4498
|
+
padding: "10px 16px",
|
|
4499
|
+
background: "transparent",
|
|
4500
|
+
border: "1px solid var(--dc-border)",
|
|
4501
|
+
borderRadius: 10,
|
|
4502
|
+
color: "var(--dc-text-muted)",
|
|
4503
|
+
fontSize: 13,
|
|
4504
|
+
cursor: disconnecting ? "not-allowed" : "pointer",
|
|
4505
|
+
fontFamily: "inherit",
|
|
4506
|
+
opacity: disconnecting ? 0.7 : 1,
|
|
4507
|
+
whiteSpace: "nowrap"
|
|
4508
|
+
}
|
|
4509
|
+
},
|
|
4510
|
+
disconnecting ? "Signing out\u2026" : "Sign out"
|
|
4511
|
+
)))
|
|
4512
|
+
);
|
|
4513
|
+
return (0, import_react_dom2.createPortal)(modal, document.body);
|
|
4514
|
+
}
|
|
4515
|
+
|
|
4516
|
+
// src/ui/context.tsx
|
|
4517
|
+
var import_react4 = __toESM(require("react"), 1);
|
|
4518
|
+
var DEFAULT_SOCIAL = {
|
|
4519
|
+
addresses: null,
|
|
4520
|
+
isNewUser: false,
|
|
4521
|
+
isUnlocked: false,
|
|
4522
|
+
sessionExpiresAt: null
|
|
4523
|
+
};
|
|
4524
|
+
var DecaneContext = (0, import_react4.createContext)(null);
|
|
4525
|
+
function hasSocial(mode) {
|
|
4526
|
+
return mode === "all" || mode === "social" || mode === "evm+social" || mode === "solana+social" || mode === "tron+social" || mode === "evm+tron+social";
|
|
4527
|
+
}
|
|
4528
|
+
function deriveNetworks(mode, explicit) {
|
|
4529
|
+
if (explicit) return explicit;
|
|
4530
|
+
if (mode === "evm" || mode === "evm+social") return "evm";
|
|
4531
|
+
if (mode === "solana" || mode === "solana+social") return "solana";
|
|
4532
|
+
if (mode === "tron" || mode === "tron+social") return "tron";
|
|
4533
|
+
return "all";
|
|
4534
|
+
}
|
|
2458
4535
|
function DecaneKit({ config, children }) {
|
|
2459
|
-
const
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
const
|
|
2464
|
-
const [
|
|
2465
|
-
const
|
|
2466
|
-
const
|
|
2467
|
-
const
|
|
4536
|
+
const mode = config?.mode ?? "wallets";
|
|
4537
|
+
const walletRef = (0, import_react4.useRef)(null);
|
|
4538
|
+
if (!walletRef.current) walletRef.current = createDecane(config);
|
|
4539
|
+
const socialRef = (0, import_react4.useRef)(null);
|
|
4540
|
+
const socialInitialized = (0, import_react4.useRef)(false);
|
|
4541
|
+
const [selectorOpen, setSelectorOpen] = (0, import_react4.useState)(false);
|
|
4542
|
+
const [socialModalOpen, setSocialModalOpen] = (0, import_react4.useState)(false);
|
|
4543
|
+
const [theme, setTheme] = (0, import_react4.useState)(config?.theme ?? "auto");
|
|
4544
|
+
const [socialAuth, setSocialAuth] = (0, import_react4.useState)(DEFAULT_SOCIAL);
|
|
4545
|
+
const openSelector = (0, import_react4.useCallback)(() => setSelectorOpen(true), []);
|
|
4546
|
+
const closeSelector = (0, import_react4.useCallback)(() => setSelectorOpen(false), []);
|
|
4547
|
+
const openSocialModal = (0, import_react4.useCallback)(() => setSocialModalOpen(true), []);
|
|
4548
|
+
const closeSocialModal = (0, import_react4.useCallback)(() => setSocialModalOpen(false), []);
|
|
4549
|
+
(0, import_react4.useEffect)(() => {
|
|
4550
|
+
if (!socialAuth.isUnlocked) return;
|
|
4551
|
+
const id = setInterval(() => {
|
|
4552
|
+
const sdk = socialRef.current;
|
|
4553
|
+
if (!sdk) return;
|
|
4554
|
+
setSocialAuth((p) => ({
|
|
4555
|
+
...p,
|
|
4556
|
+
isUnlocked: sdk.isUnlocked(),
|
|
4557
|
+
sessionExpiresAt: sdk.sessionExpiresAt()
|
|
4558
|
+
}));
|
|
4559
|
+
}, 1e4);
|
|
4560
|
+
return () => clearInterval(id);
|
|
4561
|
+
}, [socialAuth.isUnlocked]);
|
|
4562
|
+
(0, import_react4.useEffect)(() => {
|
|
4563
|
+
if (!hasSocial(mode) || !config?.social || socialInitialized.current) return;
|
|
4564
|
+
socialInitialized.current = true;
|
|
4565
|
+
const { apiKey, authMethods, promptPin, apiBaseUrl, chains, rpcUrls } = config.social;
|
|
4566
|
+
createDecaneConnect({
|
|
4567
|
+
apiKey,
|
|
4568
|
+
apiBaseUrl,
|
|
4569
|
+
authMethods: authMethods ?? ["google", "email"],
|
|
4570
|
+
chains: chains ?? ["evm:1", "solana:mainnet"],
|
|
4571
|
+
rpcUrls,
|
|
4572
|
+
promptPin
|
|
4573
|
+
}).then((sdk) => {
|
|
4574
|
+
socialRef.current = sdk;
|
|
4575
|
+
sdk.on(
|
|
4576
|
+
"session-expired",
|
|
4577
|
+
() => setSocialAuth((p) => ({ ...p, isUnlocked: false, sessionExpiresAt: null }))
|
|
4578
|
+
);
|
|
4579
|
+
sdk.on("disconnected", () => setSocialAuth(DEFAULT_SOCIAL));
|
|
4580
|
+
const saved = sdk.getAddresses();
|
|
4581
|
+
if (saved) {
|
|
4582
|
+
setSocialAuth((p) => ({ ...p, addresses: saved, isUnlocked: false }));
|
|
4583
|
+
}
|
|
4584
|
+
const params = new URLSearchParams(window.location.search);
|
|
4585
|
+
if (params.has("decane_jwt") || params.has("decane_error")) {
|
|
4586
|
+
sdk.connectWithGoogle().then(applyResult).catch(console.error);
|
|
4587
|
+
}
|
|
4588
|
+
}).catch(console.error);
|
|
4589
|
+
}, []);
|
|
4590
|
+
function applyResult(result) {
|
|
4591
|
+
const sdk = socialRef.current;
|
|
4592
|
+
setSocialAuth({
|
|
4593
|
+
addresses: result.addresses,
|
|
4594
|
+
isNewUser: result.isNewUser,
|
|
4595
|
+
isUnlocked: sdk?.isUnlocked() ?? false,
|
|
4596
|
+
sessionExpiresAt: sdk?.sessionExpiresAt() ?? null
|
|
4597
|
+
});
|
|
4598
|
+
}
|
|
4599
|
+
const connectWithGoogle = (0, import_react4.useCallback)(async () => {
|
|
4600
|
+
const sdk = socialRef.current;
|
|
4601
|
+
if (!sdk) throw new Error("Social sign-in not configured");
|
|
4602
|
+
await sdk.connectWithGoogle();
|
|
4603
|
+
}, []);
|
|
4604
|
+
const connectWithEmail = (0, import_react4.useCallback)(async (email) => {
|
|
4605
|
+
const sdk = socialRef.current;
|
|
4606
|
+
if (!sdk) throw new Error("Social sign-in not configured");
|
|
4607
|
+
return sdk.connectWithEmail(email);
|
|
4608
|
+
}, []);
|
|
4609
|
+
const verifyEmailCode2 = (0, import_react4.useCallback)(async (email, code) => {
|
|
4610
|
+
const sdk = socialRef.current;
|
|
4611
|
+
if (!sdk) throw new Error("Social sign-in not configured");
|
|
4612
|
+
const result = await sdk.verifyEmailCode(email, code);
|
|
4613
|
+
applyResult(result);
|
|
4614
|
+
setSelectorOpen(false);
|
|
4615
|
+
}, []);
|
|
4616
|
+
const socialUnlock = (0, import_react4.useCallback)(async () => {
|
|
4617
|
+
const sdk = socialRef.current;
|
|
4618
|
+
if (!sdk) return;
|
|
4619
|
+
await sdk.unlock();
|
|
4620
|
+
setSocialAuth((p) => ({
|
|
4621
|
+
...p,
|
|
4622
|
+
isUnlocked: sdk.isUnlocked(),
|
|
4623
|
+
sessionExpiresAt: sdk.sessionExpiresAt()
|
|
4624
|
+
}));
|
|
4625
|
+
}, []);
|
|
4626
|
+
const socialDisconnect = (0, import_react4.useCallback)(async () => {
|
|
4627
|
+
const sdk = socialRef.current;
|
|
4628
|
+
if (!sdk) return;
|
|
4629
|
+
await sdk.disconnect();
|
|
4630
|
+
setSocialAuth(DEFAULT_SOCIAL);
|
|
4631
|
+
}, []);
|
|
4632
|
+
const socialSignMessage = (0, import_react4.useCallback)(async (chain, message) => {
|
|
4633
|
+
const sdk = socialRef.current;
|
|
4634
|
+
if (!sdk) throw new Error("Social wallet not connected");
|
|
4635
|
+
return sdk.signMessage({ chain, message });
|
|
4636
|
+
}, []);
|
|
4637
|
+
const socialSendTransaction = (0, import_react4.useCallback)(async (req) => {
|
|
4638
|
+
const sdk = socialRef.current;
|
|
4639
|
+
if (!sdk) throw new Error("Social wallet not connected");
|
|
4640
|
+
return sdk.sendTransaction({ ...req, chain: req.chain });
|
|
4641
|
+
}, []);
|
|
4642
|
+
const socialSignSolanaTransaction = (0, import_react4.useCallback)(async (tx) => {
|
|
4643
|
+
const sdk = socialRef.current;
|
|
4644
|
+
if (!sdk) throw new Error("Social wallet not connected");
|
|
4645
|
+
return sdk.signSolanaTransaction(tx);
|
|
4646
|
+
}, []);
|
|
4647
|
+
const handleWalletConnect = (0, import_react4.useCallback)(
|
|
2468
4648
|
(wallet, preference) => {
|
|
2469
|
-
|
|
4649
|
+
walletRef.current.connect(wallet, preference).catch(console.error);
|
|
2470
4650
|
},
|
|
2471
4651
|
[]
|
|
2472
4652
|
);
|
|
2473
|
-
|
|
4653
|
+
const networks = deriveNetworks(mode, config?.networks);
|
|
4654
|
+
return /* @__PURE__ */ import_react4.default.createElement(
|
|
2474
4655
|
DecaneContext.Provider,
|
|
2475
4656
|
{
|
|
2476
|
-
value: {
|
|
4657
|
+
value: {
|
|
4658
|
+
provider: walletRef.current,
|
|
4659
|
+
selectorOpen,
|
|
4660
|
+
openSelector,
|
|
4661
|
+
closeSelector,
|
|
4662
|
+
theme,
|
|
4663
|
+
setTheme,
|
|
4664
|
+
mode,
|
|
4665
|
+
socialAuth,
|
|
4666
|
+
connectWithGoogle,
|
|
4667
|
+
connectWithEmail,
|
|
4668
|
+
verifyEmailCode: verifyEmailCode2,
|
|
4669
|
+
socialUnlock,
|
|
4670
|
+
socialDisconnect,
|
|
4671
|
+
socialSignMessage,
|
|
4672
|
+
socialSendTransaction,
|
|
4673
|
+
socialSignSolanaTransaction,
|
|
4674
|
+
socialModalOpen,
|
|
4675
|
+
openSocialModal,
|
|
4676
|
+
closeSocialModal
|
|
4677
|
+
}
|
|
2477
4678
|
},
|
|
2478
4679
|
children,
|
|
2479
|
-
/* @__PURE__ */
|
|
4680
|
+
/* @__PURE__ */ import_react4.default.createElement(
|
|
2480
4681
|
WalletSelector,
|
|
2481
4682
|
{
|
|
2482
|
-
provider:
|
|
4683
|
+
provider: walletRef.current,
|
|
2483
4684
|
open: selectorOpen,
|
|
2484
4685
|
onClose: closeSelector,
|
|
2485
|
-
onConnect:
|
|
4686
|
+
onConnect: handleWalletConnect,
|
|
4687
|
+
theme,
|
|
4688
|
+
networks,
|
|
4689
|
+
mode,
|
|
4690
|
+
socialAuthMethods: config?.social?.authMethods ?? ["google", "email"],
|
|
4691
|
+
onGoogleSignIn: connectWithGoogle,
|
|
4692
|
+
onEmailStart: connectWithEmail,
|
|
4693
|
+
onEmailVerify: verifyEmailCode2
|
|
4694
|
+
}
|
|
4695
|
+
),
|
|
4696
|
+
/* @__PURE__ */ import_react4.default.createElement(
|
|
4697
|
+
SocialWalletModal,
|
|
4698
|
+
{
|
|
4699
|
+
open: socialModalOpen,
|
|
4700
|
+
onClose: closeSocialModal,
|
|
2486
4701
|
theme,
|
|
2487
|
-
|
|
4702
|
+
socialAuth,
|
|
4703
|
+
onUnlock: socialUnlock,
|
|
4704
|
+
onDisconnect: socialDisconnect
|
|
2488
4705
|
}
|
|
2489
4706
|
)
|
|
2490
4707
|
);
|
|
2491
4708
|
}
|
|
2492
4709
|
function useDecaneContext() {
|
|
2493
|
-
const ctx = (0,
|
|
4710
|
+
const ctx = (0, import_react4.useContext)(DecaneContext);
|
|
2494
4711
|
if (!ctx) {
|
|
2495
4712
|
throw new Error(
|
|
2496
4713
|
"Decane hooks must be called inside <DecaneKit>. Wrap your app: <DecaneKit config={...}><App /></DecaneKit>"
|
|
@@ -2500,7 +4717,169 @@ function useDecaneContext() {
|
|
|
2500
4717
|
}
|
|
2501
4718
|
|
|
2502
4719
|
// src/ui/hooks.ts
|
|
2503
|
-
var
|
|
4720
|
+
var import_react5 = require("react");
|
|
4721
|
+
function useSocialAuth() {
|
|
4722
|
+
const {
|
|
4723
|
+
socialAuth,
|
|
4724
|
+
connectWithGoogle,
|
|
4725
|
+
connectWithEmail,
|
|
4726
|
+
verifyEmailCode: verifyEmailCode2,
|
|
4727
|
+
socialUnlock,
|
|
4728
|
+
socialDisconnect
|
|
4729
|
+
} = useDecaneContext();
|
|
4730
|
+
const [googleLoading, setGoogleLoading] = (0, import_react5.useState)(false);
|
|
4731
|
+
const [emailLoading, setEmailLoading] = (0, import_react5.useState)(false);
|
|
4732
|
+
const [error, setError] = (0, import_react5.useState)(null);
|
|
4733
|
+
const signInWithGoogle = (0, import_react5.useCallback)(async () => {
|
|
4734
|
+
setGoogleLoading(true);
|
|
4735
|
+
setError(null);
|
|
4736
|
+
try {
|
|
4737
|
+
await connectWithGoogle();
|
|
4738
|
+
} catch (e) {
|
|
4739
|
+
setError(e.message);
|
|
4740
|
+
setGoogleLoading(false);
|
|
4741
|
+
}
|
|
4742
|
+
}, [connectWithGoogle]);
|
|
4743
|
+
const sendEmailCode = (0, import_react5.useCallback)(async (email) => {
|
|
4744
|
+
setEmailLoading(true);
|
|
4745
|
+
setError(null);
|
|
4746
|
+
try {
|
|
4747
|
+
await connectWithEmail(email);
|
|
4748
|
+
return { codeRequired: true };
|
|
4749
|
+
} catch (e) {
|
|
4750
|
+
setError(e.message);
|
|
4751
|
+
throw e;
|
|
4752
|
+
} finally {
|
|
4753
|
+
setEmailLoading(false);
|
|
4754
|
+
}
|
|
4755
|
+
}, [connectWithEmail]);
|
|
4756
|
+
const confirmEmailCode = (0, import_react5.useCallback)(async (email, code) => {
|
|
4757
|
+
setEmailLoading(true);
|
|
4758
|
+
setError(null);
|
|
4759
|
+
try {
|
|
4760
|
+
await verifyEmailCode2(email, code);
|
|
4761
|
+
} catch (e) {
|
|
4762
|
+
setError(e.message);
|
|
4763
|
+
throw e;
|
|
4764
|
+
} finally {
|
|
4765
|
+
setEmailLoading(false);
|
|
4766
|
+
}
|
|
4767
|
+
}, [verifyEmailCode2]);
|
|
4768
|
+
const unlock = (0, import_react5.useCallback)(async () => {
|
|
4769
|
+
setError(null);
|
|
4770
|
+
try {
|
|
4771
|
+
await socialUnlock();
|
|
4772
|
+
} catch (e) {
|
|
4773
|
+
setError(e.message);
|
|
4774
|
+
throw e;
|
|
4775
|
+
}
|
|
4776
|
+
}, [socialUnlock]);
|
|
4777
|
+
const disconnect = (0, import_react5.useCallback)(async () => {
|
|
4778
|
+
setError(null);
|
|
4779
|
+
try {
|
|
4780
|
+
await socialDisconnect();
|
|
4781
|
+
} catch (e) {
|
|
4782
|
+
setError(e.message);
|
|
4783
|
+
throw e;
|
|
4784
|
+
}
|
|
4785
|
+
}, [socialDisconnect]);
|
|
4786
|
+
return {
|
|
4787
|
+
isConnected: !!socialAuth.addresses,
|
|
4788
|
+
isUnlocked: socialAuth.isUnlocked,
|
|
4789
|
+
isNewUser: socialAuth.isNewUser,
|
|
4790
|
+
addresses: socialAuth.addresses,
|
|
4791
|
+
sessionExpiresAt: socialAuth.sessionExpiresAt,
|
|
4792
|
+
googleLoading,
|
|
4793
|
+
emailLoading,
|
|
4794
|
+
error,
|
|
4795
|
+
clearError: () => setError(null),
|
|
4796
|
+
signInWithGoogle,
|
|
4797
|
+
sendEmailCode,
|
|
4798
|
+
confirmEmailCode,
|
|
4799
|
+
unlock,
|
|
4800
|
+
disconnect
|
|
4801
|
+
};
|
|
4802
|
+
}
|
|
4803
|
+
function useSocialWallet() {
|
|
4804
|
+
const {
|
|
4805
|
+
socialAuth,
|
|
4806
|
+
socialUnlock,
|
|
4807
|
+
socialDisconnect,
|
|
4808
|
+
socialSignMessage,
|
|
4809
|
+
socialSendTransaction,
|
|
4810
|
+
socialSignSolanaTransaction,
|
|
4811
|
+
openSocialModal,
|
|
4812
|
+
closeSocialModal,
|
|
4813
|
+
socialModalOpen
|
|
4814
|
+
} = useDecaneContext();
|
|
4815
|
+
const [loading, setLoading] = (0, import_react5.useState)(false);
|
|
4816
|
+
const [error, setError] = (0, import_react5.useState)(null);
|
|
4817
|
+
async function run(fn) {
|
|
4818
|
+
setLoading(true);
|
|
4819
|
+
setError(null);
|
|
4820
|
+
try {
|
|
4821
|
+
return await fn();
|
|
4822
|
+
} catch (e) {
|
|
4823
|
+
const msg = e.message;
|
|
4824
|
+
setError(msg);
|
|
4825
|
+
throw e;
|
|
4826
|
+
} finally {
|
|
4827
|
+
setLoading(false);
|
|
4828
|
+
}
|
|
4829
|
+
}
|
|
4830
|
+
const signMessage = (0, import_react5.useCallback)(
|
|
4831
|
+
(chain, message) => run(() => socialSignMessage(chain, message)),
|
|
4832
|
+
[socialSignMessage]
|
|
4833
|
+
);
|
|
4834
|
+
const sendTransaction = (0, import_react5.useCallback)(
|
|
4835
|
+
(req) => run(() => socialSendTransaction(req)),
|
|
4836
|
+
[socialSendTransaction]
|
|
4837
|
+
);
|
|
4838
|
+
const signSolanaTransaction2 = (0, import_react5.useCallback)(
|
|
4839
|
+
(tx) => run(() => socialSignSolanaTransaction(tx)),
|
|
4840
|
+
[socialSignSolanaTransaction]
|
|
4841
|
+
);
|
|
4842
|
+
const unlock = (0, import_react5.useCallback)(async () => {
|
|
4843
|
+
setError(null);
|
|
4844
|
+
try {
|
|
4845
|
+
await socialUnlock();
|
|
4846
|
+
} catch (e) {
|
|
4847
|
+
setError(e.message);
|
|
4848
|
+
throw e;
|
|
4849
|
+
}
|
|
4850
|
+
}, [socialUnlock]);
|
|
4851
|
+
const disconnect = (0, import_react5.useCallback)(async () => {
|
|
4852
|
+
setError(null);
|
|
4853
|
+
try {
|
|
4854
|
+
await socialDisconnect();
|
|
4855
|
+
} catch (e) {
|
|
4856
|
+
setError(e.message);
|
|
4857
|
+
throw e;
|
|
4858
|
+
}
|
|
4859
|
+
}, [socialDisconnect]);
|
|
4860
|
+
return {
|
|
4861
|
+
// State
|
|
4862
|
+
isConnected: !!socialAuth.addresses,
|
|
4863
|
+
isUnlocked: socialAuth.isUnlocked,
|
|
4864
|
+
isNewUser: socialAuth.isNewUser,
|
|
4865
|
+
addresses: socialAuth.addresses,
|
|
4866
|
+
sessionExpiresAt: socialAuth.sessionExpiresAt,
|
|
4867
|
+
// Modal
|
|
4868
|
+
modalOpen: socialModalOpen,
|
|
4869
|
+
openModal: openSocialModal,
|
|
4870
|
+
closeModal: closeSocialModal,
|
|
4871
|
+
// Auth
|
|
4872
|
+
unlock,
|
|
4873
|
+
disconnect,
|
|
4874
|
+
// Signing
|
|
4875
|
+
signMessage,
|
|
4876
|
+
sendTransaction,
|
|
4877
|
+
signSolanaTransaction: signSolanaTransaction2,
|
|
4878
|
+
loading,
|
|
4879
|
+
error,
|
|
4880
|
+
clearError: () => setError(null)
|
|
4881
|
+
};
|
|
4882
|
+
}
|
|
2504
4883
|
function toDecaneError(err) {
|
|
2505
4884
|
if (err instanceof DecaneError) return err;
|
|
2506
4885
|
const msg = err instanceof Error ? err.message : err?.message != null ? String(err.message) : String(err);
|
|
@@ -2508,8 +4887,8 @@ function toDecaneError(err) {
|
|
|
2508
4887
|
}
|
|
2509
4888
|
function useDecane() {
|
|
2510
4889
|
const { provider, theme, setTheme } = useDecaneContext();
|
|
2511
|
-
const [, forceUpdate] = (0,
|
|
2512
|
-
(0,
|
|
4890
|
+
const [, forceUpdate] = (0, import_react5.useState)(0);
|
|
4891
|
+
(0, import_react5.useEffect)(() => {
|
|
2513
4892
|
const refresh = () => forceUpdate((n) => n + 1);
|
|
2514
4893
|
const events = [
|
|
2515
4894
|
"connect",
|
|
@@ -2548,10 +4927,10 @@ function useWalletSelector() {
|
|
|
2548
4927
|
}
|
|
2549
4928
|
function useChainSwitch() {
|
|
2550
4929
|
const { provider } = useDecaneContext();
|
|
2551
|
-
const [, forceUpdate] = (0,
|
|
2552
|
-
const [loading, setLoading] = (0,
|
|
2553
|
-
const [error, setError] = (0,
|
|
2554
|
-
(0,
|
|
4930
|
+
const [, forceUpdate] = (0, import_react5.useState)(0);
|
|
4931
|
+
const [loading, setLoading] = (0, import_react5.useState)(false);
|
|
4932
|
+
const [error, setError] = (0, import_react5.useState)(null);
|
|
4933
|
+
(0, import_react5.useEffect)(() => {
|
|
2555
4934
|
const refresh = () => forceUpdate((n) => n + 1);
|
|
2556
4935
|
provider.on("connect", refresh);
|
|
2557
4936
|
provider.on("disconnect", refresh);
|
|
@@ -2560,7 +4939,7 @@ function useChainSwitch() {
|
|
|
2560
4939
|
provider.off("disconnect", refresh);
|
|
2561
4940
|
};
|
|
2562
4941
|
}, [provider]);
|
|
2563
|
-
const switchChain = (0,
|
|
4942
|
+
const switchChain = (0, import_react5.useCallback)(
|
|
2564
4943
|
async (chainId) => {
|
|
2565
4944
|
setLoading(true);
|
|
2566
4945
|
setError(null);
|
|
@@ -2614,10 +4993,10 @@ function resolveScope(provider, chainId, account) {
|
|
|
2614
4993
|
}
|
|
2615
4994
|
function useSignMessage() {
|
|
2616
4995
|
const { provider } = useDecaneContext();
|
|
2617
|
-
const [signature, setSignature] = (0,
|
|
2618
|
-
const [loading, setLoading] = (0,
|
|
2619
|
-
const [error, setError] = (0,
|
|
2620
|
-
const signMessage = (0,
|
|
4996
|
+
const [signature, setSignature] = (0, import_react5.useState)(null);
|
|
4997
|
+
const [loading, setLoading] = (0, import_react5.useState)(false);
|
|
4998
|
+
const [error, setError] = (0, import_react5.useState)(null);
|
|
4999
|
+
const signMessage = (0, import_react5.useCallback)(
|
|
2621
5000
|
async (message, options) => {
|
|
2622
5001
|
const { chain, account } = resolveScope(
|
|
2623
5002
|
provider,
|
|
@@ -2643,7 +5022,7 @@ function useSignMessage() {
|
|
|
2643
5022
|
},
|
|
2644
5023
|
[provider]
|
|
2645
5024
|
);
|
|
2646
|
-
const reset = (0,
|
|
5025
|
+
const reset = (0, import_react5.useCallback)(() => {
|
|
2647
5026
|
setSignature(null);
|
|
2648
5027
|
setError(null);
|
|
2649
5028
|
}, []);
|
|
@@ -2651,10 +5030,10 @@ function useSignMessage() {
|
|
|
2651
5030
|
}
|
|
2652
5031
|
function useSendTransaction() {
|
|
2653
5032
|
const { provider } = useDecaneContext();
|
|
2654
|
-
const [txHash, setTxHash] = (0,
|
|
2655
|
-
const [loading, setLoading] = (0,
|
|
2656
|
-
const [error, setError] = (0,
|
|
2657
|
-
const sendTransaction = (0,
|
|
5033
|
+
const [txHash, setTxHash] = (0, import_react5.useState)(null);
|
|
5034
|
+
const [loading, setLoading] = (0, import_react5.useState)(false);
|
|
5035
|
+
const [error, setError] = (0, import_react5.useState)(null);
|
|
5036
|
+
const sendTransaction = (0, import_react5.useCallback)(
|
|
2658
5037
|
async (tx, options) => {
|
|
2659
5038
|
const { chain, account } = resolveScope(
|
|
2660
5039
|
provider,
|
|
@@ -2681,7 +5060,7 @@ function useSendTransaction() {
|
|
|
2681
5060
|
},
|
|
2682
5061
|
[provider]
|
|
2683
5062
|
);
|
|
2684
|
-
const reset = (0,
|
|
5063
|
+
const reset = (0, import_react5.useCallback)(() => {
|
|
2685
5064
|
setTxHash(null);
|
|
2686
5065
|
setError(null);
|
|
2687
5066
|
}, []);
|
|
@@ -2689,10 +5068,10 @@ function useSendTransaction() {
|
|
|
2689
5068
|
}
|
|
2690
5069
|
function useSignTypedData() {
|
|
2691
5070
|
const { provider } = useDecaneContext();
|
|
2692
|
-
const [signature, setSignature] = (0,
|
|
2693
|
-
const [loading, setLoading] = (0,
|
|
2694
|
-
const [error, setError] = (0,
|
|
2695
|
-
const signTypedData = (0,
|
|
5071
|
+
const [signature, setSignature] = (0, import_react5.useState)(null);
|
|
5072
|
+
const [loading, setLoading] = (0, import_react5.useState)(false);
|
|
5073
|
+
const [error, setError] = (0, import_react5.useState)(null);
|
|
5074
|
+
const signTypedData = (0, import_react5.useCallback)(
|
|
2696
5075
|
async (typedData, options) => {
|
|
2697
5076
|
const { chain, account } = resolveScope(
|
|
2698
5077
|
provider,
|
|
@@ -2718,7 +5097,7 @@ function useSignTypedData() {
|
|
|
2718
5097
|
},
|
|
2719
5098
|
[provider]
|
|
2720
5099
|
);
|
|
2721
|
-
const reset = (0,
|
|
5100
|
+
const reset = (0, import_react5.useCallback)(() => {
|
|
2722
5101
|
setSignature(null);
|
|
2723
5102
|
setError(null);
|
|
2724
5103
|
}, []);
|
|
@@ -2726,18 +5105,43 @@ function useSignTypedData() {
|
|
|
2726
5105
|
}
|
|
2727
5106
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2728
5107
|
0 && (module.exports = {
|
|
5108
|
+
AttestationError,
|
|
2729
5109
|
BUILT_IN_CHAINS,
|
|
2730
5110
|
CHAINS,
|
|
5111
|
+
CallSequenceError,
|
|
5112
|
+
DecaneConnectError,
|
|
2731
5113
|
DecaneError,
|
|
2732
5114
|
DecaneErrorCode,
|
|
2733
5115
|
DecaneKit,
|
|
5116
|
+
ExportNotAuthorizedError,
|
|
5117
|
+
InvalidApiKeyError,
|
|
5118
|
+
InvalidPinError,
|
|
5119
|
+
InvalidRecoveryFileError,
|
|
5120
|
+
NetworkError,
|
|
5121
|
+
NewDeviceError,
|
|
5122
|
+
OriginNotAllowedError,
|
|
5123
|
+
PromptPinNotConfiguredError,
|
|
5124
|
+
RecoveryFailedError,
|
|
5125
|
+
RecoveryWalletMismatchError,
|
|
5126
|
+
SessionExpiredError,
|
|
5127
|
+
ShareCorruptedError,
|
|
5128
|
+
SocialWalletModal,
|
|
5129
|
+
UnlockRequiredError,
|
|
5130
|
+
UserCancelledError,
|
|
2734
5131
|
WalletSelector,
|
|
5132
|
+
WebAuthnNotSupportedError,
|
|
5133
|
+
WrongPasswordError,
|
|
2735
5134
|
createDecane,
|
|
5135
|
+
createDecaneConnect,
|
|
5136
|
+
startEmailAuth,
|
|
2736
5137
|
useChainSwitch,
|
|
2737
5138
|
useDecane,
|
|
2738
5139
|
useSendTransaction,
|
|
2739
5140
|
useSignMessage,
|
|
2740
5141
|
useSignTypedData,
|
|
2741
|
-
|
|
5142
|
+
useSocialAuth,
|
|
5143
|
+
useSocialWallet,
|
|
5144
|
+
useWalletSelector,
|
|
5145
|
+
verifyEmailCode
|
|
2742
5146
|
});
|
|
2743
5147
|
//# sourceMappingURL=index.cjs.map
|