├── .editorconfig ├── .eslintrc.js ├── .gitattributes ├── .gitignore ├── .husky ├── .gitignore └── commit-msg ├── .prettierrc.js ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── __tests__ └── fake_test.ts ├── commitlint.config.js ├── index.html ├── package-lock.json ├── package.json ├── postcss.config.js ├── src ├── abi │ ├── erc20-abi.ts │ ├── lockbox-abi.ts │ ├── multipay-abi.ts │ └── pns-abi.ts ├── assets │ ├── bitcoin.svg │ ├── ethereum.svg │ ├── images │ │ ├── arrow-blue.svg │ │ ├── arrow-white.svg │ │ ├── bg.jpg │ │ ├── bg1.jpg │ │ ├── bitcoin.svg │ │ ├── check.svg │ │ ├── check_circle.png │ │ ├── circle-1.svg │ │ ├── complete.jpg │ │ ├── connected.svg │ │ ├── ethereum.svg │ │ ├── failed.jpg │ │ ├── help.svg │ │ ├── inputBlock.svg │ │ ├── inputBlock1.svg │ │ ├── inputCheck.svg │ │ ├── logo.svg │ │ ├── solana.svg │ │ └── visa.svg │ ├── solana.svg │ └── visa.svg ├── components │ ├── RegistrationForm.tsx │ ├── common │ │ ├── Button.tsx │ │ ├── FormInput.tsx │ │ ├── LogoHeader.tsx │ │ ├── SearchInput.tsx │ │ └── index.ts │ ├── index.ts │ └── layout │ │ ├── MainLayout.tsx │ │ └── index.ts ├── config │ └── web3modal.tsx ├── domain-register │ ├── BillingAndInfo.tsx │ ├── DomainRegister.tsx │ ├── DomainSearchResults.tsx │ └── index.tsx ├── favicon.svg ├── index.css ├── logo.svg ├── main.tsx ├── providers │ ├── WalletHistoryProvider.tsx │ └── index.tsx ├── services │ └── wallet-service.ts ├── types │ ├── index.ts │ └── wallet.ts ├── vite-env.d.ts └── web3-register │ ├── RegistrationComplete.tsx │ ├── RegistrationFailed.tsx │ ├── Web3Register.tsx │ ├── components │ ├── RegistrationForm.tsx │ ├── RegistrationService.ts │ ├── TransactionHistory.tsx │ ├── WalletConnector.tsx │ ├── WalletStatus.tsx │ └── index.ts │ └── index.tsx ├── tailwind.config.js ├── tsconfig.json ├── tsconfig.node.json ├── vercel.json ├── vite.config.ts └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [**/**.{yml,ts,tsx,js,json,jsx,html}] 4 | indent_style = space 5 | indent_size = 2 6 | insert_final_newline = true 7 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [ 3 | 'eslint:recommended', 4 | 'plugin:@typescript-eslint/recommended', 5 | 'plugin:prettier/recommended' 6 | ], 7 | env: { 8 | node: true, 9 | mocha: true, 10 | es6: true 11 | }, 12 | plugins: ['@typescript-eslint', 'prettier'], 13 | parserOptions: { 14 | parser: '@typescript-eslint/parser' 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Fix how tsconfig.json files are displayed in GitHub's web view, 2 | # by telling it to read said files as JSON With Comments, the 3 | # JSON extension used by TypeScript. 4 | tsconfig.*.json linguist-language=JSON-with-Comments 5 | tsconfig.json linguist-language=JSON-with-Comments 6 | 7 | # Also fix how Visual Studio Code configuration files are read. 8 | .vscode/*.json linguist-language=JSON-with-Comments 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.toptal.com/developers/gitignore/api/node,jetbrains+all,visualstudiocode,macos 3 | # Edit at https://www.toptal.com/developers/gitignore?templates=node,jetbrains+all,visualstudiocode,macos 4 | 5 | ### JetBrains+all ### 6 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 7 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 8 | 9 | # User-specific stuff 10 | .idea/**/workspace.xml 11 | .idea/**/tasks.xml 12 | .idea/**/usage.statistics.xml 13 | .idea/**/dictionaries 14 | .idea/**/shelf 15 | 16 | # AWS User-specific 17 | .idea/**/aws.xml 18 | 19 | # Generated files 20 | .idea/**/contentModel.xml 21 | 22 | # Sensitive or high-churn files 23 | .idea/**/dataSources/ 24 | .idea/**/dataSources.ids 25 | .idea/**/dataSources.local.xml 26 | .idea/**/sqlDataSources.xml 27 | .idea/**/dynamic.xml 28 | .idea/**/uiDesigner.xml 29 | .idea/**/dbnavigator.xml 30 | 31 | # Gradle 32 | .idea/**/gradle.xml 33 | .idea/**/libraries 34 | 35 | # Gradle and Maven with auto-import 36 | # When using Gradle or Maven with auto-import, you should exclude module files, 37 | # since they will be recreated, and may cause churn. Uncomment if using 38 | # auto-import. 39 | # .idea/artifacts 40 | # .idea/compiler.xml 41 | # .idea/jarRepositories.xml 42 | # .idea/modules.xml 43 | # .idea/*.iml 44 | # .idea/modules 45 | # *.iml 46 | # *.ipr 47 | 48 | # CMake 49 | cmake-build-*/ 50 | 51 | # Mongo Explorer plugin 52 | .idea/**/mongoSettings.xml 53 | 54 | # File-based project format 55 | *.iws 56 | 57 | # IntelliJ 58 | out/ 59 | 60 | # mpeltonen/sbt-idea plugin 61 | .idea_modules/ 62 | 63 | # JIRA plugin 64 | atlassian-ide-plugin.xml 65 | 66 | # Cursive Clojure plugin 67 | .idea/replstate.xml 68 | 69 | # Crashlytics plugin (for Android Studio and IntelliJ) 70 | com_crashlytics_export_strings.xml 71 | crashlytics.properties 72 | crashlytics-build.properties 73 | fabric.properties 74 | 75 | # Editor-based Rest Client 76 | .idea/httpRequests 77 | 78 | # Android studio 3.1+ serialized cache file 79 | .idea/caches/build_file_checksums.ser 80 | 81 | ### JetBrains+all Patch ### 82 | # Ignores the whole .idea folder and all .iml files 83 | # See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360 84 | 85 | .idea/ 86 | 87 | # Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023 88 | 89 | *.iml 90 | modules.xml 91 | .idea/misc.xml 92 | *.ipr 93 | 94 | # Sonarlint plugin 95 | .idea/sonarlint 96 | 97 | ### macOS ### 98 | # General 99 | .DS_Store 100 | .AppleDouble 101 | .LSOverride 102 | 103 | # Icon must end with two \r 104 | Icon 105 | 106 | # Thumbnails 107 | ._* 108 | 109 | # Files that might appear in the root of a volume 110 | .DocumentRevisions-V100 111 | .fseventsd 112 | .Spotlight-V100 113 | .TemporaryItems 114 | .Trashes 115 | .VolumeIcon.icns 116 | .com.apple.timemachine.donotpresent 117 | 118 | # Directories potentially created on remote AFP share 119 | .AppleDB 120 | .AppleDesktop 121 | Network Trash Folder 122 | Temporary Items 123 | .apdisk 124 | 125 | ### Node ### 126 | # Logs 127 | logs 128 | *.log 129 | npm-debug.log* 130 | yarn-debug.log* 131 | yarn-error.log* 132 | lerna-debug.log* 133 | .pnpm-debug.log* 134 | 135 | # Diagnostic reports (https://nodejs.org/api/report.html) 136 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 137 | 138 | # Runtime data 139 | pids 140 | *.pid 141 | *.seed 142 | *.pid.lock 143 | 144 | # Directory for instrumented libs generated by jscoverage/JSCover 145 | lib-cov 146 | 147 | # Coverage directory used by tools like istanbul 148 | coverage 149 | *.lcov 150 | 151 | # nyc test coverage 152 | .nyc_output 153 | 154 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 155 | .grunt 156 | 157 | # Bower dependency directory (https://bower.io/) 158 | bower_components 159 | 160 | # node-waf configuration 161 | .lock-wscript 162 | 163 | # Compiled binary addons (https://nodejs.org/api/addons.html) 164 | build/Release 165 | 166 | # Dependency directories 167 | node_modules/ 168 | jspm_packages/ 169 | 170 | # Snowpack dependency directory (https://snowpack.dev/) 171 | web_modules/ 172 | 173 | # TypeScript cache 174 | *.tsbuildinfo 175 | 176 | # Optional npm cache directory 177 | .npm 178 | 179 | # Optional eslint cache 180 | .eslintcache 181 | 182 | # Microbundle cache 183 | .rpt2_cache/ 184 | .rts2_cache_cjs/ 185 | .rts2_cache_es/ 186 | .rts2_cache_umd/ 187 | 188 | # Optional REPL history 189 | .node_repl_history 190 | 191 | # Output of 'npm pack' 192 | *.tgz 193 | 194 | # Yarn Integrity file 195 | .yarn-integrity 196 | 197 | # dotenv environment variables file 198 | .env 199 | .env.test 200 | .env.production 201 | 202 | # parcel-bundler cache (https://parceljs.org/) 203 | .cache 204 | .parcel-cache 205 | 206 | # Next.js build output 207 | .next 208 | out 209 | 210 | # Nuxt.js build / generate output 211 | .nuxt 212 | dist 213 | 214 | # Gatsby files 215 | .cache/ 216 | # Comment in the public line in if your project uses Gatsby and not Next.js 217 | # https://nextjs.org/blog/next-9-1#public-directory-support 218 | # public 219 | 220 | # vuepress build output 221 | .vuepress/dist 222 | 223 | # Serverless directories 224 | .serverless/ 225 | 226 | # FuseBox cache 227 | .fusebox/ 228 | 229 | # DynamoDB Local files 230 | .dynamodb/ 231 | 232 | # TernJS port file 233 | .tern-port 234 | 235 | # Stores VSCode versions used for testing VSCode extensions 236 | .vscode-test 237 | 238 | # yarn v2 239 | .yarn/cache 240 | .yarn/unplugged 241 | .yarn/build-state.yml 242 | .yarn/install-state.gz 243 | .pnp.* 244 | 245 | ### Node Patch ### 246 | # Serverless Webpack directories 247 | .webpack/ 248 | 249 | ### VisualStudioCode ### 250 | .vscode/* 251 | !.vscode/settings.json 252 | !.vscode/tasks.json 253 | !.vscode/launch.json 254 | !.vscode/extensions.json 255 | *.code-workspace 256 | 257 | # Local History for Visual Studio Code 258 | .history/ 259 | 260 | ### VisualStudioCode Patch ### 261 | # Ignore all local history of files 262 | .history 263 | .ionide 264 | 265 | # End of https://www.toptal.com/developers/gitignore/api/node,jetbrains+all,visualstudiocode,macos 266 | -------------------------------------------------------------------------------- /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx --no-install commitlint --edit "$1" 5 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | printWidth: 80, 3 | tabWidth: 2, 4 | singleQuote: true, 5 | quoteProps: 'as-needed', 6 | trailingComma: 'none', 7 | bracketSpacing: true, 8 | semi: false, 9 | useTabs: false, 10 | bracketSameLine: false, 11 | proseWrap: 'never' 12 | } 13 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true, 3 | "editor.formatOnPaste": true, 4 | "[typescript]": { 5 | "editor.defaultFormatter": "esbenp.prettier-vscode" 6 | }, 7 | "[typescriptreact]": { 8 | "editor.defaultFormatter": "esbenp.prettier-vscode" 9 | }, 10 | "[json]": { 11 | "editor.defaultFormatter": "esbenp.prettier-vscode" 12 | }, 13 | "[javascript]": { 14 | "editor.defaultFormatter": "esbenp.prettier-vscode" 15 | }, 16 | "[html]": { 17 | "editor.defaultFormatter": "esbenp.prettier-vscode" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 PDMLab 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 | -------------------------------------------------------------------------------- /__tests__/fake_test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | 3 | describe('some fake test', () => { 4 | it('should succeed', () => { 5 | expect(true).toBeTruthy() 6 | }) 7 | }) 8 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { extends: ['@commitlint/config-conventional'] } 2 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Web3 Domain 8 | 9 | 10 |
14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vite-react-typescript-tailwind-starter", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "dev": "vite", 6 | "build": "tsc && vite build", 7 | "serve": "vite preview --port 3000", 8 | "lint": "eslint . --ext .ts,.tsx,.js", 9 | "prepare": "husky install", 10 | "test": "vitest" 11 | }, 12 | "dependencies": { 13 | "@reown/appkit": "^1.7.4", 14 | "@reown/appkit-adapter-wagmi": "^1.7.4", 15 | "@tanstack/react-query": "^5.75.5", 16 | "@types/styled-components": "^5.1.34", 17 | "@web3modal/wagmi": "^5.1.11", 18 | "axios": "^1.8.4", 19 | "ethers": "^6.13.5", 20 | "react": "^18.2.0", 21 | "react-dom": "^18.2.0", 22 | "styled-components": "^6.1.8", 23 | "tailwind-scrollbar": "^2.0.0", 24 | "viem": "^2.29.0", 25 | "wagmi": "^2.15.2" 26 | }, 27 | "devDependencies": { 28 | "@commitlint/cli": "^17.4.3", 29 | "@commitlint/config-conventional": "^17.4.3", 30 | "@heroicons/react": "^2.0.15", 31 | "@tailwindcss/forms": "^0.5.3", 32 | "@types/node": "18.13.0", 33 | "@types/react": "^18.2.55", 34 | "@types/react-dom": "^18.2.19", 35 | "@typescript-eslint/eslint-plugin": "^5.52.0", 36 | "@typescript-eslint/parser": "^5.52.0", 37 | "@vitejs/plugin-react": "^3.1.0", 38 | "autoprefixer": "^10.4.13", 39 | "eslint": "^8.34.0", 40 | "eslint-config-prettier": "^8.6.0", 41 | "eslint-plugin-import": "^2.27.5", 42 | "eslint-plugin-prettier": "^4.2.1", 43 | "eslint-watch": "^8.0.0", 44 | "husky": "^8.0.3", 45 | "postcss": "^8.4.21", 46 | "prettier": "^2.8.4", 47 | "react-router-dom": "6.11.2", 48 | "tailwindcss": "^3.2.6", 49 | "typescript": "^5.3.3", 50 | "vite": "^4.1.1", 51 | "vitest": "^0.32.2" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {} 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/abi/erc20-abi.ts: -------------------------------------------------------------------------------- 1 | export const erc20Abi = [ 2 | { 3 | inputs: [ 4 | { 5 | internalType: 'string', 6 | name: '_nameX', 7 | type: 'string' 8 | }, 9 | { 10 | internalType: 'string', 11 | name: '_symbolX', 12 | type: 'string' 13 | }, 14 | { 15 | internalType: 'address', 16 | name: '_toX', 17 | type: 'address' 18 | }, 19 | { 20 | internalType: 'uint256', 21 | name: '_totalSupplyX', 22 | type: 'uint256' 23 | } 24 | ], 25 | stateMutability: 'nonpayable', 26 | type: 'constructor' 27 | }, 28 | { 29 | inputs: [ 30 | { 31 | internalType: 'address', 32 | name: 'spender', 33 | type: 'address' 34 | }, 35 | { 36 | internalType: 'uint256', 37 | name: 'allowance', 38 | type: 'uint256' 39 | }, 40 | { 41 | internalType: 'uint256', 42 | name: 'needed', 43 | type: 'uint256' 44 | } 45 | ], 46 | name: 'ERC20InsufficientAllowance', 47 | type: 'error' 48 | }, 49 | { 50 | inputs: [ 51 | { 52 | internalType: 'address', 53 | name: 'sender', 54 | type: 'address' 55 | }, 56 | { 57 | internalType: 'uint256', 58 | name: 'balance', 59 | type: 'uint256' 60 | }, 61 | { 62 | internalType: 'uint256', 63 | name: 'needed', 64 | type: 'uint256' 65 | } 66 | ], 67 | name: 'ERC20InsufficientBalance', 68 | type: 'error' 69 | }, 70 | { 71 | inputs: [ 72 | { 73 | internalType: 'address', 74 | name: 'approver', 75 | type: 'address' 76 | } 77 | ], 78 | name: 'ERC20InvalidApprover', 79 | type: 'error' 80 | }, 81 | { 82 | inputs: [ 83 | { 84 | internalType: 'address', 85 | name: 'receiver', 86 | type: 'address' 87 | } 88 | ], 89 | name: 'ERC20InvalidReceiver', 90 | type: 'error' 91 | }, 92 | { 93 | inputs: [ 94 | { 95 | internalType: 'address', 96 | name: 'sender', 97 | type: 'address' 98 | } 99 | ], 100 | name: 'ERC20InvalidSender', 101 | type: 'error' 102 | }, 103 | { 104 | inputs: [ 105 | { 106 | internalType: 'address', 107 | name: 'spender', 108 | type: 'address' 109 | } 110 | ], 111 | name: 'ERC20InvalidSpender', 112 | type: 'error' 113 | }, 114 | { 115 | anonymous: false, 116 | inputs: [ 117 | { 118 | indexed: true, 119 | internalType: 'address', 120 | name: 'owner', 121 | type: 'address' 122 | }, 123 | { 124 | indexed: true, 125 | internalType: 'address', 126 | name: 'spender', 127 | type: 'address' 128 | }, 129 | { 130 | indexed: false, 131 | internalType: 'uint256', 132 | name: 'value', 133 | type: 'uint256' 134 | } 135 | ], 136 | name: 'Approval', 137 | type: 'event' 138 | }, 139 | { 140 | anonymous: false, 141 | inputs: [ 142 | { 143 | indexed: true, 144 | internalType: 'address', 145 | name: 'from', 146 | type: 'address' 147 | }, 148 | { 149 | indexed: true, 150 | internalType: 'address', 151 | name: 'to', 152 | type: 'address' 153 | }, 154 | { 155 | indexed: false, 156 | internalType: 'uint256', 157 | name: 'value', 158 | type: 'uint256' 159 | } 160 | ], 161 | name: 'Transfer', 162 | type: 'event' 163 | }, 164 | { 165 | inputs: [ 166 | { 167 | internalType: 'address', 168 | name: 'owner', 169 | type: 'address' 170 | }, 171 | { 172 | internalType: 'address', 173 | name: 'spender', 174 | type: 'address' 175 | } 176 | ], 177 | name: 'allowance', 178 | outputs: [ 179 | { 180 | internalType: 'uint256', 181 | name: '', 182 | type: 'uint256' 183 | } 184 | ], 185 | stateMutability: 'view', 186 | type: 'function' 187 | }, 188 | { 189 | inputs: [ 190 | { 191 | internalType: 'address', 192 | name: 'spender', 193 | type: 'address' 194 | }, 195 | { 196 | internalType: 'uint256', 197 | name: 'value', 198 | type: 'uint256' 199 | } 200 | ], 201 | name: 'approve', 202 | outputs: [ 203 | { 204 | internalType: 'bool', 205 | name: '', 206 | type: 'bool' 207 | } 208 | ], 209 | stateMutability: 'nonpayable', 210 | type: 'function' 211 | }, 212 | { 213 | inputs: [ 214 | { 215 | internalType: 'address', 216 | name: 'account', 217 | type: 'address' 218 | } 219 | ], 220 | name: 'balanceOf', 221 | outputs: [ 222 | { 223 | internalType: 'uint256', 224 | name: '', 225 | type: 'uint256' 226 | } 227 | ], 228 | stateMutability: 'view', 229 | type: 'function' 230 | }, 231 | { 232 | inputs: [], 233 | name: 'decimals', 234 | outputs: [ 235 | { 236 | internalType: 'uint8', 237 | name: '', 238 | type: 'uint8' 239 | } 240 | ], 241 | stateMutability: 'view', 242 | type: 'function' 243 | }, 244 | { 245 | inputs: [], 246 | name: 'name', 247 | outputs: [ 248 | { 249 | internalType: 'string', 250 | name: '', 251 | type: 'string' 252 | } 253 | ], 254 | stateMutability: 'view', 255 | type: 'function' 256 | }, 257 | { 258 | inputs: [], 259 | name: 'symbol', 260 | outputs: [ 261 | { 262 | internalType: 'string', 263 | name: '', 264 | type: 'string' 265 | } 266 | ], 267 | stateMutability: 'view', 268 | type: 'function' 269 | }, 270 | { 271 | inputs: [], 272 | name: 'totalSupply', 273 | outputs: [ 274 | { 275 | internalType: 'uint256', 276 | name: '', 277 | type: 'uint256' 278 | } 279 | ], 280 | stateMutability: 'view', 281 | type: 'function' 282 | }, 283 | { 284 | inputs: [ 285 | { 286 | internalType: 'address', 287 | name: 'to', 288 | type: 'address' 289 | }, 290 | { 291 | internalType: 'uint256', 292 | name: 'value', 293 | type: 'uint256' 294 | } 295 | ], 296 | name: 'transfer', 297 | outputs: [ 298 | { 299 | internalType: 'bool', 300 | name: '', 301 | type: 'bool' 302 | } 303 | ], 304 | stateMutability: 'nonpayable', 305 | type: 'function' 306 | }, 307 | { 308 | inputs: [ 309 | { 310 | internalType: 'address', 311 | name: 'from', 312 | type: 'address' 313 | }, 314 | { 315 | internalType: 'address', 316 | name: 'to', 317 | type: 'address' 318 | }, 319 | { 320 | internalType: 'uint256', 321 | name: 'value', 322 | type: 'uint256' 323 | } 324 | ], 325 | name: 'transferFrom', 326 | outputs: [ 327 | { 328 | internalType: 'bool', 329 | name: '', 330 | type: 'bool' 331 | } 332 | ], 333 | stateMutability: 'nonpayable', 334 | type: 'function' 335 | } 336 | ] 337 | -------------------------------------------------------------------------------- /src/abi/lockbox-abi.ts: -------------------------------------------------------------------------------- 1 | export const lockboxAbi = [ 2 | { 3 | inputs: [ 4 | { internalType: 'address', name: 'assetAddress', type: 'address' }, 5 | { internalType: 'address', name: 'lpTokenAddress', type: 'address' } 6 | ], 7 | stateMutability: 'nonpayable', 8 | type: 'constructor' 9 | }, 10 | { 11 | inputs: [{ internalType: 'address', name: 'target', type: 'address' }], 12 | name: 'AddressEmptyCode', 13 | type: 'error' 14 | }, 15 | { 16 | inputs: [{ internalType: 'address', name: 'account', type: 'address' }], 17 | name: 'AddressInsufficientBalance', 18 | type: 'error' 19 | }, 20 | { 21 | inputs: [], 22 | name: 'ERC721EnumerableForbiddenBatchMint', 23 | type: 'error' 24 | }, 25 | { 26 | inputs: [ 27 | { internalType: 'address', name: 'sender', type: 'address' }, 28 | { internalType: 'uint256', name: 'tokenId', type: 'uint256' }, 29 | { internalType: 'address', name: 'owner', type: 'address' } 30 | ], 31 | name: 'ERC721IncorrectOwner', 32 | type: 'error' 33 | }, 34 | { 35 | inputs: [ 36 | { internalType: 'address', name: 'operator', type: 'address' }, 37 | { internalType: 'uint256', name: 'tokenId', type: 'uint256' } 38 | ], 39 | name: 'ERC721InsufficientApproval', 40 | type: 'error' 41 | }, 42 | { 43 | inputs: [{ internalType: 'address', name: 'approver', type: 'address' }], 44 | name: 'ERC721InvalidApprover', 45 | type: 'error' 46 | }, 47 | { 48 | inputs: [{ internalType: 'address', name: 'operator', type: 'address' }], 49 | name: 'ERC721InvalidOperator', 50 | type: 'error' 51 | }, 52 | { 53 | inputs: [{ internalType: 'address', name: 'owner', type: 'address' }], 54 | name: 'ERC721InvalidOwner', 55 | type: 'error' 56 | }, 57 | { 58 | inputs: [{ internalType: 'address', name: 'receiver', type: 'address' }], 59 | name: 'ERC721InvalidReceiver', 60 | type: 'error' 61 | }, 62 | { 63 | inputs: [{ internalType: 'address', name: 'sender', type: 'address' }], 64 | name: 'ERC721InvalidSender', 65 | type: 'error' 66 | }, 67 | { 68 | inputs: [{ internalType: 'uint256', name: 'tokenId', type: 'uint256' }], 69 | name: 'ERC721NonexistentToken', 70 | type: 'error' 71 | }, 72 | { 73 | inputs: [ 74 | { internalType: 'address', name: 'owner', type: 'address' }, 75 | { internalType: 'uint256', name: 'index', type: 'uint256' } 76 | ], 77 | name: 'ERC721OutOfBoundsIndex', 78 | type: 'error' 79 | }, 80 | { inputs: [], name: 'EnabledAutoRelock', type: 'error' }, 81 | { inputs: [], name: 'FailedInnerCall', type: 'error' }, 82 | { inputs: [], name: 'MismatchUniswapPairToken', type: 'error' }, 83 | { inputs: [], name: 'NoLPTokenOutstanding', type: 'error' }, 84 | { inputs: [], name: 'NotOwner', type: 'error' }, 85 | { inputs: [], name: 'ReentrancyGuardReentrantCall', type: 'error' }, 86 | { 87 | inputs: [{ internalType: 'address', name: 'token', type: 'address' }], 88 | name: 'SafeERC20FailedOperation', 89 | type: 'error' 90 | }, 91 | { inputs: [], name: 'TooManyEntries', type: 'error' }, 92 | { inputs: [], name: 'UnexpiredLockup', type: 'error' }, 93 | { inputs: [], name: 'WrongDuration', type: 'error' }, 94 | { 95 | anonymous: false, 96 | inputs: [ 97 | { 98 | indexed: true, 99 | internalType: 'address', 100 | name: 'owner', 101 | type: 'address' 102 | }, 103 | { 104 | indexed: true, 105 | internalType: 'address', 106 | name: 'approved', 107 | type: 'address' 108 | }, 109 | { 110 | indexed: true, 111 | internalType: 'uint256', 112 | name: 'tokenId', 113 | type: 'uint256' 114 | } 115 | ], 116 | name: 'Approval', 117 | type: 'event' 118 | }, 119 | { 120 | anonymous: false, 121 | inputs: [ 122 | { 123 | indexed: true, 124 | internalType: 'address', 125 | name: 'owner', 126 | type: 'address' 127 | }, 128 | { 129 | indexed: true, 130 | internalType: 'address', 131 | name: 'operator', 132 | type: 'address' 133 | }, 134 | { 135 | indexed: false, 136 | internalType: 'bool', 137 | name: 'approved', 138 | type: 'bool' 139 | } 140 | ], 141 | name: 'ApprovalForAll', 142 | type: 'event' 143 | }, 144 | { 145 | anonymous: false, 146 | inputs: [ 147 | { 148 | indexed: false, 149 | internalType: 'uint256', 150 | name: 'tokenId', 151 | type: 'uint256' 152 | }, 153 | { 154 | indexed: false, 155 | internalType: 'address', 156 | name: 'addedBy', 157 | type: 'address' 158 | }, 159 | { 160 | indexed: false, 161 | internalType: 'bool', 162 | name: 'resetStartTime', 163 | type: 'bool' 164 | }, 165 | { 166 | indexed: false, 167 | internalType: 'uint256', 168 | name: 'amountAsset', 169 | type: 'uint256' 170 | }, 171 | { 172 | indexed: false, 173 | internalType: 'uint256', 174 | name: 'amountLpToken', 175 | type: 'uint256' 176 | }, 177 | { 178 | indexed: false, 179 | internalType: 'uint64', 180 | name: 'effectiveDuration', 181 | type: 'uint64' 182 | }, 183 | { 184 | indexed: false, 185 | internalType: 'uint256', 186 | name: 'lpTokenValuation', 187 | type: 'uint256' 188 | } 189 | ], 190 | name: 'AssetsAdded', 191 | type: 'event' 192 | }, 193 | { 194 | anonymous: false, 195 | inputs: [ 196 | { 197 | indexed: false, 198 | internalType: 'uint256', 199 | name: 'tokenId', 200 | type: 'uint256' 201 | }, 202 | { 203 | indexed: false, 204 | internalType: 'uint64', 205 | name: 'newDuration', 206 | type: 'uint64' 207 | }, 208 | { 209 | indexed: false, 210 | internalType: 'bool', 211 | name: 'autoRelock', 212 | type: 'bool' 213 | } 214 | ], 215 | name: 'DurationChanged', 216 | type: 'event' 217 | }, 218 | { 219 | anonymous: false, 220 | inputs: [ 221 | { 222 | indexed: false, 223 | internalType: 'uint256', 224 | name: 'tokenId', 225 | type: 'uint256' 226 | }, 227 | { 228 | indexed: false, 229 | internalType: 'string', 230 | name: 'key', 231 | type: 'string' 232 | }, 233 | { 234 | indexed: false, 235 | internalType: 'bytes', 236 | name: 'value', 237 | type: 'bytes' 238 | } 239 | ], 240 | name: 'KvUpdate', 241 | type: 'event' 242 | }, 243 | { 244 | anonymous: false, 245 | inputs: [ 246 | { 247 | indexed: false, 248 | internalType: 'uint256', 249 | name: 'tokenId', 250 | type: 'uint256' 251 | }, 252 | { 253 | indexed: false, 254 | internalType: 'address', 255 | name: 'owner', 256 | type: 'address' 257 | }, 258 | { 259 | indexed: false, 260 | internalType: 'uint256', 261 | name: 'amountAsset', 262 | type: 'uint256' 263 | }, 264 | { 265 | indexed: false, 266 | internalType: 'uint256', 267 | name: 'amountLpToken', 268 | type: 'uint256' 269 | }, 270 | { 271 | indexed: false, 272 | internalType: 'uint64', 273 | name: 'durationSeconds', 274 | type: 'uint64' 275 | }, 276 | { 277 | indexed: false, 278 | internalType: 'uint256', 279 | name: 'lpTokenValuation', 280 | type: 'uint256' 281 | }, 282 | { 283 | indexed: false, 284 | internalType: 'bool', 285 | name: 'autoRelock', 286 | type: 'bool' 287 | } 288 | ], 289 | name: 'LockupCreated', 290 | type: 'event' 291 | }, 292 | { 293 | anonymous: false, 294 | inputs: [ 295 | { 296 | indexed: true, 297 | internalType: 'address', 298 | name: 'from', 299 | type: 'address' 300 | }, 301 | { 302 | indexed: true, 303 | internalType: 'address', 304 | name: 'to', 305 | type: 'address' 306 | }, 307 | { 308 | indexed: true, 309 | internalType: 'uint256', 310 | name: 'tokenId', 311 | type: 'uint256' 312 | } 313 | ], 314 | name: 'Transfer', 315 | type: 'event' 316 | }, 317 | { 318 | anonymous: false, 319 | inputs: [ 320 | { 321 | indexed: false, 322 | internalType: 'uint256', 323 | name: 'tokenId', 324 | type: 'uint256' 325 | }, 326 | { 327 | indexed: false, 328 | internalType: 'address', 329 | name: 'owner', 330 | type: 'address' 331 | }, 332 | { 333 | indexed: false, 334 | internalType: 'uint256', 335 | name: 'amountAsset', 336 | type: 'uint256' 337 | }, 338 | { 339 | indexed: false, 340 | internalType: 'uint256', 341 | name: 'amountLpToken', 342 | type: 'uint256' 343 | } 344 | ], 345 | name: 'Unlocked', 346 | type: 'event' 347 | }, 348 | { 349 | inputs: [ 350 | { internalType: 'uint256', name: 'lockupId', type: 'uint256' }, 351 | { internalType: 'uint256', name: 'assetToAdd', type: 'uint256' }, 352 | { internalType: 'uint256', name: 'lpTokenToAdd', type: 'uint256' }, 353 | { internalType: 'bool', name: 'resetStartTime', type: 'bool' }, 354 | { 355 | components: [ 356 | { internalType: 'string', name: 'key', type: 'string' }, 357 | { internalType: 'bytes', name: 'val', type: 'bytes' } 358 | ], 359 | internalType: 'struct ILockBox.KeyVal[]', 360 | name: 'entriesToUpdate', 361 | type: 'tuple[]' 362 | } 363 | ], 364 | name: 'addAssets', 365 | outputs: [], 366 | stateMutability: 'nonpayable', 367 | type: 'function' 368 | }, 369 | { 370 | inputs: [ 371 | { internalType: 'address', name: 'to', type: 'address' }, 372 | { internalType: 'uint256', name: 'tokenId', type: 'uint256' } 373 | ], 374 | name: 'approve', 375 | outputs: [], 376 | stateMutability: 'nonpayable', 377 | type: 'function' 378 | }, 379 | { 380 | inputs: [], 381 | name: 'assetContract', 382 | outputs: [{ internalType: 'contract IERC20', name: '', type: 'address' }], 383 | stateMutability: 'view', 384 | type: 'function' 385 | }, 386 | { 387 | inputs: [{ internalType: 'address', name: 'owner', type: 'address' }], 388 | name: 'balanceOf', 389 | outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], 390 | stateMutability: 'view', 391 | type: 'function' 392 | }, 393 | { 394 | inputs: [ 395 | { internalType: 'uint256', name: 'lockupId', type: 'uint256' }, 396 | { 397 | internalType: 'uint64', 398 | name: 'newDurationSeconds', 399 | type: 'uint64' 400 | }, 401 | { internalType: 'bool', name: 'autoRelock', type: 'bool' }, 402 | { 403 | components: [ 404 | { internalType: 'string', name: 'key', type: 'string' }, 405 | { internalType: 'bytes', name: 'val', type: 'bytes' } 406 | ], 407 | internalType: 'struct ILockBox.KeyVal[]', 408 | name: 'entriesToUpdate', 409 | type: 'tuple[]' 410 | } 411 | ], 412 | name: 'changeDuration', 413 | outputs: [], 414 | stateMutability: 'nonpayable', 415 | type: 'function' 416 | }, 417 | { 418 | inputs: [], 419 | name: 'computeLpStats', 420 | outputs: [ 421 | { 422 | internalType: 'uint256', 423 | name: 'currentLpTokens', 424 | type: 'uint256' 425 | }, 426 | { internalType: 'uint256', name: 'currentAsset', type: 'uint256' }, 427 | { internalType: 'uint256', name: 'lastLpTokens', type: 'uint256' }, 428 | { internalType: 'uint256', name: 'lastAsset', type: 'uint256' }, 429 | { 430 | internalType: 'uint256', 431 | name: 'averageLpTokens', 432 | type: 'uint256' 433 | }, 434 | { internalType: 'uint256', name: 'averageAsset', type: 'uint256' }, 435 | { internalType: 'uint32', name: 'timestamp', type: 'uint32' }, 436 | { internalType: 'bool', name: 'useAverage', type: 'bool' }, 437 | { internalType: 'bool', name: 'persistUpdate', type: 'bool' } 438 | ], 439 | stateMutability: 'view', 440 | type: 'function' 441 | }, 442 | { 443 | inputs: [], 444 | name: 'computeUpdateLpStats', 445 | outputs: [ 446 | { 447 | internalType: 'uint256', 448 | name: 'totalLpTokensOutstanding', 449 | type: 'uint256' 450 | }, 451 | { 452 | internalType: 'uint256', 453 | name: 'totalAssetUnitsStaked', 454 | type: 'uint256' 455 | } 456 | ], 457 | stateMutability: 'nonpayable', 458 | type: 'function' 459 | }, 460 | { 461 | inputs: [ 462 | { internalType: 'uint256', name: 'amountAsset', type: 'uint256' }, 463 | { internalType: 'uint256', name: 'amountLpToken', type: 'uint256' }, 464 | { internalType: 'uint64', name: 'durationSeconds', type: 'uint64' }, 465 | { internalType: 'bool', name: 'autoRelock', type: 'bool' }, 466 | { 467 | components: [ 468 | { internalType: 'string', name: 'key', type: 'string' }, 469 | { internalType: 'bytes', name: 'val', type: 'bytes' } 470 | ], 471 | internalType: 'struct ILockBox.KeyVal[]', 472 | name: 'entriesToUpdate', 473 | type: 'tuple[]' 474 | } 475 | ], 476 | name: 'createLockup', 477 | outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], 478 | stateMutability: 'nonpayable', 479 | type: 'function' 480 | }, 481 | { 482 | inputs: [{ internalType: 'uint256', name: 'tokenId', type: 'uint256' }], 483 | name: 'getApproved', 484 | outputs: [{ internalType: 'address', name: '', type: 'address' }], 485 | stateMutability: 'view', 486 | type: 'function' 487 | }, 488 | { 489 | inputs: [], 490 | name: 'getCurrentLpStats', 491 | outputs: [ 492 | { 493 | internalType: 'uint256', 494 | name: 'totalLpTokensOutstanding', 495 | type: 'uint256' 496 | }, 497 | { 498 | internalType: 'uint256', 499 | name: 'totalAssetUnitsStaked', 500 | type: 'uint256' 501 | }, 502 | { internalType: 'uint32', name: 'timestamp', type: 'uint32' } 503 | ], 504 | stateMutability: 'view', 505 | type: 'function' 506 | }, 507 | { 508 | inputs: [], 509 | name: 'getLpStats', 510 | outputs: [ 511 | { internalType: 'uint256', name: 'lastLpTokens', type: 'uint256' }, 512 | { internalType: 'uint256', name: 'lastAsset', type: 'uint256' }, 513 | { 514 | internalType: 'uint256', 515 | name: 'averageLpTokens', 516 | type: 'uint256' 517 | }, 518 | { internalType: 'uint256', name: 'averageAsset', type: 'uint256' }, 519 | { internalType: 'uint32', name: 'timestamp', type: 'uint32' } 520 | ], 521 | stateMutability: 'view', 522 | type: 'function' 523 | }, 524 | { 525 | inputs: [ 526 | { internalType: 'address', name: 'owner', type: 'address' }, 527 | { internalType: 'address', name: 'operator', type: 'address' } 528 | ], 529 | name: 'isApprovedForAll', 530 | outputs: [{ internalType: 'bool', name: '', type: 'bool' }], 531 | stateMutability: 'view', 532 | type: 'function' 533 | }, 534 | { 535 | inputs: [ 536 | { internalType: 'uint256', name: '', type: 'uint256' }, 537 | { internalType: 'string', name: '', type: 'string' } 538 | ], 539 | name: 'kvStore', 540 | outputs: [{ internalType: 'bytes', name: '', type: 'bytes' }], 541 | stateMutability: 'view', 542 | type: 'function' 543 | }, 544 | { 545 | inputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], 546 | name: 'lockups', 547 | outputs: [ 548 | { internalType: 'uint256', name: 'amountAsset', type: 'uint256' }, 549 | { internalType: 'uint256', name: 'amountLpToken', type: 'uint256' }, 550 | { 551 | internalType: 'uint256', 552 | name: 'lpTokenValuation', 553 | type: 'uint256' 554 | }, 555 | { 556 | internalType: 'uint256', 557 | name: 'assetSecondsLocked', 558 | type: 'uint256' 559 | }, 560 | { 561 | internalType: 'uint256', 562 | name: 'lpSecondsLocked', 563 | type: 'uint256' 564 | }, 565 | { internalType: 'uint64', name: 'createTime', type: 'uint64' }, 566 | { internalType: 'uint64', name: 'lastDepositTime', type: 'uint64' }, 567 | { internalType: 'uint64', name: 'durationSeconds', type: 'uint64' }, 568 | { internalType: 'bool', name: 'autoRelock', type: 'bool' } 569 | ], 570 | stateMutability: 'view', 571 | type: 'function' 572 | }, 573 | { 574 | inputs: [], 575 | name: 'lpTokenContract', 576 | outputs: [{ internalType: 'contract IERC20', name: '', type: 'address' }], 577 | stateMutability: 'view', 578 | type: 'function' 579 | }, 580 | { 581 | inputs: [], 582 | name: 'name', 583 | outputs: [{ internalType: 'string', name: '', type: 'string' }], 584 | stateMutability: 'view', 585 | type: 'function' 586 | }, 587 | { 588 | inputs: [{ internalType: 'uint256', name: 'tokenId', type: 'uint256' }], 589 | name: 'ownerOf', 590 | outputs: [{ internalType: 'address', name: '', type: 'address' }], 591 | stateMutability: 'view', 592 | type: 'function' 593 | }, 594 | { 595 | inputs: [ 596 | { internalType: 'address', name: 'from', type: 'address' }, 597 | { internalType: 'address', name: 'to', type: 'address' }, 598 | { internalType: 'uint256', name: 'tokenId', type: 'uint256' } 599 | ], 600 | name: 'safeTransferFrom', 601 | outputs: [], 602 | stateMutability: 'nonpayable', 603 | type: 'function' 604 | }, 605 | { 606 | inputs: [ 607 | { internalType: 'address', name: 'from', type: 'address' }, 608 | { internalType: 'address', name: 'to', type: 'address' }, 609 | { internalType: 'uint256', name: 'tokenId', type: 'uint256' }, 610 | { internalType: 'bytes', name: 'data', type: 'bytes' } 611 | ], 612 | name: 'safeTransferFrom', 613 | outputs: [], 614 | stateMutability: 'nonpayable', 615 | type: 'function' 616 | }, 617 | { 618 | inputs: [ 619 | { internalType: 'address', name: 'operator', type: 'address' }, 620 | { internalType: 'bool', name: 'approved', type: 'bool' } 621 | ], 622 | name: 'setApprovalForAll', 623 | outputs: [], 624 | stateMutability: 'nonpayable', 625 | type: 'function' 626 | }, 627 | { 628 | inputs: [ 629 | { internalType: 'uint256', name: 'tokenId', type: 'uint256' }, 630 | { 631 | components: [ 632 | { internalType: 'string', name: 'key', type: 'string' }, 633 | { internalType: 'bytes', name: 'val', type: 'bytes' } 634 | ], 635 | internalType: 'struct ILockBox.KeyVal[]', 636 | name: 'entriesToUpdate', 637 | type: 'tuple[]' 638 | } 639 | ], 640 | name: 'setKvStore', 641 | outputs: [], 642 | stateMutability: 'nonpayable', 643 | type: 'function' 644 | }, 645 | { 646 | inputs: [{ internalType: 'bytes4', name: 'interfaceId', type: 'bytes4' }], 647 | name: 'supportsInterface', 648 | outputs: [{ internalType: 'bool', name: '', type: 'bool' }], 649 | stateMutability: 'view', 650 | type: 'function' 651 | }, 652 | { 653 | inputs: [], 654 | name: 'symbol', 655 | outputs: [{ internalType: 'string', name: '', type: 'string' }], 656 | stateMutability: 'view', 657 | type: 'function' 658 | }, 659 | { 660 | inputs: [{ internalType: 'uint256', name: 'index', type: 'uint256' }], 661 | name: 'tokenByIndex', 662 | outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], 663 | stateMutability: 'view', 664 | type: 'function' 665 | }, 666 | { 667 | inputs: [ 668 | { internalType: 'address', name: 'owner', type: 'address' }, 669 | { internalType: 'uint256', name: 'index', type: 'uint256' } 670 | ], 671 | name: 'tokenOfOwnerByIndex', 672 | outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], 673 | stateMutability: 'view', 674 | type: 'function' 675 | }, 676 | { 677 | inputs: [{ internalType: 'uint256', name: 'tokenId', type: 'uint256' }], 678 | name: 'tokenURI', 679 | outputs: [{ internalType: 'string', name: '', type: 'string' }], 680 | stateMutability: 'view', 681 | type: 'function' 682 | }, 683 | { 684 | inputs: [], 685 | name: 'totalAssetLocked', 686 | outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], 687 | stateMutability: 'view', 688 | type: 'function' 689 | }, 690 | { 691 | inputs: [], 692 | name: 'totalLpTokenLocked', 693 | outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], 694 | stateMutability: 'view', 695 | type: 'function' 696 | }, 697 | { 698 | inputs: [], 699 | name: 'totalSupply', 700 | outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], 701 | stateMutability: 'view', 702 | type: 'function' 703 | }, 704 | { 705 | inputs: [ 706 | { internalType: 'address', name: 'from', type: 'address' }, 707 | { internalType: 'address', name: 'to', type: 'address' }, 708 | { internalType: 'uint256', name: 'tokenId', type: 'uint256' } 709 | ], 710 | name: 'transferFrom', 711 | outputs: [], 712 | stateMutability: 'nonpayable', 713 | type: 'function' 714 | }, 715 | { 716 | inputs: [{ internalType: 'uint256', name: 'lockupId', type: 'uint256' }], 717 | name: 'unlock', 718 | outputs: [], 719 | stateMutability: 'nonpayable', 720 | type: 'function' 721 | } 722 | ] 723 | -------------------------------------------------------------------------------- /src/abi/multipay-abi.ts: -------------------------------------------------------------------------------- 1 | export const multiPlayAbi = [ 2 | { 3 | inputs: [ 4 | { 5 | internalType: 'contract ILockBox', 6 | name: 'lockboxContract', 7 | type: 'address' 8 | } 9 | ], 10 | name: 'createEmptyStake', 11 | outputs: [], 12 | stateMutability: 'nonpayable', 13 | type: 'function' 14 | }, 15 | { 16 | inputs: [{ internalType: 'address', name: 'user', type: 'address' }], 17 | name: 'getStake', 18 | outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], 19 | stateMutability: 'view', 20 | type: 'function' 21 | }, 22 | { 23 | inputs: [ 24 | { 25 | internalType: 'contract IYieldVault', 26 | name: 'yv', 27 | type: 'address' 28 | }, 29 | { internalType: 'uint256[]', name: 'tokenIds', type: 'uint256[]' } 30 | ], 31 | name: 'lpComputeYields', 32 | outputs: [ 33 | { internalType: 'uint256[]', name: 'available', type: 'uint256[]' } 34 | ], 35 | stateMutability: 'view', 36 | type: 'function' 37 | }, 38 | { 39 | inputs: [ 40 | { internalType: 'contract IERC20', name: 'token', type: 'address' }, 41 | { internalType: 'address', name: 'from', type: 'address' }, 42 | { 43 | components: [ 44 | { internalType: 'address', name: 'recipient', type: 'address' }, 45 | { internalType: 'uint256', name: 'amount', type: 'uint256' } 46 | ], 47 | internalType: 'struct Multipay.Payout[]', 48 | name: 'payouts', 49 | type: 'tuple[]' 50 | } 51 | ], 52 | name: 'multipay', 53 | outputs: [], 54 | stateMutability: 'nonpayable', 55 | type: 'function' 56 | }, 57 | { 58 | inputs: [ 59 | { 60 | internalType: 'contract IAssign', 61 | name: 'assignContract', 62 | type: 'address' 63 | }, 64 | { 65 | components: [ 66 | { 67 | internalType: 'enum IInfra.UnitType', 68 | name: 't', 69 | type: 'uint8' 70 | }, 71 | { 72 | internalType: 'uint64', 73 | name: 'parentDomain', 74 | type: 'uint64' 75 | }, 76 | { internalType: 'string', name: 'name', type: 'string' }, 77 | { 78 | internalType: 'uint256', 79 | name: 'yieldCredits', 80 | type: 'uint256' 81 | }, 82 | { internalType: 'address', name: 'to', type: 'address' } 83 | ], 84 | internalType: 'struct Multipay.Registration[]', 85 | name: 'regs', 86 | type: 'tuple[]' 87 | } 88 | ], 89 | name: 'multireg', 90 | outputs: [], 91 | stateMutability: 'nonpayable', 92 | type: 'function' 93 | }, 94 | { 95 | inputs: [ 96 | { 97 | internalType: 'contract IERC721', 98 | name: 'nftContract', 99 | type: 'address' 100 | }, 101 | { internalType: 'address', name: 'from', type: 'address' }, 102 | { 103 | components: [ 104 | { internalType: 'address', name: 'recipient', type: 'address' }, 105 | { internalType: 'uint256', name: 'nftId', type: 'uint256' } 106 | ], 107 | internalType: 'struct Multipay.NftSend[]', 108 | name: 'sends', 109 | type: 'tuple[]' 110 | } 111 | ], 112 | name: 'nftSend', 113 | outputs: [], 114 | stateMutability: 'nonpayable', 115 | type: 'function' 116 | }, 117 | { 118 | inputs: [ 119 | { internalType: 'address', name: '', type: 'address' }, 120 | { internalType: 'address', name: '', type: 'address' }, 121 | { internalType: 'uint256', name: '', type: 'uint256' }, 122 | { internalType: 'bytes', name: '', type: 'bytes' } 123 | ], 124 | name: 'onERC721Received', 125 | outputs: [{ internalType: 'bytes4', name: '', type: 'bytes4' }], 126 | stateMutability: 'nonpayable', 127 | type: 'function' 128 | }, 129 | { 130 | inputs: [ 131 | { internalType: 'contract IERC20', name: 'token', type: 'address' }, 132 | { 133 | internalType: 'contract ILockBox', 134 | name: 'lockboxContract', 135 | type: 'address' 136 | }, 137 | { internalType: 'contract IPNS', name: 'ipns', type: 'address' }, 138 | { 139 | internalType: 'uint256', 140 | name: 'maxStakeAmount', 141 | type: 'uint256' 142 | }, 143 | { internalType: 'bytes', name: 'name', type: 'bytes' }, 144 | { internalType: 'bytes', name: 'records', type: 'bytes' } 145 | ], 146 | name: 'stakeDomain', 147 | outputs: [], 148 | stateMutability: 'nonpayable', 149 | type: 'function' 150 | } 151 | ] 152 | -------------------------------------------------------------------------------- /src/abi/pns-abi.ts: -------------------------------------------------------------------------------- 1 | export const pnsAbi = [ 2 | { 3 | inputs: [ 4 | { 5 | internalType: 'contract ILockBox', 6 | name: '_lockboxContract', 7 | type: 'address' 8 | }, 9 | { internalType: 'bool', name: '_whitelistActive', type: 'bool' }, 10 | { internalType: 'address', name: 'pricer', type: 'address' } 11 | ], 12 | stateMutability: 'nonpayable', 13 | type: 'constructor' 14 | }, 15 | { 16 | inputs: [{ internalType: 'address', name: 'owner', type: 'address' }], 17 | name: 'OwnableInvalidOwner', 18 | type: 'error' 19 | }, 20 | { 21 | inputs: [{ internalType: 'address', name: 'account', type: 'address' }], 22 | name: 'OwnableUnauthorizedAccount', 23 | type: 'error' 24 | }, 25 | { 26 | anonymous: false, 27 | inputs: [ 28 | { 29 | indexed: false, 30 | internalType: 'address', 31 | name: 'sender', 32 | type: 'address' 33 | }, 34 | { 35 | indexed: false, 36 | internalType: 'uint64', 37 | name: 'id', 38 | type: 'uint64' 39 | }, 40 | { 41 | indexed: false, 42 | internalType: 'bool', 43 | name: 'isBlacklisted', 44 | type: 'bool' 45 | } 46 | ], 47 | name: 'Blacklist', 48 | type: 'event' 49 | }, 50 | { 51 | anonymous: false, 52 | inputs: [ 53 | { 54 | indexed: false, 55 | internalType: 'address', 56 | name: 'sender', 57 | type: 'address' 58 | }, 59 | { 60 | indexed: false, 61 | internalType: 'uint64', 62 | name: 'id', 63 | type: 'uint64' 64 | }, 65 | { 66 | indexed: false, 67 | internalType: 'uint64', 68 | name: 'parentId', 69 | type: 'uint64' 70 | }, 71 | { 72 | indexed: false, 73 | internalType: 'bytes', 74 | name: 'name', 75 | type: 'bytes' 76 | }, 77 | { 78 | indexed: false, 79 | internalType: 'bytes', 80 | name: 'records', 81 | type: 'bytes' 82 | } 83 | ], 84 | name: 'CreateSubdomain', 85 | type: 'event' 86 | }, 87 | { 88 | anonymous: false, 89 | inputs: [ 90 | { 91 | indexed: false, 92 | internalType: 'address', 93 | name: 'sender', 94 | type: 'address' 95 | }, 96 | { 97 | indexed: false, 98 | internalType: 'uint64', 99 | name: 'id', 100 | type: 'uint64' 101 | } 102 | ], 103 | name: 'Destroy', 104 | type: 'event' 105 | }, 106 | { 107 | anonymous: false, 108 | inputs: [ 109 | { 110 | indexed: false, 111 | internalType: 'address', 112 | name: 'sender', 113 | type: 'address' 114 | }, 115 | { 116 | indexed: false, 117 | internalType: 'bytes32', 118 | name: 'nameHash', 119 | type: 'bytes32' 120 | }, 121 | { 122 | indexed: false, 123 | internalType: 'uint64', 124 | name: 'lockupId', 125 | type: 'uint64' 126 | } 127 | ], 128 | name: 'DestroyPrereg', 129 | type: 'event' 130 | }, 131 | { 132 | anonymous: false, 133 | inputs: [ 134 | { 135 | indexed: true, 136 | internalType: 'address', 137 | name: 'previousOwner', 138 | type: 'address' 139 | }, 140 | { 141 | indexed: true, 142 | internalType: 'address', 143 | name: 'newOwner', 144 | type: 'address' 145 | } 146 | ], 147 | name: 'OwnershipTransferred', 148 | type: 'event' 149 | }, 150 | { 151 | anonymous: false, 152 | inputs: [ 153 | { 154 | indexed: false, 155 | internalType: 'address', 156 | name: 'sender', 157 | type: 'address' 158 | }, 159 | { 160 | indexed: false, 161 | internalType: 'bytes32', 162 | name: 'nameHash', 163 | type: 'bytes32' 164 | }, 165 | { 166 | indexed: false, 167 | internalType: 'uint64', 168 | name: 'lockupId', 169 | type: 'uint64' 170 | } 171 | ], 172 | name: 'Preregister', 173 | type: 'event' 174 | }, 175 | { 176 | anonymous: false, 177 | inputs: [ 178 | { 179 | indexed: false, 180 | internalType: 'address', 181 | name: 'sender', 182 | type: 'address' 183 | }, 184 | { 185 | indexed: false, 186 | internalType: 'uint64', 187 | name: 'id', 188 | type: 'uint64' 189 | }, 190 | { 191 | indexed: false, 192 | internalType: 'uint64', 193 | name: 'lockupId', 194 | type: 'uint64' 195 | }, 196 | { 197 | indexed: false, 198 | internalType: 'bytes', 199 | name: 'name', 200 | type: 'bytes' 201 | }, 202 | { 203 | indexed: false, 204 | internalType: 'bytes', 205 | name: 'records', 206 | type: 'bytes' 207 | } 208 | ], 209 | name: 'Register', 210 | type: 'event' 211 | }, 212 | { 213 | anonymous: false, 214 | inputs: [ 215 | { 216 | indexed: false, 217 | internalType: 'address', 218 | name: 'sender', 219 | type: 'address' 220 | }, 221 | { 222 | indexed: false, 223 | internalType: 'uint64', 224 | name: 'id', 225 | type: 'uint64' 226 | }, 227 | { 228 | indexed: false, 229 | internalType: 'uint64', 230 | name: 'oldLockupId', 231 | type: 'uint64' 232 | }, 233 | { 234 | indexed: false, 235 | internalType: 'uint64', 236 | name: 'newLockupId', 237 | type: 'uint64' 238 | }, 239 | { 240 | indexed: false, 241 | internalType: 'bytes', 242 | name: 'records', 243 | type: 'bytes' 244 | } 245 | ], 246 | name: 'Takeover', 247 | type: 'event' 248 | }, 249 | { 250 | anonymous: false, 251 | inputs: [ 252 | { 253 | indexed: false, 254 | internalType: 'address', 255 | name: 'sender', 256 | type: 'address' 257 | }, 258 | { 259 | indexed: false, 260 | internalType: 'uint64', 261 | name: 'id', 262 | type: 'uint64' 263 | }, 264 | { 265 | indexed: false, 266 | internalType: 'bytes', 267 | name: 'records', 268 | type: 'bytes' 269 | } 270 | ], 271 | name: 'UpdateRecords', 272 | type: 'event' 273 | }, 274 | { 275 | inputs: [], 276 | name: 'PREREG_LIFETIME_SECONDS', 277 | outputs: [{ internalType: 'uint64', name: '', type: 'uint64' }], 278 | stateMutability: 'view', 279 | type: 'function' 280 | }, 281 | { 282 | inputs: [ 283 | { internalType: 'uint64', name: 'id', type: 'uint64' }, 284 | { internalType: 'address', name: 'who', type: 'address' } 285 | ], 286 | name: 'authorizedFor', 287 | outputs: [{ internalType: 'bool', name: '', type: 'bool' }], 288 | stateMutability: 'view', 289 | type: 'function' 290 | }, 291 | { 292 | inputs: [ 293 | { internalType: 'uint64', name: 'lockupId', type: 'uint64' }, 294 | { internalType: 'bytes', name: 'name', type: 'bytes' }, 295 | { internalType: 'bytes', name: 'records', type: 'bytes' } 296 | ], 297 | name: 'computePreregHash', 298 | outputs: [{ internalType: 'bytes32', name: '', type: 'bytes32' }], 299 | stateMutability: 'pure', 300 | type: 'function' 301 | }, 302 | { 303 | inputs: [ 304 | { internalType: 'uint64', name: 'parentId', type: 'uint64' }, 305 | { internalType: 'bytes', name: 'subdomainLabel', type: 'bytes' }, 306 | { internalType: 'bytes', name: 'records', type: 'bytes' } 307 | ], 308 | name: 'createSubdomain', 309 | outputs: [], 310 | stateMutability: 'nonpayable', 311 | type: 'function' 312 | }, 313 | { 314 | inputs: [], 315 | name: 'currentMinLockup', 316 | outputs: [{ internalType: 'uint256', name: 'price', type: 'uint256' }], 317 | stateMutability: 'view', 318 | type: 'function' 319 | }, 320 | { 321 | inputs: [{ internalType: 'uint64', name: 'id', type: 'uint64' }], 322 | name: 'destroy', 323 | outputs: [], 324 | stateMutability: 'nonpayable', 325 | type: 'function' 326 | }, 327 | { 328 | inputs: [ 329 | { internalType: 'bytes32', name: 'nameHash', type: 'bytes32' }, 330 | { internalType: 'uint64', name: 'lockupId', type: 'uint64' } 331 | ], 332 | name: 'destroyPrereg', 333 | outputs: [], 334 | stateMutability: 'nonpayable', 335 | type: 'function' 336 | }, 337 | { 338 | inputs: [{ internalType: 'uint64', name: 'last', type: 'uint64' }], 339 | name: 'domainIdFreeList', 340 | outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], 341 | stateMutability: 'view', 342 | type: 'function' 343 | }, 344 | { 345 | inputs: [], 346 | name: 'getAdmin', 347 | outputs: [{ internalType: 'address', name: '', type: 'address' }], 348 | stateMutability: 'view', 349 | type: 'function' 350 | }, 351 | { 352 | inputs: [{ internalType: 'uint64', name: 'id', type: 'uint64' }], 353 | name: 'getDomain', 354 | outputs: [ 355 | { internalType: 'uint64', name: 'owner', type: 'uint64' }, 356 | { internalType: 'uint8', name: 'subdomains', type: 'uint8' }, 357 | { internalType: 'bool', name: 'blacklisted', type: 'bool' }, 358 | { internalType: 'bytes', name: 'name', type: 'bytes' }, 359 | { internalType: 'bytes', name: 'records', type: 'bytes' } 360 | ], 361 | stateMutability: 'view', 362 | type: 'function' 363 | }, 364 | { 365 | inputs: [{ internalType: 'bytes', name: 'fqdn', type: 'bytes' }], 366 | name: 'getDomainIdByFQDN', 367 | outputs: [{ internalType: 'uint64', name: '', type: 'uint64' }], 368 | stateMutability: 'view', 369 | type: 'function' 370 | }, 371 | { 372 | inputs: [{ internalType: 'uint64', name: 'lockupId', type: 'uint64' }], 373 | name: 'getDomainIdByLockupId', 374 | outputs: [{ internalType: 'uint64', name: '', type: 'uint64' }], 375 | stateMutability: 'view', 376 | type: 'function' 377 | }, 378 | { 379 | inputs: [{ internalType: 'uint64[]', name: 'ids', type: 'uint64[]' }], 380 | name: 'getDomains', 381 | outputs: [ 382 | { 383 | components: [ 384 | { internalType: 'uint64', name: 'owner', type: 'uint64' }, 385 | { internalType: 'uint8', name: 'subdomains', type: 'uint8' }, 386 | { internalType: 'bool', name: 'blacklisted', type: 'bool' }, 387 | { internalType: 'bytes', name: 'name', type: 'bytes' }, 388 | { internalType: 'bytes', name: 'records', type: 'bytes' } 389 | ], 390 | internalType: 'struct IPNS.Domain[]', 391 | name: 'out', 392 | type: 'tuple[]' 393 | } 394 | ], 395 | stateMutability: 'view', 396 | type: 'function' 397 | }, 398 | { 399 | inputs: [{ internalType: 'uint256', name: 'lockupId', type: 'uint256' }], 400 | name: 'getLockupValuation', 401 | outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], 402 | stateMutability: 'view', 403 | type: 'function' 404 | }, 405 | { 406 | inputs: [{ internalType: 'bytes32', name: 'nameHash', type: 'bytes32' }], 407 | name: 'getPrereg', 408 | outputs: [ 409 | { internalType: 'uint64', name: 'timestamp', type: 'uint64' }, 410 | { internalType: 'uint64', name: 'lockupId', type: 'uint64' } 411 | ], 412 | stateMutability: 'view', 413 | type: 'function' 414 | }, 415 | { 416 | inputs: [], 417 | name: 'getPricingInfo', 418 | outputs: [ 419 | { internalType: 'address', name: 'pricer', type: 'address' }, 420 | { internalType: 'uint64', name: 'lastRegTime', type: 'uint64' }, 421 | { internalType: 'uint256', name: 'lastRegPrice', type: 'uint256' } 422 | ], 423 | stateMutability: 'view', 424 | type: 'function' 425 | }, 426 | { 427 | inputs: [{ internalType: 'address', name: 'addr', type: 'address' }], 428 | name: 'isAddressWhitelisted', 429 | outputs: [{ internalType: 'bool', name: '', type: 'bool' }], 430 | stateMutability: 'view', 431 | type: 'function' 432 | }, 433 | { 434 | inputs: [], 435 | name: 'isRegistrationWhitelistActive', 436 | outputs: [{ internalType: 'bool', name: '', type: 'bool' }], 437 | stateMutability: 'view', 438 | type: 'function' 439 | }, 440 | { 441 | inputs: [], 442 | name: 'lockboxContract', 443 | outputs: [{ internalType: 'contract ILockBox', name: '', type: 'address' }], 444 | stateMutability: 'view', 445 | type: 'function' 446 | }, 447 | { 448 | inputs: [], 449 | name: 'nextDomainId', 450 | outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], 451 | stateMutability: 'view', 452 | type: 'function' 453 | }, 454 | { 455 | inputs: [], 456 | name: 'owner', 457 | outputs: [{ internalType: 'address', name: '', type: 'address' }], 458 | stateMutability: 'view', 459 | type: 'function' 460 | }, 461 | { 462 | inputs: [{ internalType: 'uint64', name: 'id', type: 'uint64' }], 463 | name: 'ownerLockup', 464 | outputs: [{ internalType: 'uint64', name: '', type: 'uint64' }], 465 | stateMutability: 'view', 466 | type: 'function' 467 | }, 468 | { 469 | inputs: [ 470 | { internalType: 'bytes32', name: 'nameHash', type: 'bytes32' }, 471 | { internalType: 'uint64', name: 'lockupId', type: 'uint64' } 472 | ], 473 | name: 'preregister', 474 | outputs: [], 475 | stateMutability: 'nonpayable', 476 | type: 'function' 477 | }, 478 | { 479 | inputs: [ 480 | { internalType: 'uint64', name: 'lockupId', type: 'uint64' }, 481 | { internalType: 'bytes', name: 'name', type: 'bytes' }, 482 | { internalType: 'bytes', name: 'records', type: 'bytes' } 483 | ], 484 | name: 'register', 485 | outputs: [], 486 | stateMutability: 'nonpayable', 487 | type: 'function' 488 | }, 489 | { 490 | inputs: [], 491 | name: 'renounceOwnership', 492 | outputs: [], 493 | stateMutability: 'nonpayable', 494 | type: 'function' 495 | }, 496 | { 497 | inputs: [{ internalType: 'address', name: 'admin', type: 'address' }], 498 | name: 'setAdmin', 499 | outputs: [], 500 | stateMutability: 'nonpayable', 501 | type: 'function' 502 | }, 503 | { 504 | inputs: [ 505 | { internalType: 'uint64', name: 'id', type: 'uint64' }, 506 | { internalType: 'bool', name: 'blacklisted', type: 'bool' } 507 | ], 508 | name: 'setDomainBlacklisted', 509 | outputs: [], 510 | stateMutability: 'nonpayable', 511 | type: 'function' 512 | }, 513 | { 514 | inputs: [{ internalType: 'address', name: 'lbox', type: 'address' }], 515 | name: 'setLockbox', 516 | outputs: [], 517 | stateMutability: 'nonpayable', 518 | type: 'function' 519 | }, 520 | { 521 | inputs: [{ internalType: 'address', name: 'pricer', type: 'address' }], 522 | name: 'setPricer', 523 | outputs: [], 524 | stateMutability: 'nonpayable', 525 | type: 'function' 526 | }, 527 | { 528 | inputs: [ 529 | { internalType: 'uint64', name: 'id', type: 'uint64' }, 530 | { internalType: 'uint64', name: 'newLockupId', type: 'uint64' }, 531 | { internalType: 'bytes', name: 'records', type: 'bytes' } 532 | ], 533 | name: 'takeover', 534 | outputs: [], 535 | stateMutability: 'nonpayable', 536 | type: 'function' 537 | }, 538 | { 539 | inputs: [{ internalType: 'address', name: 'newOwner', type: 'address' }], 540 | name: 'transferOwnership', 541 | outputs: [], 542 | stateMutability: 'nonpayable', 543 | type: 'function' 544 | }, 545 | { 546 | inputs: [ 547 | { 548 | components: [ 549 | { internalType: 'uint64', name: 'parentId', type: 'uint64' }, 550 | { 551 | internalType: 'bytes', 552 | name: 'subdomainLabel', 553 | type: 'bytes' 554 | }, 555 | { internalType: 'bytes', name: 'records', type: 'bytes' } 556 | ], 557 | internalType: 'struct IPNS.NewSubdomain[]', 558 | name: 'newSubdomains', 559 | type: 'tuple[]' 560 | }, 561 | { 562 | components: [ 563 | { internalType: 'uint64', name: 'id', type: 'uint64' }, 564 | { internalType: 'bytes', name: 'records', type: 'bytes' } 565 | ], 566 | internalType: 'struct IPNS.RecordUpdate[]', 567 | name: 'recordUpdates', 568 | type: 'tuple[]' 569 | }, 570 | { 571 | internalType: 'uint64[]', 572 | name: 'destroyDomains', 573 | type: 'uint64[]' 574 | } 575 | ], 576 | name: 'updateMultiple', 577 | outputs: [], 578 | stateMutability: 'nonpayable', 579 | type: 'function' 580 | }, 581 | { 582 | inputs: [ 583 | { internalType: 'uint64', name: 'id', type: 'uint64' }, 584 | { internalType: 'bytes', name: 'records', type: 'bytes' } 585 | ], 586 | name: 'updateRecords', 587 | outputs: [], 588 | stateMutability: 'nonpayable', 589 | type: 'function' 590 | }, 591 | { 592 | inputs: [ 593 | { 594 | components: [ 595 | { internalType: 'address', name: 'addr', type: 'address' }, 596 | { internalType: 'bool', name: 'allowed', type: 'bool' } 597 | ], 598 | internalType: 'struct IPNS.AllowedAddress[]', 599 | name: 'allowed', 600 | type: 'tuple[]' 601 | }, 602 | { internalType: 'bool', name: 'activate', type: 'bool' } 603 | ], 604 | name: 'updateRegistrationWhitelist', 605 | outputs: [], 606 | stateMutability: 'nonpayable', 607 | type: 'function' 608 | } 609 | ] 610 | -------------------------------------------------------------------------------- /src/assets/bitcoin.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/ethereum.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/assets/images/arrow-blue.svg: -------------------------------------------------------------------------------- 1 | 8 | 12 | -------------------------------------------------------------------------------- /src/assets/images/arrow-white.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/assets/images/bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Upwork-Job32/web3_domain.frontend/db7a9d1d92e26b5359d0c0304405ab2952cc95f8/src/assets/images/bg.jpg -------------------------------------------------------------------------------- /src/assets/images/bg1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Upwork-Job32/web3_domain.frontend/db7a9d1d92e26b5359d0c0304405ab2952cc95f8/src/assets/images/bg1.jpg -------------------------------------------------------------------------------- /src/assets/images/bitcoin.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/images/check.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/images/check_circle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Upwork-Job32/web3_domain.frontend/db7a9d1d92e26b5359d0c0304405ab2952cc95f8/src/assets/images/check_circle.png -------------------------------------------------------------------------------- /src/assets/images/circle-1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/assets/images/complete.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Upwork-Job32/web3_domain.frontend/db7a9d1d92e26b5359d0c0304405ab2952cc95f8/src/assets/images/complete.jpg -------------------------------------------------------------------------------- /src/assets/images/connected.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/images/ethereum.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/assets/images/failed.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Upwork-Job32/web3_domain.frontend/db7a9d1d92e26b5359d0c0304405ab2952cc95f8/src/assets/images/failed.jpg -------------------------------------------------------------------------------- /src/assets/images/help.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/images/inputBlock1.svg: -------------------------------------------------------------------------------- 1 | image.png -------------------------------------------------------------------------------- /src/assets/images/inputCheck.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/assets/images/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/assets/images/solana.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/assets/images/visa.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/assets/solana.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/assets/visa.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/components/RegistrationForm.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react' 2 | import { useWeb3Modal } from '@web3modal/wagmi/react' 3 | import { useAccount } from 'wagmi' 4 | import circleCheck from '../assets/images/check_circle.png' 5 | import circle1 from '../assets/images/circle-1.svg' 6 | 7 | type FormData = { 8 | name: string 9 | phone: string 10 | email: string 11 | domainName: string 12 | custody: 'self' | 'hosted' 13 | walletAddress?: string 14 | } 15 | 16 | interface RegistrationFormProps { 17 | onSubmit: (data: FormData) => void 18 | initialData: Omit & { walletAddress?: string } 19 | className?: string 20 | } 21 | 22 | const RegistrationForm: React.FC = ({ 23 | onSubmit, 24 | initialData, 25 | className = '' 26 | }) => { 27 | const [formData, setFormData] = useState(initialData) 28 | const { open } = useWeb3Modal() 29 | const { address, isConnected } = useAccount() 30 | 31 | useEffect(() => { 32 | if (isConnected && address) { 33 | setFormData((prevData) => ({ 34 | ...prevData, 35 | walletAddress: address 36 | })) 37 | } else { 38 | setFormData((prevData) => ({ 39 | ...prevData, 40 | walletAddress: undefined 41 | })) 42 | } 43 | }, [address, isConnected]) 44 | 45 | const handleSubmit = (e: React.FormEvent) => { 46 | e.preventDefault() 47 | onSubmit(formData) 48 | } 49 | 50 | return ( 51 |
52 |
53 | setFormData({ ...formData, name: e.target.value })} 57 | placeholder="Name" 58 | className="w-full h-[56px] px-4 pl-6 rounded-full border border-[#E5E7EB] focus:border-[#2D80FF] focus:ring-1 focus:ring-[#2D80FF] outline-none text-[#111827]" 59 | required 60 | /> 61 |
62 | 63 |
64 | {formData.phone && ( 65 |

66 | + 67 |

68 | )} 69 | { 73 | const value = e.target.value 74 | if (value.length > 15) { 75 | return 76 | } 77 | if ( 78 | value[value.length - 1] < '0' || 79 | value[value.length - 1] > '9' 80 | ) { 81 | return 82 | } 83 | setFormData({ ...formData, phone: value }) 84 | }} 85 | placeholder="Phone Number" 86 | className="w-full h-[56px] px-4 pl-6 rounded-full border border-[#E5E7EB] focus:border-[#2D80FF] focus:ring-1 focus:ring-[#2D80FF] outline-none text-[#111827]" 87 | required 88 | /> 89 |
90 | 91 |
92 | setFormData({ ...formData, email: e.target.value })} 96 | placeholder="Email" 97 | className="w-full h-[56px] px-4 pl-6 rounded-full border border-[#E5E7EB] focus:border-[#2D80FF] focus:ring-1 focus:ring-[#2D80FF] outline-none text-[#111827]" 98 | required 99 | /> 100 |
101 | 102 |
103 | 109 |
110 | check 111 |
112 |
113 | 114 |
115 | Self custody 116 | 131 |
132 | 133 | {formData.custody === 'self' ? ( 134 | 142 | ) : ( 143 |
144 | )} 145 | 146 | 152 |
153 | ) 154 | } 155 | 156 | export default RegistrationForm 157 | -------------------------------------------------------------------------------- /src/components/common/Button.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | interface ButtonProps extends React.ButtonHTMLAttributes { 4 | variant?: 'primary' | 'secondary' | 'outline' 5 | size?: 'sm' | 'md' | 'lg' 6 | fullWidth?: boolean 7 | children: React.ReactNode 8 | } 9 | 10 | const Button: React.FC = ({ 11 | variant = 'primary', 12 | size = 'md', 13 | fullWidth = false, 14 | children, 15 | className = '', 16 | ...props 17 | }) => { 18 | const baseStyles = 'rounded-full transition-all duration-300 font-normal' 19 | 20 | const variantStyles = { 21 | primary: 'bg-[#0D4AE7] text-white shadow-[0_0_20px_rgba(45,128,255,0.15)]', 22 | secondary: 23 | 'text-white bg-transparent hover:border-[rgba(255,255,255,0.25)] border-2 border-white backdrop-blur-[32px]', 24 | outline: 'border-2 border-white text-white hover:bg-white/10' 25 | } 26 | 27 | const sizeStyles = { 28 | sm: 'text-sm py-2 px-4', 29 | md: 'text-base py-[10px] px-[40px]', 30 | lg: 'text-lg py-3 px-6' 31 | } 32 | 33 | const widthStyle = fullWidth ? 'w-full' : 'w-auto' 34 | 35 | return ( 36 | 42 | ) 43 | } 44 | 45 | export default Button 46 | -------------------------------------------------------------------------------- /src/components/common/FormInput.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | interface FormInputProps { 4 | value: string 5 | onChange: (value: string) => void 6 | placeholder: string 7 | type?: string 8 | } 9 | 10 | const FormInput: React.FC = ({ 11 | value, 12 | onChange, 13 | placeholder, 14 | type = 'text' 15 | }) => { 16 | return ( 17 | onChange(e.target.value)} 21 | placeholder={placeholder} 22 | className="w-full bg-white rounded-full px-6 py-4 text-black placeholder:text-gray-400 mb-[10px]" 23 | /> 24 | ) 25 | } 26 | 27 | export default FormInput 28 | -------------------------------------------------------------------------------- /src/components/common/LogoHeader.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { useNavigate } from 'react-router-dom' 3 | import logo from '../../assets/images/logo.svg' 4 | 5 | const LogoHeader: React.FC = () => { 6 | const navigate = useNavigate() 7 | 8 | return ( 9 |
10 | PKT Logo navigate('/')} 15 | /> 16 |
17 | ) 18 | } 19 | 20 | export default LogoHeader 21 | -------------------------------------------------------------------------------- /src/components/common/SearchInput.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Button from './Button' 3 | 4 | interface SearchInputProps { 5 | value: string 6 | onChange: (value: string) => void 7 | onSubmit: (e: React.FormEvent) => void 8 | placeholder?: string 9 | className?: string 10 | } 11 | 12 | const SearchInput: React.FC = ({ 13 | value, 14 | onChange, 15 | onSubmit, 16 | placeholder = 'Search...', 17 | className = '' 18 | }) => { 19 | return ( 20 |
23 | onChange(e.target.value)} 27 | placeholder={placeholder} 28 | className="w-full h-full bg-transparent text-black text-sm sm:text-base placeholder:text-white/40 pl-4 sm:pl-6 md:pl-[24px] focus:outline-none rounded-full" 29 | /> 30 | 37 |
38 | ) 39 | } 40 | 41 | export default SearchInput 42 | -------------------------------------------------------------------------------- /src/components/common/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Button } from './Button' 2 | export { default as FormInput } from './FormInput' 3 | export { default as SearchInput } from './SearchInput' 4 | export { default as LogoHeader } from './LogoHeader' 5 | -------------------------------------------------------------------------------- /src/components/index.ts: -------------------------------------------------------------------------------- 1 | export * from './common' 2 | export * from './layout' 3 | 4 | export { default as RegistrationForm } from './RegistrationForm' 5 | -------------------------------------------------------------------------------- /src/components/layout/MainLayout.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import bgImage from '../../assets/images/bg.jpg' 3 | import LogoHeader from '../common/LogoHeader' 4 | 5 | interface MainLayoutProps { 6 | children: React.ReactNode 7 | } 8 | 9 | const MainLayout: React.FC = ({ children }) => { 10 | return ( 11 |
12 |
16 | 17 | 18 | 19 |
{children}
20 |
21 | ) 22 | } 23 | 24 | export default MainLayout 25 | -------------------------------------------------------------------------------- /src/components/layout/index.ts: -------------------------------------------------------------------------------- 1 | export { default as MainLayout } from './MainLayout' 2 | -------------------------------------------------------------------------------- /src/config/web3modal.tsx: -------------------------------------------------------------------------------- 1 | import { createWeb3Modal } from '@web3modal/wagmi/react' 2 | import { defaultWagmiConfig } from '@web3modal/base/adapters/evm/wagmi' 3 | import { WagmiProvider } from 'wagmi' 4 | import { base } from 'viem/chains' 5 | import React from 'react' 6 | 7 | const projectId = '5932391dbb0b923a14c367f7c2970edd' 8 | 9 | const metadata = { 10 | name: 'Web3 Domain Registration', 11 | description: 'Register your Web3 Domain', 12 | url: 'https://web3modal.com', 13 | icons: ['https://avatars.githubusercontent.com/u/37784886'] 14 | } 15 | 16 | const chains = [base] as const 17 | export const wagmiConfig = defaultWagmiConfig({ 18 | chains, 19 | projectId, 20 | metadata, 21 | enableCoinbase: true, 22 | enableWalletConnect: true, 23 | enableInjected: true, 24 | enableEIP6963: true 25 | }) 26 | 27 | createWeb3Modal({ 28 | wagmiConfig: wagmiConfig, 29 | projectId, 30 | enableAnalytics: true, 31 | defaultChain: base, 32 | featuredWalletIds: ['coinbaseWallet'] 33 | }) 34 | 35 | interface Web3ModalProviderProps { 36 | children: React.ReactNode 37 | } 38 | 39 | export const Web3ModalProvider: React.FC = ({ 40 | children 41 | }) => { 42 | return {children} 43 | } 44 | -------------------------------------------------------------------------------- /src/domain-register/BillingAndInfo.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react' 2 | import { useLocation } from 'react-router-dom' 3 | import RegistrationForm from '../components/RegistrationForm' 4 | import bgImage from '../assets/images/bg.jpg' 5 | import LogoHeader from '../components/common/LogoHeader' 6 | 7 | type FormData = { 8 | name: string 9 | phone: string 10 | email: string 11 | domainName: string 12 | custody: 'self' | 'hosted' 13 | walletAddress?: string 14 | } 15 | 16 | interface LocationState { 17 | domain: string 18 | custody?: 'self' | 'hosted' 19 | } 20 | 21 | const DomainRegistration: React.FC = () => { 22 | const location = useLocation() 23 | const state = location.state as LocationState | null 24 | 25 | const [formData, setFormData] = useState({ 26 | name: '', 27 | phone: '', 28 | email: '', 29 | domainName: state?.domain || '', 30 | custody: state?.custody ?? 'self' 31 | }) 32 | console.log(state?.domain) 33 | useEffect(() => { 34 | if (state?.custody) { 35 | setFormData((prev) => ({ 36 | ...prev, 37 | custody: state.custody ?? 'self' 38 | })) 39 | } 40 | }, [state]) 41 | 42 | const handleFormSubmit = (data: FormData) => { 43 | const { name, email, phone, domainName, custody, walletAddress } = data 44 | 45 | const form = document.createElement('form') 46 | form.method = 'POST' 47 | form.action = 'https://pktpal.com/cart/add' 48 | form.style.display = 'none' 49 | 50 | const appendInput = (name: string, value: string) => { 51 | const input = document.createElement('input') 52 | input.type = 'hidden' 53 | input.name = name 54 | input.value = value 55 | form.appendChild(input) 56 | } 57 | 58 | appendInput('items[0][id]', '50393018106131') 59 | appendInput('items[0][quantity]', '1') 60 | appendInput('items[0][properties][name]', name) 61 | appendInput('items[0][properties][email]', email) 62 | appendInput('items[0][properties][number]', phone) 63 | appendInput('items[0][properties][domain]', domainName) 64 | appendInput('items[0][properties][custody]', custody) 65 | 66 | if (walletAddress) { 67 | appendInput('items[0][properties][walletAddress]', walletAddress) 68 | } 69 | 70 | if (custody === 'hosted') { 71 | appendInput('items[1][id]', '50421796274451') 72 | appendInput('items[1][quantity]', '1') 73 | } 74 | 75 | document.body.appendChild(form) 76 | form.submit() 77 | } 78 | 79 | return ( 80 |
81 |
85 | 86 |
87 |
88 |
89 |
90 |
91 |

