├── .changeset └── config.json ├── .eslintignore ├── .eslintrc.json ├── .github └── workflows │ ├── add-to-project.yml │ └── release.yml ├── .gitignore ├── .lintstagedrc.json ├── .npmrc ├── .nvmrc ├── .prettierignore ├── .prettierrc ├── .simple-git-hooks.js ├── .vscode ├── extensions.json └── settings.json ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── assets ├── asset-logos │ ├── inw.png │ └── panx.png └── wallet-logos │ ├── aleph-zero-signer@128w.png │ ├── aleph-zero-signer@512w.png │ ├── nightly@128w.png │ ├── nightly@512w.png │ ├── nightlyConnect@128w.png │ ├── nightlyConnect@512w.png │ ├── nova@128w.png │ ├── nova@512w.png │ ├── polkadot@128w.png │ ├── polkadot@512w.png │ ├── subwallet@128w.png │ ├── subwallet@512w.png │ ├── talisman@128w.png │ └── talisman@512w.png ├── docs ├── .nojekyll ├── assets │ ├── highlight.css │ ├── icons.js │ ├── icons.svg │ ├── main.js │ ├── navigation.js │ ├── search.js │ └── style.css ├── enums │ ├── AssetType.html │ ├── SubstrateExplorer.html │ ├── SubstrateWalletPlatform.html │ └── UseInkathonErrorCode.html ├── functions │ ├── UseInkathonProvider.html │ ├── accountArraysAreEqual.html │ ├── accountsAreEqual.html │ ├── checkIfBalanceSufficient.html │ ├── contractCallDryRun.html │ ├── contractQuery.html │ ├── contractTx.html │ ├── decodeOutput.html │ ├── deployContract.html │ ├── enableWallet.html │ ├── formatBalance.html │ ├── getAbiMessage.html │ ├── getBalance.html │ ├── getDeployment.html │ ├── getDeploymentContract.html │ ├── getExtrinsicErrorMessage.html │ ├── getGasLimit.html │ ├── getMaxGasLimit.html │ ├── getNightlyConnectAdapter.html │ ├── getPSP22Balances.html │ ├── getSubstrateChain.html │ ├── getSubstrateWallet.html │ ├── getWebsiteIcon.html │ ├── initPolkadotJs.html │ ├── isWalletInstalled.html │ ├── parsePSP22Balance.html │ ├── registerDeployment.html │ ├── registerDeployments.html │ ├── transferBalance.html │ ├── transferFullBalance.html │ ├── unwrapResultOrDefault.html │ ├── unwrapResultOrError.html │ ├── useBalance.html │ ├── useContract.html │ ├── useInkathon.html │ ├── usePSP22Balances.html │ ├── useRegisteredContract.html │ ├── useRegisteredTypedContract.html │ ├── watchBalance.html │ └── watchPSP22Balances.html ├── index.html ├── interfaces │ ├── ChainAsset.html │ ├── DeployedContract.html │ ├── SubstrateChain.html │ ├── SubstrateDeployment.html │ ├── SubstrateWallet.html │ ├── UseInkathonError.html │ └── UseInkathonProviderProps.html ├── media │ └── inkathon-devtooling-banner.png ├── modules.html ├── types │ ├── BalanceData.html │ ├── BalanceFormatterOptions.html │ ├── ContractTxResult.html │ ├── ExstrinsicThrowErrorMessage.html │ ├── PSP22BalanceData.html │ ├── PolkadotBalanceFormatterOptions.html │ ├── TokenData.html │ ├── TransferBalanceResult.html │ ├── TypechainContractConstructor.html │ └── UseInkathonProviderContextType.html └── variables │ ├── PSP22_TOKEN_BALANCE_SUBSCRIPTION_INTERVAL.html │ ├── agungTestnet.html │ ├── alephzero.html │ ├── alephzeroSigner.html │ ├── alephzeroTestnet.html │ ├── allPSP22Assets.html │ ├── allSubstrateChains.html │ ├── allSubstrateWallets.html │ ├── amplitude.html │ ├── amplitudeTestnet.html │ ├── astar.html │ ├── bitCountryAlphaTestnet.html │ ├── contracts.html │ ├── development.html │ ├── khala.html │ ├── nightly.html │ ├── nightlyConnect.html │ ├── nova.html │ ├── pendulum.html │ ├── phala.html │ ├── phalaPOC6Testnet.html │ ├── polkadotjs.html │ ├── popNetworkTestnet.html │ ├── psp22Abi.html │ ├── rococo.html │ ├── shibuya.html │ ├── shiden.html │ ├── subwallet.html │ ├── t0rnTestnet.html │ ├── talisman.html │ ├── ternoa.html │ └── ternoaAlphanet.html ├── examples ├── react-ts │ ├── .eslintrc.cjs │ ├── .gitignore │ ├── README.md │ ├── favicon.ico │ ├── index.html │ ├── package.json │ ├── pnpm-lock.yaml │ ├── src │ │ ├── App.tsx │ │ ├── components │ │ │ ├── ConnectionSettings.tsx │ │ │ ├── ConnectionStatus.tsx │ │ │ └── TransferDialog.tsx │ │ ├── main.tsx │ │ ├── styles │ │ │ ├── pico.min.css │ │ │ └── pico.min.css.map │ │ └── vite-env.d.ts │ ├── tsconfig.json │ ├── tsconfig.node.json │ └── vite.config.ts └── scripts-ts │ ├── .eslintrc.cjs │ ├── .gitignore │ ├── README.md │ ├── package.json │ ├── pnpm-lock.yaml │ ├── src │ ├── index.ts │ └── utils │ │ └── initPolkadotJs.ts │ └── tsconfig.json ├── inkathon-devtooling-banner.png ├── package.json ├── pnpm-lock.yaml ├── pnpm-workspace.yaml ├── src ├── assets.ts ├── chains.ts ├── data │ └── PSP22_ASSETS.json ├── helpers │ ├── accountsAreEqual.ts │ ├── checkIfBalanceSufficient.ts │ ├── contractCall.ts │ ├── decodeOutput.ts │ ├── deployContract.ts │ ├── formatBalance.ts │ ├── getAbi.ts │ ├── getAbiMessage.ts │ ├── getBalance.ts │ ├── getDeployment.ts │ ├── getExtrinsicErrorMessage.ts │ ├── getGasLimit.ts │ ├── getNightlyAdapter.ts │ ├── getPSP22Balances.ts │ ├── getWebsiteIcon.ts │ ├── index.ts │ ├── initPolkadotJs.ts │ ├── transferBalance.ts │ └── unwrapResult.ts ├── hooks │ ├── index.ts │ ├── useBalance.ts │ ├── useContract.ts │ ├── usePSP22Balances.ts │ ├── useRegisteredContract.ts │ └── useRegisteredTypedContract.ts ├── index.ts ├── metadata │ └── psp22.json ├── provider.tsx ├── registry.ts ├── types │ ├── ChainAsset.ts │ ├── DeployedContract.ts │ ├── SubstrateChain.ts │ ├── SubstrateDeployment.ts │ ├── SubstrateWallet.ts │ ├── TypechainContractConstructor.ts │ ├── UseInkathonProviderContext.ts │ └── index.ts └── wallets.ts ├── tsconfig.json └── tsup.config.ts /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@2.3.1/schema.json", 3 | "changelog": ["@changesets/changelog-github", { "repo": "scio-labs/use-inkathon" }], 4 | "commit": true, 5 | "fixed": [], 6 | "linked": [], 7 | "access": "restricted", 8 | "baseBranch": "main", 9 | "updateInternalDependencies": "patch", 10 | "ignore": ["react-ts-example", "scripts-ts-example"] 11 | } 12 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | docs -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "plugins": ["@typescript-eslint"], 5 | "extends": [ 6 | "eslint:recommended", 7 | "plugin:@typescript-eslint/eslint-recommended", 8 | "plugin:@typescript-eslint/recommended", 9 | "plugin:react/recommended", 10 | "prettier" 11 | ], 12 | "rules": { 13 | "no-async-promise-executor": "off", 14 | "react/react-in-jsx-scope": "off", 15 | "@typescript-eslint/no-explicit-any": "off", 16 | "@typescript-eslint/no-restricted-imports": [ 17 | "error", 18 | { 19 | "paths": [ 20 | { 21 | "name": ".", 22 | "message": "Please don't import from global index and use full paths instead." 23 | }, 24 | { 25 | "name": "..", 26 | "message": "Please don't import from global index and use full paths instead." 27 | }, 28 | { 29 | "name": "@/", 30 | "message": "Please don't import from global index and use full paths instead." 31 | } 32 | ] 33 | } 34 | ] 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /.github/workflows/add-to-project.yml: -------------------------------------------------------------------------------- 1 | # yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json 2 | name: Add issue or pull request to project 3 | 4 | on: 5 | issues: 6 | types: 7 | - opened 8 | - reopened 9 | # pull_request: 10 | # types: 11 | # - opened 12 | # - reopened 13 | 14 | jobs: 15 | add-to-project: 16 | name: Add issue or pull request to project 17 | runs-on: ubuntu-latest 18 | steps: 19 | - uses: actions/add-to-project@v0.5.0 20 | with: 21 | project-url: https://github.com/orgs/scio-labs/projects/3 22 | github-token: ${{ secrets.ADD_TO_PROJECT_PAT }} 23 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | # yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json 2 | name: Release 3 | 4 | on: 5 | push: 6 | branches: 7 | - main 8 | 9 | concurrency: ${{ github.workflow }}-${{ github.ref }} 10 | 11 | jobs: 12 | release: 13 | name: Release 14 | runs-on: ubuntu-latest 15 | strategy: 16 | matrix: 17 | node-version: [20] 18 | pnpm-version: [9.6.0] 19 | 20 | steps: 21 | - name: Checkout 22 | uses: actions/checkout@v4 23 | with: 24 | # This makes Actions fetch all Git history so that Changesets can generate changelogs with the correct commits 25 | fetch-depth: 0 26 | submodules: true 27 | 28 | - name: Set up pnpm action ${{ matrix.pnpm-version }} 29 | uses: pnpm/action-setup@v4 30 | with: 31 | version: ${{ matrix.pnpm-version }} 32 | 33 | - name: Set up Node ${{ matrix.node-version }} 34 | uses: actions/setup-node@v4 35 | with: 36 | cache: 'pnpm' 37 | node-version: ${{ matrix.node-version }} 38 | 39 | - name: Install Dependencies 40 | run: pnpm install --frozen-lockfile 41 | 42 | - name: Create Release Pull Request 43 | uses: changesets/action@v1 44 | with: 45 | title: 'chore(changeset): Bump version & Update docs' 46 | commit: 'chore(changeset): Bump version & Update docs' 47 | version: pnpm run changeset:version 48 | publish: pnpm run changeset:publish 49 | env: 50 | GITHUB_TOKEN: ${{ secrets.CHANGESETS_PAT }} 51 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 52 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | **/node_modules 3 | /.pnp 4 | .pnp.js 5 | 6 | # yarn 7 | .pnp.* 8 | .yarn/* 9 | yarn.lock 10 | .yarnrc 11 | 12 | # misc 13 | .DS_Store 14 | *.pem 15 | 16 | # debug 17 | npm-debug.log* 18 | yarn-debug.log* 19 | yarn-error.log* 20 | .pnpm-debug.log* 21 | 22 | # local env files 23 | *.env* 24 | !.env*.example 25 | 26 | # vercel 27 | .vercel 28 | .gitsigners 29 | 30 | # typescript 31 | *.tsbuildinfo 32 | dist 33 | 34 | # turbo 35 | .turbo 36 | 37 | # coverage 38 | coverage 39 | coverage.json 40 | 41 | 42 | ### Editors ### 43 | 44 | # VSCode 45 | .vscode/* 46 | !.vscode/settings.json 47 | !.vscode/tasks.json 48 | !.vscode/launch.json 49 | !.vscode/extensions.json 50 | !.vscode/*.code-workspace 51 | .history/ 52 | 53 | # JetBrains 54 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 55 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 56 | 57 | # User-specific stuff 58 | .idea/**/workspace.xml 59 | .idea/**/tasks.xml 60 | .idea/**/usage.statistics.xml 61 | .idea/**/dictionaries 62 | .idea/**/shelf 63 | 64 | # AWS User-specific 65 | .idea/**/aws.xml 66 | 67 | # Generated files 68 | .idea/**/contentModel.xml 69 | 70 | # Sensitive or high-churn files 71 | .idea/**/dataSources/ 72 | .idea/**/dataSources.ids 73 | .idea/**/dataSources.local.xml 74 | .idea/**/sqlDataSources.xml 75 | .idea/**/dynamic.xml 76 | .idea/**/uiDesigner.xml 77 | .idea/**/dbnavigator.xml 78 | 79 | # Gradle 80 | .idea/**/gradle.xml 81 | .idea/**/libraries 82 | 83 | # Gradle and Maven with auto-import 84 | # When using Gradle or Maven with auto-import, you should exclude module files, 85 | # since they will be recreated, and may cause churn. Uncomment if using 86 | # auto-import. 87 | # .idea/artifacts 88 | # .idea/compiler.xml 89 | # .idea/jarRepositories.xml 90 | # .idea/modules.xml 91 | # .idea/*.iml 92 | # .idea/modules 93 | # *.iml 94 | # *.ipr 95 | 96 | # CMake 97 | cmake-build-*/ 98 | 99 | # Mongo Explorer plugin 100 | .idea/**/mongoSettings.xml 101 | 102 | # File-based project format 103 | *.iws 104 | 105 | # IntelliJ 106 | out/ 107 | 108 | # mpeltonen/sbt-idea plugin 109 | .idea_modules/ 110 | 111 | # JIRA plugin 112 | atlassian-ide-plugin.xml 113 | 114 | # Cursive Clojure plugin 115 | .idea/replstate.xml 116 | 117 | # SonarLint plugin 118 | .idea/sonarlint/ 119 | 120 | # Crashlytics plugin (for Android Studio and IntelliJ) 121 | com_crashlytics_export_strings.xml 122 | crashlytics.properties 123 | crashlytics-build.properties 124 | fabric.properties 125 | 126 | # Editor-based Rest Client 127 | .idea/httpRequests 128 | 129 | # Android studio 3.1+ serialized cache file 130 | .idea/caches/build_file_checksums.ser 131 | 132 | ### JetBrains Patch ### 133 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 134 | 135 | # *.iml 136 | # modules.xml 137 | # .idea/misc.xml 138 | # *.ipr 139 | 140 | # Sonarlint plugin 141 | # https://plugins.jetbrains.com/plugin/7973-sonarlint 142 | .idea/**/sonarlint/ 143 | 144 | # SonarQube Plugin 145 | # https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin 146 | .idea/**/sonarIssues.xml 147 | 148 | # Markdown Navigator plugin 149 | # https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced 150 | .idea/**/markdown-navigator.xml 151 | .idea/**/markdown-navigator-enh.xml 152 | .idea/**/markdown-navigator/ 153 | 154 | # Cache file creation bug 155 | # See https://youtrack.jetbrains.com/issue/JBR-2257 156 | .idea/$CACHE_FILE$ 157 | 158 | # CodeStream plugin 159 | # https://plugins.jetbrains.com/plugin/12206-codestream 160 | .idea/codestream.xml 161 | 162 | # Azure Toolkit for IntelliJ plugin 163 | # https://plugins.jetbrains.com/plugin/8053-azure-toolkit-for-intellij 164 | .idea/**/azureSettings.xml 165 | 166 | # Local builds 167 | /*.tgz 168 | /package 169 | 170 | # substrate-contracts-node 171 | .node-data -------------------------------------------------------------------------------- /.lintstagedrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "*.{js,jsx,ts,tsx}": ["pnpm run lint:fix"], 3 | "*.{json,md,mdx,html,css,yml,yaml}": ["pnpm run lint:format"] 4 | } 5 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | init-author-name=Scio Labs 2 | init-author-email=hello@scio.xyz 3 | init-author-url=https://scio.xyz/ 4 | init-license=GPL-3.0 5 | 6 | auto-install-peers=true 7 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v20 -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | package-lock.json 2 | pnpm-lock.yaml 3 | node_modules 4 | LICENSE 5 | dist 6 | docs 7 | src/metadata 8 | .changeset -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": true, 4 | "trailingComma": "all", 5 | "printWidth": 100, 6 | "tabWidth": 2, 7 | "useTabs": false 8 | } 9 | -------------------------------------------------------------------------------- /.simple-git-hooks.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'pre-commit': ` 3 | export NVM_DIR="$HOME/.nvm" 4 | [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" 5 | 6 | pnpm lint-staged 7 | `, 8 | } 9 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["dbaeumer.vscode-eslint", "esbenp.prettier-vscode", "jayfong.generate-index"] 3 | } 4 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "search.exclude": { 3 | "**/*.{jpg,jpeg,png,svg,webm,mp4,ttf,woff,woff2}": true, 4 | "docs": true 5 | }, 6 | "search.useIgnoreFiles": true, 7 | "search.useGlobalIgnoreFiles": true, 8 | "search.useParentIgnoreFiles": true, 9 | "editor.formatOnSave": true, 10 | "editor.codeActionsOnSave": { 11 | "source.organizeImports": "always", 12 | "source.fixAll.eslint": "always" 13 | }, 14 | "[javascriptreact][typescriptreact][javascript][typescript][markdown][html][css][scss][sass][json][jsonc][yaml]": { 15 | "editor.defaultFormatter": "esbenp.prettier-vscode" 16 | }, 17 | "typescript.updateImportsOnFileMove.enabled": "always", 18 | "javascript.updateImportsOnFileMove.enabled": "always", 19 | "javascript.preferences.importModuleSpecifier": "non-relative", 20 | "typescript.preferences.importModuleSpecifier": "non-relative" 21 | } 22 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | [Scio Labs](https://scio.xyz) actively welcomes contributions from anyone to make this a community-driven effort. There are many ways to contribute, from writing tutorials or blog posts, improving the documentation, submitting bug reports and feature requests, or writing code. 4 | 5 | Our ink! developer tooling initiative is made up of the following projects: 6 | 7 | - `create-ink-app` CLI (_Coming soon_) 8 | - [`ink!athon`](https://github.com/scio-labs/inkathon) Boilerplate 9 | - [`useInkathon`](https://github.com/scio-labs/use-inkathon) Hooks & Utility Library 10 | - [`zink!`](https://github.com/scio-labs/zink) Smart Contract Macros 11 | 12 | _To ensure a smooth and efficient collaboration, please follow the guidelines below._ 13 | 14 | ## Issues 15 | 16 | - Before starting work, please check the existing issues for your planned feature or bug fix. 17 | - If there isn't an existing issue, create one. This allows us to avoid duplicate work and discuss the implementation details upfront. 18 | - If you want to work on an issue, please comment on it to let us know. This helps us keep track of who is working on what. 19 | 20 | ## Pull Requests 21 | 22 | - All changes should be made through pull requests (PRs). 23 | - Please ensure your PR is linked to the relevant issue. 24 | - Include a clear and detailed description of the changes in your PR. 25 | - Request a review once your PR is ready. 26 | 27 | ## Code Style 28 | 29 | - Please follow the existing coding style. 30 | - Make sure your changes pass the lint checks. 31 | 32 | > [!IMPORTANT] 33 | > 34 | > - Please install all recommended VSCode extensions (see `.vscode/extensions.json`). 35 | > - After installing dependencies, enable the `pre-commit` hook in your local repository via `pnpm simple-git-hooks`. 36 | 37 | ## Communication 38 | 39 | - Be respectful and considerate in all interactions. 40 | - If you have questions or need help, don't hesitate to ask. 41 | - For code, feature, or bug discussions, please use the relevant GitHub issue or PR. 42 | - To chat with us or ask questions, join our [Telegram Group](https://t.me/inkathon). 💬 43 | 44 | Thanks for your contributions! 🙏 45 | -------------------------------------------------------------------------------- /assets/asset-logos/inw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scio-labs/use-inkathon/496538322521bcb49b454be7bd05ca40ef9e2aaf/assets/asset-logos/inw.png -------------------------------------------------------------------------------- /assets/asset-logos/panx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scio-labs/use-inkathon/496538322521bcb49b454be7bd05ca40ef9e2aaf/assets/asset-logos/panx.png -------------------------------------------------------------------------------- /assets/wallet-logos/aleph-zero-signer@128w.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scio-labs/use-inkathon/496538322521bcb49b454be7bd05ca40ef9e2aaf/assets/wallet-logos/aleph-zero-signer@128w.png -------------------------------------------------------------------------------- /assets/wallet-logos/aleph-zero-signer@512w.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scio-labs/use-inkathon/496538322521bcb49b454be7bd05ca40ef9e2aaf/assets/wallet-logos/aleph-zero-signer@512w.png -------------------------------------------------------------------------------- /assets/wallet-logos/nightly@128w.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scio-labs/use-inkathon/496538322521bcb49b454be7bd05ca40ef9e2aaf/assets/wallet-logos/nightly@128w.png -------------------------------------------------------------------------------- /assets/wallet-logos/nightly@512w.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scio-labs/use-inkathon/496538322521bcb49b454be7bd05ca40ef9e2aaf/assets/wallet-logos/nightly@512w.png -------------------------------------------------------------------------------- /assets/wallet-logos/nightlyConnect@128w.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scio-labs/use-inkathon/496538322521bcb49b454be7bd05ca40ef9e2aaf/assets/wallet-logos/nightlyConnect@128w.png -------------------------------------------------------------------------------- /assets/wallet-logos/nightlyConnect@512w.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scio-labs/use-inkathon/496538322521bcb49b454be7bd05ca40ef9e2aaf/assets/wallet-logos/nightlyConnect@512w.png -------------------------------------------------------------------------------- /assets/wallet-logos/nova@128w.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scio-labs/use-inkathon/496538322521bcb49b454be7bd05ca40ef9e2aaf/assets/wallet-logos/nova@128w.png -------------------------------------------------------------------------------- /assets/wallet-logos/nova@512w.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scio-labs/use-inkathon/496538322521bcb49b454be7bd05ca40ef9e2aaf/assets/wallet-logos/nova@512w.png -------------------------------------------------------------------------------- /assets/wallet-logos/polkadot@128w.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scio-labs/use-inkathon/496538322521bcb49b454be7bd05ca40ef9e2aaf/assets/wallet-logos/polkadot@128w.png -------------------------------------------------------------------------------- /assets/wallet-logos/polkadot@512w.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scio-labs/use-inkathon/496538322521bcb49b454be7bd05ca40ef9e2aaf/assets/wallet-logos/polkadot@512w.png -------------------------------------------------------------------------------- /assets/wallet-logos/subwallet@128w.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scio-labs/use-inkathon/496538322521bcb49b454be7bd05ca40ef9e2aaf/assets/wallet-logos/subwallet@128w.png -------------------------------------------------------------------------------- /assets/wallet-logos/subwallet@512w.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scio-labs/use-inkathon/496538322521bcb49b454be7bd05ca40ef9e2aaf/assets/wallet-logos/subwallet@512w.png -------------------------------------------------------------------------------- /assets/wallet-logos/talisman@128w.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scio-labs/use-inkathon/496538322521bcb49b454be7bd05ca40ef9e2aaf/assets/wallet-logos/talisman@128w.png -------------------------------------------------------------------------------- /assets/wallet-logos/talisman@512w.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scio-labs/use-inkathon/496538322521bcb49b454be7bd05ca40ef9e2aaf/assets/wallet-logos/talisman@512w.png -------------------------------------------------------------------------------- /docs/.nojekyll: -------------------------------------------------------------------------------- 1 | TypeDoc added this file to prevent GitHub Pages from using Jekyll. You can turn off this behavior by setting the `githubPages` option to false. -------------------------------------------------------------------------------- /docs/assets/highlight.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --light-hl-0: #795E26; 3 | --dark-hl-0: #DCDCAA; 4 | --light-hl-1: #000000; 5 | --dark-hl-1: #D4D4D4; 6 | --light-hl-2: #A31515; 7 | --dark-hl-2: #CE9178; 8 | --light-hl-3: #008000; 9 | --dark-hl-3: #6A9955; 10 | --light-hl-4: #AF00DB; 11 | --dark-hl-4: #C586C0; 12 | --light-hl-5: #001080; 13 | --dark-hl-5: #9CDCFE; 14 | --light-hl-6: #800000; 15 | --dark-hl-6: #808080; 16 | --light-hl-7: #267F99; 17 | --dark-hl-7: #4EC9B0; 18 | --light-hl-8: #E50000; 19 | --dark-hl-8: #9CDCFE; 20 | --light-hl-9: #0000FF; 21 | --dark-hl-9: #569CD6; 22 | --light-hl-10: #0070C1; 23 | --dark-hl-10: #4FC1FF; 24 | --light-hl-11: #000000FF; 25 | --dark-hl-11: #D4D4D4; 26 | --light-hl-12: #800000; 27 | --dark-hl-12: #569CD6; 28 | --light-code-background: #FFFFFF; 29 | --dark-code-background: #1E1E1E; 30 | } 31 | 32 | @media (prefers-color-scheme: light) { :root { 33 | --hl-0: var(--light-hl-0); 34 | --hl-1: var(--light-hl-1); 35 | --hl-2: var(--light-hl-2); 36 | --hl-3: var(--light-hl-3); 37 | --hl-4: var(--light-hl-4); 38 | --hl-5: var(--light-hl-5); 39 | --hl-6: var(--light-hl-6); 40 | --hl-7: var(--light-hl-7); 41 | --hl-8: var(--light-hl-8); 42 | --hl-9: var(--light-hl-9); 43 | --hl-10: var(--light-hl-10); 44 | --hl-11: var(--light-hl-11); 45 | --hl-12: var(--light-hl-12); 46 | --code-background: var(--light-code-background); 47 | } } 48 | 49 | @media (prefers-color-scheme: dark) { :root { 50 | --hl-0: var(--dark-hl-0); 51 | --hl-1: var(--dark-hl-1); 52 | --hl-2: var(--dark-hl-2); 53 | --hl-3: var(--dark-hl-3); 54 | --hl-4: var(--dark-hl-4); 55 | --hl-5: var(--dark-hl-5); 56 | --hl-6: var(--dark-hl-6); 57 | --hl-7: var(--dark-hl-7); 58 | --hl-8: var(--dark-hl-8); 59 | --hl-9: var(--dark-hl-9); 60 | --hl-10: var(--dark-hl-10); 61 | --hl-11: var(--dark-hl-11); 62 | --hl-12: var(--dark-hl-12); 63 | --code-background: var(--dark-code-background); 64 | } } 65 | 66 | :root[data-theme='light'] { 67 | --hl-0: var(--light-hl-0); 68 | --hl-1: var(--light-hl-1); 69 | --hl-2: var(--light-hl-2); 70 | --hl-3: var(--light-hl-3); 71 | --hl-4: var(--light-hl-4); 72 | --hl-5: var(--light-hl-5); 73 | --hl-6: var(--light-hl-6); 74 | --hl-7: var(--light-hl-7); 75 | --hl-8: var(--light-hl-8); 76 | --hl-9: var(--light-hl-9); 77 | --hl-10: var(--light-hl-10); 78 | --hl-11: var(--light-hl-11); 79 | --hl-12: var(--light-hl-12); 80 | --code-background: var(--light-code-background); 81 | } 82 | 83 | :root[data-theme='dark'] { 84 | --hl-0: var(--dark-hl-0); 85 | --hl-1: var(--dark-hl-1); 86 | --hl-2: var(--dark-hl-2); 87 | --hl-3: var(--dark-hl-3); 88 | --hl-4: var(--dark-hl-4); 89 | --hl-5: var(--dark-hl-5); 90 | --hl-6: var(--dark-hl-6); 91 | --hl-7: var(--dark-hl-7); 92 | --hl-8: var(--dark-hl-8); 93 | --hl-9: var(--dark-hl-9); 94 | --hl-10: var(--dark-hl-10); 95 | --hl-11: var(--dark-hl-11); 96 | --hl-12: var(--dark-hl-12); 97 | --code-background: var(--dark-code-background); 98 | } 99 | 100 | .hl-0 { color: var(--hl-0); } 101 | .hl-1 { color: var(--hl-1); } 102 | .hl-2 { color: var(--hl-2); } 103 | .hl-3 { color: var(--hl-3); } 104 | .hl-4 { color: var(--hl-4); } 105 | .hl-5 { color: var(--hl-5); } 106 | .hl-6 { color: var(--hl-6); } 107 | .hl-7 { color: var(--hl-7); } 108 | .hl-8 { color: var(--hl-8); } 109 | .hl-9 { color: var(--hl-9); } 110 | .hl-10 { color: var(--hl-10); } 111 | .hl-11 { color: var(--hl-11); } 112 | .hl-12 { color: var(--hl-12); } 113 | pre, code { background: var(--code-background); } 114 | -------------------------------------------------------------------------------- /docs/assets/navigation.js: -------------------------------------------------------------------------------- 1 | window.navigationData = "data:application/octet-stream;base64,H4sIAAAAAAAAA5WZUXPiNhDHv4ufM82V9tI2b4RwHdocUODuHm46GWEvWEVIqiQncJ1+9w7GjiV5vXbfMvn/96f1Spbk5es/iYOTS+6TsbXgNmcNyU2imcuT+wRkcbS3b8J3uTuK5CY5cJkl9z//e/MWuy621hnmYHrSQhkwMaNlGMD6woQAtxTM7ZQ5dhJDWzf3k4WZPDCXKzk1RpmJylqPinm6iZOccVlWp+Fw6cDsWAr2tpFDxOj9nQd5BC3UGbKJks6wFEfFJgr4VphyfBQXWgbBrhkcQeIJIr5B2Ovk0cirh8LF04byYtNA4NKoF56BWRqlbR84MFMDPDDBZAqPzLGG6c4a7K0nRYR3v/z0/ftRm/JBmSNzDsxCO66k7SDGtj56vdg2pxXYQrgYG+t9vOnJOsOl5ekmN+q1nISPYC3bQ4wmrH2jLNfL0Ygob6z38pQ4sEy5gbXusfeNtlEHkFjab0IvwTBpd2CqBPCpQ0295LOG9LJh1BM/UdI6U6TOf+OqAQhv3zjI+3ShwCk6na4j0e5Bq+V5s/h9On9+GD+N55Pp8/rTw3qymi03s8X8eTbfTFefx0/NsC/McLYV9VIaEhxm8YOfANsXcr8B66S/DTZj+DqFEaDzb2AUyqjFIYA130v//EYwV8sQGPVgkYfEibLU5VlqcZjvoFHh4deFi1wDkdejqpdZ2SjoUQvuCv+G4qFqcQiAmoLIQ+GsY/iiuAhE4Ja7iSqkM+ex0DkjssGdBDqttha03G8iAcjgBYTS4aWmQXgyATnkTDAsvBSIQMn3uRNnLLSS+oMnSkpI0exDB4VSL2j+l/8TYRpkVojiiIXWGhXeVTbdU7ZSXy4md8RSij0Urjqv/0JXUaOSCD0H96rMgUopNlFAq0ej8ZajnEojwo1KVYoeBFclDr1JUsGsLV8IbSBlDrLE49mcb4szOlmVRCRjc56B7IjNQFKhxfY1+jzwomuRALh3RhJT4skUhAlujwx9hFqjwsFIhdbuqvSGlpth1xMEDgKFXJIa3q6QaXk5xe5SIfTuR/9MSNPLfj02hp3t2MD074IJDIsa+8FDmENwaQ7pYbar7rrrYrfjKQ/2/Abb5aXw9Q2XCfFozqtCouCWawDyjwLMmaKVhgGgzYmibE4EIoNUZbAonC7Qivk6ibn0BdpNDh/kOwgUyMvqjzsHDcjXCcyu/DSrZhrjBAYCtAc33vLWl2wDCgw0iEinUWkE1qkJKF0tmm4QNW2okQZPT9XHPd4CCNiol8b/yuwTP/KubGuZhnxkpx6O56BR8+AmNs6Ydvj22+Wl8X5Dw3ZgAw+N62ofBjyqgdgJ7H5j2y4a+QW2ljuYpaorQc9BoLjkrm7Y/IbWLnRQKHtNfCatu/yRobTYRAA1Mxb8ecOALRMBNLDn1oGh94e2638h0SIiNgLqwt4UBowsA2AfCiEGAD0bAS3kq2H62jRbmEfYsaDF1mBR42Bw1MruwiLN7BBqgXjyRqUR1BHgyTSkvlx2QGqZhvTud7GHxq2qxYn9AhMw28ah4Esjcjg9cBNDvDKX5sTM+nofprembVcL+ed/WyqzsUscAAA=" -------------------------------------------------------------------------------- /docs/media/inkathon-devtooling-banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scio-labs/use-inkathon/496538322521bcb49b454be7bd05ca40ef9e2aaf/docs/media/inkathon-devtooling-banner.png -------------------------------------------------------------------------------- /docs/variables/agungTestnet.html: -------------------------------------------------------------------------------- 1 | agungTestnet | @scio-labs/use-inkathon

