507 Commits
Author SHA1 Message Date
zramsay 7490e24a1d Update scripts/request-app-deployment.sh
Publish ApplicationRecord to Registry / cns_publish (push) Successful in 1m26s
2024-06-25 16:06:14 +00:00
zramsay da62b2fbf3 Update scripts/request-app-deployment.sh
Publish ApplicationRecord to Registry / cns_publish (push) Has been cancelled
2024-06-25 16:05:59 +00:00
zramsay 8a08acf250 Update scripts/request-app-deployment.sh
Publish ApplicationRecord to Registry / cns_publish (push) Successful in 1m16s
2024-06-25 16:00:19 +00:00
zramsay 6c6ee08024 increase CERC_MAX_GENERATE_TIME
Publish ApplicationRecord to Registry / cns_publish (push) Successful in 1m19s
2024-06-25 15:35:11 +00:00
zramsay e6f56c0509 Add .env
Publish ApplicationRecord to Registry / cns_publish (push) Successful in 1m20s
2024-06-25 15:33:18 +00:00
zramsay 50489c00cd ffs
Publish ApplicationRecord to Registry / cns_publish (push) Successful in 1m20s
2024-06-25 15:10:05 +00:00
zramsay ac5a7f2cb6 Update package.json
Publish ApplicationRecord to Registry / cns_publish (push) Successful in 1m24s
2024-06-25 15:07:12 +00:00
zramsay 04397853d9 set name
Publish ApplicationRecord to Registry / cns_publish (push) Successful in 1m23s
2024-06-25 15:01:36 +00:00
zramsay 2ab4cb9349 test
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 a3466c869b Fix factory validation on init issue 2024-02-07 11:15:03 +03: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 1d1a901312 Merge pull request #330 from public-awesome/sample-airdrop-files
Provide sample .csv files for airdrops
2024-02-05 21:17:34 +02:00
Serkan Reis 85d38d7882 Provide sample .csv files for airdrops 2024-02-05 22:10:42 +03: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 1bce40a6f8 Merge pull request #328 from public-awesome/switch-ipfs-gateway
Switch default IPFS gateway
2024-02-03 03:47:49 +02:00
Serkan Reis d651a10dc0 Switch IPFS gateway used for metadata validation 2024-02-03 04:45:54 +03:00
Serkan Reis abac647879 Prevalidate default vending factory 2024-02-03 04:44:54 +03:00
Serkan Reis d6d9e3c666 Merge pull request #327 from public-awesome/time-input-updates
Time input updates (mainnet)
2024-02-02 15:49:21 +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 ae2b565af6 Dictate minting start time wrt whitelist start time 2024-02-02 16:23:09 +03:00
Serkan Reis d28af744a5 Fix date cancellation issues 2024-02-02 13:49:47 +03:00
Serkan Reis 5eff907b9f Merge pull request #325 from public-awesome/factory-validation
Factory validation (mainnet)
2024-02-02 09:03:51 +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 954f73e99e Check if a valid factory exists for the selected parameters prior to collection creation 2024-02-02 09:55:47 +03:00
Serkan Reis 3cb943c6d0 Merge pull request #323 from public-awesome/featured-check
Add option to create featured collections (mainnet)
2024-01-31 20:01:48 +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 0293b12821 Include featured minter on recent sg721 list 2024-01-31 20:25:48 +03:00
Serkan Reis fce1fa6bf4 Update .env.example 2024-01-31 20:12:44 +03:00
Serkan Reis c56659e05a Add option to create featured collections 2024-01-31 20:07:47 +03: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 8ceacb6725 Merge pull request #319 from public-awesome/batch-remove-wl-members
Batch remove members from whitelists
2024-01-25 11:08:40 +02:00
Serkan Reis 5a34f3f7e4 Add option to batch remove members from whitelists 2024-01-25 11:59:20 +03: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 0e5d66f79a Merge pull request #317 from public-awesome/snapshot-active-users
Snapshot active users
2024-01-24 11:46:18 +02:00
Serkan Reis e219566cd2 Add empty collection address warning - 2 2024-01-24 12:39:14 +03:00
Serkan Reis 2a293f1a62 Add empty collection address warning 2024-01-24 12:35:28 +03:00
Serkan Reis 19e5fd0abb Recommend active user list exports for free mints 2024-01-24 12:27:02 +03:00
Serkan Reis f35d9954c3 Add loading spinner 2024-01-24 12:14:50 +03:00
Serkan Reis d60c554125 Update link tabs index 2024-01-23 23:54:24 +03:00
Serkan Reis e51659b15f Update copy 2024-01-23 23:53:28 +03:00
Serkan Reis 8d3b337afa Add snapshot option for active Stargaze users 2024-01-23 23:50:17 +03: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 aba83c21a4 Merge pull request #315 from public-awesome/base-factory-update
Update sg721 code id for 1/1 collection instantiation
2024-01-22 22:37:15 +02:00
Serkan Reis 724fcf1120 Update sg721 code id for 1/1 collection instantiation 2024-01-22 23:29:04 +03:00
Serkan Reis d7235264f9 Include export options for collection holder snapshots 2024-01-22 23:22:42 +03: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 6f403fe70a Merge pull request #313 from public-awesome/launchpad-link-update
Update View on Launchpad URL
2024-01-18 16:00:01 +02:00
Serkan Reis 1ad2de146f Update View on Launchpad URL 2024-01-18 16:50:00 +03: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 8470008360 Merge pull request #311 from public-awesome/develop
Sync dev > main
2024-01-17 12:28:25 +02:00
Serkan Reis a4b114e01d Merge pull request #310 from public-awesome/authz-update
Authz update
2024-01-17 12:27:15 +02:00
Serkan Reis e4564511d1 Address build issues 2024-01-17 13:21:07 +03:00
Serkan Reis 520df5669a Bump Studio version 2024-01-17 13:16:25 +03:00
Serkan Reis c87af7dbec Remove non-generic typeUrls from revoke options 2024-01-17 13:04:44 +03:00
Serkan Reis eb1a448896 Add ContractMigrationAuthorization 2024-01-17 13:03:56 +03:00
Serkan Reis 9e63aba259 Fix undefined expiration issue 2024-01-17 12:09:56 +03:00
Serkan Reis 57fabf90da Include MsgStoreCode among authz grant & revoke options 2024-01-16 18:30:13 +03:00
Serkan Reis 58ae7c05af Cover the rest of generic messages for authz grant & revoke 2024-01-16 18:05:31 +03:00
Serkan Reis 1015590bed Update revoke message types 2024-01-16 16:26:12 +03:00
Serkan Reis 490bd3e84d Include ContractExecutionAuthorization among available auth types 2024-01-16 13:30:44 +03:00
Serkan Reis ab801768b9 Include Send among available auth types 2024-01-15 18:02:45 +03:00
Serkan Reis 7526cc91d1 Bump Studio version 2024-01-15 15:06:48 +03:00
Serkan Reis f6253dd6c7 Update sidebar 2024-01-15 15:05:16 +03:00
Serkan Reis 680b2ac258 Initial authz logic 2024-01-15 14:55:16 +03:00
Serkan Reis 1f3e3f56da Merge pull request #309 from public-awesome/develop
Sync dev > main
2024-01-05 11:32:32 +02:00
Serkan Reis d8afd9f637 Merge pull request #308 from public-awesome/tokenfactory-update
Add tokenfactory related features
2024-01-05 11:31:33 +02:00
Serkan Reis 203e0614b0 Bump Studio version 2024-01-05 12:18:11 +03:00
Serkan Reis da79f4f6e5 Update sidebar 2024-01-05 12:16:55 +03:00
Serkan Reis cd6a69970b Add logic for other message types 2024-01-05 11:57:10 +03:00
Serkan Reis 323837e67f Init okenfactory actions 2024-01-04 23:12:07 +03:00
Serkan Reis f4490fb237 Merge pull request #307 from public-awesome/develop
Sync dev > main
2024-01-01 14:50:38 +02:00
Serkan Reis 5787c90811 Merge pull request #306 from public-awesome/collection-snapshots
Collection holder snapshots
2024-01-01 14:42:58 +02:00
Serkan Reis df571b6c58 Toastify snapshot related errors 2024-01-01 15:33:18 +03:00
Serkan Reis 6caab5ce69 Fix typo 2023-12-31 22:10:02 +03:00
Serkan Reis b4290ba9b9 Enable taking collection snapshots 2023-12-31 21:47:30 +03:00
Serkan Reis 57d73d9ed9 Merge pull request #305 from public-awesome/develop
Sync dev > main
2023-12-31 20:04:30 +02:00
Serkan Reis adfdfd22e8 Update yarn.lock 2023-12-31 20:57:51 +03:00
Serkan Reis 6e51a57e46 Revert snapshot related changes 2023-12-31 20:48:41 +03:00
Serkan Reis 8f56ac0390 Merge pull request #304 from public-awesome/develop
Sync dev > main
2023-12-31 14:54:00 +02:00
Serkan Reis d88596559a Address build issues 2023-12-31 15:33:38 +03:00
Serkan Reis b1ce309cb2 Address build issues 2023-12-31 15:25:11 +03:00
Serkan Reis 064d966855 Merge pull request #302 from public-awesome/develop
Sync dev > main
2023-12-31 14:11:01 +02:00
Serkan Reis 59f15801ab Merge pull request #301 from public-awesome/collection-snapshots
Collection Snapshots
2023-12-31 14:10:28 +02:00
Serkan Reis dcb2a9c072 Update title for snapshots 2023-12-31 15:09:05 +03:00
Serkan Reis 3065090803 Bump Studio version 2023-12-31 15:04:10 +03:00
Serkan Reis 412a467391 Enable taking holder snapshots for collections 2023-12-31 15:01:48 +03:00
Serkan Reis 529e727a7d Merge pull request #300 from public-awesome/develop
Sync dev > main
2023-12-27 22:15:04 +02:00
Serkan Reis ee0953e18a Update mint price update copy 2023-12-27 23:14:01 +03:00
Serkan Reis 71685b8688 Merge pull request #299 from public-awesome/develop
Sync dev > main
2023-12-27 14:56:26 +02:00
Serkan Reis a44ab5cc75 Improve sidebar responsivity 2023-12-27 15:55:05 +03:00
Serkan Reis 794558dea3 Merge pull request #298 from public-awesome/develop
Sync dev > main
2023-12-21 13:30:24 +02:00
Serkan Reis 4f40da141d Merge pull request #297 from public-awesome/update-mint-denoms
Include CRBRUS among mint price denom options
2023-12-21 13:29:48 +02:00
Serkan Reis 35f1ba0ca6 Include CRBRUS among mint price denom options 2023-12-21 14:28:45 +03: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
Serkan Reis 039f1d02fb Merge pull request #295 from public-awesome/develop
Sync dev > main
2023-12-13 12:03:30 +02:00
Serkan Reis 9a7e2fea5e Merge pull request #294 from public-awesome/wallet-loader-token-display-name-fix
Display token names on wallet loader
2023-12-13 12:02:48 +02:00
Serkan Reis 9760ba5bc2 Display token names on wallet loader 2023-12-13 13:01:39 +03:00
Serkan Reis da3182f5a6 Merge pull request #293 from public-awesome/develop
Sync dev > main
2023-12-12 19:46:43 +02:00
Serkan Reis 1cdc9d8662 Merge pull request #292 from public-awesome/update-mint-denoms
Include BRNCH among OE mint price denom options
2023-12-12 19:44:55 +02:00
Serkan Reis 294fda9136 Include BRNCH among OE mint price denoms 2023-12-12 20:40:54 +03:00
Serkan Reis c62d7c10ba Merge pull request #291 from public-awesome/develop
Sync dev > main
2023-12-11 10:24:43 +02:00
Serkan Reis ac3f65f866 Merge pull request #290 from public-awesome/update-mint-price-denom-list
Include HUAHUA among mint price denoms
2023-12-11 10:23:19 +02:00
Serkan Reis 3d24e5a8f7 Include HUAHUA among mint price denoms 2023-12-11 11:22:12 +03:00
Serkan Reis cbf401638a Merge pull request #289 from public-awesome/develop
Sync dev > main
2023-12-08 10:45:51 +02:00
Serkan Reis 2f2b628782 Update wallet connection options 2023-12-08 11:44:35 +03:00
Serkan Reis 005646fd18 Merge pull request #288 from public-awesome/develop
Sync dev > main
2023-12-08 08:48:26 +02:00
Serkan Reis 16e6d45c18 Update wallet connection options 2023-12-08 09:41:13 +03:00
Serkan Reis f0ba060a14 Merge pull request #287 from public-awesome/develop
Sync dev > main
2023-12-08 07:43:07 +02:00
Serkan Reis b40eacf5f9 Merge pull request #286 from public-awesome/update-wallet-options
Update wallet connection options
2023-12-08 07:39:25 +02:00
Serkan Reis ef7bed1479 Update wallet connection options 2023-12-08 08:37:21 +03:00
Serkan Reis b969e0cd22 Merge pull request #285 from public-awesome/develop
Sync dev > main
2023-12-07 08:04:38 +03:00
Serkan Reis fde773e2f2 Merge pull request #284 from public-awesome/upload-contract-support
Upload contract support
2023-12-07 08:04:05 +03:00
Serkan Reis 108e6e034d Upload contract support 2023-12-07 08:03:27 +03:00
Serkan Reis 8ef767ef02 Merge pull request #283 from public-awesome/develop
Sync dev > main
2023-11-28 13:31:48 +03:00
Serkan Reis 1d5ba3aa78 Merge pull request #282 from public-awesome/mint-price-denom-kuji-update
Include KUJI among mint price denom options
2023-11-28 13:31:07 +03:00
Serkan Reis 87c2f43540 Include KUJI among mint price denom options 2023-11-28 13:27:12 +03:00
Serkan Reis 939552255b Merge pull request #281 from public-awesome/develop
Enable OE collection creation for STRDST
2023-11-26 20:06:38 +03:00
Serkan Reis 9e1a558148 Update OE sg721 code id for STRDST 2023-11-26 20:05:13 +03:00
Serkan Reis 70e17fd36e Bump Studio version 2023-11-26 20:01:07 +03:00
Serkan Reis f4b1760e3c Enable OE collection creation with STRDST 2023-11-26 20:00:21 +03:00
Serkan Reis cd9861ff54 Merge pull request #280 from public-awesome/develop
Sync dev > main
2023-11-24 07:24:10 +03:00
Serkan Reis ea0765e1a7 Revert sg721 code ID change for STRDST collections 2023-11-24 07:23:01 +03:00
Serkan Reis 0543b8459d Merge pull request #279 from public-awesome/develop
Sync dev > main
2023-11-24 06:24:17 +03:00
Serkan Reis 816a834f75 Update sg721 code ID for STRDST collections 2023-11-24 06:22:53 +03:00
Serkan Reis 9111b0b8a7 Merge pull request #278 from public-awesome/develop
Sync dev > main
2023-11-22 23:29:07 +03:00
Serkan Reis fb78db22cc Merge pull request #277 from public-awesome/pdf-support
PDF support for collection creation
2023-11-22 23:28:32 +03:00
Serkan Reis 2d5f5ed511 PDF support for collection creation 2023-11-22 23:23:51 +03:00
Serkan Reis 90ec372f96 Merge pull request #276 from public-awesome/develop
Fix configuration import issue for mainnet
2023-11-22 10:46:10 +03:00
Serkan Reis 50a8ea53b1 Fix import config issue for mainnet 2023-11-22 10:44:46 +03:00
Serkan Reis 9ae9948252 Merge pull request #275 from public-awesome/develop
Sync dev > main
2023-11-20 23:19:17 +03:00
Serkan Reis 6ab7d4017b Merge pull request #274 from public-awesome/wl-easy-access
Include easy access to whitelists on My Collections
2023-11-20 23:18:34 +03:00
Serkan Reis 4d53d498d7 Retain contract address while switching between link tabs 2023-11-20 23:16:16 +03:00
Serkan Reis 3e052e6e1e List whitelists on My Collections 2023-11-20 22:32:59 +03:00
Serkan Reis 8a27afe29a Merge pull request #273 from public-awesome/develop
Sync dev > main
2023-11-19 15:37:06 +03:00
Serkan Reis 626993ed83 Merge pull request #272 from public-awesome/whitelist-improvements
Add query pagination & member list export for whitelists
2023-11-19 15:36:12 +03:00
Serkan Reis 2ef9e3ccb9 Bump Studio version 2023-11-19 15:34:56 +03:00
Serkan Reis a13e0610e4 Add members query pagination & list export for whitelists 2023-11-19 15:34:05 +03:00
Serkan Reis 322b8c681c Merge pull request #271 from public-awesome/develop
Sync dev > main
2023-11-16 18:04:01 +03:00
Serkan Reis 017096ceb0 Merge pull request #270 from public-awesome/blacklist-for-royalty-payment-address
Blacklist minter & sg721 contract addresses for royalty payments
2023-11-16 18:03:27 +03:00
Serkan Reis f19ced2d32 Blacklist minter & sg721 contract addresses for royalty payments 2023-11-16 17:40:56 +03:00
Serkan Reis e6de5297da Merge pull request #269 from public-awesome/develop
Enable contract queries without a wallet connection
2023-11-14 19:07:52 +03:00
Serkan Reis 0ca2d63161 Enable contract queries without a wallet connection 2023-11-14 19:06:49 +03:00
Serkan Reis a204face96 Merge pull request #268 from public-awesome/develop
Sync dev > main
2023-11-12 21:22:43 +03:00
Serkan Reis 79bb24d33a Address the issue with #s in uploaded file names 2023-11-12 21:18:04 +03:00
Serkan Reis f475cec576 Merge pull request #267 from public-awesome/develop
Sync dev > main
2023-11-10 09:40:51 +03:00
Serkan Reis a0bbc0ebeb Disable updatable option for standard collection creation 2023-11-10 09:32:01 +03:00
Serkan Reis a2b2a072e6 Disable updatable option for collection creation 2023-11-10 09:16:18 +03:00
Serkan Reis 32fe46081e Merge pull request #266 from public-awesome/develop
Include USK & USDC among OE mint price denom options
2023-11-08 17:29:02 +03:00
Serkan Reis e8f3e0e4b5 Include USK & USDC among OE mint price denom options 2023-11-08 17:27:26 +03:00
Serkan Reis a611016953 Merge pull request #265 from public-awesome/develop
Sync dev > main
2023-10-27 10:14:50 +03:00
Serkan Reis bfcb84c8de Merge pull request #264 from public-awesome/nbtc-update
Include nBTC as an option for mint price denom
2023-10-27 10:14:12 +03:00
Serkan Reis 77b80fc989 Include nBTC as an option for mint price denom 2023-10-27 10:11:22 +03:00
Serkan Reis f77ab91245 Merge pull request #263 from public-awesome/develop
Sync dev > main
2023-10-25 11:13:44 +03:00
Serkan Reis 488d330623 Merge pull request #262 from public-awesome/fetch-factory-params-no-wallet
Allow factory parameters to be fetched without a wallet connection
2023-10-25 11:12:50 +03:00
Serkan Reis 187784261f Update mint price denom selection UI 2023-10-25 11:11:46 +03:00
Serkan Reis 758899b031 Allow factory parameters to be fetched with no wallet connection 2023-10-25 11:09:25 +03:00
Serkan Reis 03ae3e522f Merge pull request #261 from public-awesome/develop
Sync dev > main
2023-10-20 09:30:46 +03:00
Serkan Reis e65a79bf8b Update airdrop instructions for OE - 2 2023-10-20 09:29:45 +03:00
Serkan Reis 5e64456fe2 Merge pull request #260 from public-awesome/develop
Update airdrop instructions for OE
2023-10-20 09:22:35 +03:00
Serkan Reis e9d3efefed Update airdrop instructions for OE 2023-10-20 09:21:40 +03:00
Serkan Reis 32b3c5ffd6 Merge pull request #259 from public-awesome/develop
Sync dev > main
2023-10-19 23:34:45 +03:00
Serkan Reis 99e0caabfd Merge pull request #258 from NoahSaso/noah/update-cosmos-kit
Update Cosmos Kit packages
2023-10-19 23:33:57 +03:00
Noah Saso aa53f38554 Added recursive iframe architecture 2023-10-19 00:18:56 -07:00
Noah Saso 0968423e05 Update Cosmos Kit packages. 2023-10-18 23:18:47 -07:00
Serkan Reis 4b8f9be8b6 Merge pull request #257 from public-awesome/develop
Include USDC as a mint price denom option
2023-10-19 00:09:15 +03:00
Serkan Reis aea28679bb Use the latest sg721 code id with the USDC factory 2023-10-18 23:42:15 +03:00
Serkan Reis e036f22eb1 Merge pull request #256 from public-awesome/develop
Sync dev > main
2023-10-18 23:17:02 +03:00
Serkan Reis 7201210e93 Add USK to token list 2023-10-18 23:15:56 +03:00
Serkan Reis 1901bc7c49 Merge pull request #255 from public-awesome/develop
Sync dev > main
2023-10-18 23:02:39 +03:00
Serkan Reis 2a616fe794 Merge pull request #254 from public-awesome/usk-factory-update
Include USK as a mint price denom option
2023-10-18 23:01:12 +03:00
Serkan Reis a9efc0cbfa Use the latest sg721 code id with the USK factory 2023-10-18 22:50:44 +03:00
Serkan Reis 9c31d30a0c Update token & minter list 2023-10-18 22:47:40 +03:00
Serkan Reis a6f30994df Update env variables 2023-10-18 22:34:24 +03:00
Serkan Reis 9b01e7205f Merge pull request #253 from public-awesome/develop
Sync dev > main
2023-10-17 09:29:25 +03:00
Serkan Reis b41b927632 Merge pull request #252 from public-awesome/collection-actions-no-wallet-issue
Fix: Collection actions no wallet issue
2023-10-17 09:28:44 +03:00
Serkan Reis 7e084b01d7 Add content check for JSON preview 2023-10-17 09:20:30 +03:00
Serkan Reis e99832c283 Add no wallet warning on execute 2023-10-17 08:49:44 +03:00
Serkan Reis f8b7e4aea6 Merge pull request #251 from public-awesome/develop
Sync dev > main
2023-10-16 13:18:29 +03:00
Serkan Reis 3638b04d0d Merge pull request #250 from public-awesome/leap-wallet-integration
Leap Wallet integration
2023-10-16 13:17:33 +03:00
Serkan Reis cfa160d5eb Bump Studio version 2023-10-16 13:12:34 +03:00
Serkan Reis 17751c5455 Add Leap Wallet support to Wallet Provider 2023-10-16 13:12:04 +03:00
Serkan Reis 8615176670 Update .env.example 2023-10-16 13:09:58 +03:00
Serkan Reis fc9ba16ffb Merge pull request #249 from public-awesome/develop
Sync dev > main
2023-10-15 21:00:22 +03:00
Serkan Reis 3d43af4680 Merge pull request #234 from NoahSaso/noah/cosmos-kit
Use Cosmos Kit with Keplr Extension support
2023-10-15 20:53:04 +03:00
Serkan Reis d6cc8a700f Merge branch 'develop' into noah/cosmos-kit 2023-10-15 20:46:20 +03:00
Serkan Reis cce90213d8 Merge pull request #248 from public-awesome/develop
Sync dev > main
2023-10-13 19:21:54 +03:00
Serkan Reis 120efa9028 Merge pull request #247 from public-awesome/strdst-flexible
Add flexible minter option for STRDST
2023-10-13 19:21:21 +03:00
Serkan Reis dc4822aa70 Add flexible minter option for STRDST 2023-10-13 19:17:09 +03:00
Serkan Reis 53220109d1 Merge pull request #246 from public-awesome/develop
Sync dev > main
2023-10-13 18:39:27 +03:00
Serkan Reis b8d416b99f Merge pull request #245 from public-awesome/disable-oe-on-chain-metadata
Temporarily disable on-chain metadata option for OE collection creation
2023-10-13 18:38:52 +03:00
Serkan Reis a48fb8c5cc Disable on-chain metadata option for OE collection creation 2023-10-13 18:37:59 +03:00
Serkan Reis 570f6990b0 Merge pull request #244 from public-awesome/develop
Sync dev > main
2023-10-13 18:23:00 +03:00
Serkan Reis c11e99ecc9 Merge pull request #243 from public-awesome/whitelist-mint-price-denom
Add option to select whitelist unit price denom
2023-10-13 18:22:15 +03:00
Serkan Reis abdfd74663 Add option to select whitelist unit price denom 2023-10-13 18:21:20 +03:00
Serkan Reis d94ab200c2 Merge pull request #242 from public-awesome/develop
Sync dev > main
2023-10-13 13:04:36 +03:00
Serkan Reis 1bf56eebf3 Merge pull request #241 from public-awesome/base-minter-upload-logic-update
Update file upload logic for 1/1 collections
2023-10-13 13:04:01 +03:00
Serkan Reis e8be3c54eb Update base minter upload logic 2023-10-13 12:57:50 +03:00
Serkan Reis 5f675d9177 Merge pull request #240 from public-awesome/develop
Sync dev > main
2023-10-12 16:47:47 +03:00
Serkan Reis d62873a2db Merge pull request #239 from public-awesome/inifinity-swap-update
Update Collection Actions > Infinity Swap UI
2023-10-12 16:46:19 +03:00
Serkan Reis fb51f1519f Update Collection Actions > Infinity Swap UI 2023-10-12 16:45:33 +03:00
Serkan Reis eaf60d5594 Merge pull request #238 from public-awesome/develop
Sync dev > main
2023-10-12 11:35:37 +03:00
shane.stars bf6ae87a69 Merge pull request #237 from public-awesome/fix-naming
Added a space in the name
2023-10-12 11:30:46 +03:00
Shane Vitarana 5c098ed313 Added a space in the name 2023-10-12 11:25:18 +03:00
Serkan Reis fd335f766b Merge pull request #236 from public-awesome/develop
Sync dev > main
2023-10-12 10:09:55 +03:00
Serkan Reis 3a4e595ae5 Merge pull request #235 from public-awesome/strdst-sg721-update
Use latest sg721 code id with STRDST Vending Factory
2023-10-12 10:09:13 +03:00
Serkan Reis af3e0f2186 Update VM instantiation logic 2023-10-12 10:02:48 +03:00
Serkan Reis fddf9e28cf Update env variables 2023-10-12 09:54:43 +03:00
Noah Saso dfe0a27f77 Use Cosmos Kit with Keplr Extension support. 2023-10-11 16:48:20 -07:00
Serkan Reis 27864e20f1 Merge pull request #233 from public-awesome/develop
Sync dev > main
2023-10-11 08:44:30 +03:00
Serkan Reis 8b1c9e669d Merge pull request #232 from public-awesome/stardust-update
Include STRDST as a mint price denom option
2023-10-11 08:43:49 +03:00
Serkan Reis 60a03a3069 Include STRDST as a mint price denom option 2023-10-11 08:37:08 +03:00
Serkan Reis fbeeb4212a Merge pull request #231 from public-awesome/develop
Sync dev > main
2023-10-10 14:22:18 +03:00
Serkan Reis 8fc3f71413 Merge pull request #230 from public-awesome/royalty-registry-update
Royalty registry update
2023-10-10 14:21:40 +03:00
Serkan Reis 840483a830 Bump Studio version 2023-10-10 14:20:33 +03:00
Serkan Reis b2e61d5529 Add Infinity Swap related queries to Collection Actions > Queries 2023-10-10 14:19:52 +03:00
Serkan Reis 0365aa5a7b Add Infinity Swap related actions to Collection Actions > Actions 2023-10-10 13:43:14 +03:00
Serkan Reis 2dcbda6f25 Update execute list subtitles for royalty registry 2023-10-10 12:37:30 +03:00
Serkan Reis 5631362990 Update .env.example 2023-10-10 12:16:46 +03:00
Serkan Reis e6f0a5b91f Update Royalty Registry > Query 2023-10-10 11:41:52 +03:00
Serkan Reis 2e55923ac3 Update Royalty Registry > Execute 2023-10-10 11:01:40 +03:00
Serkan Reis ca5ffa0a00 Update env veriables 2023-10-09 21:42:54 +03:00
Serkan Reis 1e1acf5e07 Merge pull request #229 from public-awesome/develop
Sync dev > main
2023-10-07 13:03:46 +03:00
Serkan Reis 3a43fb5420 Merge pull request #228 from public-awesome/royalty-registry
Royalty registry support
2023-10-07 13:01:25 +03:00
Serkan Reis 5a80ad1587 Bump Studio version 2023-10-07 12:51:37 +03:00
Serkan Reis 3325a93edb Match inputs with query types 2023-10-07 12:50:45 +03:00
Serkan Reis 51acae6a78 Match inputs and message types for execute 2023-10-07 12:30:54 +03:00
Serkan Reis b398650794 Update sidebar 2023-10-07 11:58:22 +03:00
Serkan Reis f092d7d926 Init Royalty Registry dashboard > Query 2023-10-07 11:53:28 +03:00
Serkan Reis 0595c4de25 Init Royalty Registry dashboard > Execute 2023-10-07 11:53:10 +03:00
Serkan Reis 584a33c388 Add link tabs for royalty registry 2023-10-07 11:04:08 +03:00
Serkan Reis 2b893e6e60 Add combobox for royalty registry / execute 2023-10-07 11:01:56 +03:00
Serkan Reis b697b1a857 Update contracts context 2023-10-07 10:46:37 +03:00
Serkan Reis ca8f5cca58 Match list items with contract helpers 2023-10-07 10:41:10 +03:00
Serkan Reis 9fcd5b82f4 Init Royalty Registry contract hooks 2023-10-07 09:05:54 +03:00
Serkan Reis 2710be6959 Update env variables 2023-10-07 08:59:28 +03:00
Serkan Reis d541fe7294 Init Royalty Registry contract helpers 2023-10-07 08:53:18 +03:00
Jorge Hernandez 3a3c4589e6 Update package.json 2023-09-30 11:01:57 -06:00
Serkan Reis bd18197e88 Merge pull request #227 from public-awesome/develop
Sync dev > main
2023-09-29 23:53:24 +03:00
Serkan Reis 883fc98cad Merge pull request #226 from public-awesome/update-frnz-denom
Update IBC denoms
2023-09-29 23:52:36 +03:00
Serkan Reis 2160533a60 Update uusdc denom 2023-09-29 23:50:59 +03:00
Serkan Reis 6dd0e5ef8b Update FRNZ denom for mainnet 2023-09-29 23:33:27 +03:00
Serkan Reis 38a33273dc Merge pull request #224 from public-awesome/update-ibc-tokens-for-testnet
Update IBC denoms for testnet
2023-09-28 19:27:39 +03:00
Serkan Reis 53322784fc Update IBC denoms for testnet 2023-09-28 19:26:55 +03:00
Serkan Reis 03094427bc Merge pull request #223 from public-awesome/develop
Sync dev > main
2023-09-26 08:47:30 +03:00
Serkan Reis 0eaa66f2e5 Merge pull request #222 from public-awesome/token-description-line-breaks
Enable line breaks with manual metadata input for 1/1 collections
2023-09-26 08:46:51 +03:00
Serkan Reis defc55abf6 Enable line breaks with manual metadata input for 1/1 collections 2023-09-26 08:46:01 +03:00
Serkan Reis fbc457f9cd Merge pull request #221 from public-awesome/develop
Sync dev > main
2023-09-21 14:35:37 +03:00
Serkan Reis 6db97d615c Merge pull request #220 from public-awesome/display-updatable-price
Display updatable metadata price on collection details
2023-09-21 14:34:44 +03:00
Serkan Reis 2f2a7c6a76 Display updatable metadata price on collection details 2023-09-21 14:29:21 +03:00
Serkan Reis a857e7fef1 Merge pull request #219 from public-awesome/develop
Sync dev > main
2023-09-21 09:05:07 +03:00
Serkan Reis 23e6278f30 Merge pull request #218 from public-awesome/import-config-update
Update import config logic
2023-09-21 09:01:54 +03:00
Serkan Reis 40fb1933e4 Update import logic to default to non-updatable on mainnet 2023-09-21 08:57:43 +03:00
Serkan Reis 572968cf24 Merge pull request #216 from public-awesome/develop
Sync dev > main
2023-09-21 08:14:16 +03:00
Serkan Reis 9d02f73347 Merge pull request #217 from public-awesome/batch-transfer-specific-addresses
Add multi-address batch transfers to collection actions
2023-09-21 07:51:24 +03:00
Serkan Reis 88177fd446 Update file selection CTO copy 2023-09-13 10:56:12 +03:00
Serkan Reis dd33b6129c Bump Studio version 2023-09-13 10:39:07 +03:00
Serkan Reis c455eafb7b Add multi-address batch transfers to collection actions 2023-09-13 10:38:26 +03:00
Jorge Hernandez e0f41fd692 Merge branch 'main' into develop 2023-09-11 15:08:08 -06:00
Serkan Reis 5a7386020e Merge pull request #215 from public-awesome/thumbnail-selection
Thumbnail selection & upload for compatible assets
2023-09-11 22:33:28 +03:00
Serkan Reis b65fd5d3c9 Bump Studio version 2023-09-10 14:55:17 +03:00
Serkan Reis c4f486f1f0 Update collection creation logic for OE/off-chain metadata 2023-09-10 13:42:37 +03:00
Serkan Reis 75a2d4c089 Update collection creation logic for OE/on-chain metadata 2023-09-10 12:46:52 +03:00
Serkan Reis 26c39e8985 Enable thumbnail selection for OE/on-chain metadata 2023-09-10 12:20:23 +03:00
Serkan Reis 387aa5c703 Update upload & metadata upload logic for standard & 1/1 collections 2023-09-09 22:01:25 +03:00
Serkan Reis be2d644ec9 Surface thumbnail compatible asset file names 2023-09-09 20:39:05 +03:00
Serkan Reis df0c7a5f1f File selection logic for thumbnails 2023-09-09 15:30:02 +03:00
Jorge Hernandez 5db159dc96 Merge pull request #214 from public-awesome/hotfix/batch-update-open-edition
Hotfix/batch update open edition
2023-09-05 19:12:51 -06:00
jhernandezb 70dad6b7c6 bump package version 2023-09-05 19:12:20 -06:00
jhernandezb cefbd37fcf revert temp change 2023-09-05 19:11:57 -06:00
Jorge Hernandez 7aa5256827 Merge pull request #213 from public-awesome/hotfix/batch-update-open-edition
batch update open edition
2023-09-05 19:04:07 -06:00
jhernandezb 1d46945df4 update package.json 2023-09-05 19:03:08 -06:00
jhernandezb 9e7afee3fc update contract execution 2023-09-05 19:02:48 -06:00
Jorge Hernandez a5b5e89d71 Update package.json 2023-09-05 18:41:36 -06:00
Jorge Hernandez 563a096483 Merge pull request #212 from public-awesome/hotfix/batch-update-open-edition
use same URI
2023-09-05 18:30:14 -06:00
jhernandezb 898c0d2eab use same URI 2023-09-05 18:23:36 -06:00
Jorge Hernandez c7a682b407 Merge pull request #210 from public-awesome/develop
Sync dev > main
2023-09-05 18:10:48 -06:00
Jorge Hernandez 93a4c6e5b3 Merge pull request #211 from public-awesome/update-disclaimer
Update collection creation disclaimer
2023-09-05 18:10:31 -06:00
Serkan Reis 6d8056fada Disable on-chain metadata for Open Edition collections 2023-09-05 17:25:25 +03:00
Serkan Reis eab00f140e Update collection creation disclaimer 2023-09-05 10:01:23 +03:00
Serkan Reis 5fc43159e3 Merge pull request #209 from public-awesome/utc-option-for-time-input
Global time input switch (local vs. UTC)
2023-09-04 10:40:30 +03:00
Serkan Reis 50ac9b9545 Re-adjust settings modal size 2023-09-01 18:24:16 +03:00
Serkan Reis 4566c1bdc7 Update settings modal placement 2023-09-01 17:35:51 +03:00
Serkan Reis 6475f55e7e Update settings modal 2023-09-01 17:13:49 +03:00
Serkan Reis ed568d4a25 Bump Studio version 2023-09-01 15:15:50 +03:00
Serkan Reis c6534d30f5 Update date & time inputs to reflect timezone settings 2023-09-01 15:14:38 +03:00
Serkan Reis 795d54e4c4 Update default time input as UTC 2023-09-01 15:13:50 +03:00
Serkan Reis 7e7fc41b85 Settings modal init 2023-09-01 12:59:01 +03:00
Serkan Reis 4324b225cc Load previous global settings on load 2023-09-01 12:58:36 +03:00
Serkan Reis 3772dec6e1 Init globalSettings 2023-08-31 23:37:29 +03:00
Serkan Reis 3118d14087 Merge pull request #205 from public-awesome/develop
Sync dev > main
2023-08-31 14:19:37 +03:00
Jorge Hernandez 1a9d7ac9c8 Merge pull request #194 from public-awesome/export-import-collection-config
Export/Import collection creation configuration
2023-08-30 07:52:48 -06:00
Jorge Hernandez e88d3529f9 Merge pull request #207 from public-awesome/update-enable-updatable-fee
Update enable updatable fee
2023-08-30 07:40:44 -06:00
Jorge Hernandez 1501c6790d Merge pull request #208 from public-awesome/factory-switching
Fix factory switching related issues
2023-08-30 07:39:57 -06:00
Serkan Reis bf697745d5 Fix factory switching related issues 2023-08-29 14:13:57 +03:00
Serkan Reis 1c689cbb19 Update enable updatable fee 2023-08-26 18:28:05 +03:00
Jorge Hernandez 1c06ae3eab Merge pull request #206 from public-awesome/update-usdc-denom
Update USDC denom for testnet
2023-08-25 10:18:00 -06:00
Serkan Reis f62348df0c Update USDC denom for testnet 2023-08-25 18:24:18 +03:00
Jorge Hernandez a293c95611 Merge pull request #203 from public-awesome/badge-creation-video-support
Badge creation video asset support
2023-08-22 21:26:17 -06:00
Jorge Hernandez 4adc25728c Merge pull request #204 from public-awesome/fix-upload-with-no-token-description
Address upload issue when token metadata lacks a description
2023-08-22 21:25:40 -06:00
Serkan Reis 26a5423599 Address upload issue when token metadata lacks a description 2023-08-22 21:52:31 +03:00
Serkan Reis 391b712bde Prevent duplicate protocol in the base token uri 2023-08-21 19:45:23 +03:00
Serkan Reis 1ca1d08b2a Reset upload details on import for 1/1 collections 2023-08-21 16:43:05 +03:00
Serkan Reis f25807f355 Unmicro whitelist unit price 2023-08-21 16:29:52 +03:00
Serkan Reis 5578c408a5 Bump Studio version 2023-08-21 15:00:06 +03:00
Serkan Reis 4cc6fdc070 Update import/export component placement 2023-08-21 14:59:12 +03:00
Serkan Reis ae9aec3bd8 Disable default upload method when importing 2023-08-21 12:40:54 +03:00
Serkan Reis 96dda936ae Address OE collection empty metadata file issue 2023-08-21 12:07:11 +03:00
Serkan Reis 8990175b03 Check end time during open edition creation - 2 2023-08-21 11:37:19 +03:00
Serkan Reis 0ed370aa67 Check end time during open edition creation 2023-08-21 10:59:10 +03:00
Serkan Reis e26253fec5 Badge creation video asset support 2023-08-18 13:19:05 +03:00
Serkan Reis bc719e1a0c Merge branch 'develop' into export-import-collection-config 2023-08-17 17:24:27 +03:00
Serkan Reis 958671a030 Merge pull request #195 from public-awesome/ibc-minter-creation
IBC minter support
2023-08-17 16:30:28 +03:00
Serkan Reis 8b902a1078 Address royalty address import issue for open edition 2023-08-17 14:22:05 +03:00
Serkan Reis 58d2a4abd7 Export/Import selected mint token 2023-08-17 14:21:30 +03:00
Serkan Reis 71e539a0b4 Export/Import open edition token metadata 2023-08-17 14:20:37 +03:00
Serkan Reis 3fbebbe03d Auto-add wallet address as whitelist admin 2023-08-16 17:57:07 +03:00
Serkan Reis 27e1727fa8 Update export logic for open edition collection summary 2023-08-16 17:48:33 +03:00
Serkan Reis ea5caff1aa Update .env.example 2023-08-15 20:09:35 +03:00
Serkan Reis 3c392381b2 Address invalid creation fee problem following minting denom change 2023-08-15 20:07:51 +03:00
Serkan Reis 702e47e9e6 Update FRNZ denom for testnet 2023-08-15 20:06:53 +03:00
Serkan Reis 6fc4022c8d Update export logic for standard collection summary 2023-08-15 18:24:27 +03:00
Serkan Reis f324cb6f50 Add token image URI to open edition minter details 2023-08-12 22:47:53 +03:00
Serkan Reis 51843cade0 Surface open edition collection summary details 2023-08-10 22:38:03 +03:00
Serkan Reis 65c2dabed6 Merge branch 'ibc-minter-creation' into export-import-collection-config 2023-08-08 14:35:46 +03:00
Serkan Reis 4c87ac298b Merge pull request #200 from public-awesome/develop
Sync dev > main
2023-08-04 12:04:53 +03:00
Adnan Deniz Corlu fa859b23d9 Merge pull request #199 from public-awesome/avoid-escaping-line-breaks
Fix: Use replaceAll instead of replace to ignore all escaped line breaks
2023-08-04 12:01:49 +03:00
Serkan Reis 18cdef9580 Fix: Use replaceAll instead of replace to ignore all escaped line breaks 2023-08-04 11:54:22 +03:00
Adnan Deniz Corlu 9b8c3e3e7c Merge pull request #198 from public-awesome/develop
Sync dev > main
2023-08-04 10:46:15 +03:00
Adnan Deniz Corlu ea582297ce Merge pull request #197 from public-awesome/line-breaks
Avoid escaping line breaks in token and collection descriptions
2023-08-04 10:41:01 +03:00
Serkan Reis c5e321e7f4 Cover collection updates 2023-08-04 10:22:11 +03:00
Serkan Reis 3d527c6682 Avoid escaping line breaks in token and collection descriptions 2023-08-04 10:11:34 +03:00
Serkan Reis 6fb1504d1f Merge pull request #192 from public-awesome/develop
Sync dev > main
2023-08-03 00:38:38 +03:00
Jorge Hernandez b66c6befd2 Merge pull request #196 from public-awesome/revoke-authorization 2023-08-02 15:36:56 -06:00
Serkan Reis 7003325d5d Add temporary revoke authorization UI 2023-08-03 00:31:55 +03:00
Serkan Reis 0481032a1f Surface standard & 1/1 collection configuration 2023-07-25 22:27:42 +03:00
Serkan Reis 2a38e79191 Surface open edition collection configuration 2023-07-25 22:26:29 +03:00
Serkan Reis 8921938c6c Initial export/import logic 2023-07-25 22:24:40 +03:00
Serkan Reis e074413a9e Update AddressList 2023-07-24 21:57:53 +03:00
Serkan Reis d5b1acc16e Create OpenEditionMinterDetailsDataProps 2023-07-23 21:54:14 +03:00
Serkan Reis 0deb4d3faa Merge pull request #188 from public-awesome/develop
Sync development > main
2023-07-07 15:29:51 +03:00
Serkan Reis fd506e402f Merge pull request #186 from public-awesome/develop
Sync development > main
2023-07-05 12:58:24 +03:00
Serkan Reis 29a27c2c3d Merge pull request #184 from public-awesome/develop
Sync development > main
2023-07-04 09:08:00 +03:00
Serkan Reis d811a1333c Merge pull request #182 from public-awesome/develop
Sync development > main
2023-07-03 16:40:14 +03:00
Jorge Hernandez ef37c8c9ac Merge pull request #179 from public-awesome/develop
Sync development > main
2023-06-22 15:35:14 -06:00
Serkan Reis 2945105a87 Merge pull request #176 from public-awesome/develop
Sync development > main
2023-06-22 21:55:36 +03:00
Serkan Reis bed4ad24e5 Merge pull request #170 from public-awesome/develop
Sync development > main
2023-06-08 12:12:18 +03:00
Serkan Reis a2029da8a4 Merge pull request #168 from public-awesome/develop
Sync development > main
2023-06-06 16:11:55 +03:00
Serkan Reis 93b8a541e4 Merge pull request #166 from public-awesome/develop
Sync development > main
2023-06-03 14:10:04 +03:00
Serkan Reis 01bbbb0836 Merge pull request #164 from public-awesome/develop
Sync development > main
2023-05-29 12:22:11 +03:00
Serkan Reis 2f77b2d365 Merge pull request #162 from public-awesome/develop
Sync development > main
2023-05-04 13:44:47 +03:00
Serkan Reis 686b7494ae Merge pull request #160 from public-awesome/develop
Sync development > main
2023-05-03 22:08:38 +03:00
Serkan Reis b37b6e44bf Merge pull request #158 from public-awesome/develop
Sync development > main
2023-05-03 12:07:57 +03:00
Serkan Reis cbbc6c5272 Merge pull request #156 from public-awesome/develop
Sync development > main
2023-04-28 15:07:14 +03:00
Serkan Reis d725f33155 Merge pull request #152 from public-awesome/develop
Sync development > main
2023-04-12 10:52:41 +03:00
Serkan Reis 51364147c9 Merge pull request #148 from public-awesome/develop
Sync development > main
2023-04-07 12:53:04 +03:00
Serkan Reis c1da34dacb Merge pull request #146 from public-awesome/develop
Sync development > main
2023-04-05 17:22:08 +03:00
Serkan Reis a7cc57c386 Merge pull request #144 from public-awesome/develop
Sync development > main
2023-04-04 15:49:18 +03:00
Adnan Deniz corlu c91e808fc0 Merge pull request #141 from public-awesome/develop
Sync development >main
2023-04-03 13:06:11 +03:00
Serkan Reis be7ee5c7ad Merge pull request #139 from public-awesome/develop
Sync development > main
2023-04-01 19:36:28 +03:00
Serkan Reis 5bab23ebce Merge pull request #137 from public-awesome/develop
sync dev > main
2023-04-01 17:08:07 +03:00
Serkan Reis 62299e02f4 Merge pull request #134 from public-awesome/develop
Sync development > main
2023-03-31 13:40:45 +03:00
Serkan Reis fe75598b17 Merge pull request #132 from public-awesome/develop
Sync development > main
2023-03-27 16:41:07 +03:00
Serkan Reis 9cb0410ae6 Merge pull request #129 from public-awesome/revert-recent-changes
Revert Splits & WL related changes
2023-03-27 15:07:19 +03:00
Serkan Reis f3418100d3 Revert Splits & WL related changes 2023-03-27 11:07:53 +03:00
Serkan Reis 2e0d8e8ed9 Merge pull request #128 from public-awesome/wl-related-changes
WL related changes
2023-03-27 07:06:31 +03:00
Serkan Reis 95a8b39b6d WL related changes 2023-03-26 04:07:53 +03:00
Serkan Reis e3ae0fb65c Merge pull request #127 from public-awesome/splits-related-updates
Splits contract dashboard & optional mint revenue payment address
2023-03-24 11:36:53 +03:00
Serkan Reis 7a21240bed Add splits contract dashboard & mint revenue payment address 2023-03-24 10:43:10 +03:00
Adnan Deniz corlu 1a076026e0 Merge pull request #121 from public-awesome/develop
Sync development > main
2023-03-07 18:05:05 +03:00
Serkan Reis e5c212751b Merge pull request #119 from public-awesome/develop
Sync development > main
2023-03-04 12:52:29 +03:00
Serkan Reis d4ad8148bb Merge pull request #117 from public-awesome/develop
Sync development > main
2023-03-04 08:24:01 +03:00
Serkan Reis eb86960ca6 Merge pull request #115 from public-awesome/develop
Sync development > main
2023-02-28 18:49:47 +03:00
Serkan Reis 69e7525349 Merge pull request #112 from public-awesome/develop
Sync development > main
2023-02-23 18:04:25 +03:00
Adnan Deniz corlu 2b9c6c2d24 Merge pull request #110 from public-awesome/develop
Sync development > main
2023-02-23 16:01:03 +03:00
Serkan Reis b81980c3d7 Merge pull request #108 from public-awesome/develop
Sync development > main
2023-02-23 15:50:11 +03:00
Serkan Reis 56afc889f4 Merge pull request #107 from public-awesome/develop
Sync development>main
2023-02-22 23:27:07 +03:00
Serkan Reis 53e43476d6 Merge pull request #105 from public-awesome/develop
Sync Development > Main
2023-02-20 13:01:34 +03:00
Serkan Reis 6d0adcc355 Merge pull request #101 from public-awesome/develop
Sync development > main
2023-02-05 14:38:00 +03:00
Serkan Reis 3870159797 Merge pull request #99 from public-awesome/develop
Sync development > main
2023-02-01 16:30:35 +03:00
Serkan Reis c0ba8d4715 Merge pull request #98 from public-awesome/develop
Sync development > main
2023-01-24 14:30:07 +03:00
Serkan Reis 600a5e063c Merge pull request #96 from public-awesome/develop
Sync development > main
2023-01-21 15:04:24 +03:00
Serkan Reis 4c6442595c Merge pull request #91 from public-awesome/develop
Sync development > main
2023-01-17 16:48:15 +03:00
Serkan Reis a57af69c49 Merge pull request #89 from public-awesome/develop
Sync development > main
2023-01-12 14:45:58 +03:00
Serkan Reis 813d0e27f8 Merge pull request #87 from public-awesome/develop
Sync development > main
2023-01-09 10:18:18 +03:00
Serkan Reis 3d40de95b1 Merge pull request #85 from public-awesome/develop
Sync development > main
2023-01-05 14:46:06 +03:00
Serkan Reis 045679bbd2 Merge pull request #83 from public-awesome/develop
Sync development > main
2022-12-26 16:23:54 +03:00
Serkan Reis aff06448a5 Merge pull request #81 from public-awesome/develop
Sync development > main
2022-12-21 10:51:41 +03:00
Serkan Reis 9cd952122e Merge pull request #79 from public-awesome/develop
Sync development > main
2022-12-20 15:01:53 +03:00
Serkan Reis 27500f8474 Merge pull request #77 from public-awesome/develop
Sync development > mainnet
2022-12-19 17:59:39 +03:00
Serkan Reis 9258432d50 Merge pull request #72 from public-awesome/develop
Sync development > main
2022-12-01 10:26:07 +03:00
Serkan Reis d40ed12c5e Merge pull request #69 from public-awesome/develop
Sync development > main
2022-11-24 09:25:58 +03:00
Jorge Hernandez 7ace365327 Merge pull request #66 from public-awesome/develop
revert ipfs lowercase
2022-11-23 14:03:29 -06:00
Serkan Reis 3edd21502a Merge pull request #65 from public-awesome/develop
Sync development > main
2022-11-18 19:59:26 +03:00
Serkan Reis fa108fe746 Merge pull request #62 from public-awesome/develop
Sync development > main
2022-11-08 16:11:18 +03:00
Serkan Reis f5d3906b41 Merge pull request #60 from public-awesome/develop
Sync development > main
2022-11-08 14:51:35 +03:00
Serkan Reis 996469d556 Merge pull request #58 from public-awesome/develop
Sync development > main
2022-11-07 10:52:12 +03:00
Serkan Reis 2f571d547b Merge pull request #55 from public-awesome/develop
Sync development > main
2022-11-02 10:58:15 +03:00
Serkan Reis aac3665781 Merge pull request #53 from public-awesome/develop
Merge development > main
2022-10-31 10:12:20 +03:00
168 changed files with 32560 additions and 1875 deletions
+1
View File
@@ -0,0 +1 @@
CERC_MAX_GENERATE_TIME=180
+80 -8
View File
@@ -1,8 +1,10 @@
APP_VERSION=0.7.3
APP_VERSION=0.8.7
NEXT_PUBLIC_PINATA_ENDPOINT_URL=https://api.pinata.cloud/pinning/pinFileToIPFS
NEXT_PUBLIC_SG721_CODE_ID=2595
NEXT_PUBLIC_SG721_UPDATABLE_CODE_ID=2596
NEXT_PUBLIC_STRDST_SG721_CODE_ID=2595
NEXT_PUBLIC_BASE_FACTORY_SG721_CODE_ID=2595
NEXT_PUBLIC_OPEN_EDITION_SG721_CODE_ID=2595
NEXT_PUBLIC_OPEN_EDITION_SG721_UPDATABLE_CODE_ID=2596
NEXT_PUBLIC_VENDING_MINTER_CODE_ID=2600
@@ -11,9 +13,13 @@ NEXT_PUBLIC_BASE_MINTER_CODE_ID=2598
NEXT_PUBLIC_OPEN_EDITION_MINTER_CODE_ID=2579
NEXT_PUBLIC_VENDING_FACTORY_ADDRESS="stars18h7ugh8eaug7wr0w4yjw0ls5s937z35pnkg935ucsek2y9xl3gaqqk4jtx"
NEXT_PUBLIC_FEATURED_VENDING_FACTORY_ADDRESS="stars14pd96yk3t6gq9l6uyrkg0n5dr09n8rt5y9v3at8x4wl4lrkxhlzq4trqmh"
NEXT_PUBLIC_VENDING_FACTORY_UPDATABLE_ADDRESS="stars1h65nms9gwg4vdktyqj84tu50gwlm34e0eczl5w2ezllxuzfxy9esa9qlt0"
NEXT_PUBLIC_VENDING_FACTORY_FLEX_ADDRESS="stars1hvu2ghqkcnvhtj2fc6wuazxt4dqcftslp2rwkkkcxy269a35a9pq60ug2q"
NEXT_PUBLIC_VENDING_FACTORY_UPDATABLE_FLEX_ADDRESS=
NEXT_PUBLIC_VENDING_FACTORY_MERKLE_TREE_ADDRESS="stars167tudcsr9n2y9ljgk4cwxhs0cvkfkk0hh6c3dzngsz7m5s9jmqnsdgr3jy"
NEXT_PUBLIC_FEATURED_VENDING_FACTORY_MERKLE_TREE_ADDRESS="stars167tudcsr9n2y9ljgk4cwxhs0cvkfkk0hh6c3dzngsz7m5s9jmqnsdgr3jy"
NEXT_PUBLIC_FEATURED_VENDING_FACTORY_FLEX_ADDRESS="stars1udlmmnmmnnqamh36hy6d7azn3ycv23yymkmg6558ntalvyt2pz7s8lhgcd"
# NEXT_PUBLIC_VENDING_FACTORY_UPDATABLE_FLEX_ADDRESS=
# NEXT_PUBLIC_VENDING_IBC_ATOM_FACTORY_ADDRESS=
# NEXT_PUBLIC_VENDING_IBC_ATOM_UPDATABLE_FACTORY_ADDRESS=
@@ -21,31 +27,92 @@ NEXT_PUBLIC_VENDING_FACTORY_UPDATABLE_FLEX_ADDRESS=
# NEXT_PUBLIC_VENDING_IBC_ATOM_UPDATABLE_FACTORY_FLEX_ADDRESS=
# NEXT_PUBLIC_VENDING_IBC_USDC_FACTORY_ADDRESS=
# NEXT_PUBLIC_FEATURED_VENDING_IBC_USDC_FACTORY_ADDRESS=
# NEXT_PUBLIC_VENDING_IBC_USDC_UPDATABLE_FACTORY_ADDRESS=
# NEXT_PUBLIC_VENDING_IBC_USDC_FACTORY_FLEX_ADDRESS=
# NEXT_PUBLIC_FEATURED_VENDING_IBC_USDC_FACTORY_FLEX_ADDRESS=
# NEXT_PUBLIC_VENDING_IBC_USDC_UPDATABLE_FACTORY_FLEX_ADDRESS=
# NEXT_PUBLIC_VENDING_IBC_USK_FACTORY_ADDRESS=
# NEXT_PUBLIC_VENDING_IBC_USK_UPDATABLE_FACTORY_ADDRESS=
# NEXT_PUBLIC_VENDING_IBC_USK_FACTORY_FLEX_ADDRESS=
# NEXT_PUBLIC_VENDING_IBC_USK_UPDATABLE_FACTORY_FLEX_ADDRESS=
# NEXT_PUBLIC_VENDING_IBC_TIA_FACTORY_ADDRESS=
# NEXT_PUBLIC_FEATURED_VENDING_IBC_TIA_FACTORY_ADDRESS=
# NEXT_PUBLIC_VENDING_IBC_TIA_UPDATABLE_FACTORY_ADDRESS=
# NEXT_PUBLIC_VENDING_IBC_TIA_FACTORY_FLEX_ADDRESS=
# NEXT_PUBLIC_VENDING_IBC_TIA_FACTORY_MERKLE_TREE_ADDRESS=
# NEXT_PUBLIC_FEATURED_VENDING_IBC_TIA_FACTORY_MERKLE_TREE_ADDRESS=
# NEXT_PUBLIC_FEATURED_VENDING_IBC_TIA_FACTORY_FLEX_ADDRESS=
# NEXT_PUBLIC_VENDING_IBC_TIA_UPDATABLE_FACTORY_FLEX_ADDRESS=
NEXT_PUBLIC_VENDING_NATIVE_STARDUST_FACTORY_ADDRESS="stars1mxwf2hjcjvqnlw0v3j7m0u34975qesp325wzrgz0ht7vr8ys2zmsenjutf"
NEXT_PUBLIC_VENDING_NATIVE_STARDUST_UPDATABLE_FACTORY_ADDRESS="stars18gjczf88jd4z3a3megwj9g5c9famu654csxfnnq59mkqeszuzy4ssdgr46"
NEXT_PUBLIC_VENDING_NATIVE_STRDST_FLEX_FACTORY_ADDRESS="stars1eluqmr6x78ehl4plrln6khxc0qrspfhc7rt3whmr59escpve0r4swcacjh"
# NEXT_PUBLIC_VENDING_NATIVE_BRNCH_FACTORY_ADDRESS=""
# NEXT_PUBLIC_VENDING_NATIVE_BRNCH_UPDATABLE_FACTORY_ADDRESS=""
# NEXT_PUBLIC_VENDING_NATIVE_BRNCH_FLEX_FACTORY_ADDRESS=""
NEXT_PUBLIC_BASE_FACTORY_ADDRESS="stars1a45hcxty3spnmm2f0papl8v4dk5ew29s4syhn4efte8u5haex99qlkrtnx"
NEXT_PUBLIC_BASE_FACTORY_UPDATABLE_ADDRESS="stars100xegx2syry4tclkmejjwxk4nfqahvcqhm9qxut5wxuzhj5d9qfsh5nmym"
NEXT_PUBLIC_OPEN_EDITION_FACTORY_ADDRESS="stars1sqweqcxlf2f7qhf27gn5naqusk5q52fkzewmy63c4sglvle3s7ls6k828e"
NEXT_PUBLIC_OPEN_EDITION_FACTORY_FLEX_ADDRESS="stars1nc59ddaa8xcx9mu8jladza82dznhxrta3njal3xylkqlsfqa7g4s9s5q02"
NEXT_PUBLIC_OPEN_EDITION_UPDATABLE_FACTORY_ADDRESS="stars1fk5dkzcylam8mcpqrn8y9spauvc3d4navtaqurcc49dc3p9f8d3qdkvymx"
NEXT_PUBLIC_VENDING_IBC_KUJI_FACTORY_ADDRESS="stars1yyje87e0h9mqg34kp3x75yesa78ve4glc3dstdrn6nscw3zjfanqkj95f0"
NEXT_PUBLIC_VENDING_IBC_KUJI_FACTORY_FLEX_ADDRESS="stars1jralxqalpw9nf3kdc0s222z3mk343wry60cjaze9xadgfn2te4usf92e9r"
NEXT_PUBLIC_VENDING_IBC_HUAHUA_FACTORY_ADDRESS="stars16luw6rxgr6as9s7eu5auvnk5tnzszjrs34etsw9fmk25yqjfq09qq9gzl4"
NEXT_PUBLIC_VENDING_IBC_HUAHUA_FACTORY_FLEX_ADDRESS="stars1d97h6nfgwqr8eynzdcrsm3p0n6rduvkrcqdjhm5z7heavtgnqg4sgy2yew"
NEXT_PUBLIC_VENDING_IBC_CRBRUS_FACTORY_ADDRESS="stars1z0upxsyxhrvygrsd2t69majd6wl8qw4h8ff2fp27z3nn93m73pwsu4hpdh"
NEXT_PUBLIC_VENDING_IBC_CRBRUS_FACTORY_FLEX_ADDRESS="stars1halhp674yxwgn3p4gpkl8790h07vkm0vjm4vj7y8ql499e3zydzqurt5m3"
# NEXT_PUBLIC_OPEN_EDITION_IBC_ATOM_FACTORY_ADDRESS=
# NEXT_PUBLIC_OPEN_EDITION_UPDATABLE_IBC_ATOM_FACTORY_ADDRESS=
# NEXT_PUBLIC_OPEN_EDITION_IBC_USDC_FACTORY_ADDRESS=
# NEXT_PUBLIC_OPEN_EDITION_IBC_USDC_FACTORY_ADDRESS="stars152a40mmd3k2kk90add606vrqxcvzdp29qrjx4pjv33cjl6svksfscrrtuk"
# NEXT_PUBLIC_OPEN_EDITION_IBC_USDC_FACTORY_FLEX_ADDRESS="stars10sz9mup3a548l34k83q5w59nrklrnvv2gdsdkr2xref4zl5j3d4q0efamx"
# NEXT_PUBLIC_OPEN_EDITION_UPDATABLE_IBC_USDC_FACTORY_ADDRESS=
# NEXT_PUBLIC_OPEN_EDITION_IBC_FRNZ_FACTORY_ADDRESS=
# NEXT_PUBLIC_OPEN_EDITION_UPDATABLE_IBC_FRNZ_FACTORY_ADDRESS=
# NEXT_PUBLIC_OPEN_EDITION_IBC_TIA_FACTORY_ADDRESS="stars1vza7k890fkejxz3mqwau0u2m89k9y76w94vvxe4d42ya9862ryfq0damns"
# NEXT_PUBLIC_OPEN_EDITION_IBC_TIA_FACTORY_FLEX_ADDRESS="stars1jgn0ntt5tut93yn756rrqa60794qdsrn6dwhl8vhfx0yxgpr44qsfzhmrt"
# NEXT_PUBLIC_OPEN_EDITION_UPDATABLE_IBC_TIA_FACTORY_ADDRESS=
NEXT_PUBLIC_OPEN_EDITION_IBC_FRNZ_FACTORY_ADDRESS="stars1vzffawsjhvspstu5lvtzz2x5n7zh07hnw09c9dfxcj78un05rcms5n3q3e"
NEXT_PUBLIC_OPEN_EDITION_UPDATABLE_IBC_FRNZ_FACTORY_ADDRESS="stars1tc09vlgdg8rqyapcxwm9qdq8naj4gym9px4ntue9cs0kse5rvess0nee3a"
NEXT_PUBLIC_OPEN_EDITION_NATIVE_STRDST_FACTORY_ADDRESS="stars10sw8fvwtetndy3ctpcvee8yq7t6qp49m5yahm5gf8qz3qt3hzvcq5c2m0s"
NEXT_PUBLIC_OPEN_EDITION_NATIVE_BRNCH_FACTORY_ADDRESS="stars1uxdqnu9ysd9q8kd43c52ufy9azfxyuvyt5nnyk4p2gtag30zre3q0cg30z"
NEXT_PUBLIC_OPEN_EDITION_IBC_USK_FACTORY_ADDRESS="stars1vxf9u6a4d5ty00k59zthv7mnpzlrfhqnf4ds0y0eake7lepuamnqymyf3t"
NEXT_PUBLIC_OPEN_EDITION_UPDATABLE_IBC_USK_FACTORY_ADDRESS="stars1njhkyyv0l8dmq528w67t8dxyg5a3h0hvusk6pfvpm52pspd9gq9s3zmdez"
NEXT_PUBLIC_OPEN_EDITION_IBC_KUJI_FACTORY_ADDRESS="stars1yjvfy6fpm4nxl0afm6e8lnx96e6v49e3fxsymsdxxtu0pdeshrxq702zaz"
NEXT_PUBLIC_OPEN_EDITION_IBC_HUAHUA_FACTORY_ADDRESS="stars1grxlqatna07y8f3tzu2l9lmt82uj8gzzshxnz2ruwn6yljpyucnq059rmn"
# NEXT_PUBLIC_OPEN_EDITION_IBC_CRBRUS_FACTORY_ADDRESS=""
NEXT_PUBLIC_OPEN_EDITION_IBC_USDC_FACTORY_ADDRESS="stars1tjzlz2e8pkucgytkjct5drt7x0dysnepqv3nmvxn0fzk2hfv73zsneevyt"
NEXT_PUBLIC_OPEN_EDITION_IBC_NBTC_FACTORY_ADDRESS="stars1cd4gykxfq4nc4yx8uzn8yr3ggu86r57chhxme4y7q2jag53cw75qgs96u8"
NEXT_PUBLIC_OPEN_EDITION_UPDATABLE_IBC_NBTC_FACTORY_ADDRESS="stars1d57xe77mvcg5q337umf4qz49vumfn6w3wss0t7u8ra6s3cyvezsqyaeejn"
NEXT_PUBLIC_VENDING_IBC_NBTC_FACTORY_ADDRESS="stars1e6t6lp052er2gu3rwjnf434vgh59ydkfg8dm589fxlx593afqmuqh75a0s"
NEXT_PUBLIC_VENDING_IBC_NBTC_UPDATABLE_FACTORY_ADDRESS="stars1k6ee8qgwvumguqnqqrvsnwluwk0rp994nkcgdemk0tj3ecc5kk8su2tcr4"
NEXT_PUBLIC_SG721_NAME_ADDRESS="stars1fx74nkqkw2748av8j7ew7r3xt9cgjqduwn8m0ur5lhe49uhlsasszc5fhr"
NEXT_PUBLIC_WHITELIST_CODE_ID=2602
NEXT_PUBLIC_WHITELIST_FLEX_CODE_ID=2603
NEXT_PUBLIC_ROYALTY_REGISTRY_ADDRESS="stars1crgx0f70fzksa57hq87wtl8f04h0qyk5la0hk0fu8dyhl67ju80qaxzr5z"
NEXT_PUBLIC_INFINITY_SWAP_PROTOCOL_ADDRESS="stars136yp6fl9h66m0cwv8weu4w4aawveuz40992ty0atj5ecjd8z0thqv9xpy5"
NEXT_PUBLIC_WHITELIST_CODE_ID=4008
NEXT_PUBLIC_WHITELIST_FLEX_CODE_ID=4009
NEXT_PUBLIC_WHITELIST_MERKLE_TREE_CODE_ID=3911
NEXT_PUBLIC_BADGE_HUB_CODE_ID=1336
NEXT_PUBLIC_BADGE_HUB_ADDRESS="stars1dacun0xn7z73qzdcmq27q3xn6xuprg8e2ugj364784al2v27tklqynhuqa"
NEXT_PUBLIC_BADGE_NFT_CODE_ID=1337
NEXT_PUBLIC_BADGE_NFT_ADDRESS="stars1vlw4y54dyzt3zg7phj8yey9fg4zj49czknssngwmgrnwymyktztstalg7t"
NEXT_PUBLIC_SPLITS_CODE_ID=1905
NEXT_PUBLIC_SPLITS_CODE_ID=4010
NEXT_PUBLIC_CW4_GROUP_CODE_ID=1904
NEXT_PUBLIC_API_URL=https://nft-api.elgafar-1.stargaze-apis.com
@@ -55,3 +122,8 @@ NEXT_PUBLIC_STARGAZE_WEBSITE_URL=https://testnet.publicawesome.dev
NEXT_PUBLIC_BADGES_URL=https://badges.publicawesome.dev
NEXT_PUBLIC_WEBSITE_URL=https://
NEXT_PUBLIC_SYNC_COLLECTIONS_API_URL="https://..."
NEXT_PUBLIC_WHITELIST_MERKLE_TREE_API_URL="https://..."
NEXT_PUBLIC_NFT_STORAGE_DEFAULT_API_KEY="..."
NEXT_PUBLIC_MEILISEARCH_HOST="https://search.publicawesome.dev"
NEXT_PUBLIC_MEILISEARCH_API_KEY= "..."
+45
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
+5 -3
View File
@@ -1,6 +1,5 @@
import { toUtf8 } from '@cosmjs/encoding'
import clsx from 'clsx'
import { useWallet } from 'contexts/wallet'
import React, { useState } from 'react'
import { toast } from 'react-hot-toast'
import { SG721_NAME_ADDRESS } from 'utils/constants'
@@ -8,6 +7,7 @@ import { csvToArray } from 'utils/csvToArray'
import type { AirdropAllocation } from 'utils/isValidAccountsFile'
import { isValidAccountsFile } from 'utils/isValidAccountsFile'
import { isValidAddress } from 'utils/isValidAddress'
import { useWallet } from 'utils/wallet'
interface AirdropUploadProps {
onChange: (data: AirdropAllocation[]) => void
@@ -22,8 +22,10 @@ export const AirdropUpload = ({ onChange }: AirdropUploadProps) => {
await new Promise((resolve) => {
let i = 0
allocationData.map(async (data) => {
if (!wallet.client) throw new Error('Wallet not connected')
await wallet.client
if (!wallet.isWalletConnected) throw new Error('Wallet not connected')
await (
await wallet.getCosmWasmClient()
)
.queryContractRaw(
SG721_NAME_ADDRESS,
toUtf8(
+11
View File
@@ -83,6 +83,17 @@ export const AssetsPreview = ({ assetFilesArray, updateMetadataFileIndex }: Asse
<span className="flex self-center ">{assetSource.name}</span>
</div>
)}
{getAssetType(assetSource.name) === 'document' && (
<div className="flex absolute flex-col items-center mt-4 ml-2">
<img
key={`document-${index}`}
alt="document_icon"
className={clsx('mb-2 ml-1 w-6 h-6 thumbnail')}
src="/pdf.png"
/>
<span className="flex self-center ">{assetSource.name}</span>
</div>
)}
{getAssetType(assetSource.name) === 'video' &&
videoPreviewElements.filter((videoPreviewElement) => videoPreviewElement.key === assetSource.name)}
+5 -3
View File
@@ -5,8 +5,8 @@ import { toUtf8 } from '@cosmjs/encoding'
import clsx from 'clsx'
import React, { useState } from 'react'
import { toast } from 'react-hot-toast'
import { useWallet } from 'utils/wallet'
import { useWallet } from '../contexts/wallet'
import { SG721_NAME_ADDRESS } from '../utils/constants'
import { isValidAddress } from '../utils/isValidAddress'
@@ -22,8 +22,10 @@ export const BadgeAirdropListUpload = ({ onChange }: BadgeAirdropListUploadProps
await new Promise((resolve) => {
let i = 0
names.map(async (name) => {
if (!wallet.client) throw new Error('Wallet not connected')
await wallet.client
if (!wallet.isWalletConnected) throw new Error('Wallet not connected')
await (
await wallet.getCosmWasmClient()
)
.queryContractRaw(
SG721_NAME_ADDRESS,
toUtf8(
+4 -2
View File
@@ -12,7 +12,7 @@ export const BadgeConfirmationModal = (props: BadgeConfirmationModalProps) => {
<input className="modal-toggle" defaultChecked id="my-modal-2" type="checkbox" />
<label className="cursor-pointer modal" htmlFor="my-modal-2">
<label
className="absolute top-[25%] bottom-5 left-1/3 max-w-[600px] max-h-[400px] border-2 no-scrollbar modal-box"
className="absolute top-[23%] bottom-5 left-1/3 max-w-[600px] max-h-[410px] border-2 no-scrollbar modal-box"
htmlFor="temp"
>
{/* <Alert type="warning"></Alert> */}
@@ -23,7 +23,9 @@ export const BadgeConfirmationModal = (props: BadgeConfirmationModalProps) => {
submit, post, promote, or display on or through the Service. You represent and warrant that such contain
material subject to copyright, trademark, publicity rights, or other intellectual property rights, unless
you have necessary permission or are otherwise legally entitled to post the material and to grant Stargaze
Parties the license described above, and that the content does not violate any laws.
Parties the license described above, and that the content does not violate any laws. Stargaze.zone
reserves the right to exercise its discretion in concealing user-generated content, should such content be
determined to have a detrimental impact on the brand.
</div>
<br />
<div className="flex flex-row pb-4">
+57
View File
@@ -0,0 +1,57 @@
/* eslint-disable eslint-comments/disable-enable-pair */
/* eslint-disable @typescript-eslint/no-unnecessary-condition */
/* eslint-disable jsx-a11y/img-redundant-alt */
import { truncateAddress } from 'utils/wallet'
export interface ClickableCollection {
contractAddress: string
name: string
media: string
onClick: () => void
}
export function CollectionsTable({ collections }: { collections: ClickableCollection[] }) {
return (
<table className="w-full divide-y divide-zinc-800 table-fixed">
<thead>
<tr>
<th className="py-3.5 pr-3 pl-4 text-sm text-left sm:pl-0 text-infinity-blue" scope="col">
Name
</th>
<th className="py-3.5 px-3 text-sm text-left text-infinity-blue" scope="col">
Address
</th>
</tr>
</thead>
<tbody className=" bg-black">
{collections
? collections?.map((collection) => (
<tr
key={collection.contractAddress}
className="hover:bg-zinc-900 cursor-pointer"
onClick={collection.onClick}
>
<td className="py-2 pr-3 pl-4 whitespace-nowrap sm:pl-0">
<div className="flex items-center">
<div className="shrink-0 w-11 h-11">
<img alt="Collection Image" src={collection.media} />
</div>
<div className="ml-4 font-medium text-white truncate">{collection.name}</div>
</div>
</td>
<td className="py-5 px-3 text-zinc-400 whitespace-nowrap">
<div className="text-left text-white">
{collection.contractAddress?.startsWith('stars')
? truncateAddress(collection.contractAddress)
: collection.contractAddress}
</div>
</td>
</tr>
))
: null}
</tbody>
</table>
)
}
+4 -2
View File
@@ -12,7 +12,7 @@ export const ConfirmationModal = (props: ConfirmationModalProps) => {
<input className="modal-toggle" defaultChecked id="my-modal-2" type="checkbox" />
<label className="cursor-pointer modal" htmlFor="my-modal-2">
<label
className="absolute top-[25%] bottom-5 left-1/3 max-w-[600px] max-h-[400px] border-2 no-scrollbar modal-box"
className="absolute top-[23%] bottom-5 left-1/3 max-w-[600px] max-h-[440px] border-2 no-scrollbar modal-box"
htmlFor="temp"
>
{/* <Alert type="warning"></Alert> */}
@@ -23,7 +23,9 @@ export const ConfirmationModal = (props: ConfirmationModalProps) => {
submit, post, promote, or display on or through the Service. You represent and warrant that such contain
material subject to copyright, trademark, publicity rights, or other intellectual property rights, unless
you have necessary permission or are otherwise legally entitled to post the material and to grant Stargaze
Parties the license described above, and that the content does not violate any laws.
Parties the license described above, and that the content does not violate any laws. Stargaze.zone
reserves the right to exercise its discretion in concealing user-generated content, should such content be
determined to have a detrimental impact on the brand.
</div>
<br />
<div className="flex flex-row pb-4">
+2 -2
View File
@@ -8,7 +8,7 @@ export function FaviconsMetaTags() {
<link href="/assets/manifest.webmanifest" rel="manifest" />
<meta content="yes" name="mobile-web-app-capable" />
<meta content="#F0827D" name="theme-color" />
<meta content="StargazeStudio" name="application-name" />
<meta content="Stargaze Studio" name="application-name" />
<link href="/assets/apple-touch-icon-57x57.png" rel="apple-touch-icon" sizes="57x57" />
<link href="/assets/apple-touch-icon-60x60.png" rel="apple-touch-icon" sizes="60x60" />
<link href="/assets/apple-touch-icon-72x72.png" rel="apple-touch-icon" sizes="72x72" />
@@ -22,7 +22,7 @@ export function FaviconsMetaTags() {
<link href="/assets/apple-touch-icon-1024x1024.png" rel="apple-touch-icon" sizes="1024x1024" />
<meta content="yes" name="apple-mobile-web-app-capable" />
<meta content="black-translucent" name="apple-mobile-web-app-status-bar-style" />
<meta content="StargazeStudio" name="apple-mobile-web-app-title" />
<meta content="Stargaze Studio" name="apple-mobile-web-app-title" />
<link
href="/assets/apple-touch-startup-image-640x1136.png"
media="(device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
+79
View File
@@ -0,0 +1,79 @@
/* eslint-disable eslint-comments/disable-enable-pair */
/* eslint-disable no-implicit-coercion */
/* eslint-disable import/no-default-export */
/* eslint-disable tsdoc/syntax */
import type { ReactNode } from 'react'
export interface FieldsetBaseType {
/**
* The input's required id, used to link the label and input, as well as the error message.
*/
id: string
/**
* Error message to show input validation.
*/
error?: string
/**
* Success message to show input validation.
*/
success?: string
/**
* Label to describe the input.
*/
label?: string | ReactNode
/**
* Hint to show optional fields or a hint to the user of what to enter in the input.
*/
hint?: string
}
type FieldsetType = FieldsetBaseType & {
children: ReactNode
}
/**
* @name Fieldset
* @description A fieldset component, used to share markup for labels, hints, and errors for Input components.
*
* @example
* <Fieldset error={error} hint={hint} id={id} label={label}>
* <input id={id} {...props} />
* </Fieldset>
*/
export default function Fieldset({ label, hint, id, children, error, success }: FieldsetType) {
return (
<div>
{!!label && (
<div className="flex justify-between mb-1">
<label className="block w-full text-sm font-medium text-zinc-700 dark:text-zinc-300" htmlFor={id}>
{label}
</label>
{typeof hint === 'string' && (
<span className="text-sm text-zinc-500 dark:text-zinc-400" id={`${id}-optional`}>
{hint}
</span>
)}
</div>
)}
{children}
{error && (
<div className="mt-2">
<p className="text-sm text-zinc-600" id={`${id}-error`}>
{error}
</p>
</div>
)}
{success && (
<div className="mt-2">
<p className="text-sm text-zinc-500" id={`${id}-success`}>
{success}
</p>
</div>
)}
</div>
)
}
+173
View File
@@ -0,0 +1,173 @@
/* eslint-disable eslint-comments/disable-enable-pair */
/* eslint-disable import/no-default-export */
/* eslint-disable @typescript-eslint/restrict-template-expressions */
/* eslint-disable jsx-a11y/autocomplete-valid */
/* eslint-disable tsdoc/syntax */
import type { PropsOf } from '@headlessui/react/dist/types'
import type { ReactNode } from 'react'
import { forwardRef } from 'react'
import { classNames } from 'utils/css'
import type { FieldsetBaseType } from './Fieldset'
import Fieldset from './Fieldset'
import type { TrailingSelectProps } from './TrailingSelect'
import TrailingSelect from './TrailingSelect'
/**
* Shared styles for all input components.
*/
export const inputClassNames = {
base: [
'block w-full rounded-lg bg-white shadow-sm dark:bg-zinc-900 sm:text-sm',
'text-white placeholder:text-zinc-500 focus:outline focus:outline-2 focus:outline-offset-2 focus:outline-primary-500 focus:ring-0 focus:ring-offset-0',
],
valid: 'border-zinc-300 focus:border-zinc-300 dark:border-zinc-800 dark:focus:border-zinc-800',
invalid: '!text-red-500 !border-red-500 focus:!border-red-500',
success: 'text-green border-green focus:border-green',
}
type InputProps = Omit<PropsOf<'input'> & FieldsetBaseType, 'className'> & {
directory?: 'true'
mozdirectory?: 'true'
webkitdirectory?: 'true'
leadingAddon?: string
trailingAddon?: string
trailingAddonIcon?: ReactNode
trailingSelectProps?: TrailingSelectProps
autoCompleteOff?: boolean
preventAutoCapitalizeFirstLetter?: boolean
className?: string
icon?: JSX.Element
}
/**
* @name Input
* @description A standard input component, defaults to the text type.
*
* @example
* // Standard input
* <Input id="first-name" name="first-name" />
*
* @example
* // Input component with label, placeholder and type email
* <Input id="email" name="email" type="email" autoComplete="email" label="Email" placeholder="name@email.com" />
*
* @example
* // Input component with label and leading and trailing addons
* <Input
* id="input-label-leading-trailing"
* label="Bid"
* placeholder="0.00"
* leadingAddon="$"
* trailingAddon="USD"
* />
*
* @example
* // Input component with label and trailing select
* const [trailingSelectValue, trailingSelectValueSet] = useState('USD');
*
* <Input
* id="input-label-trailing-select"
* label="Bid"
* placeholder="0.00"
* trailingSelectProps={{
* id: 'currency',
* label: 'Currency',
* value: trailingSelectValue,
* onChange: (event) => trailingSelectValueSet(event.target.value),
* options: ['USD', 'CAD', 'EUR'],
* }}
* />
*/
const Input = forwardRef<HTMLInputElement, InputProps>(
(
{
error,
success,
hint,
label,
leadingAddon,
trailingAddon,
trailingAddonIcon,
trailingSelectProps,
id,
className,
type = 'text',
autoCompleteOff = false,
preventAutoCapitalizeFirstLetter,
icon,
...rest
},
ref,
) => {
const cachedClassNames = classNames(
...inputClassNames.base,
className,
error ? inputClassNames.invalid : inputClassNames.valid,
success ? inputClassNames.success : inputClassNames.valid,
leadingAddon && 'pl-7',
trailingAddon && 'pr-12',
trailingSelectProps && 'pr-16',
icon && 'pl-10',
)
const describedBy = [
...(error ? [`${id}-error`] : []),
...(success ? [`${id}-success`] : []),
...(typeof hint === 'string' ? [`${id}-optional`] : []),
...(typeof trailingAddon === 'string' ? [`${id}-addon`] : []),
].join(' ')
return (
<Fieldset error={error} hint={hint} id={id} label={label} success={success}>
<div className="relative rounded-md shadow-sm">
{leadingAddon && (
<div className="flex absolute inset-y-0 left-0 items-center pl-3 pointer-events-none">
<span className="text-zinc-500 dark:text-zinc-400 sm:text-sm">{leadingAddon}</span>
</div>
)}
{icon && (
<div className="flex absolute inset-y-0 left-0 items-center pl-3 pointer-events-none">
<span className="pr-10 text-zinc-500 dark:text-zinc-400 sm:text-sm">{icon}</span>
</div>
)}
<input
aria-describedby={describedBy}
aria-invalid={error ? 'true' : undefined}
autoCapitalize={`${preventAutoCapitalizeFirstLetter ?? 'off'}`}
autoComplete={`${autoCompleteOff ? 'off' : 'on'}`}
className={cachedClassNames}
id={id}
ref={ref}
type={type}
{...rest}
/>
{!trailingAddon && trailingSelectProps && <TrailingSelect {...trailingSelectProps} />}
{trailingAddon && (
<div className="flex absolute inset-y-0 right-0 items-center pr-3 pointer-events-none">
<span className="text-zinc-500 dark:text-zinc-400 sm:text-sm" id={`${id}-addon`}>
{trailingAddon}
</span>
</div>
)}
{trailingAddonIcon && (
<div className="flex absolute inset-y-0 right-0 items-center pr-3 pointer-events-none">
<span className="text-zinc-500 dark:text-zinc-400 sm:text-sm" id={`${id}-addonicon`}>
{trailingAddonIcon}
</span>
</div>
)}
</div>
</Fieldset>
)
},
)
Input.displayName = 'Input'
export default Input
+1 -1
View File
@@ -66,7 +66,7 @@ export const JsonPreview = ({
</div>
{show && (
<div className="overflow-auto p-2 font-mono text-sm">
<pre>{JSON.stringify(content, null, 2).trim()}</pre>
<pre>{content ? JSON.stringify(content, null, 2).trim() : '{}'}</pre>
</div>
)}
</div>
+2 -2
View File
@@ -47,9 +47,9 @@ export const Layout = ({ children, metadata = {} }: LayoutProps) => {
<FaDesktop size={48} />
<h1 className="text-2xl font-bold">Unsupported Viewport</h1>
<p>
StargazeStudio is best viewed on the big screen.
Stargaze Studio is best viewed on the big screen.
<br />
Please open StargazeStudio on your tablet or desktop browser.
Please open Stargaze Studio on your tablet or desktop browser.
</p>
</div>
</div>
+6 -1
View File
@@ -1,5 +1,6 @@
import clsx from 'clsx'
import { Anchor } from 'components/Anchor'
import { useRouter } from 'next/router'
export interface LinkTabProps {
title: string
@@ -11,6 +12,10 @@ export interface LinkTabProps {
export const LinkTab = (props: LinkTabProps) => {
const { title, description, href, isActive } = props
// get contract address from the router
const router = useRouter()
const { contractAddress } = router.query
return (
<Anchor
className={clsx(
@@ -19,7 +24,7 @@ export const LinkTab = (props: LinkTabProps) => {
isActive ? 'border-plumbus' : 'border-transparent',
isActive ? 'bg-plumbus/5 hover:bg-plumbus/10' : 'hover:bg-white/5',
)}
href={href}
href={href + (contractAddress ? `?contractAddress=${contractAddress as string}` : '')}
>
<h4 className="font-bold">{title}</h4>
<span className="text-sm text-white/80 line-clamp-2">{description}</span>
+39
View File
@@ -145,3 +145,42 @@ export const splitsLinkTabs: LinkTabProps[] = [
href: '/contracts/splits/migrate',
},
]
export const royaltyRegistryLinkTabs: LinkTabProps[] = [
{
title: 'Query',
description: `Dispatch queries for your Royalty Registry contract`,
href: '/contracts/royaltyRegistry/query',
},
{
title: 'Execute',
description: `Execute Royalty Registry contract actions`,
href: '/contracts/royaltyRegistry/execute',
},
]
export const authzLinkTabs: LinkTabProps[] = [
{
title: 'Grant',
description: `Grant authorizations to a given address`,
href: '/authz/grant',
},
{
title: 'Revoke',
description: `Revoke already granted authorizations`,
href: '/authz/revoke',
},
]
export const snapshotLinkTabs: LinkTabProps[] = [
{
title: 'Collection Holders',
description: `Take a snapshot of collection holders`,
href: '/snapshots/holders',
},
{
title: 'Chain Snapshots',
description: `Export a list of users fulfilling a given condition`,
href: '/snapshots/chain',
},
]
+17 -1
View File
@@ -1,5 +1,6 @@
/* eslint-disable eslint-comments/disable-enable-pair */
/* eslint-disable jsx-a11y/media-has-caption */
import clsx from 'clsx'
import type { ReactNode } from 'react'
import { useEffect, useMemo, useState } from 'react'
import { getAssetType } from 'utils/getAssetType'
@@ -41,6 +42,16 @@ export const MetadataFormGroup = (props: MetadataFormGroupProps) => {
[relatedAsset],
)
const documentPreview = useMemo(
() => (
<div className="flex flex-col items-center mt-4 ml-2">
<img key="document-key" alt="document_icon" className={clsx('mb-2 ml-2 w-24 h-24 thumbnail')} src="/pdf.png" />
<span className="flex self-center ">{relatedAsset?.name}</span>
</div>
),
[relatedAsset],
)
useEffect(() => {
if (getAssetType(relatedAsset?.name as string) !== 'html') return
const reader = new FileReader()
@@ -60,9 +71,14 @@ export const MetadataFormGroup = (props: MetadataFormGroupProps) => {
{subtitle && <span className="text-sm text-white/50">{subtitle}</span>}
<div>
{relatedAsset && (
<div className="flex flex-row items-center mt-2 mr-4 border-2 border-dashed">
<div
className={`flex flex-row items-center mt-2 mr-4 ${
getAssetType(relatedAsset.name) === 'document' ? '' : `border-2 border-dashed`
}`}
>
{getAssetType(relatedAsset.name) === 'audio' && audioPreview}
{getAssetType(relatedAsset.name) === 'video' && videoPreview}
{getAssetType(relatedAsset.name) === 'document' && documentPreview}
{getAssetType(relatedAsset.name) === 'image' && (
<img alt="preview" src={URL.createObjectURL(relatedAsset)} />
)}
+47 -8
View File
@@ -13,12 +13,16 @@ export interface MetadataInputProps {
selectedAssetFile: File
selectedMetadataFile: File
updateMetadataToUpload: (metadataFile: File) => void
onChange?: (metadata: any) => void
importedMetadata?: any
}
export const MetadataInput = (props: MetadataInputProps) => {
const emptyMetadataFile = new File(
[JSON.stringify({})],
`${props.selectedAssetFile?.name.substring(0, props.selectedAssetFile?.name.lastIndexOf('.'))}.json`,
`${props.selectedAssetFile?.name
.substring(0, props.selectedAssetFile?.name.lastIndexOf('.'))
.replaceAll('#', '')}.json`,
{ type: 'application/json' },
)
@@ -127,7 +131,7 @@ export const MetadataInput = (props: MetadataInputProps) => {
if (nameState.value === '') delete metadata.name
else metadata.name = nameState.value
if (descriptionState.value === '') delete metadata.description
else metadata.description = descriptionState.value
else metadata.description = descriptionState.value.replaceAll('\\n', '\n')
if (externalUrlState.value === '') delete metadata.external_url
else metadata.external_url = externalUrlState.value
if (youtubeUrlState.value === '') delete metadata.youtube_url
@@ -140,8 +144,10 @@ export const MetadataInput = (props: MetadataInputProps) => {
const editedMetadataFile = new File(
[metadataFileBlob],
props.selectedMetadataFile?.name
? props.selectedMetadataFile?.name
: `${props.selectedAssetFile?.name.substring(0, props.selectedAssetFile?.name.lastIndexOf('.'))}.json`,
? props.selectedMetadataFile?.name.replaceAll('#', '')
: `${props.selectedAssetFile?.name
.substring(0, props.selectedAssetFile?.name.lastIndexOf('.'))
.replaceAll('#', '')}.json`,
{ type: 'application/json' },
)
props.updateMetadataToUpload(editedMetadataFile)
@@ -151,9 +157,12 @@ export const MetadataInput = (props: MetadataInputProps) => {
useEffect(() => {
console.log(props.selectedMetadataFile?.name)
if (props.selectedMetadataFile) void parseMetadata(props.selectedMetadataFile)
else void parseMetadata(emptyMetadataFile)
}, [props.selectedMetadataFile?.name])
if (props.selectedMetadataFile) {
void parseMetadata(props.selectedMetadataFile)
} else if (!props.importedMetadata) {
void parseMetadata(emptyMetadataFile)
}
}, [props.selectedMetadataFile?.name, props.importedMetadata])
const nameStateMemo = useMemo(() => nameState, [nameState.value])
const descriptionStateMemo = useMemo(() => descriptionState, [descriptionState.value])
@@ -163,7 +172,10 @@ export const MetadataInput = (props: MetadataInputProps) => {
useEffect(() => {
console.log('Update metadata')
if (metadata) generateUpdatedMetadata()
if (metadata) {
generateUpdatedMetadata()
if (props.onChange) props.onChange(metadata)
}
console.log(metadata)
}, [
nameStateMemo.value,
@@ -173,6 +185,33 @@ export const MetadataInput = (props: MetadataInputProps) => {
attributesStateMemo.entries,
])
useEffect(() => {
if (props.importedMetadata) {
void parseMetadata(emptyMetadataFile).then(() => {
console.log('Imported metadata: ', props.importedMetadata)
nameState.onChange(props.importedMetadata.name || '')
descriptionState.onChange(props.importedMetadata.description || '')
externalUrlState.onChange(props.importedMetadata.external_url || '')
youtubeUrlState.onChange(props.importedMetadata.youtube_url || '')
if (props.importedMetadata?.attributes && props.importedMetadata?.attributes?.length > 0) {
attributesState.reset()
props.importedMetadata?.attributes?.forEach((attribute: { trait_type: string; value: string }) => {
attributesState.add({
trait_type: attribute.trait_type,
value: attribute.value,
})
})
} else {
attributesState.reset()
attributesState.add({
trait_type: '',
value: '',
})
}
})
}
}, [props.importedMetadata])
return (
<div>
<div className="grid grid-cols-2 mt-4 mr-4 ml-8 w-full max-w-6xl max-h-full no-scrollbar">
+3 -1
View File
@@ -127,7 +127,9 @@ export const MetadataModal = (props: MetadataModalProps) => {
type: 'application/json',
})
const editedMetadataFile = new File([metadataFileBlob], metadataFile.name, { type: 'application/json' })
const editedMetadataFile = new File([metadataFileBlob], metadataFile.name.replaceAll('#', ''), {
type: 'application/json',
})
props.updateMetadata(editedMetadataFile)
toast.success('Metadata updated successfully.')
}
+63
View File
@@ -0,0 +1,63 @@
import { MagnifyingGlassIcon } from '@heroicons/react/24/outline'
import Input from 'components/Input'
import useSearch from 'hooks/useSearch'
import { useMemo, useState } from 'react'
import { useDebounce } from 'utils/debounce'
import { CollectionsTable } from './CollectionsTable'
export function SelectCollection({ selectCollection }: { selectCollection: (collectionAddress: string) => void }) {
const [search, setSearch] = useState('')
const [isInputFocused, setInputFocus] = useState(false)
const debouncedQuery = useDebounce<string>(search, 200)
const debouncedIsInputFocused = useDebounce<boolean>(isInputFocused, 200)
const collectionsQuery = useSearch(debouncedQuery, ['collections'], 5)
const collectionsResults = useMemo(() => {
return collectionsQuery.data?.find((searchResult) => searchResult.indexUid === 'collections')
}, [collectionsQuery.data])
const clickableCollections = useMemo(() => {
return (
collectionsResults?.hits.map((hit) => ({
contractAddress: hit.id,
name: hit.name,
media: hit.thumbnail_url || hit.image_url,
onClick: () => {
selectCollection(hit.id)
setSearch(hit.name)
},
})) ?? []
)
}, [collectionsResults, selectCollection, setSearch])
const handleInputFocus = () => {
setInputFocus(true)
}
const handleInputBlur = () => {
setInputFocus(false)
}
return (
<div className="flex flex-col p-4 space-y-4 w-3/4 h-full bg-black rounded-md border-2 border-gray-600 border-solid md:p-6">
<p className="text-base font-bold text-white text-start">Select the NFT collection to take a snapshot for</p>
<Input
className="py-2 w-full text-black dark:text-white rounded-sm md:w-72"
icon={<MagnifyingGlassIcon className="w-5 h-5 text-zinc-400" />}
id="collection-search"
onBlur={handleInputBlur}
onChange={(e) => setSearch(e.target.value)}
onFocus={handleInputFocus}
placeholder="Search Collections..."
value={search}
/>
{debouncedIsInputFocused && (
<div className="overflow-auto w-full">
<CollectionsTable collections={clickableCollections} />
</div>
)}
</div>
)
}
+74
View File
@@ -0,0 +1,74 @@
import type { Timezone } from 'contexts/globalSettings'
import { useGlobalSettings } from 'contexts/globalSettings'
import { useRef, useState } from 'react'
import { setTimezone } from '../contexts/globalSettings'
import { Button } from './Button'
export interface SettingsModalProps {
timezone?: Timezone
}
export const SettingsModal = (props: SettingsModalProps) => {
const globalSettings = useGlobalSettings()
const [isChecked, setIsChecked] = useState(false)
const checkBoxRef = useRef<HTMLInputElement>(null)
return (
<div>
<input className="modal-toggle" defaultChecked={false} id="my-modal-9" ref={checkBoxRef} type="checkbox" />
<label className="cursor-pointer modal" htmlFor="my-modal-9">
<label
className={`absolute top-[42%] bottom-5 left-[260px] max-w-[450px] max-h-[250px]
border-[1px] no-scrollbar modal-box`}
htmlFor="temp"
>
<div className="flex flex-col justify-between h-full">
<div className="flex flex-col">
<h1 className="text-2xl font-bold underline underline-offset-2">Settings</h1>
<div className="flex justify-start w-full">
<div className="flex-row mt-2 w-full form-control">
<h1 className="mt-[5px] text-lg font-bold">Time & Date: </h1>
<label className="justify-start ml-6 cursor-pointer label">
<span className="mr-2 font-bold">Local</span>
<input
checked={globalSettings.timezone === 'Local'}
className={`${globalSettings.timezone === 'Local' ? `bg-stargaze` : `bg-gray-600`} checkbox`}
onClick={() => {
setTimezone('Local' as Timezone)
window.localStorage.setItem('timezone', 'Local')
}}
type="checkbox"
/>
</label>
<label className="justify-start ml-4 cursor-pointer label">
<span className="mr-2 font-bold">UTC</span>
<input
checked={globalSettings.timezone === 'UTC'}
className={`${globalSettings.timezone === 'UTC' ? `bg-stargaze` : `bg-gray-600`} checkbox`}
onClick={() => {
setTimezone('UTC' as Timezone)
window.localStorage.setItem('timezone', 'UTC')
}}
type="checkbox"
/>
</label>
</div>
</div>
</div>
<Button
className="w-[40%] max-h-12 bg-blue-500 hover:bg-blue-600"
isWide
onClick={() => {
setTimezone('UTC' as Timezone)
window.localStorage.setItem('timezone', 'UTC')
}}
>
Use Defaults
</Button>
</div>
</label>
</label>
</div>
)
}
+129 -20
View File
@@ -3,18 +3,22 @@
import clsx from 'clsx'
import { Anchor } from 'components/Anchor'
import type { Timezone } from 'contexts/globalSettings'
import { setTimezone } from 'contexts/globalSettings'
import { setLogItemList, useLogStore } from 'contexts/log'
import { useWallet } from 'contexts/wallet'
import Link from 'next/link'
import { useRouter } from 'next/router'
import { useEffect } from 'react'
import { useEffect, useState } from 'react'
import { FaCog } from 'react-icons/fa'
// import BrandText from 'public/brand/brand-text.svg'
import { footerLinks, socialsLinks } from 'utils/links'
import { useWallet } from 'utils/wallet'
import { BADGE_HUB_ADDRESS, BASE_FACTORY_ADDRESS, NETWORK, OPEN_EDITION_FACTORY_ADDRESS } from '../utils/constants'
import { Conditional } from './Conditional'
import { IncomeDashboardDisclaimer } from './IncomeDashboardDisclaimer'
import { LogModal } from './LogModal'
import { SettingsModal } from './SettingsModal'
import { SidebarLayout } from './SidebarLayout'
import { WalletLoader } from './WalletLoader'
@@ -22,6 +26,7 @@ export const Sidebar = () => {
const router = useRouter()
const wallet = useWallet()
const logs = useLogStore()
const [isTallWindow, setIsTallWindow] = useState(false)
useEffect(() => {
if (logs.itemList.length === 0) return
@@ -32,6 +37,23 @@ export const Sidebar = () => {
useEffect(() => {
console.log(window.localStorage.getItem('logs'))
setLogItemList(JSON.parse(window.localStorage.getItem('logs') || '[]'))
setTimezone(
(window.localStorage.getItem('timezone') as Timezone)
? (window.localStorage.getItem('timezone') as Timezone)
: 'UTC',
)
}, [])
const handleResize = () => {
setIsTallWindow(window.innerHeight > 768)
}
useEffect(() => {
handleResize()
window.addEventListener('resize', handleResize)
// return () => {
// window.removeEventListener('resize', handleResize)
// }
}, [])
return (
@@ -44,8 +66,8 @@ export const Sidebar = () => {
<WalletLoader />
{/* main navigation routes */}
<div className="absolute top-[20%] left-[5%] mt-2">
<ul className="group p-2 w-full bg-transparent menu rounded-box">
<div className={clsx('absolute left-[5%] mt-2', isTallWindow ? 'top-[20%]' : 'top-[30%]')}>
<ul className="group py-1 px-2 w-full bg-transparent menu rounded-box">
<li tabIndex={0}>
<div
className={clsx(
@@ -86,6 +108,15 @@ export const Sidebar = () => {
>
<Link href="/collections/actions/">Collection Actions</Link>
</li>
<li
className={clsx(
'text-lg font-bold hover:text-white hover:bg-stargaze-80 rounded',
router.asPath.includes('/snapshots') ? 'text-white' : 'text-gray',
)}
tabIndex={-1}
>
<Link href="/snapshots">Snapshots</Link>
</li>
<Conditional test={NETWORK === 'mainnet'}>
<li className={clsx('text-lg font-bold hover:text-white hover:bg-stargaze-80 rounded')} tabIndex={-1}>
<label
@@ -100,7 +131,7 @@ export const Sidebar = () => {
</li>
</ul>
<Conditional test={BADGE_HUB_ADDRESS !== undefined}>
<ul className="group p-2 w-full bg-transparent menu rounded-box">
<ul className="group py-1 px-2 w-full bg-transparent menu rounded-box">
<li tabIndex={0}>
<span
className={clsx(
@@ -144,6 +175,40 @@ export const Sidebar = () => {
</ul>
</Conditional>
<ul className="group p-2 w-full bg-transparent menu rounded-box">
<li tabIndex={0}>
<span
className={clsx(
'z-40 text-xl font-bold group-hover:text-white bg-transparent rounded-lg small-caps',
'hover:bg-white/5 transition-colors',
router.asPath.includes('/tokenfactory') ? 'text-white' : 'text-gray',
)}
>
<Link href="/tokenfactory/">Tokens</Link>
</span>
<ul className="z-50 p-2 rounded-box bg-base-200">
<li
className={clsx(
'text-lg font-bold hover:text-white hover:bg-stargaze-80 rounded',
router.asPath.includes('/tokenfactory/') ? 'text-white' : 'text-gray',
)}
tabIndex={-1}
>
<Link href="/tokenfactory/">Token Factory</Link>
</li>
<li
className={clsx(
'disabled',
'text-lg font-bold hover:text-white',
router.asPath.includes('/airdrop-tokens/') ? 'text-white' : 'text-gray',
)}
tabIndex={-1}
>
<Link href="/">Airdrop Tokens</Link>
</li>
</ul>
</li>
</ul>
<ul className="group py-1 px-2 w-full bg-transparent menu rounded-box">
<li tabIndex={0}>
<span
className={clsx(
@@ -224,35 +289,78 @@ export const Sidebar = () => {
>
<Link href="/contracts/splits/">Splits Contract</Link>
</li>
<li
className={clsx(
'text-lg font-bold hover:text-white hover:bg-stargaze-80 rounded',
router.asPath.includes('/contracts/royaltyRegistry/') ? 'text-white' : 'text-gray',
)}
tabIndex={-1}
>
<Link href="/contracts/royaltyRegistry/">Royalty Registry</Link>
</li>
<li
className={clsx(
'text-lg font-bold hover:text-white hover:bg-stargaze-80 rounded',
router.asPath.includes('/contracts/upload/') ? 'text-white' : 'text-gray',
)}
tabIndex={-1}
>
<Link href="/contracts/upload/">Upload Contract</Link>
</li>
</ul>
</li>
</ul>
<ul className="group py-1 px-2 w-full bg-transparent menu rounded-box">
<li tabIndex={0}>
<span
className={clsx(
'z-40 text-xl font-bold group-hover:text-white bg-transparent rounded-lg small-caps',
'hover:bg-white/5 transition-colors',
router.asPath.includes('/authz/') ? 'text-white' : 'text-gray',
)}
>
<Link href="/authz/"> Authz </Link>
</span>
</li>
</ul>
</div>
<IncomeDashboardDisclaimer creatorAddress={wallet.address ? wallet.address : ''} />
<LogModal />
<SettingsModal />
<div className="flex-grow" />
{logs.itemList.length > 0 && (
<label
className="w-[65%] h-[4px] text-lg font-bold text-white normal-case bg-blue-500 hover:bg-blue-600 border-none animate-none btn modal-button"
htmlFor="my-modal-8"
>
View Logs
</label>
{isTallWindow && (
<div className="flex-row w-full h-full">
<label
className="absolute mb-8 w-[25%] text-lg font-bold text-white normal-case bg-zinc-500 hover:bg-zinc-600 border-none animate-none btn modal-button"
htmlFor="my-modal-9"
>
<FaCog className="justify-center align-bottom" size={20} />
</label>
<label
className="ml-16 w-[65%] text-lg font-bold text-white normal-case bg-blue-500 hover:bg-blue-600 border-none animate-none btn modal-button"
htmlFor="my-modal-8"
>
View Logs
</label>
</div>
)}
{/* Stargaze network status */}
<div className="text-sm capitalize">Network: {wallet.network}</div>
{isTallWindow && <div className="text-sm capitalize">Network: {wallet.chain.pretty_name}</div>}
{/* footer reference links */}
<ul className="text-sm list-disc list-inside">
{footerLinks.map(({ href, text }) => (
<li key={href}>
<Anchor className="hover:text-plumbus hover:underline" href={href}>
{text}
</Anchor>
</li>
))}
{isTallWindow &&
footerLinks.map(({ href, text }) => (
<li key={href}>
<Anchor className="hover:text-plumbus hover:underline" href={href}>
{text}
</Anchor>
</li>
))}
</ul>
{/* footer attribution */}
@@ -265,6 +373,7 @@ export const Sidebar = () => {
</div>
{/* footer social links */}
<div className="flex gap-x-6 items-center text-white/75">
{socialsLinks.map(({ Icon, href, text }) => (
<Anchor key={href} className="hover:text-plumbus" href={href}>
+17 -1
View File
@@ -1,5 +1,6 @@
/* eslint-disable eslint-comments/disable-enable-pair */
/* eslint-disable jsx-a11y/media-has-caption */
import clsx from 'clsx'
import type { ReactNode } from 'react'
import { useEffect, useMemo, useState } from 'react'
import { getAssetType } from 'utils/getAssetType'
@@ -41,6 +42,16 @@ export const SingleAssetPreview = (props: SingleAssetPreviewProps) => {
[relatedAsset],
)
const documentPreview = useMemo(
() => (
<div className="flex flex-col items-center mt-4 ml-2">
<img key="document-key" alt="document_icon" className={clsx('mb-2 ml-1 w-20 h-20 thumbnail')} src="/pdf.png" />
<span className="flex self-center ">{relatedAsset?.name}</span>
</div>
),
[relatedAsset],
)
useEffect(() => {
if (getAssetType(relatedAsset?.name as string) !== 'html') return
const reader = new FileReader()
@@ -59,9 +70,14 @@ export const SingleAssetPreview = (props: SingleAssetPreviewProps) => {
<div>
{/* {subtitle && <span className="text-sm text-white/50">{subtitle}</span>} */}
{relatedAsset && (
<div className="flex flex-row items-center mt-2 mr-4 border-2 border-dashed">
<div
className={`flex flex-row items-center mt-2 mr-4 ${
getAssetType(relatedAsset.name) === 'document' ? '' : `border-2 border-dashed`
}`}
>
{getAssetType(relatedAsset.name) === 'audio' && audioPreview}
{getAssetType(relatedAsset.name) === 'video' && videoPreview}
{getAssetType(relatedAsset.name) === 'document' && documentPreview}
{getAssetType(relatedAsset.name) === 'image' && (
<img alt="preview" src={URL.createObjectURL(relatedAsset)} />
)}
+37
View File
@@ -0,0 +1,37 @@
/* eslint-disable eslint-comments/disable-enable-pair */
/* eslint-disable import/no-default-export */
import type { ChangeEvent } from 'react'
import { classNames } from 'utils/css'
export interface TrailingSelectProps {
id: string
label: string
options: string[]
value: string
onChange: (event: ChangeEvent<HTMLSelectElement>) => void
}
export default function TrailingSelect({ id, label, value, onChange, options }: TrailingSelectProps) {
const cachedClassNames = classNames(
'h-full rounded-md border-transparent bg-transparent py-0 pl-2 pr-7 text-zinc-500 dark:text-zinc-400 sm:text-sm',
'focus:border-transparent focus:outline focus:outline-2 focus:outline-offset-2 focus:outline-primary-500 focus:ring-0 focus:ring-offset-0',
)
return (
<div className="flex absolute inset-y-0 right-0 items-center">
<label className="sr-only" htmlFor={id}>
{label}
</label>
<select className={cachedClassNames} id={id} name={id} onChange={onChange} value={value}>
{/* TODO - Option values in a select are supposed to be unique, remove this comment during PR review */}
{options.map((opt) => (
<option key={opt} value={opt}>
{opt}
</option>
))}
</select>
</div>
)
}
+44 -14
View File
@@ -1,36 +1,63 @@
import type { Coin } from '@cosmjs/proto-signing'
import { Popover, Transition } from '@headlessui/react'
import clsx from 'clsx'
import { useWallet, useWalletStore } from 'contexts/wallet'
import { Fragment } from 'react'
import { tokensList } from 'config/token'
import { Fragment, useEffect, useState } from 'react'
import { FaCopy, FaPowerOff, FaRedo } from 'react-icons/fa'
import { copy } from 'utils/clipboard'
import { convertDenomToReadable } from 'utils/convertDenomToReadable'
import { getShortAddress } from 'utils/getShortAddress'
import { truncateMiddle } from 'utils/text'
import { useWallet } from 'utils/wallet'
import { WalletButton } from './WalletButton'
import { WalletPanelButton } from './WalletPanelButton'
export const WalletLoader = () => {
const { address, balance, connect, disconnect, initializing: isLoading, initialized: isReady } = useWallet()
const {
address = '',
username,
connect,
disconnect,
isWalletConnecting,
isWalletConnected,
getStargateClient,
} = useWallet()
const displayName = useWalletStore((store) => store.name || getShortAddress(store.address))
// Once wallet connects, load balances.
const [balances, setBalances] = useState<readonly Coin[] | undefined>()
useEffect(() => {
if (!isWalletConnected) {
setBalances(undefined)
return
}
const loadBalances = async () => {
const client = await getStargateClient()
setBalances(await client.getAllBalances(address))
}
loadBalances().catch(console.error)
}, [isWalletConnected, getStargateClient, address])
return (
<Popover className="mt-4 mb-2">
{({ close }) => (
<>
<div className="grid -mx-4">
{!isReady && (
<WalletButton className="w-full" isLoading={isLoading} onClick={() => void connect()}>
{isWalletConnected ? (
<Popover.Button as={WalletButton} className="w-full">
{username || address}
</Popover.Button>
) : (
<WalletButton
className="w-full"
isLoading={isWalletConnecting}
onClick={() => void connect().catch(console.error)}
>
Connect Wallet
</WalletButton>
)}
{isReady && (
<Popover.Button as={WalletButton} className="w-full" isLoading={isLoading}>
{displayName}
</Popover.Button>
)}
</div>
<Transition
@@ -54,9 +81,12 @@ export const WalletLoader = () => {
{getShortAddress(address)}
</span>
<div className="font-bold">Your Balances</div>
{balance.map((val) => (
{balances?.map((val) => (
<span key={`balance-${val.denom}`}>
{convertDenomToReadable(val.amount)} {val.denom.slice(1, val.denom.length)}
{convertDenomToReadable(val.amount)}{' '}
{tokensList.find((t) => t.denom === val.denom)?.displayName
? tokensList.find((t) => t.denom === val.denom)?.displayName
: truncateMiddle(val.denom ? val.denom : '', 28)}
</span>
))}
</div>
+48
View File
@@ -0,0 +1,48 @@
// Styles required for @cosmos-kit/react modal
import '@interchain-ui/react/styles'
import { GasPrice } from '@cosmjs/stargate'
import { wallets as keplrExtensionWallets } from '@cosmos-kit/keplr-extension'
import { wallets as leapExtensionWallets } from '@cosmos-kit/leap-extension'
import { ChainProvider } from '@cosmos-kit/react'
import { assets, chains } from 'chain-registry'
import { getConfig } from 'config'
import type { ReactNode } from 'react'
import { NETWORK } from 'utils/constants'
export const WalletProvider = ({ children }: { children: ReactNode }) => {
const { gasPrice, feeToken } = getConfig(NETWORK)
return (
<ChainProvider
assetLists={assets}
chains={chains}
endpointOptions={{
endpoints: {
stargaze: {
rpc: ['https://rpc.stargaze-apis.com/'],
rest: ['https://rest.stargaze-apis.com/'],
},
stargazetestnet: {
rpc: ['https://rpc.elgafar-1.stargaze-apis.com/'],
rest: ['https://rest.elgafar-1.stargaze-apis.com/'],
},
},
isLazy: true,
}}
sessionOptions={{
duration: 1000 * 60 * 60 * 12, // 12 hours
}}
signerOptions={{
signingCosmwasm: () => ({
gasPrice: GasPrice.fromString(`${gasPrice}${feeToken}`),
}),
signingStargate: () => ({
gasPrice: GasPrice.fromString(`${gasPrice}${feeToken}`),
}),
}}
wallets={[...keplrExtensionWallets, ...leapExtensionWallets]}
>
{children}
</ChainProvider>
)
}
+5 -3
View File
@@ -1,12 +1,12 @@
import { toUtf8 } from '@cosmjs/encoding'
import clsx from 'clsx'
import { useWallet } from 'contexts/wallet'
import React, { useState } from 'react'
import { toast } from 'react-hot-toast'
import { SG721_NAME_ADDRESS } from 'utils/constants'
import { csvToFlexList } from 'utils/csvToFlexList'
import { isValidAddress } from 'utils/isValidAddress'
import { isValidFlexListFile } from 'utils/isValidFlexListFile'
import { useWallet } from 'utils/wallet'
export interface WhitelistFlexMember {
address: string
@@ -26,8 +26,10 @@ export const WhitelistFlexUpload = ({ onChange }: WhitelistFlexUploadProps) => {
await new Promise((resolve) => {
let i = 0
memberData.map(async (data) => {
if (!wallet.client) throw new Error('Wallet not connected')
await wallet.client
if (!wallet.isWalletConnected) throw new Error('Wallet not connected')
await (
await wallet.getCosmWasmClient()
)
.queryContractRaw(
SG721_NAME_ADDRESS,
toUtf8(
+6 -4
View File
@@ -5,8 +5,8 @@ import { toUtf8 } from '@cosmjs/encoding'
import clsx from 'clsx'
import React, { useState } from 'react'
import { toast } from 'react-hot-toast'
import { useWallet } from 'utils/wallet'
import { useWallet } from '../contexts/wallet'
import { SG721_NAME_ADDRESS } from '../utils/constants'
import { isValidAddress } from '../utils/isValidAddress'
@@ -22,8 +22,10 @@ export const WhitelistUpload = ({ onChange }: WhitelistUploadProps) => {
await new Promise((resolve) => {
let i = 0
names.map(async (name) => {
if (!wallet.client) throw new Error('Wallet not connected')
await wallet.client
if (!wallet.isWalletConnected) throw new Error('Wallet not connected')
await (
await wallet.getCosmWasmClient()
)
.queryContractRaw(
SG721_NAME_ADDRESS,
toUtf8(
@@ -76,7 +78,7 @@ export const WhitelistUpload = ({ onChange }: WhitelistUploadProps) => {
const printableData = data?.map((item) => item.replace(regex, ''))
const names = printableData?.filter((address) => address !== '' && address.endsWith('.stars'))
const strippedNames = names?.map((name) => name.split('.')[0])
console.log(names)
console.log('names: ', names)
if (strippedNames?.length) {
await toast
.promise(resolveAddresses(strippedNames), {
+10 -6
View File
@@ -20,7 +20,6 @@ import { useMetadataAttributesState } from 'components/forms/MetadataAttributes.
import { JsonPreview } from 'components/JsonPreview'
import { TransactionHash } from 'components/TransactionHash'
import { WhitelistUpload } from 'components/WhitelistUpload'
import { useWallet } from 'contexts/wallet'
import type { Badge, BadgeHubInstance } from 'contracts/badgeHub'
import sizeof from 'object-sizeof'
import type { FormEvent } from 'react'
@@ -32,6 +31,7 @@ import * as secp256k1 from 'secp256k1'
import { generateKeyPairs, sha256 } from 'utils/hash'
import { isValidAddress } from 'utils/isValidAddress'
import { resolveAddress } from 'utils/resolveAddress'
import { useWallet } from 'utils/wallet'
import { BadgeAirdropListUpload } from '../../BadgeAirdropListUpload'
import { AddressInput, NumberInput, TextInput } from '../../forms/FormInput'
@@ -266,7 +266,7 @@ export const BadgeActions = ({ badgeHubContractAddress, badgeId, badgeHubMessage
nft: nftState.value,
badgeHubMessages,
badgeHubContract: badgeHubContractAddress,
txSigner: wallet.address,
txSigner: wallet.address || '',
type,
}
@@ -397,7 +397,7 @@ export const BadgeActions = ({ badgeHubContractAddress, badgeId, badgeHubMessage
const { isLoading, mutate } = useMutation(
async (event: FormEvent) => {
if (!wallet.client) {
if (!wallet.isWalletConnected) {
throw new Error('Please connect your wallet.')
}
event.preventDefault()
@@ -412,8 +412,10 @@ export const BadgeActions = ({ badgeHubContractAddress, badgeId, badgeHubMessage
throw new Error('Please enter a valid private key.')
}
if (wallet.client && type === 'edit_badge') {
const feeRateRaw = await wallet.client.queryContractRaw(
if (type === 'edit_badge') {
const feeRateRaw = await (
await wallet.getCosmWasmClient()
).queryContractRaw(
badgeHubContractAddress,
toUtf8(Buffer.from(Buffer.from('fee_rate').toString('hex'), 'hex').toString()),
)
@@ -421,7 +423,9 @@ export const BadgeActions = ({ badgeHubContractAddress, badgeId, badgeHubMessage
await toast
.promise(
wallet.client.queryContractSmart(badgeHubContractAddress, {
(
await wallet.getCosmWasmClient()
).queryContractSmart(badgeHubContractAddress, {
badge: { id: badgeId },
}),
{
+43 -10
View File
@@ -1,4 +1,5 @@
/* eslint-disable eslint-comments/disable-enable-pair */
/* eslint-disable no-nested-ternary */
/* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
@@ -10,12 +11,13 @@ import { FormControl } from 'components/FormControl'
import { useInputState, useNumberInputState } from 'components/forms/FormInput.hooks'
import { useMetadataAttributesState } from 'components/forms/MetadataAttributes.hooks'
import { InputDateTime } from 'components/InputDateTime'
import { useWallet } from 'contexts/wallet'
import { useGlobalSettings } from 'contexts/globalSettings'
import type { Trait } from 'contracts/badgeHub'
import type { ChangeEvent } from 'react'
import { useEffect, useRef, useState } from 'react'
import { toast } from 'react-hot-toast'
import { BADGE_HUB_ADDRESS } from 'utils/constants'
import { useWallet } from 'utils/wallet'
import { AddressInput, NumberInput, TextInput } from '../../forms/FormInput'
import { MetadataAttributes } from '../../forms/MetadataAttributes'
@@ -44,8 +46,9 @@ export interface BadgeDetailsDataProps {
youtube_url?: string
}
export const BadgeDetails = ({ metadataSize, onChange }: BadgeDetailsProps) => {
const wallet = useWallet()
export const BadgeDetails = ({ metadataSize, onChange, uploadMethod }: BadgeDetailsProps) => {
const { address = '', isWalletConnected, getCosmWasmClient } = useWallet()
const { timezone } = useGlobalSettings()
const [timestamp, setTimestamp] = useState<Date | undefined>(undefined)
const [transferrable, setTransferrable] = useState<boolean>(false)
const [metadataFile, setMetadataFile] = useState<File>()
@@ -58,7 +61,7 @@ export const BadgeDetails = ({ metadataSize, onChange }: BadgeDetailsProps) => {
name: 'manager',
title: 'Manager',
subtitle: 'Badge Hub Manager',
defaultValue: wallet.address ? wallet.address : '',
defaultValue: address,
})
const nameState = useInputState({
@@ -172,7 +175,9 @@ export const BadgeDetails = ({ metadataSize, onChange }: BadgeDetailsProps) => {
reader.onload = (e) => {
if (!event.target.files) return toast.error('No file selected.')
if (!e.target?.result) return toast.error('Error parsing file.')
selectedFile = new File([e.target.result], event.target.files[0].name, { type: 'application/json' })
selectedFile = new File([e.target.result], event.target.files[0].name.replaceAll('#', ''), {
type: 'application/json',
})
}
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (event.target.files[0]) reader.readAsArrayBuffer(event.target.files[0])
@@ -192,6 +197,10 @@ export const BadgeDetails = ({ metadataSize, onChange }: BadgeDetailsProps) => {
})
}, [metadataFile])
useEffect(() => {
animationUrlState.onChange('')
}, [uploadMethod])
useEffect(() => {
try {
const data: BadgeDetailsDataProps = {
@@ -240,8 +249,10 @@ export const BadgeDetails = ({ metadataSize, onChange }: BadgeDetailsProps) => {
useEffect(() => {
const retrieveFeeRate = async () => {
try {
if (wallet.client) {
const feeRateRaw = await wallet.client.queryContractRaw(
if (isWalletConnected) {
const feeRateRaw = await (
await getCosmWasmClient()
).queryContractRaw(
BADGE_HUB_ADDRESS,
toUtf8(Buffer.from(Buffer.from('fee_rate').toString('hex'), 'hex').toString()),
)
@@ -256,7 +267,7 @@ export const BadgeDetails = ({ metadataSize, onChange }: BadgeDetailsProps) => {
}
}
void retrieveFeeRate()
}, [wallet.client])
}, [isWalletConnected, getCosmWasmClient])
return (
<div>
@@ -266,10 +277,32 @@ export const BadgeDetails = ({ metadataSize, onChange }: BadgeDetailsProps) => {
<TextInput className="mt-2" {...nameState} />
<TextInput className="mt-2" {...descriptionState} />
<NumberInput className="mt-2" {...maxSupplyState} />
{uploadMethod === 'existing' ? <TextInput className="mt-2" {...animationUrlState} /> : null}
<TextInput className="mt-2" {...externalUrlState} />
<FormControl className="mt-2" htmlId="expiry-date" subtitle="Badge minting expiry date" title="Expiry Date">
<InputDateTime minDate={new Date()} onChange={(date) => setTimestamp(date)} value={timestamp} />
<FormControl
className="mt-2"
htmlId="expiry-date"
subtitle={`Badge minting expiry date ${timezone === 'Local' ? '(local)' : '(UTC)'}`}
title="Expiry Date"
>
<InputDateTime
minDate={
timezone === 'Local' ? new Date() : new Date(Date.now() + new Date().getTimezoneOffset() * 60 * 1000)
}
onChange={(date) =>
setTimestamp(
timezone === 'Local' ? date : new Date(date.getTime() - new Date().getTimezoneOffset() * 60 * 1000),
)
}
value={
timezone === 'Local'
? timestamp
: timestamp
? new Date(timestamp.getTime() + new Date().getTimezoneOffset() * 60 * 1000)
: undefined
}
/>
</FormControl>
<div className="grid grid-cols-2">
<div className="mt-2 w-1/3 form-control">
@@ -1,5 +1,5 @@
/* eslint-disable eslint-comments/disable-enable-pair */
/* eslint-disable jsx-a11y/media-has-caption */
/* eslint-disable no-misleading-character-class */
/* eslint-disable no-control-regex */
@@ -10,9 +10,11 @@ import { TextInput } from 'components/forms/FormInput'
import { useInputState } from 'components/forms/FormInput.hooks'
import { SingleAssetPreview } from 'components/SingleAssetPreview'
import type { ChangeEvent } from 'react'
import { useEffect, useRef, useState } from 'react'
import { useEffect, useMemo, useRef, useState } from 'react'
import { toast } from 'react-hot-toast'
import type { UploadServiceType } from 'services/upload'
import { NFT_STORAGE_DEFAULT_API_KEY } from 'utils/constants'
import { getAssetType } from 'utils/getAssetType'
export type UploadMethod = 'new' | 'existing'
export type MintRule = 'by_key' | 'by_minter' | 'by_keys' | 'not_resolved'
@@ -36,6 +38,7 @@ export const ImageUploadDetails = ({ onChange, mintRule }: ImageUploadDetailsPro
const [assetFile, setAssetFile] = useState<File>()
const [uploadMethod, setUploadMethod] = useState<UploadMethod>('new')
const [uploadService, setUploadService] = useState<UploadServiceType>('nft-storage')
const [useDefaultApiKey, setUseDefaultApiKey] = useState(false)
const assetFileRef = useRef<HTMLInputElement | null>(null)
@@ -78,7 +81,7 @@ export const ImageUploadDetails = ({ onChange, mintRule }: ImageUploadDetailsPro
reader.onload = (e) => {
if (!event.target.files) return toast.error('No file selected.')
if (!e.target?.result) return toast.error('Error parsing file.')
selectedFile = new File([e.target.result], event.target.files[0].name, { type: 'image/jpg' })
selectedFile = new File([e.target.result], event.target.files[0].name.replaceAll('#', ''), { type: 'image/jpg' })
}
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (event.target.files[0]) reader.readAsArrayBuffer(event.target.files[0])
@@ -129,6 +132,30 @@ export const ImageUploadDetails = ({ onChange, mintRule }: ImageUploadDetailsPro
imageUrlState.onChange('')
}, [uploadMethod, mintRule])
useEffect(() => {
if (useDefaultApiKey) {
nftStorageApiKeyState.onChange(NFT_STORAGE_DEFAULT_API_KEY || '')
} else {
nftStorageApiKeyState.onChange('')
}
}, [useDefaultApiKey])
const videoPreview = useMemo(
() => (
<video
className="ml-4"
controls
id="video"
onMouseEnter={(e) => e.currentTarget.play()}
onMouseLeave={(e) => e.currentTarget.pause()}
src={
imageUrlState.value ? imageUrlState.value.replace('ipfs://', 'https://ipfs-gw.stargaze-apis.com/ipfs/') : ''
}
/>
),
[imageUrlState.value],
)
return (
<div className="justify-items-start mb-3 rounded border-2 border-white/20 flex-column">
<div className="flex justify-center">
@@ -190,13 +217,16 @@ export const ImageUploadDetails = ({ onChange, mintRule }: ImageUploadDetailsPro
<div className="flex flex-row w-full">
<TextInput {...imageUrlState} className="mt-2 ml-6 w-full max-w-2xl" />
<Conditional test={imageUrlState.value !== ''}>
<div className="mt-2 ml-4 w-1/4 border-2 border-dashed">
<img
alt="badge-preview"
className="w-full"
src={imageUrlState.value.replace('IPFS://', 'ipfs://').replace(/,/g, '').replace(/"/g, '').trim()}
/>
</div>
{getAssetType(imageUrlState.value) === 'image' && (
<div className="mt-2 ml-4 w-1/4 border-2 border-dashed">
<img
alt="badge-preview"
className="w-full"
src={imageUrlState.value.replace('IPFS://', 'ipfs://').replace(/,/g, '').replace(/"/g, '').trim()}
/>
</div>
)}
{getAssetType(imageUrlState.value) === 'video' && videoPreview}
</Conditional>
</div>
</div>
@@ -249,7 +279,22 @@ export const ImageUploadDetails = ({ onChange, mintRule }: ImageUploadDetailsPro
<div className="flex w-full">
<Conditional test={uploadService === 'nft-storage'}>
<TextInput {...nftStorageApiKeyState} className="w-full" />
<div className="flex-col w-full">
<TextInput {...nftStorageApiKeyState} className="w-full" disabled={useDefaultApiKey} />
<div className="flex-row mt-2 w-full form-control">
<label className="cursor-pointer label">
<span className="mr-2 font-bold">Use Default API Key</span>
<input
checked={useDefaultApiKey}
className={`${useDefaultApiKey ? `bg-stargaze` : `bg-gray-600`} checkbox`}
onClick={() => {
setUseDefaultApiKey(!useDefaultApiKey)
}}
type="checkbox"
/>
</label>
</div>
</div>
</Conditional>
<Conditional test={uploadService === 'pinata'}>
<TextInput {...pinataApiKeyState} className="w-full" />
@@ -277,7 +322,7 @@ export const ImageUploadDetails = ({ onChange, mintRule }: ImageUploadDetailsPro
)}
>
<input
accept="image/*"
accept="image/*, video/*"
className={clsx(
'file:py-2 file:px-4 file:mr-4 file:bg-plumbus-light file:rounded file:border-0 cursor-pointer',
'before:absolute before:inset-0 before:hover:bg-white/5 before:transition',
-3
View File
@@ -10,7 +10,6 @@ import type { BadgeHubInstance } from 'contracts/badgeHub'
import { toast } from 'react-hot-toast'
import { useQuery } from 'react-query'
import { useWallet } from '../../../contexts/wallet'
import type { MintRule } from '../creation/ImageUploadDetails'
interface BadgeQueriesProps {
@@ -20,8 +19,6 @@ interface BadgeQueriesProps {
mintRule: MintRule
}
export const BadgeQueries = ({ badgeHubContractAddress, badgeId, badgeHubMessages, mintRule }: BadgeQueriesProps) => {
const wallet = useWallet()
const comboboxState = useQueryComboboxState()
const type = comboboxState.value?.id
+1 -1
View File
@@ -51,7 +51,7 @@ export type DispatchQueryArgs = {
export const dispatchQuery = async (args: DispatchQueryArgs) => {
const { badgeHubMessages } = args
if (!badgeHubMessages) {
throw new Error('Cannot perform a query')
throw new Error('Cannot perform a query. Please connect your wallet first.')
}
switch (args.type) {
case 'config': {
+221 -33
View File
@@ -1,5 +1,9 @@
/* eslint-disable eslint-comments/disable-enable-pair */
/* eslint-disable no-nested-ternary */
import { toUtf8 } from '@cosmjs/encoding'
import clsx from 'clsx'
import { AirdropUpload } from 'components/AirdropUpload'
import { Alert } from 'components/Alert'
import { Button } from 'components/Button'
import type { DispatchExecuteArgs } from 'components/collections/actions/actions'
import { dispatchExecute, isEitherType, previewExecutePayload } from 'components/collections/actions/actions'
@@ -14,9 +18,10 @@ import { InputDateTime } from 'components/InputDateTime'
import { JsonPreview } from 'components/JsonPreview'
import { Tooltip } from 'components/Tooltip'
import { TransactionHash } from 'components/TransactionHash'
import { useWallet } from 'contexts/wallet'
import { useGlobalSettings } from 'contexts/globalSettings'
import type { BaseMinterInstance } from 'contracts/baseMinter'
import type { OpenEditionMinterInstance } from 'contracts/openEditionMinter'
import type { RoyaltyRegistryInstance } from 'contracts/royaltyRegistry'
import type { SG721Instance } from 'contracts/sg721'
import type { VendingMinterInstance } from 'contracts/vendingMinter'
import type { FormEvent } from 'react'
@@ -24,8 +29,10 @@ import { useEffect, useState } from 'react'
import { toast } from 'react-hot-toast'
import { FaArrowRight } from 'react-icons/fa'
import { useMutation } from 'react-query'
import { ROYALTY_REGISTRY_ADDRESS } from 'utils/constants'
import type { AirdropAllocation } from 'utils/isValidAccountsFile'
import { resolveAddress } from 'utils/resolveAddress'
import { useWallet } from 'utils/wallet'
import type { CollectionInfo } from '../../../contracts/sg721/contract'
import { TextInput } from '../../forms/FormInput'
@@ -38,6 +45,7 @@ interface CollectionActionsProps {
vendingMinterMessages: VendingMinterInstance | undefined
baseMinterMessages: BaseMinterInstance | undefined
openEditionMinterMessages: OpenEditionMinterInstance | undefined
royaltyRegistryMessages: RoyaltyRegistryInstance | undefined
minterType: MinterType
sg721Type: Sg721Type
}
@@ -51,11 +59,13 @@ export const CollectionActions = ({
vendingMinterMessages,
baseMinterMessages,
openEditionMinterMessages,
royaltyRegistryMessages,
minterType,
sg721Type,
}: CollectionActionsProps) => {
const wallet = useWallet()
const [lastTx, setLastTx] = useState('')
const { timezone } = useGlobalSettings()
const [timestamp, setTimestamp] = useState<Date | undefined>(undefined)
const [endTimestamp, setEndTimestamp] = useState<Date | undefined>(undefined)
@@ -65,6 +75,7 @@ export const CollectionActions = ({
const [explicitContent, setExplicitContent] = useState<ExplicitContentType>(undefined)
const [resolvedRecipientAddress, setResolvedRecipientAddress] = useState<string>('')
const [jsonExtensions, setJsonExtensions] = useState<boolean>(false)
const [decrement, setDecrement] = useState<boolean>(false)
const actionComboboxState = useActionsComboboxState()
const type = actionComboboxState.value?.id
@@ -105,6 +116,13 @@ export const CollectionActions = ({
subtitle: 'Address of the recipient',
})
const creatorState = useInputState({
id: 'creator-address',
name: 'creator',
title: 'Creator Address',
subtitle: 'Address of the creator',
})
const tokenURIState = useInputState({
id: 'token-uri',
name: 'tokenURI',
@@ -132,7 +150,7 @@ export const CollectionActions = ({
id: 'update-mint-price',
name: 'updateMintPrice',
title: type === 'update_discount_price' ? 'Discount Price' : 'Update Mint Price',
subtitle: type === 'update_discount_price' ? 'New discount price in STARS' : 'New minting price in STARS',
subtitle: type === 'update_discount_price' ? 'New discount price' : 'New minting price',
})
const descriptionState = useInputState({
@@ -166,9 +184,14 @@ export const CollectionActions = ({
const royaltyShareState = useInputState({
id: 'royalty-share',
name: 'royaltyShare',
title: 'Share Percentage',
subtitle: 'Percentage of royalties to be paid',
placeholder: '5%',
title: type !== 'update_royalties_for_infinity_swap' ? 'Share Percentage' : 'Share Delta',
subtitle:
type !== 'update_royalties_for_infinity_swap'
? 'Percentage of royalties to be paid'
: 'Change in share percentage',
placeholder: isEitherType(type, ['set_royalties_for_infinity_swap', 'update_royalties_for_infinity_swap'])
? '0.5%'
: '5%',
})
const showTokenUriField = isEitherType(type, ['mint_token_uri', 'update_token_metadata'])
@@ -194,12 +217,21 @@ export const CollectionActions = ({
'batch_transfer',
'batch_mint_for',
])
const showAirdropFileField = isEitherType(type, ['airdrop', 'airdrop_open_edition', 'airdrop_specific'])
const showAirdropFileField = isEitherType(type, [
'airdrop',
'airdrop_open_edition',
'airdrop_specific',
'batch_transfer_multi_address',
])
const showPriceField = isEitherType(type, ['update_mint_price', 'update_discount_price'])
const showDescriptionField = type === 'update_collection_info'
const showCreatorField = type === 'update_collection_info'
const showImageField = type === 'update_collection_info'
const showExternalLinkField = type === 'update_collection_info'
const showRoyaltyRelatedFields = type === 'update_collection_info'
const showRoyaltyRelatedFields =
type === 'update_collection_info' ||
type === 'set_royalties_for_infinity_swap' ||
type === 'update_royalties_for_infinity_swap'
const showExplicitContentField = type === 'update_collection_info'
const showBaseUriField = type === 'batch_update_token_metadata'
@@ -210,6 +242,7 @@ export const CollectionActions = ({
limit: limitState.value,
minterContract: minterContractAddress,
sg721Contract: sg721ContractAddress,
royaltyRegistryContract: ROYALTY_REGISTRY_ADDRESS,
tokenId: tokenIdState.value,
tokenIds: tokenIdListState.value,
tokenUri: tokenURIState.value.trim().endsWith('/')
@@ -220,10 +253,11 @@ export const CollectionActions = ({
baseMinterMessages,
openEditionMinterMessages,
sg721Messages,
royaltyRegistryMessages,
recipient: resolvedRecipientAddress,
recipients: airdropArray,
tokenRecipients: airdropAllocationArray,
txSigner: wallet.address,
txSigner: wallet.address || '',
type,
price: priceState.value.toString(),
baseUri: baseURIState.value.trim().endsWith('/')
@@ -231,6 +265,7 @@ export const CollectionActions = ({
: baseURIState.value.trim(),
collectionInfo,
jsonExtensions,
decrement,
}
const resolveRecipientAddress = async () => {
await resolveAddress(recipientState.value.trim(), wallet).then((resolvedAddress) => {
@@ -244,7 +279,7 @@ export const CollectionActions = ({
const resolveRoyaltyPaymentAddress = async () => {
await resolveAddress(royaltyPaymentAddressState.value.trim(), wallet).then((resolvedAddress) => {
setCollectionInfo({
description: descriptionState.value || undefined,
description: descriptionState.value.replaceAll('\\n', '\n') || undefined,
image: imageState.value || undefined,
explicit_content: explicitContent,
external_link: externalLinkState.value || undefined,
@@ -263,9 +298,19 @@ export const CollectionActions = ({
void resolveRoyaltyPaymentAddress()
}, [royaltyPaymentAddressState.value])
const resolveCreatorAddress = async () => {
await resolveAddress(creatorState.value.trim(), wallet).then((resolvedAddress) => {
creatorState.onChange(resolvedAddress)
})
}
useEffect(() => {
void resolveCreatorAddress()
}, [creatorState.value])
useEffect(() => {
setCollectionInfo({
description: descriptionState.value || undefined,
description: descriptionState.value.replaceAll('\\n', '\n') || undefined,
image: imageState.value || undefined,
explicit_content: explicitContent,
external_link: externalLinkState.value || undefined,
@@ -276,6 +321,7 @@ export const CollectionActions = ({
share: (Number(royaltyShareState.value) / 100).toString(),
}
: undefined,
creator: creatorState.value || undefined,
})
}, [
descriptionState.value,
@@ -284,8 +330,16 @@ export const CollectionActions = ({
externalLinkState.value,
royaltyPaymentAddressState.value,
royaltyShareState.value,
creatorState.value,
])
useEffect(() => {
if (isEitherType(type, ['set_royalties_for_infinity_swap']) && Number(royaltyShareState.value) > 5) {
royaltyShareState.onChange('5')
toast.error('Royalty share cannot be greater than 5% for Infinity Swap')
}
}, [royaltyShareState.value])
useEffect(() => {
const addresses: string[] = []
airdropAllocationArray.forEach((allocation) => {
@@ -304,20 +358,25 @@ export const CollectionActions = ({
const { isLoading, mutate } = useMutation(
async (event: FormEvent) => {
event.preventDefault()
if (!wallet.isWalletConnected) {
throw new Error('Please connect your wallet first.')
}
if (!type) {
throw new Error('Please select an action!')
throw new Error('Please select an action.')
}
if (minterContractAddress === '' && sg721ContractAddress === '') {
throw new Error('Please enter minter and sg721 contract addresses!')
}
if (wallet.client && type === 'update_mint_price') {
const contractConfig = wallet.client.queryContractSmart(minterContractAddress, {
if (wallet.isWalletConnected && type === 'update_mint_price') {
const contractConfig = (await wallet.getCosmWasmClient()).queryContractSmart(minterContractAddress, {
config: {},
})
await toast
.promise(
wallet.client.queryContractSmart(minterContractAddress, {
(
await wallet.getCosmWasmClient()
).queryContractSmart(minterContractAddress, {
mint_price: {},
}),
{
@@ -349,7 +408,9 @@ export const CollectionActions = ({
})
} else {
await contractConfig.then(async (config) => {
const factoryParameters = await wallet.client?.queryContractSmart(config.factory, {
const factoryParameters = await (
await wallet.getCosmWasmClient()
).queryContractSmart(config.factory, {
params: {},
})
if (
@@ -379,8 +440,8 @@ export const CollectionActions = ({
royaltyPaymentAddressState.value &&
!royaltyPaymentAddressState.value.trim().endsWith('.stars')
) {
const contractInfoResponse = await wallet.client
?.queryContractRaw(
const contractInfoResponse = await (await wallet.getCosmWasmClient())
.queryContractRaw(
royaltyPaymentAddressState.value.trim(),
toUtf8(Buffer.from(Buffer.from('contract_info').toString('hex'), 'hex').toString()),
)
@@ -392,8 +453,29 @@ export const CollectionActions = ({
if (contractInfoResponse !== undefined) {
const contractInfo = JSON.parse(new TextDecoder().decode(contractInfoResponse as Uint8Array))
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
if (contractInfo && !contractInfo.contract.includes('splits'))
throw new Error('The provided royalty payment address does not belong to a splits contract.')
if (contractInfo && (contractInfo.contract.includes('minter') || contractInfo.contract.includes('sg721')))
throw new Error('The provided royalty payment address does not belong to a compatible contract.')
else console.log(contractInfo)
}
}
if (type === 'update_collection_info' && creatorState.value) {
const resolvedCreatorAddress = await resolveAddress(creatorState.value.trim(), wallet)
const contractInfoResponse = await (await wallet.getCosmWasmClient())
.queryContractRaw(
resolvedCreatorAddress,
toUtf8(Buffer.from(Buffer.from('contract_info').toString('hex'), 'hex').toString()),
)
.catch((e) => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
if (e.message.includes('bech32')) throw new Error('Invalid creator address.')
console.log(e.message)
})
if (contractInfoResponse !== undefined) {
const contractInfo = JSON.parse(new TextDecoder().decode(contractInfoResponse as Uint8Array))
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
if (contractInfo && !contractInfo.contract.includes('dao'))
throw new Error('The provided creator address does not belong to a compatible contract.')
else console.log(contractInfo)
}
}
@@ -418,6 +500,28 @@ export const CollectionActions = ({
setAirdropAllocationArray(data)
}
const downloadSampleAirdropTokensFile = () => {
const csvData =
'address,amount\nstars153w5xhuqu3et29lgqk4dsynj6gjn96lr33wx4e,3\nstars1xkes5r2k8u3m3ayfpverlkcrq3k4jhdk8ws0uz,1\nstars1s8qx0zvz8yd6e4x0mqmqf7fr9vvfn622wtp3g3,2'
const blob = new Blob([csvData], { type: 'text/csv' })
const url = window.URL.createObjectURL(blob)
const a = document.createElement('a')
a.setAttribute('href', url)
a.setAttribute('download', 'airdrop_tokens.csv')
a.click()
}
const downloadSampleAirdropSpecificTokensFile = () => {
const csvData =
'address,tokenId\nstars153w5xhuqu3et29lgqk4dsynj6gjn96lr33wx4e,214\nstars1xkes5r2k8u3m3ayfpverlkcrq3k4jhdk8ws0uz,683\nstars1s8qx0zvz8yd6e4x0mqmqf7fr9vvfn622wtp3g3,102'
const blob = new Blob([csvData], { type: 'text/csv' })
const url = window.URL.createObjectURL(blob)
const a = document.createElement('a')
a.setAttribute('href', url)
a.setAttribute('download', 'airdrop_specific_tokens.csv')
a.click()
}
return (
<form>
<div className="grid grid-cols-2 mt-4">
@@ -432,6 +536,7 @@ export const CollectionActions = ({
{showBaseUriField && <TextInput className="mt-2" {...baseURIState} />}
{showNumberOfTokensField && <NumberInput className="mt-2" {...batchNumberState} />}
{showPriceField && <NumberInput className="mt-2" {...priceState} />}
{showCreatorField && <AddressInput className="mt-2" {...creatorState} />}
{showDescriptionField && <TextInput className="my-2" {...descriptionState} />}
{showImageField && <TextInput className="mb-2" {...imageState} />}
{showExternalLinkField && <TextInput className="mb-2" {...externalLinkState} />}
@@ -439,6 +544,24 @@ export const CollectionActions = ({
<div className="p-2 my-4 rounded border-2 border-gray-500/50">
<TextInput className="mb-2" {...royaltyPaymentAddressState} />
<NumberInput className="mb-2" {...royaltyShareState} />
<Conditional test={type === 'update_royalties_for_infinity_swap'}>
<div className="flex flex-row space-y-2 w-1/4">
<div className={clsx('flex flex-col space-y-2 w-full form-control')}>
<label className="justify-start cursor-pointer label">
<div className="flex flex-col">
<span className="mr-4 font-bold">Increment</span>
</div>
<input
checked={decrement}
className={`toggle ${decrement ? `bg-stargaze` : `bg-gray-600`}`}
onClick={() => setDecrement(!decrement)}
type="checkbox"
/>
</label>
</div>
<span className="mx-4 font-bold">Decrement</span>
</div>
</Conditional>
</div>
)}
{showExplicitContentField && (
@@ -489,25 +612,79 @@ export const CollectionActions = ({
</div>
)}
{showAirdropFileField && (
<FormGroup
subtitle={`CSV file that contains the airdrop addresses and the ${
type === 'airdrop' ? 'amount of tokens' : 'token ID'
} allocated for each address. Should start with the following header row: ${
type === 'airdrop' ? 'address,amount' : 'address,tokenId'
}`}
title="Airdrop File"
>
<AirdropUpload onChange={airdropFileOnChange} />
</FormGroup>
<div>
<FormGroup
subtitle={`CSV file that contains the ${
type === 'batch_transfer_multi_address' ? '' : 'airdrop'
} addresses and the ${
type === 'airdrop' || type === 'airdrop_open_edition' ? 'amount of tokens' : 'token ID'
} allocated for each address. Should start with the following header row: ${
type === 'airdrop' || type === 'airdrop_open_edition' ? 'address,amount' : 'address,tokenId'
}`}
title={`${type === 'batch_transfer_multi_address' ? 'Multi-Recipient Transfer File' : 'Airdrop File'}`}
>
<AirdropUpload onChange={airdropFileOnChange} />
</FormGroup>
<Button
className="ml-4 text-sm"
onClick={
type === 'airdrop' || type === 'airdrop_open_edition'
? downloadSampleAirdropTokensFile
: downloadSampleAirdropSpecificTokensFile
}
>
Download Sample File
</Button>
</div>
)}
<Conditional test={showDateField}>
<FormControl className="mt-2" htmlId="start-date" title="Start Time">
<InputDateTime minDate={new Date()} onChange={(date) => setTimestamp(date)} value={timestamp} />
<FormControl
className="mt-2"
htmlId="start-date"
title={`Start Time ${timezone === 'Local' ? '(local)' : '(UTC)'}`}
>
<InputDateTime
minDate={
timezone === 'Local' ? new Date() : new Date(Date.now() + new Date().getTimezoneOffset() * 60 * 1000)
}
onChange={(date) =>
setTimestamp(
timezone === 'Local' ? date : new Date(date.getTime() - new Date().getTimezoneOffset() * 60 * 1000),
)
}
value={
timezone === 'Local'
? timestamp
: timestamp
? new Date(timestamp.getTime() + new Date().getTimezoneOffset() * 60 * 1000)
: undefined
}
/>
</FormControl>
</Conditional>
<Conditional test={showEndDateField}>
<FormControl className="mt-2" htmlId="end-date" title="End Time">
<InputDateTime minDate={new Date()} onChange={(date) => setEndTimestamp(date)} value={endTimestamp} />
<FormControl
className="mt-2"
htmlId="end-date"
title={`End Time ${timezone === 'Local' ? '(local)' : '(UTC)'}`}
>
<InputDateTime
minDate={
timezone === 'Local' ? new Date() : new Date(Date.now() + new Date().getTimezoneOffset() * 60 * 1000)
}
onChange={(date) =>
setEndTimestamp(
timezone === 'Local' ? date : new Date(date.getTime() - new Date().getTimezoneOffset() * 60 * 1000),
)
}
value={
timezone === 'Local'
? endTimestamp
: endTimestamp
? new Date(endTimestamp.getTime() + new Date().getTimezoneOffset() * 60 * 1000)
: undefined
}
/>
</FormControl>
</Conditional>
<Conditional test={showBaseUriField}>
@@ -530,6 +707,17 @@ export const CollectionActions = ({
</div>
</Tooltip>
</Conditional>
<Conditional test={type === 'update_collection_info'}>
<Alert className="mt-2 text-sm" type="info">
Please note that you are only required to fill in the fields you want to update.
</Alert>
</Conditional>
<Conditional test={type === 'update_discount_price'}>
<Alert className="mt-2 text-sm" type="warning">
Please note that discount price can only be updated every 24 hours and be removed 12 hours after its last
update.
</Alert>
</Conditional>
</div>
<div className="-mt-6">
<div className="relative mb-2">
+113 -3
View File
@@ -1,10 +1,13 @@
/* eslint-disable eslint-comments/disable-enable-pair */
import { useBaseMinterContract } from 'contracts/baseMinter'
import { useOpenEditionMinterContract } from 'contracts/openEditionMinter'
import type { RoyaltyRegistryInstance } from 'contracts/royaltyRegistry'
import { useRoyaltyRegistryContract } from 'contracts/royaltyRegistry'
import type { CollectionInfo, SG721Instance } from 'contracts/sg721'
import { useSG721Contract } from 'contracts/sg721'
import type { VendingMinterInstance } from 'contracts/vendingMinter'
import { useVendingMinterContract } from 'contracts/vendingMinter'
import { INFINITY_SWAP_PROTOCOL_ADDRESS } from 'utils/constants'
import type { AirdropAllocation } from 'utils/isValidAccountsFile'
import type { BaseMinterInstance } from '../../../contracts/baseMinter/contract'
@@ -29,8 +32,11 @@ export const ACTION_TYPES = [
'update_per_address_limit',
'update_collection_info',
'freeze_collection_info',
'set_royalties_for_infinity_swap',
'update_royalties_for_infinity_swap',
'transfer',
'batch_transfer',
'batch_transfer_multi_address',
'burn',
'batch_burn',
'batch_mint_for',
@@ -72,6 +78,16 @@ export const BASE_ACTION_LIST: ActionListItem[] = [
name: 'Freeze Collection Info',
description: `Freeze collection info to prevent further updates`,
},
{
id: 'set_royalties_for_infinity_swap',
name: 'Set Royalty Details for Infinity Swap',
description: `Set royalty details for Infinity Swap`,
},
{
id: 'update_royalties_for_infinity_swap',
name: 'Update Royalty Details for Infinity Swap',
description: `Update royalty details for Infinity Swap`,
},
{
id: 'transfer',
name: 'Transfer Tokens',
@@ -82,6 +98,11 @@ export const BASE_ACTION_LIST: ActionListItem[] = [
name: 'Batch Transfer Tokens',
description: `Transfer a list of tokens to a recipient`,
},
{
id: 'batch_transfer_multi_address',
name: 'Transfer Tokens to Multiple Recipients',
description: `Transfer a list of tokens to multiple addresses`,
},
{
id: 'burn',
name: 'Burn Token',
@@ -160,6 +181,16 @@ export const VENDING_ACTION_LIST: ActionListItem[] = [
name: 'Freeze Collection Info',
description: `Freeze collection info to prevent further updates`,
},
{
id: 'set_royalties_for_infinity_swap',
name: 'Set Royalty Details for Infinity Swap',
description: `Set royalty details for Infinity Swap`,
},
{
id: 'update_royalties_for_infinity_swap',
name: 'Update Royalty Details for Infinity Swap',
description: `Update royalty details for Infinity Swap`,
},
{
id: 'transfer',
name: 'Transfer Tokens',
@@ -170,6 +201,11 @@ export const VENDING_ACTION_LIST: ActionListItem[] = [
name: 'Batch Transfer Tokens',
description: `Transfer a list of tokens to a recipient`,
},
{
id: 'batch_transfer_multi_address',
name: 'Transfer Tokens to Multiple Recipients',
description: `Transfer a list of tokens to multiple addresses`,
},
{
id: 'burn',
name: 'Burn Token',
@@ -248,6 +284,16 @@ export const OPEN_EDITION_ACTION_LIST: ActionListItem[] = [
name: 'Freeze Collection Info',
description: `Freeze collection info to prevent further updates`,
},
{
id: 'set_royalties_for_infinity_swap',
name: 'Set Royalty Details for Infinity Swap',
description: `Set royalty details for Infinity Swap`,
},
{
id: 'update_royalties_for_infinity_swap',
name: 'Update Royalty Details for Infinity Swap',
description: `Update royalty details for Infinity Swap`,
},
{
id: 'transfer',
name: 'Transfer Tokens',
@@ -258,6 +304,11 @@ export const OPEN_EDITION_ACTION_LIST: ActionListItem[] = [
name: 'Batch Transfer Tokens',
description: `Transfer a list of tokens to a recipient`,
},
{
id: 'batch_transfer_multi_address',
name: 'Transfer Tokens to Multiple Recipients',
description: `Transfer a list of tokens to multiple addresses`,
},
{
id: 'burn',
name: 'Burn Token',
@@ -307,10 +358,12 @@ export interface DispatchExecuteProps {
export interface DispatchExecuteArgs {
minterContract: string
sg721Contract: string
royaltyRegistryContract: string
vendingMinterMessages?: VendingMinterInstance
baseMinterMessages?: BaseMinterInstance
openEditionMinterMessages?: OpenEditionMinterInstance
sg721Messages?: SG721Instance
royaltyRegistryMessages?: RoyaltyRegistryInstance
txSigner: string
type: string | undefined
tokenUri: string
@@ -328,11 +381,25 @@ export interface DispatchExecuteArgs {
collectionInfo: CollectionInfo | undefined
baseUri: string
jsonExtensions: boolean
decrement: boolean
}
export const dispatchExecute = async (args: DispatchExecuteArgs) => {
const { vendingMinterMessages, baseMinterMessages, openEditionMinterMessages, sg721Messages, txSigner } = args
if (!vendingMinterMessages || !baseMinterMessages || !openEditionMinterMessages || !sg721Messages) {
const {
vendingMinterMessages,
baseMinterMessages,
openEditionMinterMessages,
sg721Messages,
royaltyRegistryMessages,
txSigner,
} = args
if (
!vendingMinterMessages ||
!baseMinterMessages ||
!openEditionMinterMessages ||
!sg721Messages ||
!royaltyRegistryMessages
) {
throw new Error('Cannot execute actions')
}
switch (args.type) {
@@ -399,12 +466,32 @@ export const dispatchExecute = async (args: DispatchExecuteArgs) => {
case 'shuffle': {
return vendingMinterMessages.shuffle(txSigner)
}
case 'set_royalties_for_infinity_swap': {
return royaltyRegistryMessages.setCollectionRoyaltyProtocol(
args.sg721Contract,
INFINITY_SWAP_PROTOCOL_ADDRESS,
args.collectionInfo?.royalty_info?.payment_address as string,
Number(args.collectionInfo?.royalty_info?.share) * 100,
)
}
case 'update_royalties_for_infinity_swap': {
return royaltyRegistryMessages.updateCollectionRoyaltyProtocol(
args.sg721Contract,
INFINITY_SWAP_PROTOCOL_ADDRESS,
args.collectionInfo?.royalty_info?.payment_address as string,
Number(args.collectionInfo?.royalty_info?.share) * 100,
args.decrement,
)
}
case 'transfer': {
return sg721Messages.transferNft(args.recipient, args.tokenId.toString())
}
case 'batch_transfer': {
return sg721Messages.batchTransfer(args.recipient, args.tokenIds)
}
case 'batch_transfer_multi_address': {
return sg721Messages.batchTransferMultiAddress(txSigner, args.tokenRecipients)
}
case 'burn': {
return sg721Messages.burn(args.tokenId.toString())
}
@@ -441,7 +528,10 @@ export const previewExecutePayload = (args: DispatchExecuteArgs) => {
const { messages: baseMinterMessages } = useBaseMinterContract()
// eslint-disable-next-line react-hooks/rules-of-hooks
const { messages: openEditionMinterMessages } = useOpenEditionMinterContract()
const { minterContract, sg721Contract } = args
// eslint-disable-next-line react-hooks/rules-of-hooks
const { messages: royaltyRegistryMessages } = useRoyaltyRegistryContract()
const { minterContract, sg721Contract, royaltyRegistryContract } = args
switch (args.type) {
case 'mint_token_uri': {
return baseMinterMessages(minterContract)?.mint(args.tokenUri)
@@ -506,12 +596,32 @@ export const previewExecutePayload = (args: DispatchExecuteArgs) => {
case 'shuffle': {
return vendingMinterMessages(minterContract)?.shuffle()
}
case 'set_royalties_for_infinity_swap': {
return royaltyRegistryMessages(royaltyRegistryContract)?.setCollectionRoyaltyProtocol(
args.sg721Contract,
INFINITY_SWAP_PROTOCOL_ADDRESS,
args.collectionInfo?.royalty_info?.payment_address as string,
Number(args.collectionInfo?.royalty_info?.share) * 100,
)
}
case 'update_royalties_for_infinity_swap': {
return royaltyRegistryMessages(royaltyRegistryContract)?.updateCollectionRoyaltyProtocol(
args.sg721Contract,
INFINITY_SWAP_PROTOCOL_ADDRESS,
args.collectionInfo?.royalty_info?.payment_address as string,
Number(args.collectionInfo?.royalty_info?.share) * 100,
args.decrement,
)
}
case 'transfer': {
return sg721Messages(sg721Contract)?.transferNft(args.recipient, args.tokenId.toString())
}
case 'batch_transfer': {
return sg721Messages(sg721Contract)?.batchTransfer(args.recipient, args.tokenIds)
}
case 'batch_transfer_multi_address': {
return sg721Messages(sg721Contract)?.batchTransferMultiAddress(args.tokenRecipients)
}
case 'burn': {
return sg721Messages(sg721Contract)?.burn(args.tokenId.toString())
}
@@ -8,10 +8,10 @@ import clsx from 'clsx'
import { Alert } from 'components/Alert'
import { Conditional } from 'components/Conditional'
import { useInputState } from 'components/forms/FormInput.hooks'
import { useWallet } from 'contexts/wallet'
import React, { useCallback, useEffect, useState } from 'react'
import { toast } from 'react-hot-toast'
import { API_URL } from 'utils/constants'
import { useWallet } from 'utils/wallet'
import { useDebounce } from '../../../utils/debounce'
import { TextInput } from '../../forms/FormInput'
@@ -28,6 +28,7 @@ export interface MinterInfo {
interface BaseMinterDetailsProps {
onChange: (data: BaseMinterDetailsDataProps) => void
minterType: MinterType
importedBaseMinterDetails?: BaseMinterDetailsDataProps
}
export interface BaseMinterDetailsDataProps {
@@ -37,7 +38,7 @@ export interface BaseMinterDetailsDataProps {
collectionTokenCount: number | undefined
}
export const BaseMinterDetails = ({ onChange, minterType }: BaseMinterDetailsProps) => {
export const BaseMinterDetails = ({ onChange, minterType, importedBaseMinterDetails }: BaseMinterDetailsProps) => {
const wallet = useWallet()
const [myBaseMinterContracts, setMyBaseMinterContracts] = useState<MinterInfo[]>([])
@@ -55,7 +56,7 @@ export const BaseMinterDetails = ({ onChange, minterType }: BaseMinterDetailsPro
const fetchMinterContracts = async (): Promise<MinterInfo[]> => {
const contracts: MinterInfo[] = await axios
.get(`${API_URL}/api/v1beta/collections/${wallet.address}`)
.get(`${API_URL}/api/v1beta/collections/${wallet.address || ''}`)
.then((response) => {
const collectionData = response.data
const minterContracts = collectionData.map((collection: any) => {
@@ -69,8 +70,8 @@ export const BaseMinterDetails = ({ onChange, minterType }: BaseMinterDetailsPro
}
async function getMinterContractType(minterContractAddress: string) {
if (wallet.client && minterContractAddress.length > 0) {
const client = wallet.client
if (wallet.isWalletConnected && minterContractAddress.length > 0) {
const client = await wallet.getCosmWasmClient()
const data = await client.queryContractRaw(
minterContractAddress,
toUtf8(Buffer.from(Buffer.from('contract_info').toString('hex'), 'hex').toString()),
@@ -128,8 +129,10 @@ export const BaseMinterDetails = ({ onChange, minterType }: BaseMinterDetailsPro
const fetchSg721Address = async () => {
if (debouncedExistingBaseMinterContract.length === 0) return
await wallet.client
?.queryContractSmart(debouncedExistingBaseMinterContract, {
await (
await wallet.getCosmWasmClient()
)
.queryContractSmart(debouncedExistingBaseMinterContract, {
config: {},
})
.then((response) => {
@@ -144,8 +147,10 @@ export const BaseMinterDetails = ({ onChange, minterType }: BaseMinterDetailsPro
const fetchCollectionTokenCount = async () => {
if (selectedCollectionAddress === undefined) return
await wallet.client
?.queryContractSmart(selectedCollectionAddress, {
await (
await wallet.getCosmWasmClient()
)
.queryContractSmart(selectedCollectionAddress, {
num_tokens: {},
})
.then((response) => {
@@ -163,7 +168,7 @@ export const BaseMinterDetails = ({ onChange, minterType }: BaseMinterDetailsPro
setMyBaseMinterContracts([])
existingBaseMinterState.onChange('')
void displayToast()
} else if (baseMinterAcquisitionMethod === 'new' || !wallet.initialized) {
} else if (baseMinterAcquisitionMethod === 'new' || !wallet.isWalletConnected) {
setMyBaseMinterContracts([])
existingBaseMinterState.onChange('')
}
@@ -193,11 +198,20 @@ export const BaseMinterDetails = ({ onChange, minterType }: BaseMinterDetailsPro
}, [
existingBaseMinterState.value,
baseMinterAcquisitionMethod,
wallet.initialized,
wallet.isWalletConnected,
selectedCollectionAddress,
collectionTokenCount,
])
useEffect(() => {
if (importedBaseMinterDetails) {
setBaseMinterAcquisitionMethod(importedBaseMinterDetails.baseMinterAcquisitionMethod)
existingBaseMinterState.onChange(
importedBaseMinterDetails.existingBaseMinter ? importedBaseMinterDetails.existingBaseMinter : '',
)
}
}, [importedBaseMinterDetails])
return (
<div className="mx-10 mb-4 rounded border-2 border-white/20">
<div className="flex justify-center mb-2">
@@ -260,7 +274,7 @@ export const BaseMinterDetails = ({ onChange, minterType }: BaseMinterDetailsPro
</Conditional>
<Conditional test={myBaseMinterContracts.length === 0}>
<div className="flex flex-col">
<Conditional test={wallet.initialized}>
<Conditional test={wallet.isWalletConnected}>
<Alert className="my-2 w-[90%]" type="info">
No previous 1/1 collections were found. You may create a new 1/1 collection or fill in the minter
contract address manually.
@@ -272,7 +286,7 @@ export const BaseMinterDetails = ({ onChange, minterType }: BaseMinterDetailsPro
isRequired
/>
</Conditional>
<Conditional test={!wallet.initialized}>
<Conditional test={!wallet.isWalletConnected}>
<Alert className="my-2 w-[90%]" type="warning">
Please connect your wallet first.
</Alert>
@@ -1,4 +1,5 @@
/* eslint-disable eslint-comments/disable-enable-pair */
/* eslint-disable no-nested-ternary */
/* eslint-disable @typescript-eslint/no-unnecessary-condition */
/* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
@@ -10,6 +11,7 @@ import { FormGroup } from 'components/FormGroup'
import { useInputState } from 'components/forms/FormInput.hooks'
import { InputDateTime } from 'components/InputDateTime'
import { Tooltip } from 'components/Tooltip'
import { useGlobalSettings } from 'contexts/globalSettings'
import { addLogItem } from 'contexts/log'
import type { ChangeEvent } from 'react'
import { useEffect, useRef, useState } from 'react'
@@ -26,6 +28,7 @@ interface CollectionDetailsProps {
uploadMethod: UploadMethod
coverImageUrl: string
minterType: MinterType
importedCollectionDetails?: CollectionDetailsDataProps
}
export interface CollectionDetailsDataProps {
@@ -39,11 +42,18 @@ export interface CollectionDetailsDataProps {
updatable: boolean
}
export const CollectionDetails = ({ onChange, uploadMethod, coverImageUrl, minterType }: CollectionDetailsProps) => {
export const CollectionDetails = ({
onChange,
uploadMethod,
coverImageUrl,
minterType,
importedCollectionDetails,
}: CollectionDetailsProps) => {
const [coverImage, setCoverImage] = useState<File | null>(null)
const [timestamp, setTimestamp] = useState<Date | undefined>()
const [explicit, setExplicit] = useState<boolean>(false)
const [updatable, setUpdatable] = useState<boolean>(false)
const { timezone } = useGlobalSettings()
const initialRender = useRef(true)
@@ -105,6 +115,23 @@ export const CollectionDetails = ({ onChange, uploadMethod, coverImageUrl, minte
updatable,
])
useEffect(() => {
if (importedCollectionDetails) {
nameState.onChange(importedCollectionDetails.name)
descriptionState.onChange(importedCollectionDetails.description)
symbolState.onChange(importedCollectionDetails.symbol)
externalLinkState.onChange(importedCollectionDetails.externalLink || '')
setTimestamp(
importedCollectionDetails.startTradingTime
? new Date(parseInt(importedCollectionDetails.startTradingTime) / 1_000_000)
: undefined,
)
setExplicit(importedCollectionDetails.explicit)
setUpdatable(importedCollectionDetails.updatable)
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [importedCollectionDetails])
const selectCoverImage = (event: ChangeEvent<HTMLInputElement>) => {
if (event.target.files === null) return toast.error('Error selecting cover image')
if (event.target.files.length === 0) {
@@ -115,7 +142,9 @@ export const CollectionDetails = ({ onChange, uploadMethod, coverImageUrl, minte
reader.onload = (e) => {
if (!e.target?.result) return toast.error('Error parsing file.')
if (!event.target.files) return toast.error('No files selected.')
const imageFile = new File([e.target.result], event.target.files[0].name, { type: 'image/jpg' })
const imageFile = new File([e.target.result], event.target.files[0].name.replaceAll('#', ''), {
type: 'image/jpg',
})
setCoverImage(imageFile)
}
reader.readAsArrayBuffer(event.target.files[0])
@@ -154,9 +183,31 @@ export const CollectionDetails = ({ onChange, uploadMethod, coverImageUrl, minte
className={clsx(minterType === 'base' ? 'mt-10' : 'mt-2')}
htmlId="timestamp"
subtitle="Trading start time offset will be set as 2 weeks by default."
title="Trading Start Time (optional)"
title={`Trading Start Time (optional | ${timezone === 'Local' ? 'local)' : 'UTC)'}`}
>
<InputDateTime minDate={new Date()} onChange={(date) => setTimestamp(date)} value={timestamp} />
<InputDateTime
minDate={
timezone === 'Local'
? new Date()
: new Date(Date.now() + new Date().getTimezoneOffset() * 60 * 1000)
}
onChange={(date) =>
date
? setTimestamp(
timezone === 'Local'
? date
: new Date(date.getTime() - new Date().getTimezoneOffset() * 60 * 1000),
)
: setTimestamp(undefined)
}
value={
timezone === 'Local'
? timestamp
: timestamp
? new Date(timestamp.getTime() + new Date().getTimezoneOffset() * 60 * 1000)
: undefined
}
/>
</FormControl>
</Conditional>
<Conditional test={minterType === 'base'}>
@@ -207,7 +258,7 @@ export const CollectionDetails = ({ onChange, uploadMethod, coverImageUrl, minte
</div>
</div>
</div>
<Conditional test={SG721_UPDATABLE_CODE_ID > 0}>
<Conditional test={false && SG721_UPDATABLE_CODE_ID > 0}>
<Tooltip
backgroundColor="bg-blue-500"
label={
@@ -222,7 +273,10 @@ export const CollectionDetails = ({ onChange, uploadMethod, coverImageUrl, minte
>
<div className={clsx('flex flex-col mt-11 space-y-2 w-full form-control')}>
<label className="justify-start cursor-pointer label">
<span className="mr-4 font-bold">Updatable Token Metadata</span>
<div className="flex flex-col">
<span className="mr-4 font-bold">Updatable Token Metadata</span>
<span className="mr-4">(Price: 2000 STARS)</span>
</div>
<input
checked={updatable}
className={`toggle ${updatable ? `bg-stargaze` : `bg-gray-600`}`}
@@ -327,7 +381,7 @@ export const CollectionDetails = ({ onChange, uploadMethod, coverImageUrl, minte
</div>
</div>
</div>
<Conditional test={SG721_UPDATABLE_CODE_ID > 0}>
<Conditional test={false && SG721_UPDATABLE_CODE_ID > 0}>
<Tooltip
backgroundColor="bg-blue-500"
label={
@@ -344,11 +398,14 @@ export const CollectionDetails = ({ onChange, uploadMethod, coverImageUrl, minte
className={clsx(
minterType === 'base'
? 'flex flex-col -ml-16 space-y-2 w-1/2 form-control'
: 'flex flex-col space-y-2 w-3/4 form-control',
: 'flex flex-col space-y-2 w-full form-control',
)}
>
<label className="justify-start cursor-pointer label">
<span className="mr-4 font-bold">Updatable Token Metadata</span>
<div className="flex flex-col">
<span className="mr-4 font-bold">Updatable Token Metadata</span>
<span className="mr-4">(Price: 2000 STARS)</span>
</div>
<input
checked={updatable}
className={`toggle ${updatable ? `bg-stargaze` : `bg-gray-600`}`}
@@ -1,4 +1,5 @@
/* eslint-disable eslint-comments/disable-enable-pair */
/* eslint-disable @typescript-eslint/no-unnecessary-condition */
/* eslint-disable no-nested-ternary */
import { FormControl } from 'components/FormControl'
import { FormGroup } from 'components/FormGroup'
@@ -7,10 +8,11 @@ import { InputDateTime } from 'components/InputDateTime'
import { vendingMinterList } from 'config/minter'
import type { TokenInfo } from 'config/token'
import { stars, tokensList } from 'config/token'
import { useGlobalSettings } from 'contexts/globalSettings'
import React, { useEffect, useState } from 'react'
import { resolveAddress } from 'utils/resolveAddress'
import { useWallet } from 'utils/wallet'
import { useWallet } from '../../../contexts/wallet'
import { NumberInput, TextInput } from '../../forms/FormInput'
import type { UploadMethod } from './UploadDetails'
@@ -20,6 +22,9 @@ interface MintingDetailsProps {
uploadMethod: UploadMethod
minimumMintPrice: number
mintingTokenFromFactory?: TokenInfo
importedMintingDetails?: MintingDetailsDataProps
isPresale: boolean
whitelistStartDate?: string
}
export interface MintingDetailsDataProps {
@@ -37,9 +42,12 @@ export const MintingDetails = ({
uploadMethod,
minimumMintPrice,
mintingTokenFromFactory,
importedMintingDetails,
isPresale,
whitelistStartDate,
}: MintingDetailsProps) => {
const wallet = useWallet()
const { timezone } = useGlobalSettings()
const [timestamp, setTimestamp] = useState<Date | undefined>()
const [selectedMintToken, setSelectedMintToken] = useState<TokenInfo | undefined>(stars)
@@ -101,6 +109,7 @@ export const MintingDetails = ({
paymentAddress: paymentAddressState.value.trim(),
selectedMintToken,
}
console.log('Timestamp:', timestamp?.getTime())
onChange(data)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [
@@ -113,6 +122,24 @@ export const MintingDetails = ({
selectedMintToken,
])
useEffect(() => {
if (importedMintingDetails) {
numberOfTokensState.onChange(importedMintingDetails.numTokens)
unitPriceState.onChange(Number(importedMintingDetails.unitPrice) / 1_000_000)
perAddressLimitState.onChange(importedMintingDetails.perAddressLimit)
setTimestamp(new Date(Number(importedMintingDetails.startTime) / 1_000_000))
paymentAddressState.onChange(importedMintingDetails.paymentAddress ? importedMintingDetails.paymentAddress : '')
setSelectedMintToken(tokensList.find((token) => token.id === importedMintingDetails.selectedMintToken?.id))
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [importedMintingDetails])
useEffect(() => {
if (isPresale) {
setTimestamp(whitelistStartDate ? new Date(Number(whitelistStartDate) / 1_000_000) : undefined)
}
}, [whitelistStartDate, isPresale])
return (
<div>
<FormGroup subtitle="Information about your minting settings" title="Minting Details">
@@ -122,14 +149,18 @@ export const MintingDetails = ({
isRequired
value={uploadMethod === 'new' ? numberOfTokens : numberOfTokensState.value}
/>
<div className="flex flex-row items-center">
<div className="flex flex-row items-end">
<NumberInput {...unitPriceState} isRequired />
<select
className="py-[9px] px-4 mt-14 ml-2 placeholder:text-white/50 bg-white/10 rounded border-2 border-white/20 focus:ring focus:ring-plumbus-20"
className="py-[9px] px-4 ml-2 placeholder:text-white/50 bg-white/10 rounded border-2 border-white/20 focus:ring focus:ring-plumbus-20"
onChange={(e) => setSelectedMintToken(tokensList.find((t) => t.displayName === e.target.value))}
value={selectedMintToken?.displayName}
>
{vendingMinterList
.filter((minter) => minter.factoryAddress !== undefined && minter.updatable === false)
.filter(
(minter) =>
minter.factoryAddress !== undefined && minter.updatable === false && minter.featured === false,
)
.map((minter) => (
<option key={minter.id} className="bg-black" value={minter.supportedToken.displayName}>
{minter.supportedToken.displayName}
@@ -139,8 +170,34 @@ export const MintingDetails = ({
</div>
<NumberInput {...perAddressLimitState} isRequired />
<FormControl htmlId="timestamp" isRequired subtitle="Minting start time (local)" title="Start Time">
<InputDateTime minDate={new Date()} onChange={(date) => setTimestamp(date)} value={timestamp} />
<FormControl
htmlId="timestamp"
isRequired
subtitle={`Minting start time ${isPresale ? '(is dictated by whitelist start time)' : ''} ${
timezone === 'Local' ? '(local)' : '(UTC)'
}`}
title="Start Time"
>
<InputDateTime
disabled={isPresale}
minDate={
timezone === 'Local' ? new Date() : new Date(Date.now() + new Date().getTimezoneOffset() * 60 * 1000)
}
onChange={(date) =>
date
? setTimestamp(
timezone === 'Local' ? date : new Date(date.getTime() - new Date().getTimezoneOffset() * 60 * 1000),
)
: setTimestamp(undefined)
}
value={
timezone === 'Local'
? timestamp
: timestamp
? new Date(timestamp.getTime() + new Date().getTimezoneOffset() * 60 * 1000)
: undefined
}
/>
</FormControl>
</FormGroup>
<TextInput className="p-4 mt-5" {...paymentAddressState} />
@@ -1,14 +1,15 @@
import { Conditional } from 'components/Conditional'
import { FormGroup } from 'components/FormGroup'
import { useInputState } from 'components/forms/FormInput.hooks'
import { useWallet } from 'contexts/wallet'
import React, { useEffect, useState } from 'react'
import { useWallet } from 'utils/wallet'
import { resolveAddress } from '../../../utils/resolveAddress'
import { NumberInput, TextInput } from '../../forms/FormInput'
interface RoyaltyDetailsProps {
onChange: (data: RoyaltyDetailsDataProps) => void
importedRoyaltyDetails?: RoyaltyDetailsDataProps
}
export interface RoyaltyDetailsDataProps {
@@ -19,7 +20,7 @@ export interface RoyaltyDetailsDataProps {
type RoyaltyState = 'none' | 'new'
export const RoyaltyDetails = ({ onChange }: RoyaltyDetailsProps) => {
export const RoyaltyDetails = ({ onChange, importedRoyaltyDetails }: RoyaltyDetailsProps) => {
const wallet = useWallet()
const [royaltyState, setRoyaltyState] = useState<RoyaltyState>('none')
@@ -60,6 +61,15 @@ export const RoyaltyDetails = ({ onChange }: RoyaltyDetailsProps) => {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [royaltyState, royaltyPaymentAddressState.value, royaltyShareState.value])
useEffect(() => {
if (importedRoyaltyDetails) {
setRoyaltyState(importedRoyaltyDetails.royaltyType)
royaltyPaymentAddressState.onChange(importedRoyaltyDetails.paymentAddress)
royaltyShareState.onChange(importedRoyaltyDetails.share.toString())
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [importedRoyaltyDetails])
return (
<div className="py-3 px-8 rounded border-2 border-white/20">
<div className="flex justify-center">
@@ -1,4 +1,5 @@
/* eslint-disable eslint-comments/disable-enable-pair */
/* eslint-disable array-callback-return */
/* eslint-disable no-nested-ternary */
/* eslint-disable no-misleading-character-class */
/* eslint-disable no-control-regex */
@@ -19,6 +20,9 @@ import type { ChangeEvent } from 'react'
import { useEffect, useRef, useState } from 'react'
import { toast } from 'react-hot-toast'
import type { UploadServiceType } from 'services/upload'
import { NFT_STORAGE_DEFAULT_API_KEY } from 'utils/constants'
import type { AssetType } from 'utils/getAssetType'
import { getAssetType } from 'utils/getAssetType'
import { uid } from 'utils/random'
import { naturalCompare } from 'utils/sort'
@@ -31,11 +35,14 @@ interface UploadDetailsProps {
onChange: (value: UploadDetailsDataProps) => void
minterType: MinterType
baseMinterAcquisitionMethod?: BaseMinterAcquisitionMethod
importedUploadDetails?: UploadDetailsDataProps
}
export interface UploadDetailsDataProps {
assetFiles: File[]
metadataFiles: File[]
thumbnailFiles?: File[]
thumbnailCompatibleAssetFileNames?: string[]
uploadService: UploadServiceType
nftStorageApiKey?: string
pinataApiKey?: string
@@ -46,18 +53,27 @@ export interface UploadDetailsDataProps {
baseMinterMetadataFile?: File
}
export const UploadDetails = ({ onChange, minterType, baseMinterAcquisitionMethod }: UploadDetailsProps) => {
export const UploadDetails = ({
onChange,
minterType,
baseMinterAcquisitionMethod,
importedUploadDetails,
}: UploadDetailsProps) => {
const [assetFilesArray, setAssetFilesArray] = useState<File[]>([])
const [metadataFilesArray, setMetadataFilesArray] = useState<File[]>([])
const [thumbnailCompatibleAssetFileNames, setThumbnailCompatibleAssetFileNames] = useState<string[]>([])
const [thumbnailFilesArray, setThumbnailFilesArray] = useState<File[]>([])
const [uploadMethod, setUploadMethod] = useState<UploadMethod>('new')
const [uploadService, setUploadService] = useState<UploadServiceType>('nft-storage')
const [metadataFileArrayIndex, setMetadataFileArrayIndex] = useState(0)
const [refreshMetadata, setRefreshMetadata] = useState(false)
const [useDefaultApiKey, setUseDefaultApiKey] = useState(false)
const [baseMinterMetadataFile, setBaseMinterMetadataFile] = useState<File | undefined>()
const assetFilesRef = useRef<HTMLInputElement | null>(null)
const metadataFilesRef = useRef<HTMLInputElement | null>(null)
const thumbnailFilesRef = useRef<HTMLInputElement | null>(null)
const nftStorageApiKeyState = useInputState({
id: 'nft-storage-api-key',
@@ -100,15 +116,27 @@ export const UploadDetails = ({ onChange, minterType, baseMinterAcquisitionMetho
const selectAssets = (event: ChangeEvent<HTMLInputElement>) => {
setAssetFilesArray([])
setMetadataFilesArray([])
setThumbnailFilesArray([])
setThumbnailCompatibleAssetFileNames([])
if (event.target.files === null) return
if (minterType === 'vending' || (minterType === 'base' && event.target.files.length > 1)) {
const thumbnailCompatibleAssetTypes: AssetType[] = ['video', 'audio', 'html', 'document']
const thumbnailCompatibleFileNamesList: string[] = []
if (minterType === 'vending') {
//sort the files
const sortedFiles = Array.from(event.target.files).sort((a, b) => naturalCompare(a.name, b.name))
//check if the sorted file names are in numerical order
const sortedFileNames = sortedFiles.map((file) => file.name.split('.')[0])
sortedFiles.map((file) => {
if (thumbnailCompatibleAssetTypes.includes(getAssetType(file.name))) {
thumbnailCompatibleFileNamesList.push(file.name.split('.')[0])
}
})
setThumbnailCompatibleAssetFileNames(thumbnailCompatibleFileNamesList)
console.log('Thumbnail Compatible Files: ', thumbnailCompatibleFileNamesList)
for (let i = 0; i < sortedFileNames.length; i++) {
if (isNaN(Number(sortedFileNames[i])) || parseInt(sortedFileNames[i]) !== i + 1) {
toast.error('The file names should be in numerical order starting from 1.')
setThumbnailCompatibleAssetFileNames([])
addLogItem({
id: uid(),
message: 'The file names should be in numerical order starting from 1.',
@@ -120,7 +148,46 @@ export const UploadDetails = ({ onChange, minterType, baseMinterAcquisitionMetho
return
}
}
} else if (minterType === 'base' && event.target.files.length > 1) {
//sort the files
const sortedFiles = Array.from(event.target.files).sort((a, b) => naturalCompare(a.name, b.name))
//check if the sorted file names are in numerical order
const sortedFileNames = sortedFiles.map((file) => file.name.split('.')[0])
sortedFiles.map((file) => {
if (thumbnailCompatibleAssetTypes.includes(getAssetType(file.name))) {
thumbnailCompatibleFileNamesList.push(file.name.split('.')[0])
}
})
setThumbnailCompatibleAssetFileNames(thumbnailCompatibleFileNamesList)
console.log('Thumbnail Compatible Files: ', thumbnailCompatibleFileNamesList)
for (let i = 0; i < sortedFileNames.length - 1; i++) {
if (
isNaN(Number(sortedFileNames[i])) ||
isNaN(Number(sortedFileNames[i + 1])) ||
parseInt(sortedFileNames[i]) !== parseInt(sortedFileNames[i + 1]) - 1
) {
toast.error('The file names should be in numerical order.')
setThumbnailCompatibleAssetFileNames([])
addLogItem({
id: uid(),
message: 'The file names should be in numerical order.',
type: 'Error',
timestamp: new Date(),
})
//clear the input
event.target.value = ''
return
}
}
} else if (minterType === 'base' && event.target.files.length === 1) {
if (thumbnailCompatibleAssetTypes.includes(getAssetType(event.target.files[0].name))) {
thumbnailCompatibleFileNamesList.push(event.target.files[0].name.split('.')[0])
}
setThumbnailCompatibleAssetFileNames(thumbnailCompatibleFileNamesList)
console.log('Thumbnail Compatible Files: ', thumbnailCompatibleFileNamesList)
}
let loadedFileCount = 0
const files: File[] = []
let reader: FileReader
@@ -129,7 +196,9 @@ export const UploadDetails = ({ onChange, minterType, baseMinterAcquisitionMetho
reader.onload = (e) => {
if (!e.target?.result) return toast.error('Error parsing file.')
if (!event.target.files) return toast.error('No files selected.')
const assetFile = new File([e.target.result], event.target.files[i].name, { type: 'image/jpg' })
const assetFile = new File([e.target.result], event.target.files[i].name.replaceAll('#', ''), {
type: 'image/jpg',
})
files.push(assetFile)
}
reader.readAsArrayBuffer(event.target.files[i])
@@ -153,7 +222,23 @@ export const UploadDetails = ({ onChange, minterType, baseMinterAcquisitionMetho
event.target.value = ''
return toast.error('The number of metadata files should be equal to the number of asset files.')
}
if (minterType === 'vending' || (minterType === 'base' && assetFilesArray.length > 1)) {
// compare the first file name for asset and metadata files
if (
minterType === 'base' &&
assetFilesArray.length > 1 &&
event.target.files[0].name.split('.')[0] !== assetFilesArray[0].name.split('.')[0]
) {
event.target.value = ''
toast.error('The metadata file names should match the asset file names.')
addLogItem({
id: uid(),
message: 'The metadata file names should match the asset file names.',
type: 'Error',
timestamp: new Date(),
})
return
}
if (minterType === 'vending') {
//sort the files
const sortedFiles = Array.from(event.target.files).sort((a, b) => naturalCompare(a.name, b.name))
//check if the sorted file names are in numerical order
@@ -171,6 +256,28 @@ export const UploadDetails = ({ onChange, minterType, baseMinterAcquisitionMetho
return
}
}
} else if (minterType === 'base' && assetFilesArray.length > 1) {
//sort the files
const sortedFiles = Array.from(event.target.files).sort((a, b) => naturalCompare(a.name, b.name))
//check if the sorted file names are in numerical order
const sortedFileNames = sortedFiles.map((file) => file.name.split('.')[0])
for (let i = 0; i < sortedFileNames.length - 1; i++) {
if (
isNaN(Number(sortedFileNames[i])) ||
isNaN(Number(sortedFileNames[i + 1])) ||
parseInt(sortedFileNames[i]) !== parseInt(sortedFileNames[i + 1]) - 1
) {
toast.error('The file names should be in numerical order.')
addLogItem({
id: uid(),
message: 'The file names should be in numerical order.',
type: 'Error',
timestamp: new Date(),
})
event.target.value = ''
return
}
}
}
let loadedFileCount = 0
const files: File[] = []
@@ -180,7 +287,9 @@ export const UploadDetails = ({ onChange, minterType, baseMinterAcquisitionMetho
reader.onload = async (e) => {
if (!e.target?.result) return toast.error('Error parsing file.')
if (!event.target.files) return toast.error('No files selected.')
const metadataFile = new File([e.target.result], event.target.files[i].name, { type: 'application/json' })
const metadataFile = new File([e.target.result], event.target.files[i].name.replaceAll('#', ''), {
type: 'application/json',
})
files.push(metadataFile)
try {
const parsedMetadata = JSON.parse(await metadataFile.text())
@@ -224,11 +333,61 @@ export const UploadDetails = ({ onChange, minterType, baseMinterAcquisitionMetho
const regex =
/[\0-\x1F\x7F-\x9F\xAD\u0378\u0379\u037F-\u0383\u038B\u038D\u03A2\u0528-\u0530\u0557\u0558\u0560\u0588\u058B-\u058E\u0590\u05C8-\u05CF\u05EB-\u05EF\u05F5-\u0605\u061C\u061D\u06DD\u070E\u070F\u074B\u074C\u07B2-\u07BF\u07FB-\u07FF\u082E\u082F\u083F\u085C\u085D\u085F-\u089F\u08A1\u08AD-\u08E3\u08FF\u0978\u0980\u0984\u098D\u098E\u0991\u0992\u09A9\u09B1\u09B3-\u09B5\u09BA\u09BB\u09C5\u09C6\u09C9\u09CA\u09CF-\u09D6\u09D8-\u09DB\u09DE\u09E4\u09E5\u09FC-\u0A00\u0A04\u0A0B-\u0A0E\u0A11\u0A12\u0A29\u0A31\u0A34\u0A37\u0A3A\u0A3B\u0A3D\u0A43-\u0A46\u0A49\u0A4A\u0A4E-\u0A50\u0A52-\u0A58\u0A5D\u0A5F-\u0A65\u0A76-\u0A80\u0A84\u0A8E\u0A92\u0AA9\u0AB1\u0AB4\u0ABA\u0ABB\u0AC6\u0ACA\u0ACE\u0ACF\u0AD1-\u0ADF\u0AE4\u0AE5\u0AF2-\u0B00\u0B04\u0B0D\u0B0E\u0B11\u0B12\u0B29\u0B31\u0B34\u0B3A\u0B3B\u0B45\u0B46\u0B49\u0B4A\u0B4E-\u0B55\u0B58-\u0B5B\u0B5E\u0B64\u0B65\u0B78-\u0B81\u0B84\u0B8B-\u0B8D\u0B91\u0B96-\u0B98\u0B9B\u0B9D\u0BA0-\u0BA2\u0BA5-\u0BA7\u0BAB-\u0BAD\u0BBA-\u0BBD\u0BC3-\u0BC5\u0BC9\u0BCE\u0BCF\u0BD1-\u0BD6\u0BD8-\u0BE5\u0BFB-\u0C00\u0C04\u0C0D\u0C11\u0C29\u0C34\u0C3A-\u0C3C\u0C45\u0C49\u0C4E-\u0C54\u0C57\u0C5A-\u0C5F\u0C64\u0C65\u0C70-\u0C77\u0C80\u0C81\u0C84\u0C8D\u0C91\u0CA9\u0CB4\u0CBA\u0CBB\u0CC5\u0CC9\u0CCE-\u0CD4\u0CD7-\u0CDD\u0CDF\u0CE4\u0CE5\u0CF0\u0CF3-\u0D01\u0D04\u0D0D\u0D11\u0D3B\u0D3C\u0D45\u0D49\u0D4F-\u0D56\u0D58-\u0D5F\u0D64\u0D65\u0D76-\u0D78\u0D80\u0D81\u0D84\u0D97-\u0D99\u0DB2\u0DBC\u0DBE\u0DBF\u0DC7-\u0DC9\u0DCB-\u0DCE\u0DD5\u0DD7\u0DE0-\u0DF1\u0DF5-\u0E00\u0E3B-\u0E3E\u0E5C-\u0E80\u0E83\u0E85\u0E86\u0E89\u0E8B\u0E8C\u0E8E-\u0E93\u0E98\u0EA0\u0EA4\u0EA6\u0EA8\u0EA9\u0EAC\u0EBA\u0EBE\u0EBF\u0EC5\u0EC7\u0ECE\u0ECF\u0EDA\u0EDB\u0EE0-\u0EFF\u0F48\u0F6D-\u0F70\u0F98\u0FBD\u0FCD\u0FDB-\u0FFF\u10C6\u10C8-\u10CC\u10CE\u10CF\u1249\u124E\u124F\u1257\u1259\u125E\u125F\u1289\u128E\u128F\u12B1\u12B6\u12B7\u12BF\u12C1\u12C6\u12C7\u12D7\u1311\u1316\u1317\u135B\u135C\u137D-\u137F\u139A-\u139F\u13F5-\u13FF\u169D-\u169F\u16F1-\u16FF\u170D\u1715-\u171F\u1737-\u173F\u1754-\u175F\u176D\u1771\u1774-\u177F\u17DE\u17DF\u17EA-\u17EF\u17FA-\u17FF\u180F\u181A-\u181F\u1878-\u187F\u18AB-\u18AF\u18F6-\u18FF\u191D-\u191F\u192C-\u192F\u193C-\u193F\u1941-\u1943\u196E\u196F\u1975-\u197F\u19AC-\u19AF\u19CA-\u19CF\u19DB-\u19DD\u1A1C\u1A1D\u1A5F\u1A7D\u1A7E\u1A8A-\u1A8F\u1A9A-\u1A9F\u1AAE-\u1AFF\u1B4C-\u1B4F\u1B7D-\u1B7F\u1BF4-\u1BFB\u1C38-\u1C3A\u1C4A-\u1C4C\u1C80-\u1CBF\u1CC8-\u1CCF\u1CF7-\u1CFF\u1DE7-\u1DFB\u1F16\u1F17\u1F1E\u1F1F\u1F46\u1F47\u1F4E\u1F4F\u1F58\u1F5A\u1F5C\u1F5E\u1F7E\u1F7F\u1FB5\u1FC5\u1FD4\u1FD5\u1FDC\u1FF0\u1FF1\u1FF5\u1FFF\u200B-\u200F\u2020-\u202E\u2060-\u206F\u2072\u2073\u208F\u209D-\u209F\u20BB-\u20CF\u20F1-\u20FF\u218A-\u218F\u23F4-\u23FF\u2427-\u243F\u244B-\u245F\u2700\u2B4D-\u2B4F\u2B5A-\u2BFF\u2C2F\u2C5F\u2CF4-\u2CF8\u2D26\u2D28-\u2D2C\u2D2E\u2D2F\u2D68-\u2D6E\u2D71-\u2D7E\u2D97-\u2D9F\u2DA7\u2DAF\u2DB7\u2DBF\u2DC7\u2DCF\u2DD7\u2DDF\u2E3C-\u2E7F\u2E9A\u2EF4-\u2EFF\u2FD6-\u2FEF\u2FFC-\u2FFF\u3040\u3097\u3098\u3100-\u3104\u312E-\u3130\u318F\u31BB-\u31BF\u31E4-\u31EF\u321F\u32FF\u4DB6-\u4DBF\u9FCD-\u9FFF\uA48D-\uA48F\uA4C7-\uA4CF\uA62C-\uA63F\uA698-\uA69E\uA6F8-\uA6FF\uA78F\uA794-\uA79F\uA7AB-\uA7F7\uA82C-\uA82F\uA83A-\uA83F\uA878-\uA87F\uA8C5-\uA8CD\uA8DA-\uA8DF\uA8FC-\uA8FF\uA954-\uA95E\uA97D-\uA97F\uA9CE\uA9DA-\uA9DD\uA9E0-\uA9FF\uAA37-\uAA3F\uAA4E\uAA4F\uAA5A\uAA5B\uAA7C-\uAA7F\uAAC3-\uAADA\uAAF7-\uAB00\uAB07\uAB08\uAB0F\uAB10\uAB17-\uAB1F\uAB27\uAB2F-\uABBF\uABEE\uABEF\uABFA-\uABFF\uD7A4-\uD7AF\uD7C7-\uD7CA\uD7FC-\uF8FF\uFA6E\uFA6F\uFADA-\uFAFF\uFB07-\uFB12\uFB18-\uFB1C\uFB37\uFB3D\uFB3F\uFB42\uFB45\uFBC2-\uFBD2\uFD40-\uFD4F\uFD90\uFD91\uFDC8-\uFDEF\uFDFE\uFDFF\uFE1A-\uFE1F\uFE27-\uFE2F\uFE53\uFE67\uFE6C-\uFE6F\uFE75\uFEFD-\uFF00\uFFBF-\uFFC1\uFFC8\uFFC9\uFFD0\uFFD1\uFFD8\uFFD9\uFFDD-\uFFDF\uFFE7\uFFEF-\uFFFB\uFFFE\uFFFF]/g
const selectThumbnails = (event: ChangeEvent<HTMLInputElement>) => {
setThumbnailFilesArray([])
if (event.target.files === null) return
// if (minterType === 'vending' || (minterType === 'base' && thumbnailCompatibleAssetFileNames.length > 1)) {
const sortedFiles = Array.from(event.target.files).sort((a, b) => naturalCompare(a.name, b.name))
const sortedFileNames = sortedFiles.map((file) => file.name.split('.')[0])
// make sure the sorted file names match thumbnail compatible asset file names
for (let i = 0; i < thumbnailCompatibleAssetFileNames.length; i++) {
if (minterType === 'base' && assetFilesArray.length === 1) break
if (sortedFileNames[i] !== thumbnailCompatibleAssetFileNames[i]) {
toast.error('The thumbnail file names should match the thumbnail compatible asset file names.')
addLogItem({
id: uid(),
message: 'The thumbnail file names should match the thumbnail compatible asset file names.',
type: 'Error',
timestamp: new Date(),
})
//clear the input
event.target.value = ''
return
}
}
// }
let loadedFileCount = 0
const files: File[] = []
let reader: FileReader
for (let i = 0; i < event.target.files.length; i++) {
reader = new FileReader()
reader.onload = (e) => {
if (!e.target?.result) return toast.error('Error parsing file.')
if (!event.target.files) return toast.error('No files selected.')
const thumbnailFile = new File([e.target.result], event.target.files[i].name.replaceAll('#', ''), {
type: 'image/jpg',
})
files.push(thumbnailFile)
}
reader.readAsArrayBuffer(event.target.files[i])
reader.onloadend = () => {
if (!event.target.files) return toast.error('No file selected.')
loadedFileCount++
if (loadedFileCount === event.target.files.length) {
setThumbnailFilesArray(files.sort((a, b) => naturalCompare(a.name, b.name)))
}
}
}
}
useEffect(() => {
try {
const data: UploadDetailsDataProps = {
assetFiles: assetFilesArray,
metadataFiles: metadataFilesArray,
thumbnailFiles: thumbnailFilesArray,
thumbnailCompatibleAssetFileNames,
uploadService,
nftStorageApiKey: nftStorageApiKeyState.value,
pinataApiKey: pinataApiKeyState.value,
@@ -258,6 +417,8 @@ export const UploadDetails = ({ onChange, minterType, baseMinterAcquisitionMetho
}, [
assetFilesArray,
metadataFilesArray,
thumbnailFilesArray,
thumbnailCompatibleAssetFileNames,
uploadService,
nftStorageApiKeyState.value,
pinataApiKeyState.value,
@@ -274,10 +435,42 @@ export const UploadDetails = ({ onChange, minterType, baseMinterAcquisitionMetho
setMetadataFilesArray([])
if (assetFilesRef.current) assetFilesRef.current.value = ''
setAssetFilesArray([])
baseTokenUriState.onChange('')
coverImageUrlState.onChange('')
if (thumbnailFilesRef.current) thumbnailFilesRef.current.value = ''
setThumbnailFilesArray([])
setThumbnailCompatibleAssetFileNames([])
if (!importedUploadDetails || minterType === 'base') {
baseTokenUriState.onChange('')
coverImageUrlState.onChange('')
}
}, [uploadMethod, minterType, baseMinterAcquisitionMethod])
useEffect(() => {
if (importedUploadDetails) {
if (importedUploadDetails.uploadMethod === 'new') {
setUploadMethod('new')
setUploadService(importedUploadDetails.uploadService)
nftStorageApiKeyState.onChange(importedUploadDetails.nftStorageApiKey || '')
pinataApiKeyState.onChange(importedUploadDetails.pinataApiKey || '')
pinataSecretKeyState.onChange(importedUploadDetails.pinataSecretKey || '')
baseTokenUriState.onChange(importedUploadDetails.baseTokenURI || '')
coverImageUrlState.onChange(importedUploadDetails.imageUrl || '')
} else if (importedUploadDetails.uploadMethod === 'existing') {
setUploadMethod('existing')
baseTokenUriState.onChange(importedUploadDetails.baseTokenURI || '')
coverImageUrlState.onChange(importedUploadDetails.imageUrl || '')
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [importedUploadDetails])
useEffect(() => {
if (useDefaultApiKey) {
nftStorageApiKeyState.onChange(NFT_STORAGE_DEFAULT_API_KEY || '')
} else {
nftStorageApiKeyState.onChange('')
}
}, [useDefaultApiKey])
return (
<div className="justify-items-start mb-3 rounded border-2 border-white/20 flex-column">
<div className="flex justify-center">
@@ -433,7 +626,22 @@ export const UploadDetails = ({ onChange, minterType, baseMinterAcquisitionMetho
<div className="flex w-full">
<Conditional test={uploadService === 'nft-storage'}>
<TextInput {...nftStorageApiKeyState} className="w-full" />
<div className="flex-col w-full">
<TextInput {...nftStorageApiKeyState} className="w-full" disabled={useDefaultApiKey} />
<div className="flex-row mt-2 w-full form-control">
<label className="cursor-pointer label">
<span className="mr-2 font-bold">Use Default API Key</span>
<input
checked={useDefaultApiKey}
className={`${useDefaultApiKey ? `bg-stargaze` : `bg-gray-600`} checkbox`}
onClick={() => {
setUseDefaultApiKey(!useDefaultApiKey)
}}
type="checkbox"
/>
</label>
</div>
</div>
</Conditional>
<Conditional test={uploadService === 'pinata'}>
<TextInput {...pinataApiKeyState} className="w-full" />
@@ -472,7 +680,7 @@ export const UploadDetails = ({ onChange, minterType, baseMinterAcquisitionMetho
)}
>
<input
accept="image/*, audio/*, video/*, .html"
accept="image/*, audio/*, video/*, .html, .pdf"
className={clsx(
'file:py-2 file:px-4 file:mr-4 file:bg-plumbus-light file:rounded file:border-0 cursor-pointer',
'before:absolute before:inset-0 before:hover:bg-white/5 before:transition',
@@ -519,6 +727,38 @@ export const UploadDetails = ({ onChange, minterType, baseMinterAcquisitionMetho
</div>
</div>
)}
{thumbnailCompatibleAssetFileNames.length > 0 && (
<div>
<label
className="block mt-5 mr-1 mb-1 ml-8 w-full font-bold text-white dark:text-gray-300"
htmlFor="thumbnailFiles"
>
{thumbnailCompatibleAssetFileNames.length > 1
? 'Thumbnail Selection for Compatible Assets (optional)'
: 'Thumbnail Selection (optional)'}
</label>
<div
className={clsx(
'flex relative justify-center items-center mx-8 mt-2 space-y-4 w-full h-32',
'rounded border-2 border-white/20 border-dashed',
)}
>
<input
accept="image/*"
className={clsx(
'file:py-2 file:px-4 file:mr-4 file:bg-plumbus-light file:rounded file:border-0 cursor-pointer',
'before:absolute before:inset-0 before:hover:bg-white/5 before:transition',
)}
id="thumbnailFiles"
multiple
onChange={selectThumbnails}
ref={thumbnailFilesRef}
type="file"
/>
</div>
</div>
)}
<Conditional test={assetFilesArray.length >= 1}>
<MetadataModal
assetFile={assetFilesArray[metadataFileArrayIndex]}
@@ -1,5 +1,7 @@
/* eslint-disable eslint-comments/disable-enable-pair */
/* eslint-disable @typescript-eslint/no-unnecessary-condition */
/* eslint-disable no-nested-ternary */
import { Button } from 'components/Button'
import { FormControl } from 'components/FormControl'
import { FormGroup } from 'components/FormGroup'
import { AddressList } from 'components/forms/AddressList'
@@ -9,8 +11,10 @@ import { InputDateTime } from 'components/InputDateTime'
import type { WhitelistFlexMember } from 'components/WhitelistFlexUpload'
import { WhitelistFlexUpload } from 'components/WhitelistFlexUpload'
import type { TokenInfo } from 'config/token'
import { useGlobalSettings } from 'contexts/globalSettings'
import React, { useEffect, useState } from 'react'
import { isValidAddress } from 'utils/isValidAddress'
import { useWallet } from 'utils/wallet'
import { Conditional } from '../../Conditional'
import { AddressInput, NumberInput } from '../../forms/FormInput'
@@ -20,6 +24,7 @@ import { WhitelistUpload } from '../../WhitelistUpload'
interface WhitelistDetailsProps {
onChange: (data: WhitelistDetailsDataProps) => void
mintingTokenFromFactory?: TokenInfo
importedWhitelistDetails?: WhitelistDetailsDataProps
}
export interface WhitelistDetailsDataProps {
@@ -38,15 +43,23 @@ export interface WhitelistDetailsDataProps {
type WhitelistState = 'none' | 'existing' | 'new'
type WhitelistType = 'standard' | 'flex'
export type WhitelistType = 'standard' | 'flex' | 'merkletree'
export const WhitelistDetails = ({
onChange,
mintingTokenFromFactory,
importedWhitelistDetails,
}: WhitelistDetailsProps) => {
const wallet = useWallet()
const { timezone } = useGlobalSettings()
export const WhitelistDetails = ({ onChange, mintingTokenFromFactory }: WhitelistDetailsProps) => {
const [whitelistState, setWhitelistState] = useState<WhitelistState>('none')
const [whitelistType, setWhitelistType] = useState<WhitelistType>('standard')
const [startDate, setStartDate] = useState<Date | undefined>(undefined)
const [endDate, setEndDate] = useState<Date | undefined>(undefined)
const [whitelistStandardArray, setWhitelistStandardArray] = useState<string[]>([])
const [whitelistFlexArray, setWhitelistFlexArray] = useState<WhitelistFlexMember[]>([])
const [whitelistMerkleTreeArray, setWhitelistMerkleTreeArray] = useState<string[]>([])
const [adminsMutable, setAdminsMutable] = useState<boolean>(true)
const whitelistAddressState = useInputState({
@@ -85,16 +98,42 @@ export const WhitelistDetails = ({ onChange, mintingTokenFromFactory }: Whitelis
const addressListState = useAddressListState()
const whitelistFileOnChange = (data: string[]) => {
setWhitelistStandardArray(data)
if (whitelistType === 'standard') setWhitelistStandardArray(data)
if (whitelistType === 'merkletree') setWhitelistMerkleTreeArray(data)
}
const whitelistFlexFileOnChange = (whitelistData: WhitelistFlexMember[]) => {
setWhitelistFlexArray(whitelistData)
}
const downloadSampleWhitelistFlexFile = () => {
const csvData =
'address,mint_count\nstars153w5xhuqu3et29lgqk4dsynj6gjn96lr33wx4e,3\nstars1xkes5r2k8u3m3ayfpverlkcrq3k4jhdk8ws0uz,1\nstars1s8qx0zvz8yd6e4x0mqmqf7fr9vvfn622wtp3g3,2'
const blob = new Blob([csvData], { type: 'text/csv' })
const url = window.URL.createObjectURL(blob)
const a = document.createElement('a')
a.setAttribute('href', url)
a.setAttribute('download', 'sample_whitelist_flex.csv')
a.click()
}
const downloadSampleWhitelistFile = () => {
const txtData =
'stars153w5xhuqu3et29lgqk4dsynj6gjn96lr33wx4e\nstars1xkes5r2k8u3m3ayfpverlkcrq3k4jhdk8ws0uz\nstars1s8qx0zvz8yd6e4x0mqmqf7fr9vvfn622wtp3g3'
const blob = new Blob([txtData], { type: 'text/txt' })
const url = window.URL.createObjectURL(blob)
const a = document.createElement('a')
a.setAttribute('href', url)
a.setAttribute('download', 'sample_whitelist.txt')
a.click()
}
useEffect(() => {
setWhitelistStandardArray([])
setWhitelistFlexArray([])
if (!importedWhitelistDetails) {
setWhitelistStandardArray([])
setWhitelistFlexArray([])
setWhitelistMerkleTreeArray([])
}
}, [whitelistType])
useEffect(() => {
@@ -107,7 +146,12 @@ export const WhitelistDetails = ({ onChange, mintingTokenFromFactory }: Whitelis
.replace(/"/g, '')
.replace(/'/g, '')
.replace(/ /g, ''),
members: whitelistType === 'standard' ? whitelistStandardArray : whitelistFlexArray,
members:
whitelistType === 'standard'
? whitelistStandardArray
: whitelistType === 'merkletree'
? whitelistMerkleTreeArray
: whitelistFlexArray,
unitPrice: unitPriceState.value
? (Number(unitPriceState.value) * 1_000_000).toString()
: unitPriceState.value === 0
@@ -137,11 +181,75 @@ export const WhitelistDetails = ({ onChange, mintingTokenFromFactory }: Whitelis
endDate,
whitelistStandardArray,
whitelistFlexArray,
whitelistMerkleTreeArray,
whitelistState,
whitelistType,
addressListState.values,
adminsMutable,
])
// make the necessary changes with respect to imported whitelist details
useEffect(() => {
if (importedWhitelistDetails) {
setWhitelistState(importedWhitelistDetails.whitelistState)
setWhitelistType(importedWhitelistDetails.whitelistType)
whitelistAddressState.onChange(
importedWhitelistDetails.contractAddress ? importedWhitelistDetails.contractAddress : '',
)
unitPriceState.onChange(
importedWhitelistDetails.unitPrice ? Number(importedWhitelistDetails.unitPrice) / 1000000 : 0,
)
memberLimitState.onChange(importedWhitelistDetails.memberLimit ? importedWhitelistDetails.memberLimit : 0)
perAddressLimitState.onChange(
importedWhitelistDetails.perAddressLimit ? importedWhitelistDetails.perAddressLimit : 0,
)
setStartDate(
importedWhitelistDetails.startTime
? new Date(Number(importedWhitelistDetails.startTime) / 1_000_000)
: undefined,
)
setEndDate(
importedWhitelistDetails.endTime ? new Date(Number(importedWhitelistDetails.endTime) / 1_000_000) : undefined,
)
setAdminsMutable(importedWhitelistDetails.adminsMutable ? importedWhitelistDetails.adminsMutable : true)
importedWhitelistDetails.admins?.forEach((admin) => {
addressListState.reset()
addressListState.add({ address: admin })
})
if (importedWhitelistDetails.whitelistType === 'standard') {
setWhitelistStandardArray([])
importedWhitelistDetails.members?.forEach((member) => {
setWhitelistStandardArray((standardArray) => [...standardArray, member as string])
})
} else if (importedWhitelistDetails.whitelistType === 'merkletree') {
setWhitelistMerkleTreeArray([])
// importedWhitelistDetails.members?.forEach((member) => {
// setWhitelistMerkleTreeArray((merkleTreeArray) => [...merkleTreeArray, member as string])
// })
} else if (importedWhitelistDetails.whitelistType === 'flex') {
setWhitelistFlexArray([])
importedWhitelistDetails.members?.forEach((member) => {
setWhitelistFlexArray((flexArray) => [
...flexArray,
{
address: (member as WhitelistFlexMember).address,
mint_count: (member as WhitelistFlexMember).mint_count,
},
])
})
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [importedWhitelistDetails])
useEffect(() => {
if (whitelistState === 'new' && wallet.address) {
addressListState.reset()
addressListState.add({ address: wallet.address })
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [whitelistState, wallet.address])
return (
<div className="py-3 px-8 rounded border-2 border-white/20">
<div className="flex justify-center">
@@ -210,7 +318,7 @@ export const WhitelistDetails = ({ onChange, mintingTokenFromFactory }: Whitelis
</Conditional>
<Conditional test={whitelistState === 'new'}>
<div className="flex justify-between mb-5 ml-6 max-w-[300px] text-lg font-bold">
<div className="flex justify-between mb-5 ml-6 max-w-[500px] text-lg font-bold">
<div className="form-check form-check-inline">
<input
checked={whitelistType === 'standard'}
@@ -221,7 +329,7 @@ export const WhitelistDetails = ({ onChange, mintingTokenFromFactory }: Whitelis
setWhitelistType('standard')
}}
type="radio"
value="nft-storage"
value="standard"
/>
<label
className="inline-block py-1 px-2 text-gray peer-checked:text-white hover:text-white peer-checked:bg-black hover:rounded-sm peer-checked:border-b-2 hover:border-b-2 peer-checked:border-plumbus hover:border-plumbus cursor-pointer form-check-label"
@@ -250,29 +358,90 @@ export const WhitelistDetails = ({ onChange, mintingTokenFromFactory }: Whitelis
Whitelist Flex
</label>
</div>
<div className="form-check form-check-inline">
<input
checked={whitelistType === 'merkletree'}
className="peer sr-only"
id="inlineRadio9"
name="inlineRadioOptions9"
onClick={() => {
setWhitelistType('merkletree')
}}
type="radio"
value="merkletree"
/>
<label
className="inline-block py-1 px-2 text-gray peer-checked:text-white hover:text-white peer-checked:bg-black hover:rounded-sm peer-checked:border-b-2 hover:border-b-2 peer-checked:border-plumbus hover:border-plumbus cursor-pointer form-check-label"
htmlFor="inlineRadio9"
>
Whitelist Merkle Tree
</label>
</div>
</div>
<div className="grid grid-cols-2">
<FormGroup subtitle="Information about your minting settings" title="Whitelist Minting Details">
<NumberInput isRequired {...unitPriceState} />
<NumberInput isRequired {...memberLimitState} />
<Conditional test={whitelistType === 'standard'}>
<Conditional test={whitelistType !== 'merkletree'}>
<NumberInput isRequired {...memberLimitState} />
</Conditional>
<Conditional test={whitelistType === 'standard' || whitelistType === 'merkletree'}>
<NumberInput isRequired {...perAddressLimitState} />
</Conditional>
<FormControl
htmlId="start-date"
isRequired
subtitle="Start time for minting tokens to whitelisted addresses"
title="Start Time"
title={`Whitelist Start Time ${timezone === 'Local' ? '(local)' : '(UTC)'}`}
>
<InputDateTime minDate={new Date()} onChange={(date) => setStartDate(date)} value={startDate} />
<InputDateTime
minDate={
timezone === 'Local' ? new Date() : new Date(Date.now() + new Date().getTimezoneOffset() * 60 * 1000)
}
onChange={(date) =>
date
? setStartDate(
timezone === 'Local'
? date
: new Date(date.getTime() - new Date().getTimezoneOffset() * 60 * 1000),
)
: setStartDate(undefined)
}
value={
timezone === 'Local'
? startDate
: startDate
? new Date(startDate.getTime() + new Date().getTimezoneOffset() * 60 * 1000)
: undefined
}
/>
</FormControl>
<FormControl
htmlId="end-date"
isRequired
subtitle="End time for minting tokens to whitelisted addresses"
title="End Time"
subtitle="Whitelist End Time dictates when public sales will start"
title={`Whitelist End Time ${timezone === 'Local' ? '(local)' : '(UTC)'}`}
>
<InputDateTime minDate={new Date()} onChange={(date) => setEndDate(date)} value={endDate} />
<InputDateTime
minDate={
timezone === 'Local' ? new Date() : new Date(Date.now() + new Date().getTimezoneOffset() * 60 * 1000)
}
onChange={(date) =>
date
? setEndDate(
timezone === 'Local'
? date
: new Date(date.getTime() - new Date().getTimezoneOffset() * 60 * 1000),
)
: setEndDate(undefined)
}
value={
timezone === 'Local'
? endDate
: endDate
? new Date(endDate.getTime() + new Date().getTimezoneOffset() * 60 * 1000)
: undefined
}
/>
</FormControl>
</FormGroup>
<div>
@@ -290,7 +459,6 @@ export const WhitelistDetails = ({ onChange, mintingTokenFromFactory }: Whitelis
<div className="my-4 ml-4">
<AddressList
entries={addressListState.entries}
isRequired
onAdd={addressListState.add}
onChange={addressListState.update}
onRemove={addressListState.remove}
@@ -299,7 +467,17 @@ export const WhitelistDetails = ({ onChange, mintingTokenFromFactory }: Whitelis
/>
</div>
<Conditional test={whitelistType === 'standard'}>
<FormGroup subtitle="TXT file that contains the whitelisted addresses" title="Whitelist File">
<FormGroup
subtitle={
<div>
<span>TXT file that contains the whitelisted addresses</span>
<Button className="mt-2 text-sm text-white" onClick={downloadSampleWhitelistFile}>
Download Sample File
</Button>
</div>
}
title="Whitelist File"
>
<WhitelistUpload onChange={whitelistFileOnChange} />
</FormGroup>
<Conditional test={whitelistStandardArray.length > 0}>
@@ -308,7 +486,14 @@ export const WhitelistDetails = ({ onChange, mintingTokenFromFactory }: Whitelis
</Conditional>
<Conditional test={whitelistType === 'flex'}>
<FormGroup
subtitle="CSV file that contains the whitelisted addresses and their corresponding mint counts"
subtitle={
<div>
<span>CSV file that contains the whitelisted addresses and corresponding mint counts</span>
<Button className="mt-2 text-sm text-white" onClick={downloadSampleWhitelistFlexFile}>
Download Sample File
</Button>
</div>
}
title="Whitelist File"
>
<WhitelistFlexUpload onChange={whitelistFlexFileOnChange} />
@@ -317,6 +502,24 @@ export const WhitelistDetails = ({ onChange, mintingTokenFromFactory }: Whitelis
<JsonPreview content={whitelistFlexArray} initialState={false} title="File Contents" />
</Conditional>
</Conditional>
<Conditional test={whitelistType === 'merkletree'}>
<FormGroup
subtitle={
<div>
<span>TXT file that contains the whitelisted addresses</span>
<Button className="mt-2 text-sm text-white" onClick={downloadSampleWhitelistFile}>
Download Sample File
</Button>
</div>
}
title="Whitelist File"
>
<WhitelistUpload onChange={whitelistFileOnChange} />
</FormGroup>
<Conditional test={whitelistStandardArray.length > 0}>
<JsonPreview content={whitelistStandardArray} initialState title="File Contents" />
</Conditional>
</Conditional>
</div>
</div>
</Conditional>
+14 -2
View File
@@ -7,22 +7,25 @@ import { useInputState } from 'components/forms/FormInput.hooks'
import { JsonPreview } from 'components/JsonPreview'
import type { BaseMinterInstance } from 'contracts/baseMinter'
import type { OpenEditionMinterInstance } from 'contracts/openEditionMinter'
import type { RoyaltyRegistryInstance } from 'contracts/royaltyRegistry'
import type { SG721Instance } from 'contracts/sg721'
import type { VendingMinterInstance } from 'contracts/vendingMinter'
import { toast } from 'react-hot-toast'
import { useQuery } from 'react-query'
import { useWallet } from 'utils/wallet'
import { useWallet } from '../../../contexts/wallet'
import { resolveAddress } from '../../../utils/resolveAddress'
import type { MinterType } from '../actions/Combobox'
interface CollectionQueriesProps {
minterContractAddress: string
sg721ContractAddress: string
royaltyRegistryContractAddress: string
sg721Messages: SG721Instance | undefined
vendingMinterMessages: VendingMinterInstance | undefined
baseMinterMessages: BaseMinterInstance | undefined
openEditionMinterMessages: OpenEditionMinterInstance | undefined
royaltyRegistryMessages: RoyaltyRegistryInstance | undefined
minterType: MinterType
}
export const CollectionQueries = ({
@@ -33,6 +36,7 @@ export const CollectionQueries = ({
openEditionMinterMessages,
baseMinterMessages,
minterType,
royaltyRegistryMessages,
}: CollectionQueriesProps) => {
const wallet = useWallet()
@@ -65,9 +69,11 @@ export const CollectionQueries = ({
baseMinterMessages,
vendingMinterMessages,
openEditionMinterMessages,
royaltyRegistryMessages,
type,
tokenId,
address,
sg721ContractAddress,
] as const,
async ({ queryKey }) => {
const [
@@ -75,9 +81,11 @@ export const CollectionQueries = ({
_baseMinterMessages_,
_vendingMinterMessages,
_openEditionMinterMessages,
_royaltyRegistryMessages,
_type,
_tokenId,
_address,
_sg721ContractAddress,
] = queryKey
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const res = await resolveAddress(_address, wallet).then(async (resolvedAddress) => {
@@ -88,8 +96,10 @@ export const CollectionQueries = ({
baseMinterMessages: _baseMinterMessages_,
openEditionMinterMessages: _openEditionMinterMessages,
sg721Messages: _sg721Messages,
royaltyRegistryMessages: _royaltyRegistryMessages,
address: resolvedAddress,
type: _type,
sg721ContractAddress: _sg721ContractAddress,
})
return result
})
@@ -102,7 +112,9 @@ export const CollectionQueries = ({
toast.error(error.message, { style: { maxWidth: 'none' } })
}
},
enabled: Boolean(sg721ContractAddress && minterContractAddress && type),
enabled:
Boolean(type && type === 'infinity_swap_royalties' && sg721ContractAddress) ||
Boolean(sg721ContractAddress && minterContractAddress && type),
retry: false,
},
)
+34 -1
View File
@@ -1,7 +1,9 @@
import type { BaseMinterInstance } from 'contracts/baseMinter'
import type { OpenEditionMinterInstance } from 'contracts/openEditionMinter/contract'
import type { RoyaltyRegistryInstance } from 'contracts/royaltyRegistry'
import type { SG721Instance } from 'contracts/sg721'
import type { VendingMinterInstance } from 'contracts/vendingMinter'
import { INFINITY_SWAP_PROTOCOL_ADDRESS } from 'utils/constants'
export type QueryType = typeof QUERY_TYPES[number]
@@ -13,6 +15,7 @@ export const QUERY_TYPES = [
'total_mint_count',
'tokens',
// 'token_owners',
'infinity_swap_royalties',
'token_info',
'config',
'status',
@@ -35,6 +38,11 @@ export const VENDING_QUERY_LIST: QueryListItem[] = [
name: 'Mint Price',
description: `Get the price of minting a token.`,
},
{
id: 'infinity_swap_royalties',
name: 'Infinity Swap Royalty Details',
description: `Get the collection's royalty details for Infinity Swap`,
},
{
id: 'num_tokens',
name: 'Mintable Number of Tokens',
@@ -77,6 +85,11 @@ export const BASE_QUERY_LIST: QueryListItem[] = [
name: 'Tokens Minted to User',
description: `Get the number of tokens minted in the collection to a user.`,
},
{
id: 'infinity_swap_royalties',
name: 'Infinity Swap Royalty Details',
description: `Get the collection's royalty details for Infinity Swap`,
},
{
id: 'token_info',
name: 'Token Info',
@@ -104,6 +117,11 @@ export const OPEN_EDITION_QUERY_LIST: QueryListItem[] = [
name: 'Mint Price',
description: `Get the price of minting a token.`,
},
{
id: 'infinity_swap_royalties',
name: 'Infinity Swap Royalty Details',
description: `Get the collection's royalty details for Infinity Swap`,
},
{
id: 'tokens_minted_to_user',
name: 'Tokens Minted to User',
@@ -148,6 +166,8 @@ export type DispatchQueryArgs = {
vendingMinterMessages?: VendingMinterInstance
openEditionMinterMessages?: OpenEditionMinterInstance
sg721Messages?: SG721Instance
royaltyRegistryMessages?: RoyaltyRegistryInstance
sg721ContractAddress?: string
} & (
| { type: undefined }
| { type: Select<'collection_info'> }
@@ -156,6 +176,7 @@ export type DispatchQueryArgs = {
| { type: Select<'tokens_minted_to_user'>; address: string }
| { type: Select<'total_mint_count'> }
| { type: Select<'tokens'>; address: string }
| { type: Select<'infinity_swap_royalties'> }
// | { type: Select<'token_owners'> }
| { type: Select<'token_info'>; tokenId: string }
| { type: Select<'config'> }
@@ -163,7 +184,13 @@ export type DispatchQueryArgs = {
)
export const dispatchQuery = async (args: DispatchQueryArgs) => {
const { baseMinterMessages, vendingMinterMessages, openEditionMinterMessages, sg721Messages } = args
const {
baseMinterMessages,
vendingMinterMessages,
openEditionMinterMessages,
sg721Messages,
royaltyRegistryMessages,
} = args
if (!baseMinterMessages || !vendingMinterMessages || !openEditionMinterMessages || !sg721Messages) {
throw new Error('Cannot execute actions')
}
@@ -189,6 +216,12 @@ export const dispatchQuery = async (args: DispatchQueryArgs) => {
// case 'token_owners': {
// return vendingMinterMessages.updateStartTime(txSigner, args.startTime)
// }
case 'infinity_swap_royalties': {
return royaltyRegistryMessages?.collectionRoyaltyProtocol(
args.sg721ContractAddress as string,
INFINITY_SWAP_PROTOCOL_ADDRESS,
)
}
case 'token_info': {
if (!args.tokenId) return
return sg721Messages.allNftInfo(args.tokenId)
@@ -0,0 +1,7 @@
import type { ExecuteListItem } from 'contracts/royaltyRegistry/messages/execute'
import { useState } from 'react'
export const useExecuteComboboxState = () => {
const [value, setValue] = useState<ExecuteListItem | null>(null)
return { value, onChange: (item: ExecuteListItem) => setValue(item) }
}
@@ -0,0 +1,92 @@
import { Combobox, Transition } from '@headlessui/react'
import clsx from 'clsx'
import { FormControl } from 'components/FormControl'
import type { ExecuteListItem } from 'contracts/royaltyRegistry/messages/execute'
import { EXECUTE_LIST } from 'contracts/royaltyRegistry/messages/execute'
import { matchSorter } from 'match-sorter'
import { Fragment, useState } from 'react'
import { FaChevronDown, FaInfoCircle } from 'react-icons/fa'
export interface ExecuteComboboxProps {
value: ExecuteListItem | null
onChange: (item: ExecuteListItem) => void
}
export const ExecuteCombobox = ({ value, onChange }: ExecuteComboboxProps) => {
const [search, setSearch] = useState('')
const filtered =
search === '' ? EXECUTE_LIST : matchSorter(EXECUTE_LIST, search, { keys: ['id', 'name', 'description'] })
return (
<Combobox
as={FormControl}
htmlId="message-type"
labelAs={Combobox.Label}
onChange={onChange}
subtitle="Contract execute message type"
title="Message Type"
value={value}
>
<div className="relative">
<Combobox.Input
className={clsx(
'w-full bg-white/10 rounded border-2 border-white/20 form-input',
'placeholder:text-white/50',
'focus:ring focus:ring-plumbus-20',
)}
displayValue={(val?: ExecuteListItem) => val?.name ?? ''}
id="message-type"
onChange={(event) => setSearch(event.target.value)}
placeholder="Select message type"
/>
<Combobox.Button
className={clsx(
'flex absolute inset-y-0 right-0 items-center p-4',
'opacity-50 hover:opacity-100 active:opacity-100',
)}
>
{({ open }) => <FaChevronDown aria-hidden="true" className={clsx('w-4 h-4', { 'rotate-180': open })} />}
</Combobox.Button>
<Transition afterLeave={() => setSearch('')} as={Fragment}>
<Combobox.Options
className={clsx(
'overflow-auto absolute z-10 mt-2 w-full max-h-[30vh]',
'bg-stone-800/80 rounded shadow-lg backdrop-blur-sm',
'divide-y divide-stone-500/50',
)}
>
{filtered.length < 1 && (
<span className="flex flex-col justify-center items-center p-4 text-sm text-center text-white/50">
Message type not found.
</span>
)}
{filtered.map((entry) => (
<Combobox.Option
key={entry.id}
className={({ active }) =>
clsx('flex relative flex-col py-2 px-4 space-y-1 cursor-pointer', { 'bg-stargaze-80': active })
}
value={entry}
>
<span className="font-bold">{entry.name}</span>
<span className="max-w-md text-sm">{entry.description}</span>
</Combobox.Option>
))}
</Combobox.Options>
</Transition>
</div>
{value && (
<div className="flex space-x-2 text-white/50">
<div className="mt-1">
<FaInfoCircle className="w-3 h-3" />
</div>
<span className="text-sm">{value.description}</span>
</div>
)}
</Combobox>
)
}
@@ -1,8 +1,11 @@
/* eslint-disable eslint-comments/disable-enable-pair */
/* eslint-disable no-nested-ternary */
import { Combobox, Transition } from '@headlessui/react'
import clsx from 'clsx'
import { FormControl } from 'components/FormControl'
import type { ExecuteListItem } from 'contracts/whitelist/messages/execute'
import { EXECUTE_LIST } from 'contracts/whitelist/messages/execute'
import { EXECUTE_LIST as WL_MERKLE_TREE_EXECUTE_LIST } from 'contracts/whitelistMerkleTree/messages/execute'
import { matchSorter } from 'match-sorter'
import { Fragment, useState } from 'react'
import { FaChevronDown, FaInfoCircle } from 'react-icons/fa'
@@ -10,13 +13,20 @@ import { FaChevronDown, FaInfoCircle } from 'react-icons/fa'
export interface ExecuteComboboxProps {
value: ExecuteListItem | null
onChange: (item: ExecuteListItem) => void
whitelistType?: 'standard' | 'flex' | 'merkletree'
}
export const ExecuteCombobox = ({ value, onChange }: ExecuteComboboxProps) => {
export const ExecuteCombobox = ({ value, onChange, whitelistType }: ExecuteComboboxProps) => {
const [search, setSearch] = useState('')
const filtered =
search === '' ? EXECUTE_LIST : matchSorter(EXECUTE_LIST, search, { keys: ['id', 'name', 'description'] })
whitelistType !== 'merkletree'
? search === ''
? EXECUTE_LIST
: matchSorter(EXECUTE_LIST, search, { keys: ['id', 'name', 'description'] })
: search === ''
? WL_MERKLE_TREE_EXECUTE_LIST
: matchSorter(WL_MERKLE_TREE_EXECUTE_LIST, search, { keys: ['id', 'name', 'description'] })
return (
<Combobox
+9 -4
View File
@@ -1,12 +1,12 @@
import { toUtf8 } from '@cosmjs/encoding'
import { FormControl } from 'components/FormControl'
import { AddressInput } from 'components/forms/FormInput'
import { useWallet } from 'contexts/wallet'
import { useEffect, useId, useMemo } from 'react'
import toast from 'react-hot-toast'
import { FaMinus, FaPlus } from 'react-icons/fa'
import { SG721_NAME_ADDRESS } from 'utils/constants'
import { isValidAddress } from 'utils/isValidAddress'
import { useWallet } from 'utils/wallet'
import { useInputState } from './FormInput.hooks'
@@ -31,6 +31,7 @@ export function AddressList(props: AddressListProps) {
{entries.map(([id], i) => (
<Address
key={`ib-${id}`}
defaultValue={entries[i][1]}
id={id}
isLast={i === entries.length - 1}
onAdd={onAdd}
@@ -48,9 +49,10 @@ export interface AddressProps {
onAdd: AddressListProps['onAdd']
onChange: AddressListProps['onChange']
onRemove: AddressListProps['onRemove']
defaultValue?: Address
}
export function Address({ id, isLast, onAdd, onChange, onRemove }: AddressProps) {
export function Address({ id, isLast, onAdd, onChange, onRemove, defaultValue }: AddressProps) {
const wallet = useWallet()
const Icon = useMemo(() => (isLast ? FaPlus : FaMinus), [isLast])
@@ -60,11 +62,14 @@ export function Address({ id, isLast, onAdd, onChange, onRemove }: AddressProps)
id: `ib-address-${htmlId}`,
name: `ib-address-${htmlId}`,
title: ``,
defaultValue: defaultValue?.address,
})
const resolveAddress = async (name: string) => {
if (!wallet.client) throw new Error('Wallet not connected')
await wallet.client
if (!wallet.isWalletConnected) throw new Error('Wallet not connected')
await (
await wallet.getCosmWasmClient()
)
.queryContractRaw(
SG721_NAME_ADDRESS,
toUtf8(
+33
View File
@@ -0,0 +1,33 @@
import { useMemo, useState } from 'react'
import { uid } from 'utils/random'
import type { DenomUnit } from './DenomUnits'
export function useDenomUnitsState() {
const [record, setRecord] = useState<Record<string, DenomUnit>>(() => ({}))
const entries = useMemo(() => Object.entries(record), [record])
const values = useMemo(() => Object.values(record), [record])
function add(attribute: DenomUnit = { denom: '', exponent: 0, aliases: '' }) {
setRecord((prev) => ({ ...prev, [uid()]: attribute }))
}
function update(key: string, attribute = record[key]) {
setRecord((prev) => ({ ...prev, [key]: attribute }))
}
function remove(key: string) {
return setRecord((prev) => {
const latest = { ...prev }
delete latest[key]
return latest
})
}
function reset() {
setRecord({})
}
return { entries, values, add, update, remove, reset }
}
+106
View File
@@ -0,0 +1,106 @@
import { FormControl } from 'components/FormControl'
import { NumberInput, TextInput } from 'components/forms/FormInput'
import { useEffect, useId, useMemo } from 'react'
import { FaMinus, FaPlus } from 'react-icons/fa'
import { useWallet } from 'utils/wallet'
import { useInputState, useNumberInputState } from './FormInput.hooks'
export interface DenomUnit {
denom: string
exponent: number
aliases: string
}
export interface DenomUnitsProps {
title: string
subtitle?: string
isRequired?: boolean
attributes: [string, DenomUnit][]
onAdd: () => void
onChange: (key: string, attribute: DenomUnit) => void
onRemove: (key: string) => void
}
export function DenomUnits(props: DenomUnitsProps) {
const { title, subtitle, isRequired, attributes, onAdd, onChange, onRemove } = props
return (
<FormControl isRequired={isRequired} subtitle={subtitle} title={title}>
{attributes.map(([id], i) => (
<DenomUnit
key={`ma-${id}`}
defaultAttribute={attributes[i][1]}
id={id}
isLast={i === attributes.length - 1}
onAdd={onAdd}
onChange={onChange}
onRemove={onRemove}
/>
))}
</FormControl>
)
}
export interface DenomUnitProps {
id: string
isLast: boolean
onAdd: DenomUnitsProps['onAdd']
onChange: DenomUnitsProps['onChange']
onRemove: DenomUnitsProps['onRemove']
defaultAttribute: DenomUnit
}
export function DenomUnit({ id, isLast, onAdd, onChange, onRemove, defaultAttribute }: DenomUnitProps) {
const wallet = useWallet()
const Icon = useMemo(() => (isLast ? FaPlus : FaMinus), [isLast])
const htmlId = useId()
const denomState = useInputState({
id: `ma-denom-${htmlId}`,
name: `ma-denom-${htmlId}`,
title: `Denom`,
defaultValue: defaultAttribute.denom,
})
const exponentState = useNumberInputState({
id: `mint-exponent-${htmlId}`,
name: `mint-exponent-${htmlId}`,
title: `Exponent`,
defaultValue: defaultAttribute.exponent,
})
const aliasesState = useInputState({
id: `ma-aliases-${htmlId}`,
name: `ma-aliases-${htmlId}`,
title: `Aliases`,
defaultValue: defaultAttribute.aliases,
placeholder: 'Comma separated aliases',
})
useEffect(() => {
onChange(id, { denom: denomState.value, exponent: exponentState.value, aliases: aliasesState.value })
}, [id, denomState.value, exponentState.value, aliasesState.value])
return (
<div className="grid relative md:grid-cols-[40%_18%_35_7%] lg:grid-cols-[55%_13%_25%_7%] 2xl:grid-cols-[55%_13%_25%_7%] 2xl:space-x-2">
<TextInput {...denomState} />
<NumberInput className="ml-2" {...exponentState} />
<TextInput className="ml-2" {...aliasesState} />
<div className="flex justify-end items-end pb-2 w-8">
<button
className="flex justify-center items-center p-2 bg-stargaze-80 hover:bg-plumbus-60 rounded-full"
onClick={(e) => {
e.preventDefault()
isLast ? onAdd() : onRemove(id)
}}
type="button"
>
<Icon className="w-3 h-3" />
</button>
</div>
</div>
)
}
+5 -3
View File
@@ -2,12 +2,12 @@ import { toUtf8 } from '@cosmjs/encoding'
import { FormControl } from 'components/FormControl'
import { AddressInput, NumberInput } from 'components/forms/FormInput'
import type { WhitelistFlexMember } from 'components/WhitelistFlexUpload'
import { useWallet } from 'contexts/wallet'
import { useEffect, useId, useMemo } from 'react'
import toast from 'react-hot-toast'
import { FaMinus, FaPlus } from 'react-icons/fa'
import { SG721_NAME_ADDRESS } from 'utils/constants'
import { isValidAddress } from 'utils/isValidAddress'
import { useWallet } from 'utils/wallet'
import { useInputState, useNumberInputState } from './FormInput.hooks'
@@ -75,8 +75,10 @@ export function FlexMemberAttribute({ id, isLast, onAdd, onChange, onRemove, def
}, [addressState.value, mintCountState.value, id])
const resolveAddress = async (name: string) => {
if (!wallet.client) throw new Error('Wallet not connected')
await wallet.client
if (!wallet.isWalletConnected) throw new Error('Wallet not connected')
await (
await wallet.getCosmWasmClient()
)
.queryContractRaw(
SG721_NAME_ADDRESS,
toUtf8(
+56 -7
View File
@@ -1,4 +1,5 @@
/* eslint-disable eslint-comments/disable-enable-pair */
/* eslint-disable no-nested-ternary */
/* eslint-disable jsx-a11y/media-has-caption */
/* eslint-disable @typescript-eslint/no-unnecessary-condition */
/* eslint-disable @typescript-eslint/no-unsafe-argument */
@@ -11,6 +12,7 @@ import { FormGroup } from 'components/FormGroup'
import { useInputState } from 'components/forms/FormInput.hooks'
import { InputDateTime } from 'components/InputDateTime'
import { Tooltip } from 'components/Tooltip'
import { useGlobalSettings } from 'contexts/globalSettings'
import { addLogItem } from 'contexts/log'
import type { ChangeEvent } from 'react'
import { useEffect, useMemo, useRef, useState } from 'react'
@@ -28,6 +30,7 @@ interface CollectionDetailsProps {
uploadMethod: UploadMethod
coverImageUrl: string
metadataStorageMethod: MetadataStorageMethod
importedCollectionDetails?: CollectionDetailsDataProps
}
export interface CollectionDetailsDataProps {
@@ -46,11 +49,13 @@ export const CollectionDetails = ({
uploadMethod,
metadataStorageMethod,
coverImageUrl,
importedCollectionDetails,
}: CollectionDetailsProps) => {
const [coverImage, setCoverImage] = useState<File | null>(null)
const [timestamp, setTimestamp] = useState<Date | undefined>()
const [explicit, setExplicit] = useState<boolean>(false)
const [updatable, setUpdatable] = useState<boolean>(false)
const { timezone } = useGlobalSettings()
const initialRender = useRef(true)
@@ -124,7 +129,9 @@ export const CollectionDetails = ({
reader.onload = (e) => {
if (!e.target?.result) return toast.error('Error parsing file.')
if (!event.target.files) return toast.error('No files selected.')
const imageFile = new File([e.target.result], event.target.files[0].name, { type: 'image/jpg' })
const imageFile = new File([e.target.result], event.target.files[0].name.replaceAll('#', ''), {
type: 'image/jpg',
})
setCoverImage(imageFile)
}
reader.readAsArrayBuffer(event.target.files[0])
@@ -152,6 +159,23 @@ export const CollectionDetails = ({
}
}, [updatable])
useEffect(() => {
if (importedCollectionDetails) {
nameState.onChange(importedCollectionDetails.name)
descriptionState.onChange(importedCollectionDetails.description)
symbolState.onChange(importedCollectionDetails.symbol)
//setCoverImage(importedCollectionDetails.imageFile[0] || null)
externalLinkState.onChange(importedCollectionDetails.externalLink || '')
setTimestamp(
importedCollectionDetails.startTradingTime
? new Date(parseInt(importedCollectionDetails.startTradingTime) / 1_000_000)
: undefined,
)
setExplicit(importedCollectionDetails.explicit)
setUpdatable(importedCollectionDetails.updatable)
}
}, [importedCollectionDetails])
const videoPreview = useMemo(() => {
if (uploadMethod === 'new' && coverImage) {
return (
@@ -204,10 +228,30 @@ export const CollectionDetails = ({
<FormControl
className={clsx('mt-2')}
htmlId="timestamp"
subtitle="Trading start time offset will be set as 2 weeks by default."
title="Trading Start Time (optional)"
subtitle="Trading start time offset will be set as 1 week by default."
title={`Trading Start Time (optional | ${timezone === 'Local' ? 'local)' : 'UTC)'}`}
>
<InputDateTime minDate={new Date()} onChange={(date) => setTimestamp(date)} value={timestamp} />
<InputDateTime
minDate={
timezone === 'Local' ? new Date() : new Date(Date.now() + new Date().getTimezoneOffset() * 60 * 1000)
}
onChange={(date) =>
date
? setTimestamp(
timezone === 'Local'
? date
: new Date(date.getTime() - new Date().getTimezoneOffset() * 60 * 1000),
)
: setTimestamp(undefined)
}
value={
timezone === 'Local'
? timestamp
: timestamp
? new Date(timestamp.getTime() + new Date().getTimezoneOffset() * 60 * 1000)
: undefined
}
/>
</FormControl>
</div>
</div>
@@ -318,7 +362,9 @@ export const CollectionDetails = ({
</div>
</div>
<Conditional
test={SG721_OPEN_EDITION_UPDATABLE_CODE_ID > 0 && OPEN_EDITION_UPDATABLE_FACTORY_ADDRESS !== undefined}
test={
false && SG721_OPEN_EDITION_UPDATABLE_CODE_ID > 0 && OPEN_EDITION_UPDATABLE_FACTORY_ADDRESS !== undefined
}
>
<Tooltip
backgroundColor="bg-blue-500"
@@ -332,9 +378,12 @@ export const CollectionDetails = ({
}
placement="bottom"
>
<div className={clsx('flex flex-col space-y-2 w-3/4 form-control')}>
<div className={clsx('flex flex-col space-y-2 w-full form-control')}>
<label className="justify-start cursor-pointer label">
<span className="mr-4 font-bold">Updatable Token Metadata</span>
<div className="flex flex-col">
<span className="mr-4 font-bold">Updatable Token Metadata</span>
<span className="mr-4">(Price: 2000 STARS)</span>
</div>
<input
checked={updatable}
className={`toggle ${updatable ? `bg-stargaze` : `bg-gray-600`}`}
+109 -4
View File
@@ -14,16 +14,21 @@ import type { ChangeEvent } from 'react'
import { useEffect, useMemo, useRef, useState } from 'react'
import { toast } from 'react-hot-toast'
import type { UploadServiceType } from 'services/upload'
import { NFT_STORAGE_DEFAULT_API_KEY } from 'utils/constants'
import type { AssetType } from 'utils/getAssetType'
import { getAssetType } from 'utils/getAssetType'
export type UploadMethod = 'new' | 'existing'
interface ImageUploadDetailsProps {
onChange: (value: ImageUploadDetailsDataProps) => void
importedImageUploadDetails?: ImageUploadDetailsDataProps
}
export interface ImageUploadDetailsDataProps {
assetFile: File | undefined
thumbnailFile?: File | undefined
isThumbnailCompatible?: boolean
uploadService: UploadServiceType
nftStorageApiKey?: string
pinataApiKey?: string
@@ -33,12 +38,16 @@ export interface ImageUploadDetailsDataProps {
coverImageUrl?: string
}
export const ImageUploadDetails = ({ onChange }: ImageUploadDetailsProps) => {
export const ImageUploadDetails = ({ onChange, importedImageUploadDetails }: ImageUploadDetailsProps) => {
const [assetFile, setAssetFile] = useState<File>()
const [thumbnailFile, setThumbnailFile] = useState<File>()
const [isThumbnailCompatible, setIsThumbnailCompatible] = useState<boolean>(false)
const [uploadMethod, setUploadMethod] = useState<UploadMethod>('new')
const [uploadService, setUploadService] = useState<UploadServiceType>('nft-storage')
const [useDefaultApiKey, setUseDefaultApiKey] = useState(false)
const assetFileRef = useRef<HTMLInputElement | null>(null)
const thumbnailFileRef = useRef<HTMLInputElement | null>(null)
const nftStorageApiKeyState = useInputState({
id: 'nft-storage-api-key',
@@ -78,8 +87,12 @@ export const ImageUploadDetails = ({ onChange }: ImageUploadDetailsProps) => {
defaultValue: '',
})
const thumbnailCompatibleAssetTypes: AssetType[] = ['video', 'audio', 'html']
const selectAsset = (event: ChangeEvent<HTMLInputElement>) => {
setAssetFile(undefined)
setThumbnailFile(undefined)
setIsThumbnailCompatible(false)
if (event.target.files === null) return
let selectedFile: File
@@ -87,17 +100,40 @@ export const ImageUploadDetails = ({ onChange }: ImageUploadDetailsProps) => {
reader.onload = (e) => {
if (!event.target.files) return toast.error('No file selected.')
if (!e.target?.result) return toast.error('Error parsing file.')
selectedFile = new File([e.target.result], event.target.files[0].name, { type: 'image/jpg' })
selectedFile = new File([e.target.result], event.target.files[0].name.replaceAll('#', ''), { type: 'image/jpg' })
}
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (event.target.files[0]) reader.readAsArrayBuffer(event.target.files[0])
else return toast.error('No file selected.')
reader.onloadend = () => {
if (!event.target.files) return toast.error('No file selected.')
if (thumbnailCompatibleAssetTypes.includes(getAssetType(event.target.files[0].name))) {
setIsThumbnailCompatible(true)
}
setAssetFile(selectedFile)
}
}
const selectThumbnail = (event: ChangeEvent<HTMLInputElement>) => {
setThumbnailFile(undefined)
if (event.target.files === null) return
let selectedFile: File
const reader = new FileReader()
reader.onload = (e) => {
if (!event.target.files) return toast.error('No file selected.')
if (!e.target?.result) return toast.error('Error parsing file.')
selectedFile = new File([e.target.result], event.target.files[0].name.replaceAll('#', ''), { type: 'image/*' })
}
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (event.target.files[0]) reader.readAsArrayBuffer(event.target.files[0])
else return toast.error('No file selected.')
reader.onloadend = () => {
if (!event.target.files) return toast.error('No file selected.')
setThumbnailFile(selectedFile)
}
}
const regex =
/[\0-\x1F\x7F-\x9F\xAD\u0378\u0379\u037F-\u0383\u038B\u038D\u03A2\u0528-\u0530\u0557\u0558\u0560\u0588\u058B-\u058E\u0590\u05C8-\u05CF\u05EB-\u05EF\u05F5-\u0605\u061C\u061D\u06DD\u070E\u070F\u074B\u074C\u07B2-\u07BF\u07FB-\u07FF\u082E\u082F\u083F\u085C\u085D\u085F-\u089F\u08A1\u08AD-\u08E3\u08FF\u0978\u0980\u0984\u098D\u098E\u0991\u0992\u09A9\u09B1\u09B3-\u09B5\u09BA\u09BB\u09C5\u09C6\u09C9\u09CA\u09CF-\u09D6\u09D8-\u09DB\u09DE\u09E4\u09E5\u09FC-\u0A00\u0A04\u0A0B-\u0A0E\u0A11\u0A12\u0A29\u0A31\u0A34\u0A37\u0A3A\u0A3B\u0A3D\u0A43-\u0A46\u0A49\u0A4A\u0A4E-\u0A50\u0A52-\u0A58\u0A5D\u0A5F-\u0A65\u0A76-\u0A80\u0A84\u0A8E\u0A92\u0AA9\u0AB1\u0AB4\u0ABA\u0ABB\u0AC6\u0ACA\u0ACE\u0ACF\u0AD1-\u0ADF\u0AE4\u0AE5\u0AF2-\u0B00\u0B04\u0B0D\u0B0E\u0B11\u0B12\u0B29\u0B31\u0B34\u0B3A\u0B3B\u0B45\u0B46\u0B49\u0B4A\u0B4E-\u0B55\u0B58-\u0B5B\u0B5E\u0B64\u0B65\u0B78-\u0B81\u0B84\u0B8B-\u0B8D\u0B91\u0B96-\u0B98\u0B9B\u0B9D\u0BA0-\u0BA2\u0BA5-\u0BA7\u0BAB-\u0BAD\u0BBA-\u0BBD\u0BC3-\u0BC5\u0BC9\u0BCE\u0BCF\u0BD1-\u0BD6\u0BD8-\u0BE5\u0BFB-\u0C00\u0C04\u0C0D\u0C11\u0C29\u0C34\u0C3A-\u0C3C\u0C45\u0C49\u0C4E-\u0C54\u0C57\u0C5A-\u0C5F\u0C64\u0C65\u0C70-\u0C77\u0C80\u0C81\u0C84\u0C8D\u0C91\u0CA9\u0CB4\u0CBA\u0CBB\u0CC5\u0CC9\u0CCE-\u0CD4\u0CD7-\u0CDD\u0CDF\u0CE4\u0CE5\u0CF0\u0CF3-\u0D01\u0D04\u0D0D\u0D11\u0D3B\u0D3C\u0D45\u0D49\u0D4F-\u0D56\u0D58-\u0D5F\u0D64\u0D65\u0D76-\u0D78\u0D80\u0D81\u0D84\u0D97-\u0D99\u0DB2\u0DBC\u0DBE\u0DBF\u0DC7-\u0DC9\u0DCB-\u0DCE\u0DD5\u0DD7\u0DE0-\u0DF1\u0DF5-\u0E00\u0E3B-\u0E3E\u0E5C-\u0E80\u0E83\u0E85\u0E86\u0E89\u0E8B\u0E8C\u0E8E-\u0E93\u0E98\u0EA0\u0EA4\u0EA6\u0EA8\u0EA9\u0EAC\u0EBA\u0EBE\u0EBF\u0EC5\u0EC7\u0ECE\u0ECF\u0EDA\u0EDB\u0EE0-\u0EFF\u0F48\u0F6D-\u0F70\u0F98\u0FBD\u0FCD\u0FDB-\u0FFF\u10C6\u10C8-\u10CC\u10CE\u10CF\u1249\u124E\u124F\u1257\u1259\u125E\u125F\u1289\u128E\u128F\u12B1\u12B6\u12B7\u12BF\u12C1\u12C6\u12C7\u12D7\u1311\u1316\u1317\u135B\u135C\u137D-\u137F\u139A-\u139F\u13F5-\u13FF\u169D-\u169F\u16F1-\u16FF\u170D\u1715-\u171F\u1737-\u173F\u1754-\u175F\u176D\u1771\u1774-\u177F\u17DE\u17DF\u17EA-\u17EF\u17FA-\u17FF\u180F\u181A-\u181F\u1878-\u187F\u18AB-\u18AF\u18F6-\u18FF\u191D-\u191F\u192C-\u192F\u193C-\u193F\u1941-\u1943\u196E\u196F\u1975-\u197F\u19AC-\u19AF\u19CA-\u19CF\u19DB-\u19DD\u1A1C\u1A1D\u1A5F\u1A7D\u1A7E\u1A8A-\u1A8F\u1A9A-\u1A9F\u1AAE-\u1AFF\u1B4C-\u1B4F\u1B7D-\u1B7F\u1BF4-\u1BFB\u1C38-\u1C3A\u1C4A-\u1C4C\u1C80-\u1CBF\u1CC8-\u1CCF\u1CF7-\u1CFF\u1DE7-\u1DFB\u1F16\u1F17\u1F1E\u1F1F\u1F46\u1F47\u1F4E\u1F4F\u1F58\u1F5A\u1F5C\u1F5E\u1F7E\u1F7F\u1FB5\u1FC5\u1FD4\u1FD5\u1FDC\u1FF0\u1FF1\u1FF5\u1FFF\u200B-\u200F\u2020-\u202E\u2060-\u206F\u2072\u2073\u208F\u209D-\u209F\u20BB-\u20CF\u20F1-\u20FF\u218A-\u218F\u23F4-\u23FF\u2427-\u243F\u244B-\u245F\u2700\u2B4D-\u2B4F\u2B5A-\u2BFF\u2C2F\u2C5F\u2CF4-\u2CF8\u2D26\u2D28-\u2D2C\u2D2E\u2D2F\u2D68-\u2D6E\u2D71-\u2D7E\u2D97-\u2D9F\u2DA7\u2DAF\u2DB7\u2DBF\u2DC7\u2DCF\u2DD7\u2DDF\u2E3C-\u2E7F\u2E9A\u2EF4-\u2EFF\u2FD6-\u2FEF\u2FFC-\u2FFF\u3040\u3097\u3098\u3100-\u3104\u312E-\u3130\u318F\u31BB-\u31BF\u31E4-\u31EF\u321F\u32FF\u4DB6-\u4DBF\u9FCD-\u9FFF\uA48D-\uA48F\uA4C7-\uA4CF\uA62C-\uA63F\uA698-\uA69E\uA6F8-\uA6FF\uA78F\uA794-\uA79F\uA7AB-\uA7F7\uA82C-\uA82F\uA83A-\uA83F\uA878-\uA87F\uA8C5-\uA8CD\uA8DA-\uA8DF\uA8FC-\uA8FF\uA954-\uA95E\uA97D-\uA97F\uA9CE\uA9DA-\uA9DD\uA9E0-\uA9FF\uAA37-\uAA3F\uAA4E\uAA4F\uAA5A\uAA5B\uAA7C-\uAA7F\uAAC3-\uAADA\uAAF7-\uAB00\uAB07\uAB08\uAB0F\uAB10\uAB17-\uAB1F\uAB27\uAB2F-\uABBF\uABEE\uABEF\uABFA-\uABFF\uD7A4-\uD7AF\uD7C7-\uD7CA\uD7FC-\uF8FF\uFA6E\uFA6F\uFADA-\uFAFF\uFB07-\uFB12\uFB18-\uFB1C\uFB37\uFB3D\uFB3F\uFB42\uFB45\uFBC2-\uFBD2\uFD40-\uFD4F\uFD90\uFD91\uFDC8-\uFDEF\uFDFE\uFDFF\uFE1A-\uFE1F\uFE27-\uFE2F\uFE53\uFE67\uFE6C-\uFE6F\uFE75\uFEFD-\uFF00\uFFBF-\uFFC1\uFFC8\uFFC9\uFFD0\uFFD1\uFFD8\uFFD9\uFFDD-\uFFDF\uFFE7\uFFEF-\uFFFB\uFFFE\uFFFF]/g
@@ -105,6 +141,8 @@ export const ImageUploadDetails = ({ onChange }: ImageUploadDetailsProps) => {
try {
const data: ImageUploadDetailsDataProps = {
assetFile,
thumbnailFile,
isThumbnailCompatible,
uploadService,
nftStorageApiKey: nftStorageApiKeyState.value,
pinataApiKey: pinataApiKeyState.value,
@@ -125,6 +163,8 @@ export const ImageUploadDetails = ({ onChange }: ImageUploadDetailsProps) => {
}
}, [
assetFile,
thumbnailFile,
isThumbnailCompatible,
uploadService,
nftStorageApiKeyState.value,
pinataApiKeyState.value,
@@ -137,9 +177,31 @@ export const ImageUploadDetails = ({ onChange }: ImageUploadDetailsProps) => {
useEffect(() => {
if (assetFileRef.current) assetFileRef.current.value = ''
setAssetFile(undefined)
setThumbnailFile(undefined)
setIsThumbnailCompatible(false)
imageUrlState.onChange('')
}, [uploadMethod])
useEffect(() => {
if (importedImageUploadDetails) {
setUploadMethod(importedImageUploadDetails.uploadMethod)
setUploadService(importedImageUploadDetails.uploadService)
nftStorageApiKeyState.onChange(importedImageUploadDetails.nftStorageApiKey || '')
pinataApiKeyState.onChange(importedImageUploadDetails.pinataApiKey || '')
pinataSecretKeyState.onChange(importedImageUploadDetails.pinataSecretKey || '')
imageUrlState.onChange(importedImageUploadDetails.imageUrl || '')
coverImageUrlState.onChange(importedImageUploadDetails.coverImageUrl || '')
}
}, [importedImageUploadDetails])
useEffect(() => {
if (useDefaultApiKey) {
nftStorageApiKeyState.onChange(NFT_STORAGE_DEFAULT_API_KEY || '')
} else {
nftStorageApiKeyState.onChange('')
}
}, [useDefaultApiKey])
const previewUrl = imageUrlState.value.toLowerCase().trim().startsWith('ipfs://')
? `https://ipfs-gw.stargaze-apis.com/ipfs/${imageUrlState.value.substring(7)}`
: imageUrlState.value
@@ -291,7 +353,22 @@ export const ImageUploadDetails = ({ onChange }: ImageUploadDetailsProps) => {
<div className="flex w-full">
<Conditional test={uploadService === 'nft-storage'}>
<TextInput {...nftStorageApiKeyState} className="w-full" />
<div className="flex-col w-full">
<TextInput {...nftStorageApiKeyState} className="w-full" disabled={useDefaultApiKey} />
<div className="flex-row mt-2 w-full form-control">
<label className="cursor-pointer label">
<span className="mr-2 font-bold">Use Default API Key</span>
<input
checked={useDefaultApiKey}
className={`${useDefaultApiKey ? `bg-stargaze` : `bg-gray-600`} checkbox`}
onClick={() => {
setUseDefaultApiKey(!useDefaultApiKey)
}}
type="checkbox"
/>
</label>
</div>
</div>
</Conditional>
<Conditional test={uploadService === 'pinata'}>
<TextInput {...pinataApiKeyState} className="w-full" />
@@ -319,7 +396,7 @@ export const ImageUploadDetails = ({ onChange }: ImageUploadDetailsProps) => {
)}
>
<input
accept="image/*, audio/*, video/*, .html"
accept="image/*, audio/*, video/*, .html, .pdf"
className={clsx(
'file:py-2 file:px-4 file:mr-4 file:bg-plumbus-light file:rounded file:border-0 cursor-pointer',
'before:absolute before:inset-0 before:hover:bg-white/5 before:transition',
@@ -331,6 +408,34 @@ export const ImageUploadDetails = ({ onChange }: ImageUploadDetailsProps) => {
/>
</div>
</div>
<Conditional test={isThumbnailCompatible}>
<div>
<label
className="block mt-5 mr-1 mb-1 ml-8 w-full font-bold text-white dark:text-gray-300"
htmlFor="thumbnailFile"
>
Thumbnail Selection (optional)
</label>
<div
className={clsx(
'flex relative justify-center items-center mx-8 mt-2 space-y-4 w-full h-32',
'rounded border-2 border-white/20 border-dashed',
)}
>
<input
accept="image/*"
className={clsx(
'file:py-2 file:px-4 file:mr-4 file:bg-plumbus-light file:rounded file:border-0 cursor-pointer',
'before:absolute before:inset-0 before:hover:bg-white/5 before:transition',
)}
id="thumbnailFile"
onChange={selectThumbnail}
ref={thumbnailFileRef}
type="file"
/>
</div>
</div>
</Conditional>
</div>
</div>
<Conditional test={assetFile !== undefined}>
+156 -11
View File
@@ -1,5 +1,7 @@
/* eslint-disable eslint-comments/disable-enable-pair */
/* eslint-disable @typescript-eslint/no-unnecessary-condition */
/* eslint-disable no-nested-ternary */
import { Conditional } from 'components/Conditional'
import { FormControl } from 'components/FormControl'
import { FormGroup } from 'components/FormGroup'
import { useInputState, useNumberInputState } from 'components/forms/FormInput.hooks'
@@ -7,27 +9,35 @@ import { InputDateTime } from 'components/InputDateTime'
import { openEditionMinterList } from 'config/minter'
import type { TokenInfo } from 'config/token'
import { stars, tokensList } from 'config/token'
import { useGlobalSettings } from 'contexts/globalSettings'
import React, { useEffect, useState } from 'react'
import { resolveAddress } from 'utils/resolveAddress'
import { useWallet } from 'utils/wallet'
import { useWallet } from '../../contexts/wallet'
import { NumberInput, TextInput } from '../forms/FormInput'
import type { UploadMethod } from './OffChainMetadataUploadDetails'
export type LimitType = 'count_limited' | 'time_limited' | 'time_and_count_limited'
interface MintingDetailsProps {
onChange: (data: MintingDetailsDataProps) => void
uploadMethod: UploadMethod
minimumMintPrice: number
mintTokenFromFactory?: TokenInfo | undefined
importedMintingDetails?: MintingDetailsDataProps
isPresale: boolean
whitelistStartDate?: string
}
export interface MintingDetailsDataProps {
unitPrice: string
perAddressLimit: number
startTime: string
endTime: string
endTime?: string
tokenCountLimit?: number
paymentAddress?: string
selectedMintToken?: TokenInfo
limitType: LimitType
}
export const MintingDetails = ({
@@ -35,12 +45,18 @@ export const MintingDetails = ({
uploadMethod,
minimumMintPrice,
mintTokenFromFactory,
importedMintingDetails,
isPresale,
whitelistStartDate,
}: MintingDetailsProps) => {
const wallet = useWallet()
const [timestamp, setTimestamp] = useState<Date | undefined>()
const [endTimestamp, setEndTimestamp] = useState<Date | undefined>()
const [selectedMintToken, setSelectedMintToken] = useState<TokenInfo | undefined>(stars)
const [mintingDetailsImported, setMintingDetailsImported] = useState(false)
const [limitType, setLimitType] = useState<LimitType>('time_limited')
const { timezone } = useGlobalSettings()
const unitPriceState = useNumberInputState({
id: 'unitPrice',
@@ -60,6 +76,14 @@ export const MintingDetails = ({
placeholder: '1',
})
const tokenCountLimitState = useNumberInputState({
id: 'tokencountlimit',
name: 'tokencountlimit',
title: 'Maximum Token Count',
subtitle: 'Total number of mintable tokens',
placeholder: '100',
})
const paymentAddressState = useInputState({
id: 'payment-address',
name: 'paymentAddress',
@@ -75,7 +99,9 @@ export const MintingDetails = ({
}
useEffect(() => {
void resolvePaymentAddress()
if (!importedMintingDetails || (importedMintingDetails && mintingDetailsImported)) {
void resolvePaymentAddress()
}
}, [paymentAddressState.value])
useEffect(() => {
@@ -87,9 +113,19 @@ export const MintingDetails = ({
: '',
perAddressLimit: perAddressLimitState.value,
startTime: timestamp ? (timestamp.getTime() * 1_000_000).toString() : '',
endTime: endTimestamp ? (endTimestamp.getTime() * 1_000_000).toString() : '',
endTime:
limitType === 'time_limited' || limitType === 'time_and_count_limited'
? endTimestamp
? (endTimestamp.getTime() * 1_000_000).toString()
: ''
: undefined,
paymentAddress: paymentAddressState.value.trim(),
selectedMintToken,
limitType,
tokenCountLimit:
limitType === 'count_limited' || limitType === 'time_and_count_limited'
? tokenCountLimitState.value
: undefined,
}
onChange(data)
// eslint-disable-next-line react-hooks/exhaustive-deps
@@ -100,16 +136,41 @@ export const MintingDetails = ({
endTimestamp,
paymentAddressState.value,
selectedMintToken,
tokenCountLimitState.value,
limitType,
])
useEffect(() => {
if (importedMintingDetails) {
console.log('Selected Token ID: ', importedMintingDetails.selectedMintToken?.id)
unitPriceState.onChange(Number(importedMintingDetails.unitPrice) / 1000000)
perAddressLimitState.onChange(importedMintingDetails.perAddressLimit)
setLimitType(importedMintingDetails.limitType)
tokenCountLimitState.onChange(importedMintingDetails.tokenCountLimit ? importedMintingDetails.tokenCountLimit : 0)
setTimestamp(new Date(Number(importedMintingDetails.startTime) / 1_000_000))
setEndTimestamp(new Date(Number(importedMintingDetails.endTime) / 1_000_000))
paymentAddressState.onChange(importedMintingDetails.paymentAddress ? importedMintingDetails.paymentAddress : '')
setSelectedMintToken(tokensList.find((token) => token.id === importedMintingDetails.selectedMintToken?.id))
setMintingDetailsImported(true)
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [importedMintingDetails])
useEffect(() => {
if (isPresale) {
setTimestamp(whitelistStartDate ? new Date(Number(whitelistStartDate) / 1_000_000) : undefined)
}
}, [whitelistStartDate, isPresale])
return (
<div className="border-l-[1px] border-gray-500 border-opacity-20">
<FormGroup subtitle="Information about your minting settings" title="Minting Details">
<div className="flex flex-row items-center">
<div className="flex flex-row items-end">
<NumberInput {...unitPriceState} isRequired />
<select
className="py-[9px] px-4 mt-14 ml-4 placeholder:text-white/50 bg-white/10 rounded border-2 border-white/20 focus:ring focus:ring-plumbus-20"
className="py-[9px] px-4 ml-4 placeholder:text-white/50 bg-white/10 rounded border-2 border-white/20 focus:ring focus:ring-plumbus-20"
onChange={(e) => setSelectedMintToken(tokensList.find((t) => t.displayName === e.target.value))}
value={selectedMintToken?.displayName}
>
{openEditionMinterList
.filter((minter) => minter.factoryAddress !== undefined && minter.updatable === false)
@@ -122,12 +183,96 @@ export const MintingDetails = ({
</div>
<NumberInput {...perAddressLimitState} isRequired />
<FormControl htmlId="timestamp" isRequired subtitle="Minting start time (local)" title="Start Time">
<InputDateTime minDate={new Date()} onChange={(date) => setTimestamp(date)} value={timestamp} />
</FormControl>
<FormControl htmlId="endTimestamp" isRequired subtitle="Minting end time (local)" title="End Time">
<InputDateTime minDate={new Date()} onChange={(date) => setEndTimestamp(date)} value={endTimestamp} />
<FormControl
htmlId="timestamp"
isRequired
subtitle={`Minting start time ${timezone === 'Local' ? '(local)' : '(UTC)'}`}
title="Start Time"
>
<InputDateTime
disabled={isPresale}
minDate={
timezone === 'Local' ? new Date() : new Date(Date.now() + new Date().getTimezoneOffset() * 60 * 1000)
}
onChange={(date) =>
date
? setTimestamp(
timezone === 'Local' ? date : new Date(date.getTime() - new Date().getTimezoneOffset() * 60 * 1000),
)
: setTimestamp(undefined)
}
value={
timezone === 'Local'
? timestamp
: timestamp
? new Date(timestamp.getTime() + new Date().getTimezoneOffset() * 60 * 1000)
: undefined
}
/>
</FormControl>
<div className="flex-row mt-2 w-full form-control">
<h1 className="mt-2 font-bold text-md">Limit Type: </h1>
<label className="justify-start ml-6 cursor-pointer label">
<span className="mr-2">Time</span>
<input
checked={limitType === 'time_limited' || limitType === 'time_and_count_limited'}
className={`${limitType === 'time_limited' ? `bg-stargaze` : `bg-gray-600`} checkbox`}
onClick={() => {
if (limitType === 'time_and_count_limited') setLimitType('count_limited' as LimitType)
else if (limitType === 'count_limited') setLimitType('time_and_count_limited' as LimitType)
else setLimitType('count_limited' as LimitType)
}}
type="checkbox"
/>
</label>
<label className="justify-start ml-4 cursor-pointer label">
<span className="mr-2">Token Count</span>
<input
checked={limitType === 'count_limited' || limitType === 'time_and_count_limited'}
className={`${limitType === 'count_limited' ? `bg-stargaze` : `bg-gray-600`} checkbox`}
onClick={() => {
if (limitType === 'time_and_count_limited') setLimitType('time_limited' as LimitType)
else if (limitType === 'time_limited') setLimitType('time_and_count_limited' as LimitType)
else setLimitType('time_limited' as LimitType)
}}
type="checkbox"
/>
</label>
</div>
<Conditional test={limitType === 'time_limited' || limitType === 'time_and_count_limited'}>
<FormControl
htmlId="endTimestamp"
isRequired
subtitle={`Minting end time ${timezone === 'Local' ? '(local)' : '(UTC)'}`}
title="End Time"
>
<InputDateTime
minDate={
timezone === 'Local' ? new Date() : new Date(Date.now() + new Date().getTimezoneOffset() * 60 * 1000)
}
onChange={(date) =>
date
? setEndTimestamp(
timezone === 'Local'
? date
: new Date(date.getTime() - new Date().getTimezoneOffset() * 60 * 1000),
)
: setEndTimestamp(undefined)
}
value={
timezone === 'Local'
? endTimestamp
: endTimestamp
? new Date(endTimestamp.getTime() + new Date().getTimezoneOffset() * 60 * 1000)
: undefined
}
/>
</FormControl>
</Conditional>
<Conditional test={limitType === 'count_limited' || limitType === 'time_and_count_limited'}>
<NumberInput {...tokenCountLimitState} isRequired />
</Conditional>
</FormGroup>
<TextInput className="pr-4 pl-4 mt-3" {...paymentAddressState} />
</div>
@@ -18,6 +18,9 @@ import type { ChangeEvent } from 'react'
import { useEffect, useRef, useState } from 'react'
import { toast } from 'react-hot-toast'
import type { UploadServiceType } from 'services/upload'
import { NFT_STORAGE_DEFAULT_API_KEY } from 'utils/constants'
import type { AssetType } from 'utils/getAssetType'
import { getAssetType } from 'utils/getAssetType'
import { uid } from 'utils/random'
import { naturalCompare } from 'utils/sort'
@@ -28,11 +31,14 @@ export type UploadMethod = 'new' | 'existing'
interface OffChainMetadataUploadDetailsProps {
onChange: (value: OffChainMetadataUploadDetailsDataProps) => void
metadataStorageMethod?: MetadataStorageMethod
importedOffChainMetadataUploadDetails?: OffChainMetadataUploadDetailsDataProps
}
export interface OffChainMetadataUploadDetailsDataProps {
assetFiles: File[]
metadataFiles: File[]
thumbnailFile?: File
isThumbnailCompatible?: boolean
uploadService: UploadServiceType
nftStorageApiKey?: string
pinataApiKey?: string
@@ -41,23 +47,31 @@ export interface OffChainMetadataUploadDetailsDataProps {
tokenURI?: string
imageUrl?: string
openEditionMinterMetadataFile?: File
exportedMetadata?: any
}
export const OffChainMetadataUploadDetails = ({
onChange,
metadataStorageMethod,
importedOffChainMetadataUploadDetails,
}: OffChainMetadataUploadDetailsProps) => {
const [assetFilesArray, setAssetFilesArray] = useState<File[]>([])
const [metadataFilesArray, setMetadataFilesArray] = useState<File[]>([])
const [thumbnailFile, setThumbnailFile] = useState<File>()
const [isThumbnailCompatible, setIsThumbnailCompatible] = useState<boolean>(false)
const [uploadMethod, setUploadMethod] = useState<UploadMethod>('new')
const [uploadService, setUploadService] = useState<UploadServiceType>('nft-storage')
const [metadataFileArrayIndex, setMetadataFileArrayIndex] = useState(0)
const [refreshMetadata, setRefreshMetadata] = useState(false)
const [exportedMetadata, setExportedMetadata] = useState(undefined)
const [openEditionMinterMetadataFile, setOpenEditionMinterMetadataFile] = useState<File | undefined>()
const [useDefaultApiKey, setUseDefaultApiKey] = useState(false)
const thumbnailCompatibleAssetTypes: AssetType[] = ['video', 'audio', 'html', 'document']
const assetFilesRef = useRef<HTMLInputElement | null>(null)
const metadataFilesRef = useRef<HTMLInputElement | null>(null)
const thumbnailFilesRef = useRef<HTMLInputElement | null>(null)
const nftStorageApiKeyState = useInputState({
id: 'nft-storage-api-key',
@@ -100,7 +114,12 @@ export const OffChainMetadataUploadDetails = ({
const selectAssets = (event: ChangeEvent<HTMLInputElement>) => {
setAssetFilesArray([])
setMetadataFilesArray([])
setThumbnailFile(undefined)
setIsThumbnailCompatible(false)
if (event.target.files === null) return
if (thumbnailCompatibleAssetTypes.includes(getAssetType(event.target.files[0].name))) {
setIsThumbnailCompatible(true)
}
let loadedFileCount = 0
const files: File[] = []
let reader: FileReader
@@ -109,7 +128,9 @@ export const OffChainMetadataUploadDetails = ({
reader.onload = (e) => {
if (!e.target?.result) return toast.error('Error parsing file.')
if (!event.target.files) return toast.error('No files selected.')
const assetFile = new File([e.target.result], event.target.files[i].name, { type: 'image/jpg' })
const assetFile = new File([e.target.result], event.target.files[i].name.replaceAll('#', ''), {
type: 'image/jpg',
})
files.push(assetFile)
}
reader.readAsArrayBuffer(event.target.files[i])
@@ -135,7 +156,9 @@ export const OffChainMetadataUploadDetails = ({
reader.onload = async (e) => {
if (!e.target?.result) return toast.error('Error parsing file.')
if (!event.target.files) return toast.error('No files selected.')
const metadataFile = new File([e.target.result], event.target.files[i].name, { type: 'application/json' })
const metadataFile = new File([e.target.result], event.target.files[i].name.replaceAll('#', ''), {
type: 'application/json',
})
files.push(metadataFile)
try {
const parsedMetadata = JSON.parse(await metadataFile.text())
@@ -162,6 +185,26 @@ export const OffChainMetadataUploadDetails = ({
}
}
const selectThumbnail = (event: ChangeEvent<HTMLInputElement>) => {
setThumbnailFile(undefined)
if (event.target.files === null) return
let selectedFile: File
const reader = new FileReader()
reader.onload = (e) => {
if (!event.target.files) return toast.error('No file selected.')
if (!e.target?.result) return toast.error('Error parsing file.')
selectedFile = new File([e.target.result], event.target.files[0].name.replaceAll('#', ''), { type: 'image/*' })
}
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (event.target.files[0]) reader.readAsArrayBuffer(event.target.files[0])
else return toast.error('No file selected.')
reader.onloadend = () => {
if (!event.target.files) return toast.error('No file selected.')
setThumbnailFile(selectedFile)
}
}
const updateMetadataFileIndex = (index: number) => {
setMetadataFileArrayIndex(index)
setRefreshMetadata((prev) => !prev)
@@ -184,6 +227,8 @@ export const OffChainMetadataUploadDetails = ({
const data: OffChainMetadataUploadDetailsDataProps = {
assetFiles: assetFilesArray,
metadataFiles: metadataFilesArray,
thumbnailFile,
isThumbnailCompatible,
uploadService,
nftStorageApiKey: nftStorageApiKeyState.value,
pinataApiKey: pinataApiKeyState.value,
@@ -204,6 +249,7 @@ export const OffChainMetadataUploadDetails = ({
.replace(regex, '')
.trim(),
openEditionMinterMetadataFile,
exportedMetadata,
}
onChange(data)
} catch (error: any) {
@@ -213,6 +259,8 @@ export const OffChainMetadataUploadDetails = ({
}, [
assetFilesArray,
metadataFilesArray,
thumbnailFile,
isThumbnailCompatible,
uploadService,
nftStorageApiKeyState.value,
pinataApiKeyState.value,
@@ -222,6 +270,7 @@ export const OffChainMetadataUploadDetails = ({
coverImageUrlState.value,
refreshMetadata,
openEditionMinterMetadataFile,
exportedMetadata,
])
useEffect(() => {
@@ -229,10 +278,35 @@ export const OffChainMetadataUploadDetails = ({
setMetadataFilesArray([])
if (assetFilesRef.current) assetFilesRef.current.value = ''
setAssetFilesArray([])
tokenUriState.onChange('')
coverImageUrlState.onChange('')
setThumbnailFile(undefined)
setIsThumbnailCompatible(false)
if (!importedOffChainMetadataUploadDetails) {
tokenUriState.onChange('')
coverImageUrlState.onChange('')
}
}, [uploadMethod, metadataStorageMethod])
useEffect(() => {
if (importedOffChainMetadataUploadDetails) {
setUploadService(importedOffChainMetadataUploadDetails.uploadService)
nftStorageApiKeyState.onChange(importedOffChainMetadataUploadDetails.nftStorageApiKey || '')
pinataApiKeyState.onChange(importedOffChainMetadataUploadDetails.pinataApiKey || '')
pinataSecretKeyState.onChange(importedOffChainMetadataUploadDetails.pinataSecretKey || '')
setUploadMethod(importedOffChainMetadataUploadDetails.uploadMethod)
tokenUriState.onChange(importedOffChainMetadataUploadDetails.tokenURI || '')
coverImageUrlState.onChange(importedOffChainMetadataUploadDetails.imageUrl || '')
// setOpenEditionMinterMetadataFile(importedOffChainMetadataUploadDetails.openEditionMinterMetadataFile)
}
}, [importedOffChainMetadataUploadDetails])
useEffect(() => {
if (useDefaultApiKey) {
nftStorageApiKeyState.onChange(NFT_STORAGE_DEFAULT_API_KEY || '')
} else {
nftStorageApiKeyState.onChange('')
}
}, [useDefaultApiKey])
return (
<div className="justify-items-start mb-3 rounded border-2 border-white/20 flex-column">
<div className="flex justify-center">
@@ -351,7 +425,22 @@ export const OffChainMetadataUploadDetails = ({
<div className="flex w-full">
<Conditional test={uploadService === 'nft-storage'}>
<TextInput {...nftStorageApiKeyState} className="w-full" />
<div className="flex-col w-full">
<TextInput {...nftStorageApiKeyState} className="w-full" disabled={useDefaultApiKey} />
<div className="flex-row mt-2 w-full form-control">
<label className="cursor-pointer label">
<span className="mr-2 font-bold">Use Default API Key</span>
<input
checked={useDefaultApiKey}
className={`${useDefaultApiKey ? `bg-stargaze` : `bg-gray-600`} checkbox`}
onClick={() => {
setUseDefaultApiKey(!useDefaultApiKey)
}}
type="checkbox"
/>
</label>
</div>
</div>
</Conditional>
<Conditional test={uploadService === 'pinata'}>
<TextInput {...pinataApiKeyState} className="w-full" />
@@ -390,7 +479,7 @@ export const OffChainMetadataUploadDetails = ({
)}
>
<input
accept="image/*, audio/*, video/*, .html"
accept="image/*, audio/*, video/*, .html, .pdf"
className={clsx(
'file:py-2 file:px-4 file:mr-4 file:bg-plumbus-light file:rounded file:border-0 cursor-pointer',
'before:absolute before:inset-0 before:hover:bg-white/5 before:transition',
@@ -402,6 +491,34 @@ export const OffChainMetadataUploadDetails = ({
/>
</div>
</div>
<Conditional test={isThumbnailCompatible}>
<div>
<label
className="block mt-5 mr-1 mb-1 ml-8 w-full font-bold text-white dark:text-gray-300"
htmlFor="thumbnailFiles"
>
Thumbnail Selection (optional)
</label>
<div
className={clsx(
'flex relative justify-center items-center mx-8 mt-2 space-y-4 w-full h-32',
'rounded border-2 border-white/20 border-dashed',
)}
>
<input
accept="image/*"
className={clsx(
'file:py-2 file:px-4 file:mr-4 file:bg-plumbus-light file:rounded file:border-0 cursor-pointer',
'before:absolute before:inset-0 before:hover:bg-white/5 before:transition',
)}
id="thumbnailFiles"
onChange={selectThumbnail}
ref={thumbnailFilesRef}
type="file"
/>
</div>
</div>
</Conditional>
{assetFilesArray.length > 0 && (
<div>
@@ -447,6 +564,8 @@ export const OffChainMetadataUploadDetails = ({
/>
</div>
<MetadataInput
importedMetadata={importedOffChainMetadataUploadDetails?.exportedMetadata}
onChange={setExportedMetadata}
selectedAssetFile={assetFilesArray[0]}
selectedMetadataFile={metadataFilesArray[0]}
updateMetadataToUpload={updateOpenEditionMinterMetadataFile}
@@ -7,7 +7,6 @@ import clsx from 'clsx'
import { Conditional } from 'components/Conditional'
import { useInputState } from 'components/forms/FormInput.hooks'
import { useMetadataAttributesState } from 'components/forms/MetadataAttributes.hooks'
import { useWallet } from 'contexts/wallet'
import type { Trait } from 'contracts/badgeHub'
import type { ChangeEvent } from 'react'
import { useEffect, useRef, useState } from 'react'
@@ -21,6 +20,7 @@ import type { UploadMethod } from './ImageUploadDetails'
interface OnChainMetadataInputDetailsProps {
onChange: (data: OnChainMetadataInputDetailsDataProps) => void
uploadMethod: UploadMethod | undefined
importedOnChainMetadataInputDetails?: OnChainMetadataInputDetailsDataProps
}
export interface OnChainMetadataInputDetailsDataProps {
@@ -34,8 +34,11 @@ export interface OnChainMetadataInputDetailsDataProps {
youtube_url?: string
}
export const OnChainMetadataInputDetails = ({ onChange, uploadMethod }: OnChainMetadataInputDetailsProps) => {
const wallet = useWallet()
export const OnChainMetadataInputDetails = ({
onChange,
uploadMethod,
importedOnChainMetadataInputDetails,
}: OnChainMetadataInputDetailsProps) => {
const [timestamp, setTimestamp] = useState<Date | undefined>(undefined)
const [metadataFile, setMetadataFile] = useState<File>()
const [metadataFeeRate, setMetadataFeeRate] = useState<number>(0)
@@ -140,7 +143,9 @@ export const OnChainMetadataInputDetails = ({ onChange, uploadMethod }: OnChainM
reader.onload = (e) => {
if (!event.target.files) return toast.error('No file selected.')
if (!e.target?.result) return toast.error('Error parsing file.')
selectedFile = new File([e.target.result], event.target.files[0].name, { type: 'application/json' })
selectedFile = new File([e.target.result], event.target.files[0].name.replaceAll('#', ''), {
type: 'application/json',
})
}
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (event.target.files[0]) reader.readAsArrayBuffer(event.target.files[0])
@@ -196,6 +201,26 @@ export const OnChainMetadataInputDetails = ({ onChange, uploadMethod }: OnChainM
youtubeUrlState.value,
])
useEffect(() => {
if (importedOnChainMetadataInputDetails) {
nameState.onChange(importedOnChainMetadataInputDetails.name || '')
descriptionState.onChange(importedOnChainMetadataInputDetails.description || '')
externalUrlState.onChange(importedOnChainMetadataInputDetails.external_url || '')
youtubeUrlState.onChange(importedOnChainMetadataInputDetails.youtube_url || '')
animationUrlState.onChange(importedOnChainMetadataInputDetails.animation_url || '')
imageDataState.onChange(importedOnChainMetadataInputDetails.image_data || '')
if (importedOnChainMetadataInputDetails.attributes) {
attributesState.reset()
importedOnChainMetadataInputDetails.attributes.forEach((attr) => {
attributesState.add({
trait_type: attr.trait_type,
value: attr.value,
})
})
}
}
}, [importedOnChainMetadataInputDetails])
return (
<div className="py-3 px-8 rounded border-2 border-white/20">
<span className="ml-4 text-xl font-bold underline underline-offset-4">NFT Metadata</span>
@@ -5,38 +5,43 @@
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
import { toUtf8 } from '@cosmjs/encoding'
import type { Coin } from '@cosmjs/proto-signing'
import { coin } from '@cosmjs/proto-signing'
import axios from 'axios'
import clsx from 'clsx'
import { Button } from 'components/Button'
import type { MinterType } from 'components/collections/actions/Combobox'
import { Conditional } from 'components/Conditional'
import { ConfirmationModal } from 'components/ConfirmationModal'
import { LoadingModal } from 'components/LoadingModal'
import { openEditionMinterList } from 'config/minter'
import type { TokenInfo } from 'config/token'
import { type TokenInfo, tokensList } from 'config/token'
import { useContracts } from 'contexts/contracts'
import { addLogItem } from 'contexts/log'
import { useWallet } from 'contexts/wallet'
import type { DispatchExecuteArgs as OpenEditionFactoryDispatchExecuteArgs } from 'contracts/openEditionFactory/messages/execute'
import { dispatchExecute as openEditionFactoryDispatchExecute } from 'contracts/openEditionFactory/messages/execute'
import React, { useEffect, useMemo, useState } from 'react'
import { toast } from 'react-hot-toast'
import { upload } from 'services/upload'
import {
OPEN_EDITION_FACTORY_ADDRESS,
OPEN_EDITION_UPDATABLE_FACTORY_ADDRESS,
SG721_OPEN_EDITION_CODE_ID,
SG721_OPEN_EDITION_UPDATABLE_CODE_ID,
STRDST_SG721_CODE_ID,
WHITELIST_CODE_ID,
WHITELIST_FLEX_CODE_ID,
WHITELIST_MERKLE_TREE_API_URL,
WHITELIST_MERKLE_TREE_CODE_ID,
} from 'utils/constants'
import { getAssetType } from 'utils/getAssetType'
import { useDebounce } from 'utils/debounce'
import type { AssetType } from 'utils/getAssetType'
import { isValidAddress } from 'utils/isValidAddress'
import { checkTokenUri } from 'utils/isValidTokenUri'
import { uid } from 'utils/random'
import { useWallet } from 'utils/wallet'
import { type CollectionDetailsDataProps, CollectionDetails } from './CollectionDetails'
import type { ImageUploadDetailsDataProps } from './ImageUploadDetails'
import { ImageUploadDetails } from './ImageUploadDetails'
import type { MintingDetailsDataProps } from './MintingDetails'
import type { LimitType, MintingDetailsDataProps } from './MintingDetails'
import { MintingDetails } from './MintingDetails'
import type { UploadMethod } from './OffChainMetadataUploadDetails'
import {
@@ -46,34 +51,43 @@ import {
import type { OnChainMetadataInputDetailsDataProps } from './OnChainMetadataInputDetails'
import { OnChainMetadataInputDetails } from './OnChainMetadataInputDetails'
import { type RoyaltyDetailsDataProps, RoyaltyDetails } from './RoyaltyDetails'
import { type WhitelistDetailsDataProps, WhitelistDetails } from './WhitelistDetails'
export type MetadataStorageMethod = 'off-chain' | 'on-chain'
export interface OpenEditionMinterDetailsDataProps {
imageUploadDetails?: ImageUploadDetailsDataProps
collectionDetails?: CollectionDetailsDataProps
whitelistDetails?: WhitelistDetailsDataProps
royaltyDetails?: RoyaltyDetailsDataProps
onChainMetadataInputDetails?: OnChainMetadataInputDetailsDataProps
offChainMetadataUploadDetails?: OffChainMetadataUploadDetailsDataProps
mintingDetails?: MintingDetailsDataProps
metadataStorageMethod?: MetadataStorageMethod
openEditionMinterContractAddress?: string | null
coverImageUrl?: string | null
tokenUri?: string | null
tokenImageUri?: string | null
isRefreshed?: boolean
}
interface OpenEditionMinterCreatorProps {
onChange: (data: OpenEditionMinterCreatorDataProps) => void
onDetailsChange: (data: OpenEditionMinterDetailsDataProps) => void
openEditionMinterUpdatableCreationFee?: string
openEditionMinterCreationFee?: string
openEditionMinterCreationFee?: Coin
minimumMintPrice?: string
minimumUpdatableMintPrice?: string
minterType?: MinterType
mintTokenFromFactory?: TokenInfo | undefined
importedOpenEditionMinterDetails?: OpenEditionMinterDetailsDataProps
isMatchingFactoryPresent?: boolean
openEditionFactoryAddress?: string
}
export interface OpenEditionMinterCreatorDataProps {
metadataStorageMethod: MetadataStorageMethod
openEditionMinterContractAddress: string | null
sg721ContractAddress: string | null
whitelistContractAddress: string | null
transactionHash: string | null
}
@@ -81,20 +95,27 @@ export const OpenEditionMinterCreator = ({
onChange,
onDetailsChange,
openEditionMinterCreationFee,
openEditionMinterUpdatableCreationFee,
minimumMintPrice,
minimumUpdatableMintPrice,
minterType,
mintTokenFromFactory,
importedOpenEditionMinterDetails,
isMatchingFactoryPresent,
openEditionFactoryAddress,
}: OpenEditionMinterCreatorProps) => {
const wallet = useWallet()
const { openEditionMinter: openEditionMinterContract, openEditionFactory: openEditionFactoryContract } =
useContracts()
const {
openEditionMinter: openEditionMinterContract,
openEditionFactory: openEditionFactoryContract,
whitelist: whitelistContract,
whitelistMerkleTree: whitelistMerkleTreeContract,
} = useContracts()
const [metadataStorageMethod, setMetadataStorageMethod] = useState<MetadataStorageMethod>('off-chain')
const [imageUploadDetails, setImageUploadDetails] = useState<ImageUploadDetailsDataProps | null>(null)
const [collectionDetails, setCollectionDetails] = useState<CollectionDetailsDataProps | null>(null)
const [whitelistDetails, setWhitelistDetails] = useState<WhitelistDetailsDataProps | null>(null)
const [royaltyDetails, setRoyaltyDetails] = useState<RoyaltyDetailsDataProps | null>(null)
const [isRefreshed, setIsRefreshed] = useState(false)
const [onChainMetadataInputDetails, setOnChainMetadataInputDetails] =
useState<OnChainMetadataInputDetailsDataProps | null>(null)
const [offChainMetadataUploadDetails, setOffChainMetadataUploadDetails] =
@@ -109,21 +130,21 @@ export const OpenEditionMinterCreator = ({
const [coverImageUrl, setCoverImageUrl] = useState<string | null>(null)
const [openEditionMinterContractAddress, setOpenEditionMinterContractAddress] = useState<string | null>(null)
const [sg721ContractAddress, setSg721ContractAddress] = useState<string | null>(null)
const [whitelistContractAddress, setWhitelistContractAddress] = useState<string | null>(null)
const [transactionHash, setTransactionHash] = useState<string | null>(null)
const [thumbnailImageUri, setThumbnailImageUri] = useState<string | undefined>(undefined)
const factoryAddressForSelectedDenom =
openEditionMinterList.find((minter) => minter.supportedToken === mintTokenFromFactory && minter.updatable === false)
?.factoryAddress || OPEN_EDITION_FACTORY_ADDRESS
const updatableFactoryAddressForSelectedDenom =
openEditionMinterList.find((minter) => minter.supportedToken === mintTokenFromFactory && minter.updatable === true)
?.factoryAddress || OPEN_EDITION_UPDATABLE_FACTORY_ADDRESS
const thumbnailCompatibleAssetTypes: AssetType[] = ['video', 'audio', 'html']
const openEditionFactoryMessages = useMemo(
() =>
openEditionFactoryContract?.use(
collectionDetails?.updatable ? updatableFactoryAddressForSelectedDenom : factoryAddressForSelectedDenom,
),
[openEditionFactoryContract, wallet.address],
() => openEditionFactoryContract?.use(openEditionFactoryAddress as string),
[
openEditionFactoryContract,
wallet.address,
collectionDetails?.updatable,
openEditionFactoryAddress,
wallet.isWalletConnected,
],
)
const performOpenEditionMinterChecks = () => {
@@ -135,13 +156,26 @@ export const OpenEditionMinterCreator = ({
.then(() => {
void checkRoyaltyDetails()
.then(() => {
void checkwalletBalance()
checkWhitelistDetails()
.then(() => {
setReadyToCreate(true)
void checkwalletBalance()
.then(() => {
setReadyToCreate(true)
})
.catch((error: any) => {
toast.error(`Error in Wallet Balance: ${error.message}`, { style: { maxWidth: 'none' } })
addLogItem({ id: uid(), message: error.message, type: 'Error', timestamp: new Date() })
setReadyToCreate(false)
})
})
.catch((error: any) => {
toast.error(`Error in Wallet Balance: ${error.message}`, { style: { maxWidth: 'none' } })
addLogItem({ id: uid(), message: error.message, type: 'Error', timestamp: new Date() })
.catch((error) => {
if (String(error.message).includes('Insufficient wallet balance')) {
toast.error(`${error.message}`, { style: { maxWidth: 'none' } })
addLogItem({ id: uid(), message: error.message, type: 'Error', timestamp: new Date() })
} else {
toast.error(`Error in Whitelist Configuration: ${error.message}`, { style: { maxWidth: 'none' } })
addLogItem({ id: uid(), message: error.message, type: 'Error', timestamp: new Date() })
}
setReadyToCreate(false)
})
})
@@ -165,7 +199,7 @@ export const OpenEditionMinterCreator = ({
}
const checkUploadDetails = async () => {
if (!wallet.initialized) throw new Error('Wallet not connected.')
if (!wallet.isWalletConnected) throw new Error('Wallet not connected.')
if (
(metadataStorageMethod === 'off-chain' && !offChainMetadataUploadDetails) ||
(metadataStorageMethod === 'on-chain' && !imageUploadDetails)
@@ -282,9 +316,9 @@ export const OpenEditionMinterCreator = ({
if (!mintingDetails) throw new Error('Please fill out the minting details')
if (mintingDetails.unitPrice === '') throw new Error('Mint price is required')
if (collectionDetails?.updatable) {
if (Number(mintingDetails.unitPrice) < Number(minimumUpdatableMintPrice))
if (Number(mintingDetails.unitPrice) < Number(minimumMintPrice))
throw new Error(
`Invalid mint price: The minimum mint price is ${Number(minimumUpdatableMintPrice) / 1000000} ${
`Invalid mint price: The minimum mint price is ${Number(minimumMintPrice) / 1000000} ${
mintTokenFromFactory?.displayName
}`,
)
@@ -297,12 +331,120 @@ export const OpenEditionMinterCreator = ({
if (!mintingDetails.perAddressLimit || mintingDetails.perAddressLimit < 1 || mintingDetails.perAddressLimit > 50)
throw new Error('Invalid limit for tokens per address')
if (mintingDetails.startTime === '') throw new Error('Start time is required')
if (mintingDetails.limitType === 'time_limited' && mintingDetails.endTime === '')
throw new Error('End time is required')
if (mintingDetails.limitType === 'count_limited' && mintingDetails.tokenCountLimit === undefined)
throw new Error('Token count limit is required')
if (
mintingDetails.limitType === 'count_limited' &&
mintingDetails.perAddressLimit > (mintingDetails.tokenCountLimit as number)
)
throw new Error('Per address limit cannot exceed maximum token count limit')
if (mintingDetails.limitType === 'count_limited' && (mintingDetails.tokenCountLimit as number) > 10000)
throw new Error('Maximum token count cannot exceed 10000')
if (Number(mintingDetails.startTime) < new Date().getTime() * 1000000) throw new Error('Invalid start time')
if (
mintingDetails.limitType === 'time_limited' &&
Number(mintingDetails.endTime) < Number(mintingDetails.startTime)
)
throw new Error('End time cannot be earlier than start time')
if (
mintingDetails.limitType === 'time_limited' &&
Number(mintingDetails.endTime) === Number(mintingDetails.startTime)
)
throw new Error('End time cannot be equal to the start time')
if (
mintingDetails.paymentAddress &&
(!isValidAddress(mintingDetails.paymentAddress) || !mintingDetails.paymentAddress.startsWith('stars1'))
)
throw new Error('Invalid payment address')
if (!isMatchingFactoryPresent)
throw new Error(
`No matching open edition factory contract found for the selected parameters (Mint Price Denom: ${mintingDetails.selectedMintToken?.displayName}, Whitelist Type: ${whitelistDetails?.whitelistType})`,
)
}
const checkWhitelistDetails = async () => {
if (!whitelistDetails) throw new Error('Please fill out the whitelist details')
if (whitelistDetails.whitelistState === 'existing') {
if (whitelistDetails.contractAddress === '') throw new Error('Whitelist contract address is required')
else {
const contract = whitelistContract?.use(whitelistDetails.contractAddress)
//check if the address belongs to a whitelist contract (see performChecks())
const config = await contract?.config()
if (JSON.stringify(config).includes('whale_cap')) whitelistDetails.whitelistType = 'flex'
else if (!JSON.stringify(config).includes('member_limit') || config?.member_limit === 0) {
// whitelistDetails.whitelistType = 'merkletree'
throw new Error(
'Whitelist Merkle Tree is not supported yet. Please use a standard or flexible whitelist contract.',
)
} else whitelistDetails.whitelistType = 'standard'
if (Number(config?.start_time) !== Number(mintingDetails?.startTime)) {
const whitelistStartDate = new Date(Number(config?.start_time) / 1000000)
throw Error(`Whitelist start time (${whitelistStartDate.toLocaleString()}) does not match minting start time`)
}
if (mintingDetails?.tokenCountLimit && config?.per_address_limit) {
if (mintingDetails.tokenCountLimit >= 100 && Number(config.per_address_limit) > 50) {
throw Error(
`Invalid limit for tokens per address (${config.per_address_limit} tokens). Tokens per address limit cannot exceed 50 regardless of the total number of tokens.`,
)
} else if (
mintingDetails.tokenCountLimit >= 100 &&
Number(config.per_address_limit) > Math.ceil((mintingDetails.tokenCountLimit / 100) * 3)
) {
throw Error(
`Invalid limit for tokens per address (${config.per_address_limit} tokens). Tokens per address limit cannot exceed 3% of the total number of tokens in the collection.`,
)
} else if (mintingDetails.tokenCountLimit < 100 && Number(config.per_address_limit) > 3) {
throw Error(
`Invalid limit for tokens per address (${config.per_address_limit} tokens). Tokens per address limit cannot exceed 3 for collections with a token count limit smaller than 100 tokens.`,
)
}
}
}
} else if (whitelistDetails.whitelistState === 'new') {
if (whitelistDetails.members?.length === 0) throw new Error('Whitelist member list cannot be empty')
if (whitelistDetails.unitPrice === undefined) throw new Error('Whitelist unit price is required')
if (Number(whitelistDetails.unitPrice) < 0)
throw new Error('Invalid unit price: The unit price cannot be negative')
if (whitelistDetails.startTime === '') throw new Error('Start time is required')
if (whitelistDetails.endTime === '') throw new Error('End time is required')
if (
whitelistDetails.whitelistType === 'standard' &&
(!whitelistDetails.perAddressLimit || whitelistDetails.perAddressLimit === 0)
)
throw new Error('Per address limit is required')
if (
whitelistDetails.whitelistType !== 'merkletree' &&
(!whitelistDetails.memberLimit || whitelistDetails.memberLimit === 0)
)
throw new Error('Member limit is required')
if (Number(whitelistDetails.startTime) >= Number(whitelistDetails.endTime))
throw new Error('Whitelist start time cannot be equal to or later than the whitelist end time')
if (Number(whitelistDetails.startTime) !== Number(mintingDetails?.startTime))
throw new Error('Whitelist start time must be the same as the minting start time')
if (whitelistDetails.perAddressLimit && mintingDetails?.tokenCountLimit) {
if (mintingDetails.tokenCountLimit >= 100 && whitelistDetails.perAddressLimit > 50) {
throw Error(
`Invalid limit for tokens per address. Tokens per address limit cannot exceed 50 regardless of the total number of tokens.`,
)
} else if (
mintingDetails.tokenCountLimit >= 100 &&
whitelistDetails.perAddressLimit > Math.ceil((mintingDetails.tokenCountLimit / 100) * 3)
) {
throw Error(
`Invalid limit for tokens per address. Tokens per address limit cannot exceed 3% of the total number of tokens in the collection.`,
)
} else if (mintingDetails.tokenCountLimit < 100 && whitelistDetails.perAddressLimit > 3) {
throw Error(
`Invalid limit for tokens per address. Tokens per address limit cannot exceed 3 for collections with a token count limit smaller than 100 tokens.`,
)
}
}
}
}
const checkRoyaltyDetails = async () => {
@@ -317,8 +459,8 @@ export const OpenEditionMinterCreator = ({
}
throw new Error('Invalid royalty payment address')
}
const contractInfoResponse = await wallet.client
?.queryContractRaw(
const contractInfoResponse = await (await wallet.getCosmWasmClient())
.queryContractRaw(
royaltyDetails.paymentAddress.trim(),
toUtf8(Buffer.from(Buffer.from('contract_info').toString('hex'), 'hex').toString()),
)
@@ -330,25 +472,54 @@ export const OpenEditionMinterCreator = ({
if (contractInfoResponse !== undefined) {
const contractInfo = JSON.parse(new TextDecoder().decode(contractInfoResponse as Uint8Array))
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
if (contractInfo && !contractInfo.contract.includes('splits'))
throw new Error('The provided royalty payment address does not belong to a splits contract.')
if (contractInfo && (contractInfo.contract.includes('minter') || contractInfo.contract.includes('sg721')))
throw new Error('The provided royalty payment address does not belong to a compatible contract.')
else console.log(contractInfo)
}
}
}
const checkwalletBalance = async () => {
if (!wallet.initialized) throw new Error('Wallet not connected.')
const amountNeeded = collectionDetails?.updatable
? Number(openEditionMinterUpdatableCreationFee)
: Number(openEditionMinterCreationFee)
await wallet.client?.getBalance(wallet.address, 'ustars').then((balance) => {
if (amountNeeded >= Number(balance.amount))
throw new Error(
`Insufficient wallet balance to instantiate the required contracts. Needed amount: ${(
amountNeeded / 1000000
).toString()} STARS`,
)
if (!wallet.isWalletConnected) throw new Error('Wallet not connected.')
const queryClient = await wallet.getCosmWasmClient()
const creationFeeDenom = tokensList.find((token) => token.denom === openEditionMinterCreationFee?.denom)
await queryClient.getBalance(wallet.address || '', 'ustars').then(async (starsBalance) => {
await queryClient
.getBalance(wallet.address || '', openEditionMinterCreationFee?.denom as string)
.then((creationFeeDenomBalance) => {
if (whitelistDetails?.whitelistState === 'new' && whitelistDetails.memberLimit) {
const whitelistCreationFee = Math.ceil(Number(whitelistDetails.memberLimit) / 1000) * 100000000
if (openEditionMinterCreationFee?.denom === 'ustars') {
const amountNeeded = whitelistCreationFee + Number(openEditionMinterCreationFee.amount)
if (amountNeeded >= Number(starsBalance.amount))
throw new Error(
`Insufficient wallet balance to instantiate the required contracts. Needed amount: ${(
amountNeeded / 1000000
).toString()} STARS`,
)
} else {
if (whitelistCreationFee >= Number(starsBalance.amount))
throw new Error(
`Insufficient wallet balance to instantiate the whitelist. Needed amount: ${(
whitelistCreationFee / 1000000
).toString()} STARS`,
)
if (Number(openEditionMinterCreationFee?.amount) > Number(creationFeeDenomBalance.amount))
throw new Error(
`Insufficient wallet balance to instantiate the required contracts. Needed amount: ${(
Number(openEditionMinterCreationFee?.amount) / 1000000
).toString()} ${
creationFeeDenom ? creationFeeDenom.displayName : openEditionMinterCreationFee?.denom
}`,
)
}
} else if (Number(openEditionMinterCreationFee?.amount) > Number(creationFeeDenomBalance.amount))
throw new Error(
`Insufficient wallet balance to instantiate the required contracts. Needed amount: ${(
Number(openEditionMinterCreationFee?.amount) / 1000000
).toString()} ${creationFeeDenom ? creationFeeDenom.displayName : openEditionMinterCreationFee?.denom}`,
)
})
})
}
@@ -360,6 +531,7 @@ export const OpenEditionMinterCreator = ({
setTokenImageUri(null)
setOpenEditionMinterContractAddress(null)
setSg721ContractAddress(null)
setWhitelistContractAddress(null)
setTransactionHash(null)
if (metadataStorageMethod === 'off-chain') {
if (offChainMetadataUploadDetails?.uploadMethod === 'new') {
@@ -384,13 +556,27 @@ export const OpenEditionMinterCreator = ({
setTokenUri(metadataUriWithBase)
setCoverImageUrl(coverImageUriWithBase)
setUploading(false)
await instantiateOpenEditionMinter(metadataUriWithBase, coverImageUriWithBase)
let whitelist: string | undefined
if (whitelistDetails?.whitelistState === 'existing') whitelist = whitelistDetails.contractAddress
else if (whitelistDetails?.whitelistState === 'new') whitelist = await instantiateWhitelist()
setWhitelistContractAddress(whitelist as string)
await instantiateOpenEditionMinter(metadataUriWithBase, coverImageUriWithBase, undefined, whitelist)
} else {
setTokenUri(offChainMetadataUploadDetails?.tokenURI as string)
setCoverImageUrl(offChainMetadataUploadDetails?.imageUrl as string)
let whitelist: string | undefined
if (whitelistDetails?.whitelistState === 'existing') whitelist = whitelistDetails.contractAddress
else if (whitelistDetails?.whitelistState === 'new') whitelist = await instantiateWhitelist()
setWhitelistContractAddress(whitelist as string)
await instantiateOpenEditionMinter(
offChainMetadataUploadDetails?.tokenURI as string,
offChainMetadataUploadDetails?.imageUrl as string,
undefined,
whitelist,
)
}
} else if (metadataStorageMethod === 'on-chain') {
@@ -418,14 +604,41 @@ export const OpenEditionMinterCreator = ({
const coverImageUriWithBase = `ipfs://${coverImageUri}/${(collectionDetails?.imageFile as File[])[0].name}`
setCoverImageUrl(coverImageUriWithBase)
let thumbnailUri: string | undefined
if (imageUploadDetails.isThumbnailCompatible && imageUploadDetails.thumbnailFile)
thumbnailUri = await upload(
[imageUploadDetails.thumbnailFile] as File[],
imageUploadDetails.uploadService,
'thumbnail',
imageUploadDetails.nftStorageApiKey as string,
imageUploadDetails.pinataApiKey as string,
imageUploadDetails.pinataSecretKey as string,
)
const thumbnailUriWithBase = thumbnailUri
? `ipfs://${thumbnailUri}/${(imageUploadDetails.thumbnailFile as File).name}`
: undefined
setThumbnailImageUri(thumbnailUriWithBase)
setUploading(false)
await instantiateOpenEditionMinter(imageUriWithBase, coverImageUriWithBase)
let whitelist: string | undefined
if (whitelistDetails?.whitelistState === 'existing') whitelist = whitelistDetails.contractAddress
else if (whitelistDetails?.whitelistState === 'new') whitelist = await instantiateWhitelist()
setWhitelistContractAddress(whitelist as string)
await instantiateOpenEditionMinter(imageUriWithBase, coverImageUriWithBase, thumbnailUriWithBase, whitelist)
} else if (imageUploadDetails?.uploadMethod === 'existing') {
setTokenImageUri(imageUploadDetails.imageUrl as string)
setCoverImageUrl(imageUploadDetails.coverImageUrl as string)
let whitelist: string | undefined
if (whitelistDetails?.whitelistState === 'existing') whitelist = whitelistDetails.contractAddress
else if (whitelistDetails?.whitelistState === 'new') whitelist = await instantiateWhitelist()
setWhitelistContractAddress(whitelist as string)
await instantiateOpenEditionMinter(
imageUploadDetails.imageUrl as string,
imageUploadDetails.coverImageUrl as string,
whitelist,
)
}
}
@@ -451,23 +664,40 @@ export const OpenEditionMinterCreator = ({
offChainMetadataUploadDetails.pinataApiKey as string,
offChainMetadataUploadDetails.pinataSecretKey as string,
)
.then((assetUri: string) => {
.then(async (assetUri: string) => {
let thumbnailUri: string | undefined
if (offChainMetadataUploadDetails.isThumbnailCompatible && offChainMetadataUploadDetails.thumbnailFile)
thumbnailUri = await upload(
[offChainMetadataUploadDetails.thumbnailFile] as File[],
offChainMetadataUploadDetails.uploadService,
'thumbnail',
offChainMetadataUploadDetails.nftStorageApiKey as string,
offChainMetadataUploadDetails.pinataApiKey as string,
offChainMetadataUploadDetails.pinataSecretKey as string,
)
const thumbnailUriWithBase = thumbnailUri
? `ipfs://${thumbnailUri}/${(offChainMetadataUploadDetails.thumbnailFile as File).name}`
: undefined
const fileArray: File[] = []
const reader: FileReader = new FileReader()
reader.onload = (e) => {
const data: any = JSON.parse(e.target?.result as string)
if (
getAssetType(offChainMetadataUploadDetails.assetFiles[0].name) === 'audio' ||
getAssetType(offChainMetadataUploadDetails.assetFiles[0].name) === 'video' ||
getAssetType(offChainMetadataUploadDetails.assetFiles[0].name) === 'html'
) {
if (offChainMetadataUploadDetails.isThumbnailCompatible) {
data.animation_url = `ipfs://${assetUri}/${offChainMetadataUploadDetails.assetFiles[0].name}`
}
if (getAssetType(offChainMetadataUploadDetails.assetFiles[0].name) !== 'html')
data.image = `ipfs://${assetUri}/${offChainMetadataUploadDetails.assetFiles[0].name}`
data.image =
offChainMetadataUploadDetails.isThumbnailCompatible && offChainMetadataUploadDetails.thumbnailFile
? thumbnailUriWithBase
: `ipfs://${assetUri}/${offChainMetadataUploadDetails.assetFiles[0].name}`
if (data.description) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
data.description = data.description.replaceAll('\\n', '\n')
}
const metadataFileBlob = new Blob([JSON.stringify(data)], {
type: 'application/json',
})
@@ -475,10 +705,12 @@ export const OpenEditionMinterCreator = ({
console.log('Name: ', (offChainMetadataUploadDetails.openEditionMinterMetadataFile as File).name)
const updatedMetadataFile = new File(
[metadataFileBlob],
(offChainMetadataUploadDetails.openEditionMinterMetadataFile as File).name.substring(
0,
(offChainMetadataUploadDetails.openEditionMinterMetadataFile as File).name.lastIndexOf('.'),
),
(offChainMetadataUploadDetails.openEditionMinterMetadataFile as File).name
.substring(
0,
(offChainMetadataUploadDetails.openEditionMinterMetadataFile as File).name.lastIndexOf('.'),
)
.replaceAll('#', ''),
{
type: 'application/json',
},
@@ -505,8 +737,105 @@ export const OpenEditionMinterCreator = ({
})
}
const instantiateOpenEditionMinter = async (uri: string, coverImageUri: string) => {
if (!wallet.initialized) throw new Error('Wallet not connected')
const instantiateWhitelist = async () => {
if (!wallet.isWalletConnected) throw new Error('Wallet not connected')
if (!whitelistContract) throw new Error('Contract not found')
if (whitelistDetails?.whitelistType === 'standard' || whitelistDetails?.whitelistType === 'flex') {
const standardMsg = {
members: whitelistDetails.members,
start_time: whitelistDetails.startTime,
end_time: whitelistDetails.endTime,
mint_price: coin(
String(Number(whitelistDetails.unitPrice)),
mintTokenFromFactory ? mintTokenFromFactory.denom : 'ustars',
),
per_address_limit: whitelistDetails.perAddressLimit,
member_limit: whitelistDetails.memberLimit,
admins: whitelistDetails.admins || [wallet.address],
admins_mutable: whitelistDetails.adminsMutable,
}
const flexMsg = {
members: whitelistDetails.members,
start_time: whitelistDetails.startTime,
end_time: whitelistDetails.endTime,
mint_price: coin(
String(Number(whitelistDetails.unitPrice)),
mintTokenFromFactory ? mintTokenFromFactory.denom : 'ustars',
),
member_limit: whitelistDetails.memberLimit,
admins: whitelistDetails.admins || [wallet.address],
admins_mutable: whitelistDetails.adminsMutable,
}
const data = await whitelistContract.instantiate(
whitelistDetails.whitelistType === 'standard' ? WHITELIST_CODE_ID : WHITELIST_FLEX_CODE_ID,
whitelistDetails.whitelistType === 'standard' ? standardMsg : flexMsg,
'Stargaze Whitelist Contract',
wallet.address,
)
return data.contractAddress
} else if (whitelistDetails?.whitelistType === 'merkletree') {
const members = whitelistDetails.members as string[]
const membersCsv = members.join('\n')
const membersBlob = new Blob([membersCsv], { type: 'text/csv' })
const membersFile = new File([membersBlob], 'members.csv', { type: 'text/csv' })
const formData = new FormData()
formData.append('whitelist', membersFile)
const response = await toast
.promise(
axios.post(`${WHITELIST_MERKLE_TREE_API_URL}/create_whitelist`, formData, {
headers: {
'Content-Type': 'multipart/form-data',
},
}),
{
loading: 'Fetching merkle root hash...',
success: 'Merkle root fetched successfully.',
error: 'Error fetching root hash from Whitelist Merkle Tree API.',
},
)
.catch((error) => {
console.log('error', error)
throw new Error('Whitelist instantiation failed.')
})
const rootHash = response.data.root_hash
console.log('rootHash', rootHash)
const merkleTreeMsg = {
merkle_root: rootHash,
merkle_tree_uri: null,
start_time: whitelistDetails.startTime,
end_time: whitelistDetails.endTime,
mint_price: coin(
String(Number(whitelistDetails.unitPrice)),
mintTokenFromFactory ? mintTokenFromFactory.denom : 'ustars',
),
per_address_limit: whitelistDetails.perAddressLimit,
admins: whitelistDetails.admins || [wallet.address],
admins_mutable: whitelistDetails.adminsMutable,
}
const data = await whitelistMerkleTreeContract?.instantiate(
WHITELIST_MERKLE_TREE_CODE_ID,
merkleTreeMsg,
'Stargaze Whitelist Merkle Tree Contract',
wallet.address,
)
return data?.contractAddress
}
}
const instantiateOpenEditionMinter = async (
uri: string,
coverImageUri: string,
thumbnailUri?: string,
whitelist?: string,
) => {
if (!wallet.isWalletConnected) throw new Error('Wallet not connected')
if (!openEditionFactoryContract) throw new Error('Contract not found')
if (!openEditionMinterContract) throw new Error('Contract not found')
@@ -527,15 +856,18 @@ export const OpenEditionMinterCreator = ({
extension:
metadataStorageMethod === 'on-chain'
? {
image: uri,
image:
imageUploadDetails?.isThumbnailCompatible && imageUploadDetails.thumbnailFile
? thumbnailUri
: uri,
name: onChainMetadataInputDetails?.name,
description: onChainMetadataInputDetails?.description,
description: onChainMetadataInputDetails?.description?.replaceAll('\\n', '\n'),
attributes: onChainMetadataInputDetails?.attributes,
external_url: onChainMetadataInputDetails?.external_url,
animation_url:
imageUploadDetails?.uploadMethod === 'existing'
? onChainMetadataInputDetails?.animation_url
: getAssetType(imageUploadDetails?.assetFile?.name as string) === 'video'
: imageUploadDetails?.isThumbnailCompatible
? uri
: undefined,
youtube_url: onChainMetadataInputDetails?.youtube_url,
@@ -543,21 +875,42 @@ export const OpenEditionMinterCreator = ({
: null,
},
start_time: mintingDetails?.startTime,
end_time: mintingDetails?.endTime,
end_time:
mintingDetails?.limitType === ('time_limited' as LimitType) ||
mintingDetails?.limitType === ('time_and_count_limited' as LimitType)
? mintingDetails.endTime
: null,
mint_price: {
amount: Number(mintingDetails?.unitPrice).toString(),
denom: (mintTokenFromFactory?.denom as string) || 'ustars',
},
per_address_limit: mintingDetails?.perAddressLimit,
num_tokens:
mintingDetails?.limitType === ('count_limited' as LimitType) ||
mintingDetails?.limitType === ('time_and_count_limited' as LimitType)
? mintingDetails.tokenCountLimit
: null,
payment_address: mintingDetails?.paymentAddress || null,
whitelist,
},
collection_params: {
code_id: collectionDetails?.updatable ? SG721_OPEN_EDITION_UPDATABLE_CODE_ID : SG721_OPEN_EDITION_CODE_ID,
code_id: collectionDetails?.updatable
? SG721_OPEN_EDITION_UPDATABLE_CODE_ID
: mintingDetails?.selectedMintToken?.displayName === 'USK' ||
mintingDetails?.selectedMintToken?.displayName === 'USDC' ||
mintingDetails?.selectedMintToken?.displayName === 'TIA' ||
mintingDetails?.selectedMintToken?.displayName === 'STRDST' ||
mintingDetails?.selectedMintToken?.displayName === 'KUJI' ||
mintingDetails?.selectedMintToken?.displayName === 'HUAHUA' ||
mintingDetails?.selectedMintToken?.displayName === 'BRNCH' ||
mintingDetails?.selectedMintToken?.displayName === 'CRBRUS'
? STRDST_SG721_CODE_ID
: SG721_OPEN_EDITION_CODE_ID,
name: collectionDetails?.name,
symbol: collectionDetails?.symbol,
info: {
creator: wallet.address,
description: collectionDetails?.description,
description: collectionDetails?.description.replaceAll('\\n', '\n'),
image: coverImageUri,
explicit_content: collectionDetails?.explicit || false,
royalty_info: royaltyInfo,
@@ -567,21 +920,12 @@ export const OpenEditionMinterCreator = ({
},
}
console.log('msg: ', msg)
console.log('Using factory address: ', factoryAddressForSelectedDenom)
const payload: OpenEditionFactoryDispatchExecuteArgs = {
contract: collectionDetails?.updatable ? updatableFactoryAddressForSelectedDenom : factoryAddressForSelectedDenom,
contract: openEditionFactoryAddress as string,
messages: openEditionFactoryMessages,
txSigner: wallet.address,
txSigner: wallet.address || '',
msg,
funds: [
coin(
collectionDetails?.updatable
? (openEditionMinterUpdatableCreationFee as string)
: (openEditionMinterCreationFee as string),
'ustars',
),
],
funds: [openEditionMinterCreationFee as Coin],
updatable: collectionDetails?.updatable,
}
await openEditionFactoryDispatchExecute(payload)
@@ -612,33 +956,93 @@ export const OpenEditionMinterCreator = ({
metadataStorageMethod,
openEditionMinterContractAddress,
sg721ContractAddress,
whitelistContractAddress,
transactionHash,
}
onChange(data)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [metadataStorageMethod, openEditionMinterContractAddress, sg721ContractAddress, transactionHash])
}, [
metadataStorageMethod,
openEditionMinterContractAddress,
sg721ContractAddress,
whitelistContractAddress,
transactionHash,
])
useEffect(() => {
const data: OpenEditionMinterDetailsDataProps = {
imageUploadDetails: imageUploadDetails ? imageUploadDetails : undefined,
collectionDetails: collectionDetails ? collectionDetails : undefined,
whitelistDetails: whitelistDetails ? whitelistDetails : undefined,
royaltyDetails: royaltyDetails ? royaltyDetails : undefined,
onChainMetadataInputDetails: onChainMetadataInputDetails ? onChainMetadataInputDetails : undefined,
offChainMetadataUploadDetails: offChainMetadataUploadDetails ? offChainMetadataUploadDetails : undefined,
mintingDetails: mintingDetails ? mintingDetails : undefined,
metadataStorageMethod,
openEditionMinterContractAddress,
coverImageUrl,
tokenUri,
tokenImageUri,
isRefreshed,
}
onDetailsChange(data)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [
imageUploadDetails,
collectionDetails,
whitelistDetails,
royaltyDetails,
onChainMetadataInputDetails,
offChainMetadataUploadDetails,
mintingDetails,
metadataStorageMethod,
openEditionMinterContractAddress,
coverImageUrl,
tokenUri,
tokenImageUri,
isRefreshed,
])
useEffect(() => {
if (importedOpenEditionMinterDetails) {
setMetadataStorageMethod(importedOpenEditionMinterDetails.metadataStorageMethod as MetadataStorageMethod)
}
}, [importedOpenEditionMinterDetails])
const fetchWhitelistConfig = async (contractAddress: string | undefined) => {
if (contractAddress === '' || !whitelistDetails) return
const contract = whitelistContract?.use(contractAddress)
await contract
?.config()
.then((config) => {
if (!config) {
whitelistDetails.whitelistType = 'standard'
return
}
if (JSON.stringify(config).includes('whale_cap')) whitelistDetails.whitelistType = 'flex'
else if (!JSON.stringify(config).includes('member_limit') || config.member_limit === 0) {
// whitelistDetails.whitelistType = 'merkletree'
toast.error(
'Whitelist Merkle Tree is not supported yet for open edition collections. Please use a standard or flexible whitelist contract.',
)
} else whitelistDetails.whitelistType = 'standard'
setIsRefreshed(!isRefreshed)
})
.catch((error) => {
console.log('error', error)
})
}
const debouncedWhitelistContractAddress = useDebounce(whitelistDetails?.contractAddress, 300)
useEffect(() => {
if (whitelistDetails?.whitelistState === 'existing' && debouncedWhitelistContractAddress !== '') {
void fetchWhitelistConfig(debouncedWhitelistContractAddress)
}
}, [whitelistDetails?.whitelistState, debouncedWhitelistContractAddress])
return (
<div>
{/* TODO: Cancel once we're able to index on-chain metadata */}
@@ -686,16 +1090,23 @@ export const OpenEditionMinterCreator = ({
</div>
</div>
</Conditional>
<div className={clsx('my-4 mx-10')}>
<div className={clsx('my-0 mx-10')}>
<Conditional test={metadataStorageMethod === 'off-chain'}>
<div>
<OffChainMetadataUploadDetails onChange={setOffChainMetadataUploadDetails} />
<OffChainMetadataUploadDetails
importedOffChainMetadataUploadDetails={importedOpenEditionMinterDetails?.offChainMetadataUploadDetails}
onChange={setOffChainMetadataUploadDetails}
/>
</div>
</Conditional>
<Conditional test={metadataStorageMethod === 'on-chain'}>
<div>
<ImageUploadDetails onChange={setImageUploadDetails} />
<ImageUploadDetails
importedImageUploadDetails={importedOpenEditionMinterDetails?.imageUploadDetails}
onChange={setImageUploadDetails}
/>
<OnChainMetadataInputDetails
importedOnChainMetadataInputDetails={importedOpenEditionMinterDetails?.onChainMetadataInputDetails}
onChange={setOnChainMetadataInputDetails}
uploadMethod={imageUploadDetails?.uploadMethod}
/>
@@ -709,6 +1120,7 @@ export const OpenEditionMinterCreator = ({
? (offChainMetadataUploadDetails?.imageUrl as string)
: (imageUploadDetails?.coverImageUrl as string)
}
importedCollectionDetails={importedOpenEditionMinterDetails?.collectionDetails}
metadataStorageMethod={metadataStorageMethod}
onChange={setCollectionDetails}
uploadMethod={
@@ -718,18 +1130,29 @@ export const OpenEditionMinterCreator = ({
}
/>
<MintingDetails
minimumMintPrice={
collectionDetails?.updatable
? Number(minimumUpdatableMintPrice) / 1000000
: Number(minimumMintPrice) / 1000000
}
importedMintingDetails={importedOpenEditionMinterDetails?.mintingDetails}
isPresale={whitelistDetails?.whitelistState === 'new'}
minimumMintPrice={Number(minimumMintPrice) / 1000000}
mintTokenFromFactory={mintTokenFromFactory}
onChange={setMintingDetails}
uploadMethod={offChainMetadataUploadDetails?.uploadMethod as UploadMethod}
whitelistStartDate={whitelistDetails?.startTime}
/>
</div>
<div className="my-6 mx-10">
<WhitelistDetails
importedWhitelistDetails={importedOpenEditionMinterDetails?.whitelistDetails}
mintingTokenFromFactory={mintTokenFromFactory}
onChange={setWhitelistDetails}
/>
</div>
<div className="my-6">
<RoyaltyDetails onChange={setRoyaltyDetails} />
<RoyaltyDetails
importedRoyaltyDetails={importedOpenEditionMinterDetails?.royaltyDetails}
onChange={setRoyaltyDetails}
/>
</div>
<div className="flex justify-end w-full">
<Button
+33 -19
View File
@@ -1,14 +1,15 @@
import { Conditional } from 'components/Conditional'
import { FormGroup } from 'components/FormGroup'
import { useInputState } from 'components/forms/FormInput.hooks'
import { useWallet } from 'contexts/wallet'
import React, { useEffect, useState } from 'react'
import { resolveAddress } from 'utils/resolveAddress'
import { useWallet } from 'utils/wallet'
import { NumberInput, TextInput } from '../forms/FormInput'
interface RoyaltyDetailsProps {
onChange: (data: RoyaltyDetailsDataProps) => void
importedRoyaltyDetails?: RoyaltyDetailsDataProps
}
export interface RoyaltyDetailsDataProps {
@@ -19,9 +20,10 @@ export interface RoyaltyDetailsDataProps {
type RoyaltyState = 'none' | 'new'
export const RoyaltyDetails = ({ onChange }: RoyaltyDetailsProps) => {
export const RoyaltyDetails = ({ onChange, importedRoyaltyDetails }: RoyaltyDetailsProps) => {
const wallet = useWallet()
const [royaltyState, setRoyaltyState] = useState<RoyaltyState>('none')
const [royaltyDetailsImported, setRoyaltyDetailsImported] = useState(false)
const royaltyPaymentAddressState = useInputState({
id: 'royalty-payment-address',
@@ -40,26 +42,38 @@ export const RoyaltyDetails = ({ onChange }: RoyaltyDetailsProps) => {
})
useEffect(() => {
void resolveAddress(
royaltyPaymentAddressState.value
.toLowerCase()
.replace(/,/g, '')
.replace(/"/g, '')
.replace(/'/g, '')
.replace(/ /g, ''),
wallet,
).then((royaltyPaymentAddress) => {
royaltyPaymentAddressState.onChange(royaltyPaymentAddress)
const data: RoyaltyDetailsDataProps = {
royaltyType: royaltyState,
paymentAddress: royaltyPaymentAddressState.value,
share: Number(royaltyShareState.value),
}
onChange(data)
})
if (!importedRoyaltyDetails || (importedRoyaltyDetails && royaltyDetailsImported)) {
void resolveAddress(
royaltyPaymentAddressState.value
.toLowerCase()
.replace(/,/g, '')
.replace(/"/g, '')
.replace(/'/g, '')
.replace(/ /g, ''),
wallet,
).then((royaltyPaymentAddress) => {
royaltyPaymentAddressState.onChange(royaltyPaymentAddress)
const data: RoyaltyDetailsDataProps = {
royaltyType: royaltyState,
paymentAddress: royaltyPaymentAddressState.value,
share: Number(royaltyShareState.value),
}
onChange(data)
})
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [royaltyState, royaltyPaymentAddressState.value, royaltyShareState.value])
useEffect(() => {
if (importedRoyaltyDetails) {
setRoyaltyState(importedRoyaltyDetails.royaltyType)
royaltyPaymentAddressState.onChange(importedRoyaltyDetails.paymentAddress.toString())
royaltyShareState.onChange(importedRoyaltyDetails.share.toString())
setRoyaltyDetailsImported(true)
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [importedRoyaltyDetails])
return (
<div className="py-3 px-8 mx-10 rounded border-2 border-white/20">
<div className="flex justify-center">
+528
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>
)
}
+324
View File
@@ -0,0 +1,324 @@
/* eslint-disable eslint-comments/disable-enable-pair */
/* eslint-disable no-nested-ternary */
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable camelcase */
import { GenericAuthorization } from 'cosmjs-types/cosmos/authz/v1beta1/authz'
import { MsgGrant } from 'cosmjs-types/cosmos/authz/v1beta1/tx'
import { SendAuthorization } from 'cosmjs-types/cosmos/bank/v1beta1/authz'
import { Coin } from 'cosmjs-types/cosmos/base/v1beta1/coin'
import type { AuthorizationType } from 'cosmjs-types/cosmos/staking/v1beta1/authz'
import { StakeAuthorization, StakeAuthorization_Validators } from 'cosmjs-types/cosmos/staking/v1beta1/authz'
import {
AcceptedMessageKeysFilter,
AllowAllMessagesFilter,
CombinedLimit,
ContractExecutionAuthorization,
ContractMigrationAuthorization,
MaxCallsLimit,
MaxFundsLimit,
} from 'cosmjs-types/cosmwasm/wasm/v1/authz'
import type { AuthorizationMode, GenericAuthorizationType, GrantAuthorizationType } from 'pages/authz/grant'
export interface Msg {
typeUrl: string
value: any
}
export interface AuthzMessage {
authzMode: AuthorizationMode
authzType: GrantAuthorizationType
displayName: string
typeUrl: string
genericAuthzType?: GenericAuthorizationType
}
export const grantGenericStakeAuthorization: AuthzMessage = {
authzMode: 'Grant',
authzType: 'Generic',
displayName: 'Stake',
typeUrl: '/cosmos.staking.v1beta1.MsgDelegate',
genericAuthzType: 'MsgDelegate',
}
export const grantGenericSendAuthorization: AuthzMessage = {
authzMode: 'Grant',
authzType: 'Generic',
displayName: 'Send',
typeUrl: '/cosmos.bank.v1beta1.MsgSend',
genericAuthzType: 'MsgSend',
}
export const authzMessages: AuthzMessage[] = [grantGenericStakeAuthorization, grantGenericSendAuthorization]
const msgAuthzGrantTypeUrl = '/cosmos.authz.v1beta1.MsgGrant'
export function AuthzSendGrantMsg(
granter: string,
grantee: string,
denom: string,
spendLimit: number,
expiration: number,
allowList?: string[],
): Msg {
const sendAuthValue = SendAuthorization.encode(
SendAuthorization.fromPartial({
spendLimit: [
Coin.fromPartial({
amount: String(spendLimit),
denom,
}),
],
// Needs cosmos-sdk >= 0.47
// allowList,
}),
).finish()
const grantValue = MsgGrant.fromPartial({
grant: {
authorization: {
typeUrl: '/cosmos.bank.v1beta1.SendAuthorization',
value: sendAuthValue,
},
expiration: expiration ? { seconds: BigInt(expiration) } : undefined,
},
grantee,
granter,
})
return {
typeUrl: msgAuthzGrantTypeUrl,
value: grantValue,
}
}
export function AuthzExecuteContractGrantMsg(
granter: string,
grantee: string,
contract: string,
expiration: number,
callsRemaining?: number,
amounts?: Coin[],
allowedMessages?: string[],
): Msg {
const sendAuthValue = ContractExecutionAuthorization.encode(
ContractExecutionAuthorization.fromPartial({
grants: [
{
contract,
filter: {
typeUrl: allowedMessages
? '/cosmwasm.wasm.v1.AcceptedMessageKeysFilter'
: '/cosmwasm.wasm.v1.AllowAllMessagesFilter',
value: allowedMessages
? AcceptedMessageKeysFilter.encode({ keys: allowedMessages }).finish()
: AllowAllMessagesFilter.encode({}).finish(),
},
limit:
callsRemaining || amounts
? {
typeUrl:
callsRemaining && amounts
? '/cosmwasm.wasm.v1.CombinedLimit'
: callsRemaining
? '/cosmwasm.wasm.v1.MaxCallsLimit'
: '/cosmwasm.wasm.v1.MaxFundsLimit',
value:
callsRemaining && amounts
? CombinedLimit.encode({
callsRemaining: BigInt(callsRemaining),
amounts,
}).finish()
: callsRemaining
? MaxCallsLimit.encode({
remaining: BigInt(callsRemaining),
}).finish()
: MaxFundsLimit.encode({
amounts: amounts || [],
}).finish(),
}
: {
// limit: undefined is not accepted
typeUrl: '/cosmwasm.wasm.v1.MaxCallsLimit',
value: MaxCallsLimit.encode({
remaining: BigInt(100000),
}).finish(),
},
},
],
}),
).finish()
const grantValue = MsgGrant.fromPartial({
grant: {
authorization: {
typeUrl: '/cosmwasm.wasm.v1.ContractExecutionAuthorization',
value: sendAuthValue,
},
expiration: expiration ? { seconds: BigInt(expiration), nanos: 0 } : undefined,
},
grantee,
granter,
})
return {
typeUrl: msgAuthzGrantTypeUrl,
value: grantValue,
}
}
export function AuthzMigrateContractGrantMsg(
granter: string,
grantee: string,
contract: string,
expiration: number,
callsRemaining?: number,
amounts?: Coin[],
allowedMessages?: string[],
): Msg {
const sendAuthValue = ContractMigrationAuthorization.encode(
ContractMigrationAuthorization.fromPartial({
grants: [
{
contract,
filter: {
typeUrl: allowedMessages
? '/cosmwasm.wasm.v1.AcceptedMessageKeysFilter'
: '/cosmwasm.wasm.v1.AllowAllMessagesFilter',
value: allowedMessages
? AcceptedMessageKeysFilter.encode({ keys: allowedMessages }).finish()
: AllowAllMessagesFilter.encode({}).finish(),
},
limit:
callsRemaining || amounts
? {
typeUrl:
callsRemaining && amounts
? '/cosmwasm.wasm.v1.CombinedLimit'
: callsRemaining
? '/cosmwasm.wasm.v1.MaxCallsLimit'
: '/cosmwasm.wasm.v1.MaxFundsLimit',
value:
callsRemaining && amounts
? CombinedLimit.encode({
callsRemaining: BigInt(callsRemaining),
amounts,
}).finish()
: callsRemaining
? MaxCallsLimit.encode({
remaining: BigInt(callsRemaining),
}).finish()
: MaxFundsLimit.encode({
amounts: amounts || [],
}).finish(),
}
: {
// limit: undefined is not accepted
typeUrl: '/cosmwasm.wasm.v1.MaxCallsLimit',
value: MaxCallsLimit.encode({
remaining: BigInt(100000),
}).finish(),
},
},
],
}),
).finish()
const grantValue = MsgGrant.fromPartial({
grant: {
authorization: {
typeUrl: '/cosmwasm.wasm.v1.ContractMigrationAuthorization',
value: sendAuthValue,
},
expiration: expiration ? { seconds: BigInt(expiration), nanos: 0 } : undefined,
},
grantee,
granter,
})
return {
typeUrl: msgAuthzGrantTypeUrl,
value: grantValue,
}
}
export function AuthzGenericGrantMsg(granter: string, grantee: string, typeURL: string, expiration: number): Msg {
return {
typeUrl: msgAuthzGrantTypeUrl,
value: {
grant: {
authorization: {
typeUrl: '/cosmos.authz.v1beta1.GenericAuthorization',
value: GenericAuthorization.encode(
GenericAuthorization.fromPartial({
msg: typeURL,
}),
).finish(),
},
expiration: expiration ? { seconds: expiration } : undefined,
},
grantee,
granter,
},
}
}
export function AuthzStakeGrantMsg({
expiration,
grantee,
granter,
allowList,
denyList,
maxTokens,
denom,
stakeAuthzType,
}: {
granter: string
grantee: string
expiration: number
allowList?: string[]
denyList?: string[]
maxTokens?: string
denom?: string
stakeAuthzType: AuthorizationType
}): Msg {
const allow_list = StakeAuthorization_Validators.encode(
StakeAuthorization_Validators.fromPartial({
address: allowList,
}),
).finish()
const deny_list = StakeAuthorization_Validators.encode(
StakeAuthorization_Validators.fromPartial({
address: denyList,
}),
).finish()
const stakeAuthValue = StakeAuthorization.encode(
StakeAuthorization.fromPartial({
authorizationType: stakeAuthzType,
allowList: allowList?.length ? StakeAuthorization_Validators.decode(allow_list) : undefined,
denyList: denyList?.length ? StakeAuthorization_Validators.decode(deny_list) : undefined,
maxTokens: maxTokens
? Coin.fromPartial({
amount: maxTokens,
denom,
})
: undefined,
}),
).finish()
const grantValue = MsgGrant.fromPartial({
grant: {
authorization: {
typeUrl: '/cosmos.staking.v1beta1.StakeAuthorization',
value: stakeAuthValue,
},
expiration: { seconds: BigInt(expiration) },
},
grantee,
granter,
})
return {
typeUrl: msgAuthzGrantTypeUrl,
value: grantValue,
}
}
+3 -3
View File
@@ -1,9 +1,9 @@
{
"path": "/assets/",
"appName": "StargazeStudio",
"appShortName": "StargazeStudio",
"appName": "Stargaze Studio",
"appShortName": "Stargaze Studio",
"appDescription": "Stargaze Studio is built to provide useful smart contract interfaces that help you build and deploy your own NFT collection in no time.",
"developerName": "StargazeStudio",
"developerName": "Stargaze Studio",
"developerURL": "https://",
"background": "#FFC27D",
"theme_color": "#FFC27D",
-1
View File
@@ -1,3 +1,2 @@
export * from './app'
export * from './keplr'
export * from './network'
-76
View File
@@ -1,76 +0,0 @@
import type { ChainInfo } from '@keplr-wallet/types'
import type { AppConfig } from './app'
export interface KeplrCoin {
readonly coinDenom: string
readonly coinMinimalDenom: string
readonly coinDecimals: number
}
export interface KeplrConfig {
readonly chainId: string
readonly chainName: string
readonly rpc: string
readonly rest?: string
readonly bech32Config: {
readonly bech32PrefixAccAddr: string
readonly bech32PrefixAccPub: string
readonly bech32PrefixValAddr: string
readonly bech32PrefixValPub: string
readonly bech32PrefixConsAddr: string
readonly bech32PrefixConsPub: string
}
readonly currencies: readonly KeplrCoin[]
readonly feeCurrencies: readonly KeplrCoin[]
readonly stakeCurrency: KeplrCoin
readonly gasPriceStep: {
readonly low: number
readonly average: number
readonly high: number
}
readonly bip44: { readonly coinType: number }
readonly coinType: number
}
export const keplrConfig = (config: AppConfig): ChainInfo => ({
chainId: config.chainId,
chainName: config.chainName,
rpc: config.rpcUrl,
rest: config.httpUrl!,
bech32Config: {
bech32PrefixAccAddr: `${config.addressPrefix}`,
bech32PrefixAccPub: `${config.addressPrefix}pub`,
bech32PrefixValAddr: `${config.addressPrefix}valoper`,
bech32PrefixValPub: `${config.addressPrefix}valoperpub`,
bech32PrefixConsAddr: `${config.addressPrefix}valcons`,
bech32PrefixConsPub: `${config.addressPrefix}valconspub`,
},
currencies: [
{
coinDenom: config.coinMap[config.feeToken].denom,
coinMinimalDenom: config.feeToken,
coinDecimals: config.coinMap[config.feeToken].fractionalDigits,
},
],
feeCurrencies: [
{
coinDenom: config.coinMap[config.feeToken].denom,
coinMinimalDenom: config.feeToken,
coinDecimals: config.coinMap[config.feeToken].fractionalDigits,
},
],
stakeCurrency: {
coinDenom: config.coinMap[config.stakingToken].denom,
coinMinimalDenom: config.stakingToken,
coinDecimals: config.coinMap[config.stakingToken].fractionalDigits,
},
gasPriceStep: {
low: config.gasPrice / 2,
average: config.gasPrice,
high: config.gasPrice * 2,
},
bip44: { coinType: 118 },
coinType: 118,
features: ['ibc-transfer', 'cosmwasm', 'ibc-go'],
})
+1 -1
View File
@@ -6,6 +6,6 @@ export const meta = {
domain: 'stargaze.tools',
url: faviconsJson.developerURL,
twitter: {
username: '@stargazestudio',
username: '@StargazeZone',
},
}
+631 -1
View File
@@ -1,28 +1,87 @@
import {
FEATURED_IBC_TIA_FACTORY_ADDRESS,
FEATURED_IBC_USDC_FACTORY_ADDRESS,
FEATURED_VENDING_FACTORY_ADDRESS,
FEATURED_VENDING_FACTORY_FLEX_ADDRESS,
FEATURED_VENDING_FACTORY_MERKLE_TREE_ADDRESS,
FEATURED_VENDING_IBC_TIA_FACTORY_FLEX_ADDRESS,
FEATURED_VENDING_IBC_TIA_FACTORY_MERKLE_TREE_ADDRESS,
FEATURED_VENDING_IBC_USDC_FACTORY_FLEX_ADDRESS,
OPEN_EDITION_FACTORY_ADDRESS,
OPEN_EDITION_FACTORY_FLEX_ADDRESS,
OPEN_EDITION_IBC_ATOM_FACTORY_ADDRESS,
OPEN_EDITION_IBC_ATOM_FACTORY_FLEX_ADDRESS,
OPEN_EDITION_IBC_CRBRUS_FACTORY_ADDRESS,
OPEN_EDITION_IBC_FRNZ_FACTORY_ADDRESS,
OPEN_EDITION_IBC_KUJI_FACTORY_ADDRESS,
OPEN_EDITION_IBC_NBTC_FACTORY_ADDRESS,
OPEN_EDITION_IBC_TIA_FACTORY_ADDRESS,
OPEN_EDITION_IBC_TIA_FACTORY_FLEX_ADDRESS,
OPEN_EDITION_IBC_USDC_FACTORY_ADDRESS,
OPEN_EDITION_IBC_USDC_FACTORY_FLEX_ADDRESS,
OPEN_EDITION_IBC_USK_FACTORY_ADDRESS,
OPEN_EDITION_NATIVE_BRNCH_FACTORY_ADDRESS,
OPEN_EDITION_NATIVE_STRDST_FACTORY_ADDRESS,
OPEN_EDITION_UPDATABLE_FACTORY_ADDRESS,
OPEN_EDITION_UPDATABLE_IBC_ATOM_FACTORY_ADDRESS,
OPEN_EDITION_UPDATABLE_IBC_FRNZ_FACTORY_ADDRESS,
OPEN_EDITION_UPDATABLE_IBC_NBTC_FACTORY_ADDRESS,
OPEN_EDITION_UPDATABLE_IBC_TIA_FACTORY_ADDRESS,
OPEN_EDITION_UPDATABLE_IBC_USDC_FACTORY_ADDRESS,
OPEN_EDITION_UPDATABLE_IBC_USK_FACTORY_ADDRESS,
VENDING_FACTORY_ADDRESS,
VENDING_FACTORY_FLEX_ADDRESS,
VENDING_FACTORY_MERKLE_TREE_ADDRESS,
VENDING_FACTORY_UPDATABLE_ADDRESS,
VENDING_FACTORY_UPDATABLE_FLEX_ADDRESS,
VENDING_IBC_ATOM_FACTORY_ADDRESS,
VENDING_IBC_ATOM_FACTORY_FLEX_ADDRESS,
VENDING_IBC_ATOM_UPDATABLE_FACTORY_ADDRESS,
VENDING_IBC_ATOM_UPDATABLE_FACTORY_FLEX_ADDRESS,
VENDING_IBC_CRBRUS_FACTORY_ADDRESS,
VENDING_IBC_CRBRUS_FACTORY_FLEX_ADDRESS,
VENDING_IBC_KUJI_FACTORY_ADDRESS,
VENDING_IBC_KUJI_FACTORY_FLEX_ADDRESS,
VENDING_IBC_NBTC_FACTORY_ADDRESS,
VENDING_IBC_NBTC_FACTORY_FLEX_ADDRESS,
VENDING_IBC_NBTC_UPDATABLE_FACTORY_ADDRESS,
VENDING_IBC_NBTC_UPDATABLE_FACTORY_FLEX_ADDRESS,
VENDING_IBC_TIA_FACTORY_ADDRESS,
VENDING_IBC_TIA_FACTORY_FLEX_ADDRESS,
VENDING_IBC_TIA_FACTORY_MERKLE_TREE_ADDRESS,
VENDING_IBC_TIA_UPDATABLE_FACTORY_ADDRESS,
VENDING_IBC_TIA_UPDATABLE_FACTORY_FLEX_ADDRESS,
VENDING_IBC_USDC_FACTORY_ADDRESS,
VENDING_IBC_USDC_FACTORY_FLEX_ADDRESS,
VENDING_IBC_USDC_UPDATABLE_FACTORY_ADDRESS,
VENDING_IBC_USDC_UPDATABLE_FACTORY_FLEX_ADDRESS,
VENDING_IBC_USK_FACTORY_ADDRESS,
VENDING_IBC_USK_FACTORY_FLEX_ADDRESS,
VENDING_IBC_USK_UPDATABLE_FACTORY_ADDRESS,
VENDING_IBC_USK_UPDATABLE_FACTORY_FLEX_ADDRESS,
VENDING_NATIVE_BRNCH_FACTORY_ADDRESS,
VENDING_NATIVE_BRNCH_FLEX_FACTORY_ADDRESS,
VENDING_NATIVE_BRNCH_UPDATABLE_FACTORY_ADDRESS,
VENDING_NATIVE_STARDUST_FACTORY_ADDRESS,
VENDING_NATIVE_STARDUST_UPDATABLE_FACTORY_ADDRESS,
VENDING_NATIVE_STRDST_FLEX_FACTORY_ADDRESS,
} from 'utils/constants'
import type { TokenInfo } from './token'
import { ibcAtom, ibcFrnz, ibcUsdc, stars } from './token'
import {
ibcAtom,
ibcCrbrus,
ibcFrnz,
// ibcHuahua,
ibcKuji,
ibcNbtc,
ibcTia,
ibcUsdc,
ibcUsk,
nativeBrnch,
nativeStardust,
stars,
} from './token'
export interface MinterInfo {
id: string
@@ -30,6 +89,8 @@ export interface MinterInfo {
supportedToken: TokenInfo
updatable?: boolean
flexible?: boolean
merkleTree?: boolean
featured?: boolean
}
export const openEditionStarsMinter: MinterInfo = {
@@ -37,6 +98,8 @@ export const openEditionStarsMinter: MinterInfo = {
factoryAddress: OPEN_EDITION_FACTORY_ADDRESS,
supportedToken: stars,
updatable: false,
featured: false,
flexible: false,
}
export const openEditionUpdatableStarsMinter: MinterInfo = {
@@ -44,6 +107,8 @@ export const openEditionUpdatableStarsMinter: MinterInfo = {
factoryAddress: OPEN_EDITION_UPDATABLE_FACTORY_ADDRESS,
supportedToken: stars,
updatable: true,
featured: false,
flexible: false,
}
export const openEditionIbcAtomMinter: MinterInfo = {
@@ -51,6 +116,8 @@ export const openEditionIbcAtomMinter: MinterInfo = {
factoryAddress: OPEN_EDITION_IBC_ATOM_FACTORY_ADDRESS,
supportedToken: ibcAtom,
updatable: false,
featured: false,
flexible: false,
}
export const openEditionUpdatableIbcAtomMinter: MinterInfo = {
@@ -58,6 +125,8 @@ export const openEditionUpdatableIbcAtomMinter: MinterInfo = {
factoryAddress: OPEN_EDITION_UPDATABLE_IBC_ATOM_FACTORY_ADDRESS,
supportedToken: ibcAtom,
updatable: true,
featured: false,
flexible: false,
}
export const openEditionIbcUsdcMinter: MinterInfo = {
@@ -65,6 +134,26 @@ export const openEditionIbcUsdcMinter: MinterInfo = {
factoryAddress: OPEN_EDITION_IBC_USDC_FACTORY_ADDRESS,
supportedToken: ibcUsdc,
updatable: false,
featured: false,
flexible: false,
}
export const openEditionIbcTiaMinter: MinterInfo = {
id: 'open-edition-ibc-tia-minter',
factoryAddress: OPEN_EDITION_IBC_TIA_FACTORY_ADDRESS,
supportedToken: ibcTia,
updatable: false,
featured: false,
flexible: false,
}
export const openEditionIbcNbtcMinter: MinterInfo = {
id: 'open-edition-ibc-nbtc-minter',
factoryAddress: OPEN_EDITION_IBC_NBTC_FACTORY_ADDRESS,
supportedToken: ibcNbtc,
updatable: false,
featured: false,
flexible: false,
}
export const openEditionUpdatableIbcUsdcMinter: MinterInfo = {
@@ -72,6 +161,26 @@ export const openEditionUpdatableIbcUsdcMinter: MinterInfo = {
factoryAddress: OPEN_EDITION_UPDATABLE_IBC_USDC_FACTORY_ADDRESS,
supportedToken: ibcUsdc,
updatable: true,
featured: false,
flexible: false,
}
export const openEditionUpdatableIbcTiaMinter: MinterInfo = {
id: 'open-edition-updatable-ibc-tia-minter',
factoryAddress: OPEN_EDITION_UPDATABLE_IBC_TIA_FACTORY_ADDRESS,
supportedToken: ibcTia,
updatable: true,
featured: false,
flexible: false,
}
export const openEditionUpdatableIbcNbtcMinter: MinterInfo = {
id: 'open-edition-updatable-ibc-nbtc-minter',
factoryAddress: OPEN_EDITION_UPDATABLE_IBC_NBTC_FACTORY_ADDRESS,
supportedToken: ibcNbtc,
updatable: true,
featured: false,
flexible: false,
}
export const openEditionIbcFrnzMinter: MinterInfo = {
@@ -79,6 +188,8 @@ export const openEditionIbcFrnzMinter: MinterInfo = {
factoryAddress: OPEN_EDITION_IBC_FRNZ_FACTORY_ADDRESS,
supportedToken: ibcFrnz,
updatable: false,
featured: false,
flexible: false,
}
export const openEditionUpdatableIbcFrnzMinter: MinterInfo = {
@@ -86,6 +197,70 @@ export const openEditionUpdatableIbcFrnzMinter: MinterInfo = {
factoryAddress: OPEN_EDITION_UPDATABLE_IBC_FRNZ_FACTORY_ADDRESS,
supportedToken: ibcFrnz,
updatable: true,
featured: false,
flexible: false,
}
export const openEditionIbcUskMinter: MinterInfo = {
id: 'open-edition-ibc-usk-minter',
factoryAddress: OPEN_EDITION_IBC_USK_FACTORY_ADDRESS,
supportedToken: ibcUsk,
updatable: false,
featured: false,
flexible: false,
}
export const openEditionUpdatableIbcUskMinter: MinterInfo = {
id: 'open-edition-updatable-ibc-usk-minter',
factoryAddress: OPEN_EDITION_UPDATABLE_IBC_USK_FACTORY_ADDRESS,
supportedToken: ibcUsk,
updatable: true,
featured: false,
flexible: false,
}
export const openEditionIbcKujiMinter: MinterInfo = {
id: 'open-edition-ibc-kuji-minter',
factoryAddress: OPEN_EDITION_IBC_KUJI_FACTORY_ADDRESS,
supportedToken: ibcKuji,
updatable: false,
featured: false,
flexible: false,
}
// export const openEditionIbcHuahuaMinter: MinterInfo = {
// id: 'open-edition-ibc-huahua-minter',
// factoryAddress: OPEN_EDITION_IBC_HUAHUA_FACTORY_ADDRESS,
// supportedToken: ibcHuahua,
// updatable: false,
// featured: false,
// }
export const openEditionIbcCrbrusMinter: MinterInfo = {
id: 'open-edition-ibc-crbrus-minter',
factoryAddress: OPEN_EDITION_IBC_CRBRUS_FACTORY_ADDRESS,
supportedToken: ibcCrbrus,
updatable: false,
featured: false,
flexible: false,
}
export const openEditionNativeStrdstMinter: MinterInfo = {
id: 'open-edition-native-strdst-minter',
factoryAddress: OPEN_EDITION_NATIVE_STRDST_FACTORY_ADDRESS,
supportedToken: nativeStardust,
updatable: false,
featured: false,
flexible: false,
}
export const openEditionNativeBrnchMinter: MinterInfo = {
id: 'open-edition-native-brnch-minter',
factoryAddress: OPEN_EDITION_NATIVE_BRNCH_FACTORY_ADDRESS,
supportedToken: nativeBrnch,
updatable: false,
featured: false,
flexible: false,
}
export const openEditionMinterList = [
@@ -97,6 +272,60 @@ export const openEditionMinterList = [
openEditionUpdatableIbcFrnzMinter,
openEditionIbcUsdcMinter,
openEditionUpdatableIbcUsdcMinter,
openEditionIbcTiaMinter,
openEditionUpdatableIbcTiaMinter,
openEditionIbcNbtcMinter,
openEditionUpdatableIbcNbtcMinter,
openEditionIbcUskMinter,
openEditionUpdatableIbcUskMinter,
openEditionIbcKujiMinter,
// openEditionIbcHuahuaMinter,
openEditionIbcCrbrusMinter,
openEditionNativeStrdstMinter,
openEditionNativeBrnchMinter,
]
export const flexibleOpenEditionStarsMinter: MinterInfo = {
id: 'flexible-open-edition-stars-minter',
factoryAddress: OPEN_EDITION_FACTORY_FLEX_ADDRESS,
supportedToken: stars,
updatable: false,
featured: false,
flexible: true,
}
export const flexibleOpenEditionIbcAtomMinter: MinterInfo = {
id: 'flexible-open-edition-ibc-atom-minter',
factoryAddress: OPEN_EDITION_IBC_ATOM_FACTORY_FLEX_ADDRESS,
supportedToken: ibcAtom,
updatable: false,
featured: false,
flexible: true,
}
export const flexibleOpenEditionIbcUsdcMinter: MinterInfo = {
id: 'flexible-open-edition-ibc-usdc-minter',
factoryAddress: OPEN_EDITION_IBC_USDC_FACTORY_FLEX_ADDRESS,
supportedToken: ibcUsdc,
updatable: false,
featured: false,
flexible: true,
}
export const flexibleOpenEditionIbcTiaMinter: MinterInfo = {
id: 'flexible-open-edition-ibc-tia-minter',
factoryAddress: OPEN_EDITION_IBC_TIA_FACTORY_FLEX_ADDRESS,
supportedToken: ibcTia,
updatable: false,
featured: false,
flexible: true,
}
export const flexibleOpenEditionMinterList = [
flexibleOpenEditionStarsMinter,
flexibleOpenEditionIbcAtomMinter,
flexibleOpenEditionIbcUsdcMinter,
flexibleOpenEditionIbcTiaMinter,
]
export const vendingStarsMinter: MinterInfo = {
@@ -105,6 +334,18 @@ export const vendingStarsMinter: MinterInfo = {
supportedToken: stars,
updatable: false,
flexible: false,
merkleTree: false,
featured: false,
}
export const vendingFeaturedStarsMinter: MinterInfo = {
id: 'vending-stars-minter',
factoryAddress: FEATURED_VENDING_FACTORY_ADDRESS,
supportedToken: stars,
updatable: false,
flexible: false,
merkleTree: false,
featured: true,
}
export const vendingUpdatableStarsMinter: MinterInfo = {
@@ -113,6 +354,8 @@ export const vendingUpdatableStarsMinter: MinterInfo = {
supportedToken: stars,
updatable: true,
flexible: false,
merkleTree: false,
featured: false,
}
export const vendingIbcAtomMinter: MinterInfo = {
@@ -121,6 +364,8 @@ export const vendingIbcAtomMinter: MinterInfo = {
supportedToken: ibcAtom,
updatable: false,
flexible: false,
merkleTree: false,
featured: false,
}
export const vendingUpdatableIbcAtomMinter: MinterInfo = {
@@ -129,6 +374,8 @@ export const vendingUpdatableIbcAtomMinter: MinterInfo = {
supportedToken: ibcAtom,
updatable: true,
flexible: false,
merkleTree: false,
featured: false,
}
export const vendingIbcUsdcMinter: MinterInfo = {
@@ -137,6 +384,48 @@ export const vendingIbcUsdcMinter: MinterInfo = {
supportedToken: ibcUsdc,
updatable: false,
flexible: false,
merkleTree: false,
featured: false,
}
export const vendingFeaturedIbcUsdcMinter: MinterInfo = {
id: 'vending-featured-ibc-usdc-minter',
factoryAddress: FEATURED_IBC_USDC_FACTORY_ADDRESS,
supportedToken: ibcUsdc,
updatable: false,
flexible: false,
merkleTree: false,
featured: true,
}
export const vendingIbcTiaMinter: MinterInfo = {
id: 'vending-ibc-tia-minter',
factoryAddress: VENDING_IBC_TIA_FACTORY_ADDRESS,
supportedToken: ibcTia,
updatable: false,
flexible: false,
merkleTree: false,
featured: false,
}
export const vendingFeaturedIbcTiaMinter: MinterInfo = {
id: 'vending-featured-ibc-tia-minter',
factoryAddress: FEATURED_IBC_TIA_FACTORY_ADDRESS,
supportedToken: ibcTia,
updatable: false,
flexible: false,
merkleTree: false,
featured: true,
}
export const vendingIbcNbtcMinter: MinterInfo = {
id: 'vending-ibc-nbtc-minter',
factoryAddress: VENDING_IBC_NBTC_FACTORY_ADDRESS,
supportedToken: ibcNbtc,
updatable: false,
flexible: false,
merkleTree: false,
featured: false,
}
export const vendingUpdatableIbcUsdcMinter: MinterInfo = {
@@ -145,15 +434,143 @@ export const vendingUpdatableIbcUsdcMinter: MinterInfo = {
supportedToken: ibcUsdc,
updatable: true,
flexible: false,
merkleTree: false,
featured: false,
}
export const vendingUpdatableIbcTiaMinter: MinterInfo = {
id: 'vending-updatable-ibc-tia-minter',
factoryAddress: VENDING_IBC_TIA_UPDATABLE_FACTORY_ADDRESS,
supportedToken: ibcTia,
updatable: true,
flexible: false,
merkleTree: false,
featured: false,
}
export const vendingUpdatableIbcNbtcMinter: MinterInfo = {
id: 'vending-updatable-ibc-nbtc-minter',
factoryAddress: VENDING_IBC_NBTC_UPDATABLE_FACTORY_ADDRESS,
supportedToken: ibcNbtc,
updatable: true,
flexible: false,
merkleTree: false,
featured: false,
}
export const vendingIbcUskMinter: MinterInfo = {
id: 'vending-ibc-usk-minter',
factoryAddress: VENDING_IBC_USK_FACTORY_ADDRESS,
supportedToken: ibcUsk,
updatable: false,
flexible: false,
merkleTree: false,
featured: false,
}
export const vendingUpdatableIbcUskMinter: MinterInfo = {
id: 'vending-updatable-ibc-usk-minter',
factoryAddress: VENDING_IBC_USK_UPDATABLE_FACTORY_ADDRESS,
supportedToken: ibcUsk,
updatable: true,
flexible: false,
merkleTree: false,
featured: false,
}
export const vendingIbcKujiMinter: MinterInfo = {
id: 'vending-ibc-kuji-minter',
factoryAddress: VENDING_IBC_KUJI_FACTORY_ADDRESS,
supportedToken: ibcKuji,
updatable: false,
flexible: false,
merkleTree: false,
featured: false,
}
// export const vendingIbcHuahuaMinter: MinterInfo = {
// id: 'vending-ibc-huahua-minter',
// factoryAddress: VENDING_IBC_HUAHUA_FACTORY_ADDRESS,
// supportedToken: ibcHuahua,
// updatable: false,
// flexible: false,
// merkleTree: false,
// featured: false,
// }
export const vendingIbcCrbrusMinter: MinterInfo = {
id: 'vending-ibc-crbrus-minter',
factoryAddress: VENDING_IBC_CRBRUS_FACTORY_ADDRESS,
supportedToken: ibcCrbrus,
updatable: false,
flexible: false,
merkleTree: false,
featured: false,
}
export const vendingNativeStardustMinter: MinterInfo = {
id: 'vending-native-stardust-minter',
factoryAddress: VENDING_NATIVE_STARDUST_FACTORY_ADDRESS,
supportedToken: nativeStardust,
updatable: false,
flexible: false,
merkleTree: false,
featured: false,
}
export const vendingUpdatableNativeStardustMinter: MinterInfo = {
id: 'vending-native-stardust-minter',
factoryAddress: VENDING_NATIVE_STARDUST_UPDATABLE_FACTORY_ADDRESS,
supportedToken: nativeStardust,
updatable: true,
flexible: false,
merkleTree: false,
featured: false,
}
export const vendingNativeBrnchMinter: MinterInfo = {
id: 'vending-native-brnch-minter',
factoryAddress: VENDING_NATIVE_BRNCH_FACTORY_ADDRESS,
supportedToken: nativeBrnch,
updatable: false,
flexible: false,
merkleTree: false,
featured: false,
}
export const vendingUpdatableNativeBrnchMinter: MinterInfo = {
id: 'vending-native-brnch-minter',
factoryAddress: VENDING_NATIVE_BRNCH_UPDATABLE_FACTORY_ADDRESS,
supportedToken: nativeBrnch,
updatable: true,
flexible: false,
merkleTree: false,
featured: false,
}
export const vendingMinterList = [
vendingStarsMinter,
vendingFeaturedStarsMinter,
vendingUpdatableStarsMinter,
vendingIbcAtomMinter,
vendingUpdatableIbcAtomMinter,
vendingIbcUsdcMinter,
vendingFeaturedIbcUsdcMinter,
vendingUpdatableIbcUsdcMinter,
vendingIbcTiaMinter,
vendingFeaturedIbcTiaMinter,
vendingUpdatableIbcTiaMinter,
vendingIbcNbtcMinter,
vendingUpdatableIbcNbtcMinter,
vendingIbcUskMinter,
vendingUpdatableIbcUskMinter,
vendingIbcKujiMinter,
// vendingIbcHuahuaMinter,
vendingIbcCrbrusMinter,
vendingNativeStardustMinter,
vendingUpdatableNativeStardustMinter,
vendingNativeBrnchMinter,
vendingUpdatableNativeBrnchMinter,
]
export const flexibleVendingStarsMinter: MinterInfo = {
@@ -162,6 +579,18 @@ export const flexibleVendingStarsMinter: MinterInfo = {
supportedToken: stars,
updatable: false,
flexible: true,
merkleTree: false,
featured: false,
}
export const flexibleFeaturedVendingStarsMinter: MinterInfo = {
id: 'flexible-vending-stars-minter',
factoryAddress: FEATURED_VENDING_FACTORY_FLEX_ADDRESS,
supportedToken: stars,
updatable: false,
flexible: true,
merkleTree: false,
featured: true,
}
export const flexibleVendingUpdatableStarsMinter: MinterInfo = {
@@ -170,6 +599,8 @@ export const flexibleVendingUpdatableStarsMinter: MinterInfo = {
supportedToken: stars,
updatable: true,
flexible: true,
merkleTree: false,
featured: false,
}
export const flexibleVendingIbcAtomMinter: MinterInfo = {
@@ -178,6 +609,8 @@ export const flexibleVendingIbcAtomMinter: MinterInfo = {
supportedToken: ibcAtom,
updatable: false,
flexible: true,
merkleTree: false,
featured: false,
}
export const flexibleVendingUpdatableIbcAtomMinter: MinterInfo = {
@@ -186,6 +619,8 @@ export const flexibleVendingUpdatableIbcAtomMinter: MinterInfo = {
supportedToken: ibcAtom,
updatable: true,
flexible: true,
merkleTree: false,
featured: false,
}
export const flexibleVendingIbcUsdcMinter: MinterInfo = {
@@ -194,6 +629,48 @@ export const flexibleVendingIbcUsdcMinter: MinterInfo = {
supportedToken: ibcUsdc,
updatable: false,
flexible: true,
merkleTree: false,
featured: false,
}
export const flexibleFeaturedVendingIbcUsdcMinter: MinterInfo = {
id: 'flexible-featured-vending-ibc-usdc-minter',
factoryAddress: FEATURED_VENDING_IBC_USDC_FACTORY_FLEX_ADDRESS,
supportedToken: ibcUsdc,
updatable: false,
flexible: true,
merkleTree: false,
featured: true,
}
export const flexibleVendingIbcTiaMinter: MinterInfo = {
id: 'flexible-vending-ibc-tia-minter',
factoryAddress: VENDING_IBC_TIA_FACTORY_FLEX_ADDRESS,
supportedToken: ibcTia,
updatable: false,
flexible: true,
merkleTree: false,
featured: false,
}
export const flexibleFeaturedVendingIbcTiaMinter: MinterInfo = {
id: 'flexible-featured-vending-ibc-tia-minter',
factoryAddress: FEATURED_VENDING_IBC_TIA_FACTORY_FLEX_ADDRESS,
supportedToken: ibcTia,
updatable: false,
flexible: true,
merkleTree: false,
featured: true,
}
export const flexibleVendingIbcNbtcMinter: MinterInfo = {
id: 'flexible-vending-ibc-nbtc-minter',
factoryAddress: VENDING_IBC_NBTC_FACTORY_FLEX_ADDRESS,
supportedToken: ibcNbtc,
updatable: false,
flexible: true,
merkleTree: false,
featured: false,
}
export const flexibleVendingUpdatableIbcUsdcMinter: MinterInfo = {
@@ -202,13 +679,166 @@ export const flexibleVendingUpdatableIbcUsdcMinter: MinterInfo = {
supportedToken: ibcUsdc,
updatable: true,
flexible: true,
merkleTree: false,
featured: false,
}
export const flexibleVendingUpdatableIbcTiaMinter: MinterInfo = {
id: 'flexible-vending-updatable-ibc-tia-minter',
factoryAddress: VENDING_IBC_TIA_UPDATABLE_FACTORY_FLEX_ADDRESS,
supportedToken: ibcTia,
updatable: true,
flexible: true,
merkleTree: false,
featured: false,
}
export const flexibleVendingUpdatableIbcNbtcMinter: MinterInfo = {
id: 'flexible-vending-updatable-ibc-nbtc-minter',
factoryAddress: VENDING_IBC_NBTC_UPDATABLE_FACTORY_FLEX_ADDRESS,
supportedToken: ibcNbtc,
updatable: true,
flexible: true,
merkleTree: false,
featured: false,
}
export const flexibleVendingIbcUskMinter: MinterInfo = {
id: 'flexible-vending-ibc-usk-minter',
factoryAddress: VENDING_IBC_USK_FACTORY_FLEX_ADDRESS,
supportedToken: ibcUsk,
updatable: false,
flexible: true,
merkleTree: false,
featured: false,
}
export const flexibleVendingUpdatableIbcUskMinter: MinterInfo = {
id: 'flexible-vending-updatable-ibc-usk-minter',
factoryAddress: VENDING_IBC_USK_UPDATABLE_FACTORY_FLEX_ADDRESS,
supportedToken: ibcUsk,
updatable: true,
flexible: true,
merkleTree: false,
featured: false,
}
export const flexibleVendingIbcKujiMinter: MinterInfo = {
id: 'flexible-vending-ibc-kuji-minter',
factoryAddress: VENDING_IBC_KUJI_FACTORY_FLEX_ADDRESS,
supportedToken: ibcKuji,
updatable: false,
flexible: true,
merkleTree: false,
featured: false,
}
// export const flexibleVendingIbcHuahuaMinter: MinterInfo = {
// id: 'flexible-vending-ibc-huahua-minter',
// factoryAddress: VENDING_IBC_HUAHUA_FACTORY_FLEX_ADDRESS,
// supportedToken: ibcHuahua,
// updatable: false,
// flexible: true,
// merkleTree: false,
// featured: false,
// }
export const flexibleVendingIbcCrbrusMinter: MinterInfo = {
id: 'flexible-vending-ibc-crbrus-minter',
factoryAddress: VENDING_IBC_CRBRUS_FACTORY_FLEX_ADDRESS,
supportedToken: ibcCrbrus,
updatable: false,
flexible: true,
merkleTree: false,
featured: false,
}
export const flexibleVendingStrdstMinter: MinterInfo = {
id: 'flexible-vending-native-strdst-minter',
factoryAddress: VENDING_NATIVE_STRDST_FLEX_FACTORY_ADDRESS,
supportedToken: nativeStardust,
updatable: false,
flexible: true,
merkleTree: false,
featured: false,
}
export const flexibleVendingBrnchMinter: MinterInfo = {
id: 'flexible-vending-native-brnch-minter',
factoryAddress: VENDING_NATIVE_BRNCH_FLEX_FACTORY_ADDRESS,
supportedToken: nativeBrnch,
updatable: false,
flexible: true,
merkleTree: false,
featured: false,
}
export const flexibleVendingMinterList = [
flexibleVendingStarsMinter,
flexibleFeaturedVendingStarsMinter,
flexibleVendingUpdatableStarsMinter,
flexibleVendingIbcAtomMinter,
flexibleVendingUpdatableIbcAtomMinter,
flexibleVendingIbcUsdcMinter,
flexibleFeaturedVendingIbcUsdcMinter,
flexibleVendingUpdatableIbcUsdcMinter,
flexibleVendingIbcTiaMinter,
flexibleFeaturedVendingIbcTiaMinter,
flexibleVendingUpdatableIbcTiaMinter,
flexibleVendingIbcNbtcMinter,
flexibleVendingUpdatableIbcNbtcMinter,
flexibleVendingIbcUskMinter,
flexibleVendingUpdatableIbcUskMinter,
flexibleVendingIbcKujiMinter,
// flexibleVendingIbcHuahuaMinter,
flexibleVendingIbcCrbrusMinter,
flexibleVendingStrdstMinter,
flexibleVendingBrnchMinter,
]
export const merkleTreeVendingStarsMinter: MinterInfo = {
id: 'merkletree-vending-stars-minter',
factoryAddress: VENDING_FACTORY_MERKLE_TREE_ADDRESS,
supportedToken: stars,
updatable: false,
flexible: false,
merkleTree: true,
featured: false,
}
export const merkleTreeVendingFeaturedStarsMinter: MinterInfo = {
id: 'merkletree-vending-featured-stars-minter',
factoryAddress: FEATURED_VENDING_FACTORY_MERKLE_TREE_ADDRESS,
supportedToken: stars,
updatable: false,
flexible: false,
merkleTree: true,
featured: true,
}
export const merkleTreeVendingIbcTiaMinter: MinterInfo = {
id: 'merkletree-vending-ibc-tia-minter',
factoryAddress: VENDING_IBC_TIA_FACTORY_MERKLE_TREE_ADDRESS,
supportedToken: ibcTia,
updatable: false,
flexible: false,
merkleTree: true,
featured: false,
}
export const merkleTreeVendingFeaturedIbcTiaMinter: MinterInfo = {
id: 'merkletree-vending-featured-ibc-tia-minter',
factoryAddress: FEATURED_VENDING_IBC_TIA_FACTORY_MERKLE_TREE_ADDRESS,
supportedToken: ibcTia,
updatable: false,
flexible: false,
merkleTree: true,
featured: true,
}
export const merkleTreeVendingMinterList = [
merkleTreeVendingStarsMinter,
merkleTreeVendingIbcTiaMinter,
merkleTreeVendingFeaturedStarsMinter,
merkleTreeVendingFeaturedIbcTiaMinter,
]
+1
View File
@@ -5,6 +5,7 @@ export const mainnetConfig: AppConfig = {
chainName: 'Stargaze',
addressPrefix: 'stars',
rpcUrl: 'https://rpc.stargaze-apis.com/',
httpUrl: 'https://rest.stargaze-apis.com/',
feeToken: 'ustars',
stakingToken: 'ustars',
coinMap: {
+100 -3
View File
@@ -1,3 +1,5 @@
import { NETWORK } from 'utils/constants'
export interface TokenInfo {
id: string
denom: string
@@ -23,16 +25,111 @@ export const ibcAtom: TokenInfo = {
export const ibcUsdc: TokenInfo = {
id: 'ibc-usdc',
denom: 'ibc/D189335C6E4A68B513C10AB227BF1C1D38C746766278BA3EEB4FB14124F1D858',
denom:
NETWORK === 'mainnet'
? 'ibc/4A1C18CA7F50544760CF306189B810CE4C1CB156C7FC870143D401FE7280E591'
: 'factory/stars1paqkeyluuw47pflgwwqaaj8y679zj96aatg5a7/uusdc',
displayName: 'USDC',
decimalPlaces: 6,
}
export const ibcUsk: TokenInfo = {
id: 'ibc-usk',
denom:
NETWORK === 'mainnet'
? 'ibc/938CEB62ABCBA6366AA369A8362E310B2A0B1A54835E4F3FF01D69D860959128'
: 'factory/stars153w5xhuqu3et29lgqk4dsynj6gjn96lr33wx4e/uusk',
displayName: 'USK',
decimalPlaces: 6,
}
export const ibcKuji: TokenInfo = {
id: 'ibc-kuji',
denom:
NETWORK === 'mainnet'
? 'ibc/0E57658B71E9CC4BB0F6FE3E01712966713B49E6FD292E6B66E3F111B103D361'
: 'factory/stars153w5xhuqu3et29lgqk4dsynj6gjn96lr33wx4e/ukuji',
displayName: 'KUJI',
decimalPlaces: 6,
}
export const ibcFrnz: TokenInfo = {
id: 'ibc-frnz',
denom: 'ibc/7FA7EC64490E3BDE5A1A28CBE73CC0AD22522794957BC891C46321E3A6074DB9',
denom:
NETWORK === 'mainnet'
? 'ibc/9C40A8368C0E1CAA4144DBDEBBCE2E7A5CC2D128F0A9F785ECB71ECFF575114C'
: 'factory/stars1paqkeyluuw47pflgwwqaaj8y679zj96aatg5a7/ufrienzies',
displayName: 'FRNZ',
decimalPlaces: 6,
}
export const tokensList = [stars, ibcAtom, ibcUsdc, ibcFrnz]
export const ibcNbtc: TokenInfo = {
id: 'ibc-nBTC',
denom: NETWORK === 'mainnet' ? 'Not available' : 'factory/stars153w5xhuqu3et29lgqk4dsynj6gjn96lr33wx4e/unbtc',
displayName: 'nBTC',
decimalPlaces: 6,
}
// export const ibcHuahua: TokenInfo = {
// id: 'ibc-huahua',
// denom:
// NETWORK === 'mainnet'
// ? 'ibc/CAD8A9F306CAAC55731C66930D6BEE539856DD12E59061C965E44D82AA26A0E7'
// : 'factory/stars153w5xhuqu3et29lgqk4dsynj6gjn96lr33wx4e/uhuahua',
// displayName: 'HUAHUA',
// decimalPlaces: 6,
// }
export const ibcCrbrus: TokenInfo = {
id: 'ibc-crbrus',
denom:
NETWORK === 'mainnet'
? 'ibc/71CEEB5CC09F75A3ACDC417108C14514351B6B2A540ACE9B37A80BF930845134'
: 'factory/stars153w5xhuqu3et29lgqk4dsynj6gjn96lr33wx4e/uCRBRUS',
displayName: 'CRBRUS',
decimalPlaces: 6,
}
export const ibcTia: TokenInfo = {
id: 'ibc-tia',
denom:
NETWORK === 'mainnet'
? 'ibc/14D1406D84227FDF4B055EA5CB2298095BBCA3F3BC3EF583AE6DF36F0FB179C8'
: 'factory/stars153w5xhuqu3et29lgqk4dsynj6gjn96lr33wx4e/utia',
displayName: 'TIA',
decimalPlaces: 6,
}
export const nativeStardust: TokenInfo = {
id: 'native-strdst',
denom:
NETWORK === 'mainnet'
? 'factory/stars16da2uus9zrsy83h23ur42v3lglg5rmyrpqnju4/dust'
: 'factory/stars18vxuarvh44wxltxqsyac36972nvaqc377sdh40/dust',
displayName: 'STRDST',
decimalPlaces: 6,
}
export const nativeBrnch: TokenInfo = {
id: 'native-brnch',
denom:
NETWORK === 'mainnet'
? 'factory/stars16da2uus9zrsy83h23ur42v3lglg5rmyrpqnju4/uBRNCH'
: 'factory/stars153w5xhuqu3et29lgqk4dsynj6gjn96lr33wx4e/uBRNCH',
displayName: 'BRNCH',
decimalPlaces: 6,
}
export const tokensList = [
stars,
ibcAtom,
ibcUsdc,
ibcUsk,
ibcFrnz,
ibcNbtc,
ibcKuji,
// ibcHuahua,
ibcCrbrus,
ibcTia,
nativeStardust,
nativeBrnch,
]
+1 -1
View File
@@ -1,4 +1,4 @@
import create from 'zustand'
import { create } from 'zustand'
export const useCollectionStore = create(() => ({
name: 'Example',
+27 -4
View File
@@ -6,6 +6,8 @@ import type { UseBaseMinterContractProps } from 'contracts/baseMinter'
import { useBaseMinterContract } from 'contracts/baseMinter'
import { type UseOpenEditionFactoryContractProps, useOpenEditionFactoryContract } from 'contracts/openEditionFactory'
import { type UseOpenEditionMinterContractProps, useOpenEditionMinterContract } from 'contracts/openEditionMinter'
import type { UseRoyaltyRegistryContractProps } from 'contracts/royaltyRegistry'
import { useRoyaltyRegistryContract } from 'contracts/royaltyRegistry'
import type { UseSG721ContractProps } from 'contracts/sg721'
import { useSG721Contract } from 'contracts/sg721'
import type { UseVendingFactoryContractProps } from 'contracts/vendingFactory'
@@ -14,10 +16,10 @@ import type { UseVendingMinterContractProps } from 'contracts/vendingMinter'
import { useVendingMinterContract } from 'contracts/vendingMinter'
import type { UseWhiteListContractProps } from 'contracts/whitelist'
import { useWhiteListContract } from 'contracts/whitelist'
import { type UseWhiteListMerkleTreeContractProps, useWhiteListMerkleTreeContract } from 'contracts/whitelistMerkleTree'
import type { ReactNode, VFC } from 'react'
import { Fragment, useEffect } from 'react'
import type { State } from 'zustand'
import create from 'zustand'
import { create } from 'zustand'
import type { UseSplitsContractProps } from '../contracts/splits/useContract'
import { useSplitsContract } from '../contracts/splits/useContract'
@@ -25,17 +27,19 @@ import { useSplitsContract } from '../contracts/splits/useContract'
/**
* Contracts store type definitions
*/
export interface ContractsStore extends State {
export interface ContractsStore {
sg721: UseSG721ContractProps | null
vendingMinter: UseVendingMinterContractProps | null
baseMinter: UseBaseMinterContractProps | null
openEditionMinter: UseOpenEditionMinterContractProps | null
whitelist: UseWhiteListContractProps | null
whitelistMerkleTree: UseWhiteListMerkleTreeContractProps | null
vendingFactory: UseVendingFactoryContractProps | null
baseFactory: UseBaseFactoryContractProps | null
openEditionFactory: UseOpenEditionFactoryContractProps | null
badgeHub: UseBadgeHubContractProps | null
splits: UseSplitsContractProps | null
royaltyRegistry: UseRoyaltyRegistryContractProps | null
}
/**
@@ -47,11 +51,13 @@ export const defaultValues: ContractsStore = {
baseMinter: null,
openEditionMinter: null,
whitelist: null,
whitelistMerkleTree: null,
vendingFactory: null,
baseFactory: null,
openEditionFactory: null,
badgeHub: null,
splits: null,
royaltyRegistry: null,
}
/**
@@ -80,11 +86,13 @@ const ContractsSubscription: VFC = () => {
const baseMinter = useBaseMinterContract()
const openEditionMinter = useOpenEditionMinterContract()
const whitelist = useWhiteListContract()
const whitelistMerkleTree = useWhiteListMerkleTreeContract()
const vendingFactory = useVendingFactoryContract()
const baseFactory = useBaseFactoryContract()
const openEditionFactory = useOpenEditionFactoryContract()
const badgeHub = useBadgeHubContract()
const splits = useSplitsContract()
const royaltyRegistry = useRoyaltyRegistryContract()
useEffect(() => {
useContracts.setState({
@@ -93,13 +101,28 @@ const ContractsSubscription: VFC = () => {
baseMinter,
openEditionMinter,
whitelist,
whitelistMerkleTree,
vendingFactory,
baseFactory,
openEditionFactory,
badgeHub,
splits,
royaltyRegistry,
})
}, [sg721, vendingMinter, baseMinter, whitelist, vendingFactory, baseFactory, badgeHub, splits])
}, [
sg721,
vendingMinter,
baseMinter,
whitelist,
whitelistMerkleTree,
vendingFactory,
baseFactory,
badgeHub,
splits,
royaltyRegistry,
openEditionMinter,
openEditionFactory,
])
return null
}
+11
View File
@@ -0,0 +1,11 @@
import { create } from 'zustand'
export type Timezone = 'UTC' | 'Local'
export const useGlobalSettings = create(() => ({
timezone: 'UTC' as Timezone,
}))
export const setTimezone = (timezone: Timezone) => {
useGlobalSettings.setState({ timezone })
}
+1 -1
View File
@@ -1,4 +1,4 @@
import create from 'zustand'
import { create } from 'zustand'
export interface LogItem {
id: string
+1 -1
View File
@@ -1,4 +1,4 @@
import create from 'zustand'
import { create } from 'zustand'
export const useSidebarStore = create(() => ({ isOpen: true }))
-289
View File
@@ -1,289 +0,0 @@
import { SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate'
import { Decimal } from '@cosmjs/math'
import type { OfflineSigner } from '@cosmjs/proto-signing'
import type { Coin } from '@cosmjs/stargate'
import type { AppConfig } from 'config'
import { getConfig, keplrConfig } from 'config'
import type { ReactNode } from 'react'
import { useEffect } from 'react'
import { toast } from 'react-hot-toast'
import { createTrackedSelector } from 'react-tracked'
import { NETWORK } from 'utils/constants'
import type { State } from 'zustand'
import create from 'zustand'
import { subscribeWithSelector } from 'zustand/middleware'
export interface KeplrWalletStore extends State {
accountNumber: number
address: string
balance: Coin[]
client: SigningCosmWasmClient | undefined
config: AppConfig
initialized: boolean
initializing: boolean
name: string
network: string
signer: OfflineSigner | undefined
readonly clear: () => void
readonly connect: (walletChange?: boolean | 'focus') => Promise<void>
readonly disconnect: () => void | Promise<void>
readonly getClient: () => SigningCosmWasmClient
readonly getSigner: () => OfflineSigner
readonly init: (signer?: OfflineSigner) => void
readonly refreshBalance: (address?: string, balance?: Coin[]) => Promise<void>
readonly setNetwork: (network: string) => void
readonly updateSigner: (singer: OfflineSigner) => void
readonly setQueryClient: () => void
}
/**
* Compatibility export for references still using `WalletContextType`
*
* @deprecated replace with {@link KeplrWalletStore}
*/
export type WalletContextType = KeplrWalletStore
/**
* Keplr wallet store default values as a separate variable for reusability
*/
const defaultStates = {
accountNumber: 0,
address: '',
balance: [],
client: undefined,
config: getConfig(NETWORK),
initialized: false,
initializing: true,
name: '',
network: NETWORK,
signer: undefined,
}
/**
* Entrypoint for keplr wallet store using {@link defaultStates}
*/
export const useWalletStore = create(
subscribeWithSelector<KeplrWalletStore>((set, get) => ({
...defaultStates,
clear: () => set({ ...defaultStates }),
connect: async (walletChange = false) => {
try {
if (walletChange !== 'focus') set({ initializing: true })
const { config, init } = get()
const signer = await loadKeplrWallet(config)
init(signer)
if (walletChange) set({ initializing: false })
} catch (err: any) {
toast.error(err?.message, { style: { maxWidth: 'none' } })
set({ initializing: false })
}
},
disconnect: () => {
window.localStorage.clear()
get().clear()
set({ initializing: false })
},
getClient: () => get().client!,
getSigner: () => get().signer!,
init: (signer) => set({ signer }),
refreshBalance: async (address = get().address, balance = get().balance) => {
const { client, config } = get()
if (!client) return
balance.length = 0
for (const denom in config.coinMap) {
// eslint-disable-next-line no-await-in-loop
const coin = await client.getBalance(address, denom)
if (coin) balance.push(coin)
}
set({ balance })
},
setNetwork: (network) => set({ network }),
updateSigner: (signer) => set({ signer }),
setQueryClient: async () => {
try {
const client = (await createQueryClient()) as SigningCosmWasmClient
set({ client })
} catch (err: any) {
toast.error(err?.message, { style: { maxWidth: 'none' } })
set({ initializing: false })
}
},
})),
)
/**
* Proxied keplr wallet store which only rerenders on called state values.
*
* Recommended if only consuming state; to set states, use {@link useWalletStore.setState}.
*
* @example
*
* ```ts
* // this will rerender if any state values has changed
* const { name } = useWalletStore()
*
* // this will rerender if only `name` has changed
* const { name } = useWallet()
* ```
*/
export const useWallet = createTrackedSelector<KeplrWalletStore>(useWalletStore)
/**
* Keplr wallet store provider to easily mount {@link WalletSubscription}
* to listen/subscribe various state changes.
*
*/
export const WalletProvider = ({ children }: { children: ReactNode }) => {
return (
<>
{children}
<WalletSubscription />
</>
)
}
/**
* Keplr wallet subscriptions (side effects)
*/
const WalletSubscription = () => {
/**
* Dispatch reconnecting wallet on first mount and register events to refresh
* on keystore change and window refocus.
*
*/
useEffect(() => {
const walletAddress = window.localStorage.getItem('wallet_address')
if (walletAddress) {
void useWalletStore.getState().connect()
} else {
useWalletStore.setState({ initializing: false })
useWalletStore.getState().setQueryClient()
}
const listenChange = () => {
void useWalletStore.getState().connect(true)
}
const listenFocus = () => {
if (walletAddress) void useWalletStore.getState().connect('focus')
}
window.addEventListener('keplr_keystorechange', listenChange)
window.addEventListener('focus', listenFocus)
return () => {
window.removeEventListener('keplr_keystorechange', listenChange)
window.removeEventListener('focus', listenFocus)
}
}, [])
/**
* Watch signer changes to initialize client state.
*
*/
useEffect(() => {
return useWalletStore.subscribe(
(x) => x.signer,
// eslint-disable-next-line @typescript-eslint/no-misused-promises
async (signer) => {
try {
if (!signer) {
useWalletStore.setState({
client: (await createQueryClient()) as SigningCosmWasmClient,
})
} else {
useWalletStore.setState({
client: await createClient({ signer }),
})
}
} catch (error) {
console.log(error)
}
},
)
}, [])
/**
* Watch client changes to refresh balance and sync wallet states.
*
*/
useEffect(() => {
return useWalletStore.subscribe(
(x) => x.client,
// eslint-disable-next-line @typescript-eslint/no-misused-promises
async (client) => {
const { config, refreshBalance, signer } = useWalletStore.getState()
if (!signer || !client) return
if (!window.keplr) {
throw new Error('window.keplr not found')
}
const balance: Coin[] = []
const address = (await signer.getAccounts())[0].address
const account = await client.getAccount(address)
const key = await window.keplr.getKey(config.chainId)
await refreshBalance(address, balance)
window.localStorage.setItem('wallet_address', address)
useWalletStore.setState({
accountNumber: account?.accountNumber || 0,
address,
balance,
initialized: true,
initializing: false,
name: key.name || '',
})
},
)
}, [])
return null
}
/**
* Function to create signing client based on {@link useWalletStore} resolved
* config state.
*
* @param arg - Object argument requiring `signer`
*/
const createClient = ({ signer }: { signer: OfflineSigner }) => {
const { config } = useWalletStore.getState()
return SigningCosmWasmClient.connectWithSigner(config.rpcUrl, signer, {
gasPrice: {
amount: Decimal.fromUserInput('0.0025', 100),
denom: config.feeToken,
},
})
}
const createQueryClient = () => {
const { config } = useWalletStore.getState()
return SigningCosmWasmClient.connect(config.rpcUrl)
}
/**
* Function to load keplr wallet signer.
*
* @param config - Application configuration
*/
const loadKeplrWallet = async (config: AppConfig) => {
if (!window.getOfflineSigner || !window.keplr || !window.getOfflineSignerAuto) {
throw new Error('Keplr extension is not available')
}
await window.keplr.experimentalSuggestChain(keplrConfig(config))
await window.keplr.enable(config.chainId)
const signer = await window.getOfflineSignerAuto(config.chainId)
Object.assign(signer, {
signAmino: (signer as any).signAmino ?? (signer as any).sign,
})
return signer
}
+2 -1
View File
@@ -342,7 +342,8 @@ export const badgeHub = (client: SigningCosmWasmClient, txSigner: string): Badge
},
'auto',
'',
editFee ? [coin(editFee, 'ustars')] : [],
[coin(200000000, 'ustars')],
// editFee ? [coin(editFee, 'ustars')] : [],
)
return res.transactionHash
+22 -6
View File
@@ -1,7 +1,7 @@
import type { Coin } from '@cosmjs/proto-signing'
import type { logs } from '@cosmjs/stargate'
import { useWallet } from 'contexts/wallet'
import { useCallback, useEffect, useState } from 'react'
import { useWallet } from 'utils/wallet'
import type { BadgeHubContract, BadgeHubInstance, BadgeHubMessages, MigrateResponse } from './contract'
import { badgeHub as initContract } from './contract'
@@ -50,9 +50,19 @@ export function useBadgeHubContract(): UseBadgeHubContractProps {
}, [])
useEffect(() => {
const BadgeHubBaseContract = initContract(wallet.getClient(), wallet.address)
setBadgeHub(BadgeHubBaseContract)
}, [wallet])
if (!wallet.isWalletConnected) {
return
}
const load = async () => {
const client = await wallet.getSigningCosmWasmClient()
const BadgeHubBaseContract = initContract(client, wallet.address || '')
setBadgeHub(BadgeHubBaseContract)
}
load().catch(console.error)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [wallet.isWalletConnected, wallet.address])
const updateContractAddress = (contractAddress: string) => {
setAddress(contractAddress)
@@ -65,7 +75,10 @@ export function useBadgeHubContract(): UseBadgeHubContractProps {
reject(new Error('Contract is not initialized.'))
return
}
badgeHub.instantiate(wallet.address, codeId, initMsg, label, admin).then(resolve).catch(reject)
badgeHub
.instantiate(wallet.address || '', codeId, initMsg, label, admin)
.then(resolve)
.catch(reject)
})
},
[badgeHub, wallet],
@@ -79,7 +92,10 @@ export function useBadgeHubContract(): UseBadgeHubContractProps {
return
}
console.log(wallet.address, contractAddress, codeId)
badgeHub.migrate(wallet.address, contractAddress, codeId, migrateMsg).then(resolve).catch(reject)
badgeHub
.migrate(wallet.address || '', contractAddress, codeId, migrateMsg)
.then(resolve)
.catch(reject)
})
},
[badgeHub, wallet],
+2 -2
View File
@@ -70,8 +70,8 @@ export const baseFactory = (client: SigningCosmWasmClient, txSigner: string): Ba
)
return {
baseMinterAddress: result.logs[0].events[5].attributes[0].value,
sg721Address: result.logs[0].events[5].attributes[2].value,
baseMinterAddress: result.logs[0].events[16].attributes[0].value,
sg721Address: result.logs[0].events[18].attributes[0].value,
transactionHash: result.transactionHash,
logs: result.logs,
}
+14 -4
View File
@@ -1,5 +1,5 @@
import { useWallet } from 'contexts/wallet'
import { useCallback, useEffect, useState } from 'react'
import { useWallet } from 'utils/wallet'
import type { BaseFactoryContract, BaseFactoryInstance, BaseFactoryMessages } from './contract'
import { baseFactory as initContract } from './contract'
@@ -22,9 +22,19 @@ export function useBaseFactoryContract(): UseBaseFactoryContractProps {
}, [])
useEffect(() => {
const BaseFactoryBaseContract = initContract(wallet.getClient(), wallet.address)
setBaseFactory(BaseFactoryBaseContract)
}, [wallet])
if (!wallet.isWalletConnected) {
return
}
const load = async () => {
const client = await wallet.getSigningCosmWasmClient()
const BaseFactoryBaseContract = initContract(client, wallet.address || '')
setBaseFactory(BaseFactoryBaseContract)
}
load().catch(console.error)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [wallet.isWalletConnected, wallet.address])
const updateContractAddress = (contractAddress: string) => {
setAddress(contractAddress)
+11 -6
View File
@@ -33,13 +33,13 @@ export interface BaseMinterInstance {
//Execute
mint: (senderAddress: string, tokenUri: string) => Promise<string>
updateStartTradingTime: (senderAddress: string, time?: Timestamp) => Promise<string>
batchMint: (senderAddress: string, recipient: string, batchCount: number) => Promise<string>
batchMint: (senderAddress: string, recipient: string, batchCount: number, startFrom: number) => Promise<string>
}
export interface BaseMinterMessages {
mint: (tokenUri: string) => MintMessage
updateStartTradingTime: (time: Timestamp) => UpdateStartTradingTimeMessage
batchMint: (recipient: string, batchNumber: number) => CustomMessage
batchMint: (recipient: string, batchNumber: number, startFrom: number) => CustomMessage
}
export interface MintMessage {
@@ -178,7 +178,12 @@ export const baseMinter = (client: SigningCosmWasmClient, txSigner: string): Bas
return res.transactionHash
}
const batchMint = async (senderAddress: string, baseUri: string, batchCount: number): Promise<string> => {
const batchMint = async (
senderAddress: string,
baseUri: string,
batchCount: number,
startFrom: number,
): Promise<string> => {
const txHash = await getConfig().then(async (response) => {
const factoryParameters = await toast.promise(getFactoryParameters(response?.config?.factory), {
loading: 'Querying Factory Parameters...',
@@ -198,7 +203,7 @@ export const baseMinter = (client: SigningCosmWasmClient, txSigner: string): Bas
const executeContractMsgs: MsgExecuteContractEncodeObject[] = []
for (let i = 0; i < batchCount; i++) {
const msg = {
mint: { token_uri: `${baseUri}/${i + 1}` },
mint: { token_uri: `${baseUri}/${i + startFrom}` },
}
const executeContractMsg: MsgExecuteContractEncodeObject = {
typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract',
@@ -282,10 +287,10 @@ export const baseMinter = (client: SigningCosmWasmClient, txSigner: string): Bas
}
}
const batchMint = (baseUri: string, batchCount: number): CustomMessage => {
const batchMint = (baseUri: string, batchCount: number, startFrom: number): CustomMessage => {
const msg: Record<string, unknown>[] = []
for (let i = 0; i < batchCount; i++) {
msg.push({ mint: { token_uri: `${baseUri}/${i + 1}` } })
msg.push({ mint: { token_uri: `${baseUri}/${i + startFrom}` } })
}
return {
sender: txSigner,
+22 -6
View File
@@ -1,7 +1,7 @@
import type { Coin } from '@cosmjs/proto-signing'
import type { logs } from '@cosmjs/stargate'
import { useWallet } from 'contexts/wallet'
import { useCallback, useEffect, useState } from 'react'
import { useWallet } from 'utils/wallet'
import type { BaseMinterContract, BaseMinterInstance, BaseMinterMessages, MigrateResponse } from './contract'
import { baseMinter as initContract } from './contract'
@@ -38,9 +38,19 @@ export function useBaseMinterContract(): UseBaseMinterContractProps {
}, [])
useEffect(() => {
const BaseMinterBaseContract = initContract(wallet.getClient(), wallet.address)
setBaseMinter(BaseMinterBaseContract)
}, [wallet])
if (!wallet.isWalletConnected) {
return
}
const load = async () => {
const client = await wallet.getSigningCosmWasmClient()
const BaseMinterBaseContract = initContract(client, wallet.address || '')
setBaseMinter(BaseMinterBaseContract)
}
load().catch(console.error)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [wallet.isWalletConnected, wallet.address])
const updateContractAddress = (contractAddress: string) => {
setAddress(contractAddress)
@@ -53,7 +63,10 @@ export function useBaseMinterContract(): UseBaseMinterContractProps {
reject(new Error('Contract is not initialized.'))
return
}
baseMinter.instantiate(wallet.address, codeId, initMsg, label, admin).then(resolve).catch(reject)
baseMinter
.instantiate(wallet.address || '', codeId, initMsg, label, admin)
.then(resolve)
.catch(reject)
})
},
[baseMinter, wallet],
@@ -67,7 +80,10 @@ export function useBaseMinterContract(): UseBaseMinterContractProps {
return
}
console.log(wallet.address, contractAddress, codeId)
baseMinter.migrate(wallet.address, contractAddress, codeId, migrateMsg).then(resolve).catch(reject)
baseMinter
.migrate(wallet.address || '', contractAddress, codeId, migrateMsg)
.then(resolve)
.catch(reject)
})
},
[baseMinter, wallet],
+2 -2
View File
@@ -61,8 +61,8 @@ export const openEditionFactory = (client: SigningCosmWasmClient, txSigner: stri
const result = await client.execute(senderAddress, contractAddress, msg, 'auto', '', funds)
return {
openEditionMinterAddress: result.logs[0].events[5].attributes[0].value,
sg721Address: result.logs[0].events[5].attributes[2].value,
openEditionMinterAddress: result.logs[0].events[16].attributes[0].value,
sg721Address: result.logs[0].events[18].attributes[0].value,
transactionHash: result.transactionHash,
logs: result.logs,
}
+14 -4
View File
@@ -1,6 +1,6 @@
import type { logs } from '@cosmjs/stargate'
import { useWallet } from 'contexts/wallet'
import { useCallback, useEffect, useState } from 'react'
import { useWallet } from 'utils/wallet'
import type { OpenEditionFactoryContract, OpenEditionFactoryInstance, OpenEditionFactoryMessages } from './contract'
import { openEditionFactory as initContract } from './contract'
@@ -41,9 +41,19 @@ export function useOpenEditionFactoryContract(): UseOpenEditionFactoryContractPr
}, [])
useEffect(() => {
const OpenEditionFactoryBaseContract = initContract(wallet.getClient(), wallet.address)
setOpenEditionFactory(OpenEditionFactoryBaseContract)
}, [wallet])
if (!wallet.isWalletConnected) {
return
}
const load = async () => {
const client = await wallet.getSigningCosmWasmClient()
const OpenEditionFactoryBaseContract = initContract(client, wallet.address || '')
setOpenEditionFactory(OpenEditionFactoryBaseContract)
}
load().catch(console.error)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [wallet.isWalletConnected, wallet.address])
const updateContractAddress = (contractAddress: string) => {
setAddress(contractAddress)
+22 -6
View File
@@ -1,7 +1,7 @@
import type { Coin } from '@cosmjs/proto-signing'
import type { logs } from '@cosmjs/stargate'
import { useWallet } from 'contexts/wallet'
import { useCallback, useEffect, useState } from 'react'
import { useWallet } from 'utils/wallet'
import type {
MigrateResponse,
@@ -55,9 +55,19 @@ export function useOpenEditionMinterContract(): UseOpenEditionMinterContractProp
}, [])
useEffect(() => {
const OpenEditionMinterBaseContract = initContract(wallet.getClient(), wallet.address)
setOpenEditionMinter(OpenEditionMinterBaseContract)
}, [wallet])
if (!wallet.isWalletConnected) {
return
}
const load = async () => {
const client = await wallet.getSigningCosmWasmClient()
const OpenEditionMinterBaseContract = initContract(client, wallet.address || '')
setOpenEditionMinter(OpenEditionMinterBaseContract)
}
load().catch(console.error)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [wallet.isWalletConnected, wallet.address])
const updateContractAddress = (contractAddress: string) => {
setAddress(contractAddress)
@@ -70,7 +80,10 @@ export function useOpenEditionMinterContract(): UseOpenEditionMinterContractProp
reject(new Error('Contract is not initialized.'))
return
}
openEditionMinter.instantiate(wallet.address, codeId, initMsg, label, admin).then(resolve).catch(reject)
openEditionMinter
.instantiate(wallet.address || '', codeId, initMsg, label, admin)
.then(resolve)
.catch(reject)
})
},
[openEditionMinter, wallet],
@@ -84,7 +97,10 @@ export function useOpenEditionMinterContract(): UseOpenEditionMinterContractProp
return
}
console.log(wallet.address, contractAddress, codeId)
openEditionMinter.migrate(wallet.address, contractAddress, codeId, migrateMsg).then(resolve).catch(reject)
openEditionMinter
.migrate(wallet.address || '', contractAddress, codeId, migrateMsg)
.then(resolve)
.catch(reject)
})
},
[openEditionMinter, wallet],
+407
View File
@@ -0,0 +1,407 @@
import type { SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate'
import type { Coin } from '@cosmjs/proto-signing'
import type { logs } from '@cosmjs/stargate'
export interface InstantiateResponse {
readonly contractAddress: string
readonly transactionHash: string
}
export interface MigrateResponse {
readonly transactionHash: string
readonly logs: readonly logs.Log[]
}
export interface RoyaltyRegistryInstance {
readonly contractAddress: string
//Query
config: () => Promise<string>
collectionRoyaltyDefault: (collection: string) => Promise<string>
collectionRoyaltyProtocol: (collection: string, protocol: string) => Promise<string>
// RoyaltyProtocolByCollection: (collection: string, queryOptions: QqueryOptions) => Promise<string>
royaltyPayment: (collection: string, protocol?: string) => Promise<string>
//Execute
initializeCollectionRoyalty: (collection: string) => Promise<string>
setCollectionRoyaltyDefault: (collection: string, recipient: string, share: number) => Promise<string>
updateCollectionRoyaltyDefault: (
collection: string,
recipient?: string,
shareDelta?: number,
decrement?: boolean,
) => Promise<string>
setCollectionRoyaltyProtocol: (
collection: string,
protocol: string,
recipient: string,
share: number,
) => Promise<string>
updateCollectionRoyaltyProtocol: (
collection: string,
protocol?: string,
recipient?: string,
shareDelta?: number,
decrement?: boolean,
) => Promise<string>
}
export interface RoyaltyRegistryMessages {
initializeCollectionRoyalty: (collection: string) => InitializeCollectionRoyaltyMessage
setCollectionRoyaltyDefault: (
collection: string,
recipient: string,
share: number,
) => SetCollectionRoyaltyDefaultMessage
updateCollectionRoyaltyDefault: (
collection: string,
recipient?: string,
shareDelta?: number,
decrement?: boolean,
) => UpdateCollectionRoyaltyDefaultMessage
setCollectionRoyaltyProtocol: (
collection: string,
protocol: string,
recipient: string,
share: number,
) => SetCollectionRoyaltyProtocolMessage
updateCollectionRoyaltyProtocol: (
collection: string,
protocol?: string,
recipient?: string,
shareDelta?: number,
decrement?: boolean,
) => UpdateCollectionRoyaltyProtocolMessage
}
export interface InitializeCollectionRoyaltyMessage {
sender: string
contract: string
msg: {
initialize_collection_royalty: { collection: string }
}
funds: Coin[]
}
export interface SetCollectionRoyaltyDefaultMessage {
sender: string
contract: string
msg: {
set_collection_royalty_default: { collection: string; recipient: string; share: number }
}
funds: Coin[]
}
export interface UpdateCollectionRoyaltyDefaultMessage {
sender: string
contract: string
msg: {
update_collection_royalty_default: {
collection: string
recipient?: string
share_delta?: number
decrement?: boolean
}
}
funds: Coin[]
}
export interface SetCollectionRoyaltyProtocolMessage {
sender: string
contract: string
msg: {
set_collection_royalty_protocol: {
collection: string
protocol: string
recipient: string
share: number
}
}
funds: Coin[]
}
export interface UpdateCollectionRoyaltyProtocolMessage {
sender: string
contract: string
msg: {
update_collection_royalty_protocol: {
collection: string
protocol?: string
recipient?: string
share_delta?: number
decrement?: boolean
}
}
funds: Coin[]
}
export interface RoyaltyRegistryContract {
instantiate: (
codeId: number,
initMsg: Record<string, unknown>,
label: string,
admin?: string,
) => Promise<InstantiateResponse>
use: (contractAddress: string) => RoyaltyRegistryInstance
migrate: (
senderAddress: string,
contractAddress: string,
codeId: number,
migrateMsg: Record<string, unknown>,
) => Promise<MigrateResponse>
messages: (contractAddress: string) => RoyaltyRegistryMessages
}
export const RoyaltyRegistry = (client: SigningCosmWasmClient, txSigner: string): RoyaltyRegistryContract => {
const use = (contractAddress: string): RoyaltyRegistryInstance => {
///QUERY
const config = async (): Promise<string> => {
return client.queryContractSmart(contractAddress, {
config: {},
})
}
const collectionRoyaltyDefault = async (collection: string): Promise<string> => {
return client.queryContractSmart(contractAddress, {
collection_royalty_default: { collection },
})
}
const collectionRoyaltyProtocol = async (collection: string, protocol: string): Promise<string> => {
return client.queryContractSmart(contractAddress, {
collection_royalty_protocol: { collection, protocol },
})
}
const royaltyPayment = async (collection: string, protocol?: string): Promise<string> => {
return client.queryContractSmart(contractAddress, {
royalty_payment: { collection, protocol },
})
}
/// EXECUTE
const initializeCollectionRoyalty = async (collection: string): Promise<string> => {
const res = await client.execute(
txSigner,
contractAddress,
{
initialize_collection_royalty: { collection },
},
'auto',
)
return res.transactionHash
}
const setCollectionRoyaltyDefault = async (
collection: string,
recipient: string,
share: number,
): Promise<string> => {
const res = await client.execute(
txSigner,
contractAddress,
{
set_collection_royalty_default: { collection, recipient, share: (share / 100).toString() },
},
'auto',
)
return res.transactionHash
}
const updateCollectionRoyaltyDefault = async (
collection: string,
recipient?: string,
shareDelta?: number,
decrement?: boolean,
): Promise<string> => {
const res = await client.execute(
txSigner,
contractAddress,
{
update_collection_royalty_default: {
collection,
recipient,
share_delta: shareDelta ? (shareDelta / 100).toString() : undefined,
decrement,
},
},
'auto',
)
return res.transactionHash
}
const setCollectionRoyaltyProtocol = async (
collection: string,
protocol: string,
recipient: string,
share: number,
): Promise<string> => {
const res = await client.execute(
txSigner,
contractAddress,
{
set_collection_royalty_protocol: { collection, protocol, recipient, share: (share / 100).toString() },
},
'auto',
)
return res.transactionHash
}
const updateCollectionRoyaltyProtocol = async (
collection: string,
protocol?: string,
recipient?: string,
shareDelta?: number,
decrement?: boolean,
): Promise<string> => {
const res = await client.execute(
txSigner,
contractAddress,
{
update_collection_royalty_protocol: {
collection,
protocol,
recipient,
share_delta: shareDelta ? (shareDelta / 100).toString() : undefined,
decrement,
},
},
'auto',
)
return res.transactionHash
}
return {
contractAddress,
config,
collectionRoyaltyDefault,
collectionRoyaltyProtocol,
royaltyPayment,
initializeCollectionRoyalty,
setCollectionRoyaltyDefault,
updateCollectionRoyaltyDefault,
setCollectionRoyaltyProtocol,
updateCollectionRoyaltyProtocol,
}
}
const instantiate = async (
codeId: number,
initMsg: Record<string, unknown>,
label: string,
admin?: string,
): Promise<InstantiateResponse> => {
const result = await client.instantiate(txSigner, codeId, initMsg, label, 'auto', {
admin,
})
return {
contractAddress: result.contractAddress,
transactionHash: result.transactionHash,
}
}
const migrate = async (
senderAddress: string,
contractAddress: string,
codeId: number,
migrateMsg: Record<string, unknown>,
): Promise<MigrateResponse> => {
const result = await client.migrate(senderAddress, contractAddress, codeId, migrateMsg, 'auto')
return {
transactionHash: result.transactionHash,
logs: result.logs,
}
}
const messages = (contractAddress: string) => {
const initializeCollectionRoyalty = (collection: string) => {
return {
sender: txSigner,
contract: contractAddress,
msg: {
initialize_collection_royalty: { collection },
},
funds: [],
}
}
const setCollectionRoyaltyDefault = (collection: string, recipient: string, share: number) => {
return {
sender: txSigner,
contract: contractAddress,
msg: {
set_collection_royalty_default: { collection, recipient, share: share / 100 },
},
funds: [],
}
}
const updateCollectionRoyaltyDefault = (
collection: string,
recipient?: string,
shareDelta?: number,
decrement?: boolean,
) => {
return {
sender: txSigner,
contract: contractAddress,
msg: {
update_collection_royalty_default: {
collection,
recipient,
share_delta: shareDelta ? shareDelta / 100 : undefined,
decrement,
},
},
funds: [],
}
}
const setCollectionRoyaltyProtocol = (collection: string, protocol: string, recipient: string, share: number) => {
return {
sender: txSigner,
contract: contractAddress,
msg: {
set_collection_royalty_protocol: { collection, protocol, recipient, share: share / 100 },
},
funds: [],
}
}
const updateCollectionRoyaltyProtocol = (
collection: string,
protocol?: string,
recipient?: string,
shareDelta?: number,
decrement?: boolean,
) => {
return {
sender: txSigner,
contract: contractAddress,
msg: {
update_collection_royalty_protocol: {
collection,
protocol,
recipient,
share_delta: shareDelta ? shareDelta / 100 : undefined,
decrement,
},
},
funds: [],
}
}
return {
initializeCollectionRoyalty,
setCollectionRoyaltyDefault,
updateCollectionRoyaltyDefault,
setCollectionRoyaltyProtocol,
updateCollectionRoyaltyProtocol,
}
}
return { use, instantiate, migrate, messages }
}
+2
View File
@@ -0,0 +1,2 @@
export * from './contract'
export * from './useContract'
@@ -0,0 +1,144 @@
import type { RoyaltyRegistryInstance } from '../index'
import { useRoyaltyRegistryContract } from '../index'
export type ExecuteType = typeof EXECUTE_TYPES[number]
export const EXECUTE_TYPES = [
'initialize_collection_royalty',
'set_collection_royalty_default',
'update_collection_royalty_default',
'set_collection_royalty_protocol',
'update_collection_royalty_protocol',
] as const
export interface ExecuteListItem {
id: ExecuteType
name: string
description?: string
}
export const EXECUTE_LIST: ExecuteListItem[] = [
{
id: 'initialize_collection_royalty',
name: 'Initialize Collection Royalty',
description: 'Initialize collection royalty',
},
{
id: 'set_collection_royalty_default',
name: 'Set Default Collection Royalty',
description: 'Set default collection royalty for unknown protocols',
},
{
id: 'update_collection_royalty_default',
name: 'Update Default Collection Royalty',
description: 'Update default collection royalty for unknown protocols',
},
{
id: 'set_collection_royalty_protocol',
name: 'Set Protocol Collection Royalty',
description: 'Set collection royalty for a specific protocol',
},
{
id: 'update_collection_royalty_protocol',
name: 'Update Protocol Collection Royalty',
description: 'Update collection royalty for a specific protocol',
},
]
export interface DispatchExecuteProps {
type: ExecuteType
[k: string]: unknown
}
type Select<T extends ExecuteType> = T
/** @see {@link RoyaltyRegistryInstance} */
export interface DispatchExecuteArgs {
contract: string
collection: string
protocol: string
recipient: string
share: number
shareDelta: number
decrement: boolean
messages?: RoyaltyRegistryInstance
type: string | undefined
}
export const dispatchExecute = async (args: DispatchExecuteArgs) => {
const { messages } = args
if (!messages) {
throw new Error('Cannot dispatch execute, messages are not defined')
}
switch (args.type) {
case 'initialize_collection_royalty': {
return messages.initializeCollectionRoyalty(args.collection)
}
case 'set_collection_royalty_default': {
return messages.setCollectionRoyaltyDefault(args.collection, args.recipient, args.share)
}
case 'update_collection_royalty_default': {
return messages.updateCollectionRoyaltyDefault(args.collection, args.recipient, args.shareDelta, args.decrement)
}
case 'set_collection_royalty_protocol': {
return messages.setCollectionRoyaltyProtocol(args.collection, args.protocol, args.recipient, args.share)
}
case 'update_collection_royalty_protocol': {
return messages.updateCollectionRoyaltyProtocol(
args.collection,
args.protocol,
args.recipient,
args.shareDelta,
args.decrement,
)
}
default: {
throw new Error('Unknown execution type')
}
}
}
export const previewExecutePayload = (args: DispatchExecuteArgs) => {
// eslint-disable-next-line react-hooks/rules-of-hooks
const { messages } = useRoyaltyRegistryContract()
const { contract } = args
switch (args.type) {
case 'initialize_collection_royalty': {
return messages(contract)?.initializeCollectionRoyalty(args.collection)
}
case 'set_collection_royalty_default': {
return messages(contract)?.setCollectionRoyaltyDefault(args.collection, args.recipient, args.share)
}
case 'update_collection_royalty_default': {
return messages(contract)?.updateCollectionRoyaltyDefault(
args.collection,
args.recipient,
args.shareDelta,
args.decrement,
)
}
case 'set_collection_royalty_protocol': {
return messages(contract)?.setCollectionRoyaltyProtocol(
args.collection,
args.protocol,
args.recipient,
args.share,
)
}
case 'update_collection_royalty_protocol': {
return messages(contract)?.updateCollectionRoyaltyProtocol(
args.collection,
args.protocol,
args.recipient,
args.shareDelta,
args.decrement,
)
}
default: {
return {}
}
}
}
export const isEitherType = <T extends ExecuteType>(type: unknown, arr: T[]): type is T => {
return arr.some((val) => type === val)
}
@@ -0,0 +1,64 @@
import type { RoyaltyRegistryInstance } from '../contract'
export type QueryType = typeof QUERY_TYPES[number]
export const QUERY_TYPES = [
'config',
'collection_royalty_default',
'collection_royalty_protocol',
'royalty_payment',
] as const
export interface QueryListItem {
id: QueryType
name: string
description?: string
}
export const QUERY_LIST: QueryListItem[] = [
{ id: 'config', name: 'Query Config', description: 'View the contract config' },
{
id: 'collection_royalty_default',
name: 'Query Collection Royalty Details',
description: 'View the collection royalty details',
},
{
id: 'collection_royalty_protocol',
name: 'Query Collection Royalty Protocol',
description: 'View the collection royalty protocol',
},
{
id: 'royalty_payment',
name: 'Query Royalty Payment',
description: 'View the royalty payment',
},
]
/*
//Query
config: () => Promise<string>
collectionRoyaltyDefault: (collection: string) => Promise<string>
collectionRoyaltyProtocol: (collection: string, protocol: string) => Promise<string>
// RoyaltyProtocolByCollection: (collection: string, queryOptions: QqueryOptions) => Promise<string>
royaltyPayment: (collection: string, protocol?: string) => Promise<string>
*/
export interface DispatchQueryProps {
messages: RoyaltyRegistryInstance | undefined
type: QueryType
collection: string
protocol: string
}
export const dispatchQuery = (props: DispatchQueryProps) => {
const { messages, type, collection, protocol } = props
switch (type) {
case 'config':
return messages?.config()
case 'collection_royalty_default':
return messages?.collectionRoyaltyDefault(collection)
case 'collection_royalty_protocol':
return messages?.collectionRoyaltyProtocol(collection, protocol)
default: {
throw new Error('unknown query type')
}
}
}
+112
View File
@@ -0,0 +1,112 @@
/* eslint-disable eslint-comments/disable-enable-pair */
import { useCallback, useEffect, useState } from 'react'
import { useWallet } from 'utils/wallet'
import type {
InstantiateResponse,
MigrateResponse,
RoyaltyRegistryContract,
RoyaltyRegistryInstance,
RoyaltyRegistryMessages,
} from './contract'
import { RoyaltyRegistry as initContract } from './contract'
export interface UseRoyaltyRegistryContractProps {
instantiate: (
codeId: number,
initMsg: Record<string, unknown>,
label: string,
admin?: string,
) => Promise<InstantiateResponse>
migrate: (contractAddress: string, codeId: number, migrateMsg: Record<string, unknown>) => Promise<MigrateResponse>
use: (customAddress?: string) => RoyaltyRegistryInstance | undefined
updateContractAddress: (contractAddress: string) => void
messages: (contractAddress: string) => RoyaltyRegistryMessages | undefined
}
export function useRoyaltyRegistryContract(): UseRoyaltyRegistryContractProps {
const wallet = useWallet()
const [address, setAddress] = useState<string>('')
const [royaltyRegistry, setRoyaltyRegistry] = useState<RoyaltyRegistryContract>()
useEffect(() => {
setAddress(localStorage.getItem('contract_address') || '')
}, [])
useEffect(() => {
if (!wallet.isWalletConnected) {
return
}
const load = async () => {
const client = await wallet.getSigningCosmWasmClient()
const royaltyRegistryContract = initContract(client, wallet.address || '')
setRoyaltyRegistry(royaltyRegistryContract)
}
load().catch(console.error)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [wallet.isWalletConnected, wallet.address])
const updateContractAddress = (contractAddress: string) => {
setAddress(contractAddress)
}
const instantiate = useCallback(
(codeId: number, initMsg: Record<string, unknown>, label: string, admin?: string): Promise<InstantiateResponse> => {
return new Promise((resolve, reject) => {
if (!royaltyRegistry) {
reject(new Error('Contract is not initialized.'))
return
}
royaltyRegistry.instantiate(codeId, initMsg, label, admin).then(resolve).catch(reject)
})
},
[royaltyRegistry],
)
const migrate = useCallback(
(contractAddress: string, codeId: number, migrateMsg: Record<string, unknown>): Promise<MigrateResponse> => {
return new Promise((resolve, reject) => {
if (!royaltyRegistry) {
reject(new Error('Contract is not initialized.'))
return
}
console.log(wallet.address, contractAddress, codeId)
royaltyRegistry
.migrate(wallet.address || '', contractAddress, codeId, migrateMsg)
.then(resolve)
.catch(reject)
})
},
[royaltyRegistry, wallet],
)
const use = useCallback(
(customAddress = ''): RoyaltyRegistryInstance | undefined => {
return royaltyRegistry?.use(address || customAddress)
},
[royaltyRegistry, address],
)
const messages = useCallback(
(customAddress = ''): RoyaltyRegistryMessages | undefined => {
return royaltyRegistry?.messages(address || customAddress)
},
[royaltyRegistry, address],
)
return {
instantiate,
migrate,
use,
updateContractAddress,
messages,
}
}
+59 -2
View File
@@ -3,6 +3,7 @@ import { toBase64, toUtf8 } from '@cosmjs/encoding'
import type { Coin, logs } from '@cosmjs/stargate'
import { coin } from '@cosmjs/stargate'
import { MsgExecuteContract } from 'cosmjs-types/cosmwasm/wasm/v1/tx'
import type { AirdropAllocation } from 'utils/isValidAccountsFile'
import type { RoyaltyInfo } from '../vendingMinter/contract'
@@ -24,6 +25,7 @@ export interface CollectionInfo {
external_link?: string
explicit_content?: boolean
royalty_info?: RoyaltyInfo | undefined
creator?: string
}
export interface SG721Instance {
@@ -86,6 +88,7 @@ export interface SG721Instance {
burn: (tokenId: string) => Promise<string>
batchBurn: (tokenIds: string) => Promise<string>
batchTransfer: (recipient: string, tokenIds: string) => Promise<string>
batchTransferMultiAddress: (senderAddress: string, tokenRecipients: AirdropAllocation[]) => Promise<string>
updateTokenMetadata: (tokenId: string, tokenURI: string) => Promise<string>
batchUpdateTokenMetadata: (tokenIds: string, tokenURI: string, jsonExtensions: boolean) => Promise<string>
freezeTokenMetadata: () => Promise<string>
@@ -103,6 +106,7 @@ export interface Sg721Messages {
burn: (tokenId: string) => BurnMessage
batchBurn: (tokenIds: string) => BatchBurnMessage
batchTransfer: (recipient: string, tokenIds: string) => BatchTransferMessage
batchTransferMultiAddress: (tokenRecipients: AirdropAllocation[]) => BatchTransferMultiAddressMessage
updateCollectionInfo: (collectionInfo: CollectionInfo) => UpdateCollectionInfoMessage
freezeCollectionInfo: () => FreezeCollectionInfoMessage
updateTokenMetadata: (tokenId: string, tokenURI: string) => UpdateTokenMetadataMessage
@@ -227,6 +231,13 @@ export interface BatchTransferMessage {
funds: Coin[]
}
export interface BatchTransferMultiAddressMessage {
sender: string
contract: string
msg: Record<string, unknown>[]
funds: Coin[]
}
export interface UpdateTokenMetadataMessage {
sender: string
contract: string
@@ -601,6 +612,37 @@ export const SG721 = (client: SigningCosmWasmClient, txSigner: string): SG721Con
return res.transactionHash
}
const batchTransferMultiAddress = async (
senderAddress: string,
recipients: AirdropAllocation[],
): Promise<string> => {
const executeContractMsgs: MsgExecuteContractEncodeObject[] = []
for (let i = 0; i < recipients.length; i++) {
const msg = {
transfer_nft: { recipient: recipients[i].address, token_id: recipients[i].tokenId as string },
}
const executeContractMsg: MsgExecuteContractEncodeObject = {
typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract',
value: MsgExecuteContract.fromPartial({
sender: senderAddress,
contract: contractAddress,
msg: toUtf8(JSON.stringify(msg)),
}),
}
executeContractMsgs.push(executeContractMsg)
}
const res = await client.signAndBroadcast(
senderAddress,
executeContractMsgs,
'auto',
'batch transfer to multiple recipients',
)
return res.transactionHash
}
// eslint-disable-next-line @typescript-eslint/no-shadow
const updateCollectionInfo = async (collectionInfo: CollectionInfo): Promise<string> => {
const res = await client.execute(
@@ -719,7 +761,7 @@ export const SG721 = (client: SigningCosmWasmClient, txSigner: string): SG721Con
},
'auto',
'',
[coin('500000000', 'ustars')],
[coin('2000000000', 'ustars')],
)
return res.transactionHash
}
@@ -748,6 +790,7 @@ export const SG721 = (client: SigningCosmWasmClient, txSigner: string): SG721Con
burn,
batchBurn,
batchTransfer,
batchTransferMultiAddress,
updateCollectionInfo,
freezeCollectionInfo,
updateTokenMetadata,
@@ -950,6 +993,19 @@ export const SG721 = (client: SigningCosmWasmClient, txSigner: string): SG721Con
}
}
const batchTransferMultiAddress = (recipients: AirdropAllocation[]): BatchTransferMultiAddressMessage => {
const msg: Record<string, unknown>[] = []
for (let i = 0; i < recipients.length; i++) {
msg.push({ transfer_nft: { recipient: recipients[i].address, token_id: recipients[i].tokenId } })
}
return {
sender: txSigner,
contract: contractAddress,
msg,
funds: [],
}
}
const batchUpdateTokenMetadata = (
tokenIds: string,
baseURI: string,
@@ -1018,7 +1074,7 @@ export const SG721 = (client: SigningCosmWasmClient, txSigner: string): SG721Con
msg: {
enable_updatable: {},
},
funds: [coin('500000000', 'ustars')],
funds: [coin('2000000000', 'ustars')],
}
}
@@ -1055,6 +1111,7 @@ export const SG721 = (client: SigningCosmWasmClient, txSigner: string): SG721Con
burn,
batchBurn,
batchTransfer,
batchTransferMultiAddress,
updateCollectionInfo,
freezeCollectionInfo,
updateTokenMetadata,
+20 -6
View File
@@ -1,6 +1,6 @@
import type { Coin } from '@cosmjs/proto-signing'
import { useWallet } from 'contexts/wallet'
import { useCallback, useEffect, useState } from 'react'
import { useWallet } from 'utils/wallet'
import type { MigrateResponse, SG721Contract, SG721Instance, Sg721Messages } from './contract'
import { SG721 as initContract } from './contract'
@@ -35,9 +35,19 @@ export function useSG721Contract(): UseSG721ContractProps {
}, [])
useEffect(() => {
const contract = initContract(wallet.getClient(), wallet.address)
setSG721(contract)
}, [wallet])
if (!wallet.isWalletConnected) {
return
}
const load = async () => {
const client = await wallet.getSigningCosmWasmClient()
const contract = initContract(client, wallet.address || '')
setSG721(contract)
}
load().catch(console.error)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [wallet.isWalletConnected, wallet.address])
const updateContractAddress = (contractAddress: string) => {
setAddress(contractAddress)
@@ -50,7 +60,9 @@ export function useSG721Contract(): UseSG721ContractProps {
reject(new Error('Contract is not initialized.'))
return
}
SG721.instantiate(wallet.address, codeId, initMsg, label, admin).then(resolve).catch(reject)
SG721.instantiate(wallet.address || '', codeId, initMsg, label, admin)
.then(resolve)
.catch(reject)
})
},
[SG721, wallet],
@@ -64,7 +76,9 @@ export function useSG721Contract(): UseSG721ContractProps {
return
}
console.log(wallet.address, contractAddress, codeId)
SG721.migrate(wallet.address, contractAddress, codeId, migrateMsg).then(resolve).catch(reject)
SG721.migrate(wallet.address || '', contractAddress, codeId, migrateMsg)
.then(resolve)
.catch(reject)
})
},
[SG721, wallet],
+18 -5
View File
@@ -1,7 +1,7 @@
/* eslint-disable eslint-comments/disable-enable-pair */
import { useWallet } from 'contexts/wallet'
import { useCallback, useEffect, useState } from 'react'
import { useWallet } from 'utils/wallet'
import type { InstantiateResponse, MigrateResponse, SplitsContract, SplitsInstance, SplitsMessages } from './contract'
import { Splits as initContract } from './contract'
@@ -34,9 +34,19 @@ export function useSplitsContract(): UseSplitsContractProps {
}, [])
useEffect(() => {
const splitsContract = initContract(wallet.getClient(), wallet.address)
setSplits(splitsContract)
}, [wallet])
if (!wallet.isWalletConnected) {
return
}
const load = async () => {
const client = await wallet.getSigningCosmWasmClient()
const contract = initContract(client, wallet.address || '')
setSplits(contract)
}
load().catch(console.error)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [wallet.isWalletConnected, wallet.address])
const updateContractAddress = (contractAddress: string) => {
setAddress(contractAddress)
@@ -63,7 +73,10 @@ export function useSplitsContract(): UseSplitsContractProps {
return
}
console.log(wallet.address, contractAddress, codeId)
splits.migrate(wallet.address, contractAddress, codeId, migrateMsg).then(resolve).catch(reject)
splits
.migrate(wallet.address || '', contractAddress, codeId, migrateMsg)
.then(resolve)
.catch(reject)
})
},
[splits, wallet],
+4 -2
View File
@@ -63,8 +63,10 @@ export const vendingFactory = (client: SigningCosmWasmClient, txSigner: string):
const result = await client.execute(senderAddress, contractAddress, msg, 'auto', '', funds)
return {
vendingMinterAddress: result.logs[0].events[5].attributes[0].value,
sg721Address: result.logs[0].events[5].attributes[2].value,
vendingMinterAddress: result.logs[0].events.filter((e) => e.type === 'instantiate')[0].attributes[0].value,
sg721Address: result.logs[0].events
.filter((e) => e.type === 'wasm')
.filter((e) => e.attributes[2]?.key === 'sg721_address')[0].attributes[2].value,
transactionHash: result.transactionHash,
logs: result.logs,
}
+14 -4
View File
@@ -1,6 +1,6 @@
import type { logs } from '@cosmjs/stargate'
import { useWallet } from 'contexts/wallet'
import { useCallback, useEffect, useState } from 'react'
import { useWallet } from 'utils/wallet'
import type { VendingFactoryContract, VendingFactoryInstance, VendingFactoryMessages } from './contract'
import { vendingFactory as initContract } from './contract'
@@ -41,9 +41,19 @@ export function useVendingFactoryContract(): UseVendingFactoryContractProps {
}, [])
useEffect(() => {
const VendingFactoryBaseContract = initContract(wallet.getClient(), wallet.address)
setVendingFactory(VendingFactoryBaseContract)
}, [wallet])
if (!wallet.isWalletConnected) {
return
}
const load = async () => {
const client = await wallet.getSigningCosmWasmClient()
const contract = initContract(client, wallet.address || '')
setVendingFactory(contract)
}
load().catch(console.error)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [wallet.isWalletConnected, wallet.address])
const updateContractAddress = (contractAddress: string) => {
setAddress(contractAddress)
+22 -6
View File
@@ -1,7 +1,7 @@
import type { Coin } from '@cosmjs/proto-signing'
import type { logs } from '@cosmjs/stargate'
import { useWallet } from 'contexts/wallet'
import { useCallback, useEffect, useState } from 'react'
import { useWallet } from 'utils/wallet'
import type { MigrateResponse, VendingMinterContract, VendingMinterInstance, VendingMinterMessages } from './contract'
import { vendingMinter as initContract } from './contract'
@@ -50,9 +50,19 @@ export function useVendingMinterContract(): UseVendingMinterContractProps {
}, [])
useEffect(() => {
const VendingMinterBaseContract = initContract(wallet.getClient(), wallet.address)
setVendingMinter(VendingMinterBaseContract)
}, [wallet])
if (!wallet.isWalletConnected) {
return
}
const load = async () => {
const client = await wallet.getSigningCosmWasmClient()
const contract = initContract(client, wallet.address || '')
setVendingMinter(contract)
}
load().catch(console.error)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [wallet.isWalletConnected, wallet.address])
const updateContractAddress = (contractAddress: string) => {
setAddress(contractAddress)
@@ -65,7 +75,10 @@ export function useVendingMinterContract(): UseVendingMinterContractProps {
reject(new Error('Contract is not initialized.'))
return
}
vendingMinter.instantiate(wallet.address, codeId, initMsg, label, admin).then(resolve).catch(reject)
vendingMinter
.instantiate(wallet.address || '', codeId, initMsg, label, admin)
.then(resolve)
.catch(reject)
})
},
[vendingMinter, wallet],
@@ -79,7 +92,10 @@ export function useVendingMinterContract(): UseVendingMinterContractProps {
return
}
console.log(wallet.address, contractAddress, codeId)
vendingMinter.migrate(wallet.address, contractAddress, codeId, migrateMsg).then(resolve).catch(reject)
vendingMinter
.migrate(wallet.address || '', contractAddress, codeId, migrateMsg)
.then(resolve)
.catch(reject)
})
},
[vendingMinter, wallet],
+4 -2
View File
@@ -32,10 +32,12 @@ export interface DispatchQueryProps {
messages: WhiteListInstance | undefined
type: QueryType
address: string
startAfter?: string
limit?: number
}
export const dispatchQuery = (props: DispatchQueryProps) => {
const { messages, type, address } = props
const { messages, type, address, startAfter, limit } = props
switch (type) {
case 'has_started':
return messages?.hasStarted()
@@ -44,7 +46,7 @@ export const dispatchQuery = (props: DispatchQueryProps) => {
case 'is_active':
return messages?.isActive()
case 'members':
return messages?.members()
return messages?.members(startAfter, limit)
case 'admin_list':
return messages?.adminList()
case 'has_member':
+14 -4
View File
@@ -1,5 +1,5 @@
import { useWallet } from 'contexts/wallet'
import { useCallback, useEffect, useState } from 'react'
import { useWallet } from 'utils/wallet'
import type { InstantiateResponse, WhiteListContract, WhiteListInstance, WhitelistMessages } from './contract'
import { WhiteList as initContract } from './contract'
@@ -30,9 +30,19 @@ export function useWhiteListContract(): UseWhiteListContractProps {
}, [])
useEffect(() => {
const whiteListContract = initContract(wallet.getClient(), wallet.address)
setWhiteList(whiteListContract)
}, [wallet])
if (!wallet.isWalletConnected) {
return
}
const load = async () => {
const client = await wallet.getSigningCosmWasmClient()
const contract = initContract(client, wallet.address || '')
setWhiteList(contract)
}
load().catch(console.error)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [wallet.isWalletConnected, wallet.address])
const updateContractAddress = (contractAddress: string) => {
setAddress(contractAddress)
+375
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 }
}
+2
View File
@@ -0,0 +1,2 @@
export * from './contract'
export * from './useContract'
@@ -0,0 +1,144 @@
import type { WhitelistFlexMember } from '../../../components/WhitelistFlexUpload'
import type { WhiteListMerkleTreeInstance } from '../index'
import { useWhiteListMerkleTreeContract } from '../index'
export type ExecuteType = typeof EXECUTE_TYPES[number]
export const EXECUTE_TYPES = [
'update_start_time',
'update_end_time',
'update_admins',
'add_members',
'remove_members',
// 'update_per_address_limit',
'freeze',
] as const
export interface ExecuteListItem {
id: ExecuteType
name: string
description?: string
}
export const EXECUTE_LIST: ExecuteListItem[] = [
{
id: 'update_start_time',
name: 'Update Start Time',
description: `Update the start time of the whitelist`,
},
{
id: 'update_end_time',
name: 'Update End Time',
description: `Update the end time of the whitelist`,
},
{
id: 'update_admins',
name: 'Update Admins',
description: `Update the list of administrators for the whitelist`,
},
// {
// id: 'add_members',
// name: 'Add Members',
// description: `Add members to the whitelist`,
// },
// {
// id: 'remove_members',
// name: 'Remove Members',
// description: `Remove members from the whitelist`,
// },
// {
// id: 'update_per_address_limit',
// name: 'Update Per Address Limit',
// description: `Update tokens per address limit`,
// },
{
id: 'freeze',
name: 'Freeze',
description: `Freeze the current state of the contract admin list`,
},
]
export interface DispatchExecuteProps {
type: ExecuteType
[k: string]: unknown
}
/** @see {@link WhiteListMerkleTreeInstance} */
export interface DispatchExecuteArgs {
contract: string
messages?: WhiteListMerkleTreeInstance
type: string | undefined
timestamp: string
members: string[] | WhitelistFlexMember[]
limit: number
admins: string[]
}
export const dispatchExecute = async (args: DispatchExecuteArgs) => {
const { messages } = args
if (!messages) {
throw new Error('cannot dispatch execute, messages is not defined')
}
switch (args.type) {
case 'update_start_time': {
return messages.updateStartTime(args.timestamp)
}
case 'update_end_time': {
return messages.updateEndTime(args.timestamp)
}
case 'update_admins': {
return messages.updateAdmins(args.admins)
}
case 'add_members': {
return messages.addMembers(args.members)
}
case 'remove_members': {
return messages.removeMembers(args.members as string[])
}
// case 'update_per_address_limit': {
// return messages.updatePerAddressLimit(args.limit)
// }
case 'freeze': {
return messages.freeze()
}
default: {
throw new Error('unknown execute type')
}
}
}
export const previewExecutePayload = (args: DispatchExecuteArgs) => {
// eslint-disable-next-line react-hooks/rules-of-hooks
const { messages } = useWhiteListMerkleTreeContract()
const { contract } = args
switch (args.type) {
case 'update_start_time': {
return messages(contract)?.updateStartTime(args.timestamp)
}
case 'update_end_time': {
return messages(contract)?.updateEndTime(args.timestamp)
}
case 'update_admins': {
return messages(contract)?.updateAdmins(args.admins)
}
case 'add_members': {
return messages(contract)?.addMembers(args.members)
}
case 'remove_members': {
return messages(contract)?.removeMembers(args.members as string[])
}
// case 'update_per_address_limit': {
// return messages(contract)?.updatePerAddressLimit(args.limit)
// }
case 'freeze': {
return messages(contract)?.freeze()
}
default: {
return {}
}
}
}
export const isEitherType = <T extends ExecuteType>(type: unknown, arr: T[]): type is T => {
return arr.some((val) => type === val)
}
@@ -0,0 +1,66 @@
import type { WhiteListMerkleTreeInstance } from '../contract'
export type WhitelistMerkleTreeQueryType = typeof WHITELIST_MERKLE_TREE_QUERY_TYPES[number]
export const WHITELIST_MERKLE_TREE_QUERY_TYPES = [
'has_started',
'has_ended',
'is_active',
'admin_list',
'has_member',
'config',
'merkle_root',
'merkle_tree_uri',
] as const
export interface QueryListItem {
id: WhitelistMerkleTreeQueryType
name: string
description?: string
}
export const WHITELIST_MERKLE_TREE_QUERY_LIST: QueryListItem[] = [
{ id: 'has_started', name: 'Has Started', description: 'Check if the whitelist minting has started' },
{ id: 'has_ended', name: 'Has Ended', description: 'Check if the whitelist minting has ended' },
{ id: 'is_active', name: 'Is Active', description: 'Check if the whitelist minting is active' },
{ id: 'admin_list', name: 'Admin List', description: 'View the whitelist admin list' },
{ id: 'has_member', name: 'Has Member', description: 'Check if a member is in the whitelist' },
{ id: 'config', name: 'Config', description: 'View the whitelist configuration' },
{ id: 'merkle_root', name: 'Merkle Root', description: 'View the whitelist merkle root' },
{ id: 'merkle_tree_uri', name: 'Merkle Tree URI', description: 'View the whitelist merkle tree URI' },
]
export interface DispatchQueryProps {
messages: WhiteListMerkleTreeInstance | undefined
type: WhitelistMerkleTreeQueryType
address: string
startAfter?: string
limit?: number
proofHashes?: string[]
}
export const dispatchQuery = (props: DispatchQueryProps) => {
const { messages, type, address, proofHashes } = props
switch (type) {
case 'has_started':
return messages?.hasStarted()
case 'has_ended':
return messages?.hasEnded()
case 'is_active':
return messages?.isActive()
case 'admin_list':
return messages?.adminList()
case 'has_member':
return messages?.hasMember(address, proofHashes || [])
case 'config':
return messages?.config()
case 'merkle_root':
return messages?.merkleRoot()
case 'merkle_tree_uri':
return messages?.merkleTreeUri()
default: {
throw new Error('unknown query type')
}
}
}
@@ -0,0 +1,89 @@
import { useCallback, useEffect, useState } from 'react'
import { useWallet } from 'utils/wallet'
import type {
InstantiateResponse,
WhiteListMerkleTreeContract,
WhiteListMerkleTreeInstance,
WhiteListMerkleTreeMessages,
} from './contract'
import { WhiteListMerkleTree as initContract } from './contract'
export interface UseWhiteListMerkleTreeContractProps {
instantiate: (
codeId: number,
initMsg: Record<string, unknown>,
label: string,
admin?: string,
) => Promise<InstantiateResponse>
use: (customAddress?: string) => WhiteListMerkleTreeInstance | undefined
updateContractAddress: (contractAddress: string) => void
messages: (contractAddress: string) => WhiteListMerkleTreeMessages | undefined
}
export function useWhiteListMerkleTreeContract(): UseWhiteListMerkleTreeContractProps {
const wallet = useWallet()
const [address, setAddress] = useState<string>('')
const [whiteListMerkleTree, setWhiteListMerkleTree] = useState<WhiteListMerkleTreeContract>()
useEffect(() => {
setAddress(localStorage.getItem('contract_address') || '')
}, [])
useEffect(() => {
if (!wallet.isWalletConnected) {
return
}
const load = async () => {
const client = await wallet.getSigningCosmWasmClient()
const contract = initContract(client, wallet.address || '')
setWhiteListMerkleTree(contract)
}
load().catch(console.error)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [wallet.isWalletConnected, wallet.address])
const updateContractAddress = (contractAddress: string) => {
setAddress(contractAddress)
}
const instantiate = useCallback(
(codeId: number, initMsg: Record<string, unknown>, label: string, admin?: string): Promise<InstantiateResponse> => {
return new Promise((resolve, reject) => {
if (!whiteListMerkleTree) {
reject(new Error('Contract is not initialized.'))
return
}
whiteListMerkleTree.instantiate(codeId, initMsg, label, admin).then(resolve).catch(reject)
})
},
[whiteListMerkleTree],
)
const use = useCallback(
(customAddress = ''): WhiteListMerkleTreeInstance | undefined => {
return whiteListMerkleTree?.use(address || customAddress)
},
[whiteListMerkleTree, address],
)
const messages = useCallback(
(customAddress = ''): WhiteListMerkleTreeMessages | undefined => {
return whiteListMerkleTree?.messages(address || customAddress)
},
[whiteListMerkleTree, address],
)
return {
instantiate,
use,
updateContractAddress,
messages,
}
}
Vendored
+68 -3
View File
@@ -16,36 +16,98 @@ declare namespace NodeJS {
readonly NEXT_PUBLIC_SG721_CODE_ID: string
readonly NEXT_PUBLIC_SG721_UPDATABLE_CODE_ID: string
readonly NEXT_PUBLIC_STRDST_SG721_CODE_ID: string
readonly NEXT_PUBLIC_BASE_FACTORY_SG721_CODE_ID: string
readonly NEXT_PUBLIC_OPEN_EDITION_SG721_CODE_ID: string
readonly NEXT_PUBLIC_OPEN_EDITION_SG721_UPDATABLE_CODE_ID: string
readonly NEXT_PUBLIC_WHITELIST_CODE_ID: string
readonly NEXT_PUBLIC_WHITELIST_FLEX_CODE_ID: string
readonly NEXT_PUBLIC_WHITELIST_MERKLE_TREE_CODE_ID: string
readonly NEXT_PUBLIC_VENDING_MINTER_CODE_ID: string
readonly NEXT_PUBLIC_VENDING_MINTER_FLEX_CODE_ID: string
readonly NEXT_PUBLIC_VENDING_FACTORY_ADDRESS: string
readonly NEXT_PUBLIC_FEATURED_VENDING_FACTORY_ADDRESS: string
readonly NEXT_PUBLIC_VENDING_FACTORY_UPDATABLE_ADDRESS: string
readonly NEXT_PUBLIC_VENDING_FACTORY_FLEX_ADDRESS: string
readonly NEXT_PUBLIC_VENDING_FACTORY_MERKLE_TREE_ADDRESS: string
readonly NEXT_PUBLIC_FEATURED_VENDING_FACTORY_MERKLE_TREE_ADDRESS: string
readonly NEXT_PUBLIC_FEATURED_VENDING_FACTORY_FLEX_ADDRESS: string
readonly NEXT_PUBLIC_VENDING_FACTORY_UPDATABLE_FLEX_ADDRESS: string
readonly NEXT_PUBLIC_VENDING_IBC_ATOM_FACTORY_ADDRESS: string
readonly NEXT_PUBLIC_VENDING_IBC_ATOM_UPDATABLE_FACTORY_ADDRESS: string
readonly NEXT_PUBLIC_VENDING_IBC_USDC_FACTORY_ADDRESS: string
readonly NEXT_PUBLIC_FEATURED_VENDING_IBC_USDC_FACTORY_ADDRESS: string
readonly NEXT_PUBLIC_VENDING_IBC_USDC_UPDATABLE_FACTORY_ADDRESS: string
readonly NEXT_PUBLIC_VENDING_IBC_TIA_FACTORY_ADDRESS: string
readonly NEXT_PUBLIC_FEATURED_VENDING_IBC_TIA_FACTORY_ADDRESS: string
readonly NEXT_PUBLIC_VENDING_IBC_TIA_UPDATABLE_FACTORY_ADDRESS: string
readonly NEXT_PUBLIC_VENDING_IBC_NBTC_FACTORY_ADDRESS: string
readonly NEXT_PUBLIC_VENDING_IBC_NBTC_UPDATABLE_FACTORY_ADDRESS: string
readonly NEXT_PUBLIC_VENDING_IBC_USK_FACTORY_ADDRESS: string
readonly NEXT_PUBLIC_VENDING_IBC_USK_UPDATABLE_FACTORY_ADDRESS: string
readonly NEXT_PUBLIC_VENDING_IBC_KUJI_FACTORY_ADDRESS: string
readonly NEXT_PUBLIC_VENDING_IBC_KUJI_UPDATABLE_FACTORY_ADDRESS: string
readonly NEXT_PUBLIC_VENDING_IBC_HUAHUA_FACTORY_ADDRESS: string
readonly NEXT_PUBLIC_VENDING_IBC_HUAHUA_UPDATABLE_FACTORY_ADDRESS: string
readonly NEXT_PUBLIC_VENDING_IBC_CRBRUS_FACTORY_ADDRESS: string
readonly NEXT_PUBLIC_VENDING_IBC_CRBRUS_UPDATABLE_FACTORY_ADDRESS: string
readonly NEXT_PUBLIC_VENDING_IBC_ATOM_FACTORY_FLEX_ADDRESS: string
readonly NEXT_PUBLIC_VENDING_IBC_ATOM_UPDATABLE_FACTORY_FLEX_ADDRESS: string
readonly NEXT_PUBLIC_VENDING_IBC_USDC_FACTORY_FLEX_ADDRESS: string
readonly NEXT_PUBLIC_FEATURED_VENDING_IBC_USDC_FACTORY_FLEX_ADDRESS: string
readonly NEXT_PUBLIC_VENDING_IBC_USDC_UPDATABLE_FACTORY_FLEX_ADDRESS: string
readonly NEXT_PUBLIC_VENDING_IBC_TIA_FACTORY_FLEX_ADDRESS: string
readonly NEXT_PUBLIC_VENDING_IBC_TIA_FACTORY_MERKLE_TREE_ADDRESS: string
readonly NEXT_PUBLIC_FEATURED_VENDING_IBC_TIA_FACTORY_MERKLE_TREE_ADDRESS: string
readonly NEXT_PUBLIC_FEATURED_VENDING_IBC_TIA_FACTORY_FLEX_ADDRESS: string
readonly NEXT_PUBLIC_VENDING_IBC_TIA_UPDATABLE_FACTORY_FLEX_ADDRESS: string
readonly NEXT_PUBLIC_VENDING_IBC_NBTC_FACTORY_FLEX_ADDRESS: string
readonly NEXT_PUBLIC_VENDING_IBC_NBTC_UPDATABLE_FACTORY_FLEX_ADDRESS: string
readonly NEXT_PUBLIC_VENDING_IBC_USK_FACTORY_FLEX_ADDRESS: string
readonly NEXT_PUBLIC_VENDING_IBC_USK_UPDATABLE_FACTORY_FLEX_ADDRESS: string
readonly NEXT_PUBLIC_VENDING_IBC_KUJI_FACTORY_FLEX_ADDRESS: string
readonly NEXT_PUBLIC_VENDING_IBC_KUJI_UPDATABLE_FACTORY_FLEX_ADDRESS: string
readonly NEXT_PUBLIC_VENDING_IBC_HUAHUA_FACTORY_FLEX_ADDRESS: string
readonly NEXT_PUBLIC_VENDING_IBC_HUAHUA_UPDATABLE_FACTORY_FLEX_ADDRESS: string
readonly NEXT_PUBLIC_VENDING_IBC_CRBRUS_FACTORY_FLEX_ADDRESS: string
readonly NEXT_PUBLIC_VENDING_IBC_CRBRUS_UPDATABLE_FACTORY_FLEX_ADDRESS: string
readonly NEXT_PUBLIC_VENDING_NATIVE_STARDUST_FACTORY_ADDRESS: string
readonly NEXT_PUBLIC_VENDING_NATIVE_STARDUST_UPDATABLE_FACTORY_ADDRESS: string
readonly NEXT_PUBLIC_VENDING_NATIVE_STRDST_FLEX_FACTORY_ADDRESS: string
readonly NEXT_PUBLIC_VENDING_NATIVE_BRNCH_FACTORY_ADDRESS: string
readonly NEXT_PUBLIC_VENDING_NATIVE_BRNCH_UPDATABLE_FACTORY_ADDRESS: string
readonly NEXT_PUBLIC_VENDING_NATIVE_BRNCH_FLEX_FACTORY_ADDRESS: string
readonly NEXT_PUBLIC_OPEN_EDITION_FACTORY_ADDRESS: string
readonly NEXT_PUBLIC_OPEN_EDITION_FACTORY_FLEX_ADDRESS: string
readonly NEXT_PUBLIC_OPEN_EDITION_UPDATABLE_FACTORY_ADDRESS: string
readonly NEXT_PUBLIC_OPEN_EDITION_IBC_ATOM_FACTORY_ADDRESS: string
readonly NEXT_PUBLIC_OPEN_EDITION_IBC_ATOM_FACTORY_FLEX_ADDRESS: string
readonly NEXT_PUBLIC_OPEN_EDITION_UPDATABLE_IBC_ATOM_FACTORY_ADDRESS: string
readonly NEXT_PUBLIC_OPEN_EDITION_IBC_USDC_FACTORY_ADDRESS: string
readonly NEXT_PUBLIC_OPEN_EDITION_IBC_USDC_FACTORY_FLEX_ADDRESS: string
readonly NEXT_PUBLIC_OPEN_EDITION_UPDATABLE_IBC_USDC_FACTORY_ADDRESS: string
readonly NEXT_PUBLIC_OPEN_EDITION_IBC_TIA_FACTORY_ADDRESS: string
readonly NEXT_PUBLIC_OPEN_EDITION_IBC_TIA_FACTORY_FLEX_ADDRESS: string
readonly NEXT_PUBLIC_OPEN_EDITION_UPDATABLE_IBC_TIA_FACTORY_ADDRESS: string
readonly NEXT_PUBLIC_OPEN_EDITION_IBC_NBTC_FACTORY_ADDRESS: string
readonly NEXT_PUBLIC_OPEN_EDITION_UPDATABLE_IBC_NBTC_FACTORY_ADDRESS: string
readonly NEXT_PUBLIC_OPEN_EDITION_IBC_FRNZ_FACTORY_ADDRESS: string
readonly NEXT_PUBLIC_OPEN_EDITION_UPDATABLE_IBC_FRNZ_FACTORY_ADDRESS: string
readonly NEXT_PUBLIC_OPEN_EDITION_IBC_USK_FACTORY_ADDRESS: string
readonly NEXT_PUBLIC_OPEN_EDITION_UPDATABLE_IBC_USK_FACTORY_ADDRESS: string
readonly NEXT_PUBLIC_OPEN_EDITION_IBC_KUJI_FACTORY_ADDRESS: string
readonly NEXT_PUBLIC_OPEN_EDITION_UPDATABLE_IBC_KUJI_FACTORY_ADDRESS: string
readonly NEXT_PUBLIC_OPEN_EDITION_IBC_HUAHUA_FACTORY_ADDRESS: string
readonly NEXT_PUBLIC_OPEN_EDITION_IBC_CRBRUS_FACTORY_ADDRESS: string
readonly NEXT_PUBLIC_OPEN_EDITION_UPDATABLE_IBC_HUAHUA_FACTORY_ADDRESS: string
readonly NEXT_PUBLIC_OPEN_EDITION_NATIVE_STRDST_FACTORY_ADDRESS: string
readonly NEXT_PUBLIC_OPEN_EDITION_NATIVE_BRNCH_FACTORY_ADDRESS: string
readonly NEXT_PUBLIC_OPEN_EDITION_MINTER_CODE_ID: string
readonly NEXT_PUBLIC_BASE_FACTORY_ADDRESS: string
readonly NEXT_PUBLIC_BASE_FACTORY_UPDATABLE_ADDRESS: string
readonly NEXT_PUBLIC_SG721_NAME_ADDRESS: string
readonly NEXT_PUBLIC_ROYALTY_REGISTRY_ADDRESS: string
readonly NEXT_PUBLIC_INFINITY_SWAP_PROTOCOL_ADDRESS: string
readonly NEXT_PUBLIC_BASE_MINTER_CODE_ID: string
readonly NEXT_PUBLIC_BADGE_HUB_CODE_ID: string
readonly NEXT_PUBLIC_BADGE_HUB_ADDRESS: string
@@ -61,12 +123,15 @@ declare namespace NodeJS {
readonly NEXT_PUBLIC_STARGAZE_WEBSITE_URL: string
readonly NEXT_PUBLIC_WEBSITE_URL: string
readonly NEXT_PUBLIC_SYNC_COLLECTIONS_API_URL: string
readonly NEXT_PUBLIC_WHITELIST_MERKLE_TREE_API_URL: string
readonly NEXT_PUBLIC_NFT_STORAGE_DEFAULT_API_KEY: string
readonly NEXT_PUBLIC_MEILISEARCH_HOST: string
readonly NEXT_PUBLIC_MEILISEARCH_API_KEY: string
}
}
type KeplrWindow = import('@keplr-wallet/types/src/window').Window
declare interface Window extends KeplrWindow {
declare interface Window {
confetti?: (obj: any) => void
}

Some files were not shown because too many files have changed in this diff Show More