├── .changeset ├── README.md └── config.json ├── .editorconfig ├── .eslintignore ├── .eslintrc.json ├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ └── Bug_report.md └── workflows │ ├── cdn-deploy.yaml │ ├── ci.yaml │ └── release.yaml ├── .gitignore ├── .husky ├── .gitignore ├── commit-msg └── pre-commit ├── .npmignore ├── .prettierignore ├── .prettierrc ├── CONTRIBUTING.md ├── LICENSE ├── MIGRATING.md ├── README.md ├── docs └── images │ ├── dev-portal-client-details.png │ ├── dev-portal-new-app.png │ └── dev-portal-redirect-urls.png ├── examples ├── ethers │ ├── .gitignore │ ├── index.html │ ├── package-lock.json │ ├── package.json │ ├── postcss.config.js │ ├── public │ │ └── callback.html │ ├── src │ │ ├── App.jsx │ │ ├── index.css │ │ └── main.jsx │ ├── tailwind.config.js │ └── vite.config.js ├── standalone │ ├── .gitignore │ ├── README.md │ ├── callback │ │ └── callback.html │ ├── common.js │ ├── connect-button │ │ ├── index.html │ │ └── index.js │ ├── index.html │ ├── package.json │ ├── sign-typed-data │ │ ├── index.html │ │ └── index.js │ └── webpack.config.js ├── viem │ ├── .gitignore │ ├── index.html │ ├── package-lock.json │ ├── package.json │ ├── postcss.config.js │ ├── public │ │ └── callback.html │ ├── src │ │ ├── App.jsx │ │ ├── index.css │ │ └── main.jsx │ ├── tailwind.config.js │ └── vite.config.js └── web3js │ ├── .eslintrc.cjs │ ├── .gitignore │ ├── index.html │ ├── package-lock.json │ ├── package.json │ ├── postcss.config.js │ ├── public │ └── callback.html │ ├── src │ ├── App.jsx │ ├── index.css │ └── main.jsx │ ├── tailwind.config.js │ └── vite.config.js ├── jsconfig.json ├── package-lock.json ├── package.json ├── packages ├── bitski-provider │ ├── .gitignore │ ├── CHANGELOG.md │ ├── README.md │ ├── jest.config.js │ ├── package.json │ ├── scripts │ │ └── insert-package-version.mjs │ ├── src │ │ ├── bitski-provider.ts │ │ ├── constants.ts │ │ ├── dialogs │ │ │ ├── iframe.ts │ │ │ ├── index.ts │ │ │ ├── popup.ts │ │ │ └── shared.ts │ │ ├── index.ts │ │ ├── middleware │ │ │ ├── block-cache.ts │ │ │ ├── chain-management.ts │ │ │ ├── eth-accounts.ts │ │ │ ├── fetch-rest.ts │ │ │ ├── fetch-rpc.ts │ │ │ ├── filter.ts │ │ │ ├── fixture.ts │ │ │ ├── signature.ts │ │ │ ├── subscription.ts │ │ │ ├── transaction-validator.ts │ │ │ └── typed-data-sanitizer.ts │ │ ├── signers │ │ │ ├── browser.ts │ │ │ ├── dialog.ts │ │ │ ├── popup.ts │ │ │ ├── rpc.ts │ │ │ └── shared.ts │ │ ├── store.ts │ │ ├── styles │ │ │ └── dialog.ts │ │ ├── types.ts │ │ └── utils │ │ │ ├── async.ts │ │ │ ├── fetch.ts │ │ │ ├── legacy-middleware.ts │ │ │ ├── parse-utils.ts │ │ │ ├── promise-queue.ts │ │ │ ├── request-context.ts │ │ │ ├── transaction.ts │ │ │ └── type-utils.ts │ ├── tests │ │ ├── connect.test.ts │ │ ├── middlewares │ │ │ ├── block-cache.test.ts │ │ │ ├── chain-management.test.ts │ │ │ ├── eth-accounts.test.ts │ │ │ ├── fetch-rest.test.ts │ │ │ ├── fetch-rpc.test.ts │ │ │ ├── filter.test.ts │ │ │ ├── fixture.test.ts │ │ │ ├── signature.test.ts │ │ │ ├── subscription.test.ts │ │ │ ├── transaction-validator.test.ts │ │ │ └── typed-data-sanitizer.test.ts │ │ ├── store.test.ts │ │ └── util │ │ │ ├── async.ts │ │ │ ├── create-provider.ts │ │ │ ├── mem-store.ts │ │ │ ├── mock-engine.ts │ │ │ └── setup-jest.ts │ ├── tsconfig.json │ ├── tsconfig.main.json │ └── tsconfig.module.json ├── bitski │ ├── .gitignore │ ├── CHANGELOG.md │ ├── callback.html │ ├── jest.config.js │ ├── package.json │ ├── rollup.config.mjs │ ├── scripts │ │ ├── copy-readme.mjs │ │ ├── insert-package-version.mjs │ │ └── minify.mjs │ ├── src │ │ ├── -private │ │ │ ├── auth │ │ │ │ ├── access-token.ts │ │ │ │ ├── auth-provider.ts │ │ │ │ ├── oauth-manager.ts │ │ │ │ ├── openid-auth-provider.ts │ │ │ │ ├── popup-handler.ts │ │ │ │ ├── token-store.ts │ │ │ │ └── user.ts │ │ │ ├── callback.ts │ │ │ ├── components │ │ │ │ └── connect-button.ts │ │ │ ├── constants.ts │ │ │ ├── network.ts │ │ │ ├── sdk.ts │ │ │ ├── styles │ │ │ │ └── connect-button.ts │ │ │ └── utils │ │ │ │ ├── callback.ts │ │ │ │ ├── no-hash-query-string-utils.ts │ │ │ │ ├── numbers.ts │ │ │ │ ├── popup-validator.ts │ │ │ │ └── request-utils.ts │ │ ├── index.ts │ │ ├── load.ts │ │ └── provider-shim.ts │ ├── tests │ │ ├── auth-provider.test.ts │ │ ├── bitski.test.ts │ │ ├── connect-button.test.ts │ │ ├── oauth-manager.test.ts │ │ ├── shim.test.ts │ │ └── util │ │ │ ├── mem-store.ts │ │ │ ├── mock-oauth-manager.ts │ │ │ └── setup-jest.ts │ ├── tsconfig.json │ ├── tsconfig.main.json │ └── tsconfig.module.json ├── eth-provider-types │ ├── .gitignore │ ├── CHANGELOG.md │ ├── README.md │ ├── index.ts │ ├── package.json │ └── tsconfig.json ├── waas-react-sdk │ ├── .gitignore │ ├── .prettierignore │ ├── .prettierrc │ ├── CHANGELOG.md │ ├── README.md │ ├── index.html │ ├── package.json │ ├── postcss.config.cjs │ ├── public │ │ └── vite.svg │ ├── schema.graphql │ ├── src │ │ ├── App.tsx │ │ ├── lib │ │ │ ├── BitskiContext.tsx │ │ │ ├── api.ts │ │ │ ├── assets │ │ │ │ ├── apple.svg │ │ │ │ ├── arrow-rotate-right-left.svg │ │ │ │ ├── chains │ │ │ │ │ ├── icon-base.svg │ │ │ │ │ ├── icon-ethereum.svg │ │ │ │ │ ├── icon-optimism.svg │ │ │ │ │ └── icon-polygon.svg │ │ │ │ ├── check-checked.svg │ │ │ │ ├── check-disabled.svg │ │ │ │ ├── chevron-left-small.svg │ │ │ │ ├── chevron-right-small.svg │ │ │ │ ├── coinbase-wallet.svg │ │ │ │ ├── connector-icon-passkey.svg │ │ │ │ ├── connector-state-connected.svg │ │ │ │ ├── connector-state-error.svg │ │ │ │ ├── cross-small.svg │ │ │ │ ├── dapp-icon.svg │ │ │ │ ├── email.svg │ │ │ │ ├── empty-activities.svg │ │ │ │ ├── empty-tokens.svg │ │ │ │ ├── external-wallets.svg │ │ │ │ ├── google.svg │ │ │ │ ├── icon-activity-selected.svg │ │ │ │ ├── icon-activity.svg │ │ │ │ ├── icon-coinbase.svg │ │ │ │ ├── icon-disconnect.svg │ │ │ │ ├── icon-eth.svg │ │ │ │ ├── icon-matic.svg │ │ │ │ ├── icon-optimism.svg │ │ │ │ ├── icon-swaps-selected.svg │ │ │ │ ├── icon-swaps.svg │ │ │ │ ├── icon-tokens-selected.svg │ │ │ │ ├── icon-tokens.svg │ │ │ │ ├── icon-walletconnect.svg │ │ │ │ ├── injected-wallet.svg │ │ │ │ ├── loading.png │ │ │ │ ├── metamask.svg │ │ │ │ ├── other-wallets.svg │ │ │ │ ├── pending.png │ │ │ │ ├── phantom.svg │ │ │ │ ├── phone.svg │ │ │ │ ├── settings.svg │ │ │ │ ├── waas-logo.png │ │ │ │ └── x.svg │ │ │ ├── components │ │ │ │ ├── BitskiWalletProvider.tsx │ │ │ │ ├── BitskiWalletViewer.tsx │ │ │ │ ├── BitskiWidget │ │ │ │ │ ├── BitskiAuth.styles.css │ │ │ │ │ ├── BitskiAuth.tsx │ │ │ │ │ ├── BitskiConnect.tsx │ │ │ │ │ ├── BitskiProvider.tsx │ │ │ │ │ ├── BitskiWidget.tsx │ │ │ │ │ ├── EmailInput.tsx │ │ │ │ │ ├── SmsInput.tsx │ │ │ │ │ ├── Socials.tsx │ │ │ │ │ ├── TOS.tsx │ │ │ │ │ ├── Wallets.tsx │ │ │ │ │ ├── constants.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── states │ │ │ │ │ │ ├── ConnectionSessionCard.tsx │ │ │ │ │ │ └── Idle.tsx │ │ │ │ │ └── types │ │ │ │ │ │ ├── config.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── provider.ts │ │ │ │ │ │ └── states.ts │ │ │ │ ├── ChainIcon.tsx │ │ │ │ ├── ChainSwitcher.tsx │ │ │ │ ├── CopyAddress.tsx │ │ │ │ ├── Dialog │ │ │ │ │ ├── Dialog.styles.css │ │ │ │ │ └── index.tsx │ │ │ │ ├── EmptyActivities.tsx │ │ │ │ ├── EmptySwaps.tsx │ │ │ │ ├── EmptyTokens.tsx │ │ │ │ ├── LoadingSpinner.tsx │ │ │ │ ├── SettingsMenu.tsx │ │ │ │ ├── Skeleton.tsx │ │ │ │ └── hooks │ │ │ │ │ ├── TotalBalanceUSD.graphql │ │ │ │ │ ├── useActivity.ts │ │ │ │ │ └── useTokens.ts │ │ │ ├── connectors │ │ │ │ ├── bitski.ts │ │ │ │ ├── index.ts │ │ │ │ ├── localStoragePopup.ts │ │ │ │ ├── loginMethodConnectorConfigurator.ts │ │ │ │ └── phantom.ts │ │ │ ├── constants.ts │ │ │ ├── generated │ │ │ │ ├── gql │ │ │ │ │ ├── fragment-masking.ts │ │ │ │ │ ├── gql.ts │ │ │ │ │ ├── graphql.ts │ │ │ │ │ └── index.ts │ │ │ │ └── graphql.schema.json │ │ │ ├── index.css │ │ │ ├── index.ts │ │ │ ├── useBitski.tsx │ │ │ └── utils │ │ │ │ ├── createBitskiConfig.ts │ │ │ │ ├── getBlockchainAccounts.ts │ │ │ │ ├── hasWindowProvider.ts │ │ │ │ ├── index.ts │ │ │ │ ├── isMobile.ts │ │ │ │ ├── toFormattedValue.ts │ │ │ │ ├── toRawValue.ts │ │ │ │ ├── truncateAddress.ts │ │ │ │ ├── truncateTitle.ts │ │ │ │ ├── validateChains.ts │ │ │ │ └── validateConnectors.ts │ │ ├── main.tsx │ │ └── vite-env.d.ts │ ├── tailwind.config.cjs │ ├── tsconfig.json │ └── vite.config.ts └── wagmi-connector │ ├── .gitignore │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ └── index.ts │ └── tsconfig.json └── turbo.json /.changeset/README.md: -------------------------------------------------------------------------------- 1 | # Changesets 2 | 3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works 4 | with multi-package repos, or single-package repos to help you version and publish your code. You can 5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets) 6 | 7 | We have a quick list of common questions to get you started engaging with this project in 8 | [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) 9 | -------------------------------------------------------------------------------- /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@2.0.0/schema.json", 3 | "changelog": [ 4 | "@changesets/changelog-github", 5 | { 6 | "repo": "BitskiCo/bitski-js" 7 | } 8 | ], 9 | "commit": false, 10 | "fixed": [], 11 | "linked": [], 12 | "access": "public", 13 | "baseBranch": "main", 14 | "updateInternalDependencies": "patch", 15 | "ignore": [] 16 | } 17 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | end_of_line = lf 9 | charset = utf-8 10 | trim_trailing_whitespace = true 11 | insert_final_newline = true 12 | indent_style = space 13 | indent_size = 2 14 | 15 | [*.{diff,md}] 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | examples 2 | docs 3 | 4 | # compiled output 5 | packages/**/dist/ 6 | packages/**/lib/ 7 | 8 | # misc 9 | packages/**/coverage/ 10 | 11 | # Jest 12 | jest.config.base.js 13 | **/jest.config.js 14 | packages/bitski/scripts/version.js 15 | 16 | typedoc.js 17 | 18 | packages/bitski/tests/bitski.test.test 19 | packages/bitski/src/index.ts 20 | packages/bitski/src/provider-shim.ts 21 | 22 | packages/eth-provider-types/index.d.ts 23 | packages/eth-provider-types/index.js -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { "browser": true, "es2020": true }, 3 | "extends": [ 4 | "eslint:recommended", 5 | "plugin:@typescript-eslint/recommended", 6 | "plugin:react-hooks/recommended", 7 | "plugin:react/recommended", 8 | "plugin:prettier/recommended" 9 | ], 10 | "ignorePatterns": ["dist"], 11 | "parser": "@typescript-eslint/parser", 12 | "parserOptions": { 13 | "ecmaVersion": 12, 14 | "sourceType": "module" 15 | }, 16 | "plugins": ["@typescript-eslint", "react-refresh"], 17 | "rules": { 18 | "@typescript-eslint/no-var-requires": "off", 19 | "@typescript-eslint/no-explicit-any": "off", 20 | "@typescript-eslint/no-empty-function": "warn", 21 | "react/react-in-jsx-scope": "off", 22 | "react-refresh/only-export-components": ["warn", { "allowConstantExport": true }], 23 | "no-console": "error" 24 | }, 25 | "settings": { 26 | "react": { 27 | "version": "detect" 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | docs/* linguist-documentation 2 | docs/api/* linguist-generated 3 | packages/*/lib/* linguist-generated 4 | packages/*/dist/* linguist-generated 5 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/Bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | custom_fields: [] 5 | --- 6 | 7 | **Describe the bug** 8 | A clear and concise description of what the bug is. 9 | 10 | **To Reproduce** 11 | Steps to reproduce the behavior: 12 | 13 | 1. Go to '...' 14 | 2. Click on '....' 15 | 3. Scroll down to '....' 16 | 4. See error 17 | 18 | **Expected behavior** 19 | A clear and concise description of what you expected to happen. 20 | 21 | **Screenshots** 22 | If applicable, add screenshots to help explain your problem. 23 | 24 | **Desktop (please complete the following information):** 25 | 26 | - OS: [e.g. iOS] 27 | - Browser [e.g. chrome, safari] 28 | - Version [e.g. 22] 29 | 30 | **Smartphone (please complete the following information):** 31 | 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **SDK:** 38 | 39 | - Version [e.g. 0.1.0] 40 | 41 | **Additional context** 42 | Add any other context about the problem here. 43 | -------------------------------------------------------------------------------- /.github/workflows/cdn-deploy.yaml: -------------------------------------------------------------------------------- 1 | name: Upload CDN 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | deploy: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/setup-node@v2 13 | with: 14 | node-version: '18' 15 | registry-url: https://npm.pkg.github.com 16 | 17 | - uses: actions/checkout@master 18 | 19 | - name: Install Dependencies 20 | run: npm ci 21 | env: 22 | NODE_AUTH_TOKEN: ${{ secrets.GH_PACKAGE_READ_PAT }} 23 | 24 | - name: Build projects 25 | run: npm run build --workspaces || echo "No workspaces found, skipping build"; 26 | 27 | - name: Build dist 28 | run: npm run build 29 | 30 | - name: Configure AWS Credentials 31 | uses: aws-actions/configure-aws-credentials@v1 32 | with: 33 | aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} 34 | aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 35 | aws-region: us-west-2 36 | 37 | - name: Deploy static site to S3 bucket 38 | run: aws s3 sync --acl public-read --delete --cache-control max-age=60 ./packages/bitski/dist/bundled/ s3://cdn.bitskistatic.com/js/sdk/v3.3 39 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: ['main'] 6 | pull_request: 7 | types: [opened, synchronize] 8 | 9 | jobs: 10 | build: 11 | name: Build and Test 12 | timeout-minutes: 15 13 | runs-on: ubuntu-latest 14 | strategy: 15 | matrix: 16 | node-version: ['20'] 17 | 18 | steps: 19 | - name: Check out code 20 | uses: actions/checkout@v4 21 | 22 | - name: Use Node.js ${{ matrix.node-version }} 23 | uses: actions/setup-node@v4 24 | with: 25 | node-version: ${{ matrix.node-version }} 26 | cache: 'npm' 27 | 28 | - name: Install dependencies 29 | run: npm install 30 | 31 | - name: Format code 32 | run: npm run format 33 | 34 | - name: Lint code 35 | run: npm run lint 36 | 37 | - name: Build 38 | run: npm run build 39 | 40 | - name: Test 41 | run: npm run test 42 | -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | push: 4 | branches: 5 | - main 6 | jobs: 7 | release: 8 | name: Release 9 | strategy: 10 | matrix: 11 | node-version: ['18'] 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Checkout Repo 15 | uses: actions/checkout@v3 16 | 17 | - name: Setup Cache 18 | uses: actions/cache@v3 19 | with: 20 | path: | 21 | node_modules/.cache/turbo 22 | key: ${{ runner.os }}-${{ matrix.os }}-${{ hashFiles('**/package-lock.json') }} 23 | restore-keys: | 24 | ${{ runner.os }}-${{ matrix.os }}- 25 | ${{ runner.os }}- 26 | 27 | - name: Use Node.js ${{ matrix.node-version }} 28 | uses: actions/setup-node@v2 29 | with: 30 | node-version: ${{ matrix.node-version }} 31 | 32 | - name: Install dependencies 33 | run: npm install 34 | 35 | - name: Import GPG key 36 | uses: crazy-max/ghaction-import-gpg@v4 37 | with: 38 | gpg_private_key: ${{ secrets.RELEASE_BOT_GPG_KEY }} 39 | git_user_signingkey: true 40 | git_commit_gpgsign: true 41 | 42 | - name: Create Release Pull Request or Publish to npm 43 | id: changesets 44 | uses: changesets/action@v1 45 | with: 46 | publish: npm run release 47 | setupGitUser: false 48 | env: 49 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 50 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 51 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | junit.xml 8 | 9 | # Runtime data 10 | pids 11 | *.pid 12 | *.seed 13 | *.pid.lock 14 | 15 | # Directory for instrumented libs generated by jscoverage/JSCover 16 | lib-cov 17 | 18 | # Coverage directory used by tools like istanbul 19 | coverage 20 | 21 | # nyc test coverage 22 | .nyc_output 23 | 24 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 25 | .grunt 26 | 27 | # Bower dependency directory (https://bower.io/) 28 | bower_components 29 | 30 | # node-waf configuration 31 | .lock-wscript 32 | 33 | # Compiled binary addons (http://nodejs.org/api/addons.html) 34 | build/Release 35 | 36 | # Dependency directories 37 | node_modules/ 38 | jspm_packages/ 39 | 40 | # Typescript v1 declaration files 41 | typings/ 42 | 43 | # Optional npm cache directory 44 | .npm 45 | 46 | # Optional eslint cache 47 | .eslintcache 48 | 49 | # Optional REPL history 50 | .node_repl_history 51 | 52 | # Output of 'npm pack' 53 | *.tgz 54 | 55 | # Yarn Integrity file 56 | .yarn-integrity 57 | 58 | # dotenv environment variables file 59 | .env 60 | 61 | # Jekyll 62 | _site/* 63 | 64 | # Compiled library 65 | dist 66 | etc 67 | 68 | # Rollup 69 | .rpt2_cache/ 70 | 71 | # package locks 72 | examples/package-lock.json 73 | packages/bitski/package-lock.json 74 | packages/bitski-provider/package-lock.json 75 | 76 | .turbo 77 | build/** 78 | dist/** 79 | 80 | # compiled output 81 | packages/**/dist/ 82 | 83 | # dependencies 84 | packages/**/node_modules/ 85 | 86 | # misc 87 | packages/**/coverage/ 88 | .eslintcache 89 | 90 | # Docs, this is copied on build from the root README.md 91 | packages/bitski/README.md 92 | -------------------------------------------------------------------------------- /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "\$(dirname "\$0")/_/husky.sh" 3 | 4 | npx --no -- commitlint --edit "\${1}" -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx lint-staged 5 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | examples 2 | docs 3 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | build 4 | package 5 | .env 6 | .env.* 7 | !.env.example 8 | packages/**/package-lock.json 9 | docs/** 10 | examples/** 11 | typedoc.js 12 | 13 | **/CHANGELOG.md -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "all", 3 | "singleQuote": true, 4 | "printWidth": 100 5 | } 6 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Bitski 2 | 3 | We welcome bug reports, feedback, and pull requests to our SDK. Here are a few notes to help those interested in contributing. 4 | 5 | ## Reporting Issues 6 | 7 | Please use our issue template to give us the details we'll need to debug. You can find the template under .github/ISSUE_TEMPLATE/Bug_report.md. 8 | 9 | ## Development 10 | 11 | This project uses lerna to manage multiple NPM packages in a single repo. In order to get started developing, clone this repo, then run `npm install` to install lerna and various dev dependencies. 12 | 13 | When you run `npm install`, we have configured the project to also run `lerna bootstrap` which installs the dependencies of each package and creates links for shared dependencies. 14 | 15 | ## Building 16 | 17 | To do a build, from the root project run `npm run build`. This will run a build on each package using lerna. 18 | 19 | ## Testing 20 | 21 | To run the tests, run `npm test` from the root project. This will also generate a coverage report under the coverage folder. 22 | 23 | ## Pull Requests 24 | 25 | Please base your changes off and submit PRs against the develop branch. Develop is our pre-release branch, while master is our released branch. Our CI will automatically run tests for each package. 26 | 27 | ## Releases 28 | 29 | In order to keep things in sync we use lerna to release new versions to NPM. Here is the process we use: 30 | 31 | 1. Checkout develop branch 32 | 2. Pull from develop 33 | 3. `lerna publish` (select version) bumps version in packages, tags, tarballs, publishes, pushes tags and commit develop 34 | 4. Checkout new branch (release/version-number) 35 | 5. Checkout master 36 | 6. `git merge --no-ff release/version-number` 37 | 7. `git push` 38 | 8. Add release under Releases with new tag 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018 Out There Labs, Inc 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /MIGRATING.md: -------------------------------------------------------------------------------- 1 | # Migrating from 2.x to 3.x 2 | 3 | - Add `https://cdn.bitskistatic.com` to your CSP to allow the full SDK to load: 4 | 5 | - `connect-src`: `https://api.bitski.com` 6 | - `script-src`: `https://cdn.bitskistatic.com` 7 | 8 | - The provider now implements the standard `request()` method. Users should 9 | update to this method and move away from `send` and `sendAsync`, but these 10 | legacy methods are still available for the time being. 11 | 12 | - The signature and behavior of `send` and `sendAsync` has been updated to match 13 | the standard behavior of other providers (see Web3 or Ethers docs for details). 14 | 15 | - The provider now supports the `wallet_switchEthereumChain` and 16 | `wallet_addEthereumChain` RPC methods. 17 | 18 | - `Bitski.addProvider` no longer returns different instances of the provider for 19 | different networks. It instead returns a single provider instance, and if a 20 | network is specified, it calls `wallet_switchEthereumChain` to change the active 21 | chain of the provider. 22 | 23 | # Migrating from 1.x to 2.x 24 | 25 | - The `authStatus` property has been changed to the `getAuthStatus()`, which now 26 | returns a promise instead of the auth status directly. 27 | 28 | ```js 29 | // before 30 | const status = bitski.authStatus; 31 | 32 | // after 33 | const status = await bitski.getAuthStatus(); 34 | ``` 35 | 36 | - The `Store` interface must now return promises for all of its functions. The 37 | default behavior still uses local storage. 38 | -------------------------------------------------------------------------------- /docs/images/dev-portal-client-details.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BitskiCo/bitski-js/82a2a6faac4bfead544ca6d26769b843ed8f6ecc/docs/images/dev-portal-client-details.png -------------------------------------------------------------------------------- /docs/images/dev-portal-new-app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BitskiCo/bitski-js/82a2a6faac4bfead544ca6d26769b843ed8f6ecc/docs/images/dev-portal-new-app.png -------------------------------------------------------------------------------- /docs/images/dev-portal-redirect-urls.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BitskiCo/bitski-js/82a2a6faac4bfead544ca6d26769b843ed8f6ecc/docs/images/dev-portal-redirect-urls.png -------------------------------------------------------------------------------- /examples/ethers/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /examples/ethers/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + React 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /examples/ethers/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bitski-web3js-example", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "lint": "eslint src --ext js,jsx --report-unused-disable-directives --max-warnings 0", 10 | "preview": "vite preview" 11 | }, 12 | "dependencies": { 13 | "react": "^18.2.0", 14 | "react-dom": "^18.2.0" 15 | }, 16 | "devDependencies": { 17 | "@types/react": "^18.0.28", 18 | "@types/react-dom": "^18.0.11", 19 | "@vitejs/plugin-react": "^4.0.0", 20 | "autoprefixer": "^10.4.14", 21 | "bitski": "^3.3.0", 22 | "daisyui": "^2.51.6", 23 | "eslint": "^8.38.0", 24 | "eslint-plugin-react": "^7.32.2", 25 | "eslint-plugin-react-hooks": "^4.6.0", 26 | "eslint-plugin-react-refresh": "^0.3.4", 27 | "ethers": "^6.3.0", 28 | "postcss": "^8.4.31", 29 | "react-daisyui": "^3.1.2", 30 | "tailwindcss": "^3.3.2", 31 | "vite": "^4.5.2" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /examples/ethers/postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /examples/ethers/public/callback.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Logging in... 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /examples/ethers/src/index.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; -------------------------------------------------------------------------------- /examples/ethers/src/main.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom/client' 3 | import App from './App.jsx' 4 | import './index.css' 5 | 6 | ReactDOM.createRoot(document.getElementById('root')).render( 7 | 8 | 9 | , 10 | ) 11 | -------------------------------------------------------------------------------- /examples/ethers/tailwind.config.js: -------------------------------------------------------------------------------- 1 | import daisyui from 'daisyui'; 2 | 3 | /** @type {import('tailwindcss').Config} */ 4 | export default { 5 | content: [ 6 | 'node_modules/daisyui/dist/**/*.js', 7 | 'node_modules/react-daisyui/dist/**/*.js', 8 | "./index.html", 9 | "./src/**/*.{js,ts,jsx,tsx}", 10 | ], 11 | theme: { 12 | extend: {}, 13 | }, 14 | plugins: [daisyui], 15 | } 16 | 17 | -------------------------------------------------------------------------------- /examples/ethers/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | }) 8 | -------------------------------------------------------------------------------- /examples/standalone/.gitignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | -------------------------------------------------------------------------------- /examples/standalone/README.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | To run these, run ```npm install; npm run start:dev``` and then open http://localhost:8080/ -------------------------------------------------------------------------------- /examples/standalone/callback/callback.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Logging in... 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /examples/standalone/common.js: -------------------------------------------------------------------------------- 1 | import { Bitski } from 'bitski'; 2 | import Web3 from 'web3'; 3 | 4 | console.log("Setting up bitski..."); 5 | 6 | window.bitski = new Bitski('3b6d0360-071c-4210-8862-176164d6ec76', 'http://localhost:8080/callback/callback.html'); 7 | window.provider = window.bitski.getProvider(); 8 | window.web3 = new Web3(window.provider); 9 | 10 | window.web3.eth.getBlockNumber().then(number => console.log(number)); 11 | -------------------------------------------------------------------------------- /examples/standalone/connect-button/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |

Connect Button Examples

10 | 11 |

Small Button

12 |

13 | 14 |

Medium Button (Default)

15 |
16 | 17 |

Large Button

18 |
19 | 20 |

Default Invocation

21 |
22 | 23 | 24 | -------------------------------------------------------------------------------- /examples/standalone/connect-button/index.js: -------------------------------------------------------------------------------- 1 | window.addEventListener("load", function(){ 2 | // Create small button 3 | window.bitski.getConnectButton({ container: document.querySelector('#small-button-container'), size: 'SMALL' }, callback); 4 | 5 | // Create medium button 6 | window.bitski.getConnectButton({ container: document.querySelector('#medium-button-container'), size: 'MEDIUM' }, callback); 7 | 8 | // Create large button 9 | window.bitski.getConnectButton({ container: document.querySelector('#large-button-container'), size: 'LARGE' }, callback); 10 | 11 | var defaultButton = window.bitski.getConnectButton(); 12 | defaultButton.callback = callback; 13 | 14 | document.querySelector('#default-button').appendChild(defaultButton.element); 15 | }); 16 | 17 | function callback(error, user) { 18 | if(user) { 19 | window.web3.eth.getAccounts().then((accounts) => { 20 | var account = accounts[0]; 21 | console.log("Getting balance for " + account); 22 | return window.web3.eth.getBalance(account); 23 | }).then((balance) => { 24 | alert('Signed in! Current balance: ' + balance); 25 | return window.bitski.signOut(); 26 | }).catch((error) => { 27 | console.error("Error: " + error); 28 | }); 29 | } 30 | 31 | if (error) { 32 | console.error("Error signing in: " + error); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /examples/standalone/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Connect Button Example 7 | Sign Typed Data 8 | 9 | 10 | -------------------------------------------------------------------------------- /examples/standalone/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "start:dev": "webpack-dev-server --mode development" 4 | }, 5 | "dependencies": { 6 | "bitski": "^3.3.0", 7 | "web3": "1.9.0" 8 | }, 9 | "devDependencies": { 10 | "webpack": "^4.17.1", 11 | "webpack-cli": "^3.1.0", 12 | "webpack-dev-server": "^3.1.3" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /examples/standalone/sign-typed-data/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |

Sign Typed Data

10 | 11 | 14 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /examples/standalone/sign-typed-data/index.js: -------------------------------------------------------------------------------- 1 | window.addEventListener("load", function() { 2 | const bitski = window.bitski; 3 | 4 | // Create connect button 5 | const connectButton = bitski.getConnectButton({ container: document.querySelector('#connect-button') }); 6 | connectButton.callback = function(error, user){ 7 | if (user) { 8 | updateLoggedInState(); 9 | } 10 | 11 | if (error) { 12 | console.error("Error signing in: " + error); 13 | } 14 | }; 15 | 16 | const signButton = document.querySelector("#sign-button"); 17 | signButton.onclick = () => { 18 | signTypedData(); 19 | return false; 20 | }; 21 | 22 | updateLoggedInState(); 23 | }); 24 | 25 | function updateLoggedInState() { 26 | const loggedOutContainer = document.querySelector('#logged-out'); 27 | const loggedInContainer = document.querySelector('#logged-in'); 28 | 29 | if (bitski.authStatus === 'NOT_CONNECTED') { 30 | loggedOutContainer.style = 'display: block;'; 31 | loggedInContainer.style = 'display: none;'; 32 | } else { 33 | loggedOutContainer.style = 'display: none;'; 34 | loggedInContainer.style = 'display: block;'; 35 | } 36 | } 37 | 38 | function signTypedData() { 39 | const web3 = window.web3; 40 | const provider = window.provider; 41 | const msgParams = { 42 | types: { 43 | EIP712Domain: [ 44 | { name: 'name', type: 'string' }, 45 | { name: 'version', type: 'string' }, 46 | { name: 'chainId', type: 'uint256' }, 47 | { name: 'verifyingContract', type: 'address' }, 48 | ], 49 | Person: [ 50 | { name: 'name', type: 'string' }, 51 | { name: 'wallet', type: 'address' } 52 | ], 53 | Mail: [ 54 | { name: 'from', type: 'Person' }, 55 | { name: 'to', type: 'Person' }, 56 | { name: 'contents', type: 'string' } 57 | ], 58 | }, 59 | primaryType: 'Mail', 60 | domain: { 61 | name: 'Ether Mail', 62 | version: '0x1', 63 | chainId: '0x1', 64 | verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC', 65 | }, 66 | message: { 67 | from: { 68 | name: 'Cow', 69 | wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', 70 | }, 71 | to: { 72 | name: 'Bob', 73 | wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB', 74 | }, 75 | contents: 'Hello, Bob!', 76 | }, 77 | }; 78 | console.log("Sending: ", JSON.stringify(msgParams)); 79 | web3.eth.getAccounts().then(accounts => { 80 | const from = accounts[0]; 81 | provider.send('eth_signTypedData', [from, msgParams]).then(response => { 82 | console.log("Success! Response: " + response); 83 | alert(`Signed data: ${response}`); 84 | }).catch(err => { 85 | console.error('Error signing: ' + err); 86 | }); 87 | }).catch(err => { 88 | console.error('Error getting accounts: '+ err); 89 | }); 90 | 91 | } 92 | -------------------------------------------------------------------------------- /examples/standalone/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | entry: './common.js', 5 | devtool: 'inline-source-map', 6 | node: { 7 | fs: 'empty' 8 | }, 9 | output: { 10 | path: path.resolve(__dirname, 'dist'), 11 | filename: 'common.js' 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /examples/viem/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /examples/viem/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + React 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /examples/viem/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bitski-web3js-example", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "lint": "eslint src --ext js,jsx --report-unused-disable-directives --max-warnings 0", 10 | "preview": "vite preview" 11 | }, 12 | "dependencies": { 13 | "react": "^18.2.0", 14 | "react-dom": "^18.2.0" 15 | }, 16 | "devDependencies": { 17 | "@types/react": "^18.0.28", 18 | "@types/react-dom": "^18.0.11", 19 | "@vitejs/plugin-react": "^4.0.0", 20 | "autoprefixer": "^10.4.14", 21 | "bitski": "^3.3.0", 22 | "daisyui": "^2.51.6", 23 | "eslint": "^8.38.0", 24 | "eslint-plugin-react": "^7.32.2", 25 | "eslint-plugin-react-hooks": "^4.6.0", 26 | "eslint-plugin-react-refresh": "^0.3.4", 27 | "postcss": "^8.4.31", 28 | "react-daisyui": "^3.1.2", 29 | "tailwindcss": "^3.3.2", 30 | "viem": "^0.3.16", 31 | "vite": "^4.5.2" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /examples/viem/postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /examples/viem/public/callback.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Logging in... 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /examples/viem/src/index.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; -------------------------------------------------------------------------------- /examples/viem/src/main.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom/client' 3 | import App from './App.jsx' 4 | import './index.css' 5 | 6 | ReactDOM.createRoot(document.getElementById('root')).render( 7 | 8 | 9 | , 10 | ) 11 | -------------------------------------------------------------------------------- /examples/viem/tailwind.config.js: -------------------------------------------------------------------------------- 1 | import daisyui from 'daisyui'; 2 | 3 | /** @type {import('tailwindcss').Config} */ 4 | export default { 5 | content: [ 6 | 'node_modules/daisyui/dist/**/*.js', 7 | 'node_modules/react-daisyui/dist/**/*.js', 8 | "./index.html", 9 | "./src/**/*.{js,ts,jsx,tsx}", 10 | ], 11 | theme: { 12 | extend: {}, 13 | }, 14 | plugins: [daisyui], 15 | } 16 | 17 | -------------------------------------------------------------------------------- /examples/viem/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | }) 8 | -------------------------------------------------------------------------------- /examples/web3js/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { browser: true, es2020: true }, 3 | extends: [ 4 | 'eslint:recommended', 5 | 'plugin:react/recommended', 6 | 'plugin:react/jsx-runtime', 7 | 'plugin:react-hooks/recommended', 8 | ], 9 | parserOptions: { ecmaVersion: 'latest', sourceType: 'module' }, 10 | settings: { react: { version: '18.2' } }, 11 | plugins: ['react-refresh'], 12 | rules: { 13 | 'react-refresh/only-export-components': 'warn', 14 | }, 15 | } 16 | -------------------------------------------------------------------------------- /examples/web3js/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /examples/web3js/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + React 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /examples/web3js/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bitski-web3js-example", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "lint": "eslint src --ext js,jsx --report-unused-disable-directives --max-warnings 0", 10 | "preview": "vite preview" 11 | }, 12 | "dependencies": { 13 | "react": "^18.2.0", 14 | "react-dom": "^18.2.0" 15 | }, 16 | "devDependencies": { 17 | "@types/react": "^18.0.28", 18 | "@types/react-dom": "^18.0.11", 19 | "@vitejs/plugin-react": "^4.0.0", 20 | "autoprefixer": "^10.4.14", 21 | "bitski": "^3.3.0", 22 | "daisyui": "^2.51.6", 23 | "eslint": "^8.38.0", 24 | "eslint-plugin-react": "^7.32.2", 25 | "eslint-plugin-react-hooks": "^4.6.0", 26 | "eslint-plugin-react-refresh": "^0.3.4", 27 | "postcss": "^8.4.31", 28 | "react-daisyui": "^3.1.2", 29 | "tailwindcss": "^3.3.2", 30 | "vite": "^4.5.2", 31 | "web3": "^4.0.3" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /examples/web3js/postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /examples/web3js/public/callback.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Logging in... 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /examples/web3js/src/index.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; -------------------------------------------------------------------------------- /examples/web3js/src/main.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom/client' 3 | import App from './App.jsx' 4 | import './index.css' 5 | 6 | ReactDOM.createRoot(document.getElementById('root')).render( 7 | 8 | 9 | , 10 | ) 11 | -------------------------------------------------------------------------------- /examples/web3js/tailwind.config.js: -------------------------------------------------------------------------------- 1 | import daisyui from 'daisyui'; 2 | 3 | /** @type {import('tailwindcss').Config} */ 4 | export default { 5 | content: [ 6 | 'node_modules/daisyui/dist/**/*.js', 7 | 'node_modules/react-daisyui/dist/**/*.js', 8 | "./index.html", 9 | "./src/**/*.{js,ts,jsx,tsx}", 10 | ], 11 | theme: { 12 | extend: {}, 13 | }, 14 | plugins: [daisyui], 15 | } 16 | 17 | -------------------------------------------------------------------------------- /examples/web3js/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | }) 8 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { "target": "es6", "experimentalDecorators": true }, 3 | "exclude": ["node_modules", "bower_components", "tmp", "vendor", ".git", "dist"] 4 | } 5 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bitski-js", 3 | "private": true, 4 | "workspaces": [ 5 | "packages/*" 6 | ], 7 | "scripts": { 8 | "build": "turbo run build", 9 | "test": "turbo run test", 10 | "check": "turbo run check", 11 | "package": "turbo run package", 12 | "lint": "eslint .", 13 | "format": "prettier --write --plugin-search-dir=. .", 14 | "release": "turbo run build && changeset publish" 15 | }, 16 | "volta": { 17 | "node": "18.17.0" 18 | }, 19 | "version": "0.0.0", 20 | "devDependencies": { 21 | "@changesets/changelog-github": "^0.4.4", 22 | "@changesets/cli": "^2.22.0", 23 | "@commitlint/cli": "^17.6.1", 24 | "@commitlint/config-conventional": "^16.2.4", 25 | "eslint-plugin-react": "^7.33.2", 26 | "eslint-plugin-react-hooks": "^4.6.0", 27 | "eslint-plugin-react-refresh": "^0.4.5", 28 | "@typescript-eslint/eslint-plugin": "^6.14.0", 29 | "@typescript-eslint/parser": "^6.14.0", 30 | "eslint": "^8.55.0", 31 | "eslint-config-prettier": "^8.3.0", 32 | "eslint-plugin-prettier": "^4.0.0", 33 | "husky": "^8.0.1", 34 | "jest-fetch-mock": "^3.0.3", 35 | "prettier": "^2.4.1", 36 | "replace-in-files": "^3.0.0", 37 | "turbo": "^1.2.4", 38 | "typescript": "^5.2.2" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /packages/bitski-provider/.gitignore: -------------------------------------------------------------------------------- 1 | lib -------------------------------------------------------------------------------- /packages/bitski-provider/jest.config.js: -------------------------------------------------------------------------------- 1 | // jshint esversion: 9 2 | module.exports = { 3 | roots: ['/src', '/tests'], 4 | transform: { 5 | '^.+\\.ts$': [ 6 | 'ts-jest', 7 | { 8 | diagnostics: false, 9 | }, 10 | ], 11 | }, 12 | testRegex: '(/tests/.*.(test|spec)).(jsx?|tsx?)$', 13 | automock: false, 14 | collectCoverage: true, 15 | collectCoverageFrom: ['src/**/*.ts', '!**/node_modules/**'], 16 | coveragePathIgnorePatterns: [], 17 | moduleFileExtensions: ['js', 'ts'], 18 | coverageReporters: ['json', 'text', 'html', 'cobertura'], 19 | verbose: true, 20 | globals: { 21 | 'ts-jest': { 22 | diagnostics: false, 23 | }, 24 | }, 25 | displayName: 'Bitski Provider', 26 | testEnvironment: 'jsdom', 27 | setupFiles: ['/tests/util/setup-jest.ts'], 28 | }; 29 | -------------------------------------------------------------------------------- /packages/bitski-provider/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bitski-provider", 3 | "description": "Core Bitski provider", 4 | "license": "MIT", 5 | "main": "lib/index.js", 6 | "module": "dist/index.js", 7 | "types": "lib/index.d.ts", 8 | "repository": { 9 | "type": "git", 10 | "url": "https://github.com/BitskiCo/bitski-js" 11 | }, 12 | "version": "3.5.2", 13 | "scripts": { 14 | "test": "jest", 15 | "lint": "eslint . --cache", 16 | "build": "tsc -p tsconfig.main.json && tsc -p tsconfig.module.json && node ./scripts/insert-package-version.mjs", 17 | "prettier": "prettier --config ../../.prettierrc '{src,tests}/**/*.ts' --write" 18 | }, 19 | "dependencies": { 20 | "@metamask/safe-event-emitter": "2.0.0", 21 | "bn.js": "^4.11.8", 22 | "decoders": "^2.0.1", 23 | "eth-block-tracker": "^5.0.0", 24 | "eth-json-rpc-filters": "^5.0.0", 25 | "eth-json-rpc-middleware": "^9.0.1", 26 | "eth-provider-types": "^0.2.0", 27 | "eth-rpc-errors": "^4.0.3", 28 | "json-rpc-engine": "^6.1.0", 29 | "json-rpc-error": "^2.0.0", 30 | "uuid": "9.0.0" 31 | }, 32 | "devDependencies": { 33 | "@babel/core": "^7.6.4", 34 | "@babel/plugin-transform-runtime": "^7.6.2", 35 | "@babel/preset-env": "^7.6.3", 36 | "@testing-library/dom": "^8.19.0", 37 | "@types/jest": "^29.5.11", 38 | "@types/node": "^20.11.5", 39 | "babelify": "^10.0.0", 40 | "jest": "29.7.0", 41 | "jest-fetch-mock": "^3.0.3", 42 | "jest-environment-jsdom": "^29.0.0", 43 | "ts-jest": "^29.1.1" 44 | }, 45 | "browserify": { 46 | "transform": [ 47 | [ 48 | "babelify", 49 | { 50 | "presets": [ 51 | "@babel/preset-env" 52 | ], 53 | "plugins": [ 54 | "@babel/plugin-transform-runtime" 55 | ] 56 | } 57 | ] 58 | ] 59 | }, 60 | "gitHead": "7b9f0b01dd8a36a4294f27740ce264ecd95af35c" 61 | } 62 | -------------------------------------------------------------------------------- /packages/bitski-provider/scripts/insert-package-version.mjs: -------------------------------------------------------------------------------- 1 | import replaceInFiles from 'replace-in-files'; 2 | import packageJson from '../package.json' assert { type: 'json' }; 3 | 4 | await replaceInFiles({ 5 | files: ['./lib/**/*', './dist/**/*'], 6 | from: 'BITSKI_PROVIDER_VERSION', 7 | to: `"bitski-provider-v${packageJson.version}"`, 8 | }); 9 | -------------------------------------------------------------------------------- /packages/bitski-provider/src/dialogs/index.ts: -------------------------------------------------------------------------------- 1 | export interface DialogOpts { 2 | url: string; 3 | handleMessage: (message: Message) => Result; 4 | handleClose: () => Result; 5 | } 6 | 7 | export type OpenDialog = ( 8 | opts: DialogOpts, 9 | ) => { 10 | result: Promise; 11 | cancel: () => void; 12 | }; 13 | 14 | export { openIframeDialog } from './iframe'; 15 | export { openPopupDialog } from './popup'; 16 | -------------------------------------------------------------------------------- /packages/bitski-provider/src/dialogs/popup.ts: -------------------------------------------------------------------------------- 1 | import { OpenDialog } from '.'; 2 | import { addPostMessageHandler } from './shared'; 3 | 4 | const POPUP_HEIGHT = 620; 5 | const POPUP_WIDTH = 390; 6 | 7 | export const openPopupDialog: OpenDialog = (opts) => { 8 | // Fixes dual-screen position Most browsers Firefox 9 | const dualScreenLeft = window.screenLeft !== undefined ? window.screenLeft : window.screenX; 10 | const dualScreenTop = window.screenTop !== undefined ? window.screenTop : window.screenY; 11 | 12 | const width = window.innerWidth 13 | ? window.innerWidth 14 | : document.documentElement.clientWidth 15 | ? document.documentElement.clientWidth 16 | : screen.width; 17 | const height = window.innerHeight 18 | ? window.innerHeight 19 | : document.documentElement.clientHeight 20 | ? document.documentElement.clientHeight 21 | : screen.height; 22 | 23 | const systemZoom = width / window.screen.availWidth; 24 | const left = (width - POPUP_WIDTH) / 2 / systemZoom + dualScreenLeft; 25 | const top = (height - POPUP_HEIGHT) / 2 / systemZoom + dualScreenTop; 26 | const newWindow = window.open( 27 | opts.url, 28 | '_blank', 29 | ` 30 | width=${POPUP_WIDTH / systemZoom}, 31 | height=${POPUP_HEIGHT / systemZoom}, 32 | top=${top}, 33 | left=${left} 34 | `, 35 | ); 36 | 37 | if (!newWindow) { 38 | throw new Error('Could not open the signer window, please enable popups.'); 39 | } 40 | 41 | return { 42 | result: new Promise((resolve, reject) => { 43 | let finished = false; 44 | 45 | addPostMessageHandler( 46 | opts.handleMessage, 47 | () => { 48 | finished = true; 49 | newWindow.close(); 50 | }, 51 | resolve, 52 | reject, 53 | ); 54 | 55 | const pollTimer = window.setInterval(() => { 56 | if (newWindow.closed !== false) { 57 | window.clearInterval(pollTimer); 58 | 59 | if (finished) return; 60 | 61 | try { 62 | resolve(opts.handleClose()); 63 | } catch (e) { 64 | reject(e); 65 | } 66 | } 67 | }, 200); 68 | 69 | newWindow.focus(); 70 | }), 71 | cancel: () => newWindow.close(), 72 | }; 73 | }; 74 | -------------------------------------------------------------------------------- /packages/bitski-provider/src/dialogs/shared.ts: -------------------------------------------------------------------------------- 1 | import { IFRAME_MESSAGE_ORIGIN_ENDS_WITH } from '../constants'; 2 | 3 | export const addPostMessageHandler = ( 4 | handleMessage: (message: Message) => Result, 5 | close: () => void, 6 | resolve: (result: Result) => void, 7 | reject: (e: unknown) => void, 8 | ) => { 9 | if (typeof window === 'undefined') return; 10 | 11 | window.addEventListener('message', (event: MessageEvent) => { 12 | // Ignore messages from the current window, and from frames that aren't on Bitski.com 13 | if (event.source === window || !event.origin.endsWith(IFRAME_MESSAGE_ORIGIN_ENDS_WITH)) { 14 | return; 15 | } 16 | 17 | const data = event.data; 18 | 19 | // Ignore message events that don't actually have data 20 | if (data === undefined || data === null) { 21 | return; 22 | } 23 | 24 | try { 25 | resolve(handleMessage(data)); 26 | } catch (e) { 27 | reject(e); 28 | } finally { 29 | close(); 30 | } 31 | }); 32 | }; 33 | -------------------------------------------------------------------------------- /packages/bitski-provider/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './types'; 2 | export * from './constants'; 3 | export { LocalStorageStore } from './store'; 4 | export { createBitskiProvider, BitskiProvider } from './bitski-provider'; 5 | export { default as createBrowserSigner } from './signers/browser'; 6 | export { default as createRpcSigner } from './signers/rpc'; 7 | -------------------------------------------------------------------------------- /packages/bitski-provider/src/middleware/block-cache.ts: -------------------------------------------------------------------------------- 1 | import { createBlockCacheMiddleware as legacyCreateBlockCacheMiddleware } from 'eth-json-rpc-middleware/dist/block-cache'; 2 | import { JsonRpcMiddleware } from 'json-rpc-engine'; 3 | import { createLegacyMiddleware } from '../utils/legacy-middleware'; 4 | 5 | export const createBlockCacheMiddleware = (): JsonRpcMiddleware => 6 | createLegacyMiddleware(({ blockTracker }) => { 7 | // TODO type mismatch but they should be compatible 8 | return legacyCreateBlockCacheMiddleware({ blockTracker: blockTracker as any }); 9 | }); 10 | -------------------------------------------------------------------------------- /packages/bitski-provider/src/middleware/chain-management.ts: -------------------------------------------------------------------------------- 1 | import { 2 | EthChainDefinition, 3 | EthEvent, 4 | EthMethod, 5 | SwitchEthereumChainParameter, 6 | } from 'eth-provider-types'; 7 | import { ethErrors } from 'eth-rpc-errors'; 8 | import { createAsyncMiddleware, JsonRpcMiddleware } from 'json-rpc-engine'; 9 | import { getRequestContext } from '../utils/request-context'; 10 | import { expect } from '../utils/type-utils'; 11 | 12 | export const createChainManagementMiddleware = (): JsonRpcMiddleware => { 13 | return createAsyncMiddleware(async (req, res, next) => { 14 | const { method } = req; 15 | const context = getRequestContext(req); 16 | 17 | if (method === EthMethod.eth_chainId) { 18 | res.result = context.chain.chainId; 19 | 20 | return; 21 | } 22 | 23 | if (method === EthMethod.wallet_addEthereumChain) { 24 | const definition = expect( 25 | req.params?.[0], 26 | 'addEthereumChain requires a chain definition parameter', 27 | ) as EthChainDefinition; 28 | 29 | await context.store.addChain(definition); 30 | 31 | res.result = null; 32 | 33 | return; 34 | } 35 | 36 | if (method === EthMethod.wallet_switchEthereumChain) { 37 | const chainDetails = expect( 38 | req.params?.[0], 39 | 'switchEthereumChain requires a chainId', 40 | ) as SwitchEthereumChainParameter; 41 | 42 | const chain = await context.store.findChain(chainDetails.chainId); 43 | 44 | if (!chain) { 45 | throw ethErrors.provider.userRejectedRequest({ message: 'Chain does not exist' }); 46 | } 47 | 48 | await context.store.setCurrentChainId(chainDetails.chainId); 49 | context.emit(EthEvent.chainChanged, chainDetails.chainId); 50 | 51 | res.result = null; 52 | 53 | return; 54 | } 55 | 56 | return next(); 57 | }); 58 | }; 59 | -------------------------------------------------------------------------------- /packages/bitski-provider/src/middleware/eth-accounts.ts: -------------------------------------------------------------------------------- 1 | import { EthMethod } from 'eth-provider-types'; 2 | import { createAsyncMiddleware, JsonRpcMiddleware } from 'json-rpc-engine'; 3 | import { getRequestContext } from '../utils/request-context'; 4 | import { InternalBitskiProviderConfig } from '../types'; 5 | import { fetchJsonWithRetry } from '../utils/fetch'; 6 | import { ethErrors } from 'eth-rpc-errors'; 7 | 8 | const REFRESH = Symbol(); 9 | const ANON_USER = 'anonymous'; 10 | 11 | interface BlockchainAccount { 12 | id: string; 13 | profileId: string; 14 | displayName: string; 15 | kind: string; 16 | coinType: number; 17 | address: string; 18 | createdAt: string; 19 | updatedAt: string; 20 | } 21 | 22 | // We fetch accounts directly in this middleware so that we ensure we're always 23 | // hitting Bitski direcly and not another RPC url (e.g. for custom RPCs/chains). 24 | const fetchAccounts = async (config: InternalBitskiProviderConfig): Promise => { 25 | const headers = { ...config.additionalHeaders }; 26 | 27 | if (config.getAccessToken) { 28 | headers['Authorization'] = `Bearer ${await config.getAccessToken()}`; 29 | } 30 | 31 | const { accounts } = (await fetchJsonWithRetry( 32 | config.fetch, 33 | 5, 34 | `${config.apiBaseUrl}/v2/blockchain/accounts`, 35 | { 36 | method: 'GET', 37 | headers, 38 | }, 39 | )) as { accounts: BlockchainAccount[] }; 40 | 41 | if (!accounts) { 42 | throw ethErrors.rpc.internal('Could not find blockchain accounts'); 43 | } 44 | 45 | const moreThanOneAccount = accounts.length > 1; 46 | const mainAccount = accounts.find((a) => a.kind === 'bitski'); 47 | 48 | if (moreThanOneAccount && !mainAccount) { 49 | throw ethErrors.rpc.internal('Could not find blockchain accounts'); 50 | } 51 | 52 | if (moreThanOneAccount && mainAccount) { 53 | return [mainAccount.address]; 54 | } 55 | 56 | const accountAddresses = accounts.map((a) => a.address); 57 | 58 | return accountAddresses; 59 | }; 60 | 61 | export const createEthAccountsMiddleware = (): JsonRpcMiddleware => { 62 | const cache = new Map(); 63 | 64 | return createAsyncMiddleware(async (req, res, next) => { 65 | if (req.method !== EthMethod.eth_accounts && req.method !== EthMethod.eth_requestAccounts) { 66 | return next(); 67 | } 68 | 69 | const { config } = getRequestContext(req); 70 | const user = await config.getUser?.(); 71 | 72 | const userId = user?.id ?? ANON_USER; 73 | 74 | let accounts = cache.get(userId); 75 | 76 | if (!accounts || accounts === REFRESH) { 77 | accounts = await fetchAccounts(config); 78 | 79 | cache.set(userId, accounts); 80 | setTimeout(() => cache.set(userId, REFRESH), 5 * 60 * 1000); 81 | } 82 | 83 | res.result = accounts; 84 | }); 85 | }; 86 | -------------------------------------------------------------------------------- /packages/bitski-provider/src/middleware/fetch-rest.ts: -------------------------------------------------------------------------------- 1 | import { EthMethod } from 'eth-provider-types'; 2 | import { createAsyncMiddleware, JsonRpcMiddleware } from 'json-rpc-engine'; 3 | import { getRequestContext } from '../utils/request-context'; 4 | import { fetchJsonWithRetry } from '../utils/fetch'; 5 | 6 | const MATCHING_METHODS: string[] = [ 7 | EthMethod.eth_getBlockByNumber, 8 | EthMethod.eth_blockNumber, 9 | EthMethod.net_version, 10 | EthMethod.eth_getLogs, 11 | ]; 12 | 13 | export const createFetchRestMiddleware = (): JsonRpcMiddleware => { 14 | return createAsyncMiddleware(async (req, res, next) => { 15 | if (!MATCHING_METHODS.includes(req.method)) { 16 | return next(); 17 | } 18 | 19 | const { config, chain } = getRequestContext(req); 20 | 21 | const rpcUrl = new URL(chain.rpcUrls[0]); 22 | if (rpcUrl.hostname !== 'api.bitski.com') { 23 | // Custom RPC url 24 | return next(); 25 | } 26 | 27 | const query = 28 | (req.params?.length ?? 0) > 0 29 | ? `?params=${encodeURIComponent(JSON.stringify(req.params))}` 30 | : ''; 31 | 32 | const url = `${chain.rpcUrls[0]}/${req.method}${query}`; 33 | 34 | const headers = { ...config.additionalHeaders }; 35 | 36 | res.result = fetchJsonWithRetry(config.fetch, 5, url, { 37 | method: 'GET', 38 | headers, 39 | credentials: 'omit', 40 | }); 41 | }); 42 | }; 43 | -------------------------------------------------------------------------------- /packages/bitski-provider/src/middleware/fetch-rpc.ts: -------------------------------------------------------------------------------- 1 | import { createAsyncMiddleware, JsonRpcMiddleware } from 'json-rpc-engine'; 2 | import { getRequestContext } from '../utils/request-context'; 3 | import { fetchJsonRpcWithRetry } from '../utils/fetch'; 4 | 5 | export const createFetchRpcMiddleware = (): JsonRpcMiddleware => { 6 | return createAsyncMiddleware(async (req, res) => { 7 | const { config, chain } = getRequestContext(req); 8 | 9 | const headers = { ...config.additionalHeaders }; 10 | 11 | res.result = await fetchJsonRpcWithRetry(config.fetch, 5, chain.rpcUrls[0], { 12 | method: 'POST', 13 | headers, 14 | body: { 15 | id: req.id, 16 | jsonrpc: req.jsonrpc, 17 | method: req.method, 18 | params: req.params, 19 | }, 20 | }); 21 | }); 22 | }; 23 | -------------------------------------------------------------------------------- /packages/bitski-provider/src/middleware/filter.ts: -------------------------------------------------------------------------------- 1 | import { JsonRpcMiddleware } from 'json-rpc-engine'; 2 | import legacyCreateFilterMiddleware from 'eth-json-rpc-filters'; 3 | import { createLegacyMiddleware } from '../utils/legacy-middleware'; 4 | 5 | export const createFilterMiddleware = (): JsonRpcMiddleware => 6 | createLegacyMiddleware(({ blockTracker, provider, context }) => { 7 | const middleware = legacyCreateFilterMiddleware({ 8 | blockTracker, 9 | provider, 10 | }) as JsonRpcMiddleware & { destroy(): void }; 11 | 12 | context.addDestructor(middleware.destroy); 13 | 14 | return middleware; 15 | }); 16 | -------------------------------------------------------------------------------- /packages/bitski-provider/src/middleware/fixture.ts: -------------------------------------------------------------------------------- 1 | import { EthMethod, EthMethodResults } from 'eth-provider-types'; 2 | import { JsonRpcMiddleware } from 'json-rpc-engine'; 3 | 4 | export type Fixtures = Partial<{ 5 | [key in EthMethod]: EthMethodResults[key]; 6 | }>; 7 | 8 | export const DEFAULT_FIXTURES: Fixtures = { 9 | [EthMethod.web3_clientVersion]: 'Bitski/latest', 10 | [EthMethod.net_listening]: true, 11 | [EthMethod.eth_hashrate]: '0x00', 12 | [EthMethod.eth_mining]: false, 13 | }; 14 | 15 | export const createFixtureMiddleware = ( 16 | fixtures: Fixtures = DEFAULT_FIXTURES, 17 | ): JsonRpcMiddleware => { 18 | return (req, res, next, end) => { 19 | const fixture = fixtures[req.method]; 20 | 21 | if (fixture !== undefined) { 22 | res.result = fixture; 23 | return end(); 24 | } else { 25 | next(); 26 | } 27 | }; 28 | }; 29 | -------------------------------------------------------------------------------- /packages/bitski-provider/src/middleware/signature.ts: -------------------------------------------------------------------------------- 1 | import { EthMethod } from 'eth-provider-types'; 2 | import { createAsyncMiddleware, JsonRpcMiddleware } from 'json-rpc-engine'; 3 | import { SIGN_METHODS, SUPPORTED_CHAIN_IDS } from '../constants'; 4 | import { getRequestContext } from '../utils/request-context'; 5 | import { EthSignMethod, EthSignMethodParams } from '../types'; 6 | 7 | export const createSignatureMiddleware = (): JsonRpcMiddleware => { 8 | return createAsyncMiddleware(async (req, res, next) => { 9 | if (!SIGN_METHODS.includes(req.method)) { 10 | return next(); 11 | } 12 | 13 | const context = getRequestContext(req); 14 | 15 | const requiresLocalSend = 16 | !SUPPORTED_CHAIN_IDS.includes(context.chain.chainId) && 17 | req.method === EthMethod.eth_sendTransaction; 18 | 19 | // When we don't support a chain on the server (e.g. custom RPC url), we need 20 | // to sign the transaction via our flow, then send the signed payload locally 21 | const method = requiresLocalSend 22 | ? EthMethod.eth_signTransaction 23 | : (req.method as EthSignMethod); 24 | 25 | const signedResponse = await context.config.sign( 26 | method, 27 | req.params as EthSignMethodParams[EthSignMethod], 28 | context, 29 | ); 30 | 31 | res.result = requiresLocalSend 32 | ? context.request({ method: EthMethod.eth_sendRawTransaction, params: [signedResponse] }) 33 | : signedResponse; 34 | }); 35 | }; 36 | -------------------------------------------------------------------------------- /packages/bitski-provider/src/middleware/subscription.ts: -------------------------------------------------------------------------------- 1 | import { JsonRpcMiddleware } from 'json-rpc-engine'; 2 | import createSubscriptionManager from 'eth-json-rpc-filters/subscriptionManager'; 3 | import { createLegacyMiddleware } from '../utils/legacy-middleware'; 4 | import SafeEventEmitter from '@metamask/safe-event-emitter'; 5 | import { EthEvent, EthProviderMessageType } from 'eth-provider-types'; 6 | 7 | interface SubNotification { 8 | params: { 9 | subscription: string; 10 | result: unknown; 11 | }; 12 | } 13 | 14 | export const createSubscriptionMiddleware = (): JsonRpcMiddleware => 15 | createLegacyMiddleware(({ blockTracker, provider, context }) => { 16 | const manager = createSubscriptionManager({ blockTracker, provider }) as { 17 | middleware: JsonRpcMiddleware & { destroy(): void }; 18 | events: SafeEventEmitter; 19 | }; 20 | 21 | manager.events.on('notification', (notification: SubNotification) => { 22 | const data = { 23 | ...notification.params, 24 | subscription: `${context.chain.chainId}:${notification.params.subscription}`, 25 | }; 26 | 27 | context.emit(EthEvent.message, { 28 | type: EthProviderMessageType.eth_subscription, 29 | data, 30 | }); 31 | 32 | context.emit(EthEvent.data, null, { params: data }); 33 | }); 34 | 35 | context.addDestructor(manager.middleware.destroy); 36 | 37 | return manager.middleware; 38 | }); 39 | -------------------------------------------------------------------------------- /packages/bitski-provider/src/middleware/transaction-validator.ts: -------------------------------------------------------------------------------- 1 | import { EthMethod, EthTransaction } from 'eth-provider-types'; 2 | import { createAsyncMiddleware, JsonRpcMiddleware } from 'json-rpc-engine'; 3 | import { getRequestContext } from '../utils/request-context'; 4 | import { expect } from '../utils/type-utils'; 5 | 6 | export const createTransactionValidatorMiddleware = (): JsonRpcMiddleware< 7 | [transaction: Partial], 8 | unknown 9 | > => { 10 | return createAsyncMiddleware(async (req, _res, next) => { 11 | if ( 12 | req.method === EthMethod.eth_sendTransaction || 13 | req.method === EthMethod.eth_signTransaction 14 | ) { 15 | const [transaction] = expect(req.params, `${req.method} request missing required parameters`); 16 | const context = getRequestContext(req); 17 | 18 | if (transaction.from === undefined) { 19 | const accounts = await context.request({ method: EthMethod.eth_accounts }); 20 | transaction.from = accounts[0]; 21 | } 22 | } 23 | 24 | next(); 25 | }); 26 | }; 27 | -------------------------------------------------------------------------------- /packages/bitski-provider/src/signers/browser.ts: -------------------------------------------------------------------------------- 1 | import { InternalBitskiProviderConfig, RequestContext, SignFn } from '../types'; 2 | import { fetchJsonWithRetry } from '../utils/fetch'; 3 | import { createBitskiTransaction, Transaction } from '../utils/transaction'; 4 | import { getSignerUrl } from './shared'; 5 | 6 | /** 7 | * Responsible for submitting the Transaction object to the API 8 | * @param transaction The Transaction object to submit 9 | * @param accessToken The current user's access token 10 | */ 11 | const submitTransaction = async ( 12 | transaction: Transaction, 13 | config: InternalBitskiProviderConfig, 14 | ): Promise => { 15 | const headers = { ...config.additionalHeaders }; 16 | 17 | if (config.getAccessToken) { 18 | headers['Authorization'] = `Bearer ${await config.getAccessToken()}`; 19 | } 20 | 21 | const transactionApiUrl = 22 | config.waas?.transactionProxyUrl ?? `${config.apiBaseUrl}/v1/transactions`; 23 | 24 | const response = (await fetchJsonWithRetry(config.fetch, 5, transactionApiUrl, { 25 | method: 'POST', 26 | body: { transaction }, 27 | headers, 28 | })) as { transaction: Transaction }; 29 | 30 | return response.transaction; 31 | }; 32 | 33 | const redirectToCallbackURL = ( 34 | transaction: Transaction, 35 | config: InternalBitskiProviderConfig, 36 | ): Promise => { 37 | window.location.href = getSignerUrl(transaction.id, config); 38 | 39 | // return a non-resolving promise so we block until redirect 40 | // eslint-disable-next-line @typescript-eslint/no-empty-function 41 | return new Promise(() => {}); 42 | }; 43 | 44 | export type ShowSignerPopupFn = ( 45 | transaction: Transaction, 46 | context: RequestContext, 47 | submitTransaction: () => Promise, 48 | ) => Promise; 49 | export interface BrowserSignerConfig { 50 | showPopup?: ShowSignerPopupFn; 51 | } 52 | 53 | export default function createBrowserSigner({ showPopup }: BrowserSignerConfig = {}): SignFn { 54 | return async (method, params, requestContext): Promise => { 55 | const { config } = requestContext; 56 | const transaction = await createBitskiTransaction( 57 | method, 58 | params, 59 | requestContext.chain, 60 | requestContext.paymaster, 61 | requestContext.config.additionalSigningContext, 62 | ); 63 | 64 | // If we have a callback URL, use the redirect flow 65 | if (config.transactionCallbackUrl) { 66 | const persisted = await submitTransaction(transaction, config); 67 | return redirectToCallbackURL(persisted, config); 68 | } else { 69 | if (!showPopup) { 70 | throw new Error('You must provide a showPopup function when using the popup sign method'); 71 | } 72 | 73 | // Show the modal (await response) 74 | return showPopup(transaction, requestContext, () => submitTransaction(transaction, config)); 75 | } 76 | }; 77 | } 78 | -------------------------------------------------------------------------------- /packages/bitski-provider/src/signers/dialog.ts: -------------------------------------------------------------------------------- 1 | import { ethErrors } from 'eth-rpc-errors'; 2 | import { getSignerUrl } from './shared'; 3 | import { ShowSignerPopupFn } from './browser'; 4 | import { PromiseQueue } from '../utils/promise-queue'; 5 | import { OpenDialog } from '../dialogs'; 6 | 7 | // Global state, this manages the currently open signer popup. 8 | interface SignRequestItem { 9 | url: string; 10 | openDialog: OpenDialog; 11 | } 12 | 13 | const SIGN_REQUEST_QUEUE = new PromiseQueue(({ url, openDialog }: SignRequestItem) => { 14 | return openDialog({ 15 | url, 16 | handleMessage(message: { result?: string; error?: string }) { 17 | if (message.error) { 18 | throw new Error(message.error); 19 | } else { 20 | return message.result!; 21 | } 22 | }, 23 | handleClose() { 24 | throw ethErrors.provider.userRejectedRequest(); 25 | }, 26 | }); 27 | }); 28 | 29 | export const createDialogSigner = 30 | (openDialog: OpenDialog, isIframe: boolean): ShowSignerPopupFn => 31 | (transaction, context, submitTransaction): Promise => { 32 | const url = getSignerUrl(transaction.id, context.config, isIframe); 33 | 34 | const signRequest = { 35 | url, 36 | openDialog, 37 | }; 38 | 39 | // We can submit the transaction and show our authorization modal at the 40 | // same time, so they load in parallel 41 | submitTransaction().catch((error) => SIGN_REQUEST_QUEUE.cancel(signRequest, error)); 42 | 43 | return SIGN_REQUEST_QUEUE.push(signRequest); 44 | }; 45 | -------------------------------------------------------------------------------- /packages/bitski-provider/src/signers/rpc.ts: -------------------------------------------------------------------------------- 1 | import { SignFn } from '../types'; 2 | import { fetchJsonRpcWithRetry } from '../utils/fetch'; 3 | 4 | const signFn: SignFn = async (method, params, { config, chain }): Promise => { 5 | const headers = { ...config.additionalHeaders }; 6 | 7 | if (config.getAccessToken) { 8 | headers.Authorization = `Bearer ${await config.getAccessToken()}`; 9 | } 10 | 11 | return (await fetchJsonRpcWithRetry(config.fetch, 5, chain.rpcUrls[0], { 12 | method: 'POST', 13 | headers, 14 | body: { 15 | id: 1, 16 | jsonrpc: '2.0', 17 | method, 18 | params, 19 | }, 20 | })) as string; 21 | }; 22 | 23 | /** 24 | * This sign function signs by directly calling the method at the RPC endpoint, 25 | * without any user interaction. It requires the user to provide an access token 26 | * with `sign` scope, e.g. a client secret issued from the Bitski developer 27 | * portal. 28 | */ 29 | export default function createRpcSigner(): SignFn { 30 | return signFn; 31 | } 32 | -------------------------------------------------------------------------------- /packages/bitski-provider/src/signers/shared.ts: -------------------------------------------------------------------------------- 1 | import { InternalBitskiProviderConfig } from '../types'; 2 | 3 | export const getSignerUrl = ( 4 | transactionId: string, 5 | config: InternalBitskiProviderConfig, 6 | isIframe = false, 7 | ): string => { 8 | const searchParams = config.signerQueryParams ?? new URLSearchParams(); 9 | 10 | if (config.transactionCallbackUrl) { 11 | searchParams.set('redirectURI', config.transactionCallbackUrl); 12 | } 13 | 14 | if (config.waas?.enabled || config.waas?.userId) { 15 | const appId = config.appId ?? config.clientId; 16 | let federatedId = btoa(`${appId}`); 17 | 18 | if (config.waas?.userId) { 19 | federatedId = btoa(`${appId}:${config.waas?.userId}`); 20 | } 21 | 22 | searchParams.set('login_hint', `fa_${federatedId}`); 23 | } 24 | 25 | if (isIframe) { 26 | searchParams.set('isIframe', 'true'); 27 | } 28 | 29 | const searchParamsSerialized = searchParams.toString(); 30 | const searchParamsString = searchParamsSerialized !== '' ? `?${searchParamsSerialized}` : ''; 31 | 32 | return `${config.signerBaseUrl}/transactions/${transactionId}${searchParamsString}`; 33 | }; 34 | -------------------------------------------------------------------------------- /packages/bitski-provider/src/styles/dialog.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | 3 | const css = ` 4 | #bitski-dialog-container { 5 | position: fixed; 6 | left: 0; 7 | right: 0; 8 | top: 0; 9 | bottom: 0; 10 | background: rgba(0, 0, 0, 0); 11 | z-index: 1000; 12 | transition: background linear 0.2s; 13 | pointer-events: none; 14 | } 15 | #bitski-dialog-container.bitski-visible { 16 | background: rgba(0, 0, 0, 0.5); 17 | pointer-events: auto; 18 | } 19 | .bitski-dialog { 20 | opacity: 0; 21 | transform: translateY(100vh); 22 | transition: opacity 300ms linear, transform 400ms cubic-bezier(0.19, 1, 0.22, 1); 23 | pointer-events: none; 24 | position: absolute; 25 | top: 0; 26 | left: 0; 27 | width: 100%; 28 | height: 100%; 29 | } 30 | #bitski-dialog-container.bitski-visible .bitski-dialog { 31 | opacity: 1; 32 | transform: none; 33 | transition: opacity 300ms linear, transform 600ms cubic-bezier(0.19, 1, 0.22, 1); 34 | pointer-events: auto; 35 | } 36 | .bitski-dialog .bitski-close-button { 37 | background: transparent url('https://cdn.bitskistatic.com/sdk/close.svg') no-repeat 50% 50%; 38 | position: absolute; 39 | right: 12px; 40 | top: 12px; 41 | border: none; 42 | outline: none; 43 | margin: 0; 44 | cursor: pointer; 45 | padding: 0; 46 | width: 28px; 47 | height: 28px; 48 | z-index: 100; 49 | overflow: hidden; 50 | text-indent: -1000px; 51 | } 52 | .bitski-dialog-body { 53 | background: #fff; 54 | position: absolute; 55 | top: 0; 56 | bottom: 0; 57 | left: 0; 58 | right: 0; 59 | z-index: 5; 60 | max-width: 100%; 61 | } 62 | .bitski-dialog-body.bitski-loading::after { 63 | content: ""; 64 | background: url('https://cdn.bitskistatic.com/sdk/loading.svg') no-repeat 50% 50%; 65 | animation: rotate 600ms linear infinite; 66 | position: absolute; 67 | top: 50%; 68 | left: 50%; 69 | opacity: 0.3; 70 | width: 38px; 71 | height: 38px; 72 | margin-left: -19px; 73 | margin-top: -19px; 74 | z-index: -1; 75 | } 76 | @media (min-width: 600px) { 77 | #bitski-dialog-container { 78 | display: flex; 79 | align-items: center; 80 | justify-content: center; 81 | } 82 | .bitski-dialog { 83 | position: relative; 84 | width: 400px; 85 | height: 420px; 86 | } 87 | .bitski-dialog-body { 88 | border-radius: 16px; 89 | overflow: hidden; 90 | box-shadow: 0px 0px 0px 1px rgba(0,0,0,0.1), 0px 10px 50px rgba(0,0,0,0.4); 91 | } 92 | } 93 | 94 | @keyframes rotate { 95 | 0% { 96 | transform: rotate(0deg); 97 | } 98 | 50% { 99 | transform: rotate(180deg); 100 | } 101 | 100% { 102 | transform: rotate(360deg); 103 | } 104 | } 105 | `; 106 | export default css; 107 | -------------------------------------------------------------------------------- /packages/bitski-provider/src/utils/async.ts: -------------------------------------------------------------------------------- 1 | export const sleep = (ms: number): Promise => 2 | new Promise((resolve) => setTimeout(resolve, ms)); 3 | -------------------------------------------------------------------------------- /packages/bitski-provider/src/utils/promise-queue.ts: -------------------------------------------------------------------------------- 1 | interface PromiseQueueItem { 2 | item: T; 3 | cancel?: () => void; 4 | finished: boolean; 5 | resolve: (result: Result) => void; 6 | reject: (e: unknown) => void; 7 | } 8 | 9 | export class PromiseQueue { 10 | private queue: PromiseQueueItem[] = []; 11 | 12 | private currentItem?: PromiseQueueItem; 13 | 14 | constructor(private runItem: (details: T) => { result: Promise; cancel?: () => void }) {} 15 | 16 | push(item: T): Promise { 17 | let resolve, reject; 18 | const promise = new Promise((res, rej) => { 19 | resolve = res; 20 | reject = rej; 21 | }); 22 | 23 | this.queue.push({ 24 | item, 25 | resolve, 26 | reject, 27 | cancel: undefined, 28 | finished: false, 29 | }); 30 | 31 | if (!this.currentItem) { 32 | this.runNextItem(); 33 | } 34 | 35 | return promise; 36 | } 37 | 38 | cancel(item: T, reason: unknown) { 39 | const { currentItem } = this; 40 | 41 | if (currentItem?.item === item) { 42 | currentItem.cancel?.(); 43 | currentItem.reject(reason); 44 | currentItem.finished = true; 45 | this.runNextItem(); 46 | } else { 47 | this.queue = this.queue.filter(({ item: queueItem }) => queueItem !== item); 48 | } 49 | } 50 | 51 | async runNextItem() { 52 | const currentItem = (this.currentItem = this.queue.shift()); 53 | 54 | if (!currentItem) { 55 | return; 56 | } 57 | 58 | const { item, resolve, reject } = currentItem; 59 | 60 | try { 61 | const { result, cancel } = this.runItem(item); 62 | currentItem.cancel = cancel; 63 | const res = await result; 64 | 65 | if (currentItem.finished) return; 66 | 67 | resolve(res); 68 | } catch (e) { 69 | if (currentItem.finished) return; 70 | 71 | reject(e); 72 | } finally { 73 | if (!currentItem.finished) { 74 | currentItem.finished = true; 75 | 76 | this.runNextItem(); 77 | } 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /packages/bitski-provider/src/utils/request-context.ts: -------------------------------------------------------------------------------- 1 | import { JsonRpcRequest } from 'json-rpc-engine'; 2 | import { RequestContext } from '../types'; 3 | import { expect } from './type-utils'; 4 | 5 | export const getRequestContext = (req: JsonRpcRequest): RequestContext => { 6 | return expect( 7 | (req as unknown as { context: RequestContext }).context, 8 | 'no context found on this request', 9 | ); 10 | }; 11 | 12 | export const setRequestContext = ( 13 | req: JsonRpcRequest, 14 | context: RequestContext, 15 | ): void => { 16 | (req as unknown as { context: RequestContext }).context = context; 17 | }; 18 | -------------------------------------------------------------------------------- /packages/bitski-provider/src/utils/type-utils.ts: -------------------------------------------------------------------------------- 1 | import { ethErrors } from 'eth-rpc-errors'; 2 | 3 | export function expect(value: T | undefined | null, message: string): T { 4 | if (value === undefined || value === null) { 5 | throw ethErrors.rpc.invalidInput(message); 6 | } 7 | 8 | return value; 9 | } 10 | 11 | export function assert(predicate: boolean, message: string): asserts predicate { 12 | if (!predicate) { 13 | throw ethErrors.rpc.invalidInput(message); 14 | } 15 | } 16 | 17 | export const isError = (value: unknown): value is Error => value instanceof Error; 18 | -------------------------------------------------------------------------------- /packages/bitski-provider/tests/connect.test.ts: -------------------------------------------------------------------------------- 1 | import { EthEvent, EthMethod } from 'eth-provider-types'; 2 | import { Goerli, Mainnet } from '../src/constants'; 3 | import { sleep } from './util/async'; 4 | import { createTestProvider } from './util/create-provider'; 5 | 6 | describe('connect event', () => { 7 | test('emits connect event when initialized', async () => { 8 | expect.assertions(1); 9 | const provider = createTestProvider(); 10 | 11 | provider.on(EthEvent.connect, ({ chainId }) => { 12 | expect(chainId).toBe(Mainnet.chainId); 13 | }); 14 | 15 | // wait for listener to be called 16 | await sleep(10); 17 | }); 18 | 19 | test('passes the correct chain id on connect event', async () => { 20 | expect.assertions(1); 21 | const provider = createTestProvider(); 22 | 23 | await provider.request({ 24 | method: EthMethod.wallet_switchEthereumChain, 25 | params: [{ chainId: Goerli.chainId }], 26 | }); 27 | 28 | provider.on(EthEvent.connect, ({ chainId }) => { 29 | expect(chainId).toBe(Goerli.chainId); 30 | }); 31 | 32 | // wait for listener to be called 33 | await sleep(10); 34 | }); 35 | 36 | test('emits connect event only after first listener for connect is added', async () => { 37 | expect.assertions(1); 38 | const provider = createTestProvider(); 39 | 40 | // sleep a random amount of time before adding the listener 41 | await sleep(123); 42 | 43 | provider.on(EthEvent.connect, ({ chainId }) => { 44 | expect(chainId).toBe(Mainnet.chainId); 45 | }); 46 | 47 | // wait for listener to be called 48 | await sleep(10); 49 | }); 50 | }); 51 | -------------------------------------------------------------------------------- /packages/bitski-provider/tests/middlewares/fetch-rest.test.ts: -------------------------------------------------------------------------------- 1 | import { EthMethod } from 'eth-provider-types'; 2 | import { createTestProvider } from '../util/create-provider'; 3 | 4 | describe('fetch-rest middleware', () => { 5 | test('sends GET requests for specific methods', async () => { 6 | expect.assertions(11); 7 | const provider = createTestProvider(); 8 | 9 | fetchMock.mockResponse(async (req) => { 10 | expect(req.url).toBe('https://api.bitski.com/v1/web3/chains/1/eth_blockNumber'); 11 | expect(req.method).toBe('GET'); 12 | 13 | expect(req.headers.get('X-API-KEY')).toBe('test-client-id'); 14 | expect(req.headers.get('X-CLIENT-ID')).toBe('test-client-id'); 15 | expect(req.headers.get('X-CLIENT-VERSION')).toBe('test-version'); 16 | 17 | return JSON.stringify('0x123'); 18 | }); 19 | 20 | const result = await provider.request({ 21 | method: EthMethod.eth_blockNumber, 22 | }); 23 | 24 | expect(result).toEqual('0x123'); 25 | }); 26 | 27 | test('encodes parameters as query params', async () => { 28 | expect.assertions(3); 29 | const provider = createTestProvider(); 30 | 31 | fetchMock.mockResponse(async (req) => { 32 | if (req.url.includes('eth_blockNumber')) { 33 | return JSON.stringify({ 34 | id: 0, 35 | jsonrpc: '2.0', 36 | result: '0x123', 37 | }); 38 | } 39 | 40 | expect(req.url).toBe( 41 | 'https://api.bitski.com/v1/web3/chains/1/eth_getBlockByNumber?params=%5B%220x123%22%2Cfalse%5D', 42 | ); 43 | expect(req.method).toBe('GET'); 44 | 45 | return JSON.stringify({ number: '0x123' }); 46 | }); 47 | 48 | const result = await provider.request({ 49 | method: EthMethod.eth_getBlockByNumber, 50 | params: ['0x123', false], 51 | }); 52 | 53 | expect(result).toEqual({ number: '0x123' }); 54 | }); 55 | }); 56 | -------------------------------------------------------------------------------- /packages/bitski-provider/tests/middlewares/fixture.test.ts: -------------------------------------------------------------------------------- 1 | import { EthMethod } from 'eth-provider-types'; 2 | import { createTestProvider } from '../util/create-provider'; 3 | 4 | describe('fixture middleware', () => { 5 | test('responds with fixtures for certain methods', async () => { 6 | const provider = createTestProvider(); 7 | 8 | const result = await provider.request({ method: EthMethod.web3_clientVersion }); 9 | 10 | expect(result).toEqual('Bitski/latest'); 11 | expect(fetchMock.mock.calls.length).toBe(0); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /packages/bitski-provider/tests/middlewares/transaction-validator.test.ts: -------------------------------------------------------------------------------- 1 | import { EthMethod } from 'eth-provider-types'; 2 | import { createTestProvider } from '../util/create-provider'; 3 | 4 | describe('transaction-validator middleware', () => { 5 | test('adds `from` if it was not present', async () => { 6 | expect.assertions(3); 7 | const provider = createTestProvider({ 8 | getUser: async () => ({ 9 | id: 'test-id', 10 | }), 11 | }); 12 | 13 | const txn = { 14 | to: '0x', 15 | value: '0x', 16 | gas: '0x', 17 | gasPrice: '0x', 18 | }; 19 | 20 | fetchMock.once(async () => { 21 | return JSON.stringify({ 22 | accounts: [ 23 | { 24 | kind: 'bitski', 25 | address: '0x123', 26 | }, 27 | ], 28 | }); 29 | }); 30 | 31 | fetchMock.once(async (req) => { 32 | const { method, params } = await req.json(); 33 | 34 | expect(method).toEqual(EthMethod.eth_sendTransaction); 35 | expect(params[0]).toEqual({ 36 | from: '0x123', 37 | ...txn, 38 | }); 39 | 40 | return JSON.stringify({ result: '0x123' }); 41 | }); 42 | 43 | const result = await provider.request({ 44 | method: EthMethod.eth_sendTransaction, 45 | params: [{ ...txn }], 46 | }); 47 | 48 | expect(result).toBe('0x123'); 49 | }); 50 | 51 | test('it only updates values that are missing', async () => { 52 | expect.assertions(3); 53 | const provider = createTestProvider({ 54 | getUser: async () => ({ 55 | id: 'test-id', 56 | accounts: ['0x123'], 57 | }), 58 | }); 59 | 60 | const txn = { 61 | from: '0x456', 62 | to: '0x', 63 | value: '0x', 64 | gas: '0x', 65 | gasPrice: '0x', 66 | }; 67 | 68 | fetchMock.mockResponse(async (req) => { 69 | const { method, params } = await req.json(); 70 | 71 | expect(method).toEqual(EthMethod.eth_sendTransaction); 72 | expect(params[0]).toEqual({ 73 | ...txn, 74 | from: '0x456', 75 | }); 76 | 77 | return JSON.stringify({ result: '0x123' }); 78 | }); 79 | 80 | const result = await provider.request({ 81 | method: EthMethod.eth_sendTransaction, 82 | params: [{ ...txn }], 83 | }); 84 | 85 | expect(result).toBe('0x123'); 86 | }); 87 | }); 88 | -------------------------------------------------------------------------------- /packages/bitski-provider/tests/store.test.ts: -------------------------------------------------------------------------------- 1 | import { EthMethod } from 'eth-provider-types'; 2 | import { CHAINS_STORAGE_KEY, CURRENT_CHAIN_STORAGE_KEY } from '../src/store'; 3 | import { DEFAULT_CHAINS, Goerli } from '../src/constants'; 4 | import { toHex } from '../src/utils/parse-utils'; 5 | import { createTestProvider } from './util/create-provider'; 6 | import MemStore from './util/mem-store'; 7 | 8 | describe('store', () => { 9 | test('stores chains and current chain id', async () => { 10 | const store = new MemStore(); 11 | const provider = createTestProvider({ store }); 12 | 13 | const customChain = { chainId: toHex(77), rpcUrls: ['http://localhost:3000'] }; 14 | 15 | await provider.request({ 16 | method: EthMethod.wallet_addEthereumChain, 17 | params: [customChain], 18 | }); 19 | 20 | await provider.request({ 21 | method: EthMethod.wallet_switchEthereumChain, 22 | params: [{ chainId: toHex(77) }], 23 | }); 24 | 25 | const chains = await store.getItem(CHAINS_STORAGE_KEY); 26 | const currentChainId = await store.getItem(CURRENT_CHAIN_STORAGE_KEY); 27 | 28 | expect(chains).toEqual([...DEFAULT_CHAINS, customChain]); 29 | expect(currentChainId).toBe(toHex(77)); 30 | }); 31 | 32 | test('restores state from store', async () => { 33 | const store = new MemStore(); 34 | 35 | const customChain = { chainId: toHex(77), rpcUrls: ['http://localhost:3000'] }; 36 | 37 | store.setItem(CHAINS_STORAGE_KEY, [...DEFAULT_CHAINS, customChain]); 38 | store.setItem(CURRENT_CHAIN_STORAGE_KEY, Goerli.chainId); 39 | 40 | const provider = createTestProvider({ store }); 41 | 42 | // defaults to persisted chain id 43 | const result1 = await provider.request({ method: EthMethod.eth_chainId }); 44 | expect(result1).toBe(Goerli.chainId); 45 | 46 | // switch to persisted custom chain 47 | const result2 = await provider.request({ 48 | method: EthMethod.wallet_switchEthereumChain, 49 | params: [{ chainId: toHex(77) }], 50 | }); 51 | expect(result2).toBe(null); 52 | }); 53 | }); 54 | -------------------------------------------------------------------------------- /packages/bitski-provider/tests/util/async.ts: -------------------------------------------------------------------------------- 1 | // Make sure we can always sleep even if we're using fake timers elsewhere 2 | const originalSetTimetout = globalThis.setTimeout; 3 | 4 | export const sleep = (ms: number): Promise => 5 | new Promise((resolve) => originalSetTimetout(resolve, ms)); 6 | -------------------------------------------------------------------------------- /packages/bitski-provider/tests/util/create-provider.ts: -------------------------------------------------------------------------------- 1 | import { BitskiProvider, BitskiProviderConfig, createBitskiProvider } from '../../src/index'; 2 | import createRpcSigner from '../../src/signers/rpc'; 3 | import MemStore from './mem-store'; 4 | 5 | export const TEST_CLIENT_ID = 'test-client-id'; 6 | 7 | export const createTestProvider = (opts?: Partial): BitskiProvider => { 8 | return createBitskiProvider({ 9 | clientId: TEST_CLIENT_ID, 10 | store: new MemStore(), 11 | sign: createRpcSigner(), 12 | ...opts, 13 | }); 14 | }; 15 | -------------------------------------------------------------------------------- /packages/bitski-provider/tests/util/mem-store.ts: -------------------------------------------------------------------------------- 1 | import { BitskiProviderStore } from '../../src/index'; 2 | 3 | export default class MemStore implements BitskiProviderStore { 4 | private store: { [key: string]: unknown } = {}; 5 | 6 | keys(): string[] { 7 | return Object.keys(this.store); 8 | } 9 | 10 | async getItem(key: string): Promise { 11 | return this.store[key]; 12 | } 13 | 14 | async setItem(key: string, value: unknown): Promise { 15 | this.store[key] = value; 16 | } 17 | 18 | async clearItem(key: string): Promise { 19 | delete this.store[key]; 20 | } 21 | 22 | async clear(): Promise { 23 | this.store = {}; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages/bitski-provider/tests/util/mock-engine.ts: -------------------------------------------------------------------------------- 1 | import Web3ProviderEngine from '@bitski/provider-engine'; 2 | import { FixtureSubprovider } from '@bitski/provider-engine'; 3 | 4 | export class MockEngine extends Web3ProviderEngine { 5 | constructor(opts) { 6 | super(opts); 7 | this.addProvider( 8 | new FixtureSubprovider({ 9 | eth_blockNumber: '0x0', 10 | eth_getBlockByNumber: false, 11 | eth_hashrate: '0x00', 12 | eth_mining: false, 13 | eth_syncing: true, 14 | net_listening: true, 15 | web3_clientVersion: 'ProviderEngine/v0.0.0/javascript', 16 | }), 17 | ); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/bitski-provider/tests/util/setup-jest.ts: -------------------------------------------------------------------------------- 1 | import { enableFetchMocks } from 'jest-fetch-mock'; 2 | 3 | enableFetchMocks(); 4 | globalThis.BITSKI_PROVIDER_VERSION = 'test-version'; 5 | -------------------------------------------------------------------------------- /packages/bitski-provider/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "dist", 4 | "noImplicitAny": false, 5 | "strict": true, 6 | "moduleResolution": "node", 7 | "lib": ["es2017", "dom"], 8 | "target": "ES2017", 9 | "esModuleInterop": true 10 | }, 11 | "include": ["src", "**/*.test.ts"] 12 | } 13 | -------------------------------------------------------------------------------- /packages/bitski-provider/tsconfig.main.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "lib", 5 | "module": "commonjs", 6 | "declaration": true, 7 | "sourceMap": true 8 | }, 9 | "include": ["src"] 10 | } 11 | -------------------------------------------------------------------------------- /packages/bitski-provider/tsconfig.module.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "dist/", 5 | "module": "es6" 6 | }, 7 | "include": ["src"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/bitski/.gitignore: -------------------------------------------------------------------------------- 1 | lib -------------------------------------------------------------------------------- /packages/bitski/callback.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Logging in... 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /packages/bitski/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | roots: ['/src', '/tests'], 3 | transform: { 4 | '^.+\\.ts$': [ 5 | 'ts-jest', 6 | { 7 | diagnostics: false, 8 | }, 9 | ], 10 | }, 11 | testRegex: '(/tests/.*.(test|spec)).(jsx?|tsx?)$', 12 | automock: false, 13 | collectCoverage: true, 14 | collectCoverageFrom: ['src/**/*.ts', '!**/node_modules/**'], 15 | coveragePathIgnorePatterns: [], 16 | moduleFileExtensions: ['js', 'ts'], 17 | coverageReporters: ['json', 'text', 'html', 'cobertura'], 18 | verbose: true, 19 | globals: { 20 | 'ts-jest': { 21 | diagnostics: false, 22 | }, 23 | }, 24 | displayName: 'Bitski SDK', 25 | testEnvironment: 'jsdom', 26 | automock: false, 27 | resetMocks: false, 28 | setupFiles: ['/tests/util/setup-jest.ts'], 29 | }; 30 | -------------------------------------------------------------------------------- /packages/bitski/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bitski", 3 | "description": "Bitski Javascript SDK", 4 | "license": "MIT", 5 | "main": "lib/index.js", 6 | "module": "dist/esm/index.js", 7 | "types": "lib/index.d.ts", 8 | "repository": { 9 | "type": "git", 10 | "url": "https://github.com/BitskiCo/bitski-js" 11 | }, 12 | "version": "4.2.1", 13 | "scripts": { 14 | "lint": "eslint . --cache", 15 | "test": "jest", 16 | "build": "tsc -p tsconfig.main.json && tsc -p tsconfig.module.json && node ./scripts/insert-package-version.mjs && npm run bundle && node ./scripts/copy-readme.mjs", 17 | "bundle": "mkdir -p dist/bundled && npm run bundle:main && npm run bundle:callback && npm run minify", 18 | "bundle:main": "rollup --config rollup.config.mjs", 19 | "bundle:callback": "browserify dist/esm/-private/callback.js -d -t [ babelify --presets [ @babel/preset-env ] --plugins [ @babel/plugin-transform-runtime ] ] > dist/bundled/callback.js", 20 | "minify": "node ./scripts/minify.mjs", 21 | "prettier": "prettier --config .prettierrc '{src,tests}/**/*.ts' --write" 22 | }, 23 | "dependencies": { 24 | "@openid/appauth": "^1.2.6", 25 | "bitski-provider": "^3.5.1", 26 | "decoders": "^2.0.1", 27 | "eth-provider-types": "^0.2.0", 28 | "hash-it": "^6.0.0" 29 | }, 30 | "devDependencies": { 31 | "@babel/core": "^7.8.7", 32 | "@babel/plugin-transform-runtime": "^7.6.2", 33 | "@babel/preset-env": "^7.6.3", 34 | "@rollup/plugin-commonjs": "^23.0.4", 35 | "@rollup/plugin-node-resolve": "^15.0.1", 36 | "@types/jest": "^29.5.11", 37 | "@types/node": "^20.11.5", 38 | "babelify": "^10.0.0", 39 | "browserify": "^16.5.0", 40 | "jest": "^29.7.0", 41 | "jest-fetch-mock": "^3.0.3", 42 | "jest-environment-jsdom": "^29.0.0", 43 | "rollup": "^3.7.1", 44 | "rollup-plugin-node-globals": "^1.4.0", 45 | "rollup-plugin-node-polyfills": "^0.2.1", 46 | "terser": "^5.16.1", 47 | "ts-jest": "^29.1.1" 48 | }, 49 | "browserslist": [ 50 | "last 3 chrome versions", 51 | "last 3 firefox versions", 52 | "last 3 safari versions", 53 | "last 3 ios versions", 54 | "last 3 chromeandroid versions", 55 | "last 3 edge versions" 56 | ], 57 | "gitHead": "7b9f0b01dd8a36a4294f27740ce264ecd95af35c" 58 | } 59 | -------------------------------------------------------------------------------- /packages/bitski/rollup.config.mjs: -------------------------------------------------------------------------------- 1 | import { nodeResolve } from '@rollup/plugin-node-resolve'; 2 | import nodePolyfills from 'rollup-plugin-node-polyfills'; 3 | import nodeGlobals from 'rollup-plugin-node-globals'; 4 | import commonjs from '@rollup/plugin-commonjs'; 5 | 6 | export default { 7 | input: 'dist/esm/-private/sdk.js', 8 | output: { 9 | file: 'dist/bundled/bitski.bundle.js', 10 | format: 'umd', 11 | name: 'Bitski', 12 | }, 13 | plugins: [ 14 | commonjs(), 15 | nodeResolve(), 16 | nodePolyfills(), 17 | nodeGlobals({ 18 | process: false, 19 | buffer: false, 20 | dirname: false, 21 | filename: false, 22 | baseDir: false, 23 | }), 24 | ], 25 | }; 26 | -------------------------------------------------------------------------------- /packages/bitski/scripts/copy-readme.mjs: -------------------------------------------------------------------------------- 1 | import { promises as fs } from 'fs'; 2 | import replaceInFiles from 'replace-in-files'; 3 | 4 | await fs.copyFile('../../README.md', './README.md'); 5 | 6 | await replaceInFiles({ 7 | files: ['./README.md'], 8 | from: /\(\/docs\/images/g, 9 | to: `(https://raw.githubusercontent.com/BitskiCo/bitski-js/main/docs/images`, 10 | }); 11 | -------------------------------------------------------------------------------- /packages/bitski/scripts/insert-package-version.mjs: -------------------------------------------------------------------------------- 1 | import replaceInFiles from 'replace-in-files'; 2 | import packageJson from '../package.json' assert { type: 'json' }; 3 | 4 | await replaceInFiles({ 5 | files: ['./lib/**/*', './dist/**/*'], 6 | from: 'BITSKI_SDK_VERSION', 7 | to: `"bitski-sdk-v${packageJson.version}"`, 8 | }); 9 | -------------------------------------------------------------------------------- /packages/bitski/scripts/minify.mjs: -------------------------------------------------------------------------------- 1 | import { minify } from 'terser'; 2 | import { promises as fs } from 'fs'; 3 | 4 | const src = await fs.readFile('./dist/bundled/bitski.bundle.js', 'utf8'); 5 | 6 | const min = await minify(src); 7 | 8 | await fs.writeFile('./dist/bundled/bitski.min.js', min.code, 'utf8'); 9 | -------------------------------------------------------------------------------- /packages/bitski/src/-private/auth/access-token.ts: -------------------------------------------------------------------------------- 1 | import { TokenResponse } from '@openid/appauth'; 2 | /** 3 | * Represents a Bitski access token 4 | */ 5 | export class AccessToken { 6 | /** 7 | * Creates a token from a TokenResponse object 8 | * @param tokenResponse The token response object to build a token from 9 | */ 10 | public static fromTokenResponse(tokenResponse: TokenResponse) { 11 | let expiresAt: number | undefined; 12 | if (tokenResponse.expiresIn) { 13 | expiresAt = Math.floor(Date.now() / 1000) + tokenResponse.expiresIn; 14 | } 15 | return new AccessToken(tokenResponse.accessToken, expiresAt, tokenResponse.scope); 16 | } 17 | 18 | /** 19 | * Creates a token from a storage string 20 | * @param s JSON string representing the token 21 | */ 22 | public static fromString(s: string): AccessToken | undefined { 23 | let parsed: any | undefined; 24 | try { 25 | parsed = JSON.parse(s); 26 | } catch (error) { 27 | return; 28 | } 29 | if (!parsed.token) { 30 | return; 31 | } 32 | return new AccessToken(parsed.token, parsed.expiresAt, parsed.scope); 33 | } 34 | 35 | /** 36 | * The actual access token 37 | */ 38 | public token: string; 39 | 40 | /** 41 | * When the token expires (in seconds) 42 | */ 43 | public expiresAt?: number; 44 | 45 | /** 46 | * Scopes this token has access to 47 | */ 48 | public scope?: string; 49 | 50 | /** 51 | * Calculates if the token is still active 52 | */ 53 | public get expired() { 54 | if (this.expiresAt) { 55 | const now = Math.floor(Date.now() / 1000); 56 | const expiresIn = this.expiresAt - now; 57 | return expiresIn <= 0; 58 | } 59 | return false; 60 | } 61 | 62 | /** 63 | * 64 | * @param token the access token 65 | * @param expiresAt the token expiration date (in seconds) (optional) 66 | * @param scope the scopes this token represents (optional) 67 | */ 68 | constructor(token: string, expiresAt?: number, scope?: string) { 69 | this.token = token; 70 | this.scope = scope; 71 | this.expiresAt = expiresAt; 72 | } 73 | 74 | /** 75 | * Returns a JSON string suitable for writing in local storage 76 | */ 77 | public toStorageString(): string { 78 | return JSON.stringify({ 79 | expiresAt: this.expiresAt, 80 | scope: this.scope, 81 | token: this.token, 82 | }); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /packages/bitski/src/-private/auth/auth-provider.ts: -------------------------------------------------------------------------------- 1 | import type { AuthenticationStatus, OAuthSignInMethod } from '../constants'; 2 | import type { SignInOptions } from './oauth-manager'; 3 | import type { User } from './user'; 4 | 5 | export interface AuthProvider { 6 | getAuthStatus(): Promise; 7 | signIn(method: OAuthSignInMethod, opts?: SignInOptions): Promise; 8 | connect(): Promise; 9 | signInOrConnect(signInMethod?: OAuthSignInMethod, opts?: SignInOptions): Promise; 10 | getUser(): Promise; 11 | redirectCallback(): Promise; 12 | signOut(): Promise; 13 | } 14 | -------------------------------------------------------------------------------- /packages/bitski/src/-private/auth/user.ts: -------------------------------------------------------------------------------- 1 | export interface UserInfoResponse { 2 | sub: string; 3 | accounts?: string[]; 4 | email?: string; 5 | phone_number?: string; 6 | phone_number_verified?: boolean; 7 | email_verified?: boolean; 8 | preferred_username?: string; 9 | } 10 | 11 | export class User { 12 | public static fromJson(json: UserInfoResponse): User { 13 | return new User( 14 | json.sub, 15 | json.accounts, 16 | json.email, 17 | json.email_verified, 18 | json.phone_number, 19 | json.phone_number_verified, 20 | json.preferred_username, 21 | ); 22 | } 23 | 24 | public static fromString(s: string): User | undefined { 25 | let parsed; 26 | try { 27 | parsed = JSON.parse(s); 28 | } catch (e) { 29 | return; 30 | } 31 | if (parsed.id) { 32 | return new User( 33 | parsed.id, 34 | parsed.accounts, 35 | parsed.email, 36 | parsed.emailVerified, 37 | parsed.phoneNumber, 38 | parsed.phoneNumberVerified, 39 | parsed.preferredUsername, 40 | ); 41 | } 42 | return; 43 | } 44 | 45 | public id: string; 46 | public accounts?: string[]; 47 | public email?: string; 48 | public emailVerified?: boolean; 49 | public phoneNumber?: string; 50 | public phoneNumberVerified?: boolean; 51 | public preferredUsername?: string; 52 | 53 | constructor( 54 | id: string, 55 | accounts?: string[], 56 | email?: string, 57 | emailVerified?: boolean, 58 | phone?: string, 59 | phoneNumberVerified?: boolean, 60 | preferredUsername?: string, 61 | ) { 62 | this.id = id; 63 | this.accounts = accounts; 64 | this.email = email; 65 | this.emailVerified = emailVerified; 66 | this.phoneNumber = phone; 67 | this.phoneNumberVerified = phoneNumberVerified; 68 | this.preferredUsername = preferredUsername; 69 | } 70 | 71 | public toStorageString() { 72 | return JSON.stringify({ 73 | accounts: this.accounts, 74 | email: this.email, 75 | emailVerified: this.emailVerified, 76 | id: this.id, 77 | phoneNumber: this.phoneNumber, 78 | phoneNumberVerified: this.phoneNumberVerified, 79 | preferredUsername: this.preferredUsername, 80 | }); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /packages/bitski/src/-private/callback.ts: -------------------------------------------------------------------------------- 1 | import { processCallback } from './utils/callback'; 2 | 3 | // Call the callback immediately 4 | try { 5 | processCallback(); 6 | } catch (error) { 7 | // eslint-disable-next-line no-console 8 | console.error('Error logging in: ' + error); // tslint:disable-line 9 | } 10 | -------------------------------------------------------------------------------- /packages/bitski/src/-private/constants.ts: -------------------------------------------------------------------------------- 1 | // SDK 2 | export const SDK_VERSION = '0.14.1'; 3 | 4 | // URLs 5 | export const BITSKI_USER_API_HOST = 'https://www.bitski.com/v1'; 6 | export const BITSKI_TRANSACTION_API_BASE_URL = 'https://api.bitski.com/v1'; 7 | export const BITSKI_WEB_BASE_URL = 'https://sign.bitski.com'; 8 | export const IFRAME_MESSAGE_ORIGIN_INCLUDES = '.bitski.com'; 9 | 10 | // OAuth 11 | export const DEFAULT_OAUTH_CONFIGURATION = { 12 | authorization_endpoint: 'https://account.bitski.com/oauth2/auth', 13 | revocation_endpoint: '', 14 | token_endpoint: 'https://account.bitski.com/oauth2/token', 15 | userinfo_endpoint: 'https://account.bitski.com/userinfo', 16 | }; 17 | export const DEFAULT_SCOPES = ['openid']; // scopes that are always included 18 | export const DEFAULT_OPTIONAL_SCOPES = ['offline']; // scopes that are included by default, but can be overridden 19 | 20 | // Popup Window 21 | export const CHECK_FOR_POPUP_CLOSE_INTERVAL = 500; 22 | export const DEFAULT_POPUP_FEATURES = { 23 | location: 'no', 24 | toolbar: 'no', 25 | width: 500, 26 | height: 500, 27 | left: 100, 28 | top: 100, 29 | }; 30 | 31 | // Storage 32 | export const REFRESH_TOKEN_KEY = 'bitski.refresh_token'; 33 | export const ACCESS_TOKEN_KEY = 'bitski.access_token'; 34 | export const ID_TOKEN_KEY = 'bitski.id_token'; 35 | export const USER_KEY = 'bitski.user'; 36 | 37 | // Methods 38 | export const CACHED_METHODS = ['eth_accounts']; 39 | export const DEFAULT_AUTHORIZED_METHODS = [ 40 | 'eth_sendTransaction', 41 | 'eth_signTransaction', 42 | 'eth_sign', 43 | 'personal_sign', 44 | 'eth_signTypedData', 45 | 'eth_signTypedData_v3', // For metamask compatibility 46 | 'eth_signTypedData_v4', 47 | ]; 48 | 49 | export enum OAuthSignInMethod { 50 | Redirect = 'REDIRECT', 51 | Popup = 'POPUP', 52 | Silent = 'SILENT', // Deprecated 53 | } 54 | 55 | export enum AuthenticationStatus { 56 | Connected = 'CONNECTED', 57 | Expired = 'EXPIRED', 58 | NotConnected = 'NOT_CONNECTED', 59 | } 60 | -------------------------------------------------------------------------------- /packages/bitski/src/-private/network.ts: -------------------------------------------------------------------------------- 1 | export interface Network { 2 | rpcUrl: string; 3 | chainId: number; 4 | } 5 | 6 | export const Mainnet: Network = { 7 | chainId: 1, 8 | rpcUrl: 'https://api.bitski.com/v1/web3/mainnet', 9 | }; 10 | 11 | export const Goerli: Network = { 12 | chainId: 5, 13 | rpcUrl: 'https://api.bitski.com/v1/web3/goerli', 14 | }; 15 | 16 | export const Sepolia: Network = { 17 | chainId: 5, 18 | rpcUrl: 'https://api.bitski.com/v1/web3/sepolia', 19 | }; 20 | 21 | export const Base: Network = { 22 | chainId: 8453, 23 | rpcUrl: 'https://api.bitski.com/v1/web3/base', 24 | }; 25 | 26 | export const BaseGoerli: Network = { 27 | chainId: 84531, 28 | rpcUrl: 'https://api.bitski.com/v1/web3/basegor', 29 | }; 30 | 31 | export const Polygon: Network = { 32 | chainId: 137, 33 | rpcUrl: 'https://api.bitski.com/v1/web3/polygon', 34 | }; 35 | 36 | export const Mumbai: Network = { 37 | chainId: 80001, 38 | rpcUrl: 'https://api.bitski.com/v1/web3/mumbai', 39 | }; 40 | 41 | export const BinanceSmartChain: Network = { 42 | chainId: 56, 43 | rpcUrl: 'https://api.bitski.com/v1/web3/bsc', 44 | }; 45 | 46 | export const BinanceSmartChainTestnet: Network = { 47 | chainId: 97, 48 | rpcUrl: 'https://api.bitski.com/v1/web3/bnbt', 49 | }; 50 | -------------------------------------------------------------------------------- /packages/bitski/src/-private/styles/connect-button.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | 3 | const css = ` 4 | .bitski-connect-button { 5 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, 'Helvetica Neue', sans-serif; 6 | font-weight: 500; 7 | background-color: #1C11D9; 8 | background-repeat: no-repeat; 9 | background-position: 0px 0px; 10 | border: none; 11 | color: #fff; 12 | margin: 0; 13 | padding: 0; 14 | cursor: pointer; 15 | text-shadow: 1px 0 1px rgba(0, 0, 0, 0.03); 16 | box-shadow: 0 1px 1px rgba(0, 0, 0, 0.14); 17 | transition: background 200ms linear, transform 200ms ease-out; 18 | -webkit-user-select: none; 19 | -moz-user-select: none; 20 | -ms-user-select: none; 21 | user-select: none; 22 | } 23 | .bitski-connect-button:focus, 24 | .bitski-connect-button:active { 25 | background-color: #2117C7; 26 | transform: scale(0.99, 0.99); 27 | color: rgba(255, 255, 255, 0.8); 28 | } 29 | .bitski-connect-button.size-small { 30 | background-image: url('https://cdn.bitskistatic.com/sdk/btn-v2-bg-sm.svg'); 31 | border-radius: 3px; 32 | font-size: 10px; 33 | height: 22px; 34 | line-height: 19px; 35 | padding-left: 30px; 36 | padding-right: 8px; 37 | } 38 | .bitski-connect-button.size-medium { 39 | background-image: url('https://cdn.bitskistatic.com/sdk/btn-v2-bg-md.svg'); 40 | border-radius: 4px; 41 | font-size: 11px; 42 | height: 30px; 43 | line-height: 29px; 44 | padding-left: 40px; 45 | padding-right: 12px; 46 | } 47 | .bitski-connect-button.size-large { 48 | background-image: url('https://cdn.bitskistatic.com/sdk/btn-v2-bg-lg.svg'); 49 | border-radius: 5px; 50 | font-size: 14px; 51 | height: 44px; 52 | line-height: 44px; 53 | padding-left: 57px; 54 | padding-right: 15px; 55 | } 56 | `; 57 | 58 | export default css; 59 | -------------------------------------------------------------------------------- /packages/bitski/src/-private/utils/callback.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Automatically handles finalizing the oauth sign in process with the Bitski SDK 3 | */ 4 | export function processCallback() { 5 | if (window.opener) { 6 | notifyOpener(window.location); 7 | } else { 8 | throw new Error('Parent window could not be found'); 9 | } 10 | } 11 | 12 | /** 13 | * Notifies the opener when in a popup 14 | * @param url the url that contains the query params 15 | */ 16 | function notifyOpener(url: Location): void { 17 | if (window.opener) { 18 | if (url) { 19 | // parse url to get state 20 | const data = parseUrlParams(url); 21 | if (data.state) { 22 | const name = `popupCallback_${data.state}`; 23 | const callback = window.opener[name]; 24 | if (callback) { 25 | callback(url); 26 | } else { 27 | throw new Error('No callback found on opener'); 28 | } 29 | } else { 30 | throw new Error('No state found in response'); 31 | } 32 | } 33 | } else { 34 | throw new Error('No window.opener'); 35 | } 36 | } 37 | 38 | /** 39 | * Extracts query params from the hash of the url 40 | * @param url the url to parse 41 | */ 42 | export function parseUrlParams(url: Location): any { 43 | let params: string | undefined; 44 | 45 | if (url.href.includes('#') && !url.href.endsWith('#')) { 46 | params = extractQuery(url.hash); 47 | } else if (url.href.includes('?')) { 48 | params = url.search.split('?').pop(); 49 | } 50 | 51 | if (!params) { 52 | throw new Error('No params found in result'); 53 | } 54 | 55 | return params.split('&').reduce((prev, item) => { 56 | const [key, value] = item.split('='); 57 | if (key && value) { 58 | prev[decodeURIComponent(key)] = decodeURIComponent(value); 59 | } 60 | return prev; 61 | }, {}); 62 | } 63 | 64 | function extractQuery(url): string { 65 | if (!url.includes('#')) { 66 | throw new Error('No params found in result'); 67 | } 68 | return url.split('#').pop(); 69 | } 70 | -------------------------------------------------------------------------------- /packages/bitski/src/-private/utils/no-hash-query-string-utils.ts: -------------------------------------------------------------------------------- 1 | import { BasicQueryStringUtils, LocationLike, StringMap } from '@openid/appauth'; 2 | 3 | export class NoHashQueryStringUtils extends BasicQueryStringUtils { 4 | public parse(input: LocationLike): StringMap { 5 | return super.parse(input, false); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/bitski/src/-private/utils/numbers.ts: -------------------------------------------------------------------------------- 1 | export function toHex(number: number): string { 2 | return `0x${number.toString(16)}`; 3 | } 4 | -------------------------------------------------------------------------------- /packages/bitski/src/-private/utils/popup-validator.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * A simple utility class that will check to see if a popup is blocked. 3 | * Derived from info and examples on this page: 4 | * https://stackoverflow.com/questions/2914/how-can-i-detect-if-a-browser-is-blocking-a-popup 5 | */ 6 | export class PopupValidator { 7 | // Will be called if the popup is blocked 8 | protected errorHandler: () => void; 9 | 10 | constructor(errorHandler: () => void) { 11 | this.errorHandler = errorHandler; 12 | } 13 | 14 | // Check a popup window to see if it has been blocked. 15 | // The error handler will be called asynchronously if 16 | // the window has been detected to have been blocked. 17 | public check(popup: Window | null) { 18 | if (popup) { 19 | if (/chrome/.test(navigator.userAgent.toLowerCase())) { 20 | setTimeout(() => { 21 | this.isPopupBlocked(popup); 22 | }, 2000); 23 | } else { 24 | popup.onload = () => { 25 | this.isPopupBlocked(popup); 26 | }; 27 | } 28 | } else { 29 | this.handleBlocked(); 30 | } 31 | } 32 | 33 | protected isPopupBlocked(popup: Window) { 34 | if (popup.innerHeight > 0 === false) { 35 | this.handleBlocked(); 36 | } 37 | } 38 | 39 | protected handleBlocked() { 40 | this.errorHandler(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /packages/bitski/src/-private/utils/request-utils.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Parses a Fetch Response to extract either the result or the error 3 | * @param response the fetch response to parse 4 | */ 5 | export function parseResponse(response: Response): Promise { 6 | return response 7 | .json() 8 | .catch(() => { 9 | throw new Error('Invalid JSON response'); 10 | }) 11 | .then((json) => { 12 | if (response.status >= 200 && response.status < 300) { 13 | return json as T; 14 | } else { 15 | if (json && json.error && json.error.message) { 16 | throw new Error(json.error.message); 17 | } else if (json && json.error) { 18 | throw new Error(json.error); 19 | } else { 20 | throw new Error('Unknown error'); 21 | } 22 | } 23 | }); 24 | } 25 | -------------------------------------------------------------------------------- /packages/bitski/tests/shim.test.ts: -------------------------------------------------------------------------------- 1 | // This test requires the production build to have been run, it ensures that the 2 | // shim is working correctly and a basic request can be made. 3 | 4 | // import the bitski min, which will set the global Bitski object 5 | import { EthMethod } from 'eth-provider-types'; 6 | import Bundle from '../dist/bundled/bitski.min.js'; 7 | 8 | globalThis.Bitski = Bundle; 9 | 10 | // import the shim 11 | import { Bitski } from '../src/index'; 12 | 13 | describe('built shim', () => { 14 | test('should be able to make a request', async () => { 15 | const bitski = new Bitski('test-client-id', 'http://localhost:3000'); 16 | const provider = bitski.getProvider(); 17 | 18 | fetchMock.mockResponse(async (req) => { 19 | if (req.method === 'GET') { 20 | // loading block number 21 | return JSON.stringify({ result: '0x123' }); 22 | } 23 | 24 | expect(req.url).toBe('https://api.bitski.com/v1/web3/chains/1'); 25 | expect(req.method).toBe('POST'); 26 | 27 | expect(req.headers.get('X-API-KEY')).toBe('test-client-id'); 28 | expect(req.headers.get('X-CLIENT-ID')).toBe('test-client-id'); 29 | expect(req.headers.get('X-CLIENT-VERSION')).toMatch(/bitski-sdk-v\d+\.\d+\.\d+/); 30 | 31 | const body = await req.json(); 32 | 33 | expect(body).toMatchObject({ 34 | method: 'eth_getBlockByHash', 35 | params: ['0x123', false], 36 | }); 37 | 38 | // Make sure we aren't including any extra parameters 39 | expect(Object.keys(body)).toEqual(['id', 'jsonrpc', 'method', 'params']); 40 | 41 | return JSON.stringify({ 42 | id: 0, 43 | jsonrpc: '2.0', 44 | result: { 45 | number: '0x123', 46 | }, 47 | }); 48 | }); 49 | 50 | const result = await provider.request({ 51 | method: EthMethod.eth_getBlockByHash, 52 | params: ['0x123', false], 53 | }); 54 | 55 | expect(result).toEqual({ number: '0x123' }); 56 | }); 57 | }); 58 | -------------------------------------------------------------------------------- /packages/bitski/tests/util/mem-store.ts: -------------------------------------------------------------------------------- 1 | import { BitskiProviderStore } from 'bitski-provider'; 2 | 3 | export default class MemStore implements BitskiProviderStore { 4 | private store: { [key: string]: unknown } = {}; 5 | 6 | keys(): string[] { 7 | return Object.keys(this.store); 8 | } 9 | 10 | async getItem(key: string): Promise { 11 | return this.store[key]; 12 | } 13 | 14 | async setItem(key: string, value: unknown): Promise { 15 | this.store[key] = value; 16 | } 17 | 18 | async clearItem(key: string): Promise { 19 | delete this.store[key]; 20 | } 21 | 22 | async clear(): Promise { 23 | this.store = {}; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages/bitski/tests/util/mock-oauth-manager.ts: -------------------------------------------------------------------------------- 1 | import { AuthorizationRequest, TokenRequest } from '@openid/appauth'; 2 | import { OAuthManager, SignInOptions } from '../../src/-private/auth/oauth-manager'; 3 | 4 | export class MockOAuthManager extends OAuthManager { 5 | public currentAuthRequest?: AuthorizationRequest; 6 | public currentTokenRequest?: TokenRequest; 7 | 8 | protected createAuthRequest(opts: SignInOptions): AuthorizationRequest { 9 | const request = super.createAuthRequest(opts); 10 | this.currentAuthRequest = request; 11 | return request; 12 | } 13 | 14 | protected createTokenRequest(code: string): TokenRequest { 15 | const request = super.createTokenRequest(code); 16 | this.currentTokenRequest = request; 17 | return request; 18 | } 19 | 20 | protected createRefreshTokenRequest(refreshToken: string): TokenRequest { 21 | const request = super.createRefreshTokenRequest(refreshToken); 22 | this.currentTokenRequest = request; 23 | return request; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages/bitski/tests/util/setup-jest.ts: -------------------------------------------------------------------------------- 1 | import { enableFetchMocks } from 'jest-fetch-mock'; 2 | 3 | enableFetchMocks(); 4 | globalThis.BITSKI_SDK_VERSION = 'test-version'; 5 | -------------------------------------------------------------------------------- /packages/bitski/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "noImplicitAny": false, 4 | "strict": true, 5 | "target": "ES2017", 6 | "moduleResolution": "node", 7 | "lib": ["es2017", "dom"], 8 | "esModuleInterop": true, 9 | "allowSyntheticDefaultImports": true 10 | }, 11 | "include": ["src", "**/*.test.ts"] 12 | } 13 | -------------------------------------------------------------------------------- /packages/bitski/tsconfig.main.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "lib", 5 | "module": "commonjs", 6 | "sourceMap": true, 7 | "declaration": true 8 | }, 9 | "include": ["src"] 10 | } 11 | -------------------------------------------------------------------------------- /packages/bitski/tsconfig.module.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "dist/esm/", 5 | "module": "es6" 6 | }, 7 | "include": ["src"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/eth-provider-types/.gitignore: -------------------------------------------------------------------------------- 1 | index.js 2 | index.d.ts 3 | index.js.map 4 | -------------------------------------------------------------------------------- /packages/eth-provider-types/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eth-provider-types", 3 | "version": "0.2.1", 4 | "description": "TypeScript types for Ethereum providers, with types for every RPC method", 5 | "main": "./index.js", 6 | "types": "index.d.ts", 7 | "files": [ 8 | "index.js", 9 | "index.js.map", 10 | "index.d.ts", 11 | "*.md" 12 | ], 13 | "author": "Bitski", 14 | "license": "ISC", 15 | "scripts": { 16 | "build": "tsc" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/eth-provider-types/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "noImplicitAny": true, 4 | "strict": true, 5 | "moduleResolution": "node", 6 | "lib": ["es2017", "dom"], 7 | "module": "commonjs", 8 | "target": "ES2017", 9 | "declaration": true, 10 | "sourceMap": true 11 | }, 12 | "include": ["index.ts"] 13 | } 14 | -------------------------------------------------------------------------------- /packages/waas-react-sdk/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /packages/waas-react-sdk/.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | /build 3 | /package 4 | .env 5 | .env.* 6 | !.env.example 7 | /static 8 | 9 | # Ignore files for PNPM, NPM and YARN 10 | pnpm-lock.yaml 11 | package-lock.json 12 | yarn.lock 13 | .cloudflare 14 | /.next 15 | -------------------------------------------------------------------------------- /packages/waas-react-sdk/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "useTabs": false, 3 | "singleQuote": true, 4 | "trailingComma": "all", 5 | "printWidth": 100, 6 | "htmlWhitespaceSensitivity": "ignore" 7 | } 8 | -------------------------------------------------------------------------------- /packages/waas-react-sdk/README.md: -------------------------------------------------------------------------------- 1 | # @bitski/waas-react-sdk 2 | 3 | > DISCLAIMER: While we are under v1.0.0 - please expect breaking changes. All changes will be communicated via the CHANGELOG.md 4 | 5 | Solve all your dApp’s authentication challenges with a simple React Widget. 6 | 7 | We aim to use tools you are already familiar with, mainly Wagmi and Viem so you can focus on building your application. 8 | 9 | To get started, create an account in the Bitski Developer portal. Follow the Web3 onboarding flow to get your Bitski `appId`` and reference the React snippet below to get going immediately. 10 | 11 | ## Quickstart 12 | 13 | Install via `npm i @bitski/waas-react-sdk` 14 | 15 | Bitski’s WaaS React SDK uses Wagmi and Viem so you don’t have to learn new tools. We support many chains—just use `viem/chains` and `LoginMethod` from the SDK. 16 | 17 | ```react 18 | import { BitskiProvider, BitskiWidget, LoginMethod } from "@bitski/waas-react-sdk"; 19 | import {base, mainnet, polygon} from "viem/chains"; 20 | 21 | export const Dapp = () => { 22 | 28 | 29 | // ... The rest of your app's code 30 | 31 | } 32 | ``` 33 | 34 | ### Callback URL 35 | 36 | In order for the Bitski Waas React SDK to work, you must provide one public callback URL with the Bitski JS SDK imported. 37 | 38 | ``` 39 | 40 | 41 | 42 | Logging in... 43 | 44 | 45 | 46 | ``` 47 | 48 | Say goodbye to the awkward authentication dance and get back to doing what you do best—building your dApp! If you’re running into issues on how to use your Bitski Wallet, free feel to file an issue. 49 | -------------------------------------------------------------------------------- /packages/waas-react-sdk/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Bitski Widget 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /packages/waas-react-sdk/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@bitski/waas-react-sdk", 3 | "version": "1.0.4", 4 | "type": "module", 5 | "main": "dist/index.cjs.js", 6 | "module": "dist/index.es.js", 7 | "types": "dist/index.d.ts", 8 | "exports": { 9 | ".": { 10 | "import": "./dist/index.es.js", 11 | "require": "./dist/index.cjs.js", 12 | "types": "./dist/index.d.ts" 13 | }, 14 | "./dist/style.css": "./dist/style.css" 15 | }, 16 | "files": [ 17 | "/dist" 18 | ], 19 | "sideEffects": true, 20 | "scripts": { 21 | "dev": "vite", 22 | "build": "tsc && vite build", 23 | "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", 24 | "format": "prettier --config ../../.prettierrc '{src,tests}/**/*.ts' --write", 25 | "preview": "vite preview" 26 | }, 27 | "peerDependencies": { 28 | "@tanstack/react-query": ">=5.0.0", 29 | "@wagmi/connectors": ">=2.0.0", 30 | "@wagmi/core": ">=2.0.0", 31 | "react": "^18", 32 | "react-dom": "^18", 33 | "viem": "2.x", 34 | "wagmi": "2.x" 35 | }, 36 | "dependencies": { 37 | "@apollo/client": "^3.8.10", 38 | "@floating-ui/react": "^0.26.8", 39 | "@xstate/react": "^4.0.3", 40 | "bitski": "^4.2.1", 41 | "graphql-request": "^6.1.0", 42 | "lodash.debounce": "^4.0.8", 43 | "xstate": "^5.6.0" 44 | }, 45 | "devDependencies": { 46 | "@apollo/rover": "^0.22.0", 47 | "@graphql-codegen/cli": "^5.0.0", 48 | "@graphql-codegen/client-preset": "^4.1.0", 49 | "@graphql-codegen/introspection": "^4.0.0", 50 | "@graphql-codegen/typescript": "^4.0.1", 51 | "@graphql-codegen/typescript-operations": "^4.0.1", 52 | "@statelyai/inspect": "^0.2.3", 53 | "@tanstack/eslint-plugin-query": "^5.18.1", 54 | "@tanstack/react-query": "^5.0.0", 55 | "@types/lodash.debounce": "^4.0.9", 56 | "@types/node": "^20.11.5", 57 | "@types/react": "^18.2.43", 58 | "@types/react-dom": "^18.2.17", 59 | "@vitejs/plugin-react": "^4.2.1", 60 | "autoprefixer": "^10.0.1", 61 | "postcss": "^8", 62 | "react": "^18.2.0", 63 | "react-dom": "^18.2.0", 64 | "tailwindcss": "^3.3.0", 65 | "viem": "^2.0.0", 66 | "vite": "^5.0.12", 67 | "vite-plugin-dts": "^3.7.1", 68 | "vite-plugin-lib-inject-css": "^1.3.0", 69 | "wagmi": "^2.0.0" 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /packages/waas-react-sdk/postcss.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /packages/waas-react-sdk/public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/waas-react-sdk/src/App.tsx: -------------------------------------------------------------------------------- 1 | import './lib/index.css'; 2 | import { BitskiProvider, BitskiWidget, Tab } from './lib'; 3 | import { LoginMethod } from './lib'; 4 | import { base, mainnet, optimism, polygon } from 'viem/chains'; 5 | 6 | function App() { 7 | const providerConfig = { 8 | loginMethods: [ 9 | LoginMethod.Email, 10 | LoginMethod.Wallet, 11 | LoginMethod.Apple, 12 | LoginMethod.Google, 13 | LoginMethod.X, 14 | ], 15 | chains: [mainnet, base, polygon, optimism], 16 | }; 17 | 18 | return ( 19 |
20 | 34 | 35 | 36 |
37 | ); 38 | } 39 | 40 | export default App; 41 | -------------------------------------------------------------------------------- /packages/waas-react-sdk/src/lib/assets/apple.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /packages/waas-react-sdk/src/lib/assets/arrow-rotate-right-left.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /packages/waas-react-sdk/src/lib/assets/chains/icon-base.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /packages/waas-react-sdk/src/lib/assets/chains/icon-ethereum.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /packages/waas-react-sdk/src/lib/assets/chains/icon-optimism.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /packages/waas-react-sdk/src/lib/assets/check-checked.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /packages/waas-react-sdk/src/lib/assets/check-disabled.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /packages/waas-react-sdk/src/lib/assets/chevron-left-small.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /packages/waas-react-sdk/src/lib/assets/chevron-right-small.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /packages/waas-react-sdk/src/lib/assets/coinbase-wallet.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /packages/waas-react-sdk/src/lib/assets/connector-icon-passkey.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /packages/waas-react-sdk/src/lib/assets/connector-state-connected.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /packages/waas-react-sdk/src/lib/assets/connector-state-error.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /packages/waas-react-sdk/src/lib/assets/cross-small.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /packages/waas-react-sdk/src/lib/assets/external-wallets.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /packages/waas-react-sdk/src/lib/assets/google.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /packages/waas-react-sdk/src/lib/assets/icon-activity-selected.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /packages/waas-react-sdk/src/lib/assets/icon-activity.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /packages/waas-react-sdk/src/lib/assets/icon-coinbase.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /packages/waas-react-sdk/src/lib/assets/icon-disconnect.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /packages/waas-react-sdk/src/lib/assets/icon-eth.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /packages/waas-react-sdk/src/lib/assets/icon-matic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /packages/waas-react-sdk/src/lib/assets/icon-optimism.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /packages/waas-react-sdk/src/lib/assets/icon-swaps-selected.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /packages/waas-react-sdk/src/lib/assets/icon-swaps.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /packages/waas-react-sdk/src/lib/assets/icon-tokens-selected.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /packages/waas-react-sdk/src/lib/assets/icon-tokens.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /packages/waas-react-sdk/src/lib/assets/icon-walletconnect.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /packages/waas-react-sdk/src/lib/assets/injected-wallet.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /packages/waas-react-sdk/src/lib/assets/loading.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BitskiCo/bitski-js/82a2a6faac4bfead544ca6d26769b843ed8f6ecc/packages/waas-react-sdk/src/lib/assets/loading.png -------------------------------------------------------------------------------- /packages/waas-react-sdk/src/lib/assets/pending.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BitskiCo/bitski-js/82a2a6faac4bfead544ca6d26769b843ed8f6ecc/packages/waas-react-sdk/src/lib/assets/pending.png -------------------------------------------------------------------------------- /packages/waas-react-sdk/src/lib/assets/phantom.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /packages/waas-react-sdk/src/lib/assets/phone.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /packages/waas-react-sdk/src/lib/assets/settings.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /packages/waas-react-sdk/src/lib/assets/waas-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BitskiCo/bitski-js/82a2a6faac4bfead544ca6d26769b843ed8f6ecc/packages/waas-react-sdk/src/lib/assets/waas-logo.png -------------------------------------------------------------------------------- /packages/waas-react-sdk/src/lib/assets/x.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /packages/waas-react-sdk/src/lib/components/BitskiWidget/BitskiAuth.styles.css: -------------------------------------------------------------------------------- 1 | /* Content animations */ 2 | @keyframes scaleIn { 3 | from { 4 | transform: scale(0.85); 5 | } 6 | to { 7 | transform: scale(1); 8 | } 9 | } 10 | 11 | @keyframes scaleOut { 12 | from { 13 | transform: scale(1); 14 | } 15 | to { 16 | transform: scale(0.85); 17 | } 18 | } 19 | 20 | .Dialog-content-entering { 21 | animation: scaleIn 0.3s ease-out; 22 | } 23 | 24 | .Dialog-content-exiting { 25 | animation: scaleOut 0.3s ease-in; 26 | } 27 | -------------------------------------------------------------------------------- /packages/waas-react-sdk/src/lib/components/BitskiWidget/BitskiAuth.tsx: -------------------------------------------------------------------------------- 1 | import IdleConnection from './states/Idle'; 2 | import { useDialogContext } from '../Dialog'; 3 | import { useEffect, useState } from 'react'; 4 | import './BitskiAuth.styles.css'; 5 | import { ConnectionState, ConnectionStateKind } from '../../BitskiContext'; 6 | import {useBitski} from "../../useBitski"; 7 | import {ConnectionSessionCard} from "./states/ConnectionSessionCard"; 8 | 9 | interface BitskiAuthProps { 10 | children?: React.ReactNode; 11 | logoUrl?: string; 12 | collapsed?: boolean; 13 | } 14 | 15 | const AuthWrapper = ({ children }: { children: React.ReactNode }) => { 16 | const { context: floatingContext } = useDialogContext(); 17 | const [contentAnimationState, setContentAnimationState] = useState('exited'); 18 | useEffect(() => { 19 | if (floatingContext.open) { 20 | setContentAnimationState('entering'); 21 | setTimeout(() => setContentAnimationState('entered'), 300); 22 | } else { 23 | setContentAnimationState('exiting'); 24 | setTimeout(() => setContentAnimationState('exited'), 300); 25 | } 26 | }, [floatingContext.open]); 27 | 28 | return
{children}
; 29 | }; 30 | 31 | function componentForConnectionState(connectionState: ConnectionState, reset: () => void) { 32 | switch (connectionState.kind) { 33 | case ConnectionStateKind.Discovering: 34 | return ; 35 | case ConnectionStateKind.NotConnected: 36 | return ; 37 | case ConnectionStateKind.Pending: 38 | case ConnectionStateKind.Connected: 39 | case ConnectionStateKind.Error: 40 | return 41 | } 42 | } 43 | 44 | export default function BitskiAuth({ collapsed }: BitskiAuthProps) { 45 | const { connectionState, reset } = useBitski(); 46 | let Component = componentForConnectionState(connectionState, reset); 47 | return collapsed ? {Component} : Component; 48 | } 49 | -------------------------------------------------------------------------------- /packages/waas-react-sdk/src/lib/components/BitskiWidget/BitskiConnect.tsx: -------------------------------------------------------------------------------- 1 | import { truncateAddress } from '../../utils'; 2 | import { ConnectionStateKind } from '../../BitskiContext'; 3 | import {useBitski} from "../../useBitski"; 4 | 5 | interface BitskiConnectProps { 6 | children?: React.ReactNode; 7 | displayText?: string; 8 | onClick?: () => void; 9 | } 10 | 11 | const DefaultConnect = ({ displayText }: { displayText: string }) => { 12 | const connectionState = useBitski().connectionState; 13 | let text: string; 14 | switch (connectionState.kind) { 15 | case ConnectionStateKind.Connected: 16 | text = truncateAddress(connectionState.address); 17 | break; 18 | default: 19 | text = displayText; 20 | } 21 | 22 | return ( 23 |

24 | {text} 25 |

26 | ); 27 | }; 28 | 29 | export default function BitskiConnect({ 30 | children, 31 | displayText = 'Login', 32 | onClick, 33 | }: BitskiConnectProps) { 34 | return ( 35 | 41 | ); 42 | } 43 | -------------------------------------------------------------------------------- /packages/waas-react-sdk/src/lib/components/BitskiWidget/BitskiProvider.tsx: -------------------------------------------------------------------------------- 1 | import {QueryClient, QueryClientProvider} from '@tanstack/react-query'; 2 | import {ReactNode, useReducer, useState} from 'react'; 3 | import {WagmiProvider} from 'wagmi'; 4 | 5 | import {createBitskiConfig, validateChains, validateConnectors} from '../../utils'; 6 | 7 | import {LoginMethod} from './constants'; 8 | import {Chain} from 'viem/chains'; 9 | import {ConnectorConfig} from './types'; 10 | import {BitskiContext, ConnectionStateKind, connectionStateReducer} from '../../BitskiContext'; 11 | import {Tab} from '../BitskiWalletViewer'; 12 | import {BitskiWalletProvider} from '../BitskiWalletProvider'; 13 | 14 | interface BitskiProviderProps { 15 | children: ReactNode; 16 | appId: string; 17 | callbackURL?: string; 18 | chains: readonly [Chain, ...Chain[]]; 19 | loginMethods: LoginMethod[]; 20 | tabs?: Tab[] 21 | config?: ConnectorConfig | ConnectorConfig[]; 22 | logoUrl?: string; 23 | signMessageOnConnect?: boolean; 24 | } 25 | 26 | function BitskiProvider({ 27 | children, 28 | chains, 29 | appId, 30 | callbackURL, 31 | loginMethods, 32 | config, 33 | logoUrl, 34 | signMessageOnConnect, 35 | tabs 36 | }: BitskiProviderProps) { 37 | const [queryClient] = useState(() => new QueryClient()); 38 | const [connectionState, dispatchConnectionAction] = useReducer(connectionStateReducer, { 39 | kind: ConnectionStateKind.Discovering, 40 | }); 41 | 42 | const wagmiConfig = createBitskiConfig({ 43 | chains: validateChains(chains), 44 | connectors: validateConnectors({ appId, callbackURL, loginMethods, config }), 45 | }); 46 | 47 | const resolvedTabs = tabs ? tabs : [Tab.Tokens] 48 | 49 | return ( 50 | 61 | 62 | 63 | {children} 64 | 65 | 66 | 67 | ); 68 | } 69 | 70 | export default BitskiProvider; 71 | -------------------------------------------------------------------------------- /packages/waas-react-sdk/src/lib/components/BitskiWidget/BitskiWidget.tsx: -------------------------------------------------------------------------------- 1 | import BitskiConnect from './BitskiConnect'; 2 | import { Dialog, DialogContent, DialogTrigger } from '../Dialog'; 3 | import { BitskiWalletViewer, Tab } from '../BitskiWalletViewer'; 4 | import { useContext } from 'react'; 5 | import { BitskiContext, ConnectionStateKind } from '../../BitskiContext'; 6 | import { BitskiAuth } from './index'; 7 | 8 | export interface BitskiWidgetProps { 9 | connect?: React.ReactNode; 10 | logoUrl?: string; 11 | loginText?: string; 12 | } 13 | 14 | function BitskiWidget({ connect, logoUrl, loginText }: BitskiWidgetProps) { 15 | const { connectionState } = useContext(BitskiContext); 16 | let dialogContent; 17 | switch (connectionState.kind) { 18 | case ConnectionStateKind.Connected: 19 | dialogContent = ; 20 | break; 21 | default: 22 | dialogContent = ; 23 | } 24 | 25 | return ( 26 | 27 | 28 | {connect ? connect : } 29 | 30 | {dialogContent} 31 | 32 | ); 33 | } 34 | 35 | export default BitskiWidget; 36 | -------------------------------------------------------------------------------- /packages/waas-react-sdk/src/lib/components/BitskiWidget/EmailInput.tsx: -------------------------------------------------------------------------------- 1 | import { Connector } from 'wagmi'; 2 | import { useState } from 'react'; 3 | import { BitskiConnector } from '../../connectors'; 4 | import React from 'react'; 5 | 6 | interface EmailInputProps { 7 | connector: Connector | BitskiConnector; 8 | connect: ({ 9 | connector, 10 | parameters, 11 | }: { 12 | connector: Connector; 13 | parameters?: Record; 14 | }) => void; 15 | } 16 | 17 | export const EmailInput = ({ connector, connect }: EmailInputProps) => { 18 | const [email, setEmail] = useState(''); 19 | 20 | const buttonEnabled = email.length > 0; 21 | const buttonBgColor = buttonEnabled ? 'bg-black' : 'bg-[color:var(--aux-light-grey)]'; 22 | const buttonTextColor = buttonEnabled ? 'text-white' : 'text-[color:var(--aux-grey)]'; 23 | const inputTextColor = email.length > 0 ? 'text-black' : 'text-[color:var(--main-grey)]'; 24 | 25 | const handleSubmit = (event: React.FormEvent) => { 26 | event.preventDefault(); 27 | 28 | if ('setEmail' in connector) { 29 | connector.setEmail(email); 30 | } 31 | 32 | connect({ connector }); 33 | }; 34 | 35 | return ( 36 |
37 | setEmail(event.target.value)} 41 | placeholder="Enter your email" 42 | value={email} 43 | className={`w-[286px] focus:outline-none border-[color:var(--aux-grey,color(display-p3_0.7569_0.7569_0.7647_/_0.20))] p-4 rounded-xl border-[1.5px] border-solid ${inputTextColor} hover:border-[color:var(--Main-Black,color(display-p3_0.2_0.2_0.2))] focus:border-[color:var(--Main-Black,color(display-p3_0.2_0.2_0.2))] text-sm not-italic font-[510] leading-[17px] tracking-[-0.084px]`} 44 | /> 45 | 56 |
57 | ); 58 | }; 59 | -------------------------------------------------------------------------------- /packages/waas-react-sdk/src/lib/components/BitskiWidget/Socials.tsx: -------------------------------------------------------------------------------- 1 | import { Connector, useConnect } from 'wagmi'; 2 | import { Social } from './constants'; 3 | import appleIcon from '../../assets/apple.svg'; 4 | import googleIcon from '../../assets/google.svg'; 5 | import xIcon from '../../assets/x.svg'; 6 | 7 | interface SocialProps { 8 | name: string; 9 | icon: string; 10 | onSocialClick: () => void; 11 | } 12 | 13 | const SocialBtn = ({ name, icon, onSocialClick }: SocialProps) => { 14 | return ( 15 | 21 | ); 22 | }; 23 | 24 | function useSocials(): Social[] { 25 | return [Social.Apple, Social.Google, Social.X]; 26 | } 27 | 28 | function socialRow( 29 | social: Social, 30 | connector: Connector, 31 | ): { name: string; icon: string; social: Social; connector: Connector } { 32 | switch (social) { 33 | case Social.Apple: 34 | return { 35 | name: 'Apple', 36 | icon: appleIcon, 37 | social, 38 | connector, 39 | }; 40 | case Social.Google: 41 | return { 42 | name: 'Google', 43 | icon: googleIcon, 44 | social, 45 | connector, 46 | }; 47 | case Social.X: 48 | return { 49 | name: 'X', 50 | icon: xIcon, 51 | social, 52 | connector, 53 | }; 54 | default: 55 | throw new Error('Invalid social'); 56 | } 57 | } 58 | 59 | interface ConnectableSocial { 60 | social: Social; 61 | connector: Connector; 62 | } 63 | 64 | export default function Socials(props: { 65 | onSocialClick: (connectableSocial: ConnectableSocial) => void; 66 | }) { 67 | const socials = useSocials(); 68 | const { connectors } = useConnect(); 69 | 70 | const connectableSocials = socials 71 | .map((social) => { 72 | const connector = connectors.filter((connector) => { 73 | return connector.id === social; 74 | })[0]; 75 | 76 | return { social, connector }; 77 | }) 78 | .filter(({ connector }) => connector); 79 | 80 | const rows = connectableSocials.map(({ social, connector }) => socialRow(social, connector)); 81 | 82 | return ( 83 |
84 | {rows.map(({ name, icon, social, connector }) => ( 85 | props.onSocialClick({ social, connector })} 90 | /> 91 | ))} 92 |
93 | ); 94 | } 95 | -------------------------------------------------------------------------------- /packages/waas-react-sdk/src/lib/components/BitskiWidget/TOS.tsx: -------------------------------------------------------------------------------- 1 | export default function TOS() { 2 | return ( 3 |
4 |

5 | By creating a wallet, you agree to our
6 | 10 | Terms of Service 11 | {' '} 12 | and{' '} 13 | 17 | Privacy Policy 18 | 19 |

20 |
21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /packages/waas-react-sdk/src/lib/components/BitskiWidget/constants.ts: -------------------------------------------------------------------------------- 1 | export enum LoginMethod { 2 | Wallet = 'wallet', 3 | Email = 'email', 4 | Google = 'google', 5 | Apple = 'apple', 6 | X = 'x', 7 | Sms = 'sms', 8 | } 9 | 10 | export type LoginMethods = `${LoginMethod}`; 11 | 12 | export enum ExternalWallet { 13 | Phantom = 'phantom', 14 | MetaMask = 'metaMaskSDK', 15 | Injected = 'injected', 16 | CoinbaseWallet = 'coinbaseWalletSDK', 17 | WalletConnect = 'walletConnect', 18 | } 19 | 20 | export enum Social { 21 | Apple = 'apple', 22 | Google = 'google', 23 | X = 'x', 24 | } 25 | 26 | export enum ConnectionState { 27 | Idle = 'idle', 28 | Pending = 'pending', 29 | Connected = 'connected', 30 | Error = 'error', 31 | } 32 | -------------------------------------------------------------------------------- /packages/waas-react-sdk/src/lib/components/BitskiWidget/index.ts: -------------------------------------------------------------------------------- 1 | export { default as BitskiWidget } from './BitskiWidget'; 2 | export { default as BitskiProvider } from './BitskiProvider'; 3 | export { default as BitskiConnect } from './BitskiConnect'; 4 | export { default as BitskiAuth } from './BitskiAuth'; 5 | -------------------------------------------------------------------------------- /packages/waas-react-sdk/src/lib/components/BitskiWidget/states/Idle.tsx: -------------------------------------------------------------------------------- 1 | import { Connector, useConnectors } from 'wagmi'; 2 | import { EmailInput } from '../EmailInput'; 3 | import { LoginMethod, Social } from '../constants'; 4 | import Wallets from '../Wallets'; 5 | import Socials from '../Socials'; 6 | import { SmsInput } from '../SmsInput'; 7 | import { useContext } from 'react'; 8 | import { BitskiContext } from '../../../BitskiContext'; 9 | import TOS from '../TOS'; 10 | import { useBitski } from '../../../useBitski'; 11 | 12 | export default function IdleConnection() { 13 | const { loginMethods, logoUrl } = useContext(BitskiContext); 14 | const connectors = useConnectors(); 15 | const { connect } = useBitski(); 16 | 17 | const emailConnector = connectors.find((connector) => connector.id === LoginMethod.Email); 18 | const smsConnector: Connector | undefined = connectors.find( 19 | (connector) => connector.id === LoginMethod.Sms, 20 | ); 21 | const socialConnectors = connectors.filter((connector) => connector.name in Social); 22 | 23 | return ( 24 |
25 |

26 | Login or Sign Up 27 |

28 | {logoUrl ? Logo : null} 29 | 30 |
31 | {emailConnector ? ( 32 | connect(emailConnector)} /> 33 | ) : null} 34 | 35 | {smsConnector ? ( 36 | connect(smsConnector)} /> 37 | ) : null} 38 | 39 | {socialConnectors.length ? ( 40 | await connect(social.connector)} /> 41 | ) : null} 42 |
43 | 44 | 45 | {loginMethods.includes(LoginMethod.Wallet) ? ( 46 | await connect(wallet.connector)} /> 47 | ) : null} 48 | 49 | 50 |
51 | ); 52 | } 53 | 54 | function Or({ loginMethods }: { loginMethods: LoginMethod[] }) { 55 | return loginMethods.includes(LoginMethod.Wallet) && loginMethods.length > 1 ? ( 56 |
57 | 58 | 59 | OR 60 | 61 | 62 |
63 | ) : null; 64 | } 65 | -------------------------------------------------------------------------------- /packages/waas-react-sdk/src/lib/components/BitskiWidget/types/config.ts: -------------------------------------------------------------------------------- 1 | import { 2 | CoinbaseWalletParameters, 3 | MetaMaskParameters, 4 | WalletConnectParameters, 5 | InjectedParameters, 6 | } from 'wagmi/connectors'; 7 | import { BitskiParameters } from '../../../connectors/bitski'; 8 | 9 | export interface ConnectorConfig { 10 | wallet: 'injected' | 'phantom' | 'coinbaseWallet' | 'metaMask' | 'walletConnect' | 'bitski'; 11 | options?: 12 | | CoinbaseWalletParameters 13 | | MetaMaskParameters 14 | | WalletConnectParameters 15 | | InjectedParameters 16 | | BitskiParameters; 17 | } 18 | 19 | export type ConfigTypeMap = { 20 | injected: InjectedParameters; 21 | phantom: InjectedParameters; 22 | walletConnect: WalletConnectParameters; 23 | metaMask: MetaMaskParameters; 24 | coinbaseWallet: CoinbaseWalletParameters; 25 | bitski: BitskiParameters; 26 | }; 27 | -------------------------------------------------------------------------------- /packages/waas-react-sdk/src/lib/components/BitskiWidget/types/index.ts: -------------------------------------------------------------------------------- 1 | export type { ConnectorConfig, ConfigTypeMap } from './config'; 2 | export type { LoginMethods } from './provider'; 3 | export type { 4 | IdleState, 5 | PendingState, 6 | ConnectedState, 7 | ErrorState, 8 | ConnectionStateType, 9 | } from './states'; 10 | -------------------------------------------------------------------------------- /packages/waas-react-sdk/src/lib/components/BitskiWidget/types/provider.ts: -------------------------------------------------------------------------------- 1 | import { LoginMethod } from '../constants'; 2 | 3 | export type LoginMethods = `${LoginMethod}`; 4 | -------------------------------------------------------------------------------- /packages/waas-react-sdk/src/lib/components/BitskiWidget/types/states.ts: -------------------------------------------------------------------------------- 1 | import { Connector } from 'wagmi'; 2 | import { ConnectionState } from '../constants'; 3 | 4 | export type IdleState = { type: ConnectionState.Idle }; 5 | export type PendingState = { type: ConnectionState.Pending; pendingConnector: Connector }; 6 | export type ConnectedState = { 7 | type: ConnectionState.Connected; 8 | connector: Connector; 9 | address: string; 10 | chain: string; 11 | }; 12 | export type ErrorState = { type: ConnectionState.Error; connector: Connector | undefined }; 13 | 14 | export type ConnectionStateType = IdleState | PendingState | ConnectedState | ErrorState; 15 | -------------------------------------------------------------------------------- /packages/waas-react-sdk/src/lib/components/ChainIcon.tsx: -------------------------------------------------------------------------------- 1 | import { base, mainnet, optimism, polygon } from 'viem/chains'; 2 | import iconEthereum from '../assets/chains/icon-ethereum.svg'; 3 | import iconPolygon from '../assets/chains/icon-polygon.svg'; 4 | import iconBase from '../assets/chains/icon-base.svg'; 5 | import iconOptimism from '../assets/chains/icon-optimism.svg'; 6 | 7 | const iconMap: Record = { 8 | [mainnet.id]: { src: iconEthereum }, 9 | [polygon.id]: { src: iconPolygon }, 10 | [base.id]: { src: iconBase }, 11 | [optimism.id]: { src: iconOptimism }, 12 | }; 13 | 14 | export function ChainIcon({ chainId, size }: { chainId: number; size: number }) { 15 | const iconEntry = iconMap[chainId]; 16 | if (!iconEntry) { 17 | return null 18 | } 19 | return ( 20 | 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /packages/waas-react-sdk/src/lib/components/ChainSwitcher.tsx: -------------------------------------------------------------------------------- 1 | import { useDismiss, useFloating, useInteractions } from '@floating-ui/react'; 2 | import { ChainIcon } from './ChainIcon'; 3 | import { useState } from 'react'; 4 | import { useAccount, useSwitchChain } from 'wagmi'; 5 | import checkChecked from '../assets/check-checked.svg'; 6 | import checkDisabled from '../assets/check-disabled.svg'; 7 | import { LoadingSpinner } from './LoadingSpinner'; 8 | 9 | interface ChainSwitcherProps {} 10 | 11 | export const ChainSwitcher = ({}: ChainSwitcherProps) => { 12 | const { chain: selectedChain } = useAccount(); 13 | const { chains, switchChain } = useSwitchChain(); 14 | 15 | const [isOpen, setIsOpen] = useState(false); 16 | 17 | const { refs, floatingStyles, context } = useFloating({ 18 | placement: 'bottom-start', 19 | open: isOpen, 20 | onOpenChange: setIsOpen, 21 | }); 22 | 23 | const dismiss = useDismiss(context); 24 | 25 | const { getReferenceProps, getFloatingProps } = useInteractions([dismiss]); 26 | 27 | const onChainClick = (chainId: number) => { 28 | switchChain({ chainId }); 29 | setIsOpen(false); 30 | }; 31 | 32 | return ( 33 | <> 34 | 42 | {isOpen && ( 43 |
49 | {chains.slice(1).map((chain) => { 50 | const checkSrc = chain.id === selectedChain?.id ? checkChecked : checkDisabled; 51 | return ( 52 | 65 | ); 66 | })} 67 |
68 | )} 69 | 70 | ); 71 | }; 72 | -------------------------------------------------------------------------------- /packages/waas-react-sdk/src/lib/components/CopyAddress.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | import { truncateAddress } from '../utils'; 3 | 4 | export const CopyAddress = ({ address }: { address: string }) => { 5 | const [copied, setCopied] = useState(false); 6 | 7 | const copy = async () => { 8 | await window.navigator.clipboard.writeText(address); 9 | setCopied(true); 10 | setTimeout(() => setCopied(false), 300); 11 | }; 12 | 13 | return ( 14 | 20 | ); 21 | }; 22 | -------------------------------------------------------------------------------- /packages/waas-react-sdk/src/lib/components/Dialog/Dialog.styles.css: -------------------------------------------------------------------------------- 1 | /* Overlay animations */ 2 | @keyframes fadeIn { 3 | from { 4 | opacity: 0; 5 | } 6 | to { 7 | opacity: 1; 8 | } 9 | } 10 | 11 | @keyframes fadeOut { 12 | from { 13 | opacity: 1; 14 | } 15 | to { 16 | opacity: 0; 17 | } 18 | } 19 | 20 | /* Overlay Styles */ 21 | .Dialog-overlay { 22 | background: rgba(0, 0, 0, 0.4); 23 | display: grid; 24 | place-items: center; 25 | } 26 | 27 | .Dialog-overlay-entering, 28 | .Dialog-overlay-entered { 29 | animation: fadeIn 0.3s ease-out; 30 | } 31 | 32 | .Dialog-overlay-exiting { 33 | animation: fadeOut 0.3s ease-in; 34 | } 35 | -------------------------------------------------------------------------------- /packages/waas-react-sdk/src/lib/components/EmptyActivities.tsx: -------------------------------------------------------------------------------- 1 | import emptyActivitiesImage from '../assets/empty-activities.svg'; 2 | 3 | export const EmptyActivities = () => { 4 | return ( 5 |
6 | No activities 7 |

No past wallet activity

8 |

9 | This is your long-term memory on the blockchain. You'll be able to view your future 10 | transaction history here. 11 |

12 |
13 | ); 14 | }; 15 | -------------------------------------------------------------------------------- /packages/waas-react-sdk/src/lib/components/EmptySwaps.tsx: -------------------------------------------------------------------------------- 1 | import emptyTokensImage from '../assets/empty-tokens.svg'; 2 | 3 | export const EmptySwaps = () => { 4 | return ( 5 |
6 | No tokens 7 |

You don't have any tokens to swap

8 |

9 | Deposit tokens into your wallet to begin swapping. You can copy your address to transfer 10 | tokens into the wallet by clicking on it above. 11 |

12 |
13 | ); 14 | }; 15 | -------------------------------------------------------------------------------- /packages/waas-react-sdk/src/lib/components/EmptyTokens.tsx: -------------------------------------------------------------------------------- 1 | import emptyTokensImage from '../assets/empty-tokens.svg'; 2 | 3 | export const EmptyTokens = () => { 4 | return ( 5 |
6 | No tokens 7 |

You don't own any tokens yet

8 |

9 | Deposit tokens into your wallet to see your balance. You can copy your address to transfer 10 | tokens into the wallet by clicking on it above. 11 |

12 |
13 | ); 14 | }; 15 | -------------------------------------------------------------------------------- /packages/waas-react-sdk/src/lib/components/LoadingSpinner.tsx: -------------------------------------------------------------------------------- 1 | export const LoadingSpinner = () => { 2 | return ( 3 | 9 | 17 | 22 | 23 | ); 24 | }; 25 | -------------------------------------------------------------------------------- /packages/waas-react-sdk/src/lib/components/SettingsMenu.tsx: -------------------------------------------------------------------------------- 1 | import { useDismiss, useFloating, useInteractions } from '@floating-ui/react'; 2 | import iconDisconnect from '../assets/icon-disconnect.svg'; 3 | import { Connector } from 'wagmi'; 4 | import { useBitski } from '..'; 5 | import { useState } from 'react'; 6 | import iconSettings from '../assets/settings.svg'; 7 | 8 | export const SettingsMenu = ({ connector }: { connector: Connector }) => { 9 | const { disconnect } = useBitski(); 10 | const [isOpen, setIsOpen] = useState(false); 11 | 12 | const { refs, floatingStyles, context } = useFloating({ 13 | placement: 'bottom-end', 14 | open: isOpen, 15 | onOpenChange: setIsOpen, 16 | }); 17 | 18 | const dismiss = useDismiss(context); 19 | 20 | const { getReferenceProps, getFloatingProps } = useInteractions([dismiss]); 21 | 22 | return ( 23 | <> 24 | 27 | {isOpen && ( 28 |
34 | 45 |
46 | )} 47 | 48 | ); 49 | }; 50 | -------------------------------------------------------------------------------- /packages/waas-react-sdk/src/lib/components/Skeleton.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export const Skeleton = () => { 4 | return ( 5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | ); 13 | }; 14 | -------------------------------------------------------------------------------- /packages/waas-react-sdk/src/lib/components/hooks/TotalBalanceUSD.graphql: -------------------------------------------------------------------------------- 1 | query TotalBalanceUSD($input: GetCurrencyBalancesV2Input!) { 2 | currencyBalances(input: $input) { 3 | totalBalanceUSD { 4 | formatted 5 | } 6 | connections { 7 | nodes { 8 | address { 9 | raw 10 | truncated 11 | } 12 | amountV2 { 13 | amount { 14 | decimals 15 | formatted 16 | value 17 | } 18 | formatted 19 | } 20 | value { 21 | decimals 22 | formatted 23 | value 24 | } 25 | currency { 26 | displayName 27 | image { 28 | url 29 | } 30 | symbol 31 | decimals 32 | } 33 | } 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /packages/waas-react-sdk/src/lib/components/hooks/useTokens.ts: -------------------------------------------------------------------------------- 1 | export interface TokenBalance { 2 | amount: string; 3 | amountUSD: string; 4 | name: string; 5 | image?: string; 6 | } 7 | 8 | interface Tokens { 9 | totalBalanceUsd: string; 10 | balances: TokenBalance[]; 11 | } 12 | 13 | export enum TokenStateKind { 14 | NoAddress = 'noAddress', 15 | Loading = 'loading', 16 | Tokens = 'tokens', 17 | Error = 'error', 18 | } 19 | 20 | export type TokensState = 21 | | { kind: TokenStateKind.NoAddress } 22 | | { kind: TokenStateKind.Loading; address: string; chainId: number } 23 | | { kind: TokenStateKind.Tokens; address: string; chainId: number; tokens: Tokens } 24 | | { kind: TokenStateKind.Error; address: string; chainId: number }; 25 | 26 | export enum TokenActionKind { 27 | FetchStart = 'Fetch Start', 28 | FetchSuccess = 'Fetch Success', 29 | FetchError = 'Fetch Error', 30 | } 31 | 32 | export type TokenAction = 33 | | { 34 | kind: TokenActionKind.FetchSuccess; 35 | address: string; 36 | chainId: number; 37 | tokens: Tokens; 38 | } 39 | | { 40 | kind: TokenActionKind.FetchStart; 41 | address: string; 42 | chainId: number; 43 | } 44 | | { 45 | kind: TokenActionKind.FetchError; 46 | address: string; 47 | chainId: number; 48 | }; 49 | 50 | export function tokensReducer(tokensState: TokensState, action: TokenAction): TokensState { 51 | switch (action.kind) { 52 | case TokenActionKind.FetchStart: 53 | return { 54 | kind: TokenStateKind.Loading, 55 | address: action.address, 56 | chainId: action.chainId, 57 | }; 58 | case TokenActionKind.FetchSuccess: 59 | switch (tokensState.kind) { 60 | case TokenStateKind.NoAddress: 61 | break; 62 | default: 63 | if (tokensState.address != action.address) { 64 | // Fetch does not match current address 65 | return tokensState; 66 | } 67 | } 68 | return { 69 | kind: TokenStateKind.Tokens, 70 | address: action.address, 71 | chainId: action.chainId, 72 | tokens: action.tokens, 73 | }; 74 | case TokenActionKind.FetchError: 75 | return { 76 | kind: TokenStateKind.Error, 77 | address: action.address, 78 | chainId: action.chainId, 79 | }; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /packages/waas-react-sdk/src/lib/connectors/index.ts: -------------------------------------------------------------------------------- 1 | export { bitski } from './bitski'; 2 | export type { BitskiConnector, BitskiParameters } from './bitski'; 3 | export { phantom } from './phantom'; 4 | -------------------------------------------------------------------------------- /packages/waas-react-sdk/src/lib/connectors/localStoragePopup.ts: -------------------------------------------------------------------------------- 1 | const POPUP_HEIGHT = 500; 2 | const POPUP_WIDTH = 500; 3 | 4 | export const openLocalStoragePopup = (): Promise => 5 | new Promise((resolve, reject) => { 6 | const width = window.screen.width; 7 | const height = window.screen.height; 8 | 9 | const leftOffset = (width - POPUP_WIDTH) / 2; 10 | const topOffset = (height - POPUP_HEIGHT) / 2; 11 | 12 | const closeId = crypto.randomUUID(); 13 | 14 | window.addEventListener( 15 | 'message', 16 | (event) => { 17 | if (event.origin !== window.location.origin) return; 18 | 19 | if (event.data?.closeId === closeId || event.data?.closeId === null) { 20 | resolve(); 21 | } 22 | }, 23 | false, 24 | ); 25 | 26 | const popup = window.open( 27 | `https://sign.bitski.com/import-local?closeId=${closeId}`, 28 | '_blank', 29 | `popup=yes, 30 | width=${POPUP_WIDTH}, 31 | height=${POPUP_HEIGHT}, 32 | top=${topOffset}, 33 | left=${leftOffset} 34 | `, 35 | ); 36 | }); 37 | -------------------------------------------------------------------------------- /packages/waas-react-sdk/src/lib/connectors/phantom.ts: -------------------------------------------------------------------------------- 1 | import { injected, InjectedParameters } from 'wagmi/connectors'; 2 | 3 | export function phantom(parameters: InjectedParameters) { 4 | return injected({ 5 | ...parameters, 6 | target() { 7 | return { 8 | id: 'phantom', 9 | name: 'Phantom', 10 | provider(window) { 11 | if (window?.phantom?.ethereum) return window.phantom?.ethereum; 12 | if (window?.ethereum?.providers) 13 | return window?.ethereum.providers.find((provider) => provider.isPhantom); 14 | if (window?.ethereum && window?.ethereum.isPhantom) return window?.ethereum; 15 | return undefined; 16 | }, 17 | }; 18 | }, 19 | }); 20 | } 21 | -------------------------------------------------------------------------------- /packages/waas-react-sdk/src/lib/constants.ts: -------------------------------------------------------------------------------- 1 | export const BITSKI_GRAPHQL_ENDPOINT = 'https://api.bitski.com/graphql'; 2 | -------------------------------------------------------------------------------- /packages/waas-react-sdk/src/lib/generated/gql/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./fragment-masking"; 2 | export * from "./gql"; -------------------------------------------------------------------------------- /packages/waas-react-sdk/src/lib/index.ts: -------------------------------------------------------------------------------- 1 | import './index.css'; 2 | export { BitskiProvider, BitskiWidget, BitskiAuth } from './components/BitskiWidget'; 3 | export { BitskiWalletViewer, Tab } from './components/BitskiWalletViewer'; 4 | export { BitskiWalletProvider } from './components/BitskiWalletProvider'; 5 | export type { LoginMethods } from './components/BitskiWidget/types'; 6 | export { LoginMethod } from './components/BitskiWidget/constants'; 7 | export { ConnectionStateKind, type ConnectionState } from './BitskiContext'; 8 | export {useBitski} from "./useBitski"; -------------------------------------------------------------------------------- /packages/waas-react-sdk/src/lib/utils/createBitskiConfig.ts: -------------------------------------------------------------------------------- 1 | import type { Transport } from 'viem'; 2 | import { http, createConfig, CreateConnectorFn } from 'wagmi'; 3 | import { type Chain } from 'wagmi/chains'; 4 | 5 | interface BitskiWagmiConfigOptions { 6 | chains: readonly [Chain, ...Chain[]]; 7 | connectors: CreateConnectorFn[]; 8 | } 9 | 10 | export const createBitskiConfig = ({ chains, connectors }: BitskiWagmiConfigOptions) => { 11 | const transports: Record = {}; 12 | 13 | for (const chain of chains) { 14 | transports[chain.id] = http(); 15 | } 16 | 17 | return createConfig({ 18 | chains, 19 | connectors, 20 | ssr: false, 21 | transports, 22 | multiInjectedProviderDiscovery: true, 23 | }); 24 | }; 25 | 26 | declare module 'wagmi' { 27 | interface Register { 28 | config: typeof createBitskiConfig; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /packages/waas-react-sdk/src/lib/utils/getBlockchainAccounts.ts: -------------------------------------------------------------------------------- 1 | export async function getBlockchainAccounts( 2 | fetch: typeof window.fetch, 3 | token: string, 4 | ): Promise { 5 | const response = await fetch('https://api.bitski.com/v2/blockchain/accounts', { 6 | method: 'GET', 7 | headers: { 8 | Authorization: `Bearer ${token}`, 9 | }, 10 | }); 11 | const { accounts } = (await response.json()) as AccountsResponse; 12 | return accounts; 13 | } 14 | 15 | export const LOCAL_STORAGE_LABEL = 'wallet.bitski.com/local-storage'; 16 | 17 | export interface BlockchainAccount { 18 | id: string; 19 | address: string; 20 | kind: string; 21 | labels: Record; 22 | annotations: Record; 23 | } 24 | 25 | export interface AccountsResponse { 26 | accounts: BlockchainAccount[]; 27 | } 28 | -------------------------------------------------------------------------------- /packages/waas-react-sdk/src/lib/utils/hasWindowProvider.ts: -------------------------------------------------------------------------------- 1 | export const hasWindowProvider = () => { 2 | if (typeof window !== 'undefined' && window.ethereum) { 3 | return true; 4 | } 5 | 6 | return false; 7 | }; 8 | -------------------------------------------------------------------------------- /packages/waas-react-sdk/src/lib/utils/index.ts: -------------------------------------------------------------------------------- 1 | export { createBitskiConfig } from './createBitskiConfig'; 2 | export { validateChains } from './validateChains'; 3 | export { validateConnectors } from './validateConnectors'; 4 | export { hasWindowProvider } from './hasWindowProvider'; 5 | export { isMobile } from './isMobile'; 6 | export { truncateAddress } from './truncateAddress'; 7 | export { truncateTitle } from './truncateTitle'; 8 | export { toFormattedValue } from './toFormattedValue'; 9 | export { toRawValue } from './toRawValue'; 10 | -------------------------------------------------------------------------------- /packages/waas-react-sdk/src/lib/utils/isMobile.ts: -------------------------------------------------------------------------------- 1 | export const isMobile = (): boolean => { 2 | if (typeof window !== 'undefined' && typeof navigator !== 'undefined') { 3 | const isMobileUserAgent = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent); 4 | const maxWidth = 768; 5 | const hasTouch = 'ontouchstart' in window || navigator.maxTouchPoints > 0; 6 | return isMobileUserAgent && window.innerWidth <= maxWidth && hasTouch; 7 | } 8 | 9 | return false; 10 | }; 11 | -------------------------------------------------------------------------------- /packages/waas-react-sdk/src/lib/utils/toFormattedValue.ts: -------------------------------------------------------------------------------- 1 | import { formatUnits } from 'viem'; 2 | 3 | export const toFormattedValue = (amount: string, decimals?: number | null) => { 4 | if (!decimals) { 5 | throw new Error('Missing decimals for setting token value.'); 6 | } 7 | 8 | return formatUnits(BigInt(amount), decimals); 9 | }; 10 | -------------------------------------------------------------------------------- /packages/waas-react-sdk/src/lib/utils/toRawValue.ts: -------------------------------------------------------------------------------- 1 | import { parseUnits } from 'viem'; 2 | 3 | export const toRawValue = (amount: string, decimals?: number | null) => { 4 | if (!decimals) { 5 | throw new Error('Missing decimals for setting token value.'); 6 | } 7 | 8 | return parseUnits(amount, decimals); 9 | }; 10 | -------------------------------------------------------------------------------- /packages/waas-react-sdk/src/lib/utils/truncateAddress.ts: -------------------------------------------------------------------------------- 1 | // Captures 0x + 4 characters, then the last 4 characters. 2 | const truncateRegex = /^(0x[a-zA-Z0-9]{4})[a-zA-Z0-9]+([a-zA-Z0-9]{4})$/; 3 | 4 | /** 5 | * Truncates an ethereum address to the format 0x0000…0000 6 | * @param address Full address to truncate 7 | * @returns Truncated address 8 | */ 9 | export const truncateAddress = (address: string) => { 10 | const match = address.match(truncateRegex); 11 | if (!match) return address; 12 | return `${match[1]}…${match[2]}`; 13 | }; 14 | -------------------------------------------------------------------------------- /packages/waas-react-sdk/src/lib/utils/truncateTitle.ts: -------------------------------------------------------------------------------- 1 | const MAX_TITLE_LENGTH = 22; 2 | 3 | export function truncateTitle(input: string): string { 4 | if (input.length <= MAX_TITLE_LENGTH) { 5 | return input; 6 | } else { 7 | return input.slice(0, MAX_TITLE_LENGTH) + '...'; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/waas-react-sdk/src/lib/utils/validateChains.ts: -------------------------------------------------------------------------------- 1 | import { Chain } from 'viem/chains'; 2 | 3 | export const validateChains = (chains: readonly [Chain, ...Chain[]]) => { 4 | // Allow all Chains, will error downstream if we don't support it 5 | return chains; 6 | }; 7 | -------------------------------------------------------------------------------- /packages/waas-react-sdk/src/main.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom/client'; 3 | import App from './App'; 4 | 5 | ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( 6 | 7 | 8 | , 9 | ); 10 | -------------------------------------------------------------------------------- /packages/waas-react-sdk/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /packages/waas-react-sdk/tailwind.config.cjs: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'], 4 | theme: {}, 5 | plugins: [], 6 | }; 7 | -------------------------------------------------------------------------------- /packages/waas-react-sdk/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "useDefineForClassFields": true, 5 | "lib": ["DOM", "DOM.Iterable", "ESNext"], 6 | "allowJs": false, 7 | "skipLibCheck": true, 8 | "esModuleInterop": false, 9 | "allowSyntheticDefaultImports": true, 10 | "strict": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "module": "ESNext", 13 | "moduleResolution": "Bundler", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "noEmit": true, 17 | "jsx": "react-jsx" 18 | }, 19 | "include": ["src"] 20 | } 21 | -------------------------------------------------------------------------------- /packages/waas-react-sdk/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import react from '@vitejs/plugin-react'; 3 | import path from 'node:path'; 4 | import dts from 'vite-plugin-dts'; 5 | import tailwindcss from 'tailwindcss'; 6 | import { libInjectCss } from 'vite-plugin-lib-inject-css'; 7 | 8 | export default defineConfig({ 9 | css: { 10 | postcss: { 11 | plugins: [tailwindcss], 12 | }, 13 | }, 14 | build: { 15 | copyPublicDir: false, 16 | commonjsOptions: { 17 | include: [/eth-provider-types/, /node_modules/], 18 | }, 19 | emptyOutDir: true, 20 | lib: { 21 | entry: path.resolve(__dirname, 'src/lib/index.ts'), 22 | name: 'BitskiWaasReactSDK', 23 | formats: ['es', 'cjs'], 24 | fileName: (format) => `index.${format}.js`, 25 | }, 26 | rollupOptions: { 27 | external: [ 28 | 'react', 29 | 'react/jsx-runtime', 30 | 'react-dom', 31 | 'tailwindcss', 32 | 'wagmi', 33 | 'viem', 34 | '@tanstack/react-query', 35 | '@wagmi/core', 36 | '@wagmi/connectors', 37 | ], 38 | output: { 39 | globals: { 40 | react: 'React', 41 | 'react/jsx-runtime': 'react/jsx-runtime', 42 | 'react-dom': 'ReactDOM', 43 | tailwindcss: 'tailwindcss', 44 | wagmi: 'wagmi', 45 | viem: 'viem', 46 | '@tanstack/react-query': '@tanstack/react-query', 47 | '@wagmi/core': '@wagmi/core', 48 | '@wagmi/connectors': '@wagmi/connectors', 49 | }, 50 | }, 51 | }, 52 | sourcemap: true, 53 | }, 54 | optimizeDeps: { 55 | include: ['eth-provider-types'], 56 | }, 57 | plugins: [libInjectCss(), react(), dts({ include: 'src/lib', rollupTypes: true })], 58 | }); 59 | -------------------------------------------------------------------------------- /packages/wagmi-connector/.gitignore: -------------------------------------------------------------------------------- 1 | lib -------------------------------------------------------------------------------- /packages/wagmi-connector/README.md: -------------------------------------------------------------------------------- 1 | # Bitski Wagmi Connector 2 | 3 | [![npm](https://img.shields.io/npm/v/@bitski/wagmi-connector.svg)](https://www.npmjs.com/package/@bitski/wagmi-connector) 4 | 5 | ## Installation 6 | 7 | ``` 8 | npm install @bitski/wagmi-connector 9 | ``` 10 | 11 | ## Usage 12 | 13 | Below are common examples. For more details on all configurable options, please also see the wagmi and RainbowKit docs. 14 | 15 | ### Wagmi Only 16 | 17 | ```javascript 18 | import { BitskiConnector } from '@bitski/wagmi-connector'; 19 | import { createConfig } from '@wagmi/core'; 20 | 21 | wagmiConfig = createConfig({ 22 | connectors: [ 23 | new BitskiConnector({ 24 | chains, 25 | options: { 26 | id: 'my-connector', 27 | name: 'My App Wallet', 28 | appId: 'my-bitski-app-id', 29 | bitskiOptions: { 30 | waas: { enabled: false }, 31 | callbackURL: 'https://callback.url:3000', 32 | // For more options, see the list of ProviderOptions under the bitski package 33 | }, 34 | }, 35 | }), 36 | new WalletConnectConnector({ 37 | chains, 38 | options: { projectId: walletConnectProjectId, showQrModal: false, metadata }, 39 | }), 40 | ], 41 | ...defaultConfig, 42 | }); 43 | ``` 44 | 45 | ### RainbowKit + Wagmi 46 | 47 | ```javascript 48 | import { bitskiWallet } from '@bitski/wagmi-connector'; 49 | import { 50 | connectorsForWallets, 51 | RainbowKitProvider, 52 | Locale, 53 | } from "@rainbow-me/rainbowkit"; 54 | import { 55 | injectedWallet, 56 | metaMaskWallet, 57 | coinbaseWallet, 58 | } from "@rainbow-me/rainbowkit/wallets"; 59 | import { configureChains, createConfig, WagmiConfig } from "wagmi"; 60 | import { mainnet, polygon, optimism, arbitrum, base, zora } from "viem/chains"; 61 | import { publicProvider } from "wagmi/providers/public"; 62 | 63 | const connectors = connectorsForWallets([ 64 | { 65 | groupName: "Recommended", 66 | wallets: [ 67 | bitskiWallet({ 68 | options: { appId: 'my-bitski-app-id', bitskiOptions: { network } }, 69 | chains, 70 | }), 71 | ], 72 | }, 73 | { 74 | groupName: "Other Wallets", 75 | wallets: [ 76 | injectedWallet({ chains }), 77 | metaMaskWallet({ chains, projectId: "YOUR_PROJECT_ID" }), 78 | coinbaseWallet({ appName: "YOUR_APP_NAME", chains }), 79 | ], 80 | }, 81 | ]); 82 | 83 | const wagmiConfig = createConfig({ 84 | autoConnect: true, 85 | connectors, 86 | publicClient, 87 | webSocketPublicClient, 88 | }); 89 | 90 | function MyApp({ Component, pageProps }: AppProps) { 91 | const { locale } = useRouter() as { locale: Locale }; 92 | return ( 93 | 94 | 95 | 96 | 97 | 98 | ); 99 | } 100 | ``` 101 | -------------------------------------------------------------------------------- /packages/wagmi-connector/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@bitski/wagmi-connector", 3 | "version": "1.0.3", 4 | "description": "Wagmi adapter for Bitski", 5 | "main": "lib/index.js", 6 | "module": "dist/index.js", 7 | "types": "lib/index.d.ts", 8 | "repository": { 9 | "type": "git", 10 | "url": "https://github.com/BitskiCo/bitski-js" 11 | }, 12 | "scripts": { 13 | "lint": "eslint . --cache", 14 | "build": "tsc -p tsconfig.json", 15 | "prettier": "prettier --config ../../.prettierrc '{src,tests}/**/*.ts' --write" 16 | }, 17 | "dependencies": { 18 | "@wagmi/core": "^1.4.13", 19 | "bitski": "^4.0.0", 20 | "viem": "^1.19.13", 21 | "@rainbow-me/rainbowkit": "^1.3.3" 22 | }, 23 | "devDependencies": { 24 | "@babel/core": "^7.6.4", 25 | "@babel/plugin-transform-runtime": "^7.6.2", 26 | "@babel/preset-env": "^7.6.3", 27 | "babelify": "^10.0.0" 28 | }, 29 | "browserify": { 30 | "transform": [ 31 | [ 32 | "babelify", 33 | { 34 | "presets": [ 35 | "@babel/preset-env" 36 | ], 37 | "plugins": [ 38 | "@babel/plugin-transform-runtime" 39 | ] 40 | } 41 | ] 42 | ] 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /packages/wagmi-connector/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "dist", 4 | "rootDir": "src", 5 | "noImplicitAny": true, 6 | "moduleResolution": "node", 7 | "lib": ["ESNext"], 8 | "target": "ES2020", 9 | "esModuleInterop": true, 10 | "isolatedModules": true, 11 | "preserveSymlinks": true, 12 | "declaration": true, 13 | "declarationDir": "lib", 14 | "jsx": "preserve", 15 | "skipLibCheck": true, 16 | "strict": true, 17 | "resolveJsonModule": true 18 | }, 19 | "include": ["src/**/*.ts"] 20 | } 21 | -------------------------------------------------------------------------------- /turbo.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://turborepo.org/schema.json", 3 | "baseBranch": "origin/main", 4 | "pipeline": { 5 | "build": { 6 | "dependsOn": ["^build"] 7 | }, 8 | "package": { 9 | "dependsOn": ["^build"], 10 | "outputs": ["package/**"] 11 | }, 12 | "test": { 13 | "dependsOn": ["^build"], 14 | "outputs": [] 15 | }, 16 | "check": { 17 | "outputs": [] 18 | } 19 | } 20 | } 21 | --------------------------------------------------------------------------------