├── .github ├── CODEOWNERS └── workflows │ └── code_checks.yml ├── .gitignore ├── LICENSE.md ├── README.md ├── eslint.config.js ├── index.html ├── package.json ├── pnpm-lock.yaml ├── postcss.config.cjs ├── public ├── AEwebp.webp ├── TL_large_white.png ├── TL_logo_white.png ├── af_logo.svg ├── banner-large.png ├── crust.png ├── defly-logo.png ├── discord-icon.webp ├── github-icon.png ├── icons │ ├── arc19d.png │ ├── arc19m.png │ ├── arc19u.png │ ├── arc3m.png │ ├── arc69d.png │ ├── arc69m.png │ ├── arc69u.png │ ├── bin.png │ ├── bulk.png │ ├── clawback.png │ ├── devtools.png │ ├── drop.png │ ├── drops.png │ ├── freeze.png │ ├── group.png │ ├── manager.png │ ├── mint.png │ ├── mintupdate.png │ ├── multihold.png │ ├── optin.png │ ├── optout.png │ ├── send.png │ ├── smint.png │ ├── track.png │ ├── vault.png │ └── wallet.png ├── logo.png ├── logo125.png ├── lute-wallet.svg ├── og.png ├── pera-logomark-white.png ├── qr.png ├── qr.svg ├── sm-small.png ├── social-icon-2.png ├── studio-maars.png ├── usalgo.gif ├── w-t-logo.png ├── wenbot.png ├── wenswap.png ├── wentols.png ├── wenwallet.png └── x-icon.png ├── src ├── App.tsx ├── arc59-helpers.ts ├── clients │ ├── Arc59Client.ts │ └── CirculatingSupplyClient.ts ├── components │ ├── BatchMetadataUpdateComponent.tsx │ ├── CarouselComponent.tsx │ ├── CollectionSnapshotComponent.tsx │ ├── ConnectButton.tsx │ ├── DataGrid.tsx │ ├── DonationDialog.tsx │ ├── DownloadCollectionData.tsx │ ├── DropdownMenu.tsx │ ├── FaqSectionComponent.tsx │ ├── Footer.tsx │ ├── Header.tsx │ ├── InfinityModeComponent.tsx │ ├── NumberFormatter.tsx │ ├── PreviewAssetComponent.tsx │ ├── ScrollToTop.tsx │ ├── SelectNetworkComponent.tsx │ ├── SelectToolComponent.tsx │ └── USAlgo2025Leaderboard.tsx ├── constants.ts ├── crust-auth.ts ├── crust.ts ├── index.css ├── main.tsx ├── pages │ ├── ARC19MintTool.tsx │ ├── ARC19UpdateTool.tsx │ ├── ARC3MintTool.tsx │ ├── ARC62ManagerTool.tsx │ ├── AirdropTool.tsx │ ├── BatchClawback.tsx │ ├── BatchCollectionMint.tsx │ ├── BatchDelete.tsx │ ├── BatchFreeze.tsx │ ├── BatchMetadataUpdateComponent.tsx │ ├── BatchOptin.tsx │ ├── BatchOptout.tsx │ ├── BulkClaimTool.tsx │ ├── CollectionSnapshotComponent.tsx │ ├── Download19CollectionData.tsx │ ├── Download69CollectionData.tsx │ ├── DownloadCollectionData.tsx │ ├── MultimintAssetHolders.tsx │ ├── ReallySimpleMint.tsx │ ├── SimpleAirdropTool.tsx │ ├── SimpleBatchMint.tsx │ ├── SimpleMint.tsx │ ├── SimpleMintClassic.tsx │ ├── SimpleSendTool.tsx │ ├── SimpleUpdate.tsx │ ├── SimpleUpdateClassic.tsx │ ├── USAlgo2025TrackerTool.tsx │ ├── VaultSendTool.tsx │ └── WalletHoldings.tsx ├── types.ts ├── utils.ts ├── views │ └── home.tsx └── vite-env.d.ts ├── tailwind.config.js ├── tsconfig.app.json ├── tsconfig.json ├── tsconfig.node.json ├── vercel.json ├── vite.config.ts └── vite.config.ts.timestamp-1738235494417-056158b984849.mjs /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @LoafPickleWW @algovado 2 | -------------------------------------------------------------------------------- /.github/workflows/code_checks.yml: -------------------------------------------------------------------------------- 1 | # Actions for checking the code for errors before deploying 2 | 3 | name: Code Checks 4 | permissions: 5 | contents: read 6 | on: 7 | push: 8 | branches: [main] 9 | paths-ignore: 10 | - "**/*.md" # Do not run these checks when only documentation files are changed 11 | pull_request: 12 | branches: [main] 13 | paths-ignore: 14 | - "**/*.md" # Do not run these checks when only documentation files are changed 15 | workflow_dispatch: null 16 | jobs: 17 | lint: # Check for style errors in code 18 | timeout-minutes: 5 19 | runs-on: ubuntu-latest 20 | steps: 21 | - name: Checkout code 22 | uses: actions/checkout@v4 23 | - name: Install Node 24 | uses: actions/setup-node@v4 25 | with: 26 | node-version: 22 27 | - name: Enable Corepack for PNPM 28 | run: corepack enable 29 | - name: Set up Node cache # Set up Node cache by installing again 30 | uses: actions/setup-node@v4 31 | with: 32 | node-version: 22 33 | cache: pnpm 34 | - name: Install dependencies 35 | run: pnpm install 36 | - name: Run linter 37 | run: pnpm lint 38 | build-check: # Check for errors by building the project 39 | timeout-minutes: 5 40 | runs-on: ubuntu-latest 41 | steps: 42 | - name: Checkout code 43 | uses: actions/checkout@v4 44 | - name: Install Node 45 | uses: actions/setup-node@v4 46 | with: 47 | node-version: 22 48 | - name: Enable Corepack for PNPM 49 | run: corepack enable 50 | - name: Set up Node cache # Set up Node cache by installing again 51 | uses: actions/setup-node@v4 52 | with: 53 | node-version: 22 54 | cache: pnpm 55 | - name: Install dependencies 56 | run: pnpm install 57 | - name: Run linter 58 | run: pnpm build 59 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | Copyright (c) 2025 wen.tools 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # wen.tools 2 | 3 | No-Code Front End Tooling for Bulk Processes on Algorand. These tools were designed with the aim of onboarding creators, collectors and developers. 4 | 5 | ## Getting Started 6 | 7 | ### Prerequisites 8 | 9 | List of things needed to install the software and how to install them. 10 | 11 | ### Installation 12 | 13 | `pnpm i` 14 | 15 | `pnpm dev` 16 | 17 | ## Deployment 18 | 19 | Changes that are merged to `main` will be deployed automatically. 20 | 21 | ## Contributing 22 | 23 | To contribute, fork this repo and propose changes back via Pull Request. One of the team members will review and merge your changes. 24 | 25 | ## Brand Kit 26 | 27 | https://drive.google.com/drive/folders/1__MffzHe_qNcpttT6GjpZHackStN_D4C?usp=sharing 28 | 29 | ## Authors 30 | 31 | - **algovado** - _Initial work_ - [algovado](https://github.com/algovado) 32 | - **LoafPickle** - _Initial work_ - [LoafPickle](https://github.com/LoafPickleWW) 33 | 34 | See the list of [contributors](https://github.com/thurstober-digital/evil-tools/contributors) who participated in this project. 35 | 36 | ## Bounties 37 | 38 | Bounties will be paid out upon successful merge and approval of the Pull Request. 39 | 40 | Total Bounties Paid: 4,450A 41 | 42 | - ~~**Have Slider Track Color and Donate Button outline have Hex Code #f57b14** - 100A~~ Completed by [Vince](https://github.com/vincealvare) 43 | - ~~**Integrate Crust IPFS Network to all Mint tools w/ Option to opt out and enter own Pinata Key** - 2000A~~ - Completed by [Crust](https://github.com/x-wagmi) 44 | - ~~**Add an airdrop to multimint asset holders to Simple Airdrop Tool** - 500A~~ Completed by [Twrtl](https://github.com/twirtle2) 45 | - ~~**Add dropdown menu to wen.tools with the links to wallet.wen.tools and swap.wen.tools added** - 250A~~ Completed by [Red](https://github.com/LoafPickleWW/wen-tools/pulls?q=is%3Apr+author%3Abwmx) 46 | - ~~**Integrate Use-Wallet** - 750A~~ Completed by [funk](https://github.com/acfunk) 47 | - **Create an On-chain Vote Tool under a new Repo. DM for details** - 500A - In Progress 48 | - ~~**Have a local save state for Simple Mint and Update so if a person needs to leave the page, all the information stays** - 500A~~ Completed by [No-Cash](https://github.com/No-Cash-7970) 49 | - ~~**Fix UI Bugs like scrolling issues (horizonatal and vertical), white screens, and footer being so high on mobile (issues between tailwind and mui maybe?)** - 500A~~ Completed by [No-Cash](https://github.com/No-Cash-7970) 50 | - ~~**Fix UI Display Error for Multimint Asset Holder tool when filters are being used. Correct number is not displayed** - 150A~~ - Completed by [Satishccy](https://github.com/satishccy) 51 | - ~~**Remove the need to claim when using Wen Swap. Have transfer execute when TX is signed** - 200A~~ - Completed by [Satishccy](https://github.com/satishccy) 52 | - **Add send to Asset Inbox (arc59) check box to Simple Send, Simple Airdrop, and Airdrop Tools. When a user checks this option, upon compiling of the tx, Algo balance will be checked to make sure sender has enough to cover MBR Requirements. Total Algo gas fee to be displayed as well.** - 500A - In progress by Tako 53 | - ~~**Fix Decimals showing as total supply on wen wallet. Also add commas for longer numbers.** - 150A~~ - Completed by [Satishccy](https://github.com/satishccy) 54 | - **Add preview functionality to simple mint. This is already a feature of simple bulk mint.** - 200A - In progress by Satish 55 | - ~~**Redesign Site per attached mockups [here](https://x.com/wendottools/status/1869572452285854164)** - 200A~~ - Completed by [Sandeep](https://github.com/satishccy) 56 | - **Merge wenswap and wenwallet repos into main repo and create a link to the tools from the homepage** - 200A - In progress by Satish 57 | - **Create a bulk claim tool that when logged in, it shows items in your asset inbox and NFD vault (if applicable) and allows you to select multiple and claim** - 250A - In progress by Satish 58 | 59 | ## License 60 | 61 | This project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md) file for details 62 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | import js from "@eslint/js"; 2 | import globals from "globals"; 3 | import reactHooks from "eslint-plugin-react-hooks"; 4 | import reactRefresh from "eslint-plugin-react-refresh"; 5 | import tseslint from "typescript-eslint"; 6 | 7 | export default tseslint.config( 8 | { ignores: ["dist"] }, 9 | { 10 | extends: [js.configs.recommended, ...tseslint.configs.recommended], 11 | files: ["**/*.{ts,tsx}"], 12 | languageOptions: { 13 | ecmaVersion: 2020, 14 | globals: globals.browser, 15 | }, 16 | plugins: { 17 | "react-hooks": reactHooks, 18 | "react-refresh": reactRefresh, 19 | }, 20 | rules: { 21 | "@typescript-eslint/no-explicit-any": "off", 22 | "@typescript-eslint/no-unused-vars": [ 23 | "error", 24 | { argsIgnorePattern: "^_" }, 25 | ], 26 | ...reactHooks.configs.recommended.rules, 27 | "react-refresh/only-export-components": [ 28 | "warn", 29 | { allowConstantExport: true }, 30 | ], 31 | }, 32 | } 33 | ); 34 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 14 | wen.tools 15 | 16 | 17 | 18 | 21 | 22 | 23 | 24 | 28 | 37 |
38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wen-tools", 3 | "private": true, 4 | "version": "2.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "tsc -b && vite build", 9 | "lint": "eslint .", 10 | "preview": "vite preview" 11 | }, 12 | "dependencies": { 13 | "@algorandfoundation/algokit-utils": "7.1.0", 14 | "@blockshake/defly-connect": "1.1.6", 15 | "@emotion/styled": "^11.13.0", 16 | "@mui/icons-material": "^6.4.0", 17 | "@mui/material": "^6.4.0", 18 | "@mui/utils": "^6.4.0", 19 | "@perawallet/connect": "^1.3.5", 20 | "@txnlab/use-wallet-react": "^3.10.0", 21 | "@vercel/analytics": "^1.3.2", 22 | "algosdk": "2.9.0", 23 | "axios": "^1.7.7", 24 | "jotai": "^2.10.1", 25 | "lute-connect": "^1.4.1", 26 | "multiformats": "^13.3.1", 27 | "papaparse": "^5.4.1", 28 | "react": "^18.3.1", 29 | "react-dom": "^18.3.1", 30 | "react-icons": "^5.3.0", 31 | "react-json-view-lite": "^2.3.0", 32 | "react-router-dom": "^6.28.0", 33 | "react-toastify": "^10.0.6" 34 | }, 35 | "devDependencies": { 36 | "@eslint/js": "^9.13.0", 37 | "@types/papaparse": "^5.3.15", 38 | "@types/react": "^18.3.12", 39 | "@types/react-dom": "^18.3.1", 40 | "@vitejs/plugin-react": "^4.3.3", 41 | "autoprefixer": "^10.4.20", 42 | "eslint": "^9.13.0", 43 | "eslint-plugin-react-hooks": "^5.0.0", 44 | "eslint-plugin-react-refresh": "^0.4.14", 45 | "globals": "^15.11.0", 46 | "postcss": "^8.4.47", 47 | "tailwindcss": "^3.4.14", 48 | "typescript": "~5.6.2", 49 | "typescript-eslint": "^8.11.0", 50 | "vite": "^5.4.10", 51 | "vite-plugin-node-polyfills": "^0.22.0" 52 | }, 53 | "packageManager": "pnpm@10.20.0+sha512.cf9998222162dd85864d0a8102e7892e7ba4ceadebbf5a31f9c2fce48dfce317a9c53b9f6464d1ef9042cba2e02ae02a9f7c143a2b438cd93c91840f0192b9dd" 54 | } 55 | -------------------------------------------------------------------------------- /postcss.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /public/AEwebp.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LoafPickleWW/wen-tools/30a8987089be6c39e2d1265fe88ee153c3b591c1/public/AEwebp.webp -------------------------------------------------------------------------------- /public/TL_large_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LoafPickleWW/wen-tools/30a8987089be6c39e2d1265fe88ee153c3b591c1/public/TL_large_white.png -------------------------------------------------------------------------------- /public/TL_logo_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LoafPickleWW/wen-tools/30a8987089be6c39e2d1265fe88ee153c3b591c1/public/TL_logo_white.png -------------------------------------------------------------------------------- /public/banner-large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LoafPickleWW/wen-tools/30a8987089be6c39e2d1265fe88ee153c3b591c1/public/banner-large.png -------------------------------------------------------------------------------- /public/crust.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LoafPickleWW/wen-tools/30a8987089be6c39e2d1265fe88ee153c3b591c1/public/crust.png -------------------------------------------------------------------------------- /public/defly-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LoafPickleWW/wen-tools/30a8987089be6c39e2d1265fe88ee153c3b591c1/public/defly-logo.png -------------------------------------------------------------------------------- /public/discord-icon.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LoafPickleWW/wen-tools/30a8987089be6c39e2d1265fe88ee153c3b591c1/public/discord-icon.webp -------------------------------------------------------------------------------- /public/github-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LoafPickleWW/wen-tools/30a8987089be6c39e2d1265fe88ee153c3b591c1/public/github-icon.png -------------------------------------------------------------------------------- /public/icons/arc19d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LoafPickleWW/wen-tools/30a8987089be6c39e2d1265fe88ee153c3b591c1/public/icons/arc19d.png -------------------------------------------------------------------------------- /public/icons/arc19m.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LoafPickleWW/wen-tools/30a8987089be6c39e2d1265fe88ee153c3b591c1/public/icons/arc19m.png -------------------------------------------------------------------------------- /public/icons/arc19u.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LoafPickleWW/wen-tools/30a8987089be6c39e2d1265fe88ee153c3b591c1/public/icons/arc19u.png -------------------------------------------------------------------------------- /public/icons/arc3m.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LoafPickleWW/wen-tools/30a8987089be6c39e2d1265fe88ee153c3b591c1/public/icons/arc3m.png -------------------------------------------------------------------------------- /public/icons/arc69d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LoafPickleWW/wen-tools/30a8987089be6c39e2d1265fe88ee153c3b591c1/public/icons/arc69d.png -------------------------------------------------------------------------------- /public/icons/arc69m.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LoafPickleWW/wen-tools/30a8987089be6c39e2d1265fe88ee153c3b591c1/public/icons/arc69m.png -------------------------------------------------------------------------------- /public/icons/arc69u.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LoafPickleWW/wen-tools/30a8987089be6c39e2d1265fe88ee153c3b591c1/public/icons/arc69u.png -------------------------------------------------------------------------------- /public/icons/bin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LoafPickleWW/wen-tools/30a8987089be6c39e2d1265fe88ee153c3b591c1/public/icons/bin.png -------------------------------------------------------------------------------- /public/icons/bulk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LoafPickleWW/wen-tools/30a8987089be6c39e2d1265fe88ee153c3b591c1/public/icons/bulk.png -------------------------------------------------------------------------------- /public/icons/clawback.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LoafPickleWW/wen-tools/30a8987089be6c39e2d1265fe88ee153c3b591c1/public/icons/clawback.png -------------------------------------------------------------------------------- /public/icons/devtools.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LoafPickleWW/wen-tools/30a8987089be6c39e2d1265fe88ee153c3b591c1/public/icons/devtools.png -------------------------------------------------------------------------------- /public/icons/drop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LoafPickleWW/wen-tools/30a8987089be6c39e2d1265fe88ee153c3b591c1/public/icons/drop.png -------------------------------------------------------------------------------- /public/icons/drops.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LoafPickleWW/wen-tools/30a8987089be6c39e2d1265fe88ee153c3b591c1/public/icons/drops.png -------------------------------------------------------------------------------- /public/icons/freeze.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LoafPickleWW/wen-tools/30a8987089be6c39e2d1265fe88ee153c3b591c1/public/icons/freeze.png -------------------------------------------------------------------------------- /public/icons/group.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LoafPickleWW/wen-tools/30a8987089be6c39e2d1265fe88ee153c3b591c1/public/icons/group.png -------------------------------------------------------------------------------- /public/icons/manager.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LoafPickleWW/wen-tools/30a8987089be6c39e2d1265fe88ee153c3b591c1/public/icons/manager.png -------------------------------------------------------------------------------- /public/icons/mint.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LoafPickleWW/wen-tools/30a8987089be6c39e2d1265fe88ee153c3b591c1/public/icons/mint.png -------------------------------------------------------------------------------- /public/icons/mintupdate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LoafPickleWW/wen-tools/30a8987089be6c39e2d1265fe88ee153c3b591c1/public/icons/mintupdate.png -------------------------------------------------------------------------------- /public/icons/multihold.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LoafPickleWW/wen-tools/30a8987089be6c39e2d1265fe88ee153c3b591c1/public/icons/multihold.png -------------------------------------------------------------------------------- /public/icons/optin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LoafPickleWW/wen-tools/30a8987089be6c39e2d1265fe88ee153c3b591c1/public/icons/optin.png -------------------------------------------------------------------------------- /public/icons/optout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LoafPickleWW/wen-tools/30a8987089be6c39e2d1265fe88ee153c3b591c1/public/icons/optout.png -------------------------------------------------------------------------------- /public/icons/send.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LoafPickleWW/wen-tools/30a8987089be6c39e2d1265fe88ee153c3b591c1/public/icons/send.png -------------------------------------------------------------------------------- /public/icons/smint.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LoafPickleWW/wen-tools/30a8987089be6c39e2d1265fe88ee153c3b591c1/public/icons/smint.png -------------------------------------------------------------------------------- /public/icons/track.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LoafPickleWW/wen-tools/30a8987089be6c39e2d1265fe88ee153c3b591c1/public/icons/track.png -------------------------------------------------------------------------------- /public/icons/vault.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LoafPickleWW/wen-tools/30a8987089be6c39e2d1265fe88ee153c3b591c1/public/icons/vault.png -------------------------------------------------------------------------------- /public/icons/wallet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LoafPickleWW/wen-tools/30a8987089be6c39e2d1265fe88ee153c3b591c1/public/icons/wallet.png -------------------------------------------------------------------------------- /public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LoafPickleWW/wen-tools/30a8987089be6c39e2d1265fe88ee153c3b591c1/public/logo.png -------------------------------------------------------------------------------- /public/logo125.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LoafPickleWW/wen-tools/30a8987089be6c39e2d1265fe88ee153c3b591c1/public/logo125.png -------------------------------------------------------------------------------- /public/lute-wallet.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /public/og.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LoafPickleWW/wen-tools/30a8987089be6c39e2d1265fe88ee153c3b591c1/public/og.png -------------------------------------------------------------------------------- /public/pera-logomark-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LoafPickleWW/wen-tools/30a8987089be6c39e2d1265fe88ee153c3b591c1/public/pera-logomark-white.png -------------------------------------------------------------------------------- /public/qr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LoafPickleWW/wen-tools/30a8987089be6c39e2d1265fe88ee153c3b591c1/public/qr.png -------------------------------------------------------------------------------- /public/sm-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LoafPickleWW/wen-tools/30a8987089be6c39e2d1265fe88ee153c3b591c1/public/sm-small.png -------------------------------------------------------------------------------- /public/social-icon-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LoafPickleWW/wen-tools/30a8987089be6c39e2d1265fe88ee153c3b591c1/public/social-icon-2.png -------------------------------------------------------------------------------- /public/studio-maars.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LoafPickleWW/wen-tools/30a8987089be6c39e2d1265fe88ee153c3b591c1/public/studio-maars.png -------------------------------------------------------------------------------- /public/usalgo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LoafPickleWW/wen-tools/30a8987089be6c39e2d1265fe88ee153c3b591c1/public/usalgo.gif -------------------------------------------------------------------------------- /public/w-t-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LoafPickleWW/wen-tools/30a8987089be6c39e2d1265fe88ee153c3b591c1/public/w-t-logo.png -------------------------------------------------------------------------------- /public/wenbot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LoafPickleWW/wen-tools/30a8987089be6c39e2d1265fe88ee153c3b591c1/public/wenbot.png -------------------------------------------------------------------------------- /public/wenswap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LoafPickleWW/wen-tools/30a8987089be6c39e2d1265fe88ee153c3b591c1/public/wenswap.png -------------------------------------------------------------------------------- /public/wentols.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LoafPickleWW/wen-tools/30a8987089be6c39e2d1265fe88ee153c3b591c1/public/wentols.png -------------------------------------------------------------------------------- /public/wenwallet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LoafPickleWW/wen-tools/30a8987089be6c39e2d1265fe88ee153c3b591c1/public/wenwallet.png -------------------------------------------------------------------------------- /public/x-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LoafPickleWW/wen-tools/30a8987089be6c39e2d1265fe88ee153c3b591c1/public/x-icon.png -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | NetworkId, 3 | WalletId, 4 | WalletManager, 5 | WalletProvider, 6 | } from "@txnlab/use-wallet-react"; 7 | import { Route, Routes, BrowserRouter as Router } from "react-router-dom"; 8 | import Home from "./views/home"; 9 | import { ToastContainer } from "react-toastify"; 10 | import "react-toastify/dist/ReactToastify.min.css"; 11 | import { Header } from "./components/Header"; 12 | import { Footer } from "./components/Footer"; 13 | import { AirdropTool } from "./pages/AirdropTool"; 14 | import { BatchCollectionMint } from "./pages/BatchCollectionMint"; 15 | import { BatchOptin } from "./pages/BatchOptin"; 16 | import { BatchOptout } from "./pages/BatchOptout"; 17 | import { CollectionSnapshot } from "./pages/CollectionSnapshotComponent"; 18 | import { Download69CollectionData } from "./pages/Download69CollectionData"; 19 | import { BatchCollectionMetadataUpdate } from "./pages/BatchMetadataUpdateComponent"; 20 | import { WalletHoldings } from "./pages/WalletHoldings"; 21 | import { MultimintAssetHolders } from "./pages/MultimintAssetHolders"; 22 | import { ARC3MintTool } from "./pages/ARC3MintTool"; 23 | import { ARC19MintTool } from "./pages/ARC19MintTool"; 24 | import { ARC19UpdateTool } from "./pages/ARC19UpdateTool"; 25 | import { Download19CollectionData } from "./pages/Download19CollectionData"; 26 | import { BatchDelete } from "./pages/BatchDelete"; 27 | import { SimpleSendTool } from "./pages/SimpleSendTool"; 28 | import { SimpleAirdropTool } from "./pages/SimpleAirdropTool"; 29 | import { SimpleMint } from "./pages/SimpleMint"; 30 | import { SimpleUpdate } from "./pages/SimpleUpdate"; 31 | import { BatchClawback } from "./pages/BatchClawback"; 32 | import { BatchFreeze } from "./pages/BatchFreeze"; 33 | import { VaultSendTool } from "./pages/VaultSendTool"; 34 | import { SimpleBatchMint } from "./pages/SimpleBatchMint"; 35 | import { SimpleMintClassic } from "./pages/SimpleMintClassic"; 36 | import { SimpleUpdateClassic } from "./pages/SimpleUpdateClassic"; 37 | import { BlukClaimTool } from "./pages/BulkClaimTool"; 38 | import ScrollToTop from "./components/ScrollToTop"; 39 | import { Analytics } from "@vercel/analytics/react"; 40 | import { ARC62ManagerTool } from "./pages/ARC62ManagerTool"; 41 | import { USAlgo2025TrackerTool } from "./pages/USAlgo2025TrackerTool"; 42 | import { ReallySimpleMint } from "./pages/ReallySimpleMint"; 43 | 44 | const walletManager = new WalletManager({ 45 | wallets: [ 46 | WalletId.PERA, 47 | WalletId.DEFLY, 48 | { 49 | id: WalletId.LUTE, 50 | options: { siteName: "wen.tools" }, 51 | }, 52 | ], 53 | network: NetworkId.MAINNET, 54 | }); 55 | 56 | function App() { 57 | return ( 58 | 59 |
60 | 72 | 73 | 74 |
75 | 76 | } /> 77 | } /> 78 | } /> 79 | } /> 80 | } /> 81 | } 84 | /> 85 | } /> 86 | } 89 | /> 90 | } /> 91 | } /> 92 | } /> 93 | } /> 94 | } /> 95 | } /> 96 | } /> 97 | } 100 | /> 101 | } 104 | /> 105 | } 108 | /> 109 | } 112 | /> 113 | } /> 114 | } 117 | /> 118 | } /> 119 | } 122 | /> 123 | } /> 124 | } /> 125 | } /> 126 | } 129 | /> 130 | } 133 | /> 134 | } /> 135 | } /> 136 | 137 |
138 | 139 |
140 | 141 |
142 | ); 143 | } 144 | export default App; 145 | -------------------------------------------------------------------------------- /src/components/BatchMetadataUpdateComponent.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import Papa from "papaparse"; 3 | import algosdk from "algosdk"; 4 | import { toast } from "react-toastify"; 5 | import { createAssetConfigArray, sliceIntoChunks, walletSign } from "../utils"; 6 | import { useWallet } from "@txnlab/use-wallet-react"; 7 | 8 | export function BatchCollectionMetadataUpdate() { 9 | const [csvData, setCsvData] = useState(null as null | any[]); 10 | const [isTransactionsFinished, setIsTransactionsFinished] = useState(false); 11 | const [txSendingInProgress, setTxSendingInProgress] = useState(false); 12 | const { activeAddress, algodClient, transactionSigner } = useWallet(); 13 | 14 | const handleFileData = async () => { 15 | let headers; 16 | const data = []; 17 | if (!csvData) throw Error("Invalid CSV"); 18 | for (let i = 0; i < csvData.length; i++) { 19 | if (csvData[i].length == 1) continue; 20 | if (i === 0) { 21 | headers = csvData[i]; 22 | } else { 23 | const obj: any = {}; 24 | for (let j = 0; j < headers.length; j++) { 25 | if (headers[j].startsWith("metadata_")) { 26 | obj[headers[j].replace("metadata_", "")] = csvData[i][j]; 27 | } else { 28 | obj[headers[j]] = csvData[i][j]; 29 | } 30 | } 31 | data.push(obj); 32 | } 33 | } 34 | const data_for_txns = data; 35 | data_for_txns.forEach((item) => { 36 | const asset_note: any = { 37 | properties: {}, 38 | }; 39 | Object.keys(item).forEach((key) => { 40 | if ( 41 | key != "index" && 42 | key != "description" && 43 | key != "mime_type" && 44 | key != "standard" && 45 | key != "external_url" 46 | ) { 47 | asset_note.properties[key] = item[key]; 48 | delete item[key]; 49 | } 50 | if ( 51 | key == "external_url" || 52 | key == "standard" || 53 | key == "description" || 54 | key == "mime_type" 55 | ) { 56 | asset_note[key] = item[key]; 57 | delete item[key]; 58 | } 59 | }); 60 | item.asset_id = parseInt(item.index); 61 | delete item.index; 62 | item.note = asset_note; 63 | if (!item.note.standard) { 64 | item.note.standard = "arc69"; 65 | } 66 | }); 67 | 68 | if (activeAddress === null || activeAddress === undefined) { 69 | toast.error("Wallet not found!"); 70 | return; 71 | } 72 | try { 73 | toast.info("Please sign the transactions!"); 74 | const txns = await createAssetConfigArray( 75 | data_for_txns, 76 | activeAddress, 77 | algodClient 78 | ); 79 | const signedTransactions = await walletSign(txns, transactionSigner); 80 | const groups = sliceIntoChunks(signedTransactions, 16); 81 | setTxSendingInProgress(true); 82 | for (let i = 0; i < groups.length; i++) { 83 | toast.info(`Sending transaction ${i + 1} of ${groups.length}`); 84 | const { txId } = await algodClient 85 | .sendRawTransaction(groups[i].map((txn) => txn.blob)) 86 | .do(); 87 | await algosdk.waitForConfirmation(algodClient, txId, 3); 88 | toast.success(`Transaction ${i + 1} of ${groups.length} confirmed!`); 89 | } 90 | setIsTransactionsFinished(true); 91 | setTxSendingInProgress(false); 92 | toast.success("All transactions confirmed!"); 93 | toast.info("You can support by donating :)"); 94 | } catch (err) { 95 | console.error(err); 96 | } 97 | }; 98 | 99 | return ( 100 |
101 |

Upload CSV file

102 | {csvData == null ? ( 103 | 131 | ) : ( 132 |
133 | {isTransactionsFinished ? ( 134 | <> 135 |

136 | All transactions completed! 137 |
138 |

139 |

140 | You can reload the page if you want to use another tool. 141 |

142 | 143 | ) : ( 144 | <> 145 |

File uploaded

146 |

147 | {csvData.length - 1} assets found! 148 |

149 |

3- Sign Your Transactions

150 | {!txSendingInProgress ? ( 151 | 158 | ) : ( 159 |
160 |
164 | Please wait... Transactions are sending to the network. 165 |
166 | )} 167 | 168 | )} 169 |
170 | )} 171 |
172 | ); 173 | } 174 | -------------------------------------------------------------------------------- /src/components/CarouselComponent.tsx: -------------------------------------------------------------------------------- 1 | // src/Carousel.js 2 | import React, { useState } from "react"; 3 | import { Image } from "../types"; 4 | 5 | const CarouselComponent = ({ images }: { images: Image[] }) => { 6 | const [currentIndex, setCurrentIndex] = useState(0); 7 | 8 | //interval to change the image every 3 seconds 9 | React.useEffect(() => { 10 | const interval = setInterval(() => { 11 | setCurrentIndex((prevIndex) => 12 | prevIndex === images.length - 1 ? 0 : prevIndex + 1 13 | ); 14 | }, 9100); 15 | return () => clearInterval(interval); 16 | }, [currentIndex, images.length]); 17 | 18 | return ( 19 |
20 |
21 | {images.map((image, index) => ( 22 | 23 | {image.path} 30 | 31 | ))} 32 |
33 |
34 | ); 35 | }; 36 | 37 | export default CarouselComponent; 38 | -------------------------------------------------------------------------------- /src/components/CollectionSnapshotComponent.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import { toast } from "react-toastify"; 3 | import axios from "axios"; 4 | import { getIndexerURL, getNfdDomain } from "../utils"; 5 | import { useWallet } from "@txnlab/use-wallet-react"; 6 | 7 | export function CollectionSnapshot() { 8 | const [creatorWallet, setCreatorWallet] = useState(""); 9 | const [collectionData, setCollectionData] = useState([]); 10 | const [loading, setLoading] = useState(false); 11 | const [counter, setCounter] = useState(0); 12 | const { activeNetwork } = useWallet(); 13 | 14 | async function getCollectionData() { 15 | if (creatorWallet) { 16 | if (creatorWallet.length != 58) { 17 | toast.error("Invalid wallet address!"); 18 | return; 19 | } 20 | try { 21 | const host = getIndexerURL(activeNetwork); 22 | const url = `${host}/v2/accounts/${creatorWallet}?exclude=assets,apps-local-state,created-apps,none`; 23 | const response = await axios.get(url); 24 | setCollectionData( 25 | response.data.account["created-assets"] 26 | .map((asset: any) => asset.index) 27 | .flat() 28 | ); 29 | } catch (err) { 30 | console.error(err); 31 | toast.error("Error getting collection data! Please try again."); 32 | } 33 | } else { 34 | toast.info("Please enter a wallet address"); 35 | } 36 | } 37 | 38 | async function getAssetOwner(asset_id: number) { 39 | try { 40 | const host = getIndexerURL(activeNetwork); 41 | const url = `${host}/v2/assets/${asset_id}/balances?include-all=false¤cy-greater-than=0`; 42 | const response = await axios.get(url); 43 | return response.data.balances[0].address; 44 | } catch (err) { 45 | console.error(err); 46 | } 47 | } 48 | 49 | function convertToCSV(headers: string[], objArray: any[]) { 50 | const array = typeof objArray != "object" ? JSON.parse(objArray) : objArray; 51 | let str = ""; 52 | let row = ""; 53 | 54 | for (const index in headers) { 55 | row += headers[index] + ","; 56 | } 57 | str += row + "\r"; 58 | 59 | Object.entries(array).forEach(([key, value]: [string, any]) => { 60 | let line = ""; 61 | line += key + ","; 62 | line += value.nfd + ","; 63 | const asset_list = 64 | "[" + value.assets.map((asset: any) => asset).join(","); 65 | line += '"' + asset_list + "]" + '",'; 66 | line += value.assets.length + ","; 67 | str += line + "\r\n"; 68 | }); 69 | 70 | return str; 71 | } 72 | 73 | function exportCSVFile(headers: string[], items: any[], fileTitle: string) { 74 | const csv = convertToCSV(headers, items); 75 | const blob = new Blob([csv], { type: "text/csv;charset=utf-8;" }); 76 | const link = document.createElement("a"); 77 | if (link.download !== undefined) { 78 | const url = URL.createObjectURL(blob); 79 | link.setAttribute("href", url); 80 | link.setAttribute("download", fileTitle); 81 | link.style.visibility = "hidden"; 82 | document.body.appendChild(link); 83 | link.click(); 84 | document.body.removeChild(link); 85 | } 86 | } 87 | 88 | async function downloadCollectionDataAsCSV() { 89 | if (collectionData.length > 0) { 90 | setLoading(true); 91 | const data = []; 92 | let count = 0; 93 | for (const asset_id of collectionData) { 94 | const asset_owner = await getAssetOwner(asset_id); 95 | count++; 96 | setCounter(count); 97 | if (data[asset_owner]) { 98 | data[asset_owner].assets.push(asset_id); 99 | } else { 100 | data[asset_owner] = { 101 | nfd: await getNfdDomain(asset_owner), 102 | assets: [asset_id], 103 | }; 104 | } 105 | } 106 | exportCSVFile( 107 | ["wallet", "nfdomain", "assets", "assets_count"], 108 | data, 109 | `${creatorWallet}-collection-snapshot.csv` 110 | ); 111 | setLoading(false); 112 | setCounter(0); 113 | toast.success("Collection data downloaded successfully!"); 114 | toast.info("You can support by donating :)"); 115 | } else { 116 | toast.info("Please get collection data first!"); 117 | } 118 | } 119 | 120 | return ( 121 |
122 | setCreatorWallet(e.target.value)} 130 | /> 131 | 137 | {collectionData.length > 0 && ( 138 | <> 139 | {creatorWallet.length == 58 && collectionData && ( 140 |
141 |

142 | {creatorWallet.substring(0, 4)}... 143 | {creatorWallet.substring( 144 | creatorWallet.length - 4, 145 | creatorWallet.length 146 | )}{" "} 147 | has{" "} 148 | 149 | {collectionData.length} 150 | {" "} 151 | created assets 152 |

153 |
154 | )} 155 | {loading ? ( 156 |
157 |
161 | Fetching data from blockchain... 162 |

163 | {counter}/{collectionData.length} 164 |

165 |
166 | ) : ( 167 | 173 | )} 174 | 175 | )} 176 |
177 | ); 178 | } 179 | -------------------------------------------------------------------------------- /src/components/DonationDialog.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Button, 3 | Dialog, 4 | DialogContent, 5 | DialogTitle, 6 | IconButton, 7 | TextField, 8 | } from "@mui/material"; 9 | import { useState } from "react"; 10 | import { FaCopy } from "react-icons/fa"; 11 | import { toast } from "react-toastify"; 12 | 13 | const DonationDialog = () => { 14 | const [open, setOpen] = useState(false); 15 | 16 | const handleOpen = () => { 17 | setOpen(true); 18 | }; 19 | 20 | const handleClose = () => { 21 | setOpen(false); 22 | }; 23 | 24 | const handleCopy = () => { 25 | navigator.clipboard.writeText( 26 | "RBZ4GUE7FFDZWCN532FFR5AIYJ6K4V2GKJS5B42JPSWOAVWUT4OHWG57YQ" 27 | ); 28 | toast.success("Copied to clipboard"); 29 | }; 30 | 31 | return ( 32 | <> 33 | 59 | 60 | Donate 61 | 62 | 63 | donate 68 | 69 |

70 | Click/Scan the QR code or copy the address below 🙏 71 |

72 | 85 | 86 | 87 | ), 88 | }} 89 | /> 90 |
91 |
92 | 93 | ); 94 | }; 95 | 96 | export default DonationDialog; 97 | -------------------------------------------------------------------------------- /src/components/DownloadCollectionData.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import { toast } from "react-toastify"; 3 | import axios from "axios"; 4 | import { Arc69, getIndexerURL } from "../utils"; 5 | import { useWallet } from "@txnlab/use-wallet-react"; 6 | 7 | export function DownloadCollectionData() { 8 | const [creatorWallet, setCreatorWallet] = useState(""); 9 | const [collectionData, setCollectionData] = useState([]); 10 | const [loading, setLoading] = useState(false); 11 | const [counter, setCounter] = useState(0); 12 | const { activeNetwork } = useWallet(); 13 | 14 | const arc69 = new Arc69(); 15 | 16 | async function getCollectionData() { 17 | if (creatorWallet) { 18 | if (creatorWallet.length != 58) { 19 | toast.error("Invalid wallet address!"); 20 | return; 21 | } 22 | try { 23 | const host = getIndexerURL(activeNetwork); 24 | const url = `${host}/v2/accounts/${creatorWallet}?exclude=assets,apps-local-state,created-apps,none`; 25 | const response = await axios.get(url); 26 | setCollectionData(response.data.account["created-assets"]); 27 | } catch (err) { 28 | console.error(err); 29 | toast.error("Error getting collection data! Please try again."); 30 | } 31 | } else { 32 | toast.info("Please enter a wallet address"); 33 | } 34 | } 35 | 36 | async function getAssetData(asset: any) { 37 | try { 38 | const metadata = await arc69.fetch(asset.index, activeNetwork); 39 | const asset_data_csv: any = { 40 | index: asset.index, 41 | name: asset.params.name, 42 | "unit-name": asset.params["unit-name"], 43 | url: asset.params.url, 44 | metadata_description: metadata.description || "", 45 | metadata_external_url: metadata.external_url || "", 46 | metadata_mime_type: metadata.mime_type || "", 47 | }; 48 | 49 | if (metadata.properties) { 50 | Object.entries(metadata.properties).map(([trait_type, value]) => { 51 | asset_data_csv[`metadata_${trait_type}`] = value; 52 | }); 53 | } 54 | if (metadata.attributes) { 55 | metadata.attributes.map( 56 | ({ trait_type, value }: { trait_type: string; value: any }) => { 57 | asset_data_csv[`metadata_${trait_type}`] = value; 58 | } 59 | ); 60 | } 61 | return asset_data_csv; 62 | } catch (err) { 63 | console.error(err); 64 | } 65 | } 66 | 67 | function convertToCSV(objArray: string) { 68 | const array = typeof objArray != "object" ? JSON.parse(objArray) : objArray; 69 | let str = ""; 70 | for (let i = 0; i < array.length; i++) { 71 | let line = ""; 72 | for (const index in array[i]) { 73 | if (line != "") line += ","; 74 | line += '"' + array[i][index] + '"'; 75 | } 76 | // don't put '\r\n' at the end of the last line 77 | if (i != array.length - 1) { 78 | str += line + "\r\n"; 79 | } else { 80 | str += line; 81 | } 82 | } 83 | return str; 84 | } 85 | 86 | function exportCSVFile(headers: string[], items: any[], fileTitle: string) { 87 | if (headers) { 88 | items.unshift(headers); 89 | } 90 | const jsonObject = JSON.stringify(items); 91 | 92 | const csv = convertToCSV(jsonObject); 93 | const blob = new Blob([csv], { type: "text/csv;charset=utf-8;" }); 94 | const link = document.createElement("a"); 95 | if (link.download !== undefined) { 96 | const url = URL.createObjectURL(blob); 97 | link.setAttribute("href", url); 98 | link.setAttribute("download", fileTitle); 99 | link.style.visibility = "hidden"; 100 | document.body.appendChild(link); 101 | link.click(); 102 | document.body.removeChild(link); 103 | } 104 | } 105 | 106 | async function downloadCollectionDataAsCSV() { 107 | if (collectionData.length > 0) { 108 | setLoading(true); 109 | const data = []; 110 | let count = 0; 111 | for (const asset of collectionData) { 112 | const asset_data = await getAssetData(asset); 113 | count++; 114 | setCounter(count); 115 | data.push(asset_data); 116 | } 117 | const headers = data[data.length / 2] 118 | ? Object.keys(data[data.length / 2]) 119 | : []; 120 | exportCSVFile( 121 | headers ? headers : ["index", "name", "unit-name", "url", "metadata"], 122 | data, 123 | `${creatorWallet}-collection-data.csv` 124 | ); 125 | setLoading(false); 126 | setCounter(0); 127 | toast.success("Collection data downloaded successfully!"); 128 | toast.info("You can support by donating :)"); 129 | } else { 130 | toast.info("Please get collection data first!"); 131 | } 132 | } 133 | 134 | return ( 135 |
136 | setCreatorWallet(e.target.value)} 144 | /> 145 | 151 | {collectionData.length > 0 && ( 152 | <> 153 | {creatorWallet.length == 58 && collectionData && ( 154 |
155 |

156 | {creatorWallet.substring(0, 4)}... 157 | {creatorWallet.substring( 158 | creatorWallet.length - 4, 159 | creatorWallet.length 160 | )}{" "} 161 | has{" "} 162 | 163 | {collectionData.length} 164 | {" "} 165 | created assets 166 |

167 |
168 | )} 169 | {loading ? ( 170 |
171 |
175 | Fetching data from blockchain... 176 |

177 | {counter}/{collectionData.length} 178 |

179 |
180 | ) : ( 181 | 187 | )} 188 | 189 | )} 190 |
191 | ); 192 | } 193 | -------------------------------------------------------------------------------- /src/components/DropdownMenu.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Drawer, 3 | Toolbar, 4 | Typography, 5 | Divider, 6 | List, 7 | ListItemIcon, 8 | ListItemText, 9 | ListItemButton, 10 | } from "@mui/material"; 11 | 12 | import { FaWallet } from "react-icons/fa"; 13 | import { IoSwapHorizontal } from "react-icons/io5"; 14 | import { type DropdownMenu } from "../types"; 15 | import DonationDialog from "./DonationDialog"; 16 | 17 | const DropdownMenu = ({ onClose, isOpen }: DropdownMenu) => { 18 | return ( 19 | 36 | 37 | 44 | More Tools 45 | 46 | 47 | 48 | 49 | 54 | 55 | 56 | 57 | 61 | 62 | 67 | 68 | 69 | 70 | 74 | 75 |
76 | 77 |
78 |
79 |
80 | ); 81 | }; 82 | 83 | export default DropdownMenu; 84 | -------------------------------------------------------------------------------- /src/components/FaqSectionComponent.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import { FAQ, type FAQItem } from "../types"; 3 | 4 | const FAQItem = ({ faq, index, toggleFAQ }: FAQItem) => ( 5 |
6 | 12 |
17 |

{faq.answer}

18 |
19 |
20 | ); 21 | 22 | const FaqSectionComponent = ({ faqData }: { faqData: FAQ[] }) => { 23 | const [faqs, setFaqs] = useState( 24 | faqData.map((faq) => ({ ...faq, open: false })) 25 | ); 26 | 27 | const toggleFAQ = (index: number) => { 28 | setFaqs( 29 | faqs.map((faq, i) => { 30 | if (i === index) { 31 | faq.open = !faq.open; 32 | } else { 33 | faq.open = false; 34 | } 35 | return faq; 36 | }) 37 | ); 38 | }; 39 | 40 | return ( 41 |
42 |

43 | Frequently Asked Questions 44 |

45 | {faqs.map((faq, index) => ( 46 | 47 | ))} 48 |
49 | ); 50 | }; 51 | 52 | export default FaqSectionComponent; 53 | -------------------------------------------------------------------------------- /src/components/Footer.tsx: -------------------------------------------------------------------------------- 1 | import { Tooltip } from "@mui/material"; 2 | 3 | export function Footer() { 4 | return ( 5 | 71 | ); 72 | } 73 | -------------------------------------------------------------------------------- /src/components/Header.tsx: -------------------------------------------------------------------------------- 1 | import AppBar from "@mui/material/AppBar"; 2 | import Toolbar from "@mui/material/Toolbar"; 3 | import Typography from "@mui/material/Typography"; 4 | import List from "@mui/material/List"; 5 | import ListItemButton from "@mui/material/ListItemButton"; 6 | 7 | import { Link } from "react-router-dom"; 8 | import ConnectButton from "./ConnectButton"; 9 | import SelectNetworkComponent from "./SelectNetworkComponent"; 10 | import { MdMenu } from "react-icons/md"; 11 | import { useState } from "react"; 12 | import DropdownMenu from "./DropdownMenu"; 13 | import DonationDialog from "./DonationDialog"; 14 | import IconButton from "@mui/material/IconButton"; 15 | 16 | export function Header() { 17 | const [isSidesheetOpen, setIsSidesheetOpen] = useState(false); 18 | 19 | const handleDrawerToggle = () => { 20 | setIsSidesheetOpen(!isSidesheetOpen); 21 | }; 22 | 23 | return ( 24 | 25 | 26 | 27 | 28 |
29 | 35 | 36 | 37 |
38 | 40 | logo 41 | 42 | 60 | 63 | 68 |

wen bot

69 |
70 | 75 |

wen wallet

76 |
77 | 83 |

wen swap

84 |
85 |
86 | 87 | 88 |
89 | 90 |
91 |
92 | {/*
93 |

94 | You can read more about Infinity Mode{" "} 95 | 101 | 🔗 here! 102 | 103 |

104 |
105 |
106 |

107 | Improve your wallet experience with the:{" "} 108 | 114 | Wen Wallet! 115 | 116 |

117 |
*/} 118 |
119 | ); 120 | } 121 | -------------------------------------------------------------------------------- /src/components/InfinityModeComponent.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import { AiOutlineInfoCircle } from "react-icons/ai"; 3 | import { InfinityData } from "../types"; 4 | 5 | const InfinityModeComponent = ({ 6 | mnemonic, 7 | setMnemonic, 8 | description = "Infinity Mode allows for no restrictions to the amount of transactions per upload.", 9 | }: InfinityData) => { 10 | const [isOpen, setIsOpen] = useState(false); 11 | 12 | const toggleAccordion = () => { 13 | setIsOpen(!isOpen); 14 | }; 15 | 16 | return ( 17 |
18 |
22 | 23 | Infinity Mode (optional) 24 | 25 |
26 | 27 | Evil Tools does not store any information on the website. As 28 | precautions, you can use burner wallets, rekey to a burner wallet 29 | and rekey back, or rekey after using. 30 | 31 | 32 |
33 |
34 | {isOpen && ( 35 |
36 | { 42 | setMnemonic(e.target.value.replace(/,/g, " ")); 43 | }} 44 | /> 45 | {description} 46 |
47 | )} 48 |
49 | ); 50 | }; 51 | 52 | export default InfinityModeComponent; 53 | -------------------------------------------------------------------------------- /src/components/NumberFormatter.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | interface NumberFormatterProps extends React.HTMLAttributes { 4 | value: number; 5 | className?: string; // Tailwind or custom styles can be passed 6 | } 7 | 8 | export const NumberFormatter: React.FC = ({ 9 | value, 10 | className = "", 11 | ...props 12 | }) => { 13 | /** 14 | * Format large numbers with appropriate suffix (e.g., M, B, T, etc.), 15 | * starting with millions. Numbers below 1 million will be formatted with commas. 16 | */ 17 | const formatNumber = (num: number): { display: string; full: string } => { 18 | const fullNumber = num.toLocaleString(); // Full number with commas 19 | const suffixes = ["", "", "M", "B", "T", "Q", "Qi", "Sx"]; // Start suffix from 'M' 20 | let index = 0; 21 | 22 | // Skip formatting for numbers less than 1 million 23 | if (num < 1_000_000) { 24 | return { display: fullNumber, full: fullNumber }; 25 | } 26 | 27 | while (num >= 1000 && index < suffixes.length - 1) { 28 | num /= 1000; 29 | index++; 30 | } 31 | 32 | const rounded = parseFloat(num.toFixed(2)); // Round to 2 decimal places 33 | const display = 34 | rounded % 1 === 0 35 | ? `${Math.floor(rounded)} ${suffixes[index]}` 36 | : `${rounded} ${suffixes[index]}`; 37 | 38 | return { display, full: fullNumber }; 39 | }; 40 | 41 | const { display, full } = formatNumber(value); 42 | 43 | return ( 44 |
49 | {display} 50 |
51 | ); 52 | }; 53 | -------------------------------------------------------------------------------- /src/components/PreviewAssetComponent.tsx: -------------------------------------------------------------------------------- 1 | import { JsonView, allExpanded, darkStyles } from "react-json-view-lite"; 2 | 3 | export const PreviewAssetComponent = ({ 4 | previewAsset, 5 | imageUrl, 6 | }: { 7 | previewAsset: any; 8 | imageUrl: string; 9 | }) => { 10 | return ( 11 |
12 |

Preview Asset

13 |
14 | {imageUrl != "" && ( 15 | preview 20 | )} 21 |

22 | {previewAsset.asset_name} | {previewAsset.unit_name} 23 |

24 | {/* metadata like json intended */} 25 |
26 | 31 |
32 |
33 |
34 | ); 35 | }; 36 | -------------------------------------------------------------------------------- /src/components/ScrollToTop.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from "react"; 2 | import { useLocation } from "react-router-dom"; 3 | 4 | export default function ScrollToTop() { 5 | const { pathname } = useLocation(); 6 | 7 | useEffect(() => { 8 | document.querySelector("#root")?.scrollIntoView(); 9 | }, [pathname]); 10 | 11 | return null; 12 | } 13 | -------------------------------------------------------------------------------- /src/components/SelectNetworkComponent.tsx: -------------------------------------------------------------------------------- 1 | import FormControlLabel from "@mui/material/FormControlLabel"; 2 | import FormGroup from "@mui/material/FormGroup"; 3 | import Switch from "@mui/material/Switch"; 4 | import { NetworkId, useWallet } from "@txnlab/use-wallet-react"; 5 | 6 | export default function SelectNetworkComponent() { 7 | const { activeNetwork, setActiveNetwork } = useWallet(); 8 | 9 | return ( 10 | 11 | 14 | {activeNetwork.charAt(0).toUpperCase() + activeNetwork.slice(1)} 15 | 16 | } 17 | control={ 18 | 21 | activeNetwork === NetworkId.MAINNET 22 | ? setActiveNetwork(NetworkId.TESTNET) 23 | : setActiveNetwork(NetworkId.MAINNET) 24 | } 25 | sx={{ 26 | "& .MuiSwitch-thumb": { 27 | backgroundColor: "#f57b14", 28 | }, 29 | "& .MuiSwitch-track": { 30 | backgroundColor: "#fff", 31 | }, 32 | "& .Mui-checked + span.MuiSwitch-track": { 33 | backgroundColor: "#f57b14", 34 | }, 35 | }} 36 | /> 37 | } 38 | classes={{ label: "text-white" }} 39 | /> 40 | 41 | ); 42 | } -------------------------------------------------------------------------------- /src/components/SelectToolComponent.tsx: -------------------------------------------------------------------------------- 1 | import { Link } from "react-router-dom"; 2 | import { TOOLS } from "../constants"; 3 | import { USAlgo2025Leaderboard } from "./USAlgo2025Leaderboard"; 4 | import CarouselComponent from "./CarouselComponent"; 5 | 6 | export function SelectToolComponent() { 7 | return ( 8 |
9 |
10 |
11 | 21 | 27 |
28 |
29 |
30 | our top tools 31 |
32 | {TOOLS.filter((tool) => 33 | [ 34 | "Simple Mint", 35 | "Airdrop", 36 | "Simple Send", 37 | "Multimint Asset Holders", 38 | ].includes(tool.label) 39 | ) 40 | .slice(0, 4) // Ensures only 4 tools are shown 41 | .map((tool) => ( 42 | 43 |
46 |
47 |
48 |
49 |
50 | icon 55 |
{" "} 56 |
57 |
58 |
59 |
{tool.label}
60 |

61 | {tool.description} 62 |

63 |
64 | 65 | ))} 66 |
67 |
68 | 69 | 70 |
71 | all asset management tools 72 |
73 | {TOOLS.filter((tool) => tool.category === "asset").map((tool) => ( 74 | 75 |
78 |
79 |
80 |
81 |
82 | icon 87 |
{" "} 88 |
89 |
90 |
91 |
{tool.label}
92 |

93 | {tool.description} 94 |

95 |
96 | 97 | ))} 98 |
99 |
100 | 101 |
102 | all mint tools 103 |
104 | {TOOLS.filter((tool) => tool.category === "mint").map((tool) => ( 105 | 106 |
109 |
110 |
111 |
112 |
113 | icon 118 |
{" "} 119 |
120 |
121 |
122 |
{tool.label}
123 |

124 | {tool.description} 125 |

126 |
127 | 128 | ))} 129 |
130 |
131 |
132 | ); 133 | } 134 | -------------------------------------------------------------------------------- /src/components/USAlgo2025Leaderboard.tsx: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import { formatAddress, getIndexerURL, getOwnerAddressOfAsset } from "../utils"; 3 | import { NetworkId } from "@txnlab/use-wallet-react"; 4 | import { useEffect, useState } from "react"; 5 | import algosdk from "algosdk"; 6 | 7 | interface AssetHolder { 8 | [name: string]: number; 9 | } 10 | 11 | interface HolderData { 12 | bronze: [string, number][]; 13 | silver: [string, number][]; 14 | gold: [string, number][]; 15 | } 16 | 17 | const CREATOR_ADDRESS = 18 | "USAHT24VO35GF4IBMKKBPJGBPHEY2I2YBBYUGVPLWEOZAE7ATSPNG3Q274"; 19 | const MIN_HOLDERS = 3; 20 | const ASSET_TYPES = { 21 | BRONZE: "USAB", 22 | SILVER: "USAS", 23 | GOLD: "USAG", 24 | } as const; 25 | 26 | async function getNfdDomain(address: string): Promise { 27 | try { 28 | const response = await axios.get( 29 | `https://api.nf.domains/nfd/lookup?address=${address}&view=tiny` 30 | ); 31 | return response.data[address]?.name || ""; 32 | } catch { 33 | return ""; 34 | } 35 | } 36 | 37 | async function fetchAllAssets(creatorAddress: string): Promise { 38 | let threshold = 1000; 39 | const createdAssets = await axios.get( 40 | `${getIndexerURL( 41 | NetworkId.MAINNET 42 | )}/v2/accounts/${creatorAddress}/created-assets?limit=${threshold}` 43 | ); 44 | while (createdAssets.data.assets.length === threshold) { 45 | const nextAssets = await axios.get( 46 | `${getIndexerURL( 47 | NetworkId.MAINNET 48 | )}/v2/accounts/${creatorAddress}/created-assets?limit=1000&next=${ 49 | createdAssets.data["next-token"] 50 | }` 51 | ); 52 | createdAssets.data.assets = createdAssets.data.assets.concat( 53 | nextAssets.data.assets 54 | ); 55 | createdAssets.data["next-token"] = nextAssets.data["next-token"]; 56 | threshold += 1000; 57 | } 58 | 59 | return createdAssets.data.assets; 60 | } 61 | 62 | async function processHolders(assetIds: number[]): Promise { 63 | const holders: AssetHolder = {}; 64 | 65 | const addresses = []; 66 | 67 | for (let i = 0; i < assetIds.length; i++) { 68 | const add = await getOwnerAddressOfAsset(assetIds[i], NetworkId.MAINNET); 69 | addresses.push(add); 70 | } 71 | 72 | // Count holdings 73 | addresses.forEach((holder) => { 74 | if (algosdk.isValidAddress(holder)) { 75 | holders[holder] = (holders[holder] || 0) + 1; 76 | } 77 | }); 78 | 79 | return holders; 80 | } 81 | 82 | async function resolveHolderNames( 83 | holders: AssetHolder 84 | ): Promise<[string, number][]> { 85 | let validAddresses = Object.entries(holders).filter( 86 | ([addr]) => algosdk.isValidAddress(addr) && addr !== CREATOR_ADDRESS 87 | ); 88 | 89 | const orderedAddresses = validAddresses.sort( 90 | ([_, count1], [__, count2]) => count2 - count1 91 | ); 92 | 93 | if (orderedAddresses.length > MIN_HOLDERS) { 94 | validAddresses = orderedAddresses.slice(0, MIN_HOLDERS); 95 | } else { 96 | validAddresses = orderedAddresses; 97 | } 98 | 99 | // Batch NFD lookups with rate limiting 100 | const resolvedEntries: [string, number][] = []; 101 | for (let i = 0; i < validAddresses.length; i++) { 102 | const [address, count] = validAddresses[i]; 103 | const nfd = await getNfdDomain(address); 104 | resolvedEntries.push([nfd || formatAddress(address), count]); 105 | await new Promise((resolve) => setTimeout(resolve, 100)); 106 | } 107 | 108 | while (resolvedEntries.length < MIN_HOLDERS) { 109 | resolvedEntries.push(["-", 0]); 110 | } 111 | 112 | return resolvedEntries; 113 | } 114 | 115 | export const USAlgo2025Leaderboard = () => { 116 | const [holderData, setHolderData] = useState({ 117 | bronze: [], 118 | silver: [], 119 | gold: [], 120 | }); 121 | 122 | useEffect(() => { 123 | async function getLeaderBoard() { 124 | try { 125 | // Fetch all assets 126 | const assets = await fetchAllAssets(CREATOR_ADDRESS); 127 | 128 | // Group assets by type 129 | const assetGroups = { 130 | bronze: assets 131 | .filter((asset) => 132 | asset.params["unit-name"].startsWith(ASSET_TYPES.BRONZE) 133 | ) 134 | .map((asset) => asset.index), 135 | silver: assets 136 | .filter((asset) => 137 | asset.params["unit-name"].startsWith(ASSET_TYPES.SILVER) 138 | ) 139 | .map((asset) => asset.index), 140 | gold: assets 141 | .filter((asset) => 142 | asset.params["unit-name"].startsWith(ASSET_TYPES.GOLD) 143 | ) 144 | .map((asset) => asset.index), 145 | }; 146 | 147 | // Process all holder types in parallel 148 | const bronzeHolders = await processHolders(assetGroups.bronze).then( 149 | resolveHolderNames 150 | ); 151 | const silverHolders = await processHolders(assetGroups.silver).then( 152 | resolveHolderNames 153 | ); 154 | const goldHolders = await processHolders(assetGroups.gold).then( 155 | resolveHolderNames 156 | ); 157 | 158 | setHolderData({ 159 | bronze: bronzeHolders, 160 | silver: silverHolders, 161 | gold: goldHolders, 162 | }); 163 | } catch (error) { 164 | console.error("Error fetching leaderboard:", error); 165 | // Handle error appropriately (e.g., show error message to user) 166 | } 167 | } 168 | 169 | getLeaderBoard(); 170 | }, []); 171 | 172 | return ( 173 | <> 174 |
175 |

176 | USAlgo 2025 Leaderboard 177 |

178 |
179 | 180 | 181 | 182 | 185 | 188 | 191 | 194 | 195 | 196 | 197 | 198 | 201 | {holderData.gold.map(([name, count], index) => ( 202 | 208 | ))} 209 | {holderData.gold.length === 0 && ( 210 | 216 | )} 217 | 218 | 219 | 222 | {holderData.silver.map(([name, count], index) => ( 223 | 229 | ))} 230 | {holderData.silver.length === 0 && ( 231 | 237 | )} 238 | 239 | 240 | 243 | {holderData.bronze.map(([name, count], index) => ( 244 | 250 | ))} 251 | {holderData.bronze.length === 0 && ( 252 | 258 | )} 259 | 260 | 261 |
183 | Position 184 | 186 | 1 187 | 189 | 2 190 | 192 | 3 193 |
199 | Gold 200 | 206 | {name === "-" ? " - " : `${name} (${count})`} 207 | 214 | Loading... 215 |
220 | Silver 221 | 227 | {name === "-" ? " - " : `${name} (${count})`} 228 | 235 | Loading... 236 |
241 | Bronze 242 | 248 | {name === "-" ? " - " : `${name} (${count})`} 249 | 256 | Loading... 257 |
262 |
263 |
264 | 265 | ); 266 | }; 267 | -------------------------------------------------------------------------------- /src/constants.ts: -------------------------------------------------------------------------------- 1 | // INDEXER 2 | export const MAINNET_ALGONODE_INDEXER = "https://mainnet-idx.algonode.cloud"; 3 | export const TESTNET_ALGONODE_INDEXER = "https://testnet-idx.algonode.cloud"; 4 | 5 | // NF 6 | export const MAINNET_NFD_API_BASE_URL = "https://api.nf.domains"; 7 | export const TESTNET_NFD_API_BASE_URL = "https://api.testnet.nf.domains"; 8 | 9 | // ASSET PREVIEW 10 | export const ASSET_PREVIEW = "https://wallet.wen.tools/asset/"; 11 | 12 | // DONATION WALLETS 13 | export const DONATE_WALLET_1 = 14 | "O2ZPSV6NJC32ZXQ7PZ5ID6PXRKAWQE2XWFZK5NK3UFULPZT6OKIOROEAPU"; 15 | export const DONATE_WALLET_2 = 16 | "VYPDFMVRXCI2Z4FPC2GHB4QC6PSCTEDAS4EU7GE3W4B3MRHXNZO6BB2RZA"; 17 | 18 | // MINT FEES 19 | export const MINT_FEE_WALLET = 20 | "RBZ4GUE7FFDZWCN532FFR5AIYJ6K4V2GKJS5B42JPSWOAVWUT4OHWG57YQ"; 21 | export const MINT_FEE_PER_ASA = 0; 22 | export const UPDATE_FEE_PER_ASA = 0; 23 | 24 | export const ALGORAND_ZERO_ADDRESS = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAY5HFKQ"; 25 | 26 | export const IPFS_ENDPOINT = "https://ipfs.algonode.dev/ipfs/"; 27 | 28 | export const CREATOR_WALLETS = []; 29 | 30 | export const PREFIXES = []; 31 | 32 | // TOOLS 33 | export const TOOLS = [ 34 | { 35 | id: "simple_batch_mint", 36 | // label: "🌿 Simple Batch Mint", 37 | label: "Simple Batch Mint", 38 | description: "Easily Mint a collection in bulk using Crust Network.", 39 | path: "/simple-batch-mint", 40 | category: "mint", 41 | icon:"/icons/mint.png" 42 | 43 | }, 44 | { 45 | id: "simple_mint", 46 | // label: "🌿 Simple Mint", 47 | label: "Simple Mint", 48 | description: "Easily mint a token or NFT using Crust Network", 49 | path: "/simple-mint", 50 | category: "mint", 51 | icon:"/icons/devtools.png" 52 | }, 53 | { 54 | id: "really_simple_mint", 55 | // label: "🌿 Really Simple Mint", 56 | label: "Really Simple Mint", 57 | description: "Easily mint an NFT using Crust Network", 58 | path: "/really-simple-mint", 59 | category: "mint", 60 | icon:"/icons/devtools.png" 61 | }, 62 | { 63 | id: "simple_update", 64 | // label: "⬆️ Simple Update", 65 | label: "Simple Update", 66 | description: "Easily update your Mutable Asset using Crust Network", 67 | path: "/simple-update", 68 | category: "mint", 69 | icon:"/icons/mintupdate.png" 70 | }, 71 | { 72 | id: "batch_mint", 73 | // label: "🌿 ARC-69 Collection Mint", 74 | label: "ARC-69 Collection Mint", 75 | description: "Mint an ARC-69 collection in bulk.", 76 | path: "/arc69-collection-mint", 77 | category: "mint", 78 | icon:"/icons/arc69m.png" 79 | }, 80 | { 81 | id: "batch_update", 82 | // label: "⬆️ ARC-69 Collection Metadata Update", 83 | label: "ARC-69 Collection Metadata Update", 84 | description: "Update the metadata for an ARC-69 collection in bulk.", 85 | path: "/arc69-metadata-update", 86 | category: "mint", 87 | icon:"/icons/arc69u.png" 88 | }, 89 | { 90 | id: "collection_data", 91 | // label: "⬇️ Download ARC-69 Collection Data", 92 | label: "Download ARC-69 Collection Data", 93 | description: "Download ARC-69 data for a collection in CSV format.", 94 | path: "/download-arc69-collection-data", 95 | category: "mint", 96 | icon:"/icons/arc69d.png" 97 | }, 98 | { 99 | id: "arc19_batch_mint", 100 | // label: "🌿 ARC-19 Collection Mint", 101 | label: "ARC-19 Collection Mint", 102 | description: "Mint an ARC-19 collection in bulk.", 103 | path: "/arc19-collection-mint", 104 | category: "mint", 105 | icon:"/icons/arc19m.png" 106 | }, 107 | { 108 | id: "arc19_batch_update", 109 | // label: "⬆️ ARC-19 Collection Metadata Update", 110 | label: "ARC-19 Collection Metadata Update", 111 | description: "Update the metadata for an ARC-19 collection in bulk.", 112 | path: "/arc19-metadata-update", 113 | category: "mint", 114 | icon:"/icons/arc19u.png" 115 | }, 116 | { 117 | id: "arc19_collection_data", 118 | // label: "⬇️ Download ARC-19 Collection Data", 119 | label: "Download ARC-19 Collection Data", 120 | description: "Download ARC-19 data for a collection in CSV format.", 121 | path: "/download-arc19-collection-data", 122 | category: "mint", 123 | icon:"/icons/arc19d.png" 124 | }, 125 | { 126 | id: "arc3_batch_mint", 127 | // label: "🌿 ARC-3 Collection Mint", 128 | label: "ARC-3 Collection Mint", 129 | description: "Mint an ARC-3 collection in bulk.", 130 | path: "/arc3-collection-mint", 131 | category: "mint", 132 | icon:"/icons/arc3m.png" 133 | }, 134 | { 135 | id: "batch_optin", 136 | // label: "➕ Asset Add", 137 | label: "Asset Add", 138 | description: "Optin assets in bulk.", 139 | path: "/batch-optin", 140 | category: "asset", 141 | icon:"/icons/optin.png" 142 | }, 143 | { 144 | id: "simple_send_tool", 145 | // label: "📨 Simple Send", 146 | label: "Simple Send", 147 | description: 148 | "Easily mass send assets to a single or an asset to multiple wallets.", 149 | path: "/simple-send", 150 | category: "asset", 151 | icon:"/icons/send.png" 152 | }, 153 | { 154 | id: "vault_send_tool", 155 | // label: "💼 Vault Send", 156 | label: "Vault Send", 157 | description: 158 | "Easily mass send assets to a single or an asset to multiple NFD vaults.", 159 | path: "/vault-send", 160 | category: "asset", 161 | icon:"/icons/vault.png" 162 | }, 163 | { 164 | id: "batch_optout", 165 | // label: "➖ Asset Remove", 166 | label: "Asset Remove", 167 | description: "Optout assets in bulk.", 168 | path: "/batch-optout", 169 | category: "asset", 170 | icon:"/icons/optout.png" 171 | }, 172 | { 173 | id: "batch_destroy", 174 | // label: "❌ Asset Destroy", 175 | label: "Asset Destroy", 176 | description: "Destroy (Delete) assets in bulk.", 177 | path: "/batch-destroy", 178 | category: "asset", 179 | icon:"/icons/bin.png" 180 | }, 181 | { 182 | id: "simple_airdrop_tool", 183 | // label: "🪂 Simple Airdrop", 184 | label: "Simple Airdrop", 185 | description: 186 | "Premium Tool - Easily Airdrop a Token of any amount to holders from a creator wallet.", 187 | path: "/simple-airdrop", 188 | category: "asset", 189 | icon:"/icons/drop.png" 190 | }, 191 | { 192 | id: "airdrop_tool", 193 | // label: "🪂 Asset Send/Airdrop", 194 | label: "Airdrop", 195 | description: "Airdrop/Send assets/ALGO to a list of addresses.", 196 | path: "/airdrop", 197 | category: "asset", 198 | icon:"/icons/drops.png" 199 | }, 200 | { 201 | id: "batch_clawback", 202 | // label: "🔙 Asset Clawback", 203 | label: "Asset Clawback", 204 | description: "Clawback assets in bulk.", 205 | path: "/batch-clawback", 206 | category: "asset", 207 | icon:"/icons/clawback.png" 208 | }, 209 | { 210 | id: "batch_freeze", 211 | // label: "🧊 Asset Freeze", 212 | label: "Asset Freeze", 213 | description: "Freeze/Unfreeze assets in bulk.", 214 | path: "/batch-freeze", 215 | category: "asset", 216 | icon:"/icons/freeze.png" 217 | }, 218 | { 219 | id: "wallet_holdings", 220 | // label: "💼 Wallet Holdings", 221 | label: "Wallet Holdings", 222 | description: "View the assets data of a wallet in CSV format.", 223 | path: "/wallet-holdings", 224 | category: "asset", 225 | icon:"/icons/wallet.png" 226 | }, 227 | { 228 | id: "collection_snapshot", 229 | // label: "🔎 Find Collection Holders", 230 | label: "Find Collection Holders", 231 | description: "Download all the holders for a collection in CSV format.", 232 | path: "/find-collection-holders", 233 | category: "asset", 234 | icon:"/icons/group.png" 235 | }, 236 | { 237 | id: "multimint_asset_holders", 238 | // label: "🌌 Multimint Asset Holders", 239 | label: "Multimint Asset Holders", 240 | description: "View the holders of a multimint asset list in CSV format.", 241 | path: "/multimint-asset-holders", 242 | category: "asset", 243 | icon:"/icons/multihold.png" 244 | }, 245 | { 246 | id: "simple_mint_classic", 247 | // label: "🌿 Simple Mint Classic", 248 | label: "Simple Mint Classic", 249 | description: "Easily Mint an Asset on Algorand using Pinata", 250 | path: "/simple-mint-classic", 251 | category: "mint", 252 | icon:"/icons/smint.png" 253 | }, 254 | { 255 | id: "simple_update_classic", 256 | // label: "⬆️ Simple Update Classic", 257 | label: "Simple Update Classic", 258 | description: "Easily update your Mutable Assets using Pinata", 259 | path: "/simple-update-classic", 260 | category: "mint", 261 | icon:"/icons/mintupdate.png" 262 | }, 263 | { 264 | id: "bluk_claim", 265 | label: "Bulk Claim", 266 | description: 267 | "Claim multiple assets from your asset Inbox & NFD vault in one go", 268 | path: "/bulk-claim", 269 | category: "asset", 270 | icon:"/icons/bulk.png" 271 | }, 272 | { 273 | id: "token_manager", 274 | label: "Token Manager", 275 | description: 276 | "Set and manage your token's circulation supply using the ARC62 Standard", 277 | path: "/token-manager", 278 | category: "asset", 279 | icon:"/icons/manager.png" 280 | }, 281 | { 282 | id: "usalgo_2025_tracker", 283 | label: "USAlgo 2025 Tracker", 284 | description: 285 | "Track your collections from USAlgo 2025", 286 | path: "/usalgo-2025-tracker", 287 | category: "asset", 288 | icon:"/icons/track.png" 289 | }, 290 | ]; 291 | -------------------------------------------------------------------------------- /src/crust-auth.ts: -------------------------------------------------------------------------------- 1 | // import { bytesToBase64 } from "./utils"; 2 | // import { toast } from "react-toastify"; 3 | 4 | // /** Text encoder used to convert strings to Uint8Arrays */ 5 | // const textEncoder = new TextEncoder; 6 | 7 | // /** Data about the user and the wallet they are using */ 8 | // type UserData = { 9 | // // The address of the user's account 10 | // account: string, 11 | // // The chain the user is using 12 | // wallet: string 13 | // } 14 | 15 | export const CRUST_DEBUG = false; 16 | 17 | /** Creates an access token for Crust IPFS W3Auth Pinning Service by signing a transaction that is 18 | * never sent. 19 | * @param address The address of the Algorand account 20 | * @return An access token that can be used to access Crust IPFS W3Auth PS endpoints 21 | */ 22 | // @ts-expect-error The argument being unused should be temporary 23 | export async function signLoginAlgorandForCrustIpfsEndpoint(address: string) { // eslint-disable-line 24 | // XXX: Fake the Crust authentication so that we can skip the authentication when connecting the 25 | // wallet while still being able to use the tools that rely on Crust. This is an actual token for 26 | // the account: BCAPWJALWA3N3EIZRFCO55PQ3YBYCSGPZXNDHOOA6R7CGHTISV2GCSYC6Y 27 | return 'YWxnby1CQ0FQV0pBTFdBM04zRUlaUkZDTzU1UFEzWUJZQ1NHUFpYTkRIT09BNlI3Q0dIVElTVjJHQ1NZQzZZOkZyYjl6RWhudVVKZ0ZaY0d1cmRTUE45dW1SL1hHMnRlalc0VFpkb3huN3ZXMTVKOFd6TGMva3R2LytnMklWRVFRMVN4Vnk3N0plZ3laZkVKMkRxaEFRPT0=' 28 | 29 | // XXX: Because signing arbitrary bytes is broken in Pera the following code was disabled and a 30 | // mock was used instead. When signing arbitrary bytes works in Pera, remove the mock code above 31 | // and uncomment the code below. 32 | 33 | // // Already logged in, so simply return the token in storage 34 | // if (isCrustAuth()) { 35 | // return localStorage.getItem('authBasic') 36 | // } 37 | 38 | // const user: UserData = { account: address, wallet: "algorand" }; 39 | // const prefix = getPrefix(user); 40 | // let signedData: (Uint8Array | null)[]; 41 | 42 | // try { 43 | // toast.info("Sign the transaction to authenticate for Crust IPFS.") 44 | 45 | // peraWallet.reconnectSession(); 46 | // // Sign a message 47 | // signedData = await peraWallet.signData( 48 | // [{ 49 | // // The data that is to be signed is simply the account address 50 | // data: textEncoder.encode(address), 51 | // // This is the message displayed to the user that is used to explain the reason for signing 52 | // // the data 53 | // message: "For login" 54 | // }], 55 | // user.account 56 | // ) 57 | // } catch (err: any) { 58 | // throw Error("Algorand wallet signing for Crust IPFS error: " + err); 59 | // } 60 | 61 | // if (signedData[0] === null || signedData[0].length === 0) { 62 | // // Signed data is empty, something must have gone wrong 63 | // throw Error('Algorand wallet signing for Crust IPFS error: No signed data returned from wallet') 64 | // } 65 | 66 | // // Create Crust IPFS W3Auth Pinning Service access token in the format described in 67 | // // and 68 | // // (as of October 2025). According 69 | // // to the docs, the access token is a Base64 encoding of a string that was in the format: 70 | // // `-
-` 71 | 72 | // const sigB64 = await bytesToBase64(signedData[0]) 73 | // const formattedSig = `${prefix}-${address}:${sigB64}` 74 | 75 | // return await bytesToBase64(textEncoder.encode(formattedSig)); 76 | } 77 | 78 | /** Returns if the user has rejected Crust authentication. This is used to stop prompting the user 79 | * to authenticate for Crust 80 | */ 81 | export function isCrustAuthFail() { 82 | return !!localStorage.getItem('authBasicFail'); 83 | } 84 | 85 | export function isCrustAuth() { 86 | const token = localStorage.getItem("authBasic"); 87 | if (token === "" || token === undefined || token === null) { 88 | return false; 89 | } 90 | 91 | return true; 92 | } 93 | 94 | // const getPrefix = (user: UserData) => { 95 | // if ( 96 | // user.wallet.startsWith("metamask") || 97 | // user.wallet === "metax" || 98 | // user.wallet === "wallet-connect" || 99 | // user.wallet === "web3auth" 100 | // ) { 101 | // return "eth"; 102 | // } 103 | 104 | // if (user.wallet === "near") { 105 | // return "near"; 106 | // } 107 | 108 | // if (user.wallet === "flow") { 109 | // return "flow"; 110 | // } 111 | 112 | // if (user.wallet === "solana") { 113 | // return "sol"; 114 | // } 115 | 116 | // if (user.wallet === "elrond") { 117 | // return "elrond"; 118 | // } 119 | 120 | // if (user.wallet === "algorand") { 121 | // return "algo"; 122 | // } 123 | 124 | // if (user.wallet === "aptos-martian") { 125 | // return "aptos"; 126 | // } 127 | 128 | // if (user.wallet === "aptos-petra") { 129 | // return "aptos"; 130 | // } 131 | 132 | // if (user.wallet === "ton-connect") { 133 | // return "ton"; 134 | // } 135 | 136 | // return "substrate"; 137 | // }; 138 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | @import url("https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap"); 2 | @tailwind base; 3 | @tailwind components; 4 | @tailwind utilities; 5 | 6 | /* Chrome, Safari, Edge, Opera */ 7 | input::-webkit-outer-spin-button, 8 | input::-webkit-inner-spin-button { 9 | -webkit-appearance: none; 10 | margin: 0; 11 | } 12 | 13 | /* Firefox */ 14 | input[type="number"] { 15 | -moz-appearance: textfield; 16 | } 17 | 18 | .tooltip { 19 | @apply invisible absolute; 20 | } 21 | 22 | .has-tooltip:hover .tooltip { 23 | @apply visible z-10; 24 | } 25 | 26 | /* scrollbar */ 27 | 28 | ::-webkit-scrollbar { 29 | width: 8px; 30 | } 31 | 32 | ::-webkit-scrollbar-track { 33 | background: #1e1e1e; 34 | border-radius: 10px; 35 | } 36 | 37 | ::-webkit-scrollbar-thumb { 38 | background: #fd941d; 39 | border-radius: 10px; 40 | } 41 | 42 | ::-webkit-scrollbar-thumb:hover { 43 | background: #357abd; 44 | } 45 | * { 46 | scrollbar-width: thin; 47 | scrollbar-color: #fd941d #1e1e1e; 48 | } 49 | body { 50 | -ms-overflow-style: none; 51 | scrollbar-face-color: #fd941d; 52 | scrollbar-track-color: #1e1e1e; 53 | } 54 | 55 | @keyframes ticker { 56 | 0% { 57 | transform: translateX(0); 58 | } 59 | 100% { 60 | transform: translateX(-100%); 61 | } 62 | } 63 | 64 | /* 65 | * Most of the code for the ticker's marquee animation is based on: 66 | * https://stackoverflow.com/a/21233577 67 | */ 68 | .ticker-text { 69 | display: inline-block; 70 | width: max-content; 71 | padding-left: 100%; 72 | padding-right: 10%; 73 | will-change: transform; 74 | animation: ticker 15s linear infinite; 75 | } 76 | 77 | /* 78 | * The ticker speed is higher at larger screen sizes, so make the animation 79 | * duration longer to decrease the ticker speed 80 | */ 81 | @media (min-width: 600px) { 82 | .ticker-text { 83 | animation-duration: 20s; 84 | } 85 | } 86 | 87 | @media (min-width: 1200px) { 88 | .ticker-text { 89 | animation-duration: 30s; 90 | } 91 | } 92 | 93 | /* Stop the animations for users who may be sensitive to motion */ 94 | @media (prefers-reduced-motion: reduce) { 95 | .ticker-text { 96 | animation: none; 97 | width: auto; 98 | padding-left: 0; 99 | padding-right: 0%; 100 | margin: 0 auto 0; 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/main.tsx: -------------------------------------------------------------------------------- 1 | import { StrictMode } from 'react' 2 | import { createRoot } from 'react-dom/client' 3 | import './index.css' 4 | import App from './App.tsx' 5 | 6 | createRoot(document.getElementById('root')!).render( 7 | 8 | 9 | , 10 | ) 11 | -------------------------------------------------------------------------------- /src/pages/BatchClawback.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import Papa from "papaparse"; 3 | import { toast } from "react-toastify"; 4 | import { 5 | createClawbackTransactions, 6 | getAssetDecimals, 7 | SignWithMnemonic, 8 | sliceIntoChunks, 9 | walletSign, 10 | } from "../utils"; 11 | import { TOOLS } from "../constants"; 12 | 13 | import InfinityModeComponent from "../components/InfinityModeComponent"; 14 | import { useWallet } from "@txnlab/use-wallet-react"; 15 | import ConnectButton from "../components/ConnectButton"; 16 | 17 | export function BatchClawback() { 18 | const [csvData, setCsvData] = useState(null as null | any); 19 | const [isTransactionsFinished, setIsTransactionsFinished] = useState(false); 20 | const [txSendingInProgress, setTxSendingInProgress] = useState(false); 21 | const [mnemonic, setMnemonic] = useState(""); 22 | const { activeAddress, algodClient, transactionSigner } = useWallet(); 23 | 24 | const handleFileData = async () => { 25 | if (activeAddress === null || activeAddress === undefined) { 26 | toast.error("Wallet not found!"); 27 | return; 28 | } 29 | let headers; 30 | const data = []; 31 | for (let i = 0; i < csvData.length; i++) { 32 | if (csvData[i].length === 1) continue; 33 | if (i === 0) { 34 | headers = csvData[i]; 35 | } else { 36 | const obj: any = {}; 37 | for (let j = 0; j < headers.length; j++) { 38 | obj[headers[j]] = csvData[i][j]; 39 | } 40 | data.push(obj); 41 | } 42 | } 43 | let assetIds: any = {}; 44 | for (let i = 0; i < data.length; i++) { 45 | if (data[i].asset_id) { 46 | assetIds[data[i].asset_id] = true; 47 | } 48 | } 49 | assetIds = Object.keys(assetIds); 50 | const assetDecimals: any = {}; 51 | for (let i = 0; i < assetIds.length; i++) { 52 | assetIds[i] = parseInt(assetIds[i]); 53 | if (assetIds[i] === 1) continue; 54 | assetDecimals[assetIds[i]] = await getAssetDecimals( 55 | assetIds[i], 56 | algodClient 57 | ); 58 | } 59 | try { 60 | try { 61 | if (mnemonic === "") toast.info("Please sign the transactions!"); 62 | const groups = await createClawbackTransactions( 63 | data, 64 | assetDecimals, 65 | activeAddress, 66 | algodClient 67 | ); 68 | let signedTransactions = []; 69 | if (mnemonic !== "") { 70 | signedTransactions = SignWithMnemonic(groups.flat(), mnemonic); 71 | } else { 72 | const flat = await walletSign(groups, transactionSigner); 73 | signedTransactions = sliceIntoChunks(flat, 16); 74 | } 75 | setTxSendingInProgress(true); 76 | for (let i = 0; i < signedTransactions.length; i++) { 77 | try { 78 | await algodClient.sendRawTransaction(signedTransactions[i]).do(); 79 | if (i % 5 === 0) { 80 | toast.success( 81 | `Transaction ${i + 1} of ${ 82 | signedTransactions.length 83 | } confirmed!`, 84 | { 85 | autoClose: 1000, 86 | } 87 | ); 88 | } 89 | } catch (err) { 90 | console.error(err); 91 | toast.error( 92 | `Transaction ${i + 1} of ${signedTransactions.length} failed!`, 93 | { 94 | autoClose: 1000, 95 | } 96 | ); 97 | } 98 | await new Promise((resolve) => setTimeout(resolve, 20)); 99 | } 100 | setIsTransactionsFinished(true); 101 | setTxSendingInProgress(false); 102 | toast.success("All transactions confirmed!"); 103 | toast.info("You can support by donating :)"); 104 | } catch (err) { 105 | console.error(err); 106 | setTxSendingInProgress(false); 107 | toast.error("Something went wrong!"); 108 | return; 109 | } 110 | } catch (err: any) { 111 | console.error(err); 112 | toast.error(err.message); 113 | setTxSendingInProgress(false); 114 | } 115 | }; 116 | 117 | return ( 118 |
119 |

120 | {TOOLS.find((tool) => tool.path === window.location.pathname)?.label} 121 |

122 | 123 | 133 | 143 | {/* mnemonic */} 144 | 145 | {/* end mnemonic */} 146 |

Upload CSV file

147 | {csvData == null ? ( 148 | 180 | ) : ( 181 |
182 | {isTransactionsFinished ? ( 183 | <> 184 |

185 | All transactions completed! 186 |
187 |

188 |

189 | You can reload the page if you want to use again. 190 |

191 | 192 | ) : ( 193 | <> 194 |

File uploaded

195 |

196 | {csvData.length - 1} transactions found! 197 |

198 |

3- Sign Your Transactions

199 | {!txSendingInProgress ? ( 200 | 207 | ) : ( 208 |
209 |
213 | Please wait... Transactions are sending to the network. 214 |
215 | )} 216 | 217 | )} 218 |
219 | )} 220 |

221 | ⚠️If you reload or close this page, you will lose your progress⚠️ 222 |
223 | You can reload the page if you want to stop/restart the process! 224 |

225 |
226 | ); 227 | } 228 | -------------------------------------------------------------------------------- /src/pages/BatchDelete.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | //import Papa from "papaparse"; 3 | import { toast } from "react-toastify"; 4 | import { 5 | createAssetDeleteTransactions, 6 | SignWithMnemonic, 7 | sliceIntoChunks, 8 | walletSign, 9 | } from "../utils"; 10 | import { TOOLS } from "../constants"; 11 | 12 | import InfinityModeComponent from "../components/InfinityModeComponent"; 13 | import { useWallet } from "@txnlab/use-wallet-react"; 14 | import ConnectButton from "../components/ConnectButton"; 15 | 16 | export function BatchDelete() { 17 | const [csvData, setCsvData] = useState(null as null | any); 18 | const [isTransactionsFinished, setIsTransactionsFinished] = useState(false); 19 | const [txSendingInProgress, setTxSendingInProgress] = useState(false); 20 | const [mnemonic, setMnemonic] = useState(""); 21 | const [assetIds, setAssetIds] = useState(""); 22 | const { activeAddress, algodClient, transactionSigner } = useWallet(); 23 | 24 | const handleFileData = async () => { 25 | const assets = []; 26 | for (let i = 0; i < csvData.length; i++) { 27 | if (i !== 0) { 28 | assets.push(parseInt(csvData[i][0])); 29 | } 30 | } 31 | if (assets.length === 0) { 32 | toast.error("No assets found in the file!"); 33 | return; 34 | } 35 | if (activeAddress === null || activeAddress === undefined) { 36 | toast.error("Wallet not found!"); 37 | return; 38 | } 39 | 40 | try { 41 | try { 42 | if (mnemonic === "") toast.info("Please sign the transactions!"); 43 | const txns = await createAssetDeleteTransactions( 44 | assets, 45 | activeAddress, 46 | algodClient 47 | ); 48 | let signedTransactions = []; 49 | if (mnemonic !== "") { 50 | signedTransactions = SignWithMnemonic(txns.flat(), mnemonic); 51 | } else { 52 | signedTransactions = await walletSign(txns, transactionSigner); 53 | } 54 | const groups = sliceIntoChunks(signedTransactions, 2); 55 | setTxSendingInProgress(true); 56 | for (let i = 0; i < groups.length; i++) { 57 | try { 58 | await algodClient.sendRawTransaction(groups[i]).do(); 59 | if (i % 5 === 0) { 60 | toast.success( 61 | `Transaction ${i + 1} of ${groups.length} confirmed!`, 62 | { 63 | autoClose: 1000, 64 | } 65 | ); 66 | } 67 | } catch (err) { 68 | console.error(err); 69 | toast.error(`Transaction ${i + 1} of ${groups.length} failed!`, { 70 | autoClose: 1000, 71 | }); 72 | } 73 | await new Promise((resolve) => setTimeout(resolve, 150)); 74 | } 75 | setIsTransactionsFinished(true); 76 | setTxSendingInProgress(false); 77 | toast.success("All transactions confirmed!"); 78 | toast.info("You can support by donating :)"); 79 | } catch (error) { 80 | console.log(error); 81 | setTxSendingInProgress(false); 82 | toast.error("Something went wrong! Please check your file!"); 83 | return; 84 | } 85 | } catch (err: any) { 86 | console.error(err); 87 | toast.error(err.message); 88 | setTxSendingInProgress(false); 89 | } 90 | }; 91 | 92 | return ( 93 |
94 |

95 | {TOOLS.find((tool) => tool.path === window.location.pathname)?.label} 96 |

97 | 98 | {/* mnemonic */} 99 | 100 | {/* end mnemonic */} 101 |

Enter Assets

102 | {csvData === null ? ( 103 |
104 | {/* */} 133 |
134 | {/*

or

*/} 135 |
136 |