92 | Fill out the form 93 |

94 | 99 |
100 |
101 |
102 |
103 |
104 |
105 | ) 106 | } 107 | 108 | export default DomainRegistration 109 | -------------------------------------------------------------------------------- /src/domain-register/DomainRegister.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import { useNavigate } from 'react-router-dom' 3 | import bgImage from '../assets/images/bg.jpg' 4 | import LogoHeader from '../components/common/LogoHeader' 5 | 6 | const setupAnimations = () => { 7 | const style = document.createElement('style') 8 | style.textContent = ` 9 | @keyframes fadeIn { 10 | from { opacity: 0; } 11 | to { opacity: 1; } 12 | } 13 | 14 | @keyframes fadeInUp { 15 | from { 16 | opacity: 0; 17 | transform: translateY(20px); 18 | } 19 | to { 20 | opacity: 1; 21 | transform: translateY(0); 22 | } 23 | } 24 | 25 | .animate-fade-in { 26 | opacity: 0; 27 | animation: fadeIn 0.7s ease-out forwards; 28 | } 29 | 30 | .animate-fade-in-up { 31 | opacity: 0; 32 | animation: fadeInUp 0.7s ease-out forwards; 33 | } 34 | 35 | .delay-200 { 36 | animation-delay: 0.2s; 37 | } 38 | ` 39 | document.head.appendChild(style) 40 | } 41 | 42 | const DomainRegister: React.FC = () => { 43 | const navigate = useNavigate() 44 | const [searchQuery, setSearchQuery] = useState('') 45 | const [isValidInput, setIsValidInput] = useState(true) 46 | 47 | const validateInput = (input: string) => { 48 | return /^[a-z0-9-]*$/.test(input) 49 | } 50 | 51 | const handleInputChange = (e: React.ChangeEvent) => { 52 | const value = e.target.value 53 | setSearchQuery(value) 54 | if (value.slice(value.length - 4, value.length) == '.pkt') { 55 | console.log(value.slice(value.length - 4, value.length - 1)) 56 | setIsValidInput(validateInput(value.slice(0, value.length - 4))) 57 | } else { 58 | setIsValidInput(validateInput(value)) 59 | } 60 | } 61 | 62 | const handleSearch = (e: React.FormEvent) => { 63 | e.preventDefault() 64 | if (searchQuery.length > 0 && isValidInput) { 65 | navigate('/search', { 66 | state: { 67 | query: searchQuery, 68 | fromDomainRegister: true 69 | } 70 | }) 71 | } 72 | } 73 | 74 | return ( 75 |
76 |
80 | 81 | 82 | 83 |
84 |
85 |
86 | Claim your 87 |
88 | PKT Domain 89 |
90 | 91 |
92 | 95 | 101 |
102 | 103 |
107 | 114 | 121 |
122 | {!isValidInput && ( 123 |
124 | Only lowercase letters (a-z), numbers (0-9) and hyphens (-) are 125 | allowed 126 |
127 | )} 128 |
129 |
130 |
131 | ) 132 | } 133 | 134 | setupAnimations() 135 | 136 | export default DomainRegister 137 | -------------------------------------------------------------------------------- /src/domain-register/DomainSearchResults.tsx: -------------------------------------------------------------------------------- 1 | import React, { useCallback, useEffect, useState } from 'react' 2 | import { useNavigate, useLocation } from 'react-router-dom' 3 | import { ethers } from 'ethers' 4 | import bgImage from '../assets/images/bg.jpg' 5 | import circleCheck from '../assets/images/check_circle.png' 6 | import circleBlock from '../assets/images/inputBlock.svg' 7 | import whiteArrow from '../assets/images/arrow-white.svg' 8 | import LogoHeader from '../components/common/LogoHeader' 9 | 10 | interface DomainData { 11 | min_value: number 12 | name: string 13 | is_available: boolean 14 | service_charge: string 15 | is_valid: boolean 16 | } 17 | 18 | const DomainSearchResults: React.FC = () => { 19 | const navigate = useNavigate() 20 | const location = useLocation() 21 | 22 | const [custodyType, setCustodyType] = useState<'self' | 'hosted'>('self') 23 | const [loading, setLoading] = useState(false) 24 | const [isValidInput, setIsValidInput] = useState(true) 25 | 26 | const [searchQuery, setSearchQuery] = useState(location.state?.query) 27 | const [domain, setDomain] = useState('') 28 | const [data, setData] = useState({ 29 | min_value: 0, 30 | name: '', 31 | is_valid: false, 32 | is_available: false, 33 | service_charge: '' 34 | }) 35 | 36 | const validateInput = (input: string) => { 37 | return /^[a-z0-9-]*$/.test(input) 38 | } 39 | 40 | const handleInputChange = (e: React.ChangeEvent) => { 41 | const value = e.target.value 42 | setSearchQuery(value) 43 | if (value.slice(value.length - 4, value.length) == '.pkt') { 44 | setIsValidInput(validateInput(value.slice(0, value.length - 4))) 45 | } else { 46 | setIsValidInput(validateInput(value)) 47 | } 48 | } 49 | 50 | const fetchMinValue = useCallback(async () => { 51 | if (!isValidInput || !searchQuery) return 52 | if ( 53 | searchQuery.slice(searchQuery.length - 4, searchQuery.length) == '.pkt' 54 | ) { 55 | setDomain(searchQuery.slice(0, searchQuery.length - 4)) 56 | } else { 57 | setDomain(searchQuery) 58 | } 59 | 60 | setLoading(true) 61 | console.log(domain) 62 | if (!domain) return 63 | try { 64 | const response = await fetch( 65 | `https://app.pkt.cash/api/v1/pns/domain-available/${domain}` 66 | ) 67 | const data = await response.json() 68 | 69 | if (data.min_value) { 70 | const minValue = (await ethers.formatEther( 71 | BigInt(data.min_value) 72 | )) as string 73 | 74 | setData(() => ({ 75 | ...data, 76 | min_value: Math.floor(Number(minValue)) 77 | })) 78 | } 79 | } catch (error) { 80 | console.error('Error fetching minimum value:', error) 81 | } finally { 82 | setLoading(false) 83 | } 84 | }, [isValidInput, searchQuery, domain, setData, setDomain]) 85 | 86 | useEffect(() => { 87 | fetchMinValue() 88 | }, [fetchMinValue]) 89 | 90 | const handleSearch = async (e: React.FormEvent) => { 91 | e.preventDefault() 92 | if (isValidInput && searchQuery) { 93 | await fetchMinValue() 94 | } 95 | } 96 | 97 | const handleRegister = (domain: string) => { 98 | console.log(domain) 99 | navigate('/register-domain', { 100 | state: { 101 | domain, 102 | custody: custodyType 103 | } 104 | }) 105 | } 106 | 107 | return ( 108 |
109 |
113 | 114 | 115 | 116 |
117 |
118 | 126 | 134 |
135 | 136 |
137 | { 144 | if (e.key === 'Enter' && isValidInput && searchQuery) { 145 | handleSearch(e) 146 | } 147 | }} 148 | /> 149 | 156 |
157 | {!isValidInput && ( 158 |
159 | Only lowercase letters (a-z) and numbers (0-9) are allowed 160 |
161 | )} 162 | {loading === false ? ( 163 |
164 |
171 |
172 | circle check 181 | {data.is_valid ? ( 182 | 183 | {`${domain}.pkt`} 184 | 185 | ) : ( 186 | 187 | {`${domain}.pkt`} 188 | 189 | )} 190 |
191 | {data.is_available && data.is_valid ? ( 192 |
193 | 204 |
205 | ) : ( 206 |
207 | 208 | Not Available 209 | 210 |
211 | )} 212 |
213 |
214 | ) : ( 215 |
216 |
217 |
218 | )} 219 |
220 |
221 | ) 222 | } 223 | 224 | export default DomainSearchResults 225 | -------------------------------------------------------------------------------- /src/domain-register/index.tsx: -------------------------------------------------------------------------------- 1 | export { default as DomainRegister } from './DomainRegister' 2 | export { default as BillingAndInfo } from './BillingAndInfo' 3 | export { default as DomainSearchResults } from './DomainSearchResults' 4 | export { default as LogoHeader } from '../components/common/LogoHeader' 5 | export { default as RegistrationForm } from '../components/RegistrationForm' 6 | -------------------------------------------------------------------------------- /src/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | 6 | 7 | @layer base { 8 | body { 9 | @apply bg-[#000B1E] text-white ; 10 | font-family: 'Inter'; 11 | } 12 | } 13 | 14 | @layer utilities { 15 | .animate-fall { 16 | animation: fall 20s linear infinite; 17 | } 18 | 19 | .animate-spin-slow { 20 | animation: spin 3s linear infinite; 21 | } 22 | .custom-scrollbar::-webkit-scrollbar { 23 | width: 20px; 24 | } 25 | 26 | .custom-scrollbar::-webkit-scrollbar-track { 27 | box-shadow: inset 0 0 5px grey; 28 | border-radius: 10px; 29 | } 30 | 31 | .custom-scrollbar::-webkit-scrollbar-thumb { 32 | background: red; 33 | border-radius: 10px; 34 | } 35 | } 36 | 37 | @keyframes fall { 38 | from { 39 | transform: translateY(-100%); 40 | } 41 | to { 42 | transform: translateY(100%); 43 | } 44 | } 45 | 46 | @keyframes spin { 47 | from { 48 | transform: rotate(0deg); 49 | } 50 | to { 51 | transform: rotate(360deg); 52 | } 53 | } 54 | 55 | /* Gradient Backgrounds */ 56 | .bg-gradient-radial { 57 | background: radial-gradient(circle at center, var(--tw-gradient-from) 0%, var(--tw-gradient-via) 50%, var(--tw-gradient-to) 100%); 58 | } 59 | 60 | .bg-gradient-blue { 61 | background: linear-gradient(103deg, #000309 20.67%, rgba(1,43,90,0.90) 47.01%, rgba(1,148,254,0.53) 66.96%, rgba(4,34,107,0.24) 76.55%); 62 | } 63 | 64 | /* Custom Scrollbar */ 65 | ::-webkit-scrollbar { 66 | width: 8px; 67 | } 68 | 69 | ::-webkit-scrollbar-track { 70 | @apply bg-white/5; 71 | } 72 | 73 | ::-webkit-scrollbar-thumb { 74 | @apply bg-white/20 rounded-full hover:bg-white/30 transition-colors; 75 | } 76 | 77 | ::-webkit-scrollbar-thumb:hover { 78 | @apply bg-white/30; 79 | } 80 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /src/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/main.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { createRoot } from 'react-dom/client' 3 | import { BrowserRouter, Route, Routes } from 'react-router-dom' 4 | 5 | import './index.css' 6 | 7 | import { Providers } from './providers' 8 | import { Web3ModalProvider } from './config/web3modal' 9 | 10 | import { 11 | DomainRegister, 12 | BillingAndInfo, 13 | DomainSearchResults 14 | } from './domain-register' 15 | 16 | import { 17 | Web3Register, 18 | RegistrationComplete, 19 | RegistrationFailed 20 | } from './web3-register' 21 | 22 | createRoot(document.getElementById('root') as HTMLElement).render( 23 | 24 | 25 | 26 | 27 | 28 | } /> 29 | } /> 30 | } /> 31 | 32 | } /> 33 | } 36 | /> 37 | } 40 | /> 41 | 42 | 43 | 44 | 45 | 46 | ) 47 | -------------------------------------------------------------------------------- /src/providers/WalletHistoryProvider.tsx: -------------------------------------------------------------------------------- 1 | import { createContext, useState, ReactNode } from 'react' 2 | import { WalletHistoryType, WalletHistoryContextType } from '../types/wallet' 3 | 4 | /** 5 | * Type definition for a transaction history entry 6 | */ 7 | export type TransactionHistoryItem = { 8 | step: number 9 | status: string 10 | txId?: string 11 | stakeAmount?: string 12 | timestamp: number 13 | domainName?: string 14 | currentPrice?: string 15 | maxPkt?: string 16 | stakeId?: string 17 | } 18 | 19 | const defaultContext: WalletHistoryContextType = { 20 | walletHistory: [], 21 | setWalletHistory: () => {} 22 | } 23 | 24 | export const WalletErrorContext = 25 | createContext(defaultContext) 26 | 27 | export const WalletHistoryProvider = ({ 28 | children 29 | }: { 30 | children: ReactNode 31 | }) => { 32 | const [walletHistory, setWalletHistory] = useState([]) 33 | 34 | return ( 35 | 36 | {children} 37 | 38 | ) 39 | } 40 | -------------------------------------------------------------------------------- /src/providers/index.tsx: -------------------------------------------------------------------------------- 1 | import { ReactNode } from 'react' 2 | import { 3 | WalletHistoryProvider, 4 | WalletErrorContext 5 | } from './WalletHistoryProvider' 6 | 7 | interface ProvidersProps { 8 | children: ReactNode 9 | } 10 | 11 | export { WalletHistoryProvider, WalletErrorContext } 12 | 13 | export type { 14 | WalletHistoryType, 15 | TransactionHistoryItem, 16 | WalletHistoryContextType 17 | } from '../types/wallet' 18 | 19 | export const Providers = ({ children }: ProvidersProps) => { 20 | return {children} 21 | } 22 | -------------------------------------------------------------------------------- /src/services/wallet-service.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from 'ethers' 2 | import { WalletState } from '../types/wallet' 3 | 4 | export const delay = (ms: number): Promise => 5 | new Promise((resolve) => setTimeout(resolve, ms)) 6 | 7 | export const stringToHex = (str: string): string => { 8 | return ( 9 | '0x' + 10 | Array.from(str) 11 | .map((c) => c.charCodeAt(0).toString(16).padStart(2, '0')) 12 | .join('') 13 | ) 14 | } 15 | 16 | export const getStakeLabel = (status: WalletState['status']): string => { 17 | return status === 'pre_allocating' ? 'Got stake:' : 'Actual stake amount:' 18 | } 19 | 20 | export const CONSTANTS = { 21 | PKT_BASE: '0x917f39bb33b2483dd19546b1e8d2f09ce481ee44', 22 | LOCKBOX: '0x14d15765c66e8f0c7f8757d1d19137b714dfcc60', 23 | PNS: '0xDc8eb1D1052a2078B33dd188201eAf3F080E0258', 24 | MULTIPAY: '0x3e11E1F68F736209662d5695148986585ca2c971', 25 | BASE_PRICE: 1000n * 10n ** 18n, // 1000 PKT in wei 26 | PRICE_HALVING_INTERVAL: 60 * 60 * 1000 // 1 hour in milliseconds 27 | } 28 | 29 | export const isWalletAvailable = (): boolean => { 30 | return typeof window.ethereum !== 'undefined' 31 | } 32 | 33 | export const connectWallet = 34 | async (): Promise => { 35 | if (!isWalletAvailable()) { 36 | return null 37 | } 38 | 39 | try { 40 | await window.ethereum!.request({ method: 'eth_requestAccounts' }) 41 | 42 | try { 43 | await window.ethereum!.request({ 44 | method: 'wallet_switchEthereumChain', 45 | params: [{ chainId: '0x2105' }] 46 | }) 47 | } catch (switchError: any) { 48 | if (switchError.code === 4902) { 49 | try { 50 | await window.ethereum!.request({ 51 | method: 'wallet_addEthereumChain', 52 | params: [ 53 | { 54 | chainId: '0x2105', 55 | chainName: 'Base', 56 | nativeCurrency: { 57 | name: 'ETH', 58 | symbol: 'ETH', 59 | decimals: 18 60 | }, 61 | rpcUrls: ['https://mainnet.base.org'], 62 | blockExplorerUrls: ['https://basescan.org'] 63 | } 64 | ] 65 | }) 66 | } catch (addError) { 67 | console.error('Failed to add Base network:', addError) 68 | return null 69 | } 70 | } else { 71 | console.error('Failed to switch to Base network:', switchError) 72 | return null 73 | } 74 | } 75 | 76 | return new ethers.BrowserProvider(window.ethereum!) 77 | } catch (error) { 78 | console.error('Error connecting to wallet:', error) 79 | return null 80 | } 81 | } 82 | 83 | export const calculatePrice = ( 84 | domainName: string, 85 | purchaseHistory: { domain: string; timestamp: number }[] 86 | ): bigint => { 87 | const now = Date.now() 88 | 89 | let price = CONSTANTS.BASE_PRICE 90 | const recentPurchases = purchaseHistory.filter( 91 | (purchase) => purchase.domain === domainName 92 | ) 93 | 94 | for (let i = 0; i < recentPurchases.length; i++) { 95 | price *= 2n 96 | } 97 | 98 | if (recentPurchases.length > 0) { 99 | const lastPurchase = recentPurchases[recentPurchases.length - 1] 100 | const hoursSinceLastPurchase = Math.floor( 101 | (now - lastPurchase.timestamp) / CONSTANTS.PRICE_HALVING_INTERVAL 102 | ) 103 | 104 | for ( 105 | let i = 0; 106 | i < hoursSinceLastPurchase && price > CONSTANTS.BASE_PRICE; 107 | i++ 108 | ) { 109 | price /= 2n 110 | } 111 | } 112 | 113 | return price 114 | } 115 | 116 | export const fetchMinValue = async ( 117 | address: string, 118 | domain?: string 119 | ): Promise => { 120 | try { 121 | if (domain) { 122 | const response = await fetch( 123 | `https://app.pkt.cash/api/v1/pns/domain-available/${domain}` 124 | ) 125 | const data = await response.json() 126 | if (data.min_value) { 127 | return BigInt(data.min_value) 128 | } 129 | } 130 | 131 | const response = await fetch( 132 | `https://app.pkt.cash/api/v1/pns/qualifying-lockups/${address}` 133 | ) 134 | const data = await response.json() 135 | if (data.min_value) { 136 | return BigInt(data.min_value) 137 | } 138 | } catch (error) { 139 | console.error('Error fetching minimum value:', error) 140 | } 141 | return CONSTANTS.BASE_PRICE 142 | } 143 | 144 | export const checkDomainAvailability = async ( 145 | domainName: string 146 | ): Promise<{ 147 | isAvailable: boolean 148 | isValid: boolean 149 | minValue?: bigint 150 | errorMessage?: string 151 | }> => { 152 | try { 153 | const response = await fetch( 154 | `https://app.pkt.cash/api/v1/pns/domain-available/${domainName}` 155 | ) 156 | const data = await response.json() 157 | 158 | let errorMessage: string | undefined 159 | 160 | if (!data.is_available) { 161 | errorMessage = `Domain ${domainName}.pkt is already registered` 162 | } else if (!data.is_valid) { 163 | errorMessage = `Domain ${domainName}.pkt is not valid (can only be letters and numbers split up by dashes)` 164 | } 165 | 166 | return { 167 | isAvailable: data.is_available, 168 | isValid: data.is_valid, 169 | minValue: data.min_value ? BigInt(data.min_value) : undefined, 170 | errorMessage 171 | } 172 | } catch (error) { 173 | console.error('Error checking domain availability:', error) 174 | return { 175 | isAvailable: false, 176 | isValid: false, 177 | errorMessage: 'Error checking domain availability' 178 | } 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /src/types/index.ts: -------------------------------------------------------------------------------- 1 | export interface LocationState { 2 | custody?: 'self' | 'hosted' 3 | query?: string 4 | fromDomainRegister?: boolean 5 | } 6 | 7 | export type RegistrarType = 'domain' | 'web3' 8 | export type PaymentMethodType = 'crypto' | 'credit' 9 | 10 | export interface RegistrationFormData { 11 | domainName: string 12 | registrar: RegistrarType 13 | paymentMethod: PaymentMethodType 14 | custody: 'self' | 'hosted' 15 | } 16 | -------------------------------------------------------------------------------- /src/types/wallet.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from 'ethers' 2 | 3 | /** 4 | * Type definition for a transaction history entry 5 | */ 6 | export type TransactionHistoryItem = { 7 | step: number 8 | status: string 9 | txId?: string 10 | stakeAmount?: string 11 | timestamp: number 12 | domainName?: string 13 | currentPrice?: string 14 | maxPkt?: string 15 | stakeId?: string 16 | } 17 | 18 | 19 | export type WalletHistoryType = TransactionHistoryItem[] 20 | 21 | /** 22 | * Type for wallet state in the registration process 23 | */ 24 | export interface WalletState { 25 | status: 26 | | 'loading' 27 | | 'connected' 28 | | 'pre_allocating' 29 | | 'authorizing' 30 | | 'staking' 31 | | 'reserved' 32 | | 'not_found' 33 | | 'price_changed' 34 | | 'low_funds' 35 | | 'success' 36 | | 'failed' 37 | step?: number 38 | txId?: string 39 | stakeAmount?: string 40 | address?: string 41 | label?: string 42 | domainId?: string 43 | stakeId?: string 44 | provider?: ethers.BrowserProvider 45 | signer?: ethers.JsonRpcSigner 46 | erc20abi?: Array 47 | lockboxabi?: Array 48 | multipayabi?: Array 49 | pnsabi?: Array 50 | transactionHistory?: WalletHistoryType 51 | } 52 | 53 | /** 54 | * Type definition for the wallet error context 55 | */ 56 | export interface WalletHistoryContextType { 57 | walletHistory: WalletHistoryType 58 | setWalletHistory: (walletHistory: WalletHistoryType) => void 59 | } 60 | -------------------------------------------------------------------------------- /src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /src/web3-register/RegistrationComplete.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { useLocation } from 'react-router-dom' 3 | import bg from '../assets/images/complete.jpg' 4 | const RegistrationComplete: React.FC = () => { 5 | const location = useLocation() 6 | const domainId = location.state?.domainId || '299' 7 | 8 | return ( 9 |
10 |
14 | 15 |
16 |

17 | Registration complete 18 |

19 | 20 |

21 | Your Domain ID {domainId} is now available in your PKT dashboard 22 |

23 | 24 | 29 | View Dashboard 30 | 31 |
32 |
33 | ) 34 | } 35 | 36 | export default RegistrationComplete 37 | -------------------------------------------------------------------------------- /src/web3-register/RegistrationFailed.tsx: -------------------------------------------------------------------------------- 1 | import React, { useContext, useEffect, useState } from 'react' 2 | import { useNavigate, useLocation } from 'react-router-dom' 3 | import bg from '../assets/images/failed.jpg' 4 | import help from '../assets/images/help.svg' 5 | import MainLayout from '../components/layout/MainLayout' 6 | import { WalletErrorContext } from '../providers' 7 | import { WalletHistoryType, TransactionHistoryItem } from '../types/wallet' 8 | import { WalletHistoryContextType } from '../types/wallet' 9 | 10 | interface LocationState { 11 | txId?: string 12 | stakeAmount?: string 13 | step?: number 14 | history?: TransactionHistoryItem[] 15 | } 16 | 17 | const RegistrationFailed: React.FC = () => { 18 | const navigate = useNavigate() 19 | const location = useLocation() 20 | const state = location.state as LocationState 21 | // const txId = state?.txId 22 | // const stakeAmount = state?.stakeAmount 23 | const step = state?.step || 4 // Default to step 4 if not provided 24 | const stateHistory = state?.history || [] 25 | const { walletHistory } = useContext( 26 | WalletErrorContext 27 | ) as WalletHistoryContextType 28 | 29 | // Combine both sources of history, prioritizing state history as it's more recent 30 | const [combinedHistory, setCombinedHistory] = useState([]) 31 | 32 | useEffect(() => { 33 | const mergedHistory = [...stateHistory] 34 | 35 | if (walletHistory && walletHistory.length > 0) { 36 | walletHistory.forEach((item) => { 37 | const isDuplicate = mergedHistory.some( 38 | (existingItem) => 39 | existingItem.step === item.step && 40 | existingItem.timestamp === item.timestamp 41 | ) 42 | if (!isDuplicate) { 43 | mergedHistory.push(item) 44 | } 45 | }) 46 | } 47 | 48 | mergedHistory.sort((a, b) => a.step - b.step) 49 | 50 | setCombinedHistory(mergedHistory) 51 | 52 | console.log('State History:', stateHistory) 53 | console.log('Context History:', walletHistory) 54 | console.log('Combined History:', mergedHistory) 55 | }, [stateHistory, walletHistory]) 56 | 57 | const renderHistory = () => { 58 | if (combinedHistory.length === 0) return null 59 | 60 | return ( 61 |
62 | {combinedHistory 63 | .filter((item) => item.step <= step) 64 | .map((item, index) => ( 65 |
66 |
67 | Step {item.step}: {item.status} 68 |
69 | {item.txId && ( 70 | <> 71 |
- TXID:
72 |
73 | {item.txId} 74 |
75 | 76 | )} 77 | {item.stakeId && ( 78 | <> 79 |
80 | - Stake ID: 81 |
82 |
83 | {item.stakeId} 84 |
85 | 86 | )} 87 | {item.domainName && ( 88 | <> 89 |
90 | - Domain: 91 |
92 |
93 | {item.domainName} 94 |
95 | 96 | )} 97 | {item.currentPrice && ( 98 | <> 99 |
100 | - Current Price: 101 |
102 |
103 | {item.currentPrice} PKT 104 |
105 | 106 | )} 107 | {item.maxPkt && ( 108 | <> 109 |
110 | - Max PKT: 111 |
112 |
113 | {item.maxPkt} PKT 114 |
115 | 116 | )} 117 |
118 | ))} 119 |
120 | ) 121 | } 122 | 123 | return ( 124 | 125 |
126 |
127 |
131 |
132 |
133 |

134 | Registration failed 135 |

136 |

137 | Please make sure you have PKT and ETH on Base in your Web3 138 | Wallet 139 |

140 |
141 |
142 | {renderHistory()} 143 |
144 |
145 |
146 | 152 | 159 |
160 |
161 |
162 |
163 |
164 | 165 | ) 166 | } 167 | 168 | export default RegistrationFailed 169 | -------------------------------------------------------------------------------- /src/web3-register/Web3Register.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect, useRef, useContext } from 'react' 2 | import { useNavigate } from 'react-router-dom' 3 | 4 | import bgImage from '../assets/images/bg.jpg' 5 | 6 | import MainLayout from '../components/layout/MainLayout' 7 | import { WalletErrorContext } from '../providers' 8 | import { WalletState, WalletHistoryContextType } from '../types/wallet' 9 | import { 10 | fetchMinValue, 11 | checkDomainAvailability, 12 | calculatePrice 13 | } from '../services/wallet-service' 14 | 15 | import { 16 | WalletStatus, 17 | RegistrationForm, 18 | TransactionHistory, 19 | WalletConnector, 20 | registerDomain 21 | } from './components' 22 | 23 | import LogoHeader from '../components/common/LogoHeader' 24 | 25 | const BASE_PRICE = 1000n * 10n ** 18n 26 | 27 | interface PurchaseHistory { 28 | domain: string 29 | timestamp: number 30 | } 31 | 32 | declare global { 33 | interface Window { 34 | ethereum?: any 35 | } 36 | } 37 | 38 | const Web3Register: React.FC = () => { 39 | const navigate = useNavigate() 40 | const { setWalletHistory } = useContext( 41 | WalletErrorContext 42 | ) as WalletHistoryContextType 43 | const scroll = useRef(null) 44 | const [walletState, setWalletState] = useState({ 45 | status: 'loading', 46 | transactionHistory: [] 47 | }) 48 | const [domainName, setDomainName] = useState('') 49 | const [stakeAmount, setStakeAmount] = useState('') 50 | 51 | const [purchaseHistory] = useState([]) 52 | const [currentPrice, setCurrentPrice] = useState(BASE_PRICE) 53 | const [isAvailableRegister, setIsAvailableRegister] = useState(false) 54 | const [isBlockInput, setIsBlockInput] = useState(false) 55 | 56 | const simulateWalletFlow = async () => { 57 | console.log('Simulating wallet flow') 58 | } 59 | 60 | const handleDomainChange = async (value: string) => { 61 | setDomainName(value) 62 | setIsAvailableRegister(true) 63 | 64 | if (value) { 65 | const result = await checkDomainAvailability(value) 66 | setIsBlockInput(!result.isAvailable || !result.isValid) 67 | setIsAvailableRegister(result.isAvailable && result.isValid) 68 | 69 | if (result.minValue) { 70 | setCurrentPrice(result.minValue) 71 | } 72 | } 73 | } 74 | 75 | const handleStakeAmountChange = (value: string) => { 76 | setStakeAmount(value) 77 | } 78 | 79 | const handleSubmit = async (e: React.FormEvent) => { 80 | e.preventDefault() 81 | await registerDomain( 82 | walletState, 83 | setWalletState, 84 | setWalletHistory, 85 | setIsAvailableRegister, 86 | navigate, 87 | domainName, 88 | stakeAmount, 89 | currentPrice 90 | ) 91 | } 92 | 93 | const renderStepHistory = (currentStep: number) => { 94 | return ( 95 | 99 | ) 100 | } 101 | 102 | // Update price when wallet is connected or domain name changes 103 | useEffect(() => { 104 | const address = walletState.address 105 | if (address && domainName) { 106 | fetchMinValue(address, domainName).then((price) => setCurrentPrice(price)) 107 | } else if (address) { 108 | fetchMinValue(address).then((price) => setCurrentPrice(price)) 109 | } 110 | }, [walletState.address, domainName]) 111 | 112 | // Update price every 30 seconds 113 | useEffect(() => { 114 | const address = walletState.address 115 | if (address) { 116 | const interval = setInterval(() => { 117 | if (domainName) { 118 | fetchMinValue(address, domainName).then((price) => 119 | setCurrentPrice(price) 120 | ) 121 | } else { 122 | fetchMinValue(address).then((price) => setCurrentPrice(price)) 123 | } 124 | }, 30000) // Check every 30 seconds 125 | 126 | return () => clearInterval(interval) 127 | } 128 | }, [walletState.address, domainName]) 129 | 130 | useEffect(() => { 131 | const interval = setInterval(() => { 132 | if (domainName) { 133 | setCurrentPrice(calculatePrice(domainName, purchaseHistory)) 134 | } 135 | }, 60000) 136 | 137 | return () => clearInterval(interval) 138 | }, [domainName, purchaseHistory]) 139 | 140 | useEffect(() => { 141 | if (domainName) { 142 | setCurrentPrice(calculatePrice(domainName, purchaseHistory)) 143 | } 144 | }, [domainName]) 145 | 146 | return ( 147 | 148 | 153 | 154 |
155 |
159 | 160 | 161 | 162 |
163 |
164 | Claim your 165 |
166 | PKT Domain 167 |
168 | 169 |

170 | This is a tool for claiming a PKT domain without first making a 171 | stake. It does the staking in the same transaction while registering 172 | the domain. 173 |

174 | 175 |
176 | 184 | 187 |
188 | 189 |
190 |
191 | 202 | 203 |
207 |
208 |
209 | 213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 | 221 | ) 222 | } 223 | 224 | export default Web3Register 225 | -------------------------------------------------------------------------------- /src/web3-register/components/RegistrationForm.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { ethers } from 'ethers' 3 | import FormInput from '../../components/common/FormInput' 4 | import inputBlock from '../../assets/images/inputBlock.svg' 5 | import inputCheck from '../../assets/images/inputCheck.svg' 6 | 7 | interface RegistrationFormProps { 8 | domainName: string 9 | stakeAmount: string 10 | currentPrice: bigint 11 | isAvailableRegister: boolean 12 | isBlockInput: boolean 13 | onDomainChange: (value: string) => void 14 | onStakeAmountChange: (value: string) => void 15 | onSubmit: (e: React.FormEvent) => void 16 | scrollRef: React.RefObject 17 | } 18 | 19 | const RegistrationForm: React.FC = ({ 20 | domainName, 21 | stakeAmount, 22 | currentPrice, 23 | isAvailableRegister, 24 | isBlockInput, 25 | onDomainChange, 26 | onStakeAmountChange, 27 | onSubmit, 28 | scrollRef 29 | }) => { 30 | return ( 31 |
{ 34 | e.preventDefault() 35 | scrollRef.current?.scrollIntoView({ behavior: 'smooth' }) 36 | setTimeout(() => { 37 | onSubmit(e) 38 | }, 500) 39 | }} 40 | > 41 |
42 | 47 | {domainName && ( 48 | 53 | )} 54 |
55 | 56 | 64 | 65 | 75 | 76 | ) 77 | } 78 | 79 | export default RegistrationForm 80 | -------------------------------------------------------------------------------- /src/web3-register/components/RegistrationService.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from 'ethers' 2 | import { WalletState, TransactionHistoryItem } from '../../types/wallet' 3 | import { 4 | stringToHex, 5 | fetchMinValue, 6 | CONSTANTS, 7 | delay 8 | } from '../../services/wallet-service' 9 | 10 | /** 11 | * Helper function to update transaction history in the wallet state 12 | */ 13 | export const updateTransactionHistory = ( 14 | walletState: WalletState, 15 | setWalletState: React.Dispatch>, 16 | setWalletHistory: (history: TransactionHistoryItem[]) => void, 17 | currentStep: number, 18 | status: WalletState['status'], 19 | values: { 20 | stakeAmount?: string 21 | domainName?: string 22 | currentPrice?: string 23 | maxPkt?: string 24 | stakeId?: string 25 | txId?: string 26 | } 27 | ) => { 28 | setWalletState((prev) => { 29 | console.log(walletState) 30 | const newHistory = [...(prev.transactionHistory || [])] 31 | // Remove any existing entry for this step 32 | const stepIndex = newHistory.findIndex((step) => step.step === currentStep) 33 | if (stepIndex >= 0) { 34 | newHistory.splice(stepIndex, 1) 35 | } 36 | // Add new entry 37 | newHistory.push({ 38 | step: currentStep, 39 | status, 40 | ...values, 41 | timestamp: Date.now() 42 | }) 43 | 44 | // Update the context with the new history 45 | setWalletHistory(newHistory) 46 | console.log(`Updating wallet history for step ${currentStep}:`, newHistory) 47 | 48 | return { 49 | ...prev, 50 | status, 51 | step: currentStep, 52 | ...values, 53 | transactionHistory: newHistory 54 | } 55 | }) 56 | } 57 | 58 | /** 59 | * Helper function to handle transaction errors 60 | */ 61 | export const handleTransactionError = async ( 62 | error: any, 63 | step: number, 64 | walletState: WalletState, 65 | setIsAvailableRegister: React.Dispatch>, 66 | setWalletHistory: (history: TransactionHistoryItem[]) => void, 67 | navigate: (path: string, options?: any) => void 68 | ) => { 69 | console.error('Transaction error:', error) 70 | setIsAvailableRegister(true) 71 | 72 | // Store the history in a local variable to ensure we have it for navigation 73 | const historyToSave = walletState.transactionHistory || [] 74 | 75 | // Update the context 76 | if (historyToSave.length > 0) { 77 | setWalletHistory(historyToSave) 78 | console.log('Setting wallet history in context:', historyToSave) 79 | } 80 | 81 | await delay(500) // Shorter delay to give context time to update 82 | 83 | // Navigate with history as state parameter for redundancy 84 | navigate('/web3-register/failed', { 85 | state: { 86 | history: historyToSave, 87 | step: walletState.step || step 88 | } 89 | }) 90 | } 91 | 92 | /** 93 | * Execute the domain registration process 94 | */ 95 | export const registerDomain = async ( 96 | walletState: WalletState, 97 | setWalletState: React.Dispatch>, 98 | setWalletHistory: (history: TransactionHistoryItem[]) => void, 99 | setIsAvailableRegister: React.Dispatch>, 100 | navigate: (path: string, options?: any) => void, 101 | domainName: string, 102 | stakeAmount: string, 103 | currentPrice: bigint 104 | ) => { 105 | const { PKT_BASE, LOCKBOX, PNS, MULTIPAY } = CONSTANTS 106 | 107 | setIsAvailableRegister(false) 108 | let maxPkt: bigint 109 | let myStakeId = 0 110 | 111 | try { 112 | if (stakeAmount === '') { 113 | maxPkt = 50000n * 10n ** 18n 114 | } else { 115 | maxPkt = ethers.parseEther(stakeAmount) 116 | } 117 | } catch (e) { 118 | const err = `The maximum PKT to stake must be a valid integer` 119 | console.log(err) 120 | alert(err) 121 | return 122 | } 123 | 124 | if (!/^[a-z0-9-]+$/.test(domainName)) { 125 | const err = `Domains can only have the letters, numbers and dashes` 126 | console.log(err) 127 | alert(err) 128 | return 129 | } 130 | 131 | try { 132 | // Get current minimum value from API 133 | const minValue = await fetchMinValue(walletState.address!, domainName) 134 | 135 | // Check if stake amount meets minimum value 136 | if (maxPkt < minValue) { 137 | const err = `The maximum PKT to stake is less than required amount: ${ethers.formatEther( 138 | minValue 139 | )}` 140 | console.log(err) 141 | setWalletState((prev) => ({ ...prev, status: 'price_changed' })) 142 | return 143 | } 144 | 145 | const availableRes = await fetch( 146 | `https://app.pkt.cash/api/v1/pns/domain-available/${domainName}` 147 | ) 148 | const available = await availableRes.json() 149 | console.log(available, domainName, 'available') 150 | 151 | if (!available.is_available) { 152 | const err = `Domain ${domainName}.pkt is already registered` 153 | console.log(err) 154 | alert(err) 155 | return 156 | } 157 | 158 | if (!available.is_valid) { 159 | const err = `Domain ${domainName}.pkt is not valid (can only be letters and numbers split up by dashes)` 160 | console.log(err) 161 | alert(err) 162 | return 163 | } 164 | 165 | if (maxPkt < BigInt(available.min_value)) { 166 | const err = `The maximum PKT to stake is less than required amount: ${ethers.formatEther( 167 | available.min_value 168 | )}` 169 | console.log(err) 170 | alert(err) 171 | return 172 | } 173 | 174 | if ( 175 | !walletState.signer || 176 | !walletState.erc20abi || 177 | !walletState.lockboxabi || 178 | !walletState.multipayabi || 179 | !walletState.pnsabi 180 | ) { 181 | console.error('Wallet state is not properly initialized') 182 | return 183 | } 184 | 185 | const token = new ethers.Contract( 186 | PKT_BASE, 187 | walletState.erc20abi, 188 | walletState.signer 189 | ) 190 | const lockbox = new ethers.Contract( 191 | LOCKBOX, 192 | walletState.lockboxabi, 193 | walletState.signer 194 | ) 195 | const multipay = new ethers.Contract( 196 | MULTIPAY, 197 | walletState.multipayabi, 198 | walletState.signer 199 | ) 200 | const pns = new ethers.Contract(PNS, walletState.pnsabi, walletState.signer) 201 | 202 | // Step 1: Pre-allocating stake 203 | console.log(`[1/4] Pre-allocating stake`) 204 | updateTransactionHistory( 205 | walletState, 206 | setWalletState, 207 | setWalletHistory, 208 | 1, 209 | 'pre_allocating', 210 | { 211 | domainName, 212 | currentPrice: Number(ethers.formatEther(currentPrice)).toLocaleString(), 213 | maxPkt: stakeAmount 214 | } 215 | ) 216 | 217 | try { 218 | myStakeId = await multipay.getStake(walletState.address) 219 | if (myStakeId > 0) { 220 | console.log(` - Got stake: ${myStakeId}`) 221 | updateTransactionHistory( 222 | walletState, 223 | setWalletState, 224 | setWalletHistory, 225 | 1, 226 | 'pre_allocating', 227 | { 228 | stakeId: myStakeId.toString(), 229 | domainName, 230 | currentPrice: Number( 231 | ethers.formatEther(currentPrice) 232 | ).toLocaleString(), 233 | maxPkt: stakeAmount 234 | } 235 | ) 236 | } else { 237 | const gasLimit = await multipay.createEmptyStake.estimateGas(LOCKBOX) 238 | const res = await multipay.createEmptyStake(LOCKBOX, { gasLimit }) 239 | const tx = await res.wait() 240 | console.log(` - TXID: ${tx.hash}`) 241 | await showTempTxId(walletState, setWalletState, tx.hash) 242 | 243 | myStakeId = await multipay.getStake(walletState.address) 244 | console.log(` - Got stake: ${myStakeId}`) 245 | updateTransactionHistory( 246 | walletState, 247 | setWalletState, 248 | setWalletHistory, 249 | 1, 250 | 'pre_allocating', 251 | { 252 | stakeId: myStakeId.toString(), 253 | domainName, 254 | currentPrice: Number( 255 | ethers.formatEther(currentPrice) 256 | ).toLocaleString(), 257 | maxPkt: stakeAmount, 258 | txId: tx.hash 259 | } 260 | ) 261 | } 262 | } catch (error) { 263 | console.error('Error in pre-allocation:', error) 264 | await handleTransactionError( 265 | error, 266 | 1, 267 | walletState, 268 | setIsAvailableRegister, 269 | setWalletHistory, 270 | navigate 271 | ) 272 | return 273 | } 274 | 275 | // Step 2: Authorizing 276 | console.log( 277 | `[2/4] Authorizing Multipay contract to stake up to ${ethers.formatEther( 278 | maxPkt 279 | )} PKT` 280 | ) 281 | updateTransactionHistory( 282 | walletState, 283 | setWalletState, 284 | setWalletHistory, 285 | 2, 286 | 'authorizing', 287 | { 288 | stakeId: myStakeId.toString(), 289 | domainName, 290 | currentPrice: Number(ethers.formatEther(currentPrice)).toLocaleString(), 291 | maxPkt: stakeAmount 292 | } 293 | ) 294 | 295 | try { 296 | const balance = await token.balanceOf(walletState.address) 297 | console.log('User balance:', ethers.formatEther(balance), 'PKT') 298 | 299 | if (balance < maxPkt) { 300 | setWalletState((prev) => ({ ...prev, status: 'low_funds' })) 301 | return 302 | } 303 | 304 | const gasLimit = await token.approve.estimateGas(MULTIPAY, maxPkt) 305 | const res = await token.approve(MULTIPAY, maxPkt, { gasLimit }) 306 | const tx = await res.wait() 307 | console.log(` - TXID: ${tx.hash}`) 308 | await showTempTxId(walletState, setWalletState, tx.hash) 309 | updateTransactionHistory( 310 | walletState, 311 | setWalletState, 312 | setWalletHistory, 313 | 2, 314 | 'authorizing', 315 | { 316 | stakeId: myStakeId.toString(), 317 | domainName, 318 | currentPrice: Number( 319 | ethers.formatEther(currentPrice) 320 | ).toLocaleString(), 321 | maxPkt: stakeAmount, 322 | txId: tx.hash 323 | } 324 | ) 325 | } catch (error) { 326 | console.error('Approval error:', error) 327 | await handleTransactionError( 328 | error, 329 | 2, 330 | walletState, 331 | setIsAvailableRegister, 332 | setWalletHistory, 333 | navigate 334 | ) 335 | return 336 | } 337 | 338 | // Step 3: Staking Domain 339 | console.log(`[3/4] Calling Multipay.stakeDomain()`) 340 | updateTransactionHistory( 341 | walletState, 342 | setWalletState, 343 | setWalletHistory, 344 | 3, 345 | 'staking', 346 | { 347 | stakeId: myStakeId.toString(), 348 | domainName, 349 | currentPrice: Number(ethers.formatEther(currentPrice)).toLocaleString(), 350 | maxPkt: stakeAmount 351 | } 352 | ) 353 | 354 | try { 355 | const encodedDomain = stringToHex(domainName) 356 | const args = [PKT_BASE, LOCKBOX, PNS, maxPkt, encodedDomain, '0x'] 357 | const gasLimit = await multipay.stakeDomain.estimateGas(...args) 358 | const res = await multipay.stakeDomain(...args, { gasLimit }) 359 | const tx = await res.wait() 360 | console.log(` - TXID: ${tx.hash}`) 361 | 362 | const stakeObj = await lockbox.lockups(myStakeId) 363 | const actualStake = ethers.formatEther(stakeObj[0]) 364 | console.log(`Actual PKT staked: ${actualStake}`) 365 | 366 | updateTransactionHistory( 367 | walletState, 368 | setWalletState, 369 | setWalletHistory, 370 | 3, 371 | 'staking', 372 | { 373 | stakeId: myStakeId.toString(), 374 | domainName, 375 | currentPrice: Number( 376 | ethers.formatEther(currentPrice) 377 | ).toLocaleString(), 378 | maxPkt: stakeAmount, 379 | txId: tx.hash 380 | } 381 | ) 382 | } catch (error) { 383 | console.error('Stake domain error:', error) 384 | await handleTransactionError( 385 | error, 386 | 3, 387 | walletState, 388 | setIsAvailableRegister, 389 | setWalletHistory, 390 | navigate 391 | ) 392 | return 393 | } 394 | 395 | // Step 4: Finalizing Registration 396 | console.log(`[4/4] Calling PNS.register() to finalize registration`) 397 | updateTransactionHistory( 398 | walletState, 399 | setWalletState, 400 | setWalletHistory, 401 | 4, 402 | 'reserved', 403 | { 404 | stakeId: myStakeId.toString(), 405 | domainName, 406 | currentPrice: Number(ethers.formatEther(currentPrice)).toLocaleString(), 407 | maxPkt: stakeAmount 408 | } 409 | ) 410 | 411 | try { 412 | const encodedDomain = stringToHex(domainName) 413 | const args = [myStakeId, encodedDomain, '0x'] 414 | const gasLimit = await pns.register.estimateGas(...args) 415 | const res = await pns.register(...args, { gasLimit }) 416 | const tx = await res.wait() 417 | console.log(` - TXID: ${tx.hash}`) 418 | await showTempTxId(walletState, setWalletState, tx.hash) 419 | 420 | const did = await pns.getDomainIdByLockupId(myStakeId) 421 | console.log(`Got domain ID ${did}`) 422 | 423 | updateTransactionHistory( 424 | walletState, 425 | setWalletState, 426 | setWalletHistory, 427 | 4, 428 | 'reserved', 429 | { 430 | stakeAmount: myStakeId.toString(), 431 | domainName, 432 | currentPrice: Number( 433 | ethers.formatEther(currentPrice) 434 | ).toLocaleString(), 435 | maxPkt: stakeAmount, 436 | txId: tx.hash 437 | } 438 | ) 439 | await delay(2000) 440 | navigate('/web3-register/success', { 441 | state: { domainId: myStakeId.toString() } 442 | }) 443 | } catch (error) { 444 | console.error('Error finalizing registration:', error) 445 | await handleTransactionError( 446 | error, 447 | 4, 448 | walletState, 449 | setIsAvailableRegister, 450 | setWalletHistory, 451 | navigate 452 | ) 453 | } 454 | } catch (error) { 455 | console.error(error) 456 | setWalletState((prev) => ({ ...prev, status: 'connected' })) 457 | } 458 | } 459 | 460 | /** 461 | * Helper function to show transaction ID temporarily 462 | */ 463 | export const showTempTxId = async ( 464 | walletState: WalletState, 465 | setWalletState: React.Dispatch>, 466 | txHash: string 467 | ) => { 468 | setWalletState((prev) => { 469 | const newHistory = [...(prev.transactionHistory || [])] 470 | const currentStep = prev.step || 1 471 | console.log(walletState) 472 | // Update or add the current step's transaction 473 | const stepIndex = newHistory.findIndex((step) => step.step === currentStep) 474 | if (stepIndex >= 0) { 475 | newHistory[stepIndex] = { 476 | ...newHistory[stepIndex], 477 | txId: txHash, 478 | stakeAmount: prev.stakeAmount, 479 | timestamp: Date.now() 480 | } 481 | } else { 482 | newHistory.push({ 483 | step: currentStep, 484 | status: prev.status || '', 485 | txId: txHash, 486 | stakeAmount: prev.stakeAmount, 487 | timestamp: Date.now() 488 | }) 489 | } 490 | 491 | return { ...prev, txId: txHash, transactionHistory: newHistory } 492 | }) 493 | await delay(2000) 494 | setWalletState((prev) => ({ ...prev, txId: undefined })) 495 | } 496 | -------------------------------------------------------------------------------- /src/web3-register/components/TransactionHistory.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { TransactionHistoryItem } from '../../types/wallet' 3 | 4 | interface TransactionHistoryProps { 5 | transactionHistory?: TransactionHistoryItem[] 6 | currentStep: number 7 | } 8 | 9 | const TransactionHistory: React.FC = ({ 10 | transactionHistory, 11 | currentStep 12 | }) => { 13 | if (!transactionHistory) return null 14 | 15 | return ( 16 |
17 | {transactionHistory 18 | .filter((step) => step.step < currentStep) 19 | .sort((a, b) => b.step - a.step) 20 | .map((step, index) => ( 21 |
22 |
23 | Step {step.step}: {step.status} 24 |
25 | {step.txId && ( 26 |
27 | TXID: {step.txId} 28 |
29 | )} 30 | {step.stakeAmount && ( 31 |
32 | Stake Amount: {step.stakeAmount} 33 |
34 | )} 35 | {step.domainName && ( 36 |
37 | Domain: {step.domainName} 38 |
39 | )} 40 | {step.currentPrice && ( 41 |
42 | Current Price: {step.currentPrice} PKT 43 |
44 | )} 45 | {step.maxPkt && ( 46 |
47 | Max PKT: {step.maxPkt} PKT 48 |
49 | )} 50 |
51 | ))} 52 |
53 | ) 54 | } 55 | 56 | export default TransactionHistory 57 | -------------------------------------------------------------------------------- /src/web3-register/components/WalletConnector.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react' 2 | import { ethers } from 'ethers' 3 | import { WalletState } from '../../types/wallet' 4 | 5 | import { erc20Abi } from '../../abi/erc20-abi' 6 | import { lockboxAbi } from '../../abi/lockbox-abi' 7 | import { multiPlayAbi } from '../../abi/multipay-abi' 8 | import { pnsAbi } from '../../abi/pns-abi' 9 | 10 | interface WalletConnectorProps { 11 | walletState: WalletState 12 | setWalletState: React.Dispatch> 13 | simulateWalletFlow: () => Promise 14 | } 15 | 16 | const WalletConnector: React.FC = ({ 17 | walletState, 18 | setWalletState, 19 | simulateWalletFlow 20 | }) => { 21 | const handleAccountsChanged = (accounts: string[]) => { 22 | if (accounts.length === 0) { 23 | setWalletState({ status: 'not_found', transactionHistory: [] }) 24 | } else { 25 | simulateWalletFlow() 26 | } 27 | } 28 | 29 | const handleChainChanged = () => { 30 | simulateWalletFlow() 31 | } 32 | 33 | useEffect(() => { 34 | const initWallet = async () => { 35 | setWalletState({ ...walletState, status: 'loading' }) 36 | 37 | if (typeof window.ethereum === 'undefined') { 38 | console.log('No wallet found') 39 | setWalletState({ ...walletState, status: 'not_found' }) 40 | return 41 | } 42 | 43 | try { 44 | try { 45 | await window.ethereum.request({ method: 'eth_requestAccounts' }) 46 | } catch (error: any) { 47 | if (error.code === -32002) { 48 | console.log( 49 | 'Wallet connection request already pending. Please check your wallet.' 50 | ) 51 | setWalletState({ ...walletState, status: 'not_found' }) 52 | return 53 | } 54 | throw error 55 | } 56 | 57 | try { 58 | await window.ethereum.request({ 59 | method: 'wallet_switchEthereumChain', 60 | params: [{ chainId: '0x2105' }] 61 | }) 62 | } catch (switchError: any) { 63 | if (switchError.code === 4902) { 64 | try { 65 | await window.ethereum.request({ 66 | method: 'wallet_addEthereumChain', 67 | params: [ 68 | { 69 | chainId: '0x2105', 70 | chainName: 'Base', 71 | nativeCurrency: { 72 | name: 'ETH', 73 | symbol: 'ETH', 74 | decimals: 18 75 | }, 76 | rpcUrls: ['https://mainnet.base.org'], 77 | blockExplorerUrls: ['https://basescan.org'] 78 | } 79 | ] 80 | }) 81 | } catch (addError) { 82 | console.error('Failed to add Base network:', addError) 83 | setWalletState({ ...walletState, status: 'not_found' }) 84 | return 85 | } 86 | } else { 87 | console.error('Failed to switch to Base network:', switchError) 88 | setWalletState({ ...walletState, status: 'not_found' }) 89 | return 90 | } 91 | } 92 | 93 | const provider = new ethers.BrowserProvider(window.ethereum) 94 | const signer = await provider.getSigner() 95 | const address = await signer.getAddress() 96 | 97 | setWalletState({ 98 | ...walletState, 99 | status: 'connected', 100 | address: address, 101 | label: address.slice(0, 6) + '...' + address.slice(-4), 102 | provider: provider, 103 | signer: signer, 104 | erc20abi: erc20Abi, 105 | lockboxabi: lockboxAbi, 106 | multipayabi: multiPlayAbi, 107 | pnsabi: pnsAbi 108 | }) 109 | console.log('Ready.') 110 | 111 | window.ethereum.on('accountsChanged', handleAccountsChanged) 112 | window.ethereum.on('chainChanged', handleChainChanged) 113 | } catch (error) { 114 | console.error('Error connecting to wallet:', error) 115 | setWalletState({ ...walletState, status: 'not_found' }) 116 | } 117 | } 118 | 119 | initWallet() 120 | 121 | return () => { 122 | if (window.ethereum) { 123 | window.ethereum.removeListener('accountsChanged', handleAccountsChanged) 124 | window.ethereum.removeListener('chainChanged', handleChainChanged) 125 | } 126 | } 127 | }, []) 128 | 129 | return null 130 | } 131 | 132 | export default WalletConnector 133 | -------------------------------------------------------------------------------- /src/web3-register/components/WalletStatus.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import connected from '../../assets/images/connected.svg' 3 | import { WalletState } from '../../types/wallet' 4 | 5 | interface WalletStatusProps { 6 | walletState: WalletState 7 | renderStepHistory: (currentStep: number) => React.ReactNode 8 | } 9 | 10 | const WalletStatus: React.FC = ({ 11 | walletState, 12 | renderStepHistory 13 | }) => { 14 | const getStatusMessage = (status: WalletState['status']): string => { 15 | switch (status) { 16 | case 'pre_allocating': 17 | return 'Pre-allocating stake' 18 | case 'authorizing': 19 | return 'Authorizing stake' 20 | case 'staking': 21 | return 'Staking domain' 22 | default: 23 | return '' 24 | } 25 | } 26 | 27 | const getStakeLabel = (status: WalletState['status']): string => { 28 | return status === 'pre_allocating' ? 'Got stake:' : 'Actual stake amount:' 29 | } 30 | 31 | const renderCurrentStep = () => { 32 | return ( 33 |
34 |
35 | Step {walletState.step}/4: {getStatusMessage(walletState.status)} 36 |
37 |
38 |
39 |
40 |
44 |
45 |
46 |
47 | {walletState.stakeId && 48 | walletState.stakeId !== '0' && 49 | walletState.step === 1 && ( 50 |
51 |
Stake ID:
52 |
{walletState.stakeId}
53 |
54 | )} 55 |
56 |
57 | {walletState.txId ? '- TXID:' : ''} 58 |
59 |
{walletState.txId}
60 |
61 | {walletState.stakeAmount && ( 62 |
63 |
64 | {getStakeLabel(walletState.status)} 65 |
66 |
{walletState.stakeAmount}
67 |
68 | )} 69 |
70 |
71 | ) 72 | } 73 | 74 | const firstStepHistory = walletState.transactionHistory?.find( 75 | (step) => step.step === 1 76 | ) 77 | 78 | switch (walletState.status) { 79 | case 'loading': 80 | return ( 81 |
82 |
Wallet loading...
83 |
84 |
85 |
86 |
Trying to detect wallet
87 |
88 | ) 89 | 90 | case 'connected': 91 | return ( 92 |
93 |
Wallet connected
94 |
95 | connected 100 |
101 |
102 | {walletState.label} 103 |
104 |
105 | ) 106 | 107 | case 'pre_allocating': 108 | return ( 109 |
110 | {renderCurrentStep()} 111 | {renderStepHistory(1)} 112 |
113 | ) 114 | 115 | case 'authorizing': 116 | return ( 117 |
118 | {renderCurrentStep()} 119 | {renderStepHistory(2)} 120 |
121 | ) 122 | 123 | case 'staking': 124 | return ( 125 |
126 | {renderCurrentStep()} 127 | {renderStepHistory(3)} 128 |
129 | ) 130 | 131 | case 'reserved': 132 | return ( 133 |
134 |
135 |
136 | Domain reserved 137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 | Domain ID: {firstStepHistory?.stakeId || walletState.stakeId} 147 |
148 |
sending to wallet
149 |
150 |
151 |
152 | ) 153 | 154 | case 'not_found': 155 | return ( 156 |
157 |
158 |
159 | Wallet{' '} 160 | 166 |
167 |
168 | Desktop wallet browser extension is required 169 |
170 |
171 | 182 |
183 | ) 184 | 185 | case 'price_changed': 186 | return ( 187 |
188 |
189 |
Price is higher than input 😕
190 |
191 | 197 |
198 | ) 199 | 200 | case 'low_funds': 201 | return ( 202 |
203 |
204 |
You are low on funds 😕
205 | 211 |
212 |
213 | ) 214 | 215 | default: 216 | return null 217 | } 218 | } 219 | 220 | export default WalletStatus 221 | -------------------------------------------------------------------------------- /src/web3-register/components/index.ts: -------------------------------------------------------------------------------- 1 | export { default as WalletStatus } from './WalletStatus' 2 | export { default as RegistrationForm } from './RegistrationForm' 3 | export { default as TransactionHistory } from './TransactionHistory' 4 | export { default as WalletConnector } from './WalletConnector' 5 | export * from './RegistrationService' 6 | -------------------------------------------------------------------------------- /src/web3-register/index.tsx: -------------------------------------------------------------------------------- 1 | // Export all web3 registration components 2 | export { default as Web3Register } from './Web3Register' 3 | export { default as RegistrationComplete } from './RegistrationComplete' 4 | export { default as RegistrationFailed } from './RegistrationFailed' 5 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | content: ['./dist/**/*.html', './src/**/*.{js,jsx,ts,tsx}', './*.html'], 3 | theme: { 4 | extend: { 5 | keyframes: { 6 | 'confetti-1': { 7 | '0%': { transform: 'translateY(0) rotate(0)' }, 8 | '100%': { transform: 'translateY(100vh) rotate(360deg)' } 9 | }, 10 | 'confetti-2': { 11 | '0%': { transform: 'translateY(0) rotate(0)' }, 12 | '100%': { transform: 'translateY(100vh) rotate(-360deg)' } 13 | }, 14 | 'confetti-3': { 15 | '0%': { transform: 'translateY(0) rotate(0)' }, 16 | '100%': { transform: 'translateY(100vh) rotate(720deg)' } 17 | }, 18 | 'confetti-4': { 19 | '0%': { transform: 'translateY(0) rotate(0)' }, 20 | '100%': { transform: 'translateY(100vh) rotate(-720deg)' } 21 | }, 22 | 'confetti-5': { 23 | '0%': { transform: 'translateY(0) rotate(0)' }, 24 | '100%': { transform: 'translateY(100vh) rotate(1080deg)' } 25 | }, 26 | 'confetti-6': { 27 | '0%': { transform: 'translateY(0) rotate(0)' }, 28 | '100%': { transform: 'translateY(100vh) rotate(-1080deg)' } 29 | } 30 | }, 31 | animation: { 32 | 'confetti-1': 'confetti-1 3s linear infinite', 33 | 'confetti-2': 'confetti-2 3.5s linear infinite', 34 | 'confetti-3': 'confetti-3 4s linear infinite', 35 | 'confetti-4': 'confetti-4 4.5s linear infinite', 36 | 'confetti-5': 'confetti-5 5s linear infinite', 37 | 'confetti-6': 'confetti-6 5.5s linear infinite' 38 | } 39 | } 40 | }, 41 | plugins: [require('@tailwindcss/forms'), require('tailwind-scrollbar')], 42 | variants: { 43 | extend: { 44 | opacity: ['disabled'], 45 | scrollbar: ['rounded'] // Optional: enable more variants for scrollbar 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | "moduleResolution": "bundler", 9 | "allowImportingTsExtensions": true, 10 | "resolveJsonModule": true, 11 | "isolatedModules": true, 12 | "noEmit": true, 13 | "jsx": "react-jsx", 14 | "strict": true, 15 | "noUnusedLocals": true, 16 | "noUnusedParameters": true, 17 | "noFallthroughCasesInSwitch": true, 18 | "baseUrl": ".", 19 | "paths": { 20 | "@/*": ["src/*"] 21 | }, 22 | "esModuleInterop": true 23 | }, 24 | "include": ["src"], 25 | "references": [{ "path": "./tsconfig.node.json" }] 26 | } 27 | -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "skipLibCheck": true, 5 | "module": "ESNext", 6 | "moduleResolution": "bundler", 7 | "allowSyntheticDefaultImports": true 8 | }, 9 | "include": ["vite.config.ts"] 10 | } -------------------------------------------------------------------------------- /vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "rewrites": [ 3 | { 4 | "source": "/(.*)", 5 | "destination": "/index.html" 6 | } 7 | ], 8 | "headers": [ 9 | { 10 | "source": "/(.*)", 11 | "headers": [ 12 | { 13 | "key": "Cache-Control", 14 | "value": "public, max-age=0, must-revalidate" 15 | } 16 | ] 17 | } 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | import path from 'path' 4 | 5 | export default defineConfig({ 6 | plugins: [react()], 7 | resolve: { 8 | alias: { 9 | '@': path.resolve(__dirname, './src') 10 | } 11 | }, 12 | server: { 13 | headers: { 14 | 'Content-Security-Policy': 15 | "script-src 'self' 'unsafe-eval' 'unsafe-inline';" 16 | } 17 | } 18 | }) 19 | --------------------------------------------------------------------------------