Compare commits
507 Commits
burn-to-mi
...
develop
| Author | SHA1 | Date | |
|---|---|---|---|
| 7490e24a1d | |||
| da62b2fbf3 | |||
| 8a08acf250 | |||
| 6c6ee08024 | |||
| e6f56c0509 | |||
| 50489c00cd | |||
| ac5a7f2cb6 | |||
| 04397853d9 | |||
|
|
2ab4cb9349 | ||
|
|
ea7dea6a2a | ||
|
|
ed9105684c | ||
|
|
3511a57eb9 | ||
|
|
28741049ee | ||
|
|
1003fcf4ae | ||
|
|
a96c80462d | ||
|
|
d889e7e04b | ||
|
|
be93200f53 | ||
|
|
3bbed658a7 | ||
|
|
fa1475f109 | ||
|
|
d1b5041312 | ||
|
|
0958b0db94 | ||
|
|
0354131ad1 | ||
|
|
f7880540ad | ||
|
|
123e07362d | ||
|
|
aaf7b82b43 | ||
|
|
467c7a4cfb | ||
|
|
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 |
88
.env.example
88
.env.example
@ -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= "..."
|
||||
|
||||
45
.github/workflows/publish.yaml
vendored
Normal file
45
.github/workflows/publish.yaml
vendored
Normal file
@ -0,0 +1,45 @@
|
||||
name: Publish ApplicationRecord to Registry
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- '*'
|
||||
|
||||
env:
|
||||
CERC_REGISTRY_USER_KEY: ${{ secrets.CICD_LACONIC_USER_KEY }}
|
||||
CERC_REGISTRY_BOND_ID: ${{ secrets.CICD_LACONIC_BOND_ID }}
|
||||
|
||||
jobs:
|
||||
cns_publish:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: "Clone project repository"
|
||||
uses: actions/checkout@v3
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18 # though you need version 14 with geojson
|
||||
# - name: "Install exiftool"
|
||||
# run: |
|
||||
# apt-get update -y
|
||||
# apt-get upgrade -y
|
||||
# apt-get install exiftool -y
|
||||
#- name: "Exiftool Version"
|
||||
# run: |
|
||||
# exiftool -ver
|
||||
- name: "Install Yarn"
|
||||
run: npm install -g yarn
|
||||
- name: "Install registry CLI"
|
||||
run: |
|
||||
npm config set @cerc-io:registry https://git.vdb.to/api/packages/cerc-io/npm/
|
||||
yarn global add @cerc-io/laconic-registry-cli
|
||||
- name: "Install jq"
|
||||
uses: dcarbone/install-jq-action@v2.1.0
|
||||
- name: "Publish App Record"
|
||||
run: scripts/publish-app-record.sh
|
||||
#- name: "Create Metadata Record"
|
||||
# run: scripts/create-metadata-record.sh
|
||||
- name: "Request Deployment"
|
||||
run: scripts/request-app-deployment.sh
|
||||
@ -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">
|
||||
|
||||
57
components/CollectionsTable.tsx
Normal file
57
components/CollectionsTable.tsx
Normal file
@ -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)"
|
||||
|
||||
79
components/Fieldset.tsx
Normal file
79
components/Fieldset.tsx
Normal file
@ -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>
|
||||
)
|
||||
}
|
||||
173
components/Input.tsx
Normal file
173
components/Input.tsx
Normal file
@ -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.')
|
||||
}
|
||||
|
||||
63
components/SelectCollection.tsx
Normal file
63
components/SelectCollection.tsx
Normal file
@ -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>
|
||||
)
|
||||
}
|
||||
74
components/SettingsModal.tsx
Normal file
74
components/SettingsModal.tsx
Normal file
@ -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>
|
||||
)
|
||||
}
|
||||
@ -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)} />
|
||||
)}
|
||||
|
||||
37
components/TrailingSelect.tsx
Normal file
37
components/TrailingSelect.tsx
Normal file
@ -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>
|
||||
)
|
||||
}
|
||||
@ -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>
|
||||
|
||||
48
components/WalletProvider.tsx
Normal file
48
components/WalletProvider.tsx
Normal file
@ -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,6 +22,9 @@ interface MintingDetailsProps {
|
||||
uploadMethod: UploadMethod
|
||||
minimumMintPrice: number
|
||||
mintingTokenFromFactory?: TokenInfo
|
||||
importedMintingDetails?: MintingDetailsDataProps
|
||||
isPresale: boolean
|
||||
whitelistStartDate?: string
|
||||
}
|
||||
|
||||
export interface MintingDetailsDataProps {
|
||||
@ -37,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)
|
||||
|
||||
@ -101,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
|
||||
}, [
|
||||
@ -113,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">
|
||||
@ -122,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}
|
||||
@ -139,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) }
|
||||
}
|
||||
92
components/contracts/royaltyRegistry/ExecuteCombobox.tsx
Normal file
92
components/contracts/royaltyRegistry/ExecuteCombobox.tsx
Normal file
@ -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(
|
||||
|
||||
33
components/forms/DenomUnits.hooks.ts
Normal file
33
components/forms/DenomUnits.hooks.ts
Normal file
@ -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 }
|
||||
}
|
||||
106
components/forms/DenomUnits.tsx
Normal file
106
components/forms/DenomUnits.tsx
Normal file
@ -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">
|
||||
|
||||
528
components/openEdition/WhitelistDetails.tsx
Normal file
528
components/openEdition/WhitelistDetails.tsx
Normal file
@ -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
config/authz.ts
Normal file
324
config/authz.ts
Normal file
@ -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'],
|
||||
})
|
||||
@ -6,6 +6,6 @@ export const meta = {
|
||||
domain: 'stargaze.tools',
|
||||
url: faviconsJson.developerURL,
|
||||
twitter: {
|
||||
username: '@stargazestudio',
|
||||
username: '@StargazeZone',
|
||||
},
|
||||
}
|
||||
|
||||
632
config/minter.ts
632
config/minter.ts
@ -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: {
|
||||
|
||||
103
config/token.ts
103
config/token.ts
@ -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',
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
11
contexts/globalSettings.ts
Normal file
11
contexts/globalSettings.ts
Normal file
@ -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,4 +1,4 @@
|
||||
import create from 'zustand'
|
||||
import { create } from 'zustand'
|
||||
|
||||
export interface LogItem {
|
||||
id: string
|
||||
|
||||
@ -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],
|
||||
|
||||
407
contracts/royaltyRegistry/contract.ts
Normal file
407
contracts/royaltyRegistry/contract.ts
Normal file
@ -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 }
|
||||
}
|
||||
2
contracts/royaltyRegistry/index.ts
Normal file
2
contracts/royaltyRegistry/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './contract'
|
||||
export * from './useContract'
|
||||
144
contracts/royaltyRegistry/messages/execute.ts
Normal file
144
contracts/royaltyRegistry/messages/execute.ts
Normal file
@ -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)
|
||||
}
|
||||
64
contracts/royaltyRegistry/messages/query.ts
Normal file
64
contracts/royaltyRegistry/messages/query.ts
Normal file
@ -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')
|
||||
}
|
||||
}
|
||||
}
|
||||
112
contracts/royaltyRegistry/useContract.ts
Normal file
112
contracts/royaltyRegistry/useContract.ts
Normal file
@ -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)
|
||||
|
||||
375
contracts/whitelistMerkleTree/contract.ts
Normal file
375
contracts/whitelistMerkleTree/contract.ts
Normal file
@ -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 }
|
||||
}
|
||||
2
contracts/whitelistMerkleTree/index.ts
Normal file
2
contracts/whitelistMerkleTree/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './contract'
|
||||
export * from './useContract'
|
||||
144
contracts/whitelistMerkleTree/messages/execute.ts
Normal file
144
contracts/whitelistMerkleTree/messages/execute.ts
Normal file
@ -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)
|
||||
}
|
||||
66
contracts/whitelistMerkleTree/messages/query.ts
Normal file
66
contracts/whitelistMerkleTree/messages/query.ts
Normal file
@ -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')
|
||||
}
|
||||
}
|
||||
}
|
||||
89
contracts/whitelistMerkleTree/useContract.ts
Normal file
89
contracts/whitelistMerkleTree/useContract.ts
Normal file
@ -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,
|
||||
}
|
||||
}
|
||||
71
env.d.ts
vendored
71
env.d.ts
vendored
@ -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
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user