Compare commits

...

129 Commits

Author SHA1 Message Date
7490e24a1d Update scripts/request-app-deployment.sh
All checks were successful
Publish ApplicationRecord to Registry / cns_publish (push) Successful in 1m26s
2024-06-25 16:06:14 +00:00
da62b2fbf3 Update scripts/request-app-deployment.sh
Some checks failed
Publish ApplicationRecord to Registry / cns_publish (push) Has been cancelled
2024-06-25 16:05:59 +00:00
8a08acf250 Update scripts/request-app-deployment.sh
All checks were successful
Publish ApplicationRecord to Registry / cns_publish (push) Successful in 1m16s
2024-06-25 16:00:19 +00:00
6c6ee08024 increase CERC_MAX_GENERATE_TIME
All checks were successful
Publish ApplicationRecord to Registry / cns_publish (push) Successful in 1m19s
2024-06-25 15:35:11 +00:00
e6f56c0509 Add .env
All checks were successful
Publish ApplicationRecord to Registry / cns_publish (push) Successful in 1m20s
2024-06-25 15:33:18 +00:00
50489c00cd ffs
All checks were successful
Publish ApplicationRecord to Registry / cns_publish (push) Successful in 1m20s
2024-06-25 15:10:05 +00:00
ac5a7f2cb6 Update package.json
All checks were successful
Publish ApplicationRecord to Registry / cns_publish (push) Successful in 1m24s
2024-06-25 15:07:12 +00:00
04397853d9 set name
All checks were successful
Publish ApplicationRecord to Registry / cns_publish (push) Successful in 1m23s
2024-06-25 15:01:36 +00:00
zramsay
2ab4cb9349 test
Some checks failed
Publish ApplicationRecord to Registry / cns_publish (push) Failing after 1m3s
2024-06-25 10:36:00 -04:00
Serkan Reis
ea7dea6a2a
Merge pull request #391 from public-awesome/collection-creation-summary
Update parser for createVendingMinter transaction result
2024-06-17 22:18:16 +03:00
Serkan Reis
ed9105684c Update parser for createVendingMinter transaction result 2024-06-17 22:17:04 +03:00
Serkan Reis
3511a57eb9 Use hardcoded fees for editing badges 2024-06-11 20:51:55 +03:00
Serkan Reis
28741049ee Remove testnet checks for contract upload 2024-06-09 16:05:30 +03:00
Serkan Reis
1003fcf4ae Contract Upload UI update 2024-06-09 16:02:53 +03:00
Serkan Reis
a96c80462d
Merge pull request #386 from public-awesome/contract-upload-authorization
Enable contract uploads with authorization
2024-06-09 15:52:19 +03:00
Serkan Reis
d889e7e04b Enable contract uploads with authorization 2024-06-09 15:45:28 +03:00
Serkan Reis
be93200f53
Merge pull request #384 from public-awesome/handle-non-stars-creation-fees
Handle non-STARS collection creation fees
2024-05-28 13:39:26 +03:00
Serkan Reis
3bbed658a7 Handle non-STARS creation fees for open/limited edition minter 2024-05-22 21:02:12 +03:00
Serkan Reis
fa1475f109 Handle non-STARS creation fees for vending & base minter 2024-05-22 20:25:41 +03:00
Serkan Reis
d1b5041312
Merge pull request #382 from public-awesome/oe-wl-on-collection-list
Add OE/LE whitelist info to the collection list
2024-05-09 16:34:53 +03:00
Serkan Reis
0958b0db94 Add WL info for OE collections on the collection list 2024-05-09 16:28:10 +03:00
Serkan Reis
0354131ad1
Merge pull request #380 from public-awesome/cosmos-kit-version-update
Update cosmos-kit package versions
2024-05-08 20:04:46 +03:00
Serkan Reis
f7880540ad Update cosmjs package versions 2024-05-08 19:57:36 +03:00
Serkan Reis
123e07362d Update versions for cosmos-kit packages 2024-05-08 19:22:23 +03:00
Serkan Reis
aaf7b82b43
Merge pull request #378 from public-awesome/cancel-auction
Add cancel auction UI
2024-05-06 17:06:25 +03:00
Serkan Reis
467c7a4cfb Add cancel auction UI 2024-05-06 17:05:40 +03:00
Serkan Reis
5e963cf615
Merge pull request #375 from public-awesome/token-factory-update
Address token factory issues
2024-05-01 17:46:18 +03:00
Serkan Reis
41d71a6765 Address token factory issues 2024-05-01 17:38:34 +03:00
Serkan Reis
22d58dbe45
Merge pull request #374 from public-awesome/main
Sync main > dev
2024-04-30 13:37:16 +03:00
Serkan Reis
471ff43fcf
Merge pull request #373 from public-awesome/enable-oe-wl-compatibility-on-mainnet
Enable WL use for Open/Limited Edition collections on mainnet
2024-04-30 13:36:02 +03:00
Serkan Reis
027c4c6821 Enable WL use for Open/Limited Edition collections on mainnet 2024-04-26 13:27:48 +03:00
Serkan Reis
f0b94422b1
Merge pull request #372 from public-awesome/skip-whitelist-checks
Skip whitelist checks on mainnet for OE collections
2024-04-16 19:24:16 +03:00
Serkan Reis
bb8b3e1791 Skip wl checks on mainnet for OE collections 2024-04-16 19:23:02 +03:00
Serkan Reis
42707adc0b
Merge pull request #371 from public-awesome/disable-oe-whitelists
Disable whitelist compatibility for Open Edition collections on mainnet
2024-04-16 19:03:41 +03:00
Serkan Reis
35bed9d6aa Disable whitelist compatibility for OEs on mainnet 2024-04-16 18:51:24 +03:00
Serkan Reis
93f98bcec6
Merge pull request #370 from public-awesome/main
Sync main > dev
2024-04-16 18:36:44 +03:00
Serkan Reis
83835dfba2
Merge branch 'develop' into main 2024-04-16 18:34:30 +03:00
Serkan Reis
d85e19b770
Merge pull request #369 from public-awesome/update-mint-price-denom-list
Update mint price denom list
2024-04-16 18:29:41 +03:00
Serkan Reis
1abb2f82df Update default sg721 code ID for base minter creation 2024-04-16 18:22:53 +03:00
Serkan Reis
b44e4512a5 Remove HUAHUA from mint price denom list 2024-04-16 18:21:26 +03:00
Serkan Reis
a226d8b341
Merge pull request #368 from public-awesome/oe-whitelist-compatibility
Whitelist compatibility for OE collections
2024-04-15 13:55:05 +03:00
Serkan Reis
8a02a7f80d Enable setting both time & token count limit for OE collections 2024-04-15 13:48:44 +03:00
Serkan Reis
122c61055f Select OE factory wrt existing whitelist type 2024-04-15 09:39:19 +03:00
Serkan Reis
81880aecdd Display WL address upon OE collection creation 2024-04-14 21:08:31 +03:00
Serkan Reis
b38562d9fd Improve factory selection logic for OE collections 2024-04-14 19:53:44 +03:00
Serkan Reis
0eb94e4ee8 Add OE whitelist compatibility 2024-04-14 17:08:49 +03:00
Serkan Reis
3a150a50f3
Merge pull request #367 from public-awesome/develop
Sync dev > main
2024-04-10 22:24:14 +03:00
Serkan Reis
03c008d0fa Add discount price update warning 2024-04-10 22:22:30 +03:00
Serkan Reis
6487646f2c
Merge pull request #365 from public-awesome/develop
Sync dev > main
2024-04-10 19:00:42 +03:00
Serkan Reis
b1094f5231
Merge pull request #364 from public-awesome/wl-merkletree-dashboard-update
Add whitelist merkle tree support to whitelist contract dashboard
2024-04-10 18:59:47 +03:00
Serkan Reis
97bb60b3ff Add option to instantiate whitelist merkletree 2024-04-10 18:48:56 +03:00
Serkan Reis
9654ec845e Update execute action list for whitelist merkle tree 2024-04-10 16:06:56 +03:00
Serkan Reis
41e8a3961a Fetch proof hashes for hasMember query 2024-04-10 14:39:00 +03:00
Serkan Reis
43fd0d7848 Update queries for whitelist merkle tree 2024-04-10 13:25:29 +03:00
Serkan Reis
cc16f7ceb1
Merge pull request #363 from public-awesome/develop
Sync dev > main
2024-04-08 09:29:00 +03:00
Serkan Reis
cc58de90a4 Update interface for CollectionInfo 2024-04-08 09:18:22 +03:00
Serkan Reis
27f8510335
Merge pull request #362 from public-awesome/develop
Sync dev > main
2024-04-08 09:01:25 +03:00
Serkan Reis
0fc60f6c05
Merge pull request #361 from public-awesome/transfer-ownership
Allow designating a new creator address when updating collection info
2024-04-08 09:00:39 +03:00
Serkan Reis
c59531f87e Allow designating a new creator address when updating collection info 2024-04-08 08:59:38 +03:00
Serkan Reis
6ddd780338
Merge pull request #360 from public-awesome/develop
Sync dev > main
2024-04-04 13:14:07 +03:00
Serkan Reis
57e36b6dbd
Merge pull request #359 from public-awesome/featured-vending-minter-wl-merkletree
Add featured vending-minter-wl-merkletree option
2024-04-04 13:13:36 +03:00
Serkan Reis
1a866db888 Add featured vending-minter-wl-merkletree option 2024-04-04 13:12:19 +03:00
Serkan Reis
7e97f5d393
Merge pull request #358 from public-awesome/develop
Sync dev > main
2024-04-04 12:10:55 +03:00
Serkan Reis
e26068085d
Merge pull request #357 from public-awesome/wl-merkletree-updates
Display spinner during merkle root hash acquisiton
2024-04-04 12:10:24 +03:00
Serkan Reis
1ccc55a3b2 Temporarily disable importing members for whitelist-merkletree 2024-04-04 12:07:59 +03:00
Serkan Reis
99f7b25f10 Add merkle root fetch spinner 2024-04-04 11:56:09 +03:00
Serkan Reis
3308cfdcf2
Merge pull request #356 from public-awesome/develop
Sync dev > main
2024-04-03 22:43:22 +03:00
Serkan Reis
5e9fdc1cf1
Merge pull request #355 from public-awesome/creation-wl-merkletree-update
Include creation fee for whitelist-merkletree
2024-04-03 22:42:51 +03:00
Serkan Reis
39847d1ab9 Include creation fee for whitelist-merkletree 2024-04-03 22:41:28 +03:00
Serkan Reis
29cd89f6a5
Merge pull request #354 from public-awesome/develop
Sync dev > main
2024-04-02 23:24:14 +03:00
Serkan Reis
6466945e28
Merge pull request #353 from public-awesome/merkle-wl-update
Add whitelist-merkletree option during collection creation
2024-04-02 23:22:58 +03:00
Serkan Reis
db9ffb0899 Revert initial OE whitelist compatibility changes 2024-04-02 23:15:17 +03:00
Serkan Reis
a295dd5d4a Update collection creation logic 2024-04-02 22:58:51 +03:00
Serkan Reis
85ac7a4f71 Add env variables for whitelist-merkletree 2024-04-02 17:02:07 +03:00
Serkan Reis
ee46fa64d3 Include whitelist-merkletree option in whitelist details 2024-04-02 14:18:37 +03:00
Serkan Reis
f5f14fb330 Add whitelist-merkletree contract helpers 2024-04-02 13:04:25 +03:00
Serkan Reis
fd65316d1f Add merkleTree generation helpers 2024-04-02 12:59:43 +03:00
Serkan Reis
004b102540 Test init WL compatible open-edition collection 2024-04-01 13:52:10 +03:00
Serkan Reis
9095c5c06c
Merge pull request #352 from public-awesome/main
Sync main > dev
2024-03-21 16:05:41 +02:00
Serkan Reis
55e46cc830
Merge pull request #351 from public-awesome/update-creation-summary
Update collection creation summary logic
2024-03-21 16:04:43 +02:00
Serkan Reis
d07ad7db04 Update collection creation summary logic 2024-03-21 16:58:31 +03:00
Serkan Reis
8f0a0e84aa
Merge pull request #350 from public-awesome/develop
Sync dev > main
2024-03-19 21:40:51 +02:00
Serkan Reis
4e6db44105
Merge pull request #349 from public-awesome/update-mint-price-denoms
Include TIA among mint price denom options
2024-03-19 21:40:09 +02:00
Serkan Reis
7b0f1f6176 Update denom list to match the right collection code id during creation 2024-03-19 11:52:39 +03:00
Serkan Reis
fe8bfd14bc Update minter list 2024-03-19 07:50:56 +03:00
Serkan Reis
1c7c0a682d Update env variables 2024-03-19 07:36:00 +03:00
Serkan Reis
b58549b1a2 Add TIA to token list 2024-03-19 06:12:12 +03:00
Serkan Reis
6cdd2a24ac
Merge pull request #348 from public-awesome/develop
Sync dev> main
2024-03-17 16:45:12 +02:00
Serkan Reis
437ac5b6f5
Merge pull request #347 from public-awesome/include-infinity-pools-for-snapshots
Add option to include tokens in Infinity Pools for snapshots
2024-03-17 16:44:24 +02:00
Serkan Reis
afef36375b Add option to include tokens in Infinity Pools for snapshots 2024-03-17 17:42:11 +03:00
Serkan Reis
0231692eb1
Merge pull request #346 from public-awesome/develop
Sync dev > main
2024-03-01 10:25:27 +02:00
Serkan Reis
e0d6aaee4d
Merge pull request #345 from public-awesome/remove-collection-offer
Add remove collection offer page
2024-03-01 10:24:37 +02:00
Serkan Reis
c4027859c0 Update testnet marketplace contract address 2024-03-01 11:23:25 +03:00
Serkan Reis
c12fd51525 Add remove collection offer page 2024-03-01 11:18:38 +03:00
Serkan Reis
f1aeeb2167
Merge pull request #344 from public-awesome/develop
Sync dev > main
2024-02-20 22:06:59 +02:00
Serkan Reis
103921d946
Merge pull request #343 from public-awesome/update-collection-info-update
Add update collection info disclaimer
2024-02-20 22:06:04 +02:00
Serkan Reis
b4fcc63de6 Add update collection info disclaimer 2024-02-20 23:05:06 +03:00
Serkan Reis
b3dea5e757
Merge pull request #342 from public-awesome/limited-edition-update
Limited edition collection update
2024-02-14 22:37:02 +02:00
Serkan Reis
e43ec78acc Revert creation summary changes for mainnet 2024-02-14 23:27:36 +03:00
Serkan Reis
4c312d0ad8
Merge pull request #339 from public-awesome/main
Sync main > dev
2024-02-12 22:16:23 +02:00
Serkan Reis
89836ad84e
Merge pull request #338 from public-awesome/sample-wl-flex-airdrop-file
Include sample files for whitelist creation
2024-02-12 22:15:54 +02:00
Serkan Reis
51f38f12d7 Include sample files for whitelist creation 2024-02-12 23:10:18 +03:00
Serkan Reis
609745ce11
Merge pull request #337 from public-awesome/main
Sync main > dev
2024-02-11 18:57:37 +02:00
Serkan Reis
3b9778270b
Merge pull request #332 from public-awesome/add-default-nft-storage-api-key-option
Add default NFT.Storage API key option
2024-02-11 18:57:01 +02:00
Serkan Reis
c66b21792c Add default API key selection option 2024-02-11 19:41:05 +03:00
Serkan Reis
35a9c0eba8 Set up env variable for default API key 2024-02-11 19:35:27 +03:00
Serkan Reis
07152745e0
Merge pull request #336 from public-awesome/main
Sync main > dev
2024-02-08 18:36:06 +02:00
Serkan Reis
7a455b60bc
Merge pull request #335 from public-awesome/remove-wl-flex-allocation-check
Disable whitelist-flex total allocation check
2024-02-08 18:35:35 +02:00
Serkan Reis
67694d4c5d Disable whitelist-flex total allocation check 2024-02-08 19:34:48 +03:00
Serkan Reis
4ec704f588
Merge pull request #334 from public-awesome/main
Sync main > dev
2024-02-07 10:28:13 +02:00
Serkan Reis
3f44e342e8
Merge pull request #333 from public-awesome/fix-factory-validation
Fix factory validation on init issue
2024-02-07 10:27:41 +02:00
Serkan Reis
245fd2253b Add API Key input method 2024-02-06 23:48:13 +03:00
Serkan Reis
ed1844fdf0
Merge pull request #331 from public-awesome/main
Sync main > dev
2024-02-05 21:29:45 +02:00
Serkan Reis
6316a68408
Merge pull request #329 from public-awesome/main
Sync main > dev
2024-02-03 03:48:44 +02:00
Serkan Reis
a949a1e103
Merge pull request #326 from public-awesome/time-input-updates
Time input updates
2024-02-02 15:45:10 +02:00
Serkan Reis
39385ee82e Address lint issues 2024-02-02 16:34:17 +03:00
Serkan Reis
440ee78122
Merge branch 'develop' into time-input-updates 2024-02-02 15:27:50 +02:00
Serkan Reis
1f31cdbe29
Merge pull request #324 from public-awesome/factory-validation
Check if a valid factory exists for the selected parameters prior to collection creation
2024-02-02 09:02:45 +02:00
Serkan Reis
b66e6d0920
Merge pull request #322 from public-awesome/featured-check
Add option to create featured collections
2024-01-31 20:00:17 +02:00
Serkan Reis
ab4d4fa31d
Merge pull request #321 from public-awesome/creation-summary-update
Update collection creation summary logic
2024-01-31 09:24:14 +02:00
Serkan Reis
92ce752b15 Update collection creation summary logic 2024-01-31 10:17:53 +03:00
Serkan Reis
01936d8f5e
Merge pull request #320 from public-awesome/main
Sync main > dev
2024-01-25 11:09:08 +02:00
Serkan Reis
02df116e09
Merge pull request #318 from public-awesome/main
Sync main > dev
2024-01-24 11:46:53 +02:00
Serkan Reis
ab724afb9c
Merge pull request #316 from public-awesome/main
Sync main > dev
2024-01-22 22:37:52 +02:00
Serkan Reis
3c3b60ebe8
Merge pull request #314 from public-awesome/main
Sync main > dev
2024-01-18 16:02:27 +02:00
Serkan Reis
de5de84873
Merge pull request #312 from public-awesome/open-edition-updates
Token Count limited OE collections
2024-01-18 12:29:56 +02:00
Serkan Reis
7951669ed2 Update checks for OE minting details 2023-12-20 23:36:32 +03:00
Serkan Reis
20944cf5de Update config import logic for OE 2023-12-20 23:35:41 +03:00
Serkan Reis
0f1c4a027b Add limit type selection to OE MintingDetails 2023-12-19 13:53:41 +03:00
48 changed files with 24544 additions and 1350 deletions

1
.env Normal file
View File

@ -0,0 +1 @@
CERC_MAX_GENERATE_TIME=180

View File

@ -16,8 +16,10 @@ NEXT_PUBLIC_VENDING_FACTORY_ADDRESS="stars18h7ugh8eaug7wr0w4yjw0ls5s937z35pnkg93
NEXT_PUBLIC_FEATURED_VENDING_FACTORY_ADDRESS="stars14pd96yk3t6gq9l6uyrkg0n5dr09n8rt5y9v3at8x4wl4lrkxhlzq4trqmh" NEXT_PUBLIC_FEATURED_VENDING_FACTORY_ADDRESS="stars14pd96yk3t6gq9l6uyrkg0n5dr09n8rt5y9v3at8x4wl4lrkxhlzq4trqmh"
NEXT_PUBLIC_VENDING_FACTORY_UPDATABLE_ADDRESS="stars1h65nms9gwg4vdktyqj84tu50gwlm34e0eczl5w2ezllxuzfxy9esa9qlt0" NEXT_PUBLIC_VENDING_FACTORY_UPDATABLE_ADDRESS="stars1h65nms9gwg4vdktyqj84tu50gwlm34e0eczl5w2ezllxuzfxy9esa9qlt0"
NEXT_PUBLIC_VENDING_FACTORY_FLEX_ADDRESS="stars1hvu2ghqkcnvhtj2fc6wuazxt4dqcftslp2rwkkkcxy269a35a9pq60ug2q" NEXT_PUBLIC_VENDING_FACTORY_FLEX_ADDRESS="stars1hvu2ghqkcnvhtj2fc6wuazxt4dqcftslp2rwkkkcxy269a35a9pq60ug2q"
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_FEATURED_VENDING_FACTORY_FLEX_ADDRESS="stars1udlmmnmmnnqamh36hy6d7azn3ycv23yymkmg6558ntalvyt2pz7s8lhgcd"
NEXT_PUBLIC_VENDING_FACTORY_UPDATABLE_FLEX_ADDRESS= # NEXT_PUBLIC_VENDING_FACTORY_UPDATABLE_FLEX_ADDRESS=
# NEXT_PUBLIC_VENDING_IBC_ATOM_FACTORY_ADDRESS= # NEXT_PUBLIC_VENDING_IBC_ATOM_FACTORY_ADDRESS=
# NEXT_PUBLIC_VENDING_IBC_ATOM_UPDATABLE_FACTORY_ADDRESS= # NEXT_PUBLIC_VENDING_IBC_ATOM_UPDATABLE_FACTORY_ADDRESS=
@ -25,8 +27,10 @@ NEXT_PUBLIC_VENDING_FACTORY_UPDATABLE_FLEX_ADDRESS=
# NEXT_PUBLIC_VENDING_IBC_ATOM_UPDATABLE_FACTORY_FLEX_ADDRESS= # NEXT_PUBLIC_VENDING_IBC_ATOM_UPDATABLE_FACTORY_FLEX_ADDRESS=
# NEXT_PUBLIC_VENDING_IBC_USDC_FACTORY_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_UPDATABLE_FACTORY_ADDRESS=
# NEXT_PUBLIC_VENDING_IBC_USDC_FACTORY_FLEX_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_USDC_UPDATABLE_FACTORY_FLEX_ADDRESS=
# NEXT_PUBLIC_VENDING_IBC_USK_FACTORY_ADDRESS= # NEXT_PUBLIC_VENDING_IBC_USK_FACTORY_ADDRESS=
@ -34,6 +38,15 @@ NEXT_PUBLIC_VENDING_FACTORY_UPDATABLE_FLEX_ADDRESS=
# NEXT_PUBLIC_VENDING_IBC_USK_FACTORY_FLEX_ADDRESS= # NEXT_PUBLIC_VENDING_IBC_USK_FACTORY_FLEX_ADDRESS=
# NEXT_PUBLIC_VENDING_IBC_USK_UPDATABLE_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_FACTORY_ADDRESS="stars1mxwf2hjcjvqnlw0v3j7m0u34975qesp325wzrgz0ht7vr8ys2zmsenjutf"
NEXT_PUBLIC_VENDING_NATIVE_STARDUST_UPDATABLE_FACTORY_ADDRESS="stars18gjczf88jd4z3a3megwj9g5c9famu654csxfnnq59mkqeszuzy4ssdgr46" NEXT_PUBLIC_VENDING_NATIVE_STARDUST_UPDATABLE_FACTORY_ADDRESS="stars18gjczf88jd4z3a3megwj9g5c9famu654csxfnnq59mkqeszuzy4ssdgr46"
NEXT_PUBLIC_VENDING_NATIVE_STRDST_FLEX_FACTORY_ADDRESS="stars1eluqmr6x78ehl4plrln6khxc0qrspfhc7rt3whmr59escpve0r4swcacjh" NEXT_PUBLIC_VENDING_NATIVE_STRDST_FLEX_FACTORY_ADDRESS="stars1eluqmr6x78ehl4plrln6khxc0qrspfhc7rt3whmr59escpve0r4swcacjh"
@ -46,6 +59,7 @@ NEXT_PUBLIC_BASE_FACTORY_ADDRESS="stars1a45hcxty3spnmm2f0papl8v4dk5ew29s4syhn4ef
NEXT_PUBLIC_BASE_FACTORY_UPDATABLE_ADDRESS="stars100xegx2syry4tclkmejjwxk4nfqahvcqhm9qxut5wxuzhj5d9qfsh5nmym" NEXT_PUBLIC_BASE_FACTORY_UPDATABLE_ADDRESS="stars100xegx2syry4tclkmejjwxk4nfqahvcqhm9qxut5wxuzhj5d9qfsh5nmym"
NEXT_PUBLIC_OPEN_EDITION_FACTORY_ADDRESS="stars1sqweqcxlf2f7qhf27gn5naqusk5q52fkzewmy63c4sglvle3s7ls6k828e" 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_OPEN_EDITION_UPDATABLE_FACTORY_ADDRESS="stars1fk5dkzcylam8mcpqrn8y9spauvc3d4navtaqurcc49dc3p9f8d3qdkvymx"
NEXT_PUBLIC_VENDING_IBC_KUJI_FACTORY_ADDRESS="stars1yyje87e0h9mqg34kp3x75yesa78ve4glc3dstdrn6nscw3zjfanqkj95f0" NEXT_PUBLIC_VENDING_IBC_KUJI_FACTORY_ADDRESS="stars1yyje87e0h9mqg34kp3x75yesa78ve4glc3dstdrn6nscw3zjfanqkj95f0"
@ -59,8 +73,12 @@ NEXT_PUBLIC_VENDING_IBC_CRBRUS_FACTORY_FLEX_ADDRESS="stars1halhp674yxwgn3p4gpkl8
# NEXT_PUBLIC_OPEN_EDITION_IBC_ATOM_FACTORY_ADDRESS= # NEXT_PUBLIC_OPEN_EDITION_IBC_ATOM_FACTORY_ADDRESS=
# NEXT_PUBLIC_OPEN_EDITION_UPDATABLE_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_UPDATABLE_IBC_USDC_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_IBC_FRNZ_FACTORY_ADDRESS="stars1vzffawsjhvspstu5lvtzz2x5n7zh07hnw09c9dfxcj78un05rcms5n3q3e"
NEXT_PUBLIC_OPEN_EDITION_UPDATABLE_IBC_FRNZ_FACTORY_ADDRESS="stars1tc09vlgdg8rqyapcxwm9qdq8naj4gym9px4ntue9cs0kse5rvess0nee3a" NEXT_PUBLIC_OPEN_EDITION_UPDATABLE_IBC_FRNZ_FACTORY_ADDRESS="stars1tc09vlgdg8rqyapcxwm9qdq8naj4gym9px4ntue9cs0kse5rvess0nee3a"
@ -87,13 +105,14 @@ NEXT_PUBLIC_VENDING_IBC_NBTC_UPDATABLE_FACTORY_ADDRESS="stars1k6ee8qgwvumguqnqqr
NEXT_PUBLIC_SG721_NAME_ADDRESS="stars1fx74nkqkw2748av8j7ew7r3xt9cgjqduwn8m0ur5lhe49uhlsasszc5fhr" NEXT_PUBLIC_SG721_NAME_ADDRESS="stars1fx74nkqkw2748av8j7ew7r3xt9cgjqduwn8m0ur5lhe49uhlsasszc5fhr"
NEXT_PUBLIC_ROYALTY_REGISTRY_ADDRESS="stars1crgx0f70fzksa57hq87wtl8f04h0qyk5la0hk0fu8dyhl67ju80qaxzr5z" NEXT_PUBLIC_ROYALTY_REGISTRY_ADDRESS="stars1crgx0f70fzksa57hq87wtl8f04h0qyk5la0hk0fu8dyhl67ju80qaxzr5z"
NEXT_PUBLIC_INFINITY_SWAP_PROTOCOL_ADDRESS="stars136yp6fl9h66m0cwv8weu4w4aawveuz40992ty0atj5ecjd8z0thqv9xpy5" NEXT_PUBLIC_INFINITY_SWAP_PROTOCOL_ADDRESS="stars136yp6fl9h66m0cwv8weu4w4aawveuz40992ty0atj5ecjd8z0thqv9xpy5"
NEXT_PUBLIC_WHITELIST_CODE_ID=3131 NEXT_PUBLIC_WHITELIST_CODE_ID=4008
NEXT_PUBLIC_WHITELIST_FLEX_CODE_ID=3130 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_CODE_ID=1336
NEXT_PUBLIC_BADGE_HUB_ADDRESS="stars1dacun0xn7z73qzdcmq27q3xn6xuprg8e2ugj364784al2v27tklqynhuqa" NEXT_PUBLIC_BADGE_HUB_ADDRESS="stars1dacun0xn7z73qzdcmq27q3xn6xuprg8e2ugj364784al2v27tklqynhuqa"
NEXT_PUBLIC_BADGE_NFT_CODE_ID=1337 NEXT_PUBLIC_BADGE_NFT_CODE_ID=1337
NEXT_PUBLIC_BADGE_NFT_ADDRESS="stars1vlw4y54dyzt3zg7phj8yey9fg4zj49czknssngwmgrnwymyktztstalg7t" 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_CW4_GROUP_CODE_ID=1904
NEXT_PUBLIC_API_URL=https://nft-api.elgafar-1.stargaze-apis.com NEXT_PUBLIC_API_URL=https://nft-api.elgafar-1.stargaze-apis.com
@ -103,6 +122,8 @@ NEXT_PUBLIC_STARGAZE_WEBSITE_URL=https://testnet.publicawesome.dev
NEXT_PUBLIC_BADGES_URL=https://badges.publicawesome.dev NEXT_PUBLIC_BADGES_URL=https://badges.publicawesome.dev
NEXT_PUBLIC_WEBSITE_URL=https:// NEXT_PUBLIC_WEBSITE_URL=https://
NEXT_PUBLIC_SYNC_COLLECTIONS_API_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_HOST="https://search.publicawesome.dev"
NEXT_PUBLIC_MEILISEARCH_API_KEY= "..." NEXT_PUBLIC_MEILISEARCH_API_KEY= "..."

45
.github/workflows/publish.yaml vendored Normal file
View 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

View File

