├── .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-inkathonVariable agungTestnetConst
2 |
--------------------------------------------------------------------------------
/docs/variables/alephzero.html:
--------------------------------------------------------------------------------
1 | alephzero | @scio-labs/use-inkathon
3 |
--------------------------------------------------------------------------------
/docs/variables/alephzeroSigner.html:
--------------------------------------------------------------------------------
1 | alephzeroSigner | @scio-labs/use-inkathonVariable alephzeroSignerConst
2 |
--------------------------------------------------------------------------------
/docs/variables/amplitude.html:
--------------------------------------------------------------------------------
1 | amplitude | @scio-labs/use-inkathon
2 |
--------------------------------------------------------------------------------
/docs/variables/amplitudeTestnet.html:
--------------------------------------------------------------------------------
1 | amplitudeTestnet | @scio-labs/use-inkathonVariable amplitudeTestnetConst
2 |
--------------------------------------------------------------------------------
/docs/variables/astar.html:
--------------------------------------------------------------------------------
1 | astar | @scio-labs/use-inkathon
2 |
--------------------------------------------------------------------------------
/docs/variables/bitCountryAlphaTestnet.html:
--------------------------------------------------------------------------------
1 | bitCountryAlphaTestnet | @scio-labs/use-inkathonVariable bitCountryAlphaTestnetConst
2 |
--------------------------------------------------------------------------------
/docs/variables/contracts.html:
--------------------------------------------------------------------------------
1 | contracts | @scio-labs/use-inkathon
2 |
--------------------------------------------------------------------------------
/docs/variables/development.html:
--------------------------------------------------------------------------------
1 | development | @scio-labs/use-inkathonVariable developmentConst
3 |
--------------------------------------------------------------------------------
/docs/variables/khala.html:
--------------------------------------------------------------------------------
1 | khala | @scio-labs/use-inkathon
2 |
--------------------------------------------------------------------------------
/docs/variables/nightly.html:
--------------------------------------------------------------------------------
1 | nightly | @scio-labs/use-inkathon
2 |
--------------------------------------------------------------------------------
/docs/variables/nightlyConnect.html:
--------------------------------------------------------------------------------
1 | nightlyConnect | @scio-labs/use-inkathonVariable nightlyConnectConst
2 |
--------------------------------------------------------------------------------
/docs/variables/nova.html:
--------------------------------------------------------------------------------
1 | nova | @scio-labs/use-inkathon
2 |
--------------------------------------------------------------------------------
/docs/variables/pendulum.html:
--------------------------------------------------------------------------------
1 | pendulum | @scio-labs/use-inkathon
2 |
--------------------------------------------------------------------------------
/docs/variables/phala.html:
--------------------------------------------------------------------------------
1 | phala | @scio-labs/use-inkathon
2 |
--------------------------------------------------------------------------------
/docs/variables/phalaPOC6Testnet.html:
--------------------------------------------------------------------------------
1 | phalaPOC6Testnet | @scio-labs/use-inkathonVariable phalaPOC6TestnetConst
2 |
--------------------------------------------------------------------------------
/docs/variables/popNetworkTestnet.html:
--------------------------------------------------------------------------------
1 | popNetworkTestnet | @scio-labs/use-inkathonVariable popNetworkTestnetConst
2 |
--------------------------------------------------------------------------------
/docs/variables/shibuya.html:
--------------------------------------------------------------------------------
1 | shibuya | @scio-labs/use-inkathon
2 |
--------------------------------------------------------------------------------
/docs/variables/shiden.html:
--------------------------------------------------------------------------------
1 | shiden | @scio-labs/use-inkathon
3 |
--------------------------------------------------------------------------------
/docs/variables/subwallet.html:
--------------------------------------------------------------------------------
1 | subwallet | @scio-labs/use-inkathon
2 |
--------------------------------------------------------------------------------
/docs/variables/t0rnTestnet.html:
--------------------------------------------------------------------------------
1 | t0rnTestnet | @scio-labs/use-inkathonVariable t0rnTestnetConst
2 |
--------------------------------------------------------------------------------
/docs/variables/talisman.html:
--------------------------------------------------------------------------------
1 | talisman | @scio-labs/use-inkathon
2 |
--------------------------------------------------------------------------------
/docs/variables/ternoa.html:
--------------------------------------------------------------------------------
1 | ternoa | @scio-labs/use-inkathon
2 |
--------------------------------------------------------------------------------
/docs/variables/ternoaAlphanet.html:
--------------------------------------------------------------------------------
1 | ternoaAlphanet | @scio-labs/use-inkathonVariable ternoaAlphanetConst
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 | 
4 | 
5 | 
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 |
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 |
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 |
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 | 
4 | 
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 |
--------------------------------------------------------------------------------
Live Mainnet Networks
2 |