Variable agungTestnetConst

agungTestnet: SubstrateChain = ...
2 | -------------------------------------------------------------------------------- /docs/variables/alephzero.html: -------------------------------------------------------------------------------- 1 | alephzero | @scio-labs/use-inkathon

Variable alephzeroConst

alephzero: SubstrateChain = ...

Live Mainnet Networks

2 |
3 | -------------------------------------------------------------------------------- /docs/variables/alephzeroSigner.html: -------------------------------------------------------------------------------- 1 | alephzeroSigner | @scio-labs/use-inkathon

Variable alephzeroSignerConst

alephzeroSigner: SubstrateWallet = ...
2 | -------------------------------------------------------------------------------- /docs/variables/amplitude.html: -------------------------------------------------------------------------------- 1 | amplitude | @scio-labs/use-inkathon

Variable amplitudeConst

amplitude: SubstrateChain = ...
2 | -------------------------------------------------------------------------------- /docs/variables/amplitudeTestnet.html: -------------------------------------------------------------------------------- 1 | amplitudeTestnet | @scio-labs/use-inkathon

Variable amplitudeTestnetConst

amplitudeTestnet: SubstrateChain = ...
2 | -------------------------------------------------------------------------------- /docs/variables/astar.html: -------------------------------------------------------------------------------- 1 | astar | @scio-labs/use-inkathon