@ -298,17 +298,16 @@ export const Sidebar = () => {
> >
<Link href="/contracts/royaltyRegistry/">Royalty Registry</Link> <Link href="/contracts/royaltyRegistry/">Royalty Registry</Link>
</li> </li>
<Conditional test={NETWORK === 'testnet'}>
<li <li
className={clsx( className={clsx(
'text-lg font-bold hover:text-white hover:bg-stargaze-80 rounded', 'text-lg font-bold hover:text-white hover:bg-stargaze-80 rounded',
router.asPath.includes('/contracts/upload/') ? 'text-white' : 'text-gray', router.asPath.includes('/contracts/upload/') ? 'text-white' : 'text-gray',
)} )}
tabIndex={-1} tabIndex={-1}
> >
<Link href="/contracts/upload/">Upload Contract</Link> <Link href="/contracts/upload/">Upload Contract</Link>
</li> </li>
</Conditional>
</ul> </ul>
</li> </li>
</ul> </ul>

View File

@ -78,7 +78,7 @@ export const WhitelistUpload = ({ onChange }: WhitelistUploadProps) => {
const printableData = data?.map((item) => item.replace(regex, '')) const printableData = data?.map((item) => item.replace(regex, ''))
const names = printableData?.filter((address) => address !== '' && address.endsWith('.stars')) const names = printableData?.filter((address) => address !== '' && address.endsWith('.stars'))
const strippedNames = names?.map((name) => name.split('.')[0]) const strippedNames = names?.map((name) => name.split('.')[0])
console.log(names) console.log('names: ', names)
if (strippedNames?.length) { if (strippedNames?.length) {
await toast await toast
.promise(resolveAddresses(strippedNames), { .promise(resolveAddresses(strippedNames), {

View File

@ -13,6 +13,7 @@ import type { ChangeEvent } from 'react'
import { useEffect, useMemo, useRef, useState } from 'react' import { useEffect, useMemo, useRef, useState } from 'react'
import { toast } from 'react-hot-toast' import { toast } from 'react-hot-toast'
import type { UploadServiceType } from 'services/upload' import type { UploadServiceType } from 'services/upload'
import { NFT_STORAGE_DEFAULT_API_KEY } from 'utils/constants'
import { getAssetType } from 'utils/getAssetType' import { getAssetType } from 'utils/getAssetType'
export type UploadMethod = 'new' | 'existing' export type UploadMethod = 'new' | 'existing'
@ -37,6 +38,7 @@ export const ImageUploadDetails = ({ onChange, mintRule }: ImageUploadDetailsPro
const [assetFile, setAssetFile] = useState<File>() const [assetFile, setAssetFile] = useState<File>()
const [uploadMethod, setUploadMethod] = useState<UploadMethod>('new') const [uploadMethod, setUploadMethod] = useState<UploadMethod>('new')
const [uploadService, setUploadService] = useState<UploadServiceType>('nft-storage') const [uploadService, setUploadService] = useState<UploadServiceType>('nft-storage')
const [useDefaultApiKey, setUseDefaultApiKey] = useState(false)
const assetFileRef = useRef<HTMLInputElement | null>(null) const assetFileRef = useRef<HTMLInputElement | null>(null)
@ -130,6 +132,14 @@ export const ImageUploadDetails = ({ onChange, mintRule }: ImageUploadDetailsPro
imageUrlState.onChange('') imageUrlState.onChange('')
}, [uploadMethod, mintRule]) }, [uploadMethod, mintRule])
useEffect(() => {
if (useDefaultApiKey) {
nftStorageApiKeyState.onChange(NFT_STORAGE_DEFAULT_API_KEY || '')
} else {
nftStorageApiKeyState.onChange('')
}
}, [useDefaultApiKey])
const videoPreview = useMemo( const videoPreview = useMemo(
() => ( () => (
<video <video
@ -269,7 +279,22 @@ export const ImageUploadDetails = ({ onChange, mintRule }: ImageUploadDetailsPro
<div className="flex w-full"> <div className="flex w-full">
<Conditional test={uploadService === 'nft-storage'}> <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>
<Conditional test={uploadService === 'pinata'}> <Conditional test={uploadService === 'pinata'}>
<TextInput {...pinataApiKeyState} className="w-full" /> <TextInput {...pinataApiKeyState} className="w-full" />

View File

@ -3,6 +3,7 @@
import { toUtf8 } from '@cosmjs/encoding' import { toUtf8 } from '@cosmjs/encoding'
import clsx from 'clsx' import clsx from 'clsx'
import { AirdropUpload } from 'components/AirdropUpload' import { AirdropUpload } from 'components/AirdropUpload'
import { Alert } from 'components/Alert'
import { Button } from 'components/Button' import { Button } from 'components/Button'
import type { DispatchExecuteArgs } from 'components/collections/actions/actions' import type { DispatchExecuteArgs } from 'components/collections/actions/actions'
import { dispatchExecute, isEitherType, previewExecutePayload } from 'components/collections/actions/actions' import { dispatchExecute, isEitherType, previewExecutePayload } from 'components/collections/actions/actions'
@ -115,6 +116,13 @@ export const CollectionActions = ({
subtitle: 'Address of the recipient', subtitle: 'Address of the recipient',
}) })
const creatorState = useInputState({
id: 'creator-address',
name: 'creator',
title: 'Creator Address',
subtitle: 'Address of the creator',
})
const tokenURIState = useInputState({ const tokenURIState = useInputState({
id: 'token-uri', id: 'token-uri',
name: 'tokenURI', name: 'tokenURI',
@ -217,6 +225,7 @@ export const CollectionActions = ({
]) ])
const showPriceField = isEitherType(type, ['update_mint_price', 'update_discount_price']) const showPriceField = isEitherType(type, ['update_mint_price', 'update_discount_price'])
const showDescriptionField = type === 'update_collection_info' const showDescriptionField = type === 'update_collection_info'
const showCreatorField = type === 'update_collection_info'
const showImageField = type === 'update_collection_info' const showImageField = type === 'update_collection_info'
const showExternalLinkField = type === 'update_collection_info' const showExternalLinkField = type === 'update_collection_info'
const showRoyaltyRelatedFields = const showRoyaltyRelatedFields =
@ -289,6 +298,16 @@ export const CollectionActions = ({
void resolveRoyaltyPaymentAddress() void resolveRoyaltyPaymentAddress()
}, [royaltyPaymentAddressState.value]) }, [royaltyPaymentAddressState.value])
const resolveCreatorAddress = async () => {
await resolveAddress(creatorState.value.trim(), wallet).then((resolvedAddress) => {
creatorState.onChange(resolvedAddress)
})
}
useEffect(() => {
void resolveCreatorAddress()
}, [creatorState.value])
useEffect(() => { useEffect(() => {
setCollectionInfo({ setCollectionInfo({
description: descriptionState.value.replaceAll('\\n', '\n') || undefined, description: descriptionState.value.replaceAll('\\n', '\n') || undefined,
@ -302,6 +321,7 @@ export const CollectionActions = ({
share: (Number(royaltyShareState.value) / 100).toString(), share: (Number(royaltyShareState.value) / 100).toString(),
} }
: undefined, : undefined,
creator: creatorState.value || undefined,
}) })
}, [ }, [
descriptionState.value, descriptionState.value,
@ -310,6 +330,7 @@ export const CollectionActions = ({
externalLinkState.value, externalLinkState.value,
royaltyPaymentAddressState.value, royaltyPaymentAddressState.value,
royaltyShareState.value, royaltyShareState.value,
creatorState.value,
]) ])
useEffect(() => { useEffect(() => {
@ -438,6 +459,27 @@ export const CollectionActions = ({
} }
} }
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)
}
}
const txHash = await toast.promise(dispatchExecute(payload), { const txHash = await toast.promise(dispatchExecute(payload), {
error: `${type.charAt(0).toUpperCase() + type.slice(1)} execute failed!`, error: `${type.charAt(0).toUpperCase() + type.slice(1)} execute failed!`,
loading: 'Executing message...', loading: 'Executing message...',
@ -494,6 +536,7 @@ export const CollectionActions = ({
{showBaseUriField && <TextInput className="mt-2" {...baseURIState} />} {showBaseUriField && <TextInput className="mt-2" {...baseURIState} />}
{showNumberOfTokensField && <NumberInput className="mt-2" {...batchNumberState} />} {showNumberOfTokensField && <NumberInput className="mt-2" {...batchNumberState} />}
{showPriceField && <NumberInput className="mt-2" {...priceState} />} {showPriceField && <NumberInput className="mt-2" {...priceState} />}
{showCreatorField && <AddressInput className="mt-2" {...creatorState} />}
{showDescriptionField && <TextInput className="my-2" {...descriptionState} />} {showDescriptionField && <TextInput className="my-2" {...descriptionState} />}
{showImageField && <TextInput className="mb-2" {...imageState} />} {showImageField && <TextInput className="mb-2" {...imageState} />}
{showExternalLinkField && <TextInput className="mb-2" {...externalLinkState} />} {showExternalLinkField && <TextInput className="mb-2" {...externalLinkState} />}
@ -664,6 +707,17 @@ export const CollectionActions = ({
</div> </div>
</Tooltip> </Tooltip>
</Conditional> </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>
<div className="-mt-6"> <div className="-mt-6">
<div className="relative mb-2"> <div className="relative mb-2">

View File

@ -20,6 +20,7 @@ import type { ChangeEvent } from 'react'
import { useEffect, useRef, useState } from 'react' import { useEffect, useRef, useState } from 'react'
import { toast } from 'react-hot-toast' import { toast } from 'react-hot-toast'
import type { UploadServiceType } from 'services/upload' import type { UploadServiceType } from 'services/upload'
import { NFT_STORAGE_DEFAULT_API_KEY } from 'utils/constants'
import type { AssetType } from 'utils/getAssetType' import type { AssetType } from 'utils/getAssetType'
import { getAssetType } from 'utils/getAssetType' import { getAssetType } from 'utils/getAssetType'
import { uid } from 'utils/random' import { uid } from 'utils/random'
@ -66,6 +67,7 @@ export const UploadDetails = ({
const [uploadService, setUploadService] = useState<UploadServiceType>('nft-storage') const [uploadService, setUploadService] = useState<UploadServiceType>('nft-storage')
const [metadataFileArrayIndex, setMetadataFileArrayIndex] = useState(0) const [metadataFileArrayIndex, setMetadataFileArrayIndex] = useState(0)
const [refreshMetadata, setRefreshMetadata] = useState(false) const [refreshMetadata, setRefreshMetadata] = useState(false)
const [useDefaultApiKey, setUseDefaultApiKey] = useState(false)
const [baseMinterMetadataFile, setBaseMinterMetadataFile] = useState<File | undefined>() const [baseMinterMetadataFile, setBaseMinterMetadataFile] = useState<File | undefined>()
@ -461,6 +463,14 @@ export const UploadDetails = ({
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [importedUploadDetails]) }, [importedUploadDetails])
useEffect(() => {
if (useDefaultApiKey) {
nftStorageApiKeyState.onChange(NFT_STORAGE_DEFAULT_API_KEY || '')
} else {
nftStorageApiKeyState.onChange('')
}
}, [useDefaultApiKey])
return ( return (
<div className="justify-items-start mb-3 rounded border-2 border-white/20 flex-column"> <div className="justify-items-start mb-3 rounded border-2 border-white/20 flex-column">
<div className="flex justify-center"> <div className="flex justify-center">
@ -616,7 +626,22 @@ export const UploadDetails = ({
<div className="flex w-full"> <div className="flex w-full">
<Conditional test={uploadService === 'nft-storage'}> <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>
<Conditional test={uploadService === 'pinata'}> <Conditional test={uploadService === 'pinata'}>
<TextInput {...pinataApiKeyState} className="w-full" /> <TextInput {...pinataApiKeyState} className="w-full" />

View File

@ -1,6 +1,7 @@
/* eslint-disable eslint-comments/disable-enable-pair */ /* eslint-disable eslint-comments/disable-enable-pair */
/* eslint-disable @typescript-eslint/no-unnecessary-condition */ /* eslint-disable @typescript-eslint/no-unnecessary-condition */
/* eslint-disable no-nested-ternary */ /* eslint-disable no-nested-ternary */
import { Button } from 'components/Button'
import { FormControl } from 'components/FormControl' import { FormControl } from 'components/FormControl'
import { FormGroup } from 'components/FormGroup' import { FormGroup } from 'components/FormGroup'
import { AddressList } from 'components/forms/AddressList' import { AddressList } from 'components/forms/AddressList'
@ -42,7 +43,7 @@ export interface WhitelistDetailsDataProps {
type WhitelistState = 'none' | 'existing' | 'new' type WhitelistState = 'none' | 'existing' | 'new'
type WhitelistType = 'standard' | 'flex' export type WhitelistType = 'standard' | 'flex' | 'merkletree'
export const WhitelistDetails = ({ export const WhitelistDetails = ({
onChange, onChange,
@ -58,6 +59,7 @@ export const WhitelistDetails = ({
const [endDate, setEndDate] = useState<Date | undefined>(undefined) const [endDate, setEndDate] = useState<Date | undefined>(undefined)
const [whitelistStandardArray, setWhitelistStandardArray] = useState<string[]>([]) const [whitelistStandardArray, setWhitelistStandardArray] = useState<string[]>([])
const [whitelistFlexArray, setWhitelistFlexArray] = useState<WhitelistFlexMember[]>([]) const [whitelistFlexArray, setWhitelistFlexArray] = useState<WhitelistFlexMember[]>([])
const [whitelistMerkleTreeArray, setWhitelistMerkleTreeArray] = useState<string[]>([])
const [adminsMutable, setAdminsMutable] = useState<boolean>(true) const [adminsMutable, setAdminsMutable] = useState<boolean>(true)
const whitelistAddressState = useInputState({ const whitelistAddressState = useInputState({
@ -96,17 +98,41 @@ export const WhitelistDetails = ({
const addressListState = useAddressListState() const addressListState = useAddressListState()
const whitelistFileOnChange = (data: string[]) => { const whitelistFileOnChange = (data: string[]) => {
setWhitelistStandardArray(data) if (whitelistType === 'standard') setWhitelistStandardArray(data)
if (whitelistType === 'merkletree') setWhitelistMerkleTreeArray(data)
} }
const whitelistFlexFileOnChange = (whitelistData: WhitelistFlexMember[]) => { const whitelistFlexFileOnChange = (whitelistData: WhitelistFlexMember[]) => {
setWhitelistFlexArray(whitelistData) 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(() => { useEffect(() => {
if (!importedWhitelistDetails) { if (!importedWhitelistDetails) {
setWhitelistStandardArray([]) setWhitelistStandardArray([])
setWhitelistFlexArray([]) setWhitelistFlexArray([])
setWhitelistMerkleTreeArray([])
} }
}, [whitelistType]) }, [whitelistType])
@ -120,7 +146,12 @@ export const WhitelistDetails = ({
.replace(/"/g, '') .replace(/"/g, '')
.replace(/'/g, '') .replace(/'/g, '')
.replace(/ /g, ''), .replace(/ /g, ''),
members: whitelistType === 'standard' ? whitelistStandardArray : whitelistFlexArray, members:
whitelistType === 'standard'
? whitelistStandardArray
: whitelistType === 'merkletree'
? whitelistMerkleTreeArray
: whitelistFlexArray,
unitPrice: unitPriceState.value unitPrice: unitPriceState.value
? (Number(unitPriceState.value) * 1_000_000).toString() ? (Number(unitPriceState.value) * 1_000_000).toString()
: unitPriceState.value === 0 : unitPriceState.value === 0
@ -150,7 +181,9 @@ export const WhitelistDetails = ({
endDate, endDate,
whitelistStandardArray, whitelistStandardArray,
whitelistFlexArray, whitelistFlexArray,
whitelistMerkleTreeArray,
whitelistState, whitelistState,
whitelistType,
addressListState.values, addressListState.values,
adminsMutable, adminsMutable,
]) ])
@ -188,7 +221,12 @@ export const WhitelistDetails = ({
importedWhitelistDetails.members?.forEach((member) => { importedWhitelistDetails.members?.forEach((member) => {
setWhitelistStandardArray((standardArray) => [...standardArray, member as string]) setWhitelistStandardArray((standardArray) => [...standardArray, member as string])
}) })
} else { } else if (importedWhitelistDetails.whitelistType === 'merkletree') {
setWhitelistMerkleTreeArray([])
// importedWhitelistDetails.members?.forEach((member) => {
// setWhitelistMerkleTreeArray((merkleTreeArray) => [...merkleTreeArray, member as string])
// })
} else if (importedWhitelistDetails.whitelistType === 'flex') {
setWhitelistFlexArray([]) setWhitelistFlexArray([])
importedWhitelistDetails.members?.forEach((member) => { importedWhitelistDetails.members?.forEach((member) => {
setWhitelistFlexArray((flexArray) => [ setWhitelistFlexArray((flexArray) => [
@ -280,7 +318,7 @@ export const WhitelistDetails = ({
</Conditional> </Conditional>
<Conditional test={whitelistState === 'new'}> <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"> <div className="form-check form-check-inline">
<input <input
checked={whitelistType === 'standard'} checked={whitelistType === 'standard'}
@ -291,7 +329,7 @@ export const WhitelistDetails = ({
setWhitelistType('standard') setWhitelistType('standard')
}} }}
type="radio" type="radio"
value="nft-storage" value="standard"
/> />
<label <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" 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"
@ -320,12 +358,33 @@ export const WhitelistDetails = ({
Whitelist Flex Whitelist Flex
</label> </label>
</div> </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>
<div className="grid grid-cols-2"> <div className="grid grid-cols-2">
<FormGroup subtitle="Information about your minting settings" title="Whitelist Minting Details"> <FormGroup subtitle="Information about your minting settings" title="Whitelist Minting Details">
<NumberInput isRequired {...unitPriceState} /> <NumberInput isRequired {...unitPriceState} />
<NumberInput isRequired {...memberLimitState} /> <Conditional test={whitelistType !== 'merkletree'}>
<Conditional test={whitelistType === 'standard'}> <NumberInput isRequired {...memberLimitState} />
</Conditional>
<Conditional test={whitelistType === 'standard' || whitelistType === 'merkletree'}>
<NumberInput isRequired {...perAddressLimitState} /> <NumberInput isRequired {...perAddressLimitState} />
</Conditional> </Conditional>
<FormControl <FormControl
@ -408,7 +467,17 @@ export const WhitelistDetails = ({
/> />
</div> </div>
<Conditional test={whitelistType === 'standard'}> <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} /> <WhitelistUpload onChange={whitelistFileOnChange} />
</FormGroup> </FormGroup>
<Conditional test={whitelistStandardArray.length > 0}> <Conditional test={whitelistStandardArray.length > 0}>
@ -417,7 +486,14 @@ export const WhitelistDetails = ({
</Conditional> </Conditional>
<Conditional test={whitelistType === 'flex'}> <Conditional test={whitelistType === 'flex'}>
<FormGroup <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" title="Whitelist File"
> >
<WhitelistFlexUpload onChange={whitelistFlexFileOnChange} /> <WhitelistFlexUpload onChange={whitelistFlexFileOnChange} />
@ -426,6 +502,24 @@ export const WhitelistDetails = ({
<JsonPreview content={whitelistFlexArray} initialState={false} title="File Contents" /> <JsonPreview content={whitelistFlexArray} initialState={false} title="File Contents" />
</Conditional> </Conditional>
</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>
</div> </div>
</Conditional> </Conditional>

View File

@ -1,8 +1,11 @@
/* eslint-disable eslint-comments/disable-enable-pair */
/* eslint-disable no-nested-ternary */
import { Combobox, Transition } from '@headlessui/react' import { Combobox, Transition } from '@headlessui/react'
import clsx from 'clsx' import clsx from 'clsx'
import { FormControl } from 'components/FormControl' import { FormControl } from 'components/FormControl'
import type { ExecuteListItem } from 'contracts/whitelist/messages/execute' import type { ExecuteListItem } from 'contracts/whitelist/messages/execute'
import { EXECUTE_LIST } 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 { matchSorter } from 'match-sorter'
import { Fragment, useState } from 'react' import { Fragment, useState } from 'react'
import { FaChevronDown, FaInfoCircle } from 'react-icons/fa' import { FaChevronDown, FaInfoCircle } from 'react-icons/fa'
@ -10,13 +13,20 @@ import { FaChevronDown, FaInfoCircle } from 'react-icons/fa'
export interface ExecuteComboboxProps { export interface ExecuteComboboxProps {
value: ExecuteListItem | null value: ExecuteListItem | null
onChange: (item: ExecuteListItem) => void 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 [search, setSearch] = useState('')
const filtered = 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 ( return (
<Combobox <Combobox

View File

@ -14,6 +14,7 @@ import type { ChangeEvent } from 'react'
import { useEffect, useMemo, useRef, useState } from 'react' import { useEffect, useMemo, useRef, useState } from 'react'
import { toast } from 'react-hot-toast' import { toast } from 'react-hot-toast'
import type { UploadServiceType } from 'services/upload' import type { UploadServiceType } from 'services/upload'
import { NFT_STORAGE_DEFAULT_API_KEY } from 'utils/constants'
import type { AssetType } from 'utils/getAssetType' import type { AssetType } from 'utils/getAssetType'
import { getAssetType } from 'utils/getAssetType' import { getAssetType } from 'utils/getAssetType'
@ -43,6 +44,7 @@ export const ImageUploadDetails = ({ onChange, importedImageUploadDetails }: Ima
const [isThumbnailCompatible, setIsThumbnailCompatible] = useState<boolean>(false) const [isThumbnailCompatible, setIsThumbnailCompatible] = useState<boolean>(false)
const [uploadMethod, setUploadMethod] = useState<UploadMethod>('new') const [uploadMethod, setUploadMethod] = useState<UploadMethod>('new')
const [uploadService, setUploadService] = useState<UploadServiceType>('nft-storage') const [uploadService, setUploadService] = useState<UploadServiceType>('nft-storage')
const [useDefaultApiKey, setUseDefaultApiKey] = useState(false)
const assetFileRef = useRef<HTMLInputElement | null>(null) const assetFileRef = useRef<HTMLInputElement | null>(null)
const thumbnailFileRef = useRef<HTMLInputElement | null>(null) const thumbnailFileRef = useRef<HTMLInputElement | null>(null)
@ -192,6 +194,14 @@ export const ImageUploadDetails = ({ onChange, importedImageUploadDetails }: Ima
} }
}, [importedImageUploadDetails]) }, [importedImageUploadDetails])
useEffect(() => {
if (useDefaultApiKey) {
nftStorageApiKeyState.onChange(NFT_STORAGE_DEFAULT_API_KEY || '')
} else {
nftStorageApiKeyState.onChange('')
}
}, [useDefaultApiKey])
const previewUrl = imageUrlState.value.toLowerCase().trim().startsWith('ipfs://') const previewUrl = imageUrlState.value.toLowerCase().trim().startsWith('ipfs://')
? `https://ipfs-gw.stargaze-apis.com/ipfs/${imageUrlState.value.substring(7)}` ? `https://ipfs-gw.stargaze-apis.com/ipfs/${imageUrlState.value.substring(7)}`
: imageUrlState.value : imageUrlState.value
@ -343,7 +353,22 @@ export const ImageUploadDetails = ({ onChange, importedImageUploadDetails }: Ima
<div className="flex w-full"> <div className="flex w-full">
<Conditional test={uploadService === 'nft-storage'}> <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>
<Conditional test={uploadService === 'pinata'}> <Conditional test={uploadService === 'pinata'}>
<TextInput {...pinataApiKeyState} className="w-full" /> <TextInput {...pinataApiKeyState} className="w-full" />

View File

@ -1,6 +1,7 @@
/* eslint-disable eslint-comments/disable-enable-pair */ /* eslint-disable eslint-comments/disable-enable-pair */
/* eslint-disable @typescript-eslint/no-unnecessary-condition */ /* eslint-disable @typescript-eslint/no-unnecessary-condition */
/* eslint-disable no-nested-ternary */ /* eslint-disable no-nested-ternary */
import { Conditional } from 'components/Conditional'
import { FormControl } from 'components/FormControl' import { FormControl } from 'components/FormControl'
import { FormGroup } from 'components/FormGroup' import { FormGroup } from 'components/FormGroup'
import { useInputState, useNumberInputState } from 'components/forms/FormInput.hooks' import { useInputState, useNumberInputState } from 'components/forms/FormInput.hooks'
@ -16,21 +17,27 @@ import { useWallet } from 'utils/wallet'
import { NumberInput, TextInput } from '../forms/FormInput' import { NumberInput, TextInput } from '../forms/FormInput'
import type { UploadMethod } from './OffChainMetadataUploadDetails' import type { UploadMethod } from './OffChainMetadataUploadDetails'
export type LimitType = 'count_limited' | 'time_limited' | 'time_and_count_limited'
interface MintingDetailsProps { interface MintingDetailsProps {
onChange: (data: MintingDetailsDataProps) => void onChange: (data: MintingDetailsDataProps) => void
uploadMethod: UploadMethod uploadMethod: UploadMethod
minimumMintPrice: number minimumMintPrice: number
mintTokenFromFactory?: TokenInfo | undefined mintTokenFromFactory?: TokenInfo | undefined
importedMintingDetails?: MintingDetailsDataProps importedMintingDetails?: MintingDetailsDataProps
isPresale: boolean
whitelistStartDate?: string
} }
export interface MintingDetailsDataProps { export interface MintingDetailsDataProps {
unitPrice: string unitPrice: string
perAddressLimit: number perAddressLimit: number
startTime: string startTime: string
endTime: string endTime?: string
tokenCountLimit?: number
paymentAddress?: string paymentAddress?: string
selectedMintToken?: TokenInfo selectedMintToken?: TokenInfo
limitType: LimitType
} }
export const MintingDetails = ({ export const MintingDetails = ({
@ -39,6 +46,8 @@ export const MintingDetails = ({
minimumMintPrice, minimumMintPrice,
mintTokenFromFactory, mintTokenFromFactory,
importedMintingDetails, importedMintingDetails,
isPresale,
whitelistStartDate,
}: MintingDetailsProps) => { }: MintingDetailsProps) => {
const wallet = useWallet() const wallet = useWallet()
@ -46,6 +55,7 @@ export const MintingDetails = ({
const [endTimestamp, setEndTimestamp] = useState<Date | undefined>() const [endTimestamp, setEndTimestamp] = useState<Date | undefined>()
const [selectedMintToken, setSelectedMintToken] = useState<TokenInfo | undefined>(stars) const [selectedMintToken, setSelectedMintToken] = useState<TokenInfo | undefined>(stars)
const [mintingDetailsImported, setMintingDetailsImported] = useState(false) const [mintingDetailsImported, setMintingDetailsImported] = useState(false)
const [limitType, setLimitType] = useState<LimitType>('time_limited')
const { timezone } = useGlobalSettings() const { timezone } = useGlobalSettings()
const unitPriceState = useNumberInputState({ const unitPriceState = useNumberInputState({
@ -66,6 +76,14 @@ export const MintingDetails = ({
placeholder: '1', placeholder: '1',
}) })
const tokenCountLimitState = useNumberInputState({
id: 'tokencountlimit',
name: 'tokencountlimit',
title: 'Maximum Token Count',
subtitle: 'Total number of mintable tokens',
placeholder: '100',
})
const paymentAddressState = useInputState({ const paymentAddressState = useInputState({
id: 'payment-address', id: 'payment-address',
name: 'paymentAddress', name: 'paymentAddress',
@ -95,9 +113,19 @@ export const MintingDetails = ({
: '', : '',
perAddressLimit: perAddressLimitState.value, perAddressLimit: perAddressLimitState.value,
startTime: timestamp ? (timestamp.getTime() * 1_000_000).toString() : '', 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(), paymentAddress: paymentAddressState.value.trim(),
selectedMintToken, selectedMintToken,
limitType,
tokenCountLimit:
limitType === 'count_limited' || limitType === 'time_and_count_limited'
? tokenCountLimitState.value
: undefined,
} }
onChange(data) onChange(data)
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
@ -108,6 +136,8 @@ export const MintingDetails = ({
endTimestamp, endTimestamp,
paymentAddressState.value, paymentAddressState.value,
selectedMintToken, selectedMintToken,
tokenCountLimitState.value,
limitType,
]) ])
useEffect(() => { useEffect(() => {
@ -115,6 +145,8 @@ export const MintingDetails = ({
console.log('Selected Token ID: ', importedMintingDetails.selectedMintToken?.id) console.log('Selected Token ID: ', importedMintingDetails.selectedMintToken?.id)
unitPriceState.onChange(Number(importedMintingDetails.unitPrice) / 1000000) unitPriceState.onChange(Number(importedMintingDetails.unitPrice) / 1000000)
perAddressLimitState.onChange(importedMintingDetails.perAddressLimit) perAddressLimitState.onChange(importedMintingDetails.perAddressLimit)
setLimitType(importedMintingDetails.limitType)
tokenCountLimitState.onChange(importedMintingDetails.tokenCountLimit ? importedMintingDetails.tokenCountLimit : 0)
setTimestamp(new Date(Number(importedMintingDetails.startTime) / 1_000_000)) setTimestamp(new Date(Number(importedMintingDetails.startTime) / 1_000_000))
setEndTimestamp(new Date(Number(importedMintingDetails.endTime) / 1_000_000)) setEndTimestamp(new Date(Number(importedMintingDetails.endTime) / 1_000_000))
paymentAddressState.onChange(importedMintingDetails.paymentAddress ? importedMintingDetails.paymentAddress : '') paymentAddressState.onChange(importedMintingDetails.paymentAddress ? importedMintingDetails.paymentAddress : '')
@ -124,6 +156,12 @@ export const MintingDetails = ({
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [importedMintingDetails]) }, [importedMintingDetails])
useEffect(() => {
if (isPresale) {
setTimestamp(whitelistStartDate ? new Date(Number(whitelistStartDate) / 1_000_000) : undefined)
}
}, [whitelistStartDate, isPresale])
return ( return (
<div className="border-l-[1px] border-gray-500 border-opacity-20"> <div className="border-l-[1px] border-gray-500 border-opacity-20">
<FormGroup subtitle="Information about your minting settings" title="Minting Details"> <FormGroup subtitle="Information about your minting settings" title="Minting Details">
@ -152,6 +190,7 @@ export const MintingDetails = ({
title="Start Time" title="Start Time"
> >
<InputDateTime <InputDateTime
disabled={isPresale}
minDate={ minDate={
timezone === 'Local' ? new Date() : new Date(Date.now() + new Date().getTimezoneOffset() * 60 * 1000) timezone === 'Local' ? new Date() : new Date(Date.now() + new Date().getTimezoneOffset() * 60 * 1000)
} }
@ -171,32 +210,69 @@ export const MintingDetails = ({
} }
/> />
</FormControl> </FormControl>
<FormControl
htmlId="endTimestamp" <div className="flex-row mt-2 w-full form-control">
isRequired <h1 className="mt-2 font-bold text-md">Limit Type: </h1>
subtitle={`Minting end time ${timezone === 'Local' ? '(local)' : '(UTC)'}`} <label className="justify-start ml-6 cursor-pointer label">
title="End Time" <span className="mr-2">Time</span>
> <input
<InputDateTime checked={limitType === 'time_limited' || limitType === 'time_and_count_limited'}
minDate={ className={`${limitType === 'time_limited' ? `bg-stargaze` : `bg-gray-600`} checkbox`}
timezone === 'Local' ? new Date() : new Date(Date.now() + new Date().getTimezoneOffset() * 60 * 1000) onClick={() => {
} if (limitType === 'time_and_count_limited') setLimitType('count_limited' as LimitType)
onChange={(date) => else if (limitType === 'count_limited') setLimitType('time_and_count_limited' as LimitType)
date else setLimitType('count_limited' as LimitType)
? setEndTimestamp( }}
timezone === 'Local' ? date : new Date(date.getTime() - new Date().getTimezoneOffset() * 60 * 1000), type="checkbox"
) />
: setEndTimestamp(undefined) </label>
} <label className="justify-start ml-4 cursor-pointer label">
value={ <span className="mr-2">Token Count</span>
timezone === 'Local' <input
? endTimestamp checked={limitType === 'count_limited' || limitType === 'time_and_count_limited'}
: endTimestamp className={`${limitType === 'count_limited' ? `bg-stargaze` : `bg-gray-600`} checkbox`}
? new Date(endTimestamp.getTime() + new Date().getTimezoneOffset() * 60 * 1000) onClick={() => {
: undefined 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)
</FormControl> }}
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> </FormGroup>
<TextInput className="pr-4 pl-4 mt-3" {...paymentAddressState} /> <TextInput className="pr-4 pl-4 mt-3" {...paymentAddressState} />
</div> </div>

View File

@ -18,6 +18,7 @@ import type { ChangeEvent } from 'react'
import { useEffect, useRef, useState } from 'react' import { useEffect, useRef, useState } from 'react'
import { toast } from 'react-hot-toast' import { toast } from 'react-hot-toast'
import type { UploadServiceType } from 'services/upload' import type { UploadServiceType } from 'services/upload'
import { NFT_STORAGE_DEFAULT_API_KEY } from 'utils/constants'
import type { AssetType } from 'utils/getAssetType' import type { AssetType } from 'utils/getAssetType'
import { getAssetType } from 'utils/getAssetType' import { getAssetType } from 'utils/getAssetType'
import { uid } from 'utils/random' import { uid } from 'utils/random'
@ -64,6 +65,7 @@ export const OffChainMetadataUploadDetails = ({
const [refreshMetadata, setRefreshMetadata] = useState(false) const [refreshMetadata, setRefreshMetadata] = useState(false)
const [exportedMetadata, setExportedMetadata] = useState(undefined) const [exportedMetadata, setExportedMetadata] = useState(undefined)
const [openEditionMinterMetadataFile, setOpenEditionMinterMetadataFile] = useState<File | undefined>() const [openEditionMinterMetadataFile, setOpenEditionMinterMetadataFile] = useState<File | undefined>()
const [useDefaultApiKey, setUseDefaultApiKey] = useState(false)
const thumbnailCompatibleAssetTypes: AssetType[] = ['video', 'audio', 'html', 'document'] const thumbnailCompatibleAssetTypes: AssetType[] = ['video', 'audio', 'html', 'document']
@ -297,6 +299,14 @@ export const OffChainMetadataUploadDetails = ({
} }
}, [importedOffChainMetadataUploadDetails]) }, [importedOffChainMetadataUploadDetails])
useEffect(() => {
if (useDefaultApiKey) {
nftStorageApiKeyState.onChange(NFT_STORAGE_DEFAULT_API_KEY || '')
} else {
nftStorageApiKeyState.onChange('')
}
}, [useDefaultApiKey])
return ( return (
<div className="justify-items-start mb-3 rounded border-2 border-white/20 flex-column"> <div className="justify-items-start mb-3 rounded border-2 border-white/20 flex-column">
<div className="flex justify-center"> <div className="flex justify-center">
@ -415,7 +425,22 @@ export const OffChainMetadataUploadDetails = ({
<div className="flex w-full"> <div className="flex w-full">
<Conditional test={uploadService === 'nft-storage'}> <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>
<Conditional test={uploadService === 'pinata'}> <Conditional test={uploadService === 'pinata'}>
<TextInput {...pinataApiKeyState} className="w-full" /> <TextInput {...pinataApiKeyState} className="w-full" />

View File

@ -5,15 +5,16 @@
/* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */
import { toUtf8 } from '@cosmjs/encoding' import { toUtf8 } from '@cosmjs/encoding'
import type { Coin } from '@cosmjs/proto-signing'
import { coin } from '@cosmjs/proto-signing' import { coin } from '@cosmjs/proto-signing'
import axios from 'axios'
import clsx from 'clsx' import clsx from 'clsx'
import { Button } from 'components/Button' import { Button } from 'components/Button'
import type { MinterType } from 'components/collections/actions/Combobox' import type { MinterType } from 'components/collections/actions/Combobox'
import { Conditional } from 'components/Conditional' import { Conditional } from 'components/Conditional'
import { ConfirmationModal } from 'components/ConfirmationModal' import { ConfirmationModal } from 'components/ConfirmationModal'
import { LoadingModal } from 'components/LoadingModal' import { LoadingModal } from 'components/LoadingModal'
import { openEditionMinterList } from 'config/minter' import { type TokenInfo, tokensList } from 'config/token'
import { type TokenInfo } from 'config/token'
import { useContracts } from 'contexts/contracts' import { useContracts } from 'contexts/contracts'
import { addLogItem } from 'contexts/log' import { addLogItem } from 'contexts/log'
import type { DispatchExecuteArgs as OpenEditionFactoryDispatchExecuteArgs } from 'contracts/openEditionFactory/messages/execute' import type { DispatchExecuteArgs as OpenEditionFactoryDispatchExecuteArgs } from 'contracts/openEditionFactory/messages/execute'
@ -22,12 +23,15 @@ import React, { useEffect, useMemo, useState } from 'react'
import { toast } from 'react-hot-toast' import { toast } from 'react-hot-toast'
import { upload } from 'services/upload' import { upload } from 'services/upload'
import { import {
OPEN_EDITION_FACTORY_ADDRESS,
OPEN_EDITION_UPDATABLE_FACTORY_ADDRESS,
SG721_OPEN_EDITION_CODE_ID, SG721_OPEN_EDITION_CODE_ID,
SG721_OPEN_EDITION_UPDATABLE_CODE_ID, SG721_OPEN_EDITION_UPDATABLE_CODE_ID,
STRDST_SG721_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' } from 'utils/constants'
import { useDebounce } from 'utils/debounce'
import type { AssetType } from 'utils/getAssetType' import type { AssetType } from 'utils/getAssetType'
import { isValidAddress } from 'utils/isValidAddress' import { isValidAddress } from 'utils/isValidAddress'
import { checkTokenUri } from 'utils/isValidTokenUri' import { checkTokenUri } from 'utils/isValidTokenUri'
@ -37,7 +41,7 @@ import { useWallet } from 'utils/wallet'
import { type CollectionDetailsDataProps, CollectionDetails } from './CollectionDetails' import { type CollectionDetailsDataProps, CollectionDetails } from './CollectionDetails'
import type { ImageUploadDetailsDataProps } from './ImageUploadDetails' import type { ImageUploadDetailsDataProps } from './ImageUploadDetails'
import { ImageUploadDetails } from './ImageUploadDetails' import { ImageUploadDetails } from './ImageUploadDetails'
import type { MintingDetailsDataProps } from './MintingDetails' import type { LimitType, MintingDetailsDataProps } from './MintingDetails'
import { MintingDetails } from './MintingDetails' import { MintingDetails } from './MintingDetails'
import type { UploadMethod } from './OffChainMetadataUploadDetails' import type { UploadMethod } from './OffChainMetadataUploadDetails'
import { import {
@ -47,12 +51,14 @@ import {
import type { OnChainMetadataInputDetailsDataProps } from './OnChainMetadataInputDetails' import type { OnChainMetadataInputDetailsDataProps } from './OnChainMetadataInputDetails'
import { OnChainMetadataInputDetails } from './OnChainMetadataInputDetails' import { OnChainMetadataInputDetails } from './OnChainMetadataInputDetails'
import { type RoyaltyDetailsDataProps, RoyaltyDetails } from './RoyaltyDetails' import { type RoyaltyDetailsDataProps, RoyaltyDetails } from './RoyaltyDetails'
import { type WhitelistDetailsDataProps, WhitelistDetails } from './WhitelistDetails'
export type MetadataStorageMethod = 'off-chain' | 'on-chain' export type MetadataStorageMethod = 'off-chain' | 'on-chain'
export interface OpenEditionMinterDetailsDataProps { export interface OpenEditionMinterDetailsDataProps {
imageUploadDetails?: ImageUploadDetailsDataProps imageUploadDetails?: ImageUploadDetailsDataProps
collectionDetails?: CollectionDetailsDataProps collectionDetails?: CollectionDetailsDataProps
whitelistDetails?: WhitelistDetailsDataProps
royaltyDetails?: RoyaltyDetailsDataProps royaltyDetails?: RoyaltyDetailsDataProps
onChainMetadataInputDetails?: OnChainMetadataInputDetailsDataProps onChainMetadataInputDetails?: OnChainMetadataInputDetailsDataProps
offChainMetadataUploadDetails?: OffChainMetadataUploadDetailsDataProps offChainMetadataUploadDetails?: OffChainMetadataUploadDetailsDataProps
@ -62,24 +68,26 @@ export interface OpenEditionMinterDetailsDataProps {
coverImageUrl?: string | null coverImageUrl?: string | null
tokenUri?: string | null tokenUri?: string | null
tokenImageUri?: string | null tokenImageUri?: string | null
isRefreshed?: boolean
} }
interface OpenEditionMinterCreatorProps { interface OpenEditionMinterCreatorProps {
onChange: (data: OpenEditionMinterCreatorDataProps) => void onChange: (data: OpenEditionMinterCreatorDataProps) => void
onDetailsChange: (data: OpenEditionMinterDetailsDataProps) => void onDetailsChange: (data: OpenEditionMinterDetailsDataProps) => void
openEditionMinterUpdatableCreationFee?: string openEditionMinterCreationFee?: Coin
openEditionMinterCreationFee?: string
minimumMintPrice?: string minimumMintPrice?: string
minimumUpdatableMintPrice?: string
minterType?: MinterType minterType?: MinterType
mintTokenFromFactory?: TokenInfo | undefined mintTokenFromFactory?: TokenInfo | undefined
importedOpenEditionMinterDetails?: OpenEditionMinterDetailsDataProps importedOpenEditionMinterDetails?: OpenEditionMinterDetailsDataProps
isMatchingFactoryPresent?: boolean
openEditionFactoryAddress?: string
} }
export interface OpenEditionMinterCreatorDataProps { export interface OpenEditionMinterCreatorDataProps {
metadataStorageMethod: MetadataStorageMethod metadataStorageMethod: MetadataStorageMethod
openEditionMinterContractAddress: string | null openEditionMinterContractAddress: string | null
sg721ContractAddress: string | null sg721ContractAddress: string | null
whitelistContractAddress: string | null
transactionHash: string | null transactionHash: string | null
} }
@ -87,21 +95,27 @@ export const OpenEditionMinterCreator = ({
onChange, onChange,
onDetailsChange, onDetailsChange,
openEditionMinterCreationFee, openEditionMinterCreationFee,
openEditionMinterUpdatableCreationFee,
minimumMintPrice, minimumMintPrice,
minimumUpdatableMintPrice,
minterType, minterType,
mintTokenFromFactory, mintTokenFromFactory,
importedOpenEditionMinterDetails, importedOpenEditionMinterDetails,
isMatchingFactoryPresent,
openEditionFactoryAddress,
}: OpenEditionMinterCreatorProps) => { }: OpenEditionMinterCreatorProps) => {
const wallet = useWallet() const wallet = useWallet()
const { openEditionMinter: openEditionMinterContract, openEditionFactory: openEditionFactoryContract } = const {
useContracts() openEditionMinter: openEditionMinterContract,
openEditionFactory: openEditionFactoryContract,
whitelist: whitelistContract,
whitelistMerkleTree: whitelistMerkleTreeContract,
} = useContracts()
const [metadataStorageMethod, setMetadataStorageMethod] = useState<MetadataStorageMethod>('off-chain') const [metadataStorageMethod, setMetadataStorageMethod] = useState<MetadataStorageMethod>('off-chain')
const [imageUploadDetails, setImageUploadDetails] = useState<ImageUploadDetailsDataProps | null>(null) const [imageUploadDetails, setImageUploadDetails] = useState<ImageUploadDetailsDataProps | null>(null)
const [collectionDetails, setCollectionDetails] = useState<CollectionDetailsDataProps | null>(null) const [collectionDetails, setCollectionDetails] = useState<CollectionDetailsDataProps | null>(null)
const [whitelistDetails, setWhitelistDetails] = useState<WhitelistDetailsDataProps | null>(null)
const [royaltyDetails, setRoyaltyDetails] = useState<RoyaltyDetailsDataProps | null>(null) const [royaltyDetails, setRoyaltyDetails] = useState<RoyaltyDetailsDataProps | null>(null)
const [isRefreshed, setIsRefreshed] = useState(false)
const [onChainMetadataInputDetails, setOnChainMetadataInputDetails] = const [onChainMetadataInputDetails, setOnChainMetadataInputDetails] =
useState<OnChainMetadataInputDetailsDataProps | null>(null) useState<OnChainMetadataInputDetailsDataProps | null>(null)
const [offChainMetadataUploadDetails, setOffChainMetadataUploadDetails] = const [offChainMetadataUploadDetails, setOffChainMetadataUploadDetails] =
@ -116,29 +130,19 @@ export const OpenEditionMinterCreator = ({
const [coverImageUrl, setCoverImageUrl] = useState<string | null>(null) const [coverImageUrl, setCoverImageUrl] = useState<string | null>(null)
const [openEditionMinterContractAddress, setOpenEditionMinterContractAddress] = useState<string | null>(null) const [openEditionMinterContractAddress, setOpenEditionMinterContractAddress] = useState<string | null>(null)
const [sg721ContractAddress, setSg721ContractAddress] = 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 [transactionHash, setTransactionHash] = useState<string | null>(null)
const [thumbnailImageUri, setThumbnailImageUri] = useState<string | undefined>(undefined) const [thumbnailImageUri, setThumbnailImageUri] = useState<string | undefined>(undefined)
const thumbnailCompatibleAssetTypes: AssetType[] = ['video', 'audio', 'html'] const thumbnailCompatibleAssetTypes: AssetType[] = ['video', 'audio', 'html']
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 openEditionFactoryMessages = useMemo( const openEditionFactoryMessages = useMemo(
() => () => openEditionFactoryContract?.use(openEditionFactoryAddress as string),
openEditionFactoryContract?.use(
collectionDetails?.updatable ? updatableFactoryAddressForSelectedDenom : factoryAddressForSelectedDenom,
),
[ [
openEditionFactoryContract, openEditionFactoryContract,
wallet.address, wallet.address,
collectionDetails?.updatable, collectionDetails?.updatable,
factoryAddressForSelectedDenom, openEditionFactoryAddress,
updatableFactoryAddressForSelectedDenom,
wallet.isWalletConnected, wallet.isWalletConnected,
], ],
) )
@ -152,13 +156,26 @@ export const OpenEditionMinterCreator = ({
.then(() => { .then(() => {
void checkRoyaltyDetails() void checkRoyaltyDetails()
.then(() => { .then(() => {
void checkwalletBalance() checkWhitelistDetails()
.then(() => { .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) => { .catch((error) => {
toast.error(`Error in Wallet Balance: ${error.message}`, { style: { maxWidth: 'none' } }) if (String(error.message).includes('Insufficient wallet balance')) {
addLogItem({ id: uid(), message: error.message, type: 'Error', timestamp: new Date() }) 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) setReadyToCreate(false)
}) })
}) })
@ -299,9 +316,9 @@ export const OpenEditionMinterCreator = ({
if (!mintingDetails) throw new Error('Please fill out the minting details') if (!mintingDetails) throw new Error('Please fill out the minting details')
if (mintingDetails.unitPrice === '') throw new Error('Mint price is required') if (mintingDetails.unitPrice === '') throw new Error('Mint price is required')
if (collectionDetails?.updatable) { if (collectionDetails?.updatable) {
if (Number(mintingDetails.unitPrice) < Number(minimumUpdatableMintPrice)) if (Number(mintingDetails.unitPrice) < Number(minimumMintPrice))
throw new Error( 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 mintTokenFromFactory?.displayName
}`, }`,
) )
@ -314,11 +331,27 @@ export const OpenEditionMinterCreator = ({
if (!mintingDetails.perAddressLimit || mintingDetails.perAddressLimit < 1 || mintingDetails.perAddressLimit > 50) if (!mintingDetails.perAddressLimit || mintingDetails.perAddressLimit < 1 || mintingDetails.perAddressLimit > 50)
throw new Error('Invalid limit for tokens per address') throw new Error('Invalid limit for tokens per address')
if (mintingDetails.startTime === '') throw new Error('Start time is required') if (mintingDetails.startTime === '') throw new Error('Start time is required')
if (mintingDetails.endTime === '') throw new Error('End 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 (Number(mintingDetails.startTime) < new Date().getTime() * 1000000) throw new Error('Invalid start time')
if (Number(mintingDetails.endTime) < Number(mintingDetails.startTime)) if (
mintingDetails.limitType === 'time_limited' &&
Number(mintingDetails.endTime) < Number(mintingDetails.startTime)
)
throw new Error('End time cannot be earlier than start time') throw new Error('End time cannot be earlier than start time')
if (Number(mintingDetails.endTime) === Number(mintingDetails.startTime)) if (
mintingDetails.limitType === 'time_limited' &&
Number(mintingDetails.endTime) === Number(mintingDetails.startTime)
)
throw new Error('End time cannot be equal to the start time') throw new Error('End time cannot be equal to the start time')
if ( if (
@ -326,6 +359,92 @@ export const OpenEditionMinterCreator = ({
(!isValidAddress(mintingDetails.paymentAddress) || !mintingDetails.paymentAddress.startsWith('stars1')) (!isValidAddress(mintingDetails.paymentAddress) || !mintingDetails.paymentAddress.startsWith('stars1'))
) )
throw new Error('Invalid payment address') 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 () => { const checkRoyaltyDetails = async () => {
@ -362,16 +481,45 @@ export const OpenEditionMinterCreator = ({
const checkwalletBalance = async () => { const checkwalletBalance = async () => {
if (!wallet.isWalletConnected) throw new Error('Wallet not connected.') if (!wallet.isWalletConnected) throw new Error('Wallet not connected.')
const amountNeeded = collectionDetails?.updatable const queryClient = await wallet.getCosmWasmClient()
? Number(openEditionMinterUpdatableCreationFee) const creationFeeDenom = tokensList.find((token) => token.denom === openEditionMinterCreationFee?.denom)
: Number(openEditionMinterCreationFee) await queryClient.getBalance(wallet.address || '', 'ustars').then(async (starsBalance) => {
await (await wallet.getCosmWasmClient()).getBalance(wallet.address || '', 'ustars').then((balance) => { await queryClient
if (amountNeeded >= Number(balance.amount)) .getBalance(wallet.address || '', openEditionMinterCreationFee?.denom as string)
throw new Error( .then((creationFeeDenomBalance) => {
`Insufficient wallet balance to instantiate the required contracts. Needed amount: ${( if (whitelistDetails?.whitelistState === 'new' && whitelistDetails.memberLimit) {
amountNeeded / 1000000 const whitelistCreationFee = Math.ceil(Number(whitelistDetails.memberLimit) / 1000) * 100000000
).toString()} STARS`, 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}`,
)
})
}) })
} }
@ -383,6 +531,7 @@ export const OpenEditionMinterCreator = ({
setTokenImageUri(null) setTokenImageUri(null)
setOpenEditionMinterContractAddress(null) setOpenEditionMinterContractAddress(null)
setSg721ContractAddress(null) setSg721ContractAddress(null)
setWhitelistContractAddress(null)
setTransactionHash(null) setTransactionHash(null)
if (metadataStorageMethod === 'off-chain') { if (metadataStorageMethod === 'off-chain') {
if (offChainMetadataUploadDetails?.uploadMethod === 'new') { if (offChainMetadataUploadDetails?.uploadMethod === 'new') {
@ -407,13 +556,27 @@ export const OpenEditionMinterCreator = ({
setTokenUri(metadataUriWithBase) setTokenUri(metadataUriWithBase)
setCoverImageUrl(coverImageUriWithBase) setCoverImageUrl(coverImageUriWithBase)
setUploading(false) 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 { } else {
setTokenUri(offChainMetadataUploadDetails?.tokenURI as string) setTokenUri(offChainMetadataUploadDetails?.tokenURI as string)
setCoverImageUrl(offChainMetadataUploadDetails?.imageUrl 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( await instantiateOpenEditionMinter(
offChainMetadataUploadDetails?.tokenURI as string, offChainMetadataUploadDetails?.tokenURI as string,
offChainMetadataUploadDetails?.imageUrl as string, offChainMetadataUploadDetails?.imageUrl as string,
undefined,
whitelist,
) )
} }
} else if (metadataStorageMethod === 'on-chain') { } else if (metadataStorageMethod === 'on-chain') {
@ -455,15 +618,27 @@ export const OpenEditionMinterCreator = ({
? `ipfs://${thumbnailUri}/${(imageUploadDetails.thumbnailFile as File).name}` ? `ipfs://${thumbnailUri}/${(imageUploadDetails.thumbnailFile as File).name}`
: undefined : undefined
setThumbnailImageUri(thumbnailUriWithBase) setThumbnailImageUri(thumbnailUriWithBase)
setUploading(false) setUploading(false)
await instantiateOpenEditionMinter(imageUriWithBase, coverImageUriWithBase, thumbnailUriWithBase)
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') { } else if (imageUploadDetails?.uploadMethod === 'existing') {
setTokenImageUri(imageUploadDetails.imageUrl as string) setTokenImageUri(imageUploadDetails.imageUrl as string)
setCoverImageUrl(imageUploadDetails.coverImageUrl 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( await instantiateOpenEditionMinter(
imageUploadDetails.imageUrl as string, imageUploadDetails.imageUrl as string,
imageUploadDetails.coverImageUrl as string, imageUploadDetails.coverImageUrl as string,
whitelist,
) )
} }
} }
@ -562,7 +737,104 @@ export const OpenEditionMinterCreator = ({
}) })
} }
const instantiateOpenEditionMinter = async (uri: string, coverImageUri: string, thumbnailUri?: string) => { 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 (!wallet.isWalletConnected) throw new Error('Wallet not connected')
if (!openEditionFactoryContract) throw new Error('Contract not found') if (!openEditionFactoryContract) throw new Error('Contract not found')
if (!openEditionMinterContract) throw new Error('Contract not found') if (!openEditionMinterContract) throw new Error('Contract not found')
@ -603,19 +875,30 @@ export const OpenEditionMinterCreator = ({
: null, : null,
}, },
start_time: mintingDetails?.startTime, 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: { mint_price: {
amount: Number(mintingDetails?.unitPrice).toString(), amount: Number(mintingDetails?.unitPrice).toString(),
denom: (mintTokenFromFactory?.denom as string) || 'ustars', denom: (mintTokenFromFactory?.denom as string) || 'ustars',
}, },
per_address_limit: mintingDetails?.perAddressLimit, 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, payment_address: mintingDetails?.paymentAddress || null,
whitelist,
}, },
collection_params: { collection_params: {
code_id: collectionDetails?.updatable code_id: collectionDetails?.updatable
? SG721_OPEN_EDITION_UPDATABLE_CODE_ID ? SG721_OPEN_EDITION_UPDATABLE_CODE_ID
: mintingDetails?.selectedMintToken?.displayName === 'USK' || : mintingDetails?.selectedMintToken?.displayName === 'USK' ||
mintingDetails?.selectedMintToken?.displayName === 'USDC' || mintingDetails?.selectedMintToken?.displayName === 'USDC' ||
mintingDetails?.selectedMintToken?.displayName === 'TIA' ||
mintingDetails?.selectedMintToken?.displayName === 'STRDST' || mintingDetails?.selectedMintToken?.displayName === 'STRDST' ||
mintingDetails?.selectedMintToken?.displayName === 'KUJI' || mintingDetails?.selectedMintToken?.displayName === 'KUJI' ||
mintingDetails?.selectedMintToken?.displayName === 'HUAHUA' || mintingDetails?.selectedMintToken?.displayName === 'HUAHUA' ||
@ -638,18 +921,11 @@ export const OpenEditionMinterCreator = ({
} }
const payload: OpenEditionFactoryDispatchExecuteArgs = { const payload: OpenEditionFactoryDispatchExecuteArgs = {
contract: collectionDetails?.updatable ? updatableFactoryAddressForSelectedDenom : factoryAddressForSelectedDenom, contract: openEditionFactoryAddress as string,
messages: openEditionFactoryMessages, messages: openEditionFactoryMessages,
txSigner: wallet.address || '', txSigner: wallet.address || '',
msg, msg,
funds: [ funds: [openEditionMinterCreationFee as Coin],
coin(
collectionDetails?.updatable
? (openEditionMinterUpdatableCreationFee as string)
: (openEditionMinterCreationFee as string),
'ustars',
),
],
updatable: collectionDetails?.updatable, updatable: collectionDetails?.updatable,
} }
await openEditionFactoryDispatchExecute(payload) await openEditionFactoryDispatchExecute(payload)
@ -680,16 +956,24 @@ export const OpenEditionMinterCreator = ({
metadataStorageMethod, metadataStorageMethod,
openEditionMinterContractAddress, openEditionMinterContractAddress,
sg721ContractAddress, sg721ContractAddress,
whitelistContractAddress,
transactionHash, transactionHash,
} }
onChange(data) onChange(data)
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [metadataStorageMethod, openEditionMinterContractAddress, sg721ContractAddress, transactionHash]) }, [
metadataStorageMethod,
openEditionMinterContractAddress,
sg721ContractAddress,
whitelistContractAddress,
transactionHash,
])
useEffect(() => { useEffect(() => {
const data: OpenEditionMinterDetailsDataProps = { const data: OpenEditionMinterDetailsDataProps = {
imageUploadDetails: imageUploadDetails ? imageUploadDetails : undefined, imageUploadDetails: imageUploadDetails ? imageUploadDetails : undefined,
collectionDetails: collectionDetails ? collectionDetails : undefined, collectionDetails: collectionDetails ? collectionDetails : undefined,
whitelistDetails: whitelistDetails ? whitelistDetails : undefined,
royaltyDetails: royaltyDetails ? royaltyDetails : undefined, royaltyDetails: royaltyDetails ? royaltyDetails : undefined,
onChainMetadataInputDetails: onChainMetadataInputDetails ? onChainMetadataInputDetails : undefined, onChainMetadataInputDetails: onChainMetadataInputDetails ? onChainMetadataInputDetails : undefined,
offChainMetadataUploadDetails: offChainMetadataUploadDetails ? offChainMetadataUploadDetails : undefined, offChainMetadataUploadDetails: offChainMetadataUploadDetails ? offChainMetadataUploadDetails : undefined,
@ -699,12 +983,14 @@ export const OpenEditionMinterCreator = ({
coverImageUrl, coverImageUrl,
tokenUri, tokenUri,
tokenImageUri, tokenImageUri,
isRefreshed,
} }
onDetailsChange(data) onDetailsChange(data)
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [ }, [
imageUploadDetails, imageUploadDetails,
collectionDetails, collectionDetails,
whitelistDetails,
royaltyDetails, royaltyDetails,
onChainMetadataInputDetails, onChainMetadataInputDetails,
offChainMetadataUploadDetails, offChainMetadataUploadDetails,
@ -714,6 +1000,7 @@ export const OpenEditionMinterCreator = ({
coverImageUrl, coverImageUrl,
tokenUri, tokenUri,
tokenImageUri, tokenImageUri,
isRefreshed,
]) ])
useEffect(() => { useEffect(() => {
@ -722,6 +1009,40 @@ export const OpenEditionMinterCreator = ({
} }
}, [importedOpenEditionMinterDetails]) }, [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 ( return (
<div> <div>
{/* TODO: Cancel once we're able to index on-chain metadata */} {/* TODO: Cancel once we're able to index on-chain metadata */}
@ -810,16 +1131,23 @@ export const OpenEditionMinterCreator = ({
/> />
<MintingDetails <MintingDetails
importedMintingDetails={importedOpenEditionMinterDetails?.mintingDetails} importedMintingDetails={importedOpenEditionMinterDetails?.mintingDetails}
minimumMintPrice={ isPresale={whitelistDetails?.whitelistState === 'new'}
collectionDetails?.updatable minimumMintPrice={Number(minimumMintPrice) / 1000000}
? Number(minimumUpdatableMintPrice) / 1000000
: Number(minimumMintPrice) / 1000000
}
mintTokenFromFactory={mintTokenFromFactory} mintTokenFromFactory={mintTokenFromFactory}
onChange={setMintingDetails} onChange={setMintingDetails}
uploadMethod={offChainMetadataUploadDetails?.uploadMethod as UploadMethod} uploadMethod={offChainMetadataUploadDetails?.uploadMethod as UploadMethod}
whitelistStartDate={whitelistDetails?.startTime}
/> />
</div> </div>
<div className="my-6 mx-10">
<WhitelistDetails
importedWhitelistDetails={importedOpenEditionMinterDetails?.whitelistDetails}
mintingTokenFromFactory={mintTokenFromFactory}
onChange={setWhitelistDetails}
/>
</div>
<div className="my-6"> <div className="my-6">
<RoyaltyDetails <RoyaltyDetails
importedRoyaltyDetails={importedOpenEditionMinterDetails?.royaltyDetails} importedRoyaltyDetails={importedOpenEditionMinterDetails?.royaltyDetails}

View 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>
)
}

View File

@ -1,14 +1,24 @@
import { import {
FEATURED_IBC_TIA_FACTORY_ADDRESS,
FEATURED_IBC_USDC_FACTORY_ADDRESS,
FEATURED_VENDING_FACTORY_ADDRESS, FEATURED_VENDING_FACTORY_ADDRESS,
FEATURED_VENDING_FACTORY_FLEX_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_ADDRESS,
OPEN_EDITION_FACTORY_FLEX_ADDRESS,
OPEN_EDITION_IBC_ATOM_FACTORY_ADDRESS, OPEN_EDITION_IBC_ATOM_FACTORY_ADDRESS,
OPEN_EDITION_IBC_ATOM_FACTORY_FLEX_ADDRESS,
OPEN_EDITION_IBC_CRBRUS_FACTORY_ADDRESS, OPEN_EDITION_IBC_CRBRUS_FACTORY_ADDRESS,
OPEN_EDITION_IBC_FRNZ_FACTORY_ADDRESS, OPEN_EDITION_IBC_FRNZ_FACTORY_ADDRESS,
OPEN_EDITION_IBC_HUAHUA_FACTORY_ADDRESS,
OPEN_EDITION_IBC_KUJI_FACTORY_ADDRESS, OPEN_EDITION_IBC_KUJI_FACTORY_ADDRESS,
OPEN_EDITION_IBC_NBTC_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_ADDRESS,
OPEN_EDITION_IBC_USDC_FACTORY_FLEX_ADDRESS,
OPEN_EDITION_IBC_USK_FACTORY_ADDRESS, OPEN_EDITION_IBC_USK_FACTORY_ADDRESS,
OPEN_EDITION_NATIVE_BRNCH_FACTORY_ADDRESS, OPEN_EDITION_NATIVE_BRNCH_FACTORY_ADDRESS,
OPEN_EDITION_NATIVE_STRDST_FACTORY_ADDRESS, OPEN_EDITION_NATIVE_STRDST_FACTORY_ADDRESS,
@ -16,10 +26,12 @@ import {
OPEN_EDITION_UPDATABLE_IBC_ATOM_FACTORY_ADDRESS, OPEN_EDITION_UPDATABLE_IBC_ATOM_FACTORY_ADDRESS,
OPEN_EDITION_UPDATABLE_IBC_FRNZ_FACTORY_ADDRESS, OPEN_EDITION_UPDATABLE_IBC_FRNZ_FACTORY_ADDRESS,
OPEN_EDITION_UPDATABLE_IBC_NBTC_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_USDC_FACTORY_ADDRESS,
OPEN_EDITION_UPDATABLE_IBC_USK_FACTORY_ADDRESS, OPEN_EDITION_UPDATABLE_IBC_USK_FACTORY_ADDRESS,
VENDING_FACTORY_ADDRESS, VENDING_FACTORY_ADDRESS,
VENDING_FACTORY_FLEX_ADDRESS, VENDING_FACTORY_FLEX_ADDRESS,
VENDING_FACTORY_MERKLE_TREE_ADDRESS,
VENDING_FACTORY_UPDATABLE_ADDRESS, VENDING_FACTORY_UPDATABLE_ADDRESS,
VENDING_FACTORY_UPDATABLE_FLEX_ADDRESS, VENDING_FACTORY_UPDATABLE_FLEX_ADDRESS,
VENDING_IBC_ATOM_FACTORY_ADDRESS, VENDING_IBC_ATOM_FACTORY_ADDRESS,
@ -28,14 +40,17 @@ import {
VENDING_IBC_ATOM_UPDATABLE_FACTORY_FLEX_ADDRESS, VENDING_IBC_ATOM_UPDATABLE_FACTORY_FLEX_ADDRESS,
VENDING_IBC_CRBRUS_FACTORY_ADDRESS, VENDING_IBC_CRBRUS_FACTORY_ADDRESS,
VENDING_IBC_CRBRUS_FACTORY_FLEX_ADDRESS, VENDING_IBC_CRBRUS_FACTORY_FLEX_ADDRESS,
VENDING_IBC_HUAHUA_FACTORY_ADDRESS,
VENDING_IBC_HUAHUA_FACTORY_FLEX_ADDRESS,
VENDING_IBC_KUJI_FACTORY_ADDRESS, VENDING_IBC_KUJI_FACTORY_ADDRESS,
VENDING_IBC_KUJI_FACTORY_FLEX_ADDRESS, VENDING_IBC_KUJI_FACTORY_FLEX_ADDRESS,
VENDING_IBC_NBTC_FACTORY_ADDRESS, VENDING_IBC_NBTC_FACTORY_ADDRESS,
VENDING_IBC_NBTC_FACTORY_FLEX_ADDRESS, VENDING_IBC_NBTC_FACTORY_FLEX_ADDRESS,
VENDING_IBC_NBTC_UPDATABLE_FACTORY_ADDRESS, VENDING_IBC_NBTC_UPDATABLE_FACTORY_ADDRESS,
VENDING_IBC_NBTC_UPDATABLE_FACTORY_FLEX_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_ADDRESS,
VENDING_IBC_USDC_FACTORY_FLEX_ADDRESS, VENDING_IBC_USDC_FACTORY_FLEX_ADDRESS,
VENDING_IBC_USDC_UPDATABLE_FACTORY_ADDRESS, VENDING_IBC_USDC_UPDATABLE_FACTORY_ADDRESS,
@ -57,9 +72,10 @@ import {
ibcAtom, ibcAtom,
ibcCrbrus, ibcCrbrus,
ibcFrnz, ibcFrnz,
ibcHuahua, // ibcHuahua,
ibcKuji, ibcKuji,
ibcNbtc, ibcNbtc,
ibcTia,
ibcUsdc, ibcUsdc,
ibcUsk, ibcUsk,
nativeBrnch, nativeBrnch,
@ -73,6 +89,7 @@ export interface MinterInfo {
supportedToken: TokenInfo supportedToken: TokenInfo
updatable?: boolean updatable?: boolean
flexible?: boolean flexible?: boolean
merkleTree?: boolean
featured?: boolean featured?: boolean
} }
@ -82,6 +99,7 @@ export const openEditionStarsMinter: MinterInfo = {
supportedToken: stars, supportedToken: stars,
updatable: false, updatable: false,
featured: false, featured: false,
flexible: false,
} }
export const openEditionUpdatableStarsMinter: MinterInfo = { export const openEditionUpdatableStarsMinter: MinterInfo = {
@ -90,6 +108,7 @@ export const openEditionUpdatableStarsMinter: MinterInfo = {
supportedToken: stars, supportedToken: stars,
updatable: true, updatable: true,
featured: false, featured: false,
flexible: false,
} }
export const openEditionIbcAtomMinter: MinterInfo = { export const openEditionIbcAtomMinter: MinterInfo = {
@ -98,6 +117,7 @@ export const openEditionIbcAtomMinter: MinterInfo = {
supportedToken: ibcAtom, supportedToken: ibcAtom,
updatable: false, updatable: false,
featured: false, featured: false,
flexible: false,
} }
export const openEditionUpdatableIbcAtomMinter: MinterInfo = { export const openEditionUpdatableIbcAtomMinter: MinterInfo = {
@ -106,6 +126,7 @@ export const openEditionUpdatableIbcAtomMinter: MinterInfo = {
supportedToken: ibcAtom, supportedToken: ibcAtom,
updatable: true, updatable: true,
featured: false, featured: false,
flexible: false,
} }
export const openEditionIbcUsdcMinter: MinterInfo = { export const openEditionIbcUsdcMinter: MinterInfo = {
@ -114,6 +135,16 @@ export const openEditionIbcUsdcMinter: MinterInfo = {
supportedToken: ibcUsdc, supportedToken: ibcUsdc,
updatable: false, updatable: false,
featured: 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 = { export const openEditionIbcNbtcMinter: MinterInfo = {
@ -122,6 +153,7 @@ export const openEditionIbcNbtcMinter: MinterInfo = {
supportedToken: ibcNbtc, supportedToken: ibcNbtc,
updatable: false, updatable: false,
featured: false, featured: false,
flexible: false,
} }
export const openEditionUpdatableIbcUsdcMinter: MinterInfo = { export const openEditionUpdatableIbcUsdcMinter: MinterInfo = {
@ -130,6 +162,16 @@ export const openEditionUpdatableIbcUsdcMinter: MinterInfo = {
supportedToken: ibcUsdc, supportedToken: ibcUsdc,
updatable: true, updatable: true,
featured: false, 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 = { export const openEditionUpdatableIbcNbtcMinter: MinterInfo = {
@ -138,6 +180,7 @@ export const openEditionUpdatableIbcNbtcMinter: MinterInfo = {
supportedToken: ibcNbtc, supportedToken: ibcNbtc,
updatable: true, updatable: true,
featured: false, featured: false,
flexible: false,
} }
export const openEditionIbcFrnzMinter: MinterInfo = { export const openEditionIbcFrnzMinter: MinterInfo = {
@ -146,6 +189,7 @@ export const openEditionIbcFrnzMinter: MinterInfo = {
supportedToken: ibcFrnz, supportedToken: ibcFrnz,
updatable: false, updatable: false,
featured: false, featured: false,
flexible: false,
} }
export const openEditionUpdatableIbcFrnzMinter: MinterInfo = { export const openEditionUpdatableIbcFrnzMinter: MinterInfo = {
@ -154,6 +198,7 @@ export const openEditionUpdatableIbcFrnzMinter: MinterInfo = {
supportedToken: ibcFrnz, supportedToken: ibcFrnz,
updatable: true, updatable: true,
featured: false, featured: false,
flexible: false,
} }
export const openEditionIbcUskMinter: MinterInfo = { export const openEditionIbcUskMinter: MinterInfo = {
@ -162,6 +207,7 @@ export const openEditionIbcUskMinter: MinterInfo = {
supportedToken: ibcUsk, supportedToken: ibcUsk,
updatable: false, updatable: false,
featured: false, featured: false,
flexible: false,
} }
export const openEditionUpdatableIbcUskMinter: MinterInfo = { export const openEditionUpdatableIbcUskMinter: MinterInfo = {
@ -170,6 +216,7 @@ export const openEditionUpdatableIbcUskMinter: MinterInfo = {
supportedToken: ibcUsk, supportedToken: ibcUsk,
updatable: true, updatable: true,
featured: false, featured: false,
flexible: false,
} }
export const openEditionIbcKujiMinter: MinterInfo = { export const openEditionIbcKujiMinter: MinterInfo = {
@ -178,15 +225,16 @@ export const openEditionIbcKujiMinter: MinterInfo = {
supportedToken: ibcKuji, supportedToken: ibcKuji,
updatable: false, updatable: false,
featured: false, featured: false,
flexible: false,
} }
export const openEditionIbcHuahuaMinter: MinterInfo = { // export const openEditionIbcHuahuaMinter: MinterInfo = {
id: 'open-edition-ibc-huahua-minter', // id: 'open-edition-ibc-huahua-minter',
factoryAddress: OPEN_EDITION_IBC_HUAHUA_FACTORY_ADDRESS, // factoryAddress: OPEN_EDITION_IBC_HUAHUA_FACTORY_ADDRESS,
supportedToken: ibcHuahua, // supportedToken: ibcHuahua,
updatable: false, // updatable: false,
featured: false, // featured: false,
} // }
export const openEditionIbcCrbrusMinter: MinterInfo = { export const openEditionIbcCrbrusMinter: MinterInfo = {
id: 'open-edition-ibc-crbrus-minter', id: 'open-edition-ibc-crbrus-minter',
@ -194,6 +242,7 @@ export const openEditionIbcCrbrusMinter: MinterInfo = {
supportedToken: ibcCrbrus, supportedToken: ibcCrbrus,
updatable: false, updatable: false,
featured: false, featured: false,
flexible: false,
} }
export const openEditionNativeStrdstMinter: MinterInfo = { export const openEditionNativeStrdstMinter: MinterInfo = {
@ -202,6 +251,7 @@ export const openEditionNativeStrdstMinter: MinterInfo = {
supportedToken: nativeStardust, supportedToken: nativeStardust,
updatable: false, updatable: false,
featured: false, featured: false,
flexible: false,
} }
export const openEditionNativeBrnchMinter: MinterInfo = { export const openEditionNativeBrnchMinter: MinterInfo = {
@ -210,6 +260,7 @@ export const openEditionNativeBrnchMinter: MinterInfo = {
supportedToken: nativeBrnch, supportedToken: nativeBrnch,
updatable: false, updatable: false,
featured: false, featured: false,
flexible: false,
} }
export const openEditionMinterList = [ export const openEditionMinterList = [
@ -221,23 +272,69 @@ export const openEditionMinterList = [
openEditionUpdatableIbcFrnzMinter, openEditionUpdatableIbcFrnzMinter,
openEditionIbcUsdcMinter, openEditionIbcUsdcMinter,
openEditionUpdatableIbcUsdcMinter, openEditionUpdatableIbcUsdcMinter,
openEditionIbcTiaMinter,
openEditionUpdatableIbcTiaMinter,
openEditionIbcNbtcMinter, openEditionIbcNbtcMinter,
openEditionUpdatableIbcNbtcMinter, openEditionUpdatableIbcNbtcMinter,
openEditionIbcUskMinter, openEditionIbcUskMinter,
openEditionUpdatableIbcUskMinter, openEditionUpdatableIbcUskMinter,
openEditionIbcKujiMinter, openEditionIbcKujiMinter,
openEditionIbcHuahuaMinter, // openEditionIbcHuahuaMinter,
openEditionIbcCrbrusMinter, openEditionIbcCrbrusMinter,
openEditionNativeStrdstMinter, openEditionNativeStrdstMinter,
openEditionNativeBrnchMinter, 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 = { export const vendingStarsMinter: MinterInfo = {
id: 'vending-stars-minter', id: 'vending-stars-minter',
factoryAddress: VENDING_FACTORY_ADDRESS, factoryAddress: VENDING_FACTORY_ADDRESS,
supportedToken: stars, supportedToken: stars,
updatable: false, updatable: false,
flexible: false, flexible: false,
merkleTree: false,
featured: false, featured: false,
} }
@ -247,6 +344,7 @@ export const vendingFeaturedStarsMinter: MinterInfo = {
supportedToken: stars, supportedToken: stars,
updatable: false, updatable: false,
flexible: false, flexible: false,
merkleTree: false,
featured: true, featured: true,
} }
@ -256,6 +354,7 @@ export const vendingUpdatableStarsMinter: MinterInfo = {
supportedToken: stars, supportedToken: stars,
updatable: true, updatable: true,
flexible: false, flexible: false,
merkleTree: false,
featured: false, featured: false,
} }
@ -265,6 +364,7 @@ export const vendingIbcAtomMinter: MinterInfo = {
supportedToken: ibcAtom, supportedToken: ibcAtom,
updatable: false, updatable: false,
flexible: false, flexible: false,
merkleTree: false,
featured: false, featured: false,
} }
@ -274,6 +374,7 @@ export const vendingUpdatableIbcAtomMinter: MinterInfo = {
supportedToken: ibcAtom, supportedToken: ibcAtom,
updatable: true, updatable: true,
flexible: false, flexible: false,
merkleTree: false,
featured: false, featured: false,
} }
@ -283,15 +384,47 @@ export const vendingIbcUsdcMinter: MinterInfo = {
supportedToken: ibcUsdc, supportedToken: ibcUsdc,
updatable: false, updatable: false,
flexible: false, flexible: false,
merkleTree: false,
featured: 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 = { export const vendingIbcNbtcMinter: MinterInfo = {
id: 'vending-ibc-nbtc-minter', id: 'vending-ibc-nbtc-minter',
factoryAddress: VENDING_IBC_NBTC_FACTORY_ADDRESS, factoryAddress: VENDING_IBC_NBTC_FACTORY_ADDRESS,
supportedToken: ibcNbtc, supportedToken: ibcNbtc,
updatable: false, updatable: false,
flexible: false, flexible: false,
merkleTree: false,
featured: false, featured: false,
} }
@ -301,6 +434,17 @@ export const vendingUpdatableIbcUsdcMinter: MinterInfo = {
supportedToken: ibcUsdc, supportedToken: ibcUsdc,
updatable: true, updatable: true,
flexible: false, 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, featured: false,
} }
@ -310,6 +454,7 @@ export const vendingUpdatableIbcNbtcMinter: MinterInfo = {
supportedToken: ibcNbtc, supportedToken: ibcNbtc,
updatable: true, updatable: true,
flexible: false, flexible: false,
merkleTree: false,
featured: false, featured: false,
} }
@ -319,6 +464,7 @@ export const vendingIbcUskMinter: MinterInfo = {
supportedToken: ibcUsk, supportedToken: ibcUsk,
updatable: false, updatable: false,
flexible: false, flexible: false,
merkleTree: false,
featured: false, featured: false,
} }
@ -328,6 +474,7 @@ export const vendingUpdatableIbcUskMinter: MinterInfo = {
supportedToken: ibcUsk, supportedToken: ibcUsk,
updatable: true, updatable: true,
flexible: false, flexible: false,
merkleTree: false,
featured: false, featured: false,
} }
@ -337,17 +484,19 @@ export const vendingIbcKujiMinter: MinterInfo = {
supportedToken: ibcKuji, supportedToken: ibcKuji,
updatable: false, updatable: false,
flexible: false, flexible: false,
merkleTree: false,
featured: false, featured: false,
} }
export const vendingIbcHuahuaMinter: MinterInfo = { // export const vendingIbcHuahuaMinter: MinterInfo = {
id: 'vending-ibc-huahua-minter', // id: 'vending-ibc-huahua-minter',
factoryAddress: VENDING_IBC_HUAHUA_FACTORY_ADDRESS, // factoryAddress: VENDING_IBC_HUAHUA_FACTORY_ADDRESS,
supportedToken: ibcHuahua, // supportedToken: ibcHuahua,
updatable: false, // updatable: false,
flexible: false, // flexible: false,
featured: false, // merkleTree: false,
} // featured: false,
// }
export const vendingIbcCrbrusMinter: MinterInfo = { export const vendingIbcCrbrusMinter: MinterInfo = {
id: 'vending-ibc-crbrus-minter', id: 'vending-ibc-crbrus-minter',
@ -355,6 +504,7 @@ export const vendingIbcCrbrusMinter: MinterInfo = {
supportedToken: ibcCrbrus, supportedToken: ibcCrbrus,
updatable: false, updatable: false,
flexible: false, flexible: false,
merkleTree: false,
featured: false, featured: false,
} }
@ -364,6 +514,7 @@ export const vendingNativeStardustMinter: MinterInfo = {
supportedToken: nativeStardust, supportedToken: nativeStardust,
updatable: false, updatable: false,
flexible: false, flexible: false,
merkleTree: false,
featured: false, featured: false,
} }
@ -373,6 +524,7 @@ export const vendingUpdatableNativeStardustMinter: MinterInfo = {
supportedToken: nativeStardust, supportedToken: nativeStardust,
updatable: true, updatable: true,
flexible: false, flexible: false,
merkleTree: false,
featured: false, featured: false,
} }
@ -382,6 +534,7 @@ export const vendingNativeBrnchMinter: MinterInfo = {
supportedToken: nativeBrnch, supportedToken: nativeBrnch,
updatable: false, updatable: false,
flexible: false, flexible: false,
merkleTree: false,
featured: false, featured: false,
} }
@ -391,6 +544,7 @@ export const vendingUpdatableNativeBrnchMinter: MinterInfo = {
supportedToken: nativeBrnch, supportedToken: nativeBrnch,
updatable: true, updatable: true,
flexible: false, flexible: false,
merkleTree: false,
featured: false, featured: false,
} }
@ -401,13 +555,17 @@ export const vendingMinterList = [
vendingIbcAtomMinter, vendingIbcAtomMinter,
vendingUpdatableIbcAtomMinter, vendingUpdatableIbcAtomMinter,
vendingIbcUsdcMinter, vendingIbcUsdcMinter,
vendingFeaturedIbcUsdcMinter,
vendingUpdatableIbcUsdcMinter, vendingUpdatableIbcUsdcMinter,
vendingIbcTiaMinter,
vendingFeaturedIbcTiaMinter,
vendingUpdatableIbcTiaMinter,
vendingIbcNbtcMinter, vendingIbcNbtcMinter,
vendingUpdatableIbcNbtcMinter, vendingUpdatableIbcNbtcMinter,
vendingIbcUskMinter, vendingIbcUskMinter,
vendingUpdatableIbcUskMinter, vendingUpdatableIbcUskMinter,
vendingIbcKujiMinter, vendingIbcKujiMinter,
vendingIbcHuahuaMinter, // vendingIbcHuahuaMinter,
vendingIbcCrbrusMinter, vendingIbcCrbrusMinter,
vendingNativeStardustMinter, vendingNativeStardustMinter,
vendingUpdatableNativeStardustMinter, vendingUpdatableNativeStardustMinter,
@ -421,6 +579,7 @@ export const flexibleVendingStarsMinter: MinterInfo = {
supportedToken: stars, supportedToken: stars,
updatable: false, updatable: false,
flexible: true, flexible: true,
merkleTree: false,
featured: false, featured: false,
} }
@ -430,6 +589,7 @@ export const flexibleFeaturedVendingStarsMinter: MinterInfo = {
supportedToken: stars, supportedToken: stars,
updatable: false, updatable: false,
flexible: true, flexible: true,
merkleTree: false,
featured: true, featured: true,
} }
@ -439,6 +599,7 @@ export const flexibleVendingUpdatableStarsMinter: MinterInfo = {
supportedToken: stars, supportedToken: stars,
updatable: true, updatable: true,
flexible: true, flexible: true,
merkleTree: false,
featured: false, featured: false,
} }
@ -448,6 +609,7 @@ export const flexibleVendingIbcAtomMinter: MinterInfo = {
supportedToken: ibcAtom, supportedToken: ibcAtom,
updatable: false, updatable: false,
flexible: true, flexible: true,
merkleTree: false,
featured: false, featured: false,
} }
@ -457,6 +619,7 @@ export const flexibleVendingUpdatableIbcAtomMinter: MinterInfo = {
supportedToken: ibcAtom, supportedToken: ibcAtom,
updatable: true, updatable: true,
flexible: true, flexible: true,
merkleTree: false,
featured: false, featured: false,
} }
@ -466,15 +629,47 @@ export const flexibleVendingIbcUsdcMinter: MinterInfo = {
supportedToken: ibcUsdc, supportedToken: ibcUsdc,
updatable: false, updatable: false,
flexible: true, flexible: true,
merkleTree: false,
featured: 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 = { export const flexibleVendingIbcNbtcMinter: MinterInfo = {
id: 'flexible-vending-ibc-nbtc-minter', id: 'flexible-vending-ibc-nbtc-minter',
factoryAddress: VENDING_IBC_NBTC_FACTORY_FLEX_ADDRESS, factoryAddress: VENDING_IBC_NBTC_FACTORY_FLEX_ADDRESS,
supportedToken: ibcNbtc, supportedToken: ibcNbtc,
updatable: false, updatable: false,
flexible: true, flexible: true,
merkleTree: false,
featured: false, featured: false,
} }
@ -484,6 +679,17 @@ export const flexibleVendingUpdatableIbcUsdcMinter: MinterInfo = {
supportedToken: ibcUsdc, supportedToken: ibcUsdc,
updatable: true, updatable: true,
flexible: 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, featured: false,
} }
@ -493,6 +699,7 @@ export const flexibleVendingUpdatableIbcNbtcMinter: MinterInfo = {
supportedToken: ibcNbtc, supportedToken: ibcNbtc,
updatable: true, updatable: true,
flexible: true, flexible: true,
merkleTree: false,
featured: false, featured: false,
} }
@ -502,6 +709,7 @@ export const flexibleVendingIbcUskMinter: MinterInfo = {
supportedToken: ibcUsk, supportedToken: ibcUsk,
updatable: false, updatable: false,
flexible: true, flexible: true,
merkleTree: false,
featured: false, featured: false,
} }
@ -511,6 +719,7 @@ export const flexibleVendingUpdatableIbcUskMinter: MinterInfo = {
supportedToken: ibcUsk, supportedToken: ibcUsk,
updatable: true, updatable: true,
flexible: true, flexible: true,
merkleTree: false,
featured: false, featured: false,
} }
@ -520,17 +729,19 @@ export const flexibleVendingIbcKujiMinter: MinterInfo = {
supportedToken: ibcKuji, supportedToken: ibcKuji,
updatable: false, updatable: false,
flexible: true, flexible: true,
merkleTree: false,
featured: false, featured: false,
} }
export const flexibleVendingIbcHuahuaMinter: MinterInfo = { // export const flexibleVendingIbcHuahuaMinter: MinterInfo = {
id: 'flexible-vending-ibc-huahua-minter', // id: 'flexible-vending-ibc-huahua-minter',
factoryAddress: VENDING_IBC_HUAHUA_FACTORY_FLEX_ADDRESS, // factoryAddress: VENDING_IBC_HUAHUA_FACTORY_FLEX_ADDRESS,
supportedToken: ibcHuahua, // supportedToken: ibcHuahua,
updatable: false, // updatable: false,
flexible: true, // flexible: true,
featured: false, // merkleTree: false,
} // featured: false,
// }
export const flexibleVendingIbcCrbrusMinter: MinterInfo = { export const flexibleVendingIbcCrbrusMinter: MinterInfo = {
id: 'flexible-vending-ibc-crbrus-minter', id: 'flexible-vending-ibc-crbrus-minter',
@ -538,6 +749,7 @@ export const flexibleVendingIbcCrbrusMinter: MinterInfo = {
supportedToken: ibcCrbrus, supportedToken: ibcCrbrus,
updatable: false, updatable: false,
flexible: true, flexible: true,
merkleTree: false,
featured: false, featured: false,
} }
@ -547,6 +759,7 @@ export const flexibleVendingStrdstMinter: MinterInfo = {
supportedToken: nativeStardust, supportedToken: nativeStardust,
updatable: false, updatable: false,
flexible: true, flexible: true,
merkleTree: false,
featured: false, featured: false,
} }
@ -556,6 +769,7 @@ export const flexibleVendingBrnchMinter: MinterInfo = {
supportedToken: nativeBrnch, supportedToken: nativeBrnch,
updatable: false, updatable: false,
flexible: true, flexible: true,
merkleTree: false,
featured: false, featured: false,
} }
@ -566,14 +780,65 @@ export const flexibleVendingMinterList = [
flexibleVendingIbcAtomMinter, flexibleVendingIbcAtomMinter,
flexibleVendingUpdatableIbcAtomMinter, flexibleVendingUpdatableIbcAtomMinter,
flexibleVendingIbcUsdcMinter, flexibleVendingIbcUsdcMinter,
flexibleFeaturedVendingIbcUsdcMinter,
flexibleVendingUpdatableIbcUsdcMinter, flexibleVendingUpdatableIbcUsdcMinter,
flexibleVendingIbcTiaMinter,
flexibleFeaturedVendingIbcTiaMinter,
flexibleVendingUpdatableIbcTiaMinter,
flexibleVendingIbcNbtcMinter, flexibleVendingIbcNbtcMinter,
flexibleVendingUpdatableIbcNbtcMinter, flexibleVendingUpdatableIbcNbtcMinter,
flexibleVendingIbcUskMinter, flexibleVendingIbcUskMinter,
flexibleVendingUpdatableIbcUskMinter, flexibleVendingUpdatableIbcUskMinter,
flexibleVendingIbcKujiMinter, flexibleVendingIbcKujiMinter,
flexibleVendingIbcHuahuaMinter, // flexibleVendingIbcHuahuaMinter,
flexibleVendingIbcCrbrusMinter, flexibleVendingIbcCrbrusMinter,
flexibleVendingStrdstMinter, flexibleVendingStrdstMinter,
flexibleVendingBrnchMinter, 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,
]

View File

@ -69,15 +69,15 @@ export const ibcNbtc: TokenInfo = {
decimalPlaces: 6, decimalPlaces: 6,
} }
export const ibcHuahua: TokenInfo = { // export const ibcHuahua: TokenInfo = {
id: 'ibc-huahua', // id: 'ibc-huahua',
denom: // denom:
NETWORK === 'mainnet' // NETWORK === 'mainnet'
? 'ibc/CAD8A9F306CAAC55731C66930D6BEE539856DD12E59061C965E44D82AA26A0E7' // ? 'ibc/CAD8A9F306CAAC55731C66930D6BEE539856DD12E59061C965E44D82AA26A0E7'
: 'factory/stars153w5xhuqu3et29lgqk4dsynj6gjn96lr33wx4e/uhuahua', // : 'factory/stars153w5xhuqu3et29lgqk4dsynj6gjn96lr33wx4e/uhuahua',
displayName: 'HUAHUA', // displayName: 'HUAHUA',
decimalPlaces: 6, // decimalPlaces: 6,
} // }
export const ibcCrbrus: TokenInfo = { export const ibcCrbrus: TokenInfo = {
id: 'ibc-crbrus', id: 'ibc-crbrus',
@ -89,6 +89,16 @@ export const ibcCrbrus: TokenInfo = {
decimalPlaces: 6, 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 = { export const nativeStardust: TokenInfo = {
id: 'native-strdst', id: 'native-strdst',
denom: denom:
@ -117,8 +127,9 @@ export const tokensList = [
ibcFrnz, ibcFrnz,
ibcNbtc, ibcNbtc,
ibcKuji, ibcKuji,
ibcHuahua, // ibcHuahua,
ibcCrbrus, ibcCrbrus,
ibcTia,
nativeStardust, nativeStardust,
nativeBrnch, nativeBrnch,
] ]

View File

@ -16,6 +16,7 @@ import type { UseVendingMinterContractProps } from 'contracts/vendingMinter'
import { useVendingMinterContract } from 'contracts/vendingMinter' import { useVendingMinterContract } from 'contracts/vendingMinter'
import type { UseWhiteListContractProps } from 'contracts/whitelist' import type { UseWhiteListContractProps } from 'contracts/whitelist'
import { useWhiteListContract } from 'contracts/whitelist' import { useWhiteListContract } from 'contracts/whitelist'
import { type UseWhiteListMerkleTreeContractProps, useWhiteListMerkleTreeContract } from 'contracts/whitelistMerkleTree'
import type { ReactNode, VFC } from 'react' import type { ReactNode, VFC } from 'react'
import { Fragment, useEffect } from 'react' import { Fragment, useEffect } from 'react'
import { create } from 'zustand' import { create } from 'zustand'
@ -32,6 +33,7 @@ export interface ContractsStore {
baseMinter: UseBaseMinterContractProps | null baseMinter: UseBaseMinterContractProps | null
openEditionMinter: UseOpenEditionMinterContractProps | null openEditionMinter: UseOpenEditionMinterContractProps | null
whitelist: UseWhiteListContractProps | null whitelist: UseWhiteListContractProps | null
whitelistMerkleTree: UseWhiteListMerkleTreeContractProps | null
vendingFactory: UseVendingFactoryContractProps | null vendingFactory: UseVendingFactoryContractProps | null
baseFactory: UseBaseFactoryContractProps | null baseFactory: UseBaseFactoryContractProps | null
openEditionFactory: UseOpenEditionFactoryContractProps | null openEditionFactory: UseOpenEditionFactoryContractProps | null
@ -49,6 +51,7 @@ export const defaultValues: ContractsStore = {
baseMinter: null, baseMinter: null,
openEditionMinter: null, openEditionMinter: null,
whitelist: null, whitelist: null,
whitelistMerkleTree: null,
vendingFactory: null, vendingFactory: null,
baseFactory: null, baseFactory: null,
openEditionFactory: null, openEditionFactory: null,
@ -83,6 +86,7 @@ const ContractsSubscription: VFC = () => {
const baseMinter = useBaseMinterContract() const baseMinter = useBaseMinterContract()
const openEditionMinter = useOpenEditionMinterContract() const openEditionMinter = useOpenEditionMinterContract()
const whitelist = useWhiteListContract() const whitelist = useWhiteListContract()
const whitelistMerkleTree = useWhiteListMerkleTreeContract()
const vendingFactory = useVendingFactoryContract() const vendingFactory = useVendingFactoryContract()
const baseFactory = useBaseFactoryContract() const baseFactory = useBaseFactoryContract()
const openEditionFactory = useOpenEditionFactoryContract() const openEditionFactory = useOpenEditionFactoryContract()
@ -97,6 +101,7 @@ const ContractsSubscription: VFC = () => {
baseMinter, baseMinter,
openEditionMinter, openEditionMinter,
whitelist, whitelist,
whitelistMerkleTree,
vendingFactory, vendingFactory,
baseFactory, baseFactory,
openEditionFactory, openEditionFactory,
@ -104,7 +109,20 @@ const ContractsSubscription: VFC = () => {
splits, splits,
royaltyRegistry, royaltyRegistry,
}) })
}, [sg721, vendingMinter, baseMinter, whitelist, vendingFactory, baseFactory, badgeHub, splits, royaltyRegistry]) }, [
sg721,
vendingMinter,
baseMinter,
whitelist,
whitelistMerkleTree,
vendingFactory,
baseFactory,
badgeHub,
splits,
royaltyRegistry,
openEditionMinter,
openEditionFactory,
])
return null return null
} }

View File

@ -342,7 +342,8 @@ export const badgeHub = (client: SigningCosmWasmClient, txSigner: string): Badge
}, },
'auto', 'auto',
'', '',
editFee ? [coin(editFee, 'ustars')] : [], [coin(200000000, 'ustars')],
// editFee ? [coin(editFee, 'ustars')] : [],
) )
return res.transactionHash return res.transactionHash

View File

@ -70,8 +70,8 @@ export const baseFactory = (client: SigningCosmWasmClient, txSigner: string): Ba
) )
return { return {
baseMinterAddress: result.logs[0].events[5].attributes[0].value, baseMinterAddress: result.logs[0].events[16].attributes[0].value,
sg721Address: result.logs[0].events[5].attributes[2].value, sg721Address: result.logs[0].events[18].attributes[0].value,
transactionHash: result.transactionHash, transactionHash: result.transactionHash,
logs: result.logs, logs: result.logs,
} }

View File

@ -61,8 +61,8 @@ export const openEditionFactory = (client: SigningCosmWasmClient, txSigner: stri
const result = await client.execute(senderAddress, contractAddress, msg, 'auto', '', funds) const result = await client.execute(senderAddress, contractAddress, msg, 'auto', '', funds)
return { return {
openEditionMinterAddress: result.logs[0].events[5].attributes[0].value, openEditionMinterAddress: result.logs[0].events[16].attributes[0].value,
sg721Address: result.logs[0].events[5].attributes[2].value, sg721Address: result.logs[0].events[18].attributes[0].value,
transactionHash: result.transactionHash, transactionHash: result.transactionHash,
logs: result.logs, logs: result.logs,
} }

View File

@ -25,6 +25,7 @@ export interface CollectionInfo {
external_link?: string external_link?: string
explicit_content?: boolean explicit_content?: boolean
royalty_info?: RoyaltyInfo | undefined royalty_info?: RoyaltyInfo | undefined
creator?: string
} }
export interface SG721Instance { export interface SG721Instance {

View File

@ -63,8 +63,10 @@ export const vendingFactory = (client: SigningCosmWasmClient, txSigner: string):
const result = await client.execute(senderAddress, contractAddress, msg, 'auto', '', funds) const result = await client.execute(senderAddress, contractAddress, msg, 'auto', '', funds)
return { return {
vendingMinterAddress: result.logs[0].events[5].attributes[0].value, vendingMinterAddress: result.logs[0].events.filter((e) => e.type === 'instantiate')[0].attributes[0].value,
sg721Address: result.logs[0].events[5].attributes[2].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, transactionHash: result.transactionHash,
logs: result.logs, logs: result.logs,
} }

View 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 }
}

View File

@ -0,0 +1,2 @@
export * from './contract'
export * from './useContract'

View 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)
}

View 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')
}
}
}

View 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,
}
}

21
env.d.ts vendored
View File

@ -22,18 +22,25 @@ declare namespace NodeJS {
readonly NEXT_PUBLIC_OPEN_EDITION_SG721_UPDATABLE_CODE_ID: string readonly NEXT_PUBLIC_OPEN_EDITION_SG721_UPDATABLE_CODE_ID: string
readonly NEXT_PUBLIC_WHITELIST_CODE_ID: string readonly NEXT_PUBLIC_WHITELIST_CODE_ID: string
readonly NEXT_PUBLIC_WHITELIST_FLEX_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_CODE_ID: string
readonly NEXT_PUBLIC_VENDING_MINTER_FLEX_CODE_ID: string readonly NEXT_PUBLIC_VENDING_MINTER_FLEX_CODE_ID: string
readonly NEXT_PUBLIC_VENDING_FACTORY_ADDRESS: string readonly NEXT_PUBLIC_VENDING_FACTORY_ADDRESS: string
readonly NEXT_PUBLIC_FEATURED_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_UPDATABLE_ADDRESS: string
readonly NEXT_PUBLIC_VENDING_FACTORY_FLEX_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_FEATURED_VENDING_FACTORY_FLEX_ADDRESS: string
readonly NEXT_PUBLIC_VENDING_FACTORY_UPDATABLE_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_FACTORY_ADDRESS: string
readonly NEXT_PUBLIC_VENDING_IBC_ATOM_UPDATABLE_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_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_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_FACTORY_ADDRESS: string
readonly NEXT_PUBLIC_VENDING_IBC_NBTC_UPDATABLE_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_FACTORY_ADDRESS: string
@ -47,7 +54,13 @@ declare namespace NodeJS {
readonly NEXT_PUBLIC_VENDING_IBC_ATOM_FACTORY_FLEX_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_ATOM_UPDATABLE_FACTORY_FLEX_ADDRESS: string
readonly NEXT_PUBLIC_VENDING_IBC_USDC_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_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_FACTORY_FLEX_ADDRESS: string
readonly NEXT_PUBLIC_VENDING_IBC_NBTC_UPDATABLE_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_FACTORY_FLEX_ADDRESS: string
@ -65,11 +78,17 @@ declare namespace NodeJS {
readonly NEXT_PUBLIC_VENDING_NATIVE_BRNCH_UPDATABLE_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_VENDING_NATIVE_BRNCH_FLEX_FACTORY_ADDRESS: string
readonly NEXT_PUBLIC_OPEN_EDITION_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_UPDATABLE_FACTORY_ADDRESS: string
readonly NEXT_PUBLIC_OPEN_EDITION_IBC_ATOM_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_UPDATABLE_IBC_ATOM_FACTORY_ADDRESS: string
readonly NEXT_PUBLIC_OPEN_EDITION_IBC_USDC_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_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_IBC_NBTC_FACTORY_ADDRESS: string
readonly NEXT_PUBLIC_OPEN_EDITION_UPDATABLE_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_IBC_FRNZ_FACTORY_ADDRESS: string
@ -104,6 +123,8 @@ declare namespace NodeJS {
readonly NEXT_PUBLIC_STARGAZE_WEBSITE_URL: string readonly NEXT_PUBLIC_STARGAZE_WEBSITE_URL: string
readonly NEXT_PUBLIC_WEBSITE_URL: string readonly NEXT_PUBLIC_WEBSITE_URL: string
readonly NEXT_PUBLIC_SYNC_COLLECTIONS_API_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_HOST: string
readonly NEXT_PUBLIC_MEILISEARCH_API_KEY: string readonly NEXT_PUBLIC_MEILISEARCH_API_KEY: string

18257
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,7 @@
{ {
"name": "stargaze-studio", "private": true,
"name": "@mito/stargaze-studio",
"repository": "https://git.vdb.to/LaconicNetwork/stargaze-studio",
"version": "0.8.7", "version": "0.8.7",
"workspaces": [ "workspaces": [
"packages/*" "packages/*"
@ -13,18 +15,21 @@
}, },
"dependencies": { "dependencies": {
"@aws-sdk/client-s3": "^3", "@aws-sdk/client-s3": "^3",
"@cosmjs/cosmwasm-stargate": "0.32.2", "@cosmjs/cosmwasm-stargate": "0.32.3",
"@cosmjs/encoding": "0.32.2", "@cosmjs/encoding": "0.32.3",
"@cosmjs/math": "0.32.2", "@cosmjs/math": "0.32.3",
"@cosmjs/proto-signing": "0.32.2", "@cosmjs/proto-signing": "0.32.3",
"@cosmjs/stargate": "0.32.2", "@cosmjs/stargate": "0.32.3",
"cosmjs-types": "0.9.0", "@cosmos-kit/keplr": "2.8.0",
"@cosmos-kit/keplr": "^2.4.4", "@cosmos-kit/leap": "2.8.0",
"@cosmos-kit/leap": "^2.4.3", "@cosmos-kit/leap-metamask-cosmos-snap": "0.8.0",
"@cosmos-kit/leap-metamask-cosmos-snap": "^0.3.3", "@cosmos-kit/react": "2.12.0",
"@cosmos-kit/react": "^2.9.3",
"@fontsource/jetbrains-mono": "^4", "@fontsource/jetbrains-mono": "^4",
"@fontsource/roboto": "^4", "@fontsource/roboto": "^4",
"@headlessui/react": "1.6.0",
"@headlessui/tailwindcss": "0.2.0",
"@heroicons/react": "2.0.18",
"@interchain-ui/react": "1.23.11",
"@leapwallet/cosmos-snap-provider": "0.1.24", "@leapwallet/cosmos-snap-provider": "0.1.24",
"@pinata/sdk": "^1.1.26", "@pinata/sdk": "^1.1.26",
"@popperjs/core": "^2", "@popperjs/core": "^2",
@ -32,20 +37,23 @@
"@tailwindcss/forms": "^0", "@tailwindcss/forms": "^0",
"@tailwindcss/line-clamp": "^0", "@tailwindcss/line-clamp": "^0",
"@typeform/embed-react": "2.21.0", "@typeform/embed-react": "2.21.0",
"@types/crypto-js": "4.2.1",
"@types/pako": "^2.0.3",
"axios": "^0", "axios": "^0",
"chain-registry": "^1.20.0", "chain-registry": "^1.20.0",
"clsx": "^1", "clsx": "^1",
"compare-versions": "^4", "compare-versions": "^4",
"cosmjs-types": "0.9.0",
"crypto-js": "4.1.1",
"daisyui": "^2.19.0", "daisyui": "^2.19.0",
"html-to-image": "1.11.11", "html-to-image": "1.11.11",
"@headlessui/react": "1.6.0",
"@headlessui/tailwindcss": "0.2.0",
"@heroicons/react": "2.0.18",
"jscrypto": "^1.0.3", "jscrypto": "^1.0.3",
"match-sorter": "^6", "match-sorter": "^6",
"merkletreejs": "0.3.11",
"next": "^12", "next": "^12",
"next-seo": "^4", "next-seo": "^4",
"nft.storage": "^6.3.0", "nft.storage": "^6.3.0",
"pako": "^2.0.2",
"qrcode.react": "3.1.0", "qrcode.react": "3.1.0",
"react": "^18", "react": "^18",
"react-datetime-picker": "^3", "react-datetime-picker": "^3",
@ -72,8 +80,8 @@
"lint-staged": "^12", "lint-staged": "^12",
"object-sizeof": "^1.6.0", "object-sizeof": "^1.6.0",
"postcss": "^8", "postcss": "^8",
"tailwindcss": "^3",
"tailwind-merge": "1.14.0", "tailwind-merge": "1.14.0",
"tailwindcss": "^3",
"typescript": "^4" "typescript": "^4"
}, },
"eslintConfig": { "eslintConfig": {

View File

@ -0,0 +1,132 @@
/* eslint-disable eslint-comments/disable-enable-pair */
import { Alert } from 'components/Alert'
import { Button } from 'components/Button'
import { Conditional } from 'components/Conditional'
import { ContractPageHeader } from 'components/ContractPageHeader'
import { AddressInput, TextInput } from 'components/forms/FormInput'
import { useInputState } from 'components/forms/FormInput.hooks'
import type { NextPage } from 'next'
import { useRouter } from 'next/router'
import { NextSeo } from 'next-seo'
import { useEffect, useState } from 'react'
import { toast } from 'react-hot-toast'
import { NETWORK } from 'utils/constants'
import { useDebounce } from 'utils/debounce'
import { withMetadata } from 'utils/layout'
import { links } from 'utils/links'
import { resolveAddress } from 'utils/resolveAddress'
import { useWallet } from 'utils/wallet'
const CancelAuctionPage: NextPage = () => {
const wallet = useWallet()
const [isLoading, setIsLoading] = useState(false)
const [txHash, setTxHash] = useState<string | undefined>(undefined)
const collectionAddressState = useInputState({
id: 'collection-address',
name: 'collectionAddress',
title: 'Collection Contract Address',
defaultValue: '',
placeholder: 'stars1...',
})
const collectionAddress = useDebounce(collectionAddressState.value, 300)
const tokenIdState = useInputState({
id: 'token-id',
name: 'tokenId',
title: 'Token ID',
defaultValue: '',
placeholder: '1',
})
const router = useRouter()
useEffect(() => {
if (collectionAddress.length > 0) {
void router.replace({ query: { contractAddress: collectionAddress } })
}
if (collectionAddress.length === 0) {
void router.replace({ query: {} })
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [collectionAddress])
useEffect(() => {
const initial = new URL(document.URL).searchParams.get('contractAddress')
if (initial && initial.length > 0) collectionAddressState.onChange(initial)
}, [])
const resolveCollectionAddress = async () => {
await resolveAddress(collectionAddressState.value.trim(), wallet).then((resolvedAddress) => {
if (resolvedAddress) {
collectionAddressState.onChange(resolvedAddress)
}
})
}
useEffect(() => {
void resolveCollectionAddress()
}, [collectionAddressState.value])
const handleCancelAuction = async () => {
if (!wallet.isWalletConnected) return toast.error('Please connect your wallet.')
if (!collectionAddressState.value) return toast.error('Please enter a collection address.')
const client = await wallet.getSigningCosmWasmClient()
setTxHash(undefined)
setIsLoading(true)
try {
const result = await client.execute(
wallet.address as string,
NETWORK === 'mainnet'
? 'stars1vvdkcn393ddyd47v9g3qv6mvne59d0ykzy9wre3ga0c58dtdg4ksm776jg'
: 'stars1dnadsd7tx0dmnpp26ms7d66zsp7tduygwjgfjzueh0lg9t5lq5vq9kn47c',
{
cancel_auction: {
collection: collectionAddressState.value,
token_id: tokenIdState.value,
},
},
'auto',
)
toast.success('Auction successfully cancelled.')
setTxHash(result.transactionHash)
} catch (error: any) {
toast.error(error.message, { style: { maxWidth: 'none' } })
setTxHash(undefined)
} finally {
setIsLoading(false)
}
}
return (
<section className="py-6 px-12 space-y-4">
<NextSeo title="Cancel Auction" />
<ContractPageHeader link={links.Documentation} title="Cancel Auction" />
<div className="space-y-2">
<AddressInput {...collectionAddressState} />
<TextInput className="w-1/4" {...tokenIdState} />
</div>
<div className="flex flex-row content-center mt-4">
<Button
isDisabled={collectionAddressState.value === ''}
isLoading={isLoading}
onClick={() => {
void handleCancelAuction()
}}
>
Cancel Auction
</Button>
</div>
<Conditional test={txHash !== undefined}>
<Alert type="info">
<b>Transaction Hash:</b> {txHash}
</Alert>
</Conditional>
</section>
)
}
export default withMetadata(CancelAuctionPage, { center: false })

View File

@ -7,6 +7,7 @@
/* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */
import { toUtf8 } from '@cosmjs/encoding' import { toUtf8 } from '@cosmjs/encoding'
import type { Coin } from '@cosmjs/proto-signing'
import { coin } from '@cosmjs/proto-signing' import { coin } from '@cosmjs/proto-signing'
import { Sidetab } from '@typeform/embed-react' import { Sidetab } from '@typeform/embed-react'
import axios from 'axios' import axios from 'axios'
@ -34,7 +35,13 @@ import { FormControl } from 'components/FormControl'
import { LoadingModal } from 'components/LoadingModal' import { LoadingModal } from 'components/LoadingModal'
import type { OpenEditionMinterCreatorDataProps } from 'components/openEdition/OpenEditionMinterCreator' import type { OpenEditionMinterCreatorDataProps } from 'components/openEdition/OpenEditionMinterCreator'
import { OpenEditionMinterCreator } from 'components/openEdition/OpenEditionMinterCreator' import { OpenEditionMinterCreator } from 'components/openEdition/OpenEditionMinterCreator'
import { flexibleVendingMinterList, openEditionMinterList, vendingMinterList } from 'config/minter' import {
flexibleOpenEditionMinterList,
flexibleVendingMinterList,
merkleTreeVendingMinterList,
openEditionMinterList,
vendingMinterList,
} from 'config/minter'
import type { TokenInfo } from 'config/token' import type { TokenInfo } from 'config/token'
import { useContracts } from 'contexts/contracts' import { useContracts } from 'contexts/contracts'
import { addLogItem } from 'contexts/log' import { addLogItem } from 'contexts/log'
@ -56,7 +63,6 @@ import {
BLOCK_EXPLORER_URL, BLOCK_EXPLORER_URL,
NETWORK, NETWORK,
OPEN_EDITION_FACTORY_ADDRESS, OPEN_EDITION_FACTORY_ADDRESS,
OPEN_EDITION_UPDATABLE_FACTORY_ADDRESS,
SG721_CODE_ID, SG721_CODE_ID,
SG721_UPDATABLE_CODE_ID, SG721_UPDATABLE_CODE_ID,
STARGAZE_URL, STARGAZE_URL,
@ -67,6 +73,8 @@ import {
VENDING_FACTORY_UPDATABLE_ADDRESS, VENDING_FACTORY_UPDATABLE_ADDRESS,
WHITELIST_CODE_ID, WHITELIST_CODE_ID,
WHITELIST_FLEX_CODE_ID, WHITELIST_FLEX_CODE_ID,
WHITELIST_MERKLE_TREE_API_URL,
WHITELIST_MERKLE_TREE_CODE_ID,
} from 'utils/constants' } from 'utils/constants'
import { checkTokenUri } from 'utils/isValidTokenUri' import { checkTokenUri } from 'utils/isValidTokenUri'
import { withMetadata } from 'utils/layout' import { withMetadata } from 'utils/layout'
@ -88,6 +96,7 @@ const CollectionCreationPage: NextPage = () => {
baseMinter: baseMinterContract, baseMinter: baseMinterContract,
vendingMinter: vendingMinterContract, vendingMinter: vendingMinterContract,
whitelist: whitelistContract, whitelist: whitelistContract,
whitelistMerkleTree: whitelistMerkleTreeContract,
vendingFactory: vendingFactoryContract, vendingFactory: vendingFactoryContract,
baseFactory: baseFactoryContract, baseFactory: baseFactoryContract,
} = useContracts() } = useContracts()
@ -118,24 +127,23 @@ const CollectionCreationPage: NextPage = () => {
const [royaltyDetails, setRoyaltyDetails] = useState<RoyaltyDetailsDataProps | null>(null) const [royaltyDetails, setRoyaltyDetails] = useState<RoyaltyDetailsDataProps | null>(null)
const [minterType, setMinterType] = useState<MinterType>('vending') const [minterType, setMinterType] = useState<MinterType>('vending')
const [vendingMinterCreationFee, setVendingMinterCreationFee] = useState<string | null>(null) const [vendingMinterCreationFee, setVendingMinterCreationFee] = useState<Coin | null>(null)
const [baseMinterCreationFee, setBaseMinterCreationFee] = useState<string | null>(null) const [baseMinterCreationFee, setBaseMinterCreationFee] = useState<Coin | null>(null)
const [vendingMinterUpdatableCreationFee, setVendingMinterUpdatableCreationFee] = useState<string | null>(null) const [vendingMinterUpdatableCreationFee, setVendingMinterUpdatableCreationFee] = useState<Coin | null>(null)
const [openEditionMinterCreationFee, setOpenEditionMinterCreationFee] = useState<string | null>(null) const [openEditionMinterCreationFee, setOpenEditionMinterCreationFee] = useState<Coin | undefined>(undefined)
const [openEditionMinterUpdatableCreationFee, setOpenEditionMinterUpdatableCreationFee] = useState<string | null>( const [vendingMinterFlexCreationFee, setVendingMinterFlexCreationFee] = useState<Coin | null>(null)
null, const [baseMinterUpdatableCreationFee, setBaseMinterUpdatableCreationFee] = useState<Coin | null>(null)
)
const [vendingMinterFlexCreationFee, setVendingMinterFlexCreationFee] = useState<string | null>(null)
const [baseMinterUpdatableCreationFee, setBaseMinterUpdatableCreationFee] = useState<string | null>(null)
const [minimumMintPrice, setMinimumMintPrice] = useState<string | null>('0') const [minimumMintPrice, setMinimumMintPrice] = useState<string | null>('0')
const [minimumUpdatableMintPrice, setMinimumUpdatableMintPrice] = useState<string | null>('0') const [minimumUpdatableMintPrice, setMinimumUpdatableMintPrice] = useState<string | null>('0')
const [minimumOpenEditionMintPrice, setMinimumOpenEditionMintPrice] = useState<string | null>('0') const [minimumOpenEditionMintPrice, setMinimumOpenEditionMintPrice] = useState<string | null>('0')
const [minimumOpenEditionUpdatableMintPrice, setMinimumOpenEditionUpdatableMintPrice] = useState<string | null>('0')
const [minimumFlexMintPrice, setMinimumFlexMintPrice] = useState<string | null>('0') const [minimumFlexMintPrice, setMinimumFlexMintPrice] = useState<string | null>('0')
const [mintTokenFromOpenEditionFactory, setMintTokenFromOpenEditionFactory] = useState<TokenInfo | undefined>(stars) const [mintTokenFromOpenEditionFactory, setMintTokenFromOpenEditionFactory] = useState<TokenInfo | undefined>(stars)
const [mintTokenFromVendingFactory, setMintTokenFromVendingFactory] = useState<TokenInfo | undefined>(stars) const [mintTokenFromVendingFactory, setMintTokenFromVendingFactory] = useState<TokenInfo | undefined>(stars)
const [vendingFactoryAddress, setVendingFactoryAddress] = useState<string | null>(VENDING_FACTORY_ADDRESS) const [vendingFactoryAddress, setVendingFactoryAddress] = useState<string | null>(VENDING_FACTORY_ADDRESS)
const [openEditionFactoryAddress, setOpenEditionFactoryAddress] = useState<string | undefined>(
OPEN_EDITION_FACTORY_ADDRESS,
)
const vendingFactoryMessages = useMemo( const vendingFactoryMessages = useMemo(
() => vendingFactoryContract?.use(vendingFactoryAddress as string), () => vendingFactoryContract?.use(vendingFactoryAddress as string),
@ -162,6 +170,7 @@ const CollectionCreationPage: NextPage = () => {
const [coverImageUrl, setCoverImageUrl] = useState<string | null>(null) const [coverImageUrl, setCoverImageUrl] = useState<string | null>(null)
const [transactionHash, setTransactionHash] = useState<string | null>(null) const [transactionHash, setTransactionHash] = useState<string | null>(null)
const [isMatchingVendingFactoryPresent, setIsMatchingVendingFactoryPresent] = useState<boolean>(true) const [isMatchingVendingFactoryPresent, setIsMatchingVendingFactoryPresent] = useState<boolean>(true)
const [isMatchingOpenEditionFactoryPresent, setIsMatchingOpenEditionFactoryPresent] = useState<boolean>(true)
const performVendingMinterChecks = () => { const performVendingMinterChecks = () => {
try { try {
@ -513,41 +522,92 @@ const CollectionCreationPage: NextPage = () => {
if (!wallet.isWalletConnected) throw new Error('Wallet not connected') if (!wallet.isWalletConnected) throw new Error('Wallet not connected')
if (!whitelistContract) throw new Error('Contract not found') if (!whitelistContract) throw new Error('Contract not found')
const standardMsg = { if (whitelistDetails?.whitelistType === 'standard' || whitelistDetails?.whitelistType === 'flex') {
members: whitelistDetails?.members, const standardMsg = {
start_time: whitelistDetails?.startTime, members: whitelistDetails.members,
end_time: whitelistDetails?.endTime, start_time: whitelistDetails.startTime,
mint_price: coin( end_time: whitelistDetails.endTime,
String(Number(whitelistDetails?.unitPrice)), mint_price: coin(
mintTokenFromVendingFactory ? mintTokenFromVendingFactory.denom : 'ustars', String(Number(whitelistDetails.unitPrice)),
), mintTokenFromVendingFactory ? mintTokenFromVendingFactory.denom : 'ustars',
per_address_limit: whitelistDetails?.perAddressLimit, ),
member_limit: whitelistDetails?.memberLimit, per_address_limit: whitelistDetails.perAddressLimit,
admins: whitelistDetails?.admins || [wallet.address], member_limit: whitelistDetails.memberLimit,
admins_mutable: whitelistDetails?.adminsMutable, 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)),
mintTokenFromVendingFactory ? mintTokenFromVendingFactory.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)),
mintTokenFromVendingFactory ? mintTokenFromVendingFactory.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 flexMsg = {
members: whitelistDetails?.members,
start_time: whitelistDetails?.startTime,
end_time: whitelistDetails?.endTime,
mint_price: coin(
String(Number(whitelistDetails?.unitPrice)),
mintTokenFromVendingFactory ? mintTokenFromVendingFactory.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
} }
const instantiateVendingMinter = async (baseUri: string, coverImageUri: string, whitelist?: string) => { const instantiateVendingMinter = async (baseUri: string, coverImageUri: string, whitelist?: string) => {
@ -582,6 +642,7 @@ const CollectionCreationPage: NextPage = () => {
: mintingDetails?.selectedMintToken?.displayName === 'STRDST' || : mintingDetails?.selectedMintToken?.displayName === 'STRDST' ||
mintingDetails?.selectedMintToken?.displayName === 'USK' || mintingDetails?.selectedMintToken?.displayName === 'USK' ||
mintingDetails?.selectedMintToken?.displayName === 'USDC' || mintingDetails?.selectedMintToken?.displayName === 'USDC' ||
mintingDetails?.selectedMintToken?.displayName === 'TIA' ||
mintingDetails?.selectedMintToken?.displayName === 'nBTC' || mintingDetails?.selectedMintToken?.displayName === 'nBTC' ||
mintingDetails?.selectedMintToken?.displayName === 'KUJI' || mintingDetails?.selectedMintToken?.displayName === 'KUJI' ||
mintingDetails?.selectedMintToken?.displayName === 'HUAHUA' || mintingDetails?.selectedMintToken?.displayName === 'HUAHUA' ||
@ -615,14 +676,11 @@ const CollectionCreationPage: NextPage = () => {
txSigner: wallet.address || '', txSigner: wallet.address || '',
msg, msg,
funds: [ funds: [
coin( whitelistDetails?.whitelistState !== 'none' && whitelistDetails?.whitelistType === 'flex'
whitelistDetails?.whitelistState !== 'none' && whitelistDetails?.whitelistType === 'flex' ? (vendingMinterFlexCreationFee as Coin)
? (vendingMinterFlexCreationFee as string) : collectionDetails?.updatable
: collectionDetails?.updatable ? (vendingMinterUpdatableCreationFee as Coin)
? (vendingMinterUpdatableCreationFee as string) : (vendingMinterCreationFee as Coin),
: (vendingMinterCreationFee as string),
'ustars',
),
], ],
updatable: collectionDetails?.updatable, updatable: collectionDetails?.updatable,
flex: whitelistDetails?.whitelistState !== 'none' && whitelistDetails?.whitelistType === 'flex', flex: whitelistDetails?.whitelistState !== 'none' && whitelistDetails?.whitelistType === 'flex',
@ -676,10 +734,7 @@ const CollectionCreationPage: NextPage = () => {
txSigner: wallet.address || '', txSigner: wallet.address || '',
msg, msg,
funds: [ funds: [
coin( collectionDetails?.updatable ? (baseMinterUpdatableCreationFee as Coin) : (baseMinterCreationFee as Coin),
collectionDetails?.updatable ? (baseMinterUpdatableCreationFee as string) : (baseMinterCreationFee as string),
'ustars',
),
], ],
updatable: collectionDetails?.updatable, updatable: collectionDetails?.updatable,
} }
@ -1046,6 +1101,8 @@ const CollectionCreationPage: NextPage = () => {
//check if the address belongs to a whitelist contract (see performChecks()) //check if the address belongs to a whitelist contract (see performChecks())
const config = await contract?.config() const config = await contract?.config()
if (JSON.stringify(config).includes('whale_cap')) whitelistDetails.whitelistType = 'flex' 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'
else whitelistDetails.whitelistType = 'standard' else whitelistDetails.whitelistType = 'standard'
if (Number(config?.start_time) !== Number(mintingDetails?.startTime)) { if (Number(config?.start_time) !== Number(mintingDetails?.startTime)) {
const whitelistStartDate = new Date(Number(config?.start_time) / 1000000) const whitelistStartDate = new Date(Number(config?.start_time) / 1000000)
@ -1083,7 +1140,10 @@ const CollectionCreationPage: NextPage = () => {
(!whitelistDetails.perAddressLimit || whitelistDetails.perAddressLimit === 0) (!whitelistDetails.perAddressLimit || whitelistDetails.perAddressLimit === 0)
) )
throw new Error('Per address limit is required') throw new Error('Per address limit is required')
if (!whitelistDetails.memberLimit || whitelistDetails.memberLimit === 0) if (
whitelistDetails.whitelistType !== 'merkletree' &&
(!whitelistDetails.memberLimit || whitelistDetails.memberLimit === 0)
)
throw new Error('Member limit is required') throw new Error('Member limit is required')
if (Number(whitelistDetails.startTime) >= Number(whitelistDetails.endTime)) if (Number(whitelistDetails.startTime) >= Number(whitelistDetails.endTime))
throw new Error('Whitelist start time cannot be equal to or later than the whitelist end time') throw new Error('Whitelist start time cannot be equal to or later than the whitelist end time')
@ -1151,7 +1211,7 @@ const CollectionCreationPage: NextPage = () => {
toast.error(`${error.message}`, { style: { maxWidth: 'none' } }) toast.error(`${error.message}`, { style: { maxWidth: 'none' } })
addLogItem({ id: uid(), message: error.message, type: 'Error', timestamp: new Date() }) addLogItem({ id: uid(), message: error.message, type: 'Error', timestamp: new Date() })
}) })
setBaseMinterCreationFee(baseFactoryParameters?.params?.creation_fee?.amount) setBaseMinterCreationFee(baseFactoryParameters?.params?.creation_fee)
} }
if (BASE_FACTORY_UPDATABLE_ADDRESS) { if (BASE_FACTORY_UPDATABLE_ADDRESS) {
const baseFactoryUpdatableParameters = await client const baseFactoryUpdatableParameters = await client
@ -1162,7 +1222,7 @@ const CollectionCreationPage: NextPage = () => {
toast.error(`${error.message}`, { style: { maxWidth: 'none' } }) toast.error(`${error.message}`, { style: { maxWidth: 'none' } })
addLogItem({ id: uid(), message: error.message, type: 'Error', timestamp: new Date() }) addLogItem({ id: uid(), message: error.message, type: 'Error', timestamp: new Date() })
}) })
setBaseMinterUpdatableCreationFee(baseFactoryUpdatableParameters?.params?.creation_fee?.amount) setBaseMinterUpdatableCreationFee(baseFactoryUpdatableParameters?.params?.creation_fee)
} }
if (VENDING_FACTORY_ADDRESS) { if (VENDING_FACTORY_ADDRESS) {
const vendingFactoryParameters = await client const vendingFactoryParameters = await client
@ -1171,7 +1231,7 @@ const CollectionCreationPage: NextPage = () => {
toast.error(`${error.message}`, { style: { maxWidth: 'none' } }) toast.error(`${error.message}`, { style: { maxWidth: 'none' } })
addLogItem({ id: uid(), message: error.message, type: 'Error', timestamp: new Date() }) addLogItem({ id: uid(), message: error.message, type: 'Error', timestamp: new Date() })
}) })
setVendingMinterCreationFee(vendingFactoryParameters?.params?.creation_fee?.amount) setVendingMinterCreationFee(vendingFactoryParameters?.params?.creation_fee)
setMinimumMintPrice(vendingFactoryParameters?.params?.min_mint_price?.amount) setMinimumMintPrice(vendingFactoryParameters?.params?.min_mint_price?.amount)
} }
if (VENDING_FACTORY_UPDATABLE_ADDRESS) { if (VENDING_FACTORY_UPDATABLE_ADDRESS) {
@ -1183,7 +1243,7 @@ const CollectionCreationPage: NextPage = () => {
toast.error(`${error.message}`, { style: { maxWidth: 'none' } }) toast.error(`${error.message}`, { style: { maxWidth: 'none' } })
addLogItem({ id: uid(), message: error.message, type: 'Error', timestamp: new Date() }) addLogItem({ id: uid(), message: error.message, type: 'Error', timestamp: new Date() })
}) })
setVendingMinterUpdatableCreationFee(vendingFactoryUpdatableParameters?.params?.creation_fee?.amount) setVendingMinterUpdatableCreationFee(vendingFactoryUpdatableParameters?.params?.creation_fee)
setMinimumUpdatableMintPrice(vendingFactoryUpdatableParameters?.params?.min_mint_price?.amount) setMinimumUpdatableMintPrice(vendingFactoryUpdatableParameters?.params?.min_mint_price?.amount)
} }
if (VENDING_FACTORY_FLEX_ADDRESS) { if (VENDING_FACTORY_FLEX_ADDRESS) {
@ -1195,7 +1255,7 @@ const CollectionCreationPage: NextPage = () => {
toast.error(`${error.message}`, { style: { maxWidth: 'none' } }) toast.error(`${error.message}`, { style: { maxWidth: 'none' } })
addLogItem({ id: uid(), message: error.message, type: 'Error', timestamp: new Date() }) addLogItem({ id: uid(), message: error.message, type: 'Error', timestamp: new Date() })
}) })
setVendingMinterFlexCreationFee(vendingFactoryFlexParameters?.params?.creation_fee?.amount) setVendingMinterFlexCreationFee(vendingFactoryFlexParameters?.params?.creation_fee)
setMinimumFlexMintPrice(vendingFactoryFlexParameters?.params?.min_mint_price?.amount) setMinimumFlexMintPrice(vendingFactoryFlexParameters?.params?.min_mint_price?.amount)
} }
if (OPEN_EDITION_FACTORY_ADDRESS) { if (OPEN_EDITION_FACTORY_ADDRESS) {
@ -1205,84 +1265,71 @@ const CollectionCreationPage: NextPage = () => {
toast.error(`${error.message}`, { style: { maxWidth: 'none' } }) toast.error(`${error.message}`, { style: { maxWidth: 'none' } })
addLogItem({ id: uid(), message: error.message, type: 'Error', timestamp: new Date() }) addLogItem({ id: uid(), message: error.message, type: 'Error', timestamp: new Date() })
}) })
setOpenEditionMinterCreationFee(openEditionFactoryParameters?.params?.creation_fee?.amount) setOpenEditionMinterCreationFee(openEditionFactoryParameters?.params?.creation_fee)
setMinimumOpenEditionMintPrice(openEditionFactoryParameters?.params?.min_mint_price?.amount) setMinimumOpenEditionMintPrice(openEditionFactoryParameters?.params?.min_mint_price?.amount)
} }
if (OPEN_EDITION_UPDATABLE_FACTORY_ADDRESS) {
const openEditionUpdatableFactoryParameters = await client
.queryContractSmart(OPEN_EDITION_UPDATABLE_FACTORY_ADDRESS, { params: {} })
.catch((error) => {
toast.error(`${error.message}`, { style: { maxWidth: 'none' } })
addLogItem({ id: uid(), message: error.message, type: 'Error', timestamp: new Date() })
})
setOpenEditionMinterUpdatableCreationFee(openEditionUpdatableFactoryParameters?.params?.creation_fee?.amount)
setMinimumOpenEditionUpdatableMintPrice(openEditionUpdatableFactoryParameters?.params?.min_mint_price?.amount)
}
setInitialParametersFetched(true) setInitialParametersFetched(true)
} }
const fetchOpenEditionFactoryParameters = useCallback(async () => { const fetchOpenEditionFactoryParameters = useCallback(async () => {
const client = await wallet.getCosmWasmClient() const client = await wallet.getCosmWasmClient()
const factoryForSelectedDenom = openEditionMinterList.find( const factoryForSelectedDenom = openEditionMinterList
(minter) => .concat(flexibleOpenEditionMinterList)
minter.supportedToken === openEditionMinterDetails?.mintingDetails?.selectedMintToken && .find(
minter.updatable === false, (minter) =>
) minter.supportedToken === openEditionMinterDetails?.mintingDetails?.selectedMintToken &&
const updatableFactoryForSelectedDenom = openEditionMinterList.find( minter.updatable === openEditionMinterDetails.collectionDetails?.updatable &&
(minter) => minter.flexible ===
minter.supportedToken === openEditionMinterDetails?.mintingDetails?.selectedMintToken && (openEditionMinterDetails.whitelistDetails?.whitelistState !== 'none' &&
minter.updatable === true, openEditionMinterDetails.whitelistDetails?.whitelistType === 'flex'),
) )
console.log('OE Factory: ', factoryForSelectedDenom?.factoryAddress)
if (factoryForSelectedDenom?.factoryAddress) { if (factoryForSelectedDenom?.factoryAddress) {
setIsMatchingOpenEditionFactoryPresent(true)
setOpenEditionFactoryAddress(factoryForSelectedDenom.factoryAddress)
const openEditionFactoryParameters = await client const openEditionFactoryParameters = await client
.queryContractSmart(factoryForSelectedDenom.factoryAddress, { params: {} }) .queryContractSmart(factoryForSelectedDenom.factoryAddress, { params: {} })
.catch((error) => { .catch((error) => {
toast.error(`${error.message}`, { style: { maxWidth: 'none' } }) toast.error(`${error.message}`, { style: { maxWidth: 'none' } })
addLogItem({ id: uid(), message: error.message, type: 'Error', timestamp: new Date() }) addLogItem({ id: uid(), message: error.message, type: 'Error', timestamp: new Date() })
}) })
setOpenEditionMinterCreationFee(openEditionFactoryParameters?.params?.creation_fee?.amount) setOpenEditionMinterCreationFee(openEditionFactoryParameters?.params?.creation_fee)
if (!openEditionMinterDetails?.collectionDetails?.updatable) { setMinimumOpenEditionMintPrice(openEditionFactoryParameters?.params?.min_mint_price?.amount)
setMinimumOpenEditionMintPrice(openEditionFactoryParameters?.params?.min_mint_price?.amount) setMintTokenFromOpenEditionFactory(
setMintTokenFromOpenEditionFactory( tokensList.find((token) => token.denom === openEditionFactoryParameters?.params?.min_mint_price?.denom),
tokensList.find((token) => token.denom === openEditionFactoryParameters?.params?.min_mint_price?.denom), )
) } else if (
} openEditionMinterDetails?.mintingDetails?.selectedMintToken &&
} openEditionMinterDetails.whitelistDetails?.whitelistState
if (updatableFactoryForSelectedDenom?.factoryAddress) { ) {
const openEditionUpdatableFactoryParameters = await client setIsMatchingOpenEditionFactoryPresent(false)
.queryContractSmart(updatableFactoryForSelectedDenom.factoryAddress, { params: {} })
.catch((error) => {
toast.error(`${error.message}`, { style: { maxWidth: 'none' } })
addLogItem({ id: uid(), message: error.message, type: 'Error', timestamp: new Date() })
})
setOpenEditionMinterUpdatableCreationFee(openEditionUpdatableFactoryParameters?.params?.creation_fee?.amount)
if (openEditionMinterDetails?.collectionDetails?.updatable) {
setMinimumOpenEditionUpdatableMintPrice(openEditionUpdatableFactoryParameters?.params?.min_mint_price?.amount)
setMintTokenFromOpenEditionFactory(
tokensList.find(
(token) => token.denom === openEditionUpdatableFactoryParameters?.params?.min_mint_price?.denom,
),
)
}
} }
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [ }, [
openEditionMinterDetails?.mintingDetails?.selectedMintToken, openEditionMinterDetails?.mintingDetails?.selectedMintToken,
openEditionMinterDetails?.collectionDetails?.updatable, openEditionMinterDetails?.collectionDetails?.updatable,
openEditionMinterDetails?.whitelistDetails?.whitelistType,
openEditionMinterDetails?.whitelistDetails?.whitelistState,
wallet.isWalletConnected, wallet.isWalletConnected,
openEditionMinterDetails?.isRefreshed,
]) ])
const fetchVendingFactoryParameters = useCallback(async () => { const fetchVendingFactoryParameters = useCallback(async () => {
const client = await wallet.getCosmWasmClient() const client = await wallet.getCosmWasmClient()
const vendingFactoryForSelectedDenom = vendingMinterList const vendingFactoryForSelectedDenom = vendingMinterList
.concat(flexibleVendingMinterList) .concat(flexibleVendingMinterList)
.concat(merkleTreeVendingMinterList)
.find( .find(
(minter) => (minter) =>
minter.supportedToken === mintingDetails?.selectedMintToken && minter.supportedToken === mintingDetails?.selectedMintToken &&
minter.updatable === collectionDetails?.updatable && minter.updatable === collectionDetails?.updatable &&
minter.flexible === (whitelistDetails?.whitelistType === 'flex') && minter.flexible === (whitelistDetails?.whitelistType === 'flex') &&
minter.merkleTree === (whitelistDetails?.whitelistType === 'merkletree') &&
minter.featured === isFeaturedCollection, minter.featured === isFeaturedCollection,
)?.factoryAddress )?.factoryAddress
console.log('Vending Factory: ', vendingFactoryForSelectedDenom)
if (vendingFactoryForSelectedDenom) { if (vendingFactoryForSelectedDenom) {
setIsMatchingVendingFactoryPresent(true) setIsMatchingVendingFactoryPresent(true)
@ -1295,13 +1342,13 @@ const CollectionCreationPage: NextPage = () => {
}) })
if (whitelistDetails?.whitelistState !== 'none' && whitelistDetails?.whitelistType === 'flex') { if (whitelistDetails?.whitelistState !== 'none' && whitelistDetails?.whitelistType === 'flex') {
setVendingMinterFlexCreationFee(vendingFactoryParameters?.params?.creation_fee?.amount) setVendingMinterFlexCreationFee(vendingFactoryParameters?.params?.creation_fee)
setMinimumFlexMintPrice(vendingFactoryParameters?.params?.min_mint_price?.amount) setMinimumFlexMintPrice(vendingFactoryParameters?.params?.min_mint_price?.amount)
} else if (collectionDetails?.updatable) { } else if (collectionDetails?.updatable) {
setVendingMinterUpdatableCreationFee(vendingFactoryParameters?.params?.creation_fee?.amount) setVendingMinterUpdatableCreationFee(vendingFactoryParameters?.params?.creation_fee)
setMinimumUpdatableMintPrice(vendingFactoryParameters?.params?.min_mint_price?.amount) setMinimumUpdatableMintPrice(vendingFactoryParameters?.params?.min_mint_price?.amount)
} else { } else {
setVendingMinterCreationFee(vendingFactoryParameters?.params?.creation_fee?.amount) setVendingMinterCreationFee(vendingFactoryParameters?.params?.creation_fee)
setMinimumMintPrice(vendingFactoryParameters?.params?.min_mint_price?.amount) setMinimumMintPrice(vendingFactoryParameters?.params?.min_mint_price?.amount)
} }
setMintTokenFromVendingFactory( setMintTokenFromVendingFactory(
@ -1320,39 +1367,82 @@ const CollectionCreationPage: NextPage = () => {
]) ])
const checkwalletBalance = async () => { const checkwalletBalance = async () => {
await (await wallet.getCosmWasmClient()).getBalance(wallet.address || '', 'ustars').then((balance) => { const queryClient = await wallet.getCosmWasmClient()
if (minterType === 'vending' && whitelistDetails?.whitelistState === 'new' && whitelistDetails.memberLimit) {
const amountNeeded = const creationFee: Coin | null =
Math.ceil(Number(whitelistDetails.memberLimit) / 1000) * 100000000 + minterType === 'vending'
(whitelistDetails.whitelistType === 'flex' ? whitelistDetails?.whitelistType === 'flex'
? Number(vendingMinterFlexCreationFee) ? vendingMinterFlexCreationFee
: collectionDetails?.updatable : collectionDetails?.updatable
? Number(vendingMinterUpdatableCreationFee) ? vendingMinterUpdatableCreationFee
: Number(vendingMinterCreationFee)) : vendingMinterCreationFee
if (amountNeeded >= Number(balance.amount)) : collectionDetails?.updatable
throw new Error( ? baseMinterUpdatableCreationFee
`Insufficient wallet balance to instantiate the required contracts. Needed amount: ${( : baseMinterCreationFee
amountNeeded / 1000000
).toString()} STARS`, const creationFeeDenom = tokensList.find((token) => token.denom === creationFee?.denom)
)
} else { await queryClient.getBalance(wallet.address || '', 'ustars').then(async (starsBalance) => {
const amountNeeded = await queryClient
minterType === 'vending' .getBalance(wallet.address || '', creationFee?.denom as string)
? whitelistDetails?.whitelistState === 'existing' && whitelistDetails.whitelistType === 'flex' .then((creationFeeDenomBalance) => {
? Number(vendingMinterFlexCreationFee) if (minterType === 'vending' && whitelistDetails?.whitelistState === 'new') {
: collectionDetails?.updatable if (whitelistDetails.whitelistType !== 'merkletree' && whitelistDetails.memberLimit) {
? Number(vendingMinterUpdatableCreationFee) const whitelistCreationFee = Math.ceil(Number(whitelistDetails.memberLimit) / 1000) * 100000000
: Number(vendingMinterCreationFee) if (creationFee?.denom === 'ustars') {
: collectionDetails?.updatable const amountNeeded = whitelistCreationFee + Number(creationFee.amount)
? Number(baseMinterUpdatableCreationFee) if (amountNeeded >= Number(starsBalance.amount))
: Number(baseMinterCreationFee) throw new Error(
if (amountNeeded >= Number(balance.amount)) `Insufficient wallet balance to instantiate the required contracts. Needed amount: ${(
throw new Error( amountNeeded / 1000000
`Insufficient wallet balance to instantiate the required contracts. Needed amount: ${( ).toString()} STARS`,
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(creationFee?.amount) > Number(creationFeeDenomBalance.amount))
throw new Error(
`Insufficient wallet balance to instantiate the required contracts. Needed amount: ${(
Number(creationFee?.amount) / 1000000
).toString()} ${creationFeeDenom ? creationFeeDenom.displayName : creationFee?.denom}`,
)
}
} else if (whitelistDetails.whitelistType === 'merkletree') {
const merkleWhitelistCreationFee = 1000000000
if (creationFee?.denom === 'ustars') {
const amountNeeded = merkleWhitelistCreationFee + Number(creationFee.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 (merkleWhitelistCreationFee >= Number(starsBalance.amount))
throw new Error(
`Insufficient wallet balance to instantiate the whitelist. Needed amount: ${(
merkleWhitelistCreationFee / 1000000
).toString()} STARS`,
)
if (Number(creationFee?.amount) > Number(creationFeeDenomBalance.amount))
throw new Error(
`Insufficient wallet balance to instantiate the required contracts. Needed amount: ${(
Number(creationFee?.amount) / 1000000
).toString()} ${creationFeeDenom ? creationFeeDenom.displayName : creationFee?.denom}`,
)
}
}
} else if (Number(creationFee?.amount) > Number(creationFeeDenomBalance.amount))
throw new Error(
`Insufficient wallet balance to instantiate the required contracts. Needed amount: ${(
Number(creationFee?.amount) / 1000000
).toString()} ${creationFeeDenom ? creationFeeDenom.displayName : creationFee?.denom}`,
)
})
}) })
} }
@ -1540,6 +1630,19 @@ const CollectionCreationPage: NextPage = () => {
> >
{openEditionMinterCreatorData?.sg721ContractAddress as string} {openEditionMinterCreatorData?.sg721ContractAddress as string}
</Anchor> </Anchor>
<Conditional test={openEditionMinterCreatorData?.whitelistContractAddress !== null}>
<br />
Whitelist Contract Address:{' '}
<Anchor
className="text-stargaze hover:underline"
external
href={`/contracts/whitelist/query/?contractAddress=${
openEditionMinterCreatorData?.whitelistContractAddress as string
}`}
>
{openEditionMinterCreatorData?.whitelistContractAddress as string}
</Anchor>
</Conditional>
<br /> <br />
Transaction Hash: {' '} Transaction Hash: {' '}
<Conditional test={NETWORK === 'testnet'}> <Conditional test={NETWORK === 'testnet'}>
@ -1848,14 +1951,14 @@ const CollectionCreationPage: NextPage = () => {
<Conditional test={minterType === 'openEdition'}> <Conditional test={minterType === 'openEdition'}>
<OpenEditionMinterCreator <OpenEditionMinterCreator
importedOpenEditionMinterDetails={importedDetails?.openEditionMinterDetails} importedOpenEditionMinterDetails={importedDetails?.openEditionMinterDetails}
isMatchingFactoryPresent={isMatchingOpenEditionFactoryPresent}
minimumMintPrice={minimumOpenEditionMintPrice as string} minimumMintPrice={minimumOpenEditionMintPrice as string}
minimumUpdatableMintPrice={minimumOpenEditionUpdatableMintPrice as string}
mintTokenFromFactory={mintTokenFromOpenEditionFactory} mintTokenFromFactory={mintTokenFromOpenEditionFactory}
minterType={minterType} minterType={minterType}
onChange={setOpenEditionMinterCreatorData} onChange={setOpenEditionMinterCreatorData}
onDetailsChange={setOpenEditionMinterDetails} onDetailsChange={setOpenEditionMinterDetails}
openEditionMinterCreationFee={openEditionMinterCreationFee as string} openEditionFactoryAddress={openEditionFactoryAddress}
openEditionMinterUpdatableCreationFee={openEditionMinterUpdatableCreationFee as string} openEditionMinterCreationFee={openEditionMinterCreationFee}
/> />
</Conditional> </Conditional>
<div className="mx-10"> <div className="mx-10">

View File

@ -60,6 +60,12 @@ const CollectionList: NextPage = () => {
if (minterConfig?.whitelist) collection.whitelist = minterConfig.whitelist if (minterConfig?.whitelist) collection.whitelist = minterConfig.whitelist
setMyStandardCollections((prevState) => [...prevState, collection]) setMyStandardCollections((prevState) => [...prevState, collection])
} else if (contractType?.includes('open-edition')) { } else if (contractType?.includes('open-edition')) {
const minterConfig = await (await wallet.getCosmWasmClient())
.queryContractSmart(collection.minter, { config: {} })
.catch(() => {
console.log('Unable to retrieve minter config')
})
if (minterConfig?.whitelist) collection.whitelist = minterConfig.whitelist
setMyOpenEditionCollections((prevState) => [...prevState, collection]) setMyOpenEditionCollections((prevState) => [...prevState, collection])
} }
}) })
@ -429,6 +435,31 @@ const CollectionList: NextPage = () => {
</Tooltip> </Tooltip>
</span> </span>
</div> </div>
<Conditional test={collection.whitelist}>
<div className="flex flex-row items-center space-x-3">
Whitelist:
<span className="ml-2">
<Tooltip
backgroundColor="bg-blue-500"
label="Click to copy the whitelist contract address"
>
<button
className="group flex space-x-2 font-mono text-base text-white/80 hover:underline"
onClick={() => void copy(collection.whitelist as string)}
type="button"
>
<span>
{truncateMiddle(
collection.whitelist ? (collection.whitelist as string) : '',
36,
)}
</span>
<FaCopy className="opacity-0 group-hover:opacity-100" />
</button>
</Tooltip>
</span>
</div>
</Conditional>
</td> </td>
<th className="bg-black"> <th className="bg-black">
<div className="flex items-center space-x-8"> <div className="flex items-center space-x-8">
@ -445,6 +476,14 @@ const CollectionList: NextPage = () => {
> >
<FaRocket /> <FaRocket />
</Anchor> </Anchor>
<Conditional test={collection.whitelist}>
<Anchor
className="text-xl text-white"
href={`/contracts/whitelist/execute/?contractAddress=${collection.whitelist}`}
>
<FaList />
</Anchor>
</Conditional>
</div> </div>
</th> </th>
</tr> </tr>

View File

@ -0,0 +1,122 @@
/* eslint-disable eslint-comments/disable-enable-pair */
import { Alert } from 'components/Alert'
import { Button } from 'components/Button'
import { Conditional } from 'components/Conditional'
import { ContractPageHeader } from 'components/ContractPageHeader'
import { AddressInput } from 'components/forms/FormInput'
import { useInputState } from 'components/forms/FormInput.hooks'
import type { NextPage } from 'next'
import { useRouter } from 'next/router'
import { NextSeo } from 'next-seo'
import { useEffect, useState } from 'react'
import { toast } from 'react-hot-toast'
import { NETWORK } from 'utils/constants'
import { useDebounce } from 'utils/debounce'
import { withMetadata } from 'utils/layout'
import { links } from 'utils/links'
import { resolveAddress } from 'utils/resolveAddress'
import { useWallet } from 'utils/wallet'
const RemoveOfferPage: NextPage = () => {
const wallet = useWallet()
const [isLoading, setIsLoading] = useState(false)
const [txHash, setTxHash] = useState<string | undefined>(undefined)
const collectionAddressState = useInputState({
id: 'collection-address',
name: 'collectionAddress',
title: 'Collection Contract Address',
defaultValue: '',
placeholder: 'stars1...',
})
const collectionAddress = useDebounce(collectionAddressState.value, 300)
const router = useRouter()
useEffect(() => {
if (collectionAddress.length > 0) {
void router.replace({ query: { contractAddress: collectionAddress } })
}
if (collectionAddress.length === 0) {
void router.replace({ query: {} })
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [collectionAddress])
useEffect(() => {
const initial = new URL(document.URL).searchParams.get('contractAddress')
if (initial && initial.length > 0) collectionAddressState.onChange(initial)
}, [])
const resolveCollectionAddress = async () => {
await resolveAddress(collectionAddressState.value.trim(), wallet).then((resolvedAddress) => {
if (resolvedAddress) {
collectionAddressState.onChange(resolvedAddress)
}
})
}
useEffect(() => {
void resolveCollectionAddress()
}, [collectionAddressState.value])
const handleRemoveOffer = async () => {
if (!wallet.isWalletConnected) return toast.error('Please connect your wallet.')
if (!collectionAddressState.value) return toast.error('Please enter a collection address.')
const client = await wallet.getSigningCosmWasmClient()
setTxHash(undefined)
setIsLoading(true)
try {
const result = await client.execute(
wallet.address as string,
NETWORK === 'mainnet'
? 'stars1fvhcnyddukcqfnt7nlwv3thm5we22lyxyxylr9h77cvgkcn43xfsvgv0pl'
: 'stars18cszlvm6pze0x9sz32qnjq4vtd45xehqs8dq7cwy8yhq35wfnn3qgzs5gu',
{
remove_collection_bid: {
collection: collectionAddressState.value,
},
},
'auto',
)
toast.success('Offer successfully removed.')
setTxHash(result.transactionHash)
} catch (error: any) {
toast.error(error.message, { style: { maxWidth: 'none' } })
setTxHash(undefined)
} finally {
setIsLoading(false)
}
}
return (
<section className="py-6 px-12 space-y-4">
<NextSeo title="Remove Collection offer" />
<ContractPageHeader link={links.Documentation} title="Remove Collection Offer" />
<div className="space-y-8">
<AddressInput {...collectionAddressState} />
</div>
<div className="flex flex-row content-center">
<Button
isDisabled={collectionAddressState.value === ''}
isLoading={isLoading}
onClick={() => {
void handleRemoveOffer()
}}
>
Remove Collection Offer
</Button>
</div>
<Conditional test={txHash !== undefined}>
<Alert type="info">
<b>Transaction Hash:</b> {txHash}
</Alert>
</Conditional>
</section>
)
}
export default withMetadata(RemoveOfferPage, { center: false })

View File

@ -23,13 +23,12 @@ import { useEffect, useState } from 'react'
import { toast } from 'react-hot-toast' import { toast } from 'react-hot-toast'
import { FaAsterisk } from 'react-icons/fa' import { FaAsterisk } from 'react-icons/fa'
import { useMutation } from 'react-query' import { useMutation } from 'react-query'
import { BASE_FACTORY_ADDRESS } from 'utils/constants' import { BASE_FACTORY_ADDRESS, BASE_FACTORY_SG721_CODE_ID } from 'utils/constants'
import { withMetadata } from 'utils/layout' import { withMetadata } from 'utils/layout'
import { links } from 'utils/links' import { links } from 'utils/links'
import { useWallet } from 'utils/wallet' import { useWallet } from 'utils/wallet'
import type { CreateBaseMinterResponse } from '../../../contracts/baseFactory/contract' import type { CreateBaseMinterResponse } from '../../../contracts/baseFactory/contract'
import { SG721_CODE_ID } from '../../../utils/constants'
import { resolveAddress } from '../../../utils/resolveAddress' import { resolveAddress } from '../../../utils/resolveAddress'
const BaseMinterInstantiatePage: NextPage = () => { const BaseMinterInstantiatePage: NextPage = () => {
@ -62,7 +61,7 @@ const BaseMinterInstantiatePage: NextPage = () => {
title: 'Code ID', title: 'Code ID',
subtitle: 'Code ID for the sg721 contract', subtitle: 'Code ID for the sg721 contract',
placeholder: '1', placeholder: '1',
defaultValue: SG721_CODE_ID, defaultValue: BASE_FACTORY_SG721_CODE_ID,
}) })
const creatorState = useInputState({ const creatorState = useInputState({

View File

@ -1,14 +1,28 @@
/* eslint-disable eslint-comments/disable-enable-pair */ /* eslint-disable eslint-comments/disable-enable-pair */
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */
import type { EncodeObject } from '@cosmjs/proto-signing'
import { GasPrice, SigningStargateClient } from '@cosmjs/stargate'
import clsx from 'clsx' import clsx from 'clsx'
import { Alert } from 'components/Alert' import { Alert } from 'components/Alert'
import { Button } from 'components/Button' import { Button } from 'components/Button'
import { Conditional } from 'components/Conditional' import { Conditional } from 'components/Conditional'
import { ContractPageHeader } from 'components/ContractPageHeader' import { ContractPageHeader } from 'components/ContractPageHeader'
import { AddressList } from 'components/forms/AddressList'
import { useAddressListState } from 'components/forms/AddressList.hooks'
import { TextInput } from 'components/forms/FormInput'
import { useInputState } from 'components/forms/FormInput.hooks'
import { JsonPreview } from 'components/JsonPreview' import { JsonPreview } from 'components/JsonPreview'
import { getConfig } from 'config'
import { MsgExec } from 'cosmjs-types/cosmos/authz/v1beta1/tx'
import { MsgStoreCode } from 'cosmjs-types/cosmwasm/wasm/v1/tx'
import { AccessConfig, AccessType } from 'cosmjs-types/cosmwasm/wasm/v1/types'
import type { NextPage } from 'next' import type { NextPage } from 'next'
import { NextSeo } from 'next-seo' import { NextSeo } from 'next-seo'
import pako from 'pako'
import { useEffect, useRef, useState } from 'react' import { useEffect, useRef, useState } from 'react'
import { toast } from 'react-hot-toast' import { toast } from 'react-hot-toast'
import { FaAsterisk } from 'react-icons/fa' import { FaAsterisk } from 'react-icons/fa'
@ -23,9 +37,38 @@ const UploadContract: NextPage = () => {
const [transactionResult, setTransactionResult] = useState<any>() const [transactionResult, setTransactionResult] = useState<any>()
const [wasmFile, setWasmFile] = useState<File | null>(null) const [wasmFile, setWasmFile] = useState<File | null>(null)
const [wasmByteArray, setWasmByteArray] = useState<Uint8Array | null>(null) const [wasmByteArray, setWasmByteArray] = useState<Uint8Array | null>(null)
const [accessType, setAccessType] = useState<
'ACCESS_TYPE_UNSPECIFIED' | 'ACCESS_TYPE_EVERYBODY' | 'ACCESS_TYPE_ANY_OF_ADDRESSES' | 'ACCESS_TYPE_NOBODY'
>('ACCESS_TYPE_UNSPECIFIED')
const [accessConfig, setAccessConfig] = useState<AccessConfig | undefined>(undefined)
const [isAuthzUpload, setIsAuthzUpload] = useState(false)
const granterAddressState = useInputState({
id: 'address',
name: 'Granter Address',
title: 'Granter Address',
subtitle: 'The address that granted the authorization for contract upload',
defaultValue: '',
placeholder: 'stars1...',
})
const memoState = useInputState({
id: 'memo',
name: 'Memo',
title: 'Transaction Memo',
defaultValue: '',
placeholder: 'My contract',
})
const permittedAddressListState = useAddressListState()
const inputFile = useRef<HTMLInputElement>(null) const inputFile = useRef<HTMLInputElement>(null)
interface MsgExecAllowanceEncodeObject extends EncodeObject {
readonly typeUrl: '/cosmos.authz.v1beta1.MsgExec'
readonly value: Partial<MsgExec>
}
const onFileChange = (e: React.ChangeEvent<HTMLInputElement>) => { const onFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
if (!e.target.files) return if (!e.target.files) return
setWasmFile(e.target.files[0]) setWasmFile(e.target.files[0])
@ -51,20 +94,70 @@ const UploadContract: NextPage = () => {
try { try {
if (!wallet.isWalletConnected) return toast.error('Please connect your wallet.') if (!wallet.isWalletConnected) return toast.error('Please connect your wallet.')
if (!wasmFile || !wasmByteArray) return toast.error('No file selected.') if (!wasmFile || !wasmByteArray) return toast.error('No file selected.')
if (accessType === 'ACCESS_TYPE_UNSPECIFIED')
return toast.error('Please select an instantiation permission type.', { style: { maxWidth: 'none' } })
setLoading(true) setLoading(true)
const client = await wallet.getSigningCosmWasmClient() const client = await wallet.getSigningCosmWasmClient()
const result = await client.upload(wallet.address as string, wasmByteArray, 'auto') if (!isAuthzUpload) {
const result = await client.upload(
wallet.address as string,
wasmByteArray,
'auto',
memoState.value ?? undefined,
accessConfig,
)
setTransactionResult({
transactionHash: result.transactionHash,
codeId: result.codeId,
originalSize: result.originalSize,
compressedSize: result.compressedSize,
originalChecksum: result.checksum,
})
} else {
if (!granterAddressState.value) {
setLoading(false)
return toast.error('Please enter the authorization granter address.', { style: { maxWidth: 'none' } })
}
const compressed = pako.gzip(wasmByteArray, { level: 9 })
setTransactionResult({ const authzExecuteContractMsg: MsgExecAllowanceEncodeObject = {
transactionHash: result.transactionHash, typeUrl: '/cosmos.authz.v1beta1.MsgExec',
codeId: result.codeId, value: MsgExec.fromPartial({
originalSize: result.originalSize, grantee: wallet.address as string,
compressedSize: result.compressedSize, msgs: [
originalChecksum: result.checksum, {
}) typeUrl: '/cosmwasm.wasm.v1.MsgStoreCode',
value: MsgStoreCode.encode({
sender: granterAddressState.value,
wasmByteCode: compressed,
instantiatePermission: accessConfig,
}).finish(),
},
],
}),
}
const offlineSigner = wallet.getOfflineSignerDirect()
const stargateClient = await SigningStargateClient.connectWithSigner(getConfig(NETWORK).rpcUrl, offlineSigner, {
gasPrice: GasPrice.fromString('0.025ustars'),
})
const result = await stargateClient.signAndBroadcast(
wallet.address || '',
[authzExecuteContractMsg],
'auto',
memoState.value ?? undefined,
)
setTransactionResult({
transactionHash: result.transactionHash,
codeId: result.events.filter((event) => event.type === 'store_code')[0].attributes[1].value,
originalChecksum: result.events.filter((event) => event.type === 'store_code')[0].attributes[0].value,
})
}
setLoading(false) setLoading(false)
} catch (err: any) { } catch (err: any) {
@ -73,55 +166,120 @@ const UploadContract: NextPage = () => {
} }
} }
useEffect(() => {
try {
if (accessType === 'ACCESS_TYPE_ANY_OF_ADDRESSES') {
setAccessConfig(
AccessConfig.fromPartial({
permission: AccessType.ACCESS_TYPE_ANY_OF_ADDRESSES,
addresses: permittedAddressListState.entries.map((entry) => entry[1].address).filter(Boolean),
}),
)
} else if (accessType === 'ACCESS_TYPE_NOBODY') {
setAccessConfig(AccessConfig.fromPartial({ permission: AccessType.ACCESS_TYPE_NOBODY }))
} else if (accessType === 'ACCESS_TYPE_EVERYBODY') {
setAccessConfig(AccessConfig.fromPartial({ permission: AccessType.ACCESS_TYPE_EVERYBODY }))
} else if (accessType === 'ACCESS_TYPE_UNSPECIFIED') {
setAccessConfig(undefined)
}
} catch (error: any) {
toast.error(error.message, { style: { maxWidth: 'none' } })
}
}, [accessType, permittedAddressListState.entries])
return ( return (
<section className="py-6 px-12 space-y-4"> <section className="py-6 px-12 space-y-4">
<Conditional test={NETWORK === 'testnet'}> <NextSeo title="Upload Contract" />
<NextSeo title="Upload Contract" /> <ContractPageHeader
<ContractPageHeader description="Here you can upload a contract on Stargaze Testnet."
description="Here you can upload a contract on Stargaze Testnet." link=""
link="" title="Upload Contract"
title="Upload Contract" />
/> <div className="inset-x-0 bottom-0 border-b-2 border-white/25" />
<div className="inset-x-0 bottom-0 border-b-2 border-white/25" />
<Conditional test={Boolean(transactionResult)}> <div className="flex flex-col w-1/2">
<Alert type="info"> <span className="text-xl font-bold text-white">Authorization Type for Contract Instantiation</span>
<b>Upload success!</b> Here is the transaction result containing the code ID, transaction hash and other <select
data. className="px-4 pt-2 pb-2 mt-2 w-1/2 placeholder:text-white/50 bg-white/10 rounded border-2 border-white/20 focus:ring focus:ring-plumbus-20"
</Alert> onChange={(e) => setAccessType(e.target.value as any)}
<JsonPreview content={transactionResult} title="Transaction Result" /> value={accessType}
<br />
</Conditional>
<div
className={clsx(
'flex relative justify-center items-center space-y-4 h-32',
'rounded border-2 border-white/20 border-dashed',
)}
> >
<option disabled value="ACCESS_TYPE_UNSPECIFIED">
Select Authorization Type
</option>
<option value="ACCESS_TYPE_EVERYBODY">Everybody</option>
<option value="ACCESS_TYPE_ANY_OF_ADDRESSES">Any of Addresses</option>
<option value="ACCESS_TYPE_NOBODY">Nobody</option>
</select>
</div>
<div className="my-2 w-1/2">
<TextInput {...memoState} />
</div>
<div className="flex flex-row justify-start">
<h1 className="mt-2 font-bold text-md">Authz Upload?</h1>
<label className="justify-start ml-6 cursor-pointer label">
<input <input
accept=".wasm" checked={isAuthzUpload}
className={clsx( className={`${isAuthzUpload ? `bg-stargaze` : `bg-gray-600`} checkbox`}
'file:py-2 file:px-4 file:mr-4 file:bg-plumbus-light file:rounded file:border-0 cursor-pointer', onClick={() => {
'before:absolute before:inset-0 before:hover:bg-white/5 before:transition', setIsAuthzUpload(!isAuthzUpload)
)} }}
onChange={onFileChange} type="checkbox"
ref={inputFile} />
type="file" </label>
</div>
<Conditional test={isAuthzUpload}>
<div className="my-2 w-3/4">
<TextInput {...granterAddressState} />
</div>
</Conditional>
<Conditional test={accessType === 'ACCESS_TYPE_ANY_OF_ADDRESSES'}>
<div className="my-2 w-3/4">
<AddressList
entries={permittedAddressListState.entries}
onAdd={permittedAddressListState.add}
onChange={permittedAddressListState.update}
onRemove={permittedAddressListState.remove}
subtitle="The list of addresses permitted to instantiate the contract"
title="Permitted Addresses"
/> />
</div> </div>
</Conditional>
<div className="flex justify-end pb-6"> <Conditional test={Boolean(transactionResult)}>
<Button isDisabled={!wasmFile} isLoading={loading} isWide leftIcon={<FaAsterisk />} onClick={upload}> <Alert type="info">
Upload Contract <b>Upload success!</b> Here is the transaction result containing the code ID, transaction hash and other data.
</Button> </Alert>
</div> <JsonPreview content={transactionResult} title="Transaction Result" />
</Conditional> <br />
<Conditional test={NETWORK === 'mainnet'}>
<NextSeo title="Upload Contract" />
<ContractPageHeader description="" link="" title="Upload Contract" />
<Alert type="info">Permissionless upload of contracts is only supported for testnet currently.</Alert>
</Conditional> </Conditional>
<div
className={clsx(
'flex relative justify-center items-center space-y-4 h-32',
'rounded border-2 border-white/20 border-dashed',
)}
>
<input
accept=".wasm"
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',
)}
onChange={onFileChange}
ref={inputFile}
type="file"
/>
</div>
<div className="flex justify-end pb-6">
<Button isDisabled={!wasmFile} isLoading={loading} isWide leftIcon={<FaAsterisk />} onClick={upload}>
Upload Contract
</Button>
</div>
</section> </section>
) )
} }

View File

@ -48,7 +48,7 @@ const WhitelistExecutePage: NextPage = () => {
const [lastTx, setLastTx] = useState('') const [lastTx, setLastTx] = useState('')
const [memberList, setMemberList] = useState<string[]>([]) const [memberList, setMemberList] = useState<string[]>([])
const [flexMemberList, setFlexMemberList] = useState<WhitelistFlexMember[]>([]) const [flexMemberList, setFlexMemberList] = useState<WhitelistFlexMember[]>([])
const [whitelistType, setWhitelistType] = useState<'standard' | 'flex'>('standard') const [whitelistType, setWhitelistType] = useState<'standard' | 'flex' | 'merkletree'>('standard')
const comboboxState = useExecuteComboboxState() const comboboxState = useExecuteComboboxState()
const type = comboboxState.value?.id const type = comboboxState.value?.id
@ -211,6 +211,8 @@ const WhitelistExecutePage: NextPage = () => {
.then((contractType) => { .then((contractType) => {
if (contractType?.includes('flex')) { if (contractType?.includes('flex')) {
setWhitelistType('flex') setWhitelistType('flex')
} else if (contractType?.includes('merkle')) {
setWhitelistType('merkletree')
} else { } else {
setWhitelistType('standard') setWhitelistType('standard')
} }
@ -236,7 +238,7 @@ const WhitelistExecutePage: NextPage = () => {
<form className="grid grid-cols-2 p-4 space-x-8" onSubmit={mutate}> <form className="grid grid-cols-2 p-4 space-x-8" onSubmit={mutate}>
<div className="space-y-8"> <div className="space-y-8">
<AddressInput {...contractState} /> <AddressInput {...contractState} />
<ExecuteCombobox {...comboboxState} /> <ExecuteCombobox whitelistType={whitelistType} {...comboboxState} />
<Conditional test={showLimitState}> <Conditional test={showLimitState}>
<NumberInput {...limitState} /> <NumberInput {...limitState} />
</Conditional> </Conditional>

View File

@ -1,6 +1,7 @@
/* eslint-disable eslint-comments/disable-enable-pair */ /* eslint-disable eslint-comments/disable-enable-pair */
/* eslint-disable no-nested-ternary */ /* eslint-disable no-nested-ternary */
import { coin } from '@cosmjs/proto-signing' import { coin } from '@cosmjs/proto-signing'
import axios from 'axios'
import { Alert } from 'components/Alert' import { Alert } from 'components/Alert'
import { Button } from 'components/Button' import { Button } from 'components/Button'
import { Conditional } from 'components/Conditional' import { Conditional } from 'components/Conditional'
@ -34,17 +35,22 @@ import { withMetadata } from 'utils/layout'
import { links } from 'utils/links' import { links } from 'utils/links'
import { useWallet } from 'utils/wallet' import { useWallet } from 'utils/wallet'
import { WHITELIST_CODE_ID, WHITELIST_FLEX_CODE_ID } from '../../../utils/constants' import {
WHITELIST_CODE_ID,
WHITELIST_FLEX_CODE_ID,
WHITELIST_MERKLE_TREE_API_URL,
WHITELIST_MERKLE_TREE_CODE_ID,
} from '../../../utils/constants'
const WhitelistInstantiatePage: NextPage = () => { const WhitelistInstantiatePage: NextPage = () => {
const wallet = useWallet() const wallet = useWallet()
const { whitelist: contract } = useContracts() const { whitelist: contract, whitelistMerkleTree: whitelistMerkleTreeContract } = useContracts()
const { timezone } = useGlobalSettings() const { timezone } = useGlobalSettings()
const [startDate, setStartDate] = useState<Date | undefined>(undefined) const [startDate, setStartDate] = useState<Date | undefined>(undefined)
const [endDate, setEndDate] = useState<Date | undefined>(undefined) const [endDate, setEndDate] = useState<Date | undefined>(undefined)
const [adminsMutable, setAdminsMutable] = useState<boolean>(true) const [adminsMutable, setAdminsMutable] = useState<boolean>(true)
const [whitelistType, setWhitelistType] = useState<'standard' | 'flex'>('standard') const [whitelistType, setWhitelistType] = useState<'standard' | 'flex' | 'merkletree'>('standard')
const [whitelistStandardArray, setWhitelistStandardArray] = useState<string[]>([]) const [whitelistStandardArray, setWhitelistStandardArray] = useState<string[]>([])
const [whitelistFlexArray, setWhitelistFlexArray] = useState<WhitelistFlexMember[]>([]) const [whitelistFlexArray, setWhitelistFlexArray] = useState<WhitelistFlexMember[]>([])
@ -85,12 +91,16 @@ const WhitelistInstantiatePage: NextPage = () => {
const addressListState = useAddressListState() const addressListState = useAddressListState()
const { data, isLoading, mutate } = useMutation( const { data, isLoading, mutate } = useMutation(
async (event: FormEvent): Promise<InstantiateResponse | null> => { async (event: FormEvent): Promise<InstantiateResponse | undefined | null> => {
event.preventDefault() event.preventDefault()
if (!contract) { if (!contract) {
throw new Error('Smart contract connection failed') throw new Error('Smart contract connection failed')
} }
if (!whitelistMerkleTreeContract && whitelistType === 'merkletree') {
throw new Error('Smart contract connection failed')
}
if (!startDate) { if (!startDate) {
throw new Error('Start date is required') throw new Error('Start date is required')
} }
@ -132,19 +142,79 @@ const WhitelistInstantiatePage: NextPage = () => {
admins_mutable: adminsMutable, admins_mutable: adminsMutable,
} }
return toast.promise( if (whitelistType !== 'merkletree') {
contract.instantiate( return toast.promise(
whitelistType === 'standard' ? WHITELIST_CODE_ID : WHITELIST_FLEX_CODE_ID, contract.instantiate(
whitelistType === 'standard' ? standardMsg : flexMsg, whitelistType === 'standard' ? WHITELIST_CODE_ID : WHITELIST_FLEX_CODE_ID,
whitelistType === 'standard' ? 'Stargaze Whitelist Contract' : 'Stargaze Whitelist Flex Contract', whitelistType === 'standard' ? standardMsg : flexMsg,
wallet.address, whitelistType === 'standard' ? 'Stargaze Whitelist Contract' : 'Stargaze Whitelist Flex Contract',
), wallet.address,
{ ),
loading: 'Instantiating contract...', {
error: 'Instantiation failed!', loading: 'Instantiating contract...',
success: 'Instantiation success!', error: 'Instantiation failed!',
}, success: 'Instantiation success!',
) },
)
} else if (whitelistType === 'merkletree') {
const members = whitelistStandardArray
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: (startDate.getTime() * 1_000_000).toString(),
end_time: (endDate.getTime() * 1_000_000).toString(),
mint_price: coin(String(Number(unitPriceState.value) * 1000000), selectedMintToken?.denom || 'ustars'),
per_address_limit: perAddressLimitState.value,
admins: [
...new Set(
addressListState.values
.map((a) => a.address.trim())
.filter((address) => address !== '' && isValidAddress(address.trim()) && address.startsWith('stars')),
),
] || [wallet.address],
admins_mutable: adminsMutable,
}
return toast.promise(
whitelistMerkleTreeContract?.instantiate(
WHITELIST_MERKLE_TREE_CODE_ID,
merkleTreeMsg,
'Stargaze Whitelist Merkle Tree Contract',
wallet.address,
) as Promise<InstantiateResponse>,
{
loading: 'Instantiating contract...',
error: 'Instantiation failed!',
success: 'Instantiation success!',
},
)
}
}, },
{ {
onError: (error) => { onError: (error) => {
@ -176,7 +246,7 @@ const WhitelistInstantiatePage: NextPage = () => {
/> />
<LinkTabs activeIndex={0} data={whitelistLinkTabs} /> <LinkTabs activeIndex={0} data={whitelistLinkTabs} />
<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-[520px] text-lg font-bold">
<div className="form-check form-check-inline"> <div className="form-check form-check-inline">
<input <input
checked={whitelistType === 'standard'} checked={whitelistType === 'standard'}
@ -216,6 +286,26 @@ const WhitelistInstantiatePage: NextPage = () => {
Whitelist Flex Whitelist Flex
</label> </label>
</div> </div>
<div className="form-check form-check-inline">
<input
checked={whitelistType === 'merkletree'}
className="peer sr-only"
id="inlineRadio3"
name="inlineRadioOptions3"
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="inlineRadio3"
>
Whitelist Merkle Tree
</label>
</div>
</div> </div>
<Conditional test={Boolean(data)}> <Conditional test={Boolean(data)}>
@ -251,7 +341,7 @@ const WhitelistInstantiatePage: NextPage = () => {
</div> </div>
<FormGroup subtitle="Your whitelisted addresses" title="Whitelist File"> <FormGroup subtitle="Your whitelisted addresses" title="Whitelist File">
<Conditional test={whitelistType === 'standard'}> <Conditional test={whitelistType === 'standard' || whitelistType === 'merkletree'}>
<WhitelistUpload onChange={whitelistFileOnChange} /> <WhitelistUpload onChange={whitelistFileOnChange} />
<Conditional test={whitelistStandardArray.length > 0}> <Conditional test={whitelistStandardArray.length > 0}>
<JsonPreview content={whitelistStandardArray} initialState={false} title="File Contents" /> <JsonPreview content={whitelistStandardArray} initialState={false} title="File Contents" />
@ -283,8 +373,10 @@ const WhitelistInstantiatePage: NextPage = () => {
</select> </select>
</div> </div>
<NumberInput isRequired {...memberLimitState} /> <Conditional test={whitelistType !== 'merkletree'}>
<Conditional test={whitelistType === 'standard'}> <NumberInput isRequired {...memberLimitState} />
</Conditional>
<Conditional test={whitelistType === 'standard' || whitelistType === 'merkletree'}>
<NumberInput isRequired {...perAddressLimitState} /> <NumberInput isRequired {...perAddressLimitState} />
</Conditional> </Conditional>
<Conditional test={whitelistType === 'flex'}> <Conditional test={whitelistType === 'flex'}>

View File

@ -1,4 +1,6 @@
/* eslint-disable eslint-comments/disable-enable-pair */ /* eslint-disable eslint-comments/disable-enable-pair */
/* eslint-disable @typescript-eslint/restrict-template-expressions */
/* eslint-disable no-nested-ternary */
/* eslint-disable no-await-in-loop */ /* eslint-disable no-await-in-loop */
/* eslint-disable @typescript-eslint/no-unsafe-return */ /* eslint-disable @typescript-eslint/no-unsafe-return */
@ -8,6 +10,7 @@
import { toUtf8 } from '@cosmjs/encoding' import { toUtf8 } from '@cosmjs/encoding'
import clsx from 'clsx' import clsx from 'clsx'
import { Button } from 'components/Button' import { Button } from 'components/Button'
import type { WhitelistType } from 'components/collections/creation/WhitelistDetails'
import { Conditional } from 'components/Conditional' import { Conditional } from 'components/Conditional'
import { ContractPageHeader } from 'components/ContractPageHeader' import { ContractPageHeader } from 'components/ContractPageHeader'
import { FormControl } from 'components/FormControl' import { FormControl } from 'components/FormControl'
@ -19,12 +22,18 @@ import { whitelistLinkTabs } from 'components/LinkTabs.data'
import { useContracts } from 'contexts/contracts' import { useContracts } from 'contexts/contracts'
import type { QueryType } from 'contracts/whitelist/messages/query' import type { QueryType } from 'contracts/whitelist/messages/query'
import { dispatchQuery, QUERY_LIST } from 'contracts/whitelist/messages/query' import { dispatchQuery, QUERY_LIST } from 'contracts/whitelist/messages/query'
import type { WhitelistMerkleTreeQueryType } from 'contracts/whitelistMerkleTree/messages/query'
import {
dispatchQuery as disptachWhitelistMerkleTreeQuery,
WHITELIST_MERKLE_TREE_QUERY_LIST,
} from 'contracts/whitelistMerkleTree/messages/query'
import type { NextPage } from 'next' import type { NextPage } from 'next'
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
import { NextSeo } from 'next-seo' import { NextSeo } from 'next-seo'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { toast } from 'react-hot-toast' import { toast } from 'react-hot-toast'
import { useQuery } from 'react-query' import { useQuery } from 'react-query'
import { WHITELIST_MERKLE_TREE_API_URL } from 'utils/constants'
import { useDebounce } from 'utils/debounce' import { useDebounce } from 'utils/debounce'
import { withMetadata } from 'utils/layout' import { withMetadata } from 'utils/layout'
import { links } from 'utils/links' import { links } from 'utils/links'
@ -33,8 +42,11 @@ import { useWallet } from 'utils/wallet'
const WhitelistQueryPage: NextPage = () => { const WhitelistQueryPage: NextPage = () => {
const { whitelist: contract } = useContracts() const { whitelist: contract } = useContracts()
const { whitelistMerkleTree: contractWhitelistMerkleTree } = useContracts()
const wallet = useWallet() const wallet = useWallet()
const [exporting, setExporting] = useState(false) const [exporting, setExporting] = useState(false)
const [whitelistType, setWhitelistType] = useState<WhitelistType>('standard')
const [proofHashes, setProofHashes] = useState<string[]>([])
const contractState = useInputState({ const contractState = useInputState({
id: 'contract-address', id: 'contract-address',
@ -44,6 +56,46 @@ const WhitelistQueryPage: NextPage = () => {
}) })
const contractAddress = contractState.value const contractAddress = contractState.value
const debouncedWhitelistContractState = useDebounce(contractAddress, 300)
useEffect(() => {
async function getWhitelistContractType() {
if (debouncedWhitelistContractState.length > 0) {
const client = await wallet.getCosmWasmClient()
const data = await toast.promise(
client.queryContractRaw(
debouncedWhitelistContractState,
toUtf8(Buffer.from(Buffer.from('contract_info').toString('hex'), 'hex').toString()),
),
{
loading: 'Retrieving whitelist type...',
error: 'Whitelist type retrieval failed.',
success: 'Whitelist type retrieved.',
},
)
const whitelistContract: string = JSON.parse(new TextDecoder().decode(data as Uint8Array)).contract
console.log(contract)
return whitelistContract
}
}
void getWhitelistContractType()
.then((whitelistContract) => {
if (whitelistContract?.includes('merkletree')) {
setWhitelistType('merkletree')
} else if (whitelistContract?.includes('flex')) {
setWhitelistType('flex')
} else {
setWhitelistType('standard')
}
})
.catch((err) => {
console.log(err)
setWhitelistType('standard')
console.log('Unable to retrieve contract type. Defaulting to "standard".')
})
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [debouncedWhitelistContractState, wallet.isWalletConnected])
const addressState = useInputState({ const addressState = useInputState({
id: 'address', id: 'address',
name: 'address', name: 'address',
@ -52,6 +104,47 @@ const WhitelistQueryPage: NextPage = () => {
}) })
const address = addressState.value const address = addressState.value
const debouncedAddress = useDebounce(address, 300)
const fetchProofHashes = async (whitelistContractAddress: string, memberAddress: string): Promise<string[]> => {
if (whitelistContractAddress.length === 0 || memberAddress.length === 0)
throw new Error('Contract or member address is empty.')
const resolvedAddress = await resolveAddress(memberAddress, wallet)
const merkleRootResponse = await (
await wallet.getCosmWasmClient()
).queryContractSmart(contractAddress, { merkle_root: {} })
const proofs = await toast.promise(
fetch(`${WHITELIST_MERKLE_TREE_API_URL}/whitelist/${merkleRootResponse.merkle_root}/${resolvedAddress}`)
.then((res) => res.json())
.then((data) => data.proofs)
.catch((e) => {
console.log(e)
setProofHashes([])
}),
{
loading: 'Fetching proof hashes...',
error: 'Error fetching proof hashes from Whitelist Merkle Tree API.',
success: 'Proof hashes fetched.',
},
)
return proofs as string[] | []
}
useEffect(() => {
if (
whitelistType === 'merkletree' &&
whitelistMerkleTreeQueryType === 'has_member' &&
debouncedAddress.length > 0
) {
void fetchProofHashes(contractAddress, debouncedAddress)
.then((proofs) => setProofHashes(proofs))
.catch((e) => {
console.log(e)
setProofHashes([])
})
}
}, [debouncedAddress])
const limit = useNumberInputState({ const limit = useNumberInputState({
id: 'limit', id: 'limit',
name: 'limit', name: 'limit',
@ -80,22 +173,59 @@ const WhitelistQueryPage: NextPage = () => {
}, [debouncedLimit]) }, [debouncedLimit])
const [type, setType] = useState<QueryType>('config') const [type, setType] = useState<QueryType>('config')
const [whitelistMerkleTreeQueryType, setWhitelistMerkleTreeQueryType] =
useState<WhitelistMerkleTreeQueryType>('config')
const addressVisible = type === 'has_member' const addressVisible = type === 'has_member' || whitelistMerkleTreeQueryType === 'has_member'
const { data: response } = useQuery( const { data: response } = useQuery(
[contractAddress, type, contract, wallet.address, address, startAfter.value, limit.value] as const, [
contractAddress,
type,
whitelistMerkleTreeQueryType,
contract,
contractWhitelistMerkleTree,
wallet.address,
address,
startAfter.value,
limit.value,
proofHashes,
whitelistType,
] as const,
async ({ queryKey }) => { async ({ queryKey }) => {
const [_contractAddress, _type, _contract, _wallet, _address, _startAfter, _limit] = queryKey const [
_contractAddress,
_type,
_whitelistMerkleTreeQueryType,
_contract,
_contractWhitelistMerkleTree,
_wallet,
_address,
_startAfter,
_limit,
_proofHashes,
_whitelistType,
] = queryKey
const messages = contract?.use(contractAddress) const messages = contract?.use(contractAddress)
const whitelistMerkleTreeMessages = contractWhitelistMerkleTree?.use(contractAddress)
const res = await resolveAddress(_address, wallet).then(async (resolvedAddress) => { const res = await resolveAddress(_address, wallet).then(async (resolvedAddress) => {
const result = await dispatchQuery({ const result =
messages, whitelistType === 'merkletree'
type, ? await disptachWhitelistMerkleTreeQuery({
address: resolvedAddress, messages: whitelistMerkleTreeMessages,
startAfter: _startAfter || undefined, address: resolvedAddress,
limit: _limit, type: whitelistMerkleTreeQueryType,
}) limit: _limit,
proofHashes: _proofHashes,
})
: await dispatchQuery({
messages,
type,
address: resolvedAddress,
startAfter: _startAfter || undefined,
limit: _limit,
})
return result return result
}) })
return res return res
@ -105,7 +235,7 @@ const WhitelistQueryPage: NextPage = () => {
onError: (error: any) => { onError: (error: any) => {
toast.error(error.message, { style: { maxWidth: 'none' } }) toast.error(error.message, { style: { maxWidth: 'none' } })
}, },
enabled: Boolean(contractAddress && contract), enabled: Boolean(contractAddress && (contract || contractWhitelistMerkleTree)),
}, },
) )
@ -230,9 +360,13 @@ const WhitelistQueryPage: NextPage = () => {
defaultValue="config" defaultValue="config"
id="contract-query-type" id="contract-query-type"
name="query-type" name="query-type"
onChange={(e) => setType(e.target.value as QueryType)} onChange={(e) =>
whitelistType === 'merkletree'
? setWhitelistMerkleTreeQueryType(e.target.value as WhitelistMerkleTreeQueryType)
: setType(e.target.value as QueryType)
}
> >
{QUERY_LIST.map(({ id, name }) => ( {(whitelistType === 'merkletree' ? WHITELIST_MERKLE_TREE_QUERY_LIST : QUERY_LIST).map(({ id, name }) => (
<option key={`query-${id}`} className="mt-2 text-lg bg-[#1A1A1A]" value={id}> <option key={`query-${id}`} className="mt-2 text-lg bg-[#1A1A1A]" value={id}>
{name} {name}
</option> </option>
@ -255,7 +389,16 @@ const WhitelistQueryPage: NextPage = () => {
</Button> </Button>
</Conditional> </Conditional>
</div> </div>
<JsonPreview content={contractAddress ? { type, response } : null} title="Query Response" /> <JsonPreview
content={
contractAddress
? whitelistType === 'merkletree'
? { type: whitelistMerkleTreeQueryType, response }
: { type, response }
: null
}
title="Query Response"
/>
</div> </div>
</section> </section>
) )

View File

@ -30,6 +30,7 @@ const Holders: NextPage = () => {
const [includeStaked, setIncludeStaked] = useState<boolean>(true) const [includeStaked, setIncludeStaked] = useState<boolean>(true)
const [includeListed, setIncludeListed] = useState<boolean>(true) const [includeListed, setIncludeListed] = useState<boolean>(true)
const [includeInPool, setIncludeInPool] = useState<boolean>(true)
const [exportIndividualTokens, setExportIndividualTokens] = useState<boolean>(false) const [exportIndividualTokens, setExportIndividualTokens] = useState<boolean>(false)
const [isLoading, setIsLoading] = useState(false) const [isLoading, setIsLoading] = useState(false)
@ -83,6 +84,17 @@ const Holders: NextPage = () => {
type="checkbox" type="checkbox"
/> />
</label> </label>
<label className="justify-start cursor-pointer label w-2/5">
<span className="mr-2 font-bold">Include tokens in Infinity Pools</span>
<input
checked={includeInPool}
className={`${includeInPool ? `bg-stargaze` : `bg-gray-600`} checkbox`}
onClick={() => {
setIncludeInPool(!includeInPool)
}}
type="checkbox"
/>
</label>
<label className="justify-start cursor-pointer label w-2/5"> <label className="justify-start cursor-pointer label w-2/5">
<span className="mr-2 font-bold">Export by Token ID</span> <span className="mr-2 font-bold">Export by Token ID</span>
<input <input
@ -122,6 +134,8 @@ const Holders: NextPage = () => {
?.map((row: any) => { ?.map((row: any) => {
if (!includeListed && row.is_listed) return '' if (!includeListed && row.is_listed) return ''
if (!includeStaked && row.is_staked) return '' if (!includeStaked && row.is_staked) return ''
if (!includeInPool && row.is_in_pool) return ''
if (row.owner_addr === null) return ''
return `${row.owner_addr},${row.token_id}\n` return `${row.owner_addr},${row.token_id}\n`
}) })
.join('')}` .join('')}`
@ -133,6 +147,8 @@ const Holders: NextPage = () => {
data.forEach((row: any) => { data.forEach((row: any) => {
if (!includeListed && row.is_listed) return if (!includeListed && row.is_listed) return
if (!includeStaked && row.is_staked) return if (!includeStaked && row.is_staked) return
if (!includeInPool && row.is_in_pool) return
if (row.owner_addr === null) return
const existingRow = aggregatedData.find((r) => r.address === row.owner_addr) const existingRow = aggregatedData.find((r) => r.address === row.owner_addr)
if (existingRow) { if (existingRow) {
existingRow.amount += 1 existingRow.amount += 1

View File

@ -1,11 +1,11 @@
/* eslint-disable eslint-comments/disable-enable-pair */ /* eslint-disable eslint-comments/disable-enable-pair */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable tailwindcss/classnames-order */ /* eslint-disable tailwindcss/classnames-order */
/* eslint-disable react/button-has-type */
import type { EncodeObject } from '@cosmjs/proto-signing' import type { EncodeObject } from '@cosmjs/proto-signing'
import { Registry } from '@cosmjs/proto-signing' import { Registry } from '@cosmjs/proto-signing'
import { GasPrice, SigningStargateClient } from '@cosmjs/stargate' import { GasPrice, SigningStargateClient } from '@cosmjs/stargate'
import { Button } from 'components/Button'
import { Conditional } from 'components/Conditional' import { Conditional } from 'components/Conditional'
import { ContractPageHeader } from 'components/ContractPageHeader' import { ContractPageHeader } from 'components/ContractPageHeader'
import { DenomUnits } from 'components/forms/DenomUnits' import { DenomUnits } from 'components/forms/DenomUnits'
@ -17,6 +17,7 @@ import { NextSeo } from 'next-seo'
import { Field, Type } from 'protobufjs' import { Field, Type } from 'protobufjs'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import toast from 'react-hot-toast' import toast from 'react-hot-toast'
import { NETWORK } from 'utils/constants'
import { withMetadata } from 'utils/layout' import { withMetadata } from 'utils/layout'
import { useWallet } from 'utils/wallet' import { useWallet } from 'utils/wallet'
@ -52,16 +53,13 @@ const MsgMint = new Type('MsgMint')
.add(new Field('sender', 1, 'string', 'required')) .add(new Field('sender', 1, 'string', 'required'))
.add(new Field('amount', 2, 'Coin', 'required')) .add(new Field('amount', 2, 'Coin', 'required'))
.add(new Field('mintToAddress', 3, 'string', 'required')) .add(new Field('mintToAddress', 3, 'string', 'required'))
.add(new Type('Coin').add(new Field('denom', 1, 'string')).add(new Field('amount', 2, 'string')))
const CoinType = new Type('Coin').add(new Field('denom', 1, 'string')).add(new Field('amount', 2, 'string'))
MsgMint.add(CoinType)
const MsgSend = new Type('MsgSend') const MsgSend = new Type('MsgSend')
.add(new Field('fromAddress', 1, 'string')) .add(new Field('fromAddress', 1, 'string'))
.add(new Field('toAddress', 2, 'string')) .add(new Field('toAddress', 2, 'string'))
.add(new Field('amount', 3, 'Coin', 'repeated')) .add(new Field('amount', 3, 'Coin', 'repeated'))
.add(new Type('Coin').add(new Field('denom', 1, 'string')).add(new Field('amount', 2, 'string')))
MsgSend.add(CoinType)
const MsgChangeAdmin = new Type('MsgChangeAdmin') const MsgChangeAdmin = new Type('MsgChangeAdmin')
.add(new Field('sender', 1, 'string', 'required')) .add(new Field('sender', 1, 'string', 'required'))
@ -88,6 +86,7 @@ const Tokenfactory: NextPage = () => {
const wallet = useWallet() const wallet = useWallet()
const [messageType, setMessageType] = useState<MessageType>('MsgCreateDenom') const [messageType, setMessageType] = useState<MessageType>('MsgCreateDenom')
const [loading, setLoading] = useState(false)
const denomState = useInputState({ const denomState = useInputState({
id: 'denom', id: 'denom',
@ -118,8 +117,8 @@ const Tokenfactory: NextPage = () => {
id: 'mintToAddress', id: 'mintToAddress',
name: 'mintToAddress', name: 'mintToAddress',
title: 'Mint To Address', title: 'Mint To Address',
//placeholder: `${wallet.isWalletConnected && wallet.address ? wallet.address : 'stars1...'}`, placeholder: 'stars1...',
placeholder: 'The tokens can only be minted to the creator address currently.', defaultValue: wallet.address ?? '',
subtitle: 'The address to mint tokens to', subtitle: 'The address to mint tokens to',
}) })
@ -209,10 +208,10 @@ const Tokenfactory: NextPage = () => {
const handleSendMessage = async () => { const handleSendMessage = async () => {
try { try {
if (!wallet.isWalletConnected) return toast.error('Please connect your wallet.') if (!wallet.isWalletConnected) return toast.error('Please connect your wallet.')
setLoading(true)
const offlineSigner = wallet.getOfflineSignerDirect() const offlineSigner = wallet.getOfflineSignerDirect()
const stargateClient = await SigningStargateClient.connectWithSigner( const stargateClient = await SigningStargateClient.connectWithSigner(
'https://rpc.elgafar-1.stargaze-apis.com/', NETWORK === 'testnet' ? 'https://rpc.elgafar-1.stargaze-apis.com/' : 'https://rpc.stargaze-apis.com/',
offlineSigner, offlineSigner,
{ {
gasPrice: GasPrice.fromString('0.025ustars'), gasPrice: GasPrice.fromString('0.025ustars'),
@ -302,9 +301,10 @@ const Tokenfactory: NextPage = () => {
'auto', 'auto',
) )
console.log('response: ', response) console.log('response: ', response)
setLoading(false)
toast.success(`${messageType} success.`, { style: { maxWidth: 'none' } }) toast.success(`${messageType} success.`, { style: { maxWidth: 'none' } })
} catch (error: any) { } catch (error: any) {
setLoading(false)
toast.error(error.message, { style: { maxWidth: 'none' } }) toast.error(error.message, { style: { maxWidth: 'none' } })
console.error('Error: ', error) console.error('Error: ', error)
} }
@ -349,7 +349,7 @@ const Tokenfactory: NextPage = () => {
<Conditional test={messageType === 'MsgMint'}> <Conditional test={messageType === 'MsgMint'}>
<TextInput className="w-3/5" {...denomState} /> <TextInput className="w-3/5" {...denomState} />
<NumberInput className="w-1/4" {...amountState} /> <NumberInput className="w-1/4" {...amountState} />
<AddressInput className="w-3/5" disabled {...mintToAddressState} /> <AddressInput className="w-3/5" {...mintToAddressState} />
</Conditional> </Conditional>
<Conditional test={messageType === 'MsgSend'}> <Conditional test={messageType === 'MsgSend'}>
<TextInput className="w-3/5" {...denomState} /> <TextInput className="w-3/5" {...denomState} />
@ -375,15 +375,16 @@ const Tokenfactory: NextPage = () => {
<TextInput className="w-1/2" {...denomState} /> <TextInput className="w-1/2" {...denomState} />
<AddressInput className="w-1/2" {...newAdminAddressState} /> <AddressInput className="w-1/2" {...newAdminAddressState} />
</Conditional> </Conditional>
<button <Button
className="px-4 py-2 font-bold text-white bg-stargaze rounded-md" className="px-4 py-2 font-bold text-white bg-stargaze rounded-md"
isLoading={loading}
onClick={() => { onClick={() => {
void handleSendMessage() void handleSendMessage()
}} }}
> >
{' '} {' '}
{getButtonName()} {getButtonName()}
</button> </Button>
</section> </section>
) )
} }

69
scripts/publish-app-record.sh Executable file
View File

@ -0,0 +1,69 @@
#!/bin/bash
set -e
RECORD_FILE=tmp.rf.$$
CONFIG_FILE=`mktemp`
CERC_APP_TYPE=${CERC_APP_TYPE:-"webapp"}
CERC_REPO_REF=${CERC_REPO_REF:-${GITHUB_SHA:-`git log -1 --format="%H"`}}
CERC_IS_LATEST_RELEASE=${CERC_IS_LATEST_RELEASE:-"true"}
rcd_name=$(jq -r '.name' package.json | sed 's/null//')
rcd_desc=$(jq -r '.description' package.json | sed 's/null//')
rcd_repository=$(jq -r '.repository' package.json | sed 's/null//')
rcd_homepage=$(jq -r '.homepage' package.json | sed 's/null//')
rcd_license=$(jq -r '.license' package.json | sed 's/null//')
rcd_author=$(jq -r '.author' package.json | sed 's/null//')
rcd_app_version=$(jq -r '.version' package.json | sed 's/null//')
cat <<EOF > "$CONFIG_FILE"
services:
cns:
restEndpoint: '${CERC_REGISTRY_REST_ENDPOINT:-http://23.111.69.218:1317}'
gqlEndpoint: '${CERC_REGISTRY_GQL_ENDPOINT:-https://lx-daemon.audubon.app/api}'
chainId: ${CERC_REGISTRY_CHAIN_ID:-laconic_9000-1}
gas: 9550000
fees: 500000aphoton
EOF
next_ver=$(laconic -c $CONFIG_FILE cns record list --type ApplicationRecord --all --name "$rcd_name" 2>/dev/null | jq -r -s ".[] | sort_by(.createTime) | reverse | [ .[] | select(.bondId == \"$CERC_REGISTRY_BOND_ID\") ] | .[0].attributes.version" | awk -F. -v OFS=. '{$NF += 1 ; print}')
if [ -z "$next_ver" ] || [ "1" == "$next_ver" ]; then
next_ver=0.0.1
fi
cat <<EOF | sed '/.*: ""$/d' > "$RECORD_FILE"
record:
type: ApplicationRecord
version: ${next_ver}
name: "$rcd_name"
description: "$rcd_desc"
homepage: "$rcd_homepage"
license: "$rcd_license"
author: "$rcd_author"
repository:
- "$rcd_repository"
repository_ref: "$CERC_REPO_REF"
app_version: "$rcd_app_version"
app_type: "$CERC_APP_TYPE"
EOF
cat $RECORD_FILE
RECORD_ID=$(laconic -c $CONFIG_FILE cns record publish --filename $RECORD_FILE --user-key "${CERC_REGISTRY_USER_KEY}" --bond-id ${CERC_REGISTRY_BOND_ID} | jq -r '.id')
echo $RECORD_ID
if [ -z "$CERC_REGISTRY_APP_CRN" ]; then
authority=$(echo "$rcd_name" | cut -d'/' -f1 | sed 's/@//')
app=$(echo "$rcd_name" | cut -d'/' -f2-)
CERC_REGISTRY_APP_CRN="crn://$authority/applications/$app"
fi
laconic -c $CONFIG_FILE cns name set --user-key "${CERC_REGISTRY_USER_KEY}" --bond-id ${CERC_REGISTRY_BOND_ID} "$CERC_REGISTRY_APP_CRN@${rcd_app_version}" "$RECORD_ID"
laconic -c $CONFIG_FILE cns name set --user-key "${CERC_REGISTRY_USER_KEY}" --bond-id ${CERC_REGISTRY_BOND_ID} "$CERC_REGISTRY_APP_CRN@${CERC_REPO_REF}" "$RECORD_ID"
if [ "true" == "$CERC_IS_LATEST_RELEASE" ]; then
laconic -c $CONFIG_FILE cns name set --user-key "${CERC_REGISTRY_USER_KEY}" --bond-id ${CERC_REGISTRY_BOND_ID} "$CERC_REGISTRY_APP_CRN" "$RECORD_ID"
fi
rm -f $RECORD_FILE $CONFIG_FILE

View File

@ -0,0 +1,56 @@
#!/bin/bash
set -e
RECORD_FILE=tmp.rf.$$
CONFIG_FILE=`mktemp`
rcd_name=$(jq -r '.name' package.json | sed 's/null//' | sed 's/^@//')
rcd_app_version=$(jq -r '.version' package.json | sed 's/null//')
cat <<EOF > "$CONFIG_FILE"
services:
cns:
restEndpoint: '${CERC_REGISTRY_REST_ENDPOINT:-http://23.111.69.218:1317}'
gqlEndpoint: '${CERC_REGISTRY_GQL_ENDPOINT:-https://lx-daemon.audubon.app/api}'
chainId: ${CERC_REGISTRY_CHAIN_ID:-laconic_9000-1}
gas: 550000
fees: 200000aphoton
EOF
if [ -z "$CERC_REGISTRY_APP_CRN" ]; then
authority=$(echo "$rcd_name" | cut -d'/' -f1 | sed 's/@//')
app=$(echo "$rcd_name" | cut -d'/' -f2-)
CERC_REGISTRY_APP_CRN="crn://$authority/applications/$app"
fi
APP_RECORD=$(laconic -c $CONFIG_FILE cns name resolve "$CERC_REGISTRY_APP_CRN" | jq '.[0]')
if [ -z "$APP_RECORD" ] || [ "null" == "$APP_RECORD" ]; then
echo "No record found for $CERC_REGISTRY_APP_CRN."
exit 1
fi
cat <<EOF | sed '/.*: ""$/d' > "$RECORD_FILE"
record:
type: ApplicationDeploymentRequest
version: 1.0.0
name: "$rcd_name@$rcd_app_version"
application: "$CERC_REGISTRY_APP_CRN@$rcd_app_version"
dns: "$CERC_REGISTRY_DEPLOYMENT_SHORT_HOSTNAME"
deployment: "$CERC_REGISTRY_DEPLOYMENT_CRN"
config:
env:
# this overrides the setting in `.env`
CERC_WEBAPP_DEBUG: "$rcd_app_version"
CERC_MAX_GENERATE_TIME: "600"
meta:
note: "Added by CI @ `date`"
repository: "`git remote get-url origin`"
repository_ref: "${GITHUB_SHA:-`git log -1 --format="%H"`}"
EOF
cat $RECORD_FILE
RECORD_ID=$(laconic -c $CONFIG_FILE cns record publish --filename $RECORD_FILE --user-key "${CERC_REGISTRY_USER_KEY}" --bond-id ${CERC_REGISTRY_BOND_ID} | jq -r '.id')
echo $RECORD_ID
rm -f $RECORD_FILE $CONFIG_FILE

View File

@ -11,18 +11,27 @@ export const WHITELIST_CODE_ID = parseInt(process.env.NEXT_PUBLIC_WHITELIST_CODE
export const WHITELIST_FLEX_CODE_ID = parseInt(process.env.NEXT_PUBLIC_WHITELIST_FLEX_CODE_ID, 10) export const WHITELIST_FLEX_CODE_ID = parseInt(process.env.NEXT_PUBLIC_WHITELIST_FLEX_CODE_ID, 10)
export const VENDING_MINTER_CODE_ID = parseInt(process.env.NEXT_PUBLIC_VENDING_MINTER_CODE_ID, 10) export const VENDING_MINTER_CODE_ID = parseInt(process.env.NEXT_PUBLIC_VENDING_MINTER_CODE_ID, 10)
export const VENDING_MINTER_FLEX_CODE_ID = parseInt(process.env.NEXT_PUBLIC_VENDING_MINTER_FLEX_CODE_ID, 10) export const VENDING_MINTER_FLEX_CODE_ID = parseInt(process.env.NEXT_PUBLIC_VENDING_MINTER_FLEX_CODE_ID, 10)
export const WHITELIST_MERKLE_TREE_CODE_ID = parseInt(process.env.NEXT_PUBLIC_WHITELIST_MERKLE_TREE_CODE_ID, 10)
export const VENDING_FACTORY_ADDRESS = process.env.NEXT_PUBLIC_VENDING_FACTORY_ADDRESS export const VENDING_FACTORY_ADDRESS = process.env.NEXT_PUBLIC_VENDING_FACTORY_ADDRESS
export const FEATURED_VENDING_FACTORY_ADDRESS = process.env.NEXT_PUBLIC_FEATURED_VENDING_FACTORY_ADDRESS export const FEATURED_VENDING_FACTORY_ADDRESS = process.env.NEXT_PUBLIC_FEATURED_VENDING_FACTORY_ADDRESS
export const VENDING_FACTORY_UPDATABLE_ADDRESS = process.env.NEXT_PUBLIC_VENDING_FACTORY_UPDATABLE_ADDRESS export const VENDING_FACTORY_UPDATABLE_ADDRESS = process.env.NEXT_PUBLIC_VENDING_FACTORY_UPDATABLE_ADDRESS
export const VENDING_FACTORY_FLEX_ADDRESS = process.env.NEXT_PUBLIC_VENDING_FACTORY_FLEX_ADDRESS export const VENDING_FACTORY_FLEX_ADDRESS = process.env.NEXT_PUBLIC_VENDING_FACTORY_FLEX_ADDRESS
export const VENDING_FACTORY_MERKLE_TREE_ADDRESS = process.env.NEXT_PUBLIC_VENDING_FACTORY_MERKLE_TREE_ADDRESS
export const FEATURED_VENDING_FACTORY_MERKLE_TREE_ADDRESS =
process.env.NEXT_PUBLIC_FEATURED_VENDING_FACTORY_MERKLE_TREE_ADDRESS
export const FEATURED_VENDING_FACTORY_FLEX_ADDRESS = process.env.NEXT_PUBLIC_FEATURED_VENDING_FACTORY_FLEX_ADDRESS export const FEATURED_VENDING_FACTORY_FLEX_ADDRESS = process.env.NEXT_PUBLIC_FEATURED_VENDING_FACTORY_FLEX_ADDRESS
export const VENDING_FACTORY_UPDATABLE_FLEX_ADDRESS = process.env.NEXT_PUBLIC_VENDING_FACTORY_UPDATABLE_FLEX_ADDRESS export const VENDING_FACTORY_UPDATABLE_FLEX_ADDRESS = process.env.NEXT_PUBLIC_VENDING_FACTORY_UPDATABLE_FLEX_ADDRESS
export const VENDING_IBC_ATOM_FACTORY_ADDRESS = process.env.NEXT_PUBLIC_VENDING_IBC_ATOM_FACTORY_ADDRESS export const VENDING_IBC_ATOM_FACTORY_ADDRESS = process.env.NEXT_PUBLIC_VENDING_IBC_ATOM_FACTORY_ADDRESS
export const VENDING_IBC_ATOM_UPDATABLE_FACTORY_ADDRESS = export const VENDING_IBC_ATOM_UPDATABLE_FACTORY_ADDRESS =
process.env.NEXT_PUBLIC_VENDING_IBC_ATOM_UPDATABLE_FACTORY_ADDRESS process.env.NEXT_PUBLIC_VENDING_IBC_ATOM_UPDATABLE_FACTORY_ADDRESS
export const VENDING_IBC_USDC_FACTORY_ADDRESS = process.env.NEXT_PUBLIC_VENDING_IBC_USDC_FACTORY_ADDRESS export const VENDING_IBC_USDC_FACTORY_ADDRESS = process.env.NEXT_PUBLIC_VENDING_IBC_USDC_FACTORY_ADDRESS
export const FEATURED_IBC_USDC_FACTORY_ADDRESS = process.env.NEXT_PUBLIC_FEATURED_VENDING_IBC_USDC_FACTORY_ADDRESS
export const VENDING_IBC_USDC_UPDATABLE_FACTORY_ADDRESS = export const VENDING_IBC_USDC_UPDATABLE_FACTORY_ADDRESS =
process.env.NEXT_PUBLIC_VENDING_IBC_USDC_UPDATABLE_FACTORY_ADDRESS process.env.NEXT_PUBLIC_VENDING_IBC_USDC_UPDATABLE_FACTORY_ADDRESS
export const VENDING_IBC_TIA_FACTORY_ADDRESS = process.env.NEXT_PUBLIC_VENDING_IBC_TIA_FACTORY_ADDRESS
export const FEATURED_IBC_TIA_FACTORY_ADDRESS = process.env.NEXT_PUBLIC_FEATURED_VENDING_IBC_TIA_FACTORY_ADDRESS
export const VENDING_IBC_TIA_UPDATABLE_FACTORY_ADDRESS =
process.env.NEXT_PUBLIC_VENDING_IBC_TIA_UPDATABLE_FACTORY_ADDRESS
export const VENDING_IBC_NBTC_FACTORY_ADDRESS = process.env.NEXT_PUBLIC_VENDING_IBC_NBTC_FACTORY_ADDRESS export const VENDING_IBC_NBTC_FACTORY_ADDRESS = process.env.NEXT_PUBLIC_VENDING_IBC_NBTC_FACTORY_ADDRESS
export const VENDING_IBC_NBTC_UPDATABLE_FACTORY_ADDRESS = export const VENDING_IBC_NBTC_UPDATABLE_FACTORY_ADDRESS =
process.env.NEXT_PUBLIC_VENDING_IBC_NBTC_UPDATABLE_FACTORY_ADDRESS process.env.NEXT_PUBLIC_VENDING_IBC_NBTC_UPDATABLE_FACTORY_ADDRESS
@ -36,8 +45,19 @@ export const VENDING_IBC_ATOM_FACTORY_FLEX_ADDRESS = process.env.NEXT_PUBLIC_VEN
export const VENDING_IBC_ATOM_UPDATABLE_FACTORY_FLEX_ADDRESS = export const VENDING_IBC_ATOM_UPDATABLE_FACTORY_FLEX_ADDRESS =
process.env.NEXT_PUBLIC_VENDING_IBC_ATOM_UPDATABLE_FACTORY_FLEX_ADDRESS process.env.NEXT_PUBLIC_VENDING_IBC_ATOM_UPDATABLE_FACTORY_FLEX_ADDRESS
export const VENDING_IBC_USDC_FACTORY_FLEX_ADDRESS = process.env.NEXT_PUBLIC_VENDING_IBC_USDC_FACTORY_FLEX_ADDRESS export const VENDING_IBC_USDC_FACTORY_FLEX_ADDRESS = process.env.NEXT_PUBLIC_VENDING_IBC_USDC_FACTORY_FLEX_ADDRESS
export const FEATURED_VENDING_IBC_USDC_FACTORY_FLEX_ADDRESS =
process.env.NEXT_PUBLIC_FEATURED_VENDING_IBC_USDC_FACTORY_FLEX_ADDRESS
export const VENDING_IBC_USDC_UPDATABLE_FACTORY_FLEX_ADDRESS = export const VENDING_IBC_USDC_UPDATABLE_FACTORY_FLEX_ADDRESS =
process.env.NEXT_PUBLIC_VENDING_IBC_USDC_UPDATABLE_FACTORY_FLEX_ADDRESS process.env.NEXT_PUBLIC_VENDING_IBC_USDC_UPDATABLE_FACTORY_FLEX_ADDRESS
export const VENDING_IBC_TIA_FACTORY_FLEX_ADDRESS = process.env.NEXT_PUBLIC_VENDING_IBC_TIA_FACTORY_FLEX_ADDRESS
export const VENDING_IBC_TIA_FACTORY_MERKLE_TREE_ADDRESS =
process.env.NEXT_PUBLIC_VENDING_IBC_TIA_FACTORY_MERKLE_TREE_ADDRESS
export const FEATURED_VENDING_IBC_TIA_FACTORY_MERKLE_TREE_ADDRESS =
process.env.NEXT_PUBLIC_FEATURED_VENDING_IBC_TIA_FACTORY_MERKLE_TREE_ADDRESS
export const FEATURED_VENDING_IBC_TIA_FACTORY_FLEX_ADDRESS =
process.env.NEXT_PUBLIC_FEATURED_VENDING_IBC_TIA_FACTORY_FLEX_ADDRESS
export const VENDING_IBC_TIA_UPDATABLE_FACTORY_FLEX_ADDRESS =
process.env.NEXT_PUBLIC_VENDING_IBC_TIA_UPDATABLE_FACTORY_FLEX_ADDRESS
export const VENDING_IBC_NBTC_FACTORY_FLEX_ADDRESS = process.env.NEXT_PUBLIC_VENDING_IBC_NBTC_FACTORY_FLEX_ADDRESS export const VENDING_IBC_NBTC_FACTORY_FLEX_ADDRESS = process.env.NEXT_PUBLIC_VENDING_IBC_NBTC_FACTORY_FLEX_ADDRESS
export const VENDING_IBC_NBTC_UPDATABLE_FACTORY_FLEX_ADDRESS = export const VENDING_IBC_NBTC_UPDATABLE_FACTORY_FLEX_ADDRESS =
process.env.NEXT_PUBLIC_VENDING_IBC_NBTC_UPDATABLE_FACTORY_FLEX_ADDRESS process.env.NEXT_PUBLIC_VENDING_IBC_NBTC_UPDATABLE_FACTORY_FLEX_ADDRESS
@ -60,13 +80,23 @@ export const VENDING_NATIVE_BRNCH_FLEX_FACTORY_ADDRESS =
export const BASE_FACTORY_ADDRESS = process.env.NEXT_PUBLIC_BASE_FACTORY_ADDRESS export const BASE_FACTORY_ADDRESS = process.env.NEXT_PUBLIC_BASE_FACTORY_ADDRESS
export const BASE_FACTORY_UPDATABLE_ADDRESS = process.env.NEXT_PUBLIC_BASE_FACTORY_UPDATABLE_ADDRESS export const BASE_FACTORY_UPDATABLE_ADDRESS = process.env.NEXT_PUBLIC_BASE_FACTORY_UPDATABLE_ADDRESS
export const OPEN_EDITION_FACTORY_ADDRESS = process.env.NEXT_PUBLIC_OPEN_EDITION_FACTORY_ADDRESS export const OPEN_EDITION_FACTORY_ADDRESS = process.env.NEXT_PUBLIC_OPEN_EDITION_FACTORY_ADDRESS
export const OPEN_EDITION_FACTORY_FLEX_ADDRESS = process.env.NEXT_PUBLIC_OPEN_EDITION_FACTORY_FLEX_ADDRESS
export const OPEN_EDITION_UPDATABLE_FACTORY_ADDRESS = process.env.NEXT_PUBLIC_OPEN_EDITION_UPDATABLE_FACTORY_ADDRESS export const OPEN_EDITION_UPDATABLE_FACTORY_ADDRESS = process.env.NEXT_PUBLIC_OPEN_EDITION_UPDATABLE_FACTORY_ADDRESS
export const OPEN_EDITION_IBC_ATOM_FACTORY_ADDRESS = process.env.NEXT_PUBLIC_OPEN_EDITION_IBC_ATOM_FACTORY_ADDRESS export const OPEN_EDITION_IBC_ATOM_FACTORY_ADDRESS = process.env.NEXT_PUBLIC_OPEN_EDITION_IBC_ATOM_FACTORY_ADDRESS
export const OPEN_EDITION_IBC_ATOM_FACTORY_FLEX_ADDRESS =
process.env.NEXT_PUBLIC_OPEN_EDITION_IBC_ATOM_FACTORY_FLEX_ADDRESS
export const OPEN_EDITION_UPDATABLE_IBC_ATOM_FACTORY_ADDRESS = export const OPEN_EDITION_UPDATABLE_IBC_ATOM_FACTORY_ADDRESS =
process.env.NEXT_PUBLIC_OPEN_EDITION_UPDATABLE_IBC_ATOM_FACTORY_ADDRESS process.env.NEXT_PUBLIC_OPEN_EDITION_UPDATABLE_IBC_ATOM_FACTORY_ADDRESS
export const OPEN_EDITION_IBC_USDC_FACTORY_ADDRESS = process.env.NEXT_PUBLIC_OPEN_EDITION_IBC_USDC_FACTORY_ADDRESS export const OPEN_EDITION_IBC_USDC_FACTORY_ADDRESS = process.env.NEXT_PUBLIC_OPEN_EDITION_IBC_USDC_FACTORY_ADDRESS
export const OPEN_EDITION_IBC_USDC_FACTORY_FLEX_ADDRESS =
process.env.NEXT_PUBLIC_OPEN_EDITION_IBC_USDC_FACTORY_FLEX_ADDRESS
export const OPEN_EDITION_UPDATABLE_IBC_USDC_FACTORY_ADDRESS = export const OPEN_EDITION_UPDATABLE_IBC_USDC_FACTORY_ADDRESS =
process.env.NEXT_PUBLIC_OPEN_EDITION_UPDATABLE_IBC_USDC_FACTORY_ADDRESS process.env.NEXT_PUBLIC_OPEN_EDITION_UPDATABLE_IBC_USDC_FACTORY_ADDRESS
export const OPEN_EDITION_IBC_TIA_FACTORY_ADDRESS = process.env.NEXT_PUBLIC_OPEN_EDITION_IBC_TIA_FACTORY_ADDRESS
export const OPEN_EDITION_IBC_TIA_FACTORY_FLEX_ADDRESS =
process.env.NEXT_PUBLIC_OPEN_EDITION_IBC_TIA_FACTORY_FLEX_ADDRESS
export const OPEN_EDITION_UPDATABLE_IBC_TIA_FACTORY_ADDRESS =
process.env.NEXT_PUBLIC_OPEN_EDITION_UPDATABLE_IBC_TIA_FACTORY_ADDRESS
export const OPEN_EDITION_IBC_NBTC_FACTORY_ADDRESS = process.env.NEXT_PUBLIC_OPEN_EDITION_IBC_NBTC_FACTORY_ADDRESS export const OPEN_EDITION_IBC_NBTC_FACTORY_ADDRESS = process.env.NEXT_PUBLIC_OPEN_EDITION_IBC_NBTC_FACTORY_ADDRESS
export const OPEN_EDITION_UPDATABLE_IBC_NBTC_FACTORY_ADDRESS = export const OPEN_EDITION_UPDATABLE_IBC_NBTC_FACTORY_ADDRESS =
process.env.NEXT_PUBLIC_OPEN_EDITION_UPDATABLE_IBC_NBTC_FACTORY_ADDRESS process.env.NEXT_PUBLIC_OPEN_EDITION_UPDATABLE_IBC_NBTC_FACTORY_ADDRESS
@ -102,6 +132,8 @@ export const STARGAZE_URL = process.env.NEXT_PUBLIC_STARGAZE_WEBSITE_URL
export const BLOCK_EXPLORER_URL = process.env.NEXT_PUBLIC_BLOCK_EXPLORER_URL export const BLOCK_EXPLORER_URL = process.env.NEXT_PUBLIC_BLOCK_EXPLORER_URL
export const WEBSITE_URL = process.env.NEXT_PUBLIC_WEBSITE_URL export const WEBSITE_URL = process.env.NEXT_PUBLIC_WEBSITE_URL
export const SYNC_COLLECTIONS_API_URL = process.env.NEXT_PUBLIC_SYNC_COLLECTIONS_API_URL export const SYNC_COLLECTIONS_API_URL = process.env.NEXT_PUBLIC_SYNC_COLLECTIONS_API_URL
export const WHITELIST_MERKLE_TREE_API_URL = process.env.NEXT_PUBLIC_WHITELIST_MERKLE_TREE_API_URL
export const NFT_STORAGE_DEFAULT_API_KEY = process.env.NEXT_PUBLIC_NFT_STORAGE_DEFAULT_API_KEY
export const MEILISEARCH_HOST = process.env.NEXT_PUBLIC_MEILISEARCH_HOST export const MEILISEARCH_HOST = process.env.NEXT_PUBLIC_MEILISEARCH_HOST
export const MEILISEARCH_API_KEY = process.env.NEXT_PUBLIC_MEILISEARCH_API_KEY export const MEILISEARCH_API_KEY = process.env.NEXT_PUBLIC_MEILISEARCH_API_KEY

View File

@ -4,14 +4,14 @@ import { toast } from 'react-hot-toast'
import { isValidAddress } from './isValidAddress' import { isValidAddress } from './isValidAddress'
export const isValidFlexListFile = (file: WhitelistFlexMember[]) => { export const isValidFlexListFile = (file: WhitelistFlexMember[]) => {
let sumOfAmounts = 0 // let sumOfAmounts = 0
file.forEach((allocation) => { // file.forEach((allocation) => {
sumOfAmounts += Number(allocation.mint_count) // sumOfAmounts += Number(allocation.mint_count)
}) // })
if (sumOfAmounts > 10000) { // if (sumOfAmounts > 10000) {
toast.error(`Total mint count should be less than 10000 tokens (current count: ${sumOfAmounts}))`) // toast.error(`Total mint count should be less than 10000 tokens (current count: ${sumOfAmounts}))`)
return false // return false
} // }
const checks = file.map((account) => { const checks = file.map((account) => {
// Check if address is valid bech32 address // Check if address is valid bech32 address

31
utils/merkleTree.ts Normal file
View File

@ -0,0 +1,31 @@
import sha256 from 'crypto-js/sha256'
import { MerkleTree } from 'merkletreejs'
export class WhitelistMerkleTree {
tree: MerkleTree
constructor(members: string[]) {
this.tree = new MerkleTree(
members.map((member) => sha256(member)),
sha256,
{
// sort: true,
// hashLeaves: false,
// sortLeaves: true,
sortPairs: true,
},
)
}
getMerkleRoot() {
return this.tree.getRoot().toString('hex')
}
getMerkleProof(member: string) {
console.log('this.tree.getProof(sha256(member).toString()): ', this.tree.getProof(sha256(member).toString()))
return this.tree.getProof(sha256(member).toString()).map((item) => item.data.toString('hex'))
}
verify(proof: string[], member: string) {
return this.tree.verify(proof, sha256(member).toString(), this.tree.getRoot())
}
}

3489
yarn.lock

File diff suppressed because it is too large Load Diff