Compare commits
691
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
52f827e8f2 | ||
|
|
6063f40f87 | ||
|
|
6bd1b82a58 | ||
|
|
052892b396 | ||
|
|
3b12c5c926 | ||
|
|
02d2377ca5 | ||
|
|
9545005908 | ||
|
|
d93286bc1f | ||
|
|
50e7dbaa9d | ||
|
|
04961f0e9a | ||
|
|
37f8433c7c | ||
|
|
c81bb882c5 | ||
|
|
701cd880df | ||
|
|
3f0a3124f3 | ||
|
|
949032ee09 | ||
|
|
3c58d17aba | ||
|
|
07954bb20b | ||
|
|
275af27a81 | ||
|
|
c63d9ea7ac | ||
|
|
8518ea18a7 | ||
|
|
18aaf5a1b8 | ||
|
|
9543a81d76 | ||
|
|
13f00cf476 | ||
|
|
9e28754bb1 | ||
|
|
095d227a13 | ||
|
|
889a1a08f5 | ||
|
|
6d7eedc213 | ||
|
|
bfdfb2ca38 | ||
|
|
17c772e839 | ||
|
|
73b5161a67 | ||
|
|
95eb75ac77 | ||
|
+2 |
01fd5237f5 | ||
|
|
6fd7497c15 | ||
|
|
25616ca794 | ||
|
|
2a345172a3 | ||
|
|
8cb002259a | ||
|
|
436d9baba2 | ||
|
|
e417f6ac64 | ||
|
|
65ea87425c | ||
|
|
0aea03e418 | ||
|
|
d8b488bbc9 | ||
|
|
cb0ef6a6a2 | ||
|
|
c377290b27 | ||
|
|
35bddd69c5 | ||
|
|
34be385a5c | ||
|
|
79c9e130fd | ||
|
|
9c884e2f55 | ||
|
|
2e21b52b97 | ||
|
|
e3f44905ca | ||
|
|
c47927708a | ||
|
|
4c511f0310 | ||
|
|
b2728ec6f2 | ||
|
|
663759975d | ||
|
|
f1971ffe36 | ||
|
|
8b134bbb55 | ||
|
|
8a20aa5825 | ||
|
|
112863ecce | ||
|
|
863049dc0c | ||
|
|
802ba8c5a9 | ||
|
|
e36ebe2243 | ||
|
|
c52e3beac1 | ||
|
|
f0250a77cd | ||
|
|
d4be6479db | ||
|
|
c67b287764 | ||
|
|
7ac4e6ba4e | ||
|
|
c494db87cb | ||
|
|
1932d601c1 | ||
|
|
846a3503fe | ||
|
|
3e7f9cb946 | ||
|
|
83526c519a | ||
|
|
2e01627c99 | ||
|
|
7b52c6bba0 | ||
|
|
e024d78ed0 | ||
|
|
a2002d1637 | ||
|
|
607ab7e860 | ||
|
|
f27989741a | ||
|
|
b2be63ada4 | ||
|
|
eee200f8d4 | ||
|
|
817ab2249f | ||
|
|
173777ade4 | ||
|
|
838233075a | ||
|
|
cabfc29119 | ||
|
|
f02570db88 | ||
|
|
c6b025e844 | ||
|
|
332a14552b | ||
|
|
c894270401 | ||
|
|
595815dc3e | ||
|
|
b959cafccc | ||
|
|
4cf0aede0e | ||
|
|
6cf298a29a | ||
|
|
62b2508a6c | ||
|
|
c3967b798e | ||
|
|
588897d1b0 | ||
|
|
862905e161 | ||
|
|
5f81e246f2 | ||
|
|
1b052d63e9 | ||
|
|
a3f6878649 | ||
|
|
74a92dcb7d | ||
|
|
38fc0b796a | ||
|
|
ef76a96c2d | ||
|
|
2667706d8d | ||
|
|
047fbe2918 | ||
|
|
af01c63b45 | ||
|
|
3796204372 | ||
|
|
9c7fbc1603 | ||
|
|
156950423b | ||
|
|
f80f6307a3 | ||
|
|
5f243bba0d | ||
|
|
7d8697ec3f | ||
|
|
ab44b8f0ae | ||
|
|
ad135b67b9 | ||
|
|
5a648f7e74 | ||
|
|
e87292ab60 | ||
|
|
d5d7d542ce | ||
|
|
bc694a1823 | ||
|
|
553f7fda47 | ||
|
|
bc198e215e | ||
|
|
73e34c083c | ||
|
|
cc22fa84e5 | ||
|
|
58b9dbc17d | ||
|
|
8d2811dd13 | ||
|
|
7d74d30b9f | ||
|
|
f3a0154b5a | ||
|
|
10b71dd7a0 | ||
|
|
1ac06b9286 | ||
|
|
cff6ef2c97 | ||
|
|
574f180dde | ||
|
|
bac0c9b4a1 | ||
|
|
a11f27be4e | ||
|
|
e7528af48f | ||
|
|
a50d3b04f6 | ||
|
|
fb5c185319 | ||
|
|
a00917cca4 | ||
|
|
7bbf3e235e | ||
|
|
abc0a53dc3 | ||
|
|
38ea61c649 | ||
|
|
652275834e | ||
|
|
64f05ad040 | ||
|
|
b5337bcfc3 | ||
|
|
2348417114 | ||
|
|
e6a2407ee5 | ||
|
|
b2a493f748 | ||
|
|
440f5f1b9e | ||
|
|
42d7510daf | ||
|
|
73a374aac2 | ||
|
|
4bb65cf8be | ||
|
|
7bcecc5628 | ||
|
|
e13b0d8cf3 | ||
|
|
db50b2fcc5 | ||
|
|
23820b2771 | ||
|
|
a4722053bd | ||
|
|
b33d13161c | ||
|
|
fa637c2e3c | ||
|
|
3137e45bb8 | ||
|
|
ed4ad8658e | ||
|
|
9e6b3e5764 | ||
|
|
94a699caae | ||
|
|
ecd82b4619 | ||
|
|
c44d6f2f9d | ||
|
|
ca70cd3882 | ||
|
|
0f9a7b2571 | ||
|
|
d33227ab2b | ||
|
|
53786fc306 | ||
|
|
f85806c067 | ||
|
|
0155adcea4 | ||
|
|
74d76ff580 | ||
|
|
537e5e063d | ||
|
|
a86478bf24 | ||
|
|
09de55f13c | ||
|
|
23d0e55308 | ||
|
|
185093c185 | ||
|
|
77400fcf21 | ||
|
|
6a41fe9829 | ||
|
|
0092e2a317 | ||
|
|
87b9e6d42d | ||
|
|
336dfd3b37 | ||
|
|
957c75a36e | ||
|
|
357886e31b | ||
|
|
330c270f3c | ||
|
|
9096bdc0f1 | ||
|
|
62ef83c9e0 | ||
|
|
1e7a7a5f3c | ||
|
|
433d49e3ab | ||
|
|
5a3f7fa123 | ||
|
|
3857cba8ab | ||
|
|
6a3cacb622 | ||
|
|
bdf60e9890 | ||
|
|
1e50caeed1 | ||
|
|
1decd52a36 | ||
|
|
0b7010eccb | ||
|
|
041c01341b | ||
|
|
439a377aa7 | ||
|
|
4f52c29611 | ||
|
|
374671fd9a | ||
|
|
0a9fc269ff | ||
|
|
2e3cc7cdd4 | ||
|
|
3ba15366be | ||
|
|
771bd1e1b2 | ||
|
|
1baa98fba4 | ||
|
|
39e79282ac | ||
|
|
13d5b86ae5 | ||
|
|
8edb3f1b34 | ||
|
|
69c3eb910f | ||
|
|
c8f881c2b9 | ||
|
|
71ac41adae | ||
|
|
ddb2121c67 | ||
|
|
558dc10a63 | ||
|
|
c9f1fd8fc2 | ||
|
|
e75b48cfe9 | ||
|
|
173b3b2ad9 | ||
|
|
654aaa2171 | ||
|
|
627d209927 | ||
|
|
57320957aa | ||
|
|
6fdade6c45 | ||
|
|
6d25586d08 | ||
|
|
4d89dc1efa | ||
|
|
a5653f07f8 | ||
|
|
7f35caff5d | ||
|
|
bf90fa5005 | ||
|
|
3d63a2cb69 | ||
|
|
466b1e75dc | ||
|
|
3dfc003902 | ||
|
|
b34737d27a | ||
|
|
1a5d41cc8a | ||
|
|
a5541a1c55 | ||
|
|
13e389a63e | ||
|
|
6884815428 | ||
|
|
f29f91d7e0 | ||
|
|
212b2e7bc2 | ||
|
|
3f2ee85a1a | ||
|
|
24450ed7ac | ||
|
|
ba978f55b0 | ||
|
|
94e4dc9aab | ||
|
|
67656c7131 | ||
|
|
dccb9ddd0e | ||
|
|
c3b264982d | ||
|
|
0efd0f9d26 | ||
|
|
7af35a0133 | ||
|
|
9caf201f24 | ||
|
|
ea7da5fb77 | ||
|
|
89641e3378 | ||
|
|
dcb6afa148 | ||
|
|
324330d354 | ||
|
|
056643ad83 | ||
|
|
e21f7bb171 | ||
|
|
148e6a9166 | ||
|
|
9a23de2be0 | ||
|
|
c377e868ed | ||
|
|
c4f1309dcd | ||
|
|
2e057827e3 | ||
|
|
9e1455294a | ||
|
|
e11e645c74 | ||
|
|
d2322fc404 | ||
|
|
e249a56892 | ||
|
|
7a74bd9c8a | ||
|
|
f6599fdc8b | ||
|
|
ed1a3e06a8 | ||
|
|
4a822298dd | ||
|
|
90a2f2d435 | ||
|
|
43dcc14568 | ||
|
|
a11b1d49bd | ||
|
|
cb103bf935 | ||
|
|
4c8716947c | ||
|
|
abbd255efa | ||
|
|
6047d67daa | ||
|
|
749a12f92f | ||
|
|
c4edead2db | ||
|
|
f689033bc5 | ||
|
|
415f2aa83b | ||
|
|
6dac95bd48 | ||
|
|
517d9a8f94 | ||
|
|
5d56700866 | ||
|
|
bc2f7cb927 | ||
|
|
eb6544ffc8 | ||
|
|
0ca92533a2 | ||
|
|
051b319c34 | ||
|
|
e100168979 | ||
|
|
3cb53c4a2a | ||
|
|
ead1e425a9 | ||
|
|
38b0f55db1 | ||
|
|
09ea738981 | ||
|
|
71de335e10 | ||
|
|
d3637a160d | ||
|
|
27c00ef779 | ||
|
|
756d5cc960 | ||
|
|
0f9b88ab68 | ||
|
|
30f16cac6d | ||
|
|
ece19ef7d3 | ||
|
|
7d18c3edea | ||
|
|
7d07490ed3 | ||
|
|
22571ba0a9 | ||
|
|
0ff90b8fba | ||
|
|
e66e906650 | ||
|
|
b3cf65b9db | ||
|
|
f953c72c4e | ||
|
|
bbc2e092b8 | ||
|
|
4c3c840d88 | ||
|
|
cf606665fc | ||
|
|
3871068aa6 | ||
|
|
d00ca7fd2a | ||
|
|
9ee3c21eac | ||
|
|
ed3afd418c | ||
|
|
b90fa2cc29 | ||
|
|
2ab517aa49 | ||
|
|
e46dd0254f | ||
|
|
1a5627f72d | ||
|
|
b808e942fd | ||
|
|
7fdb1715e4 | ||
|
|
ae2fcf7e00 | ||
|
|
94a8f7960a | ||
|
|
c410a1c18a | ||
|
|
81c46e2298 | ||
|
|
f369bffe5a | ||
|
|
fed1313a19 | ||
|
|
b7d4dee446 | ||
|
|
23db97cca5 | ||
|
|
0f54e99141 | ||
|
|
c701c388c6 | ||
|
|
328ef5dc9c | ||
|
|
07fbb799be | ||
|
|
699f31c20a | ||
|
|
c97e51b855 | ||
|
|
e10173bf8a | ||
|
|
dbfa73d75a | ||
|
|
1b1212d9fb | ||
|
|
065f85bf71 | ||
|
|
09bafa9aeb | ||
|
|
fe30c88cc3 | ||
|
|
3a9538259c | ||
|
|
0c730e57e0 | ||
|
|
33c9f6b1ab | ||
|
|
2346396bdf | ||
|
|
de5ebc9093 | ||
|
|
42c4c33d30 | ||
|
|
046603ac54 | ||
|
|
bcfa9463bb | ||
|
|
42bda873b4 | ||
|
|
d2001ca464 | ||
|
|
c8ba5cf863 | ||
|
|
85842c1596 | ||
|
|
0fb2f1fc5e | ||
|
|
497677f359 | ||
|
|
15ae5e7f95 | ||
|
|
5a0283d083 | ||
|
|
9496606964 | ||
|
|
b50e4a41b5 | ||
|
|
e595d4d92f | ||
|
|
3e526d46fa | ||
|
|
a36c7e8331 | ||
|
|
7bb054e45c | ||
|
|
cd87be4a24 | ||
|
|
aefc4cda4c | ||
|
|
cfe81b4432 | ||
|
|
72c0bb10d6 | ||
|
|
ca618ea0c9 | ||
|
|
d20e789856 | ||
|
|
a135857c7a | ||
|
|
34546c24c7 | ||
|
|
61c62b3f07 | ||
|
|
f2390b64e8 | ||
|
|
4ae1cd63bb | ||
|
|
a4339818a0 | ||
|
|
b3394277c7 | ||
|
|
fdf19b0a26 | ||
|
|
9798a31a9a | ||
|
|
f93bd325e1 | ||
|
|
97ba1fbd42 | ||
|
|
28fc3736aa | ||
|
|
06805217c2 | ||
|
|
b833250053 | ||
|
|
9455912932 | ||
|
|
4c488aa31f | ||
|
|
b0ecfd2d8d | ||
|
|
2b4f1c8e55 | ||
|
|
e79b293562 | ||
|
|
2861dff1f0 | ||
|
|
61b6a00214 | ||
|
|
b02fb91a54 | ||
|
|
8bf3be88be | ||
|
|
fb5095bff5 | ||
|
|
d28097f508 | ||
|
|
a27b10c5c3 | ||
|
|
8c2d4e5ea7 | ||
|
|
507496627b | ||
|
|
f5b89987af | ||
|
|
4bb8f5b15e | ||
|
|
914befda16 | ||
|
|
b5bbe2bd43 | ||
|
|
68288436ac | ||
|
|
d4ce855e5f | ||
|
|
47b0ed5171 | ||
|
|
00b9eea9ec | ||
|
|
bf27b88728 | ||
|
|
17fe41d42b | ||
|
|
36600decef | ||
|
|
e09221f1e0 | ||
|
|
62cc785c4a | ||
|
|
7c45941b57 | ||
|
|
7a2e0edab4 | ||
|
|
fb0cb676b9 | ||
|
|
12d85dec10 | ||
|
|
1094a03a04 | ||
|
|
6cc38b0680 | ||
|
|
7abb36a7ec | ||
|
|
6832b19fc5 | ||
|
|
1c4cbedb6a | ||
|
|
8ff87be631 | ||
|
|
e43beec36a | ||
|
|
c5b7fef5ba | ||
|
|
4c5e40ae4e | ||
|
|
ab2dfd4f5c | ||
|
|
13029e3286 | ||
|
|
44c9ccd65d | ||
|
|
7912c26323 | ||
|
|
69d68f5a25 | ||
|
|
836be52724 | ||
|
|
9f993dcc1e | ||
|
|
61760a180f | ||
|
|
a3f3e160e0 | ||
|
|
b1190914a9 | ||
|
|
e79a952061 | ||
|
|
480ed721d1 | ||
|
|
42c7436827 | ||
|
|
16e9e8b64d | ||
|
|
23ab897e32 | ||
|
|
6fcdec7c12 | ||
|
|
b734f05525 | ||
|
|
30ea9c6bf7 | ||
|
|
6de3a19b86 | ||
|
|
06e59cf916 | ||
|
|
3f3a484a3a | ||
|
|
05e52114bf | ||
|
|
1a421d3b5d | ||
|
|
97fdbc8f64 | ||
|
|
ebf4025718 | ||
|
|
a81a5285b7 | ||
|
|
95c9d56ea4 | ||
|
|
18552f3a22 | ||
|
|
9187e0f2cf | ||
|
|
20d0f53b46 | ||
|
|
0dcb15b7e6 | ||
|
|
ba49a82463 | ||
|
|
8d1b8a47e6 | ||
|
|
b90d3af5b8 | ||
|
|
c34ac468fb | ||
|
|
fefeaeef18 | ||
|
|
91c2352b03 | ||
|
|
9f454d1e08 | ||
|
|
59e5541094 | ||
|
|
8e104b443e | ||
|
|
0eed017bb9 | ||
|
|
789700cf60 | ||
|
|
570d71e8d6 | ||
|
|
961ef644d1 | ||
|
|
6fcd09ddbd | ||
|
|
a9d32f3c90 | ||
|
|
8ec0d00c75 | ||
|
+3 |
0acc530f8b | ||
|
|
7a6f516b4f | ||
|
|
7461e2a914 | ||
|
|
178609a062 | ||
|
|
8ea8f6e954 | ||
|
|
b49efc24ac | ||
|
|
6853e87a49 | ||
|
|
4197d1bf3f | ||
|
|
3faa9f21ef | ||
|
|
a75e4cc80a | ||
|
|
f7bda0904f | ||
|
|
fe7be4dd8d | ||
|
|
0f15ee5963 | ||
|
|
4fa09df6d0 | ||
|
|
b6f72c566c | ||
|
|
1934ef5fac | ||
|
|
467c9eeedc | ||
|
|
7ff0b4d547 | ||
|
|
5917e626b0 | ||
|
|
6028a88111 | ||
|
|
80def43d44 | ||
|
|
0b748f31ce | ||
|
|
0358722c1a | ||
|
|
b6267599f5 | ||
|
|
44f258d6bf | ||
|
|
4cb6e287c8 | ||
|
|
db0bbf5173 | ||
|
|
41fd6f9395 | ||
|
|
4374bd4e16 | ||
|
|
042459fe34 | ||
|
|
cc6511351f | ||
|
|
d1743a3279 | ||
|
|
f3fb440fb5 | ||
|
|
2307f11ab3 | ||
|
|
d8d48a9516 | ||
|
|
5ffbcfc302 | ||
|
|
d27a3dee83 | ||
|
|
7080ff29fc | ||
|
|
638afd8e08 | ||
|
|
947e3345ef | ||
|
|
ea51a50bfc | ||
|
|
c6f59e1b01 | ||
|
|
6d1eabe9f9 | ||
|
|
c238fde877 | ||
|
|
548601981f | ||
|
|
05481aa8c0 | ||
|
|
1cc26e43e5 | ||
|
|
99d7ec06ec | ||
|
|
ff996646b2 | ||
|
|
01260009be | ||
|
|
7dea0975ec | ||
|
|
a63f5dc7cb | ||
|
|
fd7a9f0998 | ||
|
|
246b0d4aac | ||
|
|
42ba15f2c0 | ||
|
|
aa9f48d136 | ||
|
|
64138642e8 | ||
|
|
223008b1f7 | ||
|
|
cbfd445ae7 | ||
|
|
137f46a547 | ||
|
|
1b8accde30 | ||
|
|
07544252b4 | ||
|
|
8708519cad | ||
|
|
6c30b7efc7 | ||
|
|
8b5e344f72 | ||
|
|
010955ff5c | ||
|
|
956b5645e4 | ||
|
|
068db44603 | ||
|
|
dd7d0fca2d | ||
|
|
526176c471 | ||
|
|
09120c923c | ||
|
|
d1f8e95162 | ||
|
|
6573556d0e | ||
|
|
fc62b91ee5 | ||
|
|
08e9cd206b | ||
|
|
fb0f12ae28 | ||
|
|
c710fc1c62 | ||
|
|
f41ee6fb1c | ||
|
|
513c332ed0 | ||
|
|
693f349db2 | ||
|
|
63ad0050f0 | ||
|
|
dcdb0dcb83 | ||
|
|
b3b9edca29 | ||
|
|
1fbbc2d27d | ||
|
|
4b98e487d4 | ||
|
|
95380eaed4 | ||
|
|
1f3c64d361 | ||
|
|
bb1666b92b | ||
|
|
0f12d1f903 | ||
|
|
74941603a9 | ||
|
|
4fda12aa2d | ||
|
|
e684d05e22 | ||
|
|
335744ff6d | ||
|
|
7bc40f6223 | ||
|
|
8291742ad9 | ||
|
|
fb964ecb85 | ||
|
|
7dba9f782e | ||
|
|
79f91ee2d9 | ||
|
|
2df8de0e2a | ||
|
|
ce4608bff4 | ||
|
|
125c836d88 | ||
|
|
ec36b13a30 | ||
|
|
52b1a0fcac | ||
|
|
a6c36724eb | ||
|
|
6abb845971 | ||
|
|
dc38a243c2 | ||
|
|
03a03a196d | ||
|
|
931270479e | ||
|
|
6f4c5cd746 | ||
|
|
d3c8626a5a | ||
|
|
ffb1bb0a60 | ||
|
|
9bd01de5f9 | ||
|
|
f5caf6a350 | ||
|
|
46672a490a | ||
|
|
f35fbec1be | ||
|
|
824051eb28 | ||
|
|
fde77a7e19 | ||
|
|
2ec4811f15 | ||
|
|
15fe4779f4 | ||
|
|
6d3d76cff8 | ||
|
|
04f2f7b8ff | ||
|
|
8aa5871a90 | ||
|
|
ea1861d7d9 | ||
|
|
7ebe6cde5d | ||
|
|
f64b7236bb | ||
|
|
af21d5be36 | ||
|
|
54f83a5013 | ||
|
|
6a5bbfd2f5 | ||
|
|
c5b3232e1a | ||
|
|
5dc372fc46 | ||
|
|
fd378ec3e4 | ||
|
|
0e27f15eb9 | ||
|
|
211aab9c16 | ||
|
|
eb9e9f0ce8 | ||
|
|
bd23cec7d1 | ||
|
|
c7e7ec9502 | ||
|
|
6897ad6502 | ||
|
|
ed4172dddf | ||
|
|
de05c2da17 | ||
|
|
c085d69060 | ||
|
|
c2bfc77137 | ||
|
|
81797ffde3 | ||
|
|
69e8208538 | ||
|
|
ee028e1209 | ||
|
|
b16c09df75 | ||
|
|
879d4226f6 | ||
|
|
83905ef6c6 | ||
|
|
f8c8c12ebc | ||
|
|
a9b4b0c5d8 | ||
|
|
d28b1c0f1e | ||
|
|
96e832cc80 | ||
|
|
bdb2ccc7a6 | ||
|
|
e1b05fcea3 | ||
|
|
426c75a330 | ||
|
|
a9854e69ce | ||
|
|
2097e233cb | ||
|
|
c29042b71e | ||
|
|
b5b7e5243d | ||
|
|
d5fafddea5 | ||
|
|
b6df6b8957 | ||
|
|
45112a613e | ||
|
|
ea5b9c3ecd | ||
|
|
bd18e4f5ef | ||
|
|
1018eb56a6 | ||
|
|
def6d400ab | ||
|
|
2ce89cfea9 | ||
|
|
48906a9d8e | ||
|
|
64528263f4 | ||
|
|
cbbb8ccbe3 | ||
|
|
e2e6bdd4cd | ||
|
|
d66ff413de | ||
|
|
9d553746b0 | ||
|
|
57af96c288 | ||
|
|
94bdcc142a | ||
|
|
dd3d1a87c2 | ||
|
|
4268775f9c | ||
|
|
710739c7d2 | ||
|
|
c54db48c0d | ||
|
|
903145a4ae | ||
|
|
43f188c8a7 | ||
|
|
8a3993d42b | ||
|
|
bab39980e7 | ||
|
|
48295c0604 | ||
|
|
3fd39cea0d | ||
|
|
1fe867e205 | ||
|
|
f5991387ef | ||
|
|
3f3c63c01e | ||
|
|
f80cf0e17e | ||
|
|
10b7648857 | ||
|
|
6829ee0ee9 | ||
|
|
6df24a2eb9 | ||
|
|
8b73abfd83 | ||
|
|
66c4ade868 | ||
|
|
2a873d2f80 | ||
|
|
bb320c7e97 | ||
|
|
931ee7930f | ||
|
|
f55fd291bf | ||
|
|
3056c34c64 | ||
|
|
f90427650c | ||
|
|
defe5fced3 | ||
|
|
c0070c2cc0 | ||
|
|
d50863f455 | ||
|
|
87ed24e1b4 | ||
|
|
7e564163ce | ||
|
|
0307671c63 | ||
|
|
4a7f41b3ee | ||
|
|
2f187d1375 | ||
|
|
4567faef47 | ||
|
|
d37c3a75e7 | ||
|
|
4895217142 | ||
|
|
0b59cb2e59 | ||
|
|
2088de1cac | ||
|
|
066b9cfa09 | ||
|
|
1e6f9dcc04 | ||
|
|
a023cb8cf1 | ||
|
|
a00cbaf78c | ||
|
|
d378106a85 | ||
|
|
3eb9592e10 | ||
|
|
04b6ef73e4 | ||
|
|
13ef6a50cf | ||
|
|
04c607ccde | ||
|
|
926dd7b03f | ||
|
|
e00dd12357 | ||
|
|
cdc08c5528 | ||
|
|
4df0c2ee10 | ||
|
|
48a8f9385e | ||
|
|
a91776e121 | ||
|
|
f5aed34d3c | ||
|
|
0f02706906 | ||
|
|
eb463735e2 | ||
|
|
adec589ead | ||
|
|
e5246f2667 | ||
|
|
b29ec675f9 | ||
|
|
b30566c684 |
@@ -15,7 +15,14 @@
|
||||
"access": "public",
|
||||
"baseBranch": "main",
|
||||
"updateInternalDependencies": "patch",
|
||||
"ignore": ["e2e-cra-5", "e2e-next-13", "e2e-vite-3"],
|
||||
"ignore": [
|
||||
"bundlers-esbuild",
|
||||
"bundlers-vite",
|
||||
"bundlers-webpack",
|
||||
"bundlers-swc",
|
||||
"runtimes-node-commonjs",
|
||||
"runtimes-node-module"
|
||||
],
|
||||
"___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH": {
|
||||
"updateInternalDependents": "always",
|
||||
"onlyUpdatePeerDependentsWhenOutOfRange": true
|
||||
|
||||
+2
-1
@@ -3,4 +3,5 @@
|
||||
|
||||
# used for unit tests that hit real IPFS gateway
|
||||
# get a secret key from https://thirdweb.com/create-api-key
|
||||
TW_SECRET_KEY=""
|
||||
# Learn more about API keys. https://portal.thirdweb.com/account/api-keys
|
||||
TW_SECRET_KEY=""
|
||||
|
||||
@@ -4,13 +4,13 @@ description: "Sets up Node.js and runs install"
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- uses: pnpm/action-setup@v2
|
||||
- uses: pnpm/action-setup@v3
|
||||
with:
|
||||
version: 8
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 16
|
||||
node-version: 20
|
||||
registry-url: "https://registry.npmjs.org"
|
||||
cache: "pnpm"
|
||||
|
||||
|
||||
+28
-8
@@ -22,14 +22,13 @@ We use [Turborepo](https://turborepo.org/docs/getting-started) to manage the mon
|
||||
|
||||
You can see a quick outline of each of the projects within this repo below, each living within the [/packages](/packages) directory:
|
||||
|
||||
| Package | Description | Latest Version |
|
||||
| ------------------------------ | -------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| [/sdk](./packages/sdk) | Best in class web3 SDK for Browser, Node and Mobile apps | <a href="https://www.npmjs.com/package/@thirdweb-dev/sdk"><img src="https://img.shields.io/npm/v/@thirdweb-dev/sdk?color=red&label=npm&logo=npm" alt="npm version"/></a> |
|
||||
| [/react](./packages/react) | Ultimate collection of React hooks for your web3 apps | <a href="https://www.npmjs.com/package/@thirdweb-dev/react"><img src="https://img.shields.io/npm/v/@thirdweb-dev/react?color=red&label=npm&logo=npm" alt="npm version"/></a> |
|
||||
| [/auth](./packages/auth) | Best in class wallet authentication for Node backends | <a href="https://www.npmjs.com/package/@thirdweb-dev/auth"><img src="https://img.shields.io/npm/v/@thirdweb-dev/auth?color=red&label=npm&logo=npm" alt="npm version"/></a> |
|
||||
| [/storage](./packages/storage) | Best in class decentralized storage SDK for Browser and Node | <a href="https://www.npmjs.com/package/@thirdweb-dev/storage"><img src="https://img.shields.io/npm/v/@thirdweb-dev/storage?color=red&label=npm&logo=npm" alt="npm version"/></a> |
|
||||
| [/cli](./packages/cli) | Publish and deploy smart contracts without dealing with private keys | <a href="https://www.npmjs.com/package/thirdweb"><img src="https://img.shields.io/npm/v/thirdweb?color=red&label=npm&logo=npm" alt="npm version"/></a> |
|
||||
| [/solana](./packages/solana) | Solana SDK for Browser, Node and React Native | <a href="https://www.npmjs.com/package/@thirdweb-dev/solana"><img src="https://img.shields.io/npm/v/@thirdweb-dev/solana?color=red&label=npm&logo=npm" alt="npm version"/></a> |
|
||||
| Package | Description | Latest Version |
|
||||
| ------------------------------------- | -------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| [/sdk](./legacy_packages/sdk) | Best in class web3 SDK for Browser, Node and Mobile apps | <a href="https://www.npmjs.com/package/@thirdweb-dev/sdk"><img src="https://img.shields.io/npm/v/@thirdweb-dev/sdk?color=red&label=npm&logo=npm" alt="npm version"/></a> |
|
||||
| [/react](./legacy_packages/react) | Ultimate collection of React hooks for your web3 apps | <a href="https://www.npmjs.com/package/@thirdweb-dev/react"><img src="https://img.shields.io/npm/v/@thirdweb-dev/react?color=red&label=npm&logo=npm" alt="npm version"/></a> |
|
||||
| [/auth](./legacy_packages/auth) | Best in class wallet authentication for Node backends | <a href="https://www.npmjs.com/package/@thirdweb-dev/auth"><img src="https://img.shields.io/npm/v/@thirdweb-dev/auth?color=red&label=npm&logo=npm" alt="npm version"/></a> |
|
||||
| [/storage](./legacy_packages/storage) | Best in class decentralized storage SDK for Browser and Node | <a href="https://www.npmjs.com/package/@thirdweb-dev/storage"><img src="https://img.shields.io/npm/v/@thirdweb-dev/storage?color=red&label=npm&logo=npm" alt="npm version"/></a> |
|
||||
| [/cli](./legacy_packages/cli) | Publish and deploy smart contracts without dealing with private keys | <a href="https://www.npmjs.com/package/thirdweb"><img src="https://img.shields.io/npm/v/thirdweb?color=red&label=npm&logo=npm" alt="npm version"/></a> |
|
||||
|
||||
## How to contribute
|
||||
|
||||
@@ -107,6 +106,27 @@ In your test project, you need to:
|
||||
1. Delete the dependencies cache. If you're using Next.js, that is the `.next` directory, and if you're using CRA, that is the `node_modules/.cache` directory.
|
||||
2. Restart the development server.
|
||||
|
||||
#### Testing from npm
|
||||
|
||||
You can also test your changes by publishing a dev package to npm and adding your package as you normally do in your projects.
|
||||
|
||||
Once you have your PR up you can add a comment with the text: `/release-pr`. This will trigger a GitHub action that will publish a dev version to npm.
|
||||
|
||||
You can see the action progress in GitHub's `Actions` tab, look for the workflow: `release-pr`.
|
||||
|
||||
Once the action finishes executing, select it, click on deploy and look for the `Deploy pre-release` item. Scroll all the way to the bottom and you should see something like:
|
||||
|
||||
```
|
||||
success packages published successfully:
|
||||
🦋 @***-dev/[email protected]
|
||||
🦋 @***-dev/[email protected]
|
||||
...
|
||||
```
|
||||
|
||||
These are the versions the GH action published to npm for testing purposes. Let's assume you're working on the `@thirdweb-dev/react` package, you can then run:
|
||||
|
||||
`yarn add @thirdweb-dev/react@0.0.0-dev-06a7cb1-20230901190619` to install the dev package.
|
||||
|
||||
<br/>
|
||||
|
||||
### Publish Your Changes
|
||||
|
||||
@@ -11,3 +11,9 @@ Short description of the bug fixed or feature added
|
||||
|
||||
- [ ] Automated tests: link to unit test file
|
||||
- [ ] Manual tests: step by step instructions on how to test
|
||||
|
||||
## Contributor NFT
|
||||
|
||||
Paste in your wallet address below and we will airdrop you a special NFT when your pull request is merged.
|
||||
|
||||
```Address: ```
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
name: Build, Lint & Test
|
||||
name: Build, Lint & Test & Benchmark
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -8,6 +8,7 @@ on:
|
||||
# trigger on merge group as well (merge queue)
|
||||
merge_group:
|
||||
types: [checks_requested]
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
@@ -27,10 +28,15 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 25
|
||||
|
||||
- name: Setup bun
|
||||
uses: oven-sh/setup-bun@v1
|
||||
with:
|
||||
bun-version: 1.0.35
|
||||
|
||||
- name: Install
|
||||
uses: ./.github/composite-actions/install
|
||||
|
||||
@@ -40,7 +46,7 @@ jobs:
|
||||
- id: get-week
|
||||
run: echo "WEEK=$(date +%U)" >> $GITHUB_OUTPUT
|
||||
|
||||
- uses: actions/cache@v3
|
||||
- uses: actions/cache@v4
|
||||
timeout-minutes: 5
|
||||
id: cache-build
|
||||
with:
|
||||
@@ -54,16 +60,21 @@ jobs:
|
||||
needs: build
|
||||
steps:
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 16
|
||||
node-version: 20
|
||||
check-latest: true
|
||||
|
||||
- uses: pnpm/action-setup@v2
|
||||
- name: Setup bun
|
||||
uses: oven-sh/setup-bun@v1
|
||||
with:
|
||||
bun-version: 1.0.35
|
||||
|
||||
- uses: pnpm/action-setup@v3
|
||||
with:
|
||||
version: 8
|
||||
|
||||
- uses: actions/cache@v3
|
||||
- uses: actions/cache@v4
|
||||
timeout-minutes: 5
|
||||
id: restore-build
|
||||
with:
|
||||
@@ -79,28 +90,73 @@ jobs:
|
||||
needs: build
|
||||
steps:
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 16
|
||||
node-version: 20
|
||||
check-latest: true
|
||||
|
||||
- uses: pnpm/action-setup@v2
|
||||
# we use bun for some test suites
|
||||
- name: Setup bun
|
||||
uses: oven-sh/setup-bun@v1
|
||||
with:
|
||||
bun-version: 1.0.35
|
||||
|
||||
- uses: pnpm/action-setup@v3
|
||||
with:
|
||||
version: 8
|
||||
|
||||
- uses: actions/cache@v3
|
||||
- uses: actions/cache@v4
|
||||
timeout-minutes: 5
|
||||
id: restore-build
|
||||
with:
|
||||
path: ./*
|
||||
key: ${{ github.sha }}-${{ github.run_number }}
|
||||
|
||||
- name: Set up foundry
|
||||
uses: foundry-rs/foundry-toolchain@v1
|
||||
|
||||
- run: pnpm test
|
||||
|
||||
- name: Upload coverage reports to Codecov
|
||||
uses: codecov/codecov-action@v3
|
||||
with:
|
||||
files: ./packages/sdk/coverage/evm/lcov.info,./packages/sdk/coverage/solana/lcov.info,
|
||||
files: ./packages/thirdweb/coverage/lcov.info
|
||||
|
||||
test_legacy:
|
||||
timeout-minutes: 15
|
||||
name: Unit Tests (Legacy)
|
||||
runs-on: ubuntu-latest-16
|
||||
needs: build
|
||||
steps:
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
check-latest: true
|
||||
|
||||
# we use bun for some test suites
|
||||
- name: Setup bun
|
||||
uses: oven-sh/setup-bun@v1
|
||||
with:
|
||||
bun-version: 1.0.35
|
||||
|
||||
- uses: pnpm/action-setup@v3
|
||||
with:
|
||||
version: 8
|
||||
|
||||
- uses: actions/cache@v4
|
||||
timeout-minutes: 5
|
||||
id: restore-build
|
||||
with:
|
||||
path: ./*
|
||||
key: ${{ github.sha }}-${{ github.run_number }}
|
||||
|
||||
- run: pnpm test:legacy
|
||||
|
||||
- name: Upload coverage reports to Codecov
|
||||
uses: codecov/codecov-action@v3
|
||||
with:
|
||||
files: ./legacy_packages/sdk/coverage/evm/lcov.info
|
||||
|
||||
e2e:
|
||||
timeout-minutes: 15
|
||||
@@ -109,16 +165,22 @@ jobs:
|
||||
needs: build
|
||||
steps:
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 16
|
||||
node-version: 20
|
||||
check-latest: true
|
||||
|
||||
- uses: pnpm/action-setup@v2
|
||||
# we use bun for some test suites
|
||||
- name: Setup bun
|
||||
uses: oven-sh/setup-bun@v1
|
||||
with:
|
||||
bun-version: 1.0.35
|
||||
|
||||
- uses: pnpm/action-setup@v3
|
||||
with:
|
||||
version: 8
|
||||
|
||||
- uses: actions/cache@v3
|
||||
- uses: actions/cache@v4
|
||||
timeout-minutes: 5
|
||||
id: restore-build
|
||||
with:
|
||||
@@ -133,3 +195,37 @@ jobs:
|
||||
env:
|
||||
NODE_OPTIONS: "--max_old_space_size=4096"
|
||||
CLI_E2E_API_KEY: ${{ secrets.CLI_E2E_API_KEY }}
|
||||
|
||||
benchmark:
|
||||
# only run if is not merge_group
|
||||
if: github.event_name != 'merge_group'
|
||||
timeout-minutes: 15
|
||||
name: "Benchmark"
|
||||
runs-on: ubuntu-latest-16
|
||||
needs: build
|
||||
steps:
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
check-latest: true
|
||||
|
||||
- uses: pnpm/action-setup@v3
|
||||
with:
|
||||
version: 8
|
||||
|
||||
- uses: actions/cache@v4
|
||||
timeout-minutes: 5
|
||||
id: restore-build
|
||||
with:
|
||||
path: ./*
|
||||
key: ${{ github.sha }}-${{ github.run_number }}
|
||||
|
||||
- name: Set up foundry
|
||||
uses: foundry-rs/foundry-toolchain@v1
|
||||
|
||||
- name: Run benchmarks
|
||||
uses: CodSpeedHQ/action@v2
|
||||
with:
|
||||
token: ${{ secrets.CODSPEED_TOKEN }}
|
||||
run: "pnpm bench"
|
||||
|
||||
@@ -13,12 +13,12 @@ name: "CodeQL"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
branches: ["main"]
|
||||
pull_request:
|
||||
# The branches below must be a subset of the branches above
|
||||
branches: [ "main" ]
|
||||
branches: ["main"]
|
||||
schedule:
|
||||
- cron: '37 9 * * 3'
|
||||
- cron: "37 9 * * 3"
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
@@ -32,41 +32,40 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
language: [ 'javascript' ]
|
||||
language: ["javascript"]
|
||||
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
|
||||
# Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v2
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
# By default, queries listed here will override any specified in a config file.
|
||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||
|
||||
# Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
|
||||
# queries: security-extended,security-and-quality
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v2
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
# By default, queries listed here will override any specified in a config file.
|
||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||
|
||||
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v2
|
||||
# Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
|
||||
# queries: security-extended,security-and-quality
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v2
|
||||
|
||||
# If the Autobuild fails above, remove it and uncomment the following three lines.
|
||||
# modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
||||
|
||||
# - run: |
|
||||
# echo "Run, Build Application using script"
|
||||
# ./location_of_script_within_repo/buildscript.sh
|
||||
# If the Autobuild fails above, remove it and uncomment the following three lines.
|
||||
# modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v2
|
||||
# - run: |
|
||||
# echo "Run, Build Application using script"
|
||||
# ./location_of_script_within_repo/buildscript.sh
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v2
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
name: Dispatch Doc Generation
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
dispatch:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Repository Dispatch
|
||||
uses: peter-evans/[email protected]
|
||||
with:
|
||||
token: ${{ secrets.REPO_ACCESS_TOKEN }}
|
||||
repository: thirdweb-dev/docs
|
||||
event-type: generate-docs
|
||||
@@ -0,0 +1,75 @@
|
||||
# On the Version Packages branch
|
||||
# Generate and Commit the Typedoc JSON files and the Snippets JSON files
|
||||
|
||||
name: Commit Release Artifacts
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- changeset-release/main
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
# cancel any previous runs if they were started before this one
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
||||
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
|
||||
TW_SECRET_KEY: ${{ secrets.TW_SECRET_KEY }}
|
||||
|
||||
jobs:
|
||||
release-artifacts:
|
||||
timeout-minutes: 15
|
||||
runs-on: ubuntu-latest-16
|
||||
steps:
|
||||
- name: Checkout branch
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# bun setup is required, because pnpm typdoc will run pnpm build and some packages uses bun for build
|
||||
- name: Setup bun
|
||||
uses: oven-sh/setup-bun@v1
|
||||
with:
|
||||
bun-version: 1.0.35
|
||||
|
||||
- name: Install
|
||||
uses: ./.github/composite-actions/install
|
||||
|
||||
- name: Build
|
||||
run: pnpm build
|
||||
|
||||
- name: Typedoc
|
||||
run: pnpm typedoc
|
||||
|
||||
- name: Generate Docs
|
||||
run: |
|
||||
cd legacy_packages/sdk
|
||||
pnpm generate-docs
|
||||
cd ../react-core
|
||||
pnpm generate-docs
|
||||
cd ../react
|
||||
pnpm generate-docs
|
||||
cd ../..
|
||||
|
||||
- name: Generate Snippets
|
||||
run: pnpm generate-snippets
|
||||
|
||||
- name: Commit changes
|
||||
run: |
|
||||
git config --local user.email "[email protected]"
|
||||
git config --local user.name "GitHub Action"
|
||||
git status
|
||||
git add legacy_packages/react/typedoc/documentation.json.gz
|
||||
git add legacy_packages/react-core/typedoc/documentation.json.gz
|
||||
git add legacy_packages/react-native/typedoc/documentation.json.gz
|
||||
git add legacy_packages/sdk/typedoc/documentation.json.gz
|
||||
git add legacy_packages/storage/typedoc/documentation.json.gz
|
||||
git add legacy_packages/wallets/typedoc/documentation.json.gz
|
||||
git add packages/thirdweb/typedoc/documentation.json.gz
|
||||
git add snippets/feature_snippets_react.json
|
||||
git add snippets/feature_snippets_sdk.json
|
||||
git add snippets/snippets.json
|
||||
git commit -m "Commit Release Artifacts [skip ci]"
|
||||
git push
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -61,12 +61,17 @@ jobs:
|
||||
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
|
||||
|
||||
- name: pull the repo.
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: true
|
||||
repository: ${{ fromJson(steps.get_pull_request_ref.outputs.data).head.repo.full_name }}
|
||||
ref: ${{ fromJson(steps.get_pull_request_ref.outputs.data).head.ref }}
|
||||
|
||||
- name: Setup bun
|
||||
uses: oven-sh/setup-bun@v1
|
||||
with:
|
||||
bun-version: 1.0.35
|
||||
|
||||
- name: Install
|
||||
uses: ./.github/composite-actions/install
|
||||
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
# Using CUSTOM_GITHUB_TOKEN instead of GITHUB_TOKEN is required to trigger workflows in "Version Packages" PR created by changesets/action
|
||||
# CUSTOM_GITHUB_TOKEN should be a PAT with write access to the repo and has to be created and set as a secret in the repo manually
|
||||
|
||||
name: Release Nightly & Stable
|
||||
|
||||
on:
|
||||
@@ -5,6 +8,7 @@ on:
|
||||
paths:
|
||||
- ".changeset/**"
|
||||
- "packages/**"
|
||||
- "legacy_packages/**"
|
||||
branches:
|
||||
- main
|
||||
|
||||
@@ -22,9 +26,16 @@ jobs:
|
||||
runs-on: ubuntu-latest-16
|
||||
steps:
|
||||
- name: Checkout branch
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
# Do not use the GITHUB_TOKEN by default
|
||||
token: ${{ secrets.CUSTOM_GITHUB_TOKEN }}
|
||||
|
||||
- name: Setup bun
|
||||
uses: oven-sh/setup-bun@v1
|
||||
with:
|
||||
bun-version: 1.0.35
|
||||
|
||||
- name: Install
|
||||
uses: ./.github/composite-actions/install
|
||||
@@ -41,7 +52,7 @@ jobs:
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GITHUB_TOKEN: ${{ secrets.CUSTOM_GITHUB_TOKEN }}
|
||||
|
||||
- name: Create @nightly release
|
||||
if: steps.changesets.outputs.published != 'true'
|
||||
@@ -51,4 +62,4 @@ jobs:
|
||||
pnpm release:nightly
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GITHUB_TOKEN: ${{ secrets.CUSTOM_GITHUB_TOKEN }}
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
name: Sync chains every hour
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 * * * *" # Runs every hour
|
||||
workflow_dispatch: # Allows you to manually run the workflow from the Actions tab
|
||||
|
||||
jobs:
|
||||
sync-chains:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install bun
|
||||
uses: oven-sh/setup-bun@v1
|
||||
with:
|
||||
bun-version: 1.0.35
|
||||
|
||||
- name: Run chains sync
|
||||
run: bun run db:sync
|
||||
working-directory: ./legacy_packages/chains
|
||||
|
||||
- name: Check for Changes
|
||||
id: git-check
|
||||
run: |
|
||||
if git diff --quiet; then
|
||||
echo "CHANGES_FOUND=false" >> $GITHUB_ENV
|
||||
else
|
||||
echo "CHANGES_FOUND=true" >> $GITHUB_ENV
|
||||
fi
|
||||
|
||||
- name: Create or Update sync-chains.md
|
||||
if: env.CHANGES_FOUND == 'true'
|
||||
run: |
|
||||
echo '---' > .changeset/sync-chains.md
|
||||
echo '"@thirdweb-dev/chains": patch' >> .changeset/sync-chains.md
|
||||
echo '---' >> .changeset/sync-chains.md
|
||||
echo '' >> .changeset/sync-chains.md
|
||||
echo 'Synced Chains Package' >> .changeset/sync-chains.md
|
||||
|
||||
- name: Commit Changes
|
||||
if: env.CHANGES_FOUND == 'true'
|
||||
run: |
|
||||
git config --local user.email "[email protected]"
|
||||
git config --local user.name "GitHub Action"
|
||||
git add -A
|
||||
git commit -m "chore(chains): sync from db"
|
||||
|
||||
- name: Create or Update Pull Request
|
||||
if: env.CHANGES_FOUND == 'true'
|
||||
uses: peter-evans/create-pull-request@v3
|
||||
with:
|
||||
title: "chore(chains): update chains"
|
||||
branch: "sync-chains"
|
||||
body: "This PR was automatically created by a GitHub Action. It updates the chains package with the latest changes from the database."
|
||||
commit-message: "chore(chains): sync from db"
|
||||
+2
-2
@@ -10,7 +10,7 @@ dist-ssr
|
||||
.cache
|
||||
server/dist
|
||||
public/dist
|
||||
./packages/contracts-js/abis
|
||||
./legacy_packages/contracts-js/abis
|
||||
.idea/
|
||||
.yalc/
|
||||
yalc.lock
|
||||
@@ -20,4 +20,4 @@ playwright-report/
|
||||
.env/
|
||||
|
||||
# Artifacts
|
||||
packages/cli/artifacts/
|
||||
legacy_packages/cli/artifacts/
|
||||
@@ -1,11 +1,11 @@
|
||||
<p align="center">
|
||||
<br />
|
||||
<a href="https://thirdweb.com">
|
||||
<img src="https://github.com/thirdweb-dev/js/blob/main/packages/sdk/logo.svg?raw=true" width="200" alt=""/></a>
|
||||
<img src="https://github.com/thirdweb-dev/js/blob/main/legacy_packages/sdk/logo.svg?raw=true" width="200" alt=""/></a>
|
||||
<br />
|
||||
</p>
|
||||
|
||||
<h1 align="center"><a href='https://thirdweb.com/'>thirdweb</a> JavaScript/TypeScript monorepo</h1>
|
||||
<h1 align="center"><a href='https://thirdweb.com/'>thirdweb</a> TypeScript SDK</h1>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/thirdweb-dev/js/actions/workflows/build-test-lint.yml">
|
||||
@@ -16,36 +16,71 @@
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<p align="center"><strong>Best in class web3 SDKs for Browser, Node and Mobile apps</strong></p>
|
||||
<p align="center"><strong>All-in-one web3 SDK for Browser, Node and Mobile apps</strong></p>
|
||||
|
||||
> [!IMPORTANT]
|
||||
> We rewrote the thirdweb SDK from scratch for performance! Head over to the [v5 SDK](https://github.com/thirdweb-dev/js/tree/main/packages/thirdweb).
|
||||
|
||||
## Features
|
||||
|
||||
- Support for React & React-Native
|
||||
- First party support for [Embedded Wallets](https://portal.thirdweb.com/connect/embedded-wallet/overview) (social/email login)
|
||||
- First party support for [Account Abstraction](https://portal.thirdweb.com/connect/account-abstraction)
|
||||
- Instant connection to any chain with RPC Edge integration
|
||||
- Integrated IPFS upload/download
|
||||
- UI Components for connection, transactions, nft rendering
|
||||
- High level contract extensions for interacting with common standards
|
||||
- Automatic ABI resolution
|
||||
|
||||
## Library Comparison
|
||||
|
||||
| | thirdweb | Wagmi | Viem | Ethers@6 |
|
||||
| ----------------------------------------- | -------- | ------------------ | ------------------ | -------- |
|
||||
| Type safe contract API | ✅ | ✅ | ✅ | ✅ |
|
||||
| Type safe wallet API | ✅ | ✅ | ✅ | ✅ |
|
||||
| EVM utils | ✅ | ❌ | ✅ | ✅ |
|
||||
| RPC for any EVM | ✅ | ⚠️ public RPC only | ⚠️ public RPC only | ❌ |
|
||||
| Automatic ABI Resolution | ✅ | ❌ | ❌ | ❌ |
|
||||
| IPFS Upload/Download | ✅ | ❌ | ❌ | ❌ |
|
||||
| Embedded wallet (email/ social login) | ✅ | ⚠️ via 3rd party | ❌ | ❌ |
|
||||
| Account abstraction (ERC4337) support | ✅ | ⚠️ via 3rd party | ⚠️ via 3rd party | ❌ |
|
||||
| Web3 wallet connectors | ✅ | ✅ | ❌ | ❌ |
|
||||
| Local wallet creation | ✅ | ❌ | ✅ | ✅ |
|
||||
| Auth (SIWE) | ✅ | ⚠️ via 3rd party | ❌ | ❌ |
|
||||
| Extensions functions for common standards | ✅ | ❌ | ❌ | ❌ |
|
||||
| React Hooks | ✅ | ✅ | ❌ | ❌ |
|
||||
| React UI components | ✅ | ❌ | ❌ | ❌ |
|
||||
| React Native Hooks | ✅ | ❌ | ❌ | ❌ |
|
||||
| React Native UI Components | ✅ | ❌ | ❌ | ❌ |
|
||||
|
||||
## Packages
|
||||
|
||||
| Package | Description | Latest Version |
|
||||
| ---------------------------------------- | -------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| [/sdk](./packages/sdk) | Best in class web3 SDK for Browser, Node and Mobile apps | <a href="https://www.npmjs.com/package/@thirdweb-dev/sdk"><img src="https://img.shields.io/npm/v/@thirdweb-dev/sdk?color=red&label=npm&logo=npm" alt="npm version"/></a> |
|
||||
| [/react](./packages/react) | Ultimate collection of React hooks for your web3 apps | <a href="https://www.npmjs.com/package/@thirdweb-dev/react"><img src="https://img.shields.io/npm/v/@thirdweb-dev/react?color=red&label=npm&logo=npm" alt="npm version"/></a> |
|
||||
| [/react-native](./packages/react-native) | Ultimate collection of React hooks for your native mobile web3 apps | <a href="https://www.npmjs.com/package/@thirdweb-dev/react-native"><img src="https://img.shields.io/npm/v/@thirdweb-dev/react-native?color=red&label=npm&logo=npm" alt="npm version"/></a> |
|
||||
| [/auth](./packages/auth) | Best in class wallet authentication for Node backends | <a href="https://www.npmjs.com/package/@thirdweb-dev/auth"><img src="https://img.shields.io/npm/v/@thirdweb-dev/auth?color=red&label=npm&logo=npm" alt="npm version"/></a> |
|
||||
| [/storage](./packages/storage) | Best in class decentralized storage SDK for Browser and Node | <a href="https://www.npmjs.com/package/@thirdweb-dev/storage"><img src="https://img.shields.io/npm/v/@thirdweb-dev/storage?color=red&label=npm&logo=npm" alt="npm version"/></a> |
|
||||
| [/cli](./packages/cli) | Publish and deploy smart contracts without dealing with private keys | <a href="https://www.npmjs.com/package/thirdweb"><img src="https://img.shields.io/npm/v/thirdweb?color=red&label=npm&logo=npm" alt="npm version"/></a> |
|
||||
|
||||
## Documentation
|
||||
|
||||
Visit [https://portal.thirdweb.com/](https://portal.thirdweb.com/) to view the full documentation.
|
||||
|
||||
<br />
|
||||
| Package | Description |
|
||||
| ----------------------------------------------- | --------------------------------------------------------------------------- |
|
||||
| [/sdk](./legacy_packages/sdk) | Best in class web3 SDK for Browser, Node and Mobile apps |
|
||||
| [/wallets](./legacy_packages/wallets) | Unified web3 Wallet library to integrate any wallet into your applications. |
|
||||
| [/react](./legacy_packages/react) | Ultimate collection of React hooks for your web3 apps |
|
||||
| [/react-native](./legacy_packages/react-native) | Ultimate collection of React hooks for your native mobile web3 apps |
|
||||
| [/auth](./legacy_packages/auth) | Best in class wallet authentication for Node backends |
|
||||
| [/storage](./legacy_packages/storage) | Best in class decentralized storage SDK for Browser and Node |
|
||||
| [/cli](./legacy_packages/cli) | Publish and deploy smart contracts without dealing with private keys |
|
||||
| [/chains](./legacy_packages/chains) | All EVM chain information as JS objects for easy handling |
|
||||
|
||||
## Contributing
|
||||
|
||||
We welcome contributions from all developers, regardless of experience level. If you are interested in contributing, please read our [Contributing Guide](.github/contributing.md) where you'll learn how the repo works, how to test your changes, and how to submit a pull request.
|
||||
We welcome contributions from all developers regardless of experience level. If you are interested in contributing, please read our [Contributing Guide](.github/contributing.md) to learn how the repo works, how to test your changes, and how to submit a pull request.
|
||||
|
||||
<br />
|
||||
See our [open source page](https://thirdweb.com/open-source) for more information on our open-source bounties and program.
|
||||
|
||||
## Community
|
||||
## Additional Resources
|
||||
|
||||
The best place to discuss your ideas, ask questions, and troubleshoot issues is our [Discord server](https://discord.gg/thirdweb).
|
||||
- [SDK Documentation](https://portal.thirdweb.com/typescript/v5)
|
||||
- [Templates](https://thirdweb.com/templates)
|
||||
- [YouTube](https://www.youtube.com/c/thirdweb_)
|
||||
|
||||
<br/>
|
||||
## Support
|
||||
|
||||
For help or feedback, please [visit our support site](https://thirdweb.com/support)
|
||||
|
||||
## Security
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,38 @@
|
||||
<p align="center">
|
||||
<br />
|
||||
<a href="https://thirdweb.com"><img src="https://github.com/thirdweb-dev/js/blob/main/legacy_packages/sdk/logo.svg?raw=true" width="200" alt=""/></a>
|
||||
<br />
|
||||
</p>
|
||||
<h1 align="center">thirdweb Auth</h1>
|
||||
<p align="center">
|
||||
<a href="https://www.npmjs.com/package/@thirdweb-dev/auth"><img src="https://img.shields.io/npm/v/@thirdweb-dev/auth?color=red&label=npm&logo=npm" alt="npm version"/></a>
|
||||
<a href="https://github.com/thirdweb-dev/js/actions/workflows/build-test-lint.yml"><img alt="Build Status" src="https://github.com/thirdweb-dev/js/actions/workflows/build-test-lint.yml/badge.svg"/></a>
|
||||
<a href="https://discord.gg/thirdweb"><img alt="Join our Discord!" src="https://img.shields.io/discord/834227967404146718.svg?color=7289da&label=discord&logo=discord&style=flat"/></a>
|
||||
|
||||
</p>
|
||||
<p align="center"><strong>Best in class wallet authentication for Node backends</strong></p>
|
||||
<br />
|
||||
|
||||
## Overview
|
||||
|
||||
Auth is a developer SDK that lets you integrate passwordless web3-native authentication and authorization into your applications.
|
||||
|
||||
It enables users to sign in with their wallets, allowing apps to access rich blockchain-level data and interact with their users on-chain.
|
||||
|
||||
With Auth, developers can build applications that have secure, self-custodied, web3-first authentication.
|
||||
|
||||
## Installation
|
||||
|
||||
Install the latest version of the SDK with either `npm` or `yarn`:
|
||||
|
||||
```shell
|
||||
npm install @thirdweb-dev/auth
|
||||
```
|
||||
|
||||
```shell
|
||||
yarn add @thirdweb-dev/auth
|
||||
```
|
||||
|
||||
## Resources
|
||||
|
||||
- Check out the [Auth Documentation](https://portal.thirdweb.com/auth) for full guides and API references on how to use Auth.
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"main": "dist/thirdweb-dev-auth-fastify.cjs.js",
|
||||
"module": "dist/thirdweb-dev-auth-fastify.esm.js",
|
||||
"browser": {
|
||||
"./dist/thirdweb-dev-auth-fastify.esm.js": "./dist/thirdweb-dev-auth-fastify.browser.esm.js"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
{
|
||||
"name": "@thirdweb-dev/auth",
|
||||
"version": "4.1.51",
|
||||
"main": "dist/thirdweb-dev-auth.cjs.js",
|
||||
"module": "dist/thirdweb-dev-auth.esm.js",
|
||||
"browser": {
|
||||
"./dist/thirdweb-dev-auth.esm.js": "./dist/thirdweb-dev-auth.browser.esm.js"
|
||||
},
|
||||
"exports": {
|
||||
".": {
|
||||
"module": {
|
||||
"browser": "./dist/thirdweb-dev-auth.browser.esm.js",
|
||||
"default": "./dist/thirdweb-dev-auth.esm.js"
|
||||
},
|
||||
"default": "./dist/thirdweb-dev-auth.cjs.js"
|
||||
},
|
||||
"./evm": {
|
||||
"module": {
|
||||
"browser": "./evm/dist/thirdweb-dev-auth-evm.browser.esm.js",
|
||||
"default": "./evm/dist/thirdweb-dev-auth-evm.esm.js"
|
||||
},
|
||||
"default": "./evm/dist/thirdweb-dev-auth-evm.cjs.js"
|
||||
},
|
||||
"./next": {
|
||||
"module": {
|
||||
"browser": "./next/dist/thirdweb-dev-auth-next.browser.esm.js",
|
||||
"default": "./next/dist/thirdweb-dev-auth-next.esm.js"
|
||||
},
|
||||
"default": "./next/dist/thirdweb-dev-auth-next.cjs.js"
|
||||
},
|
||||
"./express": {
|
||||
"module": {
|
||||
"browser": "./express/dist/thirdweb-dev-auth-express.browser.esm.js",
|
||||
"default": "./express/dist/thirdweb-dev-auth-express.esm.js"
|
||||
},
|
||||
"default": "./express/dist/thirdweb-dev-auth-express.cjs.js"
|
||||
},
|
||||
"./fastify": {
|
||||
"module": {
|
||||
"browser": "./fastify/dist/thirdweb-dev-auth-fastify.browser.esm.js",
|
||||
"default": "./fastify/dist/thirdweb-dev-auth-fastify.esm.js"
|
||||
},
|
||||
"default": "./fastify/dist/thirdweb-dev-auth-fastify.cjs.js"
|
||||
},
|
||||
"./next-auth": {
|
||||
"module": {
|
||||
"browser": "./next-auth/dist/thirdweb-dev-auth-next-auth.browser.esm.js",
|
||||
"default": "./next-auth/dist/thirdweb-dev-auth-next-auth.esm.js"
|
||||
},
|
||||
"default": "./next-auth/dist/thirdweb-dev-auth-next-auth.cjs.js"
|
||||
},
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"repository": "https://github.com/thirdweb-dev/js/tree/main/legacy_packages/auth",
|
||||
"author": "thirdweb eng <[email protected]>",
|
||||
"license": "Apache-2.0",
|
||||
"sideEffects": false,
|
||||
"scripts": {
|
||||
"build": "preconstruct build",
|
||||
"format": "prettier --write 'src/**/*'",
|
||||
"lint": "eslint src/ && bunx publint --strict --level warning",
|
||||
"fix": "eslint src/ --fix",
|
||||
"clean": "rm -rf dist/ && rm -rf node_modules/",
|
||||
"test": "vitest",
|
||||
"push": "yalc push"
|
||||
},
|
||||
"preconstruct": {
|
||||
"entrypoints": [
|
||||
"index.ts",
|
||||
"express/index.ts",
|
||||
"next/index.ts",
|
||||
"next-auth/index.ts",
|
||||
"fastify/index.ts",
|
||||
"evm/index.ts"
|
||||
],
|
||||
"exports": {
|
||||
"envConditions": [
|
||||
"browser"
|
||||
]
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"@microsoft/api-documenter": "^7.24.1",
|
||||
"@microsoft/api-extractor": "^7.43.0",
|
||||
"@microsoft/tsdoc": "^0.14.2",
|
||||
"@preconstruct/cli": "2.7.0",
|
||||
"@swc-node/register": "^1.9.0",
|
||||
"@swc/core": "^1.4.11",
|
||||
"@thirdweb-dev/tsconfig": "workspace:*",
|
||||
"@types/cookie": "^0.6.0",
|
||||
"@types/cookie-parser": "^1.4.7",
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/uuid": "^9.0.8",
|
||||
"cookie-parser": "^1.4.6",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-thirdweb": "workspace:*",
|
||||
"eslint-plugin-import": "^2.29.1",
|
||||
"eslint-plugin-inclusive-language": "^2.2.1",
|
||||
"eslint-plugin-prettier": "^5.1.3",
|
||||
"eslint-plugin-tsdoc": "^0.2.17",
|
||||
"ethers": "^5.7.2",
|
||||
"express": "^4.19.2",
|
||||
"fastify": "^4.26.2",
|
||||
"next": "^13.5.6",
|
||||
"next-auth": "^4.24.7",
|
||||
"typescript": "5.4.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"cookie-parser": "^1.4.6",
|
||||
"ethers": "^5",
|
||||
"express": "^4",
|
||||
"fastify": "^4.25.2",
|
||||
"next": "^13.4 || ^14",
|
||||
"next-auth": "^4"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"cookie-parser": {
|
||||
"optional": true
|
||||
},
|
||||
"ethers": {
|
||||
"optional": true
|
||||
},
|
||||
"express": {
|
||||
"optional": true
|
||||
},
|
||||
"fastify": {
|
||||
"optional": true
|
||||
},
|
||||
"next": {
|
||||
"optional": true
|
||||
},
|
||||
"next-auth": {
|
||||
"optional": true
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@fastify/cookie": "^9.3.1",
|
||||
"@thirdweb-dev/wallets": "workspace:*",
|
||||
"cookie": "^0.6.0",
|
||||
"fastify-type-provider-zod": "^1.1.9",
|
||||
"uuid": "^9.0.1",
|
||||
"zod": "^3.22.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
import {
|
||||
authenticateJWT,
|
||||
generateJWT,
|
||||
parseJWT,
|
||||
refreshJWT,
|
||||
} from "./functions/jwt";
|
||||
import {
|
||||
buildAndSignLoginPayload,
|
||||
buildLoginPayload,
|
||||
signLoginPayload,
|
||||
verifyLoginPayload,
|
||||
} from "./functions/login";
|
||||
import type { GenericAuthWallet } from "@thirdweb-dev/wallets";
|
||||
import { Json, User } from "./schema/common";
|
||||
import {
|
||||
LoginOptionsWithOptionalDomain,
|
||||
LoginPayload,
|
||||
LoginPayloadData,
|
||||
} from "./schema/login";
|
||||
import { VerifyOptionsWithOptionalDomain } from "./schema/verify";
|
||||
import { GenerateOptionsWithOptionalDomain } from "./schema/generate";
|
||||
import {
|
||||
AuthenticateOptionsWithOptionalDomain,
|
||||
AuthenticationPayload,
|
||||
} from "./schema/authenticate";
|
||||
import { ThirdwebAuthOptions } from "./types";
|
||||
|
||||
export class ThirdwebAuth {
|
||||
private domain: string;
|
||||
private wallet: GenericAuthWallet;
|
||||
private options: ThirdwebAuthOptions;
|
||||
|
||||
constructor(
|
||||
wallet: GenericAuthWallet,
|
||||
domain: string,
|
||||
options: ThirdwebAuthOptions = {},
|
||||
) {
|
||||
this.wallet = wallet;
|
||||
this.domain = domain;
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
public updateWallet(wallet: GenericAuthWallet) {
|
||||
this.wallet = wallet;
|
||||
}
|
||||
|
||||
public async payload(
|
||||
options?: LoginOptionsWithOptionalDomain,
|
||||
): Promise<LoginPayloadData> {
|
||||
return buildLoginPayload({
|
||||
wallet: this.wallet,
|
||||
options: this.formatOptions(options),
|
||||
});
|
||||
}
|
||||
|
||||
public async loginWithPayload(
|
||||
payload: LoginPayloadData,
|
||||
): Promise<LoginPayload> {
|
||||
return signLoginPayload({ wallet: this.wallet, payload });
|
||||
}
|
||||
|
||||
public async login(
|
||||
options?: LoginOptionsWithOptionalDomain,
|
||||
): Promise<LoginPayload> {
|
||||
return buildAndSignLoginPayload({
|
||||
wallet: this.wallet,
|
||||
options: this.formatOptions(options),
|
||||
});
|
||||
}
|
||||
|
||||
public async verify(
|
||||
payload: LoginPayload,
|
||||
options?: VerifyOptionsWithOptionalDomain,
|
||||
): Promise<string> {
|
||||
return verifyLoginPayload({
|
||||
payload,
|
||||
options: this.formatOptions(options),
|
||||
clientOptions: this.options,
|
||||
});
|
||||
}
|
||||
|
||||
public async generate(
|
||||
payload: LoginPayload,
|
||||
options?: GenerateOptionsWithOptionalDomain,
|
||||
): Promise<string> {
|
||||
return generateJWT({
|
||||
wallet: this.wallet,
|
||||
payload,
|
||||
options: this.formatOptions(options),
|
||||
clientOptions: this.options,
|
||||
});
|
||||
}
|
||||
|
||||
public async refresh(jwt: string, expirationTime?: Date): Promise<string> {
|
||||
return refreshJWT({
|
||||
wallet: this.wallet,
|
||||
jwt,
|
||||
options: { expirationTime },
|
||||
});
|
||||
}
|
||||
|
||||
public async authenticate<TSession extends Json = Json>(
|
||||
jwt: string,
|
||||
options?: AuthenticateOptionsWithOptionalDomain,
|
||||
): Promise<User<TSession>> {
|
||||
return authenticateJWT({
|
||||
wallet: this.wallet,
|
||||
jwt,
|
||||
options: this.formatOptions(options),
|
||||
clientOptions: this.options,
|
||||
});
|
||||
}
|
||||
|
||||
public parseToken(jwt: string): AuthenticationPayload {
|
||||
return parseJWT(jwt);
|
||||
}
|
||||
|
||||
private formatOptions<TOptions extends { domain?: string }>(
|
||||
options?: TOptions,
|
||||
): Omit<TOptions, "domain"> & { domain: string } {
|
||||
return options
|
||||
? { ...options, domain: options?.domain || this.domain }
|
||||
: ({ domain: this.domain } as Omit<TOptions, "domain"> & {
|
||||
domain: string;
|
||||
});
|
||||
}
|
||||
}
|
||||
+11
-3
@@ -14,6 +14,7 @@ import {
|
||||
import { GenerateOptionsSchema } from "../schema/generate";
|
||||
import { RefreshOptionsSchema } from "../schema/refresh";
|
||||
import { verifyLoginPayload } from "./login";
|
||||
import { verifySignature } from "./verify-signature";
|
||||
|
||||
function isBrowser() {
|
||||
return typeof window !== "undefined";
|
||||
@@ -38,7 +39,10 @@ function base64decode(data: string): string {
|
||||
/**
|
||||
* Build JWT token based on the authentication payload
|
||||
*/
|
||||
async function buildJWT({ wallet, payload }: BuildJwtParams): Promise<string> {
|
||||
export async function buildJWT({
|
||||
wallet,
|
||||
payload,
|
||||
}: BuildJwtParams): Promise<string> {
|
||||
const payloadData = AuthenticationPayloadDataSchema.parse(payload);
|
||||
|
||||
const message = JSON.stringify(payloadData);
|
||||
@@ -68,15 +72,16 @@ export async function generateJWT({
|
||||
wallet,
|
||||
payload,
|
||||
options,
|
||||
clientOptions,
|
||||
}: GenerateJwtParams): Promise<string> {
|
||||
const parsedOptions = GenerateOptionsSchema.parse(options);
|
||||
const userAddress = await verifyLoginPayload({
|
||||
wallet,
|
||||
payload,
|
||||
options: {
|
||||
domain: parsedOptions.domain,
|
||||
...parsedOptions.verifyOptions,
|
||||
},
|
||||
clientOptions,
|
||||
});
|
||||
|
||||
let session: Json | undefined = undefined;
|
||||
@@ -153,6 +158,7 @@ export async function authenticateJWT<TSession extends Json = Json>({
|
||||
wallet,
|
||||
jwt,
|
||||
options,
|
||||
clientOptions,
|
||||
}: AuthenticateJwtParams): Promise<User<TSession>> {
|
||||
const parsedOptions = AuthenticateOptionsSchema.parse(options);
|
||||
const { payload, signature } = parseJWT(jwt);
|
||||
@@ -207,11 +213,13 @@ export async function authenticateJWT<TSession extends Json = Json>({
|
||||
}
|
||||
}
|
||||
|
||||
const verified = await wallet.verifySignature(
|
||||
const verified = await verifySignature(
|
||||
JSON.stringify(payload),
|
||||
signature,
|
||||
issuerAddress,
|
||||
chainId,
|
||||
clientOptions.clientId,
|
||||
clientOptions.secretKey,
|
||||
);
|
||||
if (!verified) {
|
||||
throw new Error(
|
||||
@@ -0,0 +1,222 @@
|
||||
import {
|
||||
BuildLoginPayloadParams,
|
||||
SignLoginPayloadParams,
|
||||
VerifyLoginPayloadParams,
|
||||
} from "../schema/functions";
|
||||
import {
|
||||
LoginOptionsSchema,
|
||||
LoginPayload,
|
||||
LoginPayloadData,
|
||||
LoginPayloadDataSchema,
|
||||
} from "../schema/login";
|
||||
import { VerifyOptionsSchema } from "../schema/verify";
|
||||
import { verifySignature } from "./verify-signature";
|
||||
|
||||
/**
|
||||
* Create an EIP-4361 & CAIP-122 compliant message to sign based on the login payload
|
||||
*/
|
||||
function createLoginMessage(payload: LoginPayloadData): string {
|
||||
const typeField = "Ethereum";
|
||||
const header = `${payload.domain} wants you to sign in with your ${typeField} account:`;
|
||||
let prefix = [header, payload.address].join("\n");
|
||||
prefix = [prefix, payload.statement].join("\n\n");
|
||||
if (payload.statement) {
|
||||
prefix += "\n";
|
||||
}
|
||||
|
||||
const suffixArray = [];
|
||||
if (payload.uri) {
|
||||
const uriField = `URI: ${payload.uri}`;
|
||||
suffixArray.push(uriField);
|
||||
}
|
||||
|
||||
const versionField = `Version: ${payload.version}`;
|
||||
suffixArray.push(versionField);
|
||||
|
||||
if (payload.chain_id) {
|
||||
const chainField = `Chain ID: ` + payload.chain_id || "1";
|
||||
suffixArray.push(chainField);
|
||||
}
|
||||
|
||||
const nonceField = `Nonce: ${payload.nonce}`;
|
||||
suffixArray.push(nonceField);
|
||||
|
||||
const issuedAtField = `Issued At: ${payload.issued_at}`;
|
||||
suffixArray.push(issuedAtField);
|
||||
|
||||
const expiryField = `Expiration Time: ${payload.expiration_time}`;
|
||||
suffixArray.push(expiryField);
|
||||
|
||||
if (payload.invalid_before) {
|
||||
const invalidBeforeField = `Not Before: ${payload.invalid_before}`;
|
||||
suffixArray.push(invalidBeforeField);
|
||||
}
|
||||
|
||||
if (payload.resources) {
|
||||
suffixArray.push(
|
||||
[`Resources:`, ...payload.resources.map((x) => `- ${x}`)].join("\n"),
|
||||
);
|
||||
}
|
||||
|
||||
const suffix = suffixArray.join("\n");
|
||||
return [prefix, suffix].join("\n");
|
||||
}
|
||||
|
||||
export async function buildLoginPayload({
|
||||
wallet,
|
||||
options,
|
||||
}: BuildLoginPayloadParams): Promise<LoginPayloadData> {
|
||||
const parsedOptions = LoginOptionsSchema.parse(options);
|
||||
|
||||
let chainId: string | undefined = parsedOptions?.chainId;
|
||||
if (!chainId && wallet.getChainId) {
|
||||
try {
|
||||
chainId = (await wallet.getChainId()).toString();
|
||||
} catch {
|
||||
// ignore error
|
||||
}
|
||||
}
|
||||
|
||||
return LoginPayloadDataSchema.parse({
|
||||
type: wallet.type,
|
||||
domain: parsedOptions.domain,
|
||||
address: parsedOptions?.address || (await wallet.getAddress()),
|
||||
statement: parsedOptions?.statement,
|
||||
version: parsedOptions?.version,
|
||||
uri: parsedOptions?.uri,
|
||||
chain_id: chainId,
|
||||
nonce: parsedOptions?.nonce,
|
||||
expiration_time: parsedOptions.expirationTime,
|
||||
invalid_before: parsedOptions.invalidBefore,
|
||||
resources: parsedOptions?.resources,
|
||||
});
|
||||
}
|
||||
|
||||
export async function signLoginPayload({
|
||||
wallet,
|
||||
payload,
|
||||
}: SignLoginPayloadParams): Promise<LoginPayload> {
|
||||
const message = createLoginMessage(payload);
|
||||
const signature = await wallet.signMessage(message);
|
||||
|
||||
return {
|
||||
payload,
|
||||
signature,
|
||||
};
|
||||
}
|
||||
|
||||
export async function buildAndSignLoginPayload({
|
||||
wallet,
|
||||
options,
|
||||
}: BuildLoginPayloadParams): Promise<LoginPayload> {
|
||||
const payload = await buildLoginPayload({ wallet, options });
|
||||
return signLoginPayload({ wallet, payload });
|
||||
}
|
||||
|
||||
export async function verifyLoginPayload({
|
||||
payload,
|
||||
options,
|
||||
clientOptions,
|
||||
}: VerifyLoginPayloadParams): Promise<string> {
|
||||
const parsedOptions = VerifyOptionsSchema.parse(options);
|
||||
|
||||
// Check that the intended domain matches the domain of the payload
|
||||
if (payload.payload.domain !== parsedOptions.domain) {
|
||||
throw new Error(
|
||||
`Expected domain '${parsedOptions.domain}' does not match domain on payload '${payload.payload.domain}'`,
|
||||
);
|
||||
}
|
||||
|
||||
// Check that the payload statement matches the expected statement
|
||||
if (parsedOptions?.statement) {
|
||||
if (payload.payload.statement !== parsedOptions.statement) {
|
||||
throw new Error(
|
||||
`Expected statement '${parsedOptions.statement}' does not match statement on payload '${payload.payload.statement}'`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Check that the intended URI matches the URI of the payload
|
||||
if (parsedOptions?.uri) {
|
||||
if (payload.payload.uri !== parsedOptions.uri) {
|
||||
throw new Error(
|
||||
`Expected URI '${parsedOptions.uri}' does not match URI on payload '${payload.payload.uri}'`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Check that the intended version matches the version of the payload
|
||||
if (parsedOptions?.version) {
|
||||
if (payload.payload.version !== parsedOptions.version) {
|
||||
throw new Error(
|
||||
`Expected version '${parsedOptions.version}' does not match version on payload '${payload.payload.version}'`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Check that the intended chain ID matches the chain ID of the payload
|
||||
if (parsedOptions?.chainId) {
|
||||
if (payload.payload.chain_id !== parsedOptions.chainId) {
|
||||
throw new Error(
|
||||
`Expected chain ID '${parsedOptions.chainId}' does not match chain ID on payload '${payload.payload.chain_id}'`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Check that the payload nonce is valid
|
||||
if (parsedOptions?.validateNonce !== undefined) {
|
||||
try {
|
||||
await parsedOptions.validateNonce(payload.payload.nonce);
|
||||
} catch (err) {
|
||||
throw new Error(`Login request nonce is invalid`);
|
||||
}
|
||||
}
|
||||
|
||||
// Check that it isn't before the invalid before time
|
||||
const currentTime = new Date();
|
||||
if (currentTime < new Date(payload.payload.invalid_before)) {
|
||||
throw new Error(`Login request is not yet valid`);
|
||||
}
|
||||
|
||||
// Check that the payload hasn't expired
|
||||
if (currentTime > new Date(payload.payload.expiration_time)) {
|
||||
throw new Error(`Login request has expired`);
|
||||
}
|
||||
|
||||
// Check that the specified resources are present on the payload
|
||||
if (parsedOptions?.resources) {
|
||||
const missingResources = parsedOptions.resources.filter(
|
||||
(resource) => !payload.payload.resources?.includes(resource),
|
||||
);
|
||||
if (missingResources.length > 0) {
|
||||
throw new Error(
|
||||
`Login request is missing required resources: ${missingResources.join(
|
||||
", ",
|
||||
)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Check that the signing address is the claimed wallet address
|
||||
const message = createLoginMessage(payload.payload);
|
||||
const chainId = payload.payload.chain_id
|
||||
? parseInt(payload.payload.chain_id)
|
||||
: undefined;
|
||||
|
||||
const verified = await verifySignature(
|
||||
message,
|
||||
payload.signature,
|
||||
payload.payload.address,
|
||||
chainId,
|
||||
clientOptions?.clientId,
|
||||
clientOptions?.secretKey,
|
||||
);
|
||||
|
||||
if (!verified) {
|
||||
throw new Error(
|
||||
`Signer address does not match payload address '${payload.payload.address.toLowerCase()}'`,
|
||||
);
|
||||
}
|
||||
|
||||
return payload.payload.address;
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import { checkContractWalletSignature } from "@thirdweb-dev/wallets";
|
||||
import { utils } from "ethers";
|
||||
|
||||
export async function verifySignature(
|
||||
message: string,
|
||||
signature: string,
|
||||
address: string,
|
||||
chainId?: number,
|
||||
clientId?: string,
|
||||
secretKey?: string,
|
||||
): Promise<boolean> {
|
||||
try {
|
||||
const messageHash = utils.hashMessage(message);
|
||||
const messageHashBytes = utils.arrayify(messageHash);
|
||||
const recoveredAddress = utils.recoverAddress(messageHashBytes, signature);
|
||||
|
||||
if (recoveredAddress === address) {
|
||||
return true;
|
||||
}
|
||||
} catch {
|
||||
// no-op
|
||||
}
|
||||
|
||||
if (!chainId) {
|
||||
return false;
|
||||
}
|
||||
return checkContractWalletSignature(
|
||||
message,
|
||||
signature,
|
||||
address,
|
||||
chainId,
|
||||
clientId,
|
||||
secretKey,
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
// Export thirdweb auth class
|
||||
export { ThirdwebAuth } from "./auth";
|
||||
|
||||
// Export individual auth functions
|
||||
export {
|
||||
buildJWT,
|
||||
generateJWT,
|
||||
parseJWT,
|
||||
refreshJWT,
|
||||
authenticateJWT,
|
||||
} from "./functions/jwt";
|
||||
export {
|
||||
buildLoginPayload,
|
||||
signLoginPayload,
|
||||
buildAndSignLoginPayload,
|
||||
verifyLoginPayload,
|
||||
} from "./functions/login";
|
||||
|
||||
// Export schema files individually
|
||||
export * from "./schema/authenticate";
|
||||
export * from "./schema/common";
|
||||
export * from "./schema/functions";
|
||||
export * from "./schema/generate";
|
||||
export * from "./schema/login";
|
||||
export * from "./schema/refresh";
|
||||
export * from "./schema/verify";
|
||||
+1
-1
@@ -9,7 +9,7 @@ export const AuthenticationPayloadDataSchema = z.object({
|
||||
exp: RawDateSchema,
|
||||
nbf: RawDateSchema,
|
||||
iat: RawDateSchema,
|
||||
jti: z.string().default(uuidv4()),
|
||||
jti: z.string().default(() => uuidv4()),
|
||||
ctx: JsonSchema.optional(),
|
||||
});
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import { utils } from "ethers";
|
||||
import { z } from "zod";
|
||||
|
||||
const literalSchema = z.union([z.string(), z.number(), z.boolean(), z.null()]);
|
||||
type Literal = z.infer<typeof literalSchema>;
|
||||
export type Json = Literal | { [key: string]: Json } | Json[];
|
||||
|
||||
export const JsonSchema: z.ZodType<Json> = z.lazy(
|
||||
() => z.union([literalSchema, z.array(JsonSchema), z.record(JsonSchema)]),
|
||||
{ invalid_type_error: "Provided value was not valid JSON" },
|
||||
);
|
||||
|
||||
export const AddressSchema = z.string().refine(
|
||||
(arg) => utils.isAddress(arg),
|
||||
(out) => {
|
||||
return {
|
||||
message: `${out} is not a valid address`,
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
export const RawDateSchema = z.date().transform((i) => {
|
||||
return Math.floor(i.getTime() / 1000);
|
||||
});
|
||||
|
||||
export const AccountTypeSchema = z.literal("evm");
|
||||
|
||||
export type User<TContext extends Json = Json> = {
|
||||
address: string;
|
||||
session?: TContext;
|
||||
};
|
||||
+4
-1
@@ -7,6 +7,7 @@ import {
|
||||
} from "./authenticate";
|
||||
import { RefreshOptions } from "./refresh";
|
||||
import { GenerateOptions } from "./generate";
|
||||
import { ThirdwebAuthOptions } from "../types";
|
||||
|
||||
export type BuildLoginPayloadParams = {
|
||||
wallet: GenericAuthWallet;
|
||||
@@ -19,9 +20,9 @@ export type SignLoginPayloadParams = {
|
||||
};
|
||||
|
||||
export type VerifyLoginPayloadParams = {
|
||||
wallet: GenericAuthWallet;
|
||||
payload: LoginPayload;
|
||||
options: VerifyOptions;
|
||||
clientOptions: ThirdwebAuthOptions;
|
||||
};
|
||||
|
||||
export type BuildJwtParams = {
|
||||
@@ -33,6 +34,7 @@ export type GenerateJwtParams = {
|
||||
wallet: GenericAuthWallet;
|
||||
payload: LoginPayload;
|
||||
options: GenerateOptions;
|
||||
clientOptions: ThirdwebAuthOptions;
|
||||
};
|
||||
|
||||
export type RefreshJwtParams = {
|
||||
@@ -45,4 +47,5 @@ export type AuthenticateJwtParams = {
|
||||
wallet: GenericAuthWallet;
|
||||
jwt: string;
|
||||
options: AuthenticateOptions;
|
||||
clientOptions: ThirdwebAuthOptions;
|
||||
};
|
||||
@@ -0,0 +1,81 @@
|
||||
import { z } from "zod";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import { AccountTypeSchema } from "./common";
|
||||
import { THIRDWEB_AUTH_DEFAULT_LOGIN_PAYLOAD_DURATION_IN_SECONDS } from "../../constants";
|
||||
|
||||
export const LoginOptionsSchema = z.object({
|
||||
domain: z.string(),
|
||||
address: z.string().optional(),
|
||||
statement: z.string().optional(),
|
||||
uri: z.string().optional(),
|
||||
version: z.string().optional(),
|
||||
chainId: z.string().optional(),
|
||||
nonce: z.string().optional(),
|
||||
expirationTime: z
|
||||
.date()
|
||||
.default(
|
||||
() =>
|
||||
new Date(
|
||||
Date.now() +
|
||||
1000 * THIRDWEB_AUTH_DEFAULT_LOGIN_PAYLOAD_DURATION_IN_SECONDS,
|
||||
),
|
||||
),
|
||||
invalidBefore: z
|
||||
.date()
|
||||
.default(
|
||||
() =>
|
||||
new Date(
|
||||
Date.now() -
|
||||
1000 * THIRDWEB_AUTH_DEFAULT_LOGIN_PAYLOAD_DURATION_IN_SECONDS,
|
||||
),
|
||||
),
|
||||
resources: z.array(z.string()).optional(),
|
||||
});
|
||||
|
||||
export const LoginPayloadDataSchema = z.object({
|
||||
type: AccountTypeSchema,
|
||||
domain: z.string(),
|
||||
address: z.string(),
|
||||
statement: z
|
||||
.string()
|
||||
.default(
|
||||
"Please ensure that the domain above matches the URL of the current website.",
|
||||
),
|
||||
uri: z.string().optional(),
|
||||
version: z.string().default("1"),
|
||||
chain_id: z.string().optional(),
|
||||
nonce: z.string().default(() => uuidv4()),
|
||||
issued_at: z
|
||||
.date()
|
||||
.default(new Date())
|
||||
.transform((d) => d.toISOString()),
|
||||
expiration_time: z.date().transform((d) => d.toISOString()),
|
||||
invalid_before: z
|
||||
.date()
|
||||
.default(new Date())
|
||||
.transform((d) => d.toISOString()),
|
||||
resources: z.array(z.string()).optional(),
|
||||
});
|
||||
|
||||
export const LoginPayloadSchema = z.object({
|
||||
payload: LoginPayloadDataSchema,
|
||||
signature: z.string(),
|
||||
});
|
||||
|
||||
export const LoginPayloadOutputSchema = LoginPayloadSchema.extend({
|
||||
payload: LoginPayloadDataSchema.extend({
|
||||
issued_at: z.string(),
|
||||
expiration_time: z.string(),
|
||||
invalid_before: z.string(),
|
||||
}),
|
||||
});
|
||||
|
||||
export type LoginOptions = z.input<typeof LoginOptionsSchema>;
|
||||
|
||||
export type LoginPayloadData = z.output<typeof LoginPayloadDataSchema>;
|
||||
|
||||
export type LoginPayload = z.output<typeof LoginPayloadSchema>;
|
||||
|
||||
export type LoginOptionsWithOptionalDomain = Omit<LoginOptions, "domain"> & {
|
||||
domain?: string;
|
||||
};
|
||||
@@ -0,0 +1,4 @@
|
||||
export type ThirdwebAuthOptions = {
|
||||
clientId?: string;
|
||||
secretKey?: string;
|
||||
};
|
||||
@@ -0,0 +1,92 @@
|
||||
import {
|
||||
THIRDWEB_AUTH_ACTIVE_ACCOUNT_COOKIE,
|
||||
THIRDWEB_AUTH_TOKEN_COOKIE_PREFIX,
|
||||
} from "../../constants";
|
||||
import { Json } from "../../core";
|
||||
import { ThirdwebAuthContext, ThirdwebAuthUser } from "../types";
|
||||
import { Request } from "express";
|
||||
|
||||
export function getCookie(req: Request, cookie: string): string | undefined {
|
||||
if (typeof req.cookies.get === "function") {
|
||||
return req.cookies.get(cookie);
|
||||
}
|
||||
|
||||
return req.cookies[cookie];
|
||||
}
|
||||
|
||||
export function getActiveCookie(req: Request): string | undefined {
|
||||
if (!req.cookies) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const activeAccount = getCookie(req, THIRDWEB_AUTH_ACTIVE_ACCOUNT_COOKIE);
|
||||
if (activeAccount) {
|
||||
return `${THIRDWEB_AUTH_TOKEN_COOKIE_PREFIX}_${activeAccount}`;
|
||||
}
|
||||
|
||||
// If active account is not present, then use the old default
|
||||
return THIRDWEB_AUTH_TOKEN_COOKIE_PREFIX;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @param req
|
||||
* @returns
|
||||
*/
|
||||
export function getToken(req: Request): string | undefined {
|
||||
if (req.headers["authorization"]) {
|
||||
const authorizationHeader = req.headers["authorization"].split(" ");
|
||||
if (authorizationHeader?.length === 2) {
|
||||
return authorizationHeader[1];
|
||||
}
|
||||
}
|
||||
|
||||
if (!req.cookies) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const activeCookie = getActiveCookie(req);
|
||||
if (!activeCookie) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return getCookie(req, activeCookie);
|
||||
}
|
||||
|
||||
export async function getUser<
|
||||
TData extends Json = Json,
|
||||
TSession extends Json = Json,
|
||||
>(
|
||||
req: Request,
|
||||
ctx: ThirdwebAuthContext<TData, TSession>,
|
||||
): Promise<ThirdwebAuthUser<TData, TSession> | null> {
|
||||
const token = getToken(req);
|
||||
|
||||
if (!token) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let authenticatedUser: ThirdwebAuthUser<TData, TSession>;
|
||||
try {
|
||||
authenticatedUser = await ctx.auth.authenticate<TSession>(token, {
|
||||
validateTokenId: async (tokenId: string) => {
|
||||
if (ctx.authOptions?.validateTokenId) {
|
||||
await ctx.authOptions?.validateTokenId(tokenId);
|
||||
}
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!ctx.callbacks?.onUser) {
|
||||
return authenticatedUser;
|
||||
}
|
||||
|
||||
const data = await ctx.callbacks.onUser(authenticatedUser, req);
|
||||
if (!data) {
|
||||
return authenticatedUser;
|
||||
}
|
||||
|
||||
return { ...authenticatedUser, data: data };
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
import { Json, ThirdwebAuth as ThirdwebAuthSDK } from "../core";
|
||||
import { getUser } from "./helpers/user";
|
||||
import payloadHandler from "./routes/payload";
|
||||
import loginHandler from "./routes/login";
|
||||
import logoutHandler from "./routes/logout";
|
||||
import userHandler from "./routes/user";
|
||||
import switchAccountHandler from "./routes/switch-account";
|
||||
import {
|
||||
ThirdwebAuthConfig,
|
||||
ThirdwebAuthContext,
|
||||
ThirdwebAuthUser,
|
||||
} from "./types";
|
||||
import cookieParser from "cookie-parser";
|
||||
import express, { Request, Response } from "express";
|
||||
|
||||
export * from "./types";
|
||||
export { getToken } from "./helpers/user";
|
||||
|
||||
const asyncHandler =
|
||||
(fn: CallableFunction) =>
|
||||
(...args: any[]) => {
|
||||
const fnReturn = fn(...args);
|
||||
const next = args[args.length - 1];
|
||||
return Promise.resolve(fnReturn).catch(next);
|
||||
};
|
||||
|
||||
type ThirdwebAuthReturnType<
|
||||
TData extends Json = Json,
|
||||
TSession extends Json = Json,
|
||||
> = {
|
||||
authRouter: express.Router;
|
||||
authMiddleware: express.RequestHandler;
|
||||
getUser: (req: Request) => Promise<ThirdwebAuthUser<TData, TSession> | null>;
|
||||
};
|
||||
|
||||
export function ThirdwebAuth<
|
||||
TData extends Json = Json,
|
||||
TSession extends Json = Json,
|
||||
>(
|
||||
cfg: ThirdwebAuthConfig<TData, TSession>,
|
||||
): ThirdwebAuthReturnType<TData, TSession> {
|
||||
const ctx = {
|
||||
...cfg,
|
||||
auth: new ThirdwebAuthSDK(cfg.wallet, cfg.domain, cfg.thirdwebAuthOptions),
|
||||
};
|
||||
|
||||
const router = express.Router();
|
||||
const cookieMiddleware = cookieParser();
|
||||
|
||||
router.use(express.json());
|
||||
router.use(cookieMiddleware);
|
||||
|
||||
router.post(
|
||||
"/payload",
|
||||
asyncHandler((req: Request, res: Response) =>
|
||||
payloadHandler(req, res, ctx as ThirdwebAuthContext),
|
||||
),
|
||||
);
|
||||
|
||||
router.post(
|
||||
"/login",
|
||||
asyncHandler((req: Request, res: Response) =>
|
||||
loginHandler(req, res, ctx as ThirdwebAuthContext),
|
||||
),
|
||||
);
|
||||
|
||||
router.get(
|
||||
"/user",
|
||||
asyncHandler((req: Request, res: Response) =>
|
||||
userHandler(req, res, ctx as ThirdwebAuthContext),
|
||||
),
|
||||
);
|
||||
|
||||
router.post(
|
||||
"/logout",
|
||||
asyncHandler((req: Request, res: Response) =>
|
||||
logoutHandler(req, res, ctx as ThirdwebAuthContext),
|
||||
),
|
||||
);
|
||||
|
||||
router.post(
|
||||
"/switch-account",
|
||||
asyncHandler((req: Request, res: Response) =>
|
||||
switchAccountHandler(req, res, ctx as ThirdwebAuthContext),
|
||||
),
|
||||
);
|
||||
|
||||
return {
|
||||
authRouter: router,
|
||||
authMiddleware: cookieMiddleware,
|
||||
getUser: (req: Request) => {
|
||||
return getUser<TData, TSession>(req, ctx);
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
import {
|
||||
THIRDWEB_AUTH_ACTIVE_ACCOUNT_COOKIE,
|
||||
THIRDWEB_AUTH_TOKEN_COOKIE_PREFIX,
|
||||
} from "../../constants";
|
||||
import { GenerateOptionsWithOptionalDomain } from "../../core";
|
||||
import { LoginPayloadBodySchema, ThirdwebAuthContext } from "../types";
|
||||
import { serialize } from "cookie";
|
||||
import { Request, Response } from "express";
|
||||
|
||||
export default async function handler(
|
||||
req: Request,
|
||||
res: Response,
|
||||
ctx: ThirdwebAuthContext,
|
||||
) {
|
||||
if (req.method !== "POST") {
|
||||
return res.status(405).json({ error: "Method not allowed" });
|
||||
}
|
||||
|
||||
const parsedPayload = LoginPayloadBodySchema.safeParse(req.body);
|
||||
|
||||
// Get signed login payload from the frontend
|
||||
if (!parsedPayload.success) {
|
||||
return res.status(400).json({ error: "Invalid login payload" });
|
||||
}
|
||||
|
||||
const payload = parsedPayload.data.payload;
|
||||
|
||||
const validateNonce = async (nonce: string) => {
|
||||
if (ctx.authOptions?.validateNonce) {
|
||||
await ctx.authOptions?.validateNonce(nonce);
|
||||
}
|
||||
};
|
||||
|
||||
const getSession = async (address: string) => {
|
||||
if (ctx.callbacks?.onLogin) {
|
||||
return ctx.callbacks.onLogin(address, req);
|
||||
}
|
||||
};
|
||||
|
||||
const expirationTime = ctx.authOptions?.tokenDurationInSeconds
|
||||
? new Date(Date.now() + 1000 * ctx.authOptions.tokenDurationInSeconds)
|
||||
: undefined;
|
||||
|
||||
const generateOptions: GenerateOptionsWithOptionalDomain = {
|
||||
verifyOptions: {
|
||||
statement: ctx.authOptions?.statement,
|
||||
uri: ctx.authOptions?.uri,
|
||||
version: ctx.authOptions?.version,
|
||||
chainId: ctx.authOptions?.chainId,
|
||||
validateNonce,
|
||||
resources: ctx.authOptions?.resources,
|
||||
},
|
||||
expirationTime,
|
||||
session: getSession,
|
||||
};
|
||||
|
||||
let token: string;
|
||||
try {
|
||||
// Generate an access token with the SDK using the signed payload
|
||||
token = await ctx.auth.generate(payload, generateOptions);
|
||||
} catch (err: any) {
|
||||
if (err.message) {
|
||||
return res.status(400).json({ error: err.message });
|
||||
} else if (typeof err === "string") {
|
||||
return res.status(400).json({ error: err });
|
||||
} else {
|
||||
return res.status(400).json({ error: "Invalid login payload" });
|
||||
}
|
||||
}
|
||||
|
||||
if (ctx.callbacks?.onToken) {
|
||||
await ctx.callbacks.onToken(token, req);
|
||||
}
|
||||
|
||||
const {
|
||||
payload: { exp },
|
||||
} = ctx.auth.parseToken(token);
|
||||
|
||||
// Securely set httpOnly cookie on request to prevent XSS on frontend
|
||||
// And set path to / to enable thirdweb_auth_token usage on all endpoints
|
||||
res.setHeader("Set-Cookie", [
|
||||
serialize(
|
||||
`${THIRDWEB_AUTH_TOKEN_COOKIE_PREFIX}_${payload.payload.address}`,
|
||||
token,
|
||||
{
|
||||
domain: ctx.cookieOptions?.domain,
|
||||
path: ctx.cookieOptions?.path || "/",
|
||||
sameSite: ctx.cookieOptions?.sameSite || "none",
|
||||
expires: new Date(exp * 1000),
|
||||
httpOnly: true,
|
||||
secure: ctx.cookieOptions?.secure || true,
|
||||
},
|
||||
),
|
||||
serialize(THIRDWEB_AUTH_ACTIVE_ACCOUNT_COOKIE, payload.payload.address, {
|
||||
domain: ctx.cookieOptions?.domain,
|
||||
path: ctx.cookieOptions?.path || "/",
|
||||
sameSite: ctx.cookieOptions?.sameSite || "none",
|
||||
expires: new Date(exp * 1000),
|
||||
httpOnly: true,
|
||||
secure: ctx.cookieOptions?.secure || true,
|
||||
}),
|
||||
]);
|
||||
|
||||
// Send token in body and as cookie for frontend and backend use cases
|
||||
return res.status(200).json({ token });
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import { getActiveCookie, getUser } from "../helpers/user";
|
||||
import { ThirdwebAuthContext } from "../types";
|
||||
import { serialize } from "cookie";
|
||||
import { Request, Response } from "express";
|
||||
|
||||
export default async function handler(
|
||||
req: Request,
|
||||
res: Response,
|
||||
ctx: ThirdwebAuthContext,
|
||||
) {
|
||||
if (req.method !== "POST") {
|
||||
return res.status(405).json({
|
||||
error: "Invalid method. Only POST supported.",
|
||||
});
|
||||
}
|
||||
|
||||
const activeCookie = getActiveCookie(req);
|
||||
if (!activeCookie) {
|
||||
return res.status(400).json({
|
||||
error: "No logged in user to logout.",
|
||||
});
|
||||
}
|
||||
|
||||
if (ctx.callbacks?.onLogout) {
|
||||
const user = await getUser(req, ctx);
|
||||
if (user) {
|
||||
await ctx.callbacks.onLogout(user, req);
|
||||
}
|
||||
}
|
||||
|
||||
// Set the access token to 'none' and expire in 5 seconds
|
||||
res.setHeader(
|
||||
"Set-Cookie",
|
||||
serialize(activeCookie, "", {
|
||||
domain: ctx.cookieOptions?.domain,
|
||||
path: ctx.cookieOptions?.path || "/",
|
||||
sameSite: ctx.cookieOptions?.sameSite || "none",
|
||||
expires: new Date(Date.now() + 5 * 1000),
|
||||
httpOnly: true,
|
||||
secure: ctx.cookieOptions?.secure || true,
|
||||
}),
|
||||
);
|
||||
|
||||
return res.status(200).json({ message: "Successfully logged out" });
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
import { Request, Response } from "express";
|
||||
import { ActiveBodySchema, ThirdwebAuthContext } from "../types";
|
||||
import { serialize } from "cookie";
|
||||
import {
|
||||
THIRDWEB_AUTH_ACTIVE_ACCOUNT_COOKIE,
|
||||
THIRDWEB_AUTH_DEFAULT_TOKEN_DURATION_IN_SECONDS,
|
||||
THIRDWEB_AUTH_TOKEN_COOKIE_PREFIX,
|
||||
} from "../../constants";
|
||||
import { getCookie } from "../helpers/user";
|
||||
|
||||
export default async function handler(
|
||||
req: Request,
|
||||
res: Response,
|
||||
ctx: ThirdwebAuthContext,
|
||||
) {
|
||||
if (req.method !== "POST") {
|
||||
return res.status(405).json({
|
||||
error: "Invalid method. Only POST supported.",
|
||||
});
|
||||
}
|
||||
|
||||
const parsedPayload = ActiveBodySchema.safeParse(req.body);
|
||||
if (!parsedPayload.success) {
|
||||
return res.status(400).json({ error: "Please provide an address" });
|
||||
}
|
||||
|
||||
let cookieExpiration: Date;
|
||||
const cookie = getCookie(
|
||||
req,
|
||||
`${THIRDWEB_AUTH_TOKEN_COOKIE_PREFIX}_${parsedPayload.data.address}`,
|
||||
);
|
||||
|
||||
if (cookie) {
|
||||
// If the new account is already logged in, get the expiration time from the cookie
|
||||
const {
|
||||
payload: { exp },
|
||||
} = ctx.auth.parseToken(cookie);
|
||||
cookieExpiration = new Date(exp * 1000);
|
||||
} else if (ctx.authOptions?.tokenDurationInSeconds) {
|
||||
// Otherwise, if we have a token duration in seconds, set it to that
|
||||
cookieExpiration = new Date(
|
||||
Date.now() + 1000 * ctx.authOptions.tokenDurationInSeconds,
|
||||
);
|
||||
} else {
|
||||
// Otherwise, just default to 24 hours
|
||||
cookieExpiration = new Date(
|
||||
Date.now() + 1000 * THIRDWEB_AUTH_DEFAULT_TOKEN_DURATION_IN_SECONDS,
|
||||
);
|
||||
}
|
||||
|
||||
res.setHeader("Set-Cookie", [
|
||||
serialize(THIRDWEB_AUTH_ACTIVE_ACCOUNT_COOKIE, parsedPayload.data.address, {
|
||||
domain: ctx.cookieOptions?.domain,
|
||||
path: ctx.cookieOptions?.path || "/",
|
||||
sameSite: ctx.cookieOptions?.sameSite || "none",
|
||||
expires: cookieExpiration,
|
||||
httpOnly: true,
|
||||
secure: ctx.cookieOptions?.secure || true,
|
||||
}),
|
||||
]);
|
||||
|
||||
return res.status(200).end();
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
import { serialize } from "cookie";
|
||||
import { getToken, getUser } from "../helpers/user";
|
||||
import { ThirdwebAuthContext } from "../types";
|
||||
import { Request, Response } from "express";
|
||||
import {
|
||||
THIRDWEB_AUTH_ACTIVE_ACCOUNT_COOKIE,
|
||||
THIRDWEB_AUTH_DEFAULT_REFRESH_INTERVAL_IN_SECONDS,
|
||||
THIRDWEB_AUTH_TOKEN_COOKIE_PREFIX,
|
||||
} from "../../constants";
|
||||
|
||||
export default async function handler(
|
||||
req: Request,
|
||||
res: Response,
|
||||
ctx: ThirdwebAuthContext,
|
||||
) {
|
||||
if (req.method !== "GET") {
|
||||
return res.status(400).json({
|
||||
error: "Invalid method. Only GET supported.",
|
||||
});
|
||||
}
|
||||
|
||||
const user = await getUser(req, ctx);
|
||||
|
||||
// Importantly, make sure the user was actually logged in before refreshing
|
||||
if (user) {
|
||||
const token = getToken(req);
|
||||
if (token) {
|
||||
const payload = ctx.auth.parseToken(token);
|
||||
|
||||
const refreshDate = ctx.authOptions?.refreshIntervalInSeconds
|
||||
? new Date(
|
||||
payload.payload.iat * 1000 +
|
||||
ctx.authOptions.refreshIntervalInSeconds * 1000,
|
||||
)
|
||||
: new Date(
|
||||
payload.payload.iat * 1000 +
|
||||
THIRDWEB_AUTH_DEFAULT_REFRESH_INTERVAL_IN_SECONDS * 1000,
|
||||
);
|
||||
|
||||
if (new Date() > refreshDate) {
|
||||
const expirationTime = ctx.authOptions?.tokenDurationInSeconds
|
||||
? new Date(Date.now() + 1000 * ctx.authOptions.tokenDurationInSeconds)
|
||||
: undefined;
|
||||
const refreshedToken = await ctx.auth.refresh(token, expirationTime);
|
||||
const refreshedPayload = ctx.auth.parseToken(refreshedToken);
|
||||
res.setHeader("Set-Cookie", [
|
||||
serialize(
|
||||
`${THIRDWEB_AUTH_TOKEN_COOKIE_PREFIX}_${user.address}`,
|
||||
refreshedToken,
|
||||
{
|
||||
domain: ctx.cookieOptions?.domain,
|
||||
path: ctx.cookieOptions?.path || "/",
|
||||
sameSite: ctx.cookieOptions?.sameSite || "none",
|
||||
expires: new Date(refreshedPayload.payload.exp * 1000),
|
||||
httpOnly: true,
|
||||
secure: ctx.cookieOptions?.secure || true,
|
||||
},
|
||||
),
|
||||
serialize(THIRDWEB_AUTH_ACTIVE_ACCOUNT_COOKIE, user.address, {
|
||||
domain: ctx.cookieOptions?.domain,
|
||||
path: ctx.cookieOptions?.path || "/",
|
||||
sameSite: ctx.cookieOptions?.sameSite || "none",
|
||||
expires: new Date(refreshedPayload.payload.exp * 1000),
|
||||
httpOnly: true,
|
||||
secure: ctx.cookieOptions?.secure || true,
|
||||
}),
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return res.status(200).json(user);
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
import { ThirdwebAuth } from "../../core";
|
||||
import { ThirdwebAuthOptions } from "../../core/types";
|
||||
import { Json, LoginPayloadOutputSchema, User } from "../../core";
|
||||
import type { GenericAuthWallet } from "@thirdweb-dev/wallets";
|
||||
import { Request } from "express";
|
||||
import { z } from "zod";
|
||||
|
||||
export const PayloadBodySchema = z.object({
|
||||
address: z.string(),
|
||||
chainId: z.string().optional(),
|
||||
});
|
||||
|
||||
export const ActiveBodySchema = z.object({
|
||||
address: z.string(),
|
||||
});
|
||||
|
||||
export const LoginPayloadBodySchema = z.object({
|
||||
payload: LoginPayloadOutputSchema,
|
||||
});
|
||||
|
||||
export type ThirdwebAuthRoute = "login" | "user" | "logout";
|
||||
|
||||
export type ThirdwebAuthUser<
|
||||
TData extends Json = Json,
|
||||
TSession extends Json = Json,
|
||||
> = User<TSession> & {
|
||||
data?: TData;
|
||||
};
|
||||
|
||||
export type ThirdwebAuthConfig<
|
||||
TData extends Json = Json,
|
||||
TSession extends Json = Json,
|
||||
> = {
|
||||
domain: string;
|
||||
wallet: GenericAuthWallet;
|
||||
authOptions?: {
|
||||
statement?: string;
|
||||
uri?: string;
|
||||
version?: string;
|
||||
chainId?: string;
|
||||
resources?: string[];
|
||||
validateNonce?:
|
||||
| ((nonce: string) => void)
|
||||
| ((nonce: string) => Promise<void>);
|
||||
validateTokenId?:
|
||||
| ((tokenId: string) => void)
|
||||
| ((tokenId: string) => Promise<void>);
|
||||
loginPayloadDurationInSeconds?: number;
|
||||
tokenDurationInSeconds?: number;
|
||||
refreshIntervalInSeconds?: number;
|
||||
};
|
||||
cookieOptions?: {
|
||||
domain?: string;
|
||||
path?: string;
|
||||
sameSite?: "lax" | "strict" | "none";
|
||||
secure?: boolean;
|
||||
};
|
||||
callbacks?: {
|
||||
onLogin?:
|
||||
| ((address: string, req: Request) => void | TSession)
|
||||
| ((address: string, req: Request) => Promise<void | TSession>);
|
||||
onToken?:
|
||||
| ((token: string, req: Request) => void)
|
||||
| ((token: string, req: Request) => Promise<void>);
|
||||
onUser?:
|
||||
| ((user: User<TSession>, req: Request) => void | TData)
|
||||
| ((user: User<TSession>, req: Request) => Promise<void | TData>);
|
||||
onLogout?:
|
||||
| ((user: User, req: Request) => void)
|
||||
| ((user: User, req: Request) => Promise<void>);
|
||||
};
|
||||
thirdwebAuthOptions?: ThirdwebAuthOptions;
|
||||
};
|
||||
|
||||
export type ThirdwebAuthContext<
|
||||
TData extends Json = Json,
|
||||
TSession extends Json = Json,
|
||||
> = Omit<Omit<ThirdwebAuthConfig<TData, TSession>, "wallet">, "domain"> & {
|
||||
auth: ThirdwebAuth;
|
||||
};
|
||||
@@ -0,0 +1,93 @@
|
||||
import {
|
||||
THIRDWEB_AUTH_ACTIVE_ACCOUNT_COOKIE,
|
||||
THIRDWEB_AUTH_TOKEN_COOKIE_PREFIX,
|
||||
} from "../../constants";
|
||||
import { Json } from "../../core";
|
||||
import { ThirdwebAuthContext, ThirdwebAuthUser } from "../types";
|
||||
import { FastifyRequest } from "fastify";
|
||||
|
||||
export function getCookie(
|
||||
req: FastifyRequest,
|
||||
cookie: string,
|
||||
): string | undefined {
|
||||
return req.cookies[cookie];
|
||||
}
|
||||
|
||||
export function getActiveCookie(req: FastifyRequest): string | undefined {
|
||||
if (!req.cookies) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const activeAccount = getCookie(req, THIRDWEB_AUTH_ACTIVE_ACCOUNT_COOKIE);
|
||||
if (activeAccount) {
|
||||
return `${THIRDWEB_AUTH_TOKEN_COOKIE_PREFIX}_${activeAccount}`;
|
||||
}
|
||||
|
||||
// If active account is not present, then use the old default
|
||||
return THIRDWEB_AUTH_TOKEN_COOKIE_PREFIX;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @param req
|
||||
* @returns
|
||||
*/
|
||||
export function getToken(req: FastifyRequest): string | undefined {
|
||||
if (req.headers.authorization) {
|
||||
const authorizationHeader = req.headers.authorization.split(" ");
|
||||
if (authorizationHeader?.length === 2) {
|
||||
return authorizationHeader[1];
|
||||
}
|
||||
}
|
||||
|
||||
if (!req.cookies) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const activeCookie = getActiveCookie(req);
|
||||
if (!activeCookie) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return getCookie(req, activeCookie);
|
||||
}
|
||||
|
||||
export async function getUser<
|
||||
TData extends Json = Json,
|
||||
TSession extends Json = Json,
|
||||
>(
|
||||
req: FastifyRequest,
|
||||
ctx: ThirdwebAuthContext<TData, TSession>,
|
||||
): Promise<ThirdwebAuthUser<TData, TSession> | null> {
|
||||
const token = getToken(req);
|
||||
|
||||
if (!token) {
|
||||
console.error("Error: No auth token found");
|
||||
return null;
|
||||
}
|
||||
|
||||
let authenticatedUser: ThirdwebAuthUser<TData, TSession>;
|
||||
try {
|
||||
authenticatedUser = await ctx.auth.authenticate<TSession>(token, {
|
||||
validateTokenId: async (tokenId: string) => {
|
||||
if (ctx.authOptions?.validateTokenId) {
|
||||
await ctx.authOptions?.validateTokenId(tokenId);
|
||||
}
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(`Authenticate Error: ${(err as Error)?.message}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!ctx.callbacks?.onUser) {
|
||||
return authenticatedUser;
|
||||
}
|
||||
|
||||
const data = await ctx.callbacks.onUser(authenticatedUser, req);
|
||||
if (!data) {
|
||||
return authenticatedUser;
|
||||
}
|
||||
|
||||
return { ...authenticatedUser, data: data };
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
import { FastifyInstance, FastifyPluginOptions, FastifyRequest } from "fastify";
|
||||
import { Json, ThirdwebAuth as ThirdwebAuthSDK } from "../core";
|
||||
import fastifyCookie, { FastifyCookie } from "@fastify/cookie";
|
||||
import { payloadHandler } from "./routes/payload";
|
||||
import { loginHandler } from "./routes/login";
|
||||
import { userHandler } from "./routes/user";
|
||||
import { logoutHandler } from "./routes/logout";
|
||||
import { switchAccountHandler } from "./routes/switch-account";
|
||||
import {
|
||||
ZodTypeProvider,
|
||||
serializerCompiler,
|
||||
validatorCompiler,
|
||||
} from "fastify-type-provider-zod";
|
||||
import { getUser } from "./helpers/user";
|
||||
import {
|
||||
ThirdwebAuthConfig,
|
||||
ThirdwebAuthContext,
|
||||
ThirdwebAuthUser,
|
||||
} from "./types";
|
||||
|
||||
export * from "./types";
|
||||
export { getToken } from "./helpers/user";
|
||||
|
||||
type ThirdwebAuthReturnType<
|
||||
TData extends Json = Json,
|
||||
TSession extends Json = Json,
|
||||
> = {
|
||||
authRouter: (
|
||||
fastify: FastifyInstance,
|
||||
opts: FastifyPluginOptions,
|
||||
done: (err?: Error | undefined) => void,
|
||||
) => void;
|
||||
authMiddleware: FastifyCookie;
|
||||
getUser: (
|
||||
req: FastifyRequest,
|
||||
) => Promise<ThirdwebAuthUser<TData, TSession> | null>;
|
||||
};
|
||||
|
||||
export function ThirdwebAuth<
|
||||
TData extends Json = Json,
|
||||
TSession extends Json = Json,
|
||||
>(
|
||||
cfg: ThirdwebAuthConfig<TData, TSession>,
|
||||
): ThirdwebAuthReturnType<TData, TSession> {
|
||||
const ctx = {
|
||||
...cfg,
|
||||
auth: new ThirdwebAuthSDK(cfg.wallet, cfg.domain, cfg.thirdwebAuthOptions),
|
||||
};
|
||||
|
||||
const authRouter = async (
|
||||
fastify: FastifyInstance,
|
||||
_opts: FastifyPluginOptions,
|
||||
done: (err?: Error | undefined) => void,
|
||||
) => {
|
||||
// Setup plugin to use zod as a type provider
|
||||
fastify.setValidatorCompiler(validatorCompiler);
|
||||
fastify.setSerializerCompiler(serializerCompiler);
|
||||
const plugin = fastify.withTypeProvider<ZodTypeProvider>();
|
||||
|
||||
// Register the fastify cookie middleware with this plugin
|
||||
await plugin.register(fastifyCookie);
|
||||
|
||||
// Register individual auth endpoints
|
||||
payloadHandler(plugin, ctx as ThirdwebAuthContext);
|
||||
loginHandler(plugin, ctx as ThirdwebAuthContext);
|
||||
logoutHandler(plugin, ctx as ThirdwebAuthContext);
|
||||
userHandler(plugin, ctx as ThirdwebAuthContext);
|
||||
switchAccountHandler(plugin, ctx as ThirdwebAuthContext);
|
||||
|
||||
// Mark plugin creation as complete
|
||||
done();
|
||||
};
|
||||
|
||||
return {
|
||||
authRouter,
|
||||
authMiddleware: fastifyCookie,
|
||||
getUser: (req: FastifyRequest) => {
|
||||
return getUser<TData, TSession>(req, ctx);
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
import {
|
||||
FastifyInstanceWithZod,
|
||||
LoginPayloadBodySchema,
|
||||
ThirdwebAuthContext,
|
||||
} from "../types";
|
||||
import { GenerateOptionsWithOptionalDomain } from "../../core";
|
||||
import {
|
||||
THIRDWEB_AUTH_ACTIVE_ACCOUNT_COOKIE,
|
||||
THIRDWEB_AUTH_TOKEN_COOKIE_PREFIX,
|
||||
} from "../../constants";
|
||||
import { z } from "zod";
|
||||
|
||||
export const loginHandler = (
|
||||
fastify: FastifyInstanceWithZod,
|
||||
ctx: ThirdwebAuthContext,
|
||||
) => {
|
||||
fastify.route<{
|
||||
Body: z.infer<typeof LoginPayloadBodySchema>;
|
||||
}>({
|
||||
method: "POST",
|
||||
url: "/login",
|
||||
schema: {
|
||||
body: LoginPayloadBodySchema,
|
||||
response: {
|
||||
200: z.object({
|
||||
token: z.string(),
|
||||
}),
|
||||
},
|
||||
},
|
||||
handler: async (req, res) => {
|
||||
const validateNonce = async (nonce: string) => {
|
||||
if (ctx.authOptions?.validateNonce) {
|
||||
await ctx.authOptions?.validateNonce(nonce);
|
||||
}
|
||||
};
|
||||
|
||||
const getSession = async (address: string) => {
|
||||
if (ctx.callbacks?.onLogin) {
|
||||
return ctx.callbacks.onLogin(address, req);
|
||||
}
|
||||
};
|
||||
|
||||
const expirationTime = ctx.authOptions?.tokenDurationInSeconds
|
||||
? new Date(Date.now() + 1000 * ctx.authOptions.tokenDurationInSeconds)
|
||||
: undefined;
|
||||
|
||||
const generateOptions: GenerateOptionsWithOptionalDomain = {
|
||||
verifyOptions: {
|
||||
statement: ctx.authOptions?.statement,
|
||||
uri: ctx.authOptions?.uri,
|
||||
version: ctx.authOptions?.version,
|
||||
chainId: ctx.authOptions?.chainId,
|
||||
validateNonce,
|
||||
resources: ctx.authOptions?.resources,
|
||||
},
|
||||
expirationTime,
|
||||
session: getSession,
|
||||
};
|
||||
|
||||
let token: string;
|
||||
try {
|
||||
// Generate an access token with the SDK using the signed payload
|
||||
token = await ctx.auth.generate(req.body.payload, generateOptions);
|
||||
} catch (err: any) {
|
||||
if (err.message) {
|
||||
return res.status(400).send({ error: err.message });
|
||||
} else if (typeof err === "string") {
|
||||
return res.status(400).send({ error: err });
|
||||
} else {
|
||||
return res.status(400).send({ error: "Invalid login payload" });
|
||||
}
|
||||
}
|
||||
|
||||
if (ctx.callbacks?.onToken) {
|
||||
await ctx.callbacks.onToken(token, req);
|
||||
}
|
||||
|
||||
const {
|
||||
payload: { exp },
|
||||
} = ctx.auth.parseToken(token);
|
||||
|
||||
// Securely set httpOnly cookie on request to prevent XSS on frontend
|
||||
// And set path to / to enable thirdweb_auth_token usage on all endpoints
|
||||
res.setCookie(
|
||||
`${THIRDWEB_AUTH_TOKEN_COOKIE_PREFIX}_${req.body.payload.payload.address}`,
|
||||
token,
|
||||
{
|
||||
domain: ctx.cookieOptions?.domain,
|
||||
path: ctx.cookieOptions?.path || "/",
|
||||
sameSite: ctx.cookieOptions?.sameSite || "none",
|
||||
expires: new Date(exp * 1000),
|
||||
httpOnly: true,
|
||||
secure: ctx.cookieOptions?.secure || true,
|
||||
},
|
||||
);
|
||||
res.setCookie(
|
||||
THIRDWEB_AUTH_ACTIVE_ACCOUNT_COOKIE,
|
||||
req.body.payload.payload.address,
|
||||
{
|
||||
domain: ctx.cookieOptions?.domain,
|
||||
path: ctx.cookieOptions?.path || "/",
|
||||
sameSite: ctx.cookieOptions?.sameSite || "none",
|
||||
expires: new Date(exp * 1000),
|
||||
httpOnly: true,
|
||||
secure: ctx.cookieOptions?.secure || true,
|
||||
},
|
||||
);
|
||||
|
||||
// Send token in body and as cookie for frontend and backend use cases
|
||||
return res.status(200).send({ token });
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,52 @@
|
||||
import { z } from "zod";
|
||||
import { getActiveCookie, getUser } from "../helpers/user";
|
||||
import {
|
||||
ErrorResponseSchema,
|
||||
FastifyInstanceWithZod,
|
||||
ThirdwebAuthContext,
|
||||
} from "../types";
|
||||
|
||||
export const logoutHandler = (
|
||||
fastify: FastifyInstanceWithZod,
|
||||
ctx: ThirdwebAuthContext,
|
||||
) => {
|
||||
fastify.route({
|
||||
method: "POST",
|
||||
url: "/logout",
|
||||
schema: {
|
||||
response: {
|
||||
200: z.object({
|
||||
message: z.string(),
|
||||
}),
|
||||
400: ErrorResponseSchema,
|
||||
},
|
||||
},
|
||||
handler: async (req, res) => {
|
||||
const activeCookie = getActiveCookie(req);
|
||||
if (!activeCookie) {
|
||||
return res.status(400).send({
|
||||
error: "No logged in user to logout.",
|
||||
});
|
||||
}
|
||||
|
||||
if (ctx.callbacks?.onLogout) {
|
||||
const user = await getUser(req, ctx);
|
||||
if (user) {
|
||||
await ctx.callbacks.onLogout(user, req);
|
||||
}
|
||||
}
|
||||
|
||||
// Set the access token to 'none' and expire in 5 seconds
|
||||
res.setCookie(activeCookie, "", {
|
||||
domain: ctx.cookieOptions?.domain,
|
||||
path: ctx.cookieOptions?.path || "/",
|
||||
sameSite: ctx.cookieOptions?.sameSite || "none",
|
||||
expires: new Date(Date.now() + 5 * 1000),
|
||||
httpOnly: true,
|
||||
secure: ctx.cookieOptions?.secure || true,
|
||||
});
|
||||
|
||||
return res.status(200).send({ message: "Successfully logged out" });
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,41 @@
|
||||
import {
|
||||
FastifyInstanceWithZod,
|
||||
PayloadBodySchema,
|
||||
ThirdwebAuthContext,
|
||||
} from "../types";
|
||||
import { z } from "zod";
|
||||
|
||||
export const payloadHandler = (
|
||||
fastify: FastifyInstanceWithZod,
|
||||
ctx: ThirdwebAuthContext,
|
||||
) => {
|
||||
fastify.route<{
|
||||
Body: z.infer<typeof PayloadBodySchema>;
|
||||
}>({
|
||||
method: "POST",
|
||||
url: "/payload",
|
||||
schema: {
|
||||
body: PayloadBodySchema,
|
||||
response: {
|
||||
200: z.any(),
|
||||
},
|
||||
},
|
||||
handler: async (req, res) => {
|
||||
const payload = await ctx.auth.payload({
|
||||
address: req.body.address,
|
||||
statement: ctx.authOptions?.statement,
|
||||
uri: ctx.authOptions?.uri,
|
||||
version: ctx.authOptions?.version,
|
||||
chainId: req.body.chainId || ctx.authOptions?.chainId,
|
||||
resources: ctx.authOptions?.resources,
|
||||
expirationTime: ctx.authOptions?.loginPayloadDurationInSeconds
|
||||
? new Date(
|
||||
Date.now() + 1000 * ctx.authOptions.loginPayloadDurationInSeconds,
|
||||
)
|
||||
: undefined,
|
||||
});
|
||||
|
||||
return res.status(200).send({ payload });
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,68 @@
|
||||
import {
|
||||
ActiveBodySchema,
|
||||
ErrorResponseSchema,
|
||||
FastifyInstanceWithZod,
|
||||
ThirdwebAuthContext,
|
||||
} from "../types";
|
||||
import {
|
||||
THIRDWEB_AUTH_ACTIVE_ACCOUNT_COOKIE,
|
||||
THIRDWEB_AUTH_DEFAULT_TOKEN_DURATION_IN_SECONDS,
|
||||
THIRDWEB_AUTH_TOKEN_COOKIE_PREFIX,
|
||||
} from "../../constants";
|
||||
import { getCookie } from "../helpers/user";
|
||||
import { z } from "zod";
|
||||
|
||||
export const switchAccountHandler = (
|
||||
fastify: FastifyInstanceWithZod,
|
||||
ctx: ThirdwebAuthContext,
|
||||
) => {
|
||||
fastify.route<{
|
||||
Body: z.infer<typeof ActiveBodySchema>;
|
||||
}>({
|
||||
method: "POST",
|
||||
url: "/switch-account",
|
||||
schema: {
|
||||
body: ActiveBodySchema,
|
||||
response: {
|
||||
200: z.any(),
|
||||
400: ErrorResponseSchema,
|
||||
},
|
||||
},
|
||||
handler: async (req, res) => {
|
||||
let cookieExpiration: Date;
|
||||
const cookie = getCookie(
|
||||
req,
|
||||
`${THIRDWEB_AUTH_TOKEN_COOKIE_PREFIX}_${req.body.address}`,
|
||||
);
|
||||
|
||||
if (cookie) {
|
||||
// If the new account is already logged in, get the expiration time from the cookie
|
||||
const {
|
||||
payload: { exp },
|
||||
} = ctx.auth.parseToken(cookie);
|
||||
cookieExpiration = new Date(exp * 1000);
|
||||
} else if (ctx.authOptions?.tokenDurationInSeconds) {
|
||||
// Otherwise, if we have a token duration in seconds, set it to that
|
||||
cookieExpiration = new Date(
|
||||
Date.now() + 1000 * ctx.authOptions.tokenDurationInSeconds,
|
||||
);
|
||||
} else {
|
||||
// Otherwise, just default to 24 hours
|
||||
cookieExpiration = new Date(
|
||||
Date.now() + 1000 * THIRDWEB_AUTH_DEFAULT_TOKEN_DURATION_IN_SECONDS,
|
||||
);
|
||||
}
|
||||
|
||||
res.setCookie(THIRDWEB_AUTH_ACTIVE_ACCOUNT_COOKIE, req.body.address, {
|
||||
domain: ctx.cookieOptions?.domain,
|
||||
path: ctx.cookieOptions?.path || "/",
|
||||
sameSite: ctx.cookieOptions?.sameSite || "none",
|
||||
expires: cookieExpiration,
|
||||
httpOnly: true,
|
||||
secure: ctx.cookieOptions?.secure || true,
|
||||
});
|
||||
|
||||
return res.status(200).send();
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,80 @@
|
||||
import { getToken, getUser } from "../helpers/user";
|
||||
import { FastifyInstanceWithZod, ThirdwebAuthContext } from "../types";
|
||||
import {
|
||||
THIRDWEB_AUTH_ACTIVE_ACCOUNT_COOKIE,
|
||||
THIRDWEB_AUTH_DEFAULT_REFRESH_INTERVAL_IN_SECONDS,
|
||||
THIRDWEB_AUTH_TOKEN_COOKIE_PREFIX,
|
||||
} from "../../constants";
|
||||
import { z } from "zod";
|
||||
|
||||
export const userHandler = (
|
||||
fastify: FastifyInstanceWithZod,
|
||||
ctx: ThirdwebAuthContext,
|
||||
) => {
|
||||
fastify.route({
|
||||
method: "GET",
|
||||
url: "/user",
|
||||
schema: {
|
||||
response: {
|
||||
200: z.any(),
|
||||
},
|
||||
},
|
||||
handler: async (req, res) => {
|
||||
const user = await getUser(req, ctx);
|
||||
|
||||
// Importantly, make sure the user was actually logged in before refreshing
|
||||
if (user) {
|
||||
const token = getToken(req);
|
||||
if (token) {
|
||||
const payload = ctx.auth.parseToken(token);
|
||||
|
||||
const refreshDate = ctx.authOptions?.refreshIntervalInSeconds
|
||||
? new Date(
|
||||
payload.payload.iat * 1000 +
|
||||
ctx.authOptions.refreshIntervalInSeconds * 1000,
|
||||
)
|
||||
: new Date(
|
||||
payload.payload.iat * 1000 +
|
||||
THIRDWEB_AUTH_DEFAULT_REFRESH_INTERVAL_IN_SECONDS * 1000,
|
||||
);
|
||||
|
||||
if (new Date() > refreshDate) {
|
||||
const expirationTime = ctx.authOptions?.tokenDurationInSeconds
|
||||
? new Date(
|
||||
Date.now() + 1000 * ctx.authOptions.tokenDurationInSeconds,
|
||||
)
|
||||
: undefined;
|
||||
const refreshedToken = await ctx.auth.refresh(
|
||||
token,
|
||||
expirationTime,
|
||||
);
|
||||
const refreshedPayload = ctx.auth.parseToken(refreshedToken);
|
||||
|
||||
res.setCookie(
|
||||
`${THIRDWEB_AUTH_TOKEN_COOKIE_PREFIX}_${user.address}`,
|
||||
refreshedToken,
|
||||
{
|
||||
domain: ctx.cookieOptions?.domain,
|
||||
path: ctx.cookieOptions?.path || "/",
|
||||
sameSite: ctx.cookieOptions?.sameSite || "none",
|
||||
expires: new Date(refreshedPayload.payload.exp * 1000),
|
||||
httpOnly: true,
|
||||
secure: ctx.cookieOptions?.secure || true,
|
||||
},
|
||||
);
|
||||
res.setCookie(THIRDWEB_AUTH_ACTIVE_ACCOUNT_COOKIE, user.address, {
|
||||
domain: ctx.cookieOptions?.domain,
|
||||
path: ctx.cookieOptions?.path || "/",
|
||||
sameSite: ctx.cookieOptions?.sameSite || "none",
|
||||
expires: new Date(refreshedPayload.payload.exp * 1000),
|
||||
httpOnly: true,
|
||||
secure: ctx.cookieOptions?.secure || true,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return res.status(200).send(user);
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,96 @@
|
||||
import { GenericAuthWallet } from "@thirdweb-dev/wallets";
|
||||
import { Json, LoginPayloadOutputSchema, ThirdwebAuth, User } from "../../core";
|
||||
import {
|
||||
FastifyBaseLogger,
|
||||
FastifyInstance,
|
||||
FastifyRequest,
|
||||
RawServerDefault,
|
||||
} from "fastify";
|
||||
import { z } from "zod";
|
||||
import { IncomingMessage, ServerResponse } from "http";
|
||||
import { ZodTypeProvider } from "fastify-type-provider-zod";
|
||||
import { ThirdwebAuthOptions } from "../../core/types";
|
||||
|
||||
export const PayloadBodySchema = z.object({
|
||||
address: z.string(),
|
||||
chainId: z.string().optional(),
|
||||
});
|
||||
|
||||
export const ActiveBodySchema = z.object({
|
||||
address: z.string(),
|
||||
});
|
||||
|
||||
export const LoginPayloadBodySchema = z.object({
|
||||
payload: LoginPayloadOutputSchema,
|
||||
});
|
||||
|
||||
export const ErrorResponseSchema = z.object({
|
||||
error: z.string(),
|
||||
});
|
||||
|
||||
export type ThirdwebAuthUser<
|
||||
TData extends Json = Json,
|
||||
TSession extends Json = Json,
|
||||
> = User<TSession> & {
|
||||
data?: TData;
|
||||
};
|
||||
|
||||
export type ThirdwebAuthConfig<
|
||||
TData extends Json = Json,
|
||||
TSession extends Json = Json,
|
||||
> = {
|
||||
domain: string;
|
||||
wallet: GenericAuthWallet;
|
||||
authOptions?: {
|
||||
statement?: string;
|
||||
uri?: string;
|
||||
version?: string;
|
||||
chainId?: string;
|
||||
resources?: string[];
|
||||
validateNonce?:
|
||||
| ((nonce: string) => void)
|
||||
| ((nonce: string) => Promise<void>);
|
||||
validateTokenId?:
|
||||
| ((tokenId: string) => void)
|
||||
| ((tokenId: string) => Promise<void>);
|
||||
loginPayloadDurationInSeconds?: number;
|
||||
tokenDurationInSeconds?: number;
|
||||
refreshIntervalInSeconds?: number;
|
||||
};
|
||||
cookieOptions?: {
|
||||
domain?: string;
|
||||
path?: string;
|
||||
sameSite?: "lax" | "strict" | "none";
|
||||
secure?: boolean;
|
||||
};
|
||||
callbacks?: {
|
||||
onLogin?:
|
||||
| ((address: string, req: FastifyRequest) => void | TSession)
|
||||
| ((address: string, req: FastifyRequest) => Promise<void | TSession>);
|
||||
onToken?:
|
||||
| ((token: string, req: FastifyRequest) => void)
|
||||
| ((token: string, req: FastifyRequest) => Promise<void>);
|
||||
onUser?:
|
||||
| ((user: User<TSession>, req: FastifyRequest) => void | TData)
|
||||
| ((user: User<TSession>, req: FastifyRequest) => Promise<void | TData>);
|
||||
onLogout?:
|
||||
| ((user: User, req: FastifyRequest) => void)
|
||||
| ((user: User, req: FastifyRequest) => Promise<void>);
|
||||
};
|
||||
thirdwebAuthOptions?: ThirdwebAuthOptions;
|
||||
};
|
||||
|
||||
export type ThirdwebAuthContext<
|
||||
TData extends Json = Json,
|
||||
TSession extends Json = Json,
|
||||
> = Omit<Omit<ThirdwebAuthConfig<TData, TSession>, "wallet">, "domain"> & {
|
||||
auth: ThirdwebAuth;
|
||||
};
|
||||
|
||||
export type FastifyInstanceWithZod = FastifyInstance<
|
||||
RawServerDefault,
|
||||
IncomingMessage,
|
||||
ServerResponse<IncomingMessage>,
|
||||
FastifyBaseLogger,
|
||||
ZodTypeProvider
|
||||
>;
|
||||
Vendored
@@ -0,0 +1,64 @@
|
||||
import type { GenericAuthWallet } from "@thirdweb-dev/wallets";
|
||||
import { z } from "zod";
|
||||
|
||||
import { Json, LoginPayloadOutputSchema, User } from "../../core";
|
||||
import { ThirdwebAuthOptions } from "../../core/types";
|
||||
|
||||
export const PayloadBodySchema = z.object({
|
||||
address: z.string(),
|
||||
chainId: z.string().optional(),
|
||||
});
|
||||
|
||||
export const ActiveBodySchema = z.object({
|
||||
address: z.string(),
|
||||
});
|
||||
|
||||
export const LoginPayloadBodySchema = z.object({
|
||||
payload: LoginPayloadOutputSchema,
|
||||
});
|
||||
|
||||
export type ThirdwebAuthRoute =
|
||||
| "payload"
|
||||
| "login"
|
||||
| "logout"
|
||||
| "user"
|
||||
| "switch-account";
|
||||
|
||||
export type ThirdwebAuthUser<
|
||||
TData extends Json = Json,
|
||||
TSession extends Json = Json,
|
||||
> = User<TSession> & {
|
||||
data?: TData;
|
||||
};
|
||||
|
||||
export type ThirdwebAuthConfigShared = {
|
||||
domain: string;
|
||||
wallet: GenericAuthWallet;
|
||||
authOptions?: {
|
||||
statement?: string;
|
||||
uri?: string;
|
||||
version?: string;
|
||||
chainId?: string;
|
||||
resources?: string[];
|
||||
validateNonce?:
|
||||
| ((nonce: string) => void)
|
||||
| ((nonce: string) => Promise<void>);
|
||||
validateTokenId?:
|
||||
| ((tokenId: string) => void)
|
||||
| ((tokenId: string) => Promise<void>);
|
||||
loginPayloadDurationInSeconds?: number;
|
||||
tokenDurationInSeconds?: number;
|
||||
refreshIntervalInSeconds?: number;
|
||||
};
|
||||
cookieOptions?: {
|
||||
domain?: string;
|
||||
path?: string;
|
||||
sameSite?: "lax" | "strict" | "none";
|
||||
secure?: boolean;
|
||||
};
|
||||
thirdwebAuthOptions?: ThirdwebAuthOptions;
|
||||
};
|
||||
|
||||
export type ThirdwebNextContext = {
|
||||
params?: Record<string, string | string[]>;
|
||||
};
|
||||
@@ -0,0 +1,21 @@
|
||||
// Next.js Pages Router support
|
||||
// default and backward compatible
|
||||
export { ThirdwebAuth } from './router-pages'
|
||||
export type {
|
||||
ThirdwebAuthConfig,
|
||||
ThirdwebAuthContext,
|
||||
} from './router-pages/types'
|
||||
|
||||
// Next.js App Router support
|
||||
export { ThirdwebAuth as ThirdwebAuthAppRouter } from './router-app'
|
||||
export type {
|
||||
ThirdwebAuthConfig as ThirdwebAuthAppRouterConfig,
|
||||
ThirdwebAuthContext as ThirdwebAuthAppRouterContext,
|
||||
} from './router-app/types'
|
||||
|
||||
// common types
|
||||
export type {
|
||||
ThirdwebAuthRoute,
|
||||
ThirdwebAuthUser,
|
||||
ThirdwebNextContext,
|
||||
} from './common/types'
|
||||
@@ -0,0 +1,75 @@
|
||||
import { cookies, headers } from "next/headers";
|
||||
|
||||
import {
|
||||
THIRDWEB_AUTH_ACTIVE_ACCOUNT_COOKIE,
|
||||
THIRDWEB_AUTH_TOKEN_COOKIE_PREFIX,
|
||||
} from "../../../constants";
|
||||
import type { Json } from "../../../core";
|
||||
import type { ThirdwebAuthUser } from "../../common/types";
|
||||
import type { ThirdwebAuthContext } from "../types";
|
||||
|
||||
export function getCookie(cookie: string): string | undefined {
|
||||
return cookies().get(cookie)?.value;
|
||||
}
|
||||
|
||||
export function getActiveCookie(): string | undefined {
|
||||
const activeAccount = getCookie(THIRDWEB_AUTH_ACTIVE_ACCOUNT_COOKIE);
|
||||
if (activeAccount) {
|
||||
return `${THIRDWEB_AUTH_TOKEN_COOKIE_PREFIX}_${activeAccount}`;
|
||||
}
|
||||
|
||||
// If active account is not present, then use the old default
|
||||
return THIRDWEB_AUTH_TOKEN_COOKIE_PREFIX;
|
||||
}
|
||||
|
||||
export function getToken(): string | undefined {
|
||||
const headerList = headers();
|
||||
|
||||
if (headerList.has("authorization")) {
|
||||
const authorizationHeader = headerList.get("authorization")?.split(" ");
|
||||
if (authorizationHeader?.length === 2) {
|
||||
return authorizationHeader[1];
|
||||
}
|
||||
}
|
||||
|
||||
const activeCookie = getActiveCookie();
|
||||
if (!activeCookie) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return getCookie(activeCookie);
|
||||
}
|
||||
|
||||
export async function getUser<
|
||||
TData extends Json = Json,
|
||||
TSession extends Json = Json,
|
||||
>(
|
||||
ctx: ThirdwebAuthContext<TData, TSession>
|
||||
): Promise<ThirdwebAuthUser<TData, TSession> | null> {
|
||||
const token = getToken();
|
||||
if (!token) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let authenticatedUser: ThirdwebAuthUser<TData, TSession>;
|
||||
try {
|
||||
authenticatedUser = await ctx.auth.authenticate<TSession>(token, {
|
||||
validateTokenId: async (tokenId: string) => {
|
||||
if (ctx.authOptions?.validateTokenId) {
|
||||
await ctx.authOptions?.validateTokenId(tokenId);
|
||||
}
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (ctx.callbacks?.onUser) {
|
||||
const data = await ctx.callbacks.onUser(authenticatedUser);
|
||||
if (data) {
|
||||
return { ...authenticatedUser, data };
|
||||
}
|
||||
}
|
||||
|
||||
return authenticatedUser;
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
import type { NextRequest } from "next/server";
|
||||
|
||||
import type { Json } from "../../core";
|
||||
import { ThirdwebAuth as ThirdwebAuthSDK } from "../../core";
|
||||
import { getUser } from "./helpers/user";
|
||||
import payloadHandler from "./routes/payload";
|
||||
import loginHandler from "./routes/login";
|
||||
import logoutHandler from "./routes/logout";
|
||||
import userHandler from "./routes/user";
|
||||
import switchAccountHandler from "./routes/switch-account";
|
||||
import type { ThirdwebAuthConfig, ThirdwebAuthContext } from "./types";
|
||||
import type { ThirdwebAuthRoute, ThirdwebNextContext } from "../common/types";
|
||||
|
||||
export type { ThirdwebAuthConfig, ThirdwebAuthContext } from "./types";
|
||||
|
||||
export async function ThirdwebAuthRouter(
|
||||
req: NextRequest,
|
||||
ctx: ThirdwebNextContext,
|
||||
authCtx: ThirdwebAuthContext,
|
||||
) {
|
||||
// Catch-all route must be named with [...thirdweb]
|
||||
const action = ctx.params?.thirdweb?.[0] as ThirdwebAuthRoute;
|
||||
|
||||
switch (action) {
|
||||
case "payload":
|
||||
return await payloadHandler(req, authCtx);
|
||||
case "login":
|
||||
return await loginHandler(req, authCtx);
|
||||
case "user":
|
||||
return await userHandler(req, authCtx);
|
||||
case "logout":
|
||||
return await logoutHandler(req, authCtx);
|
||||
case "switch-account":
|
||||
return await switchAccountHandler(req, authCtx);
|
||||
default:
|
||||
return Response.json(
|
||||
{ message: "Invalid route for authentication." },
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function ThirdwebAuth<
|
||||
TData extends Json = Json,
|
||||
TSession extends Json = Json,
|
||||
>(cfg: ThirdwebAuthConfig<TData, TSession>) {
|
||||
const authCtx = {
|
||||
...cfg,
|
||||
auth: new ThirdwebAuthSDK(cfg.wallet, cfg.domain, cfg.thirdwebAuthOptions),
|
||||
};
|
||||
|
||||
async function ThirdwebAuthHandler(
|
||||
req: NextRequest,
|
||||
ctx: ThirdwebNextContext,
|
||||
) {
|
||||
return await ThirdwebAuthRouter(req, ctx, authCtx as ThirdwebAuthContext);
|
||||
}
|
||||
|
||||
return {
|
||||
ThirdwebAuthHandler,
|
||||
getUser: async () => {
|
||||
return await getUser<TData, TSession>(authCtx);
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
import type { NextRequest } from "next/server";
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
import {
|
||||
THIRDWEB_AUTH_ACTIVE_ACCOUNT_COOKIE,
|
||||
THIRDWEB_AUTH_TOKEN_COOKIE_PREFIX,
|
||||
} from "../../../constants";
|
||||
import type { GenerateOptionsWithOptionalDomain } from "../../../core";
|
||||
import type { ThirdwebAuthContext } from "../types";
|
||||
import { LoginPayloadBodySchema } from "../types";
|
||||
|
||||
export default async function handler(
|
||||
req: NextRequest,
|
||||
ctx: ThirdwebAuthContext,
|
||||
) {
|
||||
if (req.method !== "POST") {
|
||||
return Response.json(
|
||||
{ error: "Invalid method. Only POST supported" },
|
||||
{ status: 405 },
|
||||
);
|
||||
}
|
||||
|
||||
const reqBody = await req.json();
|
||||
const parsedPayload = LoginPayloadBodySchema.safeParse(reqBody);
|
||||
|
||||
// Get signed login payload from the frontend
|
||||
if (!parsedPayload.success) {
|
||||
return Response.json(
|
||||
{ error: "Invalid login payload" },
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
const payload = parsedPayload.data.payload;
|
||||
|
||||
const validateNonce = async (nonce: string) => {
|
||||
if (ctx.authOptions?.validateNonce) {
|
||||
await ctx.authOptions?.validateNonce(nonce);
|
||||
}
|
||||
};
|
||||
|
||||
const getSession = async (address: string) => {
|
||||
if (ctx.callbacks?.onLogin) {
|
||||
return ctx.callbacks.onLogin(address);
|
||||
}
|
||||
};
|
||||
|
||||
const expirationTime = ctx.authOptions?.tokenDurationInSeconds
|
||||
? new Date(Date.now() + 1000 * ctx.authOptions.tokenDurationInSeconds)
|
||||
: undefined;
|
||||
|
||||
const generateOptions: GenerateOptionsWithOptionalDomain = {
|
||||
verifyOptions: {
|
||||
statement: ctx.authOptions?.statement,
|
||||
uri: ctx.authOptions?.uri,
|
||||
version: ctx.authOptions?.version,
|
||||
chainId: ctx.authOptions?.chainId,
|
||||
validateNonce,
|
||||
resources: ctx.authOptions?.resources,
|
||||
},
|
||||
expirationTime,
|
||||
session: getSession,
|
||||
};
|
||||
|
||||
let token: string;
|
||||
try {
|
||||
// Generate an access token with the SDK using the signed payload
|
||||
token = await ctx.auth.generate(payload, generateOptions);
|
||||
} catch (err: any) {
|
||||
if (err.message) {
|
||||
return Response.json(
|
||||
{ error: err.message },
|
||||
{ status: 400 },
|
||||
);
|
||||
} else if (typeof err === "string") {
|
||||
return Response.json(
|
||||
{ error: err },
|
||||
{ status: 400 },
|
||||
);
|
||||
} else {
|
||||
return Response.json(
|
||||
{ error: "Invalid login payload" },
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (ctx.callbacks?.onToken) {
|
||||
await ctx.callbacks.onToken(token);
|
||||
}
|
||||
|
||||
const {
|
||||
payload: { exp },
|
||||
} = ctx.auth.parseToken(token);
|
||||
|
||||
// Securely set httpOnly cookie on request to prevent XSS on frontend
|
||||
// And set path to / to enable thirdweb_auth_token usage on all endpoints
|
||||
const response = NextResponse.json({ token }, { status: 200 });
|
||||
response.cookies.set({
|
||||
name: `${THIRDWEB_AUTH_TOKEN_COOKIE_PREFIX}_${payload.payload.address}`,
|
||||
value: token,
|
||||
domain: ctx.cookieOptions?.domain,
|
||||
path: ctx.cookieOptions?.path || "/",
|
||||
sameSite: ctx.cookieOptions?.sameSite || "none",
|
||||
expires: new Date(exp * 1000),
|
||||
httpOnly: true,
|
||||
secure: ctx.cookieOptions?.secure || true,
|
||||
});
|
||||
|
||||
response.cookies.set({
|
||||
name: THIRDWEB_AUTH_ACTIVE_ACCOUNT_COOKIE,
|
||||
value: payload.payload.address,
|
||||
domain: ctx.cookieOptions?.domain,
|
||||
path: ctx.cookieOptions?.path || "/",
|
||||
sameSite: ctx.cookieOptions?.sameSite || "none",
|
||||
expires: new Date(exp * 1000),
|
||||
httpOnly: true,
|
||||
secure: ctx.cookieOptions?.secure || true,
|
||||
});
|
||||
|
||||
// Send token in body and as cookie for frontend and backend use cases
|
||||
return response;
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
import type { NextRequest } from "next/server";
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
import { getActiveCookie, getUser } from "../helpers/user";
|
||||
import type { ThirdwebAuthContext } from "../types";
|
||||
|
||||
export default async function handler(
|
||||
req: NextRequest,
|
||||
ctx: ThirdwebAuthContext,
|
||||
) {
|
||||
if (req.method !== "POST") {
|
||||
return Response.json(
|
||||
{ error: "Invalid method. Only POST supported" },
|
||||
{ status: 405 },
|
||||
);
|
||||
}
|
||||
|
||||
const activeCookie = getActiveCookie();
|
||||
if (!activeCookie) {
|
||||
return Response.json(
|
||||
{ error: "No logged in user to logout" },
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
if (ctx.callbacks?.onLogout) {
|
||||
const user = await getUser(ctx);
|
||||
if (user) {
|
||||
await ctx.callbacks.onLogout(user);
|
||||
}
|
||||
}
|
||||
|
||||
// Set the access token to 'none' and expire in 5 seconds
|
||||
const response = NextResponse.json(
|
||||
{ message: "Successfully logged out" },
|
||||
{ status: 200 },
|
||||
);
|
||||
|
||||
response.cookies.set({
|
||||
name: activeCookie,
|
||||
value: '',
|
||||
domain: ctx.cookieOptions?.domain,
|
||||
path: ctx.cookieOptions?.path || "/",
|
||||
sameSite: ctx.cookieOptions?.sameSite || "none",
|
||||
expires: new Date(Date.now() + 5 * 1000),
|
||||
httpOnly: true,
|
||||
secure: ctx.cookieOptions?.secure || true,
|
||||
});
|
||||
|
||||
return response;
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import type { NextRequest } from "next/server";
|
||||
|
||||
import type { ThirdwebAuthContext } from "../types";
|
||||
import { PayloadBodySchema } from "../types";
|
||||
|
||||
export default async function handler(
|
||||
req: NextRequest,
|
||||
ctx: ThirdwebAuthContext,
|
||||
) {
|
||||
if (req.method !== "POST") {
|
||||
return Response.json(
|
||||
{ error: "Invalid method. Only POST supported" },
|
||||
{ status: 405 },
|
||||
);
|
||||
}
|
||||
|
||||
const reqBody = await req.json();
|
||||
const parsedPayload = PayloadBodySchema.safeParse(reqBody);
|
||||
|
||||
if (!parsedPayload.success) {
|
||||
return Response.json(
|
||||
{ error: "Please provide an address" },
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: Add nonce generation + custom expiration + invalid before
|
||||
const payload = await ctx.auth.payload({
|
||||
address: parsedPayload.data.address,
|
||||
statement: ctx.authOptions?.statement,
|
||||
uri: ctx.authOptions?.uri,
|
||||
version: ctx.authOptions?.version,
|
||||
chainId: parsedPayload.data.chainId || ctx.authOptions?.chainId,
|
||||
resources: ctx.authOptions?.resources,
|
||||
expirationTime: ctx.authOptions?.loginPayloadDurationInSeconds
|
||||
? new Date(
|
||||
Date.now() + 1000 * ctx.authOptions.loginPayloadDurationInSeconds,
|
||||
)
|
||||
: undefined,
|
||||
});
|
||||
|
||||
const response = Response.json({ payload }, { status: 200 });
|
||||
return response;
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
import type { NextRequest } from "next/server";
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
import type { ThirdwebAuthContext } from "../types";
|
||||
import { ActiveBodySchema } from "../types";
|
||||
import {
|
||||
THIRDWEB_AUTH_ACTIVE_ACCOUNT_COOKIE,
|
||||
THIRDWEB_AUTH_DEFAULT_TOKEN_DURATION_IN_SECONDS,
|
||||
THIRDWEB_AUTH_TOKEN_COOKIE_PREFIX,
|
||||
} from "../../../constants";
|
||||
import { getCookie } from "../helpers/user";
|
||||
|
||||
export default async function handler(
|
||||
req: NextRequest,
|
||||
ctx: ThirdwebAuthContext,
|
||||
) {
|
||||
if (req.method !== "POST") {
|
||||
return Response.json(
|
||||
{ error: "Invalid method. Only POST supported" },
|
||||
{ status: 405 },
|
||||
);
|
||||
}
|
||||
|
||||
const reqBody = await req.json();
|
||||
const parsedPayload = ActiveBodySchema.safeParse(reqBody);
|
||||
|
||||
if (!parsedPayload.success) {
|
||||
return Response.json(
|
||||
{ error: "Please provide an address" },
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
let cookieExpiration: Date;
|
||||
|
||||
const cookie = getCookie(`${THIRDWEB_AUTH_TOKEN_COOKIE_PREFIX}_${parsedPayload.data.address}`);
|
||||
if (cookie) {
|
||||
// If the new account is already logged in, get the expiration time from the cookie
|
||||
const {
|
||||
payload: { exp },
|
||||
} = ctx.auth.parseToken(cookie);
|
||||
cookieExpiration = new Date(exp * 1000);
|
||||
} else if (ctx.authOptions?.tokenDurationInSeconds) {
|
||||
// Otherwise, if we have a token duration in seconds, set it to that
|
||||
cookieExpiration = new Date(
|
||||
Date.now() + 1000 * ctx.authOptions.tokenDurationInSeconds,
|
||||
);
|
||||
} else {
|
||||
// Otherwise, just default to 24 hours
|
||||
cookieExpiration = new Date(
|
||||
Date.now() + 1000 * THIRDWEB_AUTH_DEFAULT_TOKEN_DURATION_IN_SECONDS,
|
||||
);
|
||||
}
|
||||
|
||||
const response = NextResponse.json('', { status: 200 });
|
||||
response.cookies.set({
|
||||
name: THIRDWEB_AUTH_ACTIVE_ACCOUNT_COOKIE,
|
||||
value: parsedPayload.data.address,
|
||||
domain: ctx.cookieOptions?.domain,
|
||||
path: ctx.cookieOptions?.path || "/",
|
||||
sameSite: ctx.cookieOptions?.sameSite || "none",
|
||||
expires: cookieExpiration,
|
||||
httpOnly: true,
|
||||
secure: ctx.cookieOptions?.secure || true,
|
||||
});
|
||||
|
||||
return response;
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
import type { NextRequest } from "next/server";
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
import {
|
||||
THIRDWEB_AUTH_ACTIVE_ACCOUNT_COOKIE,
|
||||
THIRDWEB_AUTH_DEFAULT_REFRESH_INTERVAL_IN_SECONDS,
|
||||
THIRDWEB_AUTH_TOKEN_COOKIE_PREFIX,
|
||||
} from "../../../constants";
|
||||
import { getToken, getUser } from "../helpers/user";
|
||||
import type { ThirdwebAuthContext } from "../types";
|
||||
|
||||
export default async function handler(
|
||||
req: NextRequest,
|
||||
ctx: ThirdwebAuthContext,
|
||||
) {
|
||||
if (req.method !== "GET") {
|
||||
return Response.json(
|
||||
{ error: "Invalid method. Only GET supported" },
|
||||
{ status: 405 },
|
||||
);
|
||||
}
|
||||
|
||||
const user = await getUser(ctx);
|
||||
|
||||
const response = NextResponse.json(user, { status: 200 });
|
||||
|
||||
// Importantly, make sure the user was actually logged in before refreshing
|
||||
if (user) {
|
||||
const token = getToken();
|
||||
if (token) {
|
||||
const payload = ctx.auth.parseToken(token);
|
||||
|
||||
const refreshDate = ctx.authOptions?.refreshIntervalInSeconds
|
||||
? new Date(
|
||||
payload.payload.iat * 1000 +
|
||||
ctx.authOptions.refreshIntervalInSeconds * 1000,
|
||||
)
|
||||
: new Date(
|
||||
payload.payload.iat * 1000 +
|
||||
THIRDWEB_AUTH_DEFAULT_REFRESH_INTERVAL_IN_SECONDS * 1000,
|
||||
);
|
||||
|
||||
if (new Date() > refreshDate) {
|
||||
const expirationTime = ctx.authOptions?.tokenDurationInSeconds
|
||||
? new Date(Date.now() + 1000 * ctx.authOptions.tokenDurationInSeconds)
|
||||
: undefined;
|
||||
const refreshedToken = await ctx.auth.refresh(token, expirationTime);
|
||||
const refreshedPayload = ctx.auth.parseToken(refreshedToken);
|
||||
|
||||
response.cookies.set({
|
||||
name: `${THIRDWEB_AUTH_TOKEN_COOKIE_PREFIX}_${user.address}`,
|
||||
value: refreshedToken,
|
||||
domain: ctx.cookieOptions?.domain,
|
||||
path: ctx.cookieOptions?.path || "/",
|
||||
sameSite: ctx.cookieOptions?.sameSite || "none",
|
||||
expires: new Date(refreshedPayload.payload.exp * 1000),
|
||||
httpOnly: true,
|
||||
secure: ctx.cookieOptions?.secure || true,
|
||||
});
|
||||
|
||||
response.cookies.set({
|
||||
name: THIRDWEB_AUTH_ACTIVE_ACCOUNT_COOKIE,
|
||||
value: user.address,
|
||||
domain: ctx.cookieOptions?.domain,
|
||||
path: ctx.cookieOptions?.path || "/",
|
||||
sameSite: ctx.cookieOptions?.sameSite || "none",
|
||||
expires: new Date(refreshedPayload.payload.exp * 1000),
|
||||
httpOnly: true,
|
||||
secure: ctx.cookieOptions?.secure || true,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import type { Json, ThirdwebAuth, User } from "../../../core";
|
||||
import type { ThirdwebAuthConfigShared } from '../../common/types'
|
||||
|
||||
export {
|
||||
ActiveBodySchema,
|
||||
LoginPayloadBodySchema,
|
||||
PayloadBodySchema
|
||||
} from '../../common/types'
|
||||
|
||||
export type ThirdwebAuthConfig<
|
||||
TData extends Json = Json,
|
||||
TSession extends Json = Json,
|
||||
> = ThirdwebAuthConfigShared & {
|
||||
callbacks?: {
|
||||
onLogin?:
|
||||
| ((address: string) => void | TSession)
|
||||
| ((address: string) => Promise<void | TSession>);
|
||||
onToken?:
|
||||
| ((token: string) => void)
|
||||
| ((token: string) => Promise<void>);
|
||||
onUser?:
|
||||
| ((user: User<TSession>) => void | TData)
|
||||
| ((user: User<TSession>) => Promise<void | TData>);
|
||||
onLogout?:
|
||||
| ((user: User) => void)
|
||||
| ((user: User) => Promise<void>);
|
||||
};
|
||||
};
|
||||
|
||||
export type ThirdwebAuthContext<
|
||||
TData extends Json = Json,
|
||||
TSession extends Json = Json,
|
||||
> = Omit<Omit<ThirdwebAuthConfig<TData, TSession>, "wallet">, "domain"> & {
|
||||
auth: ThirdwebAuth;
|
||||
};
|
||||
@@ -0,0 +1,98 @@
|
||||
import {
|
||||
THIRDWEB_AUTH_ACTIVE_ACCOUNT_COOKIE,
|
||||
THIRDWEB_AUTH_TOKEN_COOKIE_PREFIX,
|
||||
} from "../../../constants";
|
||||
import type { Json } from "../../../core";
|
||||
import type { ThirdwebAuthContext } from "../types";
|
||||
import type { ThirdwebAuthUser } from "../../common/types";
|
||||
import type { GetServerSidePropsContext, NextApiRequest } from "next";
|
||||
import type { NextRequest } from "next/server";
|
||||
|
||||
export function getCookie(
|
||||
req: GetServerSidePropsContext["req"] | NextRequest | NextApiRequest,
|
||||
cookie: string,
|
||||
): string | undefined {
|
||||
if (typeof req.cookies.get === "function") {
|
||||
return req.cookies.get(cookie)?.value;
|
||||
}
|
||||
|
||||
return (req.cookies as any)[cookie];
|
||||
}
|
||||
|
||||
export function getActiveCookie(
|
||||
req: GetServerSidePropsContext["req"] | NextRequest | NextApiRequest,
|
||||
): string | undefined {
|
||||
if (!req.cookies) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const activeAccount = getCookie(req, THIRDWEB_AUTH_ACTIVE_ACCOUNT_COOKIE);
|
||||
if (activeAccount) {
|
||||
return `${THIRDWEB_AUTH_TOKEN_COOKIE_PREFIX}_${activeAccount}`;
|
||||
}
|
||||
|
||||
// If active account is not present, then use the old default
|
||||
return THIRDWEB_AUTH_TOKEN_COOKIE_PREFIX;
|
||||
}
|
||||
|
||||
export function getToken(
|
||||
req: GetServerSidePropsContext["req"] | NextRequest | NextApiRequest,
|
||||
): string | undefined {
|
||||
if (!!(req as NextApiRequest).headers["authorization"]) {
|
||||
const authorizationHeader = (req as NextApiRequest).headers[
|
||||
"authorization"
|
||||
]?.split(" ");
|
||||
if (authorizationHeader?.length === 2) {
|
||||
return authorizationHeader[1];
|
||||
}
|
||||
}
|
||||
|
||||
if (!req.cookies) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const activeCookie = getActiveCookie(req);
|
||||
if (!activeCookie) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return getCookie(req, activeCookie);
|
||||
}
|
||||
|
||||
export async function getUser<
|
||||
TData extends Json = Json,
|
||||
TSession extends Json = Json,
|
||||
>(
|
||||
req: GetServerSidePropsContext["req"] | NextRequest | NextApiRequest,
|
||||
ctx: ThirdwebAuthContext<TData, TSession>,
|
||||
): Promise<ThirdwebAuthUser<TData, TSession> | null> {
|
||||
const token = getToken(req);
|
||||
|
||||
if (!token) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let authenticatedUser: ThirdwebAuthUser<TData, TSession>;
|
||||
try {
|
||||
authenticatedUser = await ctx.auth.authenticate<TSession>(token, {
|
||||
validateTokenId: async (tokenId: string) => {
|
||||
if (ctx.authOptions?.validateTokenId) {
|
||||
await ctx.authOptions?.validateTokenId(tokenId);
|
||||
}
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!ctx.callbacks?.onUser) {
|
||||
return authenticatedUser;
|
||||
}
|
||||
|
||||
const data = await ctx.callbacks.onUser(authenticatedUser, req);
|
||||
if (!data) {
|
||||
return authenticatedUser;
|
||||
}
|
||||
|
||||
return { ...authenticatedUser, data: data };
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
import { Json, ThirdwebAuth as ThirdwebAuthSDK } from "../../core";
|
||||
import { getUser } from "./helpers/user";
|
||||
import payloadHandler from "./routes/payload";
|
||||
import loginHandler from "./routes/login";
|
||||
import logoutHandler from "./routes/logout";
|
||||
import userHandler from "./routes/user";
|
||||
import switchAccountHandler from "./routes/switch-account";
|
||||
import {
|
||||
ThirdwebAuthConfig,
|
||||
ThirdwebAuthContext,
|
||||
ThirdwebAuthRoute,
|
||||
} from "./types";
|
||||
import { NextRequest } from "next/server";
|
||||
import {
|
||||
GetServerSidePropsContext,
|
||||
NextApiRequest,
|
||||
NextApiResponse,
|
||||
} from "next/types";
|
||||
|
||||
export * from "./types";
|
||||
|
||||
async function ThirdwebAuthRouter(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse,
|
||||
ctx: ThirdwebAuthContext,
|
||||
) {
|
||||
// Catch-all route must be named with [...thirdweb]
|
||||
const { thirdweb } = req.query;
|
||||
const action = thirdweb?.[0] as ThirdwebAuthRoute;
|
||||
|
||||
switch (action) {
|
||||
case "payload":
|
||||
return await payloadHandler(req, res, ctx);
|
||||
case "login":
|
||||
return await loginHandler(req, res, ctx);
|
||||
case "user":
|
||||
return await userHandler(req, res, ctx);
|
||||
case "logout":
|
||||
return await logoutHandler(req, res, ctx);
|
||||
case "switch-account":
|
||||
return await switchAccountHandler(req, res, ctx);
|
||||
default:
|
||||
return res.status(400).json({
|
||||
message: "Invalid route for authentication.",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function ThirdwebAuth<
|
||||
TData extends Json = Json,
|
||||
TSession extends Json = Json,
|
||||
>(cfg: ThirdwebAuthConfig<TData, TSession>) {
|
||||
const ctx = {
|
||||
...cfg,
|
||||
auth: new ThirdwebAuthSDK(cfg.wallet, cfg.domain, cfg.thirdwebAuthOptions),
|
||||
};
|
||||
|
||||
function ThirdwebAuthHandler(
|
||||
...args: [] | [NextApiRequest, NextApiResponse]
|
||||
) {
|
||||
if (args.length === 0) {
|
||||
return async (req: NextApiRequest, res: NextApiResponse) =>
|
||||
await ThirdwebAuthRouter(req, res, ctx as ThirdwebAuthContext);
|
||||
}
|
||||
|
||||
return ThirdwebAuthRouter(args[0], args[1], ctx as ThirdwebAuthContext);
|
||||
}
|
||||
|
||||
return {
|
||||
ThirdwebAuthHandler,
|
||||
getUser: (
|
||||
req: GetServerSidePropsContext["req"] | NextRequest | NextApiRequest,
|
||||
) => {
|
||||
return getUser<TData, TSession>(req, ctx);
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
import {
|
||||
THIRDWEB_AUTH_ACTIVE_ACCOUNT_COOKIE,
|
||||
THIRDWEB_AUTH_TOKEN_COOKIE_PREFIX,
|
||||
} from "../../../constants";
|
||||
import type { GenerateOptionsWithOptionalDomain } from "../../../core";
|
||||
import type { ThirdwebAuthContext } from "../types";
|
||||
import { LoginPayloadBodySchema } from "../types";
|
||||
import { serialize } from "cookie";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
export default async function handler(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse,
|
||||
ctx: ThirdwebAuthContext,
|
||||
) {
|
||||
if (req.method !== "POST") {
|
||||
return res.status(405).json({ error: "Method not allowed" });
|
||||
}
|
||||
|
||||
const parsedPayload = LoginPayloadBodySchema.safeParse(req.body);
|
||||
|
||||
// Get signed login payload from the frontend
|
||||
if (!parsedPayload.success) {
|
||||
return res.status(400).json({ error: "Invalid login payload" });
|
||||
}
|
||||
|
||||
const payload = parsedPayload.data.payload;
|
||||
|
||||
const validateNonce = async (nonce: string) => {
|
||||
if (ctx.authOptions?.validateNonce) {
|
||||
await ctx.authOptions?.validateNonce(nonce);
|
||||
}
|
||||
};
|
||||
|
||||
const getSession = async (address: string) => {
|
||||
if (ctx.callbacks?.onLogin) {
|
||||
return ctx.callbacks.onLogin(address, req);
|
||||
}
|
||||
};
|
||||
|
||||
const expirationTime = ctx.authOptions?.tokenDurationInSeconds
|
||||
? new Date(Date.now() + 1000 * ctx.authOptions.tokenDurationInSeconds)
|
||||
: undefined;
|
||||
|
||||
const generateOptions: GenerateOptionsWithOptionalDomain = {
|
||||
verifyOptions: {
|
||||
statement: ctx.authOptions?.statement,
|
||||
uri: ctx.authOptions?.uri,
|
||||
version: ctx.authOptions?.version,
|
||||
chainId: ctx.authOptions?.chainId,
|
||||
validateNonce,
|
||||
resources: ctx.authOptions?.resources,
|
||||
},
|
||||
expirationTime,
|
||||
session: getSession,
|
||||
};
|
||||
|
||||
let token: string;
|
||||
try {
|
||||
// Generate an access token with the SDK using the signed payload
|
||||
token = await ctx.auth.generate(payload, generateOptions);
|
||||
} catch (err: any) {
|
||||
if (err.message) {
|
||||
return res.status(400).json({ error: err.message });
|
||||
} else if (typeof err === "string") {
|
||||
return res.status(400).json({ error: err });
|
||||
} else {
|
||||
return res.status(400).json({ error: "Invalid login payload" });
|
||||
}
|
||||
}
|
||||
|
||||
if (ctx.callbacks?.onToken) {
|
||||
await ctx.callbacks.onToken(token, req);
|
||||
}
|
||||
|
||||
const {
|
||||
payload: { exp },
|
||||
} = ctx.auth.parseToken(token);
|
||||
|
||||
// Securely set httpOnly cookie on request to prevent XSS on frontend
|
||||
// And set path to / to enable thirdweb_auth_token usage on all endpoints
|
||||
res.setHeader("Set-Cookie", [
|
||||
serialize(
|
||||
`${THIRDWEB_AUTH_TOKEN_COOKIE_PREFIX}_${payload.payload.address}`,
|
||||
token,
|
||||
{
|
||||
domain: ctx.cookieOptions?.domain,
|
||||
path: ctx.cookieOptions?.path || "/",
|
||||
sameSite: ctx.cookieOptions?.sameSite || "none",
|
||||
expires: new Date(exp * 1000),
|
||||
httpOnly: true,
|
||||
secure: ctx.cookieOptions?.secure || true,
|
||||
},
|
||||
),
|
||||
serialize(THIRDWEB_AUTH_ACTIVE_ACCOUNT_COOKIE, payload.payload.address, {
|
||||
domain: ctx.cookieOptions?.domain,
|
||||
path: ctx.cookieOptions?.path || "/",
|
||||
sameSite: ctx.cookieOptions?.sameSite || "none",
|
||||
expires: new Date(exp * 1000),
|
||||
httpOnly: true,
|
||||
secure: ctx.cookieOptions?.secure || true,
|
||||
}),
|
||||
]);
|
||||
|
||||
// Send token in body and as cookie for frontend and backend use cases
|
||||
return res.status(200).json({ token });
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import { getActiveCookie, getUser } from "../helpers/user";
|
||||
import type { ThirdwebAuthContext } from "../types";
|
||||
import { serialize } from "cookie";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
export default async function handler(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse,
|
||||
ctx: ThirdwebAuthContext,
|
||||
) {
|
||||
if (req.method !== "POST") {
|
||||
return res.status(405).json({
|
||||
error: "Invalid method. Only POST supported",
|
||||
});
|
||||
}
|
||||
|
||||
const activeCookie = getActiveCookie(req);
|
||||
if (!activeCookie) {
|
||||
return res.status(400).json({
|
||||
error: "No logged in user to logout",
|
||||
});
|
||||
}
|
||||
|
||||
if (ctx.callbacks?.onLogout) {
|
||||
const user = await getUser(req, ctx);
|
||||
if (user) {
|
||||
await ctx.callbacks.onLogout(user, req);
|
||||
}
|
||||
}
|
||||
|
||||
// Set the access token to 'none' and expire in 5 seconds
|
||||
res.setHeader(
|
||||
"Set-Cookie",
|
||||
serialize(activeCookie, "", {
|
||||
domain: ctx.cookieOptions?.domain,
|
||||
path: ctx.cookieOptions?.path || "/",
|
||||
sameSite: ctx.cookieOptions?.sameSite || "none",
|
||||
expires: new Date(Date.now() + 5 * 1000),
|
||||
httpOnly: true,
|
||||
secure: ctx.cookieOptions?.secure || true,
|
||||
}),
|
||||
);
|
||||
|
||||
return res.status(200).json({ message: "Successfully logged out" });
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import type { ThirdwebAuthContext } from "../types";
|
||||
import { PayloadBodySchema } from "../types";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
export default async function handler(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse,
|
||||
ctx: ThirdwebAuthContext,
|
||||
) {
|
||||
if (req.method !== "POST") {
|
||||
return res.status(405).json({
|
||||
error: "Invalid method. Only POST supported",
|
||||
});
|
||||
}
|
||||
|
||||
const parsedPayload = PayloadBodySchema.safeParse(req.body);
|
||||
if (!parsedPayload.success) {
|
||||
return res.status(400).json({ error: "Please provide an address" });
|
||||
}
|
||||
|
||||
// TODO: Add nonce generation + custom expiration + invalid before
|
||||
const payload = await ctx.auth.payload({
|
||||
address: parsedPayload.data.address,
|
||||
statement: ctx.authOptions?.statement,
|
||||
uri: ctx.authOptions?.uri,
|
||||
version: ctx.authOptions?.version,
|
||||
chainId: parsedPayload.data.chainId || ctx.authOptions?.chainId,
|
||||
resources: ctx.authOptions?.resources,
|
||||
expirationTime: ctx.authOptions?.loginPayloadDurationInSeconds
|
||||
? new Date(
|
||||
Date.now() + 1000 * ctx.authOptions.loginPayloadDurationInSeconds,
|
||||
)
|
||||
: undefined,
|
||||
});
|
||||
|
||||
return res.status(200).json({ payload });
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
import { serialize } from "cookie";
|
||||
import { getCookie } from "../helpers/user";
|
||||
import type { ThirdwebAuthContext } from "../types";
|
||||
import { ActiveBodySchema } from "../types";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
import {
|
||||
THIRDWEB_AUTH_ACTIVE_ACCOUNT_COOKIE,
|
||||
THIRDWEB_AUTH_DEFAULT_TOKEN_DURATION_IN_SECONDS,
|
||||
THIRDWEB_AUTH_TOKEN_COOKIE_PREFIX,
|
||||
} from "../../../constants";
|
||||
|
||||
export default async function handler(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse,
|
||||
ctx: ThirdwebAuthContext,
|
||||
) {
|
||||
if (req.method !== "POST") {
|
||||
return res.status(405).json({
|
||||
error: "Invalid method. Only POST supported",
|
||||
});
|
||||
}
|
||||
|
||||
const parsedPayload = ActiveBodySchema.safeParse(req.body);
|
||||
if (!parsedPayload.success) {
|
||||
return res.status(400).json({ error: "Please provide an address" });
|
||||
}
|
||||
|
||||
let cookieExpiration: Date;
|
||||
const cookie = getCookie(
|
||||
req,
|
||||
`${THIRDWEB_AUTH_TOKEN_COOKIE_PREFIX}_${parsedPayload.data.address}`,
|
||||
);
|
||||
|
||||
if (cookie) {
|
||||
// If the new account is already logged in, get the expiration time from the cookie
|
||||
const {
|
||||
payload: { exp },
|
||||
} = ctx.auth.parseToken(cookie);
|
||||
cookieExpiration = new Date(exp * 1000);
|
||||
} else if (ctx.authOptions?.tokenDurationInSeconds) {
|
||||
// Otherwise, if we have a token duration in seconds, set it to that
|
||||
cookieExpiration = new Date(
|
||||
Date.now() + 1000 * ctx.authOptions.tokenDurationInSeconds,
|
||||
);
|
||||
} else {
|
||||
// Otherwise, just default to 24 hours
|
||||
cookieExpiration = new Date(
|
||||
Date.now() + 1000 * THIRDWEB_AUTH_DEFAULT_TOKEN_DURATION_IN_SECONDS,
|
||||
);
|
||||
}
|
||||
|
||||
res.setHeader("Set-Cookie", [
|
||||
serialize(THIRDWEB_AUTH_ACTIVE_ACCOUNT_COOKIE, parsedPayload.data.address, {
|
||||
domain: ctx.cookieOptions?.domain,
|
||||
path: ctx.cookieOptions?.path || "/",
|
||||
sameSite: ctx.cookieOptions?.sameSite || "none",
|
||||
expires: cookieExpiration,
|
||||
httpOnly: true,
|
||||
secure: ctx.cookieOptions?.secure || true,
|
||||
}),
|
||||
]);
|
||||
|
||||
return res.status(200).end();
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
import { serialize } from "cookie";
|
||||
import {
|
||||
THIRDWEB_AUTH_ACTIVE_ACCOUNT_COOKIE,
|
||||
THIRDWEB_AUTH_DEFAULT_REFRESH_INTERVAL_IN_SECONDS,
|
||||
THIRDWEB_AUTH_TOKEN_COOKIE_PREFIX,
|
||||
} from "../../../constants";
|
||||
import { getToken, getUser } from "../helpers/user";
|
||||
import type { ThirdwebAuthContext } from "../types";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
export default async function handler(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse,
|
||||
ctx: ThirdwebAuthContext,
|
||||
) {
|
||||
if (req.method !== "GET") {
|
||||
return res.status(405).json({
|
||||
error: "Invalid method. Only GET supported",
|
||||
});
|
||||
}
|
||||
|
||||
const user = await getUser(req, ctx);
|
||||
|
||||
// Importantly, make sure the user was actually logged in before refreshing
|
||||
if (user) {
|
||||
const token = getToken(req);
|
||||
if (token) {
|
||||
const payload = ctx.auth.parseToken(token);
|
||||
|
||||
const refreshDate = ctx.authOptions?.refreshIntervalInSeconds
|
||||
? new Date(
|
||||
payload.payload.iat * 1000 +
|
||||
ctx.authOptions.refreshIntervalInSeconds * 1000,
|
||||
)
|
||||
: new Date(
|
||||
payload.payload.iat * 1000 +
|
||||
THIRDWEB_AUTH_DEFAULT_REFRESH_INTERVAL_IN_SECONDS * 1000,
|
||||
);
|
||||
|
||||
if (new Date() > refreshDate) {
|
||||
const expirationTime = ctx.authOptions?.tokenDurationInSeconds
|
||||
? new Date(Date.now() + 1000 * ctx.authOptions.tokenDurationInSeconds)
|
||||
: undefined;
|
||||
const refreshedToken = await ctx.auth.refresh(token, expirationTime);
|
||||
const refreshedPayload = ctx.auth.parseToken(refreshedToken);
|
||||
res.setHeader("Set-Cookie", [
|
||||
serialize(
|
||||
`${THIRDWEB_AUTH_TOKEN_COOKIE_PREFIX}_${user.address}`,
|
||||
refreshedToken,
|
||||
{
|
||||
domain: ctx.cookieOptions?.domain,
|
||||
path: ctx.cookieOptions?.path || "/",
|
||||
sameSite: ctx.cookieOptions?.sameSite || "none",
|
||||
expires: new Date(refreshedPayload.payload.exp * 1000),
|
||||
httpOnly: true,
|
||||
secure: ctx.cookieOptions?.secure || true,
|
||||
},
|
||||
),
|
||||
serialize(THIRDWEB_AUTH_ACTIVE_ACCOUNT_COOKIE, user.address, {
|
||||
domain: ctx.cookieOptions?.domain,
|
||||
path: ctx.cookieOptions?.path || "/",
|
||||
sameSite: ctx.cookieOptions?.sameSite || "none",
|
||||
expires: new Date(refreshedPayload.payload.exp * 1000),
|
||||
httpOnly: true,
|
||||
secure: ctx.cookieOptions?.secure || true,
|
||||
}),
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return res.status(200).json(user);
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import type { Json, ThirdwebAuth, User } from "../../../core";
|
||||
import type { GetServerSidePropsContext, NextApiRequest } from "next";
|
||||
import type { NextRequest } from "next/server";
|
||||
import type { ThirdwebAuthConfigShared } from '../../common/types'
|
||||
|
||||
export {
|
||||
ActiveBodySchema,
|
||||
LoginPayloadBodySchema,
|
||||
PayloadBodySchema
|
||||
} from '../../common/types'
|
||||
|
||||
type RequestType =
|
||||
| GetServerSidePropsContext["req"]
|
||||
| NextRequest
|
||||
| NextApiRequest;
|
||||
|
||||
export type ThirdwebAuthConfig<
|
||||
TData extends Json = Json,
|
||||
TSession extends Json = Json,
|
||||
> = ThirdwebAuthConfigShared & {
|
||||
callbacks?: {
|
||||
onLogin?:
|
||||
| ((address: string, req: NextApiRequest) => void | TSession)
|
||||
| ((address: string, req: NextApiRequest) => Promise<void | TSession>);
|
||||
onToken?:
|
||||
| ((token: string, req: NextApiRequest) => void)
|
||||
| ((token: string, req: NextApiRequest) => Promise<void>);
|
||||
onUser?:
|
||||
| (<TRequestType extends RequestType = RequestType>(
|
||||
user: User<TSession>,
|
||||
req: TRequestType,
|
||||
) => void | TData)
|
||||
| (<TRequestType extends RequestType = RequestType>(
|
||||
user: User<TSession>,
|
||||
req: TRequestType,
|
||||
) => Promise<void | TData>);
|
||||
onLogout?:
|
||||
| ((user: User, req: NextApiRequest) => void)
|
||||
| ((user: User, req: NextApiRequest) => Promise<void>);
|
||||
};
|
||||
};
|
||||
|
||||
export type ThirdwebAuthContext<
|
||||
TData extends Json = Json,
|
||||
TSession extends Json = Json,
|
||||
> = Omit<Omit<ThirdwebAuthConfig<TData, TSession>, "wallet">, "domain"> & {
|
||||
auth: ThirdwebAuth;
|
||||
};
|
||||
@@ -0,0 +1,384 @@
|
||||
import { ThirdwebAuth } from "../src/core";
|
||||
import { EthersWallet } from "@thirdweb-dev/wallets/evm/wallets/ethers";
|
||||
import { Wallet } from "ethers";
|
||||
import { describe, it, beforeAll, beforeEach, expect } from "vitest";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
require("dotenv-mono").load();
|
||||
|
||||
describe(
|
||||
"Wallet Authentication - EVM",
|
||||
{
|
||||
timeout: 60000,
|
||||
},
|
||||
async () => {
|
||||
let adminWallet: any, signerWallet: any, attackerWallet: any;
|
||||
let auth: ThirdwebAuth;
|
||||
|
||||
beforeAll(async () => {
|
||||
const [adminSigner, signerSigner, attackerSigner] = [
|
||||
Wallet.createRandom(),
|
||||
Wallet.createRandom(),
|
||||
Wallet.createRandom(),
|
||||
];
|
||||
|
||||
adminWallet = new EthersWallet(adminSigner);
|
||||
signerWallet = new EthersWallet(signerSigner);
|
||||
attackerWallet = new EthersWallet(attackerSigner);
|
||||
|
||||
auth = new ThirdwebAuth(signerWallet, "thirdweb.com", {
|
||||
secretKey: process.env.TW_SECRET_KEY,
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
auth.updateWallet(signerWallet);
|
||||
});
|
||||
|
||||
it("Should verify logged in wallet", async () => {
|
||||
const payload = await auth.login();
|
||||
|
||||
auth.updateWallet(adminWallet);
|
||||
const address = await auth.verify(payload);
|
||||
|
||||
expect(address).to.equal(await signerWallet.getAddress());
|
||||
});
|
||||
|
||||
it("Should verify logged in wallet with chain ID and expiration", async () => {
|
||||
const payload = await auth.login({
|
||||
expirationTime: new Date(Date.now() + 1000 * 60 * 5),
|
||||
chainId: "137",
|
||||
});
|
||||
|
||||
auth.updateWallet(adminWallet);
|
||||
const address = await auth.verify(payload, {
|
||||
chainId: "137",
|
||||
});
|
||||
|
||||
expect(address).to.equal(await signerWallet.getAddress());
|
||||
});
|
||||
|
||||
it("Should verify payload with resources", async () => {
|
||||
const payload = await auth.login({
|
||||
resources: ["https://example.com", "https://test.com"],
|
||||
});
|
||||
|
||||
auth.updateWallet(adminWallet);
|
||||
const address = await auth.verify(payload, {
|
||||
resources: ["https://example.com", "https://test.com"],
|
||||
});
|
||||
|
||||
expect(address).to.equal(await signerWallet.getAddress());
|
||||
});
|
||||
|
||||
it("Should reject payload without necessary resources", async () => {
|
||||
const payload = await auth.login({
|
||||
resources: ["https://example.com"],
|
||||
});
|
||||
|
||||
auth.updateWallet(adminWallet);
|
||||
try {
|
||||
await auth.verify(payload, {
|
||||
resources: ["https://example.com", "https://test.com"],
|
||||
});
|
||||
expect.fail();
|
||||
} catch (err: any) {
|
||||
expect(err.message).to.equal(
|
||||
"Login request is missing required resources: https://test.com",
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it("Should verify payload with customized statement", async () => {
|
||||
const payload = await auth.login({
|
||||
statement: "Please sign!",
|
||||
});
|
||||
|
||||
auth.updateWallet(adminWallet);
|
||||
const address = await auth.verify(payload, {
|
||||
statement: "Please sign!",
|
||||
});
|
||||
|
||||
expect(address).to.equal(await signerWallet.getAddress());
|
||||
});
|
||||
|
||||
it("Should reject payload with incorrect statement", async () => {
|
||||
const payload = await auth.login({
|
||||
statement: "Please sign!",
|
||||
});
|
||||
|
||||
auth.updateWallet(adminWallet);
|
||||
try {
|
||||
await auth.verify(payload, {
|
||||
statement: "Please sign again!",
|
||||
});
|
||||
expect.fail();
|
||||
} catch (err: any) {
|
||||
expect(err.message).to.include(
|
||||
"Expected statement 'Please sign again!' does not match statement on payload",
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it("Should reject invalid nonce", async () => {
|
||||
const payload = await auth.login();
|
||||
|
||||
auth.updateWallet(adminWallet);
|
||||
try {
|
||||
await auth.verify(payload, {
|
||||
validateNonce: (nonce: string) => {
|
||||
if (nonce === payload.payload.nonce) {
|
||||
throw new Error();
|
||||
}
|
||||
},
|
||||
});
|
||||
expect.fail();
|
||||
} catch (err: any) {
|
||||
expect(err.message).to.equal("Login request nonce is invalid");
|
||||
}
|
||||
});
|
||||
|
||||
it("Should accept valid nonce", async () => {
|
||||
const payload = await auth.login();
|
||||
|
||||
auth.updateWallet(adminWallet);
|
||||
const address = await auth.verify(payload, {
|
||||
validateNonce: (nonce: string) => {
|
||||
if (nonce !== payload.payload.nonce) {
|
||||
throw new Error();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
expect(address).to.equal(await signerWallet.getAddress());
|
||||
});
|
||||
|
||||
it("Should reject payload with incorrect domain", async () => {
|
||||
const payload = await auth.login();
|
||||
|
||||
auth.updateWallet(adminWallet);
|
||||
try {
|
||||
await auth.verify(payload, { domain: "test.thirdweb.com" });
|
||||
expect.fail();
|
||||
} catch (err: any) {
|
||||
expect(err.message).to.equal(
|
||||
"Expected domain 'test.thirdweb.com' does not match domain on payload 'thirdweb.com'",
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it("Should reject expired login payload", async () => {
|
||||
const payload = await auth.login({
|
||||
expirationTime: new Date(Date.now() - 1000 * 60 * 5),
|
||||
});
|
||||
|
||||
auth.updateWallet(adminWallet);
|
||||
try {
|
||||
await auth.verify(payload);
|
||||
expect.fail();
|
||||
} catch (err: any) {
|
||||
expect(err.message).to.equal("Login request has expired");
|
||||
}
|
||||
});
|
||||
|
||||
it("Should reject payload with incorrect chain ID", async () => {
|
||||
const payload = await auth.login({
|
||||
chainId: "1",
|
||||
});
|
||||
|
||||
auth.updateWallet(adminWallet);
|
||||
try {
|
||||
await auth.verify(payload, {
|
||||
chainId: "137",
|
||||
});
|
||||
expect.fail();
|
||||
} catch (err: any) {
|
||||
expect(err.message).to.equal(
|
||||
"Expected chain ID '137' does not match chain ID on payload '1'",
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it("Should reject payload with incorrect signer", async () => {
|
||||
const payload = await auth.login();
|
||||
payload.payload.address = await attackerWallet.getAddress();
|
||||
|
||||
auth.updateWallet(adminWallet);
|
||||
try {
|
||||
await auth.verify(payload);
|
||||
expect.fail();
|
||||
} catch (err: any) {
|
||||
expect(err.message).to.contain("does not match payload address");
|
||||
}
|
||||
});
|
||||
|
||||
it("Should generate valid authentication token", async () => {
|
||||
const payload = await auth.login();
|
||||
|
||||
auth.updateWallet(adminWallet);
|
||||
const token = await auth.generate(payload);
|
||||
const user = await auth.authenticate(token);
|
||||
|
||||
expect(user.address).to.equal(await signerWallet.getAddress());
|
||||
});
|
||||
|
||||
it("Should reject token with incorrect domain", async () => {
|
||||
const payload = await auth.login();
|
||||
|
||||
auth.updateWallet(adminWallet);
|
||||
const token = await auth.generate(payload);
|
||||
|
||||
try {
|
||||
await auth.authenticate(token, { domain: "test.thirdweb.com" });
|
||||
expect.fail();
|
||||
} catch (err: any) {
|
||||
expect(err.message).to.contain(
|
||||
"Expected token to be for the domain 'test.thirdweb.com', but found token with domain 'thirdweb.com'",
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it("Should reject token before invalid before", async () => {
|
||||
const payload = await auth.login();
|
||||
|
||||
auth.updateWallet(adminWallet);
|
||||
const token = await auth.generate(payload, {
|
||||
invalidBefore: new Date(Date.now() + 1000 * 60 * 5),
|
||||
});
|
||||
|
||||
try {
|
||||
await auth.authenticate(token);
|
||||
expect.fail();
|
||||
} catch (err: any) {
|
||||
expect(err.message).to.contain("This token is invalid before");
|
||||
}
|
||||
});
|
||||
|
||||
it("Should reject expired authentication token", async () => {
|
||||
const payload = await auth.login();
|
||||
|
||||
auth.updateWallet(adminWallet);
|
||||
const token = await auth.generate(payload, {
|
||||
expirationTime: new Date(Date.now() - 1000 * 60 * 5),
|
||||
});
|
||||
|
||||
try {
|
||||
await auth.authenticate(token);
|
||||
expect.fail();
|
||||
} catch (err: any) {
|
||||
expect(err.message).to.contain("This token expired");
|
||||
}
|
||||
});
|
||||
|
||||
it("Should reject if admin address is not connected wallet address", async () => {
|
||||
const payload = await auth.login();
|
||||
|
||||
auth.updateWallet(adminWallet);
|
||||
const token = await auth.generate(payload);
|
||||
|
||||
auth.updateWallet(signerWallet);
|
||||
try {
|
||||
await auth.authenticate(token);
|
||||
expect.fail();
|
||||
} catch (err: any) {
|
||||
expect(err.message).to.contain(
|
||||
`The expected issuer address '${await signerWallet.getAddress()}' did not match the token issuer address '${await adminWallet.getAddress()}'`,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it("Should accept token with valid token ID", async () => {
|
||||
const payload = await auth.login();
|
||||
|
||||
auth.updateWallet(adminWallet);
|
||||
const token = await auth.generate(payload, {
|
||||
tokenId: "test",
|
||||
});
|
||||
|
||||
const user = await auth.authenticate(token, {
|
||||
validateTokenId: (tokenId: string) => {
|
||||
if (tokenId !== "test") {
|
||||
throw new Error();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
expect(user.address).to.equal(await signerWallet.getAddress());
|
||||
});
|
||||
|
||||
it("Should reject token with invalid token ID", async () => {
|
||||
const payload = await auth.login();
|
||||
|
||||
auth.updateWallet(adminWallet);
|
||||
const token = await auth.generate(payload, {
|
||||
tokenId: "test",
|
||||
});
|
||||
|
||||
try {
|
||||
await auth.authenticate(token, {
|
||||
validateTokenId: (tokenId: string) => {
|
||||
if (tokenId !== "invalid") {
|
||||
throw new Error();
|
||||
}
|
||||
},
|
||||
});
|
||||
expect.fail();
|
||||
} catch (err: any) {
|
||||
expect(err.message).to.contain("Token ID is invalid");
|
||||
}
|
||||
});
|
||||
|
||||
it("Should propagate session on token", async () => {
|
||||
const payload = await auth.login();
|
||||
|
||||
auth.updateWallet(adminWallet);
|
||||
const token = await auth.generate(payload, {
|
||||
session: { role: "admin" },
|
||||
});
|
||||
|
||||
const user = await auth.authenticate(token);
|
||||
|
||||
expect(user.address).to.equal(await signerWallet.getAddress());
|
||||
expect(user.session).to.deep.equal({ role: "admin" });
|
||||
});
|
||||
|
||||
it("Should call session callback function", async () => {
|
||||
const payload = await auth.login();
|
||||
|
||||
auth.updateWallet(adminWallet);
|
||||
const token = await auth.generate(payload, {
|
||||
session: (address: string) => {
|
||||
return { address, role: "admin" };
|
||||
},
|
||||
});
|
||||
|
||||
const user = await auth.authenticate(token);
|
||||
|
||||
expect(user.address).to.equal(await signerWallet.getAddress());
|
||||
expect(user.session).to.deep.equal({
|
||||
address: await signerWallet.getAddress(),
|
||||
role: "admin",
|
||||
});
|
||||
});
|
||||
|
||||
it("Should authenticate with issuer address", async () => {
|
||||
const payload = await auth.login();
|
||||
|
||||
auth.updateWallet(adminWallet);
|
||||
const token = await auth.generate(payload);
|
||||
|
||||
auth.updateWallet(attackerWallet);
|
||||
try {
|
||||
await auth.authenticate(token);
|
||||
expect.fail();
|
||||
} catch (err: any) {
|
||||
expect(err.message).to.contain("The expected issuer address");
|
||||
}
|
||||
|
||||
const user = await auth.authenticate(token, {
|
||||
issuerAddress: await adminWallet.getAddress(),
|
||||
});
|
||||
expect(user.address).to.equal(await signerWallet.getAddress());
|
||||
});
|
||||
},
|
||||
);
|
||||
@@ -0,0 +1,391 @@
|
||||
import { LocalWallet, SmartWallet } from "@thirdweb-dev/wallets";
|
||||
import { ThirdwebAuth } from "../src/core";
|
||||
import { EthersWallet } from "@thirdweb-dev/wallets/evm/wallets/ethers";
|
||||
import { Wallet } from "ethers";
|
||||
import { describe, it, beforeEach, beforeAll, expect } from "vitest";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
require("dotenv-mono").load();
|
||||
|
||||
describe(
|
||||
"Wallet Authentication - EVM - Smart Wallet",
|
||||
{
|
||||
timeout: 60000,
|
||||
},
|
||||
async () => {
|
||||
let adminWallet: any, signerWallet: any, attackerWallet: any;
|
||||
let auth: ThirdwebAuth;
|
||||
|
||||
beforeAll(async () => {
|
||||
const factoryAddress = "0xbf1C9aA4B1A085f7DA890a44E82B0A1289A40052";
|
||||
const chain = 421614;
|
||||
const localWallet = new LocalWallet();
|
||||
await localWallet.generate();
|
||||
const smartWallet = new SmartWallet({
|
||||
chain: chain,
|
||||
factoryAddress: factoryAddress,
|
||||
gasless: true,
|
||||
secretKey: process.env.TW_SECRET_KEY,
|
||||
});
|
||||
await smartWallet.connect({ personalWallet: localWallet });
|
||||
|
||||
adminWallet = new EthersWallet(Wallet.createRandom());
|
||||
signerWallet = smartWallet;
|
||||
attackerWallet = new EthersWallet(Wallet.createRandom());
|
||||
|
||||
auth = new ThirdwebAuth(signerWallet, "thirdweb.com", {
|
||||
secretKey: process.env.TW_SECRET_KEY,
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
auth.updateWallet(signerWallet);
|
||||
});
|
||||
|
||||
it("Should verify logged in wallet", async () => {
|
||||
const payload = await auth.login();
|
||||
|
||||
auth.updateWallet(adminWallet);
|
||||
const address = await auth.verify(payload);
|
||||
|
||||
expect(address).to.equal(await signerWallet.getAddress());
|
||||
});
|
||||
|
||||
it("Should verify logged in wallet with chain ID and expiration", async () => {
|
||||
const payload = await auth.login({
|
||||
expirationTime: new Date(Date.now() + 1000 * 60 * 5),
|
||||
chainId: "421614",
|
||||
});
|
||||
|
||||
auth.updateWallet(adminWallet);
|
||||
const address = await auth.verify(payload, {
|
||||
chainId: "421614",
|
||||
});
|
||||
|
||||
expect(address).to.equal(await signerWallet.getAddress());
|
||||
});
|
||||
|
||||
it("Should verify payload with resources", async () => {
|
||||
const payload = await auth.login({
|
||||
resources: ["https://example.com", "https://test.com"],
|
||||
});
|
||||
|
||||
auth.updateWallet(adminWallet);
|
||||
const address = await auth.verify(payload, {
|
||||
resources: ["https://example.com", "https://test.com"],
|
||||
});
|
||||
|
||||
expect(address).to.equal(await signerWallet.getAddress());
|
||||
});
|
||||
|
||||
it("Should reject payload without necessary resources", async () => {
|
||||
const payload = await auth.login({
|
||||
resources: ["https://example.com"],
|
||||
});
|
||||
|
||||
auth.updateWallet(adminWallet);
|
||||
try {
|
||||
await auth.verify(payload, {
|
||||
resources: ["https://example.com", "https://test.com"],
|
||||
});
|
||||
expect.fail();
|
||||
} catch (err: any) {
|
||||
expect(err.message).to.equal(
|
||||
"Login request is missing required resources: https://test.com",
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it("Should verify payload with customized statement", async () => {
|
||||
const payload = await auth.login({
|
||||
statement: "Please sign!",
|
||||
});
|
||||
|
||||
auth.updateWallet(adminWallet);
|
||||
const address = await auth.verify(payload, {
|
||||
statement: "Please sign!",
|
||||
});
|
||||
|
||||
expect(address).to.equal(await signerWallet.getAddress());
|
||||
});
|
||||
|
||||
it("Should reject payload with incorrect statement", async () => {
|
||||
const payload = await auth.login({
|
||||
statement: "Please sign!",
|
||||
});
|
||||
|
||||
auth.updateWallet(adminWallet);
|
||||
try {
|
||||
await auth.verify(payload, {
|
||||
statement: "Please sign again!",
|
||||
});
|
||||
expect.fail();
|
||||
} catch (err: any) {
|
||||
expect(err.message).to.include(
|
||||
"Expected statement 'Please sign again!' does not match statement on payload",
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it("Should reject invalid nonce", async () => {
|
||||
const payload = await auth.login();
|
||||
|
||||
auth.updateWallet(adminWallet);
|
||||
try {
|
||||
await auth.verify(payload, {
|
||||
validateNonce: (nonce: string) => {
|
||||
if (nonce === payload.payload.nonce) {
|
||||
throw new Error();
|
||||
}
|
||||
},
|
||||
});
|
||||
expect.fail();
|
||||
} catch (err: any) {
|
||||
expect(err.message).to.equal("Login request nonce is invalid");
|
||||
}
|
||||
});
|
||||
|
||||
it("Should accept valid nonce", async () => {
|
||||
const payload = await auth.login();
|
||||
|
||||
auth.updateWallet(adminWallet);
|
||||
const address = await auth.verify(payload, {
|
||||
validateNonce: (nonce: string) => {
|
||||
if (nonce !== payload.payload.nonce) {
|
||||
throw new Error();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
expect(address).to.equal(await signerWallet.getAddress());
|
||||
});
|
||||
|
||||
it("Should reject payload with incorrect domain", async () => {
|
||||
const payload = await auth.login();
|
||||
|
||||
auth.updateWallet(adminWallet);
|
||||
try {
|
||||
await auth.verify(payload, { domain: "test.thirdweb.com" });
|
||||
expect.fail();
|
||||
} catch (err: any) {
|
||||
expect(err.message).to.equal(
|
||||
"Expected domain 'test.thirdweb.com' does not match domain on payload 'thirdweb.com'",
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it("Should reject expired login payload", async () => {
|
||||
const payload = await auth.login({
|
||||
expirationTime: new Date(Date.now() - 1000 * 60 * 5),
|
||||
});
|
||||
|
||||
auth.updateWallet(adminWallet);
|
||||
try {
|
||||
await auth.verify(payload);
|
||||
expect.fail();
|
||||
} catch (err: any) {
|
||||
expect(err.message).to.equal("Login request has expired");
|
||||
}
|
||||
});
|
||||
|
||||
it("Should reject payload with incorrect chain ID", async () => {
|
||||
const payload = await auth.login({
|
||||
chainId: "1",
|
||||
});
|
||||
|
||||
auth.updateWallet(adminWallet);
|
||||
try {
|
||||
await auth.verify(payload, {
|
||||
chainId: "137",
|
||||
});
|
||||
expect.fail();
|
||||
} catch (err: any) {
|
||||
expect(err.message).to.equal(
|
||||
"Expected chain ID '137' does not match chain ID on payload '1'",
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it("Should reject payload with incorrect signer", async () => {
|
||||
const payload = await auth.login();
|
||||
payload.payload.address = await attackerWallet.getAddress();
|
||||
|
||||
auth.updateWallet(adminWallet);
|
||||
try {
|
||||
await auth.verify(payload);
|
||||
expect.fail();
|
||||
} catch (err: any) {
|
||||
expect(err.message).to.contain("does not match payload address");
|
||||
}
|
||||
});
|
||||
|
||||
it("Should generate valid authentication token", async () => {
|
||||
const payload = await auth.login();
|
||||
|
||||
auth.updateWallet(adminWallet);
|
||||
const token = await auth.generate(payload);
|
||||
const user = await auth.authenticate(token);
|
||||
|
||||
expect(user.address).to.equal(await signerWallet.getAddress());
|
||||
});
|
||||
|
||||
it("Should reject token with incorrect domain", async () => {
|
||||
const payload = await auth.login();
|
||||
|
||||
auth.updateWallet(adminWallet);
|
||||
const token = await auth.generate(payload);
|
||||
|
||||
try {
|
||||
await auth.authenticate(token, { domain: "test.thirdweb.com" });
|
||||
expect.fail();
|
||||
} catch (err: any) {
|
||||
expect(err.message).to.contain(
|
||||
"Expected token to be for the domain 'test.thirdweb.com', but found token with domain 'thirdweb.com'",
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it("Should reject token before invalid before", async () => {
|
||||
const payload = await auth.login();
|
||||
|
||||
auth.updateWallet(adminWallet);
|
||||
const token = await auth.generate(payload, {
|
||||
invalidBefore: new Date(Date.now() + 1000 * 60 * 5),
|
||||
});
|
||||
|
||||
try {
|
||||
await auth.authenticate(token);
|
||||
expect.fail();
|
||||
} catch (err: any) {
|
||||
expect(err.message).to.contain("This token is invalid before");
|
||||
}
|
||||
});
|
||||
|
||||
it("Should reject expired authentication token", async () => {
|
||||
const payload = await auth.login();
|
||||
|
||||
auth.updateWallet(adminWallet);
|
||||
const token = await auth.generate(payload, {
|
||||
expirationTime: new Date(Date.now() - 1000 * 60 * 5),
|
||||
});
|
||||
|
||||
try {
|
||||
await auth.authenticate(token);
|
||||
expect.fail();
|
||||
} catch (err: any) {
|
||||
expect(err.message).to.contain("This token expired");
|
||||
}
|
||||
});
|
||||
|
||||
it("Should reject if admin address is not connected wallet address", async () => {
|
||||
const payload = await auth.login();
|
||||
|
||||
auth.updateWallet(adminWallet);
|
||||
const token = await auth.generate(payload);
|
||||
|
||||
auth.updateWallet(signerWallet);
|
||||
try {
|
||||
await auth.authenticate(token);
|
||||
expect.fail();
|
||||
} catch (err: any) {
|
||||
expect(err.message).to.contain(
|
||||
`The expected issuer address '${await signerWallet.getAddress()}' did not match the token issuer address '${await adminWallet.getAddress()}'`,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it("Should accept token with valid token ID", async () => {
|
||||
const payload = await auth.login();
|
||||
|
||||
auth.updateWallet(adminWallet);
|
||||
const token = await auth.generate(payload, {
|
||||
tokenId: "test",
|
||||
});
|
||||
|
||||
const user = await auth.authenticate(token, {
|
||||
validateTokenId: (tokenId: string) => {
|
||||
if (tokenId !== "test") {
|
||||
throw new Error();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
expect(user.address).to.equal(await signerWallet.getAddress());
|
||||
});
|
||||
|
||||
it("Should reject token with invalid token ID", async () => {
|
||||
const payload = await auth.login();
|
||||
|
||||
auth.updateWallet(adminWallet);
|
||||
const token = await auth.generate(payload, {
|
||||
tokenId: "test",
|
||||
});
|
||||
|
||||
try {
|
||||
await auth.authenticate(token, {
|
||||
validateTokenId: (tokenId: string) => {
|
||||
if (tokenId !== "invalid") {
|
||||
throw new Error();
|
||||
}
|
||||
},
|
||||
});
|
||||
expect.fail();
|
||||
} catch (err: any) {
|
||||
expect(err.message).to.contain("Token ID is invalid");
|
||||
}
|
||||
});
|
||||
|
||||
it("Should propagate session on token", async () => {
|
||||
const payload = await auth.login();
|
||||
|
||||
auth.updateWallet(adminWallet);
|
||||
const token = await auth.generate(payload, {
|
||||
session: { role: "admin" },
|
||||
});
|
||||
|
||||
const user = await auth.authenticate(token);
|
||||
|
||||
expect(user.address).to.equal(await signerWallet.getAddress());
|
||||
expect(user.session).to.deep.equal({ role: "admin" });
|
||||
});
|
||||
|
||||
it("Should call session callback function", async () => {
|
||||
const payload = await auth.login();
|
||||
|
||||
auth.updateWallet(adminWallet);
|
||||
const token = await auth.generate(payload, {
|
||||
session: (address: string) => {
|
||||
return { address, role: "admin" };
|
||||
},
|
||||
});
|
||||
|
||||
const user = await auth.authenticate(token);
|
||||
|
||||
expect(user.address).to.equal(await signerWallet.getAddress());
|
||||
expect(user.session).to.deep.equal({
|
||||
address: await signerWallet.getAddress(),
|
||||
role: "admin",
|
||||
});
|
||||
});
|
||||
|
||||
it("Should authenticate with issuer address", async () => {
|
||||
const payload = await auth.login();
|
||||
|
||||
auth.updateWallet(adminWallet);
|
||||
const token = await auth.generate(payload);
|
||||
|
||||
auth.updateWallet(attackerWallet);
|
||||
try {
|
||||
await auth.authenticate(token);
|
||||
expect.fail();
|
||||
} catch (err: any) {
|
||||
expect(err.message).to.contain("The expected issuer address");
|
||||
}
|
||||
|
||||
const user = await auth.authenticate(token, {
|
||||
issuerAddress: await adminWallet.getAddress(),
|
||||
});
|
||||
expect(user.address).to.equal(await signerWallet.getAddress());
|
||||
});
|
||||
},
|
||||
);
|
||||
@@ -0,0 +1,729 @@
|
||||
# @thirdweb-dev/chains
|
||||
|
||||
## 0.1.84
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#2577](https://github.com/thirdweb-dev/js/pull/2577) [`d93286b`](https://github.com/thirdweb-dev/js/commit/d93286bc1f8224d055b50ce3ffa4f302869cb2b1) Thanks [@jnsdls](https://github.com/jnsdls)! - update dependencies
|
||||
|
||||
- [#2580](https://github.com/thirdweb-dev/js/pull/2580) [`50e7dba`](https://github.com/thirdweb-dev/js/commit/50e7dbaa9dae77cb8cd865405c322495e2498c65) Thanks [@github-actions](https://github.com/apps/github-actions)! - Synced Chains Package
|
||||
|
||||
## 0.1.83
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#2559](https://github.com/thirdweb-dev/js/pull/2559) [`095d227`](https://github.com/thirdweb-dev/js/commit/095d227a13d0c9c1de76fa3812b7f71b2130d532) Thanks [@github-actions](https://github.com/apps/github-actions)! - Synced Chains Package
|
||||
|
||||
## 0.1.82
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#2527](https://github.com/thirdweb-dev/js/pull/2527) [`cb0ef6a`](https://github.com/thirdweb-dev/js/commit/cb0ef6a6a25cee255e18b8489fdb4123bb69c279) Thanks [@github-actions](https://github.com/apps/github-actions)! - Synced Chains Package
|
||||
|
||||
## 0.1.81
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#2496](https://github.com/thirdweb-dev/js/pull/2496) [`863049d`](https://github.com/thirdweb-dev/js/commit/863049dc0ca989d2654b4aacba811263931a33fe) Thanks [@jnsdls](https://github.com/jnsdls)! - do not append `?bundleId` query param to rpc url if the bundle id is undefined
|
||||
|
||||
- [#2427](https://github.com/thirdweb-dev/js/pull/2427) [`8a20aa5`](https://github.com/thirdweb-dev/js/commit/8a20aa58259edc5bac578dc5e4f4d971ed7adf36) Thanks [@github-actions](https://github.com/apps/github-actions)! - Synced Chains Package
|
||||
|
||||
## 0.1.80
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#2484](https://github.com/thirdweb-dev/js/pull/2484) [`c494db8`](https://github.com/thirdweb-dev/js/commit/c494db87cbfa4b6fc4013abb01936e86a4350ea8) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - Update chains
|
||||
|
||||
## 0.1.79
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#2464](https://github.com/thirdweb-dev/js/pull/2464) [`2e01627`](https://github.com/thirdweb-dev/js/commit/2e01627c998a49a1dd41041f3743a28488c92697) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - Sync chains
|
||||
|
||||
## 0.1.78
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#2416](https://github.com/thirdweb-dev/js/pull/2416) [`588897d`](https://github.com/thirdweb-dev/js/commit/588897d1b04e6c63da13639aeb47ca701308e1b2) Thanks [@github-actions](https://github.com/apps/github-actions)! - Synced Chains Package
|
||||
|
||||
## 0.1.77
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#2406](https://github.com/thirdweb-dev/js/pull/2406) [`047fbe2`](https://github.com/thirdweb-dev/js/commit/047fbe2918808cbc8fded90104458b6e38d1207d) Thanks [@github-actions](https://github.com/apps/github-actions)! - Synced Chains Package
|
||||
|
||||
## 0.1.76
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#2378](https://github.com/thirdweb-dev/js/pull/2378) [`5f243bb`](https://github.com/thirdweb-dev/js/commit/5f243bba0ded5dace9344c79e8904a4a342d16af) Thanks [@github-actions](https://github.com/apps/github-actions)! - Synced Chains Package
|
||||
|
||||
## 0.1.75
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#2372](https://github.com/thirdweb-dev/js/pull/2372) [`e87292a`](https://github.com/thirdweb-dev/js/commit/e87292ab604897ddfc337d24d2d1fe0f3560baae) Thanks [@github-actions](https://github.com/apps/github-actions)! - Synced Chains Package
|
||||
|
||||
## 0.1.74
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#2336](https://github.com/thirdweb-dev/js/pull/2336) [`abc0a53`](https://github.com/thirdweb-dev/js/commit/abc0a53dc398264f205d194e3f87a79f74e91eb1) Thanks [@github-actions](https://github.com/apps/github-actions)! - Synced Chains Package
|
||||
|
||||
## 0.1.73
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#2285](https://github.com/thirdweb-dev/js/pull/2285) [`ca70cd3`](https://github.com/thirdweb-dev/js/commit/ca70cd3882b39ffd56275d267e6af9c04fae179b) Thanks [@github-actions](https://github.com/apps/github-actions)! - Synced Chains Package
|
||||
|
||||
## 0.1.72
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#2280](https://github.com/thirdweb-dev/js/pull/2280) [`23d0e553`](https://github.com/thirdweb-dev/js/commit/23d0e553088321edfe3971950c2e7f27209d9aaf) Thanks [@github-actions](https://github.com/apps/github-actions)! - Synced Chains Package
|
||||
|
||||
## 0.1.71
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#2258](https://github.com/thirdweb-dev/js/pull/2258) [`357886e3`](https://github.com/thirdweb-dev/js/commit/357886e31b37aad950731ce8f013a101190ebd07) Thanks [@github-actions](https://github.com/apps/github-actions)! - Synced Chains Package
|
||||
|
||||
## 0.1.70
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#2255](https://github.com/thirdweb-dev/js/pull/2255) [`1e50caee`](https://github.com/thirdweb-dev/js/commit/1e50caeed1b85f91a6d2efb2e5204f3acf300134) Thanks [@github-actions](https://github.com/apps/github-actions)! - Synced Chains Package
|
||||
|
||||
## 0.1.69
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#2247](https://github.com/thirdweb-dev/js/pull/2247) [`374671fd`](https://github.com/thirdweb-dev/js/commit/374671fd9ac97a93263b09c6c0a53cfd129881d7) Thanks [@github-actions](https://github.com/apps/github-actions)! - Synced Chains Package
|
||||
|
||||
## 0.1.68
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#2245](https://github.com/thirdweb-dev/js/pull/2245) [`771bd1e1`](https://github.com/thirdweb-dev/js/commit/771bd1e1b280681992195103c22aafa1fb98f7e0) Thanks [@github-actions](https://github.com/apps/github-actions)! - Synced Chains Package
|
||||
|
||||
## 0.1.67
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#2243](https://github.com/thirdweb-dev/js/pull/2243) [`39e79282`](https://github.com/thirdweb-dev/js/commit/39e79282acb70959ba8140c779fa521c41c80a5f) Thanks [@github-actions](https://github.com/apps/github-actions)! - Synced Chains Package
|
||||
|
||||
- [#2240](https://github.com/thirdweb-dev/js/pull/2240) [`69c3eb91`](https://github.com/thirdweb-dev/js/commit/69c3eb910feb2265833a07b7472e32fac5a79d90) Thanks [@jnsdls](https://github.com/jnsdls)! - sync chains
|
||||
|
||||
## 0.1.66
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#2232](https://github.com/thirdweb-dev/js/pull/2232) [`627d2099`](https://github.com/thirdweb-dev/js/commit/627d20992770670ab0c954ceea0b8679ce4f3088) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - Update chains
|
||||
|
||||
## 0.1.65
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#2203](https://github.com/thirdweb-dev/js/pull/2203) [`3f2ee85a`](https://github.com/thirdweb-dev/js/commit/3f2ee85a1ab70e74aefe7f458379a2ccaf4058a9) Thanks [@iketw](https://github.com/iketw)! - Syncs Chains
|
||||
|
||||
## 0.1.64
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#2187](https://github.com/thirdweb-dev/js/pull/2187) [`e21f7bb1`](https://github.com/thirdweb-dev/js/commit/e21f7bb171334fa3003c954060e60c1ce8c1228f) Thanks [@gerrysaporito](https://github.com/gerrysaporito)! - synced chains package with chains db
|
||||
|
||||
## 0.1.63
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#2145](https://github.com/thirdweb-dev/js/pull/2145) [`d3637a16`](https://github.com/thirdweb-dev/js/commit/d3637a160d088dde6e35dc213ee611b73b831c3f) Thanks [@jnsdls](https://github.com/jnsdls)! - update chains
|
||||
|
||||
## 0.1.62
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#2084](https://github.com/thirdweb-dev/js/pull/2084) [`c410a1c1`](https://github.com/thirdweb-dev/js/commit/c410a1c18a704d118555215db9ba7d92c3b24d8c) Thanks [@MananTank](https://github.com/MananTank)! - JSDoc comments Improvements
|
||||
|
||||
## 0.1.61
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#2066](https://github.com/thirdweb-dev/js/pull/2066) [`d2001ca4`](https://github.com/thirdweb-dev/js/commit/d2001ca464aa699ac821c97eb37de7409ba865f3) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - Updatechains
|
||||
|
||||
## 0.1.60
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#2019](https://github.com/thirdweb-dev/js/pull/2019) [`b8332500`](https://github.com/thirdweb-dev/js/commit/b833250053320c8608109053f5cffe2dc96ce70a) Thanks [@jnsdls](https://github.com/jnsdls)! - add & update chains
|
||||
|
||||
- [#2024](https://github.com/thirdweb-dev/js/pull/2024) [`28fc3736`](https://github.com/thirdweb-dev/js/commit/28fc3736aa30c89690084aa2c62556c183796352) Thanks [@kien-ngo](https://github.com/kien-ngo)! - Add Base Sepolia to defaultChains
|
||||
|
||||
- [#2023](https://github.com/thirdweb-dev/js/pull/2023) [`06805217`](https://github.com/thirdweb-dev/js/commit/06805217c26de203a57c21246acba22def8a78fa) Thanks [@kien-ngo](https://github.com/kien-ngo)! - Add Eth Sepolia to defaultChains
|
||||
|
||||
## 0.1.59
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#1967](https://github.com/thirdweb-dev/js/pull/1967) [`47b0ed51`](https://github.com/thirdweb-dev/js/commit/47b0ed5171be5608ae23c19481a1b04948443c6a) Thanks [@kien-ngo](https://github.com/kien-ngo)! - Add Arbitrum Sepolia to defaultChains
|
||||
|
||||
- [#1990](https://github.com/thirdweb-dev/js/pull/1990) [`d4ce855e`](https://github.com/thirdweb-dev/js/commit/d4ce855e5f5e6c4206a3efa250e92e690ae87281) Thanks [@jnsdls](https://github.com/jnsdls)! - deprecate `allChains` `getChainById` and `getChainBySlug`
|
||||
|
||||
all of these would (necessarily) pull in every single chain in the package (>600kb)
|
||||
|
||||
instead these can be replaced with their async variants:
|
||||
|
||||
- `allChains` -> `await fetchChains()`
|
||||
- `getChainById(chainId)` -> `await getChainByIdAsync(chainId)`
|
||||
- `getChainBySlug(chainSlug)` -> `await getChainBySlugAsync(chainSlug)`
|
||||
|
||||
these async functions will return the same data as their sync counterparts, but without having to pull in every single chain at build-time.
|
||||
|
||||
## 0.1.58
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#1911](https://github.com/thirdweb-dev/js/pull/1911) [`0dcb15b7`](https://github.com/thirdweb-dev/js/commit/0dcb15b7e647acd038b0ec8b1a2b200808aae00e) Thanks [@iketw](https://github.com/iketw)! - Syncs Chains
|
||||
|
||||
## 0.1.57
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#1865](https://github.com/thirdweb-dev/js/pull/1865) [`a75e4cc8`](https://github.com/thirdweb-dev/js/commit/a75e4cc80a5a36bf6baeeb40e8ae3be485d35618) Thanks [@jnsdls](https://github.com/jnsdls)! - sync chains
|
||||
|
||||
## 0.1.56
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#1843](https://github.com/thirdweb-dev/js/pull/1843) [`6028a881`](https://github.com/thirdweb-dev/js/commit/6028a88111d9071155370c7aeaf22d4ee0c3ec93) Thanks [@jnsdls](https://github.com/jnsdls)! - sync chains
|
||||
|
||||
- [#1855](https://github.com/thirdweb-dev/js/pull/1855) [`b6f72c56`](https://github.com/thirdweb-dev/js/commit/b6f72c566c9cec5c2d0a0ebe709d6177b2af68e4) Thanks [@jnsdls](https://github.com/jnsdls)! - require minimum node version: `>=18`
|
||||
|
||||
- [#1805](https://github.com/thirdweb-dev/js/pull/1805) [`cc651135`](https://github.com/thirdweb-dev/js/commit/cc6511351fea568246ddf49f687a5616d484d2a4) Thanks [@kumaryash90](https://github.com/kumaryash90)! - Spicy Chain deployments
|
||||
|
||||
## 0.1.55
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#1783](https://github.com/thirdweb-dev/js/pull/1783) [`137f46a5`](https://github.com/thirdweb-dev/js/commit/137f46a5470d2b5f9d7f9eda9b2d839a53ddeb64) Thanks [@jnsdls](https://github.com/jnsdls)! - sync chains
|
||||
|
||||
## 0.1.54
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#1702](https://github.com/thirdweb-dev/js/pull/1702) [`f64b7236`](https://github.com/thirdweb-dev/js/commit/f64b7236bbcc5b15fea582db22f120d71d9e126f) Thanks [@jnsdls](https://github.com/jnsdls)! - updated chains
|
||||
|
||||
## 0.1.53
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#1631](https://github.com/thirdweb-dev/js/pull/1631) [`dd3d1a87`](https://github.com/thirdweb-dev/js/commit/dd3d1a87c2dadbadecc9ac3722941a8992bc8131) Thanks [@jnsdls](https://github.com/jnsdls)! - add & update chains
|
||||
|
||||
- [#1662](https://github.com/thirdweb-dev/js/pull/1662) [`d5fafdde`](https://github.com/thirdweb-dev/js/commit/d5fafddea58bc307c9b514a1c9578cafd18b5861) Thanks [@jnsdls](https://github.com/jnsdls)! - added & updated chains
|
||||
|
||||
## 0.1.52
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#1534](https://github.com/thirdweb-dev/js/pull/1534) [`48295c06`](https://github.com/thirdweb-dev/js/commit/48295c060499371035980d08e362d9858d0fc18b) Thanks [@ElasticBottle](https://github.com/ElasticBottle)! - feat(chains): add new chain information
|
||||
|
||||
## 0.1.51
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#1607](https://github.com/thirdweb-dev/js/pull/1607) [`8b73abfd`](https://github.com/thirdweb-dev/js/commit/8b73abfd83c7a8235f5d65f07dc3ad1296b40ae0) Thanks [@nkrishang](https://github.com/nkrishang)! - Use IAccountFactoryCore
|
||||
|
||||
- [#1599](https://github.com/thirdweb-dev/js/pull/1599) [`3056c34c`](https://github.com/thirdweb-dev/js/commit/3056c34c646e1a8c80f1323899c163e0fa867fd1) Thanks [@MananTank](https://github.com/MananTank)! - Update chains
|
||||
|
||||
## 0.1.50
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#1558](https://github.com/thirdweb-dev/js/pull/1558) [`926dd7b0`](https://github.com/thirdweb-dev/js/commit/926dd7b03f38ed25ca303dc23d3323d5edd28005) Thanks [@Marfuen](https://github.com/Marfuen)! - Updating chains list
|
||||
|
||||
## 0.1.49
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#1540](https://github.com/thirdweb-dev/js/pull/1540) [`b30566c6`](https://github.com/thirdweb-dev/js/commit/b30566c68436ad94ddc938a380eccc13a8a7147d) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - updated chains
|
||||
|
||||
- [#1422](https://github.com/thirdweb-dev/js/pull/1422) [`0f027069`](https://github.com/thirdweb-dev/js/commit/0f027069064bebe647f9235fa86ef7f165ffc7b3) Thanks [@Marfuen](https://github.com/Marfuen)! - CLI will now use an OAuth like login flow to authenticate the device with the wallet on the dashboard.
|
||||
Any actions performed by the CLI will be linked to your account / wallet on the dashboard, including but not limited to tracking your usage, and billing as needed.
|
||||
|
||||
- [#1551](https://github.com/thirdweb-dev/js/pull/1551) [`f5aed34d`](https://github.com/thirdweb-dev/js/commit/f5aed34d3c71065c3f45df2c1eb84ba9c36162d5) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - Update chains
|
||||
|
||||
## 0.1.48
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#1530](https://github.com/thirdweb-dev/js/pull/1530) [`911e14fc`](https://github.com/thirdweb-dev/js/commit/911e14fcac743b07fa1a66440c72d662c08e971c) Thanks [@kumaryash90](https://github.com/kumaryash90)! - Add Chiliz Spicy testnet
|
||||
|
||||
## 0.1.47
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#1518](https://github.com/thirdweb-dev/js/pull/1518) [`34a3bb8a`](https://github.com/thirdweb-dev/js/commit/34a3bb8ae3c1d7a506e5568a9e79ab7e469557a8) Thanks [@nkrishang](https://github.com/nkrishang)! - Update contracts package
|
||||
|
||||
## 0.1.46
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#1508](https://github.com/thirdweb-dev/js/pull/1508) [`184c325a`](https://github.com/thirdweb-dev/js/commit/184c325ab2ef028022a050c4274f2ab12b1a3a7f) Thanks [@jnsdls](https://github.com/jnsdls)! - update xpla chain icons
|
||||
|
||||
- [#1513](https://github.com/thirdweb-dev/js/pull/1513) [`c12f0874`](https://github.com/thirdweb-dev/js/commit/c12f0874b4dac43c263c7edb20d0343c16381c34) Thanks [@jnsdls](https://github.com/jnsdls)! - add additonal chains
|
||||
|
||||
## 0.1.45
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#1506](https://github.com/thirdweb-dev/js/pull/1506) [`f97ddf4c`](https://github.com/thirdweb-dev/js/commit/f97ddf4c7f14854f3b204ad9741b52ddb8dac736) Thanks [@jnsdls](https://github.com/jnsdls)! - add xpla chains & update chains
|
||||
|
||||
## 0.1.44
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#1499](https://github.com/thirdweb-dev/js/pull/1499) [`8f3b685a`](https://github.com/thirdweb-dev/js/commit/8f3b685ad2bd73cb4d5d8c8aa25c04ffc10fb7cf) Thanks [@Marfuen](https://github.com/Marfuen)! - Update deploy command from CLI to use show a public gateway url temporarily. Updated chains too.
|
||||
|
||||
## 0.1.43
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#1469](https://github.com/thirdweb-dev/js/pull/1469) [`cb1c9937`](https://github.com/thirdweb-dev/js/commit/cb1c9937fadb2d06eb148cf9819f2b3601376308) Thanks [@jnsdls](https://github.com/jnsdls)! - update chains
|
||||
|
||||
- [#1474](https://github.com/thirdweb-dev/js/pull/1474) [`a1732663`](https://github.com/thirdweb-dev/js/commit/a17326634e758c3bf42f0cc3869b8792f1e18cc9) Thanks [@adam-maj](https://github.com/adam-maj)! - Add back scroll-alpha-testnet
|
||||
|
||||
- [#1458](https://github.com/thirdweb-dev/js/pull/1458) [`588b2106`](https://github.com/thirdweb-dev/js/commit/588b21060139a7c0bf5805e2c629a16792ed2c76) Thanks [@kien-ngo](https://github.com/kien-ngo)! - Update chains
|
||||
|
||||
- [#1461](https://github.com/thirdweb-dev/js/pull/1461) [`269e68c0`](https://github.com/thirdweb-dev/js/commit/269e68c0a15e8f78cb5b68c9456ca8094b9e1f30) Thanks [@0xFirekeeper](https://github.com/0xFirekeeper)! - add xai-goerli chain and support BigInteger from Unity
|
||||
|
||||
## 0.1.42
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#1450](https://github.com/thirdweb-dev/js/pull/1450) [`262edc6a`](https://github.com/thirdweb-dev/js/commit/262edc6a46792da88f49ff6ef0a756a932a6a0cf) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - update chains
|
||||
|
||||
## 0.1.41
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#1436](https://github.com/thirdweb-dev/js/pull/1436) [`fd981655`](https://github.com/thirdweb-dev/js/commit/fd9816556fe595b0c764e34dbcf15b0ad1677edb) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - Updated chains
|
||||
|
||||
- [#1432](https://github.com/thirdweb-dev/js/pull/1432) [`9f5adc5c`](https://github.com/thirdweb-dev/js/commit/9f5adc5c5c2782ffb878759df481e5fb1e1740e5) Thanks [@jnsdls](https://github.com/jnsdls)! - update chains
|
||||
|
||||
## 0.1.40
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#1415](https://github.com/thirdweb-dev/js/pull/1415) [`256ee0d5`](https://github.com/thirdweb-dev/js/commit/256ee0d5ec9c8598aa79cd4cb1fd839c6cc7d390) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - Downgrade preconstruct to 2.7.0
|
||||
|
||||
## 0.1.39
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#1407](https://github.com/thirdweb-dev/js/pull/1407) [`0a5eb19d`](https://github.com/thirdweb-dev/js/commit/0a5eb19d672909027bb6c7e79ea76d431535559c) Thanks [@jnsdls](https://github.com/jnsdls)! - remove non-standard urls
|
||||
|
||||
- [#1409](https://github.com/thirdweb-dev/js/pull/1409) [`b1e8c8e2`](https://github.com/thirdweb-dev/js/commit/b1e8c8e231013182eb46c16d0c441ee0f3bdfdb2) Thanks [@jnsdls](https://github.com/jnsdls)! - update dependencies
|
||||
|
||||
- [#1376](https://github.com/thirdweb-dev/js/pull/1376) [`48ca58b7`](https://github.com/thirdweb-dev/js/commit/48ca58b7aa45348b928932b9c7b76b3dc233e429) Thanks [@iketw](https://github.com/iketw)! - Pass bundleId if present
|
||||
|
||||
- [#1399](https://github.com/thirdweb-dev/js/pull/1399) [`3152d4e9`](https://github.com/thirdweb-dev/js/commit/3152d4e9b42e2777316b1b58513657f4430cb79a) Thanks [@jnsdls](https://github.com/jnsdls)! - update chains
|
||||
|
||||
- [#1387](https://github.com/thirdweb-dev/js/pull/1387) [`aa6bdd08`](https://github.com/thirdweb-dev/js/commit/aa6bdd0809d1d5536c837c59b2d407ee974c1f9c) Thanks [@nkrishang](https://github.com/nkrishang)! - Simplify account permissions
|
||||
|
||||
- [#1405](https://github.com/thirdweb-dev/js/pull/1405) [`75587c8b`](https://github.com/thirdweb-dev/js/commit/75587c8b38bbbcf68d2101526e9792349cce728f) Thanks [@jnsdls](https://github.com/jnsdls)! - sort explorers (etherscan -> blockscout -> other)
|
||||
|
||||
- [#1385](https://github.com/thirdweb-dev/js/pull/1385) [`cc5e2ec5`](https://github.com/thirdweb-dev/js/commit/cc5e2ec51fce09af05be33811eb46980825829ee) Thanks [@iketw](https://github.com/iketw)! - Adds chains and improve error messages
|
||||
|
||||
- [#1378](https://github.com/thirdweb-dev/js/pull/1378) [`5a02c5ec`](https://github.com/thirdweb-dev/js/commit/5a02c5ec0288fd6dfb2b765ef70bb18e714aca19) Thanks [@MananTank](https://github.com/MananTank)! - add icon in MinimalChain
|
||||
|
||||
## 0.1.38
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#1374](https://github.com/thirdweb-dev/js/pull/1374) [`7cb55e70`](https://github.com/thirdweb-dev/js/commit/7cb55e7051617268bba1b80146865c606ff6e66d) Thanks [@jnsdls](https://github.com/jnsdls)! -
|
||||
|
||||
## 0.1.37
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#1371](https://github.com/thirdweb-dev/js/pull/1371) [`d665954f`](https://github.com/thirdweb-dev/js/commit/d665954fee0554985055bf06abbed8d7b8d5bc38) Thanks [@jnsdls](https://github.com/jnsdls)! - add more chains
|
||||
|
||||
## 0.1.36
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#1366](https://github.com/thirdweb-dev/js/pull/1366) [`02ab92cc`](https://github.com/thirdweb-dev/js/commit/02ab92cc5c97f475e3b5642e5a7bdbe63ca136ee) Thanks [@jnsdls](https://github.com/jnsdls)! - new supported chains
|
||||
|
||||
- [#1358](https://github.com/thirdweb-dev/js/pull/1358) [`6cc4b8d2`](https://github.com/thirdweb-dev/js/commit/6cc4b8d28b982c5be3e1cd17d2a9a947001d1608) Thanks [@DLHTX](https://github.com/DLHTX)! - feat(chain): add iotex chain icon
|
||||
|
||||
## 0.1.35
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#1316](https://github.com/thirdweb-dev/js/pull/1316) [`d8447146`](https://github.com/thirdweb-dev/js/commit/d8447146092c1962f410155ab2047225453aaa2b) Thanks [@iketw](https://github.com/iketw)! - Adds support for clientId / secretKey
|
||||
|
||||
## 0.1.34
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#1338](https://github.com/thirdweb-dev/js/pull/1338) [`8dd7540c`](https://github.com/thirdweb-dev/js/commit/8dd7540c455aa70534f6d29986537592fd12169b) Thanks [@jnsdls](https://github.com/jnsdls)! - update chains
|
||||
|
||||
## 0.1.33
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#1335](https://github.com/thirdweb-dev/js/pull/1335) [`0407b2c2`](https://github.com/thirdweb-dev/js/commit/0407b2c2f39b5f3fa06e613e671623b5644b3a28) Thanks [@jnsdls](https://github.com/jnsdls)! - update chains again
|
||||
|
||||
## 0.1.32
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#1330](https://github.com/thirdweb-dev/js/pull/1330) [`a9093bcf`](https://github.com/thirdweb-dev/js/commit/a9093bcf287c01e3335fd780e2ccbfdb3380bf95) Thanks [@adam-maj](https://github.com/adam-maj)! - Override binance slug
|
||||
|
||||
## 0.1.31
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#1328](https://github.com/thirdweb-dev/js/pull/1328) [`4393b228`](https://github.com/thirdweb-dev/js/commit/4393b2280505fd0b2284555d64eae6567e8401a5) Thanks [@adam-maj](https://github.com/adam-maj)! - Make base default chain
|
||||
|
||||
## 0.1.30
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#1326](https://github.com/thirdweb-dev/js/pull/1326) [`40682191`](https://github.com/thirdweb-dev/js/commit/40682191450de08ad40b9d2957afced248657af2) Thanks [@adam-maj](https://github.com/adam-maj)! - Add base chain support
|
||||
|
||||
## 0.1.29
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#1320](https://github.com/thirdweb-dev/js/pull/1320) [`fd74d791`](https://github.com/thirdweb-dev/js/commit/fd74d7918072cda03b52f852ebb3f8dccb84074d) Thanks [@kumaryash90](https://github.com/kumaryash90)! - Linea mainnet support
|
||||
|
||||
- [#1315](https://github.com/thirdweb-dev/js/pull/1315) [`35f20ceb`](https://github.com/thirdweb-dev/js/commit/35f20ceb4f943e95d9566105096f06412978da7a) Thanks [@jnsdls](https://github.com/jnsdls)! - add more chains
|
||||
|
||||
## 0.1.28
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#1299](https://github.com/thirdweb-dev/js/pull/1299) [`7e044c66`](https://github.com/thirdweb-dev/js/commit/7e044c664d8a034f5324b859ac3596860c86f9a5) Thanks [@nkrishang](https://github.com/nkrishang)! - Add ERC721LoyaltyCard and ERC721UpdatableMetadata
|
||||
|
||||
- [#1297](https://github.com/thirdweb-dev/js/pull/1297) [`b5c6eedb`](https://github.com/thirdweb-dev/js/commit/b5c6eedb38aa3c52eb97f3d25ad83e38c55afe61) Thanks [@0xFirekeeper](https://github.com/0xFirekeeper)! - add zksync as default chain
|
||||
|
||||
- [#1309](https://github.com/thirdweb-dev/js/pull/1309) [`4961b597`](https://github.com/thirdweb-dev/js/commit/4961b597a098dae0a4eff01a9ef268a65fe1a352) Thanks [@jnsdls](https://github.com/jnsdls)! - unblock storage domains
|
||||
|
||||
## 0.1.27
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#1278](https://github.com/thirdweb-dev/js/pull/1278) [`8a389f12`](https://github.com/thirdweb-dev/js/commit/8a389f1295d2bf726059997ea0ca10cf0424f2a2) Thanks [@jnsdls](https://github.com/jnsdls)! - updated various dependencies
|
||||
|
||||
- [#1281](https://github.com/thirdweb-dev/js/pull/1281) [`a3bb17cb`](https://github.com/thirdweb-dev/js/commit/a3bb17cb33f033846fb3b4c8a0a4809ba76cab96) Thanks [@nkrishang](https://github.com/nkrishang)! - Handle ERC4337Signer correctly for signTypedData
|
||||
|
||||
## 0.1.26
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#1276](https://github.com/thirdweb-dev/js/pull/1276) [`5882091e`](https://github.com/thirdweb-dev/js/commit/5882091eab65978009a5a5305701f121851b10ad) Thanks [@MananTank](https://github.com/MananTank)! - Do not change order or RPCs in `getChainRPCs` to Fix RPC override issue
|
||||
|
||||
## 0.1.25
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#1248](https://github.com/thirdweb-dev/js/pull/1248) [`620e89dc`](https://github.com/thirdweb-dev/js/commit/620e89dc25c91557e2164a602c7aedd733525087) Thanks [@nachoiacovino](https://github.com/nachoiacovino)! - update chains
|
||||
|
||||
- [#1243](https://github.com/thirdweb-dev/js/pull/1243) [`0fd8aa04`](https://github.com/thirdweb-dev/js/commit/0fd8aa04a9424497758d13a51a72363edcc30e19) Thanks [@therealharpaljadeja](https://github.com/therealharpaljadeja)! - update chains
|
||||
|
||||
## 0.1.24
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#1241](https://github.com/thirdweb-dev/js/pull/1241) [`9af346ee`](https://github.com/thirdweb-dev/js/commit/9af346eeada1037be27c8d3c9e1777f7be11a8ea) Thanks [@nkrishang](https://github.com/nkrishang)! - Use fn getAllAccounts instead of event logs AccountCreated
|
||||
|
||||
## 0.1.23
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#1229](https://github.com/thirdweb-dev/js/pull/1229) [`6816219a`](https://github.com/thirdweb-dev/js/commit/6816219ac13ae571a0c90db6ab389c319bc1f052) Thanks [@jnsdls](https://github.com/jnsdls)! - add/update chains
|
||||
|
||||
## 0.1.22
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#1192](https://github.com/thirdweb-dev/js/pull/1192) [`f41f1a29`](https://github.com/thirdweb-dev/js/commit/f41f1a2958a2cedcf0496c9a3ca284d0b98f1b89) Thanks [@jnsdls](https://github.com/jnsdls)! - fix arbitrum goerli infura url
|
||||
|
||||
## 0.1.21
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#1074](https://github.com/thirdweb-dev/js/pull/1074) [`cc20f93e`](https://github.com/thirdweb-dev/js/commit/cc20f93e178d86ee2b3f39102bbb0811de211f05) Thanks [@Marfuen](https://github.com/Marfuen)! - Enhance CLI Install command and update chains
|
||||
|
||||
- [#1185](https://github.com/thirdweb-dev/js/pull/1185) [`8e28b0f5`](https://github.com/thirdweb-dev/js/commit/8e28b0f5e75596d29273ed80269bcee6d209adb4) Thanks [@jnsdls](https://github.com/jnsdls)! - add Zora Mainnet
|
||||
|
||||
## 0.1.20
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#1140](https://github.com/thirdweb-dev/js/pull/1140) [`16232de9`](https://github.com/thirdweb-dev/js/commit/16232de9eab9966e24e335929b2a3919346af265) Thanks [@kumaryash90](https://github.com/kumaryash90)! - Update chains
|
||||
|
||||
- [#1138](https://github.com/thirdweb-dev/js/pull/1138) [`645b0303`](https://github.com/thirdweb-dev/js/commit/645b0303cc1a9cbb0a0e9cbd67d11c3b865e4584) Thanks [@jcstein](https://github.com/jcstein)! - feat: create Taro Testnet
|
||||
|
||||
## 0.1.19
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#1065](https://github.com/thirdweb-dev/js/pull/1065) [`bd86661f`](https://github.com/thirdweb-dev/js/commit/bd86661f54ca2f1eb09cbae35c704dc79be1b63a) Thanks [@iketw](https://github.com/iketw)! - [Chains] update chains
|
||||
|
||||
- [#1064](https://github.com/thirdweb-dev/js/pull/1064) [`c85810ee`](https://github.com/thirdweb-dev/js/commit/c85810eee318b10eee4ada61828adaa51f94ea6c) Thanks [@MananTank](https://github.com/MananTank)! - Do not cache signer in wallets
|
||||
|
||||
- [#1047](https://github.com/thirdweb-dev/js/pull/1047) [`6fd10f94`](https://github.com/thirdweb-dev/js/commit/6fd10f94b469dc5659e2ff4ce92a5aff86f3c89d) Thanks [@MananTank](https://github.com/MananTank)! - Fix tree-shaking issue in `@thirdweb-dev/chains`
|
||||
|
||||
- [#1090](https://github.com/thirdweb-dev/js/pull/1090) [`5f1e6abb`](https://github.com/thirdweb-dev/js/commit/5f1e6abb391f5c58dbdb207f569b3dd0b5d4729c) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - Add base-goerli to default chains
|
||||
|
||||
## 0.1.18
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#1040](https://github.com/thirdweb-dev/js/pull/1040) [`30e5593d`](https://github.com/thirdweb-dev/js/commit/30e5593dd1ce9abd809ad216a1cfce77b897093c) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - Update chains
|
||||
|
||||
## 0.1.17
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#1031](https://github.com/thirdweb-dev/js/pull/1031) [`3c8c5d56`](https://github.com/thirdweb-dev/js/commit/3c8c5d56f2a21c0918fede71061c6745f2956f83) Thanks [@jnsdls](https://github.com/jnsdls)! - update chains
|
||||
|
||||
- [#1033](https://github.com/thirdweb-dev/js/pull/1033) [`907d97be`](https://github.com/thirdweb-dev/js/commit/907d97bedef7331148bdfe8b9bf1e19459282e4c) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - Added faucet for optimism goerli
|
||||
|
||||
## 0.1.16
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#996](https://github.com/thirdweb-dev/js/pull/996) [`c3645c45`](https://github.com/thirdweb-dev/js/commit/c3645c451b5e9a0fcf651fa07eb0e31ebf1882ca) Thanks [@MananTank](https://github.com/MananTank)! - - add `theme` in `ConfiguredWallet.connectUI`'s props - to use theme aware UI for wallets
|
||||
|
||||
- add `useWalletConfig` hook to get the `ConfiguredWallet` object for active wallet
|
||||
- add hooks `useSetConnectedWallet`, `useSetConnectionStatus`
|
||||
- rename `useActiveChain` to `useChain` - keep the `useActiveChain` also with deprecated tag
|
||||
- make `useSafe` hook await-able by returning the promise of connect() call
|
||||
- add hook `useSmartWallet`
|
||||
- allow rendering custom wallet details button via `<ConnectWallet detailsButton={} />` prop
|
||||
- Rename "Export" to "Backup" in local wallet UI
|
||||
|
||||
- [#1011](https://github.com/thirdweb-dev/js/pull/1011) [`470e0a14`](https://github.com/thirdweb-dev/js/commit/470e0a144db6aa03e7789e231bbdfae43144f0e0) Thanks [@MananTank](https://github.com/MananTank)! - rename ConfiguredWallet to WalletConfig
|
||||
|
||||
```diff
|
||||
- import { ConfiguredWallet } from '@thirdweb-dev/react';
|
||||
+ import { WalletConfig } from '@thirdweb-dev/react';
|
||||
```
|
||||
|
||||
- [#995](https://github.com/thirdweb-dev/js/pull/995) [`482f6d1b`](https://github.com/thirdweb-dev/js/commit/482f6d1b58ac99b331fc750d3eeb6082556fd526) Thanks [@iketw](https://github.com/iketw)! - [Chains] Update chains
|
||||
|
||||
- [#989](https://github.com/thirdweb-dev/js/pull/989) [`8db78299`](https://github.com/thirdweb-dev/js/commit/8db78299ea6cfb51d93b91bb1a351644a83c73d2) Thanks [@iketw](https://github.com/iketw)! - [React/ReactNative] Updated useThirdwebWallet to useWalletContext
|
||||
|
||||
## 0.1.15
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#990](https://github.com/thirdweb-dev/js/pull/990) [`6a4aab0b`](https://github.com/thirdweb-dev/js/commit/6a4aab0b8a2e0f6ff1b47992a3c1e5426a74f7ff) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - updated chains
|
||||
|
||||
## 0.1.14
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#954](https://github.com/thirdweb-dev/js/pull/954) [`5305b42d`](https://github.com/thirdweb-dev/js/commit/5305b42db554b69f903b3d95f3ba0eeddabd6114) Thanks [@jarrodwatts](https://github.com/jarrodwatts)! - useContract now types correctly for thirdweb generate
|
||||
|
||||
## 0.1.13
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#787](https://github.com/thirdweb-dev/js/pull/787) [`d2c7f6d7`](https://github.com/thirdweb-dev/js/commit/d2c7f6d758787fab102ecc0cec16ac74f3c87a1f) Thanks [@iketw](https://github.com/iketw)! - [ReactNative] Adds Device Wallet
|
||||
|
||||
- [#931](https://github.com/thirdweb-dev/js/pull/931) [`f12a80a4`](https://github.com/thirdweb-dev/js/commit/f12a80a4758aa91c43084acedb212de9f36a7371) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - Updated chains
|
||||
|
||||
## 0.1.12
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#896](https://github.com/thirdweb-dev/js/pull/896) [`a6610f12`](https://github.com/thirdweb-dev/js/commit/a6610f1211e9359885b948bf69a66d834707ec07) Thanks [@atharvadeosthale](https://github.com/atharvadeosthale)! - Added links to portal
|
||||
|
||||
- [#903](https://github.com/thirdweb-dev/js/pull/903) [`77224646`](https://github.com/thirdweb-dev/js/commit/77224646d542db3171394d744b455497cd057633) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - new chains
|
||||
|
||||
## 0.1.11
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#870](https://github.com/thirdweb-dev/js/pull/870) [`235eb046`](https://github.com/thirdweb-dev/js/commit/235eb0460ae0638f63acf82957bcfea41b9e955d) Thanks [@jnsdls](https://github.com/jnsdls)! - update chains
|
||||
|
||||
## 0.1.10
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#848](https://github.com/thirdweb-dev/js/pull/848) [`1137a20d`](https://github.com/thirdweb-dev/js/commit/1137a20de44603d35e71eae2f2b6fec79febec00) Thanks [@jnsdls](https://github.com/jnsdls)! - more complete types for chains and better generation
|
||||
|
||||
## 0.1.9
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#843](https://github.com/thirdweb-dev/js/pull/843) [`839fce1f`](https://github.com/thirdweb-dev/js/commit/839fce1f6f2747d6102033b26c292294e908f75d) Thanks [@jnsdls](https://github.com/jnsdls)! - update chains
|
||||
|
||||
## 0.1.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#839](https://github.com/thirdweb-dev/js/pull/839) [`9b303829`](https://github.com/thirdweb-dev/js/commit/9b3038291d1c9f4eb243718a6070e3dac829a354) Thanks [@jnsdls](https://github.com/jnsdls)! - update chains (add telos icon & avalanche fuji faucet)
|
||||
|
||||
## 0.1.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#584](https://github.com/thirdweb-dev/js/pull/584) [`d01b135d`](https://github.com/thirdweb-dev/js/commit/d01b135d26efe6cebd84110b1a8eacee5c1b98db) Thanks [@MananTank](https://github.com/MananTank)! - [Wallets, RN, React, ReactCore] New Wallets SDK implementation
|
||||
|
||||
#### Breaking changes:
|
||||
|
||||
### React and React Native
|
||||
|
||||
1. Replaced `walletConnectors` with `supportedWallets` in the ThirdwebProvider.
|
||||
|
||||
```diff
|
||||
import { coinbaseWallet, metamaskWallet, ThirdwebProvider } from "@thirdweb-dev/react";
|
||||
|
||||
const App = () => {
|
||||
- return <ThirdwebProvider walletConnectors={["metamask", "coinbaseWallet"]}>{...}</ThirdwebProvider>
|
||||
+ return <ThirdwebProvider supportedWallets={[metamaskWallet(), coinbaseWallet()]}>{...}</ThirdwebProvider>
|
||||
}
|
||||
```
|
||||
|
||||
2. `magicLinkConnector` not yet implemented in this version
|
||||
|
||||
3. Removed `desiredChainId`, use `activeChain` instead
|
||||
|
||||
4. `DAppMetaData` type now requires the `url` field to be passed in
|
||||
|
||||
5. Removed `chainRpc` prop from the ThirdwebProvider. You can pass custom rpcs in the Chain object through the `activeChain` and `supportedChains` props.
|
||||
|
||||
6. Replaced the `useNetwork` hook for `useChain` and `useSwitchChain`
|
||||
|
||||
7. Updated `ConnectWallet` button in React:
|
||||
|
||||
- Removed `accentColor` and `colorMode`
|
||||
- Added a `theme` prop with `dark` and `light` values
|
||||
|
||||
8. Updated the `Web3Button` button in React:
|
||||
|
||||
- Removed `accentColor` and `colorMode`
|
||||
- Added a `theme` prop with `dark` and `light` values
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- You can now pass a `theme` prop to the ThirdwebProvider. Values are `light` and `dark`
|
||||
|
||||
```
|
||||
import { ThirdwebProvider } from "@thirdweb-dev/react";
|
||||
|
||||
const App = () => {
|
||||
return <ThirdwebProvider theme='light'>{...}</ThirdwebProvider>
|
||||
}
|
||||
```
|
||||
|
||||
- New wallet hooks added
|
||||
|
||||
- useWallet()
|
||||
- useConenct()
|
||||
- useConnectionStatus()
|
||||
- useSwitchChain()
|
||||
|
||||
- Removed `wagmi` dependency from the `react-native-compat` package and updated shims
|
||||
|
||||
- New `ConnectWallet` and `Web3Button` components in React Native
|
||||
|
||||
- New wallets package with support for the most common wallets. MetaMask, Coinbase, WalletConnect V1 and V2 and PaperWallet
|
||||
|
||||
- Added `autoSwitch` prop to the ThirdwebProvider to control whether or not to automatically switch to wallet's network to active chain
|
||||
|
||||
```
|
||||
import { ThirdwebProvider } from "@thirdweb-dev/react";
|
||||
|
||||
const App = () => {
|
||||
return <ThirdwebProvider autoSwitch>{...}</ThirdwebProvider>
|
||||
}
|
||||
```
|
||||
|
||||
- [#771](https://github.com/thirdweb-dev/js/pull/771) [`e3161e59`](https://github.com/thirdweb-dev/js/commit/e3161e5986e1831c6dae517889b6a6ba181ccd36) Thanks [@iketw](https://github.com/iketw)! - [ReactNativeSDK] Use wallet metadata from the wallet class
|
||||
|
||||
- [#702](https://github.com/thirdweb-dev/js/pull/702) [`33d1cc7f`](https://github.com/thirdweb-dev/js/commit/33d1cc7f92cd982e9e55130472c0006bb999f682) Thanks [@jnsdls](https://github.com/jnsdls)! - enable `browser` export
|
||||
|
||||
- [#801](https://github.com/thirdweb-dev/js/pull/801) [`6b145d4b`](https://github.com/thirdweb-dev/js/commit/6b145d4b36d2706f8a2dcad4b8f680c41606a556) Thanks [@jnsdls](https://github.com/jnsdls)! - updated chains
|
||||
|
||||
- [#822](https://github.com/thirdweb-dev/js/pull/822) [`e2ec70c4`](https://github.com/thirdweb-dev/js/commit/e2ec70c49264737fbd2afb1cacabded82262bc6c) Thanks [@jnsdls](https://github.com/jnsdls)! - update chains again
|
||||
|
||||
- [#721](https://github.com/thirdweb-dev/js/pull/721) [`1baed0b1`](https://github.com/thirdweb-dev/js/commit/1baed0b1d83b4c92dac44430af5436d04727d92f) Thanks [@adam-maj](https://github.com/adam-maj)! - Update chains
|
||||
|
||||
- [#748](https://github.com/thirdweb-dev/js/pull/748) [`2221f97d`](https://github.com/thirdweb-dev/js/commit/2221f97ddc997d864db3a5f00e82862ece574922) Thanks [@MananTank](https://github.com/MananTank)! - [ReactCore] Inject api key to chains
|
||||
|
||||
- [#770](https://github.com/thirdweb-dev/js/pull/770) [`5c7c0923`](https://github.com/thirdweb-dev/js/commit/5c7c0923e45b3f0ee27c83a9c4c691ce9bbb8539) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - update chains
|
||||
|
||||
- [#809](https://github.com/thirdweb-dev/js/pull/809) [`9a4a542c`](https://github.com/thirdweb-dev/js/commit/9a4a542ce9650605d48745a40126ca6b52a16722) Thanks [@jnsdls](https://github.com/jnsdls)! - add new chains
|
||||
|
||||
- [#719](https://github.com/thirdweb-dev/js/pull/719) [`b2d0ffb0`](https://github.com/thirdweb-dev/js/commit/b2d0ffb049208de9f9212eae7059212aed74fec4) Thanks [@nachoiacovino](https://github.com/nachoiacovino)! - Replace Goerli default faucet
|
||||
|
||||
## 0.1.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#635](https://github.com/thirdweb-dev/js/pull/635) [`7832041c`](https://github.com/thirdweb-dev/js/commit/7832041c0fb25852489c73453c2b26e844d94582) Thanks [@jnsdls](https://github.com/jnsdls)! - Add additional chains
|
||||
|
||||
- [#643](https://github.com/thirdweb-dev/js/pull/643) [`85250cf7`](https://github.com/thirdweb-dev/js/commit/85250cf71190092b61023d56d1e786d395a008a6) Thanks [@jnsdls](https://github.com/jnsdls)! - expose `getChainBySlug` and `ChainSlug` and `ChainId` type
|
||||
|
||||
- [#638](https://github.com/thirdweb-dev/js/pull/638) [`e33bd2a8`](https://github.com/thirdweb-dev/js/commit/e33bd2a856bbc2e2f6b0c90b46be5166281875ae) Thanks [@jnsdls](https://github.com/jnsdls)! - Add `getChainByChainId()` to retrieve chain by chainId
|
||||
|
||||
- [#685](https://github.com/thirdweb-dev/js/pull/685) [`4355518a`](https://github.com/thirdweb-dev/js/commit/4355518afea68cd8026d3ab8a0144c15d66b9e24) Thanks [@jnsdls](https://github.com/jnsdls)! - update chains
|
||||
|
||||
## 0.1.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#617](https://github.com/thirdweb-dev/js/pull/617) [`d7deaa4`](https://github.com/thirdweb-dev/js/commit/d7deaa48f2f943deb8f2ad7459d17de930c00517) Thanks [@jnsdls](https://github.com/jnsdls)! - Add Base Goerli
|
||||
|
||||
- [#619](https://github.com/thirdweb-dev/js/pull/619) [`b7a5b45`](https://github.com/thirdweb-dev/js/commit/b7a5b454415596316f58a75f14472631242cc115) Thanks [@jnsdls](https://github.com/jnsdls)! - fix the slug for base-goerli
|
||||
|
||||
## 0.1.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#613](https://github.com/thirdweb-dev/js/pull/613) [`a3472a1`](https://github.com/thirdweb-dev/js/commit/a3472a133175826d052ee986907de014e3cf3ad9) Thanks [@jnsdls](https://github.com/jnsdls)! - Update Chains
|
||||
|
||||
- [#616](https://github.com/thirdweb-dev/js/pull/616) [`5712650`](https://github.com/thirdweb-dev/js/commit/5712650074e2415bbea4173a0bb68d727ff2db90) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - new chains
|
||||
|
||||
## 0.1.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#608](https://github.com/thirdweb-dev/js/pull/608) [`3d644fb`](https://github.com/thirdweb-dev/js/commit/3d644fb8cbae8bc3ee624505831b9f5c6996898a) Thanks [@jnsdls](https://github.com/jnsdls)! - update chains
|
||||
|
||||
## 0.1.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#601](https://github.com/thirdweb-dev/js/pull/601) [`66cf1fb`](https://github.com/thirdweb-dev/js/commit/66cf1fb5c2e8deb486543ee028d786bb8eef6c19) Thanks [@jnsdls](https://github.com/jnsdls)! - upgrade dependencies
|
||||
|
||||
## 0.1.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#599](https://github.com/thirdweb-dev/js/pull/599) [`f580b8a`](https://github.com/thirdweb-dev/js/commit/f580b8ac06534df24b0194cbc632b4a8fd447611) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - New chains added
|
||||
|
||||
## 0.1.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- [`af8cf40`](https://github.com/thirdweb-dev/js/commit/af8cf40e4e1dab6afcc7622f7f9bbcfc6e8534d8) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - Initial release for `@thirdweb-dev/chains`.
|
||||
|
||||
Use this package to easily configure chains with `@thirdweb-dev/sdk` and `@thirdweb-dev/react`.
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [`6a50719`](https://github.com/thirdweb-dev/js/commit/6a507194861b0712fd753c49ac63a8af68eb21d5) Thanks [@jnsdls](https://github.com/jnsdls)! - add `getChainRPCs` export to get a list of RPCs instead of a single RPC via `getChainRPC`
|
||||
|
||||
- [`5d25ee1`](https://github.com/thirdweb-dev/js/commit/5d25ee1ab7abb4bfbded283a18f2d7740bb6995d) Thanks [@jnsdls](https://github.com/jnsdls)! - fix `getChainRPC()` utility function logic
|
||||
|
||||
- [`d0bcd2c`](https://github.com/thirdweb-dev/js/commit/d0bcd2c5871ca9480efc8d97e27e337eb9bbf830) Thanks [@jnsdls](https://github.com/jnsdls)! - add new chains + localhost special case
|
||||
|
||||
- [#589](https://github.com/thirdweb-dev/js/pull/589) [`017b0d5`](https://github.com/thirdweb-dev/js/commit/017b0d56b64651b290440b60789e058afba9f9a5) Thanks [@jnsdls](https://github.com/jnsdls)! - add `minimizeChain` and `configureChain` util functions
|
||||
|
||||
- [`500a0e6`](https://github.com/thirdweb-dev/js/commit/500a0e671b3feb01aedd2c34443b682d0934f389) Thanks [@jnsdls](https://github.com/jnsdls)! - add thirdweb rpc to all available chains
|
||||
@@ -0,0 +1,36 @@
|
||||
<p align="center">
|
||||
<br />
|
||||
<a href="https://thirdweb.com"><img src="https://github.com/thirdweb-dev/js/blob/main/legacy_packages/sdk/logo.svg?raw=true" width="200" alt=""/></a>
|
||||
<br />
|
||||
</p>
|
||||
<h1 align="center">thirdweb chains</h1>
|
||||
<p align="center">
|
||||
<a href="https://www.npmjs.com/package/@thirdweb-dev/chains"><img src="https://img.shields.io/npm/v/@thirdweb-dev/chains?color=red&label=npm&logo=npm" alt="npm version"/></a>
|
||||
<a href="https://github.com/thirdweb-dev/js/actions/workflows/build-test-lint.yml"><img alt="Build Status" src="https://github.com/thirdweb-dev/js/actions/workflows/build-test-lint.yml/badge.svg"/></a>
|
||||
<a href="https://discord.gg/thirdweb"><img alt="Join our Discord!" src="https://img.shields.io/discord/834227967404146718.svg?color=7289da&label=discord&logo=discord&style=flat"/></a>
|
||||
|
||||
</p>
|
||||
<!-- <p align="center"><strong>Best in class Web3 SDK for Browser, Node and Mobile apps</strong></p> -->
|
||||
<br />
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
yarn add @thirdweb-dev/chains
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
### Adding / Overriding Chain Data
|
||||
|
||||
Chain data is automatically pulled in from [ethereum-lists/chains](https://github.com/ethereum-lists/chains) on every build.
|
||||
|
||||
#### Overriding Chain Data
|
||||
|
||||
Chain data can be overridden by running `yarn override-chain` and following the prompts.
|
||||
|
||||
#### Adding A New Chain
|
||||
|
||||
In cases where a chain is not listed in the [ethereum-lists/chains](https://github.com/ethereum-lists/chains) repo, you can add it manually by running `yarn add-chain` and following the prompts.
|
||||
|
||||
> Prefer adding chains to the ethereum-lists/chains repo instead of manually adding them here.
|
||||
@@ -0,0 +1,82 @@
|
||||
import type { Chain } from "../src/types";
|
||||
export default {
|
||||
"chain": "ETH",
|
||||
"chainId": 1,
|
||||
"ens": {
|
||||
"registry": "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e"
|
||||
},
|
||||
"explorers": [
|
||||
{
|
||||
"name": "etherscan",
|
||||
"url": "https://etherscan.io",
|
||||
"standard": "EIP3091"
|
||||
},
|
||||
{
|
||||
"name": "blockscout",
|
||||
"url": "https://eth.blockscout.com",
|
||||
"standard": "EIP3091",
|
||||
"icon": {
|
||||
"url": "ipfs://QmYtUimyqHkkFxYdbXXRbUqNg2VLPUg6Uu2C2nmFWowiZM",
|
||||
"width": 551,
|
||||
"height": 540,
|
||||
"format": "png"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "dexguru",
|
||||
"url": "https://ethereum.dex.guru",
|
||||
"standard": "EIP3091",
|
||||
"icon": {
|
||||
"url": "ipfs://QmRaASKRSjQ5btoUQ2rNTJNxKtx2a2RoewgA7DMQkLVEne",
|
||||
"width": 83,
|
||||
"height": 82,
|
||||
"format": "svg"
|
||||
}
|
||||
}
|
||||
],
|
||||
"faucets": [],
|
||||
"features": [
|
||||
{
|
||||
"name": "EIP155"
|
||||
},
|
||||
{
|
||||
"name": "EIP1559"
|
||||
}
|
||||
],
|
||||
"icon": {
|
||||
"url": "ipfs://QmcxZHpyJa8T4i63xqjPYrZ6tKrt55tZJpbXcjSDKuKaf9/ethereum/512.png",
|
||||
"width": 512,
|
||||
"height": 512,
|
||||
"format": "png"
|
||||
},
|
||||
"infoURL": "https://ethereum.org",
|
||||
"name": "Ethereum Mainnet",
|
||||
"nativeCurrency": {
|
||||
"name": "Ether",
|
||||
"symbol": "ETH",
|
||||
"decimals": 18
|
||||
},
|
||||
"networkId": 1,
|
||||
"redFlags": [],
|
||||
"rpc": [
|
||||
"https://1.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
|
||||
"https://mainnet.infura.io/v3/${INFURA_API_KEY}",
|
||||
"wss://mainnet.infura.io/ws/v3/${INFURA_API_KEY}",
|
||||
"https://eth-mainnet.g.alchemy.com/v2/${ALCHEMY_API_KEY}",
|
||||
"https://api.mycryptoapi.com/eth",
|
||||
"https://cloudflare-eth.com",
|
||||
"https://ethereum-rpc.publicnode.com",
|
||||
"wss://ethereum-rpc.publicnode.com",
|
||||
"https://mainnet.gateway.tenderly.co",
|
||||
"wss://mainnet.gateway.tenderly.co",
|
||||
"https://rpc.blocknative.com/boost",
|
||||
"https://rpc.flashbots.net/fast",
|
||||
"https://rpc.mevblocker.io/fullprivacy",
|
||||
"https://eth.drpc.org",
|
||||
"wss://eth.drpc.org"
|
||||
],
|
||||
"shortName": "eth",
|
||||
"slip44": 60,
|
||||
"slug": "ethereum",
|
||||
"testnet": false
|
||||
} as const satisfies Chain;
|
||||
@@ -0,0 +1,67 @@
|
||||
import type { Chain } from "../src/types";
|
||||
export default {
|
||||
"chain": "ETH",
|
||||
"chainId": 10,
|
||||
"explorers": [
|
||||
{
|
||||
"name": "etherscan",
|
||||
"url": "https://optimistic.etherscan.io",
|
||||
"standard": "EIP3091"
|
||||
},
|
||||
{
|
||||
"name": "blockscout",
|
||||
"url": "https://optimism.blockscout.com",
|
||||
"standard": "EIP3091",
|
||||
"icon": {
|
||||
"url": "ipfs://QmYtUimyqHkkFxYdbXXRbUqNg2VLPUg6Uu2C2nmFWowiZM",
|
||||
"width": 551,
|
||||
"height": 540,
|
||||
"format": "png"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "dexguru",
|
||||
"url": "https://optimism.dex.guru",
|
||||
"standard": "EIP3091",
|
||||
"icon": {
|
||||
"url": "ipfs://QmRaASKRSjQ5btoUQ2rNTJNxKtx2a2RoewgA7DMQkLVEne",
|
||||
"width": 83,
|
||||
"height": 82,
|
||||
"format": "svg"
|
||||
}
|
||||
}
|
||||
],
|
||||
"faucets": [],
|
||||
"features": [],
|
||||
"icon": {
|
||||
"url": "ipfs://QmcxZHpyJa8T4i63xqjPYrZ6tKrt55tZJpbXcjSDKuKaf9/optimism/512.png",
|
||||
"width": 512,
|
||||
"height": 512,
|
||||
"format": "png"
|
||||
},
|
||||
"infoURL": "https://optimism.io",
|
||||
"name": "OP Mainnet",
|
||||
"nativeCurrency": {
|
||||
"name": "Ether",
|
||||
"symbol": "ETH",
|
||||
"decimals": 18
|
||||
},
|
||||
"networkId": 10,
|
||||
"redFlags": [],
|
||||
"rpc": [
|
||||
"https://10.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
|
||||
"https://optimism-mainnet.infura.io/v3/${INFURA_API_KEY}",
|
||||
"https://mainnet.optimism.io",
|
||||
"https://optimism.publicnode.com",
|
||||
"wss://optimism.publicnode.com",
|
||||
"https://optimism.gateway.tenderly.co",
|
||||
"wss://optimism.gateway.tenderly.co",
|
||||
"https://optimism-rpc.publicnode.com",
|
||||
"wss://optimism-rpc.publicnode.com",
|
||||
"https://optimism.drpc.org",
|
||||
"wss://optimism.drpc.org"
|
||||
],
|
||||
"shortName": "oeth",
|
||||
"slug": "optimism",
|
||||
"testnet": false
|
||||
} as const satisfies Chain;
|
||||
@@ -0,0 +1,72 @@
|
||||
import type { Chain } from "../src/types";
|
||||
export default {
|
||||
"chain": "GNO",
|
||||
"chainId": 100,
|
||||
"explorers": [
|
||||
{
|
||||
"name": "blockscout",
|
||||
"url": "https://gnosis.blockscout.com",
|
||||
"standard": "EIP3091",
|
||||
"icon": {
|
||||
"url": "ipfs://QmYtUimyqHkkFxYdbXXRbUqNg2VLPUg6Uu2C2nmFWowiZM",
|
||||
"width": 551,
|
||||
"height": 540,
|
||||
"format": "png"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "gnosisscan",
|
||||
"url": "https://gnosisscan.io",
|
||||
"standard": "EIP3091"
|
||||
},
|
||||
{
|
||||
"name": "dexguru",
|
||||
"url": "https://gnosis.dex.guru",
|
||||
"standard": "EIP3091",
|
||||
"icon": {
|
||||
"url": "ipfs://QmRaASKRSjQ5btoUQ2rNTJNxKtx2a2RoewgA7DMQkLVEne",
|
||||
"width": 83,
|
||||
"height": 82,
|
||||
"format": "svg"
|
||||
}
|
||||
}
|
||||
],
|
||||
"faucets": [
|
||||
"https://gnosisfaucet.com",
|
||||
"https://stakely.io/faucet/gnosis-chain-xdai",
|
||||
"https://faucet.prussia.dev/xdai"
|
||||
],
|
||||
"icon": {
|
||||
"url": "ipfs://bafybeidk4swpgdyqmpz6shd5onvpaujvwiwthrhypufnwr6xh3dausz2dm",
|
||||
"width": 1800,
|
||||
"height": 1800,
|
||||
"format": "png"
|
||||
},
|
||||
"infoURL": "https://docs.gnosischain.com",
|
||||
"name": "Gnosis",
|
||||
"nativeCurrency": {
|
||||
"name": "xDAI",
|
||||
"symbol": "XDAI",
|
||||
"decimals": 18
|
||||
},
|
||||
"networkId": 100,
|
||||
"rpc": [
|
||||
"https://100.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
|
||||
"https://rpc.gnosischain.com",
|
||||
"https://rpc.gnosis.gateway.fm",
|
||||
"https://rpc.ankr.com/gnosis",
|
||||
"https://gnosischain-rpc.gateway.pokt.network",
|
||||
"https://gnosis-mainnet.public.blastapi.io",
|
||||
"https://gnosis.api.onfinality.io/public",
|
||||
"https://gnosis.blockpi.network/v1/rpc/public",
|
||||
"https://web3endpoints.com/gnosischain-mainnet",
|
||||
"https://gnosis.oat.farm",
|
||||
"wss://rpc.gnosischain.com/wss",
|
||||
"https://gnosis-rpc.publicnode.com",
|
||||
"wss://gnosis-rpc.publicnode.com"
|
||||
],
|
||||
"shortName": "gno",
|
||||
"slip44": 700,
|
||||
"slug": "gnosis",
|
||||
"testnet": false
|
||||
} as const satisfies Chain;
|
||||
@@ -1,21 +1,7 @@
|
||||
import type { Chain } from "../src/types";
|
||||
export default {
|
||||
"name": "GTON Mainnet",
|
||||
"chain": "GTON",
|
||||
"rpc": [
|
||||
"https://gton.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
|
||||
"https://rpc.gton.network/"
|
||||
],
|
||||
"faucets": [],
|
||||
"nativeCurrency": {
|
||||
"name": "GCD",
|
||||
"symbol": "GCD",
|
||||
"decimals": 18
|
||||
},
|
||||
"infoURL": "https://gton.capital",
|
||||
"shortName": "gton",
|
||||
"chainId": 1000,
|
||||
"networkId": 1000,
|
||||
"explorers": [
|
||||
{
|
||||
"name": "GTON Network Explorer",
|
||||
@@ -23,10 +9,24 @@ export default {
|
||||
"standard": "EIP3091"
|
||||
}
|
||||
],
|
||||
"faucets": [],
|
||||
"infoURL": "https://gton.capital",
|
||||
"name": "GTON Mainnet",
|
||||
"nativeCurrency": {
|
||||
"name": "GCD",
|
||||
"symbol": "GCD",
|
||||
"decimals": 18
|
||||
},
|
||||
"networkId": 1000,
|
||||
"parent": {
|
||||
"type": "L2",
|
||||
"chain": "eip155-1"
|
||||
},
|
||||
"testnet": false,
|
||||
"slug": "gton"
|
||||
"rpc": [
|
||||
"https://1000.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
|
||||
"https://rpc.gton.network/"
|
||||
],
|
||||
"shortName": "gton",
|
||||
"slug": "gton",
|
||||
"testnet": false
|
||||
} as const satisfies Chain;
|
||||
@@ -1,24 +1,25 @@
|
||||
import type { Chain } from "../src/types";
|
||||
export default {
|
||||
"name": "Smart Bitcoin Cash",
|
||||
"chain": "smartBCH",
|
||||
"rpc": [
|
||||
"https://smart-bitcoin-cash.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
|
||||
"https://smartbch.greyh.at",
|
||||
"https://rpc-mainnet.smartbch.org",
|
||||
"https://smartbch.fountainhead.cash/mainnet",
|
||||
"https://smartbch.devops.cash/mainnet"
|
||||
],
|
||||
"chainId": 10000,
|
||||
"explorers": [],
|
||||
"faucets": [],
|
||||
"infoURL": "https://smartbch.org/",
|
||||
"name": "Smart Bitcoin Cash",
|
||||
"nativeCurrency": {
|
||||
"name": "Bitcoin Cash",
|
||||
"symbol": "BCH",
|
||||
"decimals": 18
|
||||
},
|
||||
"infoURL": "https://smartbch.org/",
|
||||
"shortName": "smartbch",
|
||||
"chainId": 10000,
|
||||
"networkId": 10000,
|
||||
"testnet": false,
|
||||
"slug": "smart-bitcoin-cash"
|
||||
"rpc": [
|
||||
"https://10000.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
|
||||
"https://smartbch.greyh.at",
|
||||
"https://rpc-mainnet.smartbch.org",
|
||||
"https://smartbch.fountainhead.cash/mainnet",
|
||||
"https://smartbch.devops.cash/mainnet"
|
||||
],
|
||||
"shortName": "smartbch",
|
||||
"slug": "smart-bitcoin-cash",
|
||||
"testnet": false
|
||||
} as const satisfies Chain;
|
||||
@@ -0,0 +1,22 @@
|
||||
import type { Chain } from "../src/types";
|
||||
export default {
|
||||
"chain": "QuarkChain",
|
||||
"chainId": 100000,
|
||||
"explorers": [],
|
||||
"faucets": [],
|
||||
"infoURL": "https://www.quarkchain.io",
|
||||
"name": "QuarkChain Mainnet Root",
|
||||
"nativeCurrency": {
|
||||
"name": "QKC",
|
||||
"symbol": "QKC",
|
||||
"decimals": 18
|
||||
},
|
||||
"networkId": 100000,
|
||||
"rpc": [
|
||||
"https://100000.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
|
||||
"http://jrpc.mainnet.quarkchain.io:38391"
|
||||
],
|
||||
"shortName": "qkc-r",
|
||||
"slug": "quarkchain-root",
|
||||
"testnet": false
|
||||
} as const satisfies Chain;
|
||||
@@ -1,26 +1,7 @@
|
||||
import type { Chain } from "../src/types";
|
||||
export default {
|
||||
"name": "QuarkChain Mainnet Shard 0",
|
||||
"chain": "QuarkChain",
|
||||
"rpc": [
|
||||
"https://quarkchain-shard-0.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
|
||||
"https://mainnet-s0-ethapi.quarkchain.io",
|
||||
"http://eth-jrpc.mainnet.quarkchain.io:39000"
|
||||
],
|
||||
"faucets": [],
|
||||
"nativeCurrency": {
|
||||
"name": "QKC",
|
||||
"symbol": "QKC",
|
||||
"decimals": 18
|
||||
},
|
||||
"infoURL": "https://www.quarkchain.io",
|
||||
"shortName": "qkc-s0",
|
||||
"chainId": 100001,
|
||||
"networkId": 100001,
|
||||
"parent": {
|
||||
"chain": "eip155-100000",
|
||||
"type": "shard"
|
||||
},
|
||||
"explorers": [
|
||||
{
|
||||
"name": "quarkchain-mainnet",
|
||||
@@ -28,6 +9,25 @@ export default {
|
||||
"standard": "EIP3091"
|
||||
}
|
||||
],
|
||||
"testnet": false,
|
||||
"slug": "quarkchain-shard-0"
|
||||
"faucets": [],
|
||||
"infoURL": "https://www.quarkchain.io",
|
||||
"name": "QuarkChain Mainnet Shard 0",
|
||||
"nativeCurrency": {
|
||||
"name": "QKC",
|
||||
"symbol": "QKC",
|
||||
"decimals": 18
|
||||
},
|
||||
"networkId": 100001,
|
||||
"parent": {
|
||||
"type": "shard",
|
||||
"chain": "eip155-100000"
|
||||
},
|
||||
"rpc": [
|
||||
"https://100001.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
|
||||
"https://mainnet-s0-ethapi.quarkchain.io",
|
||||
"http://eth-jrpc.mainnet.quarkchain.io:39000"
|
||||
],
|
||||
"shortName": "qkc-s0",
|
||||
"slug": "quarkchain-shard-0",
|
||||
"testnet": false
|
||||
} as const satisfies Chain;
|
||||
@@ -1,26 +1,7 @@
|
||||
import type { Chain } from "../src/types";
|
||||
export default {
|
||||
"name": "QuarkChain Mainnet Shard 1",
|
||||
"chain": "QuarkChain",
|
||||
"rpc": [
|
||||
"https://quarkchain-shard-1.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
|
||||
"https://mainnet-s1-ethapi.quarkchain.io",
|
||||
"http://eth-jrpc.mainnet.quarkchain.io:39001"
|
||||
],
|
||||
"faucets": [],
|
||||
"nativeCurrency": {
|
||||
"name": "QKC",
|
||||
"symbol": "QKC",
|
||||
"decimals": 18
|
||||
},
|
||||
"infoURL": "https://www.quarkchain.io",
|
||||
"shortName": "qkc-s1",
|
||||
"chainId": 100002,
|
||||
"networkId": 100002,
|
||||
"parent": {
|
||||
"chain": "eip155-100000",
|
||||
"type": "shard"
|
||||
},
|
||||
"explorers": [
|
||||
{
|
||||
"name": "quarkchain-mainnet",
|
||||
@@ -28,6 +9,25 @@ export default {
|
||||
"standard": "EIP3091"
|
||||
}
|
||||
],
|
||||
"testnet": false,
|
||||
"slug": "quarkchain-shard-1"
|
||||
"faucets": [],
|
||||
"infoURL": "https://www.quarkchain.io",
|
||||
"name": "QuarkChain Mainnet Shard 1",
|
||||
"nativeCurrency": {
|
||||
"name": "QKC",
|
||||
"symbol": "QKC",
|
||||
"decimals": 18
|
||||
},
|
||||
"networkId": 100002,
|
||||
"parent": {
|
||||
"type": "shard",
|
||||
"chain": "eip155-100000"
|
||||
},
|
||||
"rpc": [
|
||||
"https://100002.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
|
||||
"https://mainnet-s1-ethapi.quarkchain.io",
|
||||
"http://eth-jrpc.mainnet.quarkchain.io:39001"
|
||||
],
|
||||
"shortName": "qkc-s1",
|
||||
"slug": "quarkchain-shard-1",
|
||||
"testnet": false
|
||||
} as const satisfies Chain;
|
||||
@@ -1,26 +1,7 @@
|
||||
import type { Chain } from "../src/types";
|
||||
export default {
|
||||
"name": "QuarkChain Mainnet Shard 2",
|
||||
"chain": "QuarkChain",
|
||||
"rpc": [
|
||||
"https://quarkchain-shard-2.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
|
||||
"https://mainnet-s2-ethapi.quarkchain.io",
|
||||
"http://eth-jrpc.mainnet.quarkchain.io:39002"
|
||||
],
|
||||
"faucets": [],
|
||||
"nativeCurrency": {
|
||||
"name": "QKC",
|
||||
"symbol": "QKC",
|
||||
"decimals": 18
|
||||
},
|
||||
"infoURL": "https://www.quarkchain.io",
|
||||
"shortName": "qkc-s2",
|
||||
"chainId": 100003,
|
||||
"networkId": 100003,
|
||||
"parent": {
|
||||
"chain": "eip155-100000",
|
||||
"type": "shard"
|
||||
},
|
||||
"explorers": [
|
||||
{
|
||||
"name": "quarkchain-mainnet",
|
||||
@@ -28,6 +9,25 @@ export default {
|
||||
"standard": "EIP3091"
|
||||
}
|
||||
],
|
||||
"testnet": false,
|
||||
"slug": "quarkchain-shard-2"
|
||||
"faucets": [],
|
||||
"infoURL": "https://www.quarkchain.io",
|
||||
"name": "QuarkChain Mainnet Shard 2",
|
||||
"nativeCurrency": {
|
||||
"name": "QKC",
|
||||
"symbol": "QKC",
|
||||
"decimals": 18
|
||||
},
|
||||
"networkId": 100003,
|
||||
"parent": {
|
||||
"type": "shard",
|
||||
"chain": "eip155-100000"
|
||||
},
|
||||
"rpc": [
|
||||
"https://100003.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
|
||||
"https://mainnet-s2-ethapi.quarkchain.io",
|
||||
"http://eth-jrpc.mainnet.quarkchain.io:39002"
|
||||
],
|
||||
"shortName": "qkc-s2",
|
||||
"slug": "quarkchain-shard-2",
|
||||
"testnet": false
|
||||
} as const satisfies Chain;
|
||||
@@ -1,26 +1,7 @@
|
||||
import type { Chain } from "../src/types";
|
||||
export default {
|
||||
"name": "QuarkChain Mainnet Shard 3",
|
||||
"chain": "QuarkChain",
|
||||
"rpc": [
|
||||
"https://quarkchain-shard-3.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
|
||||
"https://mainnet-s3-ethapi.quarkchain.io",
|
||||
"http://eth-jrpc.mainnet.quarkchain.io:39003"
|
||||
],
|
||||
"faucets": [],
|
||||
"nativeCurrency": {
|
||||
"name": "QKC",
|
||||
"symbol": "QKC",
|
||||
"decimals": 18
|
||||
},
|
||||
"infoURL": "https://www.quarkchain.io",
|
||||
"shortName": "qkc-s3",
|
||||
"chainId": 100004,
|
||||
"networkId": 100004,
|
||||
"parent": {
|
||||
"chain": "eip155-100000",
|
||||
"type": "shard"
|
||||
},
|
||||
"explorers": [
|
||||
{
|
||||
"name": "quarkchain-mainnet",
|
||||
@@ -28,6 +9,25 @@ export default {
|
||||
"standard": "EIP3091"
|
||||
}
|
||||
],
|
||||
"testnet": false,
|
||||
"slug": "quarkchain-shard-3"
|
||||
"faucets": [],
|
||||
"infoURL": "https://www.quarkchain.io",
|
||||
"name": "QuarkChain Mainnet Shard 3",
|
||||
"nativeCurrency": {
|
||||
"name": "QKC",
|
||||
"symbol": "QKC",
|
||||
"decimals": 18
|
||||
},
|
||||
"networkId": 100004,
|
||||
"parent": {
|
||||
"type": "shard",
|
||||
"chain": "eip155-100000"
|
||||
},
|
||||
"rpc": [
|
||||
"https://100004.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
|
||||
"https://mainnet-s3-ethapi.quarkchain.io",
|
||||
"http://eth-jrpc.mainnet.quarkchain.io:39003"
|
||||
],
|
||||
"shortName": "qkc-s3",
|
||||
"slug": "quarkchain-shard-3",
|
||||
"testnet": false
|
||||
} as const satisfies Chain;
|
||||
@@ -1,26 +1,7 @@
|
||||
import type { Chain } from "../src/types";
|
||||
export default {
|
||||
"name": "QuarkChain Mainnet Shard 4",
|
||||
"chain": "QuarkChain",
|
||||
"rpc": [
|
||||
"https://quarkchain-shard-4.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
|
||||
"https://mainnet-s4-ethapi.quarkchain.io",
|
||||
"http://eth-jrpc.mainnet.quarkchain.io:39004"
|
||||
],
|
||||
"faucets": [],
|
||||
"nativeCurrency": {
|
||||
"name": "QKC",
|
||||
"symbol": "QKC",
|
||||
"decimals": 18
|
||||
},
|
||||
"infoURL": "https://www.quarkchain.io",
|
||||
"shortName": "qkc-s4",
|
||||
"chainId": 100005,
|
||||
"networkId": 100005,
|
||||
"parent": {
|
||||
"chain": "eip155-100000",
|
||||
"type": "shard"
|
||||
},
|
||||
"explorers": [
|
||||
{
|
||||
"name": "quarkchain-mainnet",
|
||||
@@ -28,6 +9,25 @@ export default {
|
||||
"standard": "EIP3091"
|
||||
}
|
||||
],
|
||||
"testnet": false,
|
||||
"slug": "quarkchain-shard-4"
|
||||
"faucets": [],
|
||||
"infoURL": "https://www.quarkchain.io",
|
||||
"name": "QuarkChain Mainnet Shard 4",
|
||||
"nativeCurrency": {
|
||||
"name": "QKC",
|
||||
"symbol": "QKC",
|
||||
"decimals": 18
|
||||
},
|
||||
"networkId": 100005,
|
||||
"parent": {
|
||||
"type": "shard",
|
||||
"chain": "eip155-100000"
|
||||
},
|
||||
"rpc": [
|
||||
"https://100005.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
|
||||
"https://mainnet-s4-ethapi.quarkchain.io",
|
||||
"http://eth-jrpc.mainnet.quarkchain.io:39004"
|
||||
],
|
||||
"shortName": "qkc-s4",
|
||||
"slug": "quarkchain-shard-4",
|
||||
"testnet": false
|
||||
} as const satisfies Chain;
|
||||
@@ -1,26 +1,7 @@
|
||||
import type { Chain } from "../src/types";
|
||||
export default {
|
||||
"name": "QuarkChain Mainnet Shard 5",
|
||||
"chain": "QuarkChain",
|
||||
"rpc": [
|
||||
"https://quarkchain-shard-5.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
|
||||
"https://mainnet-s5-ethapi.quarkchain.io",
|
||||
"http://eth-jrpc.mainnet.quarkchain.io:39005"
|
||||
],
|
||||
"faucets": [],
|
||||
"nativeCurrency": {
|
||||
"name": "QKC",
|
||||
"symbol": "QKC",
|
||||
"decimals": 18
|
||||
},
|
||||
"infoURL": "https://www.quarkchain.io",
|
||||
"shortName": "qkc-s5",
|
||||
"chainId": 100006,
|
||||
"networkId": 100006,
|
||||
"parent": {
|
||||
"chain": "eip155-100000",
|
||||
"type": "shard"
|
||||
},
|
||||
"explorers": [
|
||||
{
|
||||
"name": "quarkchain-mainnet",
|
||||
@@ -28,6 +9,25 @@ export default {
|
||||
"standard": "EIP3091"
|
||||
}
|
||||
],
|
||||
"testnet": false,
|
||||
"slug": "quarkchain-shard-5"
|
||||
"faucets": [],
|
||||
"infoURL": "https://www.quarkchain.io",
|
||||
"name": "QuarkChain Mainnet Shard 5",
|
||||
"nativeCurrency": {
|
||||
"name": "QKC",
|
||||
"symbol": "QKC",
|
||||
"decimals": 18
|
||||
},
|
||||
"networkId": 100006,
|
||||
"parent": {
|
||||
"type": "shard",
|
||||
"chain": "eip155-100000"
|
||||
},
|
||||
"rpc": [
|
||||
"https://100006.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
|
||||
"https://mainnet-s5-ethapi.quarkchain.io",
|
||||
"http://eth-jrpc.mainnet.quarkchain.io:39005"
|
||||
],
|
||||
"shortName": "qkc-s5",
|
||||
"slug": "quarkchain-shard-5",
|
||||
"testnet": false
|
||||
} as const satisfies Chain;
|
||||
@@ -1,26 +1,7 @@
|
||||
import type { Chain } from "../src/types";
|
||||
export default {
|
||||
"name": "QuarkChain Mainnet Shard 6",
|
||||
"chain": "QuarkChain",
|
||||
"rpc": [
|
||||
"https://quarkchain-shard-6.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
|
||||
"https://mainnet-s6-ethapi.quarkchain.io",
|
||||
"http://eth-jrpc.mainnet.quarkchain.io:39006"
|
||||
],
|
||||
"faucets": [],
|
||||
"nativeCurrency": {
|
||||
"name": "QKC",
|
||||
"symbol": "QKC",
|
||||
"decimals": 18
|
||||
},
|
||||
"infoURL": "https://www.quarkchain.io",
|
||||
"shortName": "qkc-s6",
|
||||
"chainId": 100007,
|
||||
"networkId": 100007,
|
||||
"parent": {
|
||||
"chain": "eip155-100000",
|
||||
"type": "shard"
|
||||
},
|
||||
"explorers": [
|
||||
{
|
||||
"name": "quarkchain-mainnet",
|
||||
@@ -28,6 +9,25 @@ export default {
|
||||
"standard": "EIP3091"
|
||||
}
|
||||
],
|
||||
"testnet": false,
|
||||
"slug": "quarkchain-shard-6"
|
||||
"faucets": [],
|
||||
"infoURL": "https://www.quarkchain.io",
|
||||
"name": "QuarkChain Mainnet Shard 6",
|
||||
"nativeCurrency": {
|
||||
"name": "QKC",
|
||||
"symbol": "QKC",
|
||||
"decimals": 18
|
||||
},
|
||||
"networkId": 100007,
|
||||
"parent": {
|
||||
"type": "shard",
|
||||
"chain": "eip155-100000"
|
||||
},
|
||||
"rpc": [
|
||||
"https://100007.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
|
||||
"https://mainnet-s6-ethapi.quarkchain.io",
|
||||
"http://eth-jrpc.mainnet.quarkchain.io:39006"
|
||||
],
|
||||
"shortName": "qkc-s6",
|
||||
"slug": "quarkchain-shard-6",
|
||||
"testnet": false
|
||||
} as const satisfies Chain;
|
||||
@@ -1,26 +1,7 @@
|
||||
import type { Chain } from "../src/types";
|
||||
export default {
|
||||
"name": "QuarkChain Mainnet Shard 7",
|
||||
"chain": "QuarkChain",
|
||||
"rpc": [
|
||||
"https://quarkchain-shard-7.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
|
||||
"https://mainnet-s7-ethapi.quarkchain.io",
|
||||
"http://eth-jrpc.mainnet.quarkchain.io:39007"
|
||||
],
|
||||
"faucets": [],
|
||||
"nativeCurrency": {
|
||||
"name": "QKC",
|
||||
"symbol": "QKC",
|
||||
"decimals": 18
|
||||
},
|
||||
"infoURL": "https://www.quarkchain.io",
|
||||
"shortName": "qkc-s7",
|
||||
"chainId": 100008,
|
||||
"networkId": 100008,
|
||||
"parent": {
|
||||
"chain": "eip155-100000",
|
||||
"type": "shard"
|
||||
},
|
||||
"explorers": [
|
||||
{
|
||||
"name": "quarkchain-mainnet",
|
||||
@@ -28,6 +9,25 @@ export default {
|
||||
"standard": "EIP3091"
|
||||
}
|
||||
],
|
||||
"testnet": false,
|
||||
"slug": "quarkchain-shard-7"
|
||||
"faucets": [],
|
||||
"infoURL": "https://www.quarkchain.io",
|
||||
"name": "QuarkChain Mainnet Shard 7",
|
||||
"nativeCurrency": {
|
||||
"name": "QKC",
|
||||
"symbol": "QKC",
|
||||
"decimals": 18
|
||||
},
|
||||
"networkId": 100008,
|
||||
"parent": {
|
||||
"type": "shard",
|
||||
"chain": "eip155-100000"
|
||||
},
|
||||
"rpc": [
|
||||
"https://100008.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
|
||||
"https://mainnet-s7-ethapi.quarkchain.io",
|
||||
"http://eth-jrpc.mainnet.quarkchain.io:39007"
|
||||
],
|
||||
"shortName": "qkc-s7",
|
||||
"slug": "quarkchain-shard-7",
|
||||
"testnet": false
|
||||
} as const satisfies Chain;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user