Compare commits
508
Commits
burn-to-mint
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1dedab9375 | ||
|
|
ea7dea6a2a | ||
|
|
ed9105684c | ||
|
|
ecfd667ffb | ||
|
|
3511a57eb9 | ||
|
|
15d1121a61 | ||
|
|
28741049ee | ||
|
|
dd4265a0fc | ||
|
|
1003fcf4ae | ||
|
|
b0dff1d436 | ||
|
|
a96c80462d | ||
|
|
d889e7e04b | ||
|
|
66ef7d32e5 | ||
|
|
be93200f53 | ||
|
|
3bbed658a7 | ||
|
|
fa1475f109 | ||
|
|
2d08ce1f8a | ||
|
|
d1b5041312 | ||
|
|
0958b0db94 | ||
|
|
3cf6ed3111 | ||
|
|
0354131ad1 | ||
|
|
f7880540ad | ||
|
|
123e07362d | ||
|
|
1dcba927f7 | ||
|
|
aaf7b82b43 | ||
|
|
467c7a4cfb | ||
|
|
f9ad7cfc11 | ||
|
|
5e963cf615 | ||
|
|
41d71a6765 | ||
|
|
22d58dbe45 | ||
|
|
471ff43fcf | ||
|
|
027c4c6821 | ||
|
|
f0b94422b1 | ||
|
|
bb8b3e1791 | ||
|
|
42707adc0b | ||
|
|
35bed9d6aa | ||
|
|
93f98bcec6 | ||
|
|
83835dfba2 | ||
|
|
d85e19b770 | ||
|
|
1abb2f82df | ||
|
|
b44e4512a5 | ||
|
|
a226d8b341 | ||
|
|
8a02a7f80d | ||
|
|
122c61055f | ||
|
|
81880aecdd | ||
|
|
b38562d9fd | ||
|
|
0eb94e4ee8 | ||
|
|
3a150a50f3 | ||
|
|
03c008d0fa | ||
|
|
6487646f2c | ||
|
|
b1094f5231 | ||
|
|
97bb60b3ff | ||
|
|
9654ec845e | ||
|
|
41e8a3961a | ||
|
|
43fd0d7848 | ||
|
|
cc16f7ceb1 | ||
|
|
cc58de90a4 | ||
|
|
27f8510335 | ||
|
|
0fc60f6c05 | ||
|
|
c59531f87e | ||
|
|
6ddd780338 | ||
|
|
57e36b6dbd | ||
|
|
1a866db888 | ||
|
|
7e97f5d393 | ||
|
|
e26068085d | ||
|
|
1ccc55a3b2 | ||
|
|
99f7b25f10 | ||
|
|
3308cfdcf2 | ||
|
|
5e9fdc1cf1 | ||
|
|
39847d1ab9 | ||
|
|
29cd89f6a5 | ||
|
|
6466945e28 | ||
|
|
db9ffb0899 | ||
|
|
a295dd5d4a | ||
|
|
85ac7a4f71 | ||
|
|
ee46fa64d3 | ||
|
|
f5f14fb330 | ||
|
|
fd65316d1f | ||
|
|
004b102540 | ||
|
|
9095c5c06c | ||
|
|
55e46cc830 | ||
|
|
d07ad7db04 | ||
|
|
8f0a0e84aa | ||
|
|
4e6db44105 | ||
|
|
7b0f1f6176 | ||
|
|
fe8bfd14bc | ||
|
|
1c7c0a682d | ||
|
|
b58549b1a2 | ||
|
|
6cdd2a24ac | ||
|
|
437ac5b6f5 | ||
|
|
afef36375b | ||
|
|
0231692eb1 | ||
|
|
e0d6aaee4d | ||
|
|
c4027859c0 | ||
|
|
c12fd51525 | ||
|
|
f1aeeb2167 | ||
|
|
103921d946 | ||
|
|
b4fcc63de6 | ||
|
|
b3dea5e757 | ||
|
|
e43ec78acc | ||
|
|
4c312d0ad8 | ||
|
|
89836ad84e | ||
|
|
51f38f12d7 | ||
|
|
609745ce11 | ||
|
|
3b9778270b | ||
|
|
c66b21792c | ||
|
|
35a9c0eba8 | ||
|
|
07152745e0 | ||
|
|
7a455b60bc | ||
|
|
67694d4c5d | ||
|
|
4ec704f588 | ||
|
|
3f44e342e8 | ||
|
|
a3466c869b | ||
|
|
245fd2253b | ||
|
|
ed1844fdf0 | ||
|
|
1d1a901312 | ||
|
|
85d38d7882 | ||
|
|
6316a68408 | ||
|
|
1bce40a6f8 | ||
|
|
d651a10dc0 | ||
|
|
abac647879 | ||
|
|
d6d9e3c666 | ||
|
|
a949a1e103 | ||
|
|
39385ee82e | ||
|
|
440ee78122 | ||
|
|
ae2b565af6 | ||
|
|
d28af744a5 | ||
|
|
5eff907b9f | ||
|
|
1f31cdbe29 | ||
|
|
954f73e99e | ||
|
|
3cb943c6d0 | ||
|
|
b66e6d0920 | ||
|
|
0293b12821 | ||
|
|
fce1fa6bf4 | ||
|
|
c56659e05a | ||
|
|
ab4d4fa31d | ||
|
|
92ce752b15 | ||
|
|
01936d8f5e | ||
|
|
8ceacb6725 | ||
|
|
5a34f3f7e4 | ||
|
|
02df116e09 | ||
|
|
0e5d66f79a | ||
|
|
e219566cd2 | ||
|
|
2a293f1a62 | ||
|
|
19e5fd0abb | ||
|
|
f35d9954c3 | ||
|
|
d60c554125 | ||
|
|
e51659b15f | ||
|
|
8d3b337afa | ||
|
|
ab724afb9c | ||
|
|
aba83c21a4 | ||
|
|
724fcf1120 | ||
|
|
d7235264f9 | ||
|
|
3c3b60ebe8 | ||
|
|
6f403fe70a | ||
|
|
1ad2de146f | ||
|
|
de5de84873 | ||
|
|
8470008360 | ||
|
|
a4b114e01d | ||
|
|
e4564511d1 | ||
|
|
520df5669a | ||
|
|
c87af7dbec | ||
|
|
eb1a448896 | ||
|
|
9e63aba259 | ||
|
|
57fabf90da | ||
|
|
58ae7c05af | ||
|
|
1015590bed | ||
|
|
490bd3e84d | ||
|
|
ab801768b9 | ||
|
|
7526cc91d1 | ||
|
|
f6253dd6c7 | ||
|
|
680b2ac258 | ||
|
|
1f3e3f56da | ||
|
|
d8afd9f637 | ||
|
|
203e0614b0 | ||
|
|
da79f4f6e5 | ||
|
|
cd6a69970b | ||
|
|
323837e67f | ||
|
|
f4490fb237 | ||
|
|
5787c90811 | ||
|
|
df571b6c58 | ||
|
|
6caab5ce69 | ||
|
|
b4290ba9b9 | ||
|
|
57d73d9ed9 | ||
|
|
adfdfd22e8 | ||
|
|
6e51a57e46 | ||
|
|
8f56ac0390 | ||
|
|
d88596559a | ||
|
|
b1ce309cb2 | ||
|
|
064d966855 | ||
|
|
59f15801ab | ||
|
|
dcb2a9c072 | ||
|
|
3065090803 | ||
|
|
412a467391 | ||
|
|
529e727a7d | ||
|
|
ee0953e18a | ||
|
|
71685b8688 | ||
|
|
a44ab5cc75 | ||
|
|
794558dea3 | ||
|
|
4f40da141d | ||
|
|
35f1ba0ca6 | ||
|
|
7951669ed2 | ||
|
|
20944cf5de | ||
|
|
0f1c4a027b | ||
|
|
039f1d02fb | ||
|
|
9a7e2fea5e | ||
|
|
9760ba5bc2 | ||
|
|
da3182f5a6 | ||
|
|
1cdc9d8662 | ||
|
|
294fda9136 | ||
|
|
c62d7c10ba | ||
|
|
ac3f65f866 | ||
|
|
3d24e5a8f7 | ||
|
|
cbf401638a | ||
|
|
2f2b628782 | ||
|
|
005646fd18 | ||
|
|
16e6d45c18 | ||
|
|
f0ba060a14 | ||
|
|
b40eacf5f9 | ||
|
|
ef7bed1479 | ||
|
|
b969e0cd22 | ||
|
|
fde773e2f2 | ||
|
|
108e6e034d | ||
|
|
8ef767ef02 | ||
|
|
1d5ba3aa78 | ||
|
|
87c2f43540 | ||
|
|
939552255b | ||
|
|
9e1a558148 | ||
|
|
70e17fd36e | ||
|
|
f4b1760e3c | ||
|
|
cd9861ff54 | ||
|
|
ea0765e1a7 | ||
|
|
0543b8459d | ||
|
|
816a834f75 | ||
|
|
9111b0b8a7 | ||
|
|
fb78db22cc | ||
|
|
2d5f5ed511 | ||
|
|
90ec372f96 | ||
|
|
50a8ea53b1 | ||
|
|
9ae9948252 | ||
|
|
6ab7d4017b | ||
|
|
4d53d498d7 | ||
|
|
3e052e6e1e | ||
|
|
8a27afe29a | ||
|
|
626993ed83 | ||
|
|
2ef9e3ccb9 | ||
|
|
a13e0610e4 | ||
|
|
322b8c681c | ||
|
|
017096ceb0 | ||
|
|
f19ced2d32 | ||
|
|
e6de5297da | ||
|
|
0ca2d63161 | ||
|
|
a204face96 | ||
|
|
79bb24d33a | ||
|
|
f475cec576 | ||
|
|
a0bbc0ebeb | ||
|
|
a2b2a072e6 | ||
|
|
32fe46081e | ||
|
|
e8f3e0e4b5 | ||
|
|
a611016953 | ||
|
|
bfcb84c8de | ||
|
|
77b80fc989 | ||
|
|
f77ab91245 | ||
|
|
488d330623 | ||
|
|
187784261f | ||
|
|
758899b031 | ||
|
|
03ae3e522f | ||
|
|
e65a79bf8b | ||
|
|
5e64456fe2 | ||
|
|
e9d3efefed | ||
|
|
32b3c5ffd6 | ||
|
|
99e0caabfd | ||
|
|
aa53f38554 | ||
|
|
0968423e05 | ||
|
|
4b8f9be8b6 | ||
|
|
aea28679bb | ||
|
|
e036f22eb1 | ||
|
|
7201210e93 | ||
|
|
1901bc7c49 | ||
|
|
2a616fe794 | ||
|
|
a9efc0cbfa | ||
|
|
9c31d30a0c | ||
|
|
a6f30994df | ||
|
|
9b01e7205f | ||
|
|
b41b927632 | ||
|
|
7e084b01d7 | ||
|
|
e99832c283 | ||
|
|
f8b7e4aea6 | ||
|
|
3638b04d0d | ||
|
|
cfa160d5eb | ||
|
|
17751c5455 | ||
|
|
8615176670 | ||
|
|
fc9ba16ffb | ||
|
|
3d43af4680 | ||
|
|
d6cc8a700f | ||
|
|
cce90213d8 | ||
|
|
120efa9028 | ||
|
|
dc4822aa70 | ||
|
|
53220109d1 | ||
|
|
b8d416b99f | ||
|
|
a48fb8c5cc | ||
|
|
570f6990b0 | ||
|
|
c11e99ecc9 | ||
|
|
abdfd74663 | ||
|
|
d94ab200c2 | ||
|
|
1bf56eebf3 | ||
|
|
e8be3c54eb | ||
|
|
5f675d9177 | ||
|
|
d62873a2db | ||
|
|
fb51f1519f | ||
|
|
eaf60d5594 | ||
|
|
bf6ae87a69 | ||
|
|
5c098ed313 | ||
|
|
fd335f766b | ||
|
|
3a4e595ae5 | ||
|
|
af3e0f2186 | ||
|
|
fddf9e28cf | ||
|
|
dfe0a27f77 | ||
|
|
27864e20f1 | ||
|
|
8b1c9e669d | ||
|
|
60a03a3069 | ||
|
|
fbeeb4212a | ||
|
|
8fc3f71413 | ||
|
|
840483a830 | ||
|
|
b2e61d5529 | ||
|
|
0365aa5a7b | ||
|
|
2dcbda6f25 | ||
|
|
5631362990 | ||
|
|
e6f0a5b91f | ||
|
|
2e55923ac3 | ||
|
|
ca5ffa0a00 | ||
|
|
1e1acf5e07 | ||
|
|
3a43fb5420 | ||
|
|
5a80ad1587 | ||
|
|
3325a93edb | ||
|
|
51acae6a78 | ||
|
|
b398650794 | ||
|
|
f092d7d926 | ||
|
|
0595c4de25 | ||
|
|
584a33c388 | ||
|
|
2b893e6e60 | ||
|
|
b697b1a857 | ||
|
|
ca8f5cca58 | ||
|
|
9fcd5b82f4 | ||
|
|
2710be6959 | ||
|
|
d541fe7294 | ||
|
|
3a3c4589e6 | ||
|
|
bd18197e88 | ||
|
|
883fc98cad | ||
|
|
2160533a60 | ||
|
|
6dd0e5ef8b | ||
|
|
38a33273dc | ||
|
|
53322784fc | ||
|
|
03094427bc | ||
|
|
0eaa66f2e5 | ||
|
|
defc55abf6 | ||
|
|
fbc457f9cd | ||
|
|
6db97d615c | ||
|
|
2f2a7c6a76 | ||
|
|
a857e7fef1 | ||
|
|
23e6278f30 | ||
|
|
40fb1933e4 | ||
|
|
572968cf24 | ||
|
|
9d02f73347 | ||
|
|
88177fd446 | ||
|
|
dd33b6129c | ||
|
|
c455eafb7b | ||
|
|
e0f41fd692 | ||
|
|
5a7386020e | ||
|
|
b65fd5d3c9 | ||
|
|
c4f486f1f0 | ||
|
|
75a2d4c089 | ||
|
|
26c39e8985 | ||
|
|
387aa5c703 | ||
|
|
be2d644ec9 | ||
|
|
df0c7a5f1f | ||
|
|
5db159dc96 | ||
|
|
70dad6b7c6 | ||
|
|
cefbd37fcf | ||
|
|
7aa5256827 | ||
|
|
1d46945df4 | ||
|
|
9e7afee3fc | ||
|
|
a5b5e89d71 | ||
|
|
563a096483 | ||
|
|
898c0d2eab | ||
|
|
c7a682b407 | ||
|
|
93a4c6e5b3 | ||
|
|
6d8056fada | ||
|
|
eab00f140e | ||
|
|
5fc43159e3 | ||
|
|
50ac9b9545 | ||
|
|
4566c1bdc7 | ||
|
|
6475f55e7e | ||
|
|
ed568d4a25 | ||
|
|
c6534d30f5 | ||
|
|
795d54e4c4 | ||
|
|
7e7fc41b85 | ||
|
|
4324b225cc | ||
|
|
3772dec6e1 | ||
|
|
3118d14087 | ||
|
|
1a9d7ac9c8 | ||
|
|
e88d3529f9 | ||
|
|
1501c6790d | ||
|
|
bf697745d5 | ||
|
|
1c689cbb19 | ||
|
|
1c06ae3eab | ||
|
|
f62348df0c | ||
|
|
a293c95611 | ||
|
|
4adc25728c | ||
|
|
26a5423599 | ||
|
|
391b712bde | ||
|
|
1ca1d08b2a | ||
|
|
f25807f355 | ||
|
|
5578c408a5 | ||
|
|
4cc6fdc070 | ||
|
|
ae9aec3bd8 | ||
|
|
96dda936ae | ||
|
|
8990175b03 | ||
|
|
0ed370aa67 | ||
|
|
e26253fec5 | ||
|
|
bc719e1a0c | ||
|
|
958671a030 | ||
|
|
8b902a1078 | ||
|
|
58d2a4abd7 | ||
|
|
71e539a0b4 | ||
|
|
3fbebbe03d | ||
|
|
27e1727fa8 | ||
|
|
ea5caff1aa | ||
|
|
3c392381b2 | ||
|
|
702e47e9e6 | ||
|
|
6fc4022c8d | ||
|
|
f324cb6f50 | ||
|
|
51843cade0 | ||
|
|
65c2dabed6 | ||
|
|
4c87ac298b | ||
|
|
fa859b23d9 | ||
|
|
18cdef9580 | ||
|
|
9b8c3e3e7c | ||
|
|
ea582297ce | ||
|
|
c5e321e7f4 | ||
|
|
3d527c6682 | ||
|
|
6fb1504d1f | ||
|
|
b66c6befd2 | ||
|
|
7003325d5d | ||
|
|
0481032a1f | ||
|
|
2a38e79191 | ||
|
|
8921938c6c | ||
|
|
e074413a9e | ||
|
|
d5b1acc16e | ||
|
|
0deb4d3faa | ||
|
|
fd506e402f | ||
|
|
29a27c2c3d | ||
|
|
d811a1333c | ||
|
|
ef37c8c9ac | ||
|
|
2945105a87 | ||
|
|
bed4ad24e5 | ||
|
|
a2029da8a4 | ||
|
|
93b8a541e4 | ||
|
|
01bbbb0836 | ||
|
|
2f77b2d365 | ||
|
|
686b7494ae | ||
|
|
b37b6e44bf | ||
|
|
cbbc6c5272 | ||
|
|
d725f33155 | ||
|
|
51364147c9 | ||
|
|
c1da34dacb | ||
|
|
a7cc57c386 | ||
|
|
c91e808fc0 | ||
|
|
be7ee5c7ad | ||
|
|
5bab23ebce | ||
|
|
62299e02f4 | ||
|
|
fe75598b17 | ||
|
|
9cb0410ae6 | ||
|
|
f3418100d3 | ||
|
|
2e0d8e8ed9 | ||
|
|
95a8b39b6d | ||
|
|
e3ae0fb65c | ||
|
|
7a21240bed | ||
|
|
1a076026e0 | ||
|
|
e5c212751b | ||
|
|
d4ad8148bb | ||
|
|
eb86960ca6 | ||
|
|
69e7525349 | ||
|
|
2b9c6c2d24 | ||
|
|
b81980c3d7 | ||
|
|
56afc889f4 | ||
|
|
53e43476d6 | ||
|
|
6d0adcc355 | ||
|
|
3870159797 | ||
|
|
c0ba8d4715 | ||
|
|
600a5e063c | ||
|
|
4c6442595c | ||
|
|
a57af69c49 | ||
|
|
813d0e27f8 | ||
|
|
3d40de95b1 | ||
|
|
045679bbd2 | ||
|
|
aff06448a5 | ||
|
|
9cd952122e | ||
|
|
27500f8474 | ||
|
|
9258432d50 | ||
|
|
d40ed12c5e | ||
|
|
7ace365327 | ||
|
|
3edd21502a | ||
|
|
fa108fe746 | ||
|
|
f5d3906b41 | ||
|
|
996469d556 | ||
|
|
2f571d547b | ||
|
|
aac3665781 |
+80
-8
@@ -1,8 +1,10 @@
|
||||
APP_VERSION=0.7.3
|
||||
APP_VERSION=0.8.7
|
||||
|
||||
NEXT_PUBLIC_PINATA_ENDPOINT_URL=https://api.pinata.cloud/pinning/pinFileToIPFS
|
||||
NEXT_PUBLIC_SG721_CODE_ID=2595
|
||||
NEXT_PUBLIC_SG721_UPDATABLE_CODE_ID=2596
|
||||
NEXT_PUBLIC_STRDST_SG721_CODE_ID=2595
|
||||
NEXT_PUBLIC_BASE_FACTORY_SG721_CODE_ID=2595
|
||||
NEXT_PUBLIC_OPEN_EDITION_SG721_CODE_ID=2595
|
||||
NEXT_PUBLIC_OPEN_EDITION_SG721_UPDATABLE_CODE_ID=2596
|
||||
NEXT_PUBLIC_VENDING_MINTER_CODE_ID=2600
|
||||
@@ -11,9 +13,13 @@ NEXT_PUBLIC_BASE_MINTER_CODE_ID=2598
|
||||
NEXT_PUBLIC_OPEN_EDITION_MINTER_CODE_ID=2579
|
||||
|
||||
NEXT_PUBLIC_VENDING_FACTORY_ADDRESS="stars18h7ugh8eaug7wr0w4yjw0ls5s937z35pnkg935ucsek2y9xl3gaqqk4jtx"
|
||||
NEXT_PUBLIC_FEATURED_VENDING_FACTORY_ADDRESS="stars14pd96yk3t6gq9l6uyrkg0n5dr09n8rt5y9v3at8x4wl4lrkxhlzq4trqmh"
|
||||
NEXT_PUBLIC_VENDING_FACTORY_UPDATABLE_ADDRESS="stars1h65nms9gwg4vdktyqj84tu50gwlm34e0eczl5w2ezllxuzfxy9esa9qlt0"
|
||||
NEXT_PUBLIC_VENDING_FACTORY_FLEX_ADDRESS="stars1hvu2ghqkcnvhtj2fc6wuazxt4dqcftslp2rwkkkcxy269a35a9pq60ug2q"
|
||||
NEXT_PUBLIC_VENDING_FACTORY_UPDATABLE_FLEX_ADDRESS=
|
||||
NEXT_PUBLIC_VENDING_FACTORY_MERKLE_TREE_ADDRESS="stars167tudcsr9n2y9ljgk4cwxhs0cvkfkk0hh6c3dzngsz7m5s9jmqnsdgr3jy"
|
||||
NEXT_PUBLIC_FEATURED_VENDING_FACTORY_MERKLE_TREE_ADDRESS="stars167tudcsr9n2y9ljgk4cwxhs0cvkfkk0hh6c3dzngsz7m5s9jmqnsdgr3jy"
|
||||
NEXT_PUBLIC_FEATURED_VENDING_FACTORY_FLEX_ADDRESS="stars1udlmmnmmnnqamh36hy6d7azn3ycv23yymkmg6558ntalvyt2pz7s8lhgcd"
|
||||
# NEXT_PUBLIC_VENDING_FACTORY_UPDATABLE_FLEX_ADDRESS=
|
||||
|
||||
# NEXT_PUBLIC_VENDING_IBC_ATOM_FACTORY_ADDRESS=
|
||||
# NEXT_PUBLIC_VENDING_IBC_ATOM_UPDATABLE_FACTORY_ADDRESS=
|
||||
@@ -21,31 +27,92 @@ NEXT_PUBLIC_VENDING_FACTORY_UPDATABLE_FLEX_ADDRESS=
|
||||
# NEXT_PUBLIC_VENDING_IBC_ATOM_UPDATABLE_FACTORY_FLEX_ADDRESS=
|
||||
|
||||
# NEXT_PUBLIC_VENDING_IBC_USDC_FACTORY_ADDRESS=
|
||||
# NEXT_PUBLIC_FEATURED_VENDING_IBC_USDC_FACTORY_ADDRESS=
|
||||
# NEXT_PUBLIC_VENDING_IBC_USDC_UPDATABLE_FACTORY_ADDRESS=
|
||||
# NEXT_PUBLIC_VENDING_IBC_USDC_FACTORY_FLEX_ADDRESS=
|
||||
# NEXT_PUBLIC_FEATURED_VENDING_IBC_USDC_FACTORY_FLEX_ADDRESS=
|
||||
# NEXT_PUBLIC_VENDING_IBC_USDC_UPDATABLE_FACTORY_FLEX_ADDRESS=
|
||||
|
||||
# NEXT_PUBLIC_VENDING_IBC_USK_FACTORY_ADDRESS=
|
||||
# NEXT_PUBLIC_VENDING_IBC_USK_UPDATABLE_FACTORY_ADDRESS=
|
||||
# NEXT_PUBLIC_VENDING_IBC_USK_FACTORY_FLEX_ADDRESS=
|
||||
# NEXT_PUBLIC_VENDING_IBC_USK_UPDATABLE_FACTORY_FLEX_ADDRESS=
|
||||
|
||||
# NEXT_PUBLIC_VENDING_IBC_TIA_FACTORY_ADDRESS=
|
||||
# NEXT_PUBLIC_FEATURED_VENDING_IBC_TIA_FACTORY_ADDRESS=
|
||||
# NEXT_PUBLIC_VENDING_IBC_TIA_UPDATABLE_FACTORY_ADDRESS=
|
||||
# NEXT_PUBLIC_VENDING_IBC_TIA_FACTORY_FLEX_ADDRESS=
|
||||
# NEXT_PUBLIC_VENDING_IBC_TIA_FACTORY_MERKLE_TREE_ADDRESS=
|
||||
# NEXT_PUBLIC_FEATURED_VENDING_IBC_TIA_FACTORY_MERKLE_TREE_ADDRESS=
|
||||
# NEXT_PUBLIC_FEATURED_VENDING_IBC_TIA_FACTORY_FLEX_ADDRESS=
|
||||
# NEXT_PUBLIC_VENDING_IBC_TIA_UPDATABLE_FACTORY_FLEX_ADDRESS=
|
||||
|
||||
NEXT_PUBLIC_VENDING_NATIVE_STARDUST_FACTORY_ADDRESS="stars1mxwf2hjcjvqnlw0v3j7m0u34975qesp325wzrgz0ht7vr8ys2zmsenjutf"
|
||||
NEXT_PUBLIC_VENDING_NATIVE_STARDUST_UPDATABLE_FACTORY_ADDRESS="stars18gjczf88jd4z3a3megwj9g5c9famu654csxfnnq59mkqeszuzy4ssdgr46"
|
||||
NEXT_PUBLIC_VENDING_NATIVE_STRDST_FLEX_FACTORY_ADDRESS="stars1eluqmr6x78ehl4plrln6khxc0qrspfhc7rt3whmr59escpve0r4swcacjh"
|
||||
|
||||
# NEXT_PUBLIC_VENDING_NATIVE_BRNCH_FACTORY_ADDRESS=""
|
||||
# NEXT_PUBLIC_VENDING_NATIVE_BRNCH_UPDATABLE_FACTORY_ADDRESS=""
|
||||
# NEXT_PUBLIC_VENDING_NATIVE_BRNCH_FLEX_FACTORY_ADDRESS=""
|
||||
|
||||
NEXT_PUBLIC_BASE_FACTORY_ADDRESS="stars1a45hcxty3spnmm2f0papl8v4dk5ew29s4syhn4efte8u5haex99qlkrtnx"
|
||||
NEXT_PUBLIC_BASE_FACTORY_UPDATABLE_ADDRESS="stars100xegx2syry4tclkmejjwxk4nfqahvcqhm9qxut5wxuzhj5d9qfsh5nmym"
|
||||
|
||||
NEXT_PUBLIC_OPEN_EDITION_FACTORY_ADDRESS="stars1sqweqcxlf2f7qhf27gn5naqusk5q52fkzewmy63c4sglvle3s7ls6k828e"
|
||||
NEXT_PUBLIC_OPEN_EDITION_FACTORY_FLEX_ADDRESS="stars1nc59ddaa8xcx9mu8jladza82dznhxrta3njal3xylkqlsfqa7g4s9s5q02"
|
||||
NEXT_PUBLIC_OPEN_EDITION_UPDATABLE_FACTORY_ADDRESS="stars1fk5dkzcylam8mcpqrn8y9spauvc3d4navtaqurcc49dc3p9f8d3qdkvymx"
|
||||
|
||||
NEXT_PUBLIC_VENDING_IBC_KUJI_FACTORY_ADDRESS="stars1yyje87e0h9mqg34kp3x75yesa78ve4glc3dstdrn6nscw3zjfanqkj95f0"
|
||||
NEXT_PUBLIC_VENDING_IBC_KUJI_FACTORY_FLEX_ADDRESS="stars1jralxqalpw9nf3kdc0s222z3mk343wry60cjaze9xadgfn2te4usf92e9r"
|
||||
|
||||
NEXT_PUBLIC_VENDING_IBC_HUAHUA_FACTORY_ADDRESS="stars16luw6rxgr6as9s7eu5auvnk5tnzszjrs34etsw9fmk25yqjfq09qq9gzl4"
|
||||
NEXT_PUBLIC_VENDING_IBC_HUAHUA_FACTORY_FLEX_ADDRESS="stars1d97h6nfgwqr8eynzdcrsm3p0n6rduvkrcqdjhm5z7heavtgnqg4sgy2yew"
|
||||
|
||||
NEXT_PUBLIC_VENDING_IBC_CRBRUS_FACTORY_ADDRESS="stars1z0upxsyxhrvygrsd2t69majd6wl8qw4h8ff2fp27z3nn93m73pwsu4hpdh"
|
||||
NEXT_PUBLIC_VENDING_IBC_CRBRUS_FACTORY_FLEX_ADDRESS="stars1halhp674yxwgn3p4gpkl8790h07vkm0vjm4vj7y8ql499e3zydzqurt5m3"
|
||||
|
||||
# NEXT_PUBLIC_OPEN_EDITION_IBC_ATOM_FACTORY_ADDRESS=
|
||||
# NEXT_PUBLIC_OPEN_EDITION_UPDATABLE_IBC_ATOM_FACTORY_ADDRESS=
|
||||
# NEXT_PUBLIC_OPEN_EDITION_IBC_USDC_FACTORY_ADDRESS=
|
||||
# NEXT_PUBLIC_OPEN_EDITION_IBC_USDC_FACTORY_ADDRESS="stars152a40mmd3k2kk90add606vrqxcvzdp29qrjx4pjv33cjl6svksfscrrtuk"
|
||||
# NEXT_PUBLIC_OPEN_EDITION_IBC_USDC_FACTORY_FLEX_ADDRESS="stars10sz9mup3a548l34k83q5w59nrklrnvv2gdsdkr2xref4zl5j3d4q0efamx"
|
||||
# NEXT_PUBLIC_OPEN_EDITION_UPDATABLE_IBC_USDC_FACTORY_ADDRESS=
|
||||
# NEXT_PUBLIC_OPEN_EDITION_IBC_FRNZ_FACTORY_ADDRESS=
|
||||
# NEXT_PUBLIC_OPEN_EDITION_UPDATABLE_IBC_FRNZ_FACTORY_ADDRESS=
|
||||
# NEXT_PUBLIC_OPEN_EDITION_IBC_TIA_FACTORY_ADDRESS="stars1vza7k890fkejxz3mqwau0u2m89k9y76w94vvxe4d42ya9862ryfq0damns"
|
||||
# NEXT_PUBLIC_OPEN_EDITION_IBC_TIA_FACTORY_FLEX_ADDRESS="stars1jgn0ntt5tut93yn756rrqa60794qdsrn6dwhl8vhfx0yxgpr44qsfzhmrt"
|
||||
# NEXT_PUBLIC_OPEN_EDITION_UPDATABLE_IBC_TIA_FACTORY_ADDRESS=
|
||||
NEXT_PUBLIC_OPEN_EDITION_IBC_FRNZ_FACTORY_ADDRESS="stars1vzffawsjhvspstu5lvtzz2x5n7zh07hnw09c9dfxcj78un05rcms5n3q3e"
|
||||
NEXT_PUBLIC_OPEN_EDITION_UPDATABLE_IBC_FRNZ_FACTORY_ADDRESS="stars1tc09vlgdg8rqyapcxwm9qdq8naj4gym9px4ntue9cs0kse5rvess0nee3a"
|
||||
|
||||
NEXT_PUBLIC_OPEN_EDITION_NATIVE_STRDST_FACTORY_ADDRESS="stars10sw8fvwtetndy3ctpcvee8yq7t6qp49m5yahm5gf8qz3qt3hzvcq5c2m0s"
|
||||
NEXT_PUBLIC_OPEN_EDITION_NATIVE_BRNCH_FACTORY_ADDRESS="stars1uxdqnu9ysd9q8kd43c52ufy9azfxyuvyt5nnyk4p2gtag30zre3q0cg30z"
|
||||
|
||||
NEXT_PUBLIC_OPEN_EDITION_IBC_USK_FACTORY_ADDRESS="stars1vxf9u6a4d5ty00k59zthv7mnpzlrfhqnf4ds0y0eake7lepuamnqymyf3t"
|
||||
NEXT_PUBLIC_OPEN_EDITION_UPDATABLE_IBC_USK_FACTORY_ADDRESS="stars1njhkyyv0l8dmq528w67t8dxyg5a3h0hvusk6pfvpm52pspd9gq9s3zmdez"
|
||||
|
||||
NEXT_PUBLIC_OPEN_EDITION_IBC_KUJI_FACTORY_ADDRESS="stars1yjvfy6fpm4nxl0afm6e8lnx96e6v49e3fxsymsdxxtu0pdeshrxq702zaz"
|
||||
|
||||
NEXT_PUBLIC_OPEN_EDITION_IBC_HUAHUA_FACTORY_ADDRESS="stars1grxlqatna07y8f3tzu2l9lmt82uj8gzzshxnz2ruwn6yljpyucnq059rmn"
|
||||
|
||||
# NEXT_PUBLIC_OPEN_EDITION_IBC_CRBRUS_FACTORY_ADDRESS=""
|
||||
|
||||
NEXT_PUBLIC_OPEN_EDITION_IBC_USDC_FACTORY_ADDRESS="stars1tjzlz2e8pkucgytkjct5drt7x0dysnepqv3nmvxn0fzk2hfv73zsneevyt"
|
||||
|
||||
NEXT_PUBLIC_OPEN_EDITION_IBC_NBTC_FACTORY_ADDRESS="stars1cd4gykxfq4nc4yx8uzn8yr3ggu86r57chhxme4y7q2jag53cw75qgs96u8"
|
||||
NEXT_PUBLIC_OPEN_EDITION_UPDATABLE_IBC_NBTC_FACTORY_ADDRESS="stars1d57xe77mvcg5q337umf4qz49vumfn6w3wss0t7u8ra6s3cyvezsqyaeejn"
|
||||
|
||||
NEXT_PUBLIC_VENDING_IBC_NBTC_FACTORY_ADDRESS="stars1e6t6lp052er2gu3rwjnf434vgh59ydkfg8dm589fxlx593afqmuqh75a0s"
|
||||
NEXT_PUBLIC_VENDING_IBC_NBTC_UPDATABLE_FACTORY_ADDRESS="stars1k6ee8qgwvumguqnqqrvsnwluwk0rp994nkcgdemk0tj3ecc5kk8su2tcr4"
|
||||
|
||||
NEXT_PUBLIC_SG721_NAME_ADDRESS="stars1fx74nkqkw2748av8j7ew7r3xt9cgjqduwn8m0ur5lhe49uhlsasszc5fhr"
|
||||
NEXT_PUBLIC_WHITELIST_CODE_ID=2602
|
||||
NEXT_PUBLIC_WHITELIST_FLEX_CODE_ID=2603
|
||||
NEXT_PUBLIC_ROYALTY_REGISTRY_ADDRESS="stars1crgx0f70fzksa57hq87wtl8f04h0qyk5la0hk0fu8dyhl67ju80qaxzr5z"
|
||||
NEXT_PUBLIC_INFINITY_SWAP_PROTOCOL_ADDRESS="stars136yp6fl9h66m0cwv8weu4w4aawveuz40992ty0atj5ecjd8z0thqv9xpy5"
|
||||
NEXT_PUBLIC_WHITELIST_CODE_ID=4008
|
||||
NEXT_PUBLIC_WHITELIST_FLEX_CODE_ID=4009
|
||||
NEXT_PUBLIC_WHITELIST_MERKLE_TREE_CODE_ID=3911
|
||||
NEXT_PUBLIC_BADGE_HUB_CODE_ID=1336
|
||||
NEXT_PUBLIC_BADGE_HUB_ADDRESS="stars1dacun0xn7z73qzdcmq27q3xn6xuprg8e2ugj364784al2v27tklqynhuqa"
|
||||
NEXT_PUBLIC_BADGE_NFT_CODE_ID=1337
|
||||
NEXT_PUBLIC_BADGE_NFT_ADDRESS="stars1vlw4y54dyzt3zg7phj8yey9fg4zj49czknssngwmgrnwymyktztstalg7t"
|
||||
NEXT_PUBLIC_SPLITS_CODE_ID=1905
|
||||
NEXT_PUBLIC_SPLITS_CODE_ID=4010
|
||||
NEXT_PUBLIC_CW4_GROUP_CODE_ID=1904
|
||||
|
||||
NEXT_PUBLIC_API_URL=https://nft-api.elgafar-1.stargaze-apis.com
|
||||
@@ -55,3 +122,8 @@ NEXT_PUBLIC_STARGAZE_WEBSITE_URL=https://testnet.publicawesome.dev
|
||||
NEXT_PUBLIC_BADGES_URL=https://badges.publicawesome.dev
|
||||
NEXT_PUBLIC_WEBSITE_URL=https://
|
||||
NEXT_PUBLIC_SYNC_COLLECTIONS_API_URL="https://..."
|
||||
NEXT_PUBLIC_WHITELIST_MERKLE_TREE_API_URL="https://..."
|
||||
NEXT_PUBLIC_NFT_STORAGE_DEFAULT_API_KEY="..."
|
||||
|
||||
NEXT_PUBLIC_MEILISEARCH_HOST="https://search.publicawesome.dev"
|
||||
NEXT_PUBLIC_MEILISEARCH_API_KEY= "..."
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { toUtf8 } from '@cosmjs/encoding'
|
||||
import clsx from 'clsx'
|
||||
import { useWallet } from 'contexts/wallet'
|
||||
import React, { useState } from 'react'
|
||||
import { toast } from 'react-hot-toast'
|
||||
import { SG721_NAME_ADDRESS } from 'utils/constants'
|
||||
@@ -8,6 +7,7 @@ import { csvToArray } from 'utils/csvToArray'
|
||||
import type { AirdropAllocation } from 'utils/isValidAccountsFile'
|
||||
import { isValidAccountsFile } from 'utils/isValidAccountsFile'
|
||||
import { isValidAddress } from 'utils/isValidAddress'
|
||||
import { useWallet } from 'utils/wallet'
|
||||
|
||||
interface AirdropUploadProps {
|
||||
onChange: (data: AirdropAllocation[]) => void
|
||||
@@ -22,8 +22,10 @@ export const AirdropUpload = ({ onChange }: AirdropUploadProps) => {
|
||||
await new Promise((resolve) => {
|
||||
let i = 0
|
||||
allocationData.map(async (data) => {
|
||||
if (!wallet.client) throw new Error('Wallet not connected')
|
||||
await wallet.client
|
||||
if (!wallet.isWalletConnected) throw new Error('Wallet not connected')
|
||||
await (
|
||||
await wallet.getCosmWasmClient()
|
||||
)
|
||||
.queryContractRaw(
|
||||
SG721_NAME_ADDRESS,
|
||||
toUtf8(
|
||||
|
||||
@@ -83,6 +83,17 @@ export const AssetsPreview = ({ assetFilesArray, updateMetadataFileIndex }: Asse
|
||||
<span className="flex self-center ">{assetSource.name}</span>
|
||||
</div>
|
||||
)}
|
||||
{getAssetType(assetSource.name) === 'document' && (
|
||||
<div className="flex absolute flex-col items-center mt-4 ml-2">
|
||||
<img
|
||||
key={`document-${index}`}
|
||||
alt="document_icon"
|
||||
className={clsx('mb-2 ml-1 w-6 h-6 thumbnail')}
|
||||
src="/pdf.png"
|
||||
/>
|
||||
<span className="flex self-center ">{assetSource.name}</span>
|
||||
</div>
|
||||
)}
|
||||
{getAssetType(assetSource.name) === 'video' &&
|
||||
videoPreviewElements.filter((videoPreviewElement) => videoPreviewElement.key === assetSource.name)}
|
||||
|
||||
|
||||
@@ -5,8 +5,8 @@ import { toUtf8 } from '@cosmjs/encoding'
|
||||
import clsx from 'clsx'
|
||||
import React, { useState } from 'react'
|
||||
import { toast } from 'react-hot-toast'
|
||||
import { useWallet } from 'utils/wallet'
|
||||
|
||||
import { useWallet } from '../contexts/wallet'
|
||||
import { SG721_NAME_ADDRESS } from '../utils/constants'
|
||||
import { isValidAddress } from '../utils/isValidAddress'
|
||||
|
||||
@@ -22,8 +22,10 @@ export const BadgeAirdropListUpload = ({ onChange }: BadgeAirdropListUploadProps
|
||||
await new Promise((resolve) => {
|
||||
let i = 0
|
||||
names.map(async (name) => {
|
||||
if (!wallet.client) throw new Error('Wallet not connected')
|
||||
await wallet.client
|
||||
if (!wallet.isWalletConnected) throw new Error('Wallet not connected')
|
||||
await (
|
||||
await wallet.getCosmWasmClient()
|
||||
)
|
||||
.queryContractRaw(
|
||||
SG721_NAME_ADDRESS,
|
||||
toUtf8(
|
||||
|
||||
@@ -12,7 +12,7 @@ export const BadgeConfirmationModal = (props: BadgeConfirmationModalProps) => {
|
||||
<input className="modal-toggle" defaultChecked id="my-modal-2" type="checkbox" />
|
||||
<label className="cursor-pointer modal" htmlFor="my-modal-2">
|
||||
<label
|
||||
className="absolute top-[25%] bottom-5 left-1/3 max-w-[600px] max-h-[400px] border-2 no-scrollbar modal-box"
|
||||
className="absolute top-[23%] bottom-5 left-1/3 max-w-[600px] max-h-[410px] border-2 no-scrollbar modal-box"
|
||||
htmlFor="temp"
|
||||
>
|
||||
{/* <Alert type="warning"></Alert> */}
|
||||
@@ -23,7 +23,9 @@ export const BadgeConfirmationModal = (props: BadgeConfirmationModalProps) => {
|
||||
submit, post, promote, or display on or through the Service. You represent and warrant that such contain
|
||||
material subject to copyright, trademark, publicity rights, or other intellectual property rights, unless
|
||||
you have necessary permission or are otherwise legally entitled to post the material and to grant Stargaze
|
||||
Parties the license described above, and that the content does not violate any laws.
|
||||
Parties the license described above, and that the content does not violate any laws. Stargaze.zone
|
||||
reserves the right to exercise its discretion in concealing user-generated content, should such content be
|
||||
determined to have a detrimental impact on the brand.
|
||||
</div>
|
||||
<br />
|
||||
<div className="flex flex-row pb-4">
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
/* eslint-disable eslint-comments/disable-enable-pair */
|
||||
/* eslint-disable @typescript-eslint/no-unnecessary-condition */
|
||||
|
||||
/* eslint-disable jsx-a11y/img-redundant-alt */
|
||||
import { truncateAddress } from 'utils/wallet'
|
||||
|
||||
export interface ClickableCollection {
|
||||
contractAddress: string
|
||||
name: string
|
||||
media: string
|
||||
onClick: () => void
|
||||
}
|
||||
|
||||
export function CollectionsTable({ collections }: { collections: ClickableCollection[] }) {
|
||||
return (
|
||||
<table className="w-full divide-y divide-zinc-800 table-fixed">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="py-3.5 pr-3 pl-4 text-sm text-left sm:pl-0 text-infinity-blue" scope="col">
|
||||
Name
|
||||
</th>
|
||||
<th className="py-3.5 px-3 text-sm text-left text-infinity-blue" scope="col">
|
||||
Address
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className=" bg-black">
|
||||
{collections
|
||||
? collections?.map((collection) => (
|
||||
<tr
|
||||
key={collection.contractAddress}
|
||||
className="hover:bg-zinc-900 cursor-pointer"
|
||||
onClick={collection.onClick}
|
||||
>
|
||||
<td className="py-2 pr-3 pl-4 whitespace-nowrap sm:pl-0">
|
||||
<div className="flex items-center">
|
||||
<div className="shrink-0 w-11 h-11">
|
||||
<img alt="Collection Image" src={collection.media} />
|
||||
</div>
|
||||
<div className="ml-4 font-medium text-white truncate">{collection.name}</div>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<td className="py-5 px-3 text-zinc-400 whitespace-nowrap">
|
||||
<div className="text-left text-white">
|
||||
{collection.contractAddress?.startsWith('stars')
|
||||
? truncateAddress(collection.contractAddress)
|
||||
: collection.contractAddress}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
))
|
||||
: null}
|
||||
</tbody>
|
||||
</table>
|
||||
)
|
||||
}
|
||||
@@ -12,7 +12,7 @@ export const ConfirmationModal = (props: ConfirmationModalProps) => {
|
||||
<input className="modal-toggle" defaultChecked id="my-modal-2" type="checkbox" />
|
||||
<label className="cursor-pointer modal" htmlFor="my-modal-2">
|
||||
<label
|
||||
className="absolute top-[25%] bottom-5 left-1/3 max-w-[600px] max-h-[400px] border-2 no-scrollbar modal-box"
|
||||
className="absolute top-[23%] bottom-5 left-1/3 max-w-[600px] max-h-[440px] border-2 no-scrollbar modal-box"
|
||||
htmlFor="temp"
|
||||
>
|
||||
{/* <Alert type="warning"></Alert> */}
|
||||
@@ -23,7 +23,9 @@ export const ConfirmationModal = (props: ConfirmationModalProps) => {
|
||||
submit, post, promote, or display on or through the Service. You represent and warrant that such contain
|
||||
material subject to copyright, trademark, publicity rights, or other intellectual property rights, unless
|
||||
you have necessary permission or are otherwise legally entitled to post the material and to grant Stargaze
|
||||
Parties the license described above, and that the content does not violate any laws.
|
||||
Parties the license described above, and that the content does not violate any laws. Stargaze.zone
|
||||
reserves the right to exercise its discretion in concealing user-generated content, should such content be
|
||||
determined to have a detrimental impact on the brand.
|
||||
</div>
|
||||
<br />
|
||||
<div className="flex flex-row pb-4">
|
||||
|
||||
@@ -8,7 +8,7 @@ export function FaviconsMetaTags() {
|
||||
<link href="/assets/manifest.webmanifest" rel="manifest" />
|
||||
<meta content="yes" name="mobile-web-app-capable" />
|
||||
<meta content="#F0827D" name="theme-color" />
|
||||
<meta content="StargazeStudio" name="application-name" />
|
||||
<meta content="Stargaze Studio" name="application-name" />
|
||||
<link href="/assets/apple-touch-icon-57x57.png" rel="apple-touch-icon" sizes="57x57" />
|
||||
<link href="/assets/apple-touch-icon-60x60.png" rel="apple-touch-icon" sizes="60x60" />
|
||||
<link href="/assets/apple-touch-icon-72x72.png" rel="apple-touch-icon" sizes="72x72" />
|
||||
@@ -22,7 +22,7 @@ export function FaviconsMetaTags() {
|
||||
<link href="/assets/apple-touch-icon-1024x1024.png" rel="apple-touch-icon" sizes="1024x1024" />
|
||||
<meta content="yes" name="apple-mobile-web-app-capable" />
|
||||
<meta content="black-translucent" name="apple-mobile-web-app-status-bar-style" />
|
||||
<meta content="StargazeStudio" name="apple-mobile-web-app-title" />
|
||||
<meta content="Stargaze Studio" name="apple-mobile-web-app-title" />
|
||||
<link
|
||||
href="/assets/apple-touch-startup-image-640x1136.png"
|
||||
media="(device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
/* eslint-disable eslint-comments/disable-enable-pair */
|
||||
/* eslint-disable no-implicit-coercion */
|
||||
/* eslint-disable import/no-default-export */
|
||||
/* eslint-disable tsdoc/syntax */
|
||||
import type { ReactNode } from 'react'
|
||||
|
||||
export interface FieldsetBaseType {
|
||||
/**
|
||||
* The input's required id, used to link the label and input, as well as the error message.
|
||||
*/
|
||||
id: string
|
||||
/**
|
||||
* Error message to show input validation.
|
||||
*/
|
||||
error?: string
|
||||
/**
|
||||
* Success message to show input validation.
|
||||
*/
|
||||
success?: string
|
||||
/**
|
||||
* Label to describe the input.
|
||||
*/
|
||||
label?: string | ReactNode
|
||||
/**
|
||||
* Hint to show optional fields or a hint to the user of what to enter in the input.
|
||||
*/
|
||||
hint?: string
|
||||
}
|
||||
|
||||
type FieldsetType = FieldsetBaseType & {
|
||||
children: ReactNode
|
||||
}
|
||||
|
||||
/**
|
||||
* @name Fieldset
|
||||
* @description A fieldset component, used to share markup for labels, hints, and errors for Input components.
|
||||
*
|
||||
* @example
|
||||
* <Fieldset error={error} hint={hint} id={id} label={label}>
|
||||
* <input id={id} {...props} />
|
||||
* </Fieldset>
|
||||
*/
|
||||
export default function Fieldset({ label, hint, id, children, error, success }: FieldsetType) {
|
||||
return (
|
||||
<div>
|
||||
{!!label && (
|
||||
<div className="flex justify-between mb-1">
|
||||
<label className="block w-full text-sm font-medium text-zinc-700 dark:text-zinc-300" htmlFor={id}>
|
||||
{label}
|
||||
</label>
|
||||
|
||||
{typeof hint === 'string' && (
|
||||
<span className="text-sm text-zinc-500 dark:text-zinc-400" id={`${id}-optional`}>
|
||||
{hint}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{children}
|
||||
|
||||
{error && (
|
||||
<div className="mt-2">
|
||||
<p className="text-sm text-zinc-600" id={`${id}-error`}>
|
||||
{error}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{success && (
|
||||
<div className="mt-2">
|
||||
<p className="text-sm text-zinc-500" id={`${id}-success`}>
|
||||
{success}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
/* eslint-disable eslint-comments/disable-enable-pair */
|
||||
/* eslint-disable import/no-default-export */
|
||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||
/* eslint-disable jsx-a11y/autocomplete-valid */
|
||||
/* eslint-disable tsdoc/syntax */
|
||||
import type { PropsOf } from '@headlessui/react/dist/types'
|
||||
import type { ReactNode } from 'react'
|
||||
import { forwardRef } from 'react'
|
||||
import { classNames } from 'utils/css'
|
||||
|
||||
import type { FieldsetBaseType } from './Fieldset'
|
||||
import Fieldset from './Fieldset'
|
||||
import type { TrailingSelectProps } from './TrailingSelect'
|
||||
import TrailingSelect from './TrailingSelect'
|
||||
|
||||
/**
|
||||
* Shared styles for all input components.
|
||||
*/
|
||||
export const inputClassNames = {
|
||||
base: [
|
||||
'block w-full rounded-lg bg-white shadow-sm dark:bg-zinc-900 sm:text-sm',
|
||||
'text-white placeholder:text-zinc-500 focus:outline focus:outline-2 focus:outline-offset-2 focus:outline-primary-500 focus:ring-0 focus:ring-offset-0',
|
||||
],
|
||||
valid: 'border-zinc-300 focus:border-zinc-300 dark:border-zinc-800 dark:focus:border-zinc-800',
|
||||
invalid: '!text-red-500 !border-red-500 focus:!border-red-500',
|
||||
success: 'text-green border-green focus:border-green',
|
||||
}
|
||||
|
||||
type InputProps = Omit<PropsOf<'input'> & FieldsetBaseType, 'className'> & {
|
||||
directory?: 'true'
|
||||
mozdirectory?: 'true'
|
||||
webkitdirectory?: 'true'
|
||||
leadingAddon?: string
|
||||
trailingAddon?: string
|
||||
trailingAddonIcon?: ReactNode
|
||||
trailingSelectProps?: TrailingSelectProps
|
||||
autoCompleteOff?: boolean
|
||||
preventAutoCapitalizeFirstLetter?: boolean
|
||||
className?: string
|
||||
icon?: JSX.Element
|
||||
}
|
||||
|
||||
/**
|
||||
* @name Input
|
||||
* @description A standard input component, defaults to the text type.
|
||||
*
|
||||
* @example
|
||||
* // Standard input
|
||||
* <Input id="first-name" name="first-name" />
|
||||
*
|
||||
* @example
|
||||
* // Input component with label, placeholder and type email
|
||||
* <Input id="email" name="email" type="email" autoComplete="email" label="Email" placeholder="name@email.com" />
|
||||
*
|
||||
* @example
|
||||
* // Input component with label and leading and trailing addons
|
||||
* <Input
|
||||
* id="input-label-leading-trailing"
|
||||
* label="Bid"
|
||||
* placeholder="0.00"
|
||||
* leadingAddon="$"
|
||||
* trailingAddon="USD"
|
||||
* />
|
||||
*
|
||||
* @example
|
||||
* // Input component with label and trailing select
|
||||
* const [trailingSelectValue, trailingSelectValueSet] = useState('USD');
|
||||
*
|
||||
* <Input
|
||||
* id="input-label-trailing-select"
|
||||
* label="Bid"
|
||||
* placeholder="0.00"
|
||||
* trailingSelectProps={{
|
||||
* id: 'currency',
|
||||
* label: 'Currency',
|
||||
* value: trailingSelectValue,
|
||||
* onChange: (event) => trailingSelectValueSet(event.target.value),
|
||||
* options: ['USD', 'CAD', 'EUR'],
|
||||
* }}
|
||||
* />
|
||||
*/
|
||||
const Input = forwardRef<HTMLInputElement, InputProps>(
|
||||
(
|
||||
{
|
||||
error,
|
||||
success,
|
||||
hint,
|
||||
label,
|
||||
leadingAddon,
|
||||
trailingAddon,
|
||||
trailingAddonIcon,
|
||||
trailingSelectProps,
|
||||
id,
|
||||
className,
|
||||
type = 'text',
|
||||
autoCompleteOff = false,
|
||||
preventAutoCapitalizeFirstLetter,
|
||||
icon,
|
||||
...rest
|
||||
},
|
||||
ref,
|
||||
) => {
|
||||
const cachedClassNames = classNames(
|
||||
...inputClassNames.base,
|
||||
className,
|
||||
error ? inputClassNames.invalid : inputClassNames.valid,
|
||||
success ? inputClassNames.success : inputClassNames.valid,
|
||||
leadingAddon && 'pl-7',
|
||||
trailingAddon && 'pr-12',
|
||||
trailingSelectProps && 'pr-16',
|
||||
icon && 'pl-10',
|
||||
)
|
||||
|
||||
const describedBy = [
|
||||
...(error ? [`${id}-error`] : []),
|
||||
...(success ? [`${id}-success`] : []),
|
||||
...(typeof hint === 'string' ? [`${id}-optional`] : []),
|
||||
...(typeof trailingAddon === 'string' ? [`${id}-addon`] : []),
|
||||
].join(' ')
|
||||
|
||||
return (
|
||||
<Fieldset error={error} hint={hint} id={id} label={label} success={success}>
|
||||
<div className="relative rounded-md shadow-sm">
|
||||
{leadingAddon && (
|
||||
<div className="flex absolute inset-y-0 left-0 items-center pl-3 pointer-events-none">
|
||||
<span className="text-zinc-500 dark:text-zinc-400 sm:text-sm">{leadingAddon}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{icon && (
|
||||
<div className="flex absolute inset-y-0 left-0 items-center pl-3 pointer-events-none">
|
||||
<span className="pr-10 text-zinc-500 dark:text-zinc-400 sm:text-sm">{icon}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<input
|
||||
aria-describedby={describedBy}
|
||||
aria-invalid={error ? 'true' : undefined}
|
||||
autoCapitalize={`${preventAutoCapitalizeFirstLetter ?? 'off'}`}
|
||||
autoComplete={`${autoCompleteOff ? 'off' : 'on'}`}
|
||||
className={cachedClassNames}
|
||||
id={id}
|
||||
ref={ref}
|
||||
type={type}
|
||||
{...rest}
|
||||
/>
|
||||
|
||||
{!trailingAddon && trailingSelectProps && <TrailingSelect {...trailingSelectProps} />}
|
||||
|
||||
{trailingAddon && (
|
||||
<div className="flex absolute inset-y-0 right-0 items-center pr-3 pointer-events-none">
|
||||
<span className="text-zinc-500 dark:text-zinc-400 sm:text-sm" id={`${id}-addon`}>
|
||||
{trailingAddon}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{trailingAddonIcon && (
|
||||
<div className="flex absolute inset-y-0 right-0 items-center pr-3 pointer-events-none">
|
||||
<span className="text-zinc-500 dark:text-zinc-400 sm:text-sm" id={`${id}-addonicon`}>
|
||||
{trailingAddonIcon}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Fieldset>
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
Input.displayName = 'Input'
|
||||
|
||||
export default Input
|
||||
@@ -66,7 +66,7 @@ export const JsonPreview = ({
|
||||
</div>
|
||||
{show && (
|
||||
<div className="overflow-auto p-2 font-mono text-sm">
|
||||
<pre>{JSON.stringify(content, null, 2).trim()}</pre>
|
||||
<pre>{content ? JSON.stringify(content, null, 2).trim() : '{}'}</pre>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -47,9 +47,9 @@ export const Layout = ({ children, metadata = {} }: LayoutProps) => {
|
||||
<FaDesktop size={48} />
|
||||
<h1 className="text-2xl font-bold">Unsupported Viewport</h1>
|
||||
<p>
|
||||
StargazeStudio is best viewed on the big screen.
|
||||
Stargaze Studio is best viewed on the big screen.
|
||||
<br />
|
||||
Please open StargazeStudio on your tablet or desktop browser.
|
||||
Please open Stargaze Studio on your tablet or desktop browser.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import clsx from 'clsx'
|
||||
import { Anchor } from 'components/Anchor'
|
||||
import { useRouter } from 'next/router'
|
||||
|
||||
export interface LinkTabProps {
|
||||
title: string
|
||||
@@ -11,6 +12,10 @@ export interface LinkTabProps {
|
||||
export const LinkTab = (props: LinkTabProps) => {
|
||||
const { title, description, href, isActive } = props
|
||||
|
||||
// get contract address from the router
|
||||
const router = useRouter()
|
||||
const { contractAddress } = router.query
|
||||
|
||||
return (
|
||||
<Anchor
|
||||
className={clsx(
|
||||
@@ -19,7 +24,7 @@ export const LinkTab = (props: LinkTabProps) => {
|
||||
isActive ? 'border-plumbus' : 'border-transparent',
|
||||
isActive ? 'bg-plumbus/5 hover:bg-plumbus/10' : 'hover:bg-white/5',
|
||||
)}
|
||||
href={href}
|
||||
href={href + (contractAddress ? `?contractAddress=${contractAddress as string}` : '')}
|
||||
>
|
||||
<h4 className="font-bold">{title}</h4>
|
||||
<span className="text-sm text-white/80 line-clamp-2">{description}</span>
|
||||
|
||||
@@ -145,3 +145,42 @@ export const splitsLinkTabs: LinkTabProps[] = [
|
||||
href: '/contracts/splits/migrate',
|
||||
},
|
||||
]
|
||||
|
||||
export const royaltyRegistryLinkTabs: LinkTabProps[] = [
|
||||
{
|
||||
title: 'Query',
|
||||
description: `Dispatch queries for your Royalty Registry contract`,
|
||||
href: '/contracts/royaltyRegistry/query',
|
||||
},
|
||||
{
|
||||
title: 'Execute',
|
||||
description: `Execute Royalty Registry contract actions`,
|
||||
href: '/contracts/royaltyRegistry/execute',
|
||||
},
|
||||
]
|
||||
|
||||
export const authzLinkTabs: LinkTabProps[] = [
|
||||
{
|
||||
title: 'Grant',
|
||||
description: `Grant authorizations to a given address`,
|
||||
href: '/authz/grant',
|
||||
},
|
||||
{
|
||||
title: 'Revoke',
|
||||
description: `Revoke already granted authorizations`,
|
||||
href: '/authz/revoke',
|
||||
},
|
||||
]
|
||||
|
||||
export const snapshotLinkTabs: LinkTabProps[] = [
|
||||
{
|
||||
title: 'Collection Holders',
|
||||
description: `Take a snapshot of collection holders`,
|
||||
href: '/snapshots/holders',
|
||||
},
|
||||
{
|
||||
title: 'Chain Snapshots',
|
||||
description: `Export a list of users fulfilling a given condition`,
|
||||
href: '/snapshots/chain',
|
||||
},
|
||||
]
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/* eslint-disable eslint-comments/disable-enable-pair */
|
||||
/* eslint-disable jsx-a11y/media-has-caption */
|
||||
import clsx from 'clsx'
|
||||
import type { ReactNode } from 'react'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import { getAssetType } from 'utils/getAssetType'
|
||||
@@ -41,6 +42,16 @@ export const MetadataFormGroup = (props: MetadataFormGroupProps) => {
|
||||
[relatedAsset],
|
||||
)
|
||||
|
||||
const documentPreview = useMemo(
|
||||
() => (
|
||||
<div className="flex flex-col items-center mt-4 ml-2">
|
||||
<img key="document-key" alt="document_icon" className={clsx('mb-2 ml-2 w-24 h-24 thumbnail')} src="/pdf.png" />
|
||||
<span className="flex self-center ">{relatedAsset?.name}</span>
|
||||
</div>
|
||||
),
|
||||
[relatedAsset],
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (getAssetType(relatedAsset?.name as string) !== 'html') return
|
||||
const reader = new FileReader()
|
||||
@@ -60,9 +71,14 @@ export const MetadataFormGroup = (props: MetadataFormGroupProps) => {
|
||||
{subtitle && <span className="text-sm text-white/50">{subtitle}</span>}
|
||||
<div>
|
||||
{relatedAsset && (
|
||||
<div className="flex flex-row items-center mt-2 mr-4 border-2 border-dashed">
|
||||
<div
|
||||
className={`flex flex-row items-center mt-2 mr-4 ${
|
||||
getAssetType(relatedAsset.name) === 'document' ? '' : `border-2 border-dashed`
|
||||
}`}
|
||||
>
|
||||
{getAssetType(relatedAsset.name) === 'audio' && audioPreview}
|
||||
{getAssetType(relatedAsset.name) === 'video' && videoPreview}
|
||||
{getAssetType(relatedAsset.name) === 'document' && documentPreview}
|
||||
{getAssetType(relatedAsset.name) === 'image' && (
|
||||
<img alt="preview" src={URL.createObjectURL(relatedAsset)} />
|
||||
)}
|
||||
|
||||
@@ -13,12 +13,16 @@ export interface MetadataInputProps {
|
||||
selectedAssetFile: File
|
||||
selectedMetadataFile: File
|
||||
updateMetadataToUpload: (metadataFile: File) => void
|
||||
onChange?: (metadata: any) => void
|
||||
importedMetadata?: any
|
||||
}
|
||||
|
||||
export const MetadataInput = (props: MetadataInputProps) => {
|
||||
const emptyMetadataFile = new File(
|
||||
[JSON.stringify({})],
|
||||
`${props.selectedAssetFile?.name.substring(0, props.selectedAssetFile?.name.lastIndexOf('.'))}.json`,
|
||||
`${props.selectedAssetFile?.name
|
||||
.substring(0, props.selectedAssetFile?.name.lastIndexOf('.'))
|
||||
.replaceAll('#', '')}.json`,
|
||||
{ type: 'application/json' },
|
||||
)
|
||||
|
||||
@@ -127,7 +131,7 @@ export const MetadataInput = (props: MetadataInputProps) => {
|
||||
if (nameState.value === '') delete metadata.name
|
||||
else metadata.name = nameState.value
|
||||
if (descriptionState.value === '') delete metadata.description
|
||||
else metadata.description = descriptionState.value
|
||||
else metadata.description = descriptionState.value.replaceAll('\\n', '\n')
|
||||
if (externalUrlState.value === '') delete metadata.external_url
|
||||
else metadata.external_url = externalUrlState.value
|
||||
if (youtubeUrlState.value === '') delete metadata.youtube_url
|
||||
@@ -140,8 +144,10 @@ export const MetadataInput = (props: MetadataInputProps) => {
|
||||
const editedMetadataFile = new File(
|
||||
[metadataFileBlob],
|
||||
props.selectedMetadataFile?.name
|
||||
? props.selectedMetadataFile?.name
|
||||
: `${props.selectedAssetFile?.name.substring(0, props.selectedAssetFile?.name.lastIndexOf('.'))}.json`,
|
||||
? props.selectedMetadataFile?.name.replaceAll('#', '')
|
||||
: `${props.selectedAssetFile?.name
|
||||
.substring(0, props.selectedAssetFile?.name.lastIndexOf('.'))
|
||||
.replaceAll('#', '')}.json`,
|
||||
{ type: 'application/json' },
|
||||
)
|
||||
props.updateMetadataToUpload(editedMetadataFile)
|
||||
@@ -151,9 +157,12 @@ export const MetadataInput = (props: MetadataInputProps) => {
|
||||
|
||||
useEffect(() => {
|
||||
console.log(props.selectedMetadataFile?.name)
|
||||
if (props.selectedMetadataFile) void parseMetadata(props.selectedMetadataFile)
|
||||
else void parseMetadata(emptyMetadataFile)
|
||||
}, [props.selectedMetadataFile?.name])
|
||||
if (props.selectedMetadataFile) {
|
||||
void parseMetadata(props.selectedMetadataFile)
|
||||
} else if (!props.importedMetadata) {
|
||||
void parseMetadata(emptyMetadataFile)
|
||||
}
|
||||
}, [props.selectedMetadataFile?.name, props.importedMetadata])
|
||||
|
||||
const nameStateMemo = useMemo(() => nameState, [nameState.value])
|
||||
const descriptionStateMemo = useMemo(() => descriptionState, [descriptionState.value])
|
||||
@@ -163,7 +172,10 @@ export const MetadataInput = (props: MetadataInputProps) => {
|
||||
|
||||
useEffect(() => {
|
||||
console.log('Update metadata')
|
||||
if (metadata) generateUpdatedMetadata()
|
||||
if (metadata) {
|
||||
generateUpdatedMetadata()
|
||||
if (props.onChange) props.onChange(metadata)
|
||||
}
|
||||
console.log(metadata)
|
||||
}, [
|
||||
nameStateMemo.value,
|
||||
@@ -173,6 +185,33 @@ export const MetadataInput = (props: MetadataInputProps) => {
|
||||
attributesStateMemo.entries,
|
||||
])
|
||||
|
||||
useEffect(() => {
|
||||
if (props.importedMetadata) {
|
||||
void parseMetadata(emptyMetadataFile).then(() => {
|
||||
console.log('Imported metadata: ', props.importedMetadata)
|
||||
nameState.onChange(props.importedMetadata.name || '')
|
||||
descriptionState.onChange(props.importedMetadata.description || '')
|
||||
externalUrlState.onChange(props.importedMetadata.external_url || '')
|
||||
youtubeUrlState.onChange(props.importedMetadata.youtube_url || '')
|
||||
if (props.importedMetadata?.attributes && props.importedMetadata?.attributes?.length > 0) {
|
||||
attributesState.reset()
|
||||
props.importedMetadata?.attributes?.forEach((attribute: { trait_type: string; value: string }) => {
|
||||
attributesState.add({
|
||||
trait_type: attribute.trait_type,
|
||||
value: attribute.value,
|
||||
})
|
||||
})
|
||||
} else {
|
||||
attributesState.reset()
|
||||
attributesState.add({
|
||||
trait_type: '',
|
||||
value: '',
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}, [props.importedMetadata])
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="grid grid-cols-2 mt-4 mr-4 ml-8 w-full max-w-6xl max-h-full no-scrollbar">
|
||||
|
||||
@@ -127,7 +127,9 @@ export const MetadataModal = (props: MetadataModalProps) => {
|
||||
type: 'application/json',
|
||||
})
|
||||
|
||||
const editedMetadataFile = new File([metadataFileBlob], metadataFile.name, { type: 'application/json' })
|
||||
const editedMetadataFile = new File([metadataFileBlob], metadataFile.name.replaceAll('#', ''), {
|
||||
type: 'application/json',
|
||||
})
|
||||
props.updateMetadata(editedMetadataFile)
|
||||
toast.success('Metadata updated successfully.')
|
||||
}
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
import { MagnifyingGlassIcon } from '@heroicons/react/24/outline'
|
||||
import Input from 'components/Input'
|
||||
import useSearch from 'hooks/useSearch'
|
||||
import { useMemo, useState } from 'react'
|
||||
import { useDebounce } from 'utils/debounce'
|
||||
|
||||
import { CollectionsTable } from './CollectionsTable'
|
||||
|
||||
export function SelectCollection({ selectCollection }: { selectCollection: (collectionAddress: string) => void }) {
|
||||
const [search, setSearch] = useState('')
|
||||
const [isInputFocused, setInputFocus] = useState(false)
|
||||
|
||||
const debouncedQuery = useDebounce<string>(search, 200)
|
||||
const debouncedIsInputFocused = useDebounce<boolean>(isInputFocused, 200)
|
||||
const collectionsQuery = useSearch(debouncedQuery, ['collections'], 5)
|
||||
const collectionsResults = useMemo(() => {
|
||||
return collectionsQuery.data?.find((searchResult) => searchResult.indexUid === 'collections')
|
||||
}, [collectionsQuery.data])
|
||||
|
||||
const clickableCollections = useMemo(() => {
|
||||
return (
|
||||
collectionsResults?.hits.map((hit) => ({
|
||||
contractAddress: hit.id,
|
||||
name: hit.name,
|
||||
media: hit.thumbnail_url || hit.image_url,
|
||||
onClick: () => {
|
||||
selectCollection(hit.id)
|
||||
setSearch(hit.name)
|
||||
},
|
||||
})) ?? []
|
||||
)
|
||||
}, [collectionsResults, selectCollection, setSearch])
|
||||
|
||||
const handleInputFocus = () => {
|
||||
setInputFocus(true)
|
||||
}
|
||||
|
||||
const handleInputBlur = () => {
|
||||
setInputFocus(false)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col p-4 space-y-4 w-3/4 h-full bg-black rounded-md border-2 border-gray-600 border-solid md:p-6">
|
||||
<p className="text-base font-bold text-white text-start">Select the NFT collection to take a snapshot for</p>
|
||||
<Input
|
||||
className="py-2 w-full text-black dark:text-white rounded-sm md:w-72"
|
||||
icon={<MagnifyingGlassIcon className="w-5 h-5 text-zinc-400" />}
|
||||
id="collection-search"
|
||||
onBlur={handleInputBlur}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
onFocus={handleInputFocus}
|
||||
placeholder="Search Collections..."
|
||||
value={search}
|
||||
/>
|
||||
|
||||
{debouncedIsInputFocused && (
|
||||
<div className="overflow-auto w-full">
|
||||
<CollectionsTable collections={clickableCollections} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
import type { Timezone } from 'contexts/globalSettings'
|
||||
import { useGlobalSettings } from 'contexts/globalSettings'
|
||||
import { useRef, useState } from 'react'
|
||||
|
||||
import { setTimezone } from '../contexts/globalSettings'
|
||||
import { Button } from './Button'
|
||||
|
||||
export interface SettingsModalProps {
|
||||
timezone?: Timezone
|
||||
}
|
||||
export const SettingsModal = (props: SettingsModalProps) => {
|
||||
const globalSettings = useGlobalSettings()
|
||||
const [isChecked, setIsChecked] = useState(false)
|
||||
|
||||
const checkBoxRef = useRef<HTMLInputElement>(null)
|
||||
|
||||
return (
|
||||
<div>
|
||||
<input className="modal-toggle" defaultChecked={false} id="my-modal-9" ref={checkBoxRef} type="checkbox" />
|
||||
<label className="cursor-pointer modal" htmlFor="my-modal-9">
|
||||
<label
|
||||
className={`absolute top-[42%] bottom-5 left-[260px] max-w-[450px] max-h-[250px]
|
||||
border-[1px] no-scrollbar modal-box`}
|
||||
htmlFor="temp"
|
||||
>
|
||||
<div className="flex flex-col justify-between h-full">
|
||||
<div className="flex flex-col">
|
||||
<h1 className="text-2xl font-bold underline underline-offset-2">Settings</h1>
|
||||
<div className="flex justify-start w-full">
|
||||
<div className="flex-row mt-2 w-full form-control">
|
||||
<h1 className="mt-[5px] text-lg font-bold">Time & Date: </h1>
|
||||
<label className="justify-start ml-6 cursor-pointer label">
|
||||
<span className="mr-2 font-bold">Local</span>
|
||||
<input
|
||||
checked={globalSettings.timezone === 'Local'}
|
||||
className={`${globalSettings.timezone === 'Local' ? `bg-stargaze` : `bg-gray-600`} checkbox`}
|
||||
onClick={() => {
|
||||
setTimezone('Local' as Timezone)
|
||||
window.localStorage.setItem('timezone', 'Local')
|
||||
}}
|
||||
type="checkbox"
|
||||
/>
|
||||
</label>
|
||||
<label className="justify-start ml-4 cursor-pointer label">
|
||||
<span className="mr-2 font-bold">UTC</span>
|
||||
<input
|
||||
checked={globalSettings.timezone === 'UTC'}
|
||||
className={`${globalSettings.timezone === 'UTC' ? `bg-stargaze` : `bg-gray-600`} checkbox`}
|
||||
onClick={() => {
|
||||
setTimezone('UTC' as Timezone)
|
||||
window.localStorage.setItem('timezone', 'UTC')
|
||||
}}
|
||||
type="checkbox"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
className="w-[40%] max-h-12 bg-blue-500 hover:bg-blue-600"
|
||||
isWide
|
||||
onClick={() => {
|
||||
setTimezone('UTC' as Timezone)
|
||||
window.localStorage.setItem('timezone', 'UTC')
|
||||
}}
|
||||
>
|
||||
Use Defaults
|
||||
</Button>
|
||||
</div>
|
||||
</label>
|
||||
</label>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
+129
-20
@@ -3,18 +3,22 @@
|
||||
|
||||
import clsx from 'clsx'
|
||||
import { Anchor } from 'components/Anchor'
|
||||
import type { Timezone } from 'contexts/globalSettings'
|
||||
import { setTimezone } from 'contexts/globalSettings'
|
||||
import { setLogItemList, useLogStore } from 'contexts/log'
|
||||
import { useWallet } from 'contexts/wallet'
|
||||
import Link from 'next/link'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useEffect } from 'react'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { FaCog } from 'react-icons/fa'
|
||||
// import BrandText from 'public/brand/brand-text.svg'
|
||||
import { footerLinks, socialsLinks } from 'utils/links'
|
||||
import { useWallet } from 'utils/wallet'
|
||||
|
||||
import { BADGE_HUB_ADDRESS, BASE_FACTORY_ADDRESS, NETWORK, OPEN_EDITION_FACTORY_ADDRESS } from '../utils/constants'
|
||||
import { Conditional } from './Conditional'
|
||||
import { IncomeDashboardDisclaimer } from './IncomeDashboardDisclaimer'
|
||||
import { LogModal } from './LogModal'
|
||||
import { SettingsModal } from './SettingsModal'
|
||||
import { SidebarLayout } from './SidebarLayout'
|
||||
import { WalletLoader } from './WalletLoader'
|
||||
|
||||
@@ -22,6 +26,7 @@ export const Sidebar = () => {
|
||||
const router = useRouter()
|
||||
const wallet = useWallet()
|
||||
const logs = useLogStore()
|
||||
const [isTallWindow, setIsTallWindow] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
if (logs.itemList.length === 0) return
|
||||
@@ -32,6 +37,23 @@ export const Sidebar = () => {
|
||||
useEffect(() => {
|
||||
console.log(window.localStorage.getItem('logs'))
|
||||
setLogItemList(JSON.parse(window.localStorage.getItem('logs') || '[]'))
|
||||
setTimezone(
|
||||
(window.localStorage.getItem('timezone') as Timezone)
|
||||
? (window.localStorage.getItem('timezone') as Timezone)
|
||||
: 'UTC',
|
||||
)
|
||||
}, [])
|
||||
|
||||
const handleResize = () => {
|
||||
setIsTallWindow(window.innerHeight > 768)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
handleResize()
|
||||
window.addEventListener('resize', handleResize)
|
||||
// return () => {
|
||||
// window.removeEventListener('resize', handleResize)
|
||||
// }
|
||||
}, [])
|
||||
|
||||
return (
|
||||
@@ -44,8 +66,8 @@ export const Sidebar = () => {
|
||||
<WalletLoader />
|
||||
{/* main navigation routes */}
|
||||
|
||||
<div className="absolute top-[20%] left-[5%] mt-2">
|
||||
<ul className="group p-2 w-full bg-transparent menu rounded-box">
|
||||
<div className={clsx('absolute left-[5%] mt-2', isTallWindow ? 'top-[20%]' : 'top-[30%]')}>
|
||||
<ul className="group py-1 px-2 w-full bg-transparent menu rounded-box">
|
||||
<li tabIndex={0}>
|
||||
<div
|
||||
className={clsx(
|
||||
@@ -86,6 +108,15 @@ export const Sidebar = () => {
|
||||
>
|
||||
<Link href="/collections/actions/">Collection Actions</Link>
|
||||
</li>
|
||||
<li
|
||||
className={clsx(
|
||||
'text-lg font-bold hover:text-white hover:bg-stargaze-80 rounded',
|
||||
router.asPath.includes('/snapshots') ? 'text-white' : 'text-gray',
|
||||
)}
|
||||
tabIndex={-1}
|
||||
>
|
||||
<Link href="/snapshots">Snapshots</Link>
|
||||
</li>
|
||||
<Conditional test={NETWORK === 'mainnet'}>
|
||||
<li className={clsx('text-lg font-bold hover:text-white hover:bg-stargaze-80 rounded')} tabIndex={-1}>
|
||||
<label
|
||||
@@ -100,7 +131,7 @@ export const Sidebar = () => {
|
||||
</li>
|
||||
</ul>
|
||||
<Conditional test={BADGE_HUB_ADDRESS !== undefined}>
|
||||
<ul className="group p-2 w-full bg-transparent menu rounded-box">
|
||||
<ul className="group py-1 px-2 w-full bg-transparent menu rounded-box">
|
||||
<li tabIndex={0}>
|
||||
<span
|
||||
className={clsx(
|
||||
@@ -144,6 +175,40 @@ export const Sidebar = () => {
|
||||
</ul>
|
||||
</Conditional>
|
||||
<ul className="group p-2 w-full bg-transparent menu rounded-box">
|
||||
<li tabIndex={0}>
|
||||
<span
|
||||
className={clsx(
|
||||
'z-40 text-xl font-bold group-hover:text-white bg-transparent rounded-lg small-caps',
|
||||
'hover:bg-white/5 transition-colors',
|
||||
router.asPath.includes('/tokenfactory') ? 'text-white' : 'text-gray',
|
||||
)}
|
||||
>
|
||||
<Link href="/tokenfactory/">Tokens</Link>
|
||||
</span>
|
||||
<ul className="z-50 p-2 rounded-box bg-base-200">
|
||||
<li
|
||||
className={clsx(
|
||||
'text-lg font-bold hover:text-white hover:bg-stargaze-80 rounded',
|
||||
router.asPath.includes('/tokenfactory/') ? 'text-white' : 'text-gray',
|
||||
)}
|
||||
tabIndex={-1}
|
||||
>
|
||||
<Link href="/tokenfactory/">Token Factory</Link>
|
||||
</li>
|
||||
<li
|
||||
className={clsx(
|
||||
'disabled',
|
||||
'text-lg font-bold hover:text-white',
|
||||
router.asPath.includes('/airdrop-tokens/') ? 'text-white' : 'text-gray',
|
||||
)}
|
||||
tabIndex={-1}
|
||||
>
|
||||
<Link href="/">Airdrop Tokens</Link>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
<ul className="group py-1 px-2 w-full bg-transparent menu rounded-box">
|
||||
<li tabIndex={0}>
|
||||
<span
|
||||
className={clsx(
|
||||
@@ -224,35 +289,78 @@ export const Sidebar = () => {
|
||||
>
|
||||
<Link href="/contracts/splits/">Splits Contract</Link>
|
||||
</li>
|
||||
<li
|
||||
className={clsx(
|
||||
'text-lg font-bold hover:text-white hover:bg-stargaze-80 rounded',
|
||||
router.asPath.includes('/contracts/royaltyRegistry/') ? 'text-white' : 'text-gray',
|
||||
)}
|
||||
tabIndex={-1}
|
||||
>
|
||||
<Link href="/contracts/royaltyRegistry/">Royalty Registry</Link>
|
||||
</li>
|
||||
|
||||
<li
|
||||
className={clsx(
|
||||
'text-lg font-bold hover:text-white hover:bg-stargaze-80 rounded',
|
||||
router.asPath.includes('/contracts/upload/') ? 'text-white' : 'text-gray',
|
||||
)}
|
||||
tabIndex={-1}
|
||||
>
|
||||
<Link href="/contracts/upload/">Upload Contract</Link>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
<ul className="group py-1 px-2 w-full bg-transparent menu rounded-box">
|
||||
<li tabIndex={0}>
|
||||
<span
|
||||
className={clsx(
|
||||
'z-40 text-xl font-bold group-hover:text-white bg-transparent rounded-lg small-caps',
|
||||
'hover:bg-white/5 transition-colors',
|
||||
router.asPath.includes('/authz/') ? 'text-white' : 'text-gray',
|
||||
)}
|
||||
>
|
||||
<Link href="/authz/"> Authz </Link>
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<IncomeDashboardDisclaimer creatorAddress={wallet.address ? wallet.address : ''} />
|
||||
<LogModal />
|
||||
<SettingsModal />
|
||||
|
||||
<div className="flex-grow" />
|
||||
{logs.itemList.length > 0 && (
|
||||
<label
|
||||
className="w-[65%] h-[4px] text-lg font-bold text-white normal-case bg-blue-500 hover:bg-blue-600 border-none animate-none btn modal-button"
|
||||
htmlFor="my-modal-8"
|
||||
>
|
||||
View Logs
|
||||
</label>
|
||||
{isTallWindow && (
|
||||
<div className="flex-row w-full h-full">
|
||||
<label
|
||||
className="absolute mb-8 w-[25%] text-lg font-bold text-white normal-case bg-zinc-500 hover:bg-zinc-600 border-none animate-none btn modal-button"
|
||||
htmlFor="my-modal-9"
|
||||
>
|
||||
<FaCog className="justify-center align-bottom" size={20} />
|
||||
</label>
|
||||
|
||||
<label
|
||||
className="ml-16 w-[65%] text-lg font-bold text-white normal-case bg-blue-500 hover:bg-blue-600 border-none animate-none btn modal-button"
|
||||
htmlFor="my-modal-8"
|
||||
>
|
||||
View Logs
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
{/* Stargaze network status */}
|
||||
<div className="text-sm capitalize">Network: {wallet.network}</div>
|
||||
{isTallWindow && <div className="text-sm capitalize">Network: {wallet.chain.pretty_name}</div>}
|
||||
|
||||
{/* footer reference links */}
|
||||
<ul className="text-sm list-disc list-inside">
|
||||
{footerLinks.map(({ href, text }) => (
|
||||
<li key={href}>
|
||||
<Anchor className="hover:text-plumbus hover:underline" href={href}>
|
||||
{text}
|
||||
</Anchor>
|
||||
</li>
|
||||
))}
|
||||
{isTallWindow &&
|
||||
footerLinks.map(({ href, text }) => (
|
||||
<li key={href}>
|
||||
<Anchor className="hover:text-plumbus hover:underline" href={href}>
|
||||
{text}
|
||||
</Anchor>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
{/* footer attribution */}
|
||||
@@ -265,6 +373,7 @@ export const Sidebar = () => {
|
||||
</div>
|
||||
|
||||
{/* footer social links */}
|
||||
|
||||
<div className="flex gap-x-6 items-center text-white/75">
|
||||
{socialsLinks.map(({ Icon, href, text }) => (
|
||||
<Anchor key={href} className="hover:text-plumbus" href={href}>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/* eslint-disable eslint-comments/disable-enable-pair */
|
||||
/* eslint-disable jsx-a11y/media-has-caption */
|
||||
import clsx from 'clsx'
|
||||
import type { ReactNode } from 'react'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import { getAssetType } from 'utils/getAssetType'
|
||||
@@ -41,6 +42,16 @@ export const SingleAssetPreview = (props: SingleAssetPreviewProps) => {
|
||||
[relatedAsset],
|
||||
)
|
||||
|
||||
const documentPreview = useMemo(
|
||||
() => (
|
||||
<div className="flex flex-col items-center mt-4 ml-2">
|
||||
<img key="document-key" alt="document_icon" className={clsx('mb-2 ml-1 w-20 h-20 thumbnail')} src="/pdf.png" />
|
||||
<span className="flex self-center ">{relatedAsset?.name}</span>
|
||||
</div>
|
||||
),
|
||||
[relatedAsset],
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (getAssetType(relatedAsset?.name as string) !== 'html') return
|
||||
const reader = new FileReader()
|
||||
@@ -59,9 +70,14 @@ export const SingleAssetPreview = (props: SingleAssetPreviewProps) => {
|
||||
<div>
|
||||
{/* {subtitle && <span className="text-sm text-white/50">{subtitle}</span>} */}
|
||||
{relatedAsset && (
|
||||
<div className="flex flex-row items-center mt-2 mr-4 border-2 border-dashed">
|
||||
<div
|
||||
className={`flex flex-row items-center mt-2 mr-4 ${
|
||||
getAssetType(relatedAsset.name) === 'document' ? '' : `border-2 border-dashed`
|
||||
}`}
|
||||
>
|
||||
{getAssetType(relatedAsset.name) === 'audio' && audioPreview}
|
||||
{getAssetType(relatedAsset.name) === 'video' && videoPreview}
|
||||
{getAssetType(relatedAsset.name) === 'document' && documentPreview}
|
||||
{getAssetType(relatedAsset.name) === 'image' && (
|
||||
<img alt="preview" src={URL.createObjectURL(relatedAsset)} />
|
||||
)}
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
/* eslint-disable eslint-comments/disable-enable-pair */
|
||||
/* eslint-disable import/no-default-export */
|
||||
|
||||
import type { ChangeEvent } from 'react'
|
||||
import { classNames } from 'utils/css'
|
||||
|
||||
export interface TrailingSelectProps {
|
||||
id: string
|
||||
label: string
|
||||
options: string[]
|
||||
value: string
|
||||
onChange: (event: ChangeEvent<HTMLSelectElement>) => void
|
||||
}
|
||||
|
||||
export default function TrailingSelect({ id, label, value, onChange, options }: TrailingSelectProps) {
|
||||
const cachedClassNames = classNames(
|
||||
'h-full rounded-md border-transparent bg-transparent py-0 pl-2 pr-7 text-zinc-500 dark:text-zinc-400 sm:text-sm',
|
||||
'focus:border-transparent focus:outline focus:outline-2 focus:outline-offset-2 focus:outline-primary-500 focus:ring-0 focus:ring-offset-0',
|
||||
)
|
||||
|
||||
return (
|
||||
<div className="flex absolute inset-y-0 right-0 items-center">
|
||||
<label className="sr-only" htmlFor={id}>
|
||||
{label}
|
||||
</label>
|
||||
|
||||
<select className={cachedClassNames} id={id} name={id} onChange={onChange} value={value}>
|
||||
{/* TODO - Option values in a select are supposed to be unique, remove this comment during PR review */}
|
||||
{options.map((opt) => (
|
||||
<option key={opt} value={opt}>
|
||||
{opt}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
+44
-14
@@ -1,36 +1,63 @@
|
||||
import type { Coin } from '@cosmjs/proto-signing'
|
||||
import { Popover, Transition } from '@headlessui/react'
|
||||
import clsx from 'clsx'
|
||||
import { useWallet, useWalletStore } from 'contexts/wallet'
|
||||
import { Fragment } from 'react'
|
||||
import { tokensList } from 'config/token'
|
||||
import { Fragment, useEffect, useState } from 'react'
|
||||
import { FaCopy, FaPowerOff, FaRedo } from 'react-icons/fa'
|
||||
import { copy } from 'utils/clipboard'
|
||||
import { convertDenomToReadable } from 'utils/convertDenomToReadable'
|
||||
import { getShortAddress } from 'utils/getShortAddress'
|
||||
import { truncateMiddle } from 'utils/text'
|
||||
import { useWallet } from 'utils/wallet'
|
||||
|
||||
import { WalletButton } from './WalletButton'
|
||||
import { WalletPanelButton } from './WalletPanelButton'
|
||||
|
||||
export const WalletLoader = () => {
|
||||
const { address, balance, connect, disconnect, initializing: isLoading, initialized: isReady } = useWallet()
|
||||
const {
|
||||
address = '',
|
||||
username,
|
||||
connect,
|
||||
disconnect,
|
||||
isWalletConnecting,
|
||||
isWalletConnected,
|
||||
getStargateClient,
|
||||
} = useWallet()
|
||||
|
||||
const displayName = useWalletStore((store) => store.name || getShortAddress(store.address))
|
||||
// Once wallet connects, load balances.
|
||||
const [balances, setBalances] = useState<readonly Coin[] | undefined>()
|
||||
useEffect(() => {
|
||||
if (!isWalletConnected) {
|
||||
setBalances(undefined)
|
||||
return
|
||||
}
|
||||
|
||||
const loadBalances = async () => {
|
||||
const client = await getStargateClient()
|
||||
setBalances(await client.getAllBalances(address))
|
||||
}
|
||||
|
||||
loadBalances().catch(console.error)
|
||||
}, [isWalletConnected, getStargateClient, address])
|
||||
|
||||
return (
|
||||
<Popover className="mt-4 mb-2">
|
||||
{({ close }) => (
|
||||
<>
|
||||
<div className="grid -mx-4">
|
||||
{!isReady && (
|
||||
<WalletButton className="w-full" isLoading={isLoading} onClick={() => void connect()}>
|
||||
{isWalletConnected ? (
|
||||
<Popover.Button as={WalletButton} className="w-full">
|
||||
{username || address}
|
||||
</Popover.Button>
|
||||
) : (
|
||||
<WalletButton
|
||||
className="w-full"
|
||||
isLoading={isWalletConnecting}
|
||||
onClick={() => void connect().catch(console.error)}
|
||||
>
|
||||
Connect Wallet
|
||||
</WalletButton>
|
||||
)}
|
||||
|
||||
{isReady && (
|
||||
<Popover.Button as={WalletButton} className="w-full" isLoading={isLoading}>
|
||||
{displayName}
|
||||
</Popover.Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Transition
|
||||
@@ -54,9 +81,12 @@ export const WalletLoader = () => {
|
||||
{getShortAddress(address)}
|
||||
</span>
|
||||
<div className="font-bold">Your Balances</div>
|
||||
{balance.map((val) => (
|
||||
{balances?.map((val) => (
|
||||
<span key={`balance-${val.denom}`}>
|
||||
{convertDenomToReadable(val.amount)} {val.denom.slice(1, val.denom.length)}
|
||||
{convertDenomToReadable(val.amount)}{' '}
|
||||
{tokensList.find((t) => t.denom === val.denom)?.displayName
|
||||
? tokensList.find((t) => t.denom === val.denom)?.displayName
|
||||
: truncateMiddle(val.denom ? val.denom : '', 28)}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
// Styles required for @cosmos-kit/react modal
|
||||
import '@interchain-ui/react/styles'
|
||||
|
||||
import { GasPrice } from '@cosmjs/stargate'
|
||||
import { wallets as keplrExtensionWallets } from '@cosmos-kit/keplr-extension'
|
||||
import { wallets as leapExtensionWallets } from '@cosmos-kit/leap-extension'
|
||||
import { ChainProvider } from '@cosmos-kit/react'
|
||||
import { assets, chains } from 'chain-registry'
|
||||
import { getConfig } from 'config'
|
||||
import type { ReactNode } from 'react'
|
||||
import { NETWORK } from 'utils/constants'
|
||||
|
||||
export const WalletProvider = ({ children }: { children: ReactNode }) => {
|
||||
const { gasPrice, feeToken } = getConfig(NETWORK)
|
||||
return (
|
||||
<ChainProvider
|
||||
assetLists={assets}
|
||||
chains={chains}
|
||||
endpointOptions={{
|
||||
endpoints: {
|
||||
stargaze: {
|
||||
rpc: ['https://rpc.stargaze-apis.com/'],
|
||||
rest: ['https://rest.stargaze-apis.com/'],
|
||||
},
|
||||
stargazetestnet: {
|
||||
rpc: ['https://rpc.elgafar-1.stargaze-apis.com/'],
|
||||
rest: ['https://rest.elgafar-1.stargaze-apis.com/'],
|
||||
},
|
||||
},
|
||||
isLazy: true,
|
||||
}}
|
||||
sessionOptions={{
|
||||
duration: 1000 * 60 * 60 * 12, // 12 hours
|
||||
}}
|
||||
signerOptions={{
|
||||
signingCosmwasm: () => ({
|
||||
gasPrice: GasPrice.fromString(`${gasPrice}${feeToken}`),
|
||||
}),
|
||||
signingStargate: () => ({
|
||||
gasPrice: GasPrice.fromString(`${gasPrice}${feeToken}`),
|
||||
}),
|
||||
}}
|
||||
wallets={[...keplrExtensionWallets, ...leapExtensionWallets]}
|
||||
>
|
||||
{children}
|
||||
</ChainProvider>
|
||||
)
|
||||
}
|
||||
@@ -1,12 +1,12 @@
|
||||
import { toUtf8 } from '@cosmjs/encoding'
|
||||
import clsx from 'clsx'
|
||||
import { useWallet } from 'contexts/wallet'
|
||||
import React, { useState } from 'react'
|
||||
import { toast } from 'react-hot-toast'
|
||||
import { SG721_NAME_ADDRESS } from 'utils/constants'
|
||||
import { csvToFlexList } from 'utils/csvToFlexList'
|
||||
import { isValidAddress } from 'utils/isValidAddress'
|
||||
import { isValidFlexListFile } from 'utils/isValidFlexListFile'
|
||||
import { useWallet } from 'utils/wallet'
|
||||
|
||||
export interface WhitelistFlexMember {
|
||||
address: string
|
||||
@@ -26,8 +26,10 @@ export const WhitelistFlexUpload = ({ onChange }: WhitelistFlexUploadProps) => {
|
||||
await new Promise((resolve) => {
|
||||
let i = 0
|
||||
memberData.map(async (data) => {
|
||||
if (!wallet.client) throw new Error('Wallet not connected')
|
||||
await wallet.client
|
||||
if (!wallet.isWalletConnected) throw new Error('Wallet not connected')
|
||||
await (
|
||||
await wallet.getCosmWasmClient()
|
||||
)
|
||||
.queryContractRaw(
|
||||
SG721_NAME_ADDRESS,
|
||||
toUtf8(
|
||||
|
||||
@@ -5,8 +5,8 @@ import { toUtf8 } from '@cosmjs/encoding'
|
||||
import clsx from 'clsx'
|
||||
import React, { useState } from 'react'
|
||||
import { toast } from 'react-hot-toast'
|
||||
import { useWallet } from 'utils/wallet'
|
||||
|
||||
import { useWallet } from '../contexts/wallet'
|
||||
import { SG721_NAME_ADDRESS } from '../utils/constants'
|
||||
import { isValidAddress } from '../utils/isValidAddress'
|
||||
|
||||
@@ -22,8 +22,10 @@ export const WhitelistUpload = ({ onChange }: WhitelistUploadProps) => {
|
||||
await new Promise((resolve) => {
|
||||
let i = 0
|
||||
names.map(async (name) => {
|
||||
if (!wallet.client) throw new Error('Wallet not connected')
|
||||
await wallet.client
|
||||
if (!wallet.isWalletConnected) throw new Error('Wallet not connected')
|
||||
await (
|
||||
await wallet.getCosmWasmClient()
|
||||
)
|
||||
.queryContractRaw(
|
||||
SG721_NAME_ADDRESS,
|
||||
toUtf8(
|
||||
@@ -76,7 +78,7 @@ export const WhitelistUpload = ({ onChange }: WhitelistUploadProps) => {
|
||||
const printableData = data?.map((item) => item.replace(regex, ''))
|
||||
const names = printableData?.filter((address) => address !== '' && address.endsWith('.stars'))
|
||||
const strippedNames = names?.map((name) => name.split('.')[0])
|
||||
console.log(names)
|
||||
console.log('names: ', names)
|
||||
if (strippedNames?.length) {
|
||||
await toast
|
||||
.promise(resolveAddresses(strippedNames), {
|
||||
|
||||
@@ -20,7 +20,6 @@ import { useMetadataAttributesState } from 'components/forms/MetadataAttributes.
|
||||
import { JsonPreview } from 'components/JsonPreview'
|
||||
import { TransactionHash } from 'components/TransactionHash'
|
||||
import { WhitelistUpload } from 'components/WhitelistUpload'
|
||||
import { useWallet } from 'contexts/wallet'
|
||||
import type { Badge, BadgeHubInstance } from 'contracts/badgeHub'
|
||||
import sizeof from 'object-sizeof'
|
||||
import type { FormEvent } from 'react'
|
||||
@@ -32,6 +31,7 @@ import * as secp256k1 from 'secp256k1'
|
||||
import { generateKeyPairs, sha256 } from 'utils/hash'
|
||||
import { isValidAddress } from 'utils/isValidAddress'
|
||||
import { resolveAddress } from 'utils/resolveAddress'
|
||||
import { useWallet } from 'utils/wallet'
|
||||
|
||||
import { BadgeAirdropListUpload } from '../../BadgeAirdropListUpload'
|
||||
import { AddressInput, NumberInput, TextInput } from '../../forms/FormInput'
|
||||
@@ -266,7 +266,7 @@ export const BadgeActions = ({ badgeHubContractAddress, badgeId, badgeHubMessage
|
||||
nft: nftState.value,
|
||||
badgeHubMessages,
|
||||
badgeHubContract: badgeHubContractAddress,
|
||||
txSigner: wallet.address,
|
||||
txSigner: wallet.address || '',
|
||||
type,
|
||||
}
|
||||
|
||||
@@ -397,7 +397,7 @@ export const BadgeActions = ({ badgeHubContractAddress, badgeId, badgeHubMessage
|
||||
|
||||
const { isLoading, mutate } = useMutation(
|
||||
async (event: FormEvent) => {
|
||||
if (!wallet.client) {
|
||||
if (!wallet.isWalletConnected) {
|
||||
throw new Error('Please connect your wallet.')
|
||||
}
|
||||
event.preventDefault()
|
||||
@@ -412,8 +412,10 @@ export const BadgeActions = ({ badgeHubContractAddress, badgeId, badgeHubMessage
|
||||
throw new Error('Please enter a valid private key.')
|
||||
}
|
||||
|
||||
if (wallet.client && type === 'edit_badge') {
|
||||
const feeRateRaw = await wallet.client.queryContractRaw(
|
||||
if (type === 'edit_badge') {
|
||||
const feeRateRaw = await (
|
||||
await wallet.getCosmWasmClient()
|
||||
).queryContractRaw(
|
||||
badgeHubContractAddress,
|
||||
toUtf8(Buffer.from(Buffer.from('fee_rate').toString('hex'), 'hex').toString()),
|
||||
)
|
||||
@@ -421,7 +423,9 @@ export const BadgeActions = ({ badgeHubContractAddress, badgeId, badgeHubMessage
|
||||
|
||||
await toast
|
||||
.promise(
|
||||
wallet.client.queryContractSmart(badgeHubContractAddress, {
|
||||
(
|
||||
await wallet.getCosmWasmClient()
|
||||
).queryContractSmart(badgeHubContractAddress, {
|
||||
badge: { id: badgeId },
|
||||
}),
|
||||
{
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
/* eslint-disable eslint-comments/disable-enable-pair */
|
||||
/* eslint-disable no-nested-ternary */
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
@@ -10,12 +11,13 @@ import { FormControl } from 'components/FormControl'
|
||||
import { useInputState, useNumberInputState } from 'components/forms/FormInput.hooks'
|
||||
import { useMetadataAttributesState } from 'components/forms/MetadataAttributes.hooks'
|
||||
import { InputDateTime } from 'components/InputDateTime'
|
||||
import { useWallet } from 'contexts/wallet'
|
||||
import { useGlobalSettings } from 'contexts/globalSettings'
|
||||
import type { Trait } from 'contracts/badgeHub'
|
||||
import type { ChangeEvent } from 'react'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { toast } from 'react-hot-toast'
|
||||
import { BADGE_HUB_ADDRESS } from 'utils/constants'
|
||||
import { useWallet } from 'utils/wallet'
|
||||
|
||||
import { AddressInput, NumberInput, TextInput } from '../../forms/FormInput'
|
||||
import { MetadataAttributes } from '../../forms/MetadataAttributes'
|
||||
@@ -44,8 +46,9 @@ export interface BadgeDetailsDataProps {
|
||||
youtube_url?: string
|
||||
}
|
||||
|
||||
export const BadgeDetails = ({ metadataSize, onChange }: BadgeDetailsProps) => {
|
||||
const wallet = useWallet()
|
||||
export const BadgeDetails = ({ metadataSize, onChange, uploadMethod }: BadgeDetailsProps) => {
|
||||
const { address = '', isWalletConnected, getCosmWasmClient } = useWallet()
|
||||
const { timezone } = useGlobalSettings()
|
||||
const [timestamp, setTimestamp] = useState<Date | undefined>(undefined)
|
||||
const [transferrable, setTransferrable] = useState<boolean>(false)
|
||||
const [metadataFile, setMetadataFile] = useState<File>()
|
||||
@@ -58,7 +61,7 @@ export const BadgeDetails = ({ metadataSize, onChange }: BadgeDetailsProps) => {
|
||||
name: 'manager',
|
||||
title: 'Manager',
|
||||
subtitle: 'Badge Hub Manager',
|
||||
defaultValue: wallet.address ? wallet.address : '',
|
||||
defaultValue: address,
|
||||
})
|
||||
|
||||
const nameState = useInputState({
|
||||
@@ -172,7 +175,9 @@ export const BadgeDetails = ({ metadataSize, onChange }: BadgeDetailsProps) => {
|
||||
reader.onload = (e) => {
|
||||
if (!event.target.files) return toast.error('No file selected.')
|
||||
if (!e.target?.result) return toast.error('Error parsing file.')
|
||||
selectedFile = new File([e.target.result], event.target.files[0].name, { type: 'application/json' })
|
||||
selectedFile = new File([e.target.result], event.target.files[0].name.replaceAll('#', ''), {
|
||||
type: 'application/json',
|
||||
})
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||
if (event.target.files[0]) reader.readAsArrayBuffer(event.target.files[0])
|
||||
@@ -192,6 +197,10 @@ export const BadgeDetails = ({ metadataSize, onChange }: BadgeDetailsProps) => {
|
||||
})
|
||||
}, [metadataFile])
|
||||
|
||||
useEffect(() => {
|
||||
animationUrlState.onChange('')
|
||||
}, [uploadMethod])
|
||||
|
||||
useEffect(() => {
|
||||
try {
|
||||
const data: BadgeDetailsDataProps = {
|
||||
@@ -240,8 +249,10 @@ export const BadgeDetails = ({ metadataSize, onChange }: BadgeDetailsProps) => {
|
||||
useEffect(() => {
|
||||
const retrieveFeeRate = async () => {
|
||||
try {
|
||||
if (wallet.client) {
|
||||
const feeRateRaw = await wallet.client.queryContractRaw(
|
||||
if (isWalletConnected) {
|
||||
const feeRateRaw = await (
|
||||
await getCosmWasmClient()
|
||||
).queryContractRaw(
|
||||
BADGE_HUB_ADDRESS,
|
||||
toUtf8(Buffer.from(Buffer.from('fee_rate').toString('hex'), 'hex').toString()),
|
||||
)
|
||||
@@ -256,7 +267,7 @@ export const BadgeDetails = ({ metadataSize, onChange }: BadgeDetailsProps) => {
|
||||
}
|
||||
}
|
||||
void retrieveFeeRate()
|
||||
}, [wallet.client])
|
||||
}, [isWalletConnected, getCosmWasmClient])
|
||||
|
||||
return (
|
||||
<div>
|
||||
@@ -266,10 +277,32 @@ export const BadgeDetails = ({ metadataSize, onChange }: BadgeDetailsProps) => {
|
||||
<TextInput className="mt-2" {...nameState} />
|
||||
<TextInput className="mt-2" {...descriptionState} />
|
||||
<NumberInput className="mt-2" {...maxSupplyState} />
|
||||
{uploadMethod === 'existing' ? <TextInput className="mt-2" {...animationUrlState} /> : null}
|
||||
<TextInput className="mt-2" {...externalUrlState} />
|
||||
|
||||
<FormControl className="mt-2" htmlId="expiry-date" subtitle="Badge minting expiry date" title="Expiry Date">
|
||||
<InputDateTime minDate={new Date()} onChange={(date) => setTimestamp(date)} value={timestamp} />
|
||||
<FormControl
|
||||
className="mt-2"
|
||||
htmlId="expiry-date"
|
||||
subtitle={`Badge minting expiry date ${timezone === 'Local' ? '(local)' : '(UTC)'}`}
|
||||
title="Expiry Date"
|
||||
>
|
||||
<InputDateTime
|
||||
minDate={
|
||||
timezone === 'Local' ? new Date() : new Date(Date.now() + new Date().getTimezoneOffset() * 60 * 1000)
|
||||
}
|
||||
onChange={(date) =>
|
||||
setTimestamp(
|
||||
timezone === 'Local' ? date : new Date(date.getTime() - new Date().getTimezoneOffset() * 60 * 1000),
|
||||
)
|
||||
}
|
||||
value={
|
||||
timezone === 'Local'
|
||||
? timestamp
|
||||
: timestamp
|
||||
? new Date(timestamp.getTime() + new Date().getTimezoneOffset() * 60 * 1000)
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
</FormControl>
|
||||
<div className="grid grid-cols-2">
|
||||
<div className="mt-2 w-1/3 form-control">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/* eslint-disable eslint-comments/disable-enable-pair */
|
||||
|
||||
/* eslint-disable jsx-a11y/media-has-caption */
|
||||
/* eslint-disable no-misleading-character-class */
|
||||
/* eslint-disable no-control-regex */
|
||||
|
||||
@@ -10,9 +10,11 @@ import { TextInput } from 'components/forms/FormInput'
|
||||
import { useInputState } from 'components/forms/FormInput.hooks'
|
||||
import { SingleAssetPreview } from 'components/SingleAssetPreview'
|
||||
import type { ChangeEvent } from 'react'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { toast } from 'react-hot-toast'
|
||||
import type { UploadServiceType } from 'services/upload'
|
||||
import { NFT_STORAGE_DEFAULT_API_KEY } from 'utils/constants'
|
||||
import { getAssetType } from 'utils/getAssetType'
|
||||
|
||||
export type UploadMethod = 'new' | 'existing'
|
||||
export type MintRule = 'by_key' | 'by_minter' | 'by_keys' | 'not_resolved'
|
||||
@@ -36,6 +38,7 @@ export const ImageUploadDetails = ({ onChange, mintRule }: ImageUploadDetailsPro
|
||||
const [assetFile, setAssetFile] = useState<File>()
|
||||
const [uploadMethod, setUploadMethod] = useState<UploadMethod>('new')
|
||||
const [uploadService, setUploadService] = useState<UploadServiceType>('nft-storage')
|
||||
const [useDefaultApiKey, setUseDefaultApiKey] = useState(false)
|
||||
|
||||
const assetFileRef = useRef<HTMLInputElement | null>(null)
|
||||
|
||||
@@ -78,7 +81,7 @@ export const ImageUploadDetails = ({ onChange, mintRule }: ImageUploadDetailsPro
|
||||
reader.onload = (e) => {
|
||||
if (!event.target.files) return toast.error('No file selected.')
|
||||
if (!e.target?.result) return toast.error('Error parsing file.')
|
||||
selectedFile = new File([e.target.result], event.target.files[0].name, { type: 'image/jpg' })
|
||||
selectedFile = new File([e.target.result], event.target.files[0].name.replaceAll('#', ''), { type: 'image/jpg' })
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||
if (event.target.files[0]) reader.readAsArrayBuffer(event.target.files[0])
|
||||
@@ -129,6 +132,30 @@ export const ImageUploadDetails = ({ onChange, mintRule }: ImageUploadDetailsPro
|
||||
imageUrlState.onChange('')
|
||||
}, [uploadMethod, mintRule])
|
||||
|
||||
useEffect(() => {
|
||||
if (useDefaultApiKey) {
|
||||
nftStorageApiKeyState.onChange(NFT_STORAGE_DEFAULT_API_KEY || '')
|
||||
} else {
|
||||
nftStorageApiKeyState.onChange('')
|
||||
}
|
||||
}, [useDefaultApiKey])
|
||||
|
||||
const videoPreview = useMemo(
|
||||
() => (
|
||||
<video
|
||||
className="ml-4"
|
||||
controls
|
||||
id="video"
|
||||
onMouseEnter={(e) => e.currentTarget.play()}
|
||||
onMouseLeave={(e) => e.currentTarget.pause()}
|
||||
src={
|
||||
imageUrlState.value ? imageUrlState.value.replace('ipfs://', 'https://ipfs-gw.stargaze-apis.com/ipfs/') : ''
|
||||
}
|
||||
/>
|
||||
),
|
||||
[imageUrlState.value],
|
||||
)
|
||||
|
||||
return (
|
||||
<div className="justify-items-start mb-3 rounded border-2 border-white/20 flex-column">
|
||||
<div className="flex justify-center">
|
||||
@@ -190,13 +217,16 @@ export const ImageUploadDetails = ({ onChange, mintRule }: ImageUploadDetailsPro
|
||||
<div className="flex flex-row w-full">
|
||||
<TextInput {...imageUrlState} className="mt-2 ml-6 w-full max-w-2xl" />
|
||||
<Conditional test={imageUrlState.value !== ''}>
|
||||
<div className="mt-2 ml-4 w-1/4 border-2 border-dashed">
|
||||
<img
|
||||
alt="badge-preview"
|
||||
className="w-full"
|
||||
src={imageUrlState.value.replace('IPFS://', 'ipfs://').replace(/,/g, '').replace(/"/g, '').trim()}
|
||||
/>
|
||||
</div>
|
||||
{getAssetType(imageUrlState.value) === 'image' && (
|
||||
<div className="mt-2 ml-4 w-1/4 border-2 border-dashed">
|
||||
<img
|
||||
alt="badge-preview"
|
||||
className="w-full"
|
||||
src={imageUrlState.value.replace('IPFS://', 'ipfs://').replace(/,/g, '').replace(/"/g, '').trim()}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{getAssetType(imageUrlState.value) === 'video' && videoPreview}
|
||||
</Conditional>
|
||||
</div>
|
||||
</div>
|
||||
@@ -249,7 +279,22 @@ export const ImageUploadDetails = ({ onChange, mintRule }: ImageUploadDetailsPro
|
||||
|
||||
<div className="flex w-full">
|
||||
<Conditional test={uploadService === 'nft-storage'}>
|
||||
<TextInput {...nftStorageApiKeyState} className="w-full" />
|
||||
<div className="flex-col w-full">
|
||||
<TextInput {...nftStorageApiKeyState} className="w-full" disabled={useDefaultApiKey} />
|
||||
<div className="flex-row mt-2 w-full form-control">
|
||||
<label className="cursor-pointer label">
|
||||
<span className="mr-2 font-bold">Use Default API Key</span>
|
||||
<input
|
||||
checked={useDefaultApiKey}
|
||||
className={`${useDefaultApiKey ? `bg-stargaze` : `bg-gray-600`} checkbox`}
|
||||
onClick={() => {
|
||||
setUseDefaultApiKey(!useDefaultApiKey)
|
||||
}}
|
||||
type="checkbox"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</Conditional>
|
||||
<Conditional test={uploadService === 'pinata'}>
|
||||
<TextInput {...pinataApiKeyState} className="w-full" />
|
||||
@@ -277,7 +322,7 @@ export const ImageUploadDetails = ({ onChange, mintRule }: ImageUploadDetailsPro
|
||||
)}
|
||||
>
|
||||
<input
|
||||
accept="image/*"
|
||||
accept="image/*, video/*"
|
||||
className={clsx(
|
||||
'file:py-2 file:px-4 file:mr-4 file:bg-plumbus-light file:rounded file:border-0 cursor-pointer',
|
||||
'before:absolute before:inset-0 before:hover:bg-white/5 before:transition',
|
||||
|
||||
@@ -10,7 +10,6 @@ import type { BadgeHubInstance } from 'contracts/badgeHub'
|
||||
import { toast } from 'react-hot-toast'
|
||||
import { useQuery } from 'react-query'
|
||||
|
||||
import { useWallet } from '../../../contexts/wallet'
|
||||
import type { MintRule } from '../creation/ImageUploadDetails'
|
||||
|
||||
interface BadgeQueriesProps {
|
||||
@@ -20,8 +19,6 @@ interface BadgeQueriesProps {
|
||||
mintRule: MintRule
|
||||
}
|
||||
export const BadgeQueries = ({ badgeHubContractAddress, badgeId, badgeHubMessages, mintRule }: BadgeQueriesProps) => {
|
||||
const wallet = useWallet()
|
||||
|
||||
const comboboxState = useQueryComboboxState()
|
||||
const type = comboboxState.value?.id
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ export type DispatchQueryArgs = {
|
||||
export const dispatchQuery = async (args: DispatchQueryArgs) => {
|
||||
const { badgeHubMessages } = args
|
||||
if (!badgeHubMessages) {
|
||||
throw new Error('Cannot perform a query')
|
||||
throw new Error('Cannot perform a query. Please connect your wallet first.')
|
||||
}
|
||||
switch (args.type) {
|
||||
case 'config': {
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
/* eslint-disable eslint-comments/disable-enable-pair */
|
||||
/* eslint-disable no-nested-ternary */
|
||||
import { toUtf8 } from '@cosmjs/encoding'
|
||||
import clsx from 'clsx'
|
||||
import { AirdropUpload } from 'components/AirdropUpload'
|
||||
import { Alert } from 'components/Alert'
|
||||
import { Button } from 'components/Button'
|
||||
import type { DispatchExecuteArgs } from 'components/collections/actions/actions'
|
||||
import { dispatchExecute, isEitherType, previewExecutePayload } from 'components/collections/actions/actions'
|
||||
@@ -14,9 +18,10 @@ import { InputDateTime } from 'components/InputDateTime'
|
||||
import { JsonPreview } from 'components/JsonPreview'
|
||||
import { Tooltip } from 'components/Tooltip'
|
||||
import { TransactionHash } from 'components/TransactionHash'
|
||||
import { useWallet } from 'contexts/wallet'
|
||||
import { useGlobalSettings } from 'contexts/globalSettings'
|
||||
import type { BaseMinterInstance } from 'contracts/baseMinter'
|
||||
import type { OpenEditionMinterInstance } from 'contracts/openEditionMinter'
|
||||
import type { RoyaltyRegistryInstance } from 'contracts/royaltyRegistry'
|
||||
import type { SG721Instance } from 'contracts/sg721'
|
||||
import type { VendingMinterInstance } from 'contracts/vendingMinter'
|
||||
import type { FormEvent } from 'react'
|
||||
@@ -24,8 +29,10 @@ import { useEffect, useState } from 'react'
|
||||
import { toast } from 'react-hot-toast'
|
||||
import { FaArrowRight } from 'react-icons/fa'
|
||||
import { useMutation } from 'react-query'
|
||||
import { ROYALTY_REGISTRY_ADDRESS } from 'utils/constants'
|
||||
import type { AirdropAllocation } from 'utils/isValidAccountsFile'
|
||||
import { resolveAddress } from 'utils/resolveAddress'
|
||||
import { useWallet } from 'utils/wallet'
|
||||
|
||||
import type { CollectionInfo } from '../../../contracts/sg721/contract'
|
||||
import { TextInput } from '../../forms/FormInput'
|
||||
@@ -38,6 +45,7 @@ interface CollectionActionsProps {
|
||||
vendingMinterMessages: VendingMinterInstance | undefined
|
||||
baseMinterMessages: BaseMinterInstance | undefined
|
||||
openEditionMinterMessages: OpenEditionMinterInstance | undefined
|
||||
royaltyRegistryMessages: RoyaltyRegistryInstance | undefined
|
||||
minterType: MinterType
|
||||
sg721Type: Sg721Type
|
||||
}
|
||||
@@ -51,11 +59,13 @@ export const CollectionActions = ({
|
||||
vendingMinterMessages,
|
||||
baseMinterMessages,
|
||||
openEditionMinterMessages,
|
||||
royaltyRegistryMessages,
|
||||
minterType,
|
||||
sg721Type,
|
||||
}: CollectionActionsProps) => {
|
||||
const wallet = useWallet()
|
||||
const [lastTx, setLastTx] = useState('')
|
||||
const { timezone } = useGlobalSettings()
|
||||
|
||||
const [timestamp, setTimestamp] = useState<Date | undefined>(undefined)
|
||||
const [endTimestamp, setEndTimestamp] = useState<Date | undefined>(undefined)
|
||||
@@ -65,6 +75,7 @@ export const CollectionActions = ({
|
||||
const [explicitContent, setExplicitContent] = useState<ExplicitContentType>(undefined)
|
||||
const [resolvedRecipientAddress, setResolvedRecipientAddress] = useState<string>('')
|
||||
const [jsonExtensions, setJsonExtensions] = useState<boolean>(false)
|
||||
const [decrement, setDecrement] = useState<boolean>(false)
|
||||
|
||||
const actionComboboxState = useActionsComboboxState()
|
||||
const type = actionComboboxState.value?.id
|
||||
@@ -105,6 +116,13 @@ export const CollectionActions = ({
|
||||
subtitle: 'Address of the recipient',
|
||||
})
|
||||
|
||||
const creatorState = useInputState({
|
||||
id: 'creator-address',
|
||||
name: 'creator',
|
||||
title: 'Creator Address',
|
||||
subtitle: 'Address of the creator',
|
||||
})
|
||||
|
||||
const tokenURIState = useInputState({
|
||||
id: 'token-uri',
|
||||
name: 'tokenURI',
|
||||
@@ -132,7 +150,7 @@ export const CollectionActions = ({
|
||||
id: 'update-mint-price',
|
||||
name: 'updateMintPrice',
|
||||
title: type === 'update_discount_price' ? 'Discount Price' : 'Update Mint Price',
|
||||
subtitle: type === 'update_discount_price' ? 'New discount price in STARS' : 'New minting price in STARS',
|
||||
subtitle: type === 'update_discount_price' ? 'New discount price' : 'New minting price',
|
||||
})
|
||||
|
||||
const descriptionState = useInputState({
|
||||
@@ -166,9 +184,14 @@ export const CollectionActions = ({
|
||||
const royaltyShareState = useInputState({
|
||||
id: 'royalty-share',
|
||||
name: 'royaltyShare',
|
||||
title: 'Share Percentage',
|
||||
subtitle: 'Percentage of royalties to be paid',
|
||||
placeholder: '5%',
|
||||
title: type !== 'update_royalties_for_infinity_swap' ? 'Share Percentage' : 'Share Delta',
|
||||
subtitle:
|
||||
type !== 'update_royalties_for_infinity_swap'
|
||||
? 'Percentage of royalties to be paid'
|
||||
: 'Change in share percentage',
|
||||
placeholder: isEitherType(type, ['set_royalties_for_infinity_swap', 'update_royalties_for_infinity_swap'])
|
||||
? '0.5%'
|
||||
: '5%',
|
||||
})
|
||||
|
||||
const showTokenUriField = isEitherType(type, ['mint_token_uri', 'update_token_metadata'])
|
||||
@@ -194,12 +217,21 @@ export const CollectionActions = ({
|
||||
'batch_transfer',
|
||||
'batch_mint_for',
|
||||
])
|
||||
const showAirdropFileField = isEitherType(type, ['airdrop', 'airdrop_open_edition', 'airdrop_specific'])
|
||||
const showAirdropFileField = isEitherType(type, [
|
||||
'airdrop',
|
||||
'airdrop_open_edition',
|
||||
'airdrop_specific',
|
||||
'batch_transfer_multi_address',
|
||||
])
|
||||
const showPriceField = isEitherType(type, ['update_mint_price', 'update_discount_price'])
|
||||
const showDescriptionField = type === 'update_collection_info'
|
||||
const showCreatorField = type === 'update_collection_info'
|
||||
const showImageField = type === 'update_collection_info'
|
||||
const showExternalLinkField = type === 'update_collection_info'
|
||||
const showRoyaltyRelatedFields = type === 'update_collection_info'
|
||||
const showRoyaltyRelatedFields =
|
||||
type === 'update_collection_info' ||
|
||||
type === 'set_royalties_for_infinity_swap' ||
|
||||
type === 'update_royalties_for_infinity_swap'
|
||||
const showExplicitContentField = type === 'update_collection_info'
|
||||
const showBaseUriField = type === 'batch_update_token_metadata'
|
||||
|
||||
@@ -210,6 +242,7 @@ export const CollectionActions = ({
|
||||
limit: limitState.value,
|
||||
minterContract: minterContractAddress,
|
||||
sg721Contract: sg721ContractAddress,
|
||||
royaltyRegistryContract: ROYALTY_REGISTRY_ADDRESS,
|
||||
tokenId: tokenIdState.value,
|
||||
tokenIds: tokenIdListState.value,
|
||||
tokenUri: tokenURIState.value.trim().endsWith('/')
|
||||
@@ -220,10 +253,11 @@ export const CollectionActions = ({
|
||||
baseMinterMessages,
|
||||
openEditionMinterMessages,
|
||||
sg721Messages,
|
||||
royaltyRegistryMessages,
|
||||
recipient: resolvedRecipientAddress,
|
||||
recipients: airdropArray,
|
||||
tokenRecipients: airdropAllocationArray,
|
||||
txSigner: wallet.address,
|
||||
txSigner: wallet.address || '',
|
||||
type,
|
||||
price: priceState.value.toString(),
|
||||
baseUri: baseURIState.value.trim().endsWith('/')
|
||||
@@ -231,6 +265,7 @@ export const CollectionActions = ({
|
||||
: baseURIState.value.trim(),
|
||||
collectionInfo,
|
||||
jsonExtensions,
|
||||
decrement,
|
||||
}
|
||||
const resolveRecipientAddress = async () => {
|
||||
await resolveAddress(recipientState.value.trim(), wallet).then((resolvedAddress) => {
|
||||
@@ -244,7 +279,7 @@ export const CollectionActions = ({
|
||||
const resolveRoyaltyPaymentAddress = async () => {
|
||||
await resolveAddress(royaltyPaymentAddressState.value.trim(), wallet).then((resolvedAddress) => {
|
||||
setCollectionInfo({
|
||||
description: descriptionState.value || undefined,
|
||||
description: descriptionState.value.replaceAll('\\n', '\n') || undefined,
|
||||
image: imageState.value || undefined,
|
||||
explicit_content: explicitContent,
|
||||
external_link: externalLinkState.value || undefined,
|
||||
@@ -263,9 +298,19 @@ export const CollectionActions = ({
|
||||
void resolveRoyaltyPaymentAddress()
|
||||
}, [royaltyPaymentAddressState.value])
|
||||
|
||||
const resolveCreatorAddress = async () => {
|
||||
await resolveAddress(creatorState.value.trim(), wallet).then((resolvedAddress) => {
|
||||
creatorState.onChange(resolvedAddress)
|
||||
})
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
void resolveCreatorAddress()
|
||||
}, [creatorState.value])
|
||||
|
||||
useEffect(() => {
|
||||
setCollectionInfo({
|
||||
description: descriptionState.value || undefined,
|
||||
description: descriptionState.value.replaceAll('\\n', '\n') || undefined,
|
||||
image: imageState.value || undefined,
|
||||
explicit_content: explicitContent,
|
||||
external_link: externalLinkState.value || undefined,
|
||||
@@ -276,6 +321,7 @@ export const CollectionActions = ({
|
||||
share: (Number(royaltyShareState.value) / 100).toString(),
|
||||
}
|
||||
: undefined,
|
||||
creator: creatorState.value || undefined,
|
||||
})
|
||||
}, [
|
||||
descriptionState.value,
|
||||
@@ -284,8 +330,16 @@ export const CollectionActions = ({
|
||||
externalLinkState.value,
|
||||
royaltyPaymentAddressState.value,
|
||||
royaltyShareState.value,
|
||||
creatorState.value,
|
||||
])
|
||||
|
||||
useEffect(() => {
|
||||
if (isEitherType(type, ['set_royalties_for_infinity_swap']) && Number(royaltyShareState.value) > 5) {
|
||||
royaltyShareState.onChange('5')
|
||||
toast.error('Royalty share cannot be greater than 5% for Infinity Swap')
|
||||
}
|
||||
}, [royaltyShareState.value])
|
||||
|
||||
useEffect(() => {
|
||||
const addresses: string[] = []
|
||||
airdropAllocationArray.forEach((allocation) => {
|
||||
@@ -304,20 +358,25 @@ export const CollectionActions = ({
|
||||
const { isLoading, mutate } = useMutation(
|
||||
async (event: FormEvent) => {
|
||||
event.preventDefault()
|
||||
if (!wallet.isWalletConnected) {
|
||||
throw new Error('Please connect your wallet first.')
|
||||
}
|
||||
if (!type) {
|
||||
throw new Error('Please select an action!')
|
||||
throw new Error('Please select an action.')
|
||||
}
|
||||
if (minterContractAddress === '' && sg721ContractAddress === '') {
|
||||
throw new Error('Please enter minter and sg721 contract addresses!')
|
||||
}
|
||||
|
||||
if (wallet.client && type === 'update_mint_price') {
|
||||
const contractConfig = wallet.client.queryContractSmart(minterContractAddress, {
|
||||
if (wallet.isWalletConnected && type === 'update_mint_price') {
|
||||
const contractConfig = (await wallet.getCosmWasmClient()).queryContractSmart(minterContractAddress, {
|
||||
config: {},
|
||||
})
|
||||
await toast
|
||||
.promise(
|
||||
wallet.client.queryContractSmart(minterContractAddress, {
|
||||
(
|
||||
await wallet.getCosmWasmClient()
|
||||
).queryContractSmart(minterContractAddress, {
|
||||
mint_price: {},
|
||||
}),
|
||||
{
|
||||
@@ -349,7 +408,9 @@ export const CollectionActions = ({
|
||||
})
|
||||
} else {
|
||||
await contractConfig.then(async (config) => {
|
||||
const factoryParameters = await wallet.client?.queryContractSmart(config.factory, {
|
||||
const factoryParameters = await (
|
||||
await wallet.getCosmWasmClient()
|
||||
).queryContractSmart(config.factory, {
|
||||
params: {},
|
||||
})
|
||||
if (
|
||||
@@ -379,8 +440,8 @@ export const CollectionActions = ({
|
||||
royaltyPaymentAddressState.value &&
|
||||
!royaltyPaymentAddressState.value.trim().endsWith('.stars')
|
||||
) {
|
||||
const contractInfoResponse = await wallet.client
|
||||
?.queryContractRaw(
|
||||
const contractInfoResponse = await (await wallet.getCosmWasmClient())
|
||||
.queryContractRaw(
|
||||
royaltyPaymentAddressState.value.trim(),
|
||||
toUtf8(Buffer.from(Buffer.from('contract_info').toString('hex'), 'hex').toString()),
|
||||
)
|
||||
@@ -392,8 +453,29 @@ export const CollectionActions = ({
|
||||
if (contractInfoResponse !== undefined) {
|
||||
const contractInfo = JSON.parse(new TextDecoder().decode(contractInfoResponse as Uint8Array))
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
||||
if (contractInfo && !contractInfo.contract.includes('splits'))
|
||||
throw new Error('The provided royalty payment address does not belong to a splits contract.')
|
||||
if (contractInfo && (contractInfo.contract.includes('minter') || contractInfo.contract.includes('sg721')))
|
||||
throw new Error('The provided royalty payment address does not belong to a compatible contract.')
|
||||
else console.log(contractInfo)
|
||||
}
|
||||
}
|
||||
|
||||
if (type === 'update_collection_info' && creatorState.value) {
|
||||
const resolvedCreatorAddress = await resolveAddress(creatorState.value.trim(), wallet)
|
||||
const contractInfoResponse = await (await wallet.getCosmWasmClient())
|
||||
.queryContractRaw(
|
||||
resolvedCreatorAddress,
|
||||
toUtf8(Buffer.from(Buffer.from('contract_info').toString('hex'), 'hex').toString()),
|
||||
)
|
||||
.catch((e) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
||||
if (e.message.includes('bech32')) throw new Error('Invalid creator address.')
|
||||
console.log(e.message)
|
||||
})
|
||||
if (contractInfoResponse !== undefined) {
|
||||
const contractInfo = JSON.parse(new TextDecoder().decode(contractInfoResponse as Uint8Array))
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
||||
if (contractInfo && !contractInfo.contract.includes('dao'))
|
||||
throw new Error('The provided creator address does not belong to a compatible contract.')
|
||||
else console.log(contractInfo)
|
||||
}
|
||||
}
|
||||
@@ -418,6 +500,28 @@ export const CollectionActions = ({
|
||||
setAirdropAllocationArray(data)
|
||||
}
|
||||
|
||||
const downloadSampleAirdropTokensFile = () => {
|
||||
const csvData =
|
||||
'address,amount\nstars153w5xhuqu3et29lgqk4dsynj6gjn96lr33wx4e,3\nstars1xkes5r2k8u3m3ayfpverlkcrq3k4jhdk8ws0uz,1\nstars1s8qx0zvz8yd6e4x0mqmqf7fr9vvfn622wtp3g3,2'
|
||||
const blob = new Blob([csvData], { type: 'text/csv' })
|
||||
const url = window.URL.createObjectURL(blob)
|
||||
const a = document.createElement('a')
|
||||
a.setAttribute('href', url)
|
||||
a.setAttribute('download', 'airdrop_tokens.csv')
|
||||
a.click()
|
||||
}
|
||||
|
||||
const downloadSampleAirdropSpecificTokensFile = () => {
|
||||
const csvData =
|
||||
'address,tokenId\nstars153w5xhuqu3et29lgqk4dsynj6gjn96lr33wx4e,214\nstars1xkes5r2k8u3m3ayfpverlkcrq3k4jhdk8ws0uz,683\nstars1s8qx0zvz8yd6e4x0mqmqf7fr9vvfn622wtp3g3,102'
|
||||
const blob = new Blob([csvData], { type: 'text/csv' })
|
||||
const url = window.URL.createObjectURL(blob)
|
||||
const a = document.createElement('a')
|
||||
a.setAttribute('href', url)
|
||||
a.setAttribute('download', 'airdrop_specific_tokens.csv')
|
||||
a.click()
|
||||
}
|
||||
|
||||
return (
|
||||
<form>
|
||||
<div className="grid grid-cols-2 mt-4">
|
||||
@@ -432,6 +536,7 @@ export const CollectionActions = ({
|
||||
{showBaseUriField && <TextInput className="mt-2" {...baseURIState} />}
|
||||
{showNumberOfTokensField && <NumberInput className="mt-2" {...batchNumberState} />}
|
||||
{showPriceField && <NumberInput className="mt-2" {...priceState} />}
|
||||
{showCreatorField && <AddressInput className="mt-2" {...creatorState} />}
|
||||
{showDescriptionField && <TextInput className="my-2" {...descriptionState} />}
|
||||
{showImageField && <TextInput className="mb-2" {...imageState} />}
|
||||
{showExternalLinkField && <TextInput className="mb-2" {...externalLinkState} />}
|
||||
@@ -439,6 +544,24 @@ export const CollectionActions = ({
|
||||
<div className="p-2 my-4 rounded border-2 border-gray-500/50">
|
||||
<TextInput className="mb-2" {...royaltyPaymentAddressState} />
|
||||
<NumberInput className="mb-2" {...royaltyShareState} />
|
||||
<Conditional test={type === 'update_royalties_for_infinity_swap'}>
|
||||
<div className="flex flex-row space-y-2 w-1/4">
|
||||
<div className={clsx('flex flex-col space-y-2 w-full form-control')}>
|
||||
<label className="justify-start cursor-pointer label">
|
||||
<div className="flex flex-col">
|
||||
<span className="mr-4 font-bold">Increment</span>
|
||||
</div>
|
||||
<input
|
||||
checked={decrement}
|
||||
className={`toggle ${decrement ? `bg-stargaze` : `bg-gray-600`}`}
|
||||
onClick={() => setDecrement(!decrement)}
|
||||
type="checkbox"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<span className="mx-4 font-bold">Decrement</span>
|
||||
</div>
|
||||
</Conditional>
|
||||
</div>
|
||||
)}
|
||||
{showExplicitContentField && (
|
||||
@@ -489,25 +612,79 @@ export const CollectionActions = ({
|
||||
</div>
|
||||
)}
|
||||
{showAirdropFileField && (
|
||||
<FormGroup
|
||||
subtitle={`CSV file that contains the airdrop addresses and the ${
|
||||
type === 'airdrop' ? 'amount of tokens' : 'token ID'
|
||||
} allocated for each address. Should start with the following header row: ${
|
||||
type === 'airdrop' ? 'address,amount' : 'address,tokenId'
|
||||
}`}
|
||||
title="Airdrop File"
|
||||
>
|
||||
<AirdropUpload onChange={airdropFileOnChange} />
|
||||
</FormGroup>
|
||||
<div>
|
||||
<FormGroup
|
||||
subtitle={`CSV file that contains the ${
|
||||
type === 'batch_transfer_multi_address' ? '' : 'airdrop'
|
||||
} addresses and the ${
|
||||
type === 'airdrop' || type === 'airdrop_open_edition' ? 'amount of tokens' : 'token ID'
|
||||
} allocated for each address. Should start with the following header row: ${
|
||||
type === 'airdrop' || type === 'airdrop_open_edition' ? 'address,amount' : 'address,tokenId'
|
||||
}`}
|
||||
title={`${type === 'batch_transfer_multi_address' ? 'Multi-Recipient Transfer File' : 'Airdrop File'}`}
|
||||
>
|
||||
<AirdropUpload onChange={airdropFileOnChange} />
|
||||
</FormGroup>
|
||||
<Button
|
||||
className="ml-4 text-sm"
|
||||
onClick={
|
||||
type === 'airdrop' || type === 'airdrop_open_edition'
|
||||
? downloadSampleAirdropTokensFile
|
||||
: downloadSampleAirdropSpecificTokensFile
|
||||
}
|
||||
>
|
||||
Download Sample File
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
<Conditional test={showDateField}>
|
||||
<FormControl className="mt-2" htmlId="start-date" title="Start Time">
|
||||
<InputDateTime minDate={new Date()} onChange={(date) => setTimestamp(date)} value={timestamp} />
|
||||
<FormControl
|
||||
className="mt-2"
|
||||
htmlId="start-date"
|
||||
title={`Start Time ${timezone === 'Local' ? '(local)' : '(UTC)'}`}
|
||||
>
|
||||
<InputDateTime
|
||||
minDate={
|
||||
timezone === 'Local' ? new Date() : new Date(Date.now() + new Date().getTimezoneOffset() * 60 * 1000)
|
||||
}
|
||||
onChange={(date) =>
|
||||
setTimestamp(
|
||||
timezone === 'Local' ? date : new Date(date.getTime() - new Date().getTimezoneOffset() * 60 * 1000),
|
||||
)
|
||||
}
|
||||
value={
|
||||
timezone === 'Local'
|
||||
? timestamp
|
||||
: timestamp
|
||||
? new Date(timestamp.getTime() + new Date().getTimezoneOffset() * 60 * 1000)
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
</FormControl>
|
||||
</Conditional>
|
||||
<Conditional test={showEndDateField}>
|
||||
<FormControl className="mt-2" htmlId="end-date" title="End Time">
|
||||
<InputDateTime minDate={new Date()} onChange={(date) => setEndTimestamp(date)} value={endTimestamp} />
|
||||
<FormControl
|
||||
className="mt-2"
|
||||
htmlId="end-date"
|
||||
title={`End Time ${timezone === 'Local' ? '(local)' : '(UTC)'}`}
|
||||
>
|
||||
<InputDateTime
|
||||
minDate={
|
||||
timezone === 'Local' ? new Date() : new Date(Date.now() + new Date().getTimezoneOffset() * 60 * 1000)
|
||||
}
|
||||
onChange={(date) =>
|
||||
setEndTimestamp(
|
||||
timezone === 'Local' ? date : new Date(date.getTime() - new Date().getTimezoneOffset() * 60 * 1000),
|
||||
)
|
||||
}
|
||||
value={
|
||||
timezone === 'Local'
|
||||
? endTimestamp
|
||||
: endTimestamp
|
||||
? new Date(endTimestamp.getTime() + new Date().getTimezoneOffset() * 60 * 1000)
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
</FormControl>
|
||||
</Conditional>
|
||||
<Conditional test={showBaseUriField}>
|
||||
@@ -530,6 +707,17 @@ export const CollectionActions = ({
|
||||
</div>
|
||||
</Tooltip>
|
||||
</Conditional>
|
||||
<Conditional test={type === 'update_collection_info'}>
|
||||
<Alert className="mt-2 text-sm" type="info">
|
||||
Please note that you are only required to fill in the fields you want to update.
|
||||
</Alert>
|
||||
</Conditional>
|
||||
<Conditional test={type === 'update_discount_price'}>
|
||||
<Alert className="mt-2 text-sm" type="warning">
|
||||
Please note that discount price can only be updated every 24 hours and be removed 12 hours after its last
|
||||
update.
|
||||
</Alert>
|
||||
</Conditional>
|
||||
</div>
|
||||
<div className="-mt-6">
|
||||
<div className="relative mb-2">
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
/* eslint-disable eslint-comments/disable-enable-pair */
|
||||
import { useBaseMinterContract } from 'contracts/baseMinter'
|
||||
import { useOpenEditionMinterContract } from 'contracts/openEditionMinter'
|
||||
import type { RoyaltyRegistryInstance } from 'contracts/royaltyRegistry'
|
||||
import { useRoyaltyRegistryContract } from 'contracts/royaltyRegistry'
|
||||
import type { CollectionInfo, SG721Instance } from 'contracts/sg721'
|
||||
import { useSG721Contract } from 'contracts/sg721'
|
||||
import type { VendingMinterInstance } from 'contracts/vendingMinter'
|
||||
import { useVendingMinterContract } from 'contracts/vendingMinter'
|
||||
import { INFINITY_SWAP_PROTOCOL_ADDRESS } from 'utils/constants'
|
||||
import type { AirdropAllocation } from 'utils/isValidAccountsFile'
|
||||
|
||||
import type { BaseMinterInstance } from '../../../contracts/baseMinter/contract'
|
||||
@@ -29,8 +32,11 @@ export const ACTION_TYPES = [
|
||||
'update_per_address_limit',
|
||||
'update_collection_info',
|
||||
'freeze_collection_info',
|
||||
'set_royalties_for_infinity_swap',
|
||||
'update_royalties_for_infinity_swap',
|
||||
'transfer',
|
||||
'batch_transfer',
|
||||
'batch_transfer_multi_address',
|
||||
'burn',
|
||||
'batch_burn',
|
||||
'batch_mint_for',
|
||||
@@ -72,6 +78,16 @@ export const BASE_ACTION_LIST: ActionListItem[] = [
|
||||
name: 'Freeze Collection Info',
|
||||
description: `Freeze collection info to prevent further updates`,
|
||||
},
|
||||
{
|
||||
id: 'set_royalties_for_infinity_swap',
|
||||
name: 'Set Royalty Details for Infinity Swap',
|
||||
description: `Set royalty details for Infinity Swap`,
|
||||
},
|
||||
{
|
||||
id: 'update_royalties_for_infinity_swap',
|
||||
name: 'Update Royalty Details for Infinity Swap',
|
||||
description: `Update royalty details for Infinity Swap`,
|
||||
},
|
||||
{
|
||||
id: 'transfer',
|
||||
name: 'Transfer Tokens',
|
||||
@@ -82,6 +98,11 @@ export const BASE_ACTION_LIST: ActionListItem[] = [
|
||||
name: 'Batch Transfer Tokens',
|
||||
description: `Transfer a list of tokens to a recipient`,
|
||||
},
|
||||
{
|
||||
id: 'batch_transfer_multi_address',
|
||||
name: 'Transfer Tokens to Multiple Recipients',
|
||||
description: `Transfer a list of tokens to multiple addresses`,
|
||||
},
|
||||
{
|
||||
id: 'burn',
|
||||
name: 'Burn Token',
|
||||
@@ -160,6 +181,16 @@ export const VENDING_ACTION_LIST: ActionListItem[] = [
|
||||
name: 'Freeze Collection Info',
|
||||
description: `Freeze collection info to prevent further updates`,
|
||||
},
|
||||
{
|
||||
id: 'set_royalties_for_infinity_swap',
|
||||
name: 'Set Royalty Details for Infinity Swap',
|
||||
description: `Set royalty details for Infinity Swap`,
|
||||
},
|
||||
{
|
||||
id: 'update_royalties_for_infinity_swap',
|
||||
name: 'Update Royalty Details for Infinity Swap',
|
||||
description: `Update royalty details for Infinity Swap`,
|
||||
},
|
||||
{
|
||||
id: 'transfer',
|
||||
name: 'Transfer Tokens',
|
||||
@@ -170,6 +201,11 @@ export const VENDING_ACTION_LIST: ActionListItem[] = [
|
||||
name: 'Batch Transfer Tokens',
|
||||
description: `Transfer a list of tokens to a recipient`,
|
||||
},
|
||||
{
|
||||
id: 'batch_transfer_multi_address',
|
||||
name: 'Transfer Tokens to Multiple Recipients',
|
||||
description: `Transfer a list of tokens to multiple addresses`,
|
||||
},
|
||||
{
|
||||
id: 'burn',
|
||||
name: 'Burn Token',
|
||||
@@ -248,6 +284,16 @@ export const OPEN_EDITION_ACTION_LIST: ActionListItem[] = [
|
||||
name: 'Freeze Collection Info',
|
||||
description: `Freeze collection info to prevent further updates`,
|
||||
},
|
||||
{
|
||||
id: 'set_royalties_for_infinity_swap',
|
||||
name: 'Set Royalty Details for Infinity Swap',
|
||||
description: `Set royalty details for Infinity Swap`,
|
||||
},
|
||||
{
|
||||
id: 'update_royalties_for_infinity_swap',
|
||||
name: 'Update Royalty Details for Infinity Swap',
|
||||
description: `Update royalty details for Infinity Swap`,
|
||||
},
|
||||
{
|
||||
id: 'transfer',
|
||||
name: 'Transfer Tokens',
|
||||
@@ -258,6 +304,11 @@ export const OPEN_EDITION_ACTION_LIST: ActionListItem[] = [
|
||||
name: 'Batch Transfer Tokens',
|
||||
description: `Transfer a list of tokens to a recipient`,
|
||||
},
|
||||
{
|
||||
id: 'batch_transfer_multi_address',
|
||||
name: 'Transfer Tokens to Multiple Recipients',
|
||||
description: `Transfer a list of tokens to multiple addresses`,
|
||||
},
|
||||
{
|
||||
id: 'burn',
|
||||
name: 'Burn Token',
|
||||
@@ -307,10 +358,12 @@ export interface DispatchExecuteProps {
|
||||
export interface DispatchExecuteArgs {
|
||||
minterContract: string
|
||||
sg721Contract: string
|
||||
royaltyRegistryContract: string
|
||||
vendingMinterMessages?: VendingMinterInstance
|
||||
baseMinterMessages?: BaseMinterInstance
|
||||
openEditionMinterMessages?: OpenEditionMinterInstance
|
||||
sg721Messages?: SG721Instance
|
||||
royaltyRegistryMessages?: RoyaltyRegistryInstance
|
||||
txSigner: string
|
||||
type: string | undefined
|
||||
tokenUri: string
|
||||
@@ -328,11 +381,25 @@ export interface DispatchExecuteArgs {
|
||||
collectionInfo: CollectionInfo | undefined
|
||||
baseUri: string
|
||||
jsonExtensions: boolean
|
||||
decrement: boolean
|
||||
}
|
||||
|
||||
export const dispatchExecute = async (args: DispatchExecuteArgs) => {
|
||||
const { vendingMinterMessages, baseMinterMessages, openEditionMinterMessages, sg721Messages, txSigner } = args
|
||||
if (!vendingMinterMessages || !baseMinterMessages || !openEditionMinterMessages || !sg721Messages) {
|
||||
const {
|
||||
vendingMinterMessages,
|
||||
baseMinterMessages,
|
||||
openEditionMinterMessages,
|
||||
sg721Messages,
|
||||
royaltyRegistryMessages,
|
||||
txSigner,
|
||||
} = args
|
||||
if (
|
||||
!vendingMinterMessages ||
|
||||
!baseMinterMessages ||
|
||||
!openEditionMinterMessages ||
|
||||
!sg721Messages ||
|
||||
!royaltyRegistryMessages
|
||||
) {
|
||||
throw new Error('Cannot execute actions')
|
||||
}
|
||||
switch (args.type) {
|
||||
@@ -399,12 +466,32 @@ export const dispatchExecute = async (args: DispatchExecuteArgs) => {
|
||||
case 'shuffle': {
|
||||
return vendingMinterMessages.shuffle(txSigner)
|
||||
}
|
||||
case 'set_royalties_for_infinity_swap': {
|
||||
return royaltyRegistryMessages.setCollectionRoyaltyProtocol(
|
||||
args.sg721Contract,
|
||||
INFINITY_SWAP_PROTOCOL_ADDRESS,
|
||||
args.collectionInfo?.royalty_info?.payment_address as string,
|
||||
Number(args.collectionInfo?.royalty_info?.share) * 100,
|
||||
)
|
||||
}
|
||||
case 'update_royalties_for_infinity_swap': {
|
||||
return royaltyRegistryMessages.updateCollectionRoyaltyProtocol(
|
||||
args.sg721Contract,
|
||||
INFINITY_SWAP_PROTOCOL_ADDRESS,
|
||||
args.collectionInfo?.royalty_info?.payment_address as string,
|
||||
Number(args.collectionInfo?.royalty_info?.share) * 100,
|
||||
args.decrement,
|
||||
)
|
||||
}
|
||||
case 'transfer': {
|
||||
return sg721Messages.transferNft(args.recipient, args.tokenId.toString())
|
||||
}
|
||||
case 'batch_transfer': {
|
||||
return sg721Messages.batchTransfer(args.recipient, args.tokenIds)
|
||||
}
|
||||
case 'batch_transfer_multi_address': {
|
||||
return sg721Messages.batchTransferMultiAddress(txSigner, args.tokenRecipients)
|
||||
}
|
||||
case 'burn': {
|
||||
return sg721Messages.burn(args.tokenId.toString())
|
||||
}
|
||||
@@ -441,7 +528,10 @@ export const previewExecutePayload = (args: DispatchExecuteArgs) => {
|
||||
const { messages: baseMinterMessages } = useBaseMinterContract()
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
const { messages: openEditionMinterMessages } = useOpenEditionMinterContract()
|
||||
const { minterContract, sg721Contract } = args
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
const { messages: royaltyRegistryMessages } = useRoyaltyRegistryContract()
|
||||
|
||||
const { minterContract, sg721Contract, royaltyRegistryContract } = args
|
||||
switch (args.type) {
|
||||
case 'mint_token_uri': {
|
||||
return baseMinterMessages(minterContract)?.mint(args.tokenUri)
|
||||
@@ -506,12 +596,32 @@ export const previewExecutePayload = (args: DispatchExecuteArgs) => {
|
||||
case 'shuffle': {
|
||||
return vendingMinterMessages(minterContract)?.shuffle()
|
||||
}
|
||||
case 'set_royalties_for_infinity_swap': {
|
||||
return royaltyRegistryMessages(royaltyRegistryContract)?.setCollectionRoyaltyProtocol(
|
||||
args.sg721Contract,
|
||||
INFINITY_SWAP_PROTOCOL_ADDRESS,
|
||||
args.collectionInfo?.royalty_info?.payment_address as string,
|
||||
Number(args.collectionInfo?.royalty_info?.share) * 100,
|
||||
)
|
||||
}
|
||||
case 'update_royalties_for_infinity_swap': {
|
||||
return royaltyRegistryMessages(royaltyRegistryContract)?.updateCollectionRoyaltyProtocol(
|
||||
args.sg721Contract,
|
||||
INFINITY_SWAP_PROTOCOL_ADDRESS,
|
||||
args.collectionInfo?.royalty_info?.payment_address as string,
|
||||
Number(args.collectionInfo?.royalty_info?.share) * 100,
|
||||
args.decrement,
|
||||
)
|
||||
}
|
||||
case 'transfer': {
|
||||
return sg721Messages(sg721Contract)?.transferNft(args.recipient, args.tokenId.toString())
|
||||
}
|
||||
case 'batch_transfer': {
|
||||
return sg721Messages(sg721Contract)?.batchTransfer(args.recipient, args.tokenIds)
|
||||
}
|
||||
case 'batch_transfer_multi_address': {
|
||||
return sg721Messages(sg721Contract)?.batchTransferMultiAddress(args.tokenRecipients)
|
||||
}
|
||||
case 'burn': {
|
||||
return sg721Messages(sg721Contract)?.burn(args.tokenId.toString())
|
||||
}
|
||||
|
||||
@@ -8,10 +8,10 @@ import clsx from 'clsx'
|
||||
import { Alert } from 'components/Alert'
|
||||
import { Conditional } from 'components/Conditional'
|
||||
import { useInputState } from 'components/forms/FormInput.hooks'
|
||||
import { useWallet } from 'contexts/wallet'
|
||||
import React, { useCallback, useEffect, useState } from 'react'
|
||||
import { toast } from 'react-hot-toast'
|
||||
import { API_URL } from 'utils/constants'
|
||||
import { useWallet } from 'utils/wallet'
|
||||
|
||||
import { useDebounce } from '../../../utils/debounce'
|
||||
import { TextInput } from '../../forms/FormInput'
|
||||
@@ -28,6 +28,7 @@ export interface MinterInfo {
|
||||
interface BaseMinterDetailsProps {
|
||||
onChange: (data: BaseMinterDetailsDataProps) => void
|
||||
minterType: MinterType
|
||||
importedBaseMinterDetails?: BaseMinterDetailsDataProps
|
||||
}
|
||||
|
||||
export interface BaseMinterDetailsDataProps {
|
||||
@@ -37,7 +38,7 @@ export interface BaseMinterDetailsDataProps {
|
||||
collectionTokenCount: number | undefined
|
||||
}
|
||||
|
||||
export const BaseMinterDetails = ({ onChange, minterType }: BaseMinterDetailsProps) => {
|
||||
export const BaseMinterDetails = ({ onChange, minterType, importedBaseMinterDetails }: BaseMinterDetailsProps) => {
|
||||
const wallet = useWallet()
|
||||
|
||||
const [myBaseMinterContracts, setMyBaseMinterContracts] = useState<MinterInfo[]>([])
|
||||
@@ -55,7 +56,7 @@ export const BaseMinterDetails = ({ onChange, minterType }: BaseMinterDetailsPro
|
||||
|
||||
const fetchMinterContracts = async (): Promise<MinterInfo[]> => {
|
||||
const contracts: MinterInfo[] = await axios
|
||||
.get(`${API_URL}/api/v1beta/collections/${wallet.address}`)
|
||||
.get(`${API_URL}/api/v1beta/collections/${wallet.address || ''}`)
|
||||
.then((response) => {
|
||||
const collectionData = response.data
|
||||
const minterContracts = collectionData.map((collection: any) => {
|
||||
@@ -69,8 +70,8 @@ export const BaseMinterDetails = ({ onChange, minterType }: BaseMinterDetailsPro
|
||||
}
|
||||
|
||||
async function getMinterContractType(minterContractAddress: string) {
|
||||
if (wallet.client && minterContractAddress.length > 0) {
|
||||
const client = wallet.client
|
||||
if (wallet.isWalletConnected && minterContractAddress.length > 0) {
|
||||
const client = await wallet.getCosmWasmClient()
|
||||
const data = await client.queryContractRaw(
|
||||
minterContractAddress,
|
||||
toUtf8(Buffer.from(Buffer.from('contract_info').toString('hex'), 'hex').toString()),
|
||||
@@ -128,8 +129,10 @@ export const BaseMinterDetails = ({ onChange, minterType }: BaseMinterDetailsPro
|
||||
|
||||
const fetchSg721Address = async () => {
|
||||
if (debouncedExistingBaseMinterContract.length === 0) return
|
||||
await wallet.client
|
||||
?.queryContractSmart(debouncedExistingBaseMinterContract, {
|
||||
await (
|
||||
await wallet.getCosmWasmClient()
|
||||
)
|
||||
.queryContractSmart(debouncedExistingBaseMinterContract, {
|
||||
config: {},
|
||||
})
|
||||
.then((response) => {
|
||||
@@ -144,8 +147,10 @@ export const BaseMinterDetails = ({ onChange, minterType }: BaseMinterDetailsPro
|
||||
|
||||
const fetchCollectionTokenCount = async () => {
|
||||
if (selectedCollectionAddress === undefined) return
|
||||
await wallet.client
|
||||
?.queryContractSmart(selectedCollectionAddress, {
|
||||
await (
|
||||
await wallet.getCosmWasmClient()
|
||||
)
|
||||
.queryContractSmart(selectedCollectionAddress, {
|
||||
num_tokens: {},
|
||||
})
|
||||
.then((response) => {
|
||||
@@ -163,7 +168,7 @@ export const BaseMinterDetails = ({ onChange, minterType }: BaseMinterDetailsPro
|
||||
setMyBaseMinterContracts([])
|
||||
existingBaseMinterState.onChange('')
|
||||
void displayToast()
|
||||
} else if (baseMinterAcquisitionMethod === 'new' || !wallet.initialized) {
|
||||
} else if (baseMinterAcquisitionMethod === 'new' || !wallet.isWalletConnected) {
|
||||
setMyBaseMinterContracts([])
|
||||
existingBaseMinterState.onChange('')
|
||||
}
|
||||
@@ -193,11 +198,20 @@ export const BaseMinterDetails = ({ onChange, minterType }: BaseMinterDetailsPro
|
||||
}, [
|
||||
existingBaseMinterState.value,
|
||||
baseMinterAcquisitionMethod,
|
||||
wallet.initialized,
|
||||
wallet.isWalletConnected,
|
||||
selectedCollectionAddress,
|
||||
collectionTokenCount,
|
||||
])
|
||||
|
||||
useEffect(() => {
|
||||
if (importedBaseMinterDetails) {
|
||||
setBaseMinterAcquisitionMethod(importedBaseMinterDetails.baseMinterAcquisitionMethod)
|
||||
existingBaseMinterState.onChange(
|
||||
importedBaseMinterDetails.existingBaseMinter ? importedBaseMinterDetails.existingBaseMinter : '',
|
||||
)
|
||||
}
|
||||
}, [importedBaseMinterDetails])
|
||||
|
||||
return (
|
||||
<div className="mx-10 mb-4 rounded border-2 border-white/20">
|
||||
<div className="flex justify-center mb-2">
|
||||
@@ -260,7 +274,7 @@ export const BaseMinterDetails = ({ onChange, minterType }: BaseMinterDetailsPro
|
||||
</Conditional>
|
||||
<Conditional test={myBaseMinterContracts.length === 0}>
|
||||
<div className="flex flex-col">
|
||||
<Conditional test={wallet.initialized}>
|
||||
<Conditional test={wallet.isWalletConnected}>
|
||||
<Alert className="my-2 w-[90%]" type="info">
|
||||
No previous 1/1 collections were found. You may create a new 1/1 collection or fill in the minter
|
||||
contract address manually.
|
||||
@@ -272,7 +286,7 @@ export const BaseMinterDetails = ({ onChange, minterType }: BaseMinterDetailsPro
|
||||
isRequired
|
||||
/>
|
||||
</Conditional>
|
||||
<Conditional test={!wallet.initialized}>
|
||||
<Conditional test={!wallet.isWalletConnected}>
|
||||
<Alert className="my-2 w-[90%]" type="warning">
|
||||
Please connect your wallet first.
|
||||
</Alert>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
/* eslint-disable eslint-comments/disable-enable-pair */
|
||||
/* eslint-disable no-nested-ternary */
|
||||
/* eslint-disable @typescript-eslint/no-unnecessary-condition */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
@@ -10,6 +11,7 @@ import { FormGroup } from 'components/FormGroup'
|
||||
import { useInputState } from 'components/forms/FormInput.hooks'
|
||||
import { InputDateTime } from 'components/InputDateTime'
|
||||
import { Tooltip } from 'components/Tooltip'
|
||||
import { useGlobalSettings } from 'contexts/globalSettings'
|
||||
import { addLogItem } from 'contexts/log'
|
||||
import type { ChangeEvent } from 'react'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
@@ -26,6 +28,7 @@ interface CollectionDetailsProps {
|
||||
uploadMethod: UploadMethod
|
||||
coverImageUrl: string
|
||||
minterType: MinterType
|
||||
importedCollectionDetails?: CollectionDetailsDataProps
|
||||
}
|
||||
|
||||
export interface CollectionDetailsDataProps {
|
||||
@@ -39,11 +42,18 @@ export interface CollectionDetailsDataProps {
|
||||
updatable: boolean
|
||||
}
|
||||
|
||||
export const CollectionDetails = ({ onChange, uploadMethod, coverImageUrl, minterType }: CollectionDetailsProps) => {
|
||||
export const CollectionDetails = ({
|
||||
onChange,
|
||||
uploadMethod,
|
||||
coverImageUrl,
|
||||
minterType,
|
||||
importedCollectionDetails,
|
||||
}: CollectionDetailsProps) => {
|
||||
const [coverImage, setCoverImage] = useState<File | null>(null)
|
||||
const [timestamp, setTimestamp] = useState<Date | undefined>()
|
||||
const [explicit, setExplicit] = useState<boolean>(false)
|
||||
const [updatable, setUpdatable] = useState<boolean>(false)
|
||||
const { timezone } = useGlobalSettings()
|
||||
|
||||
const initialRender = useRef(true)
|
||||
|
||||
@@ -105,6 +115,23 @@ export const CollectionDetails = ({ onChange, uploadMethod, coverImageUrl, minte
|
||||
updatable,
|
||||
])
|
||||
|
||||
useEffect(() => {
|
||||
if (importedCollectionDetails) {
|
||||
nameState.onChange(importedCollectionDetails.name)
|
||||
descriptionState.onChange(importedCollectionDetails.description)
|
||||
symbolState.onChange(importedCollectionDetails.symbol)
|
||||
externalLinkState.onChange(importedCollectionDetails.externalLink || '')
|
||||
setTimestamp(
|
||||
importedCollectionDetails.startTradingTime
|
||||
? new Date(parseInt(importedCollectionDetails.startTradingTime) / 1_000_000)
|
||||
: undefined,
|
||||
)
|
||||
setExplicit(importedCollectionDetails.explicit)
|
||||
setUpdatable(importedCollectionDetails.updatable)
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [importedCollectionDetails])
|
||||
|
||||
const selectCoverImage = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
if (event.target.files === null) return toast.error('Error selecting cover image')
|
||||
if (event.target.files.length === 0) {
|
||||
@@ -115,7 +142,9 @@ export const CollectionDetails = ({ onChange, uploadMethod, coverImageUrl, minte
|
||||
reader.onload = (e) => {
|
||||
if (!e.target?.result) return toast.error('Error parsing file.')
|
||||
if (!event.target.files) return toast.error('No files selected.')
|
||||
const imageFile = new File([e.target.result], event.target.files[0].name, { type: 'image/jpg' })
|
||||
const imageFile = new File([e.target.result], event.target.files[0].name.replaceAll('#', ''), {
|
||||
type: 'image/jpg',
|
||||
})
|
||||
setCoverImage(imageFile)
|
||||
}
|
||||
reader.readAsArrayBuffer(event.target.files[0])
|
||||
@@ -154,9 +183,31 @@ export const CollectionDetails = ({ onChange, uploadMethod, coverImageUrl, minte
|
||||
className={clsx(minterType === 'base' ? 'mt-10' : 'mt-2')}
|
||||
htmlId="timestamp"
|
||||
subtitle="Trading start time offset will be set as 2 weeks by default."
|
||||
title="Trading Start Time (optional)"
|
||||
title={`Trading Start Time (optional | ${timezone === 'Local' ? 'local)' : 'UTC)'}`}
|
||||
>
|
||||
<InputDateTime minDate={new Date()} onChange={(date) => setTimestamp(date)} value={timestamp} />
|
||||
<InputDateTime
|
||||
minDate={
|
||||
timezone === 'Local'
|
||||
? new Date()
|
||||
: new Date(Date.now() + new Date().getTimezoneOffset() * 60 * 1000)
|
||||
}
|
||||
onChange={(date) =>
|
||||
date
|
||||
? setTimestamp(
|
||||
timezone === 'Local'
|
||||
? date
|
||||
: new Date(date.getTime() - new Date().getTimezoneOffset() * 60 * 1000),
|
||||
)
|
||||
: setTimestamp(undefined)
|
||||
}
|
||||
value={
|
||||
timezone === 'Local'
|
||||
? timestamp
|
||||
: timestamp
|
||||
? new Date(timestamp.getTime() + new Date().getTimezoneOffset() * 60 * 1000)
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
</FormControl>
|
||||
</Conditional>
|
||||
<Conditional test={minterType === 'base'}>
|
||||
@@ -207,7 +258,7 @@ export const CollectionDetails = ({ onChange, uploadMethod, coverImageUrl, minte
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Conditional test={SG721_UPDATABLE_CODE_ID > 0}>
|
||||
<Conditional test={false && SG721_UPDATABLE_CODE_ID > 0}>
|
||||
<Tooltip
|
||||
backgroundColor="bg-blue-500"
|
||||
label={
|
||||
@@ -222,7 +273,10 @@ export const CollectionDetails = ({ onChange, uploadMethod, coverImageUrl, minte
|
||||
>
|
||||
<div className={clsx('flex flex-col mt-11 space-y-2 w-full form-control')}>
|
||||
<label className="justify-start cursor-pointer label">
|
||||
<span className="mr-4 font-bold">Updatable Token Metadata</span>
|
||||
<div className="flex flex-col">
|
||||
<span className="mr-4 font-bold">Updatable Token Metadata</span>
|
||||
<span className="mr-4">(Price: 2000 STARS)</span>
|
||||
</div>
|
||||
<input
|
||||
checked={updatable}
|
||||
className={`toggle ${updatable ? `bg-stargaze` : `bg-gray-600`}`}
|
||||
@@ -327,7 +381,7 @@ export const CollectionDetails = ({ onChange, uploadMethod, coverImageUrl, minte
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Conditional test={SG721_UPDATABLE_CODE_ID > 0}>
|
||||
<Conditional test={false && SG721_UPDATABLE_CODE_ID > 0}>
|
||||
<Tooltip
|
||||
backgroundColor="bg-blue-500"
|
||||
label={
|
||||
@@ -344,11 +398,14 @@ export const CollectionDetails = ({ onChange, uploadMethod, coverImageUrl, minte
|
||||
className={clsx(
|
||||
minterType === 'base'
|
||||
? 'flex flex-col -ml-16 space-y-2 w-1/2 form-control'
|
||||
: 'flex flex-col space-y-2 w-3/4 form-control',
|
||||
: 'flex flex-col space-y-2 w-full form-control',
|
||||
)}
|
||||
>
|
||||
<label className="justify-start cursor-pointer label">
|
||||
<span className="mr-4 font-bold">Updatable Token Metadata</span>
|
||||
<div className="flex flex-col">
|
||||
<span className="mr-4 font-bold">Updatable Token Metadata</span>
|
||||
<span className="mr-4">(Price: 2000 STARS)</span>
|
||||
</div>
|
||||
<input
|
||||
checked={updatable}
|
||||
className={`toggle ${updatable ? `bg-stargaze` : `bg-gray-600`}`}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
/* eslint-disable eslint-comments/disable-enable-pair */
|
||||
/* eslint-disable @typescript-eslint/no-unnecessary-condition */
|
||||
/* eslint-disable no-nested-ternary */
|
||||
import { FormControl } from 'components/FormControl'
|
||||
import { FormGroup } from 'components/FormGroup'
|
||||
@@ -7,10 +8,11 @@ import { InputDateTime } from 'components/InputDateTime'
|
||||
import { vendingMinterList } from 'config/minter'
|
||||
import type { TokenInfo } from 'config/token'
|
||||
import { stars, tokensList } from 'config/token'
|
||||
import { useGlobalSettings } from 'contexts/globalSettings'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { resolveAddress } from 'utils/resolveAddress'
|
||||
import { useWallet } from 'utils/wallet'
|
||||
|
||||
import { useWallet } from '../../../contexts/wallet'
|
||||
import { NumberInput, TextInput } from '../../forms/FormInput'
|
||||
import type { UploadMethod } from './UploadDetails'
|
||||
|
||||
@@ -20,9 +22,11 @@ interface MintingDetailsProps {
|
||||
uploadMethod: UploadMethod
|
||||
minimumMintPrice: number
|
||||
mintingTokenFromFactory?: TokenInfo
|
||||
importedMintingDetails?: MintingDetailsDataProps
|
||||
isPresale: boolean
|
||||
whitelistStartDate?: string
|
||||
}
|
||||
|
||||
export type TokenType = 'fungible' | 'non-fungible'
|
||||
export interface MintingDetailsDataProps {
|
||||
numTokens: number
|
||||
unitPrice: string
|
||||
@@ -30,7 +34,6 @@ export interface MintingDetailsDataProps {
|
||||
startTime: string
|
||||
paymentAddress?: string
|
||||
selectedMintToken?: TokenInfo
|
||||
selectesTokenType?: TokenType
|
||||
}
|
||||
|
||||
export const MintingDetails = ({
|
||||
@@ -39,9 +42,12 @@ export const MintingDetails = ({
|
||||
uploadMethod,
|
||||
minimumMintPrice,
|
||||
mintingTokenFromFactory,
|
||||
importedMintingDetails,
|
||||
isPresale,
|
||||
whitelistStartDate,
|
||||
}: MintingDetailsProps) => {
|
||||
const wallet = useWallet()
|
||||
|
||||
const { timezone } = useGlobalSettings()
|
||||
const [timestamp, setTimestamp] = useState<Date | undefined>()
|
||||
const [selectedMintToken, setSelectedMintToken] = useState<TokenInfo | undefined>(stars)
|
||||
|
||||
@@ -103,6 +109,7 @@ export const MintingDetails = ({
|
||||
paymentAddress: paymentAddressState.value.trim(),
|
||||
selectedMintToken,
|
||||
}
|
||||
console.log('Timestamp:', timestamp?.getTime())
|
||||
onChange(data)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [
|
||||
@@ -115,6 +122,24 @@ export const MintingDetails = ({
|
||||
selectedMintToken,
|
||||
])
|
||||
|
||||
useEffect(() => {
|
||||
if (importedMintingDetails) {
|
||||
numberOfTokensState.onChange(importedMintingDetails.numTokens)
|
||||
unitPriceState.onChange(Number(importedMintingDetails.unitPrice) / 1_000_000)
|
||||
perAddressLimitState.onChange(importedMintingDetails.perAddressLimit)
|
||||
setTimestamp(new Date(Number(importedMintingDetails.startTime) / 1_000_000))
|
||||
paymentAddressState.onChange(importedMintingDetails.paymentAddress ? importedMintingDetails.paymentAddress : '')
|
||||
setSelectedMintToken(tokensList.find((token) => token.id === importedMintingDetails.selectedMintToken?.id))
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [importedMintingDetails])
|
||||
|
||||
useEffect(() => {
|
||||
if (isPresale) {
|
||||
setTimestamp(whitelistStartDate ? new Date(Number(whitelistStartDate) / 1_000_000) : undefined)
|
||||
}
|
||||
}, [whitelistStartDate, isPresale])
|
||||
|
||||
return (
|
||||
<div>
|
||||
<FormGroup subtitle="Information about your minting settings" title="Minting Details">
|
||||
@@ -124,14 +149,18 @@ export const MintingDetails = ({
|
||||
isRequired
|
||||
value={uploadMethod === 'new' ? numberOfTokens : numberOfTokensState.value}
|
||||
/>
|
||||
<div className="flex flex-row items-center">
|
||||
<div className="flex flex-row items-end">
|
||||
<NumberInput {...unitPriceState} isRequired />
|
||||
<select
|
||||
className="py-[9px] px-4 mt-14 ml-2 placeholder:text-white/50 bg-white/10 rounded border-2 border-white/20 focus:ring focus:ring-plumbus-20"
|
||||
className="py-[9px] px-4 ml-2 placeholder:text-white/50 bg-white/10 rounded border-2 border-white/20 focus:ring focus:ring-plumbus-20"
|
||||
onChange={(e) => setSelectedMintToken(tokensList.find((t) => t.displayName === e.target.value))}
|
||||
value={selectedMintToken?.displayName}
|
||||
>
|
||||
{vendingMinterList
|
||||
.filter((minter) => minter.factoryAddress !== undefined && minter.updatable === false)
|
||||
.filter(
|
||||
(minter) =>
|
||||
minter.factoryAddress !== undefined && minter.updatable === false && minter.featured === false,
|
||||
)
|
||||
.map((minter) => (
|
||||
<option key={minter.id} className="bg-black" value={minter.supportedToken.displayName}>
|
||||
{minter.supportedToken.displayName}
|
||||
@@ -141,8 +170,34 @@ export const MintingDetails = ({
|
||||
</div>
|
||||
|
||||
<NumberInput {...perAddressLimitState} isRequired />
|
||||
<FormControl htmlId="timestamp" isRequired subtitle="Minting start time (local)" title="Start Time">
|
||||
<InputDateTime minDate={new Date()} onChange={(date) => setTimestamp(date)} value={timestamp} />
|
||||
<FormControl
|
||||
htmlId="timestamp"
|
||||
isRequired
|
||||
subtitle={`Minting start time ${isPresale ? '(is dictated by whitelist start time)' : ''} ${
|
||||
timezone === 'Local' ? '(local)' : '(UTC)'
|
||||
}`}
|
||||
title="Start Time"
|
||||
>
|
||||
<InputDateTime
|
||||
disabled={isPresale}
|
||||
minDate={
|
||||
timezone === 'Local' ? new Date() : new Date(Date.now() + new Date().getTimezoneOffset() * 60 * 1000)
|
||||
}
|
||||
onChange={(date) =>
|
||||
date
|
||||
? setTimestamp(
|
||||
timezone === 'Local' ? date : new Date(date.getTime() - new Date().getTimezoneOffset() * 60 * 1000),
|
||||
)
|
||||
: setTimestamp(undefined)
|
||||
}
|
||||
value={
|
||||
timezone === 'Local'
|
||||
? timestamp
|
||||
: timestamp
|
||||
? new Date(timestamp.getTime() + new Date().getTimezoneOffset() * 60 * 1000)
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormGroup>
|
||||
<TextInput className="p-4 mt-5" {...paymentAddressState} />
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
import { Conditional } from 'components/Conditional'
|
||||
import { FormGroup } from 'components/FormGroup'
|
||||
import { useInputState } from 'components/forms/FormInput.hooks'
|
||||
import { useWallet } from 'contexts/wallet'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { useWallet } from 'utils/wallet'
|
||||
|
||||
import { resolveAddress } from '../../../utils/resolveAddress'
|
||||
import { NumberInput, TextInput } from '../../forms/FormInput'
|
||||
|
||||
interface RoyaltyDetailsProps {
|
||||
onChange: (data: RoyaltyDetailsDataProps) => void
|
||||
importedRoyaltyDetails?: RoyaltyDetailsDataProps
|
||||
}
|
||||
|
||||
export interface RoyaltyDetailsDataProps {
|
||||
@@ -19,7 +20,7 @@ export interface RoyaltyDetailsDataProps {
|
||||
|
||||
type RoyaltyState = 'none' | 'new'
|
||||
|
||||
export const RoyaltyDetails = ({ onChange }: RoyaltyDetailsProps) => {
|
||||
export const RoyaltyDetails = ({ onChange, importedRoyaltyDetails }: RoyaltyDetailsProps) => {
|
||||
const wallet = useWallet()
|
||||
const [royaltyState, setRoyaltyState] = useState<RoyaltyState>('none')
|
||||
|
||||
@@ -60,6 +61,15 @@ export const RoyaltyDetails = ({ onChange }: RoyaltyDetailsProps) => {
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [royaltyState, royaltyPaymentAddressState.value, royaltyShareState.value])
|
||||
|
||||
useEffect(() => {
|
||||
if (importedRoyaltyDetails) {
|
||||
setRoyaltyState(importedRoyaltyDetails.royaltyType)
|
||||
royaltyPaymentAddressState.onChange(importedRoyaltyDetails.paymentAddress)
|
||||
royaltyShareState.onChange(importedRoyaltyDetails.share.toString())
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [importedRoyaltyDetails])
|
||||
|
||||
return (
|
||||
<div className="py-3 px-8 rounded border-2 border-white/20">
|
||||
<div className="flex justify-center">
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
/* eslint-disable eslint-comments/disable-enable-pair */
|
||||
/* eslint-disable array-callback-return */
|
||||
/* eslint-disable no-nested-ternary */
|
||||
/* eslint-disable no-misleading-character-class */
|
||||
/* eslint-disable no-control-regex */
|
||||
@@ -19,6 +20,9 @@ import type { ChangeEvent } from 'react'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { toast } from 'react-hot-toast'
|
||||
import type { UploadServiceType } from 'services/upload'
|
||||
import { NFT_STORAGE_DEFAULT_API_KEY } from 'utils/constants'
|
||||
import type { AssetType } from 'utils/getAssetType'
|
||||
import { getAssetType } from 'utils/getAssetType'
|
||||
import { uid } from 'utils/random'
|
||||
import { naturalCompare } from 'utils/sort'
|
||||
|
||||
@@ -31,11 +35,14 @@ interface UploadDetailsProps {
|
||||
onChange: (value: UploadDetailsDataProps) => void
|
||||
minterType: MinterType
|
||||
baseMinterAcquisitionMethod?: BaseMinterAcquisitionMethod
|
||||
importedUploadDetails?: UploadDetailsDataProps
|
||||
}
|
||||
|
||||
export interface UploadDetailsDataProps {
|
||||
assetFiles: File[]
|
||||
metadataFiles: File[]
|
||||
thumbnailFiles?: File[]
|
||||
thumbnailCompatibleAssetFileNames?: string[]
|
||||
uploadService: UploadServiceType
|
||||
nftStorageApiKey?: string
|
||||
pinataApiKey?: string
|
||||
@@ -46,18 +53,27 @@ export interface UploadDetailsDataProps {
|
||||
baseMinterMetadataFile?: File
|
||||
}
|
||||
|
||||
export const UploadDetails = ({ onChange, minterType, baseMinterAcquisitionMethod }: UploadDetailsProps) => {
|
||||
export const UploadDetails = ({
|
||||
onChange,
|
||||
minterType,
|
||||
baseMinterAcquisitionMethod,
|
||||
importedUploadDetails,
|
||||
}: UploadDetailsProps) => {
|
||||
const [assetFilesArray, setAssetFilesArray] = useState<File[]>([])
|
||||
const [metadataFilesArray, setMetadataFilesArray] = useState<File[]>([])
|
||||
const [thumbnailCompatibleAssetFileNames, setThumbnailCompatibleAssetFileNames] = useState<string[]>([])
|
||||
const [thumbnailFilesArray, setThumbnailFilesArray] = useState<File[]>([])
|
||||
const [uploadMethod, setUploadMethod] = useState<UploadMethod>('new')
|
||||
const [uploadService, setUploadService] = useState<UploadServiceType>('nft-storage')
|
||||
const [metadataFileArrayIndex, setMetadataFileArrayIndex] = useState(0)
|
||||
const [refreshMetadata, setRefreshMetadata] = useState(false)
|
||||
const [useDefaultApiKey, setUseDefaultApiKey] = useState(false)
|
||||
|
||||
const [baseMinterMetadataFile, setBaseMinterMetadataFile] = useState<File | undefined>()
|
||||
|
||||
const assetFilesRef = useRef<HTMLInputElement | null>(null)
|
||||
const metadataFilesRef = useRef<HTMLInputElement | null>(null)
|
||||
const thumbnailFilesRef = useRef<HTMLInputElement | null>(null)
|
||||
|
||||
const nftStorageApiKeyState = useInputState({
|
||||
id: 'nft-storage-api-key',
|
||||
@@ -100,15 +116,27 @@ export const UploadDetails = ({ onChange, minterType, baseMinterAcquisitionMetho
|
||||
const selectAssets = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
setAssetFilesArray([])
|
||||
setMetadataFilesArray([])
|
||||
setThumbnailFilesArray([])
|
||||
setThumbnailCompatibleAssetFileNames([])
|
||||
if (event.target.files === null) return
|
||||
if (minterType === 'vending' || (minterType === 'base' && event.target.files.length > 1)) {
|
||||
const thumbnailCompatibleAssetTypes: AssetType[] = ['video', 'audio', 'html', 'document']
|
||||
const thumbnailCompatibleFileNamesList: string[] = []
|
||||
if (minterType === 'vending') {
|
||||
//sort the files
|
||||
const sortedFiles = Array.from(event.target.files).sort((a, b) => naturalCompare(a.name, b.name))
|
||||
//check if the sorted file names are in numerical order
|
||||
const sortedFileNames = sortedFiles.map((file) => file.name.split('.')[0])
|
||||
sortedFiles.map((file) => {
|
||||
if (thumbnailCompatibleAssetTypes.includes(getAssetType(file.name))) {
|
||||
thumbnailCompatibleFileNamesList.push(file.name.split('.')[0])
|
||||
}
|
||||
})
|
||||
setThumbnailCompatibleAssetFileNames(thumbnailCompatibleFileNamesList)
|
||||
console.log('Thumbnail Compatible Files: ', thumbnailCompatibleFileNamesList)
|
||||
for (let i = 0; i < sortedFileNames.length; i++) {
|
||||
if (isNaN(Number(sortedFileNames[i])) || parseInt(sortedFileNames[i]) !== i + 1) {
|
||||
toast.error('The file names should be in numerical order starting from 1.')
|
||||
setThumbnailCompatibleAssetFileNames([])
|
||||
addLogItem({
|
||||
id: uid(),
|
||||
message: 'The file names should be in numerical order starting from 1.',
|
||||
@@ -120,7 +148,46 @@ export const UploadDetails = ({ onChange, minterType, baseMinterAcquisitionMetho
|
||||
return
|
||||
}
|
||||
}
|
||||
} else if (minterType === 'base' && event.target.files.length > 1) {
|
||||
//sort the files
|
||||
const sortedFiles = Array.from(event.target.files).sort((a, b) => naturalCompare(a.name, b.name))
|
||||
//check if the sorted file names are in numerical order
|
||||
const sortedFileNames = sortedFiles.map((file) => file.name.split('.')[0])
|
||||
sortedFiles.map((file) => {
|
||||
if (thumbnailCompatibleAssetTypes.includes(getAssetType(file.name))) {
|
||||
thumbnailCompatibleFileNamesList.push(file.name.split('.')[0])
|
||||
}
|
||||
})
|
||||
setThumbnailCompatibleAssetFileNames(thumbnailCompatibleFileNamesList)
|
||||
console.log('Thumbnail Compatible Files: ', thumbnailCompatibleFileNamesList)
|
||||
|
||||
for (let i = 0; i < sortedFileNames.length - 1; i++) {
|
||||
if (
|
||||
isNaN(Number(sortedFileNames[i])) ||
|
||||
isNaN(Number(sortedFileNames[i + 1])) ||
|
||||
parseInt(sortedFileNames[i]) !== parseInt(sortedFileNames[i + 1]) - 1
|
||||
) {
|
||||
toast.error('The file names should be in numerical order.')
|
||||
setThumbnailCompatibleAssetFileNames([])
|
||||
addLogItem({
|
||||
id: uid(),
|
||||
message: 'The file names should be in numerical order.',
|
||||
type: 'Error',
|
||||
timestamp: new Date(),
|
||||
})
|
||||
//clear the input
|
||||
event.target.value = ''
|
||||
return
|
||||
}
|
||||
}
|
||||
} else if (minterType === 'base' && event.target.files.length === 1) {
|
||||
if (thumbnailCompatibleAssetTypes.includes(getAssetType(event.target.files[0].name))) {
|
||||
thumbnailCompatibleFileNamesList.push(event.target.files[0].name.split('.')[0])
|
||||
}
|
||||
setThumbnailCompatibleAssetFileNames(thumbnailCompatibleFileNamesList)
|
||||
console.log('Thumbnail Compatible Files: ', thumbnailCompatibleFileNamesList)
|
||||
}
|
||||
|
||||
let loadedFileCount = 0
|
||||
const files: File[] = []
|
||||
let reader: FileReader
|
||||
@@ -129,7 +196,9 @@ export const UploadDetails = ({ onChange, minterType, baseMinterAcquisitionMetho
|
||||
reader.onload = (e) => {
|
||||
if (!e.target?.result) return toast.error('Error parsing file.')
|
||||
if (!event.target.files) return toast.error('No files selected.')
|
||||
const assetFile = new File([e.target.result], event.target.files[i].name, { type: 'image/jpg' })
|
||||
const assetFile = new File([e.target.result], event.target.files[i].name.replaceAll('#', ''), {
|
||||
type: 'image/jpg',
|
||||
})
|
||||
files.push(assetFile)
|
||||
}
|
||||
reader.readAsArrayBuffer(event.target.files[i])
|
||||
@@ -153,7 +222,23 @@ export const UploadDetails = ({ onChange, minterType, baseMinterAcquisitionMetho
|
||||
event.target.value = ''
|
||||
return toast.error('The number of metadata files should be equal to the number of asset files.')
|
||||
}
|
||||
if (minterType === 'vending' || (minterType === 'base' && assetFilesArray.length > 1)) {
|
||||
// compare the first file name for asset and metadata files
|
||||
if (
|
||||
minterType === 'base' &&
|
||||
assetFilesArray.length > 1 &&
|
||||
event.target.files[0].name.split('.')[0] !== assetFilesArray[0].name.split('.')[0]
|
||||
) {
|
||||
event.target.value = ''
|
||||
toast.error('The metadata file names should match the asset file names.')
|
||||
addLogItem({
|
||||
id: uid(),
|
||||
message: 'The metadata file names should match the asset file names.',
|
||||
type: 'Error',
|
||||
timestamp: new Date(),
|
||||
})
|
||||
return
|
||||
}
|
||||
if (minterType === 'vending') {
|
||||
//sort the files
|
||||
const sortedFiles = Array.from(event.target.files).sort((a, b) => naturalCompare(a.name, b.name))
|
||||
//check if the sorted file names are in numerical order
|
||||
@@ -171,6 +256,28 @@ export const UploadDetails = ({ onChange, minterType, baseMinterAcquisitionMetho
|
||||
return
|
||||
}
|
||||
}
|
||||
} else if (minterType === 'base' && assetFilesArray.length > 1) {
|
||||
//sort the files
|
||||
const sortedFiles = Array.from(event.target.files).sort((a, b) => naturalCompare(a.name, b.name))
|
||||
//check if the sorted file names are in numerical order
|
||||
const sortedFileNames = sortedFiles.map((file) => file.name.split('.')[0])
|
||||
for (let i = 0; i < sortedFileNames.length - 1; i++) {
|
||||
if (
|
||||
isNaN(Number(sortedFileNames[i])) ||
|
||||
isNaN(Number(sortedFileNames[i + 1])) ||
|
||||
parseInt(sortedFileNames[i]) !== parseInt(sortedFileNames[i + 1]) - 1
|
||||
) {
|
||||
toast.error('The file names should be in numerical order.')
|
||||
addLogItem({
|
||||
id: uid(),
|
||||
message: 'The file names should be in numerical order.',
|
||||
type: 'Error',
|
||||
timestamp: new Date(),
|
||||
})
|
||||
event.target.value = ''
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
let loadedFileCount = 0
|
||||
const files: File[] = []
|
||||
@@ -180,7 +287,9 @@ export const UploadDetails = ({ onChange, minterType, baseMinterAcquisitionMetho
|
||||
reader.onload = async (e) => {
|
||||
if (!e.target?.result) return toast.error('Error parsing file.')
|
||||
if (!event.target.files) return toast.error('No files selected.')
|
||||
const metadataFile = new File([e.target.result], event.target.files[i].name, { type: 'application/json' })
|
||||
const metadataFile = new File([e.target.result], event.target.files[i].name.replaceAll('#', ''), {
|
||||
type: 'application/json',
|
||||
})
|
||||
files.push(metadataFile)
|
||||
try {
|
||||
const parsedMetadata = JSON.parse(await metadataFile.text())
|
||||
@@ -224,11 +333,61 @@ export const UploadDetails = ({ onChange, minterType, baseMinterAcquisitionMetho
|
||||
const regex =
|
||||
/[\0-\x1F\x7F-\x9F\xAD\u0378\u0379\u037F-\u0383\u038B\u038D\u03A2\u0528-\u0530\u0557\u0558\u0560\u0588\u058B-\u058E\u0590\u05C8-\u05CF\u05EB-\u05EF\u05F5-\u0605\u061C\u061D\u06DD\u070E\u070F\u074B\u074C\u07B2-\u07BF\u07FB-\u07FF\u082E\u082F\u083F\u085C\u085D\u085F-\u089F\u08A1\u08AD-\u08E3\u08FF\u0978\u0980\u0984\u098D\u098E\u0991\u0992\u09A9\u09B1\u09B3-\u09B5\u09BA\u09BB\u09C5\u09C6\u09C9\u09CA\u09CF-\u09D6\u09D8-\u09DB\u09DE\u09E4\u09E5\u09FC-\u0A00\u0A04\u0A0B-\u0A0E\u0A11\u0A12\u0A29\u0A31\u0A34\u0A37\u0A3A\u0A3B\u0A3D\u0A43-\u0A46\u0A49\u0A4A\u0A4E-\u0A50\u0A52-\u0A58\u0A5D\u0A5F-\u0A65\u0A76-\u0A80\u0A84\u0A8E\u0A92\u0AA9\u0AB1\u0AB4\u0ABA\u0ABB\u0AC6\u0ACA\u0ACE\u0ACF\u0AD1-\u0ADF\u0AE4\u0AE5\u0AF2-\u0B00\u0B04\u0B0D\u0B0E\u0B11\u0B12\u0B29\u0B31\u0B34\u0B3A\u0B3B\u0B45\u0B46\u0B49\u0B4A\u0B4E-\u0B55\u0B58-\u0B5B\u0B5E\u0B64\u0B65\u0B78-\u0B81\u0B84\u0B8B-\u0B8D\u0B91\u0B96-\u0B98\u0B9B\u0B9D\u0BA0-\u0BA2\u0BA5-\u0BA7\u0BAB-\u0BAD\u0BBA-\u0BBD\u0BC3-\u0BC5\u0BC9\u0BCE\u0BCF\u0BD1-\u0BD6\u0BD8-\u0BE5\u0BFB-\u0C00\u0C04\u0C0D\u0C11\u0C29\u0C34\u0C3A-\u0C3C\u0C45\u0C49\u0C4E-\u0C54\u0C57\u0C5A-\u0C5F\u0C64\u0C65\u0C70-\u0C77\u0C80\u0C81\u0C84\u0C8D\u0C91\u0CA9\u0CB4\u0CBA\u0CBB\u0CC5\u0CC9\u0CCE-\u0CD4\u0CD7-\u0CDD\u0CDF\u0CE4\u0CE5\u0CF0\u0CF3-\u0D01\u0D04\u0D0D\u0D11\u0D3B\u0D3C\u0D45\u0D49\u0D4F-\u0D56\u0D58-\u0D5F\u0D64\u0D65\u0D76-\u0D78\u0D80\u0D81\u0D84\u0D97-\u0D99\u0DB2\u0DBC\u0DBE\u0DBF\u0DC7-\u0DC9\u0DCB-\u0DCE\u0DD5\u0DD7\u0DE0-\u0DF1\u0DF5-\u0E00\u0E3B-\u0E3E\u0E5C-\u0E80\u0E83\u0E85\u0E86\u0E89\u0E8B\u0E8C\u0E8E-\u0E93\u0E98\u0EA0\u0EA4\u0EA6\u0EA8\u0EA9\u0EAC\u0EBA\u0EBE\u0EBF\u0EC5\u0EC7\u0ECE\u0ECF\u0EDA\u0EDB\u0EE0-\u0EFF\u0F48\u0F6D-\u0F70\u0F98\u0FBD\u0FCD\u0FDB-\u0FFF\u10C6\u10C8-\u10CC\u10CE\u10CF\u1249\u124E\u124F\u1257\u1259\u125E\u125F\u1289\u128E\u128F\u12B1\u12B6\u12B7\u12BF\u12C1\u12C6\u12C7\u12D7\u1311\u1316\u1317\u135B\u135C\u137D-\u137F\u139A-\u139F\u13F5-\u13FF\u169D-\u169F\u16F1-\u16FF\u170D\u1715-\u171F\u1737-\u173F\u1754-\u175F\u176D\u1771\u1774-\u177F\u17DE\u17DF\u17EA-\u17EF\u17FA-\u17FF\u180F\u181A-\u181F\u1878-\u187F\u18AB-\u18AF\u18F6-\u18FF\u191D-\u191F\u192C-\u192F\u193C-\u193F\u1941-\u1943\u196E\u196F\u1975-\u197F\u19AC-\u19AF\u19CA-\u19CF\u19DB-\u19DD\u1A1C\u1A1D\u1A5F\u1A7D\u1A7E\u1A8A-\u1A8F\u1A9A-\u1A9F\u1AAE-\u1AFF\u1B4C-\u1B4F\u1B7D-\u1B7F\u1BF4-\u1BFB\u1C38-\u1C3A\u1C4A-\u1C4C\u1C80-\u1CBF\u1CC8-\u1CCF\u1CF7-\u1CFF\u1DE7-\u1DFB\u1F16\u1F17\u1F1E\u1F1F\u1F46\u1F47\u1F4E\u1F4F\u1F58\u1F5A\u1F5C\u1F5E\u1F7E\u1F7F\u1FB5\u1FC5\u1FD4\u1FD5\u1FDC\u1FF0\u1FF1\u1FF5\u1FFF\u200B-\u200F\u2020-\u202E\u2060-\u206F\u2072\u2073\u208F\u209D-\u209F\u20BB-\u20CF\u20F1-\u20FF\u218A-\u218F\u23F4-\u23FF\u2427-\u243F\u244B-\u245F\u2700\u2B4D-\u2B4F\u2B5A-\u2BFF\u2C2F\u2C5F\u2CF4-\u2CF8\u2D26\u2D28-\u2D2C\u2D2E\u2D2F\u2D68-\u2D6E\u2D71-\u2D7E\u2D97-\u2D9F\u2DA7\u2DAF\u2DB7\u2DBF\u2DC7\u2DCF\u2DD7\u2DDF\u2E3C-\u2E7F\u2E9A\u2EF4-\u2EFF\u2FD6-\u2FEF\u2FFC-\u2FFF\u3040\u3097\u3098\u3100-\u3104\u312E-\u3130\u318F\u31BB-\u31BF\u31E4-\u31EF\u321F\u32FF\u4DB6-\u4DBF\u9FCD-\u9FFF\uA48D-\uA48F\uA4C7-\uA4CF\uA62C-\uA63F\uA698-\uA69E\uA6F8-\uA6FF\uA78F\uA794-\uA79F\uA7AB-\uA7F7\uA82C-\uA82F\uA83A-\uA83F\uA878-\uA87F\uA8C5-\uA8CD\uA8DA-\uA8DF\uA8FC-\uA8FF\uA954-\uA95E\uA97D-\uA97F\uA9CE\uA9DA-\uA9DD\uA9E0-\uA9FF\uAA37-\uAA3F\uAA4E\uAA4F\uAA5A\uAA5B\uAA7C-\uAA7F\uAAC3-\uAADA\uAAF7-\uAB00\uAB07\uAB08\uAB0F\uAB10\uAB17-\uAB1F\uAB27\uAB2F-\uABBF\uABEE\uABEF\uABFA-\uABFF\uD7A4-\uD7AF\uD7C7-\uD7CA\uD7FC-\uF8FF\uFA6E\uFA6F\uFADA-\uFAFF\uFB07-\uFB12\uFB18-\uFB1C\uFB37\uFB3D\uFB3F\uFB42\uFB45\uFBC2-\uFBD2\uFD40-\uFD4F\uFD90\uFD91\uFDC8-\uFDEF\uFDFE\uFDFF\uFE1A-\uFE1F\uFE27-\uFE2F\uFE53\uFE67\uFE6C-\uFE6F\uFE75\uFEFD-\uFF00\uFFBF-\uFFC1\uFFC8\uFFC9\uFFD0\uFFD1\uFFD8\uFFD9\uFFDD-\uFFDF\uFFE7\uFFEF-\uFFFB\uFFFE\uFFFF]/g
|
||||
|
||||
const selectThumbnails = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
setThumbnailFilesArray([])
|
||||
if (event.target.files === null) return
|
||||
|
||||
// if (minterType === 'vending' || (minterType === 'base' && thumbnailCompatibleAssetFileNames.length > 1)) {
|
||||
const sortedFiles = Array.from(event.target.files).sort((a, b) => naturalCompare(a.name, b.name))
|
||||
const sortedFileNames = sortedFiles.map((file) => file.name.split('.')[0])
|
||||
// make sure the sorted file names match thumbnail compatible asset file names
|
||||
for (let i = 0; i < thumbnailCompatibleAssetFileNames.length; i++) {
|
||||
if (minterType === 'base' && assetFilesArray.length === 1) break
|
||||
if (sortedFileNames[i] !== thumbnailCompatibleAssetFileNames[i]) {
|
||||
toast.error('The thumbnail file names should match the thumbnail compatible asset file names.')
|
||||
addLogItem({
|
||||
id: uid(),
|
||||
message: 'The thumbnail file names should match the thumbnail compatible asset file names.',
|
||||
type: 'Error',
|
||||
timestamp: new Date(),
|
||||
})
|
||||
//clear the input
|
||||
event.target.value = ''
|
||||
return
|
||||
}
|
||||
}
|
||||
// }
|
||||
let loadedFileCount = 0
|
||||
const files: File[] = []
|
||||
let reader: FileReader
|
||||
for (let i = 0; i < event.target.files.length; i++) {
|
||||
reader = new FileReader()
|
||||
reader.onload = (e) => {
|
||||
if (!e.target?.result) return toast.error('Error parsing file.')
|
||||
if (!event.target.files) return toast.error('No files selected.')
|
||||
const thumbnailFile = new File([e.target.result], event.target.files[i].name.replaceAll('#', ''), {
|
||||
type: 'image/jpg',
|
||||
})
|
||||
files.push(thumbnailFile)
|
||||
}
|
||||
reader.readAsArrayBuffer(event.target.files[i])
|
||||
reader.onloadend = () => {
|
||||
if (!event.target.files) return toast.error('No file selected.')
|
||||
loadedFileCount++
|
||||
if (loadedFileCount === event.target.files.length) {
|
||||
setThumbnailFilesArray(files.sort((a, b) => naturalCompare(a.name, b.name)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
try {
|
||||
const data: UploadDetailsDataProps = {
|
||||
assetFiles: assetFilesArray,
|
||||
metadataFiles: metadataFilesArray,
|
||||
thumbnailFiles: thumbnailFilesArray,
|
||||
thumbnailCompatibleAssetFileNames,
|
||||
uploadService,
|
||||
nftStorageApiKey: nftStorageApiKeyState.value,
|
||||
pinataApiKey: pinataApiKeyState.value,
|
||||
@@ -258,6 +417,8 @@ export const UploadDetails = ({ onChange, minterType, baseMinterAcquisitionMetho
|
||||
}, [
|
||||
assetFilesArray,
|
||||
metadataFilesArray,
|
||||
thumbnailFilesArray,
|
||||
thumbnailCompatibleAssetFileNames,
|
||||
uploadService,
|
||||
nftStorageApiKeyState.value,
|
||||
pinataApiKeyState.value,
|
||||
@@ -274,10 +435,42 @@ export const UploadDetails = ({ onChange, minterType, baseMinterAcquisitionMetho
|
||||
setMetadataFilesArray([])
|
||||
if (assetFilesRef.current) assetFilesRef.current.value = ''
|
||||
setAssetFilesArray([])
|
||||
baseTokenUriState.onChange('')
|
||||
coverImageUrlState.onChange('')
|
||||
if (thumbnailFilesRef.current) thumbnailFilesRef.current.value = ''
|
||||
setThumbnailFilesArray([])
|
||||
setThumbnailCompatibleAssetFileNames([])
|
||||
if (!importedUploadDetails || minterType === 'base') {
|
||||
baseTokenUriState.onChange('')
|
||||
coverImageUrlState.onChange('')
|
||||
}
|
||||
}, [uploadMethod, minterType, baseMinterAcquisitionMethod])
|
||||
|
||||
useEffect(() => {
|
||||
if (importedUploadDetails) {
|
||||
if (importedUploadDetails.uploadMethod === 'new') {
|
||||
setUploadMethod('new')
|
||||
setUploadService(importedUploadDetails.uploadService)
|
||||
nftStorageApiKeyState.onChange(importedUploadDetails.nftStorageApiKey || '')
|
||||
pinataApiKeyState.onChange(importedUploadDetails.pinataApiKey || '')
|
||||
pinataSecretKeyState.onChange(importedUploadDetails.pinataSecretKey || '')
|
||||
baseTokenUriState.onChange(importedUploadDetails.baseTokenURI || '')
|
||||
coverImageUrlState.onChange(importedUploadDetails.imageUrl || '')
|
||||
} else if (importedUploadDetails.uploadMethod === 'existing') {
|
||||
setUploadMethod('existing')
|
||||
baseTokenUriState.onChange(importedUploadDetails.baseTokenURI || '')
|
||||
coverImageUrlState.onChange(importedUploadDetails.imageUrl || '')
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [importedUploadDetails])
|
||||
|
||||
useEffect(() => {
|
||||
if (useDefaultApiKey) {
|
||||
nftStorageApiKeyState.onChange(NFT_STORAGE_DEFAULT_API_KEY || '')
|
||||
} else {
|
||||
nftStorageApiKeyState.onChange('')
|
||||
}
|
||||
}, [useDefaultApiKey])
|
||||
|
||||
return (
|
||||
<div className="justify-items-start mb-3 rounded border-2 border-white/20 flex-column">
|
||||
<div className="flex justify-center">
|
||||
@@ -433,7 +626,22 @@ export const UploadDetails = ({ onChange, minterType, baseMinterAcquisitionMetho
|
||||
|
||||
<div className="flex w-full">
|
||||
<Conditional test={uploadService === 'nft-storage'}>
|
||||
<TextInput {...nftStorageApiKeyState} className="w-full" />
|
||||
<div className="flex-col w-full">
|
||||
<TextInput {...nftStorageApiKeyState} className="w-full" disabled={useDefaultApiKey} />
|
||||
<div className="flex-row mt-2 w-full form-control">
|
||||
<label className="cursor-pointer label">
|
||||
<span className="mr-2 font-bold">Use Default API Key</span>
|
||||
<input
|
||||
checked={useDefaultApiKey}
|
||||
className={`${useDefaultApiKey ? `bg-stargaze` : `bg-gray-600`} checkbox`}
|
||||
onClick={() => {
|
||||
setUseDefaultApiKey(!useDefaultApiKey)
|
||||
}}
|
||||
type="checkbox"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</Conditional>
|
||||
<Conditional test={uploadService === 'pinata'}>
|
||||
<TextInput {...pinataApiKeyState} className="w-full" />
|
||||
@@ -472,7 +680,7 @@ export const UploadDetails = ({ onChange, minterType, baseMinterAcquisitionMetho
|
||||
)}
|
||||
>
|
||||
<input
|
||||
accept="image/*, audio/*, video/*, .html"
|
||||
accept="image/*, audio/*, video/*, .html, .pdf"
|
||||
className={clsx(
|
||||
'file:py-2 file:px-4 file:mr-4 file:bg-plumbus-light file:rounded file:border-0 cursor-pointer',
|
||||
'before:absolute before:inset-0 before:hover:bg-white/5 before:transition',
|
||||
@@ -519,6 +727,38 @@ export const UploadDetails = ({ onChange, minterType, baseMinterAcquisitionMetho
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{thumbnailCompatibleAssetFileNames.length > 0 && (
|
||||
<div>
|
||||
<label
|
||||
className="block mt-5 mr-1 mb-1 ml-8 w-full font-bold text-white dark:text-gray-300"
|
||||
htmlFor="thumbnailFiles"
|
||||
>
|
||||
{thumbnailCompatibleAssetFileNames.length > 1
|
||||
? 'Thumbnail Selection for Compatible Assets (optional)'
|
||||
: 'Thumbnail Selection (optional)'}
|
||||
</label>
|
||||
<div
|
||||
className={clsx(
|
||||
'flex relative justify-center items-center mx-8 mt-2 space-y-4 w-full h-32',
|
||||
'rounded border-2 border-white/20 border-dashed',
|
||||
)}
|
||||
>
|
||||
<input
|
||||
accept="image/*"
|
||||
className={clsx(
|
||||
'file:py-2 file:px-4 file:mr-4 file:bg-plumbus-light file:rounded file:border-0 cursor-pointer',
|
||||
'before:absolute before:inset-0 before:hover:bg-white/5 before:transition',
|
||||
)}
|
||||
id="thumbnailFiles"
|
||||
multiple
|
||||
onChange={selectThumbnails}
|
||||
ref={thumbnailFilesRef}
|
||||
type="file"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<Conditional test={assetFilesArray.length >= 1}>
|
||||
<MetadataModal
|
||||
assetFile={assetFilesArray[metadataFileArrayIndex]}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
/* eslint-disable eslint-comments/disable-enable-pair */
|
||||
/* eslint-disable @typescript-eslint/no-unnecessary-condition */
|
||||
/* eslint-disable no-nested-ternary */
|
||||
import { Button } from 'components/Button'
|
||||
import { FormControl } from 'components/FormControl'
|
||||
import { FormGroup } from 'components/FormGroup'
|
||||
import { AddressList } from 'components/forms/AddressList'
|
||||
@@ -9,8 +11,10 @@ import { InputDateTime } from 'components/InputDateTime'
|
||||
import type { WhitelistFlexMember } from 'components/WhitelistFlexUpload'
|
||||
import { WhitelistFlexUpload } from 'components/WhitelistFlexUpload'
|
||||
import type { TokenInfo } from 'config/token'
|
||||
import { useGlobalSettings } from 'contexts/globalSettings'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { isValidAddress } from 'utils/isValidAddress'
|
||||
import { useWallet } from 'utils/wallet'
|
||||
|
||||
import { Conditional } from '../../Conditional'
|
||||
import { AddressInput, NumberInput } from '../../forms/FormInput'
|
||||
@@ -20,6 +24,7 @@ import { WhitelistUpload } from '../../WhitelistUpload'
|
||||
interface WhitelistDetailsProps {
|
||||
onChange: (data: WhitelistDetailsDataProps) => void
|
||||
mintingTokenFromFactory?: TokenInfo
|
||||
importedWhitelistDetails?: WhitelistDetailsDataProps
|
||||
}
|
||||
|
||||
export interface WhitelistDetailsDataProps {
|
||||
@@ -38,15 +43,23 @@ export interface WhitelistDetailsDataProps {
|
||||
|
||||
type WhitelistState = 'none' | 'existing' | 'new'
|
||||
|
||||
type WhitelistType = 'standard' | 'flex'
|
||||
export type WhitelistType = 'standard' | 'flex' | 'merkletree'
|
||||
|
||||
export const WhitelistDetails = ({
|
||||
onChange,
|
||||
mintingTokenFromFactory,
|
||||
importedWhitelistDetails,
|
||||
}: WhitelistDetailsProps) => {
|
||||
const wallet = useWallet()
|
||||
const { timezone } = useGlobalSettings()
|
||||
|
||||
export const WhitelistDetails = ({ onChange, mintingTokenFromFactory }: WhitelistDetailsProps) => {
|
||||
const [whitelistState, setWhitelistState] = useState<WhitelistState>('none')
|
||||
const [whitelistType, setWhitelistType] = useState<WhitelistType>('standard')
|
||||
const [startDate, setStartDate] = useState<Date | undefined>(undefined)
|
||||
const [endDate, setEndDate] = useState<Date | undefined>(undefined)
|
||||
const [whitelistStandardArray, setWhitelistStandardArray] = useState<string[]>([])
|
||||
const [whitelistFlexArray, setWhitelistFlexArray] = useState<WhitelistFlexMember[]>([])
|
||||
const [whitelistMerkleTreeArray, setWhitelistMerkleTreeArray] = useState<string[]>([])
|
||||
const [adminsMutable, setAdminsMutable] = useState<boolean>(true)
|
||||
|
||||
const whitelistAddressState = useInputState({
|
||||
@@ -85,16 +98,42 @@ export const WhitelistDetails = ({ onChange, mintingTokenFromFactory }: Whitelis
|
||||
const addressListState = useAddressListState()
|
||||
|
||||
const whitelistFileOnChange = (data: string[]) => {
|
||||
setWhitelistStandardArray(data)
|
||||
if (whitelistType === 'standard') setWhitelistStandardArray(data)
|
||||
if (whitelistType === 'merkletree') setWhitelistMerkleTreeArray(data)
|
||||
}
|
||||
|
||||
const whitelistFlexFileOnChange = (whitelistData: WhitelistFlexMember[]) => {
|
||||
setWhitelistFlexArray(whitelistData)
|
||||
}
|
||||
|
||||
const downloadSampleWhitelistFlexFile = () => {
|
||||
const csvData =
|
||||
'address,mint_count\nstars153w5xhuqu3et29lgqk4dsynj6gjn96lr33wx4e,3\nstars1xkes5r2k8u3m3ayfpverlkcrq3k4jhdk8ws0uz,1\nstars1s8qx0zvz8yd6e4x0mqmqf7fr9vvfn622wtp3g3,2'
|
||||
const blob = new Blob([csvData], { type: 'text/csv' })
|
||||
const url = window.URL.createObjectURL(blob)
|
||||
const a = document.createElement('a')
|
||||
a.setAttribute('href', url)
|
||||
a.setAttribute('download', 'sample_whitelist_flex.csv')
|
||||
a.click()
|
||||
}
|
||||
|
||||
const downloadSampleWhitelistFile = () => {
|
||||
const txtData =
|
||||
'stars153w5xhuqu3et29lgqk4dsynj6gjn96lr33wx4e\nstars1xkes5r2k8u3m3ayfpverlkcrq3k4jhdk8ws0uz\nstars1s8qx0zvz8yd6e4x0mqmqf7fr9vvfn622wtp3g3'
|
||||
const blob = new Blob([txtData], { type: 'text/txt' })
|
||||
const url = window.URL.createObjectURL(blob)
|
||||
const a = document.createElement('a')
|
||||
a.setAttribute('href', url)
|
||||
a.setAttribute('download', 'sample_whitelist.txt')
|
||||
a.click()
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setWhitelistStandardArray([])
|
||||
setWhitelistFlexArray([])
|
||||
if (!importedWhitelistDetails) {
|
||||
setWhitelistStandardArray([])
|
||||
setWhitelistFlexArray([])
|
||||
setWhitelistMerkleTreeArray([])
|
||||
}
|
||||
}, [whitelistType])
|
||||
|
||||
useEffect(() => {
|
||||
@@ -107,7 +146,12 @@ export const WhitelistDetails = ({ onChange, mintingTokenFromFactory }: Whitelis
|
||||
.replace(/"/g, '')
|
||||
.replace(/'/g, '')
|
||||
.replace(/ /g, ''),
|
||||
members: whitelistType === 'standard' ? whitelistStandardArray : whitelistFlexArray,
|
||||
members:
|
||||
whitelistType === 'standard'
|
||||
? whitelistStandardArray
|
||||
: whitelistType === 'merkletree'
|
||||
? whitelistMerkleTreeArray
|
||||
: whitelistFlexArray,
|
||||
unitPrice: unitPriceState.value
|
||||
? (Number(unitPriceState.value) * 1_000_000).toString()
|
||||
: unitPriceState.value === 0
|
||||
@@ -137,11 +181,75 @@ export const WhitelistDetails = ({ onChange, mintingTokenFromFactory }: Whitelis
|
||||
endDate,
|
||||
whitelistStandardArray,
|
||||
whitelistFlexArray,
|
||||
whitelistMerkleTreeArray,
|
||||
whitelistState,
|
||||
whitelistType,
|
||||
addressListState.values,
|
||||
adminsMutable,
|
||||
])
|
||||
|
||||
// make the necessary changes with respect to imported whitelist details
|
||||
useEffect(() => {
|
||||
if (importedWhitelistDetails) {
|
||||
setWhitelistState(importedWhitelistDetails.whitelistState)
|
||||
setWhitelistType(importedWhitelistDetails.whitelistType)
|
||||
whitelistAddressState.onChange(
|
||||
importedWhitelistDetails.contractAddress ? importedWhitelistDetails.contractAddress : '',
|
||||
)
|
||||
unitPriceState.onChange(
|
||||
importedWhitelistDetails.unitPrice ? Number(importedWhitelistDetails.unitPrice) / 1000000 : 0,
|
||||
)
|
||||
memberLimitState.onChange(importedWhitelistDetails.memberLimit ? importedWhitelistDetails.memberLimit : 0)
|
||||
perAddressLimitState.onChange(
|
||||
importedWhitelistDetails.perAddressLimit ? importedWhitelistDetails.perAddressLimit : 0,
|
||||
)
|
||||
setStartDate(
|
||||
importedWhitelistDetails.startTime
|
||||
? new Date(Number(importedWhitelistDetails.startTime) / 1_000_000)
|
||||
: undefined,
|
||||
)
|
||||
setEndDate(
|
||||
importedWhitelistDetails.endTime ? new Date(Number(importedWhitelistDetails.endTime) / 1_000_000) : undefined,
|
||||
)
|
||||
setAdminsMutable(importedWhitelistDetails.adminsMutable ? importedWhitelistDetails.adminsMutable : true)
|
||||
importedWhitelistDetails.admins?.forEach((admin) => {
|
||||
addressListState.reset()
|
||||
addressListState.add({ address: admin })
|
||||
})
|
||||
if (importedWhitelistDetails.whitelistType === 'standard') {
|
||||
setWhitelistStandardArray([])
|
||||
importedWhitelistDetails.members?.forEach((member) => {
|
||||
setWhitelistStandardArray((standardArray) => [...standardArray, member as string])
|
||||
})
|
||||
} else if (importedWhitelistDetails.whitelistType === 'merkletree') {
|
||||
setWhitelistMerkleTreeArray([])
|
||||
// importedWhitelistDetails.members?.forEach((member) => {
|
||||
// setWhitelistMerkleTreeArray((merkleTreeArray) => [...merkleTreeArray, member as string])
|
||||
// })
|
||||
} else if (importedWhitelistDetails.whitelistType === 'flex') {
|
||||
setWhitelistFlexArray([])
|
||||
importedWhitelistDetails.members?.forEach((member) => {
|
||||
setWhitelistFlexArray((flexArray) => [
|
||||
...flexArray,
|
||||
{
|
||||
address: (member as WhitelistFlexMember).address,
|
||||
mint_count: (member as WhitelistFlexMember).mint_count,
|
||||
},
|
||||
])
|
||||
})
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [importedWhitelistDetails])
|
||||
|
||||
useEffect(() => {
|
||||
if (whitelistState === 'new' && wallet.address) {
|
||||
addressListState.reset()
|
||||
addressListState.add({ address: wallet.address })
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [whitelistState, wallet.address])
|
||||
|
||||
return (
|
||||
<div className="py-3 px-8 rounded border-2 border-white/20">
|
||||
<div className="flex justify-center">
|
||||
@@ -210,7 +318,7 @@ export const WhitelistDetails = ({ onChange, mintingTokenFromFactory }: Whitelis
|
||||
</Conditional>
|
||||
|
||||
<Conditional test={whitelistState === 'new'}>
|
||||
<div className="flex justify-between mb-5 ml-6 max-w-[300px] text-lg font-bold">
|
||||
<div className="flex justify-between mb-5 ml-6 max-w-[500px] text-lg font-bold">
|
||||
<div className="form-check form-check-inline">
|
||||
<input
|
||||
checked={whitelistType === 'standard'}
|
||||
@@ -221,7 +329,7 @@ export const WhitelistDetails = ({ onChange, mintingTokenFromFactory }: Whitelis
|
||||
setWhitelistType('standard')
|
||||
}}
|
||||
type="radio"
|
||||
value="nft-storage"
|
||||
value="standard"
|
||||
/>
|
||||
<label
|
||||
className="inline-block py-1 px-2 text-gray peer-checked:text-white hover:text-white peer-checked:bg-black hover:rounded-sm peer-checked:border-b-2 hover:border-b-2 peer-checked:border-plumbus hover:border-plumbus cursor-pointer form-check-label"
|
||||
@@ -250,29 +358,90 @@ export const WhitelistDetails = ({ onChange, mintingTokenFromFactory }: Whitelis
|
||||
Whitelist Flex
|
||||
</label>
|
||||
</div>
|
||||
<div className="form-check form-check-inline">
|
||||
<input
|
||||
checked={whitelistType === 'merkletree'}
|
||||
className="peer sr-only"
|
||||
id="inlineRadio9"
|
||||
name="inlineRadioOptions9"
|
||||
onClick={() => {
|
||||
setWhitelistType('merkletree')
|
||||
}}
|
||||
type="radio"
|
||||
value="merkletree"
|
||||
/>
|
||||
<label
|
||||
className="inline-block py-1 px-2 text-gray peer-checked:text-white hover:text-white peer-checked:bg-black hover:rounded-sm peer-checked:border-b-2 hover:border-b-2 peer-checked:border-plumbus hover:border-plumbus cursor-pointer form-check-label"
|
||||
htmlFor="inlineRadio9"
|
||||
>
|
||||
Whitelist Merkle Tree
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-2">
|
||||
<FormGroup subtitle="Information about your minting settings" title="Whitelist Minting Details">
|
||||
<NumberInput isRequired {...unitPriceState} />
|
||||
<NumberInput isRequired {...memberLimitState} />
|
||||
<Conditional test={whitelistType === 'standard'}>
|
||||
<Conditional test={whitelistType !== 'merkletree'}>
|
||||
<NumberInput isRequired {...memberLimitState} />
|
||||
</Conditional>
|
||||
<Conditional test={whitelistType === 'standard' || whitelistType === 'merkletree'}>
|
||||
<NumberInput isRequired {...perAddressLimitState} />
|
||||
</Conditional>
|
||||
<FormControl
|
||||
htmlId="start-date"
|
||||
isRequired
|
||||
subtitle="Start time for minting tokens to whitelisted addresses"
|
||||
title="Start Time"
|
||||
title={`Whitelist Start Time ${timezone === 'Local' ? '(local)' : '(UTC)'}`}
|
||||
>
|
||||
<InputDateTime minDate={new Date()} onChange={(date) => setStartDate(date)} value={startDate} />
|
||||
<InputDateTime
|
||||
minDate={
|
||||
timezone === 'Local' ? new Date() : new Date(Date.now() + new Date().getTimezoneOffset() * 60 * 1000)
|
||||
}
|
||||
onChange={(date) =>
|
||||
date
|
||||
? setStartDate(
|
||||
timezone === 'Local'
|
||||
? date
|
||||
: new Date(date.getTime() - new Date().getTimezoneOffset() * 60 * 1000),
|
||||
)
|
||||
: setStartDate(undefined)
|
||||
}
|
||||
value={
|
||||
timezone === 'Local'
|
||||
? startDate
|
||||
: startDate
|
||||
? new Date(startDate.getTime() + new Date().getTimezoneOffset() * 60 * 1000)
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormControl
|
||||
htmlId="end-date"
|
||||
isRequired
|
||||
subtitle="End time for minting tokens to whitelisted addresses"
|
||||
title="End Time"
|
||||
subtitle="Whitelist End Time dictates when public sales will start"
|
||||
title={`Whitelist End Time ${timezone === 'Local' ? '(local)' : '(UTC)'}`}
|
||||
>
|
||||
<InputDateTime minDate={new Date()} onChange={(date) => setEndDate(date)} value={endDate} />
|
||||
<InputDateTime
|
||||
minDate={
|
||||
timezone === 'Local' ? new Date() : new Date(Date.now() + new Date().getTimezoneOffset() * 60 * 1000)
|
||||
}
|
||||
onChange={(date) =>
|
||||
date
|
||||
? setEndDate(
|
||||
timezone === 'Local'
|
||||
? date
|
||||
: new Date(date.getTime() - new Date().getTimezoneOffset() * 60 * 1000),
|
||||
)
|
||||
: setEndDate(undefined)
|
||||
}
|
||||
value={
|
||||
timezone === 'Local'
|
||||
? endDate
|
||||
: endDate
|
||||
? new Date(endDate.getTime() + new Date().getTimezoneOffset() * 60 * 1000)
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormGroup>
|
||||
<div>
|
||||
@@ -290,7 +459,6 @@ export const WhitelistDetails = ({ onChange, mintingTokenFromFactory }: Whitelis
|
||||
<div className="my-4 ml-4">
|
||||
<AddressList
|
||||
entries={addressListState.entries}
|
||||
isRequired
|
||||
onAdd={addressListState.add}
|
||||
onChange={addressListState.update}
|
||||
onRemove={addressListState.remove}
|
||||
@@ -299,7 +467,17 @@ export const WhitelistDetails = ({ onChange, mintingTokenFromFactory }: Whitelis
|
||||
/>
|
||||
</div>
|
||||
<Conditional test={whitelistType === 'standard'}>
|
||||
<FormGroup subtitle="TXT file that contains the whitelisted addresses" title="Whitelist File">
|
||||
<FormGroup
|
||||
subtitle={
|
||||
<div>
|
||||
<span>TXT file that contains the whitelisted addresses</span>
|
||||
<Button className="mt-2 text-sm text-white" onClick={downloadSampleWhitelistFile}>
|
||||
Download Sample File
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
title="Whitelist File"
|
||||
>
|
||||
<WhitelistUpload onChange={whitelistFileOnChange} />
|
||||
</FormGroup>
|
||||
<Conditional test={whitelistStandardArray.length > 0}>
|
||||
@@ -308,7 +486,14 @@ export const WhitelistDetails = ({ onChange, mintingTokenFromFactory }: Whitelis
|
||||
</Conditional>
|
||||
<Conditional test={whitelistType === 'flex'}>
|
||||
<FormGroup
|
||||
subtitle="CSV file that contains the whitelisted addresses and their corresponding mint counts"
|
||||
subtitle={
|
||||
<div>
|
||||
<span>CSV file that contains the whitelisted addresses and corresponding mint counts</span>
|
||||
<Button className="mt-2 text-sm text-white" onClick={downloadSampleWhitelistFlexFile}>
|
||||
Download Sample File
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
title="Whitelist File"
|
||||
>
|
||||
<WhitelistFlexUpload onChange={whitelistFlexFileOnChange} />
|
||||
@@ -317,6 +502,24 @@ export const WhitelistDetails = ({ onChange, mintingTokenFromFactory }: Whitelis
|
||||
<JsonPreview content={whitelistFlexArray} initialState={false} title="File Contents" />
|
||||
</Conditional>
|
||||
</Conditional>
|
||||
<Conditional test={whitelistType === 'merkletree'}>
|
||||
<FormGroup
|
||||
subtitle={
|
||||
<div>
|
||||
<span>TXT file that contains the whitelisted addresses</span>
|
||||
<Button className="mt-2 text-sm text-white" onClick={downloadSampleWhitelistFile}>
|
||||
Download Sample File
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
title="Whitelist File"
|
||||
>
|
||||
<WhitelistUpload onChange={whitelistFileOnChange} />
|
||||
</FormGroup>
|
||||
<Conditional test={whitelistStandardArray.length > 0}>
|
||||
<JsonPreview content={whitelistStandardArray} initialState title="File Contents" />
|
||||
</Conditional>
|
||||
</Conditional>
|
||||
</div>
|
||||
</div>
|
||||
</Conditional>
|
||||
|
||||
@@ -7,22 +7,25 @@ import { useInputState } from 'components/forms/FormInput.hooks'
|
||||
import { JsonPreview } from 'components/JsonPreview'
|
||||
import type { BaseMinterInstance } from 'contracts/baseMinter'
|
||||
import type { OpenEditionMinterInstance } from 'contracts/openEditionMinter'
|
||||
import type { RoyaltyRegistryInstance } from 'contracts/royaltyRegistry'
|
||||
import type { SG721Instance } from 'contracts/sg721'
|
||||
import type { VendingMinterInstance } from 'contracts/vendingMinter'
|
||||
import { toast } from 'react-hot-toast'
|
||||
import { useQuery } from 'react-query'
|
||||
import { useWallet } from 'utils/wallet'
|
||||
|
||||
import { useWallet } from '../../../contexts/wallet'
|
||||
import { resolveAddress } from '../../../utils/resolveAddress'
|
||||
import type { MinterType } from '../actions/Combobox'
|
||||
|
||||
interface CollectionQueriesProps {
|
||||
minterContractAddress: string
|
||||
sg721ContractAddress: string
|
||||
royaltyRegistryContractAddress: string
|
||||
sg721Messages: SG721Instance | undefined
|
||||
vendingMinterMessages: VendingMinterInstance | undefined
|
||||
baseMinterMessages: BaseMinterInstance | undefined
|
||||
openEditionMinterMessages: OpenEditionMinterInstance | undefined
|
||||
royaltyRegistryMessages: RoyaltyRegistryInstance | undefined
|
||||
minterType: MinterType
|
||||
}
|
||||
export const CollectionQueries = ({
|
||||
@@ -33,6 +36,7 @@ export const CollectionQueries = ({
|
||||
openEditionMinterMessages,
|
||||
baseMinterMessages,
|
||||
minterType,
|
||||
royaltyRegistryMessages,
|
||||
}: CollectionQueriesProps) => {
|
||||
const wallet = useWallet()
|
||||
|
||||
@@ -65,9 +69,11 @@ export const CollectionQueries = ({
|
||||
baseMinterMessages,
|
||||
vendingMinterMessages,
|
||||
openEditionMinterMessages,
|
||||
royaltyRegistryMessages,
|
||||
type,
|
||||
tokenId,
|
||||
address,
|
||||
sg721ContractAddress,
|
||||
] as const,
|
||||
async ({ queryKey }) => {
|
||||
const [
|
||||
@@ -75,9 +81,11 @@ export const CollectionQueries = ({
|
||||
_baseMinterMessages_,
|
||||
_vendingMinterMessages,
|
||||
_openEditionMinterMessages,
|
||||
_royaltyRegistryMessages,
|
||||
_type,
|
||||
_tokenId,
|
||||
_address,
|
||||
_sg721ContractAddress,
|
||||
] = queryKey
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const res = await resolveAddress(_address, wallet).then(async (resolvedAddress) => {
|
||||
@@ -88,8 +96,10 @@ export const CollectionQueries = ({
|
||||
baseMinterMessages: _baseMinterMessages_,
|
||||
openEditionMinterMessages: _openEditionMinterMessages,
|
||||
sg721Messages: _sg721Messages,
|
||||
royaltyRegistryMessages: _royaltyRegistryMessages,
|
||||
address: resolvedAddress,
|
||||
type: _type,
|
||||
sg721ContractAddress: _sg721ContractAddress,
|
||||
})
|
||||
return result
|
||||
})
|
||||
@@ -102,7 +112,9 @@ export const CollectionQueries = ({
|
||||
toast.error(error.message, { style: { maxWidth: 'none' } })
|
||||
}
|
||||
},
|
||||
enabled: Boolean(sg721ContractAddress && minterContractAddress && type),
|
||||
enabled:
|
||||
Boolean(type && type === 'infinity_swap_royalties' && sg721ContractAddress) ||
|
||||
Boolean(sg721ContractAddress && minterContractAddress && type),
|
||||
retry: false,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import type { BaseMinterInstance } from 'contracts/baseMinter'
|
||||
import type { OpenEditionMinterInstance } from 'contracts/openEditionMinter/contract'
|
||||
import type { RoyaltyRegistryInstance } from 'contracts/royaltyRegistry'
|
||||
import type { SG721Instance } from 'contracts/sg721'
|
||||
import type { VendingMinterInstance } from 'contracts/vendingMinter'
|
||||
import { INFINITY_SWAP_PROTOCOL_ADDRESS } from 'utils/constants'
|
||||
|
||||
export type QueryType = typeof QUERY_TYPES[number]
|
||||
|
||||
@@ -13,6 +15,7 @@ export const QUERY_TYPES = [
|
||||
'total_mint_count',
|
||||
'tokens',
|
||||
// 'token_owners',
|
||||
'infinity_swap_royalties',
|
||||
'token_info',
|
||||
'config',
|
||||
'status',
|
||||
@@ -35,6 +38,11 @@ export const VENDING_QUERY_LIST: QueryListItem[] = [
|
||||
name: 'Mint Price',
|
||||
description: `Get the price of minting a token.`,
|
||||
},
|
||||
{
|
||||
id: 'infinity_swap_royalties',
|
||||
name: 'Infinity Swap Royalty Details',
|
||||
description: `Get the collection's royalty details for Infinity Swap`,
|
||||
},
|
||||
{
|
||||
id: 'num_tokens',
|
||||
name: 'Mintable Number of Tokens',
|
||||
@@ -77,6 +85,11 @@ export const BASE_QUERY_LIST: QueryListItem[] = [
|
||||
name: 'Tokens Minted to User',
|
||||
description: `Get the number of tokens minted in the collection to a user.`,
|
||||
},
|
||||
{
|
||||
id: 'infinity_swap_royalties',
|
||||
name: 'Infinity Swap Royalty Details',
|
||||
description: `Get the collection's royalty details for Infinity Swap`,
|
||||
},
|
||||
{
|
||||
id: 'token_info',
|
||||
name: 'Token Info',
|
||||
@@ -104,6 +117,11 @@ export const OPEN_EDITION_QUERY_LIST: QueryListItem[] = [
|
||||
name: 'Mint Price',
|
||||
description: `Get the price of minting a token.`,
|
||||
},
|
||||
{
|
||||
id: 'infinity_swap_royalties',
|
||||
name: 'Infinity Swap Royalty Details',
|
||||
description: `Get the collection's royalty details for Infinity Swap`,
|
||||
},
|
||||
{
|
||||
id: 'tokens_minted_to_user',
|
||||
name: 'Tokens Minted to User',
|
||||
@@ -148,6 +166,8 @@ export type DispatchQueryArgs = {
|
||||
vendingMinterMessages?: VendingMinterInstance
|
||||
openEditionMinterMessages?: OpenEditionMinterInstance
|
||||
sg721Messages?: SG721Instance
|
||||
royaltyRegistryMessages?: RoyaltyRegistryInstance
|
||||
sg721ContractAddress?: string
|
||||
} & (
|
||||
| { type: undefined }
|
||||
| { type: Select<'collection_info'> }
|
||||
@@ -156,6 +176,7 @@ export type DispatchQueryArgs = {
|
||||
| { type: Select<'tokens_minted_to_user'>; address: string }
|
||||
| { type: Select<'total_mint_count'> }
|
||||
| { type: Select<'tokens'>; address: string }
|
||||
| { type: Select<'infinity_swap_royalties'> }
|
||||
// | { type: Select<'token_owners'> }
|
||||
| { type: Select<'token_info'>; tokenId: string }
|
||||
| { type: Select<'config'> }
|
||||
@@ -163,7 +184,13 @@ export type DispatchQueryArgs = {
|
||||
)
|
||||
|
||||
export const dispatchQuery = async (args: DispatchQueryArgs) => {
|
||||
const { baseMinterMessages, vendingMinterMessages, openEditionMinterMessages, sg721Messages } = args
|
||||
const {
|
||||
baseMinterMessages,
|
||||
vendingMinterMessages,
|
||||
openEditionMinterMessages,
|
||||
sg721Messages,
|
||||
royaltyRegistryMessages,
|
||||
} = args
|
||||
if (!baseMinterMessages || !vendingMinterMessages || !openEditionMinterMessages || !sg721Messages) {
|
||||
throw new Error('Cannot execute actions')
|
||||
}
|
||||
@@ -189,6 +216,12 @@ export const dispatchQuery = async (args: DispatchQueryArgs) => {
|
||||
// case 'token_owners': {
|
||||
// return vendingMinterMessages.updateStartTime(txSigner, args.startTime)
|
||||
// }
|
||||
case 'infinity_swap_royalties': {
|
||||
return royaltyRegistryMessages?.collectionRoyaltyProtocol(
|
||||
args.sg721ContractAddress as string,
|
||||
INFINITY_SWAP_PROTOCOL_ADDRESS,
|
||||
)
|
||||
}
|
||||
case 'token_info': {
|
||||
if (!args.tokenId) return
|
||||
return sg721Messages.allNftInfo(args.tokenId)
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
import type { ExecuteListItem } from 'contracts/royaltyRegistry/messages/execute'
|
||||
import { useState } from 'react'
|
||||
|
||||
export const useExecuteComboboxState = () => {
|
||||
const [value, setValue] = useState<ExecuteListItem | null>(null)
|
||||
return { value, onChange: (item: ExecuteListItem) => setValue(item) }
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
import { Combobox, Transition } from '@headlessui/react'
|
||||
import clsx from 'clsx'
|
||||
import { FormControl } from 'components/FormControl'
|
||||
import type { ExecuteListItem } from 'contracts/royaltyRegistry/messages/execute'
|
||||
import { EXECUTE_LIST } from 'contracts/royaltyRegistry/messages/execute'
|
||||
import { matchSorter } from 'match-sorter'
|
||||
import { Fragment, useState } from 'react'
|
||||
import { FaChevronDown, FaInfoCircle } from 'react-icons/fa'
|
||||
|
||||
export interface ExecuteComboboxProps {
|
||||
value: ExecuteListItem | null
|
||||
onChange: (item: ExecuteListItem) => void
|
||||
}
|
||||
|
||||
export const ExecuteCombobox = ({ value, onChange }: ExecuteComboboxProps) => {
|
||||
const [search, setSearch] = useState('')
|
||||
|
||||
const filtered =
|
||||
search === '' ? EXECUTE_LIST : matchSorter(EXECUTE_LIST, search, { keys: ['id', 'name', 'description'] })
|
||||
|
||||
return (
|
||||
<Combobox
|
||||
as={FormControl}
|
||||
htmlId="message-type"
|
||||
labelAs={Combobox.Label}
|
||||
onChange={onChange}
|
||||
subtitle="Contract execute message type"
|
||||
title="Message Type"
|
||||
value={value}
|
||||
>
|
||||
<div className="relative">
|
||||
<Combobox.Input
|
||||
className={clsx(
|
||||
'w-full bg-white/10 rounded border-2 border-white/20 form-input',
|
||||
'placeholder:text-white/50',
|
||||
'focus:ring focus:ring-plumbus-20',
|
||||
)}
|
||||
displayValue={(val?: ExecuteListItem) => val?.name ?? ''}
|
||||
id="message-type"
|
||||
onChange={(event) => setSearch(event.target.value)}
|
||||
placeholder="Select message type"
|
||||
/>
|
||||
|
||||
<Combobox.Button
|
||||
className={clsx(
|
||||
'flex absolute inset-y-0 right-0 items-center p-4',
|
||||
'opacity-50 hover:opacity-100 active:opacity-100',
|
||||
)}
|
||||
>
|
||||
{({ open }) => <FaChevronDown aria-hidden="true" className={clsx('w-4 h-4', { 'rotate-180': open })} />}
|
||||
</Combobox.Button>
|
||||
|
||||
<Transition afterLeave={() => setSearch('')} as={Fragment}>
|
||||
<Combobox.Options
|
||||
className={clsx(
|
||||
'overflow-auto absolute z-10 mt-2 w-full max-h-[30vh]',
|
||||
'bg-stone-800/80 rounded shadow-lg backdrop-blur-sm',
|
||||
'divide-y divide-stone-500/50',
|
||||
)}
|
||||
>
|
||||
{filtered.length < 1 && (
|
||||
<span className="flex flex-col justify-center items-center p-4 text-sm text-center text-white/50">
|
||||
Message type not found.
|
||||
</span>
|
||||
)}
|
||||
{filtered.map((entry) => (
|
||||
<Combobox.Option
|
||||
key={entry.id}
|
||||
className={({ active }) =>
|
||||
clsx('flex relative flex-col py-2 px-4 space-y-1 cursor-pointer', { 'bg-stargaze-80': active })
|
||||
}
|
||||
value={entry}
|
||||
>
|
||||
<span className="font-bold">{entry.name}</span>
|
||||
<span className="max-w-md text-sm">{entry.description}</span>
|
||||
</Combobox.Option>
|
||||
))}
|
||||
</Combobox.Options>
|
||||
</Transition>
|
||||
</div>
|
||||
|
||||
{value && (
|
||||
<div className="flex space-x-2 text-white/50">
|
||||
<div className="mt-1">
|
||||
<FaInfoCircle className="w-3 h-3" />
|
||||
</div>
|
||||
<span className="text-sm">{value.description}</span>
|
||||
</div>
|
||||
)}
|
||||
</Combobox>
|
||||
)
|
||||
}
|
||||
@@ -1,8 +1,11 @@
|
||||
/* eslint-disable eslint-comments/disable-enable-pair */
|
||||
/* eslint-disable no-nested-ternary */
|
||||
import { Combobox, Transition } from '@headlessui/react'
|
||||
import clsx from 'clsx'
|
||||
import { FormControl } from 'components/FormControl'
|
||||
import type { ExecuteListItem } from 'contracts/whitelist/messages/execute'
|
||||
import { EXECUTE_LIST } from 'contracts/whitelist/messages/execute'
|
||||
import { EXECUTE_LIST as WL_MERKLE_TREE_EXECUTE_LIST } from 'contracts/whitelistMerkleTree/messages/execute'
|
||||
import { matchSorter } from 'match-sorter'
|
||||
import { Fragment, useState } from 'react'
|
||||
import { FaChevronDown, FaInfoCircle } from 'react-icons/fa'
|
||||
@@ -10,13 +13,20 @@ import { FaChevronDown, FaInfoCircle } from 'react-icons/fa'
|
||||
export interface ExecuteComboboxProps {
|
||||
value: ExecuteListItem | null
|
||||
onChange: (item: ExecuteListItem) => void
|
||||
whitelistType?: 'standard' | 'flex' | 'merkletree'
|
||||
}
|
||||
|
||||
export const ExecuteCombobox = ({ value, onChange }: ExecuteComboboxProps) => {
|
||||
export const ExecuteCombobox = ({ value, onChange, whitelistType }: ExecuteComboboxProps) => {
|
||||
const [search, setSearch] = useState('')
|
||||
|
||||
const filtered =
|
||||
search === '' ? EXECUTE_LIST : matchSorter(EXECUTE_LIST, search, { keys: ['id', 'name', 'description'] })
|
||||
whitelistType !== 'merkletree'
|
||||
? search === ''
|
||||
? EXECUTE_LIST
|
||||
: matchSorter(EXECUTE_LIST, search, { keys: ['id', 'name', 'description'] })
|
||||
: search === ''
|
||||
? WL_MERKLE_TREE_EXECUTE_LIST
|
||||
: matchSorter(WL_MERKLE_TREE_EXECUTE_LIST, search, { keys: ['id', 'name', 'description'] })
|
||||
|
||||
return (
|
||||
<Combobox
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { toUtf8 } from '@cosmjs/encoding'
|
||||
import { FormControl } from 'components/FormControl'
|
||||
import { AddressInput } from 'components/forms/FormInput'
|
||||
import { useWallet } from 'contexts/wallet'
|
||||
import { useEffect, useId, useMemo } from 'react'
|
||||
import toast from 'react-hot-toast'
|
||||
import { FaMinus, FaPlus } from 'react-icons/fa'
|
||||
import { SG721_NAME_ADDRESS } from 'utils/constants'
|
||||
import { isValidAddress } from 'utils/isValidAddress'
|
||||
import { useWallet } from 'utils/wallet'
|
||||
|
||||
import { useInputState } from './FormInput.hooks'
|
||||
|
||||
@@ -31,6 +31,7 @@ export function AddressList(props: AddressListProps) {
|
||||
{entries.map(([id], i) => (
|
||||
<Address
|
||||
key={`ib-${id}`}
|
||||
defaultValue={entries[i][1]}
|
||||
id={id}
|
||||
isLast={i === entries.length - 1}
|
||||
onAdd={onAdd}
|
||||
@@ -48,9 +49,10 @@ export interface AddressProps {
|
||||
onAdd: AddressListProps['onAdd']
|
||||
onChange: AddressListProps['onChange']
|
||||
onRemove: AddressListProps['onRemove']
|
||||
defaultValue?: Address
|
||||
}
|
||||
|
||||
export function Address({ id, isLast, onAdd, onChange, onRemove }: AddressProps) {
|
||||
export function Address({ id, isLast, onAdd, onChange, onRemove, defaultValue }: AddressProps) {
|
||||
const wallet = useWallet()
|
||||
const Icon = useMemo(() => (isLast ? FaPlus : FaMinus), [isLast])
|
||||
|
||||
@@ -60,11 +62,14 @@ export function Address({ id, isLast, onAdd, onChange, onRemove }: AddressProps)
|
||||
id: `ib-address-${htmlId}`,
|
||||
name: `ib-address-${htmlId}`,
|
||||
title: ``,
|
||||
defaultValue: defaultValue?.address,
|
||||
})
|
||||
|
||||
const resolveAddress = async (name: string) => {
|
||||
if (!wallet.client) throw new Error('Wallet not connected')
|
||||
await wallet.client
|
||||
if (!wallet.isWalletConnected) throw new Error('Wallet not connected')
|
||||
await (
|
||||
await wallet.getCosmWasmClient()
|
||||
)
|
||||
.queryContractRaw(
|
||||
SG721_NAME_ADDRESS,
|
||||
toUtf8(
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import { useMemo, useState } from 'react'
|
||||
import { uid } from 'utils/random'
|
||||
|
||||
import type { DenomUnit } from './DenomUnits'
|
||||
|
||||
export function useDenomUnitsState() {
|
||||
const [record, setRecord] = useState<Record<string, DenomUnit>>(() => ({}))
|
||||
|
||||
const entries = useMemo(() => Object.entries(record), [record])
|
||||
const values = useMemo(() => Object.values(record), [record])
|
||||
|
||||
function add(attribute: DenomUnit = { denom: '', exponent: 0, aliases: '' }) {
|
||||
setRecord((prev) => ({ ...prev, [uid()]: attribute }))
|
||||
}
|
||||
|
||||
function update(key: string, attribute = record[key]) {
|
||||
setRecord((prev) => ({ ...prev, [key]: attribute }))
|
||||
}
|
||||
|
||||
function remove(key: string) {
|
||||
return setRecord((prev) => {
|
||||
const latest = { ...prev }
|
||||
delete latest[key]
|
||||
return latest
|
||||
})
|
||||
}
|
||||
|
||||
function reset() {
|
||||
setRecord({})
|
||||
}
|
||||
|
||||
return { entries, values, add, update, remove, reset }
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
import { FormControl } from 'components/FormControl'
|
||||
import { NumberInput, TextInput } from 'components/forms/FormInput'
|
||||
import { useEffect, useId, useMemo } from 'react'
|
||||
import { FaMinus, FaPlus } from 'react-icons/fa'
|
||||
import { useWallet } from 'utils/wallet'
|
||||
|
||||
import { useInputState, useNumberInputState } from './FormInput.hooks'
|
||||
|
||||
export interface DenomUnit {
|
||||
denom: string
|
||||
exponent: number
|
||||
aliases: string
|
||||
}
|
||||
|
||||
export interface DenomUnitsProps {
|
||||
title: string
|
||||
subtitle?: string
|
||||
isRequired?: boolean
|
||||
attributes: [string, DenomUnit][]
|
||||
onAdd: () => void
|
||||
onChange: (key: string, attribute: DenomUnit) => void
|
||||
onRemove: (key: string) => void
|
||||
}
|
||||
|
||||
export function DenomUnits(props: DenomUnitsProps) {
|
||||
const { title, subtitle, isRequired, attributes, onAdd, onChange, onRemove } = props
|
||||
|
||||
return (
|
||||
<FormControl isRequired={isRequired} subtitle={subtitle} title={title}>
|
||||
{attributes.map(([id], i) => (
|
||||
<DenomUnit
|
||||
key={`ma-${id}`}
|
||||
defaultAttribute={attributes[i][1]}
|
||||
id={id}
|
||||
isLast={i === attributes.length - 1}
|
||||
onAdd={onAdd}
|
||||
onChange={onChange}
|
||||
onRemove={onRemove}
|
||||
/>
|
||||
))}
|
||||
</FormControl>
|
||||
)
|
||||
}
|
||||
|
||||
export interface DenomUnitProps {
|
||||
id: string
|
||||
isLast: boolean
|
||||
onAdd: DenomUnitsProps['onAdd']
|
||||
onChange: DenomUnitsProps['onChange']
|
||||
onRemove: DenomUnitsProps['onRemove']
|
||||
defaultAttribute: DenomUnit
|
||||
}
|
||||
|
||||
export function DenomUnit({ id, isLast, onAdd, onChange, onRemove, defaultAttribute }: DenomUnitProps) {
|
||||
const wallet = useWallet()
|
||||
const Icon = useMemo(() => (isLast ? FaPlus : FaMinus), [isLast])
|
||||
|
||||
const htmlId = useId()
|
||||
|
||||
const denomState = useInputState({
|
||||
id: `ma-denom-${htmlId}`,
|
||||
name: `ma-denom-${htmlId}`,
|
||||
title: `Denom`,
|
||||
defaultValue: defaultAttribute.denom,
|
||||
})
|
||||
|
||||
const exponentState = useNumberInputState({
|
||||
id: `mint-exponent-${htmlId}`,
|
||||
name: `mint-exponent-${htmlId}`,
|
||||
title: `Exponent`,
|
||||
defaultValue: defaultAttribute.exponent,
|
||||
})
|
||||
|
||||
const aliasesState = useInputState({
|
||||
id: `ma-aliases-${htmlId}`,
|
||||
name: `ma-aliases-${htmlId}`,
|
||||
title: `Aliases`,
|
||||
defaultValue: defaultAttribute.aliases,
|
||||
placeholder: 'Comma separated aliases',
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
onChange(id, { denom: denomState.value, exponent: exponentState.value, aliases: aliasesState.value })
|
||||
}, [id, denomState.value, exponentState.value, aliasesState.value])
|
||||
|
||||
return (
|
||||
<div className="grid relative md:grid-cols-[40%_18%_35_7%] lg:grid-cols-[55%_13%_25%_7%] 2xl:grid-cols-[55%_13%_25%_7%] 2xl:space-x-2">
|
||||
<TextInput {...denomState} />
|
||||
<NumberInput className="ml-2" {...exponentState} />
|
||||
<TextInput className="ml-2" {...aliasesState} />
|
||||
|
||||
<div className="flex justify-end items-end pb-2 w-8">
|
||||
<button
|
||||
className="flex justify-center items-center p-2 bg-stargaze-80 hover:bg-plumbus-60 rounded-full"
|
||||
onClick={(e) => {
|
||||
e.preventDefault()
|
||||
isLast ? onAdd() : onRemove(id)
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
<Icon className="w-3 h-3" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -2,12 +2,12 @@ import { toUtf8 } from '@cosmjs/encoding'
|
||||
import { FormControl } from 'components/FormControl'
|
||||
import { AddressInput, NumberInput } from 'components/forms/FormInput'
|
||||
import type { WhitelistFlexMember } from 'components/WhitelistFlexUpload'
|
||||
import { useWallet } from 'contexts/wallet'
|
||||
import { useEffect, useId, useMemo } from 'react'
|
||||
import toast from 'react-hot-toast'
|
||||
import { FaMinus, FaPlus } from 'react-icons/fa'
|
||||
import { SG721_NAME_ADDRESS } from 'utils/constants'
|
||||
import { isValidAddress } from 'utils/isValidAddress'
|
||||
import { useWallet } from 'utils/wallet'
|
||||
|
||||
import { useInputState, useNumberInputState } from './FormInput.hooks'
|
||||
|
||||
@@ -75,8 +75,10 @@ export function FlexMemberAttribute({ id, isLast, onAdd, onChange, onRemove, def
|
||||
}, [addressState.value, mintCountState.value, id])
|
||||
|
||||
const resolveAddress = async (name: string) => {
|
||||
if (!wallet.client) throw new Error('Wallet not connected')
|
||||
await wallet.client
|
||||
if (!wallet.isWalletConnected) throw new Error('Wallet not connected')
|
||||
await (
|
||||
await wallet.getCosmWasmClient()
|
||||
)
|
||||
.queryContractRaw(
|
||||
SG721_NAME_ADDRESS,
|
||||
toUtf8(
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
/* eslint-disable eslint-comments/disable-enable-pair */
|
||||
/* eslint-disable no-nested-ternary */
|
||||
/* eslint-disable jsx-a11y/media-has-caption */
|
||||
/* eslint-disable @typescript-eslint/no-unnecessary-condition */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
||||
@@ -11,6 +12,7 @@ import { FormGroup } from 'components/FormGroup'
|
||||
import { useInputState } from 'components/forms/FormInput.hooks'
|
||||
import { InputDateTime } from 'components/InputDateTime'
|
||||
import { Tooltip } from 'components/Tooltip'
|
||||
import { useGlobalSettings } from 'contexts/globalSettings'
|
||||
import { addLogItem } from 'contexts/log'
|
||||
import type { ChangeEvent } from 'react'
|
||||
import { useEffect, useMemo, useRef, useState } from 'react'
|
||||
@@ -28,6 +30,7 @@ interface CollectionDetailsProps {
|
||||
uploadMethod: UploadMethod
|
||||
coverImageUrl: string
|
||||
metadataStorageMethod: MetadataStorageMethod
|
||||
importedCollectionDetails?: CollectionDetailsDataProps
|
||||
}
|
||||
|
||||
export interface CollectionDetailsDataProps {
|
||||
@@ -46,11 +49,13 @@ export const CollectionDetails = ({
|
||||
uploadMethod,
|
||||
metadataStorageMethod,
|
||||
coverImageUrl,
|
||||
importedCollectionDetails,
|
||||
}: CollectionDetailsProps) => {
|
||||
const [coverImage, setCoverImage] = useState<File | null>(null)
|
||||
const [timestamp, setTimestamp] = useState<Date | undefined>()
|
||||
const [explicit, setExplicit] = useState<boolean>(false)
|
||||
const [updatable, setUpdatable] = useState<boolean>(false)
|
||||
const { timezone } = useGlobalSettings()
|
||||
|
||||
const initialRender = useRef(true)
|
||||
|
||||
@@ -124,7 +129,9 @@ export const CollectionDetails = ({
|
||||
reader.onload = (e) => {
|
||||
if (!e.target?.result) return toast.error('Error parsing file.')
|
||||
if (!event.target.files) return toast.error('No files selected.')
|
||||
const imageFile = new File([e.target.result], event.target.files[0].name, { type: 'image/jpg' })
|
||||
const imageFile = new File([e.target.result], event.target.files[0].name.replaceAll('#', ''), {
|
||||
type: 'image/jpg',
|
||||
})
|
||||
setCoverImage(imageFile)
|
||||
}
|
||||
reader.readAsArrayBuffer(event.target.files[0])
|
||||
@@ -152,6 +159,23 @@ export const CollectionDetails = ({
|
||||
}
|
||||
}, [updatable])
|
||||
|
||||
useEffect(() => {
|
||||
if (importedCollectionDetails) {
|
||||
nameState.onChange(importedCollectionDetails.name)
|
||||
descriptionState.onChange(importedCollectionDetails.description)
|
||||
symbolState.onChange(importedCollectionDetails.symbol)
|
||||
//setCoverImage(importedCollectionDetails.imageFile[0] || null)
|
||||
externalLinkState.onChange(importedCollectionDetails.externalLink || '')
|
||||
setTimestamp(
|
||||
importedCollectionDetails.startTradingTime
|
||||
? new Date(parseInt(importedCollectionDetails.startTradingTime) / 1_000_000)
|
||||
: undefined,
|
||||
)
|
||||
setExplicit(importedCollectionDetails.explicit)
|
||||
setUpdatable(importedCollectionDetails.updatable)
|
||||
}
|
||||
}, [importedCollectionDetails])
|
||||
|
||||
const videoPreview = useMemo(() => {
|
||||
if (uploadMethod === 'new' && coverImage) {
|
||||
return (
|
||||
@@ -204,10 +228,30 @@ export const CollectionDetails = ({
|
||||
<FormControl
|
||||
className={clsx('mt-2')}
|
||||
htmlId="timestamp"
|
||||
subtitle="Trading start time offset will be set as 2 weeks by default."
|
||||
title="Trading Start Time (optional)"
|
||||
subtitle="Trading start time offset will be set as 1 week by default."
|
||||
title={`Trading Start Time (optional | ${timezone === 'Local' ? 'local)' : 'UTC)'}`}
|
||||
>
|
||||
<InputDateTime minDate={new Date()} onChange={(date) => setTimestamp(date)} value={timestamp} />
|
||||
<InputDateTime
|
||||
minDate={
|
||||
timezone === 'Local' ? new Date() : new Date(Date.now() + new Date().getTimezoneOffset() * 60 * 1000)
|
||||
}
|
||||
onChange={(date) =>
|
||||
date
|
||||
? setTimestamp(
|
||||
timezone === 'Local'
|
||||
? date
|
||||
: new Date(date.getTime() - new Date().getTimezoneOffset() * 60 * 1000),
|
||||
)
|
||||
: setTimestamp(undefined)
|
||||
}
|
||||
value={
|
||||
timezone === 'Local'
|
||||
? timestamp
|
||||
: timestamp
|
||||
? new Date(timestamp.getTime() + new Date().getTimezoneOffset() * 60 * 1000)
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
</FormControl>
|
||||
</div>
|
||||
</div>
|
||||
@@ -318,7 +362,9 @@ export const CollectionDetails = ({
|
||||
</div>
|
||||
</div>
|
||||
<Conditional
|
||||
test={SG721_OPEN_EDITION_UPDATABLE_CODE_ID > 0 && OPEN_EDITION_UPDATABLE_FACTORY_ADDRESS !== undefined}
|
||||
test={
|
||||
false && SG721_OPEN_EDITION_UPDATABLE_CODE_ID > 0 && OPEN_EDITION_UPDATABLE_FACTORY_ADDRESS !== undefined
|
||||
}
|
||||
>
|
||||
<Tooltip
|
||||
backgroundColor="bg-blue-500"
|
||||
@@ -332,9 +378,12 @@ export const CollectionDetails = ({
|
||||
}
|
||||
placement="bottom"
|
||||
>
|
||||
<div className={clsx('flex flex-col space-y-2 w-3/4 form-control')}>
|
||||
<div className={clsx('flex flex-col space-y-2 w-full form-control')}>
|
||||
<label className="justify-start cursor-pointer label">
|
||||
<span className="mr-4 font-bold">Updatable Token Metadata</span>
|
||||
<div className="flex flex-col">
|
||||
<span className="mr-4 font-bold">Updatable Token Metadata</span>
|
||||
<span className="mr-4">(Price: 2000 STARS)</span>
|
||||
</div>
|
||||
<input
|
||||
checked={updatable}
|
||||
className={`toggle ${updatable ? `bg-stargaze` : `bg-gray-600`}`}
|
||||
|
||||
@@ -14,16 +14,21 @@ import type { ChangeEvent } from 'react'
|
||||
import { useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { toast } from 'react-hot-toast'
|
||||
import type { UploadServiceType } from 'services/upload'
|
||||
import { NFT_STORAGE_DEFAULT_API_KEY } from 'utils/constants'
|
||||
import type { AssetType } from 'utils/getAssetType'
|
||||
import { getAssetType } from 'utils/getAssetType'
|
||||
|
||||
export type UploadMethod = 'new' | 'existing'
|
||||
|
||||
interface ImageUploadDetailsProps {
|
||||
onChange: (value: ImageUploadDetailsDataProps) => void
|
||||
importedImageUploadDetails?: ImageUploadDetailsDataProps
|
||||
}
|
||||
|
||||
export interface ImageUploadDetailsDataProps {
|
||||
assetFile: File | undefined
|
||||
thumbnailFile?: File | undefined
|
||||
isThumbnailCompatible?: boolean
|
||||
uploadService: UploadServiceType
|
||||
nftStorageApiKey?: string
|
||||
pinataApiKey?: string
|
||||
@@ -33,12 +38,16 @@ export interface ImageUploadDetailsDataProps {
|
||||
coverImageUrl?: string
|
||||
}
|
||||
|
||||
export const ImageUploadDetails = ({ onChange }: ImageUploadDetailsProps) => {
|
||||
export const ImageUploadDetails = ({ onChange, importedImageUploadDetails }: ImageUploadDetailsProps) => {
|
||||
const [assetFile, setAssetFile] = useState<File>()
|
||||
const [thumbnailFile, setThumbnailFile] = useState<File>()
|
||||
const [isThumbnailCompatible, setIsThumbnailCompatible] = useState<boolean>(false)
|
||||
const [uploadMethod, setUploadMethod] = useState<UploadMethod>('new')
|
||||
const [uploadService, setUploadService] = useState<UploadServiceType>('nft-storage')
|
||||
const [useDefaultApiKey, setUseDefaultApiKey] = useState(false)
|
||||
|
||||
const assetFileRef = useRef<HTMLInputElement | null>(null)
|
||||
const thumbnailFileRef = useRef<HTMLInputElement | null>(null)
|
||||
|
||||
const nftStorageApiKeyState = useInputState({
|
||||
id: 'nft-storage-api-key',
|
||||
@@ -78,8 +87,12 @@ export const ImageUploadDetails = ({ onChange }: ImageUploadDetailsProps) => {
|
||||
defaultValue: '',
|
||||
})
|
||||
|
||||
const thumbnailCompatibleAssetTypes: AssetType[] = ['video', 'audio', 'html']
|
||||
|
||||
const selectAsset = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
setAssetFile(undefined)
|
||||
setThumbnailFile(undefined)
|
||||
setIsThumbnailCompatible(false)
|
||||
if (event.target.files === null) return
|
||||
|
||||
let selectedFile: File
|
||||
@@ -87,17 +100,40 @@ export const ImageUploadDetails = ({ onChange }: ImageUploadDetailsProps) => {
|
||||
reader.onload = (e) => {
|
||||
if (!event.target.files) return toast.error('No file selected.')
|
||||
if (!e.target?.result) return toast.error('Error parsing file.')
|
||||
selectedFile = new File([e.target.result], event.target.files[0].name, { type: 'image/jpg' })
|
||||
selectedFile = new File([e.target.result], event.target.files[0].name.replaceAll('#', ''), { type: 'image/jpg' })
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||
if (event.target.files[0]) reader.readAsArrayBuffer(event.target.files[0])
|
||||
else return toast.error('No file selected.')
|
||||
reader.onloadend = () => {
|
||||
if (!event.target.files) return toast.error('No file selected.')
|
||||
if (thumbnailCompatibleAssetTypes.includes(getAssetType(event.target.files[0].name))) {
|
||||
setIsThumbnailCompatible(true)
|
||||
}
|
||||
setAssetFile(selectedFile)
|
||||
}
|
||||
}
|
||||
|
||||
const selectThumbnail = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
setThumbnailFile(undefined)
|
||||
if (event.target.files === null) return
|
||||
|
||||
let selectedFile: File
|
||||
const reader = new FileReader()
|
||||
reader.onload = (e) => {
|
||||
if (!event.target.files) return toast.error('No file selected.')
|
||||
if (!e.target?.result) return toast.error('Error parsing file.')
|
||||
selectedFile = new File([e.target.result], event.target.files[0].name.replaceAll('#', ''), { type: 'image/*' })
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||
if (event.target.files[0]) reader.readAsArrayBuffer(event.target.files[0])
|
||||
else return toast.error('No file selected.')
|
||||
reader.onloadend = () => {
|
||||
if (!event.target.files) return toast.error('No file selected.')
|
||||
setThumbnailFile(selectedFile)
|
||||
}
|
||||
}
|
||||
|
||||
const regex =
|
||||
/[\0-\x1F\x7F-\x9F\xAD\u0378\u0379\u037F-\u0383\u038B\u038D\u03A2\u0528-\u0530\u0557\u0558\u0560\u0588\u058B-\u058E\u0590\u05C8-\u05CF\u05EB-\u05EF\u05F5-\u0605\u061C\u061D\u06DD\u070E\u070F\u074B\u074C\u07B2-\u07BF\u07FB-\u07FF\u082E\u082F\u083F\u085C\u085D\u085F-\u089F\u08A1\u08AD-\u08E3\u08FF\u0978\u0980\u0984\u098D\u098E\u0991\u0992\u09A9\u09B1\u09B3-\u09B5\u09BA\u09BB\u09C5\u09C6\u09C9\u09CA\u09CF-\u09D6\u09D8-\u09DB\u09DE\u09E4\u09E5\u09FC-\u0A00\u0A04\u0A0B-\u0A0E\u0A11\u0A12\u0A29\u0A31\u0A34\u0A37\u0A3A\u0A3B\u0A3D\u0A43-\u0A46\u0A49\u0A4A\u0A4E-\u0A50\u0A52-\u0A58\u0A5D\u0A5F-\u0A65\u0A76-\u0A80\u0A84\u0A8E\u0A92\u0AA9\u0AB1\u0AB4\u0ABA\u0ABB\u0AC6\u0ACA\u0ACE\u0ACF\u0AD1-\u0ADF\u0AE4\u0AE5\u0AF2-\u0B00\u0B04\u0B0D\u0B0E\u0B11\u0B12\u0B29\u0B31\u0B34\u0B3A\u0B3B\u0B45\u0B46\u0B49\u0B4A\u0B4E-\u0B55\u0B58-\u0B5B\u0B5E\u0B64\u0B65\u0B78-\u0B81\u0B84\u0B8B-\u0B8D\u0B91\u0B96-\u0B98\u0B9B\u0B9D\u0BA0-\u0BA2\u0BA5-\u0BA7\u0BAB-\u0BAD\u0BBA-\u0BBD\u0BC3-\u0BC5\u0BC9\u0BCE\u0BCF\u0BD1-\u0BD6\u0BD8-\u0BE5\u0BFB-\u0C00\u0C04\u0C0D\u0C11\u0C29\u0C34\u0C3A-\u0C3C\u0C45\u0C49\u0C4E-\u0C54\u0C57\u0C5A-\u0C5F\u0C64\u0C65\u0C70-\u0C77\u0C80\u0C81\u0C84\u0C8D\u0C91\u0CA9\u0CB4\u0CBA\u0CBB\u0CC5\u0CC9\u0CCE-\u0CD4\u0CD7-\u0CDD\u0CDF\u0CE4\u0CE5\u0CF0\u0CF3-\u0D01\u0D04\u0D0D\u0D11\u0D3B\u0D3C\u0D45\u0D49\u0D4F-\u0D56\u0D58-\u0D5F\u0D64\u0D65\u0D76-\u0D78\u0D80\u0D81\u0D84\u0D97-\u0D99\u0DB2\u0DBC\u0DBE\u0DBF\u0DC7-\u0DC9\u0DCB-\u0DCE\u0DD5\u0DD7\u0DE0-\u0DF1\u0DF5-\u0E00\u0E3B-\u0E3E\u0E5C-\u0E80\u0E83\u0E85\u0E86\u0E89\u0E8B\u0E8C\u0E8E-\u0E93\u0E98\u0EA0\u0EA4\u0EA6\u0EA8\u0EA9\u0EAC\u0EBA\u0EBE\u0EBF\u0EC5\u0EC7\u0ECE\u0ECF\u0EDA\u0EDB\u0EE0-\u0EFF\u0F48\u0F6D-\u0F70\u0F98\u0FBD\u0FCD\u0FDB-\u0FFF\u10C6\u10C8-\u10CC\u10CE\u10CF\u1249\u124E\u124F\u1257\u1259\u125E\u125F\u1289\u128E\u128F\u12B1\u12B6\u12B7\u12BF\u12C1\u12C6\u12C7\u12D7\u1311\u1316\u1317\u135B\u135C\u137D-\u137F\u139A-\u139F\u13F5-\u13FF\u169D-\u169F\u16F1-\u16FF\u170D\u1715-\u171F\u1737-\u173F\u1754-\u175F\u176D\u1771\u1774-\u177F\u17DE\u17DF\u17EA-\u17EF\u17FA-\u17FF\u180F\u181A-\u181F\u1878-\u187F\u18AB-\u18AF\u18F6-\u18FF\u191D-\u191F\u192C-\u192F\u193C-\u193F\u1941-\u1943\u196E\u196F\u1975-\u197F\u19AC-\u19AF\u19CA-\u19CF\u19DB-\u19DD\u1A1C\u1A1D\u1A5F\u1A7D\u1A7E\u1A8A-\u1A8F\u1A9A-\u1A9F\u1AAE-\u1AFF\u1B4C-\u1B4F\u1B7D-\u1B7F\u1BF4-\u1BFB\u1C38-\u1C3A\u1C4A-\u1C4C\u1C80-\u1CBF\u1CC8-\u1CCF\u1CF7-\u1CFF\u1DE7-\u1DFB\u1F16\u1F17\u1F1E\u1F1F\u1F46\u1F47\u1F4E\u1F4F\u1F58\u1F5A\u1F5C\u1F5E\u1F7E\u1F7F\u1FB5\u1FC5\u1FD4\u1FD5\u1FDC\u1FF0\u1FF1\u1FF5\u1FFF\u200B-\u200F\u2020-\u202E\u2060-\u206F\u2072\u2073\u208F\u209D-\u209F\u20BB-\u20CF\u20F1-\u20FF\u218A-\u218F\u23F4-\u23FF\u2427-\u243F\u244B-\u245F\u2700\u2B4D-\u2B4F\u2B5A-\u2BFF\u2C2F\u2C5F\u2CF4-\u2CF8\u2D26\u2D28-\u2D2C\u2D2E\u2D2F\u2D68-\u2D6E\u2D71-\u2D7E\u2D97-\u2D9F\u2DA7\u2DAF\u2DB7\u2DBF\u2DC7\u2DCF\u2DD7\u2DDF\u2E3C-\u2E7F\u2E9A\u2EF4-\u2EFF\u2FD6-\u2FEF\u2FFC-\u2FFF\u3040\u3097\u3098\u3100-\u3104\u312E-\u3130\u318F\u31BB-\u31BF\u31E4-\u31EF\u321F\u32FF\u4DB6-\u4DBF\u9FCD-\u9FFF\uA48D-\uA48F\uA4C7-\uA4CF\uA62C-\uA63F\uA698-\uA69E\uA6F8-\uA6FF\uA78F\uA794-\uA79F\uA7AB-\uA7F7\uA82C-\uA82F\uA83A-\uA83F\uA878-\uA87F\uA8C5-\uA8CD\uA8DA-\uA8DF\uA8FC-\uA8FF\uA954-\uA95E\uA97D-\uA97F\uA9CE\uA9DA-\uA9DD\uA9E0-\uA9FF\uAA37-\uAA3F\uAA4E\uAA4F\uAA5A\uAA5B\uAA7C-\uAA7F\uAAC3-\uAADA\uAAF7-\uAB00\uAB07\uAB08\uAB0F\uAB10\uAB17-\uAB1F\uAB27\uAB2F-\uABBF\uABEE\uABEF\uABFA-\uABFF\uD7A4-\uD7AF\uD7C7-\uD7CA\uD7FC-\uF8FF\uFA6E\uFA6F\uFADA-\uFAFF\uFB07-\uFB12\uFB18-\uFB1C\uFB37\uFB3D\uFB3F\uFB42\uFB45\uFBC2-\uFBD2\uFD40-\uFD4F\uFD90\uFD91\uFDC8-\uFDEF\uFDFE\uFDFF\uFE1A-\uFE1F\uFE27-\uFE2F\uFE53\uFE67\uFE6C-\uFE6F\uFE75\uFEFD-\uFF00\uFFBF-\uFFC1\uFFC8\uFFC9\uFFD0\uFFD1\uFFD8\uFFD9\uFFDD-\uFFDF\uFFE7\uFFEF-\uFFFB\uFFFE\uFFFF]/g
|
||||
|
||||
@@ -105,6 +141,8 @@ export const ImageUploadDetails = ({ onChange }: ImageUploadDetailsProps) => {
|
||||
try {
|
||||
const data: ImageUploadDetailsDataProps = {
|
||||
assetFile,
|
||||
thumbnailFile,
|
||||
isThumbnailCompatible,
|
||||
uploadService,
|
||||
nftStorageApiKey: nftStorageApiKeyState.value,
|
||||
pinataApiKey: pinataApiKeyState.value,
|
||||
@@ -125,6 +163,8 @@ export const ImageUploadDetails = ({ onChange }: ImageUploadDetailsProps) => {
|
||||
}
|
||||
}, [
|
||||
assetFile,
|
||||
thumbnailFile,
|
||||
isThumbnailCompatible,
|
||||
uploadService,
|
||||
nftStorageApiKeyState.value,
|
||||
pinataApiKeyState.value,
|
||||
@@ -137,9 +177,31 @@ export const ImageUploadDetails = ({ onChange }: ImageUploadDetailsProps) => {
|
||||
useEffect(() => {
|
||||
if (assetFileRef.current) assetFileRef.current.value = ''
|
||||
setAssetFile(undefined)
|
||||
setThumbnailFile(undefined)
|
||||
setIsThumbnailCompatible(false)
|
||||
imageUrlState.onChange('')
|
||||
}, [uploadMethod])
|
||||
|
||||
useEffect(() => {
|
||||
if (importedImageUploadDetails) {
|
||||
setUploadMethod(importedImageUploadDetails.uploadMethod)
|
||||
setUploadService(importedImageUploadDetails.uploadService)
|
||||
nftStorageApiKeyState.onChange(importedImageUploadDetails.nftStorageApiKey || '')
|
||||
pinataApiKeyState.onChange(importedImageUploadDetails.pinataApiKey || '')
|
||||
pinataSecretKeyState.onChange(importedImageUploadDetails.pinataSecretKey || '')
|
||||
imageUrlState.onChange(importedImageUploadDetails.imageUrl || '')
|
||||
coverImageUrlState.onChange(importedImageUploadDetails.coverImageUrl || '')
|
||||
}
|
||||
}, [importedImageUploadDetails])
|
||||
|
||||
useEffect(() => {
|
||||
if (useDefaultApiKey) {
|
||||
nftStorageApiKeyState.onChange(NFT_STORAGE_DEFAULT_API_KEY || '')
|
||||
} else {
|
||||
nftStorageApiKeyState.onChange('')
|
||||
}
|
||||
}, [useDefaultApiKey])
|
||||
|
||||
const previewUrl = imageUrlState.value.toLowerCase().trim().startsWith('ipfs://')
|
||||
? `https://ipfs-gw.stargaze-apis.com/ipfs/${imageUrlState.value.substring(7)}`
|
||||
: imageUrlState.value
|
||||
@@ -291,7 +353,22 @@ export const ImageUploadDetails = ({ onChange }: ImageUploadDetailsProps) => {
|
||||
|
||||
<div className="flex w-full">
|
||||
<Conditional test={uploadService === 'nft-storage'}>
|
||||
<TextInput {...nftStorageApiKeyState} className="w-full" />
|
||||
<div className="flex-col w-full">
|
||||
<TextInput {...nftStorageApiKeyState} className="w-full" disabled={useDefaultApiKey} />
|
||||
<div className="flex-row mt-2 w-full form-control">
|
||||
<label className="cursor-pointer label">
|
||||
<span className="mr-2 font-bold">Use Default API Key</span>
|
||||
<input
|
||||
checked={useDefaultApiKey}
|
||||
className={`${useDefaultApiKey ? `bg-stargaze` : `bg-gray-600`} checkbox`}
|
||||
onClick={() => {
|
||||
setUseDefaultApiKey(!useDefaultApiKey)
|
||||
}}
|
||||
type="checkbox"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</Conditional>
|
||||
<Conditional test={uploadService === 'pinata'}>
|
||||
<TextInput {...pinataApiKeyState} className="w-full" />
|
||||
@@ -319,7 +396,7 @@ export const ImageUploadDetails = ({ onChange }: ImageUploadDetailsProps) => {
|
||||
)}
|
||||
>
|
||||
<input
|
||||
accept="image/*, audio/*, video/*, .html"
|
||||
accept="image/*, audio/*, video/*, .html, .pdf"
|
||||
className={clsx(
|
||||
'file:py-2 file:px-4 file:mr-4 file:bg-plumbus-light file:rounded file:border-0 cursor-pointer',
|
||||
'before:absolute before:inset-0 before:hover:bg-white/5 before:transition',
|
||||
@@ -331,6 +408,34 @@ export const ImageUploadDetails = ({ onChange }: ImageUploadDetailsProps) => {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<Conditional test={isThumbnailCompatible}>
|
||||
<div>
|
||||
<label
|
||||
className="block mt-5 mr-1 mb-1 ml-8 w-full font-bold text-white dark:text-gray-300"
|
||||
htmlFor="thumbnailFile"
|
||||
>
|
||||
Thumbnail Selection (optional)
|
||||
</label>
|
||||
<div
|
||||
className={clsx(
|
||||
'flex relative justify-center items-center mx-8 mt-2 space-y-4 w-full h-32',
|
||||
'rounded border-2 border-white/20 border-dashed',
|
||||
)}
|
||||
>
|
||||
<input
|
||||
accept="image/*"
|
||||
className={clsx(
|
||||
'file:py-2 file:px-4 file:mr-4 file:bg-plumbus-light file:rounded file:border-0 cursor-pointer',
|
||||
'before:absolute before:inset-0 before:hover:bg-white/5 before:transition',
|
||||
)}
|
||||
id="thumbnailFile"
|
||||
onChange={selectThumbnail}
|
||||
ref={thumbnailFileRef}
|
||||
type="file"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Conditional>
|
||||
</div>
|
||||
</div>
|
||||
<Conditional test={assetFile !== undefined}>
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
/* eslint-disable eslint-comments/disable-enable-pair */
|
||||
/* eslint-disable @typescript-eslint/no-unnecessary-condition */
|
||||
/* eslint-disable no-nested-ternary */
|
||||
import { Conditional } from 'components/Conditional'
|
||||
import { FormControl } from 'components/FormControl'
|
||||
import { FormGroup } from 'components/FormGroup'
|
||||
import { useInputState, useNumberInputState } from 'components/forms/FormInput.hooks'
|
||||
@@ -7,27 +9,35 @@ import { InputDateTime } from 'components/InputDateTime'
|
||||
import { openEditionMinterList } from 'config/minter'
|
||||
import type { TokenInfo } from 'config/token'
|
||||
import { stars, tokensList } from 'config/token'
|
||||
import { useGlobalSettings } from 'contexts/globalSettings'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { resolveAddress } from 'utils/resolveAddress'
|
||||
import { useWallet } from 'utils/wallet'
|
||||
|
||||
import { useWallet } from '../../contexts/wallet'
|
||||
import { NumberInput, TextInput } from '../forms/FormInput'
|
||||
import type { UploadMethod } from './OffChainMetadataUploadDetails'
|
||||
|
||||
export type LimitType = 'count_limited' | 'time_limited' | 'time_and_count_limited'
|
||||
|
||||
interface MintingDetailsProps {
|
||||
onChange: (data: MintingDetailsDataProps) => void
|
||||
uploadMethod: UploadMethod
|
||||
minimumMintPrice: number
|
||||
mintTokenFromFactory?: TokenInfo | undefined
|
||||
importedMintingDetails?: MintingDetailsDataProps
|
||||
isPresale: boolean
|
||||
whitelistStartDate?: string
|
||||
}
|
||||
|
||||
export interface MintingDetailsDataProps {
|
||||
unitPrice: string
|
||||
perAddressLimit: number
|
||||
startTime: string
|
||||
endTime: string
|
||||
endTime?: string
|
||||
tokenCountLimit?: number
|
||||
paymentAddress?: string
|
||||
selectedMintToken?: TokenInfo
|
||||
limitType: LimitType
|
||||
}
|
||||
|
||||
export const MintingDetails = ({
|
||||
@@ -35,12 +45,18 @@ export const MintingDetails = ({
|
||||
uploadMethod,
|
||||
minimumMintPrice,
|
||||
mintTokenFromFactory,
|
||||
importedMintingDetails,
|
||||
isPresale,
|
||||
whitelistStartDate,
|
||||
}: MintingDetailsProps) => {
|
||||
const wallet = useWallet()
|
||||
|
||||
const [timestamp, setTimestamp] = useState<Date | undefined>()
|
||||
const [endTimestamp, setEndTimestamp] = useState<Date | undefined>()
|
||||
const [selectedMintToken, setSelectedMintToken] = useState<TokenInfo | undefined>(stars)
|
||||
const [mintingDetailsImported, setMintingDetailsImported] = useState(false)
|
||||
const [limitType, setLimitType] = useState<LimitType>('time_limited')
|
||||
const { timezone } = useGlobalSettings()
|
||||
|
||||
const unitPriceState = useNumberInputState({
|
||||
id: 'unitPrice',
|
||||
@@ -60,6 +76,14 @@ export const MintingDetails = ({
|
||||
placeholder: '1',
|
||||
})
|
||||
|
||||
const tokenCountLimitState = useNumberInputState({
|
||||
id: 'tokencountlimit',
|
||||
name: 'tokencountlimit',
|
||||
title: 'Maximum Token Count',
|
||||
subtitle: 'Total number of mintable tokens',
|
||||
placeholder: '100',
|
||||
})
|
||||
|
||||
const paymentAddressState = useInputState({
|
||||
id: 'payment-address',
|
||||
name: 'paymentAddress',
|
||||
@@ -75,7 +99,9 @@ export const MintingDetails = ({
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
void resolvePaymentAddress()
|
||||
if (!importedMintingDetails || (importedMintingDetails && mintingDetailsImported)) {
|
||||
void resolvePaymentAddress()
|
||||
}
|
||||
}, [paymentAddressState.value])
|
||||
|
||||
useEffect(() => {
|
||||
@@ -87,9 +113,19 @@ export const MintingDetails = ({
|
||||
: '',
|
||||
perAddressLimit: perAddressLimitState.value,
|
||||
startTime: timestamp ? (timestamp.getTime() * 1_000_000).toString() : '',
|
||||
endTime: endTimestamp ? (endTimestamp.getTime() * 1_000_000).toString() : '',
|
||||
endTime:
|
||||
limitType === 'time_limited' || limitType === 'time_and_count_limited'
|
||||
? endTimestamp
|
||||
? (endTimestamp.getTime() * 1_000_000).toString()
|
||||
: ''
|
||||
: undefined,
|
||||
paymentAddress: paymentAddressState.value.trim(),
|
||||
selectedMintToken,
|
||||
limitType,
|
||||
tokenCountLimit:
|
||||
limitType === 'count_limited' || limitType === 'time_and_count_limited'
|
||||
? tokenCountLimitState.value
|
||||
: undefined,
|
||||
}
|
||||
onChange(data)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
@@ -100,16 +136,41 @@ export const MintingDetails = ({
|
||||
endTimestamp,
|
||||
paymentAddressState.value,
|
||||
selectedMintToken,
|
||||
tokenCountLimitState.value,
|
||||
limitType,
|
||||
])
|
||||
|
||||
useEffect(() => {
|
||||
if (importedMintingDetails) {
|
||||
console.log('Selected Token ID: ', importedMintingDetails.selectedMintToken?.id)
|
||||
unitPriceState.onChange(Number(importedMintingDetails.unitPrice) / 1000000)
|
||||
perAddressLimitState.onChange(importedMintingDetails.perAddressLimit)
|
||||
setLimitType(importedMintingDetails.limitType)
|
||||
tokenCountLimitState.onChange(importedMintingDetails.tokenCountLimit ? importedMintingDetails.tokenCountLimit : 0)
|
||||
setTimestamp(new Date(Number(importedMintingDetails.startTime) / 1_000_000))
|
||||
setEndTimestamp(new Date(Number(importedMintingDetails.endTime) / 1_000_000))
|
||||
paymentAddressState.onChange(importedMintingDetails.paymentAddress ? importedMintingDetails.paymentAddress : '')
|
||||
setSelectedMintToken(tokensList.find((token) => token.id === importedMintingDetails.selectedMintToken?.id))
|
||||
setMintingDetailsImported(true)
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [importedMintingDetails])
|
||||
|
||||
useEffect(() => {
|
||||
if (isPresale) {
|
||||
setTimestamp(whitelistStartDate ? new Date(Number(whitelistStartDate) / 1_000_000) : undefined)
|
||||
}
|
||||
}, [whitelistStartDate, isPresale])
|
||||
|
||||
return (
|
||||
<div className="border-l-[1px] border-gray-500 border-opacity-20">
|
||||
<FormGroup subtitle="Information about your minting settings" title="Minting Details">
|
||||
<div className="flex flex-row items-center">
|
||||
<div className="flex flex-row items-end">
|
||||
<NumberInput {...unitPriceState} isRequired />
|
||||
<select
|
||||
className="py-[9px] px-4 mt-14 ml-4 placeholder:text-white/50 bg-white/10 rounded border-2 border-white/20 focus:ring focus:ring-plumbus-20"
|
||||
className="py-[9px] px-4 ml-4 placeholder:text-white/50 bg-white/10 rounded border-2 border-white/20 focus:ring focus:ring-plumbus-20"
|
||||
onChange={(e) => setSelectedMintToken(tokensList.find((t) => t.displayName === e.target.value))}
|
||||
value={selectedMintToken?.displayName}
|
||||
>
|
||||
{openEditionMinterList
|
||||
.filter((minter) => minter.factoryAddress !== undefined && minter.updatable === false)
|
||||
@@ -122,12 +183,96 @@ export const MintingDetails = ({
|
||||
</div>
|
||||
|
||||
<NumberInput {...perAddressLimitState} isRequired />
|
||||
<FormControl htmlId="timestamp" isRequired subtitle="Minting start time (local)" title="Start Time">
|
||||
<InputDateTime minDate={new Date()} onChange={(date) => setTimestamp(date)} value={timestamp} />
|
||||
</FormControl>
|
||||
<FormControl htmlId="endTimestamp" isRequired subtitle="Minting end time (local)" title="End Time">
|
||||
<InputDateTime minDate={new Date()} onChange={(date) => setEndTimestamp(date)} value={endTimestamp} />
|
||||
<FormControl
|
||||
htmlId="timestamp"
|
||||
isRequired
|
||||
subtitle={`Minting start time ${timezone === 'Local' ? '(local)' : '(UTC)'}`}
|
||||
title="Start Time"
|
||||
>
|
||||
<InputDateTime
|
||||
disabled={isPresale}
|
||||
minDate={
|
||||
timezone === 'Local' ? new Date() : new Date(Date.now() + new Date().getTimezoneOffset() * 60 * 1000)
|
||||
}
|
||||
onChange={(date) =>
|
||||
date
|
||||
? setTimestamp(
|
||||
timezone === 'Local' ? date : new Date(date.getTime() - new Date().getTimezoneOffset() * 60 * 1000),
|
||||
)
|
||||
: setTimestamp(undefined)
|
||||
}
|
||||
value={
|
||||
timezone === 'Local'
|
||||
? timestamp
|
||||
: timestamp
|
||||
? new Date(timestamp.getTime() + new Date().getTimezoneOffset() * 60 * 1000)
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
<div className="flex-row mt-2 w-full form-control">
|
||||
<h1 className="mt-2 font-bold text-md">Limit Type: </h1>
|
||||
<label className="justify-start ml-6 cursor-pointer label">
|
||||
<span className="mr-2">Time</span>
|
||||
<input
|
||||
checked={limitType === 'time_limited' || limitType === 'time_and_count_limited'}
|
||||
className={`${limitType === 'time_limited' ? `bg-stargaze` : `bg-gray-600`} checkbox`}
|
||||
onClick={() => {
|
||||
if (limitType === 'time_and_count_limited') setLimitType('count_limited' as LimitType)
|
||||
else if (limitType === 'count_limited') setLimitType('time_and_count_limited' as LimitType)
|
||||
else setLimitType('count_limited' as LimitType)
|
||||
}}
|
||||
type="checkbox"
|
||||
/>
|
||||
</label>
|
||||
<label className="justify-start ml-4 cursor-pointer label">
|
||||
<span className="mr-2">Token Count</span>
|
||||
<input
|
||||
checked={limitType === 'count_limited' || limitType === 'time_and_count_limited'}
|
||||
className={`${limitType === 'count_limited' ? `bg-stargaze` : `bg-gray-600`} checkbox`}
|
||||
onClick={() => {
|
||||
if (limitType === 'time_and_count_limited') setLimitType('time_limited' as LimitType)
|
||||
else if (limitType === 'time_limited') setLimitType('time_and_count_limited' as LimitType)
|
||||
else setLimitType('time_limited' as LimitType)
|
||||
}}
|
||||
type="checkbox"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<Conditional test={limitType === 'time_limited' || limitType === 'time_and_count_limited'}>
|
||||
<FormControl
|
||||
htmlId="endTimestamp"
|
||||
isRequired
|
||||
subtitle={`Minting end time ${timezone === 'Local' ? '(local)' : '(UTC)'}`}
|
||||
title="End Time"
|
||||
>
|
||||
<InputDateTime
|
||||
minDate={
|
||||
timezone === 'Local' ? new Date() : new Date(Date.now() + new Date().getTimezoneOffset() * 60 * 1000)
|
||||
}
|
||||
onChange={(date) =>
|
||||
date
|
||||
? setEndTimestamp(
|
||||
timezone === 'Local'
|
||||
? date
|
||||
: new Date(date.getTime() - new Date().getTimezoneOffset() * 60 * 1000),
|
||||
)
|
||||
: setEndTimestamp(undefined)
|
||||
}
|
||||
value={
|
||||
timezone === 'Local'
|
||||
? endTimestamp
|
||||
: endTimestamp
|
||||
? new Date(endTimestamp.getTime() + new Date().getTimezoneOffset() * 60 * 1000)
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
</FormControl>
|
||||
</Conditional>
|
||||
<Conditional test={limitType === 'count_limited' || limitType === 'time_and_count_limited'}>
|
||||
<NumberInput {...tokenCountLimitState} isRequired />
|
||||
</Conditional>
|
||||
</FormGroup>
|
||||
<TextInput className="pr-4 pl-4 mt-3" {...paymentAddressState} />
|
||||
</div>
|
||||
|
||||
@@ -18,6 +18,9 @@ import type { ChangeEvent } from 'react'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { toast } from 'react-hot-toast'
|
||||
import type { UploadServiceType } from 'services/upload'
|
||||
import { NFT_STORAGE_DEFAULT_API_KEY } from 'utils/constants'
|
||||
import type { AssetType } from 'utils/getAssetType'
|
||||
import { getAssetType } from 'utils/getAssetType'
|
||||
import { uid } from 'utils/random'
|
||||
import { naturalCompare } from 'utils/sort'
|
||||
|
||||
@@ -28,11 +31,14 @@ export type UploadMethod = 'new' | 'existing'
|
||||
interface OffChainMetadataUploadDetailsProps {
|
||||
onChange: (value: OffChainMetadataUploadDetailsDataProps) => void
|
||||
metadataStorageMethod?: MetadataStorageMethod
|
||||
importedOffChainMetadataUploadDetails?: OffChainMetadataUploadDetailsDataProps
|
||||
}
|
||||
|
||||
export interface OffChainMetadataUploadDetailsDataProps {
|
||||
assetFiles: File[]
|
||||
metadataFiles: File[]
|
||||
thumbnailFile?: File
|
||||
isThumbnailCompatible?: boolean
|
||||
uploadService: UploadServiceType
|
||||
nftStorageApiKey?: string
|
||||
pinataApiKey?: string
|
||||
@@ -41,23 +47,31 @@ export interface OffChainMetadataUploadDetailsDataProps {
|
||||
tokenURI?: string
|
||||
imageUrl?: string
|
||||
openEditionMinterMetadataFile?: File
|
||||
exportedMetadata?: any
|
||||
}
|
||||
|
||||
export const OffChainMetadataUploadDetails = ({
|
||||
onChange,
|
||||
metadataStorageMethod,
|
||||
importedOffChainMetadataUploadDetails,
|
||||
}: OffChainMetadataUploadDetailsProps) => {
|
||||
const [assetFilesArray, setAssetFilesArray] = useState<File[]>([])
|
||||
const [metadataFilesArray, setMetadataFilesArray] = useState<File[]>([])
|
||||
const [thumbnailFile, setThumbnailFile] = useState<File>()
|
||||
const [isThumbnailCompatible, setIsThumbnailCompatible] = useState<boolean>(false)
|
||||
const [uploadMethod, setUploadMethod] = useState<UploadMethod>('new')
|
||||
const [uploadService, setUploadService] = useState<UploadServiceType>('nft-storage')
|
||||
const [metadataFileArrayIndex, setMetadataFileArrayIndex] = useState(0)
|
||||
const [refreshMetadata, setRefreshMetadata] = useState(false)
|
||||
|
||||
const [exportedMetadata, setExportedMetadata] = useState(undefined)
|
||||
const [openEditionMinterMetadataFile, setOpenEditionMinterMetadataFile] = useState<File | undefined>()
|
||||
const [useDefaultApiKey, setUseDefaultApiKey] = useState(false)
|
||||
|
||||
const thumbnailCompatibleAssetTypes: AssetType[] = ['video', 'audio', 'html', 'document']
|
||||
|
||||
const assetFilesRef = useRef<HTMLInputElement | null>(null)
|
||||
const metadataFilesRef = useRef<HTMLInputElement | null>(null)
|
||||
const thumbnailFilesRef = useRef<HTMLInputElement | null>(null)
|
||||
|
||||
const nftStorageApiKeyState = useInputState({
|
||||
id: 'nft-storage-api-key',
|
||||
@@ -100,7 +114,12 @@ export const OffChainMetadataUploadDetails = ({
|
||||
const selectAssets = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
setAssetFilesArray([])
|
||||
setMetadataFilesArray([])
|
||||
setThumbnailFile(undefined)
|
||||
setIsThumbnailCompatible(false)
|
||||
if (event.target.files === null) return
|
||||
if (thumbnailCompatibleAssetTypes.includes(getAssetType(event.target.files[0].name))) {
|
||||
setIsThumbnailCompatible(true)
|
||||
}
|
||||
let loadedFileCount = 0
|
||||
const files: File[] = []
|
||||
let reader: FileReader
|
||||
@@ -109,7 +128,9 @@ export const OffChainMetadataUploadDetails = ({
|
||||
reader.onload = (e) => {
|
||||
if (!e.target?.result) return toast.error('Error parsing file.')
|
||||
if (!event.target.files) return toast.error('No files selected.')
|
||||
const assetFile = new File([e.target.result], event.target.files[i].name, { type: 'image/jpg' })
|
||||
const assetFile = new File([e.target.result], event.target.files[i].name.replaceAll('#', ''), {
|
||||
type: 'image/jpg',
|
||||
})
|
||||
files.push(assetFile)
|
||||
}
|
||||
reader.readAsArrayBuffer(event.target.files[i])
|
||||
@@ -135,7 +156,9 @@ export const OffChainMetadataUploadDetails = ({
|
||||
reader.onload = async (e) => {
|
||||
if (!e.target?.result) return toast.error('Error parsing file.')
|
||||
if (!event.target.files) return toast.error('No files selected.')
|
||||
const metadataFile = new File([e.target.result], event.target.files[i].name, { type: 'application/json' })
|
||||
const metadataFile = new File([e.target.result], event.target.files[i].name.replaceAll('#', ''), {
|
||||
type: 'application/json',
|
||||
})
|
||||
files.push(metadataFile)
|
||||
try {
|
||||
const parsedMetadata = JSON.parse(await metadataFile.text())
|
||||
@@ -162,6 +185,26 @@ export const OffChainMetadataUploadDetails = ({
|
||||
}
|
||||
}
|
||||
|
||||
const selectThumbnail = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
setThumbnailFile(undefined)
|
||||
if (event.target.files === null) return
|
||||
|
||||
let selectedFile: File
|
||||
const reader = new FileReader()
|
||||
reader.onload = (e) => {
|
||||
if (!event.target.files) return toast.error('No file selected.')
|
||||
if (!e.target?.result) return toast.error('Error parsing file.')
|
||||
selectedFile = new File([e.target.result], event.target.files[0].name.replaceAll('#', ''), { type: 'image/*' })
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||
if (event.target.files[0]) reader.readAsArrayBuffer(event.target.files[0])
|
||||
else return toast.error('No file selected.')
|
||||
reader.onloadend = () => {
|
||||
if (!event.target.files) return toast.error('No file selected.')
|
||||
setThumbnailFile(selectedFile)
|
||||
}
|
||||
}
|
||||
|
||||
const updateMetadataFileIndex = (index: number) => {
|
||||
setMetadataFileArrayIndex(index)
|
||||
setRefreshMetadata((prev) => !prev)
|
||||
@@ -184,6 +227,8 @@ export const OffChainMetadataUploadDetails = ({
|
||||
const data: OffChainMetadataUploadDetailsDataProps = {
|
||||
assetFiles: assetFilesArray,
|
||||
metadataFiles: metadataFilesArray,
|
||||
thumbnailFile,
|
||||
isThumbnailCompatible,
|
||||
uploadService,
|
||||
nftStorageApiKey: nftStorageApiKeyState.value,
|
||||
pinataApiKey: pinataApiKeyState.value,
|
||||
@@ -204,6 +249,7 @@ export const OffChainMetadataUploadDetails = ({
|
||||
.replace(regex, '')
|
||||
.trim(),
|
||||
openEditionMinterMetadataFile,
|
||||
exportedMetadata,
|
||||
}
|
||||
onChange(data)
|
||||
} catch (error: any) {
|
||||
@@ -213,6 +259,8 @@ export const OffChainMetadataUploadDetails = ({
|
||||
}, [
|
||||
assetFilesArray,
|
||||
metadataFilesArray,
|
||||
thumbnailFile,
|
||||
isThumbnailCompatible,
|
||||
uploadService,
|
||||
nftStorageApiKeyState.value,
|
||||
pinataApiKeyState.value,
|
||||
@@ -222,6 +270,7 @@ export const OffChainMetadataUploadDetails = ({
|
||||
coverImageUrlState.value,
|
||||
refreshMetadata,
|
||||
openEditionMinterMetadataFile,
|
||||
exportedMetadata,
|
||||
])
|
||||
|
||||
useEffect(() => {
|
||||
@@ -229,10 +278,35 @@ export const OffChainMetadataUploadDetails = ({
|
||||
setMetadataFilesArray([])
|
||||
if (assetFilesRef.current) assetFilesRef.current.value = ''
|
||||
setAssetFilesArray([])
|
||||
tokenUriState.onChange('')
|
||||
coverImageUrlState.onChange('')
|
||||
setThumbnailFile(undefined)
|
||||
setIsThumbnailCompatible(false)
|
||||
if (!importedOffChainMetadataUploadDetails) {
|
||||
tokenUriState.onChange('')
|
||||
coverImageUrlState.onChange('')
|
||||
}
|
||||
}, [uploadMethod, metadataStorageMethod])
|
||||
|
||||
useEffect(() => {
|
||||
if (importedOffChainMetadataUploadDetails) {
|
||||
setUploadService(importedOffChainMetadataUploadDetails.uploadService)
|
||||
nftStorageApiKeyState.onChange(importedOffChainMetadataUploadDetails.nftStorageApiKey || '')
|
||||
pinataApiKeyState.onChange(importedOffChainMetadataUploadDetails.pinataApiKey || '')
|
||||
pinataSecretKeyState.onChange(importedOffChainMetadataUploadDetails.pinataSecretKey || '')
|
||||
setUploadMethod(importedOffChainMetadataUploadDetails.uploadMethod)
|
||||
tokenUriState.onChange(importedOffChainMetadataUploadDetails.tokenURI || '')
|
||||
coverImageUrlState.onChange(importedOffChainMetadataUploadDetails.imageUrl || '')
|
||||
// setOpenEditionMinterMetadataFile(importedOffChainMetadataUploadDetails.openEditionMinterMetadataFile)
|
||||
}
|
||||
}, [importedOffChainMetadataUploadDetails])
|
||||
|
||||
useEffect(() => {
|
||||
if (useDefaultApiKey) {
|
||||
nftStorageApiKeyState.onChange(NFT_STORAGE_DEFAULT_API_KEY || '')
|
||||
} else {
|
||||
nftStorageApiKeyState.onChange('')
|
||||
}
|
||||
}, [useDefaultApiKey])
|
||||
|
||||
return (
|
||||
<div className="justify-items-start mb-3 rounded border-2 border-white/20 flex-column">
|
||||
<div className="flex justify-center">
|
||||
@@ -351,7 +425,22 @@ export const OffChainMetadataUploadDetails = ({
|
||||
|
||||
<div className="flex w-full">
|
||||
<Conditional test={uploadService === 'nft-storage'}>
|
||||
<TextInput {...nftStorageApiKeyState} className="w-full" />
|
||||
<div className="flex-col w-full">
|
||||
<TextInput {...nftStorageApiKeyState} className="w-full" disabled={useDefaultApiKey} />
|
||||
<div className="flex-row mt-2 w-full form-control">
|
||||
<label className="cursor-pointer label">
|
||||
<span className="mr-2 font-bold">Use Default API Key</span>
|
||||
<input
|
||||
checked={useDefaultApiKey}
|
||||
className={`${useDefaultApiKey ? `bg-stargaze` : `bg-gray-600`} checkbox`}
|
||||
onClick={() => {
|
||||
setUseDefaultApiKey(!useDefaultApiKey)
|
||||
}}
|
||||
type="checkbox"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</Conditional>
|
||||
<Conditional test={uploadService === 'pinata'}>
|
||||
<TextInput {...pinataApiKeyState} className="w-full" />
|
||||
@@ -390,7 +479,7 @@ export const OffChainMetadataUploadDetails = ({
|
||||
)}
|
||||
>
|
||||
<input
|
||||
accept="image/*, audio/*, video/*, .html"
|
||||
accept="image/*, audio/*, video/*, .html, .pdf"
|
||||
className={clsx(
|
||||
'file:py-2 file:px-4 file:mr-4 file:bg-plumbus-light file:rounded file:border-0 cursor-pointer',
|
||||
'before:absolute before:inset-0 before:hover:bg-white/5 before:transition',
|
||||
@@ -402,6 +491,34 @@ export const OffChainMetadataUploadDetails = ({
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<Conditional test={isThumbnailCompatible}>
|
||||
<div>
|
||||
<label
|
||||
className="block mt-5 mr-1 mb-1 ml-8 w-full font-bold text-white dark:text-gray-300"
|
||||
htmlFor="thumbnailFiles"
|
||||
>
|
||||
Thumbnail Selection (optional)
|
||||
</label>
|
||||
<div
|
||||
className={clsx(
|
||||
'flex relative justify-center items-center mx-8 mt-2 space-y-4 w-full h-32',
|
||||
'rounded border-2 border-white/20 border-dashed',
|
||||
)}
|
||||
>
|
||||
<input
|
||||
accept="image/*"
|
||||
className={clsx(
|
||||
'file:py-2 file:px-4 file:mr-4 file:bg-plumbus-light file:rounded file:border-0 cursor-pointer',
|
||||
'before:absolute before:inset-0 before:hover:bg-white/5 before:transition',
|
||||
)}
|
||||
id="thumbnailFiles"
|
||||
onChange={selectThumbnail}
|
||||
ref={thumbnailFilesRef}
|
||||
type="file"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Conditional>
|
||||
|
||||
{assetFilesArray.length > 0 && (
|
||||
<div>
|
||||
@@ -447,6 +564,8 @@ export const OffChainMetadataUploadDetails = ({
|
||||
/>
|
||||
</div>
|
||||
<MetadataInput
|
||||
importedMetadata={importedOffChainMetadataUploadDetails?.exportedMetadata}
|
||||
onChange={setExportedMetadata}
|
||||
selectedAssetFile={assetFilesArray[0]}
|
||||
selectedMetadataFile={metadataFilesArray[0]}
|
||||
updateMetadataToUpload={updateOpenEditionMinterMetadataFile}
|
||||
|
||||
@@ -7,7 +7,6 @@ import clsx from 'clsx'
|
||||
import { Conditional } from 'components/Conditional'
|
||||
import { useInputState } from 'components/forms/FormInput.hooks'
|
||||
import { useMetadataAttributesState } from 'components/forms/MetadataAttributes.hooks'
|
||||
import { useWallet } from 'contexts/wallet'
|
||||
import type { Trait } from 'contracts/badgeHub'
|
||||
import type { ChangeEvent } from 'react'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
@@ -21,6 +20,7 @@ import type { UploadMethod } from './ImageUploadDetails'
|
||||
interface OnChainMetadataInputDetailsProps {
|
||||
onChange: (data: OnChainMetadataInputDetailsDataProps) => void
|
||||
uploadMethod: UploadMethod | undefined
|
||||
importedOnChainMetadataInputDetails?: OnChainMetadataInputDetailsDataProps
|
||||
}
|
||||
|
||||
export interface OnChainMetadataInputDetailsDataProps {
|
||||
@@ -34,8 +34,11 @@ export interface OnChainMetadataInputDetailsDataProps {
|
||||
youtube_url?: string
|
||||
}
|
||||
|
||||
export const OnChainMetadataInputDetails = ({ onChange, uploadMethod }: OnChainMetadataInputDetailsProps) => {
|
||||
const wallet = useWallet()
|
||||
export const OnChainMetadataInputDetails = ({
|
||||
onChange,
|
||||
uploadMethod,
|
||||
importedOnChainMetadataInputDetails,
|
||||
}: OnChainMetadataInputDetailsProps) => {
|
||||
const [timestamp, setTimestamp] = useState<Date | undefined>(undefined)
|
||||
const [metadataFile, setMetadataFile] = useState<File>()
|
||||
const [metadataFeeRate, setMetadataFeeRate] = useState<number>(0)
|
||||
@@ -140,7 +143,9 @@ export const OnChainMetadataInputDetails = ({ onChange, uploadMethod }: OnChainM
|
||||
reader.onload = (e) => {
|
||||
if (!event.target.files) return toast.error('No file selected.')
|
||||
if (!e.target?.result) return toast.error('Error parsing file.')
|
||||
selectedFile = new File([e.target.result], event.target.files[0].name, { type: 'application/json' })
|
||||
selectedFile = new File([e.target.result], event.target.files[0].name.replaceAll('#', ''), {
|
||||
type: 'application/json',
|
||||
})
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||
if (event.target.files[0]) reader.readAsArrayBuffer(event.target.files[0])
|
||||
@@ -196,6 +201,26 @@ export const OnChainMetadataInputDetails = ({ onChange, uploadMethod }: OnChainM
|
||||
youtubeUrlState.value,
|
||||
])
|
||||
|
||||
useEffect(() => {
|
||||
if (importedOnChainMetadataInputDetails) {
|
||||
nameState.onChange(importedOnChainMetadataInputDetails.name || '')
|
||||
descriptionState.onChange(importedOnChainMetadataInputDetails.description || '')
|
||||
externalUrlState.onChange(importedOnChainMetadataInputDetails.external_url || '')
|
||||
youtubeUrlState.onChange(importedOnChainMetadataInputDetails.youtube_url || '')
|
||||
animationUrlState.onChange(importedOnChainMetadataInputDetails.animation_url || '')
|
||||
imageDataState.onChange(importedOnChainMetadataInputDetails.image_data || '')
|
||||
if (importedOnChainMetadataInputDetails.attributes) {
|
||||
attributesState.reset()
|
||||
importedOnChainMetadataInputDetails.attributes.forEach((attr) => {
|
||||
attributesState.add({
|
||||
trait_type: attr.trait_type,
|
||||
value: attr.value,
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}, [importedOnChainMetadataInputDetails])
|
||||
|
||||
return (
|
||||
<div className="py-3 px-8 rounded border-2 border-white/20">
|
||||
<span className="ml-4 text-xl font-bold underline underline-offset-4">NFT Metadata</span>
|
||||
|
||||
@@ -5,38 +5,43 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
import { toUtf8 } from '@cosmjs/encoding'
|
||||
import type { Coin } from '@cosmjs/proto-signing'
|
||||
import { coin } from '@cosmjs/proto-signing'
|
||||
import axios from 'axios'
|
||||
import clsx from 'clsx'
|
||||
import { Button } from 'components/Button'
|
||||
import type { MinterType } from 'components/collections/actions/Combobox'
|
||||
import { Conditional } from 'components/Conditional'
|
||||
import { ConfirmationModal } from 'components/ConfirmationModal'
|
||||
import { LoadingModal } from 'components/LoadingModal'
|
||||
import { openEditionMinterList } from 'config/minter'
|
||||
import type { TokenInfo } from 'config/token'
|
||||
import { type TokenInfo, tokensList } from 'config/token'
|
||||
import { useContracts } from 'contexts/contracts'
|
||||
import { addLogItem } from 'contexts/log'
|
||||
import { useWallet } from 'contexts/wallet'
|
||||
import type { DispatchExecuteArgs as OpenEditionFactoryDispatchExecuteArgs } from 'contracts/openEditionFactory/messages/execute'
|
||||
import { dispatchExecute as openEditionFactoryDispatchExecute } from 'contracts/openEditionFactory/messages/execute'
|
||||
import React, { useEffect, useMemo, useState } from 'react'
|
||||
import { toast } from 'react-hot-toast'
|
||||
import { upload } from 'services/upload'
|
||||
import {
|
||||
OPEN_EDITION_FACTORY_ADDRESS,
|
||||
OPEN_EDITION_UPDATABLE_FACTORY_ADDRESS,
|
||||
SG721_OPEN_EDITION_CODE_ID,
|
||||
SG721_OPEN_EDITION_UPDATABLE_CODE_ID,
|
||||
STRDST_SG721_CODE_ID,
|
||||
WHITELIST_CODE_ID,
|
||||
WHITELIST_FLEX_CODE_ID,
|
||||
WHITELIST_MERKLE_TREE_API_URL,
|
||||
WHITELIST_MERKLE_TREE_CODE_ID,
|
||||
} from 'utils/constants'
|
||||
import { getAssetType } from 'utils/getAssetType'
|
||||
import { useDebounce } from 'utils/debounce'
|
||||
import type { AssetType } from 'utils/getAssetType'
|
||||
import { isValidAddress } from 'utils/isValidAddress'
|
||||
import { checkTokenUri } from 'utils/isValidTokenUri'
|
||||
import { uid } from 'utils/random'
|
||||
import { useWallet } from 'utils/wallet'
|
||||
|
||||
import { type CollectionDetailsDataProps, CollectionDetails } from './CollectionDetails'
|
||||
import type { ImageUploadDetailsDataProps } from './ImageUploadDetails'
|
||||
import { ImageUploadDetails } from './ImageUploadDetails'
|
||||
import type { MintingDetailsDataProps } from './MintingDetails'
|
||||
import type { LimitType, MintingDetailsDataProps } from './MintingDetails'
|
||||
import { MintingDetails } from './MintingDetails'
|
||||
import type { UploadMethod } from './OffChainMetadataUploadDetails'
|
||||
import {
|
||||
@@ -46,34 +51,43 @@ import {
|
||||
import type { OnChainMetadataInputDetailsDataProps } from './OnChainMetadataInputDetails'
|
||||
import { OnChainMetadataInputDetails } from './OnChainMetadataInputDetails'
|
||||
import { type RoyaltyDetailsDataProps, RoyaltyDetails } from './RoyaltyDetails'
|
||||
import { type WhitelistDetailsDataProps, WhitelistDetails } from './WhitelistDetails'
|
||||
|
||||
export type MetadataStorageMethod = 'off-chain' | 'on-chain'
|
||||
|
||||
export interface OpenEditionMinterDetailsDataProps {
|
||||
imageUploadDetails?: ImageUploadDetailsDataProps
|
||||
collectionDetails?: CollectionDetailsDataProps
|
||||
whitelistDetails?: WhitelistDetailsDataProps
|
||||
royaltyDetails?: RoyaltyDetailsDataProps
|
||||
onChainMetadataInputDetails?: OnChainMetadataInputDetailsDataProps
|
||||
offChainMetadataUploadDetails?: OffChainMetadataUploadDetailsDataProps
|
||||
mintingDetails?: MintingDetailsDataProps
|
||||
metadataStorageMethod?: MetadataStorageMethod
|
||||
openEditionMinterContractAddress?: string | null
|
||||
coverImageUrl?: string | null
|
||||
tokenUri?: string | null
|
||||
tokenImageUri?: string | null
|
||||
isRefreshed?: boolean
|
||||
}
|
||||
|
||||
interface OpenEditionMinterCreatorProps {
|
||||
onChange: (data: OpenEditionMinterCreatorDataProps) => void
|
||||
onDetailsChange: (data: OpenEditionMinterDetailsDataProps) => void
|
||||
openEditionMinterUpdatableCreationFee?: string
|
||||
openEditionMinterCreationFee?: string
|
||||
openEditionMinterCreationFee?: Coin
|
||||
minimumMintPrice?: string
|
||||
minimumUpdatableMintPrice?: string
|
||||
minterType?: MinterType
|
||||
mintTokenFromFactory?: TokenInfo | undefined
|
||||
importedOpenEditionMinterDetails?: OpenEditionMinterDetailsDataProps
|
||||
isMatchingFactoryPresent?: boolean
|
||||
openEditionFactoryAddress?: string
|
||||
}
|
||||
|
||||
export interface OpenEditionMinterCreatorDataProps {
|
||||
metadataStorageMethod: MetadataStorageMethod
|
||||
openEditionMinterContractAddress: string | null
|
||||
sg721ContractAddress: string | null
|
||||
whitelistContractAddress: string | null
|
||||
transactionHash: string | null
|
||||
}
|
||||
|
||||
@@ -81,20 +95,27 @@ export const OpenEditionMinterCreator = ({
|
||||
onChange,
|
||||
onDetailsChange,
|
||||
openEditionMinterCreationFee,
|
||||
openEditionMinterUpdatableCreationFee,
|
||||
minimumMintPrice,
|
||||
minimumUpdatableMintPrice,
|
||||
minterType,
|
||||
mintTokenFromFactory,
|
||||
importedOpenEditionMinterDetails,
|
||||
isMatchingFactoryPresent,
|
||||
openEditionFactoryAddress,
|
||||
}: OpenEditionMinterCreatorProps) => {
|
||||
const wallet = useWallet()
|
||||
const { openEditionMinter: openEditionMinterContract, openEditionFactory: openEditionFactoryContract } =
|
||||
useContracts()
|
||||
const {
|
||||
openEditionMinter: openEditionMinterContract,
|
||||
openEditionFactory: openEditionFactoryContract,
|
||||
whitelist: whitelistContract,
|
||||
whitelistMerkleTree: whitelistMerkleTreeContract,
|
||||
} = useContracts()
|
||||
|
||||
const [metadataStorageMethod, setMetadataStorageMethod] = useState<MetadataStorageMethod>('off-chain')
|
||||
const [imageUploadDetails, setImageUploadDetails] = useState<ImageUploadDetailsDataProps | null>(null)
|
||||
const [collectionDetails, setCollectionDetails] = useState<CollectionDetailsDataProps | null>(null)
|
||||
const [whitelistDetails, setWhitelistDetails] = useState<WhitelistDetailsDataProps | null>(null)
|
||||
const [royaltyDetails, setRoyaltyDetails] = useState<RoyaltyDetailsDataProps | null>(null)
|
||||
const [isRefreshed, setIsRefreshed] = useState(false)
|
||||
const [onChainMetadataInputDetails, setOnChainMetadataInputDetails] =
|
||||
useState<OnChainMetadataInputDetailsDataProps | null>(null)
|
||||
const [offChainMetadataUploadDetails, setOffChainMetadataUploadDetails] =
|
||||
@@ -109,21 +130,21 @@ export const OpenEditionMinterCreator = ({
|
||||
const [coverImageUrl, setCoverImageUrl] = useState<string | null>(null)
|
||||
const [openEditionMinterContractAddress, setOpenEditionMinterContractAddress] = useState<string | null>(null)
|
||||
const [sg721ContractAddress, setSg721ContractAddress] = useState<string | null>(null)
|
||||
const [whitelistContractAddress, setWhitelistContractAddress] = useState<string | null>(null)
|
||||
const [transactionHash, setTransactionHash] = useState<string | null>(null)
|
||||
const [thumbnailImageUri, setThumbnailImageUri] = useState<string | undefined>(undefined)
|
||||
|
||||
const factoryAddressForSelectedDenom =
|
||||
openEditionMinterList.find((minter) => minter.supportedToken === mintTokenFromFactory && minter.updatable === false)
|
||||
?.factoryAddress || OPEN_EDITION_FACTORY_ADDRESS
|
||||
const updatableFactoryAddressForSelectedDenom =
|
||||
openEditionMinterList.find((minter) => minter.supportedToken === mintTokenFromFactory && minter.updatable === true)
|
||||
?.factoryAddress || OPEN_EDITION_UPDATABLE_FACTORY_ADDRESS
|
||||
const thumbnailCompatibleAssetTypes: AssetType[] = ['video', 'audio', 'html']
|
||||
|
||||
const openEditionFactoryMessages = useMemo(
|
||||
() =>
|
||||
openEditionFactoryContract?.use(
|
||||
collectionDetails?.updatable ? updatableFactoryAddressForSelectedDenom : factoryAddressForSelectedDenom,
|
||||
),
|
||||
[openEditionFactoryContract, wallet.address],
|
||||
() => openEditionFactoryContract?.use(openEditionFactoryAddress as string),
|
||||
[
|
||||
openEditionFactoryContract,
|
||||
wallet.address,
|
||||
collectionDetails?.updatable,
|
||||
openEditionFactoryAddress,
|
||||
wallet.isWalletConnected,
|
||||
],
|
||||
)
|
||||
|
||||
const performOpenEditionMinterChecks = () => {
|
||||
@@ -135,13 +156,26 @@ export const OpenEditionMinterCreator = ({
|
||||
.then(() => {
|
||||
void checkRoyaltyDetails()
|
||||
.then(() => {
|
||||
void checkwalletBalance()
|
||||
checkWhitelistDetails()
|
||||
.then(() => {
|
||||
setReadyToCreate(true)
|
||||
void checkwalletBalance()
|
||||
.then(() => {
|
||||
setReadyToCreate(true)
|
||||
})
|
||||
.catch((error: any) => {
|
||||
toast.error(`Error in Wallet Balance: ${error.message}`, { style: { maxWidth: 'none' } })
|
||||
addLogItem({ id: uid(), message: error.message, type: 'Error', timestamp: new Date() })
|
||||
setReadyToCreate(false)
|
||||
})
|
||||
})
|
||||
.catch((error: any) => {
|
||||
toast.error(`Error in Wallet Balance: ${error.message}`, { style: { maxWidth: 'none' } })
|
||||
addLogItem({ id: uid(), message: error.message, type: 'Error', timestamp: new Date() })
|
||||
.catch((error) => {
|
||||
if (String(error.message).includes('Insufficient wallet balance')) {
|
||||
toast.error(`${error.message}`, { style: { maxWidth: 'none' } })
|
||||
addLogItem({ id: uid(), message: error.message, type: 'Error', timestamp: new Date() })
|
||||
} else {
|
||||
toast.error(`Error in Whitelist Configuration: ${error.message}`, { style: { maxWidth: 'none' } })
|
||||
addLogItem({ id: uid(), message: error.message, type: 'Error', timestamp: new Date() })
|
||||
}
|
||||
setReadyToCreate(false)
|
||||
})
|
||||
})
|
||||
@@ -165,7 +199,7 @@ export const OpenEditionMinterCreator = ({
|
||||
}
|
||||
|
||||
const checkUploadDetails = async () => {
|
||||
if (!wallet.initialized) throw new Error('Wallet not connected.')
|
||||
if (!wallet.isWalletConnected) throw new Error('Wallet not connected.')
|
||||
if (
|
||||
(metadataStorageMethod === 'off-chain' && !offChainMetadataUploadDetails) ||
|
||||
(metadataStorageMethod === 'on-chain' && !imageUploadDetails)
|
||||
@@ -282,9 +316,9 @@ export const OpenEditionMinterCreator = ({
|
||||
if (!mintingDetails) throw new Error('Please fill out the minting details')
|
||||
if (mintingDetails.unitPrice === '') throw new Error('Mint price is required')
|
||||
if (collectionDetails?.updatable) {
|
||||
if (Number(mintingDetails.unitPrice) < Number(minimumUpdatableMintPrice))
|
||||
if (Number(mintingDetails.unitPrice) < Number(minimumMintPrice))
|
||||
throw new Error(
|
||||
`Invalid mint price: The minimum mint price is ${Number(minimumUpdatableMintPrice) / 1000000} ${
|
||||
`Invalid mint price: The minimum mint price is ${Number(minimumMintPrice) / 1000000} ${
|
||||
mintTokenFromFactory?.displayName
|
||||
}`,
|
||||
)
|
||||
@@ -297,12 +331,120 @@ export const OpenEditionMinterCreator = ({
|
||||
if (!mintingDetails.perAddressLimit || mintingDetails.perAddressLimit < 1 || mintingDetails.perAddressLimit > 50)
|
||||
throw new Error('Invalid limit for tokens per address')
|
||||
if (mintingDetails.startTime === '') throw new Error('Start time is required')
|
||||
if (mintingDetails.limitType === 'time_limited' && mintingDetails.endTime === '')
|
||||
throw new Error('End time is required')
|
||||
if (mintingDetails.limitType === 'count_limited' && mintingDetails.tokenCountLimit === undefined)
|
||||
throw new Error('Token count limit is required')
|
||||
if (
|
||||
mintingDetails.limitType === 'count_limited' &&
|
||||
mintingDetails.perAddressLimit > (mintingDetails.tokenCountLimit as number)
|
||||
)
|
||||
throw new Error('Per address limit cannot exceed maximum token count limit')
|
||||
if (mintingDetails.limitType === 'count_limited' && (mintingDetails.tokenCountLimit as number) > 10000)
|
||||
throw new Error('Maximum token count cannot exceed 10000')
|
||||
if (Number(mintingDetails.startTime) < new Date().getTime() * 1000000) throw new Error('Invalid start time')
|
||||
if (
|
||||
mintingDetails.limitType === 'time_limited' &&
|
||||
Number(mintingDetails.endTime) < Number(mintingDetails.startTime)
|
||||
)
|
||||
throw new Error('End time cannot be earlier than start time')
|
||||
if (
|
||||
mintingDetails.limitType === 'time_limited' &&
|
||||
Number(mintingDetails.endTime) === Number(mintingDetails.startTime)
|
||||
)
|
||||
throw new Error('End time cannot be equal to the start time')
|
||||
|
||||
if (
|
||||
mintingDetails.paymentAddress &&
|
||||
(!isValidAddress(mintingDetails.paymentAddress) || !mintingDetails.paymentAddress.startsWith('stars1'))
|
||||
)
|
||||
throw new Error('Invalid payment address')
|
||||
|
||||
if (!isMatchingFactoryPresent)
|
||||
throw new Error(
|
||||
`No matching open edition factory contract found for the selected parameters (Mint Price Denom: ${mintingDetails.selectedMintToken?.displayName}, Whitelist Type: ${whitelistDetails?.whitelistType})`,
|
||||
)
|
||||
}
|
||||
|
||||
const checkWhitelistDetails = async () => {
|
||||
if (!whitelistDetails) throw new Error('Please fill out the whitelist details')
|
||||
if (whitelistDetails.whitelistState === 'existing') {
|
||||
if (whitelistDetails.contractAddress === '') throw new Error('Whitelist contract address is required')
|
||||
else {
|
||||
const contract = whitelistContract?.use(whitelistDetails.contractAddress)
|
||||
//check if the address belongs to a whitelist contract (see performChecks())
|
||||
const config = await contract?.config()
|
||||
if (JSON.stringify(config).includes('whale_cap')) whitelistDetails.whitelistType = 'flex'
|
||||
else if (!JSON.stringify(config).includes('member_limit') || config?.member_limit === 0) {
|
||||
// whitelistDetails.whitelistType = 'merkletree'
|
||||
throw new Error(
|
||||
'Whitelist Merkle Tree is not supported yet. Please use a standard or flexible whitelist contract.',
|
||||
)
|
||||
} else whitelistDetails.whitelistType = 'standard'
|
||||
if (Number(config?.start_time) !== Number(mintingDetails?.startTime)) {
|
||||
const whitelistStartDate = new Date(Number(config?.start_time) / 1000000)
|
||||
throw Error(`Whitelist start time (${whitelistStartDate.toLocaleString()}) does not match minting start time`)
|
||||
}
|
||||
|
||||
if (mintingDetails?.tokenCountLimit && config?.per_address_limit) {
|
||||
if (mintingDetails.tokenCountLimit >= 100 && Number(config.per_address_limit) > 50) {
|
||||
throw Error(
|
||||
`Invalid limit for tokens per address (${config.per_address_limit} tokens). Tokens per address limit cannot exceed 50 regardless of the total number of tokens.`,
|
||||
)
|
||||
} else if (
|
||||
mintingDetails.tokenCountLimit >= 100 &&
|
||||
Number(config.per_address_limit) > Math.ceil((mintingDetails.tokenCountLimit / 100) * 3)
|
||||
) {
|
||||
throw Error(
|
||||
`Invalid limit for tokens per address (${config.per_address_limit} tokens). Tokens per address limit cannot exceed 3% of the total number of tokens in the collection.`,
|
||||
)
|
||||
} else if (mintingDetails.tokenCountLimit < 100 && Number(config.per_address_limit) > 3) {
|
||||
throw Error(
|
||||
`Invalid limit for tokens per address (${config.per_address_limit} tokens). Tokens per address limit cannot exceed 3 for collections with a token count limit smaller than 100 tokens.`,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (whitelistDetails.whitelistState === 'new') {
|
||||
if (whitelistDetails.members?.length === 0) throw new Error('Whitelist member list cannot be empty')
|
||||
if (whitelistDetails.unitPrice === undefined) throw new Error('Whitelist unit price is required')
|
||||
if (Number(whitelistDetails.unitPrice) < 0)
|
||||
throw new Error('Invalid unit price: The unit price cannot be negative')
|
||||
if (whitelistDetails.startTime === '') throw new Error('Start time is required')
|
||||
if (whitelistDetails.endTime === '') throw new Error('End time is required')
|
||||
if (
|
||||
whitelistDetails.whitelistType === 'standard' &&
|
||||
(!whitelistDetails.perAddressLimit || whitelistDetails.perAddressLimit === 0)
|
||||
)
|
||||
throw new Error('Per address limit is required')
|
||||
if (
|
||||
whitelistDetails.whitelistType !== 'merkletree' &&
|
||||
(!whitelistDetails.memberLimit || whitelistDetails.memberLimit === 0)
|
||||
)
|
||||
throw new Error('Member limit is required')
|
||||
if (Number(whitelistDetails.startTime) >= Number(whitelistDetails.endTime))
|
||||
throw new Error('Whitelist start time cannot be equal to or later than the whitelist end time')
|
||||
if (Number(whitelistDetails.startTime) !== Number(mintingDetails?.startTime))
|
||||
throw new Error('Whitelist start time must be the same as the minting start time')
|
||||
if (whitelistDetails.perAddressLimit && mintingDetails?.tokenCountLimit) {
|
||||
if (mintingDetails.tokenCountLimit >= 100 && whitelistDetails.perAddressLimit > 50) {
|
||||
throw Error(
|
||||
`Invalid limit for tokens per address. Tokens per address limit cannot exceed 50 regardless of the total number of tokens.`,
|
||||
)
|
||||
} else if (
|
||||
mintingDetails.tokenCountLimit >= 100 &&
|
||||
whitelistDetails.perAddressLimit > Math.ceil((mintingDetails.tokenCountLimit / 100) * 3)
|
||||
) {
|
||||
throw Error(
|
||||
`Invalid limit for tokens per address. Tokens per address limit cannot exceed 3% of the total number of tokens in the collection.`,
|
||||
)
|
||||
} else if (mintingDetails.tokenCountLimit < 100 && whitelistDetails.perAddressLimit > 3) {
|
||||
throw Error(
|
||||
`Invalid limit for tokens per address. Tokens per address limit cannot exceed 3 for collections with a token count limit smaller than 100 tokens.`,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const checkRoyaltyDetails = async () => {
|
||||
@@ -317,8 +459,8 @@ export const OpenEditionMinterCreator = ({
|
||||
}
|
||||
throw new Error('Invalid royalty payment address')
|
||||
}
|
||||
const contractInfoResponse = await wallet.client
|
||||
?.queryContractRaw(
|
||||
const contractInfoResponse = await (await wallet.getCosmWasmClient())
|
||||
.queryContractRaw(
|
||||
royaltyDetails.paymentAddress.trim(),
|
||||
toUtf8(Buffer.from(Buffer.from('contract_info').toString('hex'), 'hex').toString()),
|
||||
)
|
||||
@@ -330,25 +472,54 @@ export const OpenEditionMinterCreator = ({
|
||||
if (contractInfoResponse !== undefined) {
|
||||
const contractInfo = JSON.parse(new TextDecoder().decode(contractInfoResponse as Uint8Array))
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
||||
if (contractInfo && !contractInfo.contract.includes('splits'))
|
||||
throw new Error('The provided royalty payment address does not belong to a splits contract.')
|
||||
if (contractInfo && (contractInfo.contract.includes('minter') || contractInfo.contract.includes('sg721')))
|
||||
throw new Error('The provided royalty payment address does not belong to a compatible contract.')
|
||||
else console.log(contractInfo)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const checkwalletBalance = async () => {
|
||||
if (!wallet.initialized) throw new Error('Wallet not connected.')
|
||||
const amountNeeded = collectionDetails?.updatable
|
||||
? Number(openEditionMinterUpdatableCreationFee)
|
||||
: Number(openEditionMinterCreationFee)
|
||||
await wallet.client?.getBalance(wallet.address, 'ustars').then((balance) => {
|
||||
if (amountNeeded >= Number(balance.amount))
|
||||
throw new Error(
|
||||
`Insufficient wallet balance to instantiate the required contracts. Needed amount: ${(
|
||||
amountNeeded / 1000000
|
||||
).toString()} STARS`,
|
||||
)
|
||||
if (!wallet.isWalletConnected) throw new Error('Wallet not connected.')
|
||||
const queryClient = await wallet.getCosmWasmClient()
|
||||
const creationFeeDenom = tokensList.find((token) => token.denom === openEditionMinterCreationFee?.denom)
|
||||
await queryClient.getBalance(wallet.address || '', 'ustars').then(async (starsBalance) => {
|
||||
await queryClient
|
||||
.getBalance(wallet.address || '', openEditionMinterCreationFee?.denom as string)
|
||||
.then((creationFeeDenomBalance) => {
|
||||
if (whitelistDetails?.whitelistState === 'new' && whitelistDetails.memberLimit) {
|
||||
const whitelistCreationFee = Math.ceil(Number(whitelistDetails.memberLimit) / 1000) * 100000000
|
||||
if (openEditionMinterCreationFee?.denom === 'ustars') {
|
||||
const amountNeeded = whitelistCreationFee + Number(openEditionMinterCreationFee.amount)
|
||||
if (amountNeeded >= Number(starsBalance.amount))
|
||||
throw new Error(
|
||||
`Insufficient wallet balance to instantiate the required contracts. Needed amount: ${(
|
||||
amountNeeded / 1000000
|
||||
).toString()} STARS`,
|
||||
)
|
||||
} else {
|
||||
if (whitelistCreationFee >= Number(starsBalance.amount))
|
||||
throw new Error(
|
||||
`Insufficient wallet balance to instantiate the whitelist. Needed amount: ${(
|
||||
whitelistCreationFee / 1000000
|
||||
).toString()} STARS`,
|
||||
)
|
||||
if (Number(openEditionMinterCreationFee?.amount) > Number(creationFeeDenomBalance.amount))
|
||||
throw new Error(
|
||||
`Insufficient wallet balance to instantiate the required contracts. Needed amount: ${(
|
||||
Number(openEditionMinterCreationFee?.amount) / 1000000
|
||||
).toString()} ${
|
||||
creationFeeDenom ? creationFeeDenom.displayName : openEditionMinterCreationFee?.denom
|
||||
}`,
|
||||
)
|
||||
}
|
||||
} else if (Number(openEditionMinterCreationFee?.amount) > Number(creationFeeDenomBalance.amount))
|
||||
throw new Error(
|
||||
`Insufficient wallet balance to instantiate the required contracts. Needed amount: ${(
|
||||
Number(openEditionMinterCreationFee?.amount) / 1000000
|
||||
).toString()} ${creationFeeDenom ? creationFeeDenom.displayName : openEditionMinterCreationFee?.denom}`,
|
||||
)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -360,6 +531,7 @@ export const OpenEditionMinterCreator = ({
|
||||
setTokenImageUri(null)
|
||||
setOpenEditionMinterContractAddress(null)
|
||||
setSg721ContractAddress(null)
|
||||
setWhitelistContractAddress(null)
|
||||
setTransactionHash(null)
|
||||
if (metadataStorageMethod === 'off-chain') {
|
||||
if (offChainMetadataUploadDetails?.uploadMethod === 'new') {
|
||||
@@ -384,13 +556,27 @@ export const OpenEditionMinterCreator = ({
|
||||
setTokenUri(metadataUriWithBase)
|
||||
setCoverImageUrl(coverImageUriWithBase)
|
||||
setUploading(false)
|
||||
await instantiateOpenEditionMinter(metadataUriWithBase, coverImageUriWithBase)
|
||||
|
||||
let whitelist: string | undefined
|
||||
if (whitelistDetails?.whitelistState === 'existing') whitelist = whitelistDetails.contractAddress
|
||||
else if (whitelistDetails?.whitelistState === 'new') whitelist = await instantiateWhitelist()
|
||||
setWhitelistContractAddress(whitelist as string)
|
||||
|
||||
await instantiateOpenEditionMinter(metadataUriWithBase, coverImageUriWithBase, undefined, whitelist)
|
||||
} else {
|
||||
setTokenUri(offChainMetadataUploadDetails?.tokenURI as string)
|
||||
setCoverImageUrl(offChainMetadataUploadDetails?.imageUrl as string)
|
||||
|
||||
let whitelist: string | undefined
|
||||
if (whitelistDetails?.whitelistState === 'existing') whitelist = whitelistDetails.contractAddress
|
||||
else if (whitelistDetails?.whitelistState === 'new') whitelist = await instantiateWhitelist()
|
||||
setWhitelistContractAddress(whitelist as string)
|
||||
|
||||
await instantiateOpenEditionMinter(
|
||||
offChainMetadataUploadDetails?.tokenURI as string,
|
||||
offChainMetadataUploadDetails?.imageUrl as string,
|
||||
undefined,
|
||||
whitelist,
|
||||
)
|
||||
}
|
||||
} else if (metadataStorageMethod === 'on-chain') {
|
||||
@@ -418,14 +604,41 @@ export const OpenEditionMinterCreator = ({
|
||||
const coverImageUriWithBase = `ipfs://${coverImageUri}/${(collectionDetails?.imageFile as File[])[0].name}`
|
||||
setCoverImageUrl(coverImageUriWithBase)
|
||||
|
||||
let thumbnailUri: string | undefined
|
||||
if (imageUploadDetails.isThumbnailCompatible && imageUploadDetails.thumbnailFile)
|
||||
thumbnailUri = await upload(
|
||||
[imageUploadDetails.thumbnailFile] as File[],
|
||||
imageUploadDetails.uploadService,
|
||||
'thumbnail',
|
||||
imageUploadDetails.nftStorageApiKey as string,
|
||||
imageUploadDetails.pinataApiKey as string,
|
||||
imageUploadDetails.pinataSecretKey as string,
|
||||
)
|
||||
const thumbnailUriWithBase = thumbnailUri
|
||||
? `ipfs://${thumbnailUri}/${(imageUploadDetails.thumbnailFile as File).name}`
|
||||
: undefined
|
||||
setThumbnailImageUri(thumbnailUriWithBase)
|
||||
setUploading(false)
|
||||
await instantiateOpenEditionMinter(imageUriWithBase, coverImageUriWithBase)
|
||||
|
||||
let whitelist: string | undefined
|
||||
if (whitelistDetails?.whitelistState === 'existing') whitelist = whitelistDetails.contractAddress
|
||||
else if (whitelistDetails?.whitelistState === 'new') whitelist = await instantiateWhitelist()
|
||||
setWhitelistContractAddress(whitelist as string)
|
||||
|
||||
await instantiateOpenEditionMinter(imageUriWithBase, coverImageUriWithBase, thumbnailUriWithBase, whitelist)
|
||||
} else if (imageUploadDetails?.uploadMethod === 'existing') {
|
||||
setTokenImageUri(imageUploadDetails.imageUrl as string)
|
||||
setCoverImageUrl(imageUploadDetails.coverImageUrl as string)
|
||||
|
||||
let whitelist: string | undefined
|
||||
if (whitelistDetails?.whitelistState === 'existing') whitelist = whitelistDetails.contractAddress
|
||||
else if (whitelistDetails?.whitelistState === 'new') whitelist = await instantiateWhitelist()
|
||||
setWhitelistContractAddress(whitelist as string)
|
||||
|
||||
await instantiateOpenEditionMinter(
|
||||
imageUploadDetails.imageUrl as string,
|
||||
imageUploadDetails.coverImageUrl as string,
|
||||
whitelist,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -451,23 +664,40 @@ export const OpenEditionMinterCreator = ({
|
||||
offChainMetadataUploadDetails.pinataApiKey as string,
|
||||
offChainMetadataUploadDetails.pinataSecretKey as string,
|
||||
)
|
||||
.then((assetUri: string) => {
|
||||
.then(async (assetUri: string) => {
|
||||
let thumbnailUri: string | undefined
|
||||
if (offChainMetadataUploadDetails.isThumbnailCompatible && offChainMetadataUploadDetails.thumbnailFile)
|
||||
thumbnailUri = await upload(
|
||||
[offChainMetadataUploadDetails.thumbnailFile] as File[],
|
||||
offChainMetadataUploadDetails.uploadService,
|
||||
'thumbnail',
|
||||
offChainMetadataUploadDetails.nftStorageApiKey as string,
|
||||
offChainMetadataUploadDetails.pinataApiKey as string,
|
||||
offChainMetadataUploadDetails.pinataSecretKey as string,
|
||||
)
|
||||
const thumbnailUriWithBase = thumbnailUri
|
||||
? `ipfs://${thumbnailUri}/${(offChainMetadataUploadDetails.thumbnailFile as File).name}`
|
||||
: undefined
|
||||
|
||||
const fileArray: File[] = []
|
||||
const reader: FileReader = new FileReader()
|
||||
|
||||
reader.onload = (e) => {
|
||||
const data: any = JSON.parse(e.target?.result as string)
|
||||
|
||||
if (
|
||||
getAssetType(offChainMetadataUploadDetails.assetFiles[0].name) === 'audio' ||
|
||||
getAssetType(offChainMetadataUploadDetails.assetFiles[0].name) === 'video' ||
|
||||
getAssetType(offChainMetadataUploadDetails.assetFiles[0].name) === 'html'
|
||||
) {
|
||||
if (offChainMetadataUploadDetails.isThumbnailCompatible) {
|
||||
data.animation_url = `ipfs://${assetUri}/${offChainMetadataUploadDetails.assetFiles[0].name}`
|
||||
}
|
||||
if (getAssetType(offChainMetadataUploadDetails.assetFiles[0].name) !== 'html')
|
||||
data.image = `ipfs://${assetUri}/${offChainMetadataUploadDetails.assetFiles[0].name}`
|
||||
|
||||
data.image =
|
||||
offChainMetadataUploadDetails.isThumbnailCompatible && offChainMetadataUploadDetails.thumbnailFile
|
||||
? thumbnailUriWithBase
|
||||
: `ipfs://${assetUri}/${offChainMetadataUploadDetails.assetFiles[0].name}`
|
||||
|
||||
if (data.description) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
||||
data.description = data.description.replaceAll('\\n', '\n')
|
||||
}
|
||||
const metadataFileBlob = new Blob([JSON.stringify(data)], {
|
||||
type: 'application/json',
|
||||
})
|
||||
@@ -475,10 +705,12 @@ export const OpenEditionMinterCreator = ({
|
||||
console.log('Name: ', (offChainMetadataUploadDetails.openEditionMinterMetadataFile as File).name)
|
||||
const updatedMetadataFile = new File(
|
||||
[metadataFileBlob],
|
||||
(offChainMetadataUploadDetails.openEditionMinterMetadataFile as File).name.substring(
|
||||
0,
|
||||
(offChainMetadataUploadDetails.openEditionMinterMetadataFile as File).name.lastIndexOf('.'),
|
||||
),
|
||||
(offChainMetadataUploadDetails.openEditionMinterMetadataFile as File).name
|
||||
.substring(
|
||||
0,
|
||||
(offChainMetadataUploadDetails.openEditionMinterMetadataFile as File).name.lastIndexOf('.'),
|
||||
)
|
||||
.replaceAll('#', ''),
|
||||
{
|
||||
type: 'application/json',
|
||||
},
|
||||
@@ -505,8 +737,105 @@ export const OpenEditionMinterCreator = ({
|
||||
})
|
||||
}
|
||||
|
||||
const instantiateOpenEditionMinter = async (uri: string, coverImageUri: string) => {
|
||||
if (!wallet.initialized) throw new Error('Wallet not connected')
|
||||
const instantiateWhitelist = async () => {
|
||||
if (!wallet.isWalletConnected) throw new Error('Wallet not connected')
|
||||
if (!whitelistContract) throw new Error('Contract not found')
|
||||
|
||||
if (whitelistDetails?.whitelistType === 'standard' || whitelistDetails?.whitelistType === 'flex') {
|
||||
const standardMsg = {
|
||||
members: whitelistDetails.members,
|
||||
start_time: whitelistDetails.startTime,
|
||||
end_time: whitelistDetails.endTime,
|
||||
mint_price: coin(
|
||||
String(Number(whitelistDetails.unitPrice)),
|
||||
mintTokenFromFactory ? mintTokenFromFactory.denom : 'ustars',
|
||||
),
|
||||
per_address_limit: whitelistDetails.perAddressLimit,
|
||||
member_limit: whitelistDetails.memberLimit,
|
||||
admins: whitelistDetails.admins || [wallet.address],
|
||||
admins_mutable: whitelistDetails.adminsMutable,
|
||||
}
|
||||
|
||||
const flexMsg = {
|
||||
members: whitelistDetails.members,
|
||||
start_time: whitelistDetails.startTime,
|
||||
end_time: whitelistDetails.endTime,
|
||||
mint_price: coin(
|
||||
String(Number(whitelistDetails.unitPrice)),
|
||||
mintTokenFromFactory ? mintTokenFromFactory.denom : 'ustars',
|
||||
),
|
||||
member_limit: whitelistDetails.memberLimit,
|
||||
admins: whitelistDetails.admins || [wallet.address],
|
||||
admins_mutable: whitelistDetails.adminsMutable,
|
||||
}
|
||||
|
||||
const data = await whitelistContract.instantiate(
|
||||
whitelistDetails.whitelistType === 'standard' ? WHITELIST_CODE_ID : WHITELIST_FLEX_CODE_ID,
|
||||
whitelistDetails.whitelistType === 'standard' ? standardMsg : flexMsg,
|
||||
'Stargaze Whitelist Contract',
|
||||
wallet.address,
|
||||
)
|
||||
|
||||
return data.contractAddress
|
||||
} else if (whitelistDetails?.whitelistType === 'merkletree') {
|
||||
const members = whitelistDetails.members as string[]
|
||||
const membersCsv = members.join('\n')
|
||||
const membersBlob = new Blob([membersCsv], { type: 'text/csv' })
|
||||
const membersFile = new File([membersBlob], 'members.csv', { type: 'text/csv' })
|
||||
const formData = new FormData()
|
||||
formData.append('whitelist', membersFile)
|
||||
const response = await toast
|
||||
.promise(
|
||||
axios.post(`${WHITELIST_MERKLE_TREE_API_URL}/create_whitelist`, formData, {
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
},
|
||||
}),
|
||||
{
|
||||
loading: 'Fetching merkle root hash...',
|
||||
success: 'Merkle root fetched successfully.',
|
||||
error: 'Error fetching root hash from Whitelist Merkle Tree API.',
|
||||
},
|
||||
)
|
||||
.catch((error) => {
|
||||
console.log('error', error)
|
||||
throw new Error('Whitelist instantiation failed.')
|
||||
})
|
||||
|
||||
const rootHash = response.data.root_hash
|
||||
console.log('rootHash', rootHash)
|
||||
|
||||
const merkleTreeMsg = {
|
||||
merkle_root: rootHash,
|
||||
merkle_tree_uri: null,
|
||||
start_time: whitelistDetails.startTime,
|
||||
end_time: whitelistDetails.endTime,
|
||||
mint_price: coin(
|
||||
String(Number(whitelistDetails.unitPrice)),
|
||||
mintTokenFromFactory ? mintTokenFromFactory.denom : 'ustars',
|
||||
),
|
||||
per_address_limit: whitelistDetails.perAddressLimit,
|
||||
admins: whitelistDetails.admins || [wallet.address],
|
||||
admins_mutable: whitelistDetails.adminsMutable,
|
||||
}
|
||||
|
||||
const data = await whitelistMerkleTreeContract?.instantiate(
|
||||
WHITELIST_MERKLE_TREE_CODE_ID,
|
||||
merkleTreeMsg,
|
||||
'Stargaze Whitelist Merkle Tree Contract',
|
||||
wallet.address,
|
||||
)
|
||||
return data?.contractAddress
|
||||
}
|
||||
}
|
||||
|
||||
const instantiateOpenEditionMinter = async (
|
||||
uri: string,
|
||||
coverImageUri: string,
|
||||
thumbnailUri?: string,
|
||||
whitelist?: string,
|
||||
) => {
|
||||
if (!wallet.isWalletConnected) throw new Error('Wallet not connected')
|
||||
if (!openEditionFactoryContract) throw new Error('Contract not found')
|
||||
if (!openEditionMinterContract) throw new Error('Contract not found')
|
||||
|
||||
@@ -527,15 +856,18 @@ export const OpenEditionMinterCreator = ({
|
||||
extension:
|
||||
metadataStorageMethod === 'on-chain'
|
||||
? {
|
||||
image: uri,
|
||||
image:
|
||||
imageUploadDetails?.isThumbnailCompatible && imageUploadDetails.thumbnailFile
|
||||
? thumbnailUri
|
||||
: uri,
|
||||
name: onChainMetadataInputDetails?.name,
|
||||
description: onChainMetadataInputDetails?.description,
|
||||
description: onChainMetadataInputDetails?.description?.replaceAll('\\n', '\n'),
|
||||
attributes: onChainMetadataInputDetails?.attributes,
|
||||
external_url: onChainMetadataInputDetails?.external_url,
|
||||
animation_url:
|
||||
imageUploadDetails?.uploadMethod === 'existing'
|
||||
? onChainMetadataInputDetails?.animation_url
|
||||
: getAssetType(imageUploadDetails?.assetFile?.name as string) === 'video'
|
||||
: imageUploadDetails?.isThumbnailCompatible
|
||||
? uri
|
||||
: undefined,
|
||||
youtube_url: onChainMetadataInputDetails?.youtube_url,
|
||||
@@ -543,21 +875,42 @@ export const OpenEditionMinterCreator = ({
|
||||
: null,
|
||||
},
|
||||
start_time: mintingDetails?.startTime,
|
||||
end_time: mintingDetails?.endTime,
|
||||
end_time:
|
||||
mintingDetails?.limitType === ('time_limited' as LimitType) ||
|
||||
mintingDetails?.limitType === ('time_and_count_limited' as LimitType)
|
||||
? mintingDetails.endTime
|
||||
: null,
|
||||
mint_price: {
|
||||
amount: Number(mintingDetails?.unitPrice).toString(),
|
||||
denom: (mintTokenFromFactory?.denom as string) || 'ustars',
|
||||
},
|
||||
per_address_limit: mintingDetails?.perAddressLimit,
|
||||
num_tokens:
|
||||
mintingDetails?.limitType === ('count_limited' as LimitType) ||
|
||||
mintingDetails?.limitType === ('time_and_count_limited' as LimitType)
|
||||
? mintingDetails.tokenCountLimit
|
||||
: null,
|
||||
payment_address: mintingDetails?.paymentAddress || null,
|
||||
whitelist,
|
||||
},
|
||||
collection_params: {
|
||||
code_id: collectionDetails?.updatable ? SG721_OPEN_EDITION_UPDATABLE_CODE_ID : SG721_OPEN_EDITION_CODE_ID,
|
||||
code_id: collectionDetails?.updatable
|
||||
? SG721_OPEN_EDITION_UPDATABLE_CODE_ID
|
||||
: mintingDetails?.selectedMintToken?.displayName === 'USK' ||
|
||||
mintingDetails?.selectedMintToken?.displayName === 'USDC' ||
|
||||
mintingDetails?.selectedMintToken?.displayName === 'TIA' ||
|
||||
mintingDetails?.selectedMintToken?.displayName === 'STRDST' ||
|
||||
mintingDetails?.selectedMintToken?.displayName === 'KUJI' ||
|
||||
mintingDetails?.selectedMintToken?.displayName === 'HUAHUA' ||
|
||||
mintingDetails?.selectedMintToken?.displayName === 'BRNCH' ||
|
||||
mintingDetails?.selectedMintToken?.displayName === 'CRBRUS'
|
||||
? STRDST_SG721_CODE_ID
|
||||
: SG721_OPEN_EDITION_CODE_ID,
|
||||
name: collectionDetails?.name,
|
||||
symbol: collectionDetails?.symbol,
|
||||
info: {
|
||||
creator: wallet.address,
|
||||
description: collectionDetails?.description,
|
||||
description: collectionDetails?.description.replaceAll('\\n', '\n'),
|
||||
image: coverImageUri,
|
||||
explicit_content: collectionDetails?.explicit || false,
|
||||
royalty_info: royaltyInfo,
|
||||
@@ -567,21 +920,12 @@ export const OpenEditionMinterCreator = ({
|
||||
},
|
||||
}
|
||||
|
||||
console.log('msg: ', msg)
|
||||
console.log('Using factory address: ', factoryAddressForSelectedDenom)
|
||||
const payload: OpenEditionFactoryDispatchExecuteArgs = {
|
||||
contract: collectionDetails?.updatable ? updatableFactoryAddressForSelectedDenom : factoryAddressForSelectedDenom,
|
||||
contract: openEditionFactoryAddress as string,
|
||||
messages: openEditionFactoryMessages,
|
||||
txSigner: wallet.address,
|
||||
txSigner: wallet.address || '',
|
||||
msg,
|
||||
funds: [
|
||||
coin(
|
||||
collectionDetails?.updatable
|
||||
? (openEditionMinterUpdatableCreationFee as string)
|
||||
: (openEditionMinterCreationFee as string),
|
||||
'ustars',
|
||||
),
|
||||
],
|
||||
funds: [openEditionMinterCreationFee as Coin],
|
||||
updatable: collectionDetails?.updatable,
|
||||
}
|
||||
await openEditionFactoryDispatchExecute(payload)
|
||||
@@ -612,33 +956,93 @@ export const OpenEditionMinterCreator = ({
|
||||
metadataStorageMethod,
|
||||
openEditionMinterContractAddress,
|
||||
sg721ContractAddress,
|
||||
whitelistContractAddress,
|
||||
transactionHash,
|
||||
}
|
||||
onChange(data)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [metadataStorageMethod, openEditionMinterContractAddress, sg721ContractAddress, transactionHash])
|
||||
}, [
|
||||
metadataStorageMethod,
|
||||
openEditionMinterContractAddress,
|
||||
sg721ContractAddress,
|
||||
whitelistContractAddress,
|
||||
transactionHash,
|
||||
])
|
||||
|
||||
useEffect(() => {
|
||||
const data: OpenEditionMinterDetailsDataProps = {
|
||||
imageUploadDetails: imageUploadDetails ? imageUploadDetails : undefined,
|
||||
collectionDetails: collectionDetails ? collectionDetails : undefined,
|
||||
whitelistDetails: whitelistDetails ? whitelistDetails : undefined,
|
||||
royaltyDetails: royaltyDetails ? royaltyDetails : undefined,
|
||||
onChainMetadataInputDetails: onChainMetadataInputDetails ? onChainMetadataInputDetails : undefined,
|
||||
offChainMetadataUploadDetails: offChainMetadataUploadDetails ? offChainMetadataUploadDetails : undefined,
|
||||
mintingDetails: mintingDetails ? mintingDetails : undefined,
|
||||
metadataStorageMethod,
|
||||
openEditionMinterContractAddress,
|
||||
coverImageUrl,
|
||||
tokenUri,
|
||||
tokenImageUri,
|
||||
isRefreshed,
|
||||
}
|
||||
onDetailsChange(data)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [
|
||||
imageUploadDetails,
|
||||
collectionDetails,
|
||||
whitelistDetails,
|
||||
royaltyDetails,
|
||||
onChainMetadataInputDetails,
|
||||
offChainMetadataUploadDetails,
|
||||
mintingDetails,
|
||||
metadataStorageMethod,
|
||||
openEditionMinterContractAddress,
|
||||
coverImageUrl,
|
||||
tokenUri,
|
||||
tokenImageUri,
|
||||
isRefreshed,
|
||||
])
|
||||
|
||||
useEffect(() => {
|
||||
if (importedOpenEditionMinterDetails) {
|
||||
setMetadataStorageMethod(importedOpenEditionMinterDetails.metadataStorageMethod as MetadataStorageMethod)
|
||||
}
|
||||
}, [importedOpenEditionMinterDetails])
|
||||
|
||||
const fetchWhitelistConfig = async (contractAddress: string | undefined) => {
|
||||
if (contractAddress === '' || !whitelistDetails) return
|
||||
const contract = whitelistContract?.use(contractAddress)
|
||||
|
||||
await contract
|
||||
?.config()
|
||||
.then((config) => {
|
||||
if (!config) {
|
||||
whitelistDetails.whitelistType = 'standard'
|
||||
return
|
||||
}
|
||||
|
||||
if (JSON.stringify(config).includes('whale_cap')) whitelistDetails.whitelistType = 'flex'
|
||||
else if (!JSON.stringify(config).includes('member_limit') || config.member_limit === 0) {
|
||||
// whitelistDetails.whitelistType = 'merkletree'
|
||||
toast.error(
|
||||
'Whitelist Merkle Tree is not supported yet for open edition collections. Please use a standard or flexible whitelist contract.',
|
||||
)
|
||||
} else whitelistDetails.whitelistType = 'standard'
|
||||
setIsRefreshed(!isRefreshed)
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log('error', error)
|
||||
})
|
||||
}
|
||||
|
||||
const debouncedWhitelistContractAddress = useDebounce(whitelistDetails?.contractAddress, 300)
|
||||
|
||||
useEffect(() => {
|
||||
if (whitelistDetails?.whitelistState === 'existing' && debouncedWhitelistContractAddress !== '') {
|
||||
void fetchWhitelistConfig(debouncedWhitelistContractAddress)
|
||||
}
|
||||
}, [whitelistDetails?.whitelistState, debouncedWhitelistContractAddress])
|
||||
|
||||
return (
|
||||
<div>
|
||||
{/* TODO: Cancel once we're able to index on-chain metadata */}
|
||||
@@ -686,16 +1090,23 @@ export const OpenEditionMinterCreator = ({
|
||||
</div>
|
||||
</div>
|
||||
</Conditional>
|
||||
<div className={clsx('my-4 mx-10')}>
|
||||
<div className={clsx('my-0 mx-10')}>
|
||||
<Conditional test={metadataStorageMethod === 'off-chain'}>
|
||||
<div>
|
||||
<OffChainMetadataUploadDetails onChange={setOffChainMetadataUploadDetails} />
|
||||
<OffChainMetadataUploadDetails
|
||||
importedOffChainMetadataUploadDetails={importedOpenEditionMinterDetails?.offChainMetadataUploadDetails}
|
||||
onChange={setOffChainMetadataUploadDetails}
|
||||
/>
|
||||
</div>
|
||||
</Conditional>
|
||||
<Conditional test={metadataStorageMethod === 'on-chain'}>
|
||||
<div>
|
||||
<ImageUploadDetails onChange={setImageUploadDetails} />
|
||||
<ImageUploadDetails
|
||||
importedImageUploadDetails={importedOpenEditionMinterDetails?.imageUploadDetails}
|
||||
onChange={setImageUploadDetails}
|
||||
/>
|
||||
<OnChainMetadataInputDetails
|
||||
importedOnChainMetadataInputDetails={importedOpenEditionMinterDetails?.onChainMetadataInputDetails}
|
||||
onChange={setOnChainMetadataInputDetails}
|
||||
uploadMethod={imageUploadDetails?.uploadMethod}
|
||||
/>
|
||||
@@ -709,6 +1120,7 @@ export const OpenEditionMinterCreator = ({
|
||||
? (offChainMetadataUploadDetails?.imageUrl as string)
|
||||
: (imageUploadDetails?.coverImageUrl as string)
|
||||
}
|
||||
importedCollectionDetails={importedOpenEditionMinterDetails?.collectionDetails}
|
||||
metadataStorageMethod={metadataStorageMethod}
|
||||
onChange={setCollectionDetails}
|
||||
uploadMethod={
|
||||
@@ -718,18 +1130,29 @@ export const OpenEditionMinterCreator = ({
|
||||
}
|
||||
/>
|
||||
<MintingDetails
|
||||
minimumMintPrice={
|
||||
collectionDetails?.updatable
|
||||
? Number(minimumUpdatableMintPrice) / 1000000
|
||||
: Number(minimumMintPrice) / 1000000
|
||||
}
|
||||
importedMintingDetails={importedOpenEditionMinterDetails?.mintingDetails}
|
||||
isPresale={whitelistDetails?.whitelistState === 'new'}
|
||||
minimumMintPrice={Number(minimumMintPrice) / 1000000}
|
||||
mintTokenFromFactory={mintTokenFromFactory}
|
||||
onChange={setMintingDetails}
|
||||
uploadMethod={offChainMetadataUploadDetails?.uploadMethod as UploadMethod}
|
||||
whitelistStartDate={whitelistDetails?.startTime}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="my-6 mx-10">
|
||||
<WhitelistDetails
|
||||
importedWhitelistDetails={importedOpenEditionMinterDetails?.whitelistDetails}
|
||||
mintingTokenFromFactory={mintTokenFromFactory}
|
||||
onChange={setWhitelistDetails}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="my-6">
|
||||
<RoyaltyDetails onChange={setRoyaltyDetails} />
|
||||
<RoyaltyDetails
|
||||
importedRoyaltyDetails={importedOpenEditionMinterDetails?.royaltyDetails}
|
||||
onChange={setRoyaltyDetails}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex justify-end w-full">
|
||||
<Button
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
import { Conditional } from 'components/Conditional'
|
||||
import { FormGroup } from 'components/FormGroup'
|
||||
import { useInputState } from 'components/forms/FormInput.hooks'
|
||||
import { useWallet } from 'contexts/wallet'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { resolveAddress } from 'utils/resolveAddress'
|
||||
import { useWallet } from 'utils/wallet'
|
||||
|
||||
import { NumberInput, TextInput } from '../forms/FormInput'
|
||||
|
||||
interface RoyaltyDetailsProps {
|
||||
onChange: (data: RoyaltyDetailsDataProps) => void
|
||||
importedRoyaltyDetails?: RoyaltyDetailsDataProps
|
||||
}
|
||||
|
||||
export interface RoyaltyDetailsDataProps {
|
||||
@@ -19,9 +20,10 @@ export interface RoyaltyDetailsDataProps {
|
||||
|
||||
type RoyaltyState = 'none' | 'new'
|
||||
|
||||
export const RoyaltyDetails = ({ onChange }: RoyaltyDetailsProps) => {
|
||||
export const RoyaltyDetails = ({ onChange, importedRoyaltyDetails }: RoyaltyDetailsProps) => {
|
||||
const wallet = useWallet()
|
||||
const [royaltyState, setRoyaltyState] = useState<RoyaltyState>('none')
|
||||
const [royaltyDetailsImported, setRoyaltyDetailsImported] = useState(false)
|
||||
|
||||
const royaltyPaymentAddressState = useInputState({
|
||||
id: 'royalty-payment-address',
|
||||
@@ -40,26 +42,38 @@ export const RoyaltyDetails = ({ onChange }: RoyaltyDetailsProps) => {
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
void resolveAddress(
|
||||
royaltyPaymentAddressState.value
|
||||
.toLowerCase()
|
||||
.replace(/,/g, '')
|
||||
.replace(/"/g, '')
|
||||
.replace(/'/g, '')
|
||||
.replace(/ /g, ''),
|
||||
wallet,
|
||||
).then((royaltyPaymentAddress) => {
|
||||
royaltyPaymentAddressState.onChange(royaltyPaymentAddress)
|
||||
const data: RoyaltyDetailsDataProps = {
|
||||
royaltyType: royaltyState,
|
||||
paymentAddress: royaltyPaymentAddressState.value,
|
||||
share: Number(royaltyShareState.value),
|
||||
}
|
||||
onChange(data)
|
||||
})
|
||||
if (!importedRoyaltyDetails || (importedRoyaltyDetails && royaltyDetailsImported)) {
|
||||
void resolveAddress(
|
||||
royaltyPaymentAddressState.value
|
||||
.toLowerCase()
|
||||
.replace(/,/g, '')
|
||||
.replace(/"/g, '')
|
||||
.replace(/'/g, '')
|
||||
.replace(/ /g, ''),
|
||||
wallet,
|
||||
).then((royaltyPaymentAddress) => {
|
||||
royaltyPaymentAddressState.onChange(royaltyPaymentAddress)
|
||||
const data: RoyaltyDetailsDataProps = {
|
||||
royaltyType: royaltyState,
|
||||
paymentAddress: royaltyPaymentAddressState.value,
|
||||
share: Number(royaltyShareState.value),
|
||||
}
|
||||
onChange(data)
|
||||
})
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [royaltyState, royaltyPaymentAddressState.value, royaltyShareState.value])
|
||||
|
||||
useEffect(() => {
|
||||
if (importedRoyaltyDetails) {
|
||||
setRoyaltyState(importedRoyaltyDetails.royaltyType)
|
||||
royaltyPaymentAddressState.onChange(importedRoyaltyDetails.paymentAddress.toString())
|
||||
royaltyShareState.onChange(importedRoyaltyDetails.share.toString())
|
||||
setRoyaltyDetailsImported(true)
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [importedRoyaltyDetails])
|
||||
|
||||
return (
|
||||
<div className="py-3 px-8 mx-10 rounded border-2 border-white/20">
|
||||
<div className="flex justify-center">
|
||||
|
||||
@@ -0,0 +1,528 @@
|
||||
/* eslint-disable eslint-comments/disable-enable-pair */
|
||||
/* eslint-disable @typescript-eslint/no-unnecessary-condition */
|
||||
/* eslint-disable no-nested-ternary */
|
||||
import { Button } from 'components/Button'
|
||||
import { FormControl } from 'components/FormControl'
|
||||
import { FormGroup } from 'components/FormGroup'
|
||||
import { AddressList } from 'components/forms/AddressList'
|
||||
import { useAddressListState } from 'components/forms/AddressList.hooks'
|
||||
import { useInputState, useNumberInputState } from 'components/forms/FormInput.hooks'
|
||||
import { InputDateTime } from 'components/InputDateTime'
|
||||
import type { WhitelistFlexMember } from 'components/WhitelistFlexUpload'
|
||||
import { WhitelistFlexUpload } from 'components/WhitelistFlexUpload'
|
||||
import type { TokenInfo } from 'config/token'
|
||||
import { useGlobalSettings } from 'contexts/globalSettings'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { isValidAddress } from 'utils/isValidAddress'
|
||||
import { useWallet } from 'utils/wallet'
|
||||
|
||||
import { Conditional } from '../Conditional'
|
||||
import { AddressInput, NumberInput } from '../forms/FormInput'
|
||||
import { JsonPreview } from '../JsonPreview'
|
||||
import { WhitelistUpload } from '../WhitelistUpload'
|
||||
|
||||
interface WhitelistDetailsProps {
|
||||
onChange: (data: WhitelistDetailsDataProps) => void
|
||||
mintingTokenFromFactory?: TokenInfo
|
||||
importedWhitelistDetails?: WhitelistDetailsDataProps
|
||||
}
|
||||
|
||||
export interface WhitelistDetailsDataProps {
|
||||
whitelistState: WhitelistState
|
||||
whitelistType: WhitelistType
|
||||
contractAddress?: string
|
||||
members?: string[] | WhitelistFlexMember[]
|
||||
unitPrice?: string
|
||||
startTime?: string
|
||||
endTime?: string
|
||||
perAddressLimit?: number
|
||||
memberLimit?: number
|
||||
admins?: string[]
|
||||
adminsMutable?: boolean
|
||||
}
|
||||
|
||||
type WhitelistState = 'none' | 'existing' | 'new'
|
||||
|
||||
export type WhitelistType = 'standard' | 'flex' | 'merkletree'
|
||||
|
||||
export const WhitelistDetails = ({
|
||||
onChange,
|
||||
mintingTokenFromFactory,
|
||||
importedWhitelistDetails,
|
||||
}: WhitelistDetailsProps) => {
|
||||
const wallet = useWallet()
|
||||
const { timezone } = useGlobalSettings()
|
||||
|
||||
const [whitelistState, setWhitelistState] = useState<WhitelistState>('none')
|
||||
const [whitelistType, setWhitelistType] = useState<WhitelistType>('standard')
|
||||
const [startDate, setStartDate] = useState<Date | undefined>(undefined)
|
||||
const [endDate, setEndDate] = useState<Date | undefined>(undefined)
|
||||
const [whitelistStandardArray, setWhitelistStandardArray] = useState<string[]>([])
|
||||
const [whitelistFlexArray, setWhitelistFlexArray] = useState<WhitelistFlexMember[]>([])
|
||||
const [whitelistMerkleTreeArray, setWhitelistMerkleTreeArray] = useState<string[]>([])
|
||||
const [adminsMutable, setAdminsMutable] = useState<boolean>(true)
|
||||
|
||||
const whitelistAddressState = useInputState({
|
||||
id: 'whitelist-address',
|
||||
name: 'whitelistAddress',
|
||||
title: 'Whitelist Address',
|
||||
defaultValue: '',
|
||||
})
|
||||
|
||||
const unitPriceState = useNumberInputState({
|
||||
id: 'unit-price',
|
||||
name: 'unitPrice',
|
||||
title: 'Unit Price',
|
||||
subtitle: `Token price for whitelisted addresses \n (min. 0 ${
|
||||
mintingTokenFromFactory ? mintingTokenFromFactory.displayName : 'STARS'
|
||||
})`,
|
||||
placeholder: '25',
|
||||
})
|
||||
|
||||
const memberLimitState = useNumberInputState({
|
||||
id: 'member-limit',
|
||||
name: 'memberLimit',
|
||||
title: 'Member Limit',
|
||||
subtitle: 'Maximum number of whitelisted addresses',
|
||||
placeholder: '1000',
|
||||
})
|
||||
|
||||
const perAddressLimitState = useNumberInputState({
|
||||
id: 'per-address-limit',
|
||||
name: 'perAddressLimit',
|
||||
title: 'Per Address Limit',
|
||||
subtitle: 'Maximum number of tokens per whitelisted address',
|
||||
placeholder: '5',
|
||||
})
|
||||
|
||||
const addressListState = useAddressListState()
|
||||
|
||||
const whitelistFileOnChange = (data: string[]) => {
|
||||
if (whitelistType === 'standard') setWhitelistStandardArray(data)
|
||||
if (whitelistType === 'merkletree') setWhitelistMerkleTreeArray(data)
|
||||
}
|
||||
|
||||
const whitelistFlexFileOnChange = (whitelistData: WhitelistFlexMember[]) => {
|
||||
setWhitelistFlexArray(whitelistData)
|
||||
}
|
||||
|
||||
const downloadSampleWhitelistFlexFile = () => {
|
||||
const csvData =
|
||||
'address,mint_count\nstars153w5xhuqu3et29lgqk4dsynj6gjn96lr33wx4e,3\nstars1xkes5r2k8u3m3ayfpverlkcrq3k4jhdk8ws0uz,1\nstars1s8qx0zvz8yd6e4x0mqmqf7fr9vvfn622wtp3g3,2'
|
||||
const blob = new Blob([csvData], { type: 'text/csv' })
|
||||
const url = window.URL.createObjectURL(blob)
|
||||
const a = document.createElement('a')
|
||||
a.setAttribute('href', url)
|
||||
a.setAttribute('download', 'sample_whitelist_flex.csv')
|
||||
a.click()
|
||||
}
|
||||
|
||||
const downloadSampleWhitelistFile = () => {
|
||||
const txtData =
|
||||
'stars153w5xhuqu3et29lgqk4dsynj6gjn96lr33wx4e\nstars1xkes5r2k8u3m3ayfpverlkcrq3k4jhdk8ws0uz\nstars1s8qx0zvz8yd6e4x0mqmqf7fr9vvfn622wtp3g3'
|
||||
const blob = new Blob([txtData], { type: 'text/txt' })
|
||||
const url = window.URL.createObjectURL(blob)
|
||||
const a = document.createElement('a')
|
||||
a.setAttribute('href', url)
|
||||
a.setAttribute('download', 'sample_whitelist.txt')
|
||||
a.click()
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!importedWhitelistDetails) {
|
||||
setWhitelistStandardArray([])
|
||||
setWhitelistFlexArray([])
|
||||
setWhitelistMerkleTreeArray([])
|
||||
}
|
||||
}, [whitelistType])
|
||||
|
||||
useEffect(() => {
|
||||
const data: WhitelistDetailsDataProps = {
|
||||
whitelistState,
|
||||
whitelistType,
|
||||
contractAddress: whitelistAddressState.value
|
||||
.toLowerCase()
|
||||
.replace(/,/g, '')
|
||||
.replace(/"/g, '')
|
||||
.replace(/'/g, '')
|
||||
.replace(/ /g, ''),
|
||||
members:
|
||||
whitelistType === 'standard'
|
||||
? whitelistStandardArray
|
||||
: whitelistType === 'merkletree'
|
||||
? whitelistMerkleTreeArray
|
||||
: whitelistFlexArray,
|
||||
unitPrice: unitPriceState.value
|
||||
? (Number(unitPriceState.value) * 1_000_000).toString()
|
||||
: unitPriceState.value === 0
|
||||
? '0'
|
||||
: undefined,
|
||||
startTime: startDate ? (startDate.getTime() * 1_000_000).toString() : '',
|
||||
endTime: endDate ? (endDate.getTime() * 1_000_000).toString() : '',
|
||||
perAddressLimit: perAddressLimitState.value,
|
||||
memberLimit: memberLimitState.value,
|
||||
admins: [
|
||||
...new Set(
|
||||
addressListState.values
|
||||
.map((a) => a.address.trim())
|
||||
.filter((address) => address !== '' && isValidAddress(address.trim()) && address.startsWith('stars')),
|
||||
),
|
||||
],
|
||||
adminsMutable,
|
||||
}
|
||||
onChange(data)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [
|
||||
whitelistAddressState.value,
|
||||
unitPriceState.value,
|
||||
memberLimitState.value,
|
||||
perAddressLimitState.value,
|
||||
startDate,
|
||||
endDate,
|
||||
whitelistStandardArray,
|
||||
whitelistFlexArray,
|
||||
whitelistMerkleTreeArray,
|
||||
whitelistState,
|
||||
whitelistType,
|
||||
addressListState.values,
|
||||
adminsMutable,
|
||||
])
|
||||
|
||||
// make the necessary changes with respect to imported whitelist details
|
||||
useEffect(() => {
|
||||
if (importedWhitelistDetails) {
|
||||
setWhitelistState(importedWhitelistDetails.whitelistState)
|
||||
setWhitelistType(importedWhitelistDetails.whitelistType)
|
||||
whitelistAddressState.onChange(
|
||||
importedWhitelistDetails.contractAddress ? importedWhitelistDetails.contractAddress : '',
|
||||
)
|
||||
unitPriceState.onChange(
|
||||
importedWhitelistDetails.unitPrice ? Number(importedWhitelistDetails.unitPrice) / 1000000 : 0,
|
||||
)
|
||||
memberLimitState.onChange(importedWhitelistDetails.memberLimit ? importedWhitelistDetails.memberLimit : 0)
|
||||
perAddressLimitState.onChange(
|
||||
importedWhitelistDetails.perAddressLimit ? importedWhitelistDetails.perAddressLimit : 0,
|
||||
)
|
||||
setStartDate(
|
||||
importedWhitelistDetails.startTime
|
||||
? new Date(Number(importedWhitelistDetails.startTime) / 1_000_000)
|
||||
: undefined,
|
||||
)
|
||||
setEndDate(
|
||||
importedWhitelistDetails.endTime ? new Date(Number(importedWhitelistDetails.endTime) / 1_000_000) : undefined,
|
||||
)
|
||||
setAdminsMutable(importedWhitelistDetails.adminsMutable ? importedWhitelistDetails.adminsMutable : true)
|
||||
importedWhitelistDetails.admins?.forEach((admin) => {
|
||||
addressListState.reset()
|
||||
addressListState.add({ address: admin })
|
||||
})
|
||||
if (importedWhitelistDetails.whitelistType === 'standard') {
|
||||
setWhitelistStandardArray([])
|
||||
importedWhitelistDetails.members?.forEach((member) => {
|
||||
setWhitelistStandardArray((standardArray) => [...standardArray, member as string])
|
||||
})
|
||||
} else if (importedWhitelistDetails.whitelistType === 'merkletree') {
|
||||
setWhitelistMerkleTreeArray([])
|
||||
// importedWhitelistDetails.members?.forEach((member) => {
|
||||
// setWhitelistMerkleTreeArray((merkleTreeArray) => [...merkleTreeArray, member as string])
|
||||
// })
|
||||
} else if (importedWhitelistDetails.whitelistType === 'flex') {
|
||||
setWhitelistFlexArray([])
|
||||
importedWhitelistDetails.members?.forEach((member) => {
|
||||
setWhitelistFlexArray((flexArray) => [
|
||||
...flexArray,
|
||||
{
|
||||
address: (member as WhitelistFlexMember).address,
|
||||
mint_count: (member as WhitelistFlexMember).mint_count,
|
||||
},
|
||||
])
|
||||
})
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [importedWhitelistDetails])
|
||||
|
||||
useEffect(() => {
|
||||
if (whitelistState === 'new' && wallet.address) {
|
||||
addressListState.reset()
|
||||
addressListState.add({ address: wallet.address })
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [whitelistState, wallet.address])
|
||||
|
||||
return (
|
||||
<div className="py-3 px-8 rounded border-2 border-white/20">
|
||||
<div className="flex justify-center">
|
||||
<div className="ml-4 font-bold form-check form-check-inline">
|
||||
<input
|
||||
checked={whitelistState === 'none'}
|
||||
className="peer sr-only"
|
||||
id="whitelistRadio1"
|
||||
name="whitelistRadioOptions1"
|
||||
onClick={() => {
|
||||
setWhitelistState('none')
|
||||
setWhitelistType('standard')
|
||||
}}
|
||||
type="radio"
|
||||
value="None"
|
||||
/>
|
||||
<label
|
||||
className="inline-block py-1 px-2 text-gray peer-checked:text-white hover:text-white peer-checked:bg-black hover:rounded-sm peer-checked:border-b-2 hover:border-b-2 peer-checked:border-plumbus hover:border-plumbus cursor-pointer form-check-label"
|
||||
htmlFor="whitelistRadio1"
|
||||
>
|
||||
No whitelist
|
||||
</label>
|
||||
</div>
|
||||
<div className="ml-4 font-bold form-check form-check-inline">
|
||||
<input
|
||||
checked={whitelistState === 'existing'}
|
||||
className="peer sr-only"
|
||||
id="whitelistRadio2"
|
||||
name="whitelistRadioOptions2"
|
||||
onClick={() => {
|
||||
setWhitelistState('existing')
|
||||
}}
|
||||
type="radio"
|
||||
value="Existing"
|
||||
/>
|
||||
<label
|
||||
className="inline-block py-1 px-2 text-gray peer-checked:text-white hover:text-white peer-checked:bg-black hover:rounded-sm peer-checked:border-b-2 hover:border-b-2 peer-checked:border-plumbus hover:border-plumbus cursor-pointer form-check-label"
|
||||
htmlFor="whitelistRadio2"
|
||||
>
|
||||
Existing whitelist
|
||||
</label>
|
||||
</div>
|
||||
<div className="ml-4 font-bold form-check form-check-inline">
|
||||
<input
|
||||
checked={whitelistState === 'new'}
|
||||
className="peer sr-only"
|
||||
id="whitelistRadio3"
|
||||
name="whitelistRadioOptions3"
|
||||
onClick={() => {
|
||||
setWhitelistState('new')
|
||||
}}
|
||||
type="radio"
|
||||
value="New"
|
||||
/>
|
||||
<label
|
||||
className="inline-block py-1 px-2 text-gray peer-checked:text-white hover:text-white peer-checked:bg-black hover:rounded-sm peer-checked:border-b-2 hover:border-b-2 peer-checked:border-plumbus hover:border-plumbus cursor-pointer form-check-label"
|
||||
htmlFor="whitelistRadio3"
|
||||
>
|
||||
New whitelist
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Conditional test={whitelistState === 'existing'}>
|
||||
<AddressInput {...whitelistAddressState} className="pb-5" isRequired />
|
||||
</Conditional>
|
||||
|
||||
<Conditional test={whitelistState === 'new'}>
|
||||
<div className="flex justify-between mb-5 ml-6 max-w-[300px] text-lg font-bold">
|
||||
<div className="form-check form-check-inline">
|
||||
<input
|
||||
checked={whitelistType === 'standard'}
|
||||
className="peer sr-only"
|
||||
id="inlineRadio7"
|
||||
name="inlineRadioOptions7"
|
||||
onClick={() => {
|
||||
setWhitelistType('standard')
|
||||
}}
|
||||
type="radio"
|
||||
value="standard"
|
||||
/>
|
||||
<label
|
||||
className="inline-block py-1 px-2 text-gray peer-checked:text-white hover:text-white peer-checked:bg-black hover:rounded-sm peer-checked:border-b-2 hover:border-b-2 peer-checked:border-plumbus hover:border-plumbus cursor-pointer form-check-label"
|
||||
htmlFor="inlineRadio7"
|
||||
>
|
||||
Standard Whitelist
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="form-check form-check-inline">
|
||||
<input
|
||||
checked={whitelistType === 'flex'}
|
||||
className="peer sr-only"
|
||||
id="inlineRadio8"
|
||||
name="inlineRadioOptions8"
|
||||
onClick={() => {
|
||||
setWhitelistType('flex')
|
||||
}}
|
||||
type="radio"
|
||||
value="flex"
|
||||
/>
|
||||
<label
|
||||
className="inline-block py-1 px-2 text-gray peer-checked:text-white hover:text-white peer-checked:bg-black hover:rounded-sm peer-checked:border-b-2 hover:border-b-2 peer-checked:border-plumbus hover:border-plumbus cursor-pointer form-check-label"
|
||||
htmlFor="inlineRadio8"
|
||||
>
|
||||
Whitelist Flex
|
||||
</label>
|
||||
</div>
|
||||
{/* <div className="form-check form-check-inline">
|
||||
<input
|
||||
checked={whitelistType === 'merkletree'}
|
||||
className="peer sr-only"
|
||||
id="inlineRadio9"
|
||||
name="inlineRadioOptions9"
|
||||
onClick={() => {
|
||||
setWhitelistType('merkletree')
|
||||
}}
|
||||
type="radio"
|
||||
value="merkletree"
|
||||
/>
|
||||
<label
|
||||
className="inline-block py-1 px-2 text-gray peer-checked:text-white hover:text-white peer-checked:bg-black hover:rounded-sm peer-checked:border-b-2 hover:border-b-2 peer-checked:border-plumbus hover:border-plumbus cursor-pointer form-check-label"
|
||||
htmlFor="inlineRadio9"
|
||||
>
|
||||
Whitelist Merkle Tree
|
||||
</label>
|
||||
</div> */}
|
||||
</div>
|
||||
<div className="grid grid-cols-2">
|
||||
<FormGroup subtitle="Information about your minting settings" title="Whitelist Minting Details">
|
||||
<NumberInput isRequired {...unitPriceState} />
|
||||
<Conditional test={whitelistType !== 'merkletree'}>
|
||||
<NumberInput isRequired {...memberLimitState} />
|
||||
</Conditional>
|
||||
<Conditional test={whitelistType === 'standard' || whitelistType === 'merkletree'}>
|
||||
<NumberInput isRequired {...perAddressLimitState} />
|
||||
</Conditional>
|
||||
<FormControl
|
||||
htmlId="start-date"
|
||||
isRequired
|
||||
subtitle="Start time for minting tokens to whitelisted addresses"
|
||||
title={`Whitelist Start Time ${timezone === 'Local' ? '(local)' : '(UTC)'}`}
|
||||
>
|
||||
<InputDateTime
|
||||
minDate={
|
||||
timezone === 'Local' ? new Date() : new Date(Date.now() + new Date().getTimezoneOffset() * 60 * 1000)
|
||||
}
|
||||
onChange={(date) =>
|
||||
date
|
||||
? setStartDate(
|
||||
timezone === 'Local'
|
||||
? date
|
||||
: new Date(date.getTime() - new Date().getTimezoneOffset() * 60 * 1000),
|
||||
)
|
||||
: setStartDate(undefined)
|
||||
}
|
||||
value={
|
||||
timezone === 'Local'
|
||||
? startDate
|
||||
: startDate
|
||||
? new Date(startDate.getTime() + new Date().getTimezoneOffset() * 60 * 1000)
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormControl
|
||||
htmlId="end-date"
|
||||
isRequired
|
||||
subtitle="Whitelist End Time dictates when public sales will start"
|
||||
title={`Whitelist End Time ${timezone === 'Local' ? '(local)' : '(UTC)'}`}
|
||||
>
|
||||
<InputDateTime
|
||||
minDate={
|
||||
timezone === 'Local' ? new Date() : new Date(Date.now() + new Date().getTimezoneOffset() * 60 * 1000)
|
||||
}
|
||||
onChange={(date) =>
|
||||
date
|
||||
? setEndDate(
|
||||
timezone === 'Local'
|
||||
? date
|
||||
: new Date(date.getTime() - new Date().getTimezoneOffset() * 60 * 1000),
|
||||
)
|
||||
: setEndDate(undefined)
|
||||
}
|
||||
value={
|
||||
timezone === 'Local'
|
||||
? endDate
|
||||
: endDate
|
||||
? new Date(endDate.getTime() + new Date().getTimezoneOffset() * 60 * 1000)
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormGroup>
|
||||
<div>
|
||||
<div className="mt-2 ml-3 w-[65%] form-control">
|
||||
<label className="justify-start cursor-pointer label">
|
||||
<span className="mr-4 font-bold">Mutable Administrator Addresses</span>
|
||||
<input
|
||||
checked={adminsMutable}
|
||||
className={`toggle ${adminsMutable ? `bg-stargaze` : `bg-gray-600`}`}
|
||||
onClick={() => setAdminsMutable(!adminsMutable)}
|
||||
type="checkbox"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div className="my-4 ml-4">
|
||||
<AddressList
|
||||
entries={addressListState.entries}
|
||||
onAdd={addressListState.add}
|
||||
onChange={addressListState.update}
|
||||
onRemove={addressListState.remove}
|
||||
subtitle="The list of administrator addresses"
|
||||
title="Administrator Addresses"
|
||||
/>
|
||||
</div>
|
||||
<Conditional test={whitelistType === 'standard'}>
|
||||
<FormGroup
|
||||
subtitle={
|
||||
<div>
|
||||
<span>TXT file that contains the whitelisted addresses</span>
|
||||
<Button className="mt-2 text-sm text-white" onClick={downloadSampleWhitelistFile}>
|
||||
Download Sample File
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
title="Whitelist File"
|
||||
>
|
||||
<WhitelistUpload onChange={whitelistFileOnChange} />
|
||||
</FormGroup>
|
||||
<Conditional test={whitelistStandardArray.length > 0}>
|
||||
<JsonPreview content={whitelistStandardArray} initialState title="File Contents" />
|
||||
</Conditional>
|
||||
</Conditional>
|
||||
<Conditional test={whitelistType === 'flex'}>
|
||||
<FormGroup
|
||||
subtitle={
|
||||
<div>
|
||||
<span>CSV file that contains the whitelisted addresses and corresponding mint counts</span>
|
||||
<Button className="mt-2 text-sm text-white" onClick={downloadSampleWhitelistFlexFile}>
|
||||
Download Sample File
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
title="Whitelist File"
|
||||
>
|
||||
<WhitelistFlexUpload onChange={whitelistFlexFileOnChange} />
|
||||
</FormGroup>
|
||||
<Conditional test={whitelistFlexArray.length > 0}>
|
||||
<JsonPreview content={whitelistFlexArray} initialState={false} title="File Contents" />
|
||||
</Conditional>
|
||||
</Conditional>
|
||||
<Conditional test={whitelistType === 'merkletree'}>
|
||||
<FormGroup
|
||||
subtitle={
|
||||
<div>
|
||||
<span>TXT file that contains the whitelisted addresses</span>
|
||||
<Button className="mt-2 text-sm text-white" onClick={downloadSampleWhitelistFile}>
|
||||
Download Sample File
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
title="Whitelist File"
|
||||
>
|
||||
<WhitelistUpload onChange={whitelistFileOnChange} />
|
||||
</FormGroup>
|
||||
<Conditional test={whitelistStandardArray.length > 0}>
|
||||
<JsonPreview content={whitelistStandardArray} initialState title="File Contents" />
|
||||
</Conditional>
|
||||
</Conditional>
|
||||
</div>
|
||||
</div>
|
||||
</Conditional>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
+324
@@ -0,0 +1,324 @@
|
||||
/* eslint-disable eslint-comments/disable-enable-pair */
|
||||
/* eslint-disable no-nested-ternary */
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/* eslint-disable camelcase */
|
||||
|
||||
import { GenericAuthorization } from 'cosmjs-types/cosmos/authz/v1beta1/authz'
|
||||
import { MsgGrant } from 'cosmjs-types/cosmos/authz/v1beta1/tx'
|
||||
import { SendAuthorization } from 'cosmjs-types/cosmos/bank/v1beta1/authz'
|
||||
import { Coin } from 'cosmjs-types/cosmos/base/v1beta1/coin'
|
||||
import type { AuthorizationType } from 'cosmjs-types/cosmos/staking/v1beta1/authz'
|
||||
import { StakeAuthorization, StakeAuthorization_Validators } from 'cosmjs-types/cosmos/staking/v1beta1/authz'
|
||||
import {
|
||||
AcceptedMessageKeysFilter,
|
||||
AllowAllMessagesFilter,
|
||||
CombinedLimit,
|
||||
ContractExecutionAuthorization,
|
||||
ContractMigrationAuthorization,
|
||||
MaxCallsLimit,
|
||||
MaxFundsLimit,
|
||||
} from 'cosmjs-types/cosmwasm/wasm/v1/authz'
|
||||
import type { AuthorizationMode, GenericAuthorizationType, GrantAuthorizationType } from 'pages/authz/grant'
|
||||
|
||||
export interface Msg {
|
||||
typeUrl: string
|
||||
value: any
|
||||
}
|
||||
|
||||
export interface AuthzMessage {
|
||||
authzMode: AuthorizationMode
|
||||
authzType: GrantAuthorizationType
|
||||
displayName: string
|
||||
typeUrl: string
|
||||
genericAuthzType?: GenericAuthorizationType
|
||||
}
|
||||
|
||||
export const grantGenericStakeAuthorization: AuthzMessage = {
|
||||
authzMode: 'Grant',
|
||||
authzType: 'Generic',
|
||||
displayName: 'Stake',
|
||||
typeUrl: '/cosmos.staking.v1beta1.MsgDelegate',
|
||||
genericAuthzType: 'MsgDelegate',
|
||||
}
|
||||
|
||||
export const grantGenericSendAuthorization: AuthzMessage = {
|
||||
authzMode: 'Grant',
|
||||
authzType: 'Generic',
|
||||
displayName: 'Send',
|
||||
typeUrl: '/cosmos.bank.v1beta1.MsgSend',
|
||||
genericAuthzType: 'MsgSend',
|
||||
}
|
||||
|
||||
export const authzMessages: AuthzMessage[] = [grantGenericStakeAuthorization, grantGenericSendAuthorization]
|
||||
|
||||
const msgAuthzGrantTypeUrl = '/cosmos.authz.v1beta1.MsgGrant'
|
||||
|
||||
export function AuthzSendGrantMsg(
|
||||
granter: string,
|
||||
grantee: string,
|
||||
denom: string,
|
||||
spendLimit: number,
|
||||
expiration: number,
|
||||
allowList?: string[],
|
||||
): Msg {
|
||||
const sendAuthValue = SendAuthorization.encode(
|
||||
SendAuthorization.fromPartial({
|
||||
spendLimit: [
|
||||
Coin.fromPartial({
|
||||
amount: String(spendLimit),
|
||||
denom,
|
||||
}),
|
||||
],
|
||||
// Needs cosmos-sdk >= 0.47
|
||||
// allowList,
|
||||
}),
|
||||
).finish()
|
||||
|
||||
const grantValue = MsgGrant.fromPartial({
|
||||
grant: {
|
||||
authorization: {
|
||||
typeUrl: '/cosmos.bank.v1beta1.SendAuthorization',
|
||||
value: sendAuthValue,
|
||||
},
|
||||
expiration: expiration ? { seconds: BigInt(expiration) } : undefined,
|
||||
},
|
||||
grantee,
|
||||
granter,
|
||||
})
|
||||
|
||||
return {
|
||||
typeUrl: msgAuthzGrantTypeUrl,
|
||||
value: grantValue,
|
||||
}
|
||||
}
|
||||
|
||||
export function AuthzExecuteContractGrantMsg(
|
||||
granter: string,
|
||||
grantee: string,
|
||||
contract: string,
|
||||
expiration: number,
|
||||
callsRemaining?: number,
|
||||
amounts?: Coin[],
|
||||
allowedMessages?: string[],
|
||||
): Msg {
|
||||
const sendAuthValue = ContractExecutionAuthorization.encode(
|
||||
ContractExecutionAuthorization.fromPartial({
|
||||
grants: [
|
||||
{
|
||||
contract,
|
||||
filter: {
|
||||
typeUrl: allowedMessages
|
||||
? '/cosmwasm.wasm.v1.AcceptedMessageKeysFilter'
|
||||
: '/cosmwasm.wasm.v1.AllowAllMessagesFilter',
|
||||
value: allowedMessages
|
||||
? AcceptedMessageKeysFilter.encode({ keys: allowedMessages }).finish()
|
||||
: AllowAllMessagesFilter.encode({}).finish(),
|
||||
},
|
||||
limit:
|
||||
callsRemaining || amounts
|
||||
? {
|
||||
typeUrl:
|
||||
callsRemaining && amounts
|
||||
? '/cosmwasm.wasm.v1.CombinedLimit'
|
||||
: callsRemaining
|
||||
? '/cosmwasm.wasm.v1.MaxCallsLimit'
|
||||
: '/cosmwasm.wasm.v1.MaxFundsLimit',
|
||||
value:
|
||||
callsRemaining && amounts
|
||||
? CombinedLimit.encode({
|
||||
callsRemaining: BigInt(callsRemaining),
|
||||
amounts,
|
||||
}).finish()
|
||||
: callsRemaining
|
||||
? MaxCallsLimit.encode({
|
||||
remaining: BigInt(callsRemaining),
|
||||
}).finish()
|
||||
: MaxFundsLimit.encode({
|
||||
amounts: amounts || [],
|
||||
}).finish(),
|
||||
}
|
||||
: {
|
||||
// limit: undefined is not accepted
|
||||
typeUrl: '/cosmwasm.wasm.v1.MaxCallsLimit',
|
||||
value: MaxCallsLimit.encode({
|
||||
remaining: BigInt(100000),
|
||||
}).finish(),
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
).finish()
|
||||
|
||||
const grantValue = MsgGrant.fromPartial({
|
||||
grant: {
|
||||
authorization: {
|
||||
typeUrl: '/cosmwasm.wasm.v1.ContractExecutionAuthorization',
|
||||
value: sendAuthValue,
|
||||
},
|
||||
expiration: expiration ? { seconds: BigInt(expiration), nanos: 0 } : undefined,
|
||||
},
|
||||
grantee,
|
||||
granter,
|
||||
})
|
||||
|
||||
return {
|
||||
typeUrl: msgAuthzGrantTypeUrl,
|
||||
value: grantValue,
|
||||
}
|
||||
}
|
||||
|
||||
export function AuthzMigrateContractGrantMsg(
|
||||
granter: string,
|
||||
grantee: string,
|
||||
contract: string,
|
||||
expiration: number,
|
||||
callsRemaining?: number,
|
||||
amounts?: Coin[],
|
||||
allowedMessages?: string[],
|
||||
): Msg {
|
||||
const sendAuthValue = ContractMigrationAuthorization.encode(
|
||||
ContractMigrationAuthorization.fromPartial({
|
||||
grants: [
|
||||
{
|
||||
contract,
|
||||
filter: {
|
||||
typeUrl: allowedMessages
|
||||
? '/cosmwasm.wasm.v1.AcceptedMessageKeysFilter'
|
||||
: '/cosmwasm.wasm.v1.AllowAllMessagesFilter',
|
||||
value: allowedMessages
|
||||
? AcceptedMessageKeysFilter.encode({ keys: allowedMessages }).finish()
|
||||
: AllowAllMessagesFilter.encode({}).finish(),
|
||||
},
|
||||
limit:
|
||||
callsRemaining || amounts
|
||||
? {
|
||||
typeUrl:
|
||||
callsRemaining && amounts
|
||||
? '/cosmwasm.wasm.v1.CombinedLimit'
|
||||
: callsRemaining
|
||||
? '/cosmwasm.wasm.v1.MaxCallsLimit'
|
||||
: '/cosmwasm.wasm.v1.MaxFundsLimit',
|
||||
value:
|
||||
callsRemaining && amounts
|
||||
? CombinedLimit.encode({
|
||||
callsRemaining: BigInt(callsRemaining),
|
||||
amounts,
|
||||
}).finish()
|
||||
: callsRemaining
|
||||
? MaxCallsLimit.encode({
|
||||
remaining: BigInt(callsRemaining),
|
||||
}).finish()
|
||||
: MaxFundsLimit.encode({
|
||||
amounts: amounts || [],
|
||||
}).finish(),
|
||||
}
|
||||
: {
|
||||
// limit: undefined is not accepted
|
||||
typeUrl: '/cosmwasm.wasm.v1.MaxCallsLimit',
|
||||
value: MaxCallsLimit.encode({
|
||||
remaining: BigInt(100000),
|
||||
}).finish(),
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
).finish()
|
||||
|
||||
const grantValue = MsgGrant.fromPartial({
|
||||
grant: {
|
||||
authorization: {
|
||||
typeUrl: '/cosmwasm.wasm.v1.ContractMigrationAuthorization',
|
||||
value: sendAuthValue,
|
||||
},
|
||||
expiration: expiration ? { seconds: BigInt(expiration), nanos: 0 } : undefined,
|
||||
},
|
||||
grantee,
|
||||
granter,
|
||||
})
|
||||
|
||||
return {
|
||||
typeUrl: msgAuthzGrantTypeUrl,
|
||||
value: grantValue,
|
||||
}
|
||||
}
|
||||
|
||||
export function AuthzGenericGrantMsg(granter: string, grantee: string, typeURL: string, expiration: number): Msg {
|
||||
return {
|
||||
typeUrl: msgAuthzGrantTypeUrl,
|
||||
value: {
|
||||
grant: {
|
||||
authorization: {
|
||||
typeUrl: '/cosmos.authz.v1beta1.GenericAuthorization',
|
||||
value: GenericAuthorization.encode(
|
||||
GenericAuthorization.fromPartial({
|
||||
msg: typeURL,
|
||||
}),
|
||||
).finish(),
|
||||
},
|
||||
expiration: expiration ? { seconds: expiration } : undefined,
|
||||
},
|
||||
grantee,
|
||||
granter,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export function AuthzStakeGrantMsg({
|
||||
expiration,
|
||||
grantee,
|
||||
granter,
|
||||
allowList,
|
||||
denyList,
|
||||
maxTokens,
|
||||
denom,
|
||||
stakeAuthzType,
|
||||
}: {
|
||||
granter: string
|
||||
grantee: string
|
||||
expiration: number
|
||||
allowList?: string[]
|
||||
denyList?: string[]
|
||||
maxTokens?: string
|
||||
denom?: string
|
||||
stakeAuthzType: AuthorizationType
|
||||
}): Msg {
|
||||
const allow_list = StakeAuthorization_Validators.encode(
|
||||
StakeAuthorization_Validators.fromPartial({
|
||||
address: allowList,
|
||||
}),
|
||||
).finish()
|
||||
const deny_list = StakeAuthorization_Validators.encode(
|
||||
StakeAuthorization_Validators.fromPartial({
|
||||
address: denyList,
|
||||
}),
|
||||
).finish()
|
||||
const stakeAuthValue = StakeAuthorization.encode(
|
||||
StakeAuthorization.fromPartial({
|
||||
authorizationType: stakeAuthzType,
|
||||
allowList: allowList?.length ? StakeAuthorization_Validators.decode(allow_list) : undefined,
|
||||
denyList: denyList?.length ? StakeAuthorization_Validators.decode(deny_list) : undefined,
|
||||
maxTokens: maxTokens
|
||||
? Coin.fromPartial({
|
||||
amount: maxTokens,
|
||||
denom,
|
||||
})
|
||||
: undefined,
|
||||
}),
|
||||
).finish()
|
||||
const grantValue = MsgGrant.fromPartial({
|
||||
grant: {
|
||||
authorization: {
|
||||
typeUrl: '/cosmos.staking.v1beta1.StakeAuthorization',
|
||||
value: stakeAuthValue,
|
||||
},
|
||||
expiration: { seconds: BigInt(expiration) },
|
||||
},
|
||||
grantee,
|
||||
granter,
|
||||
})
|
||||
|
||||
return {
|
||||
typeUrl: msgAuthzGrantTypeUrl,
|
||||
value: grantValue,
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
{
|
||||
"path": "/assets/",
|
||||
"appName": "StargazeStudio",
|
||||
"appShortName": "StargazeStudio",
|
||||
"appName": "Stargaze Studio",
|
||||
"appShortName": "Stargaze Studio",
|
||||
"appDescription": "Stargaze Studio is built to provide useful smart contract interfaces that help you build and deploy your own NFT collection in no time.",
|
||||
"developerName": "StargazeStudio",
|
||||
"developerName": "Stargaze Studio",
|
||||
"developerURL": "https://",
|
||||
"background": "#FFC27D",
|
||||
"theme_color": "#FFC27D",
|
||||
|
||||
@@ -1,3 +1,2 @@
|
||||
export * from './app'
|
||||
export * from './keplr'
|
||||
export * from './network'
|
||||
|
||||
@@ -1,76 +0,0 @@
|
||||
import type { ChainInfo } from '@keplr-wallet/types'
|
||||
|
||||
import type { AppConfig } from './app'
|
||||
|
||||
export interface KeplrCoin {
|
||||
readonly coinDenom: string
|
||||
readonly coinMinimalDenom: string
|
||||
readonly coinDecimals: number
|
||||
}
|
||||
|
||||
export interface KeplrConfig {
|
||||
readonly chainId: string
|
||||
readonly chainName: string
|
||||
readonly rpc: string
|
||||
readonly rest?: string
|
||||
readonly bech32Config: {
|
||||
readonly bech32PrefixAccAddr: string
|
||||
readonly bech32PrefixAccPub: string
|
||||
readonly bech32PrefixValAddr: string
|
||||
readonly bech32PrefixValPub: string
|
||||
readonly bech32PrefixConsAddr: string
|
||||
readonly bech32PrefixConsPub: string
|
||||
}
|
||||
readonly currencies: readonly KeplrCoin[]
|
||||
readonly feeCurrencies: readonly KeplrCoin[]
|
||||
readonly stakeCurrency: KeplrCoin
|
||||
readonly gasPriceStep: {
|
||||
readonly low: number
|
||||
readonly average: number
|
||||
readonly high: number
|
||||
}
|
||||
readonly bip44: { readonly coinType: number }
|
||||
readonly coinType: number
|
||||
}
|
||||
|
||||
export const keplrConfig = (config: AppConfig): ChainInfo => ({
|
||||
chainId: config.chainId,
|
||||
chainName: config.chainName,
|
||||
rpc: config.rpcUrl,
|
||||
rest: config.httpUrl!,
|
||||
bech32Config: {
|
||||
bech32PrefixAccAddr: `${config.addressPrefix}`,
|
||||
bech32PrefixAccPub: `${config.addressPrefix}pub`,
|
||||
bech32PrefixValAddr: `${config.addressPrefix}valoper`,
|
||||
bech32PrefixValPub: `${config.addressPrefix}valoperpub`,
|
||||
bech32PrefixConsAddr: `${config.addressPrefix}valcons`,
|
||||
bech32PrefixConsPub: `${config.addressPrefix}valconspub`,
|
||||
},
|
||||
currencies: [
|
||||
{
|
||||
coinDenom: config.coinMap[config.feeToken].denom,
|
||||
coinMinimalDenom: config.feeToken,
|
||||
coinDecimals: config.coinMap[config.feeToken].fractionalDigits,
|
||||
},
|
||||
],
|
||||
feeCurrencies: [
|
||||
{
|
||||
coinDenom: config.coinMap[config.feeToken].denom,
|
||||
coinMinimalDenom: config.feeToken,
|
||||
coinDecimals: config.coinMap[config.feeToken].fractionalDigits,
|
||||
},
|
||||
],
|
||||
stakeCurrency: {
|
||||
coinDenom: config.coinMap[config.stakingToken].denom,
|
||||
coinMinimalDenom: config.stakingToken,
|
||||
coinDecimals: config.coinMap[config.stakingToken].fractionalDigits,
|
||||
},
|
||||
gasPriceStep: {
|
||||
low: config.gasPrice / 2,
|
||||
average: config.gasPrice,
|
||||
high: config.gasPrice * 2,
|
||||
},
|
||||
bip44: { coinType: 118 },
|
||||
coinType: 118,
|
||||
features: ['ibc-transfer', 'cosmwasm', 'ibc-go'],
|
||||
})
|
||||
+1
-1
@@ -6,6 +6,6 @@ export const meta = {
|
||||
domain: 'stargaze.tools',
|
||||
url: faviconsJson.developerURL,
|
||||
twitter: {
|
||||
username: '@stargazestudio',
|
||||
username: '@StargazeZone',
|
||||
},
|
||||
}
|
||||
|
||||
+631
-1
@@ -1,28 +1,87 @@
|
||||
import {
|
||||
FEATURED_IBC_TIA_FACTORY_ADDRESS,
|
||||
FEATURED_IBC_USDC_FACTORY_ADDRESS,
|
||||
FEATURED_VENDING_FACTORY_ADDRESS,
|
||||
FEATURED_VENDING_FACTORY_FLEX_ADDRESS,
|
||||
FEATURED_VENDING_FACTORY_MERKLE_TREE_ADDRESS,
|
||||
FEATURED_VENDING_IBC_TIA_FACTORY_FLEX_ADDRESS,
|
||||
FEATURED_VENDING_IBC_TIA_FACTORY_MERKLE_TREE_ADDRESS,
|
||||
FEATURED_VENDING_IBC_USDC_FACTORY_FLEX_ADDRESS,
|
||||
OPEN_EDITION_FACTORY_ADDRESS,
|
||||
OPEN_EDITION_FACTORY_FLEX_ADDRESS,
|
||||
OPEN_EDITION_IBC_ATOM_FACTORY_ADDRESS,
|
||||
OPEN_EDITION_IBC_ATOM_FACTORY_FLEX_ADDRESS,
|
||||
OPEN_EDITION_IBC_CRBRUS_FACTORY_ADDRESS,
|
||||
OPEN_EDITION_IBC_FRNZ_FACTORY_ADDRESS,
|
||||
OPEN_EDITION_IBC_KUJI_FACTORY_ADDRESS,
|
||||
OPEN_EDITION_IBC_NBTC_FACTORY_ADDRESS,
|
||||
OPEN_EDITION_IBC_TIA_FACTORY_ADDRESS,
|
||||
OPEN_EDITION_IBC_TIA_FACTORY_FLEX_ADDRESS,
|
||||
OPEN_EDITION_IBC_USDC_FACTORY_ADDRESS,
|
||||
OPEN_EDITION_IBC_USDC_FACTORY_FLEX_ADDRESS,
|
||||
OPEN_EDITION_IBC_USK_FACTORY_ADDRESS,
|
||||
OPEN_EDITION_NATIVE_BRNCH_FACTORY_ADDRESS,
|
||||
OPEN_EDITION_NATIVE_STRDST_FACTORY_ADDRESS,
|
||||
OPEN_EDITION_UPDATABLE_FACTORY_ADDRESS,
|
||||
OPEN_EDITION_UPDATABLE_IBC_ATOM_FACTORY_ADDRESS,
|
||||
OPEN_EDITION_UPDATABLE_IBC_FRNZ_FACTORY_ADDRESS,
|
||||
OPEN_EDITION_UPDATABLE_IBC_NBTC_FACTORY_ADDRESS,
|
||||
OPEN_EDITION_UPDATABLE_IBC_TIA_FACTORY_ADDRESS,
|
||||
OPEN_EDITION_UPDATABLE_IBC_USDC_FACTORY_ADDRESS,
|
||||
OPEN_EDITION_UPDATABLE_IBC_USK_FACTORY_ADDRESS,
|
||||
VENDING_FACTORY_ADDRESS,
|
||||
VENDING_FACTORY_FLEX_ADDRESS,
|
||||
VENDING_FACTORY_MERKLE_TREE_ADDRESS,
|
||||
VENDING_FACTORY_UPDATABLE_ADDRESS,
|
||||
VENDING_FACTORY_UPDATABLE_FLEX_ADDRESS,
|
||||
VENDING_IBC_ATOM_FACTORY_ADDRESS,
|
||||
VENDING_IBC_ATOM_FACTORY_FLEX_ADDRESS,
|
||||
VENDING_IBC_ATOM_UPDATABLE_FACTORY_ADDRESS,
|
||||
VENDING_IBC_ATOM_UPDATABLE_FACTORY_FLEX_ADDRESS,
|
||||
VENDING_IBC_CRBRUS_FACTORY_ADDRESS,
|
||||
VENDING_IBC_CRBRUS_FACTORY_FLEX_ADDRESS,
|
||||
VENDING_IBC_KUJI_FACTORY_ADDRESS,
|
||||
VENDING_IBC_KUJI_FACTORY_FLEX_ADDRESS,
|
||||
VENDING_IBC_NBTC_FACTORY_ADDRESS,
|
||||
VENDING_IBC_NBTC_FACTORY_FLEX_ADDRESS,
|
||||
VENDING_IBC_NBTC_UPDATABLE_FACTORY_ADDRESS,
|
||||
VENDING_IBC_NBTC_UPDATABLE_FACTORY_FLEX_ADDRESS,
|
||||
VENDING_IBC_TIA_FACTORY_ADDRESS,
|
||||
VENDING_IBC_TIA_FACTORY_FLEX_ADDRESS,
|
||||
VENDING_IBC_TIA_FACTORY_MERKLE_TREE_ADDRESS,
|
||||
VENDING_IBC_TIA_UPDATABLE_FACTORY_ADDRESS,
|
||||
VENDING_IBC_TIA_UPDATABLE_FACTORY_FLEX_ADDRESS,
|
||||
VENDING_IBC_USDC_FACTORY_ADDRESS,
|
||||
VENDING_IBC_USDC_FACTORY_FLEX_ADDRESS,
|
||||
VENDING_IBC_USDC_UPDATABLE_FACTORY_ADDRESS,
|
||||
VENDING_IBC_USDC_UPDATABLE_FACTORY_FLEX_ADDRESS,
|
||||
VENDING_IBC_USK_FACTORY_ADDRESS,
|
||||
VENDING_IBC_USK_FACTORY_FLEX_ADDRESS,
|
||||
VENDING_IBC_USK_UPDATABLE_FACTORY_ADDRESS,
|
||||
VENDING_IBC_USK_UPDATABLE_FACTORY_FLEX_ADDRESS,
|
||||
VENDING_NATIVE_BRNCH_FACTORY_ADDRESS,
|
||||
VENDING_NATIVE_BRNCH_FLEX_FACTORY_ADDRESS,
|
||||
VENDING_NATIVE_BRNCH_UPDATABLE_FACTORY_ADDRESS,
|
||||
VENDING_NATIVE_STARDUST_FACTORY_ADDRESS,
|
||||
VENDING_NATIVE_STARDUST_UPDATABLE_FACTORY_ADDRESS,
|
||||
VENDING_NATIVE_STRDST_FLEX_FACTORY_ADDRESS,
|
||||
} from 'utils/constants'
|
||||
|
||||
import type { TokenInfo } from './token'
|
||||
import { ibcAtom, ibcFrnz, ibcUsdc, stars } from './token'
|
||||
import {
|
||||
ibcAtom,
|
||||
ibcCrbrus,
|
||||
ibcFrnz,
|
||||
// ibcHuahua,
|
||||
ibcKuji,
|
||||
ibcNbtc,
|
||||
ibcTia,
|
||||
ibcUsdc,
|
||||
ibcUsk,
|
||||
nativeBrnch,
|
||||
nativeStardust,
|
||||
stars,
|
||||
} from './token'
|
||||
|
||||
export interface MinterInfo {
|
||||
id: string
|
||||
@@ -30,6 +89,8 @@ export interface MinterInfo {
|
||||
supportedToken: TokenInfo
|
||||
updatable?: boolean
|
||||
flexible?: boolean
|
||||
merkleTree?: boolean
|
||||
featured?: boolean
|
||||
}
|
||||
|
||||
export const openEditionStarsMinter: MinterInfo = {
|
||||
@@ -37,6 +98,8 @@ export const openEditionStarsMinter: MinterInfo = {
|
||||
factoryAddress: OPEN_EDITION_FACTORY_ADDRESS,
|
||||
supportedToken: stars,
|
||||
updatable: false,
|
||||
featured: false,
|
||||
flexible: false,
|
||||
}
|
||||
|
||||
export const openEditionUpdatableStarsMinter: MinterInfo = {
|
||||
@@ -44,6 +107,8 @@ export const openEditionUpdatableStarsMinter: MinterInfo = {
|
||||
factoryAddress: OPEN_EDITION_UPDATABLE_FACTORY_ADDRESS,
|
||||
supportedToken: stars,
|
||||
updatable: true,
|
||||
featured: false,
|
||||
flexible: false,
|
||||
}
|
||||
|
||||
export const openEditionIbcAtomMinter: MinterInfo = {
|
||||
@@ -51,6 +116,8 @@ export const openEditionIbcAtomMinter: MinterInfo = {
|
||||
factoryAddress: OPEN_EDITION_IBC_ATOM_FACTORY_ADDRESS,
|
||||
supportedToken: ibcAtom,
|
||||
updatable: false,
|
||||
featured: false,
|
||||
flexible: false,
|
||||
}
|
||||
|
||||
export const openEditionUpdatableIbcAtomMinter: MinterInfo = {
|
||||
@@ -58,6 +125,8 @@ export const openEditionUpdatableIbcAtomMinter: MinterInfo = {
|
||||
factoryAddress: OPEN_EDITION_UPDATABLE_IBC_ATOM_FACTORY_ADDRESS,
|
||||
supportedToken: ibcAtom,
|
||||
updatable: true,
|
||||
featured: false,
|
||||
flexible: false,
|
||||
}
|
||||
|
||||
export const openEditionIbcUsdcMinter: MinterInfo = {
|
||||
@@ -65,6 +134,26 @@ export const openEditionIbcUsdcMinter: MinterInfo = {
|
||||
factoryAddress: OPEN_EDITION_IBC_USDC_FACTORY_ADDRESS,
|
||||
supportedToken: ibcUsdc,
|
||||
updatable: false,
|
||||
featured: false,
|
||||
flexible: false,
|
||||
}
|
||||
|
||||
export const openEditionIbcTiaMinter: MinterInfo = {
|
||||
id: 'open-edition-ibc-tia-minter',
|
||||
factoryAddress: OPEN_EDITION_IBC_TIA_FACTORY_ADDRESS,
|
||||
supportedToken: ibcTia,
|
||||
updatable: false,
|
||||
featured: false,
|
||||
flexible: false,
|
||||
}
|
||||
|
||||
export const openEditionIbcNbtcMinter: MinterInfo = {
|
||||
id: 'open-edition-ibc-nbtc-minter',
|
||||
factoryAddress: OPEN_EDITION_IBC_NBTC_FACTORY_ADDRESS,
|
||||
supportedToken: ibcNbtc,
|
||||
updatable: false,
|
||||
featured: false,
|
||||
flexible: false,
|
||||
}
|
||||
|
||||
export const openEditionUpdatableIbcUsdcMinter: MinterInfo = {
|
||||
@@ -72,6 +161,26 @@ export const openEditionUpdatableIbcUsdcMinter: MinterInfo = {
|
||||
factoryAddress: OPEN_EDITION_UPDATABLE_IBC_USDC_FACTORY_ADDRESS,
|
||||
supportedToken: ibcUsdc,
|
||||
updatable: true,
|
||||
featured: false,
|
||||
flexible: false,
|
||||
}
|
||||
|
||||
export const openEditionUpdatableIbcTiaMinter: MinterInfo = {
|
||||
id: 'open-edition-updatable-ibc-tia-minter',
|
||||
factoryAddress: OPEN_EDITION_UPDATABLE_IBC_TIA_FACTORY_ADDRESS,
|
||||
supportedToken: ibcTia,
|
||||
updatable: true,
|
||||
featured: false,
|
||||
flexible: false,
|
||||
}
|
||||
|
||||
export const openEditionUpdatableIbcNbtcMinter: MinterInfo = {
|
||||
id: 'open-edition-updatable-ibc-nbtc-minter',
|
||||
factoryAddress: OPEN_EDITION_UPDATABLE_IBC_NBTC_FACTORY_ADDRESS,
|
||||
supportedToken: ibcNbtc,
|
||||
updatable: true,
|
||||
featured: false,
|
||||
flexible: false,
|
||||
}
|
||||
|
||||
export const openEditionIbcFrnzMinter: MinterInfo = {
|
||||
@@ -79,6 +188,8 @@ export const openEditionIbcFrnzMinter: MinterInfo = {
|
||||
factoryAddress: OPEN_EDITION_IBC_FRNZ_FACTORY_ADDRESS,
|
||||
supportedToken: ibcFrnz,
|
||||
updatable: false,
|
||||
featured: false,
|
||||
flexible: false,
|
||||
}
|
||||
|
||||
export const openEditionUpdatableIbcFrnzMinter: MinterInfo = {
|
||||
@@ -86,6 +197,70 @@ export const openEditionUpdatableIbcFrnzMinter: MinterInfo = {
|
||||
factoryAddress: OPEN_EDITION_UPDATABLE_IBC_FRNZ_FACTORY_ADDRESS,
|
||||
supportedToken: ibcFrnz,
|
||||
updatable: true,
|
||||
featured: false,
|
||||
flexible: false,
|
||||
}
|
||||
|
||||
export const openEditionIbcUskMinter: MinterInfo = {
|
||||
id: 'open-edition-ibc-usk-minter',
|
||||
factoryAddress: OPEN_EDITION_IBC_USK_FACTORY_ADDRESS,
|
||||
supportedToken: ibcUsk,
|
||||
updatable: false,
|
||||
featured: false,
|
||||
flexible: false,
|
||||
}
|
||||
|
||||
export const openEditionUpdatableIbcUskMinter: MinterInfo = {
|
||||
id: 'open-edition-updatable-ibc-usk-minter',
|
||||
factoryAddress: OPEN_EDITION_UPDATABLE_IBC_USK_FACTORY_ADDRESS,
|
||||
supportedToken: ibcUsk,
|
||||
updatable: true,
|
||||
featured: false,
|
||||
flexible: false,
|
||||
}
|
||||
|
||||
export const openEditionIbcKujiMinter: MinterInfo = {
|
||||
id: 'open-edition-ibc-kuji-minter',
|
||||
factoryAddress: OPEN_EDITION_IBC_KUJI_FACTORY_ADDRESS,
|
||||
supportedToken: ibcKuji,
|
||||
updatable: false,
|
||||
featured: false,
|
||||
flexible: false,
|
||||
}
|
||||
|
||||
// export const openEditionIbcHuahuaMinter: MinterInfo = {
|
||||
// id: 'open-edition-ibc-huahua-minter',
|
||||
// factoryAddress: OPEN_EDITION_IBC_HUAHUA_FACTORY_ADDRESS,
|
||||
// supportedToken: ibcHuahua,
|
||||
// updatable: false,
|
||||
// featured: false,
|
||||
// }
|
||||
|
||||
export const openEditionIbcCrbrusMinter: MinterInfo = {
|
||||
id: 'open-edition-ibc-crbrus-minter',
|
||||
factoryAddress: OPEN_EDITION_IBC_CRBRUS_FACTORY_ADDRESS,
|
||||
supportedToken: ibcCrbrus,
|
||||
updatable: false,
|
||||
featured: false,
|
||||
flexible: false,
|
||||
}
|
||||
|
||||
export const openEditionNativeStrdstMinter: MinterInfo = {
|
||||
id: 'open-edition-native-strdst-minter',
|
||||
factoryAddress: OPEN_EDITION_NATIVE_STRDST_FACTORY_ADDRESS,
|
||||
supportedToken: nativeStardust,
|
||||
updatable: false,
|
||||
featured: false,
|
||||
flexible: false,
|
||||
}
|
||||
|
||||
export const openEditionNativeBrnchMinter: MinterInfo = {
|
||||
id: 'open-edition-native-brnch-minter',
|
||||
factoryAddress: OPEN_EDITION_NATIVE_BRNCH_FACTORY_ADDRESS,
|
||||
supportedToken: nativeBrnch,
|
||||
updatable: false,
|
||||
featured: false,
|
||||
flexible: false,
|
||||
}
|
||||
|
||||
export const openEditionMinterList = [
|
||||
@@ -97,6 +272,60 @@ export const openEditionMinterList = [
|
||||
openEditionUpdatableIbcFrnzMinter,
|
||||
openEditionIbcUsdcMinter,
|
||||
openEditionUpdatableIbcUsdcMinter,
|
||||
openEditionIbcTiaMinter,
|
||||
openEditionUpdatableIbcTiaMinter,
|
||||
openEditionIbcNbtcMinter,
|
||||
openEditionUpdatableIbcNbtcMinter,
|
||||
openEditionIbcUskMinter,
|
||||
openEditionUpdatableIbcUskMinter,
|
||||
openEditionIbcKujiMinter,
|
||||
// openEditionIbcHuahuaMinter,
|
||||
openEditionIbcCrbrusMinter,
|
||||
openEditionNativeStrdstMinter,
|
||||
openEditionNativeBrnchMinter,
|
||||
]
|
||||
|
||||
export const flexibleOpenEditionStarsMinter: MinterInfo = {
|
||||
id: 'flexible-open-edition-stars-minter',
|
||||
factoryAddress: OPEN_EDITION_FACTORY_FLEX_ADDRESS,
|
||||
supportedToken: stars,
|
||||
updatable: false,
|
||||
featured: false,
|
||||
flexible: true,
|
||||
}
|
||||
|
||||
export const flexibleOpenEditionIbcAtomMinter: MinterInfo = {
|
||||
id: 'flexible-open-edition-ibc-atom-minter',
|
||||
factoryAddress: OPEN_EDITION_IBC_ATOM_FACTORY_FLEX_ADDRESS,
|
||||
supportedToken: ibcAtom,
|
||||
updatable: false,
|
||||
featured: false,
|
||||
flexible: true,
|
||||
}
|
||||
|
||||
export const flexibleOpenEditionIbcUsdcMinter: MinterInfo = {
|
||||
id: 'flexible-open-edition-ibc-usdc-minter',
|
||||
factoryAddress: OPEN_EDITION_IBC_USDC_FACTORY_FLEX_ADDRESS,
|
||||
supportedToken: ibcUsdc,
|
||||
updatable: false,
|
||||
featured: false,
|
||||
flexible: true,
|
||||
}
|
||||
|
||||
export const flexibleOpenEditionIbcTiaMinter: MinterInfo = {
|
||||
id: 'flexible-open-edition-ibc-tia-minter',
|
||||
factoryAddress: OPEN_EDITION_IBC_TIA_FACTORY_FLEX_ADDRESS,
|
||||
supportedToken: ibcTia,
|
||||
updatable: false,
|
||||
featured: false,
|
||||
flexible: true,
|
||||
}
|
||||
|
||||
export const flexibleOpenEditionMinterList = [
|
||||
flexibleOpenEditionStarsMinter,
|
||||
flexibleOpenEditionIbcAtomMinter,
|
||||
flexibleOpenEditionIbcUsdcMinter,
|
||||
flexibleOpenEditionIbcTiaMinter,
|
||||
]
|
||||
|
||||
export const vendingStarsMinter: MinterInfo = {
|
||||
@@ -105,6 +334,18 @@ export const vendingStarsMinter: MinterInfo = {
|
||||
supportedToken: stars,
|
||||
updatable: false,
|
||||
flexible: false,
|
||||
merkleTree: false,
|
||||
featured: false,
|
||||
}
|
||||
|
||||
export const vendingFeaturedStarsMinter: MinterInfo = {
|
||||
id: 'vending-stars-minter',
|
||||
factoryAddress: FEATURED_VENDING_FACTORY_ADDRESS,
|
||||
supportedToken: stars,
|
||||
updatable: false,
|
||||
flexible: false,
|
||||
merkleTree: false,
|
||||
featured: true,
|
||||
}
|
||||
|
||||
export const vendingUpdatableStarsMinter: MinterInfo = {
|
||||
@@ -113,6 +354,8 @@ export const vendingUpdatableStarsMinter: MinterInfo = {
|
||||
supportedToken: stars,
|
||||
updatable: true,
|
||||
flexible: false,
|
||||
merkleTree: false,
|
||||
featured: false,
|
||||
}
|
||||
|
||||
export const vendingIbcAtomMinter: MinterInfo = {
|
||||
@@ -121,6 +364,8 @@ export const vendingIbcAtomMinter: MinterInfo = {
|
||||
supportedToken: ibcAtom,
|
||||
updatable: false,
|
||||
flexible: false,
|
||||
merkleTree: false,
|
||||
featured: false,
|
||||
}
|
||||
|
||||
export const vendingUpdatableIbcAtomMinter: MinterInfo = {
|
||||
@@ -129,6 +374,8 @@ export const vendingUpdatableIbcAtomMinter: MinterInfo = {
|
||||
supportedToken: ibcAtom,
|
||||
updatable: true,
|
||||
flexible: false,
|
||||
merkleTree: false,
|
||||
featured: false,
|
||||
}
|
||||
|
||||
export const vendingIbcUsdcMinter: MinterInfo = {
|
||||
@@ -137,6 +384,48 @@ export const vendingIbcUsdcMinter: MinterInfo = {
|
||||
supportedToken: ibcUsdc,
|
||||
updatable: false,
|
||||
flexible: false,
|
||||
merkleTree: false,
|
||||
featured: false,
|
||||
}
|
||||
|
||||
export const vendingFeaturedIbcUsdcMinter: MinterInfo = {
|
||||
id: 'vending-featured-ibc-usdc-minter',
|
||||
factoryAddress: FEATURED_IBC_USDC_FACTORY_ADDRESS,
|
||||
supportedToken: ibcUsdc,
|
||||
updatable: false,
|
||||
flexible: false,
|
||||
merkleTree: false,
|
||||
featured: true,
|
||||
}
|
||||
|
||||
export const vendingIbcTiaMinter: MinterInfo = {
|
||||
id: 'vending-ibc-tia-minter',
|
||||
factoryAddress: VENDING_IBC_TIA_FACTORY_ADDRESS,
|
||||
supportedToken: ibcTia,
|
||||
updatable: false,
|
||||
flexible: false,
|
||||
merkleTree: false,
|
||||
featured: false,
|
||||
}
|
||||
|
||||
export const vendingFeaturedIbcTiaMinter: MinterInfo = {
|
||||
id: 'vending-featured-ibc-tia-minter',
|
||||
factoryAddress: FEATURED_IBC_TIA_FACTORY_ADDRESS,
|
||||
supportedToken: ibcTia,
|
||||
updatable: false,
|
||||
flexible: false,
|
||||
merkleTree: false,
|
||||
featured: true,
|
||||
}
|
||||
|
||||
export const vendingIbcNbtcMinter: MinterInfo = {
|
||||
id: 'vending-ibc-nbtc-minter',
|
||||
factoryAddress: VENDING_IBC_NBTC_FACTORY_ADDRESS,
|
||||
supportedToken: ibcNbtc,
|
||||
updatable: false,
|
||||
flexible: false,
|
||||
merkleTree: false,
|
||||
featured: false,
|
||||
}
|
||||
|
||||
export const vendingUpdatableIbcUsdcMinter: MinterInfo = {
|
||||
@@ -145,15 +434,143 @@ export const vendingUpdatableIbcUsdcMinter: MinterInfo = {
|
||||
supportedToken: ibcUsdc,
|
||||
updatable: true,
|
||||
flexible: false,
|
||||
merkleTree: false,
|
||||
featured: false,
|
||||
}
|
||||
|
||||
export const vendingUpdatableIbcTiaMinter: MinterInfo = {
|
||||
id: 'vending-updatable-ibc-tia-minter',
|
||||
factoryAddress: VENDING_IBC_TIA_UPDATABLE_FACTORY_ADDRESS,
|
||||
supportedToken: ibcTia,
|
||||
updatable: true,
|
||||
flexible: false,
|
||||
merkleTree: false,
|
||||
featured: false,
|
||||
}
|
||||
|
||||
export const vendingUpdatableIbcNbtcMinter: MinterInfo = {
|
||||
id: 'vending-updatable-ibc-nbtc-minter',
|
||||
factoryAddress: VENDING_IBC_NBTC_UPDATABLE_FACTORY_ADDRESS,
|
||||
supportedToken: ibcNbtc,
|
||||
updatable: true,
|
||||
flexible: false,
|
||||
merkleTree: false,
|
||||
featured: false,
|
||||
}
|
||||
|
||||
export const vendingIbcUskMinter: MinterInfo = {
|
||||
id: 'vending-ibc-usk-minter',
|
||||
factoryAddress: VENDING_IBC_USK_FACTORY_ADDRESS,
|
||||
supportedToken: ibcUsk,
|
||||
updatable: false,
|
||||
flexible: false,
|
||||
merkleTree: false,
|
||||
featured: false,
|
||||
}
|
||||
|
||||
export const vendingUpdatableIbcUskMinter: MinterInfo = {
|
||||
id: 'vending-updatable-ibc-usk-minter',
|
||||
factoryAddress: VENDING_IBC_USK_UPDATABLE_FACTORY_ADDRESS,
|
||||
supportedToken: ibcUsk,
|
||||
updatable: true,
|
||||
flexible: false,
|
||||
merkleTree: false,
|
||||
featured: false,
|
||||
}
|
||||
|
||||
export const vendingIbcKujiMinter: MinterInfo = {
|
||||
id: 'vending-ibc-kuji-minter',
|
||||
factoryAddress: VENDING_IBC_KUJI_FACTORY_ADDRESS,
|
||||
supportedToken: ibcKuji,
|
||||
updatable: false,
|
||||
flexible: false,
|
||||
merkleTree: false,
|
||||
featured: false,
|
||||
}
|
||||
|
||||
// export const vendingIbcHuahuaMinter: MinterInfo = {
|
||||
// id: 'vending-ibc-huahua-minter',
|
||||
// factoryAddress: VENDING_IBC_HUAHUA_FACTORY_ADDRESS,
|
||||
// supportedToken: ibcHuahua,
|
||||
// updatable: false,
|
||||
// flexible: false,
|
||||
// merkleTree: false,
|
||||
// featured: false,
|
||||
// }
|
||||
|
||||
export const vendingIbcCrbrusMinter: MinterInfo = {
|
||||
id: 'vending-ibc-crbrus-minter',
|
||||
factoryAddress: VENDING_IBC_CRBRUS_FACTORY_ADDRESS,
|
||||
supportedToken: ibcCrbrus,
|
||||
updatable: false,
|
||||
flexible: false,
|
||||
merkleTree: false,
|
||||
featured: false,
|
||||
}
|
||||
|
||||
export const vendingNativeStardustMinter: MinterInfo = {
|
||||
id: 'vending-native-stardust-minter',
|
||||
factoryAddress: VENDING_NATIVE_STARDUST_FACTORY_ADDRESS,
|
||||
supportedToken: nativeStardust,
|
||||
updatable: false,
|
||||
flexible: false,
|
||||
merkleTree: false,
|
||||
featured: false,
|
||||
}
|
||||
|
||||
export const vendingUpdatableNativeStardustMinter: MinterInfo = {
|
||||
id: 'vending-native-stardust-minter',
|
||||
factoryAddress: VENDING_NATIVE_STARDUST_UPDATABLE_FACTORY_ADDRESS,
|
||||
supportedToken: nativeStardust,
|
||||
updatable: true,
|
||||
flexible: false,
|
||||
merkleTree: false,
|
||||
featured: false,
|
||||
}
|
||||
|
||||
export const vendingNativeBrnchMinter: MinterInfo = {
|
||||
id: 'vending-native-brnch-minter',
|
||||
factoryAddress: VENDING_NATIVE_BRNCH_FACTORY_ADDRESS,
|
||||
supportedToken: nativeBrnch,
|
||||
updatable: false,
|
||||
flexible: false,
|
||||
merkleTree: false,
|
||||
featured: false,
|
||||
}
|
||||
|
||||
export const vendingUpdatableNativeBrnchMinter: MinterInfo = {
|
||||
id: 'vending-native-brnch-minter',
|
||||
factoryAddress: VENDING_NATIVE_BRNCH_UPDATABLE_FACTORY_ADDRESS,
|
||||
supportedToken: nativeBrnch,
|
||||
updatable: true,
|
||||
flexible: false,
|
||||
merkleTree: false,
|
||||
featured: false,
|
||||
}
|
||||
|
||||
export const vendingMinterList = [
|
||||
vendingStarsMinter,
|
||||
vendingFeaturedStarsMinter,
|
||||
vendingUpdatableStarsMinter,
|
||||
vendingIbcAtomMinter,
|
||||
vendingUpdatableIbcAtomMinter,
|
||||
vendingIbcUsdcMinter,
|
||||
vendingFeaturedIbcUsdcMinter,
|
||||
vendingUpdatableIbcUsdcMinter,
|
||||
vendingIbcTiaMinter,
|
||||
vendingFeaturedIbcTiaMinter,
|
||||
vendingUpdatableIbcTiaMinter,
|
||||
vendingIbcNbtcMinter,
|
||||
vendingUpdatableIbcNbtcMinter,
|
||||
vendingIbcUskMinter,
|
||||
vendingUpdatableIbcUskMinter,
|
||||
vendingIbcKujiMinter,
|
||||
// vendingIbcHuahuaMinter,
|
||||
vendingIbcCrbrusMinter,
|
||||
vendingNativeStardustMinter,
|
||||
vendingUpdatableNativeStardustMinter,
|
||||
vendingNativeBrnchMinter,
|
||||
vendingUpdatableNativeBrnchMinter,
|
||||
]
|
||||
|
||||
export const flexibleVendingStarsMinter: MinterInfo = {
|
||||
@@ -162,6 +579,18 @@ export const flexibleVendingStarsMinter: MinterInfo = {
|
||||
supportedToken: stars,
|
||||
updatable: false,
|
||||
flexible: true,
|
||||
merkleTree: false,
|
||||
featured: false,
|
||||
}
|
||||
|
||||
export const flexibleFeaturedVendingStarsMinter: MinterInfo = {
|
||||
id: 'flexible-vending-stars-minter',
|
||||
factoryAddress: FEATURED_VENDING_FACTORY_FLEX_ADDRESS,
|
||||
supportedToken: stars,
|
||||
updatable: false,
|
||||
flexible: true,
|
||||
merkleTree: false,
|
||||
featured: true,
|
||||
}
|
||||
|
||||
export const flexibleVendingUpdatableStarsMinter: MinterInfo = {
|
||||
@@ -170,6 +599,8 @@ export const flexibleVendingUpdatableStarsMinter: MinterInfo = {
|
||||
supportedToken: stars,
|
||||
updatable: true,
|
||||
flexible: true,
|
||||
merkleTree: false,
|
||||
featured: false,
|
||||
}
|
||||
|
||||
export const flexibleVendingIbcAtomMinter: MinterInfo = {
|
||||
@@ -178,6 +609,8 @@ export const flexibleVendingIbcAtomMinter: MinterInfo = {
|
||||
supportedToken: ibcAtom,
|
||||
updatable: false,
|
||||
flexible: true,
|
||||
merkleTree: false,
|
||||
featured: false,
|
||||
}
|
||||
|
||||
export const flexibleVendingUpdatableIbcAtomMinter: MinterInfo = {
|
||||
@@ -186,6 +619,8 @@ export const flexibleVendingUpdatableIbcAtomMinter: MinterInfo = {
|
||||
supportedToken: ibcAtom,
|
||||
updatable: true,
|
||||
flexible: true,
|
||||
merkleTree: false,
|
||||
featured: false,
|
||||
}
|
||||
|
||||
export const flexibleVendingIbcUsdcMinter: MinterInfo = {
|
||||
@@ -194,6 +629,48 @@ export const flexibleVendingIbcUsdcMinter: MinterInfo = {
|
||||
supportedToken: ibcUsdc,
|
||||
updatable: false,
|
||||
flexible: true,
|
||||
merkleTree: false,
|
||||
featured: false,
|
||||
}
|
||||
|
||||
export const flexibleFeaturedVendingIbcUsdcMinter: MinterInfo = {
|
||||
id: 'flexible-featured-vending-ibc-usdc-minter',
|
||||
factoryAddress: FEATURED_VENDING_IBC_USDC_FACTORY_FLEX_ADDRESS,
|
||||
supportedToken: ibcUsdc,
|
||||
updatable: false,
|
||||
flexible: true,
|
||||
merkleTree: false,
|
||||
featured: true,
|
||||
}
|
||||
|
||||
export const flexibleVendingIbcTiaMinter: MinterInfo = {
|
||||
id: 'flexible-vending-ibc-tia-minter',
|
||||
factoryAddress: VENDING_IBC_TIA_FACTORY_FLEX_ADDRESS,
|
||||
supportedToken: ibcTia,
|
||||
updatable: false,
|
||||
flexible: true,
|
||||
merkleTree: false,
|
||||
featured: false,
|
||||
}
|
||||
|
||||
export const flexibleFeaturedVendingIbcTiaMinter: MinterInfo = {
|
||||
id: 'flexible-featured-vending-ibc-tia-minter',
|
||||
factoryAddress: FEATURED_VENDING_IBC_TIA_FACTORY_FLEX_ADDRESS,
|
||||
supportedToken: ibcTia,
|
||||
updatable: false,
|
||||
flexible: true,
|
||||
merkleTree: false,
|
||||
featured: true,
|
||||
}
|
||||
|
||||
export const flexibleVendingIbcNbtcMinter: MinterInfo = {
|
||||
id: 'flexible-vending-ibc-nbtc-minter',
|
||||
factoryAddress: VENDING_IBC_NBTC_FACTORY_FLEX_ADDRESS,
|
||||
supportedToken: ibcNbtc,
|
||||
updatable: false,
|
||||
flexible: true,
|
||||
merkleTree: false,
|
||||
featured: false,
|
||||
}
|
||||
|
||||
export const flexibleVendingUpdatableIbcUsdcMinter: MinterInfo = {
|
||||
@@ -202,13 +679,166 @@ export const flexibleVendingUpdatableIbcUsdcMinter: MinterInfo = {
|
||||
supportedToken: ibcUsdc,
|
||||
updatable: true,
|
||||
flexible: true,
|
||||
merkleTree: false,
|
||||
featured: false,
|
||||
}
|
||||
|
||||
export const flexibleVendingUpdatableIbcTiaMinter: MinterInfo = {
|
||||
id: 'flexible-vending-updatable-ibc-tia-minter',
|
||||
factoryAddress: VENDING_IBC_TIA_UPDATABLE_FACTORY_FLEX_ADDRESS,
|
||||
supportedToken: ibcTia,
|
||||
updatable: true,
|
||||
flexible: true,
|
||||
merkleTree: false,
|
||||
featured: false,
|
||||
}
|
||||
|
||||
export const flexibleVendingUpdatableIbcNbtcMinter: MinterInfo = {
|
||||
id: 'flexible-vending-updatable-ibc-nbtc-minter',
|
||||
factoryAddress: VENDING_IBC_NBTC_UPDATABLE_FACTORY_FLEX_ADDRESS,
|
||||
supportedToken: ibcNbtc,
|
||||
updatable: true,
|
||||
flexible: true,
|
||||
merkleTree: false,
|
||||
featured: false,
|
||||
}
|
||||
|
||||
export const flexibleVendingIbcUskMinter: MinterInfo = {
|
||||
id: 'flexible-vending-ibc-usk-minter',
|
||||
factoryAddress: VENDING_IBC_USK_FACTORY_FLEX_ADDRESS,
|
||||
supportedToken: ibcUsk,
|
||||
updatable: false,
|
||||
flexible: true,
|
||||
merkleTree: false,
|
||||
featured: false,
|
||||
}
|
||||
|
||||
export const flexibleVendingUpdatableIbcUskMinter: MinterInfo = {
|
||||
id: 'flexible-vending-updatable-ibc-usk-minter',
|
||||
factoryAddress: VENDING_IBC_USK_UPDATABLE_FACTORY_FLEX_ADDRESS,
|
||||
supportedToken: ibcUsk,
|
||||
updatable: true,
|
||||
flexible: true,
|
||||
merkleTree: false,
|
||||
featured: false,
|
||||
}
|
||||
|
||||
export const flexibleVendingIbcKujiMinter: MinterInfo = {
|
||||
id: 'flexible-vending-ibc-kuji-minter',
|
||||
factoryAddress: VENDING_IBC_KUJI_FACTORY_FLEX_ADDRESS,
|
||||
supportedToken: ibcKuji,
|
||||
updatable: false,
|
||||
flexible: true,
|
||||
merkleTree: false,
|
||||
featured: false,
|
||||
}
|
||||
|
||||
// export const flexibleVendingIbcHuahuaMinter: MinterInfo = {
|
||||
// id: 'flexible-vending-ibc-huahua-minter',
|
||||
// factoryAddress: VENDING_IBC_HUAHUA_FACTORY_FLEX_ADDRESS,
|
||||
// supportedToken: ibcHuahua,
|
||||
// updatable: false,
|
||||
// flexible: true,
|
||||
// merkleTree: false,
|
||||
// featured: false,
|
||||
// }
|
||||
|
||||
export const flexibleVendingIbcCrbrusMinter: MinterInfo = {
|
||||
id: 'flexible-vending-ibc-crbrus-minter',
|
||||
factoryAddress: VENDING_IBC_CRBRUS_FACTORY_FLEX_ADDRESS,
|
||||
supportedToken: ibcCrbrus,
|
||||
updatable: false,
|
||||
flexible: true,
|
||||
merkleTree: false,
|
||||
featured: false,
|
||||
}
|
||||
|
||||
export const flexibleVendingStrdstMinter: MinterInfo = {
|
||||
id: 'flexible-vending-native-strdst-minter',
|
||||
factoryAddress: VENDING_NATIVE_STRDST_FLEX_FACTORY_ADDRESS,
|
||||
supportedToken: nativeStardust,
|
||||
updatable: false,
|
||||
flexible: true,
|
||||
merkleTree: false,
|
||||
featured: false,
|
||||
}
|
||||
|
||||
export const flexibleVendingBrnchMinter: MinterInfo = {
|
||||
id: 'flexible-vending-native-brnch-minter',
|
||||
factoryAddress: VENDING_NATIVE_BRNCH_FLEX_FACTORY_ADDRESS,
|
||||
supportedToken: nativeBrnch,
|
||||
updatable: false,
|
||||
flexible: true,
|
||||
merkleTree: false,
|
||||
featured: false,
|
||||
}
|
||||
|
||||
export const flexibleVendingMinterList = [
|
||||
flexibleVendingStarsMinter,
|
||||
flexibleFeaturedVendingStarsMinter,
|
||||
flexibleVendingUpdatableStarsMinter,
|
||||
flexibleVendingIbcAtomMinter,
|
||||
flexibleVendingUpdatableIbcAtomMinter,
|
||||
flexibleVendingIbcUsdcMinter,
|
||||
flexibleFeaturedVendingIbcUsdcMinter,
|
||||
flexibleVendingUpdatableIbcUsdcMinter,
|
||||
flexibleVendingIbcTiaMinter,
|
||||
flexibleFeaturedVendingIbcTiaMinter,
|
||||
flexibleVendingUpdatableIbcTiaMinter,
|
||||
flexibleVendingIbcNbtcMinter,
|
||||
flexibleVendingUpdatableIbcNbtcMinter,
|
||||
flexibleVendingIbcUskMinter,
|
||||
flexibleVendingUpdatableIbcUskMinter,
|
||||
flexibleVendingIbcKujiMinter,
|
||||
// flexibleVendingIbcHuahuaMinter,
|
||||
flexibleVendingIbcCrbrusMinter,
|
||||
flexibleVendingStrdstMinter,
|
||||
flexibleVendingBrnchMinter,
|
||||
]
|
||||
|
||||
export const merkleTreeVendingStarsMinter: MinterInfo = {
|
||||
id: 'merkletree-vending-stars-minter',
|
||||
factoryAddress: VENDING_FACTORY_MERKLE_TREE_ADDRESS,
|
||||
supportedToken: stars,
|
||||
updatable: false,
|
||||
flexible: false,
|
||||
merkleTree: true,
|
||||
featured: false,
|
||||
}
|
||||
|
||||
export const merkleTreeVendingFeaturedStarsMinter: MinterInfo = {
|
||||
id: 'merkletree-vending-featured-stars-minter',
|
||||
factoryAddress: FEATURED_VENDING_FACTORY_MERKLE_TREE_ADDRESS,
|
||||
supportedToken: stars,
|
||||
updatable: false,
|
||||
flexible: false,
|
||||
merkleTree: true,
|
||||
featured: true,
|
||||
}
|
||||
|
||||
export const merkleTreeVendingIbcTiaMinter: MinterInfo = {
|
||||
id: 'merkletree-vending-ibc-tia-minter',
|
||||
factoryAddress: VENDING_IBC_TIA_FACTORY_MERKLE_TREE_ADDRESS,
|
||||
supportedToken: ibcTia,
|
||||
updatable: false,
|
||||
flexible: false,
|
||||
merkleTree: true,
|
||||
featured: false,
|
||||
}
|
||||
|
||||
export const merkleTreeVendingFeaturedIbcTiaMinter: MinterInfo = {
|
||||
id: 'merkletree-vending-featured-ibc-tia-minter',
|
||||
factoryAddress: FEATURED_VENDING_IBC_TIA_FACTORY_MERKLE_TREE_ADDRESS,
|
||||
supportedToken: ibcTia,
|
||||
updatable: false,
|
||||
flexible: false,
|
||||
merkleTree: true,
|
||||
featured: true,
|
||||
}
|
||||
|
||||
export const merkleTreeVendingMinterList = [
|
||||
merkleTreeVendingStarsMinter,
|
||||
merkleTreeVendingIbcTiaMinter,
|
||||
merkleTreeVendingFeaturedStarsMinter,
|
||||
merkleTreeVendingFeaturedIbcTiaMinter,
|
||||
]
|
||||
|
||||
@@ -5,6 +5,7 @@ export const mainnetConfig: AppConfig = {
|
||||
chainName: 'Stargaze',
|
||||
addressPrefix: 'stars',
|
||||
rpcUrl: 'https://rpc.stargaze-apis.com/',
|
||||
httpUrl: 'https://rest.stargaze-apis.com/',
|
||||
feeToken: 'ustars',
|
||||
stakingToken: 'ustars',
|
||||
coinMap: {
|
||||
|
||||
+100
-3
@@ -1,3 +1,5 @@
|
||||
import { NETWORK } from 'utils/constants'
|
||||
|
||||
export interface TokenInfo {
|
||||
id: string
|
||||
denom: string
|
||||
@@ -23,16 +25,111 @@ export const ibcAtom: TokenInfo = {
|
||||
|
||||
export const ibcUsdc: TokenInfo = {
|
||||
id: 'ibc-usdc',
|
||||
denom: 'ibc/D189335C6E4A68B513C10AB227BF1C1D38C746766278BA3EEB4FB14124F1D858',
|
||||
denom:
|
||||
NETWORK === 'mainnet'
|
||||
? 'ibc/4A1C18CA7F50544760CF306189B810CE4C1CB156C7FC870143D401FE7280E591'
|
||||
: 'factory/stars1paqkeyluuw47pflgwwqaaj8y679zj96aatg5a7/uusdc',
|
||||
displayName: 'USDC',
|
||||
decimalPlaces: 6,
|
||||
}
|
||||
export const ibcUsk: TokenInfo = {
|
||||
id: 'ibc-usk',
|
||||
denom:
|
||||
NETWORK === 'mainnet'
|
||||
? 'ibc/938CEB62ABCBA6366AA369A8362E310B2A0B1A54835E4F3FF01D69D860959128'
|
||||
: 'factory/stars153w5xhuqu3et29lgqk4dsynj6gjn96lr33wx4e/uusk',
|
||||
displayName: 'USK',
|
||||
decimalPlaces: 6,
|
||||
}
|
||||
|
||||
export const ibcKuji: TokenInfo = {
|
||||
id: 'ibc-kuji',
|
||||
denom:
|
||||
NETWORK === 'mainnet'
|
||||
? 'ibc/0E57658B71E9CC4BB0F6FE3E01712966713B49E6FD292E6B66E3F111B103D361'
|
||||
: 'factory/stars153w5xhuqu3et29lgqk4dsynj6gjn96lr33wx4e/ukuji',
|
||||
displayName: 'KUJI',
|
||||
decimalPlaces: 6,
|
||||
}
|
||||
|
||||
export const ibcFrnz: TokenInfo = {
|
||||
id: 'ibc-frnz',
|
||||
denom: 'ibc/7FA7EC64490E3BDE5A1A28CBE73CC0AD22522794957BC891C46321E3A6074DB9',
|
||||
denom:
|
||||
NETWORK === 'mainnet'
|
||||
? 'ibc/9C40A8368C0E1CAA4144DBDEBBCE2E7A5CC2D128F0A9F785ECB71ECFF575114C'
|
||||
: 'factory/stars1paqkeyluuw47pflgwwqaaj8y679zj96aatg5a7/ufrienzies',
|
||||
displayName: 'FRNZ',
|
||||
decimalPlaces: 6,
|
||||
}
|
||||
|
||||
export const tokensList = [stars, ibcAtom, ibcUsdc, ibcFrnz]
|
||||
export const ibcNbtc: TokenInfo = {
|
||||
id: 'ibc-nBTC',
|
||||
denom: NETWORK === 'mainnet' ? 'Not available' : 'factory/stars153w5xhuqu3et29lgqk4dsynj6gjn96lr33wx4e/unbtc',
|
||||
displayName: 'nBTC',
|
||||
decimalPlaces: 6,
|
||||
}
|
||||
|
||||
// export const ibcHuahua: TokenInfo = {
|
||||
// id: 'ibc-huahua',
|
||||
// denom:
|
||||
// NETWORK === 'mainnet'
|
||||
// ? 'ibc/CAD8A9F306CAAC55731C66930D6BEE539856DD12E59061C965E44D82AA26A0E7'
|
||||
// : 'factory/stars153w5xhuqu3et29lgqk4dsynj6gjn96lr33wx4e/uhuahua',
|
||||
// displayName: 'HUAHUA',
|
||||
// decimalPlaces: 6,
|
||||
// }
|
||||
|
||||
export const ibcCrbrus: TokenInfo = {
|
||||
id: 'ibc-crbrus',
|
||||
denom:
|
||||
NETWORK === 'mainnet'
|
||||
? 'ibc/71CEEB5CC09F75A3ACDC417108C14514351B6B2A540ACE9B37A80BF930845134'
|
||||
: 'factory/stars153w5xhuqu3et29lgqk4dsynj6gjn96lr33wx4e/uCRBRUS',
|
||||
displayName: 'CRBRUS',
|
||||
decimalPlaces: 6,
|
||||
}
|
||||
|
||||
export const ibcTia: TokenInfo = {
|
||||
id: 'ibc-tia',
|
||||
denom:
|
||||
NETWORK === 'mainnet'
|
||||
? 'ibc/14D1406D84227FDF4B055EA5CB2298095BBCA3F3BC3EF583AE6DF36F0FB179C8'
|
||||
: 'factory/stars153w5xhuqu3et29lgqk4dsynj6gjn96lr33wx4e/utia',
|
||||
displayName: 'TIA',
|
||||
decimalPlaces: 6,
|
||||
}
|
||||
|
||||
export const nativeStardust: TokenInfo = {
|
||||
id: 'native-strdst',
|
||||
denom:
|
||||
NETWORK === 'mainnet'
|
||||
? 'factory/stars16da2uus9zrsy83h23ur42v3lglg5rmyrpqnju4/dust'
|
||||
: 'factory/stars18vxuarvh44wxltxqsyac36972nvaqc377sdh40/dust',
|
||||
displayName: 'STRDST',
|
||||
decimalPlaces: 6,
|
||||
}
|
||||
|
||||
export const nativeBrnch: TokenInfo = {
|
||||
id: 'native-brnch',
|
||||
denom:
|
||||
NETWORK === 'mainnet'
|
||||
? 'factory/stars16da2uus9zrsy83h23ur42v3lglg5rmyrpqnju4/uBRNCH'
|
||||
: 'factory/stars153w5xhuqu3et29lgqk4dsynj6gjn96lr33wx4e/uBRNCH',
|
||||
displayName: 'BRNCH',
|
||||
decimalPlaces: 6,
|
||||
}
|
||||
|
||||
export const tokensList = [
|
||||
stars,
|
||||
ibcAtom,
|
||||
ibcUsdc,
|
||||
ibcUsk,
|
||||
ibcFrnz,
|
||||
ibcNbtc,
|
||||
ibcKuji,
|
||||
// ibcHuahua,
|
||||
ibcCrbrus,
|
||||
ibcTia,
|
||||
nativeStardust,
|
||||
nativeBrnch,
|
||||
]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import create from 'zustand'
|
||||
import { create } from 'zustand'
|
||||
|
||||
export const useCollectionStore = create(() => ({
|
||||
name: 'Example',
|
||||
|
||||
+27
-4
@@ -6,6 +6,8 @@ import type { UseBaseMinterContractProps } from 'contracts/baseMinter'
|
||||
import { useBaseMinterContract } from 'contracts/baseMinter'
|
||||
import { type UseOpenEditionFactoryContractProps, useOpenEditionFactoryContract } from 'contracts/openEditionFactory'
|
||||
import { type UseOpenEditionMinterContractProps, useOpenEditionMinterContract } from 'contracts/openEditionMinter'
|
||||
import type { UseRoyaltyRegistryContractProps } from 'contracts/royaltyRegistry'
|
||||
import { useRoyaltyRegistryContract } from 'contracts/royaltyRegistry'
|
||||
import type { UseSG721ContractProps } from 'contracts/sg721'
|
||||
import { useSG721Contract } from 'contracts/sg721'
|
||||
import type { UseVendingFactoryContractProps } from 'contracts/vendingFactory'
|
||||
@@ -14,10 +16,10 @@ import type { UseVendingMinterContractProps } from 'contracts/vendingMinter'
|
||||
import { useVendingMinterContract } from 'contracts/vendingMinter'
|
||||
import type { UseWhiteListContractProps } from 'contracts/whitelist'
|
||||
import { useWhiteListContract } from 'contracts/whitelist'
|
||||
import { type UseWhiteListMerkleTreeContractProps, useWhiteListMerkleTreeContract } from 'contracts/whitelistMerkleTree'
|
||||
import type { ReactNode, VFC } from 'react'
|
||||
import { Fragment, useEffect } from 'react'
|
||||
import type { State } from 'zustand'
|
||||
import create from 'zustand'
|
||||
import { create } from 'zustand'
|
||||
|
||||
import type { UseSplitsContractProps } from '../contracts/splits/useContract'
|
||||
import { useSplitsContract } from '../contracts/splits/useContract'
|
||||
@@ -25,17 +27,19 @@ import { useSplitsContract } from '../contracts/splits/useContract'
|
||||
/**
|
||||
* Contracts store type definitions
|
||||
*/
|
||||
export interface ContractsStore extends State {
|
||||
export interface ContractsStore {
|
||||
sg721: UseSG721ContractProps | null
|
||||
vendingMinter: UseVendingMinterContractProps | null
|
||||
baseMinter: UseBaseMinterContractProps | null
|
||||
openEditionMinter: UseOpenEditionMinterContractProps | null
|
||||
whitelist: UseWhiteListContractProps | null
|
||||
whitelistMerkleTree: UseWhiteListMerkleTreeContractProps | null
|
||||
vendingFactory: UseVendingFactoryContractProps | null
|
||||
baseFactory: UseBaseFactoryContractProps | null
|
||||
openEditionFactory: UseOpenEditionFactoryContractProps | null
|
||||
badgeHub: UseBadgeHubContractProps | null
|
||||
splits: UseSplitsContractProps | null
|
||||
royaltyRegistry: UseRoyaltyRegistryContractProps | null
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -47,11 +51,13 @@ export const defaultValues: ContractsStore = {
|
||||
baseMinter: null,
|
||||
openEditionMinter: null,
|
||||
whitelist: null,
|
||||
whitelistMerkleTree: null,
|
||||
vendingFactory: null,
|
||||
baseFactory: null,
|
||||
openEditionFactory: null,
|
||||
badgeHub: null,
|
||||
splits: null,
|
||||
royaltyRegistry: null,
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -80,11 +86,13 @@ const ContractsSubscription: VFC = () => {
|
||||
const baseMinter = useBaseMinterContract()
|
||||
const openEditionMinter = useOpenEditionMinterContract()
|
||||
const whitelist = useWhiteListContract()
|
||||
const whitelistMerkleTree = useWhiteListMerkleTreeContract()
|
||||
const vendingFactory = useVendingFactoryContract()
|
||||
const baseFactory = useBaseFactoryContract()
|
||||
const openEditionFactory = useOpenEditionFactoryContract()
|
||||
const badgeHub = useBadgeHubContract()
|
||||
const splits = useSplitsContract()
|
||||
const royaltyRegistry = useRoyaltyRegistryContract()
|
||||
|
||||
useEffect(() => {
|
||||
useContracts.setState({
|
||||
@@ -93,13 +101,28 @@ const ContractsSubscription: VFC = () => {
|
||||
baseMinter,
|
||||
openEditionMinter,
|
||||
whitelist,
|
||||
whitelistMerkleTree,
|
||||
vendingFactory,
|
||||
baseFactory,
|
||||
openEditionFactory,
|
||||
badgeHub,
|
||||
splits,
|
||||
royaltyRegistry,
|
||||
})
|
||||
}, [sg721, vendingMinter, baseMinter, whitelist, vendingFactory, baseFactory, badgeHub, splits])
|
||||
}, [
|
||||
sg721,
|
||||
vendingMinter,
|
||||
baseMinter,
|
||||
whitelist,
|
||||
whitelistMerkleTree,
|
||||
vendingFactory,
|
||||
baseFactory,
|
||||
badgeHub,
|
||||
splits,
|
||||
royaltyRegistry,
|
||||
openEditionMinter,
|
||||
openEditionFactory,
|
||||
])
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
import { create } from 'zustand'
|
||||
|
||||
export type Timezone = 'UTC' | 'Local'
|
||||
|
||||
export const useGlobalSettings = create(() => ({
|
||||
timezone: 'UTC' as Timezone,
|
||||
}))
|
||||
|
||||
export const setTimezone = (timezone: Timezone) => {
|
||||
useGlobalSettings.setState({ timezone })
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import create from 'zustand'
|
||||
import { create } from 'zustand'
|
||||
|
||||
export interface LogItem {
|
||||
id: string
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import create from 'zustand'
|
||||
import { create } from 'zustand'
|
||||
|
||||
export const useSidebarStore = create(() => ({ isOpen: true }))
|
||||
|
||||
|
||||
@@ -1,289 +0,0 @@
|
||||
import { SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate'
|
||||
import { Decimal } from '@cosmjs/math'
|
||||
import type { OfflineSigner } from '@cosmjs/proto-signing'
|
||||
import type { Coin } from '@cosmjs/stargate'
|
||||
import type { AppConfig } from 'config'
|
||||
import { getConfig, keplrConfig } from 'config'
|
||||
import type { ReactNode } from 'react'
|
||||
import { useEffect } from 'react'
|
||||
import { toast } from 'react-hot-toast'
|
||||
import { createTrackedSelector } from 'react-tracked'
|
||||
import { NETWORK } from 'utils/constants'
|
||||
import type { State } from 'zustand'
|
||||
import create from 'zustand'
|
||||
import { subscribeWithSelector } from 'zustand/middleware'
|
||||
|
||||
export interface KeplrWalletStore extends State {
|
||||
accountNumber: number
|
||||
address: string
|
||||
balance: Coin[]
|
||||
client: SigningCosmWasmClient | undefined
|
||||
config: AppConfig
|
||||
initialized: boolean
|
||||
initializing: boolean
|
||||
name: string
|
||||
network: string
|
||||
signer: OfflineSigner | undefined
|
||||
|
||||
readonly clear: () => void
|
||||
|
||||
readonly connect: (walletChange?: boolean | 'focus') => Promise<void>
|
||||
|
||||
readonly disconnect: () => void | Promise<void>
|
||||
|
||||
readonly getClient: () => SigningCosmWasmClient
|
||||
readonly getSigner: () => OfflineSigner
|
||||
|
||||
readonly init: (signer?: OfflineSigner) => void
|
||||
|
||||
readonly refreshBalance: (address?: string, balance?: Coin[]) => Promise<void>
|
||||
|
||||
readonly setNetwork: (network: string) => void
|
||||
|
||||
readonly updateSigner: (singer: OfflineSigner) => void
|
||||
|
||||
readonly setQueryClient: () => void
|
||||
}
|
||||
|
||||
/**
|
||||
* Compatibility export for references still using `WalletContextType`
|
||||
*
|
||||
* @deprecated replace with {@link KeplrWalletStore}
|
||||
*/
|
||||
export type WalletContextType = KeplrWalletStore
|
||||
|
||||
/**
|
||||
* Keplr wallet store default values as a separate variable for reusability
|
||||
*/
|
||||
const defaultStates = {
|
||||
accountNumber: 0,
|
||||
address: '',
|
||||
balance: [],
|
||||
client: undefined,
|
||||
config: getConfig(NETWORK),
|
||||
initialized: false,
|
||||
initializing: true,
|
||||
name: '',
|
||||
network: NETWORK,
|
||||
signer: undefined,
|
||||
}
|
||||
|
||||
/**
|
||||
* Entrypoint for keplr wallet store using {@link defaultStates}
|
||||
*/
|
||||
export const useWalletStore = create(
|
||||
subscribeWithSelector<KeplrWalletStore>((set, get) => ({
|
||||
...defaultStates,
|
||||
clear: () => set({ ...defaultStates }),
|
||||
connect: async (walletChange = false) => {
|
||||
try {
|
||||
if (walletChange !== 'focus') set({ initializing: true })
|
||||
const { config, init } = get()
|
||||
const signer = await loadKeplrWallet(config)
|
||||
init(signer)
|
||||
if (walletChange) set({ initializing: false })
|
||||
} catch (err: any) {
|
||||
toast.error(err?.message, { style: { maxWidth: 'none' } })
|
||||
set({ initializing: false })
|
||||
}
|
||||
},
|
||||
disconnect: () => {
|
||||
window.localStorage.clear()
|
||||
get().clear()
|
||||
set({ initializing: false })
|
||||
},
|
||||
getClient: () => get().client!,
|
||||
getSigner: () => get().signer!,
|
||||
init: (signer) => set({ signer }),
|
||||
refreshBalance: async (address = get().address, balance = get().balance) => {
|
||||
const { client, config } = get()
|
||||
if (!client) return
|
||||
balance.length = 0
|
||||
for (const denom in config.coinMap) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
const coin = await client.getBalance(address, denom)
|
||||
if (coin) balance.push(coin)
|
||||
}
|
||||
set({ balance })
|
||||
},
|
||||
setNetwork: (network) => set({ network }),
|
||||
updateSigner: (signer) => set({ signer }),
|
||||
setQueryClient: async () => {
|
||||
try {
|
||||
const client = (await createQueryClient()) as SigningCosmWasmClient
|
||||
set({ client })
|
||||
} catch (err: any) {
|
||||
toast.error(err?.message, { style: { maxWidth: 'none' } })
|
||||
set({ initializing: false })
|
||||
}
|
||||
},
|
||||
})),
|
||||
)
|
||||
|
||||
/**
|
||||
* Proxied keplr wallet store which only rerenders on called state values.
|
||||
*
|
||||
* Recommended if only consuming state; to set states, use {@link useWalletStore.setState}.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* // this will rerender if any state values has changed
|
||||
* const { name } = useWalletStore()
|
||||
*
|
||||
* // this will rerender if only `name` has changed
|
||||
* const { name } = useWallet()
|
||||
* ```
|
||||
*/
|
||||
export const useWallet = createTrackedSelector<KeplrWalletStore>(useWalletStore)
|
||||
|
||||
/**
|
||||
* Keplr wallet store provider to easily mount {@link WalletSubscription}
|
||||
* to listen/subscribe various state changes.
|
||||
*
|
||||
*/
|
||||
export const WalletProvider = ({ children }: { children: ReactNode }) => {
|
||||
return (
|
||||
<>
|
||||
{children}
|
||||
<WalletSubscription />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Keplr wallet subscriptions (side effects)
|
||||
*/
|
||||
const WalletSubscription = () => {
|
||||
/**
|
||||
* Dispatch reconnecting wallet on first mount and register events to refresh
|
||||
* on keystore change and window refocus.
|
||||
*
|
||||
*/
|
||||
useEffect(() => {
|
||||
const walletAddress = window.localStorage.getItem('wallet_address')
|
||||
if (walletAddress) {
|
||||
void useWalletStore.getState().connect()
|
||||
} else {
|
||||
useWalletStore.setState({ initializing: false })
|
||||
useWalletStore.getState().setQueryClient()
|
||||
}
|
||||
|
||||
const listenChange = () => {
|
||||
void useWalletStore.getState().connect(true)
|
||||
}
|
||||
const listenFocus = () => {
|
||||
if (walletAddress) void useWalletStore.getState().connect('focus')
|
||||
}
|
||||
|
||||
window.addEventListener('keplr_keystorechange', listenChange)
|
||||
window.addEventListener('focus', listenFocus)
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('keplr_keystorechange', listenChange)
|
||||
window.removeEventListener('focus', listenFocus)
|
||||
}
|
||||
}, [])
|
||||
|
||||
/**
|
||||
* Watch signer changes to initialize client state.
|
||||
*
|
||||
*/
|
||||
useEffect(() => {
|
||||
return useWalletStore.subscribe(
|
||||
(x) => x.signer,
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
async (signer) => {
|
||||
try {
|
||||
if (!signer) {
|
||||
useWalletStore.setState({
|
||||
client: (await createQueryClient()) as SigningCosmWasmClient,
|
||||
})
|
||||
} else {
|
||||
useWalletStore.setState({
|
||||
client: await createClient({ signer }),
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
},
|
||||
)
|
||||
}, [])
|
||||
|
||||
/**
|
||||
* Watch client changes to refresh balance and sync wallet states.
|
||||
*
|
||||
*/
|
||||
useEffect(() => {
|
||||
return useWalletStore.subscribe(
|
||||
(x) => x.client,
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
async (client) => {
|
||||
const { config, refreshBalance, signer } = useWalletStore.getState()
|
||||
if (!signer || !client) return
|
||||
if (!window.keplr) {
|
||||
throw new Error('window.keplr not found')
|
||||
}
|
||||
const balance: Coin[] = []
|
||||
const address = (await signer.getAccounts())[0].address
|
||||
const account = await client.getAccount(address)
|
||||
const key = await window.keplr.getKey(config.chainId)
|
||||
await refreshBalance(address, balance)
|
||||
window.localStorage.setItem('wallet_address', address)
|
||||
useWalletStore.setState({
|
||||
accountNumber: account?.accountNumber || 0,
|
||||
address,
|
||||
balance,
|
||||
initialized: true,
|
||||
initializing: false,
|
||||
name: key.name || '',
|
||||
})
|
||||
},
|
||||
)
|
||||
}, [])
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to create signing client based on {@link useWalletStore} resolved
|
||||
* config state.
|
||||
*
|
||||
* @param arg - Object argument requiring `signer`
|
||||
*/
|
||||
const createClient = ({ signer }: { signer: OfflineSigner }) => {
|
||||
const { config } = useWalletStore.getState()
|
||||
return SigningCosmWasmClient.connectWithSigner(config.rpcUrl, signer, {
|
||||
gasPrice: {
|
||||
amount: Decimal.fromUserInput('0.0025', 100),
|
||||
denom: config.feeToken,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const createQueryClient = () => {
|
||||
const { config } = useWalletStore.getState()
|
||||
return SigningCosmWasmClient.connect(config.rpcUrl)
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to load keplr wallet signer.
|
||||
*
|
||||
* @param config - Application configuration
|
||||
*/
|
||||
const loadKeplrWallet = async (config: AppConfig) => {
|
||||
if (!window.getOfflineSigner || !window.keplr || !window.getOfflineSignerAuto) {
|
||||
throw new Error('Keplr extension is not available')
|
||||
}
|
||||
|
||||
await window.keplr.experimentalSuggestChain(keplrConfig(config))
|
||||
await window.keplr.enable(config.chainId)
|
||||
|
||||
const signer = await window.getOfflineSignerAuto(config.chainId)
|
||||
Object.assign(signer, {
|
||||
signAmino: (signer as any).signAmino ?? (signer as any).sign,
|
||||
})
|
||||
|
||||
return signer
|
||||
}
|
||||
@@ -342,7 +342,8 @@ export const badgeHub = (client: SigningCosmWasmClient, txSigner: string): Badge
|
||||
},
|
||||
'auto',
|
||||
'',
|
||||
editFee ? [coin(editFee, 'ustars')] : [],
|
||||
[coin(200000000, 'ustars')],
|
||||
// editFee ? [coin(editFee, 'ustars')] : [],
|
||||
)
|
||||
|
||||
return res.transactionHash
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { Coin } from '@cosmjs/proto-signing'
|
||||
import type { logs } from '@cosmjs/stargate'
|
||||
import { useWallet } from 'contexts/wallet'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { useWallet } from 'utils/wallet'
|
||||
|
||||
import type { BadgeHubContract, BadgeHubInstance, BadgeHubMessages, MigrateResponse } from './contract'
|
||||
import { badgeHub as initContract } from './contract'
|
||||
@@ -50,9 +50,19 @@ export function useBadgeHubContract(): UseBadgeHubContractProps {
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
const BadgeHubBaseContract = initContract(wallet.getClient(), wallet.address)
|
||||
setBadgeHub(BadgeHubBaseContract)
|
||||
}, [wallet])
|
||||
if (!wallet.isWalletConnected) {
|
||||
return
|
||||
}
|
||||
|
||||
const load = async () => {
|
||||
const client = await wallet.getSigningCosmWasmClient()
|
||||
const BadgeHubBaseContract = initContract(client, wallet.address || '')
|
||||
setBadgeHub(BadgeHubBaseContract)
|
||||
}
|
||||
|
||||
load().catch(console.error)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [wallet.isWalletConnected, wallet.address])
|
||||
|
||||
const updateContractAddress = (contractAddress: string) => {
|
||||
setAddress(contractAddress)
|
||||
@@ -65,7 +75,10 @@ export function useBadgeHubContract(): UseBadgeHubContractProps {
|
||||
reject(new Error('Contract is not initialized.'))
|
||||
return
|
||||
}
|
||||
badgeHub.instantiate(wallet.address, codeId, initMsg, label, admin).then(resolve).catch(reject)
|
||||
badgeHub
|
||||
.instantiate(wallet.address || '', codeId, initMsg, label, admin)
|
||||
.then(resolve)
|
||||
.catch(reject)
|
||||
})
|
||||
},
|
||||
[badgeHub, wallet],
|
||||
@@ -79,7 +92,10 @@ export function useBadgeHubContract(): UseBadgeHubContractProps {
|
||||
return
|
||||
}
|
||||
console.log(wallet.address, contractAddress, codeId)
|
||||
badgeHub.migrate(wallet.address, contractAddress, codeId, migrateMsg).then(resolve).catch(reject)
|
||||
badgeHub
|
||||
.migrate(wallet.address || '', contractAddress, codeId, migrateMsg)
|
||||
.then(resolve)
|
||||
.catch(reject)
|
||||
})
|
||||
},
|
||||
[badgeHub, wallet],
|
||||
|
||||
@@ -70,8 +70,8 @@ export const baseFactory = (client: SigningCosmWasmClient, txSigner: string): Ba
|
||||
)
|
||||
|
||||
return {
|
||||
baseMinterAddress: result.logs[0].events[5].attributes[0].value,
|
||||
sg721Address: result.logs[0].events[5].attributes[2].value,
|
||||
baseMinterAddress: result.logs[0].events[16].attributes[0].value,
|
||||
sg721Address: result.logs[0].events[18].attributes[0].value,
|
||||
transactionHash: result.transactionHash,
|
||||
logs: result.logs,
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useWallet } from 'contexts/wallet'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { useWallet } from 'utils/wallet'
|
||||
|
||||
import type { BaseFactoryContract, BaseFactoryInstance, BaseFactoryMessages } from './contract'
|
||||
import { baseFactory as initContract } from './contract'
|
||||
@@ -22,9 +22,19 @@ export function useBaseFactoryContract(): UseBaseFactoryContractProps {
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
const BaseFactoryBaseContract = initContract(wallet.getClient(), wallet.address)
|
||||
setBaseFactory(BaseFactoryBaseContract)
|
||||
}, [wallet])
|
||||
if (!wallet.isWalletConnected) {
|
||||
return
|
||||
}
|
||||
|
||||
const load = async () => {
|
||||
const client = await wallet.getSigningCosmWasmClient()
|
||||
const BaseFactoryBaseContract = initContract(client, wallet.address || '')
|
||||
setBaseFactory(BaseFactoryBaseContract)
|
||||
}
|
||||
|
||||
load().catch(console.error)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [wallet.isWalletConnected, wallet.address])
|
||||
|
||||
const updateContractAddress = (contractAddress: string) => {
|
||||
setAddress(contractAddress)
|
||||
|
||||
@@ -33,13 +33,13 @@ export interface BaseMinterInstance {
|
||||
//Execute
|
||||
mint: (senderAddress: string, tokenUri: string) => Promise<string>
|
||||
updateStartTradingTime: (senderAddress: string, time?: Timestamp) => Promise<string>
|
||||
batchMint: (senderAddress: string, recipient: string, batchCount: number) => Promise<string>
|
||||
batchMint: (senderAddress: string, recipient: string, batchCount: number, startFrom: number) => Promise<string>
|
||||
}
|
||||
|
||||
export interface BaseMinterMessages {
|
||||
mint: (tokenUri: string) => MintMessage
|
||||
updateStartTradingTime: (time: Timestamp) => UpdateStartTradingTimeMessage
|
||||
batchMint: (recipient: string, batchNumber: number) => CustomMessage
|
||||
batchMint: (recipient: string, batchNumber: number, startFrom: number) => CustomMessage
|
||||
}
|
||||
|
||||
export interface MintMessage {
|
||||
@@ -178,7 +178,12 @@ export const baseMinter = (client: SigningCosmWasmClient, txSigner: string): Bas
|
||||
return res.transactionHash
|
||||
}
|
||||
|
||||
const batchMint = async (senderAddress: string, baseUri: string, batchCount: number): Promise<string> => {
|
||||
const batchMint = async (
|
||||
senderAddress: string,
|
||||
baseUri: string,
|
||||
batchCount: number,
|
||||
startFrom: number,
|
||||
): Promise<string> => {
|
||||
const txHash = await getConfig().then(async (response) => {
|
||||
const factoryParameters = await toast.promise(getFactoryParameters(response?.config?.factory), {
|
||||
loading: 'Querying Factory Parameters...',
|
||||
@@ -198,7 +203,7 @@ export const baseMinter = (client: SigningCosmWasmClient, txSigner: string): Bas
|
||||
const executeContractMsgs: MsgExecuteContractEncodeObject[] = []
|
||||
for (let i = 0; i < batchCount; i++) {
|
||||
const msg = {
|
||||
mint: { token_uri: `${baseUri}/${i + 1}` },
|
||||
mint: { token_uri: `${baseUri}/${i + startFrom}` },
|
||||
}
|
||||
const executeContractMsg: MsgExecuteContractEncodeObject = {
|
||||
typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract',
|
||||
@@ -282,10 +287,10 @@ export const baseMinter = (client: SigningCosmWasmClient, txSigner: string): Bas
|
||||
}
|
||||
}
|
||||
|
||||
const batchMint = (baseUri: string, batchCount: number): CustomMessage => {
|
||||
const batchMint = (baseUri: string, batchCount: number, startFrom: number): CustomMessage => {
|
||||
const msg: Record<string, unknown>[] = []
|
||||
for (let i = 0; i < batchCount; i++) {
|
||||
msg.push({ mint: { token_uri: `${baseUri}/${i + 1}` } })
|
||||
msg.push({ mint: { token_uri: `${baseUri}/${i + startFrom}` } })
|
||||
}
|
||||
return {
|
||||
sender: txSigner,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { Coin } from '@cosmjs/proto-signing'
|
||||
import type { logs } from '@cosmjs/stargate'
|
||||
import { useWallet } from 'contexts/wallet'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { useWallet } from 'utils/wallet'
|
||||
|
||||
import type { BaseMinterContract, BaseMinterInstance, BaseMinterMessages, MigrateResponse } from './contract'
|
||||
import { baseMinter as initContract } from './contract'
|
||||
@@ -38,9 +38,19 @@ export function useBaseMinterContract(): UseBaseMinterContractProps {
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
const BaseMinterBaseContract = initContract(wallet.getClient(), wallet.address)
|
||||
setBaseMinter(BaseMinterBaseContract)
|
||||
}, [wallet])
|
||||
if (!wallet.isWalletConnected) {
|
||||
return
|
||||
}
|
||||
|
||||
const load = async () => {
|
||||
const client = await wallet.getSigningCosmWasmClient()
|
||||
const BaseMinterBaseContract = initContract(client, wallet.address || '')
|
||||
setBaseMinter(BaseMinterBaseContract)
|
||||
}
|
||||
|
||||
load().catch(console.error)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [wallet.isWalletConnected, wallet.address])
|
||||
|
||||
const updateContractAddress = (contractAddress: string) => {
|
||||
setAddress(contractAddress)
|
||||
@@ -53,7 +63,10 @@ export function useBaseMinterContract(): UseBaseMinterContractProps {
|
||||
reject(new Error('Contract is not initialized.'))
|
||||
return
|
||||
}
|
||||
baseMinter.instantiate(wallet.address, codeId, initMsg, label, admin).then(resolve).catch(reject)
|
||||
baseMinter
|
||||
.instantiate(wallet.address || '', codeId, initMsg, label, admin)
|
||||
.then(resolve)
|
||||
.catch(reject)
|
||||
})
|
||||
},
|
||||
[baseMinter, wallet],
|
||||
@@ -67,7 +80,10 @@ export function useBaseMinterContract(): UseBaseMinterContractProps {
|
||||
return
|
||||
}
|
||||
console.log(wallet.address, contractAddress, codeId)
|
||||
baseMinter.migrate(wallet.address, contractAddress, codeId, migrateMsg).then(resolve).catch(reject)
|
||||
baseMinter
|
||||
.migrate(wallet.address || '', contractAddress, codeId, migrateMsg)
|
||||
.then(resolve)
|
||||
.catch(reject)
|
||||
})
|
||||
},
|
||||
[baseMinter, wallet],
|
||||
|
||||
@@ -61,8 +61,8 @@ export const openEditionFactory = (client: SigningCosmWasmClient, txSigner: stri
|
||||
const result = await client.execute(senderAddress, contractAddress, msg, 'auto', '', funds)
|
||||
|
||||
return {
|
||||
openEditionMinterAddress: result.logs[0].events[5].attributes[0].value,
|
||||
sg721Address: result.logs[0].events[5].attributes[2].value,
|
||||
openEditionMinterAddress: result.logs[0].events[16].attributes[0].value,
|
||||
sg721Address: result.logs[0].events[18].attributes[0].value,
|
||||
transactionHash: result.transactionHash,
|
||||
logs: result.logs,
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { logs } from '@cosmjs/stargate'
|
||||
import { useWallet } from 'contexts/wallet'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { useWallet } from 'utils/wallet'
|
||||
|
||||
import type { OpenEditionFactoryContract, OpenEditionFactoryInstance, OpenEditionFactoryMessages } from './contract'
|
||||
import { openEditionFactory as initContract } from './contract'
|
||||
@@ -41,9 +41,19 @@ export function useOpenEditionFactoryContract(): UseOpenEditionFactoryContractPr
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
const OpenEditionFactoryBaseContract = initContract(wallet.getClient(), wallet.address)
|
||||
setOpenEditionFactory(OpenEditionFactoryBaseContract)
|
||||
}, [wallet])
|
||||
if (!wallet.isWalletConnected) {
|
||||
return
|
||||
}
|
||||
|
||||
const load = async () => {
|
||||
const client = await wallet.getSigningCosmWasmClient()
|
||||
const OpenEditionFactoryBaseContract = initContract(client, wallet.address || '')
|
||||
setOpenEditionFactory(OpenEditionFactoryBaseContract)
|
||||
}
|
||||
|
||||
load().catch(console.error)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [wallet.isWalletConnected, wallet.address])
|
||||
|
||||
const updateContractAddress = (contractAddress: string) => {
|
||||
setAddress(contractAddress)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { Coin } from '@cosmjs/proto-signing'
|
||||
import type { logs } from '@cosmjs/stargate'
|
||||
import { useWallet } from 'contexts/wallet'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { useWallet } from 'utils/wallet'
|
||||
|
||||
import type {
|
||||
MigrateResponse,
|
||||
@@ -55,9 +55,19 @@ export function useOpenEditionMinterContract(): UseOpenEditionMinterContractProp
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
const OpenEditionMinterBaseContract = initContract(wallet.getClient(), wallet.address)
|
||||
setOpenEditionMinter(OpenEditionMinterBaseContract)
|
||||
}, [wallet])
|
||||
if (!wallet.isWalletConnected) {
|
||||
return
|
||||
}
|
||||
|
||||
const load = async () => {
|
||||
const client = await wallet.getSigningCosmWasmClient()
|
||||
const OpenEditionMinterBaseContract = initContract(client, wallet.address || '')
|
||||
setOpenEditionMinter(OpenEditionMinterBaseContract)
|
||||
}
|
||||
|
||||
load().catch(console.error)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [wallet.isWalletConnected, wallet.address])
|
||||
|
||||
const updateContractAddress = (contractAddress: string) => {
|
||||
setAddress(contractAddress)
|
||||
@@ -70,7 +80,10 @@ export function useOpenEditionMinterContract(): UseOpenEditionMinterContractProp
|
||||
reject(new Error('Contract is not initialized.'))
|
||||
return
|
||||
}
|
||||
openEditionMinter.instantiate(wallet.address, codeId, initMsg, label, admin).then(resolve).catch(reject)
|
||||
openEditionMinter
|
||||
.instantiate(wallet.address || '', codeId, initMsg, label, admin)
|
||||
.then(resolve)
|
||||
.catch(reject)
|
||||
})
|
||||
},
|
||||
[openEditionMinter, wallet],
|
||||
@@ -84,7 +97,10 @@ export function useOpenEditionMinterContract(): UseOpenEditionMinterContractProp
|
||||
return
|
||||
}
|
||||
console.log(wallet.address, contractAddress, codeId)
|
||||
openEditionMinter.migrate(wallet.address, contractAddress, codeId, migrateMsg).then(resolve).catch(reject)
|
||||
openEditionMinter
|
||||
.migrate(wallet.address || '', contractAddress, codeId, migrateMsg)
|
||||
.then(resolve)
|
||||
.catch(reject)
|
||||
})
|
||||
},
|
||||
[openEditionMinter, wallet],
|
||||
|
||||
@@ -0,0 +1,407 @@
|
||||
import type { SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate'
|
||||
import type { Coin } from '@cosmjs/proto-signing'
|
||||
import type { logs } from '@cosmjs/stargate'
|
||||
|
||||
export interface InstantiateResponse {
|
||||
readonly contractAddress: string
|
||||
readonly transactionHash: string
|
||||
}
|
||||
|
||||
export interface MigrateResponse {
|
||||
readonly transactionHash: string
|
||||
readonly logs: readonly logs.Log[]
|
||||
}
|
||||
|
||||
export interface RoyaltyRegistryInstance {
|
||||
readonly contractAddress: string
|
||||
//Query
|
||||
config: () => Promise<string>
|
||||
collectionRoyaltyDefault: (collection: string) => Promise<string>
|
||||
collectionRoyaltyProtocol: (collection: string, protocol: string) => Promise<string>
|
||||
// RoyaltyProtocolByCollection: (collection: string, queryOptions: QqueryOptions) => Promise<string>
|
||||
royaltyPayment: (collection: string, protocol?: string) => Promise<string>
|
||||
|
||||
//Execute
|
||||
initializeCollectionRoyalty: (collection: string) => Promise<string>
|
||||
setCollectionRoyaltyDefault: (collection: string, recipient: string, share: number) => Promise<string>
|
||||
updateCollectionRoyaltyDefault: (
|
||||
collection: string,
|
||||
recipient?: string,
|
||||
shareDelta?: number,
|
||||
decrement?: boolean,
|
||||
) => Promise<string>
|
||||
setCollectionRoyaltyProtocol: (
|
||||
collection: string,
|
||||
protocol: string,
|
||||
recipient: string,
|
||||
share: number,
|
||||
) => Promise<string>
|
||||
updateCollectionRoyaltyProtocol: (
|
||||
collection: string,
|
||||
protocol?: string,
|
||||
recipient?: string,
|
||||
shareDelta?: number,
|
||||
decrement?: boolean,
|
||||
) => Promise<string>
|
||||
}
|
||||
|
||||
export interface RoyaltyRegistryMessages {
|
||||
initializeCollectionRoyalty: (collection: string) => InitializeCollectionRoyaltyMessage
|
||||
setCollectionRoyaltyDefault: (
|
||||
collection: string,
|
||||
recipient: string,
|
||||
share: number,
|
||||
) => SetCollectionRoyaltyDefaultMessage
|
||||
updateCollectionRoyaltyDefault: (
|
||||
collection: string,
|
||||
recipient?: string,
|
||||
shareDelta?: number,
|
||||
decrement?: boolean,
|
||||
) => UpdateCollectionRoyaltyDefaultMessage
|
||||
setCollectionRoyaltyProtocol: (
|
||||
collection: string,
|
||||
protocol: string,
|
||||
recipient: string,
|
||||
share: number,
|
||||
) => SetCollectionRoyaltyProtocolMessage
|
||||
updateCollectionRoyaltyProtocol: (
|
||||
collection: string,
|
||||
protocol?: string,
|
||||
recipient?: string,
|
||||
shareDelta?: number,
|
||||
decrement?: boolean,
|
||||
) => UpdateCollectionRoyaltyProtocolMessage
|
||||
}
|
||||
|
||||
export interface InitializeCollectionRoyaltyMessage {
|
||||
sender: string
|
||||
contract: string
|
||||
msg: {
|
||||
initialize_collection_royalty: { collection: string }
|
||||
}
|
||||
funds: Coin[]
|
||||
}
|
||||
|
||||
export interface SetCollectionRoyaltyDefaultMessage {
|
||||
sender: string
|
||||
contract: string
|
||||
msg: {
|
||||
set_collection_royalty_default: { collection: string; recipient: string; share: number }
|
||||
}
|
||||
funds: Coin[]
|
||||
}
|
||||
|
||||
export interface UpdateCollectionRoyaltyDefaultMessage {
|
||||
sender: string
|
||||
contract: string
|
||||
msg: {
|
||||
update_collection_royalty_default: {
|
||||
collection: string
|
||||
recipient?: string
|
||||
share_delta?: number
|
||||
decrement?: boolean
|
||||
}
|
||||
}
|
||||
funds: Coin[]
|
||||
}
|
||||
|
||||
export interface SetCollectionRoyaltyProtocolMessage {
|
||||
sender: string
|
||||
contract: string
|
||||
msg: {
|
||||
set_collection_royalty_protocol: {
|
||||
collection: string
|
||||
protocol: string
|
||||
recipient: string
|
||||
share: number
|
||||
}
|
||||
}
|
||||
funds: Coin[]
|
||||
}
|
||||
|
||||
export interface UpdateCollectionRoyaltyProtocolMessage {
|
||||
sender: string
|
||||
contract: string
|
||||
msg: {
|
||||
update_collection_royalty_protocol: {
|
||||
collection: string
|
||||
protocol?: string
|
||||
recipient?: string
|
||||
share_delta?: number
|
||||
decrement?: boolean
|
||||
}
|
||||
}
|
||||
funds: Coin[]
|
||||
}
|
||||
|
||||
export interface RoyaltyRegistryContract {
|
||||
instantiate: (
|
||||
codeId: number,
|
||||
initMsg: Record<string, unknown>,
|
||||
label: string,
|
||||
admin?: string,
|
||||
) => Promise<InstantiateResponse>
|
||||
|
||||
use: (contractAddress: string) => RoyaltyRegistryInstance
|
||||
|
||||
migrate: (
|
||||
senderAddress: string,
|
||||
contractAddress: string,
|
||||
codeId: number,
|
||||
migrateMsg: Record<string, unknown>,
|
||||
) => Promise<MigrateResponse>
|
||||
|
||||
messages: (contractAddress: string) => RoyaltyRegistryMessages
|
||||
}
|
||||
|
||||
export const RoyaltyRegistry = (client: SigningCosmWasmClient, txSigner: string): RoyaltyRegistryContract => {
|
||||
const use = (contractAddress: string): RoyaltyRegistryInstance => {
|
||||
///QUERY
|
||||
const config = async (): Promise<string> => {
|
||||
return client.queryContractSmart(contractAddress, {
|
||||
config: {},
|
||||
})
|
||||
}
|
||||
|
||||
const collectionRoyaltyDefault = async (collection: string): Promise<string> => {
|
||||
return client.queryContractSmart(contractAddress, {
|
||||
collection_royalty_default: { collection },
|
||||
})
|
||||
}
|
||||
|
||||
const collectionRoyaltyProtocol = async (collection: string, protocol: string): Promise<string> => {
|
||||
return client.queryContractSmart(contractAddress, {
|
||||
collection_royalty_protocol: { collection, protocol },
|
||||
})
|
||||
}
|
||||
|
||||
const royaltyPayment = async (collection: string, protocol?: string): Promise<string> => {
|
||||
return client.queryContractSmart(contractAddress, {
|
||||
royalty_payment: { collection, protocol },
|
||||
})
|
||||
}
|
||||
|
||||
/// EXECUTE
|
||||
const initializeCollectionRoyalty = async (collection: string): Promise<string> => {
|
||||
const res = await client.execute(
|
||||
txSigner,
|
||||
contractAddress,
|
||||
{
|
||||
initialize_collection_royalty: { collection },
|
||||
},
|
||||
'auto',
|
||||
)
|
||||
return res.transactionHash
|
||||
}
|
||||
|
||||
const setCollectionRoyaltyDefault = async (
|
||||
collection: string,
|
||||
recipient: string,
|
||||
share: number,
|
||||
): Promise<string> => {
|
||||
const res = await client.execute(
|
||||
txSigner,
|
||||
contractAddress,
|
||||
{
|
||||
set_collection_royalty_default: { collection, recipient, share: (share / 100).toString() },
|
||||
},
|
||||
'auto',
|
||||
)
|
||||
return res.transactionHash
|
||||
}
|
||||
|
||||
const updateCollectionRoyaltyDefault = async (
|
||||
collection: string,
|
||||
recipient?: string,
|
||||
shareDelta?: number,
|
||||
decrement?: boolean,
|
||||
): Promise<string> => {
|
||||
const res = await client.execute(
|
||||
txSigner,
|
||||
contractAddress,
|
||||
{
|
||||
update_collection_royalty_default: {
|
||||
collection,
|
||||
recipient,
|
||||
share_delta: shareDelta ? (shareDelta / 100).toString() : undefined,
|
||||
decrement,
|
||||
},
|
||||
},
|
||||
'auto',
|
||||
)
|
||||
return res.transactionHash
|
||||
}
|
||||
|
||||
const setCollectionRoyaltyProtocol = async (
|
||||
collection: string,
|
||||
protocol: string,
|
||||
recipient: string,
|
||||
share: number,
|
||||
): Promise<string> => {
|
||||
const res = await client.execute(
|
||||
txSigner,
|
||||
contractAddress,
|
||||
{
|
||||
set_collection_royalty_protocol: { collection, protocol, recipient, share: (share / 100).toString() },
|
||||
},
|
||||
'auto',
|
||||
)
|
||||
return res.transactionHash
|
||||
}
|
||||
|
||||
const updateCollectionRoyaltyProtocol = async (
|
||||
collection: string,
|
||||
protocol?: string,
|
||||
recipient?: string,
|
||||
shareDelta?: number,
|
||||
decrement?: boolean,
|
||||
): Promise<string> => {
|
||||
const res = await client.execute(
|
||||
txSigner,
|
||||
contractAddress,
|
||||
{
|
||||
update_collection_royalty_protocol: {
|
||||
collection,
|
||||
protocol,
|
||||
recipient,
|
||||
share_delta: shareDelta ? (shareDelta / 100).toString() : undefined,
|
||||
decrement,
|
||||
},
|
||||
},
|
||||
'auto',
|
||||
)
|
||||
return res.transactionHash
|
||||
}
|
||||
|
||||
return {
|
||||
contractAddress,
|
||||
config,
|
||||
collectionRoyaltyDefault,
|
||||
collectionRoyaltyProtocol,
|
||||
royaltyPayment,
|
||||
initializeCollectionRoyalty,
|
||||
setCollectionRoyaltyDefault,
|
||||
updateCollectionRoyaltyDefault,
|
||||
setCollectionRoyaltyProtocol,
|
||||
updateCollectionRoyaltyProtocol,
|
||||
}
|
||||
}
|
||||
|
||||
const instantiate = async (
|
||||
codeId: number,
|
||||
initMsg: Record<string, unknown>,
|
||||
label: string,
|
||||
admin?: string,
|
||||
): Promise<InstantiateResponse> => {
|
||||
const result = await client.instantiate(txSigner, codeId, initMsg, label, 'auto', {
|
||||
admin,
|
||||
})
|
||||
|
||||
return {
|
||||
contractAddress: result.contractAddress,
|
||||
transactionHash: result.transactionHash,
|
||||
}
|
||||
}
|
||||
|
||||
const migrate = async (
|
||||
senderAddress: string,
|
||||
contractAddress: string,
|
||||
codeId: number,
|
||||
migrateMsg: Record<string, unknown>,
|
||||
): Promise<MigrateResponse> => {
|
||||
const result = await client.migrate(senderAddress, contractAddress, codeId, migrateMsg, 'auto')
|
||||
return {
|
||||
transactionHash: result.transactionHash,
|
||||
logs: result.logs,
|
||||
}
|
||||
}
|
||||
|
||||
const messages = (contractAddress: string) => {
|
||||
const initializeCollectionRoyalty = (collection: string) => {
|
||||
return {
|
||||
sender: txSigner,
|
||||
contract: contractAddress,
|
||||
msg: {
|
||||
initialize_collection_royalty: { collection },
|
||||
},
|
||||
funds: [],
|
||||
}
|
||||
}
|
||||
|
||||
const setCollectionRoyaltyDefault = (collection: string, recipient: string, share: number) => {
|
||||
return {
|
||||
sender: txSigner,
|
||||
contract: contractAddress,
|
||||
msg: {
|
||||
set_collection_royalty_default: { collection, recipient, share: share / 100 },
|
||||
},
|
||||
funds: [],
|
||||
}
|
||||
}
|
||||
|
||||
const updateCollectionRoyaltyDefault = (
|
||||
collection: string,
|
||||
recipient?: string,
|
||||
shareDelta?: number,
|
||||
decrement?: boolean,
|
||||
) => {
|
||||
return {
|
||||
sender: txSigner,
|
||||
contract: contractAddress,
|
||||
msg: {
|
||||
update_collection_royalty_default: {
|
||||
collection,
|
||||
recipient,
|
||||
share_delta: shareDelta ? shareDelta / 100 : undefined,
|
||||
decrement,
|
||||
},
|
||||
},
|
||||
funds: [],
|
||||
}
|
||||
}
|
||||
|
||||
const setCollectionRoyaltyProtocol = (collection: string, protocol: string, recipient: string, share: number) => {
|
||||
return {
|
||||
sender: txSigner,
|
||||
contract: contractAddress,
|
||||
msg: {
|
||||
set_collection_royalty_protocol: { collection, protocol, recipient, share: share / 100 },
|
||||
},
|
||||
funds: [],
|
||||
}
|
||||
}
|
||||
|
||||
const updateCollectionRoyaltyProtocol = (
|
||||
collection: string,
|
||||
protocol?: string,
|
||||
recipient?: string,
|
||||
shareDelta?: number,
|
||||
decrement?: boolean,
|
||||
) => {
|
||||
return {
|
||||
sender: txSigner,
|
||||
contract: contractAddress,
|
||||
msg: {
|
||||
update_collection_royalty_protocol: {
|
||||
collection,
|
||||
protocol,
|
||||
recipient,
|
||||
share_delta: shareDelta ? shareDelta / 100 : undefined,
|
||||
decrement,
|
||||
},
|
||||
},
|
||||
funds: [],
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
initializeCollectionRoyalty,
|
||||
setCollectionRoyaltyDefault,
|
||||
updateCollectionRoyaltyDefault,
|
||||
setCollectionRoyaltyProtocol,
|
||||
updateCollectionRoyaltyProtocol,
|
||||
}
|
||||
}
|
||||
|
||||
return { use, instantiate, migrate, messages }
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from './contract'
|
||||
export * from './useContract'
|
||||
@@ -0,0 +1,144 @@
|
||||
import type { RoyaltyRegistryInstance } from '../index'
|
||||
import { useRoyaltyRegistryContract } from '../index'
|
||||
|
||||
export type ExecuteType = typeof EXECUTE_TYPES[number]
|
||||
|
||||
export const EXECUTE_TYPES = [
|
||||
'initialize_collection_royalty',
|
||||
'set_collection_royalty_default',
|
||||
'update_collection_royalty_default',
|
||||
'set_collection_royalty_protocol',
|
||||
'update_collection_royalty_protocol',
|
||||
] as const
|
||||
|
||||
export interface ExecuteListItem {
|
||||
id: ExecuteType
|
||||
name: string
|
||||
description?: string
|
||||
}
|
||||
|
||||
export const EXECUTE_LIST: ExecuteListItem[] = [
|
||||
{
|
||||
id: 'initialize_collection_royalty',
|
||||
name: 'Initialize Collection Royalty',
|
||||
description: 'Initialize collection royalty',
|
||||
},
|
||||
{
|
||||
id: 'set_collection_royalty_default',
|
||||
name: 'Set Default Collection Royalty',
|
||||
description: 'Set default collection royalty for unknown protocols',
|
||||
},
|
||||
{
|
||||
id: 'update_collection_royalty_default',
|
||||
name: 'Update Default Collection Royalty',
|
||||
description: 'Update default collection royalty for unknown protocols',
|
||||
},
|
||||
{
|
||||
id: 'set_collection_royalty_protocol',
|
||||
name: 'Set Protocol Collection Royalty',
|
||||
description: 'Set collection royalty for a specific protocol',
|
||||
},
|
||||
{
|
||||
id: 'update_collection_royalty_protocol',
|
||||
name: 'Update Protocol Collection Royalty',
|
||||
description: 'Update collection royalty for a specific protocol',
|
||||
},
|
||||
]
|
||||
|
||||
export interface DispatchExecuteProps {
|
||||
type: ExecuteType
|
||||
[k: string]: unknown
|
||||
}
|
||||
|
||||
type Select<T extends ExecuteType> = T
|
||||
/** @see {@link RoyaltyRegistryInstance} */
|
||||
export interface DispatchExecuteArgs {
|
||||
contract: string
|
||||
collection: string
|
||||
protocol: string
|
||||
recipient: string
|
||||
share: number
|
||||
shareDelta: number
|
||||
decrement: boolean
|
||||
messages?: RoyaltyRegistryInstance
|
||||
type: string | undefined
|
||||
}
|
||||
|
||||
export const dispatchExecute = async (args: DispatchExecuteArgs) => {
|
||||
const { messages } = args
|
||||
if (!messages) {
|
||||
throw new Error('Cannot dispatch execute, messages are not defined')
|
||||
}
|
||||
switch (args.type) {
|
||||
case 'initialize_collection_royalty': {
|
||||
return messages.initializeCollectionRoyalty(args.collection)
|
||||
}
|
||||
case 'set_collection_royalty_default': {
|
||||
return messages.setCollectionRoyaltyDefault(args.collection, args.recipient, args.share)
|
||||
}
|
||||
case 'update_collection_royalty_default': {
|
||||
return messages.updateCollectionRoyaltyDefault(args.collection, args.recipient, args.shareDelta, args.decrement)
|
||||
}
|
||||
case 'set_collection_royalty_protocol': {
|
||||
return messages.setCollectionRoyaltyProtocol(args.collection, args.protocol, args.recipient, args.share)
|
||||
}
|
||||
case 'update_collection_royalty_protocol': {
|
||||
return messages.updateCollectionRoyaltyProtocol(
|
||||
args.collection,
|
||||
args.protocol,
|
||||
args.recipient,
|
||||
args.shareDelta,
|
||||
args.decrement,
|
||||
)
|
||||
}
|
||||
default: {
|
||||
throw new Error('Unknown execution type')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const previewExecutePayload = (args: DispatchExecuteArgs) => {
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
const { messages } = useRoyaltyRegistryContract()
|
||||
const { contract } = args
|
||||
switch (args.type) {
|
||||
case 'initialize_collection_royalty': {
|
||||
return messages(contract)?.initializeCollectionRoyalty(args.collection)
|
||||
}
|
||||
case 'set_collection_royalty_default': {
|
||||
return messages(contract)?.setCollectionRoyaltyDefault(args.collection, args.recipient, args.share)
|
||||
}
|
||||
case 'update_collection_royalty_default': {
|
||||
return messages(contract)?.updateCollectionRoyaltyDefault(
|
||||
args.collection,
|
||||
args.recipient,
|
||||
args.shareDelta,
|
||||
args.decrement,
|
||||
)
|
||||
}
|
||||
case 'set_collection_royalty_protocol': {
|
||||
return messages(contract)?.setCollectionRoyaltyProtocol(
|
||||
args.collection,
|
||||
args.protocol,
|
||||
args.recipient,
|
||||
args.share,
|
||||
)
|
||||
}
|
||||
case 'update_collection_royalty_protocol': {
|
||||
return messages(contract)?.updateCollectionRoyaltyProtocol(
|
||||
args.collection,
|
||||
args.protocol,
|
||||
args.recipient,
|
||||
args.shareDelta,
|
||||
args.decrement,
|
||||
)
|
||||
}
|
||||
default: {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const isEitherType = <T extends ExecuteType>(type: unknown, arr: T[]): type is T => {
|
||||
return arr.some((val) => type === val)
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
import type { RoyaltyRegistryInstance } from '../contract'
|
||||
|
||||
export type QueryType = typeof QUERY_TYPES[number]
|
||||
|
||||
export const QUERY_TYPES = [
|
||||
'config',
|
||||
'collection_royalty_default',
|
||||
'collection_royalty_protocol',
|
||||
'royalty_payment',
|
||||
] as const
|
||||
export interface QueryListItem {
|
||||
id: QueryType
|
||||
name: string
|
||||
description?: string
|
||||
}
|
||||
|
||||
export const QUERY_LIST: QueryListItem[] = [
|
||||
{ id: 'config', name: 'Query Config', description: 'View the contract config' },
|
||||
{
|
||||
id: 'collection_royalty_default',
|
||||
name: 'Query Collection Royalty Details',
|
||||
description: 'View the collection royalty details',
|
||||
},
|
||||
{
|
||||
id: 'collection_royalty_protocol',
|
||||
name: 'Query Collection Royalty Protocol',
|
||||
description: 'View the collection royalty protocol',
|
||||
},
|
||||
{
|
||||
id: 'royalty_payment',
|
||||
name: 'Query Royalty Payment',
|
||||
description: 'View the royalty payment',
|
||||
},
|
||||
]
|
||||
/*
|
||||
//Query
|
||||
config: () => Promise<string>
|
||||
collectionRoyaltyDefault: (collection: string) => Promise<string>
|
||||
collectionRoyaltyProtocol: (collection: string, protocol: string) => Promise<string>
|
||||
// RoyaltyProtocolByCollection: (collection: string, queryOptions: QqueryOptions) => Promise<string>
|
||||
royaltyPayment: (collection: string, protocol?: string) => Promise<string>
|
||||
*/
|
||||
|
||||
export interface DispatchQueryProps {
|
||||
messages: RoyaltyRegistryInstance | undefined
|
||||
type: QueryType
|
||||
collection: string
|
||||
protocol: string
|
||||
}
|
||||
|
||||
export const dispatchQuery = (props: DispatchQueryProps) => {
|
||||
const { messages, type, collection, protocol } = props
|
||||
switch (type) {
|
||||
case 'config':
|
||||
return messages?.config()
|
||||
case 'collection_royalty_default':
|
||||
return messages?.collectionRoyaltyDefault(collection)
|
||||
case 'collection_royalty_protocol':
|
||||
return messages?.collectionRoyaltyProtocol(collection, protocol)
|
||||
default: {
|
||||
throw new Error('unknown query type')
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
/* eslint-disable eslint-comments/disable-enable-pair */
|
||||
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { useWallet } from 'utils/wallet'
|
||||
|
||||
import type {
|
||||
InstantiateResponse,
|
||||
MigrateResponse,
|
||||
RoyaltyRegistryContract,
|
||||
RoyaltyRegistryInstance,
|
||||
RoyaltyRegistryMessages,
|
||||
} from './contract'
|
||||
import { RoyaltyRegistry as initContract } from './contract'
|
||||
|
||||
export interface UseRoyaltyRegistryContractProps {
|
||||
instantiate: (
|
||||
codeId: number,
|
||||
initMsg: Record<string, unknown>,
|
||||
label: string,
|
||||
admin?: string,
|
||||
) => Promise<InstantiateResponse>
|
||||
|
||||
migrate: (contractAddress: string, codeId: number, migrateMsg: Record<string, unknown>) => Promise<MigrateResponse>
|
||||
|
||||
use: (customAddress?: string) => RoyaltyRegistryInstance | undefined
|
||||
|
||||
updateContractAddress: (contractAddress: string) => void
|
||||
|
||||
messages: (contractAddress: string) => RoyaltyRegistryMessages | undefined
|
||||
}
|
||||
|
||||
export function useRoyaltyRegistryContract(): UseRoyaltyRegistryContractProps {
|
||||
const wallet = useWallet()
|
||||
|
||||
const [address, setAddress] = useState<string>('')
|
||||
const [royaltyRegistry, setRoyaltyRegistry] = useState<RoyaltyRegistryContract>()
|
||||
|
||||
useEffect(() => {
|
||||
setAddress(localStorage.getItem('contract_address') || '')
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (!wallet.isWalletConnected) {
|
||||
return
|
||||
}
|
||||
|
||||
const load = async () => {
|
||||
const client = await wallet.getSigningCosmWasmClient()
|
||||
const royaltyRegistryContract = initContract(client, wallet.address || '')
|
||||
setRoyaltyRegistry(royaltyRegistryContract)
|
||||
}
|
||||
|
||||
load().catch(console.error)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [wallet.isWalletConnected, wallet.address])
|
||||
|
||||
const updateContractAddress = (contractAddress: string) => {
|
||||
setAddress(contractAddress)
|
||||
}
|
||||
|
||||
const instantiate = useCallback(
|
||||
(codeId: number, initMsg: Record<string, unknown>, label: string, admin?: string): Promise<InstantiateResponse> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!royaltyRegistry) {
|
||||
reject(new Error('Contract is not initialized.'))
|
||||
return
|
||||
}
|
||||
royaltyRegistry.instantiate(codeId, initMsg, label, admin).then(resolve).catch(reject)
|
||||
})
|
||||
},
|
||||
[royaltyRegistry],
|
||||
)
|
||||
|
||||
const migrate = useCallback(
|
||||
(contractAddress: string, codeId: number, migrateMsg: Record<string, unknown>): Promise<MigrateResponse> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!royaltyRegistry) {
|
||||
reject(new Error('Contract is not initialized.'))
|
||||
return
|
||||
}
|
||||
console.log(wallet.address, contractAddress, codeId)
|
||||
royaltyRegistry
|
||||
.migrate(wallet.address || '', contractAddress, codeId, migrateMsg)
|
||||
.then(resolve)
|
||||
.catch(reject)
|
||||
})
|
||||
},
|
||||
[royaltyRegistry, wallet],
|
||||
)
|
||||
|
||||
const use = useCallback(
|
||||
(customAddress = ''): RoyaltyRegistryInstance | undefined => {
|
||||
return royaltyRegistry?.use(address || customAddress)
|
||||
},
|
||||
[royaltyRegistry, address],
|
||||
)
|
||||
|
||||
const messages = useCallback(
|
||||
(customAddress = ''): RoyaltyRegistryMessages | undefined => {
|
||||
return royaltyRegistry?.messages(address || customAddress)
|
||||
},
|
||||
[royaltyRegistry, address],
|
||||
)
|
||||
|
||||
return {
|
||||
instantiate,
|
||||
migrate,
|
||||
use,
|
||||
updateContractAddress,
|
||||
messages,
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import { toBase64, toUtf8 } from '@cosmjs/encoding'
|
||||
import type { Coin, logs } from '@cosmjs/stargate'
|
||||
import { coin } from '@cosmjs/stargate'
|
||||
import { MsgExecuteContract } from 'cosmjs-types/cosmwasm/wasm/v1/tx'
|
||||
import type { AirdropAllocation } from 'utils/isValidAccountsFile'
|
||||
|
||||
import type { RoyaltyInfo } from '../vendingMinter/contract'
|
||||
|
||||
@@ -24,6 +25,7 @@ export interface CollectionInfo {
|
||||
external_link?: string
|
||||
explicit_content?: boolean
|
||||
royalty_info?: RoyaltyInfo | undefined
|
||||
creator?: string
|
||||
}
|
||||
|
||||
export interface SG721Instance {
|
||||
@@ -86,6 +88,7 @@ export interface SG721Instance {
|
||||
burn: (tokenId: string) => Promise<string>
|
||||
batchBurn: (tokenIds: string) => Promise<string>
|
||||
batchTransfer: (recipient: string, tokenIds: string) => Promise<string>
|
||||
batchTransferMultiAddress: (senderAddress: string, tokenRecipients: AirdropAllocation[]) => Promise<string>
|
||||
updateTokenMetadata: (tokenId: string, tokenURI: string) => Promise<string>
|
||||
batchUpdateTokenMetadata: (tokenIds: string, tokenURI: string, jsonExtensions: boolean) => Promise<string>
|
||||
freezeTokenMetadata: () => Promise<string>
|
||||
@@ -103,6 +106,7 @@ export interface Sg721Messages {
|
||||
burn: (tokenId: string) => BurnMessage
|
||||
batchBurn: (tokenIds: string) => BatchBurnMessage
|
||||
batchTransfer: (recipient: string, tokenIds: string) => BatchTransferMessage
|
||||
batchTransferMultiAddress: (tokenRecipients: AirdropAllocation[]) => BatchTransferMultiAddressMessage
|
||||
updateCollectionInfo: (collectionInfo: CollectionInfo) => UpdateCollectionInfoMessage
|
||||
freezeCollectionInfo: () => FreezeCollectionInfoMessage
|
||||
updateTokenMetadata: (tokenId: string, tokenURI: string) => UpdateTokenMetadataMessage
|
||||
@@ -227,6 +231,13 @@ export interface BatchTransferMessage {
|
||||
funds: Coin[]
|
||||
}
|
||||
|
||||
export interface BatchTransferMultiAddressMessage {
|
||||
sender: string
|
||||
contract: string
|
||||
msg: Record<string, unknown>[]
|
||||
funds: Coin[]
|
||||
}
|
||||
|
||||
export interface UpdateTokenMetadataMessage {
|
||||
sender: string
|
||||
contract: string
|
||||
@@ -601,6 +612,37 @@ export const SG721 = (client: SigningCosmWasmClient, txSigner: string): SG721Con
|
||||
return res.transactionHash
|
||||
}
|
||||
|
||||
const batchTransferMultiAddress = async (
|
||||
senderAddress: string,
|
||||
recipients: AirdropAllocation[],
|
||||
): Promise<string> => {
|
||||
const executeContractMsgs: MsgExecuteContractEncodeObject[] = []
|
||||
for (let i = 0; i < recipients.length; i++) {
|
||||
const msg = {
|
||||
transfer_nft: { recipient: recipients[i].address, token_id: recipients[i].tokenId as string },
|
||||
}
|
||||
const executeContractMsg: MsgExecuteContractEncodeObject = {
|
||||
typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract',
|
||||
value: MsgExecuteContract.fromPartial({
|
||||
sender: senderAddress,
|
||||
contract: contractAddress,
|
||||
msg: toUtf8(JSON.stringify(msg)),
|
||||
}),
|
||||
}
|
||||
|
||||
executeContractMsgs.push(executeContractMsg)
|
||||
}
|
||||
|
||||
const res = await client.signAndBroadcast(
|
||||
senderAddress,
|
||||
executeContractMsgs,
|
||||
'auto',
|
||||
'batch transfer to multiple recipients',
|
||||
)
|
||||
|
||||
return res.transactionHash
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||
const updateCollectionInfo = async (collectionInfo: CollectionInfo): Promise<string> => {
|
||||
const res = await client.execute(
|
||||
@@ -719,7 +761,7 @@ export const SG721 = (client: SigningCosmWasmClient, txSigner: string): SG721Con
|
||||
},
|
||||
'auto',
|
||||
'',
|
||||
[coin('500000000', 'ustars')],
|
||||
[coin('2000000000', 'ustars')],
|
||||
)
|
||||
return res.transactionHash
|
||||
}
|
||||
@@ -748,6 +790,7 @@ export const SG721 = (client: SigningCosmWasmClient, txSigner: string): SG721Con
|
||||
burn,
|
||||
batchBurn,
|
||||
batchTransfer,
|
||||
batchTransferMultiAddress,
|
||||
updateCollectionInfo,
|
||||
freezeCollectionInfo,
|
||||
updateTokenMetadata,
|
||||
@@ -950,6 +993,19 @@ export const SG721 = (client: SigningCosmWasmClient, txSigner: string): SG721Con
|
||||
}
|
||||
}
|
||||
|
||||
const batchTransferMultiAddress = (recipients: AirdropAllocation[]): BatchTransferMultiAddressMessage => {
|
||||
const msg: Record<string, unknown>[] = []
|
||||
for (let i = 0; i < recipients.length; i++) {
|
||||
msg.push({ transfer_nft: { recipient: recipients[i].address, token_id: recipients[i].tokenId } })
|
||||
}
|
||||
return {
|
||||
sender: txSigner,
|
||||
contract: contractAddress,
|
||||
msg,
|
||||
funds: [],
|
||||
}
|
||||
}
|
||||
|
||||
const batchUpdateTokenMetadata = (
|
||||
tokenIds: string,
|
||||
baseURI: string,
|
||||
@@ -1018,7 +1074,7 @@ export const SG721 = (client: SigningCosmWasmClient, txSigner: string): SG721Con
|
||||
msg: {
|
||||
enable_updatable: {},
|
||||
},
|
||||
funds: [coin('500000000', 'ustars')],
|
||||
funds: [coin('2000000000', 'ustars')],
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1055,6 +1111,7 @@ export const SG721 = (client: SigningCosmWasmClient, txSigner: string): SG721Con
|
||||
burn,
|
||||
batchBurn,
|
||||
batchTransfer,
|
||||
batchTransferMultiAddress,
|
||||
updateCollectionInfo,
|
||||
freezeCollectionInfo,
|
||||
updateTokenMetadata,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { Coin } from '@cosmjs/proto-signing'
|
||||
import { useWallet } from 'contexts/wallet'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { useWallet } from 'utils/wallet'
|
||||
|
||||
import type { MigrateResponse, SG721Contract, SG721Instance, Sg721Messages } from './contract'
|
||||
import { SG721 as initContract } from './contract'
|
||||
@@ -35,9 +35,19 @@ export function useSG721Contract(): UseSG721ContractProps {
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
const contract = initContract(wallet.getClient(), wallet.address)
|
||||
setSG721(contract)
|
||||
}, [wallet])
|
||||
if (!wallet.isWalletConnected) {
|
||||
return
|
||||
}
|
||||
|
||||
const load = async () => {
|
||||
const client = await wallet.getSigningCosmWasmClient()
|
||||
const contract = initContract(client, wallet.address || '')
|
||||
setSG721(contract)
|
||||
}
|
||||
|
||||
load().catch(console.error)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [wallet.isWalletConnected, wallet.address])
|
||||
|
||||
const updateContractAddress = (contractAddress: string) => {
|
||||
setAddress(contractAddress)
|
||||
@@ -50,7 +60,9 @@ export function useSG721Contract(): UseSG721ContractProps {
|
||||
reject(new Error('Contract is not initialized.'))
|
||||
return
|
||||
}
|
||||
SG721.instantiate(wallet.address, codeId, initMsg, label, admin).then(resolve).catch(reject)
|
||||
SG721.instantiate(wallet.address || '', codeId, initMsg, label, admin)
|
||||
.then(resolve)
|
||||
.catch(reject)
|
||||
})
|
||||
},
|
||||
[SG721, wallet],
|
||||
@@ -64,7 +76,9 @@ export function useSG721Contract(): UseSG721ContractProps {
|
||||
return
|
||||
}
|
||||
console.log(wallet.address, contractAddress, codeId)
|
||||
SG721.migrate(wallet.address, contractAddress, codeId, migrateMsg).then(resolve).catch(reject)
|
||||
SG721.migrate(wallet.address || '', contractAddress, codeId, migrateMsg)
|
||||
.then(resolve)
|
||||
.catch(reject)
|
||||
})
|
||||
},
|
||||
[SG721, wallet],
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/* eslint-disable eslint-comments/disable-enable-pair */
|
||||
|
||||
import { useWallet } from 'contexts/wallet'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { useWallet } from 'utils/wallet'
|
||||
|
||||
import type { InstantiateResponse, MigrateResponse, SplitsContract, SplitsInstance, SplitsMessages } from './contract'
|
||||
import { Splits as initContract } from './contract'
|
||||
@@ -34,9 +34,19 @@ export function useSplitsContract(): UseSplitsContractProps {
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
const splitsContract = initContract(wallet.getClient(), wallet.address)
|
||||
setSplits(splitsContract)
|
||||
}, [wallet])
|
||||
if (!wallet.isWalletConnected) {
|
||||
return
|
||||
}
|
||||
|
||||
const load = async () => {
|
||||
const client = await wallet.getSigningCosmWasmClient()
|
||||
const contract = initContract(client, wallet.address || '')
|
||||
setSplits(contract)
|
||||
}
|
||||
|
||||
load().catch(console.error)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [wallet.isWalletConnected, wallet.address])
|
||||
|
||||
const updateContractAddress = (contractAddress: string) => {
|
||||
setAddress(contractAddress)
|
||||
@@ -63,7 +73,10 @@ export function useSplitsContract(): UseSplitsContractProps {
|
||||
return
|
||||
}
|
||||
console.log(wallet.address, contractAddress, codeId)
|
||||
splits.migrate(wallet.address, contractAddress, codeId, migrateMsg).then(resolve).catch(reject)
|
||||
splits
|
||||
.migrate(wallet.address || '', contractAddress, codeId, migrateMsg)
|
||||
.then(resolve)
|
||||
.catch(reject)
|
||||
})
|
||||
},
|
||||
[splits, wallet],
|
||||
|
||||
@@ -63,8 +63,10 @@ export const vendingFactory = (client: SigningCosmWasmClient, txSigner: string):
|
||||
const result = await client.execute(senderAddress, contractAddress, msg, 'auto', '', funds)
|
||||
|
||||
return {
|
||||
vendingMinterAddress: result.logs[0].events[5].attributes[0].value,
|
||||
sg721Address: result.logs[0].events[5].attributes[2].value,
|
||||
vendingMinterAddress: result.logs[0].events.filter((e) => e.type === 'instantiate')[0].attributes[0].value,
|
||||
sg721Address: result.logs[0].events
|
||||
.filter((e) => e.type === 'wasm')
|
||||
.filter((e) => e.attributes[2]?.key === 'sg721_address')[0].attributes[2].value,
|
||||
transactionHash: result.transactionHash,
|
||||
logs: result.logs,
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { logs } from '@cosmjs/stargate'
|
||||
import { useWallet } from 'contexts/wallet'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { useWallet } from 'utils/wallet'
|
||||
|
||||
import type { VendingFactoryContract, VendingFactoryInstance, VendingFactoryMessages } from './contract'
|
||||
import { vendingFactory as initContract } from './contract'
|
||||
@@ -41,9 +41,19 @@ export function useVendingFactoryContract(): UseVendingFactoryContractProps {
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
const VendingFactoryBaseContract = initContract(wallet.getClient(), wallet.address)
|
||||
setVendingFactory(VendingFactoryBaseContract)
|
||||
}, [wallet])
|
||||
if (!wallet.isWalletConnected) {
|
||||
return
|
||||
}
|
||||
|
||||
const load = async () => {
|
||||
const client = await wallet.getSigningCosmWasmClient()
|
||||
const contract = initContract(client, wallet.address || '')
|
||||
setVendingFactory(contract)
|
||||
}
|
||||
|
||||
load().catch(console.error)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [wallet.isWalletConnected, wallet.address])
|
||||
|
||||
const updateContractAddress = (contractAddress: string) => {
|
||||
setAddress(contractAddress)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { Coin } from '@cosmjs/proto-signing'
|
||||
import type { logs } from '@cosmjs/stargate'
|
||||
import { useWallet } from 'contexts/wallet'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { useWallet } from 'utils/wallet'
|
||||
|
||||
import type { MigrateResponse, VendingMinterContract, VendingMinterInstance, VendingMinterMessages } from './contract'
|
||||
import { vendingMinter as initContract } from './contract'
|
||||
@@ -50,9 +50,19 @@ export function useVendingMinterContract(): UseVendingMinterContractProps {
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
const VendingMinterBaseContract = initContract(wallet.getClient(), wallet.address)
|
||||
setVendingMinter(VendingMinterBaseContract)
|
||||
}, [wallet])
|
||||
if (!wallet.isWalletConnected) {
|
||||
return
|
||||
}
|
||||
|
||||
const load = async () => {
|
||||
const client = await wallet.getSigningCosmWasmClient()
|
||||
const contract = initContract(client, wallet.address || '')
|
||||
setVendingMinter(contract)
|
||||
}
|
||||
|
||||
load().catch(console.error)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [wallet.isWalletConnected, wallet.address])
|
||||
|
||||
const updateContractAddress = (contractAddress: string) => {
|
||||
setAddress(contractAddress)
|
||||
@@ -65,7 +75,10 @@ export function useVendingMinterContract(): UseVendingMinterContractProps {
|
||||
reject(new Error('Contract is not initialized.'))
|
||||
return
|
||||
}
|
||||
vendingMinter.instantiate(wallet.address, codeId, initMsg, label, admin).then(resolve).catch(reject)
|
||||
vendingMinter
|
||||
.instantiate(wallet.address || '', codeId, initMsg, label, admin)
|
||||
.then(resolve)
|
||||
.catch(reject)
|
||||
})
|
||||
},
|
||||
[vendingMinter, wallet],
|
||||
@@ -79,7 +92,10 @@ export function useVendingMinterContract(): UseVendingMinterContractProps {
|
||||
return
|
||||
}
|
||||
console.log(wallet.address, contractAddress, codeId)
|
||||
vendingMinter.migrate(wallet.address, contractAddress, codeId, migrateMsg).then(resolve).catch(reject)
|
||||
vendingMinter
|
||||
.migrate(wallet.address || '', contractAddress, codeId, migrateMsg)
|
||||
.then(resolve)
|
||||
.catch(reject)
|
||||
})
|
||||
},
|
||||
[vendingMinter, wallet],
|
||||
|
||||
@@ -32,10 +32,12 @@ export interface DispatchQueryProps {
|
||||
messages: WhiteListInstance | undefined
|
||||
type: QueryType
|
||||
address: string
|
||||
startAfter?: string
|
||||
limit?: number
|
||||
}
|
||||
|
||||
export const dispatchQuery = (props: DispatchQueryProps) => {
|
||||
const { messages, type, address } = props
|
||||
const { messages, type, address, startAfter, limit } = props
|
||||
switch (type) {
|
||||
case 'has_started':
|
||||
return messages?.hasStarted()
|
||||
@@ -44,7 +46,7 @@ export const dispatchQuery = (props: DispatchQueryProps) => {
|
||||
case 'is_active':
|
||||
return messages?.isActive()
|
||||
case 'members':
|
||||
return messages?.members()
|
||||
return messages?.members(startAfter, limit)
|
||||
case 'admin_list':
|
||||
return messages?.adminList()
|
||||
case 'has_member':
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useWallet } from 'contexts/wallet'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { useWallet } from 'utils/wallet'
|
||||
|
||||
import type { InstantiateResponse, WhiteListContract, WhiteListInstance, WhitelistMessages } from './contract'
|
||||
import { WhiteList as initContract } from './contract'
|
||||
@@ -30,9 +30,19 @@ export function useWhiteListContract(): UseWhiteListContractProps {
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
const whiteListContract = initContract(wallet.getClient(), wallet.address)
|
||||
setWhiteList(whiteListContract)
|
||||
}, [wallet])
|
||||
if (!wallet.isWalletConnected) {
|
||||
return
|
||||
}
|
||||
|
||||
const load = async () => {
|
||||
const client = await wallet.getSigningCosmWasmClient()
|
||||
const contract = initContract(client, wallet.address || '')
|
||||
setWhiteList(contract)
|
||||
}
|
||||
|
||||
load().catch(console.error)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [wallet.isWalletConnected, wallet.address])
|
||||
|
||||
const updateContractAddress = (contractAddress: string) => {
|
||||
setAddress(contractAddress)
|
||||
|
||||
@@ -0,0 +1,375 @@
|
||||
import type { SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate'
|
||||
import { type Coin, coin } from '@cosmjs/proto-signing'
|
||||
import type { WhitelistFlexMember } from 'components/WhitelistFlexUpload'
|
||||
|
||||
export interface InstantiateResponse {
|
||||
readonly contractAddress: string
|
||||
readonly transactionHash: string
|
||||
}
|
||||
|
||||
export interface ConfigResponse {
|
||||
readonly per_address_limit: number
|
||||
readonly start_time: string
|
||||
readonly end_time: string
|
||||
readonly mint_price: Coin
|
||||
readonly is_active: boolean
|
||||
}
|
||||
export interface WhiteListMerkleTreeInstance {
|
||||
readonly contractAddress: string
|
||||
//Query
|
||||
hasStarted: () => Promise<boolean>
|
||||
hasEnded: () => Promise<boolean>
|
||||
isActive: () => Promise<boolean>
|
||||
hasMember: (member: string, proof_hashes: string[]) => Promise<boolean>
|
||||
adminList: () => Promise<string[]>
|
||||
config: () => Promise<ConfigResponse>
|
||||
canExecute: (sender: string, msg: string) => Promise<boolean>
|
||||
merkleRoot: () => Promise<string>
|
||||
merkleTreeUri: () => Promise<string>
|
||||
|
||||
//Execute
|
||||
updateStartTime: (startTime: string) => Promise<string>
|
||||
updateEndTime: (endTime: string) => Promise<string>
|
||||
addMembers: (memberList: string[] | WhitelistFlexMember[]) => Promise<string>
|
||||
removeMembers: (memberList: string[]) => Promise<string>
|
||||
// updatePerAddressLimit: (limit: number) => Promise<string>
|
||||
updateAdmins: (admins: string[]) => Promise<string>
|
||||
freeze: () => Promise<string>
|
||||
}
|
||||
|
||||
export interface WhiteListMerkleTreeMessages {
|
||||
updateStartTime: (startTime: string) => UpdateStartTimeMessage
|
||||
updateEndTime: (endTime: string) => UpdateEndTimeMessage
|
||||
addMembers: (memberList: string[] | WhitelistFlexMember[]) => AddMembersMessage
|
||||
removeMembers: (memberList: string[]) => RemoveMembersMessage
|
||||
// updatePerAddressLimit: (limit: number) => UpdatePerAddressLimitMessage
|
||||
updateAdmins: (admins: string[]) => UpdateAdminsMessage
|
||||
freeze: () => FreezeMessage
|
||||
}
|
||||
|
||||
export interface UpdateStartTimeMessage {
|
||||
sender: string
|
||||
contract: string
|
||||
msg: {
|
||||
update_start_time: string
|
||||
}
|
||||
funds: Coin[]
|
||||
}
|
||||
|
||||
export interface UpdateEndTimeMessage {
|
||||
sender: string
|
||||
contract: string
|
||||
msg: {
|
||||
update_end_time: string
|
||||
}
|
||||
funds: Coin[]
|
||||
}
|
||||
|
||||
export interface UpdateAdminsMessage {
|
||||
sender: string
|
||||
contract: string
|
||||
msg: {
|
||||
update_admins: { admins: string[] }
|
||||
}
|
||||
funds: Coin[]
|
||||
}
|
||||
|
||||
export interface FreezeMessage {
|
||||
sender: string
|
||||
contract: string
|
||||
msg: { freeze: Record<string, never> }
|
||||
funds: Coin[]
|
||||
}
|
||||
export interface AddMembersMessage {
|
||||
sender: string
|
||||
contract: string
|
||||
msg: {
|
||||
add_members: { to_add: string[] | WhitelistFlexMember[] }
|
||||
}
|
||||
funds: Coin[]
|
||||
}
|
||||
|
||||
export interface RemoveMembersMessage {
|
||||
sender: string
|
||||
contract: string
|
||||
msg: {
|
||||
remove_members: { to_remove: string[] }
|
||||
}
|
||||
funds: Coin[]
|
||||
}
|
||||
|
||||
// export interface UpdatePerAddressLimitMessage {
|
||||
// sender: string
|
||||
|
||||
// contract: string
|
||||
// msg: {
|
||||
// update_per_address_limit: number
|
||||
// }
|
||||
// funds: Coin[]
|
||||
// }
|
||||
|
||||
export interface WhiteListMerkleTreeContract {
|
||||
instantiate: (
|
||||
codeId: number,
|
||||
initMsg: Record<string, unknown>,
|
||||
label: string,
|
||||
admin?: string,
|
||||
) => Promise<InstantiateResponse>
|
||||
|
||||
use: (contractAddress: string) => WhiteListMerkleTreeInstance
|
||||
|
||||
messages: (contractAddress: string) => WhiteListMerkleTreeMessages
|
||||
}
|
||||
|
||||
export const WhiteListMerkleTree = (client: SigningCosmWasmClient, txSigner: string): WhiteListMerkleTreeContract => {
|
||||
const use = (contractAddress: string): WhiteListMerkleTreeInstance => {
|
||||
///QUERY START
|
||||
const hasStarted = async (): Promise<boolean> => {
|
||||
return client.queryContractSmart(contractAddress, { has_started: {} })
|
||||
}
|
||||
|
||||
const hasEnded = async (): Promise<boolean> => {
|
||||
return client.queryContractSmart(contractAddress, { has_ended: {} })
|
||||
}
|
||||
|
||||
const isActive = async (): Promise<boolean> => {
|
||||
return client.queryContractSmart(contractAddress, { is_active: {} })
|
||||
}
|
||||
|
||||
const hasMember = async (member: string, proofHashes: string[]): Promise<boolean> => {
|
||||
return client.queryContractSmart(contractAddress, {
|
||||
has_member: { member, proof_hashes: proofHashes },
|
||||
})
|
||||
}
|
||||
|
||||
const adminList = async (): Promise<string[]> => {
|
||||
return client.queryContractSmart(contractAddress, {
|
||||
admin_list: {},
|
||||
})
|
||||
}
|
||||
|
||||
const config = async (): Promise<ConfigResponse> => {
|
||||
return client.queryContractSmart(contractAddress, {
|
||||
config: {},
|
||||
})
|
||||
}
|
||||
|
||||
const merkleRoot = async (): Promise<string> => {
|
||||
return client.queryContractSmart(contractAddress, {
|
||||
merkle_root: {},
|
||||
})
|
||||
}
|
||||
|
||||
const merkleTreeUri = async (): Promise<string> => {
|
||||
return client.queryContractSmart(contractAddress, {
|
||||
merkle_tree_u_r_i: {},
|
||||
})
|
||||
}
|
||||
|
||||
const canExecute = async (sender: string, msg: string): Promise<boolean> => {
|
||||
return client.queryContractSmart(contractAddress, {
|
||||
can_execute: { sender, msg },
|
||||
})
|
||||
}
|
||||
/// QUERY END
|
||||
/// EXECUTE START
|
||||
const updateStartTime = async (startTime: string): Promise<string> => {
|
||||
const res = await client.execute(txSigner, contractAddress, { update_start_time: startTime }, 'auto')
|
||||
return res.transactionHash
|
||||
}
|
||||
|
||||
const updateEndTime = async (endTime: string): Promise<string> => {
|
||||
const res = await client.execute(txSigner, contractAddress, { update_end_time: endTime }, 'auto')
|
||||
return res.transactionHash
|
||||
}
|
||||
|
||||
const addMembers = async (memberList: string[] | WhitelistFlexMember[]): Promise<string> => {
|
||||
const res = await client.execute(
|
||||
txSigner,
|
||||
contractAddress,
|
||||
{
|
||||
add_members: {
|
||||
to_add: memberList,
|
||||
},
|
||||
},
|
||||
'auto',
|
||||
)
|
||||
return res.transactionHash
|
||||
}
|
||||
|
||||
const updateAdmins = async (admins: string[]): Promise<string> => {
|
||||
const res = await client.execute(
|
||||
txSigner,
|
||||
contractAddress,
|
||||
{
|
||||
update_admins: {
|
||||
admins,
|
||||
},
|
||||
},
|
||||
'auto',
|
||||
)
|
||||
return res.transactionHash
|
||||
}
|
||||
|
||||
const freeze = async (): Promise<string> => {
|
||||
const res = await client.execute(
|
||||
txSigner,
|
||||
contractAddress,
|
||||
{
|
||||
freeze: {},
|
||||
},
|
||||
'auto',
|
||||
)
|
||||
return res.transactionHash
|
||||
}
|
||||
|
||||
const removeMembers = async (memberList: string[]): Promise<string> => {
|
||||
const res = await client.execute(
|
||||
txSigner,
|
||||
contractAddress,
|
||||
{
|
||||
remove_members: {
|
||||
to_remove: memberList,
|
||||
},
|
||||
},
|
||||
'auto',
|
||||
)
|
||||
return res.transactionHash
|
||||
}
|
||||
|
||||
// const updatePerAddressLimit = async (limit: number): Promise<string> => {
|
||||
// const res = await client.execute(txSigner, contractAddress, { update_per_address_limit: limit }, 'auto')
|
||||
// return res.transactionHash
|
||||
// }
|
||||
|
||||
/// EXECUTE END
|
||||
|
||||
return {
|
||||
contractAddress,
|
||||
updateStartTime,
|
||||
updateEndTime,
|
||||
updateAdmins,
|
||||
freeze,
|
||||
addMembers,
|
||||
removeMembers,
|
||||
// updatePerAddressLimit,
|
||||
hasStarted,
|
||||
hasEnded,
|
||||
isActive,
|
||||
hasMember,
|
||||
adminList,
|
||||
config,
|
||||
merkleRoot,
|
||||
merkleTreeUri,
|
||||
canExecute,
|
||||
}
|
||||
}
|
||||
|
||||
const instantiate = async (
|
||||
codeId: number,
|
||||
initMsg: Record<string, unknown>,
|
||||
label: string,
|
||||
admin?: string,
|
||||
): Promise<InstantiateResponse> => {
|
||||
const result = await client.instantiate(txSigner, codeId, initMsg, label, 'auto', {
|
||||
admin,
|
||||
funds: [coin(1000000000, 'ustars')],
|
||||
})
|
||||
|
||||
return {
|
||||
contractAddress: result.contractAddress,
|
||||
transactionHash: result.transactionHash,
|
||||
}
|
||||
}
|
||||
|
||||
const messages = (contractAddress: string) => {
|
||||
const updateStartTime = (startTime: string) => {
|
||||
return {
|
||||
sender: txSigner,
|
||||
contract: contractAddress,
|
||||
msg: {
|
||||
update_start_time: startTime,
|
||||
},
|
||||
funds: [],
|
||||
}
|
||||
}
|
||||
|
||||
const updateEndTime = (endTime: string) => {
|
||||
return {
|
||||
sender: txSigner,
|
||||
contract: contractAddress,
|
||||
msg: {
|
||||
update_end_time: endTime,
|
||||
},
|
||||
funds: [],
|
||||
}
|
||||
}
|
||||
|
||||
const addMembers = (memberList: string[] | WhitelistFlexMember[]) => {
|
||||
return {
|
||||
sender: txSigner,
|
||||
contract: contractAddress,
|
||||
msg: {
|
||||
add_members: { to_add: memberList },
|
||||
},
|
||||
funds: [],
|
||||
}
|
||||
}
|
||||
|
||||
const updateAdmins = (admins: string[]) => {
|
||||
return {
|
||||
sender: txSigner,
|
||||
contract: contractAddress,
|
||||
msg: {
|
||||
update_admins: { admins },
|
||||
},
|
||||
funds: [],
|
||||
}
|
||||
}
|
||||
|
||||
const freeze = () => {
|
||||
return {
|
||||
sender: txSigner,
|
||||
contract: contractAddress,
|
||||
msg: {
|
||||
freeze: {},
|
||||
},
|
||||
funds: [],
|
||||
}
|
||||
}
|
||||
|
||||
const removeMembers = (memberList: string[]) => {
|
||||
return {
|
||||
sender: txSigner,
|
||||
contract: contractAddress,
|
||||
msg: {
|
||||
remove_members: { to_remove: memberList },
|
||||
},
|
||||
funds: [],
|
||||
}
|
||||
}
|
||||
|
||||
// const updatePerAddressLimit = (limit: number) => {
|
||||
// return {
|
||||
// sender: txSigner,
|
||||
// contract: contractAddress,
|
||||
// msg: {
|
||||
// update_per_address_limit: limit,
|
||||
// },
|
||||
// funds: [],
|
||||
// }
|
||||
// }
|
||||
|
||||
return {
|
||||
updateStartTime,
|
||||
updateEndTime,
|
||||
updateAdmins,
|
||||
addMembers,
|
||||
removeMembers,
|
||||
// updatePerAddressLimit,
|
||||
freeze,
|
||||
}
|
||||
}
|
||||
|
||||
return { use, instantiate, messages }
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from './contract'
|
||||
export * from './useContract'
|
||||
@@ -0,0 +1,144 @@
|
||||
import type { WhitelistFlexMember } from '../../../components/WhitelistFlexUpload'
|
||||
import type { WhiteListMerkleTreeInstance } from '../index'
|
||||
import { useWhiteListMerkleTreeContract } from '../index'
|
||||
|
||||
export type ExecuteType = typeof EXECUTE_TYPES[number]
|
||||
|
||||
export const EXECUTE_TYPES = [
|
||||
'update_start_time',
|
||||
'update_end_time',
|
||||
'update_admins',
|
||||
'add_members',
|
||||
'remove_members',
|
||||
// 'update_per_address_limit',
|
||||
'freeze',
|
||||
] as const
|
||||
|
||||
export interface ExecuteListItem {
|
||||
id: ExecuteType
|
||||
name: string
|
||||
description?: string
|
||||
}
|
||||
|
||||
export const EXECUTE_LIST: ExecuteListItem[] = [
|
||||
{
|
||||
id: 'update_start_time',
|
||||
name: 'Update Start Time',
|
||||
description: `Update the start time of the whitelist`,
|
||||
},
|
||||
{
|
||||
id: 'update_end_time',
|
||||
name: 'Update End Time',
|
||||
description: `Update the end time of the whitelist`,
|
||||
},
|
||||
{
|
||||
id: 'update_admins',
|
||||
name: 'Update Admins',
|
||||
description: `Update the list of administrators for the whitelist`,
|
||||
},
|
||||
// {
|
||||
// id: 'add_members',
|
||||
// name: 'Add Members',
|
||||
// description: `Add members to the whitelist`,
|
||||
// },
|
||||
// {
|
||||
// id: 'remove_members',
|
||||
// name: 'Remove Members',
|
||||
// description: `Remove members from the whitelist`,
|
||||
// },
|
||||
// {
|
||||
// id: 'update_per_address_limit',
|
||||
// name: 'Update Per Address Limit',
|
||||
// description: `Update tokens per address limit`,
|
||||
// },
|
||||
{
|
||||
id: 'freeze',
|
||||
name: 'Freeze',
|
||||
description: `Freeze the current state of the contract admin list`,
|
||||
},
|
||||
]
|
||||
|
||||
export interface DispatchExecuteProps {
|
||||
type: ExecuteType
|
||||
[k: string]: unknown
|
||||
}
|
||||
|
||||
/** @see {@link WhiteListMerkleTreeInstance} */
|
||||
export interface DispatchExecuteArgs {
|
||||
contract: string
|
||||
messages?: WhiteListMerkleTreeInstance
|
||||
type: string | undefined
|
||||
timestamp: string
|
||||
members: string[] | WhitelistFlexMember[]
|
||||
limit: number
|
||||
admins: string[]
|
||||
}
|
||||
|
||||
export const dispatchExecute = async (args: DispatchExecuteArgs) => {
|
||||
const { messages } = args
|
||||
if (!messages) {
|
||||
throw new Error('cannot dispatch execute, messages is not defined')
|
||||
}
|
||||
switch (args.type) {
|
||||
case 'update_start_time': {
|
||||
return messages.updateStartTime(args.timestamp)
|
||||
}
|
||||
case 'update_end_time': {
|
||||
return messages.updateEndTime(args.timestamp)
|
||||
}
|
||||
case 'update_admins': {
|
||||
return messages.updateAdmins(args.admins)
|
||||
}
|
||||
case 'add_members': {
|
||||
return messages.addMembers(args.members)
|
||||
}
|
||||
case 'remove_members': {
|
||||
return messages.removeMembers(args.members as string[])
|
||||
}
|
||||
// case 'update_per_address_limit': {
|
||||
// return messages.updatePerAddressLimit(args.limit)
|
||||
// }
|
||||
case 'freeze': {
|
||||
return messages.freeze()
|
||||
}
|
||||
default: {
|
||||
throw new Error('unknown execute type')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const previewExecutePayload = (args: DispatchExecuteArgs) => {
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
const { messages } = useWhiteListMerkleTreeContract()
|
||||
const { contract } = args
|
||||
switch (args.type) {
|
||||
case 'update_start_time': {
|
||||
return messages(contract)?.updateStartTime(args.timestamp)
|
||||
}
|
||||
case 'update_end_time': {
|
||||
return messages(contract)?.updateEndTime(args.timestamp)
|
||||
}
|
||||
case 'update_admins': {
|
||||
return messages(contract)?.updateAdmins(args.admins)
|
||||
}
|
||||
case 'add_members': {
|
||||
return messages(contract)?.addMembers(args.members)
|
||||
}
|
||||
case 'remove_members': {
|
||||
return messages(contract)?.removeMembers(args.members as string[])
|
||||
}
|
||||
// case 'update_per_address_limit': {
|
||||
// return messages(contract)?.updatePerAddressLimit(args.limit)
|
||||
// }
|
||||
case 'freeze': {
|
||||
return messages(contract)?.freeze()
|
||||
}
|
||||
default: {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const isEitherType = <T extends ExecuteType>(type: unknown, arr: T[]): type is T => {
|
||||
return arr.some((val) => type === val)
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
import type { WhiteListMerkleTreeInstance } from '../contract'
|
||||
|
||||
export type WhitelistMerkleTreeQueryType = typeof WHITELIST_MERKLE_TREE_QUERY_TYPES[number]
|
||||
|
||||
export const WHITELIST_MERKLE_TREE_QUERY_TYPES = [
|
||||
'has_started',
|
||||
'has_ended',
|
||||
'is_active',
|
||||
'admin_list',
|
||||
'has_member',
|
||||
'config',
|
||||
'merkle_root',
|
||||
'merkle_tree_uri',
|
||||
] as const
|
||||
|
||||
export interface QueryListItem {
|
||||
id: WhitelistMerkleTreeQueryType
|
||||
name: string
|
||||
description?: string
|
||||
}
|
||||
|
||||
export const WHITELIST_MERKLE_TREE_QUERY_LIST: QueryListItem[] = [
|
||||
{ id: 'has_started', name: 'Has Started', description: 'Check if the whitelist minting has started' },
|
||||
{ id: 'has_ended', name: 'Has Ended', description: 'Check if the whitelist minting has ended' },
|
||||
{ id: 'is_active', name: 'Is Active', description: 'Check if the whitelist minting is active' },
|
||||
{ id: 'admin_list', name: 'Admin List', description: 'View the whitelist admin list' },
|
||||
{ id: 'has_member', name: 'Has Member', description: 'Check if a member is in the whitelist' },
|
||||
{ id: 'config', name: 'Config', description: 'View the whitelist configuration' },
|
||||
{ id: 'merkle_root', name: 'Merkle Root', description: 'View the whitelist merkle root' },
|
||||
{ id: 'merkle_tree_uri', name: 'Merkle Tree URI', description: 'View the whitelist merkle tree URI' },
|
||||
]
|
||||
|
||||
export interface DispatchQueryProps {
|
||||
messages: WhiteListMerkleTreeInstance | undefined
|
||||
type: WhitelistMerkleTreeQueryType
|
||||
address: string
|
||||
startAfter?: string
|
||||
limit?: number
|
||||
proofHashes?: string[]
|
||||
}
|
||||
|
||||
export const dispatchQuery = (props: DispatchQueryProps) => {
|
||||
const { messages, type, address, proofHashes } = props
|
||||
switch (type) {
|
||||
case 'has_started':
|
||||
return messages?.hasStarted()
|
||||
case 'has_ended':
|
||||
return messages?.hasEnded()
|
||||
case 'is_active':
|
||||
return messages?.isActive()
|
||||
case 'admin_list':
|
||||
return messages?.adminList()
|
||||
case 'has_member':
|
||||
return messages?.hasMember(address, proofHashes || [])
|
||||
case 'config':
|
||||
return messages?.config()
|
||||
case 'merkle_root':
|
||||
return messages?.merkleRoot()
|
||||
case 'merkle_tree_uri':
|
||||
return messages?.merkleTreeUri()
|
||||
|
||||
default: {
|
||||
throw new Error('unknown query type')
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { useWallet } from 'utils/wallet'
|
||||
|
||||
import type {
|
||||
InstantiateResponse,
|
||||
WhiteListMerkleTreeContract,
|
||||
WhiteListMerkleTreeInstance,
|
||||
WhiteListMerkleTreeMessages,
|
||||
} from './contract'
|
||||
import { WhiteListMerkleTree as initContract } from './contract'
|
||||
|
||||
export interface UseWhiteListMerkleTreeContractProps {
|
||||
instantiate: (
|
||||
codeId: number,
|
||||
initMsg: Record<string, unknown>,
|
||||
label: string,
|
||||
admin?: string,
|
||||
) => Promise<InstantiateResponse>
|
||||
|
||||
use: (customAddress?: string) => WhiteListMerkleTreeInstance | undefined
|
||||
|
||||
updateContractAddress: (contractAddress: string) => void
|
||||
|
||||
messages: (contractAddress: string) => WhiteListMerkleTreeMessages | undefined
|
||||
}
|
||||
|
||||
export function useWhiteListMerkleTreeContract(): UseWhiteListMerkleTreeContractProps {
|
||||
const wallet = useWallet()
|
||||
|
||||
const [address, setAddress] = useState<string>('')
|
||||
const [whiteListMerkleTree, setWhiteListMerkleTree] = useState<WhiteListMerkleTreeContract>()
|
||||
|
||||
useEffect(() => {
|
||||
setAddress(localStorage.getItem('contract_address') || '')
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (!wallet.isWalletConnected) {
|
||||
return
|
||||
}
|
||||
|
||||
const load = async () => {
|
||||
const client = await wallet.getSigningCosmWasmClient()
|
||||
const contract = initContract(client, wallet.address || '')
|
||||
setWhiteListMerkleTree(contract)
|
||||
}
|
||||
|
||||
load().catch(console.error)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [wallet.isWalletConnected, wallet.address])
|
||||
|
||||
const updateContractAddress = (contractAddress: string) => {
|
||||
setAddress(contractAddress)
|
||||
}
|
||||
|
||||
const instantiate = useCallback(
|
||||
(codeId: number, initMsg: Record<string, unknown>, label: string, admin?: string): Promise<InstantiateResponse> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!whiteListMerkleTree) {
|
||||
reject(new Error('Contract is not initialized.'))
|
||||
return
|
||||
}
|
||||
whiteListMerkleTree.instantiate(codeId, initMsg, label, admin).then(resolve).catch(reject)
|
||||
})
|
||||
},
|
||||
[whiteListMerkleTree],
|
||||
)
|
||||
|
||||
const use = useCallback(
|
||||
(customAddress = ''): WhiteListMerkleTreeInstance | undefined => {
|
||||
return whiteListMerkleTree?.use(address || customAddress)
|
||||
},
|
||||
[whiteListMerkleTree, address],
|
||||
)
|
||||
|
||||
const messages = useCallback(
|
||||
(customAddress = ''): WhiteListMerkleTreeMessages | undefined => {
|
||||
return whiteListMerkleTree?.messages(address || customAddress)
|
||||
},
|
||||
[whiteListMerkleTree, address],
|
||||
)
|
||||
|
||||
return {
|
||||
instantiate,
|
||||
use,
|
||||
updateContractAddress,
|
||||
messages,
|
||||
}
|
||||
}
|
||||
@@ -16,36 +16,98 @@ declare namespace NodeJS {
|
||||
|
||||
readonly NEXT_PUBLIC_SG721_CODE_ID: string
|
||||
readonly NEXT_PUBLIC_SG721_UPDATABLE_CODE_ID: string
|
||||
readonly NEXT_PUBLIC_STRDST_SG721_CODE_ID: string
|
||||
readonly NEXT_PUBLIC_BASE_FACTORY_SG721_CODE_ID: string
|
||||
readonly NEXT_PUBLIC_OPEN_EDITION_SG721_CODE_ID: string
|
||||
readonly NEXT_PUBLIC_OPEN_EDITION_SG721_UPDATABLE_CODE_ID: string
|
||||
readonly NEXT_PUBLIC_WHITELIST_CODE_ID: string
|
||||
readonly NEXT_PUBLIC_WHITELIST_FLEX_CODE_ID: string
|
||||
readonly NEXT_PUBLIC_WHITELIST_MERKLE_TREE_CODE_ID: string
|
||||
readonly NEXT_PUBLIC_VENDING_MINTER_CODE_ID: string
|
||||
readonly NEXT_PUBLIC_VENDING_MINTER_FLEX_CODE_ID: string
|
||||
readonly NEXT_PUBLIC_VENDING_FACTORY_ADDRESS: string
|
||||
readonly NEXT_PUBLIC_FEATURED_VENDING_FACTORY_ADDRESS: string
|
||||
readonly NEXT_PUBLIC_VENDING_FACTORY_UPDATABLE_ADDRESS: string
|
||||
readonly NEXT_PUBLIC_VENDING_FACTORY_FLEX_ADDRESS: string
|
||||
readonly NEXT_PUBLIC_VENDING_FACTORY_MERKLE_TREE_ADDRESS: string
|
||||
readonly NEXT_PUBLIC_FEATURED_VENDING_FACTORY_MERKLE_TREE_ADDRESS: string
|
||||
readonly NEXT_PUBLIC_FEATURED_VENDING_FACTORY_FLEX_ADDRESS: string
|
||||
readonly NEXT_PUBLIC_VENDING_FACTORY_UPDATABLE_FLEX_ADDRESS: string
|
||||
readonly NEXT_PUBLIC_VENDING_IBC_ATOM_FACTORY_ADDRESS: string
|
||||
readonly NEXT_PUBLIC_VENDING_IBC_ATOM_UPDATABLE_FACTORY_ADDRESS: string
|
||||
readonly NEXT_PUBLIC_VENDING_IBC_USDC_FACTORY_ADDRESS: string
|
||||
readonly NEXT_PUBLIC_FEATURED_VENDING_IBC_USDC_FACTORY_ADDRESS: string
|
||||
readonly NEXT_PUBLIC_VENDING_IBC_USDC_UPDATABLE_FACTORY_ADDRESS: string
|
||||
readonly NEXT_PUBLIC_VENDING_IBC_TIA_FACTORY_ADDRESS: string
|
||||
readonly NEXT_PUBLIC_FEATURED_VENDING_IBC_TIA_FACTORY_ADDRESS: string
|
||||
readonly NEXT_PUBLIC_VENDING_IBC_TIA_UPDATABLE_FACTORY_ADDRESS: string
|
||||
readonly NEXT_PUBLIC_VENDING_IBC_NBTC_FACTORY_ADDRESS: string
|
||||
readonly NEXT_PUBLIC_VENDING_IBC_NBTC_UPDATABLE_FACTORY_ADDRESS: string
|
||||
readonly NEXT_PUBLIC_VENDING_IBC_USK_FACTORY_ADDRESS: string
|
||||
readonly NEXT_PUBLIC_VENDING_IBC_USK_UPDATABLE_FACTORY_ADDRESS: string
|
||||
readonly NEXT_PUBLIC_VENDING_IBC_KUJI_FACTORY_ADDRESS: string
|
||||
readonly NEXT_PUBLIC_VENDING_IBC_KUJI_UPDATABLE_FACTORY_ADDRESS: string
|
||||
readonly NEXT_PUBLIC_VENDING_IBC_HUAHUA_FACTORY_ADDRESS: string
|
||||
readonly NEXT_PUBLIC_VENDING_IBC_HUAHUA_UPDATABLE_FACTORY_ADDRESS: string
|
||||
readonly NEXT_PUBLIC_VENDING_IBC_CRBRUS_FACTORY_ADDRESS: string
|
||||
readonly NEXT_PUBLIC_VENDING_IBC_CRBRUS_UPDATABLE_FACTORY_ADDRESS: string
|
||||
readonly NEXT_PUBLIC_VENDING_IBC_ATOM_FACTORY_FLEX_ADDRESS: string
|
||||
readonly NEXT_PUBLIC_VENDING_IBC_ATOM_UPDATABLE_FACTORY_FLEX_ADDRESS: string
|
||||
readonly NEXT_PUBLIC_VENDING_IBC_USDC_FACTORY_FLEX_ADDRESS: string
|
||||
readonly NEXT_PUBLIC_FEATURED_VENDING_IBC_USDC_FACTORY_FLEX_ADDRESS: string
|
||||
readonly NEXT_PUBLIC_VENDING_IBC_USDC_UPDATABLE_FACTORY_FLEX_ADDRESS: string
|
||||
readonly NEXT_PUBLIC_VENDING_IBC_TIA_FACTORY_FLEX_ADDRESS: string
|
||||
readonly NEXT_PUBLIC_VENDING_IBC_TIA_FACTORY_MERKLE_TREE_ADDRESS: string
|
||||
readonly NEXT_PUBLIC_FEATURED_VENDING_IBC_TIA_FACTORY_MERKLE_TREE_ADDRESS: string
|
||||
readonly NEXT_PUBLIC_FEATURED_VENDING_IBC_TIA_FACTORY_FLEX_ADDRESS: string
|
||||
readonly NEXT_PUBLIC_VENDING_IBC_TIA_UPDATABLE_FACTORY_FLEX_ADDRESS: string
|
||||
readonly NEXT_PUBLIC_VENDING_IBC_NBTC_FACTORY_FLEX_ADDRESS: string
|
||||
readonly NEXT_PUBLIC_VENDING_IBC_NBTC_UPDATABLE_FACTORY_FLEX_ADDRESS: string
|
||||
readonly NEXT_PUBLIC_VENDING_IBC_USK_FACTORY_FLEX_ADDRESS: string
|
||||
readonly NEXT_PUBLIC_VENDING_IBC_USK_UPDATABLE_FACTORY_FLEX_ADDRESS: string
|
||||
readonly NEXT_PUBLIC_VENDING_IBC_KUJI_FACTORY_FLEX_ADDRESS: string
|
||||
readonly NEXT_PUBLIC_VENDING_IBC_KUJI_UPDATABLE_FACTORY_FLEX_ADDRESS: string
|
||||
readonly NEXT_PUBLIC_VENDING_IBC_HUAHUA_FACTORY_FLEX_ADDRESS: string
|
||||
readonly NEXT_PUBLIC_VENDING_IBC_HUAHUA_UPDATABLE_FACTORY_FLEX_ADDRESS: string
|
||||
readonly NEXT_PUBLIC_VENDING_IBC_CRBRUS_FACTORY_FLEX_ADDRESS: string
|
||||
readonly NEXT_PUBLIC_VENDING_IBC_CRBRUS_UPDATABLE_FACTORY_FLEX_ADDRESS: string
|
||||
readonly NEXT_PUBLIC_VENDING_NATIVE_STARDUST_FACTORY_ADDRESS: string
|
||||
readonly NEXT_PUBLIC_VENDING_NATIVE_STARDUST_UPDATABLE_FACTORY_ADDRESS: string
|
||||
readonly NEXT_PUBLIC_VENDING_NATIVE_STRDST_FLEX_FACTORY_ADDRESS: string
|
||||
readonly NEXT_PUBLIC_VENDING_NATIVE_BRNCH_FACTORY_ADDRESS: string
|
||||
readonly NEXT_PUBLIC_VENDING_NATIVE_BRNCH_UPDATABLE_FACTORY_ADDRESS: string
|
||||
readonly NEXT_PUBLIC_VENDING_NATIVE_BRNCH_FLEX_FACTORY_ADDRESS: string
|
||||
readonly NEXT_PUBLIC_OPEN_EDITION_FACTORY_ADDRESS: string
|
||||
readonly NEXT_PUBLIC_OPEN_EDITION_FACTORY_FLEX_ADDRESS: string
|
||||
readonly NEXT_PUBLIC_OPEN_EDITION_UPDATABLE_FACTORY_ADDRESS: string
|
||||
readonly NEXT_PUBLIC_OPEN_EDITION_IBC_ATOM_FACTORY_ADDRESS: string
|
||||
readonly NEXT_PUBLIC_OPEN_EDITION_IBC_ATOM_FACTORY_FLEX_ADDRESS: string
|
||||
readonly NEXT_PUBLIC_OPEN_EDITION_UPDATABLE_IBC_ATOM_FACTORY_ADDRESS: string
|
||||
readonly NEXT_PUBLIC_OPEN_EDITION_IBC_USDC_FACTORY_ADDRESS: string
|
||||
readonly NEXT_PUBLIC_OPEN_EDITION_IBC_USDC_FACTORY_FLEX_ADDRESS: string
|
||||
readonly NEXT_PUBLIC_OPEN_EDITION_UPDATABLE_IBC_USDC_FACTORY_ADDRESS: string
|
||||
readonly NEXT_PUBLIC_OPEN_EDITION_IBC_TIA_FACTORY_ADDRESS: string
|
||||
readonly NEXT_PUBLIC_OPEN_EDITION_IBC_TIA_FACTORY_FLEX_ADDRESS: string
|
||||
readonly NEXT_PUBLIC_OPEN_EDITION_UPDATABLE_IBC_TIA_FACTORY_ADDRESS: string
|
||||
readonly NEXT_PUBLIC_OPEN_EDITION_IBC_NBTC_FACTORY_ADDRESS: string
|
||||
readonly NEXT_PUBLIC_OPEN_EDITION_UPDATABLE_IBC_NBTC_FACTORY_ADDRESS: string
|
||||
readonly NEXT_PUBLIC_OPEN_EDITION_IBC_FRNZ_FACTORY_ADDRESS: string
|
||||
readonly NEXT_PUBLIC_OPEN_EDITION_UPDATABLE_IBC_FRNZ_FACTORY_ADDRESS: string
|
||||
readonly NEXT_PUBLIC_OPEN_EDITION_IBC_USK_FACTORY_ADDRESS: string
|
||||
readonly NEXT_PUBLIC_OPEN_EDITION_UPDATABLE_IBC_USK_FACTORY_ADDRESS: string
|
||||
readonly NEXT_PUBLIC_OPEN_EDITION_IBC_KUJI_FACTORY_ADDRESS: string
|
||||
readonly NEXT_PUBLIC_OPEN_EDITION_UPDATABLE_IBC_KUJI_FACTORY_ADDRESS: string
|
||||
readonly NEXT_PUBLIC_OPEN_EDITION_IBC_HUAHUA_FACTORY_ADDRESS: string
|
||||
readonly NEXT_PUBLIC_OPEN_EDITION_IBC_CRBRUS_FACTORY_ADDRESS: string
|
||||
readonly NEXT_PUBLIC_OPEN_EDITION_UPDATABLE_IBC_HUAHUA_FACTORY_ADDRESS: string
|
||||
readonly NEXT_PUBLIC_OPEN_EDITION_NATIVE_STRDST_FACTORY_ADDRESS: string
|
||||
readonly NEXT_PUBLIC_OPEN_EDITION_NATIVE_BRNCH_FACTORY_ADDRESS: string
|
||||
readonly NEXT_PUBLIC_OPEN_EDITION_MINTER_CODE_ID: string
|
||||
readonly NEXT_PUBLIC_BASE_FACTORY_ADDRESS: string
|
||||
readonly NEXT_PUBLIC_BASE_FACTORY_UPDATABLE_ADDRESS: string
|
||||
readonly NEXT_PUBLIC_SG721_NAME_ADDRESS: string
|
||||
readonly NEXT_PUBLIC_ROYALTY_REGISTRY_ADDRESS: string
|
||||
readonly NEXT_PUBLIC_INFINITY_SWAP_PROTOCOL_ADDRESS: string
|
||||
readonly NEXT_PUBLIC_BASE_MINTER_CODE_ID: string
|
||||
readonly NEXT_PUBLIC_BADGE_HUB_CODE_ID: string
|
||||
readonly NEXT_PUBLIC_BADGE_HUB_ADDRESS: string
|
||||
@@ -61,12 +123,15 @@ declare namespace NodeJS {
|
||||
readonly NEXT_PUBLIC_STARGAZE_WEBSITE_URL: string
|
||||
readonly NEXT_PUBLIC_WEBSITE_URL: string
|
||||
readonly NEXT_PUBLIC_SYNC_COLLECTIONS_API_URL: string
|
||||
readonly NEXT_PUBLIC_WHITELIST_MERKLE_TREE_API_URL: string
|
||||
readonly NEXT_PUBLIC_NFT_STORAGE_DEFAULT_API_KEY: string
|
||||
|
||||
readonly NEXT_PUBLIC_MEILISEARCH_HOST: string
|
||||
readonly NEXT_PUBLIC_MEILISEARCH_API_KEY: string
|
||||
}
|
||||
}
|
||||
|
||||
type KeplrWindow = import('@keplr-wallet/types/src/window').Window
|
||||
|
||||
declare interface Window extends KeplrWindow {
|
||||
declare interface Window {
|
||||
confetti?: (obj: any) => void
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
/* eslint-disable eslint-comments/disable-enable-pair */
|
||||
/* eslint-disable @typescript-eslint/no-inferrable-types */
|
||||
/* eslint-disable @typescript-eslint/no-shadow */
|
||||
/* eslint-disable no-return-await */
|
||||
/* eslint-disable import/no-default-export */
|
||||
import { useQuery } from 'react-query'
|
||||
import { MEILISEARCH_API_KEY, MEILISEARCH_HOST } from 'utils/constants'
|
||||
|
||||
export default function useSearch(query: string, includeUids: string[] = [], limit: number = 4) {
|
||||
return useQuery<SearchResult[] | null>(
|
||||
['globalSearch', query],
|
||||
async () => await getSearchResults(query, includeUids, limit),
|
||||
)
|
||||
}
|
||||
|
||||
async function getSearchResults(query: string, includeUids: string[], limit: number) {
|
||||
let queries = [
|
||||
{
|
||||
indexUid: 'collections',
|
||||
sort: query === '' ? ['score_1d:desc'] : [],
|
||||
q: query ?? '',
|
||||
limit,
|
||||
},
|
||||
{
|
||||
indexUid: 'names',
|
||||
q: query ?? '',
|
||||
limit,
|
||||
},
|
||||
]
|
||||
if (includeUids.length) {
|
||||
queries = queries.filter((query) => includeUids.includes(query.indexUid))
|
||||
}
|
||||
|
||||
try {
|
||||
const response = (await fetch(`${MEILISEARCH_HOST}/multi-search`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: `Bearer ${MEILISEARCH_API_KEY}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
queries,
|
||||
}),
|
||||
}).then((res) => res.json())) as SearchResponse
|
||||
|
||||
return response.results
|
||||
} catch (e) {
|
||||
console.log('error', e)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
export interface SearchResult {
|
||||
indexUid: string
|
||||
hits: Hit[]
|
||||
estimatedTotalHits: number
|
||||
limit: number
|
||||
offset: number
|
||||
processingTimeMs: number
|
||||
query: string
|
||||
}
|
||||
|
||||
export interface SearchResponse {
|
||||
results: SearchResult[]
|
||||
}
|
||||
|
||||
export interface Hit {
|
||||
created_by: string
|
||||
description: string
|
||||
id: string
|
||||
collection_uri: string
|
||||
image_url: string
|
||||
minting: boolean
|
||||
minter: string
|
||||
name: string
|
||||
thumbnail_url: string
|
||||
tokens_count: number
|
||||
type: string
|
||||
// name
|
||||
address: string
|
||||
profile_picture: string
|
||||
profile_picture_thumbnail: string
|
||||
twitter_acct: string
|
||||
verified: boolean
|
||||
}
|
||||
Generated
+18257
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user