Variable astarConst

astar: SubstrateChain = ...
2 | -------------------------------------------------------------------------------- /docs/variables/bitCountryAlphaTestnet.html: -------------------------------------------------------------------------------- 1 | bitCountryAlphaTestnet | @scio-labs/use-inkathon

Variable bitCountryAlphaTestnetConst

bitCountryAlphaTestnet: SubstrateChain = ...
2 | -------------------------------------------------------------------------------- /docs/variables/contracts.html: -------------------------------------------------------------------------------- 1 | contracts | @scio-labs/use-inkathon

Variable contractsConst

contracts: SubstrateChain = ...
2 | -------------------------------------------------------------------------------- /docs/variables/development.html: -------------------------------------------------------------------------------- 1 | development | @scio-labs/use-inkathon

Variable developmentConst

development: SubstrateChain = ...

Local Development Network

2 |
3 | -------------------------------------------------------------------------------- /docs/variables/khala.html: -------------------------------------------------------------------------------- 1 | khala | @scio-labs/use-inkathon

Variable khalaConst

khala: SubstrateChain = ...
2 | -------------------------------------------------------------------------------- /docs/variables/nightly.html: -------------------------------------------------------------------------------- 1 | nightly | @scio-labs/use-inkathon

Variable nightlyConst

nightly: SubstrateWallet = ...
2 | -------------------------------------------------------------------------------- /docs/variables/nightlyConnect.html: -------------------------------------------------------------------------------- 1 | nightlyConnect | @scio-labs/use-inkathon

Variable nightlyConnectConst

nightlyConnect: SubstrateWallet = ...
2 | -------------------------------------------------------------------------------- /docs/variables/nova.html: -------------------------------------------------------------------------------- 1 | nova | @scio-labs/use-inkathon

Variable novaConst

nova: SubstrateWallet = ...
2 | -------------------------------------------------------------------------------- /docs/variables/pendulum.html: -------------------------------------------------------------------------------- 1 | pendulum | @scio-labs/use-inkathon

Variable pendulumConst

pendulum: SubstrateChain = ...
2 | -------------------------------------------------------------------------------- /docs/variables/phala.html: -------------------------------------------------------------------------------- 1 | phala | @scio-labs/use-inkathon

Variable phalaConst

phala: SubstrateChain = ...
2 | -------------------------------------------------------------------------------- /docs/variables/phalaPOC6Testnet.html: -------------------------------------------------------------------------------- 1 | phalaPOC6Testnet | @scio-labs/use-inkathon

Variable phalaPOC6TestnetConst

phalaPOC6Testnet: SubstrateChain = ...
2 | -------------------------------------------------------------------------------- /docs/variables/popNetworkTestnet.html: -------------------------------------------------------------------------------- 1 | popNetworkTestnet | @scio-labs/use-inkathon

Variable popNetworkTestnetConst

popNetworkTestnet: SubstrateChain = ...
2 | -------------------------------------------------------------------------------- /docs/variables/shibuya.html: -------------------------------------------------------------------------------- 1 | shibuya | @scio-labs/use-inkathon

Variable shibuyaConst

shibuya: SubstrateChain = ...
2 | -------------------------------------------------------------------------------- /docs/variables/shiden.html: -------------------------------------------------------------------------------- 1 | shiden | @scio-labs/use-inkathon

Variable shidenConst

shiden: SubstrateChain = ...

Live Canary Networks

2 |
3 | -------------------------------------------------------------------------------- /docs/variables/subwallet.html: -------------------------------------------------------------------------------- 1 | subwallet | @scio-labs/use-inkathon

Variable subwalletConst

subwallet: SubstrateWallet = ...
2 | -------------------------------------------------------------------------------- /docs/variables/t0rnTestnet.html: -------------------------------------------------------------------------------- 1 | t0rnTestnet | @scio-labs/use-inkathon

Variable t0rnTestnetConst

t0rnTestnet: SubstrateChain = ...
2 | -------------------------------------------------------------------------------- /docs/variables/talisman.html: -------------------------------------------------------------------------------- 1 | talisman | @scio-labs/use-inkathon

Variable talismanConst

talisman: SubstrateWallet = ...
2 | -------------------------------------------------------------------------------- /docs/variables/ternoa.html: -------------------------------------------------------------------------------- 1 | ternoa | @scio-labs/use-inkathon

Variable ternoaConst

ternoa: SubstrateChain = ...
2 | -------------------------------------------------------------------------------- /docs/variables/ternoaAlphanet.html: -------------------------------------------------------------------------------- 1 | ternoaAlphanet | @scio-labs/use-inkathon

Variable ternoaAlphanetConst

ternoaAlphanet: SubstrateChain = ...
2 | -------------------------------------------------------------------------------- /examples/react-ts/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { browser: true, es2020: true }, 4 | extends: [ 5 | 'eslint:recommended', 6 | 'plugin:@typescript-eslint/recommended', 7 | 'plugin:react-hooks/recommended', 8 | 'plugin:react/recommended', 9 | 'prettier', 10 | ], 11 | ignorePatterns: ['dist', '.eslintrc.cjs'], 12 | parser: '@typescript-eslint/parser', 13 | plugins: ['react-refresh'], 14 | rules: { 15 | 'react-refresh/only-export-components': ['warn', { allowConstantExport: true }], 16 | 'no-async-promise-executor': 'off', 17 | 'react/react-in-jsx-scope': 'off', 18 | '@typescript-eslint/no-explicit-any': 'off', 19 | }, 20 | } 21 | -------------------------------------------------------------------------------- /examples/react-ts/.gitignore: -------------------------------------------------------------------------------- 1 | # system files 2 | .DS_Store 3 | Thumbs.db 4 | *.pem 5 | 6 | # log files 7 | *.log 8 | 9 | # node modules 10 | node_modules/ 11 | 12 | # environment files 13 | *.env* 14 | !.env*.example 15 | 16 | # build files 17 | dist 18 | dist-ssr 19 | *.local 20 | -------------------------------------------------------------------------------- /examples/react-ts/README.md: -------------------------------------------------------------------------------- 1 | # React Example 2 | 3 | ![Vite](https://img.shields.io/badge/Vite-000000?logo=vite&logoColor=white) 4 | ![React](https://img.shields.io/badge/React-000000?logo=react&logoColor=white) 5 | ![TypeScript](https://img.shields.io/badge/TypeScript-000000?logo=typescript&logoColor=white) 6 | 7 | This is a small vanilla React example app setup showcasing `@scio-labs/use-inkathon`. 8 | 9 | ## Usage 10 | 11 | ```bash 12 | pnpm install 13 | pnpm run dev 14 | 15 | # Or compile & watch the package and run the example in parallel: 16 | # NOTE: Run this from the root directory 17 | pnpm run dev:react-example 18 | ``` 19 | -------------------------------------------------------------------------------- /examples/react-ts/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scio-labs/use-inkathon/496538322521bcb49b454be7bd05ca40ef9e2aaf/examples/react-ts/favicon.ico -------------------------------------------------------------------------------- /examples/react-ts/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | React Example 7 | 8 | 9 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /examples/react-ts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-ts-example", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite --port 3000", 8 | "build": "tsc && vite build", 9 | "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", 10 | "preview": "vite preview" 11 | }, 12 | "dependencies": { 13 | "@polkadot/api": "^12.2.3", 14 | "@polkadot/api-contract": "^12.2.3", 15 | "@polkadot/util-crypto": "^13.0.2", 16 | "@scio-labs/use-inkathon": "workspace:*", 17 | "react": "^18.3.1", 18 | "react-dom": "^18.3.1" 19 | }, 20 | "devDependencies": { 21 | "@types/react": "^18.3.3", 22 | "@types/react-dom": "^18.3.0", 23 | "@typescript-eslint/eslint-plugin": "^7.18.0", 24 | "@typescript-eslint/parser": "^7.18.0", 25 | "@vitejs/plugin-react-swc": "^3.7.0", 26 | "eslint": "^8.57.0", 27 | "eslint-config-prettier": "^9.1.0", 28 | "eslint-plugin-react": "^7.35.0", 29 | "eslint-plugin-react-hooks": "^4.6.2", 30 | "eslint-plugin-react-refresh": "^0.4.9", 31 | "prettier": "^3.3.3", 32 | "typescript": "^5.5.4", 33 | "vite": "^5.3.5" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /examples/react-ts/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { ConnectionSettings } from './components/ConnectionSettings' 2 | import { ConnectionStatus } from './components/ConnectionStatus' 3 | 4 | export default function App() { 5 | return ( 6 | <> 7 |
8 |
9 |

React Example

10 |

11 | This is a small vanilla React app showcasing @scio-labs/use-inkathon. 12 |

