@horus-wallet/sdk-react 0.1.0-beta.2 → 0.3.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +133 -174
- package/dist/connect.cjs +218 -0
- package/dist/connect.d.cts +155 -0
- package/dist/connect.d.ts +155 -0
- package/dist/connect.js +188 -0
- package/dist/index.cjs +608 -55
- package/dist/index.d.cts +449 -31
- package/dist/index.d.ts +449 -31
- package/dist/index.js +599 -55
- package/package.json +7 -2
package/dist/index.js
CHANGED
|
@@ -89,19 +89,21 @@ function makeHttp(deps) {
|
|
|
89
89
|
return path.startsWith("/") ? `${base}${path}` : `${base}/${path}`;
|
|
90
90
|
};
|
|
91
91
|
async function rawRequest(method, path, body, options = {}) {
|
|
92
|
-
const headers = {
|
|
92
|
+
const headers = {
|
|
93
|
+
"Content-Type": "application/json",
|
|
94
|
+
"x-horus-key": deps.appId
|
|
95
|
+
};
|
|
93
96
|
if (options.auth !== false) {
|
|
94
97
|
const tokens = deps.getTokens();
|
|
95
98
|
if (tokens?.idToken) {
|
|
96
|
-
headers["
|
|
99
|
+
headers["Authorization"] = `Bearer ${tokens.idToken}`;
|
|
97
100
|
}
|
|
98
101
|
}
|
|
99
102
|
const res = await fetch(url(path), {
|
|
100
103
|
method,
|
|
101
104
|
headers,
|
|
102
105
|
body: body !== void 0 ? JSON.stringify(body) : void 0,
|
|
103
|
-
signal: options.signal
|
|
104
|
-
credentials: "same-origin"
|
|
106
|
+
signal: options.signal
|
|
105
107
|
});
|
|
106
108
|
const text = await res.text();
|
|
107
109
|
let parsed = void 0;
|
|
@@ -142,9 +144,15 @@ function makeHttp(deps) {
|
|
|
142
144
|
|
|
143
145
|
// src/HorusProvider.tsx
|
|
144
146
|
import { jsx } from "react/jsx-runtime";
|
|
145
|
-
var DEFAULT_API_BASE = "
|
|
147
|
+
var DEFAULT_API_BASE = "https://api.horuswallet.com";
|
|
146
148
|
var DEFAULT_AUTH_PAGE = "https://auth.horuswallet.com";
|
|
147
149
|
var REFRESH_LEAD_SECONDS = 60;
|
|
150
|
+
var DEFAULT_AUTO_PROVISION = {
|
|
151
|
+
network: "EVM",
|
|
152
|
+
networkType: "MAINNET"
|
|
153
|
+
};
|
|
154
|
+
var DEFAULT_CHAIN = { network: "EVM", networkType: "MAINNET" };
|
|
155
|
+
var AUTO_PROVISION_STORAGE_KEY = "horus.autoProvisioned.localIds";
|
|
148
156
|
function HorusProvider(props) {
|
|
149
157
|
const {
|
|
150
158
|
appId,
|
|
@@ -153,11 +161,21 @@ function HorusProvider(props) {
|
|
|
153
161
|
branding,
|
|
154
162
|
tokenStorage = "localStorage",
|
|
155
163
|
autoRefresh = true,
|
|
164
|
+
autoProvisionWallet = DEFAULT_AUTO_PROVISION,
|
|
165
|
+
defaultChain = DEFAULT_CHAIN,
|
|
156
166
|
children
|
|
157
167
|
} = props;
|
|
158
168
|
const tokenStoreRef = useRef(createTokenStore(tokenStorage));
|
|
159
169
|
const tokensRef = useRef(null);
|
|
160
170
|
const [state, setState] = useState({ status: "loading" });
|
|
171
|
+
const [walletsVersion, setWalletsVersion] = useState(0);
|
|
172
|
+
const revalidateWallets = useCallback(() => {
|
|
173
|
+
setWalletsVersion((v) => v + 1);
|
|
174
|
+
}, []);
|
|
175
|
+
const [currentChain, setCurrentChain] = useState(defaultChain);
|
|
176
|
+
const setChain = useCallback((chain) => {
|
|
177
|
+
setCurrentChain(chain);
|
|
178
|
+
}, []);
|
|
161
179
|
const setTokens = useCallback((tokens) => {
|
|
162
180
|
tokensRef.current = tokens;
|
|
163
181
|
if (tokens) {
|
|
@@ -178,9 +196,11 @@ function HorusProvider(props) {
|
|
|
178
196
|
if (!cur?.refreshToken) throw new Error("no refresh token cached");
|
|
179
197
|
const fresh = await fetch(joinUrl(apiBase, "/auth/refresh"), {
|
|
180
198
|
method: "POST",
|
|
181
|
-
headers: {
|
|
182
|
-
|
|
183
|
-
|
|
199
|
+
headers: {
|
|
200
|
+
"Content-Type": "application/json",
|
|
201
|
+
"x-horus-key": appId
|
|
202
|
+
},
|
|
203
|
+
body: JSON.stringify({ refreshToken: cur.refreshToken })
|
|
184
204
|
});
|
|
185
205
|
if (!fresh.ok) throw new Error(`refresh failed: ${fresh.status}`);
|
|
186
206
|
const raw = await fresh.json();
|
|
@@ -190,11 +210,12 @@ function HorusProvider(props) {
|
|
|
190
210
|
};
|
|
191
211
|
return makeHttp({
|
|
192
212
|
apiBase,
|
|
213
|
+
appId,
|
|
193
214
|
getTokens: () => tokensRef.current,
|
|
194
215
|
refreshTokens: refreshOnce,
|
|
195
216
|
autoRefresh
|
|
196
217
|
});
|
|
197
|
-
}, [apiBase, autoRefresh, setTokens]);
|
|
218
|
+
}, [apiBase, appId, autoRefresh, setTokens]);
|
|
198
219
|
useEffect(() => {
|
|
199
220
|
const stored = tokenStoreRef.current.read();
|
|
200
221
|
if (stored) {
|
|
@@ -233,6 +254,33 @@ function HorusProvider(props) {
|
|
|
233
254
|
}, refreshIn);
|
|
234
255
|
return () => clearTimeout(handle);
|
|
235
256
|
}, [state, autoRefresh, http, setTokens]);
|
|
257
|
+
useEffect(() => {
|
|
258
|
+
if (state.status !== "authenticated") return;
|
|
259
|
+
if (!autoProvisionWallet) return;
|
|
260
|
+
const localId = state.tokens.localId;
|
|
261
|
+
if (!localId) return;
|
|
262
|
+
if (alreadyAutoProvisioned(localId)) return;
|
|
263
|
+
let cancelled = false;
|
|
264
|
+
(async () => {
|
|
265
|
+
try {
|
|
266
|
+
const existing = await http.get("/getWallet");
|
|
267
|
+
if (cancelled) return;
|
|
268
|
+
const hasAny = Object.values(existing?.wallets ?? {}).some(
|
|
269
|
+
(group) => Array.isArray(group?.wallets) && group.wallets.length > 0
|
|
270
|
+
);
|
|
271
|
+
markAutoProvisioned(localId);
|
|
272
|
+
if (hasAny) return;
|
|
273
|
+
await http.post("/createWallet", autoProvisionWallet);
|
|
274
|
+
if (cancelled) return;
|
|
275
|
+
revalidateWallets();
|
|
276
|
+
} catch (err) {
|
|
277
|
+
console.warn("@horus-wallet/sdk-react: auto-provision wallet failed", err);
|
|
278
|
+
}
|
|
279
|
+
})();
|
|
280
|
+
return () => {
|
|
281
|
+
cancelled = true;
|
|
282
|
+
};
|
|
283
|
+
}, [state, autoProvisionWallet, http, revalidateWallets]);
|
|
236
284
|
const ctx = useMemo(
|
|
237
285
|
() => ({
|
|
238
286
|
state,
|
|
@@ -253,9 +301,24 @@ function HorusProvider(props) {
|
|
|
253
301
|
const stamped = stampExpiry(raw);
|
|
254
302
|
setTokens(stamped);
|
|
255
303
|
return stamped;
|
|
256
|
-
}
|
|
304
|
+
},
|
|
305
|
+
walletsVersion,
|
|
306
|
+
revalidateWallets,
|
|
307
|
+
currentChain,
|
|
308
|
+
setChain
|
|
257
309
|
}),
|
|
258
|
-
[
|
|
310
|
+
[
|
|
311
|
+
state,
|
|
312
|
+
http,
|
|
313
|
+
authPageUrl,
|
|
314
|
+
appId,
|
|
315
|
+
branding,
|
|
316
|
+
setTokens,
|
|
317
|
+
walletsVersion,
|
|
318
|
+
revalidateWallets,
|
|
319
|
+
currentChain,
|
|
320
|
+
setChain
|
|
321
|
+
]
|
|
259
322
|
);
|
|
260
323
|
return /* @__PURE__ */ jsx(HorusContext.Provider, { value: ctx, children });
|
|
261
324
|
}
|
|
@@ -269,6 +332,36 @@ function userFromTokens(t) {
|
|
|
269
332
|
providerId: t.providerId
|
|
270
333
|
};
|
|
271
334
|
}
|
|
335
|
+
var memoryAutoProvisioned = /* @__PURE__ */ new Set();
|
|
336
|
+
function readAutoProvisionedSet() {
|
|
337
|
+
if (typeof window === "undefined" || !window.localStorage) {
|
|
338
|
+
return memoryAutoProvisioned;
|
|
339
|
+
}
|
|
340
|
+
try {
|
|
341
|
+
const raw = window.localStorage.getItem(AUTO_PROVISION_STORAGE_KEY);
|
|
342
|
+
if (!raw) return /* @__PURE__ */ new Set();
|
|
343
|
+
const parsed = JSON.parse(raw);
|
|
344
|
+
return new Set(Array.isArray(parsed) ? parsed : []);
|
|
345
|
+
} catch {
|
|
346
|
+
return /* @__PURE__ */ new Set();
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
function alreadyAutoProvisioned(localId) {
|
|
350
|
+
return readAutoProvisionedSet().has(localId);
|
|
351
|
+
}
|
|
352
|
+
function markAutoProvisioned(localId) {
|
|
353
|
+
if (typeof window === "undefined" || !window.localStorage) {
|
|
354
|
+
memoryAutoProvisioned.add(localId);
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
try {
|
|
358
|
+
const set = readAutoProvisionedSet();
|
|
359
|
+
set.add(localId);
|
|
360
|
+
window.localStorage.setItem(AUTO_PROVISION_STORAGE_KEY, JSON.stringify([...set]));
|
|
361
|
+
} catch {
|
|
362
|
+
memoryAutoProvisioned.add(localId);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
272
365
|
function joinUrl(base, path) {
|
|
273
366
|
const b = base.endsWith("/") ? base.slice(0, -1) : base;
|
|
274
367
|
const p = path.startsWith("/") ? path : `/${path}`;
|
|
@@ -277,6 +370,36 @@ function joinUrl(base, path) {
|
|
|
277
370
|
|
|
278
371
|
// src/hooks/useHorusAuth.ts
|
|
279
372
|
import { useCallback as useCallback2 } from "react";
|
|
373
|
+
|
|
374
|
+
// src/internal/authUrl.ts
|
|
375
|
+
function buildAuthUrl(p, state) {
|
|
376
|
+
const url = new URL(p.baseUrl);
|
|
377
|
+
url.searchParams.set("flow", p.flow);
|
|
378
|
+
url.searchParams.set("origin", window.location.origin);
|
|
379
|
+
url.searchParams.set("state", state);
|
|
380
|
+
url.searchParams.set("appKey", p.appId);
|
|
381
|
+
url.searchParams.set("mode", p.mode);
|
|
382
|
+
if (p.phone) url.searchParams.set("phone", p.phone);
|
|
383
|
+
const b = p.branding;
|
|
384
|
+
if (b) {
|
|
385
|
+
if (b.logoUrl) url.searchParams.set("b_logo", b.logoUrl);
|
|
386
|
+
if (b.brandName) url.searchParams.set("b_name", b.brandName);
|
|
387
|
+
if (b.primaryColor) url.searchParams.set("b_color", b.primaryColor);
|
|
388
|
+
if (b.backgroundColor) url.searchParams.set("b_bg", b.backgroundColor);
|
|
389
|
+
if (b.fontFamily) url.searchParams.set("b_font", b.fontFamily);
|
|
390
|
+
if (b.termsUrl) url.searchParams.set("b_terms", b.termsUrl);
|
|
391
|
+
if (b.privacyUrl) url.searchParams.set("b_privacy", b.privacyUrl);
|
|
392
|
+
if (b.showPoweredByHorus === false) url.searchParams.set("b_poweredby", "0");
|
|
393
|
+
}
|
|
394
|
+
return url.toString();
|
|
395
|
+
}
|
|
396
|
+
function randomState() {
|
|
397
|
+
const bytes = new Uint8Array(16);
|
|
398
|
+
crypto.getRandomValues(bytes);
|
|
399
|
+
return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// src/hooks/useHorusAuth.ts
|
|
280
403
|
function useHorusAuth() {
|
|
281
404
|
const ctx = useHorusContext();
|
|
282
405
|
const { state, http, signIn, signOut, refreshTokens, authPageUrl, appId, branding } = ctx;
|
|
@@ -376,7 +499,7 @@ function openPopupFlow(params) {
|
|
|
376
499
|
return;
|
|
377
500
|
}
|
|
378
501
|
const state = randomState();
|
|
379
|
-
const url = buildAuthUrl(params, state);
|
|
502
|
+
const url = buildAuthUrl({ ...params, mode: "popup" }, state);
|
|
380
503
|
const popup = window.open(url, "horus-auth", defaultPopupFeatures());
|
|
381
504
|
if (!popup) {
|
|
382
505
|
reject(new Error("Popup blocked \u2014 call from a click handler so the browser allows it."));
|
|
@@ -438,26 +561,6 @@ function openPopupFlow(params) {
|
|
|
438
561
|
}, 400);
|
|
439
562
|
});
|
|
440
563
|
}
|
|
441
|
-
function buildAuthUrl(p, state) {
|
|
442
|
-
const url = new URL(p.baseUrl);
|
|
443
|
-
url.searchParams.set("flow", p.flow);
|
|
444
|
-
url.searchParams.set("origin", window.location.origin);
|
|
445
|
-
url.searchParams.set("state", state);
|
|
446
|
-
url.searchParams.set("appKey", p.appId);
|
|
447
|
-
if (p.phone) url.searchParams.set("phone", p.phone);
|
|
448
|
-
const b = p.branding;
|
|
449
|
-
if (b) {
|
|
450
|
-
if (b.logoUrl) url.searchParams.set("b_logo", b.logoUrl);
|
|
451
|
-
if (b.brandName) url.searchParams.set("b_name", b.brandName);
|
|
452
|
-
if (b.primaryColor) url.searchParams.set("b_color", b.primaryColor);
|
|
453
|
-
if (b.backgroundColor) url.searchParams.set("b_bg", b.backgroundColor);
|
|
454
|
-
if (b.fontFamily) url.searchParams.set("b_font", b.fontFamily);
|
|
455
|
-
if (b.termsUrl) url.searchParams.set("b_terms", b.termsUrl);
|
|
456
|
-
if (b.privacyUrl) url.searchParams.set("b_privacy", b.privacyUrl);
|
|
457
|
-
if (b.showPoweredByHorus === false) url.searchParams.set("b_poweredby", "0");
|
|
458
|
-
}
|
|
459
|
-
return url.toString();
|
|
460
|
-
}
|
|
461
564
|
function defaultPopupFeatures() {
|
|
462
565
|
const w = 480, h = 640;
|
|
463
566
|
if (typeof window === "undefined") return `width=${w},height=${h}`;
|
|
@@ -465,11 +568,6 @@ function defaultPopupFeatures() {
|
|
|
465
568
|
const top = Math.max(0, (window.innerHeight - h) / 2 + (window.screenY ?? 0));
|
|
466
569
|
return `width=${w},height=${h},left=${left},top=${top},popup=1`;
|
|
467
570
|
}
|
|
468
|
-
function randomState() {
|
|
469
|
-
const bytes = new Uint8Array(16);
|
|
470
|
-
crypto.getRandomValues(bytes);
|
|
471
|
-
return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
472
|
-
}
|
|
473
571
|
|
|
474
572
|
// src/hooks/useUser.ts
|
|
475
573
|
function useUser() {
|
|
@@ -480,7 +578,7 @@ function useUser() {
|
|
|
480
578
|
// src/hooks/useWallets.ts
|
|
481
579
|
import { useCallback as useCallback3, useEffect as useEffect2, useState as useState2 } from "react";
|
|
482
580
|
function useWallets() {
|
|
483
|
-
const { http, state } = useHorusContext();
|
|
581
|
+
const { http, state, walletsVersion } = useHorusContext();
|
|
484
582
|
const [wallets, setWallets] = useState2([]);
|
|
485
583
|
const [ready, setReady] = useState2(false);
|
|
486
584
|
const [error, setError] = useState2(void 0);
|
|
@@ -494,7 +592,7 @@ function useWallets() {
|
|
|
494
592
|
setError(void 0);
|
|
495
593
|
try {
|
|
496
594
|
const response = await http.get(
|
|
497
|
-
"/
|
|
595
|
+
"/getWallet"
|
|
498
596
|
);
|
|
499
597
|
const flat = [];
|
|
500
598
|
for (const [network, group] of Object.entries(response.wallets ?? {})) {
|
|
@@ -511,22 +609,87 @@ function useWallets() {
|
|
|
511
609
|
}, [http, state.status]);
|
|
512
610
|
useEffect2(() => {
|
|
513
611
|
void load();
|
|
514
|
-
}, [load]);
|
|
612
|
+
}, [load, walletsVersion]);
|
|
515
613
|
return { ready, wallets, refresh: load, error };
|
|
516
614
|
}
|
|
517
615
|
|
|
518
|
-
// src/hooks/
|
|
616
|
+
// src/hooks/useCreateWallet.ts
|
|
519
617
|
import { useCallback as useCallback4, useState as useState3 } from "react";
|
|
520
|
-
function
|
|
521
|
-
const { http } = useHorusContext();
|
|
618
|
+
function useCreateWallet() {
|
|
619
|
+
const { http, revalidateWallets } = useHorusContext();
|
|
522
620
|
const [pending, setPending] = useState3(false);
|
|
523
621
|
const [error, setError] = useState3(void 0);
|
|
524
|
-
const
|
|
622
|
+
const create = useCallback4(
|
|
525
623
|
async (input) => {
|
|
624
|
+
if (pending) {
|
|
625
|
+
const err = new Error(
|
|
626
|
+
"useCreateWallet: a previous create() call is still in flight"
|
|
627
|
+
);
|
|
628
|
+
setError(err);
|
|
629
|
+
throw err;
|
|
630
|
+
}
|
|
526
631
|
setPending(true);
|
|
527
632
|
setError(void 0);
|
|
528
633
|
try {
|
|
529
|
-
const response = await http.post("/
|
|
634
|
+
const response = await http.post("/createWallet", {
|
|
635
|
+
network: input.network,
|
|
636
|
+
networkType: input.networkType,
|
|
637
|
+
...input.password ? { password: input.password } : {}
|
|
638
|
+
});
|
|
639
|
+
revalidateWallets();
|
|
640
|
+
return response;
|
|
641
|
+
} catch (err) {
|
|
642
|
+
const e = err instanceof Error ? err : new Error(String(err));
|
|
643
|
+
setError(e);
|
|
644
|
+
throw e;
|
|
645
|
+
} finally {
|
|
646
|
+
setPending(false);
|
|
647
|
+
}
|
|
648
|
+
},
|
|
649
|
+
[http, revalidateWallets, pending]
|
|
650
|
+
);
|
|
651
|
+
return { create, pending, error };
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
// src/hooks/useSwitchChain.ts
|
|
655
|
+
import { useCallback as useCallback5 } from "react";
|
|
656
|
+
var SUPPORTED_CHAINS = [
|
|
657
|
+
"EVM",
|
|
658
|
+
"ETHEREUM",
|
|
659
|
+
"BASE",
|
|
660
|
+
"POLYGON",
|
|
661
|
+
"BSC",
|
|
662
|
+
"ARBITRUM",
|
|
663
|
+
"OPTIMISM",
|
|
664
|
+
"BITCOIN",
|
|
665
|
+
"ICP",
|
|
666
|
+
"CASPER",
|
|
667
|
+
"AETERNITY"
|
|
668
|
+
];
|
|
669
|
+
function useSwitchChain() {
|
|
670
|
+
const { currentChain, setChain } = useHorusContext();
|
|
671
|
+
const switchChain = useCallback5(
|
|
672
|
+
(chain) => {
|
|
673
|
+
setChain(chain);
|
|
674
|
+
},
|
|
675
|
+
[setChain]
|
|
676
|
+
);
|
|
677
|
+
return { chain: currentChain, switchChain, supportedChains: SUPPORTED_CHAINS };
|
|
678
|
+
}
|
|
679
|
+
var useChain = useSwitchChain;
|
|
680
|
+
|
|
681
|
+
// src/hooks/useSignMessage.ts
|
|
682
|
+
import { useCallback as useCallback6, useState as useState4 } from "react";
|
|
683
|
+
function useSignMessage() {
|
|
684
|
+
const { http } = useHorusContext();
|
|
685
|
+
const [pending, setPending] = useState4(false);
|
|
686
|
+
const [error, setError] = useState4(void 0);
|
|
687
|
+
const signMessage = useCallback6(
|
|
688
|
+
async (input) => {
|
|
689
|
+
setPending(true);
|
|
690
|
+
setError(void 0);
|
|
691
|
+
try {
|
|
692
|
+
const response = await http.post("/signMessage", input);
|
|
530
693
|
return response.signature;
|
|
531
694
|
} catch (err) {
|
|
532
695
|
const e = err instanceof Error ? err : new Error(String(err));
|
|
@@ -541,13 +704,79 @@ function useSignMessage() {
|
|
|
541
704
|
return { signMessage, pending, error };
|
|
542
705
|
}
|
|
543
706
|
|
|
707
|
+
// src/hooks/useSignTypedData.ts
|
|
708
|
+
import { useCallback as useCallback7, useState as useState5 } from "react";
|
|
709
|
+
function useSignTypedData() {
|
|
710
|
+
const { http } = useHorusContext();
|
|
711
|
+
const [pending, setPending] = useState5(false);
|
|
712
|
+
const [error, setError] = useState5(void 0);
|
|
713
|
+
const signTypedData = useCallback7(
|
|
714
|
+
async (input) => {
|
|
715
|
+
setPending(true);
|
|
716
|
+
setError(void 0);
|
|
717
|
+
try {
|
|
718
|
+
const response = await http.post("/signTypedData", {
|
|
719
|
+
network: input.network,
|
|
720
|
+
networkType: input.networkType,
|
|
721
|
+
typedData: input.typedData,
|
|
722
|
+
...typeof input.walletIndex === "number" ? { walletIndex: input.walletIndex } : {},
|
|
723
|
+
...input.password ? { password: input.password } : {}
|
|
724
|
+
});
|
|
725
|
+
return response.signature;
|
|
726
|
+
} catch (err) {
|
|
727
|
+
const e = err instanceof Error ? err : new Error(String(err));
|
|
728
|
+
setError(e);
|
|
729
|
+
throw e;
|
|
730
|
+
} finally {
|
|
731
|
+
setPending(false);
|
|
732
|
+
}
|
|
733
|
+
},
|
|
734
|
+
[http]
|
|
735
|
+
);
|
|
736
|
+
return { signTypedData, pending, error };
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
// src/hooks/useSendTransaction.ts
|
|
740
|
+
import { useCallback as useCallback8, useState as useState6 } from "react";
|
|
741
|
+
function useSendTransaction() {
|
|
742
|
+
const { http } = useHorusContext();
|
|
743
|
+
const [pending, setPending] = useState6(false);
|
|
744
|
+
const [error, setError] = useState6(void 0);
|
|
745
|
+
const sendTransaction = useCallback8(
|
|
746
|
+
async (input) => {
|
|
747
|
+
setPending(true);
|
|
748
|
+
setError(void 0);
|
|
749
|
+
try {
|
|
750
|
+
return await http.post("/sendTransaction", {
|
|
751
|
+
network: input.network,
|
|
752
|
+
networkType: input.networkType,
|
|
753
|
+
to: input.to,
|
|
754
|
+
...input.value !== void 0 ? { value: String(input.value) } : {},
|
|
755
|
+
...input.data !== void 0 ? { data: input.data } : {},
|
|
756
|
+
...input.gasLimit !== void 0 ? { gasLimit: String(input.gasLimit) } : {},
|
|
757
|
+
...typeof input.walletIndex === "number" ? { walletIndex: input.walletIndex } : {},
|
|
758
|
+
...input.password ? { password: input.password } : {}
|
|
759
|
+
});
|
|
760
|
+
} catch (err) {
|
|
761
|
+
const e = err instanceof Error ? err : new Error(String(err));
|
|
762
|
+
setError(e);
|
|
763
|
+
throw e;
|
|
764
|
+
} finally {
|
|
765
|
+
setPending(false);
|
|
766
|
+
}
|
|
767
|
+
},
|
|
768
|
+
[http]
|
|
769
|
+
);
|
|
770
|
+
return { sendTransaction, pending, error };
|
|
771
|
+
}
|
|
772
|
+
|
|
544
773
|
// src/hooks/useTransfer.ts
|
|
545
|
-
import { useCallback as
|
|
774
|
+
import { useCallback as useCallback9, useState as useState7 } from "react";
|
|
546
775
|
function useTransfer() {
|
|
547
776
|
const { http } = useHorusContext();
|
|
548
|
-
const [pending, setPending] =
|
|
549
|
-
const [error, setError] =
|
|
550
|
-
const wrap =
|
|
777
|
+
const [pending, setPending] = useState7(false);
|
|
778
|
+
const [error, setError] = useState7(void 0);
|
|
779
|
+
const wrap = useCallback9(
|
|
551
780
|
async (fn) => {
|
|
552
781
|
setPending(true);
|
|
553
782
|
setError(void 0);
|
|
@@ -563,18 +792,18 @@ function useTransfer() {
|
|
|
563
792
|
},
|
|
564
793
|
[]
|
|
565
794
|
);
|
|
566
|
-
const native =
|
|
795
|
+
const native = useCallback9(
|
|
567
796
|
(input) => wrap(
|
|
568
|
-
() => http.post("/
|
|
797
|
+
() => http.post("/nativeTransfer", {
|
|
569
798
|
...input,
|
|
570
799
|
amount: typeof input.amount === "bigint" ? input.amount.toString() : String(input.amount)
|
|
571
800
|
})
|
|
572
801
|
),
|
|
573
802
|
[http, wrap]
|
|
574
803
|
);
|
|
575
|
-
const token =
|
|
804
|
+
const token = useCallback9(
|
|
576
805
|
(input) => wrap(
|
|
577
|
-
() => http.post("/
|
|
806
|
+
() => http.post("/tokenTransfer", {
|
|
578
807
|
...input,
|
|
579
808
|
amount: typeof input.amount === "bigint" ? input.amount.toString() : String(input.amount)
|
|
580
809
|
})
|
|
@@ -585,7 +814,7 @@ function useTransfer() {
|
|
|
585
814
|
}
|
|
586
815
|
|
|
587
816
|
// src/components/HorusLoginButton.tsx
|
|
588
|
-
import { useState as
|
|
817
|
+
import { useState as useState8 } from "react";
|
|
589
818
|
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
590
819
|
function HorusLoginButton({
|
|
591
820
|
flow = "google",
|
|
@@ -598,7 +827,7 @@ function HorusLoginButton({
|
|
|
598
827
|
...rest
|
|
599
828
|
}) {
|
|
600
829
|
const auth = useHorusAuth();
|
|
601
|
-
const [busy, setBusy] =
|
|
830
|
+
const [busy, setBusy] = useState8(false);
|
|
602
831
|
const onClick = async () => {
|
|
603
832
|
if (busy) return;
|
|
604
833
|
setBusy(true);
|
|
@@ -636,12 +865,327 @@ function HorusLoginButton({
|
|
|
636
865
|
}
|
|
637
866
|
);
|
|
638
867
|
}
|
|
868
|
+
|
|
869
|
+
// src/components/HorusAuthModal.tsx
|
|
870
|
+
import { useCallback as useCallback10, useEffect as useEffect3, useMemo as useMemo2, useRef as useRef2 } from "react";
|
|
871
|
+
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
872
|
+
var defaultBackdropStyle = {
|
|
873
|
+
position: "fixed",
|
|
874
|
+
inset: 0,
|
|
875
|
+
background: "rgba(0, 0, 0, 0.5)",
|
|
876
|
+
display: "flex",
|
|
877
|
+
alignItems: "center",
|
|
878
|
+
justifyContent: "center",
|
|
879
|
+
zIndex: 2147483600
|
|
880
|
+
};
|
|
881
|
+
var defaultDialogStyle = {
|
|
882
|
+
width: "480px",
|
|
883
|
+
maxWidth: "95vw",
|
|
884
|
+
height: "640px",
|
|
885
|
+
maxHeight: "95vh",
|
|
886
|
+
background: "#fff",
|
|
887
|
+
borderRadius: "16px",
|
|
888
|
+
boxShadow: "0 24px 56px rgba(0, 0, 0, 0.32)",
|
|
889
|
+
overflow: "hidden"
|
|
890
|
+
};
|
|
891
|
+
function HorusAuthModal(props) {
|
|
892
|
+
const {
|
|
893
|
+
flow,
|
|
894
|
+
phone,
|
|
895
|
+
open,
|
|
896
|
+
onClose,
|
|
897
|
+
onSuccess,
|
|
898
|
+
onError,
|
|
899
|
+
dialogStyle,
|
|
900
|
+
backdropStyle
|
|
901
|
+
} = props;
|
|
902
|
+
const { appId, authPageUrl, branding, signIn } = useHorusContext();
|
|
903
|
+
const stateRef = useRef2("");
|
|
904
|
+
if (stateRef.current === "" || !open) {
|
|
905
|
+
stateRef.current = randomState();
|
|
906
|
+
}
|
|
907
|
+
const iframeSrc = useMemo2(() => {
|
|
908
|
+
if (!open) return "";
|
|
909
|
+
return buildAuthUrl(
|
|
910
|
+
{
|
|
911
|
+
flow,
|
|
912
|
+
appId,
|
|
913
|
+
baseUrl: authPageUrl,
|
|
914
|
+
mode: "iframe",
|
|
915
|
+
branding,
|
|
916
|
+
phone
|
|
917
|
+
},
|
|
918
|
+
stateRef.current
|
|
919
|
+
);
|
|
920
|
+
}, [open, flow, appId, authPageUrl, branding, phone]);
|
|
921
|
+
const expectedOrigin = useMemo2(() => {
|
|
922
|
+
try {
|
|
923
|
+
return new URL(authPageUrl).origin;
|
|
924
|
+
} catch {
|
|
925
|
+
return "";
|
|
926
|
+
}
|
|
927
|
+
}, [authPageUrl]);
|
|
928
|
+
useEffect3(() => {
|
|
929
|
+
if (!open) return;
|
|
930
|
+
const onMessage = (ev) => {
|
|
931
|
+
if (ev.origin !== expectedOrigin) return;
|
|
932
|
+
const data = ev.data;
|
|
933
|
+
if (!data || data.channel !== "horus.auth" || data.version !== 1) return;
|
|
934
|
+
if (data.state !== void 0 && data.state !== stateRef.current) return;
|
|
935
|
+
const body = data.body;
|
|
936
|
+
if (!body) return;
|
|
937
|
+
switch (body.type) {
|
|
938
|
+
case "success": {
|
|
939
|
+
const stamped = stampExpiry({
|
|
940
|
+
idToken: body.idToken,
|
|
941
|
+
refreshToken: "",
|
|
942
|
+
expiresIn: body.expiresAt ? Math.max(60, body.expiresAt - Math.floor(Date.now() / 1e3)) : 3600,
|
|
943
|
+
localId: body.user?.uid ?? "",
|
|
944
|
+
email: body.user?.email,
|
|
945
|
+
displayName: body.user?.displayName,
|
|
946
|
+
photoUrl: body.user?.photoURL,
|
|
947
|
+
providerId: body.user?.providerId
|
|
948
|
+
});
|
|
949
|
+
signIn(stamped);
|
|
950
|
+
onSuccess?.({
|
|
951
|
+
uid: stamped.localId,
|
|
952
|
+
email: stamped.email,
|
|
953
|
+
emailVerified: stamped.emailVerified,
|
|
954
|
+
displayName: stamped.displayName,
|
|
955
|
+
photoUrl: stamped.photoUrl,
|
|
956
|
+
providerId: stamped.providerId
|
|
957
|
+
});
|
|
958
|
+
onClose();
|
|
959
|
+
return;
|
|
960
|
+
}
|
|
961
|
+
case "cancel":
|
|
962
|
+
onClose();
|
|
963
|
+
return;
|
|
964
|
+
case "error":
|
|
965
|
+
onError?.(new Error(body.message ?? "Sign-in failed."));
|
|
966
|
+
onClose();
|
|
967
|
+
return;
|
|
968
|
+
}
|
|
969
|
+
};
|
|
970
|
+
window.addEventListener("message", onMessage);
|
|
971
|
+
return () => window.removeEventListener("message", onMessage);
|
|
972
|
+
}, [open, expectedOrigin, signIn, onSuccess, onError, onClose]);
|
|
973
|
+
useEffect3(() => {
|
|
974
|
+
if (!open) return;
|
|
975
|
+
const onKey = (ev) => {
|
|
976
|
+
if (ev.key === "Escape") onClose();
|
|
977
|
+
};
|
|
978
|
+
window.addEventListener("keydown", onKey);
|
|
979
|
+
return () => window.removeEventListener("keydown", onKey);
|
|
980
|
+
}, [open, onClose]);
|
|
981
|
+
useEffect3(() => {
|
|
982
|
+
if (!open) return;
|
|
983
|
+
if (typeof document === "undefined") return;
|
|
984
|
+
const prev = document.body.style.overflow;
|
|
985
|
+
document.body.style.overflow = "hidden";
|
|
986
|
+
return () => {
|
|
987
|
+
document.body.style.overflow = prev;
|
|
988
|
+
};
|
|
989
|
+
}, [open]);
|
|
990
|
+
const backdropClick = useCallback10(
|
|
991
|
+
(ev) => {
|
|
992
|
+
if (ev.target === ev.currentTarget) onClose();
|
|
993
|
+
},
|
|
994
|
+
[onClose]
|
|
995
|
+
);
|
|
996
|
+
if (!open || typeof window === "undefined") return null;
|
|
997
|
+
return /* @__PURE__ */ jsx3(
|
|
998
|
+
"div",
|
|
999
|
+
{
|
|
1000
|
+
role: "dialog",
|
|
1001
|
+
"aria-modal": "true",
|
|
1002
|
+
"aria-label": "Sign in with Horus",
|
|
1003
|
+
style: { ...defaultBackdropStyle, ...backdropStyle },
|
|
1004
|
+
onClick: backdropClick,
|
|
1005
|
+
children: /* @__PURE__ */ jsx3("div", { style: { ...defaultDialogStyle, ...dialogStyle }, children: /* @__PURE__ */ jsx3(
|
|
1006
|
+
"iframe",
|
|
1007
|
+
{
|
|
1008
|
+
src: iframeSrc,
|
|
1009
|
+
title: "Horus authentication",
|
|
1010
|
+
allow: "publickey-credentials-get; publickey-credentials-create; clipboard-write",
|
|
1011
|
+
style: { width: "100%", height: "100%", border: "none" }
|
|
1012
|
+
}
|
|
1013
|
+
) })
|
|
1014
|
+
}
|
|
1015
|
+
);
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
// src/components/HorusRevealModal.tsx
|
|
1019
|
+
import { useCallback as useCallback11, useEffect as useEffect4, useMemo as useMemo3, useRef as useRef3 } from "react";
|
|
1020
|
+
import { jsx as jsx4 } from "react/jsx-runtime";
|
|
1021
|
+
var defaultBackdropStyle2 = {
|
|
1022
|
+
position: "fixed",
|
|
1023
|
+
inset: 0,
|
|
1024
|
+
background: "rgba(0, 0, 0, 0.5)",
|
|
1025
|
+
display: "flex",
|
|
1026
|
+
alignItems: "center",
|
|
1027
|
+
justifyContent: "center",
|
|
1028
|
+
zIndex: 2147483600
|
|
1029
|
+
};
|
|
1030
|
+
var defaultDialogStyle2 = {
|
|
1031
|
+
width: "480px",
|
|
1032
|
+
maxWidth: "95vw",
|
|
1033
|
+
height: "640px",
|
|
1034
|
+
maxHeight: "95vh",
|
|
1035
|
+
background: "#fff",
|
|
1036
|
+
borderRadius: "16px",
|
|
1037
|
+
boxShadow: "0 24px 56px rgba(0, 0, 0, 0.32)",
|
|
1038
|
+
overflow: "hidden"
|
|
1039
|
+
};
|
|
1040
|
+
function HorusRevealModal(props) {
|
|
1041
|
+
const { revealToken, open, onClose, onComplete, onError, dialogStyle, backdropStyle } = props;
|
|
1042
|
+
const { appId, authPageUrl, branding } = useHorusContext();
|
|
1043
|
+
const stateRef = useRef3("");
|
|
1044
|
+
if (stateRef.current === "" || !open) {
|
|
1045
|
+
stateRef.current = randomState();
|
|
1046
|
+
}
|
|
1047
|
+
const iframeSrc = useMemo3(() => {
|
|
1048
|
+
if (!open || !revealToken) return "";
|
|
1049
|
+
const baseUrl = buildAuthUrl(
|
|
1050
|
+
{
|
|
1051
|
+
flow: "reveal",
|
|
1052
|
+
appId,
|
|
1053
|
+
baseUrl: authPageUrl,
|
|
1054
|
+
mode: "iframe",
|
|
1055
|
+
branding
|
|
1056
|
+
},
|
|
1057
|
+
stateRef.current
|
|
1058
|
+
);
|
|
1059
|
+
const u = new URL(baseUrl);
|
|
1060
|
+
u.searchParams.set("token", revealToken);
|
|
1061
|
+
return u.toString();
|
|
1062
|
+
}, [open, revealToken, appId, authPageUrl, branding]);
|
|
1063
|
+
const expectedOrigin = useMemo3(() => {
|
|
1064
|
+
try {
|
|
1065
|
+
return new URL(authPageUrl).origin;
|
|
1066
|
+
} catch {
|
|
1067
|
+
return "";
|
|
1068
|
+
}
|
|
1069
|
+
}, [authPageUrl]);
|
|
1070
|
+
useEffect4(() => {
|
|
1071
|
+
if (!open) return;
|
|
1072
|
+
const onMessage = (ev) => {
|
|
1073
|
+
if (ev.origin !== expectedOrigin) return;
|
|
1074
|
+
const data = ev.data;
|
|
1075
|
+
if (!data || data.channel !== "horus.auth" || data.version !== 1) return;
|
|
1076
|
+
if (data.state !== void 0 && data.state !== stateRef.current) return;
|
|
1077
|
+
const body = data.body;
|
|
1078
|
+
if (!body) return;
|
|
1079
|
+
switch (body.type) {
|
|
1080
|
+
case "reveal_complete":
|
|
1081
|
+
onComplete?.(Boolean(body.viewed));
|
|
1082
|
+
onClose();
|
|
1083
|
+
return;
|
|
1084
|
+
case "cancel":
|
|
1085
|
+
onClose();
|
|
1086
|
+
return;
|
|
1087
|
+
case "error":
|
|
1088
|
+
onError?.(new Error(body.message ?? "Reveal failed."));
|
|
1089
|
+
onClose();
|
|
1090
|
+
return;
|
|
1091
|
+
}
|
|
1092
|
+
};
|
|
1093
|
+
window.addEventListener("message", onMessage);
|
|
1094
|
+
return () => window.removeEventListener("message", onMessage);
|
|
1095
|
+
}, [open, expectedOrigin, onComplete, onClose, onError]);
|
|
1096
|
+
useEffect4(() => {
|
|
1097
|
+
if (!open) return;
|
|
1098
|
+
const onKey = (ev) => {
|
|
1099
|
+
if (ev.key === "Escape") onClose();
|
|
1100
|
+
};
|
|
1101
|
+
window.addEventListener("keydown", onKey);
|
|
1102
|
+
return () => window.removeEventListener("keydown", onKey);
|
|
1103
|
+
}, [open, onClose]);
|
|
1104
|
+
useEffect4(() => {
|
|
1105
|
+
if (!open) return;
|
|
1106
|
+
if (typeof document === "undefined") return;
|
|
1107
|
+
const prev = document.body.style.overflow;
|
|
1108
|
+
document.body.style.overflow = "hidden";
|
|
1109
|
+
return () => {
|
|
1110
|
+
document.body.style.overflow = prev;
|
|
1111
|
+
};
|
|
1112
|
+
}, [open]);
|
|
1113
|
+
const backdropClick = useCallback11(
|
|
1114
|
+
(ev) => {
|
|
1115
|
+
if (ev.target === ev.currentTarget) onClose();
|
|
1116
|
+
},
|
|
1117
|
+
[onClose]
|
|
1118
|
+
);
|
|
1119
|
+
if (!open || !revealToken || typeof window === "undefined") return null;
|
|
1120
|
+
return /* @__PURE__ */ jsx4(
|
|
1121
|
+
"div",
|
|
1122
|
+
{
|
|
1123
|
+
role: "dialog",
|
|
1124
|
+
"aria-modal": "true",
|
|
1125
|
+
"aria-label": "Reveal wallet private key",
|
|
1126
|
+
style: { ...defaultBackdropStyle2, ...backdropStyle },
|
|
1127
|
+
onClick: backdropClick,
|
|
1128
|
+
children: /* @__PURE__ */ jsx4("div", { style: { ...defaultDialogStyle2, ...dialogStyle }, children: /* @__PURE__ */ jsx4(
|
|
1129
|
+
"iframe",
|
|
1130
|
+
{
|
|
1131
|
+
src: iframeSrc,
|
|
1132
|
+
title: "Horus wallet export",
|
|
1133
|
+
allow: "clipboard-write",
|
|
1134
|
+
style: { width: "100%", height: "100%", border: "none" }
|
|
1135
|
+
}
|
|
1136
|
+
) })
|
|
1137
|
+
}
|
|
1138
|
+
);
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
// src/hooks/useExportWallet.ts
|
|
1142
|
+
import { useCallback as useCallback12, useState as useState9 } from "react";
|
|
1143
|
+
function useExportWallet() {
|
|
1144
|
+
const { http } = useHorusContext();
|
|
1145
|
+
const [pending, setPending] = useState9(false);
|
|
1146
|
+
const [error, setError] = useState9(void 0);
|
|
1147
|
+
const reveal = useCallback12(
|
|
1148
|
+
async (input) => {
|
|
1149
|
+
setPending(true);
|
|
1150
|
+
setError(void 0);
|
|
1151
|
+
try {
|
|
1152
|
+
const response = await http.post(
|
|
1153
|
+
"/exportWallet/grant",
|
|
1154
|
+
{
|
|
1155
|
+
network: input.network,
|
|
1156
|
+
networkType: input.networkType,
|
|
1157
|
+
password: input.password,
|
|
1158
|
+
...typeof input.walletIndex === "number" ? { walletIndex: input.walletIndex } : {}
|
|
1159
|
+
}
|
|
1160
|
+
);
|
|
1161
|
+
return response;
|
|
1162
|
+
} catch (err) {
|
|
1163
|
+
const e = err instanceof Error ? err : new Error(String(err));
|
|
1164
|
+
setError(e);
|
|
1165
|
+
throw e;
|
|
1166
|
+
} finally {
|
|
1167
|
+
setPending(false);
|
|
1168
|
+
}
|
|
1169
|
+
},
|
|
1170
|
+
[http]
|
|
1171
|
+
);
|
|
1172
|
+
return { reveal, pending, error };
|
|
1173
|
+
}
|
|
639
1174
|
export {
|
|
1175
|
+
HorusAuthModal,
|
|
640
1176
|
HorusHttpError,
|
|
641
1177
|
HorusLoginButton,
|
|
642
1178
|
HorusProvider,
|
|
1179
|
+
HorusRevealModal,
|
|
1180
|
+
SUPPORTED_CHAINS,
|
|
1181
|
+
useChain,
|
|
1182
|
+
useCreateWallet,
|
|
1183
|
+
useExportWallet,
|
|
643
1184
|
useHorusAuth,
|
|
1185
|
+
useSendTransaction,
|
|
644
1186
|
useSignMessage,
|
|
1187
|
+
useSignTypedData,
|
|
1188
|
+
useSwitchChain,
|
|
645
1189
|
useTransfer,
|
|
646
1190
|
useUser,
|
|
647
1191
|
useWallets
|