13 |
14 |
15 | 16 |
17 |
18 | {/* Connection Settings (pick network & wallet) */} 19 | 20 | 21 | {/* Connection status & Disconnect */} 22 | 23 |
24 |
25 | 26 | ) 27 | } 28 | -------------------------------------------------------------------------------- /examples/react-ts/src/components/ConnectionSettings.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | allSubstrateChains, 3 | allSubstrateWallets, 4 | getSubstrateChain, 5 | getSubstrateWallet, 6 | useInkathon, 7 | } from '@scio-labs/use-inkathon' 8 | import { useState } from 'react' 9 | 10 | export function ConnectionSettings() { 11 | const { connect } = useInkathon() 12 | const [isLoading, setIsLoading] = useState(false) 13 | 14 | const handleConnect = async () => { 15 | setIsLoading(true) 16 | const selectedNetwork = (document.getElementById('select-network') as HTMLSelectElement)?.value 17 | const selectedWallet = (document.getElementById('select-wallet') as HTMLSelectElement)?.value 18 | const substrateChain = getSubstrateChain(selectedNetwork) 19 | const substrateWallet = getSubstrateWallet(selectedWallet) 20 | console.log('Connection settings:', { substrateChain, substrateWallet }) 21 | 22 | await connect?.(substrateChain, substrateWallet) 23 | setIsLoading(false) 24 | } 25 | 26 | return ( 27 |
28 |

Connection Settings

29 | 30 |
31 | {/* Network list */} 32 | 33 | 40 | 41 | {/* Wallet list */} 42 | 43 | 51 |
52 | 53 | {/* Connect button */} 54 | 57 |
58 | ) 59 | } 60 | -------------------------------------------------------------------------------- /examples/react-ts/src/components/ConnectionStatus.tsx: -------------------------------------------------------------------------------- 1 | import { useBalance, useInkathon } from '@scio-labs/use-inkathon' 2 | import { useEffect, useState } from 'react' 3 | import { TransferDialog } from './TransferDialog' 4 | 5 | export function ConnectionStatus() { 6 | const { api, error, isConnected, activeChain, activeAccount, disconnect } = useInkathon() 7 | 8 | // Fetch & watch balance 9 | const { balanceFormatted } = useBalance(activeAccount?.address, true) 10 | 11 | // Check whether the connected chain has pallet-contracts 12 | const [hasPalletContracts, setHasPalletContracts] = useState(undefined) 13 | useEffect(() => { 14 | const getPalletVersion = api?.query?.contracts?.palletVersion 15 | setHasPalletContracts(!!getPalletVersion) 16 | }, [api]) 17 | 18 | return ( 19 |
20 |

Status

21 | 22 |

23 | {isConnected ? ( 24 | 25 | Connected 26 | 27 | ) : error?.message ? ( 28 | 29 | {error.message} 30 | 31 | ) : ( 32 | Disconnected 33 | )} 34 |

35 | 36 | {isConnected && ( 37 | <> 38 | {/* Chain */} 39 | Network 40 | 41 |

42 | {activeChain?.name}{' '} 43 | {!hasPalletContracts && ( 44 | (pallet-contracts not found) 45 | )} 46 |

47 |
48 | 49 | {/* Wallet Address */} 50 | Account 51 | 52 |

{activeAccount?.address}

53 |
54 | 55 | {/* Balance */} 56 | Balance 57 | 58 |

{balanceFormatted}

59 |
60 | 61 |
62 | {/* Transfer dialog */} 63 | 64 | 65 | {/* Disconnect button */} 66 | 69 |
70 | 71 | )} 72 |
73 | ) 74 | } 75 | -------------------------------------------------------------------------------- /examples/react-ts/src/components/TransferDialog.tsx: -------------------------------------------------------------------------------- 1 | import { BN } from '@polkadot/util' 2 | import { checkAddress } from '@polkadot/util-crypto' 3 | import { formatBalance, transferBalance, useBalance, useInkathon } from '@scio-labs/use-inkathon' 4 | import { useState } from 'react' 5 | 6 | export function TransferDialog() { 7 | const [isOpen, setIsOpen] = useState(false) 8 | 9 | return ( 10 | <> 11 | {/* Dialog Trigger */} 12 | 15 | 16 | {/* Transfer Dialog */} 17 | 18 | 30 | 31 | 32 | ) 33 | } 34 | 35 | interface TransferDialogFormProps { 36 | onCloseDialog: () => void 37 | } 38 | function TransferDialogForm({ onCloseDialog }: TransferDialogFormProps) { 39 | const { api, activeAccount } = useInkathon() 40 | const { tokenSymbol, tokenDecimals, reducibleBalance } = useBalance(activeAccount?.address, true) 41 | const [isLoading, setIsLoading] = useState(false) 42 | 43 | const handleConfirm = async () => { 44 | if (!api || !activeAccount || !reducibleBalance) return 45 | setIsLoading(true) 46 | 47 | try { 48 | // Determine transfer parameters 49 | const transferAddress = (document.getElementById('transfer-address') as HTMLInputElement) 50 | ?.value 51 | const prefix = api?.registry.chainSS58 || 42 52 | if (!transferAddress && !checkAddress(transferAddress, prefix)[0]) { 53 | throw new Error('Invalid address. Aborting.') 54 | } 55 | 56 | const transferAmount = parseFloat( 57 | (document.getElementById('transfer-amount') as HTMLInputElement)?.value, 58 | ) 59 | if (!transferAmount || transferAmount <= 0) { 60 | throw new Error('Invalid amount. Aborting.') 61 | } 62 | 63 | // Convert value to BN with chain's decimals 64 | const precision = 5 65 | const amount = new BN(transferAmount * 10 ** precision).mul( 66 | new BN(10).pow(new BN(tokenDecimals - precision)), 67 | ) 68 | const amountFormatted = formatBalance(api, amount) 69 | 70 | // Perform transfer 71 | await transferBalance(api, activeAccount.address, transferAddress, amount) 72 | console.log(`Successfully transferred ${amountFormatted} to ${transferAddress}.`) 73 | onCloseDialog() 74 | } catch (e) { 75 | console.error(e) 76 | } finally { 77 | setIsLoading(false) 78 | } 79 | } 80 | 81 | return ( 82 | <> 83 | {/* Transfer form */} 84 |
85 | 95 | 96 | 107 |
108 | {/* Footer */} 109 |
110 | 113 | 116 |
117 | 118 | ) 119 | } 120 | -------------------------------------------------------------------------------- /examples/react-ts/src/main.tsx: -------------------------------------------------------------------------------- 1 | import { UseInkathonProvider, alephzeroTestnet } from '@scio-labs/use-inkathon' 2 | import React from 'react' 3 | import ReactDOM from 'react-dom/client' 4 | import App from './App.tsx' 5 | 6 | ReactDOM.createRoot(document.getElementById('root')!).render( 7 | 8 | 13 | 14 | 15 | , 16 | ) 17 | -------------------------------------------------------------------------------- /examples/react-ts/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /examples/react-ts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "module": "ESNext", 5 | "target": "ES2020", 6 | "useDefineForClassFields": true, 7 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 8 | "skipLibCheck": true, 9 | "moduleResolution": "Bundler", 10 | "allowImportingTsExtensions": true, 11 | "resolveJsonModule": true, 12 | "isolatedModules": true, 13 | "noEmit": true, 14 | "jsx": "react-jsx", 15 | "noUnusedLocals": true, 16 | "noUnusedParameters": true, 17 | "noFallthroughCasesInSwitch": true 18 | }, 19 | "include": ["src"], 20 | "exclude": ["dist"], 21 | "references": [{ "path": "./tsconfig.node.json" }] 22 | } 23 | -------------------------------------------------------------------------------- /examples/react-ts/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 | } 11 | -------------------------------------------------------------------------------- /examples/react-ts/vite.config.ts: -------------------------------------------------------------------------------- 1 | import react from '@vitejs/plugin-react-swc' 2 | import { defineConfig } from 'vite' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | define: { 8 | 'process.env': {}, 9 | }, 10 | }) 11 | -------------------------------------------------------------------------------- /examples/scripts-ts/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { browser: true, es2020: true }, 4 | extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'prettier'], 5 | ignorePatterns: ['dist', '.eslintrc.cjs'], 6 | parser: '@typescript-eslint/parser', 7 | rules: { 8 | 'no-async-promise-executor': 'off', 9 | '@typescript-eslint/no-explicit-any': 'off', 10 | }, 11 | } 12 | -------------------------------------------------------------------------------- /examples/scripts-ts/.gitignore: -------------------------------------------------------------------------------- 1 | # system files 2 | .DS_Store 3 | Thumbs.db 4 | *.pem 5 | 6 | # log files 7 | *.log 8 | 9 | # node modules 10 | node_modules/ 11 | 12 | # environment files 13 | *.env* 14 | !.env*.example 15 | 16 | # build files 17 | dist 18 | build -------------------------------------------------------------------------------- /examples/scripts-ts/README.md: -------------------------------------------------------------------------------- 1 | # CLI Scripts Example 2 | 3 | ![Vite](https://img.shields.io/badge/Vite-000000?logo=vite&logoColor=white) 4 | ![TypeScript](https://img.shields.io/badge/TypeScript-000000?logo=typescript&logoColor=white) 5 | 6 | These are small vanilla TypeScript example scripts (without UI) showcasing `@scio-labs/use-inkathon`. 7 | 8 | ## Usage 9 | 10 | ```bash 11 | pnpm install 12 | pnpm run dev 13 | 14 | # Or run a different script than index.ts 15 | pnpm run script 16 | 17 | # Or compile the package and then run the example script: 18 | # NOTE: Run this from the root directory 19 | pnpm run dev:scripts-example 20 | ``` 21 | -------------------------------------------------------------------------------- /examples/scripts-ts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "scripts-ts-example", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "build": "tsc", 8 | "script": "f() { tsx ./src/$1; }; f \"$@\"", 9 | "dev": "pnpm run script index", 10 | "type-check": "tsc --pretty --noEmit" 11 | }, 12 | "dependencies": { 13 | "@polkadot/api": "^12.2.3", 14 | "@polkadot/api-contract": "^12.2.3", 15 | "@polkadot/util-crypto": "^13.0.2", 16 | "@scio-labs/use-inkathon": "workspace:*" 17 | }, 18 | "devDependencies": { 19 | "@polkadot/types": "^12.2.3", 20 | "@typescript-eslint/eslint-plugin": "^7.18.0", 21 | "@typescript-eslint/parser": "^7.18.0", 22 | "eslint": "^8.57.0", 23 | "eslint-config-prettier": "^9.1.0", 24 | "prettier": "^3.3.3", 25 | "tsx": "^4.16.2", 26 | "typescript": "^5.5.4" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /examples/scripts-ts/src/index.ts: -------------------------------------------------------------------------------- 1 | import { BN } from '@polkadot/util' 2 | import { getBalance, transferBalance } from '@scio-labs/use-inkathon' 3 | import { initPolkadotJs } from './utils/initPolkadotJs' 4 | 5 | const main = async () => { 6 | const chainId = process.env.CHAIN || 'alephzero-testnet' 7 | const accountUri = process.env.ACCOUNT_URI || '//Alice' 8 | const { 9 | api, 10 | decimals, 11 | symbol, 12 | keyring, 13 | account: alice, 14 | } = await initPolkadotJs(chainId, accountUri) 15 | 16 | // Initialize second account (//Bob) 17 | const bob = keyring.addFromUri('//Bob') 18 | 19 | // Print balances 20 | let balanceAlice = await getBalance(api, alice.address) 21 | let balanceBob = await getBalance(api, bob.address) 22 | console.log(`Balance of Alice: ${balanceAlice.balanceFormatted}`) 23 | console.log(`Balance of Bob: ${balanceBob.balanceFormatted}`) 24 | 25 | // Transfer 1 unit from Alice to Bob 26 | try { 27 | await transferBalance(api, alice, bob.address, new BN(1).mul(new BN(10).pow(new BN(decimals)))) 28 | console.log(`\nTransferred 1 ${symbol} from Alice to Bob\n`) 29 | } catch (e) { 30 | console.error("Couldn't transfer balance:", e) 31 | console.log(` 32 | Per default, this scripts connects to a live testnet node. 33 | Alice, with widely known private key, could simply not have enough funds. 34 | You can fund her at https://faucet.alephzero.org/. 35 | `) 36 | } 37 | 38 | // Print balances again 39 | balanceAlice = await getBalance(api, alice.address) 40 | balanceBob = await getBalance(api, bob.address) 41 | console.log(`Balance of Alice: ${balanceAlice.balanceFormatted}`) 42 | console.log(`Balance of Bob: ${balanceBob.balanceFormatted}`) 43 | } 44 | 45 | main() 46 | .catch((error) => { 47 | console.error(error) 48 | process.exit(1) 49 | }) 50 | .finally(() => process.exit(0)) 51 | -------------------------------------------------------------------------------- /examples/scripts-ts/src/utils/initPolkadotJs.ts: -------------------------------------------------------------------------------- 1 | import { ApiPromise, Keyring } from '@polkadot/api' 2 | import { IKeyringPair } from '@polkadot/types/types/interfaces' 3 | import { 4 | BalanceData, 5 | SubstrateChain, 6 | getBalance, 7 | getSubstrateChain, 8 | initPolkadotJs as initApi, 9 | } from '@scio-labs/use-inkathon' 10 | 11 | /** 12 | * Initialize Polkadot.js API with given RPC & account from given URI. 13 | */ 14 | export type InitParams = { 15 | chain: SubstrateChain 16 | api: ApiPromise 17 | keyring: Keyring 18 | account: IKeyringPair 19 | balance: BalanceData 20 | decimals: number 21 | symbol: string 22 | prefix: number 23 | } 24 | export const initPolkadotJs = async (chainId: string, uri: string): Promise => { 25 | const chain = getSubstrateChain(chainId) 26 | if (!chain) throw new Error(`Chain '${chainId}' not found`) 27 | 28 | // Initialize api 29 | const { api } = await initApi(chain, { noInitWarn: true }) 30 | 31 | // Print chain info 32 | const network = (await api.rpc.system.chain())?.toString() || '' 33 | const version = (await api.rpc.system.version())?.toString() || '' 34 | console.log(`Initialized API on ${network} (${version})`) 35 | 36 | // Get decimals & prefix 37 | const decimals = api.registry.chainDecimals?.[0] || 12 38 | const symbol = api.registry.chainTokens?.[0] || 'Unit' 39 | const prefix = api.registry.chainSS58 || 42 40 | 41 | // Initialize account & set signer 42 | const keyring = new Keyring({ type: 'sr25519' }) 43 | const account = keyring.addFromUri(uri) 44 | const balance = await getBalance(api, account.address) 45 | console.log(`Initialized Account: ${account.address} (${balance.balanceFormatted})\n`) 46 | 47 | return { api, chain, keyring, account, balance, decimals, symbol, prefix } 48 | } 49 | -------------------------------------------------------------------------------- /examples/scripts-ts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "ts-node": { 3 | "esm": true, 4 | "experimentalSpecifierResolution": "node" 5 | }, 6 | "compilerOptions": { 7 | "strict": true, 8 | "module": "ESNext", 9 | "moduleResolution": "Node", 10 | "target": "ESNext", 11 | "sourceMap": true, 12 | "outDir": "dist", 13 | "resolveJsonModule": true, 14 | "esModuleInterop": true, 15 | "skipLibCheck": true, 16 | "allowJs": true, 17 | "baseUrl": ".", 18 | "composite": true 19 | }, 20 | "include": ["src"], 21 | "exclude": ["dist"] 22 | } 23 | -------------------------------------------------------------------------------- /inkathon-devtooling-banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scio-labs/use-inkathon/496538322521bcb49b454be7bd05ca40ef9e2aaf/inkathon-devtooling-banner.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@scio-labs/use-inkathon", 3 | "author": "Scio Labs (https://scio.xyz)", 4 | "version": "0.11.1", 5 | "description": "Typesafe React Hooks abstracting functionality by polkadot.js for working with Substrate-based networks and ink! Smart Contracts.", 6 | "homepage": "https://inkathon.xyz", 7 | "license": "GPL-3.0", 8 | "repository": { 9 | "type": "git", 10 | "url": "git+https://github.com/scio-labs/use-inkathon" 11 | }, 12 | "bugs": { 13 | "url": "https://github.com/scio-labs/use-inkathon/issues" 14 | }, 15 | "publishConfig": { 16 | "access": "public" 17 | }, 18 | "engines": { 19 | "node": ">=18" 20 | }, 21 | "packageManager": "pnpm@9.6.0", 22 | "type": "module", 23 | "main": "dist/index.js", 24 | "types": "./dist/index.d.ts", 25 | "files": [ 26 | "LICENSE", 27 | "README.md", 28 | "dist" 29 | ], 30 | "exports": { 31 | ".": { 32 | "types": "./dist/index.d.ts", 33 | "import": "./dist/index.js" 34 | }, 35 | "./chains": { 36 | "types": "./dist/chains.d.ts", 37 | "import": "./dist/chains.js" 38 | }, 39 | "./wallets": { 40 | "types": "./dist/wallets.d.ts", 41 | "import": "./dist/wallets.js" 42 | }, 43 | "./helpers": { 44 | "types": "./dist/helpers/index.d.ts", 45 | "import": "./dist/helpers/index.js" 46 | }, 47 | "./hooks": { 48 | "types": "./dist/hooks/index.d.ts", 49 | "import": "./dist/hooks/index.js" 50 | }, 51 | "./types": { 52 | "types": "./dist/types/index.d.ts", 53 | "import": "./dist/types/index.js" 54 | } 55 | }, 56 | "scripts": { 57 | "build": "tsup", 58 | "dev": "tsup --watch", 59 | "dev:react-example": "concurrently \"pnpm run dev\" \"pnpm run -F \"{examples/react-ts}\" dev\"", 60 | "dev:scripts-example": "pnpm run build && pnpm run -F \"{examples/scripts-ts}\" script", 61 | "node": "substrate-contracts-node --dev --port 9944 --base-path ./.node-data", 62 | "lint": "prettier . --check && pnpm eslint .", 63 | "lint:fix": "prettier . --write && pnpm eslint . --fix", 64 | "lint:format": "prettier . --write", 65 | "typecheck": "tsc --pretty --noEmit", 66 | "typesync": "typesync && pnpm install", 67 | "typedoc": "typedoc src/index.ts --titleLink \"https://github.com/scio-labs/use-inkathon\"", 68 | "changeset:version": "pnpm run typedoc && changeset version", 69 | "changeset:publish": "pnpm run build && changeset publish" 70 | }, 71 | "simple-git-hooks": { 72 | "pre-commit": "pnpm lint-staged" 73 | }, 74 | "lint-staged": { 75 | "*.{js,jsx,ts,tsx}": [ 76 | "pnpm lint:fix" 77 | ], 78 | "*.{json,md,mdx,html,css,yml,yaml}": [ 79 | "pnpm lint:format" 80 | ] 81 | }, 82 | "peerDependencies": { 83 | "@nightlylabs/wallet-selector-polkadot": ">=0.1.10", 84 | "@polkadot/api": ">=10", 85 | "@polkadot/api-contract": ">=10", 86 | "@polkadot/extension-inject": ">=0.46", 87 | "@polkadot/keyring": ">=10", 88 | "@polkadot/types": ">=10", 89 | "@polkadot/util": ">=10", 90 | "@polkadot/util-crypto": ">=10", 91 | "react": ">=18", 92 | "react-dom": ">=18" 93 | }, 94 | "devDependencies": { 95 | "@changesets/changelog-github": "^0.5.0", 96 | "@changesets/cli": "^2.27.1", 97 | "@types/node": "^20.12.6", 98 | "@types/react": "^18.3.3", 99 | "@typescript-eslint/eslint-plugin": "^7.18.0", 100 | "@typescript-eslint/parser": "^7.18.0", 101 | "concurrently": "^8.2.2", 102 | "eslint": "^8.57.0", 103 | "eslint-config-prettier": "^9.1.0", 104 | "eslint-plugin-react": "^7.35.0", 105 | "lint-staged": "^15.2.2", 106 | "prettier": "^3.3.3", 107 | "simple-git-hooks": "^2.11.1", 108 | "tsup": "^8.0.2", 109 | "typedoc": "^0.26.5", 110 | "typescript": "^5.5.4", 111 | "typesync": "^0.13.0" 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - . 3 | - examples/* 4 | -------------------------------------------------------------------------------- /src/assets.ts: -------------------------------------------------------------------------------- 1 | import { ChainAsset } from '@/types' 2 | import PSP22_ASSETS from './data/PSP22_ASSETS.json' 3 | 4 | /** 5 | * Acknowledgement: PSP22_ASSETS.json is inspired by Subwallet's `ChainAsset.json` 6 | */ 7 | export const allPSP22Assets = PSP22_ASSETS as Record 8 | -------------------------------------------------------------------------------- /src/data/PSP22_ASSETS.json: -------------------------------------------------------------------------------- 1 | { 2 | "alephzero-PSP22-INW-5H4aCwLKUpVpct6XGJzDGPPXFockNKQU2JUVNgUw6BXEPzST": { 3 | "originChain": "alephzero", 4 | "slug": "alephzero-PSP22-INW-5H4aCwLKUpVpct6XGJzDGPPXFockNKQU2JUVNgUw6BXEPzST", 5 | "name": "Ink Whale Token", 6 | "symbol": "INW", 7 | "decimals": 12, 8 | "assetType": "PSP22", 9 | "metadata": { 10 | "contractAddress": "5H4aCwLKUpVpct6XGJzDGPPXFockNKQU2JUVNgUw6BXEPzST" 11 | }, 12 | "iconPath": "https://github.com/scio-labs/use-inkathon/raw/main/assets/asset-logos/inw.png" 13 | }, 14 | "alephzero-PSP22-PANX-5GSGAcvqpF5SuH2MhJ1YUdbLAbssCjeqCn2miMUCWUjnr5DQ": { 15 | "originChain": "alephzero", 16 | "slug": "alephzero-PSP22-PANX-5GSGAcvqpF5SuH2MhJ1YUdbLAbssCjeqCn2miMUCWUjnr5DQ", 17 | "name": "Panorama Swap Token", 18 | "symbol": "PANX", 19 | "decimals": 12, 20 | "assetType": "PSP22", 21 | "metadata": { 22 | "contractAddress": "5GSGAcvqpF5SuH2MhJ1YUdbLAbssCjeqCn2miMUCWUjnr5DQ" 23 | }, 24 | "iconPath": "https://github.com/scio-labs/use-inkathon/raw/main/assets/asset-logos/panx.png" 25 | }, 26 | "alephzero-testnet-PSP22-INW-5FrXTf3NXRWZ1wzq9Aka7kTGCgGotf6wifzV7RzxoCYtrjiX": { 27 | "originChain": "alephzero-testnet", 28 | "slug": "alephzero-testnet-PSP22-INW-5FrXTf3NXRWZ1wzq9Aka7kTGCgGotf6wifzV7RzxoCYtrjiX", 29 | "name": "Ink Whale Token", 30 | "symbol": "INW", 31 | "decimals": 12, 32 | "assetType": "PSP22", 33 | "metadata": { 34 | "contractAddress": "5FrXTf3NXRWZ1wzq9Aka7kTGCgGotf6wifzV7RzxoCYtrjiX" 35 | }, 36 | "iconPath": "https://github.com/scio-labs/use-inkathon/raw/main/assets/asset-logos/inw.png" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/helpers/accountsAreEqual.ts: -------------------------------------------------------------------------------- 1 | import { InjectedAccount } from '@polkadot/extension-inject/types' 2 | 3 | /** 4 | * Returns true if both given injected accounts have the same address. 5 | */ 6 | export const accountsAreEqual = (a1?: InjectedAccount, a2?: InjectedAccount) => { 7 | return (a1?.address || '').toLowerCase() === (a2?.address || '').toLowerCase() 8 | } 9 | 10 | /** 11 | * Returns true if both given arrays of injected accounts contain the 12 | * same objects with the same addresses in the same order. 13 | */ 14 | export const accountArraysAreEqual = (a1: InjectedAccount[], a2: InjectedAccount[]) => { 15 | if (a1.length !== a2.length) return false 16 | return a1.every((a, i) => accountsAreEqual(a, a2[i])) 17 | } 18 | -------------------------------------------------------------------------------- /src/helpers/checkIfBalanceSufficient.ts: -------------------------------------------------------------------------------- 1 | import { ApiPromise } from '@polkadot/api' 2 | import { IKeyringPair } from '@polkadot/types/types' 3 | import { BN, bnToBn } from '@polkadot/util' 4 | import { getBalance } from './getBalance' 5 | 6 | /** 7 | * Fetches the balance of the given address and returns a boolean 8 | * whether this is zero or below an optionally passed minimum value. 9 | */ 10 | export const checkIfBalanceSufficient = async ( 11 | api: ApiPromise, 12 | account: IKeyringPair | string, 13 | minBalance?: bigint | BN | string | number, 14 | ): Promise => { 15 | try { 16 | const accountAddress = typeof account === 'string' ? account : account.address 17 | const { reducibleBalance } = await getBalance(api, accountAddress) 18 | const hasZeroBalance = !reducibleBalance || reducibleBalance.isZero() 19 | const hasBalanceBelowMin = 20 | minBalance && reducibleBalance && reducibleBalance.lte(bnToBn(minBalance)) 21 | return !hasZeroBalance && !hasBalanceBelowMin 22 | } catch (e) { 23 | console.error('Error while checking for minimum balance:', e) 24 | } 25 | 26 | return false 27 | } 28 | -------------------------------------------------------------------------------- /src/helpers/decodeOutput.ts: -------------------------------------------------------------------------------- 1 | import { ContractPromise } from '@polkadot/api-contract' 2 | import { ContractExecResult } from '@polkadot/types/interfaces' 3 | import { AnyJson, TypeDef } from '@polkadot/types/types' 4 | import { getAbiMessage } from './getAbiMessage' 5 | 6 | /** 7 | * Helper types & functions 8 | * SOURCE: https://github.com/paritytech/contracts-ui (GPL-3.0-only) 9 | */ 10 | type ContractResultErr = { 11 | Err: AnyJson 12 | } 13 | 14 | interface ContractResultOk { 15 | Ok: AnyJson 16 | } 17 | 18 | function isErr(o: ContractResultErr | ContractResultOk | AnyJson): o is ContractResultErr { 19 | return typeof o === 'object' && o !== null && 'Err' in o 20 | } 21 | 22 | function isOk(o: ContractResultErr | ContractResultOk | AnyJson): o is ContractResultOk { 23 | return typeof o === 'object' && o !== null && 'Ok' in o 24 | } 25 | 26 | function getReturnTypeName(type: TypeDef | null | undefined) { 27 | return type?.lookupName || type?.type || '' 28 | } 29 | 30 | /** 31 | * Decodes & unwraps outputs and errors of a given result, contract, and method. 32 | * Parsed error message can be found in `decodedOutput` if `isError` is true. 33 | * SOURCE: https://github.com/paritytech/contracts-ui (GPL-3.0-only) 34 | */ 35 | export function decodeOutput( 36 | { result }: Pick, 37 | contract: ContractPromise, 38 | method: string, 39 | ): { 40 | output: any 41 | decodedOutput: string 42 | isError: boolean 43 | } { 44 | let output 45 | let decodedOutput = '' 46 | let isError = true 47 | 48 | if (result.isOk) { 49 | const flags = result.asOk.flags.toHuman() 50 | isError = flags.includes('Revert') 51 | const abiMessage = getAbiMessage(contract, method) 52 | const returnType = abiMessage.returnType 53 | const returnTypeName = getReturnTypeName(returnType) 54 | const registry = contract.abi.registry 55 | const r = returnType 56 | ? registry.createTypeUnsafe(returnTypeName, [result.asOk.data]).toHuman() 57 | : '()' 58 | output = isOk(r) ? r.Ok : isErr(r) ? r.Err : r 59 | 60 | const errorText = isErr(output) 61 | ? typeof output.Err === 'object' 62 | ? JSON.stringify(output.Err, null, 2) 63 | : (output.Err?.toString() ?? 'Error') 64 | : output !== 'Ok' 65 | ? output?.toString() || 'Error' 66 | : 'Error' 67 | 68 | const okText = isOk(r) 69 | ? typeof output === 'object' 70 | ? JSON.stringify(output, null, '\t') 71 | : (output?.toString() ?? '()') 72 | : (JSON.stringify(output, null, '\t') ?? '()') 73 | 74 | decodedOutput = isError ? errorText : okText 75 | } else if (result.isErr) { 76 | output = result.toHuman() 77 | 78 | let errorText 79 | if ( 80 | isErr(output) && 81 | typeof output.Err === 'object' && 82 | Object.keys(output.Err || {}).length && 83 | typeof Object.values(output.Err || {})[0] === 'string' 84 | ) { 85 | const [errorKey, errorValue] = Object.entries(output.Err || {})[0] 86 | errorText = `${errorKey}${errorValue}` 87 | } 88 | 89 | decodedOutput = errorText || 'Error' 90 | } 91 | 92 | return { 93 | output, 94 | decodedOutput, 95 | isError, 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/helpers/deployContract.ts: -------------------------------------------------------------------------------- 1 | import { DeployedContract } from '@/types' 2 | import { ApiPromise } from '@polkadot/api' 3 | import { CodePromise } from '@polkadot/api-contract' 4 | import { ContractOptions } from '@polkadot/api-contract/types' 5 | import { SignerOptions } from '@polkadot/api/types/submittable' 6 | import { EventRecord, SignedBlock } from '@polkadot/types/interfaces' 7 | import { IKeyringPair } from '@polkadot/types/types' 8 | import { stringCamelCase } from '@polkadot/util' 9 | import { getMaxGasLimit } from './getGasLimit' 10 | 11 | /** 12 | * Uploads & instantiates a contract on-chain. 13 | */ 14 | export const deployContract = async ( 15 | api: ApiPromise, 16 | account: IKeyringPair | string, 17 | abi: any, 18 | wasm: Uint8Array | string | Buffer, 19 | constructorMethod = 'new', 20 | args = [] as unknown[], 21 | contractOptions = {} as Partial, 22 | signerOptions = {} as Partial, 23 | ): Promise => { 24 | return new Promise<{ 25 | address: string 26 | hash: string 27 | block: SignedBlock 28 | blockNumber: number 29 | }>(async (resolve, reject) => { 30 | const code = new CodePromise(api, abi, wasm) 31 | const gasLimit = getMaxGasLimit(api) 32 | const constructorFn = code.tx[stringCamelCase(constructorMethod)] 33 | const unsub = await constructorFn({ gasLimit, ...contractOptions }, ...args).signAndSend( 34 | account, 35 | signerOptions, 36 | async ({ events, contract, status }: any) => { 37 | if (status?.isInBlock) { 38 | unsub?.() 39 | 40 | const extrinsicFailedEvent = events.find( 41 | ({ event: { method } }: any) => method === 'ExtrinsicFailed', 42 | ) as EventRecord 43 | if (!!extrinsicFailedEvent || !contract?.address) { 44 | console.error( 45 | `Contract '${abi?.contract.name}' could not be deployed`, 46 | extrinsicFailedEvent?.event?.data?.toHuman(), 47 | ) 48 | return reject() 49 | } 50 | 51 | const hash = abi?.source.hash 52 | const address = contract.address.toString() 53 | 54 | // Determine block number 55 | const blockHash = status.asInBlock.toHex() 56 | const block = await api.rpc.chain.getBlock(blockHash) 57 | const blockNumber = block.block.header.number.toNumber() 58 | 59 | console.log( 60 | `Contract '${abi?.contract.name}' deployed under ${address} at block #${blockNumber}`, 61 | ) 62 | 63 | return resolve({ 64 | address, 65 | hash, 66 | block, 67 | blockNumber, 68 | }) 69 | } 70 | }, 71 | ) 72 | }) 73 | } 74 | -------------------------------------------------------------------------------- /src/helpers/formatBalance.ts: -------------------------------------------------------------------------------- 1 | import { ApiPromise } from '@polkadot/api' 2 | import { BN, formatBalance as polkadotFormatBalance } from '@polkadot/util' 3 | 4 | export type PolkadotBalanceFormatterOptions = NonNullable< 5 | Parameters['1'] 6 | > 7 | 8 | export type BalanceFormatterOptions = Omit< 9 | PolkadotBalanceFormatterOptions, 10 | 'forceUnit' | 'withZero' 11 | > & { 12 | forceUnit?: string | undefined | false 13 | fixedDecimals?: number 14 | removeTrailingZeros?: boolean 15 | } 16 | 17 | export type TokenData = { 18 | tokenDecimals: number 19 | tokenSymbol: string 20 | } 21 | 22 | /** 23 | * Improved & extended version of `formatBalance` from `@polkadot/util`. 24 | */ 25 | export const formatBalance = ( 26 | api: ApiPromise | undefined, 27 | value?: BN, 28 | options?: BalanceFormatterOptions, 29 | tokenData?: TokenData, 30 | ): string => { 31 | if (!value) return '' 32 | 33 | const tokenDecimals = api?.registry?.chainDecimals?.[0] || tokenData?.tokenDecimals || 12 34 | const tokenSymbol = api?.registry?.chainTokens?.[0] || tokenData?.tokenSymbol || 'Unit' 35 | 36 | const _options: BalanceFormatterOptions = Object.assign( 37 | { 38 | decimals: tokenDecimals, 39 | withUnit: true, 40 | forceUnit: '-', 41 | } satisfies BalanceFormatterOptions, 42 | options, 43 | ) 44 | 45 | let formattedBalance = polkadotFormatBalance(value, { 46 | ..._options, 47 | withUnit: false, 48 | withZero: false, 49 | } as PolkadotBalanceFormatterOptions) 50 | 51 | // Convert to fixed decimals 52 | if (_options.fixedDecimals !== undefined) { 53 | // Remove siUnit amd add it back later 54 | let siUnit 55 | if (_options.forceUnit !== '-') { 56 | siUnit = formattedBalance.split(' ')[1] 57 | formattedBalance = formattedBalance.split(' ')[0] 58 | } 59 | 60 | // Apply fixed decimals 61 | formattedBalance = toFixed( 62 | formattedBalance, 63 | _options.fixedDecimals, 64 | _options.removeTrailingZeros, 65 | ) 66 | 67 | if (siUnit) formattedBalance = `${formattedBalance} ${siUnit}` 68 | } 69 | 70 | // Place hairline space between number and `siUnit` 71 | if (_options.forceUnit !== '-') { 72 | const siUnit = formattedBalance.split(' ')[1] 73 | formattedBalance = formattedBalance.split(' ')[0] 74 | if (siUnit) formattedBalance = `${formattedBalance}\u200A${siUnit}` 75 | } 76 | 77 | // Add token symbol 78 | if (_options.withUnit === true) { 79 | formattedBalance = `${formattedBalance} ${tokenSymbol}` 80 | } 81 | 82 | return formattedBalance 83 | } 84 | 85 | /** 86 | * Helper function to convert a number (as string) to have fixed decimals. 87 | */ 88 | const toFixed = (value: string | number, decimals: number, removeTrailingZeros?: boolean) => { 89 | const _value: string = typeof value === 'string' ? value : `${value}` 90 | 91 | let valueDecimals = _value.split('.')[1] || '0' 92 | valueDecimals = parseFloat(`0.${valueDecimals}`).toFixed(decimals) 93 | if (removeTrailingZeros) { 94 | valueDecimals = `${+valueDecimals}` 95 | } 96 | 97 | const formattedValue = valueDecimals.split('.')[1] 98 | ? `${_value.split('.')[0]}.${valueDecimals.split('.')[1]}` 99 | : _value.split('.')[0] 100 | 101 | return formattedValue 102 | } 103 | -------------------------------------------------------------------------------- /src/helpers/getAbi.ts: -------------------------------------------------------------------------------- 1 | import PSP22_Abi from '../metadata/psp22.json' 2 | export const psp22Abi = PSP22_Abi as Record 3 | -------------------------------------------------------------------------------- /src/helpers/getAbiMessage.ts: -------------------------------------------------------------------------------- 1 | import { ContractPromise } from '@polkadot/api-contract' 2 | import { stringCamelCase } from '@polkadot/util' 3 | 4 | /** 5 | * Returns the ABI message for the given method name within the given contract. 6 | */ 7 | export const getAbiMessage = (contract: ContractPromise, method: string) => { 8 | const abiMessage = contract.abi.messages.find( 9 | (m) => stringCamelCase(m.method) === stringCamelCase(method), 10 | ) 11 | if (!abiMessage) { 12 | throw new Error(`"${method}" not found in Contract`) 13 | } 14 | return abiMessage 15 | } 16 | -------------------------------------------------------------------------------- /src/helpers/getBalance.ts: -------------------------------------------------------------------------------- 1 | import { ApiPromise } from '@polkadot/api' 2 | import { AccountId } from '@polkadot/types/interfaces' 3 | import { BN } from '@polkadot/util' 4 | import { BalanceFormatterOptions, formatBalance } from './formatBalance' 5 | 6 | export type BalanceData = { 7 | tokenDecimals: number 8 | tokenSymbol: string 9 | freeBalance?: BN 10 | freeBalanceFormatted?: string 11 | reservedBalance?: BN 12 | reservedBalanceFormatted?: string 13 | reducibleBalance?: BN 14 | reducibleBalanceFormatted?: string 15 | balance?: BN 16 | balanceFormatted?: string 17 | } 18 | 19 | /** 20 | * Returns the native token balance of the given `address`. 21 | */ 22 | export const getBalance = async ( 23 | api: ApiPromise, 24 | address: string | AccountId | undefined, 25 | formatterOptions?: BalanceFormatterOptions, 26 | ): Promise => { 27 | if (!address) { 28 | const { tokenDecimals, tokenSymbol } = parseBalanceData(api) 29 | return { 30 | tokenDecimals, 31 | tokenSymbol, 32 | } 33 | } 34 | 35 | // Query the chain and parse data 36 | const result: any = await api.query.system.account(address) 37 | const balanceData = parseBalanceData(api, result?.data, formatterOptions) 38 | return balanceData 39 | } 40 | 41 | /** 42 | * Watches the native token balance of the given `address` and returns it in a callback. 43 | * The returned void function can be used to unsubscribe. 44 | */ 45 | export const watchBalance = async ( 46 | api: ApiPromise, 47 | address: string | AccountId | undefined, 48 | callback: (data: BalanceData) => void, 49 | formatterOptions?: BalanceFormatterOptions, 50 | ): Promise => { 51 | const { tokenDecimals, tokenSymbol } = parseBalanceData(api) 52 | if (!address) { 53 | callback({ 54 | tokenDecimals, 55 | tokenSymbol, 56 | }) 57 | return null 58 | } 59 | 60 | // Query the chain, parse data, and call the callback 61 | const unsubscribe: any = await api.query.system.account(address, ({ data }: any) => { 62 | const balanceData = parseBalanceData(api, data, formatterOptions) 63 | callback(balanceData) 64 | }) 65 | return unsubscribe 66 | } 67 | 68 | /** 69 | * Helper to parse the fetched balance data. 70 | */ 71 | const parseBalanceData = ( 72 | api: ApiPromise, 73 | data?: any, 74 | formatterOptions?: BalanceFormatterOptions, 75 | ): BalanceData => { 76 | // Get the token decimals and symbol 77 | const tokenDecimals = api.registry.chainDecimals?.[0] || 12 78 | const tokenSymbol = api.registry.chainTokens?.[0] || 'Unit' 79 | 80 | // Get the balance 81 | const freeBalance: BN = new BN(data?.free || 0) 82 | const reservedBalance: BN = new BN(data?.reserved || 0) 83 | const balance = reservedBalance.add(freeBalance) 84 | 85 | // Calculate the reducible balance (see: https://substrate.stackexchange.com/a/3009/3470) 86 | // (https://wiki.polkadot.network/docs/learn-guides-accounts#query-account-data-in-polkadot-js) 87 | let reducibleBalance = new BN(0) 88 | 89 | if (data?.frozen) { 90 | const frozenBalance: BN = new BN(data?.frozen || 0) 91 | reducibleBalance = freeBalance.sub(frozenBalance.sub(reservedBalance)) 92 | } else { 93 | const miscFrozenBalance: BN = new BN(data?.miscFrozen || 0) 94 | const feeFrozenBalance: BN = new BN(data?.feeFrozen || 0) 95 | reducibleBalance = freeBalance.sub( 96 | miscFrozenBalance.gt(feeFrozenBalance) ? miscFrozenBalance : feeFrozenBalance, 97 | ) 98 | } 99 | 100 | // Format the balance 101 | const freeBalanceFormatted = formatBalance(api, freeBalance, formatterOptions) 102 | const reservedBalanceFormatted = formatBalance(api, reservedBalance, formatterOptions) 103 | const reducibleBalanceFormatted = formatBalance(api, reducibleBalance, formatterOptions) 104 | const balanceFormatted = formatBalance(api, balance, formatterOptions) 105 | 106 | return { 107 | tokenDecimals, 108 | tokenSymbol, 109 | freeBalance, 110 | freeBalanceFormatted, 111 | reservedBalance, 112 | reservedBalanceFormatted, 113 | reducibleBalance, 114 | reducibleBalanceFormatted, 115 | balance, 116 | balanceFormatted, 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/helpers/getDeployment.ts: -------------------------------------------------------------------------------- 1 | import { SubstrateDeployment } from '@/types' 2 | import { ApiPromise } from '@polkadot/api' 3 | import { ContractPromise } from '@polkadot/api-contract' 4 | 5 | /** 6 | * Returns the first matching deployment from the given `deployments` array 7 | * with an equal `contractId` and `networkId` 8 | */ 9 | export const getDeployment = ( 10 | deployments: SubstrateDeployment[], 11 | contractId: string, 12 | networkId: string, 13 | ) => { 14 | return deployments.find((deployment) => { 15 | return ( 16 | deployment.contractId.toLowerCase() === contractId.toLowerCase() && 17 | deployment.networkId.toLowerCase() === (networkId || '').toLowerCase() 18 | ) 19 | }) 20 | } 21 | 22 | /** 23 | * Takes the first matching deployment from the given `deployments` array 24 | * with an equal `contractId` and `networkId` and creates a `ContractPromise`. 25 | */ 26 | export const getDeploymentContract = ( 27 | api: ApiPromise, 28 | deployments: SubstrateDeployment[], 29 | contractId: string, 30 | networkId: string, 31 | ) => { 32 | const deployment = getDeployment(deployments || [], contractId, networkId) 33 | if (!deployment) return undefined 34 | return new ContractPromise(api, deployment?.abi, deployment?.address) 35 | } 36 | -------------------------------------------------------------------------------- /src/helpers/getExtrinsicErrorMessage.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Decodes the error message from an extrinsic's error event. 3 | */ 4 | export type ExstrinsicThrowErrorMessage = 'UserCancelled' | 'TokenBelowMinimum' | 'Error' 5 | export const getExtrinsicErrorMessage = (errorEvent: any): ExstrinsicThrowErrorMessage => { 6 | let errorMessage: ExstrinsicThrowErrorMessage = 'Error' 7 | 8 | // Somewhat hacky way to detect user cancellations, but all wallets throw different errors. 9 | if ( 10 | errorEvent?.message?.match( 11 | /(user reject request|cancelled|rejected by user|user rejected approval)/i, 12 | ) 13 | ) { 14 | errorMessage = 'UserCancelled' 15 | } 16 | 17 | // Decode the error code from the RPC error message. 18 | const errorText = errorEvent?.toString?.() 19 | const rpcErrorCode = 20 | errorText && typeof errorText === 'string' ? errorText.match(/RpcError: (\d+):/i)?.[1] : null 21 | switch (rpcErrorCode) { 22 | case '1010': 23 | errorMessage = 'TokenBelowMinimum' 24 | break 25 | default: 26 | break 27 | } 28 | 29 | return errorMessage 30 | } 31 | -------------------------------------------------------------------------------- /src/helpers/getGasLimit.ts: -------------------------------------------------------------------------------- 1 | import { ApiPromise } from '@polkadot/api' 2 | import type { WeightV2 } from '@polkadot/types/interfaces' 3 | import { BN, bnToBn } from '@polkadot/util' 4 | 5 | /** 6 | * Helper function that returns Weights V2 `gasLimit` object. 7 | */ 8 | export const getGasLimit = (api: ApiPromise, _refTime: string | BN, _proofSize: string | BN) => { 9 | const refTime = bnToBn(_refTime) 10 | const proofSize = bnToBn(_proofSize) 11 | 12 | return api.registry.createType('WeightV2', { 13 | refTime, 14 | proofSize, 15 | }) as WeightV2 16 | } 17 | 18 | /** 19 | * Helper function that returns the maximum gas limit Weights V2 object 20 | * for an extrinsic based on the api chain constants. 21 | * NOTE: It's reduced by a given factor (defaults to 80%) to avoid storage exhaust. 22 | */ 23 | export const getMaxGasLimit = (api: ApiPromise, reductionFactor = 0.8) => { 24 | const blockWeights = api.consts.system.blockWeights.toPrimitive() as any 25 | const maxExtrinsic = blockWeights?.perClass?.normal?.maxExtrinsic 26 | const maxRefTime = maxExtrinsic?.refTime 27 | ? bnToBn(maxExtrinsic.refTime) 28 | .mul(new BN(reductionFactor * 100)) 29 | .div(new BN(100)) 30 | : new BN(0) 31 | const maxProofSize = maxExtrinsic?.proofSize 32 | ? bnToBn(maxExtrinsic.proofSize) 33 | .mul(new BN(reductionFactor * 100)) 34 | .div(new BN(100)) 35 | : new BN(0) 36 | 37 | return getGasLimit(api, maxRefTime, maxProofSize) 38 | } 39 | -------------------------------------------------------------------------------- /src/helpers/getNightlyAdapter.ts: -------------------------------------------------------------------------------- 1 | import { NightlyConnectAdapter } from '@nightlylabs/wallet-selector-polkadot' 2 | import { getWebsiteIcon } from './getWebsiteIcon' 3 | 4 | // In the case of the optional library, types are not available 5 | let _adapter: any | undefined 6 | export const getNightlyConnectAdapter = async ( 7 | appName?: string, 8 | appIcon?: string, 9 | appOrigin?: string, 10 | connectionOptions = { 11 | disableEagerConnect: true, 12 | } as Parameters[1], 13 | ) => { 14 | if (_adapter) return _adapter 15 | 16 | try { 17 | const name = appName || window?.location.hostname 18 | const icon = appIcon || (await getWebsiteIcon(window?.origin)) 19 | const description = appOrigin || window?.origin 20 | 21 | _adapter = await NightlyConnectAdapter.build( 22 | { 23 | appMetadata: { name, icon, description }, 24 | network: 'AlephZero', 25 | }, 26 | connectionOptions, 27 | ) 28 | } catch (e) { 29 | return undefined 30 | } 31 | 32 | return _adapter 33 | } 34 | -------------------------------------------------------------------------------- /src/helpers/getWebsiteIcon.ts: -------------------------------------------------------------------------------- 1 | export const getWebsiteIcon = async (origin: string): Promise => { 2 | try { 3 | const text = await (await fetch(origin)).text() 4 | const appleTouchIconRegex = //i 5 | const faviconRegex = //i 6 | const appleTouchIconMatch = text.match(appleTouchIconRegex) 7 | const faviconMatch = text.match(faviconRegex) 8 | if (appleTouchIconMatch?.[1]) { 9 | const iconUrl = new URL(appleTouchIconMatch[1], origin).href 10 | return iconUrl 11 | } else if (faviconMatch?.[1]) { 12 | const iconUrl = new URL(faviconMatch[1], origin).href 13 | return iconUrl 14 | } 15 | 16 | const faviconExist = await fetch(origin + '/favicon.ico') 17 | if (faviconExist.status === 200) { 18 | return origin + '/favicon.ico' 19 | } 20 | 21 | return undefined 22 | } catch { 23 | return undefined 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/helpers/index.ts: -------------------------------------------------------------------------------- 1 | // @index('./*.(tsx|ts)', f => `export * from '${f.path}'`) 2 | export * from './accountsAreEqual' 3 | export * from './checkIfBalanceSufficient' 4 | export * from './contractCall' 5 | export * from './decodeOutput' 6 | export * from './deployContract' 7 | export * from './formatBalance' 8 | export * from './getAbi' 9 | export * from './getAbiMessage' 10 | export * from './getBalance' 11 | export * from './getDeployment' 12 | export * from './getExtrinsicErrorMessage' 13 | export * from './getGasLimit' 14 | export * from './getNightlyAdapter' 15 | export * from './getPSP22Balances' 16 | export * from './getWebsiteIcon' 17 | export * from './initPolkadotJs' 18 | export * from './transferBalance' 19 | export * from './unwrapResult' 20 | // @endindex 21 | -------------------------------------------------------------------------------- /src/helpers/initPolkadotJs.ts: -------------------------------------------------------------------------------- 1 | import { SubstrateChain } from '@/types' 2 | import { ApiPromise, HttpProvider, WsProvider } from '@polkadot/api' 3 | import { ApiOptions } from '@polkadot/api/types' 4 | import { cryptoWaitReady } from '@polkadot/util-crypto' 5 | 6 | /** 7 | * Helper to initialize polkadot.js API with given chain and options. 8 | */ 9 | export const initPolkadotJs = async ( 10 | chain: SubstrateChain, 11 | options?: Omit, 12 | ): Promise<{ api: ApiPromise; provider: WsProvider | HttpProvider }> => { 13 | const rpcUrl = chain.rpcUrls[0] 14 | if (!rpcUrl) { 15 | throw new Error('Given chain has no RPC url defined') 16 | } 17 | 18 | // Wait for crypto to be ready to prevent initialization issues 19 | await cryptoWaitReady() 20 | 21 | const provider = rpcUrl.startsWith('http') ? new HttpProvider(rpcUrl) : new WsProvider(rpcUrl) 22 | const api = await ApiPromise.create({ 23 | provider, 24 | ...options, 25 | }) 26 | 27 | return { api, provider } 28 | } 29 | -------------------------------------------------------------------------------- /src/helpers/transferBalance.ts: -------------------------------------------------------------------------------- 1 | import { ApiPromise } from '@polkadot/api' 2 | import { SubmittableExtrinsicFunction } from '@polkadot/api/types' 3 | import { AccountId, EventRecord } from '@polkadot/types/interfaces' 4 | import { AnyTuple, Callback, IKeyringPair, ISubmittableResult } from '@polkadot/types/types' 5 | import { BN, bnToBn } from '@polkadot/util' 6 | import { checkIfBalanceSufficient } from './checkIfBalanceSufficient' 7 | import { ExstrinsicThrowErrorMessage, getExtrinsicErrorMessage } from './getExtrinsicErrorMessage' 8 | 9 | export type TransferBalanceResult = { 10 | result?: ISubmittableResult 11 | errorMessage?: ExstrinsicThrowErrorMessage | 'ExtrinsicFailed' 12 | errorEvent?: EventRecord 13 | } 14 | 15 | /** 16 | * Transfers a given amount of tokens from one account to another. 17 | */ 18 | export const transferBalance = async ( 19 | api: ApiPromise, 20 | fromAccount: IKeyringPair | string, 21 | toAddress: string | AccountId, 22 | amount: bigint | BN | string | number, 23 | allowDeath?: boolean, 24 | statusCb?: Callback, 25 | ): Promise => { 26 | const hasSufficientBalance = await checkIfBalanceSufficient(api, fromAccount, amount) 27 | if (!hasSufficientBalance) { 28 | return Promise.reject({ errorMessage: 'TokenBelowMinimum' } satisfies TransferBalanceResult) 29 | } 30 | 31 | return new Promise(async (resolve, reject) => { 32 | try { 33 | const transferFn = (api.tx.balances[ 34 | allowDeath ? 'transferAllowDeath' : 'transferKeepAlive' 35 | ] || api.tx.balances['transfer']) as SubmittableExtrinsicFunction<'promise', AnyTuple> 36 | 37 | const unsub = await transferFn(toAddress, bnToBn(amount)).signAndSend( 38 | fromAccount, 39 | (result: ISubmittableResult) => { 40 | statusCb?.(result) 41 | const isInBlock = result?.status?.isInBlock 42 | if (!isInBlock) return 43 | const errorEvent = result?.events.find( 44 | ({ event: { method } }: any) => method === 'ExtrinsicFailed', 45 | ) 46 | if (isInBlock && errorEvent) { 47 | // Reject if `ExtrinsicFailed` event was found 48 | reject({ errorMessage: 'ExtrinsicFailed', errorEvent } satisfies TransferBalanceResult) 49 | unsub?.() 50 | } else if (isInBlock) { 51 | // Otherwise resolve succesfully if transaction is in block 52 | resolve({ result }) 53 | unsub?.() 54 | } 55 | }, 56 | ) 57 | } catch (e: any) { 58 | console.error('Error while transferring balance:', e) 59 | reject({ 60 | errorMessage: getExtrinsicErrorMessage(e), 61 | errorEvent: e, 62 | } satisfies TransferBalanceResult) 63 | } 64 | }) 65 | } 66 | 67 | /** 68 | * Transfers all available tokens from one account to another. 69 | */ 70 | export const transferFullBalance = async ( 71 | api: ApiPromise, 72 | fromAccount: IKeyringPair | string, 73 | toAddress: string | AccountId, 74 | keepAlive?: boolean, 75 | statusCb?: Callback, 76 | ): Promise => { 77 | const hasSufficientBalance = await checkIfBalanceSufficient(api, fromAccount) 78 | if (!hasSufficientBalance) { 79 | return Promise.reject({ errorMessage: 'TokenBelowMinimum' } satisfies TransferBalanceResult) 80 | } 81 | 82 | return new Promise(async (resolve, reject) => { 83 | try { 84 | const unsub = await api.tx.balances 85 | .transferAll(toAddress, !!keepAlive) 86 | .signAndSend(fromAccount, (result: ISubmittableResult) => { 87 | statusCb?.(result) 88 | const isInBlock = result?.status?.isInBlock 89 | if (!isInBlock) return 90 | const errorEvent = result?.events.find( 91 | ({ event: { method } }: any) => method === 'ExtrinsicFailed', 92 | ) 93 | if (isInBlock && errorEvent) { 94 | // Reject if `ExtrinsicFailed` event was found 95 | reject({ errorMessage: 'ExtrinsicFailed', errorEvent } satisfies TransferBalanceResult) 96 | unsub?.() 97 | } else if (isInBlock) { 98 | // Otherwise resolve succesfully if transaction is in block 99 | resolve({ result }) 100 | unsub?.() 101 | } 102 | }) 103 | } catch (e: any) { 104 | console.error('Error while transferring full balance:', e) 105 | reject({ 106 | errorMessage: getExtrinsicErrorMessage(e), 107 | errorEvent: e, 108 | } satisfies TransferBalanceResult) 109 | } 110 | }) 111 | } 112 | -------------------------------------------------------------------------------- /src/helpers/unwrapResult.ts: -------------------------------------------------------------------------------- 1 | import { ContractCallOutcome } from '@polkadot/api-contract/types' 2 | 3 | /** 4 | * Unwraps a Weights V2 result type or errors if there is no 'ok' value. 5 | */ 6 | export const unwrapResultOrError = ( 7 | outcome: Pick, 8 | ): T => { 9 | const { result, output } = outcome 10 | if (!result?.isOk || !output) { 11 | throw new Error(`Error while unwrapping: ${result.toString()}`) 12 | } 13 | return (output.toPrimitive() as { ok: T }).ok 14 | } 15 | 16 | /** 17 | * Unwraps a Weights V2 result type or returns the given default if there is no 'ok' value. 18 | */ 19 | export const unwrapResultOrDefault = ( 20 | outcome: Pick, 21 | defaultValue: T, 22 | ): T => { 23 | const { result, output } = outcome 24 | let unwrappedResult: T = defaultValue 25 | if (result?.isOk && !!output) { 26 | unwrappedResult = (output.toPrimitive() as { ok: T }).ok 27 | } 28 | return unwrappedResult 29 | } 30 | -------------------------------------------------------------------------------- /src/hooks/index.ts: -------------------------------------------------------------------------------- 1 | // @index('./*.(tsx|ts)', f => `export * from '${f.path}'`) 2 | export * from './useBalance' 3 | export * from './useContract' 4 | export * from './usePSP22Balances' 5 | export * from './useRegisteredContract' 6 | export * from './useRegisteredTypedContract' 7 | // @endindex 8 | -------------------------------------------------------------------------------- /src/hooks/useBalance.ts: -------------------------------------------------------------------------------- 1 | import { BalanceData, BalanceFormatterOptions, getBalance, watchBalance } from '@/helpers' 2 | import { useInkathon } from '@/provider' 3 | import { AccountId } from '@polkadot/types/interfaces' 4 | import { useEffect, useState } from 'react' 5 | 6 | /** 7 | * Hook that returns the native token balance of the given `address`. 8 | */ 9 | export const useBalance = ( 10 | address?: string | AccountId, 11 | watch?: boolean, 12 | formatterOptions?: BalanceFormatterOptions, 13 | ): BalanceData => { 14 | const { api } = useInkathon() 15 | const [balanceData, setBalanceData] = useState({ 16 | tokenSymbol: 'Unit', 17 | tokenDecimals: 12, 18 | } satisfies BalanceData) 19 | const [unsubscribes, setUnsubscribes] = useState<(VoidFunction | null)[]>([]) 20 | 21 | useEffect(() => { 22 | const updateBalanceData = (data: BalanceData) => { 23 | setBalanceData(() => data) 24 | } 25 | 26 | if (!api) { 27 | updateBalanceData({} as BalanceData) 28 | return 29 | } 30 | 31 | if (watch) { 32 | watchBalance(api, address, updateBalanceData, formatterOptions).then((unsubscribe) => { 33 | setUnsubscribes((prev) => [...prev, unsubscribe]) 34 | }) 35 | } else { 36 | getBalance(api, address, formatterOptions).then(updateBalanceData) 37 | } 38 | 39 | return () => { 40 | unsubscribes.forEach((unsubscribe) => unsubscribe?.()) 41 | setUnsubscribes(() => []) 42 | } 43 | }, [api, address]) 44 | 45 | return balanceData 46 | } 47 | -------------------------------------------------------------------------------- /src/hooks/useContract.ts: -------------------------------------------------------------------------------- 1 | import { useInkathon } from '@/provider' 2 | import { Abi, ContractPromise } from '@polkadot/api-contract' 3 | import { AccountId } from '@polkadot/types/interfaces' 4 | import { useEffect, useState } from 'react' 5 | 6 | /** 7 | * React Hook that returns a `ContractPromise` object configured with 8 | * the active api & chain as well as the given `abi` and `address`. 9 | */ 10 | export const useContract = ( 11 | abi?: string | Record | Abi, 12 | address?: string | AccountId, 13 | ) => { 14 | const { api, isConnecting } = useInkathon() 15 | const [contract, setContract] = useState() 16 | 17 | const initialize = async () => { 18 | if (isConnecting || !api || !abi || !address) { 19 | setContract(undefined) 20 | return 21 | } 22 | try { 23 | const contract = new ContractPromise(api, abi, address) 24 | setContract(contract) 25 | } catch (error) { 26 | console.error('Error during Contract initialization', error) 27 | } 28 | } 29 | useEffect(() => { 30 | initialize() 31 | }, [api, isConnecting, abi, address]) 32 | 33 | return { 34 | contract, 35 | address, 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/hooks/usePSP22Balances.ts: -------------------------------------------------------------------------------- 1 | import { 2 | BalanceFormatterOptions, 3 | PSP22BalanceData, 4 | getPSP22Balances, 5 | watchPSP22Balances, 6 | } from '@/helpers' 7 | import { useInkathon } from '@/provider' 8 | import { AccountId } from '@polkadot/types/interfaces' 9 | import { useEffect, useState } from 'react' 10 | 11 | /** 12 | * Hook that returns the PSP-22 token balances of the given `address`. 13 | */ 14 | export const usePSP22Balances = ( 15 | address?: string | AccountId, 16 | watch?: boolean, 17 | formatterOptions?: BalanceFormatterOptions, 18 | ): PSP22BalanceData[] => { 19 | const { api, activeChain } = useInkathon() 20 | const [balanceData, setBalanceData] = useState( 21 | [] satisfies PSP22BalanceData[], 22 | ) 23 | const [unsubscribes, setUnsubscribes] = useState<(VoidFunction | null)[]>([]) 24 | 25 | useEffect(() => { 26 | const updateBalanceData = (data: PSP22BalanceData[]) => { 27 | setBalanceData(() => data) 28 | } 29 | 30 | if (!api || !activeChain) { 31 | setBalanceData([] as PSP22BalanceData[]) 32 | return 33 | } 34 | 35 | if (watch) { 36 | const unsubscribe = watchPSP22Balances( 37 | api, 38 | address, 39 | updateBalanceData, 40 | activeChain.network, 41 | formatterOptions, 42 | ) 43 | unsubscribe && setUnsubscribes((prev) => [...prev, unsubscribe]) 44 | } else { 45 | getPSP22Balances(api, address, activeChain.network, formatterOptions).then(updateBalanceData) 46 | } 47 | 48 | return () => { 49 | unsubscribes.forEach((unsubscribe) => unsubscribe?.()) 50 | setUnsubscribes(() => []) 51 | } 52 | }, [api, address, activeChain]) 53 | 54 | return balanceData 55 | } 56 | -------------------------------------------------------------------------------- /src/hooks/useRegisteredContract.ts: -------------------------------------------------------------------------------- 1 | import { getDeployment } from '@/helpers' 2 | import { useInkathon } from '@/provider' 3 | import { useContract } from './useContract' 4 | 5 | /** 6 | * React Hook that returns a `ContractPromise` object configured with 7 | * the active api & chain with the given deployment contract id which 8 | * is looked up from the deployments registry. 9 | */ 10 | export const useRegisteredContract = (contractId: string, networkId?: string) => { 11 | const { deployments, activeChain } = useInkathon() 12 | 13 | networkId = networkId || activeChain?.network || '' 14 | 15 | const deployment = getDeployment(deployments || [], contractId, networkId) 16 | 17 | return useContract(deployment?.abi, deployment?.address) 18 | } 19 | -------------------------------------------------------------------------------- /src/hooks/useRegisteredTypedContract.ts: -------------------------------------------------------------------------------- 1 | import { useRegisteredContract } from '@/hooks/useRegisteredContract' 2 | import { useInkathon } from '@/provider' 3 | import { TypechainContractConstructor } from '@/types' 4 | import { useEffect, useState } from 'react' 5 | 6 | /** 7 | * React Hook that returns a type-safe contract object by `typechain-polkadot`, 8 | * configured with the active api & chain for the given deployment contract id 9 | * which is looked up from the deployments registry. 10 | */ 11 | export const useRegisteredTypedContract = ( 12 | contractId: string, 13 | Contract: TypechainContractConstructor, 14 | networkId?: string, 15 | ) => { 16 | const { api, activeAccount } = useInkathon() 17 | const registeredContract = useRegisteredContract(contractId, networkId) 18 | 19 | const [typedContract, setTypedContract] = useState(undefined) 20 | useEffect(() => { 21 | if (!registeredContract?.address || !activeAccount?.address || !api) { 22 | setTypedContract(undefined) 23 | return 24 | } 25 | 26 | // IMPORTANT: Right now, only KeyringPair is supported as signer, but as we don't have 27 | // those anyways in the frontend, we can alreaday start using the new API. 28 | const typedContract = new Contract( 29 | registeredContract.address.toString(), 30 | activeAccount.address as any, 31 | api, 32 | ) 33 | setTypedContract(typedContract) 34 | }, [registeredContract?.address, activeAccount?.address]) 35 | 36 | return { ...registeredContract, typedContract } 37 | } 38 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | // @index(['./*.(tsx|ts)', './*/index.ts'], f => `export * from '${f.path.replace(/\/index$/, '')}'`) 2 | export * from './assets' 3 | export * from './chains' 4 | export * from './helpers' 5 | export * from './hooks' 6 | export * from './provider' 7 | export * from './registry' 8 | export * from './types' 9 | export * from './wallets' 10 | // @endindex 11 | -------------------------------------------------------------------------------- /src/registry.ts: -------------------------------------------------------------------------------- 1 | import { SubstrateDeployment } from '@/types' 2 | import { Dispatch, SetStateAction } from 'react' 3 | 4 | /** 5 | * Registering the given `deployment` with the given `setDeployments` dispatcher. 6 | * The registry is kept unique, so if there is already one deployment with 7 | * equal `contractId` and `networkId` it will be replaced. 8 | */ 9 | export const registerDeployment = ( 10 | setDeployments: Dispatch>, 11 | deployment: SubstrateDeployment, 12 | ) => { 13 | setDeployments((deployments) => { 14 | // Check if deployment already exists & remove 15 | const idx = deployments.findIndex( 16 | ({ contractId, networkId }) => 17 | contractId.toLowerCase() === deployment.contractId.toLowerCase() && 18 | networkId.toLowerCase() === deployment.networkId.toLowerCase(), 19 | ) 20 | if (idx >= 0) deployments.splice(idx, 1) 21 | 22 | // Add new deployment 23 | return [...deployments, deployment] 24 | }) 25 | } 26 | 27 | /** 28 | * Registers all given `deployments` via `registerDeployment` after awaiting the promise. 29 | */ 30 | export const registerDeployments = async ( 31 | setDeployments: Dispatch>, 32 | deployments: Promise, 33 | ) => { 34 | ;(await deployments).forEach((deployment) => registerDeployment(setDeployments, deployment)) 35 | } 36 | -------------------------------------------------------------------------------- /src/types/ChainAsset.ts: -------------------------------------------------------------------------------- 1 | export interface ChainAsset { 2 | originChain: string 3 | slug: string 4 | name: string 5 | symbol: string 6 | decimals: number 7 | assetType: AssetType 8 | metadata: Record | null 9 | iconPath: string 10 | } 11 | 12 | export enum AssetType { 13 | PSP22 = 'PSP22', 14 | PSP34 = 'PSP34', 15 | } 16 | -------------------------------------------------------------------------------- /src/types/DeployedContract.ts: -------------------------------------------------------------------------------- 1 | import { SignedBlock } from '@polkadot/types/interfaces' 2 | 3 | export interface DeployedContract { 4 | address: string 5 | hash: string 6 | block: SignedBlock 7 | blockNumber: number 8 | } 9 | -------------------------------------------------------------------------------- /src/types/SubstrateChain.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Substrate Chain Type 3 | */ 4 | export interface SubstrateChain { 5 | network: string 6 | name: string 7 | rpcUrls: [string, ...string[]] 8 | ss58Prefix?: number 9 | explorerUrls?: Partial> 10 | testnet?: boolean 11 | faucetUrls?: string[] 12 | } 13 | 14 | export enum SubstrateExplorer { 15 | Subscan = 'subscan', 16 | PolkadotJs = 'polkadotjs', 17 | Other = 'other', 18 | } 19 | -------------------------------------------------------------------------------- /src/types/SubstrateDeployment.ts: -------------------------------------------------------------------------------- 1 | import { Abi } from '@polkadot/api-contract' 2 | import { AccountId } from '@polkadot/types/interfaces' 3 | 4 | export interface SubstrateDeployment { 5 | contractId: string 6 | networkId: string 7 | abi: string | Record | Abi 8 | address: string | AccountId 9 | } 10 | -------------------------------------------------------------------------------- /src/types/SubstrateWallet.ts: -------------------------------------------------------------------------------- 1 | export interface SubstrateWallet { 2 | id: string 3 | name: string 4 | platforms: [SubstrateWalletPlatform, ...SubstrateWalletPlatform[]] 5 | urls: { 6 | website: string 7 | chromeExtension?: string 8 | firefoxExtension?: string 9 | iosApp?: string 10 | androidApp?: string 11 | } 12 | logoUrls: [string, ...string[]] 13 | } 14 | export enum SubstrateWalletPlatform { 15 | Browser = 'browser', 16 | Android = 'android', 17 | iOS = 'ios', 18 | } 19 | -------------------------------------------------------------------------------- /src/types/TypechainContractConstructor.ts: -------------------------------------------------------------------------------- 1 | import { ApiPromise } from '@polkadot/api' 2 | import { KeyringPair } from '@polkadot/keyring/types' 3 | 4 | export type TypechainContractConstructor = new ( 5 | address: string, 6 | signer: KeyringPair, 7 | nativeAPI: ApiPromise, 8 | ) => T 9 | -------------------------------------------------------------------------------- /src/types/UseInkathonProviderContext.ts: -------------------------------------------------------------------------------- 1 | import { SubstrateChain, SubstrateWallet } from '@/types' 2 | import { ApiPromise, HttpProvider, WsProvider } from '@polkadot/api' 3 | import { InjectedAccount, InjectedExtension } from '@polkadot/extension-inject/types' 4 | import { Signer } from '@polkadot/types/types' 5 | import { Dispatch, SetStateAction } from 'react' 6 | import { SubstrateDeployment } from './SubstrateDeployment' 7 | 8 | export type UseInkathonProviderContextType = { 9 | isInitializing?: boolean 10 | isInitialized?: boolean 11 | isConnecting?: boolean 12 | isConnected?: boolean 13 | error?: UseInkathonError 14 | activeChain?: SubstrateChain 15 | switchActiveChain?: (chain: SubstrateChain) => Promise 16 | api?: ApiPromise 17 | provider?: WsProvider | HttpProvider 18 | connect?: ( 19 | chain?: SubstrateChain, 20 | wallet?: SubstrateWallet, 21 | lastActiveAccountAddress?: string, 22 | ) => Promise 23 | disconnect?: () => void 24 | accounts?: InjectedAccount[] 25 | activeAccount?: InjectedAccount 26 | activeExtension?: InjectedExtension 27 | activeSigner?: Signer 28 | setActiveAccount?: Dispatch> 29 | lastActiveAccount?: InjectedAccount 30 | deployments?: SubstrateDeployment[] 31 | supportedWallets?: SubstrateWallet[] 32 | } 33 | 34 | export interface UseInkathonError { 35 | code: UseInkathonErrorCode 36 | message: string 37 | } 38 | 39 | export enum UseInkathonErrorCode { 40 | InitializationError, 41 | NoSubstrateExtensionDetected, 42 | NoAccountInjected, 43 | } 44 | -------------------------------------------------------------------------------- /src/types/index.ts: -------------------------------------------------------------------------------- 1 | // @index('./*.ts', f => `export * from '${f.path}'`) 2 | export * from './ChainAsset' 3 | export * from './DeployedContract' 4 | export * from './SubstrateChain' 5 | export * from './SubstrateDeployment' 6 | export * from './SubstrateWallet' 7 | export * from './TypechainContractConstructor' 8 | export * from './UseInkathonProviderContext' 9 | // @endindex 10 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "target": "es2016", 5 | "lib": ["dom", "dom.iterable", "esnext"], 6 | "jsx": "react-jsx", 7 | "module": "esnext", 8 | "moduleResolution": "node", 9 | "esModuleInterop": true, 10 | "skipLibCheck": true, 11 | "resolveJsonModule": true, 12 | "baseUrl": ".", 13 | "paths": { 14 | "@/*": ["./src/*"] 15 | } 16 | }, 17 | "exclude": ["dist", "docs"], 18 | "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.json"] 19 | } 20 | -------------------------------------------------------------------------------- /tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'tsup' 2 | 3 | export default defineConfig((options) => { 4 | return { 5 | entry: [ 6 | 'src/index.ts', 7 | 'src/chains.ts', 8 | 'src/wallets.ts', 9 | 'src/helpers/index.ts', 10 | 'src/hooks/index.ts', 11 | 'src/types/index.ts', 12 | ], 13 | splitting: true, 14 | sourcemap: true, 15 | clean: true, 16 | minify: !options.watch, 17 | treeshake: true, 18 | dts: true, 19 | format: ['esm'], 20 | external: ['react'], 21 | } 22 | }) 23 | --------------------------------------------------------------------------------