├── .codeflow.yml ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ ├── config.yml │ └── feature_request.yml ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── codeql-analysis.yml │ ├── main.yml │ ├── notification.yml │ ├── release-notes.yml │ ├── tagging.yml │ └── versioning.yml ├── .gitignore ├── .nvmrc ├── CODEOWNERS ├── LICENSE ├── README.md ├── examples ├── README.md ├── rainbowkit-demo │ ├── .env │ ├── .gitignore │ ├── README.md │ ├── config-overrides.js │ ├── package-lock.json │ ├── package.json │ ├── public │ │ ├── cbw.png │ │ ├── favicon.ico │ │ ├── index.html │ │ ├── manifest.json │ │ ├── mm.png │ │ └── wc.png │ └── src │ │ ├── App.js │ │ ├── index.js │ │ ├── styles.css │ │ └── utils.js ├── verify-webapp │ ├── README.md │ ├── a-profile-app │ │ ├── index.html │ │ ├── package.json │ │ ├── postcss.config.js │ │ ├── src │ │ │ ├── App.css │ │ │ ├── App.tsx │ │ │ ├── favicon.ico │ │ │ ├── index.css │ │ │ ├── main.tsx │ │ │ └── vite-env.d.ts │ │ ├── tailwind.config.js │ │ ├── tsconfig.json │ │ ├── tsconfig.node.json │ │ └── vite.config.ts │ ├── a-wallet │ │ ├── favicon.ico │ │ ├── index.html │ │ ├── package.json │ │ ├── src │ │ │ ├── main.ts │ │ │ ├── style.css │ │ │ ├── util.ts │ │ │ └── vite-env.d.ts │ │ └── tsconfig.json │ ├── demo.gif │ └── package.json ├── wagmi-demo │ ├── .env │ ├── .gitignore │ ├── README.md │ ├── config-overrides.js │ ├── package-lock.json │ ├── package.json │ ├── public │ │ ├── cbw.png │ │ ├── favicon.ico │ │ ├── index.html │ │ ├── manifest.json │ │ ├── mm.png │ │ └── wc.png │ └── src │ │ ├── App.js │ │ ├── Modal.js │ │ ├── connectors.js │ │ ├── index.js │ │ ├── styles.css │ │ └── utils.js ├── web3-onboard-demo │ ├── .env │ ├── .gitignore │ ├── README.md │ ├── config-overrides.js │ ├── package-lock.json │ ├── package.json │ ├── public │ │ ├── cbw.png │ │ ├── favicon.ico │ │ ├── index.html │ │ ├── manifest.json │ │ ├── mm.png │ │ └── wc.png │ └── src │ │ ├── App.js │ │ ├── index.js │ │ ├── styles.css │ │ └── utils.js ├── web3-react-demo │ ├── .env │ ├── .gitignore │ ├── README.md │ ├── config-overrides.js │ ├── package-lock.json │ ├── package.json │ ├── public │ │ ├── cbw.png │ │ ├── favicon.ico │ │ ├── index.html │ │ ├── manifest.json │ │ ├── mm.png │ │ └── wc.png │ └── src │ │ ├── App.js │ │ ├── Modal.js │ │ ├── connectors.js │ │ ├── index.js │ │ ├── networks.js │ │ ├── styles.css │ │ └── utils.js └── web3modal-demo │ ├── .env │ ├── .gitignore │ ├── README.md │ ├── config-overrides.js │ ├── package-lock.json │ ├── package.json │ ├── public │ ├── cbw.png │ ├── favicon.ico │ ├── index.html │ ├── manifest.json │ ├── mm.png │ └── wc.png │ └── src │ ├── App.js │ ├── index.js │ ├── networks.js │ ├── providerOptions.js │ ├── styles.css │ └── utils.js └── packages └── wallet-sdk ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .prettierignore ├── .prettierrc.yml ├── LICENSE ├── __tests__ ├── addressStorage.test.js └── encryptDecrypt.test.js ├── assets └── coinbase_wallet_logo_kit.zip ├── babel.config.js ├── compile-assets.js ├── jest.config.ts ├── jest.setup.ts ├── karma.conf.js ├── package.json ├── scripts └── release.sh ├── src ├── CoinbaseWalletSDK.test.ts ├── CoinbaseWalletSDK.ts ├── __mocks__ │ ├── provider.ts │ └── relay.ts ├── assets │ └── wallet-logo.ts ├── components │ ├── ConnectContent │ │ ├── ConnectContent.scss │ │ ├── ConnectContent.tsx │ │ └── index.tsx │ ├── ConnectDialog │ │ ├── ConnectDialog.scss │ │ ├── ConnectDialog.test.tsx │ │ ├── ConnectDialog.tsx │ │ └── index.tsx │ ├── LinkFlow │ │ ├── LinkFlow.test.tsx │ │ ├── LinkFlow.tsx │ │ └── index.tsx │ ├── QRCode.tsx │ ├── Snackbar │ │ ├── Snackbar.scss │ │ ├── Snackbar.test.tsx │ │ ├── Snackbar.tsx │ │ ├── SnackbarContainer.test.tsx │ │ └── index.tsx │ ├── Spinner │ │ ├── Spinner.scss │ │ ├── Spinner.test.tsx │ │ ├── Spinner.tsx │ │ └── index.tsx │ ├── TryExtensionContent │ │ ├── TryExtensionContent.scss │ │ ├── TryExtensionContent.tsx │ │ └── index.tsx │ ├── icons │ │ ├── ArrowLeftIcon.tsx │ │ ├── CloseIcon.tsx │ │ ├── LaptopIcon.tsx │ │ ├── QRCodeIcon.tsx │ │ ├── QRLogoCoinbase.tsx │ │ ├── QRLogoWallet.ts │ │ ├── SafeIcon.tsx │ │ ├── StatusDotIcon.tsx │ │ ├── coinbase-round.svg │ │ ├── coinbase-wallet-round.svg │ │ └── coinbase.svg │ ├── theme.scss │ └── types.ts ├── connection │ ├── ClientMessage.ts │ ├── DiagnosticLogger.test.ts │ ├── DiagnosticLogger.ts │ ├── EventListener.ts │ ├── RxWebSocket.test.ts │ ├── RxWebSocket.ts │ ├── ServerMessage.ts │ ├── SessionConfig.ts │ └── WalletSDKConnection.ts ├── fixtures │ └── provider.ts ├── index.ts ├── lib │ ├── ScopedLocalStorage.test.ts │ ├── ScopedLocalStorage.ts │ ├── cssReset.scss │ └── cssReset.ts ├── provider │ ├── CoinbaseWalletProvider.test.ts │ ├── CoinbaseWalletProvider.ts │ ├── FilterPolyfill.ts │ ├── JSONRPC.ts │ ├── SolanaProvider.test.ts │ ├── SolanaProvider.ts │ ├── SubscriptionManager.ts │ ├── WalletSDKUI.test.ts │ ├── WalletSDKUI.ts │ ├── WalletUI.ts │ ├── WalletUIError.ts │ └── Web3Provider.ts ├── relay │ ├── EthereumTransactionParams.ts │ ├── RelayMessage.ts │ ├── Session.ts │ ├── WalletSDKRelay.ts │ ├── WalletSDKRelayAbstract.ts │ ├── WalletSDKRelayEventManager.test.ts │ ├── WalletSDKRelayEventManager.ts │ ├── Web3Method.ts │ ├── Web3Request.ts │ ├── Web3RequestCanceledMessage.ts │ ├── Web3RequestMessage.ts │ ├── Web3Response.ts │ ├── Web3ResponseMessage.ts │ ├── aes256gcm.test.ts │ ├── aes256gcm.ts │ └── solana │ │ ├── SolanaWeb3Method.ts │ │ ├── SolanaWeb3Request.ts │ │ └── SolanaWeb3Response.ts ├── types.ts ├── util.test.ts ├── util.ts ├── vendor-js │ ├── eth-eip712-util │ │ ├── LICENSE │ │ ├── abi.js │ │ ├── index.d.ts │ │ ├── index.js │ │ └── util.js │ └── qrcode-svg │ │ ├── LICENSE │ │ ├── index.d.ts │ │ └── index.js └── version.ts ├── tsconfig.build.json ├── tsconfig.json ├── webpack.config.js └── yarn.lock /.codeflow.yml: -------------------------------------------------------------------------------- 1 | secure: 2 | required_reviews: 1 3 | upstream_repository: coinbase/coinbase-wallet-sdk 4 | operate: 5 | slack_channels: 6 | - "#wallet-squad-build" 7 | - "#wallet-feedback" 8 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug report 2 | description: Create a report to help us improve 3 | title: "Bug: " 4 | labels: ["type: bug"] 5 | 6 | body: 7 | - type: input 8 | id: description 9 | attributes: 10 | label: Describe the bug 11 | description: A clear and concise description of what the bug is. 12 | placeholder: Tell us what you see! 13 | validations: 14 | required: true 15 | 16 | - type: textarea 17 | id: steps 18 | attributes: 19 | label: Steps 20 | description: Steps to reproduce the behavior. 21 | placeholder: | 22 | 1. Go to '...' 23 | 2. Click on '....' 24 | 3. Scroll down to '....' 25 | 4. See error 26 | validations: 27 | required: true 28 | 29 | - type: textarea 30 | id: expected 31 | attributes: 32 | label: Expected behavior 33 | description: A clear and concise description of what you expected to happen. 34 | validations: 35 | required: true 36 | 37 | - type: input 38 | id: version 39 | attributes: 40 | label: Version 41 | description: Which version of Coinbase Wallet SDK 42 | 43 | - type: textarea 44 | id: additional 45 | attributes: 46 | label: Additional info 47 | description: If applicable, include links to screenshots or error logs 48 | 49 | - type: textarea 50 | id: desktop 51 | attributes: 52 | label: Desktop 53 | description: Please fill in details for bugs reported on desktop 54 | placeholder: | 55 | - OS: [e.g. iOS] 56 | - Browser [e.g. chrome, safari] 57 | - Version [e.g. 22] 58 | 59 | - type: textarea 60 | id: smartphone 61 | attributes: 62 | label: Smartphone 63 | description: Please fill in details for bugs reported on smartphone devices 64 | placeholder: | 65 | - Device: [e.g. iPhone6] 66 | - OS: [e.g. iOS8.1] 67 | - Browser [e.g. stock browser, safari] 68 | - Version [e.g. 22] 69 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Coinbase Wallet Developer Docs 4 | url: https://docs.cloud.coinbase.com/wallet-sdk/docs 5 | about: Coinbase Wallet's developer documentation 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: Feature request 2 | description: Suggest an idea for this project 3 | title: "Feature Request: " 4 | labels: ["type: enhancement"] 5 | 6 | body: 7 | - type: markdown 8 | attributes: 9 | value: | 10 | This issue form is for feature requests only! 11 | If you've found a bug, please use [bug_report](/new?template=bug_report.yml) 12 | - type: textarea 13 | id: problem 14 | attributes: 15 | label: Is your feature request related to a problem? Please describe. 16 | description: A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 17 | 18 | - type: textarea 19 | id: solution 20 | attributes: 21 | label: Describe the solution you'd like 22 | description: A clear and concise description of what you want to happen. 23 | validations: 24 | required: true 25 | 26 | - type: textarea 27 | id: alternatives 28 | attributes: 29 | label: Describe alternatives you've considered 30 | description: Describe any alternative solutions or features you've considered. 31 | 32 | - type: textarea 33 | id: other 34 | attributes: 35 | label: Additional context 36 | description: Add any other context or screenshots about the feature request here. 37 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### _Summary_ 2 | 3 | 6 | 7 | ### _How did you test your changes?_ 8 | 9 | 12 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [master] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [master] 20 | schedule: 21 | - cron: "45 14 * * 6" 22 | 23 | # paths-ignore: 24 | # - packages/wallet-native-sdk 25 | 26 | jobs: 27 | analyze: 28 | name: Analyze 29 | runs-on: ubuntu-latest 30 | permissions: 31 | actions: read 32 | contents: read 33 | security-events: write 34 | 35 | strategy: 36 | fail-fast: false 37 | matrix: 38 | language: ["javascript"] 39 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 40 | # Learn more about CodeQL language support at https://git.io/codeql-language-support 41 | 42 | steps: 43 | - name: Checkout repository 44 | uses: actions/checkout@v3 45 | 46 | # Initializes the CodeQL tools for scanning. 47 | - name: Initialize CodeQL 48 | uses: github/codeql-action/init@v2 49 | with: 50 | languages: ${{ matrix.language }} 51 | # If you wish to specify custom queries, you can do so here or in a config file. 52 | # By default, queries listed here will override any specified in a config file. 53 | # Prefix the list here with "+" to use these queries and those in the config file. 54 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 55 | 56 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 57 | # If this step fails, then you should remove it and run the build manually (see below) 58 | - name: Autobuild 59 | uses: github/codeql-action/autobuild@v2 60 | 61 | # ℹ️ Command-line programs to run using the OS shell. 62 | # 📚 https://git.io/JvXDl 63 | 64 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 65 | # and modify them (or add more) to build your code if your project 66 | # uses a compiled language 67 | 68 | #- run: | 69 | # make bootstrap 70 | # make release 71 | 72 | - name: Perform CodeQL Analysis 73 | uses: github/codeql-action/analyze@v2 74 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | lint-check: 7 | name: Lint / Type Check 8 | runs-on: ubuntu-latest 9 | defaults: 10 | run: 11 | working-directory: ./packages/wallet-sdk 12 | 13 | steps: 14 | - name: Checkout 15 | uses: actions/checkout@v3 16 | 17 | - name: Checkout node action 18 | uses: actions/setup-node@v3 19 | with: 20 | node-version-file: '.nvmrc' 21 | cache-dependency-path: '**/yarn.lock' 22 | 23 | - name: Install NPM dependencies 24 | run: yarn install 25 | 26 | - name: Check ESLint 27 | run: yarn lint:eslint 28 | 29 | - name: Check prettier formatting 30 | run: yarn lint:prettier 31 | 32 | - name: Runs compile asset script 33 | run: node ./compile-assets.js 34 | 35 | - name: Check Types 36 | run: yarn lint:types 37 | 38 | tests: 39 | name: Tests 40 | runs-on: ubuntu-latest 41 | defaults: 42 | run: 43 | working-directory: ./packages/wallet-sdk 44 | 45 | strategy: 46 | matrix: 47 | node-version: [14.x, 16.x, 18.x] 48 | 49 | steps: 50 | - name: Checkout 51 | uses: actions/checkout@v3 52 | 53 | - name: Use Node.js ${{ matrix.node-version }} 54 | uses: actions/setup-node@v3 55 | with: 56 | node-version: ${{ matrix.node-version }} 57 | cache-dependency-path: '**/yarn.lock' 58 | 59 | - name: Install NPM dependencies 60 | run: yarn install 61 | 62 | - name: Run Unit Tests 63 | run: yarn test:unit 64 | -------------------------------------------------------------------------------- /.github/workflows/notification.yml: -------------------------------------------------------------------------------- 1 | name: Slack notifications for PRs and Issues 2 | 3 | on: 4 | pull_request: 5 | types: [opened, reopened] 6 | issues: 7 | types: [opened, reopened] 8 | 9 | env: 10 | CHANNEL_WALLET_FEEDBACK: ${{ vars.CHANNEL_WALLET_FEEDBACK }} 11 | CHANNEL_WALLET_SQUAD_BUILD: ${{ vars.CHANNEL_WALLET_SQUAD_BUILD }} 12 | ON_CALL_WALLET: ${{ vars.ON_CALL_WALLET }} 13 | ON_CALL_BUILD_SQUAD: ${{ vars.ON_CALL_BUILD_SQUAD }} 14 | 15 | jobs: 16 | checks: 17 | runs-on: ubuntu-latest 18 | outputs: 19 | skip: ${{ env.skip }} 20 | steps: 21 | - name: Check spam labels 22 | if: ${{ contains(github.event.*.labels.*.name, 'spam') }} 23 | run: | 24 | echo "skip=true" >> $GITHUB_ENV 25 | echo "::error:: Spam label found." 26 | - name: Check write permission 27 | run: | 28 | permission=$(curl -s -H "Authorization: token $GITHUB_TOKEN" https://api.github.com/repos/$REPO/collaborators/$USER/permission | jq -r '.permission') 29 | if [ "$permission" != "admin" ] && [ "$permission" != "write" ]; then 30 | echo "::notice:: User $USER has $permission permission." 31 | exit 0 32 | fi 33 | echo "skip=true" >> $GITHUB_ENV 34 | echo "::error:: User $USER has $permission permission. No slack alert required." 35 | env: 36 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 37 | REPO: ${{ github.repository }} 38 | USER: ${{ github.actor }} 39 | 40 | parse-event-details: 41 | runs-on: ubuntu-latest 42 | needs: checks 43 | if: ${{ vars.SLACK_ENABLED == 'true' && needs.checks.outputs.skip != 'true' }} 44 | outputs: 45 | event: ${{ env.event }} 46 | title: ${{ env.title }} 47 | url: ${{ env.url }} 48 | author: ${{ env.author }} 49 | steps: 50 | - name: Set pull request details 51 | if: ${{ github.event_name == 'pull_request' }} 52 | env: 53 | title: ${{ github.event.pull_request.title }} 54 | run: | 55 | echo "event=PR ${{ github.event.action }}" >> $GITHUB_ENV 56 | echo "title=${{ env.title }}" >> $GITHUB_ENV 57 | echo "url=${{ github.event.pull_request.html_url }}" >> $GITHUB_ENV 58 | echo "author=${{ github.event.pull_request.user.login }}" >> $GITHUB_ENV 59 | 60 | - name: Set issue details 61 | if: ${{ github.event_name == 'issues' }} 62 | env: 63 | title: ${{ github.event.issue.title }} 64 | run: | 65 | echo "event=Issue ${{ github.event.action }}" >> $GITHUB_ENV 66 | echo "title=${{ env.title }}" >> $GITHUB_ENV 67 | echo "url=${{ github.event.issue.html_url }}" >> $GITHUB_ENV 68 | echo "author=${{ github.event.issue.user.login }}" >> $GITHUB_ENV 69 | 70 | notify: 71 | runs-on: ubuntu-latest 72 | needs: parse-event-details 73 | steps: 74 | - name: Checkout code 75 | uses: actions/checkout@v3 76 | 77 | - name: Set channel and mention 78 | run: | 79 | if ${{ contains(join(github.event.issue.labels.*.name), 'type:') }}; then 80 | echo "channel=${{ env.CHANNEL_WALLET_FEEDBACK }}" >> $GITHUB_ENV 81 | echo "mention=!subteam^${{ env.ON_CALL_WALLET }}" >> $GITHUB_ENV 82 | else 83 | echo "channel=${{ env.CHANNEL_WALLET_SQUAD_BUILD }}" >> $GITHUB_ENV 84 | echo "mention=!subteam^${{ env.ON_CALL_BUILD_SQUAD }}" >> $GITHUB_ENV 85 | fi 86 | 87 | - name: Set text 88 | run: | 89 | text=$(echo "${{ vars.SLACK_TEMPLATE }}") 90 | text=${text//'{{event}}'/${{ needs.parse-event-details.outputs.event }}} 91 | text=${text//'{{title}}'/${{ needs.parse-event-details.outputs.title }}} 92 | text=${text//'{{url}}'/${{ needs.parse-event-details.outputs.url }}} 93 | text=${text//'{{author}}'/${{ needs.parse-event-details.outputs.author }}} 94 | text=${text//'{{mention}}'/${{ env.mention }}} 95 | text=${text//'{{repo}}'/${{ github.repository }}} 96 | text="${text//$'\r\n'/'\n'}" 97 | text="${text//$'\n'/'\n'}" 98 | echo "text=$text" >> $GITHUB_ENV 99 | 100 | - name: Notify Slack 101 | uses: slackapi/slack-github-action@v1.23.0 102 | with: 103 | payload: | 104 | { 105 | "channel": "#${{ env.channel }}", 106 | "text": "${{ env.text }}", 107 | "icon_emoji": ":${{ vars.ICON_EMOJI }}:" 108 | } 109 | env: 110 | SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} 111 | -------------------------------------------------------------------------------- /.github/workflows/release-notes.yml: -------------------------------------------------------------------------------- 1 | name: Release notes 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v*.*.*" 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | name: Create release notes 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v2 15 | - name: Release 16 | uses: softprops/action-gh-release@v1 17 | with: 18 | generate_release_notes: true 19 | draft: true 20 | -------------------------------------------------------------------------------- /.github/workflows/tagging.yml: -------------------------------------------------------------------------------- 1 | name: Version - Tag 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | autotag: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v2 13 | - uses: butlerlogic/action-autotag@stable 14 | env: 15 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 16 | with: 17 | root: "./packages/wallet-sdk" 18 | tag_prefix: "v" 19 | -------------------------------------------------------------------------------- /.github/workflows/versioning.yml: -------------------------------------------------------------------------------- 1 | name: Version - PR 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | packageVersion: 7 | description: "The version to publish in MAJOR.MINOR.PATCH format" 8 | required: true 9 | default: "" 10 | 11 | jobs: 12 | authorize: 13 | name: Authorize 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: ${{ github.actor }} permission check to update release version 17 | uses: "lannonbr/repo-permission-check-action@2.0.2" 18 | with: 19 | permission: "write" 20 | env: 21 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 22 | 23 | version: 24 | name: Updates package version 25 | runs-on: ubuntu-latest 26 | defaults: 27 | run: 28 | working-directory: ./packages/wallet-sdk 29 | needs: [authorize] 30 | env: 31 | PACKAGE_VERSION: ${{ github.event.inputs.packageVersion }} 32 | 33 | steps: 34 | - name: Checkout 35 | uses: actions/checkout@v3 36 | 37 | - name: Use Node.js 38 | uses: actions/setup-node@v3 39 | with: 40 | node-version-file: '.nvmrc' 41 | cache-dependency-path: '**/yarn.lock' 42 | 43 | - name: Update version 44 | run: yarn version --new-version ${{ env.PACKAGE_VERSION }} --no-git-tag-version 45 | 46 | - name: Update version.ts file 47 | run: yarn prebuild 48 | 49 | - name: Commit and branch updated version 50 | uses: peter-evans/create-pull-request@v4 51 | with: 52 | title: "[Version update] v${{ env.PACKAGE_VERSION }}" 53 | body: "Automated workflow: version update" 54 | branch: release-v${{ env.PACKAGE_VERSION }} 55 | delete-branch: true 56 | commit-message: "v${{ env.PACKAGE_VERSION }}" 57 | labels: version-update 58 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # misc 2 | .DS_Store 3 | 4 | # Native mobile 5 | 6 | ## User settings 7 | xcuserdata/ 8 | 9 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 10 | *.xcscmblueprint 11 | *.xccheckout 12 | 13 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 14 | build/ 15 | DerivedData/ 16 | *.moved-aside 17 | *.pbxuser 18 | !default.pbxuser 19 | *.mode1v3 20 | !default.mode1v3 21 | *.mode2v3 22 | !default.mode2v3 23 | *.perspectivev3 24 | !default.perspectivev3 25 | 26 | ## Obj-C/Swift specific 27 | *.hmap 28 | 29 | ## App packaging 30 | *.ipa 31 | *.dSYM.zip 32 | *.dSYM 33 | 34 | ## Playgrounds 35 | timeline.xctimeline 36 | playground.xcworkspace 37 | 38 | # Swift Package Manager 39 | # 40 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 41 | # Packages/ 42 | # Package.pins 43 | # Package.resolved 44 | # *.xcodeproj 45 | # 46 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata 47 | # hence it is not needed unless you have added a package configuration file to your project 48 | # .swiftpm 49 | 50 | .build/ 51 | 52 | # CocoaPods 53 | # 54 | # We recommend against adding the Pods directory to your .gitignore. However 55 | # you should judge for yourself, the pros and cons are mentioned at: 56 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 57 | # 58 | Pods/ 59 | # 60 | # Add this line if you want to avoid checking in source code from the Xcode workspace 61 | *.xcworkspace 62 | 63 | # Carthage 64 | # 65 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 66 | # Carthage/Checkouts 67 | 68 | Carthage/Build/ 69 | 70 | # Accio dependency management 71 | Dependencies/ 72 | .accio/ 73 | 74 | # fastlane 75 | # 76 | # It is recommended to not store the screenshots in the git repo. 77 | # Instead, use fastlane to re-generate the screenshots whenever they are needed. 78 | # For more information about the recommended setup visit: 79 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 80 | 81 | fastlane/report.xml 82 | fastlane/Preview.html 83 | fastlane/screenshots/**/*.png 84 | fastlane/test_output 85 | 86 | # Code Injection 87 | # 88 | # After new code Injection tools there's a generated folder /iOSInjectionProject 89 | # https://github.com/johnno1962/injectionforxcode 90 | 91 | iOSInjectionProject/ 92 | .DS_Store 93 | xcshareddata 94 | 95 | # Gradle files 96 | .gradle/ 97 | build/ 98 | 99 | # Local configuration file (sdk path, etc) 100 | local.properties 101 | 102 | # Log/OS Files 103 | *.log 104 | 105 | # Android Studio generated files and folders 106 | captures/ 107 | .externalNativeBuild/ 108 | .cxx/ 109 | *.apk 110 | output.json 111 | 112 | # IntelliJ 113 | *.iml 114 | .idea/ 115 | misc.xml 116 | deploymentTargetDropDown.xml 117 | render.experimental.xml 118 | 119 | # Keystore files 120 | *.jks 121 | *.keystore 122 | 123 | # Google Services (e.g. APIs or Firebase) 124 | google-services.json 125 | 126 | # Android Profiling 127 | *.hprof 128 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 14.17 -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @coinbase/coinbase-wallet-sdk 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018-2022 Coinbase, Inc. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | ## Coinbase Wallet Examples 2 | 3 | ### Verify Web App 4 | 5 | [Verify Web App Example](verify-webapp/) 6 | 7 | - Related documentation: [Deep Linking into the Coinbase Wallet Dapp Browser](https://docs.cloud.coinbase.com/wallet-sdk/docs/deep-link-into-dapp-browser) 8 | 9 | ### Integrate with Rainbowkit 10 | 11 | [Rainbowkit example](rainbowkit-demo/) 12 | 13 | - Related documentation: [Integrating with rainbowkit](https://www.rainbowkit.com/docs/introduction) 14 | 15 | ### Integrate with wagmi 16 | 17 | [wagmi example](wagmi-demo/) 18 | 19 | - Related documentation: [Integrating with wagmi](https://docs.cloud.coinbase.com/wallet-sdk/docs/wagmi) 20 | 21 | ### Integrate with web3-react 22 | 23 | [web3-react example](web3-react-demo/) 24 | 25 | - Related documentation: [Integrating with Web3-React](https://docs.cloud.coinbase.com/wallet-sdk/docs/web3-react) 26 | 27 | ### Integrate with web3modal 28 | 29 | [web3modal example](web3modal-demo/) 30 | 31 | - Related documentation: [Integrating with Web3Modal](https://docs.cloud.coinbase.com/wallet-sdk/docs/web3modal) 32 | 33 | ### Integrate with web3-onboard 34 | 35 | [web3-onboard example](web3-onboard-demo/) 36 | 37 | - Related documentation: [Integrating with Web3-Onboard](https://docs.cloud.coinbase.com/wallet-sdk/docs/web3-onboard) 38 | -------------------------------------------------------------------------------- /examples/rainbowkit-demo/.env: -------------------------------------------------------------------------------- 1 | GENERATE_SOURCEMAP=false 2 | INFURA_UD= -------------------------------------------------------------------------------- /examples/rainbowkit-demo/.gitignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | /node_modules 3 | /.pnp 4 | .pnp.js 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | -------------------------------------------------------------------------------- /examples/rainbowkit-demo/README.md: -------------------------------------------------------------------------------- 1 | # Coinbase Wallet: Integrate with wagmi 2 | 3 | This sample application demonstrates how to integrate Coinbase Wallet into a decentralized application using the wagmi library. 4 | 5 | Install packages: 6 | 7 | `npm install` 8 | 9 | Run app: 10 | 11 | `npm start` 12 | 13 | Open app: 14 | 15 | http://localhost:3000/ 16 | -------------------------------------------------------------------------------- /examples/rainbowkit-demo/config-overrides.js: -------------------------------------------------------------------------------- 1 | const webpack = require("webpack"); 2 | 3 | module.exports = function override(config, env) { 4 | config.resolve.fallback = { 5 | ...config.resolve.fallback, 6 | util: require.resolve("util/"), 7 | }; 8 | config.resolve.extensions = [ 9 | ...config.resolve.extensions, 10 | ".ts", 11 | ".tsx", 12 | ".js", 13 | ]; 14 | config.plugins = [ 15 | ...config.plugins, 16 | new webpack.DefinePlugin({ 17 | "process.env.INFURA_ID": JSON.stringify(process.env.INFURA_ID), 18 | }), 19 | ]; 20 | 21 | return config; 22 | }; 23 | -------------------------------------------------------------------------------- /examples/rainbowkit-demo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wagmi-demo", 3 | "version": "1.0.0", 4 | "description": "", 5 | "private": true, 6 | "keywords": [], 7 | "main": "src/index.js", 8 | "dependencies": { 9 | "@chakra-ui/icons": "^2.0.2", 10 | "@chakra-ui/react": "^2.2.1", 11 | "@coinbase/wallet-sdk": "^3.2.0", 12 | "@rainbow-me/rainbowkit": "^0.8.1", 13 | "ethers": "^5.7.2", 14 | "react": "^18.1.0", 15 | "react-dom": "^18.1.0", 16 | "util": "^0.12.4", 17 | "wagmi": "^0.9.6" 18 | }, 19 | "devDependencies": { 20 | "react-app-rewired": "^2.2.1" 21 | }, 22 | "overrides": { 23 | "react-scripts": { 24 | "nth-check": "^2.0.1" 25 | } 26 | }, 27 | "scripts": { 28 | "start": "react-app-rewired start", 29 | "build": "react-app-rewired build", 30 | "test": "react-app-rewired test", 31 | "eject": "react-app-rewired eject" 32 | }, 33 | "eslintConfig": { 34 | "extends": [ 35 | "react-app", 36 | "react-app/jest" 37 | ] 38 | }, 39 | "browserslist": { 40 | "production": [ 41 | ">0.2%", 42 | "not dead", 43 | "not op_mini all" 44 | ], 45 | "development": [ 46 | "last 1 chrome version", 47 | "last 1 firefox version", 48 | "last 1 safari version" 49 | ] 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /examples/rainbowkit-demo/public/cbw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cb-jake/coinbase-wallet-sdk/3d52c09ffb2395fd51107ffc1862381d1e4e641c/examples/rainbowkit-demo/public/cbw.png -------------------------------------------------------------------------------- /examples/rainbowkit-demo/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cb-jake/coinbase-wallet-sdk/3d52c09ffb2395fd51107ffc1862381d1e4e641c/examples/rainbowkit-demo/public/favicon.ico -------------------------------------------------------------------------------- /examples/rainbowkit-demo/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 14 | 15 | 16 | 25 | Coinbase Wallet: Rainbowkit Demo 26 | 27 | 28 | 29 | 32 |
33 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /examples/rainbowkit-demo/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "wagmi Demo", 3 | "name": "Coinbase Wallet with wagmi Demo", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "512x512 192x192 64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": ".", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } -------------------------------------------------------------------------------- /examples/rainbowkit-demo/public/mm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cb-jake/coinbase-wallet-sdk/3d52c09ffb2395fd51107ffc1862381d1e4e641c/examples/rainbowkit-demo/public/mm.png -------------------------------------------------------------------------------- /examples/rainbowkit-demo/public/wc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cb-jake/coinbase-wallet-sdk/3d52c09ffb2395fd51107ffc1862381d1e4e641c/examples/rainbowkit-demo/public/wc.png -------------------------------------------------------------------------------- /examples/rainbowkit-demo/src/index.js: -------------------------------------------------------------------------------- 1 | import { StrictMode } from "react"; 2 | import { createRoot } from 'react-dom/client'; 3 | import { ChakraProvider } from "@chakra-ui/react"; 4 | import { 5 | getDefaultWallets, 6 | RainbowKitProvider, 7 | } from '@rainbow-me/rainbowkit'; 8 | import { 9 | WagmiConfig, 10 | createClient, 11 | configureChains 12 | } from "wagmi"; 13 | import { arbitrum, avalanche, bsc, fantom, goerli, mainnet, optimism, polygon, sepolia } from 'wagmi/chains'; 14 | import { infuraProvider } from 'wagmi/providers/infura' 15 | import { publicProvider } from 'wagmi/providers/public' 16 | import App from "./App"; 17 | 18 | // API key for Ethereum node 19 | // Two popular services are Infura (infura.io) and Alchemy (alchemy.com) 20 | const infuraId = process.env.INFURA_ID; 21 | 22 | // Configure chains for connectors to support 23 | const { chains, provider } = configureChains( 24 | [ arbitrum, avalanche, bsc, fantom, goerli, mainnet, optimism, polygon, sepolia ], 25 | [ 26 | infuraProvider({ infuraId }), 27 | publicProvider(), 28 | ] 29 | ); 30 | 31 | export const { connectors } = getDefaultWallets({ 32 | appName: 'RainbowKit Demo', 33 | chains 34 | }); 35 | 36 | const client = createClient({ 37 | autoConnect: true, 38 | connectors, 39 | provider 40 | }); 41 | 42 | const rootElement = document.getElementById("root"); 43 | const root = createRoot(rootElement); 44 | root.render( 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | ); 55 | -------------------------------------------------------------------------------- /examples/rainbowkit-demo/src/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: Inter; 3 | text-align: center; 4 | } 5 | -------------------------------------------------------------------------------- /examples/rainbowkit-demo/src/utils.js: -------------------------------------------------------------------------------- 1 | export const truncateAddress = (address) => { 2 | if (!address) return "No Account"; 3 | const match = address.match( 4 | /^(0x[a-zA-Z0-9]{2})[a-zA-Z0-9]+([a-zA-Z0-9]{2})$/ 5 | ); 6 | if (!match) return address; 7 | return `${match[1]}…${match[2]}`; 8 | }; 9 | -------------------------------------------------------------------------------- /examples/verify-webapp/README.md: -------------------------------------------------------------------------------- 1 | # Demo: Verify Webapp 2 | 3 | Demo app to verify user's wallet address from third-party app. For Android & iOS, but using web for demo app. 4 | 5 | Install packages: 6 | 7 | ```yarn build:app``` 8 | 9 | ```yarn build:wallet``` 10 | 11 | Run apps: 12 | 13 | ```yarn dev``` 14 | 15 | Open app: 16 | 17 | http://localhost:3001/ 18 | 19 | ![demo](demo.gif) -------------------------------------------------------------------------------- /examples/verify-webapp/a-profile-app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Verify Demo App 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /examples/verify-webapp/a-profile-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "a-profile-app", 3 | "description": "A simple profile app that connects to the wallet and shows the user's profile", 4 | "private": true, 5 | "version": "0.1.0", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "tsc && vite build", 9 | "preview": "vite preview" 10 | }, 11 | "dependencies": { 12 | "react": "^18.0.0", 13 | "react-dom": "^18.0.0" 14 | }, 15 | "devDependencies": { 16 | "@types/react": "^18.0.0", 17 | "@types/react-dom": "^18.0.0", 18 | "@vitejs/plugin-react": "^1.3.0", 19 | "autoprefixer": "^10.4.7", 20 | "ethers": "^5.6.8", 21 | "postcss": "^8.4.14", 22 | "tailwindcss": "^3.0.24", 23 | "typescript": "^4.6.3", 24 | "vite": "^2.9.9" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /examples/verify-webapp/a-profile-app/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /examples/verify-webapp/a-profile-app/src/App.css: -------------------------------------------------------------------------------- 1 | .card-container { 2 | width: 768px; 3 | } 4 | 5 | .card { 6 | background-image: linear-gradient(135deg, #81ffef 10%, #f067b4 100%); 7 | } 8 | 9 | .card-profile { 10 | position: absolute; 11 | top: -80px; 12 | left: 124px; 13 | } 14 | 15 | .card-profile-pic { 16 | width: 150px; 17 | height: 150px; 18 | border: 2px solid white; 19 | } 20 | -------------------------------------------------------------------------------- /examples/verify-webapp/a-profile-app/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cb-jake/coinbase-wallet-sdk/3d52c09ffb2395fd51107ffc1862381d1e4e641c/examples/verify-webapp/a-profile-app/src/favicon.ico -------------------------------------------------------------------------------- /examples/verify-webapp/a-profile-app/src/index.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | body { 6 | margin: 0; 7 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", 8 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", 9 | sans-serif; 10 | -webkit-font-smoothing: antialiased; 11 | -moz-osx-font-smoothing: grayscale; 12 | background-image: linear-gradient(135deg, #09203f 10%, #537895 100%); 13 | height: 100vh; 14 | overflow: hidden; 15 | border-top: 6px solid #f067b4; 16 | } 17 | 18 | code { 19 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", 20 | monospace; 21 | } 22 | -------------------------------------------------------------------------------- /examples/verify-webapp/a-profile-app/src/main.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom/client' 3 | import App from './App' 4 | import './index.css' 5 | 6 | ReactDOM.createRoot(document.getElementById('root')!).render( 7 | 8 | 9 | 10 | ) 11 | -------------------------------------------------------------------------------- /examples/verify-webapp/a-profile-app/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /examples/verify-webapp/a-profile-app/tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | content: ["./src/**/*.{html,js,tsx}"], 3 | theme: { 4 | extend: {}, 5 | }, 6 | plugins: [], 7 | } 8 | -------------------------------------------------------------------------------- /examples/verify-webapp/a-profile-app/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": "Node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "noEmit": true, 17 | "jsx": "react-jsx" 18 | }, 19 | "include": ["src"], 20 | "references": [{ "path": "./tsconfig.node.json" }] 21 | } 22 | -------------------------------------------------------------------------------- /examples/verify-webapp/a-profile-app/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "module": "esnext", 5 | "moduleResolution": "node" 6 | }, 7 | "include": ["vite.config.ts"] 8 | } 9 | -------------------------------------------------------------------------------- /examples/verify-webapp/a-profile-app/vite.config.ts: -------------------------------------------------------------------------------- 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 | server: { 8 | port: 3001, 9 | } 10 | }) 11 | -------------------------------------------------------------------------------- /examples/verify-webapp/a-wallet/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cb-jake/coinbase-wallet-sdk/3d52c09ffb2395fd51107ffc1862381d1e4e641c/examples/verify-webapp/a-wallet/favicon.ico -------------------------------------------------------------------------------- /examples/verify-webapp/a-wallet/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Verify Demo App 8 | 9 | 10 |
11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /examples/verify-webapp/a-wallet/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "a-wallet", 3 | "description": "An interstitial page to connect to the wallet through the dApp", 4 | "private": true, 5 | "version": "0.1.0", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "tsc && vite build", 9 | "preview": "vite preview" 10 | }, 11 | "devDependencies": { 12 | "ethers": "^5.6.8", 13 | "typescript": "^4.6.3", 14 | "vite": "^2.9.9" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /examples/verify-webapp/a-wallet/src/main.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from "ethers"; 2 | import "./style.css"; 3 | import { fetchToken } from "./util"; 4 | 5 | const app = document.querySelector("#app")!; 6 | app.innerHTML = `Connecting...`; 7 | 8 | const IOS_UNIVERSAL_LINK = "http://localhost:3001"; // Using web schema for demo purposes 9 | const ANDROID_DEEP_LINK = "pineapple://"; 10 | export const APP_NAME = "Pineapple Inc."; 11 | 12 | const token = new URLSearchParams(window.location.search).get("token"); 13 | 14 | declare global { 15 | interface Window { 16 | ethereum?: any; 17 | web3?: any; 18 | } 19 | } 20 | 21 | function isAndroid() { 22 | const isAndroidUserAgent = /(android)/i.test(navigator.userAgent); 23 | return isAndroidUserAgent; 24 | } 25 | 26 | async function signEthereum() { 27 | console.info("signEthereum"); 28 | 29 | try { 30 | if (!token) { 31 | throw new Error("Not a valid user"); 32 | } 33 | 34 | const provider = new ethers.providers.Web3Provider(window.ethereum); 35 | const signer = provider.getSigner(); 36 | const address = await signer.getAddress(); 37 | 38 | const message = await fetchToken(token); 39 | const signature = await signer.signMessage(message); 40 | 41 | app.innerHTML = `Connected! Redirecting back...`; 42 | 43 | // Redirect URL with callback info 44 | if (isAndroid()) { 45 | // Handle if Android 46 | window.location.href = `${ANDROID_DEEP_LINK}/?address=${address}&sign=${signature}&message=${message}`; 47 | } else { 48 | // Handle if iOS 49 | window.location.href = `${IOS_UNIVERSAL_LINK}/?address=${address}&sign=${signature}&message=${message}`; 50 | } 51 | } catch (err) { 52 | // Display error state 53 | console.error(err); 54 | 55 | if (err instanceof Error) { 56 | app.innerHTML = `${err.message}. Redirecting back ...`; 57 | } 58 | 59 | // Go back to app 60 | setTimeout(() => window.history.back(), 800); 61 | } 62 | } 63 | 64 | async function handleEthereum() { 65 | console.info("handleEthereum"); 66 | try { 67 | await window.ethereum.enable(); 68 | console.info("enabled"); 69 | 70 | setTimeout(signEthereum, 500); 71 | } catch (err) { 72 | console.error(err); 73 | } 74 | } 75 | 76 | function initListener() { 77 | console.info("initListener"); 78 | window.addEventListener("ethereum#intialized", handleEthereum, { 79 | once: true, 80 | }); 81 | 82 | setTimeout(handleEthereum, 5000); // 5 seconds 83 | } 84 | 85 | window.ethereum ? handleEthereum() : initListener(); 86 | -------------------------------------------------------------------------------- /examples/verify-webapp/a-wallet/src/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | height: 100vh; 3 | font-family: Avenir, Helvetica, Arial, sans-serif; 4 | -webkit-font-smoothing: antialiased; 5 | -moz-osx-font-smoothing: grayscale; 6 | background-image: linear-gradient(135deg, #81ffef 10%, #f067b4 100%); 7 | background-size: 400% 400%; 8 | animation: gradient 15s ease infinite; 9 | } 10 | 11 | #app { 12 | text-align: center; 13 | font-size: larger; 14 | color: #2c3e50; 15 | margin-top: 60px; 16 | } 17 | 18 | @keyframes gradient { 19 | 0% { 20 | background-position: 0% 50%; 21 | } 22 | 50% { 23 | background-position: 100% 50%; 24 | } 25 | 100% { 26 | background-position: 0% 50%; 27 | } 28 | } 29 | 30 | .loader { 31 | display: block; 32 | font-size: 3em; 33 | margin: 0 0.5em; 34 | position: relative; 35 | height: 0.5em; 36 | width: 1em; 37 | margin: 25px auto; 38 | } 39 | 40 | .loader:before, 41 | .loader:after { 42 | content: ""; 43 | display: block; 44 | height: 0.5em; 45 | position: absolute; 46 | width: 0.5em; 47 | border-radius: 50%; 48 | } 49 | 50 | .loader:before { 51 | animation: load-before 2s ease-in-out infinite, zoom-before 0.66s ease-in-out infinite; 52 | background: #f067b4; 53 | } 54 | 55 | .loader:after { 56 | animation: load-after 2s ease-in-out infinite, zoom-after 0.66s ease-in-out infinite; 57 | background: #81ffef; 58 | } 59 | 60 | @keyframes load-before { 61 | 0% { 62 | left: 0; 63 | transform: scale(1.1); 64 | } 65 | 66 | 50% { 67 | left: 100%; 68 | transform: scale(1); 69 | } 70 | 71 | 100% { 72 | left: 0; 73 | transform: scale(1.1); 74 | } 75 | } 76 | 77 | @keyframes load-after { 78 | 0% { 79 | left: 100%; 80 | transform: scale(1.1); 81 | } 82 | 83 | 50% { 84 | left: 0; 85 | transform: scale(1); 86 | } 87 | 88 | 100% { 89 | left: 100%; 90 | transform: scale(1.1); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /examples/verify-webapp/a-wallet/src/util.ts: -------------------------------------------------------------------------------- 1 | import { APP_NAME } from "./main"; 2 | 3 | export const fetchToken = (token: string): Promise => { 4 | return new Promise((res, rej) => { 5 | if (token === "test_app_token") { 6 | res(`Verify by signing below to authenticate ${APP_NAME}.`); 7 | } else { 8 | rej("Error verifying user"); 9 | } 10 | }); 11 | }; 12 | -------------------------------------------------------------------------------- /examples/verify-webapp/a-wallet/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /examples/verify-webapp/a-wallet/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "useDefineForClassFields": true, 5 | "module": "ESNext", 6 | "lib": ["ESNext", "DOM"], 7 | "moduleResolution": "Node", 8 | "strict": true, 9 | "sourceMap": true, 10 | "resolveJsonModule": true, 11 | "isolatedModules": true, 12 | "esModuleInterop": true, 13 | "noEmit": true, 14 | "noUnusedLocals": true, 15 | "noUnusedParameters": true, 16 | "noImplicitReturns": true, 17 | "skipLibCheck": true 18 | }, 19 | "include": ["src"] 20 | } 21 | -------------------------------------------------------------------------------- /examples/verify-webapp/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cb-jake/coinbase-wallet-sdk/3d52c09ffb2395fd51107ffc1862381d1e4e641c/examples/verify-webapp/demo.gif -------------------------------------------------------------------------------- /examples/verify-webapp/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "verify-webapp", 3 | "version": "0.1.0", 4 | "description": "Sample verify web app", 5 | "scripts": { 6 | "dev:app": "cd ./a-profile-app && yarn dev", 7 | "dev:wallet": "cd ./a-wallet && yarn dev", 8 | "build:app": "cd ./a-profile-app && yarn", 9 | "build:wallet": "cd ./a-wallet && yarn", 10 | "dev": "concurrently --kill-others \"yarn dev:app\" \"yarn dev:wallet\"" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/coinbase/coinbase-wallet-sdk.git" 15 | }, 16 | "keywords": [ 17 | "verification", 18 | "ethers", 19 | "dapp", 20 | "ethereum" 21 | ], 22 | "author": "Coinbase Engineering", 23 | "license": "ISC", 24 | "bugs": { 25 | "url": "https://github.com/coinbase/coinbase-wallet-sdk/issues" 26 | }, 27 | "homepage": "https://github.com/coinbase/coinbase-wallet-sdk/examples/verify-webapp", 28 | "devDependencies": { 29 | "concurrently": "^7.2.1" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /examples/wagmi-demo/.env: -------------------------------------------------------------------------------- 1 | GENERATE_SOURCEMAP=false 2 | INFURA_UD= -------------------------------------------------------------------------------- /examples/wagmi-demo/.gitignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | /node_modules 3 | /.pnp 4 | .pnp.js 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | -------------------------------------------------------------------------------- /examples/wagmi-demo/README.md: -------------------------------------------------------------------------------- 1 | # Coinbase Wallet: Integrate with wagmi 2 | 3 | This sample application demonstrates how to integrate Coinbase Wallet into a decentralized application using the wagmi library. 4 | 5 | Install packages: 6 | 7 | `npm install` 8 | 9 | Run app: 10 | 11 | `npm start` 12 | 13 | Open app: 14 | 15 | http://localhost:3000/ 16 | -------------------------------------------------------------------------------- /examples/wagmi-demo/config-overrides.js: -------------------------------------------------------------------------------- 1 | const webpack = require("webpack"); 2 | 3 | module.exports = function override(config, env) { 4 | config.resolve.fallback = { 5 | ...config.resolve.fallback, 6 | util: require.resolve("util/"), 7 | }; 8 | config.resolve.extensions = [ 9 | ...config.resolve.extensions, 10 | ".ts", 11 | ".tsx", 12 | ".js", 13 | ]; 14 | config.plugins = [ 15 | ...config.plugins, 16 | new webpack.DefinePlugin({ 17 | "process.env.INFURA_ID": JSON.stringify(process.env.INFURA_ID), 18 | }), 19 | ]; 20 | 21 | return config; 22 | }; 23 | -------------------------------------------------------------------------------- /examples/wagmi-demo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wagmi-demo", 3 | "version": "1.0.0", 4 | "description": "", 5 | "private": true, 6 | "keywords": [], 7 | "main": "src/index.js", 8 | "dependencies": { 9 | "@chakra-ui/icons": "^2.0.2", 10 | "@chakra-ui/react": "^2.2.1", 11 | "@coinbase/wallet-sdk": "^3.2.0", 12 | "ethers": "^5.6.8", 13 | "react": "^18.1.0", 14 | "react-dom": "^18.1.0", 15 | "util": "^0.12.4", 16 | "wagmi": "^0.4.10" 17 | }, 18 | "devDependencies": { 19 | "react-app-rewired": "^2.2.1" 20 | }, 21 | "overrides": { 22 | "react-scripts": { 23 | "nth-check": "^2.0.1" 24 | } 25 | }, 26 | "scripts": { 27 | "start": "react-app-rewired start", 28 | "build": "react-app-rewired build", 29 | "test": "react-app-rewired test", 30 | "eject": "react-app-rewired eject" 31 | }, 32 | "eslintConfig": { 33 | "extends": [ 34 | "react-app", 35 | "react-app/jest" 36 | ] 37 | }, 38 | "browserslist": { 39 | "production": [ 40 | ">0.2%", 41 | "not dead", 42 | "not op_mini all" 43 | ], 44 | "development": [ 45 | "last 1 chrome version", 46 | "last 1 firefox version", 47 | "last 1 safari version" 48 | ] 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /examples/wagmi-demo/public/cbw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cb-jake/coinbase-wallet-sdk/3d52c09ffb2395fd51107ffc1862381d1e4e641c/examples/wagmi-demo/public/cbw.png -------------------------------------------------------------------------------- /examples/wagmi-demo/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cb-jake/coinbase-wallet-sdk/3d52c09ffb2395fd51107ffc1862381d1e4e641c/examples/wagmi-demo/public/favicon.ico -------------------------------------------------------------------------------- /examples/wagmi-demo/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 14 | 15 | 16 | 25 | Coinbase Wallet: wagmi Demo 26 | 27 | 28 | 29 | 30 |
31 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /examples/wagmi-demo/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "wagmi Demo", 3 | "name": "Coinbase Wallet with wagmi Demo", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "512x512 192x192 64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": ".", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /examples/wagmi-demo/public/mm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cb-jake/coinbase-wallet-sdk/3d52c09ffb2395fd51107ffc1862381d1e4e641c/examples/wagmi-demo/public/mm.png -------------------------------------------------------------------------------- /examples/wagmi-demo/public/wc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cb-jake/coinbase-wallet-sdk/3d52c09ffb2395fd51107ffc1862381d1e4e641c/examples/wagmi-demo/public/wc.png -------------------------------------------------------------------------------- /examples/wagmi-demo/src/Modal.js: -------------------------------------------------------------------------------- 1 | import { 2 | Button, 3 | HStack, 4 | Image, 5 | Modal, 6 | ModalOverlay, 7 | ModalContent, 8 | ModalHeader, 9 | ModalBody, 10 | ModalCloseButton, 11 | Text, 12 | VStack, 13 | } from "@chakra-ui/react"; 14 | import { useConnect } from "wagmi"; 15 | 16 | export default function SelectWalletModal({ isOpen, closeModal }) { 17 | const { connect, connectors } = useConnect(); 18 | 19 | return ( 20 | 21 | 22 | 23 | Select Wallet 24 | 29 | 30 | 31 | 50 | 69 | 88 | 89 | 90 | 91 | 92 | ); 93 | } 94 | -------------------------------------------------------------------------------- /examples/wagmi-demo/src/connectors.js: -------------------------------------------------------------------------------- 1 | import { configureChains, defaultChains } from "wagmi"; 2 | 3 | import { infuraProvider } from "wagmi/providers/infura"; 4 | import { publicProvider } from "wagmi/providers/public"; 5 | 6 | import { CoinbaseWalletConnector } from "wagmi/connectors/coinbaseWallet"; 7 | import { MetaMaskConnector } from "wagmi/connectors/metaMask"; 8 | import { WalletConnectConnector } from "wagmi/connectors/walletConnect"; 9 | 10 | // API key for Ethereum node 11 | // Two popular services are Infura (infura.io) and Alchemy (alchemy.com) 12 | const infuraId = process.env.INFURA_ID; 13 | 14 | // Configure chains for connectors to support 15 | const { chains } = configureChains(defaultChains, [ 16 | infuraProvider({ infuraId }), 17 | publicProvider(), 18 | ]); 19 | 20 | // Set up connectors 21 | export const connectors = [ 22 | new CoinbaseWalletConnector({ 23 | chains, 24 | options: { 25 | appName: "wagmi demo", 26 | }, 27 | }), 28 | new WalletConnectConnector({ 29 | chains, 30 | options: { 31 | infuraId, 32 | qrcode: true, 33 | }, 34 | }), 35 | new MetaMaskConnector({ 36 | chains, 37 | }), 38 | ]; 39 | -------------------------------------------------------------------------------- /examples/wagmi-demo/src/index.js: -------------------------------------------------------------------------------- 1 | import { StrictMode } from "react"; 2 | import { createRoot } from "react-dom/client"; 3 | import { ChakraProvider } from "@chakra-ui/react"; 4 | import { WagmiConfig, createClient } from "wagmi"; 5 | import { connectors } from "./connectors"; 6 | import App from "./App"; 7 | 8 | const rootElement = document.getElementById("root"); 9 | const root = createRoot(rootElement); 10 | const client = createClient({ 11 | autoConnect: true, 12 | connectors, 13 | }); 14 | root.render( 15 | 16 | 17 | 18 | 19 | 20 | 21 | , 22 | ); 23 | -------------------------------------------------------------------------------- /examples/wagmi-demo/src/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: Inter; 3 | text-align: center; 4 | } 5 | -------------------------------------------------------------------------------- /examples/wagmi-demo/src/utils.js: -------------------------------------------------------------------------------- 1 | export const truncateAddress = address => { 2 | if (!address) return "No Account"; 3 | const match = address.match( 4 | /^(0x[a-zA-Z0-9]{2})[a-zA-Z0-9]+([a-zA-Z0-9]{2})$/, 5 | ); 6 | if (!match) return address; 7 | return `${match[1]}…${match[2]}`; 8 | }; 9 | -------------------------------------------------------------------------------- /examples/web3-onboard-demo/.env: -------------------------------------------------------------------------------- 1 | GENERATE_SOURCEMAP=false 2 | INFURA_ID= -------------------------------------------------------------------------------- /examples/web3-onboard-demo/.gitignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | /node_modules 3 | /.pnp 4 | .pnp.js 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | -------------------------------------------------------------------------------- /examples/web3-onboard-demo/README.md: -------------------------------------------------------------------------------- 1 | # Coinbase Wallet: Integrate with Web3-Onboard 2 | 3 | This sample application demonstrates how to integrate Coinbase Wallet into a decentralized application using the Web3-Onboard library. 4 | 5 | Install packages: 6 | 7 | `npm install` 8 | 9 | Run app: 10 | 11 | `npm start` 12 | 13 | Open app: 14 | 15 | http://localhost:3000/ 16 | -------------------------------------------------------------------------------- /examples/web3-onboard-demo/config-overrides.js: -------------------------------------------------------------------------------- 1 | const webpack = require("webpack"); 2 | 3 | module.exports = function override(config, env) { 4 | config.resolve.fallback = { 5 | ...config.resolve.fallback, 6 | assert: require.resolve("assert/"), 7 | stream: require.resolve("stream-browserify"), 8 | util: require.resolve("util/"), 9 | }; 10 | config.resolve.extensions = [ 11 | ...config.resolve.extensions, 12 | ".ts", 13 | ".tsx", 14 | ".js", 15 | ]; 16 | config.plugins = [ 17 | ...config.plugins, 18 | new webpack.DefinePlugin({ 19 | "process.env.INFURA_ID": JSON.stringify(process.env.INFURA_ID), 20 | }), 21 | ]; 22 | 23 | return config; 24 | }; 25 | -------------------------------------------------------------------------------- /examples/web3-onboard-demo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "web3-onboard-demo", 3 | "version": "1.0.0", 4 | "description": "", 5 | "private": true, 6 | "keywords": [], 7 | "main": "src/index.js", 8 | "dependencies": { 9 | "@chakra-ui/icons": "^2.0.2", 10 | "@chakra-ui/react": "^2.2.1", 11 | "@coinbase/wallet-sdk": "^3.2.0", 12 | "@walletconnect/web3-provider": "^1.7.8", 13 | "@web3-onboard/coinbase": "^2.0.3", 14 | "@web3-onboard/core": "^2.2.10", 15 | "@web3-onboard/injected-wallets": "^2.0.7", 16 | "@web3-onboard/walletconnect": "^2.0.1", 17 | "assert": "^2.0.0", 18 | "ethers": "^5.6.8", 19 | "react": "^18.1.0", 20 | "react-dom": "^18.1.0", 21 | "stream-browserify": "^3.0.0", 22 | "util": "^0.12.4", 23 | "web3modal": "^1.9.7" 24 | }, 25 | "devDependencies": { 26 | "react-app-rewired": "^2.2.1" 27 | }, 28 | "overrides": { 29 | "react-scripts": { 30 | "nth-check": "^2.0.1" 31 | } 32 | }, 33 | "scripts": { 34 | "start": "react-app-rewired start", 35 | "build": "react-app-rewired build", 36 | "test": "react-app-rewired test", 37 | "eject": "react-app-rewired eject" 38 | }, 39 | "eslintConfig": { 40 | "extends": [ 41 | "react-app", 42 | "react-app/jest" 43 | ] 44 | }, 45 | "browserslist": { 46 | "production": [ 47 | ">0.2%", 48 | "not dead", 49 | "not op_mini all" 50 | ], 51 | "development": [ 52 | "last 1 chrome version", 53 | "last 1 firefox version", 54 | "last 1 safari version" 55 | ] 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /examples/web3-onboard-demo/public/cbw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cb-jake/coinbase-wallet-sdk/3d52c09ffb2395fd51107ffc1862381d1e4e641c/examples/web3-onboard-demo/public/cbw.png -------------------------------------------------------------------------------- /examples/web3-onboard-demo/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cb-jake/coinbase-wallet-sdk/3d52c09ffb2395fd51107ffc1862381d1e4e641c/examples/web3-onboard-demo/public/favicon.ico -------------------------------------------------------------------------------- /examples/web3-onboard-demo/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 14 | 15 | 16 | 25 | Coinbase Wallet: Web3-Onboard Demo 26 | 27 | 28 | 29 | 30 |
31 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /examples/web3-onboard-demo/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Web3-Onboard Demo", 3 | "name": "Coinbase Wallet with Web3-Onboard Demo", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "512x512 192x192 64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": ".", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /examples/web3-onboard-demo/public/mm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cb-jake/coinbase-wallet-sdk/3d52c09ffb2395fd51107ffc1862381d1e4e641c/examples/web3-onboard-demo/public/mm.png -------------------------------------------------------------------------------- /examples/web3-onboard-demo/public/wc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cb-jake/coinbase-wallet-sdk/3d52c09ffb2395fd51107ffc1862381d1e4e641c/examples/web3-onboard-demo/public/wc.png -------------------------------------------------------------------------------- /examples/web3-onboard-demo/src/index.js: -------------------------------------------------------------------------------- 1 | import { StrictMode } from "react"; 2 | import { createRoot } from "react-dom/client"; 3 | import { ChakraProvider } from "@chakra-ui/react"; 4 | import App from "./App"; 5 | 6 | const rootElement = document.getElementById("root"); 7 | const root = createRoot(rootElement); 8 | root.render( 9 | 10 | 11 | 12 | 13 | , 14 | ); 15 | -------------------------------------------------------------------------------- /examples/web3-onboard-demo/src/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: Inter; 3 | text-align: center; 4 | } 5 | -------------------------------------------------------------------------------- /examples/web3-onboard-demo/src/utils.js: -------------------------------------------------------------------------------- 1 | export const truncateAddress = address => { 2 | if (!address) return "No Account"; 3 | const match = address.match( 4 | /^(0x[a-zA-Z0-9]{2})[a-zA-Z0-9]+([a-zA-Z0-9]{2})$/, 5 | ); 6 | if (!match) return address; 7 | return `${match[1]}…${match[2]}`; 8 | }; 9 | 10 | export const toHex = num => { 11 | const val = Number(num); 12 | return "0x" + val.toString(16); 13 | }; 14 | -------------------------------------------------------------------------------- /examples/web3-react-demo/.env: -------------------------------------------------------------------------------- 1 | GENERATE_SOURCEMAP=false 2 | INFURA_ID= 3 | -------------------------------------------------------------------------------- /examples/web3-react-demo/.gitignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | /node_modules 3 | /.pnp 4 | .pnp.js 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | -------------------------------------------------------------------------------- /examples/web3-react-demo/README.md: -------------------------------------------------------------------------------- 1 | # Coinbase Wallet: Integrate with web3-react 2 | 3 | This sample application demonstrates how to integrate Coinbase Wallet into a decentralized application using the web3-react library. 4 | 5 | Install packages: 6 | 7 | `npm install` 8 | 9 | Run app: 10 | 11 | `npm start` 12 | 13 | Open app: 14 | 15 | http://localhost:3000/ 16 | -------------------------------------------------------------------------------- /examples/web3-react-demo/config-overrides.js: -------------------------------------------------------------------------------- 1 | const webpack = require("webpack"); 2 | 3 | module.exports = function override(config, env) { 4 | config.resolve.fallback = { 5 | ...config.resolve.fallback, 6 | buffer: require.resolve("buffer/"), 7 | util: require.resolve("util/"), 8 | }; 9 | config.resolve.extensions = [ 10 | ...config.resolve.extensions, 11 | ".ts", 12 | ".tsx", 13 | ".js", 14 | ]; 15 | config.plugins = [ 16 | ...config.plugins, 17 | new webpack.ProvidePlugin({ 18 | Buffer: ["buffer", "Buffer"], 19 | }), 20 | new webpack.DefinePlugin({ 21 | "process.env.INFURA_ID": JSON.stringify(process.env.INFURA_ID), 22 | }), 23 | ]; 24 | 25 | return config; 26 | }; 27 | -------------------------------------------------------------------------------- /examples/web3-react-demo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "web3-react-demo", 3 | "version": "1.0.0", 4 | "description": "", 5 | "private": true, 6 | "keywords": [], 7 | "main": "src/index.js", 8 | "dependencies": { 9 | "@chakra-ui/icons": "^2.0.2", 10 | "@chakra-ui/react": "^2.2.1", 11 | "@web3-react/core": "^6.1.9", 12 | "@web3-react/injected-connector": "^6.0.7", 13 | "@web3-react/walletconnect-connector": "^6.2.13", 14 | "@web3-react/walletlink-connector": "^6.2.14", 15 | "buffer": "^6.0.3", 16 | "ethers": "^5.6.8", 17 | "react": "^18.1.0", 18 | "react-dom": "^18.1.0", 19 | "util": "^0.12.4", 20 | "web-vitals": "^2.1.4" 21 | }, 22 | "devDependencies": { 23 | "react-app-rewired": "^2.2.1" 24 | }, 25 | "overrides": { 26 | "react-scripts": { 27 | "nth-check": "^2.0.1" 28 | } 29 | }, 30 | "scripts": { 31 | "start": "react-app-rewired start", 32 | "build": "react-app-rewired build", 33 | "test": "react-app-rewired test", 34 | "eject": "react-app-rewired eject" 35 | }, 36 | "eslintConfig": { 37 | "extends": [ 38 | "react-app", 39 | "react-app/jest" 40 | ] 41 | }, 42 | "browserslist": { 43 | "production": [ 44 | ">0.2%", 45 | "not dead", 46 | "not op_mini all" 47 | ], 48 | "development": [ 49 | "last 1 chrome version", 50 | "last 1 firefox version", 51 | "last 1 safari version" 52 | ] 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /examples/web3-react-demo/public/cbw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cb-jake/coinbase-wallet-sdk/3d52c09ffb2395fd51107ffc1862381d1e4e641c/examples/web3-react-demo/public/cbw.png -------------------------------------------------------------------------------- /examples/web3-react-demo/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cb-jake/coinbase-wallet-sdk/3d52c09ffb2395fd51107ffc1862381d1e4e641c/examples/web3-react-demo/public/favicon.ico -------------------------------------------------------------------------------- /examples/web3-react-demo/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 14 | 15 | 16 | 25 | Coinbase Wallet: web3-react Demo 26 | 27 | 28 | 29 | 30 |
31 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /examples/web3-react-demo/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "web3-react Demo", 3 | "name": "Coinbase Wallet with web3-react Demo", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "512x512 192x192 64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": ".", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /examples/web3-react-demo/public/mm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cb-jake/coinbase-wallet-sdk/3d52c09ffb2395fd51107ffc1862381d1e4e641c/examples/web3-react-demo/public/mm.png -------------------------------------------------------------------------------- /examples/web3-react-demo/public/wc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cb-jake/coinbase-wallet-sdk/3d52c09ffb2395fd51107ffc1862381d1e4e641c/examples/web3-react-demo/public/wc.png -------------------------------------------------------------------------------- /examples/web3-react-demo/src/Modal.js: -------------------------------------------------------------------------------- 1 | import { 2 | VStack, 3 | HStack, 4 | Modal, 5 | ModalOverlay, 6 | ModalContent, 7 | ModalHeader, 8 | ModalBody, 9 | ModalCloseButton, 10 | Button, 11 | Text, 12 | } from "@chakra-ui/react"; 13 | import { Image } from "@chakra-ui/react"; 14 | import { useWeb3React } from "@web3-react/core"; 15 | import { connectors } from "./connectors"; 16 | 17 | export default function SelectWalletModal({ isOpen, closeModal }) { 18 | const { activate } = useWeb3React(); 19 | 20 | const setProvider = type => { 21 | window.localStorage.setItem("provider", type); 22 | }; 23 | 24 | return ( 25 | 26 | 27 | 28 | Select Wallet 29 | 34 | 35 | 36 | 56 | 76 | 96 | 97 | 98 | 99 | 100 | ); 101 | } 102 | -------------------------------------------------------------------------------- /examples/web3-react-demo/src/connectors.js: -------------------------------------------------------------------------------- 1 | import { InjectedConnector } from "@web3-react/injected-connector"; 2 | import { WalletConnectConnector } from "@web3-react/walletconnect-connector"; 3 | import { WalletLinkConnector } from "@web3-react/walletlink-connector"; 4 | 5 | const injected = new InjectedConnector({ 6 | supportedChainIds: [1, 5, 11155111], 7 | }); 8 | 9 | const walletconnect = new WalletConnectConnector({ 10 | rpcUrl: `https://mainnet.infura.io/v3/${process.env.INFURA_ID}`, 11 | bridge: "https://bridge.walletconnect.org", 12 | qrcode: true, 13 | }); 14 | 15 | const walletlink = new WalletLinkConnector({ 16 | url: `https://mainnet.infura.io/v3/${process.env.INFURA_ID}`, 17 | appName: "web3-react-demo", 18 | }); 19 | 20 | export const connectors = { 21 | injected: injected, 22 | walletConnect: walletconnect, 23 | coinbaseWallet: walletlink, 24 | }; 25 | -------------------------------------------------------------------------------- /examples/web3-react-demo/src/index.js: -------------------------------------------------------------------------------- 1 | import { StrictMode } from "react"; 2 | import { createRoot } from "react-dom/client"; 3 | import { ChakraProvider } from "@chakra-ui/react"; 4 | import { Web3ReactProvider } from "@web3-react/core"; 5 | import { ethers } from "ethers"; 6 | 7 | import App from "./App"; 8 | 9 | const getLibrary = provider => { 10 | const library = new ethers.providers.Web3Provider(provider); 11 | library.pollingInterval = 8000; // frequency provider is polling 12 | return library; 13 | }; 14 | 15 | const rootElement = document.getElementById("root"); 16 | const root = createRoot(rootElement); 17 | root.render( 18 | 19 | 20 | 21 | 22 | 23 | 24 | , 25 | ); 26 | -------------------------------------------------------------------------------- /examples/web3-react-demo/src/networks.js: -------------------------------------------------------------------------------- 1 | export const networkParams = { 2 | "0x63564c40": { 3 | chainId: "0x63564c40", 4 | rpcUrls: ["https://api.harmony.one"], 5 | chainName: "Harmony Mainnet", 6 | nativeCurrency: { name: "ONE", decimals: 18, symbol: "ONE" }, 7 | blockExplorerUrls: ["https://explorer.harmony.one"], 8 | iconUrls: ["https://harmonynews.one/wp-content/uploads/2019/11/slfdjs.png"], 9 | }, 10 | "0xa4ec": { 11 | chainId: "0xa4ec", 12 | rpcUrls: ["https://forno.celo.org"], 13 | chainName: "Celo Mainnet", 14 | nativeCurrency: { name: "CELO", decimals: 18, symbol: "CELO" }, 15 | blockExplorerUrl: ["https://explorer.celo.org"], 16 | iconUrls: [ 17 | "https://celo.org/images/marketplace-icons/icon-celo-CELO-color-f.svg", 18 | ], 19 | }, 20 | }; 21 | -------------------------------------------------------------------------------- /examples/web3-react-demo/src/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: Inter; 3 | text-align: center; 4 | } 5 | -------------------------------------------------------------------------------- /examples/web3-react-demo/src/utils.js: -------------------------------------------------------------------------------- 1 | export const truncateAddress = address => { 2 | if (!address) return "No Account"; 3 | const match = address.match( 4 | /^(0x[a-zA-Z0-9]{2})[a-zA-Z0-9]+([a-zA-Z0-9]{2})$/, 5 | ); 6 | if (!match) return address; 7 | return `${match[1]}…${match[2]}`; 8 | }; 9 | 10 | export const toHex = num => { 11 | const val = Number(num); 12 | return "0x" + val.toString(16); 13 | }; 14 | -------------------------------------------------------------------------------- /examples/web3modal-demo/.env: -------------------------------------------------------------------------------- 1 | GENERATE_SOURCEMAP=false 2 | INFURA_ID= 3 | -------------------------------------------------------------------------------- /examples/web3modal-demo/.gitignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | /node_modules 3 | /.pnp 4 | .pnp.js 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | -------------------------------------------------------------------------------- /examples/web3modal-demo/README.md: -------------------------------------------------------------------------------- 1 | # Coinbase Wallet: Integrate with Web3Modal 2 | 3 | This sample application demonstrates how to integrate Coinbase Wallet into a decentralized application using the Web3Modal library. 4 | 5 | Install packages: 6 | 7 | `npm install` 8 | 9 | Run app: 10 | 11 | `npm start` 12 | 13 | Open app: 14 | 15 | http://localhost:3000/ 16 | -------------------------------------------------------------------------------- /examples/web3modal-demo/config-overrides.js: -------------------------------------------------------------------------------- 1 | const webpack = require("webpack"); 2 | 3 | module.exports = function override(config, env) { 4 | config.resolve.fallback = { 5 | ...config.resolve.fallback, 6 | assert: require.resolve("assert/"), 7 | buffer: require.resolve("buffer/"), 8 | http: require.resolve("stream-http"), 9 | https: require.resolve("https-browserify"), 10 | os: require.resolve("os-browserify/browser"), 11 | stream: require.resolve("stream-browserify"), 12 | url: require.resolve("url/"), 13 | util: require.resolve("util/"), 14 | }; 15 | config.resolve.extensions = [ 16 | ...config.resolve.extensions, 17 | ".ts", 18 | ".tsx", 19 | ".js", 20 | ]; 21 | config.plugins = [ 22 | ...config.plugins, 23 | new webpack.ProvidePlugin({ 24 | Buffer: ["buffer", "Buffer"], 25 | }), 26 | new webpack.DefinePlugin({ 27 | "process.env.INFURA_ID": JSON.stringify(process.env.INFURA_ID), 28 | }), 29 | ]; 30 | 31 | return config; 32 | }; 33 | -------------------------------------------------------------------------------- /examples/web3modal-demo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "web3modal-demo", 3 | "version": "1.0.0", 4 | "description": "", 5 | "private": true, 6 | "keywords": [], 7 | "main": "src/index.js", 8 | "dependencies": { 9 | "@chakra-ui/icons": "^2.0.2", 10 | "@chakra-ui/react": "^2.2.1", 11 | "@coinbase/wallet-sdk": "^3.2.0", 12 | "@walletconnect/web3-provider": "^1.7.8", 13 | "assert": "^2.0.0", 14 | "buffer": "^6.0.3", 15 | "ethers": "^5.6.8", 16 | "https-browserify": "^1.0.0", 17 | "os-browserify": "^0.3.0", 18 | "react": "^18.1.0", 19 | "react-dom": "^18.1.0", 20 | "stream-browserify": "^3.0.0", 21 | "stream-http": "^3.2.0", 22 | "url": "^0.11.0", 23 | "util": "^0.12.4", 24 | "web3modal": "^1.9.7" 25 | }, 26 | "devDependencies": { 27 | "react-app-rewired": "^2.2.1" 28 | }, 29 | "overrides": { 30 | "react-scripts": { 31 | "nth-check": "^2.0.1" 32 | } 33 | }, 34 | "scripts": { 35 | "start": "react-app-rewired start", 36 | "build": "react-app-rewired build", 37 | "test": "react-app-rewired test", 38 | "eject": "react-app-rewired eject" 39 | }, 40 | "eslintConfig": { 41 | "extends": [ 42 | "react-app", 43 | "react-app/jest" 44 | ] 45 | }, 46 | "browserslist": { 47 | "production": [ 48 | ">0.2%", 49 | "not dead", 50 | "not op_mini all" 51 | ], 52 | "development": [ 53 | "last 1 chrome version", 54 | "last 1 firefox version", 55 | "last 1 safari version" 56 | ] 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /examples/web3modal-demo/public/cbw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cb-jake/coinbase-wallet-sdk/3d52c09ffb2395fd51107ffc1862381d1e4e641c/examples/web3modal-demo/public/cbw.png -------------------------------------------------------------------------------- /examples/web3modal-demo/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cb-jake/coinbase-wallet-sdk/3d52c09ffb2395fd51107ffc1862381d1e4e641c/examples/web3modal-demo/public/favicon.ico -------------------------------------------------------------------------------- /examples/web3modal-demo/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 14 | 15 | 16 | 25 | Coinbase Wallet: Web3Modal Demo 26 | 27 | 28 | 29 | 30 |
31 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /examples/web3modal-demo/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Web3Modal Demo", 3 | "name": "Coinbase Wallet with Web3Modal Demo", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "512x512 192x192 64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": ".", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /examples/web3modal-demo/public/mm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cb-jake/coinbase-wallet-sdk/3d52c09ffb2395fd51107ffc1862381d1e4e641c/examples/web3modal-demo/public/mm.png -------------------------------------------------------------------------------- /examples/web3modal-demo/public/wc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cb-jake/coinbase-wallet-sdk/3d52c09ffb2395fd51107ffc1862381d1e4e641c/examples/web3modal-demo/public/wc.png -------------------------------------------------------------------------------- /examples/web3modal-demo/src/index.js: -------------------------------------------------------------------------------- 1 | import { StrictMode } from "react"; 2 | import { createRoot } from "react-dom/client"; 3 | import { ChakraProvider } from "@chakra-ui/react"; 4 | import App from "./App"; 5 | 6 | const rootElement = document.getElementById("root"); 7 | const root = createRoot(rootElement); 8 | root.render( 9 | 10 | 11 | 12 | 13 | , 14 | ); 15 | -------------------------------------------------------------------------------- /examples/web3modal-demo/src/networks.js: -------------------------------------------------------------------------------- 1 | export const networkParams = { 2 | "0x63564c40": { 3 | chainId: "0x63564c40", 4 | rpcUrls: ["https://api.harmony.one"], 5 | chainName: "Harmony Mainnet", 6 | nativeCurrency: { name: "ONE", decimals: 18, symbol: "ONE" }, 7 | blockExplorerUrls: ["https://explorer.harmony.one"], 8 | iconUrls: ["https://harmonynews.one/wp-content/uploads/2019/11/slfdjs.png"], 9 | }, 10 | "0xa4ec": { 11 | chainId: "0xa4ec", 12 | rpcUrls: ["https://forno.celo.org"], 13 | chainName: "Celo Mainnet", 14 | nativeCurrency: { name: "CELO", decimals: 18, symbol: "CELO" }, 15 | blockExplorerUrl: ["https://explorer.celo.org"], 16 | iconUrls: [ 17 | "https://celo.org/images/marketplace-icons/icon-celo-CELO-color-f.svg", 18 | ], 19 | }, 20 | }; 21 | -------------------------------------------------------------------------------- /examples/web3modal-demo/src/providerOptions.js: -------------------------------------------------------------------------------- 1 | import WalletConnect from "@walletconnect/web3-provider"; 2 | import CoinbaseWalletSDK from "@coinbase/wallet-sdk"; 3 | 4 | export const providerOptions = { 5 | walletlink: { 6 | package: CoinbaseWalletSDK, // Required 7 | options: { 8 | appName: "Web3Modal Demo", // Required 9 | infuraId: process.env.INFURA_ID, // Required unless you provide a JSON RPC url; see `rpc` below 10 | }, 11 | }, 12 | walletconnect: { 13 | package: WalletConnect, // required 14 | options: { 15 | infuraId: process.env.INFURA_ID, // required 16 | }, 17 | }, 18 | }; 19 | -------------------------------------------------------------------------------- /examples/web3modal-demo/src/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: Inter; 3 | text-align: center; 4 | } 5 | -------------------------------------------------------------------------------- /examples/web3modal-demo/src/utils.js: -------------------------------------------------------------------------------- 1 | export const truncateAddress = address => { 2 | if (!address) return "No Account"; 3 | const match = address.match( 4 | /^(0x[a-zA-Z0-9]{2})[a-zA-Z0-9]+([a-zA-Z0-9]{2})$/, 5 | ); 6 | if (!match) return address; 7 | return `${match[1]}…${match[2]}`; 8 | }; 9 | 10 | export const toHex = num => { 11 | const val = Number(num); 12 | return "0x" + val.toString(16); 13 | }; 14 | -------------------------------------------------------------------------------- /packages/wallet-sdk/.eslintignore: -------------------------------------------------------------------------------- 1 | # specific files to ignore 2 | *.md 3 | .eslintrc.js 4 | .eslintignore 5 | webpack.*.js 6 | 7 | # folders to ignore 8 | build 9 | chrome 10 | dist 11 | node_modules 12 | -------------------------------------------------------------------------------- /packages/wallet-sdk/.gitignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | node_modules 3 | /.pnp 4 | .pnp.js 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | /dist 12 | 13 | # misc 14 | .DS_Store 15 | .env.local 16 | .env.development.local 17 | .env.test.local 18 | .env.production.local 19 | 20 | npm-debug.log* 21 | yarn-debug.log* 22 | yarn-error.log* 23 | 24 | # eslint cache 25 | .eslintcache 26 | 27 | .vscode 28 | .idea 29 | 30 | *-svg.ts 31 | *-css.ts 32 | 33 | examples/**/yarn.lock 34 | -------------------------------------------------------------------------------- /packages/wallet-sdk/.prettierignore: -------------------------------------------------------------------------------- 1 | package.json 2 | yarn.lock 3 | coverage/** 4 | **/build 5 | **/dist 6 | **/node_modules 7 | **/src/vendor-js 8 | **/*-css.ts 9 | **/*-svg.ts -------------------------------------------------------------------------------- /packages/wallet-sdk/.prettierrc.yml: -------------------------------------------------------------------------------- 1 | printWidth: 80 2 | tabWidth: 2 3 | useTabs: false 4 | semi: true 5 | singleQuote: false 6 | trailingComma: all 7 | bracketSpacing: true 8 | bracketSameLine: false 9 | arrowParens: avoid 10 | proseWrap: preserve 11 | -------------------------------------------------------------------------------- /packages/wallet-sdk/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018-2020 Coinbase, Inc. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /packages/wallet-sdk/__tests__/addressStorage.test.js: -------------------------------------------------------------------------------- 1 | const { ensureAddressString } = require("../build/npm/dist/util"); 2 | const { CoinbaseWalletSDK } = require("../build/npm/dist"); 3 | 4 | describe("address storage tests", () => { 5 | it("ensureAddressString returns lowercase string", () => { 6 | const input = "0xFadAFCE89EA2221fa33005640Acf2C923312F2b9"; 7 | const output = ensureAddressString(input); 8 | expect(output).toEqual("0xfadafce89ea2221fa33005640acf2c923312f2b9"); 9 | }); 10 | 11 | it("cannot mutate addresses via window.ethereum.enable()", done => { 12 | const coinbaseWalletSDK = new CoinbaseWalletSDK({ 13 | appName: "My Awesome DApp", 14 | appLogoUrl: "https://example.com/logo.png", 15 | }); 16 | 17 | const provider = coinbaseWalletSDK.makeWeb3Provider( 18 | "https://mainnet.infura.io/v3/INFURA_API_KEY", 19 | 1, 20 | ); 21 | 22 | provider._addresses = ["0xfadafce89ea2221fa33005640acf2c923312f2b9"]; 23 | void provider.enable().then(addresses => { 24 | addresses[0] = "0xFadAFCE89EA2221fa33005640Acf2C923312F2b9"; 25 | expect(provider._addresses[0]).toEqual( 26 | "0xfadafce89ea2221fa33005640acf2c923312f2b9", 27 | ); 28 | done(); 29 | }); 30 | }); 31 | 32 | it("cannot mutate addresses via window.ethereum.request eth_accounts", done => { 33 | const coinbaseWalletSDK = new CoinbaseWalletSDK({ 34 | appName: "My Awesome DApp", 35 | appLogoUrl: "https://example.com/logo.png", 36 | }); 37 | 38 | const provider = coinbaseWalletSDK.makeWeb3Provider( 39 | "https://mainnet.infura.io/v3/INFURA_API_KEY", 40 | 1, 41 | ); 42 | 43 | provider._addresses = ["0xfadafce89ea2221fa33005640acf2c923312f2b9"]; 44 | void provider.request({ method: "eth_accounts" }).then(addresses => { 45 | addresses[0] = "0xFadAFCE89EA2221fa33005640Acf2C923312F2b9"; 46 | expect(provider._addresses[0]).toEqual( 47 | "0xfadafce89ea2221fa33005640acf2c923312f2b9", 48 | ); 49 | done(); 50 | }); 51 | }); 52 | }); 53 | -------------------------------------------------------------------------------- /packages/wallet-sdk/__tests__/encryptDecrypt.test.js: -------------------------------------------------------------------------------- 1 | const { encrypt, decrypt } = require("../build/npm/dist/relay/aes256gcm"); 2 | const { randomBytesHex } = require("../build/npm/dist/util"); 3 | 4 | describe("encryption and decryption tests", () => { 5 | it("decrypted output matches original input", done => { 6 | void (async function () { 7 | const secret = randomBytesHex(32); 8 | const input = "coinbasewallet:2.0"; 9 | const cipherText = await encrypt(input, secret); 10 | 11 | decrypt(cipherText, secret).subscribe({ 12 | next: decryptedText => { 13 | expect(input).toEqual(decryptedText); 14 | done(); 15 | }, 16 | }); 17 | })(); 18 | }); 19 | 20 | it("decrypt data sample from client", done => { 21 | (function () { 22 | const cipherText = 23 | "06593325a922a928913b5c6ea26f848c4545bcea4e26c4f5ee745316ff22b2780aeccc565730514b2820a94b03f5f89fe7542a35bbdd87a1d52a4352f49482781113db09266c668696778e0a94bc9f866f1e92e7262fd0bb811838284cc64cbc4552b33e9c6fb2582cea4f49471d6d46a16a5c8ac83ee8483ed4dc01f1fde3bfd7a2f173715e0a8d09dd4907483f096a845bff698831ea277c1ca4223d3f6073174cb35119d0a795c1a9cb4f32ee1dcc254d8931"; 24 | const secret = 25 | "c356fe708ea7bbf7b1cc9ff9813c32772b6e0d16332da4c031ba9ea88be9b5ed"; 26 | const sampleDataResult = 27 | '{"type":"WEB3_RESPONSE","id":"791fe0ec3dc3de49","response":{"method":"requestEthereumAccounts","result":["0xdf0635793e91d4f8e7426dbd9ed08471186f428d"]}}'; 28 | decrypt(cipherText, secret).subscribe({ 29 | next: value => { 30 | expect(sampleDataResult).toEqual(value); 31 | done(); 32 | }, 33 | }); 34 | })(); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /packages/wallet-sdk/assets/coinbase_wallet_logo_kit.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cb-jake/coinbase-wallet-sdk/3d52c09ffb2395fd51107ffc1862381d1e4e641c/packages/wallet-sdk/assets/coinbase_wallet_logo_kit.zip -------------------------------------------------------------------------------- /packages/wallet-sdk/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | ["@babel/preset-env", { targets: { node: "current" } }], 4 | [ 5 | "@babel/preset-typescript", 6 | { 7 | jsxPragma: "h", 8 | jsxPragmaFrag: "Fragment", 9 | }, 10 | ], 11 | ], 12 | plugins: [ 13 | [ 14 | "@babel/plugin-transform-react-jsx", 15 | { 16 | pragma: "h", 17 | pragmaFrag: "Fragment", 18 | }, 19 | ], 20 | ["@babel/plugin-proposal-decorators", { legacy: true }], 21 | ], 22 | }; 23 | -------------------------------------------------------------------------------- /packages/wallet-sdk/compile-assets.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018-2022 Coinbase, Inc. 2 | // Licensed under the Apache License, version 2.0 3 | 4 | const fs = require("fs"); 5 | const glob = require("glob"); 6 | const sass = require("sass"); 7 | const { optimize } = require("svgo"); 8 | 9 | const COPYRIGHT = `// Copyright (c) 2018-2022 Coinbase, Inc. 10 | // Licensed under the Apache License, version 2.0`; 11 | 12 | async function main() { 13 | // compile SCSS 14 | const scssFiles = glob.sync(`${__dirname}/src/**/*.scss`); 15 | for (const filePath of scssFiles) { 16 | console.log(`Compiling ${filePath}...`); 17 | const css = sass 18 | .renderSync({ file: filePath, outputStyle: "compressed" }) 19 | .css.toString("utf8"); 20 | const ts = `${COPYRIGHT}\n\nexport default \`${css}\``; 21 | fs.writeFileSync(filePath.replace(/\.scss$/, "-css.ts"), ts, { 22 | mode: 0o644, 23 | }); 24 | } 25 | 26 | // compile SVG 27 | const svgFiles = glob.sync(`${__dirname}/src/**/*.svg`); 28 | for (const filePath of svgFiles) { 29 | console.log(`Compiling ${filePath}...`); 30 | const svg = fs.readFileSync(filePath, { encoding: "utf8" }); 31 | const { data } = optimize(svg, { 32 | path: filePath, 33 | datauri: "base64", 34 | // datauri inlining won't happen until min size has been reached per 35 | // https://github.com/svg/svgo/blob/b37d90e12a87312bba87a6c52780884e6e595e23/lib/svgo.js#L57-L68 36 | // so we enable multipass for that to happen 37 | multipass: true, 38 | }); 39 | const ts = `${COPYRIGHT}\n\nexport default \`${data}\``; 40 | fs.writeFileSync(filePath.replace(/\.svg$/, "-svg.ts"), ts, { 41 | mode: 0o644, 42 | }); 43 | } 44 | 45 | console.log("DONE!"); 46 | } 47 | 48 | main(); 49 | -------------------------------------------------------------------------------- /packages/wallet-sdk/jest.config.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * For a detailed explanation regarding each configuration property and type check, visit: 3 | * https://jestjs.io/docs/configuration 4 | */ 5 | 6 | export default { 7 | // Automatically clear mock calls, instances and results before every test 8 | clearMocks: true, 9 | 10 | // Indicates whether the coverage information should be collected while executing the test 11 | collectCoverage: true, 12 | 13 | // An array of glob patterns indicating a set of files for which coverage information should be collected 14 | collectCoverageFrom: [ 15 | "./src/util.ts", 16 | "./src/CoinbaseWalletSDK.ts", 17 | "./src/connection/RxWebSocket.ts", 18 | "./src/connection/WalletSDKConnection.ts", 19 | "./src/lib/ScopedLocalStorage.ts", 20 | "./src/provider/CoinbaseWalletProvider.ts", 21 | "./src/provider/SolanaProvider.ts", 22 | "./src/provider/FilterPolyfill.ts", 23 | "./src/provider/SubscriptionManager.ts", 24 | "./src/provider/WalletSDKUI.ts", 25 | "./src/relay/aes256gcm.ts", 26 | "./src/relay/Session.ts", 27 | "./src/relay/WalletSDKRelay.ts", 28 | "./src/relay/WalletSDKRelayEventManager.ts", 29 | "./src/components/**/*.tsx", 30 | ], 31 | // The directory where Jest should output its coverage files 32 | coverageDirectory: "coverage", 33 | 34 | // An array of regexp pattern strings used to skip coverage collection 35 | coveragePathIgnorePatterns: ["/node_modules/"], 36 | 37 | // A list of reporter names that Jest uses when writing coverage reports 38 | coverageReporters: ["json", "text", "text-summary", "lcov"], 39 | 40 | // TODO: Increase threshold as additional tests are added 41 | coverageThreshold: { 42 | global: { 43 | branches: 54, 44 | functions: 55, 45 | statements: 63, 46 | }, 47 | }, 48 | 49 | // An array of file extensions your modules use 50 | moduleFileExtensions: ["js", "jsx", "ts", "tsx", "json", "node"], 51 | 52 | // A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module 53 | moduleNameMapper: { 54 | "^src/(.*)$": "/src/$1", 55 | }, 56 | 57 | // A list of paths to directories that Jest should use to search for files in 58 | roots: ["/src"], 59 | 60 | // A list of paths to modules that run some code to configure or set up the testing framework before each test 61 | setupFilesAfterEnv: ["/jest.setup.ts"], 62 | 63 | // The test environment that will be used for testing 64 | testEnvironment: "jsdom", 65 | 66 | // The glob patterns Jest uses to detect test files 67 | testMatch: ["/src/**/*.test.[tj]s?(x)"], 68 | 69 | // A map from regular expressions to paths to transformers 70 | transform: { 71 | "^.+\\.(js|ts|tsx)$": ["babel-jest"], 72 | }, 73 | 74 | // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation 75 | testPathIgnorePatterns: ["/node_modules/", "/build/"], 76 | }; 77 | -------------------------------------------------------------------------------- /packages/wallet-sdk/jest.setup.ts: -------------------------------------------------------------------------------- 1 | import "@testing-library/jest-dom"; 2 | 3 | import { Crypto } from "@peculiar/webcrypto"; 4 | import { TextDecoder, TextEncoder } from "util"; 5 | 6 | global.crypto = new Crypto(); 7 | 8 | global.TextEncoder = TextEncoder; 9 | 10 | // @ts-expect-error Use util TextDecoder 11 | global.TextDecoder = TextDecoder; 12 | -------------------------------------------------------------------------------- /packages/wallet-sdk/karma.conf.js: -------------------------------------------------------------------------------- 1 | module.exports = function (config) { 2 | config.set({ 3 | plugins: ["karma-jasmine", "karma-chrome-launcher", "karma-browserify"], 4 | 5 | // base path that will be used to resolve all patterns (eg. files, exclude) 6 | basePath: "", 7 | 8 | frameworks: ["jasmine", "browserify"], 9 | 10 | // list of files / patterns to load in the browser 11 | files: ["__tests__/**/*.js"], 12 | 13 | preprocessors: { 14 | "__tests__/**/*.js": ["browserify"], 15 | }, 16 | 17 | browserify: { 18 | debug: true, 19 | }, 20 | }); 21 | }; 22 | -------------------------------------------------------------------------------- /packages/wallet-sdk/scripts/release.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | # COLORS 4 | REDBOLD='\033[1;31m' 5 | RED='\033[0;31m' 6 | PURPLE='\033[0;35m' 7 | TEAL='\033[0;36m' 8 | GREEN='\033[1;32m' 9 | 10 | gitMessage=$(git log --oneline -n 1) 11 | mainBranch="master" 12 | branch=$(git rev-parse --abbrev-ref HEAD) 13 | 14 | if [ $branch == $mainBranch ]; then 15 | echo -e "${PURPLE}Checking all branches are up-to-date..." 16 | echo -e "=================================================" 17 | echo -e " git fetch --all" 18 | git fetch --all 19 | echo -e " git pull" 20 | git pull 21 | echo -e "-------------------------------------------------" 22 | echo -e "${TEAL}Build production and publish..." 23 | echo "=================================================" 24 | echo -e "rm -rf ./node_modules" 25 | rm -rf ./node_modules 26 | echo -e "yarn install" 27 | yarn install 28 | echo -e "yarn build:prod" 29 | yarn build:prod 30 | echo "=================================================" 31 | echo -e " ${GREEN}cd build/npm and run 'npm publish'" 32 | echo "=================================================" 33 | else 34 | echo -e "${RED}⚠️ Need to publish from ${mainBranch} branch" 35 | echo -e "${REDBOLD}Checking out ${mainBranch}... " 36 | git checkout master 37 | echo -e "${RED}Run again" 38 | fi 39 | 40 | # TODO: Add Slack notification? 41 | # - Add release notes generation? 42 | # - Add version tag as argument? 43 | # - Add prompt for npm login? -------------------------------------------------------------------------------- /packages/wallet-sdk/src/__mocks__/provider.ts: -------------------------------------------------------------------------------- 1 | import { ScopedLocalStorage } from "../lib/ScopedLocalStorage"; 2 | import { 3 | CoinbaseWalletProvider, 4 | CoinbaseWalletProviderOptions, 5 | } from "../provider/CoinbaseWalletProvider"; 6 | import { WalletSDKRelayEventManager } from "../relay/WalletSDKRelayEventManager"; 7 | 8 | export const mockSetAppInfo = jest.fn(); 9 | 10 | export class MockProviderClass extends CoinbaseWalletProvider { 11 | constructor(opts: Readonly) { 12 | super(opts); 13 | } 14 | 15 | public async close() { 16 | return Promise.resolve(); 17 | } 18 | 19 | // @ts-expect-error mock relay 20 | private async initializeRelay() { 21 | return Promise.resolve({ 22 | setAppInfo: mockSetAppInfo, 23 | }); 24 | } 25 | } 26 | 27 | export const mockExtensionProvider = new MockProviderClass({ 28 | chainId: 1, 29 | jsonRpcUrl: "jsonrpc-url", 30 | overrideIsMetaMask: false, 31 | relayEventManager: new WalletSDKRelayEventManager(), 32 | relayProvider: jest.fn(), 33 | storage: new ScopedLocalStorage("-walletlink"), 34 | }); 35 | -------------------------------------------------------------------------------- /packages/wallet-sdk/src/components/ConnectContent/index.tsx: -------------------------------------------------------------------------------- 1 | export * from "./ConnectContent"; 2 | -------------------------------------------------------------------------------- /packages/wallet-sdk/src/components/ConnectDialog/ConnectDialog.scss: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018-2022 Coinbase, Inc. 2 | // Licensed under the Apache License, version 2.0 3 | 4 | @import "../theme.scss"; 5 | 6 | .-cbwsdk-css-reset { 7 | .-cbwsdk-connect-dialog { 8 | z-index: 2147483647; 9 | position: fixed; 10 | top: 0; 11 | left: 0; 12 | right: 0; 13 | bottom: 0; 14 | display: flex; 15 | align-items: center; 16 | justify-content: center; 17 | 18 | $white: #fff; 19 | $black: #000; 20 | 21 | &-backdrop { 22 | z-index: 2147483647; 23 | position: fixed; 24 | top: 0; 25 | left: 0; 26 | right: 0; 27 | bottom: 0; 28 | transition: opacity 0.25s; 29 | 30 | &.light { 31 | background-color: rgba($black, 0.5); 32 | } 33 | 34 | &.dark { 35 | background-color: rgba(50, 53, 61, 0.4); 36 | } 37 | 38 | &-hidden { 39 | opacity: 0; 40 | } 41 | } 42 | 43 | &-box { 44 | display: flex; 45 | position: relative; 46 | flex-direction: column; 47 | transform: scale(1); 48 | transition: opacity 0.25s, transform 0.25s; 49 | 50 | &-hidden { 51 | opacity: 0; 52 | transform: scale(0.85); 53 | } 54 | } 55 | 56 | &-container { 57 | display: block; 58 | 59 | &-hidden { 60 | display: none; 61 | } 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /packages/wallet-sdk/src/components/ConnectDialog/ConnectDialog.test.tsx: -------------------------------------------------------------------------------- 1 | import { fireEvent, render, screen, waitFor } from "@testing-library/preact"; 2 | import { h } from "preact"; 3 | 4 | import { ConnectDialog } from "./ConnectDialog"; 5 | 6 | const renderConnectDialog = ({ 7 | connectDisabled = false, 8 | isConnected = true, 9 | }) => { 10 | return render( 11 | , 24 | ); 25 | }; 26 | 27 | const windowOpenSpy = jest.spyOn(window, "open"); 28 | 29 | describe("TryExtensionLinkDialog", () => { 30 | test("should show scan QR box when connectDisabled is false", async () => { 31 | renderConnectDialog({ connectDisabled: false }); 32 | 33 | await waitFor(() => { 34 | expect(screen.queryByTestId("connect-content")).toBeTruthy(); 35 | }); 36 | }); 37 | 38 | test("should not show scan QR box when connectDisabled is true", async () => { 39 | renderConnectDialog({ connectDisabled: true }); 40 | 41 | await waitFor(() => { 42 | expect(screen.queryByTestId("connect-content")).toBeNull(); 43 | }); 44 | }); 45 | 46 | test("should show connecting spinner when not connected", async () => { 47 | renderConnectDialog({ isConnected: false }); 48 | 49 | await waitFor(() => { 50 | expect(screen.queryByTestId("connecting-spinner")).toBeTruthy(); 51 | }); 52 | }); 53 | 54 | test("should navigate to extension store in new tab after pressing install", async () => { 55 | const mockedWindowOpen = jest.fn(); 56 | windowOpenSpy.mockImplementation(mockedWindowOpen); 57 | 58 | renderConnectDialog({}); 59 | 60 | await waitFor(async () => { 61 | const button = await screen.findByRole("button", { name: "Install" }); 62 | fireEvent.click(button); 63 | expect(mockedWindowOpen).toBeCalledWith( 64 | "https://api.wallet.coinbase.com/rpc/v2/desktop/chrome", 65 | "_blank", 66 | ); 67 | }); 68 | }); 69 | 70 | test("should show refresh button after pressing install", async () => { 71 | windowOpenSpy.mockImplementation(() => null); 72 | 73 | renderConnectDialog({}); 74 | 75 | await waitFor(async () => { 76 | const button = await screen.findByRole("button", { name: "Install" }); 77 | expect(button.textContent).toEqual("Install"); 78 | 79 | fireEvent.click(button); 80 | expect(button.textContent).toEqual("Refresh"); 81 | }); 82 | }); 83 | }); 84 | -------------------------------------------------------------------------------- /packages/wallet-sdk/src/components/ConnectDialog/ConnectDialog.tsx: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018-2022 Coinbase, Inc. 2 | // Licensed under the Apache License, version 2.0 3 | 4 | import clsx from "clsx"; 5 | import { h } from "preact"; 6 | import { useEffect, useState } from "preact/hooks"; 7 | 8 | import { ConnectContent } from "../ConnectContent/ConnectContent"; 9 | import { TryExtensionContent } from "../TryExtensionContent/TryExtensionContent"; 10 | import css from "./ConnectDialog-css"; 11 | 12 | type ConnectDialogProps = { 13 | darkMode: boolean; 14 | version: string; 15 | sessionId: string; 16 | sessionSecret: string; 17 | linkAPIUrl: string; 18 | isOpen: boolean; 19 | isConnected: boolean; 20 | isParentConnection: boolean; 21 | chainId: number; 22 | connectDisabled: boolean; 23 | onCancel: (() => void) | null; 24 | }; 25 | 26 | export const ConnectDialog = (props: ConnectDialogProps) => { 27 | const { isOpen, darkMode } = props; 28 | const [containerHidden, setContainerHidden] = useState(!isOpen); 29 | const [dialogHidden, setDialogHidden] = useState(!isOpen); 30 | 31 | useEffect(() => { 32 | const timers = [ 33 | window.setTimeout(() => { 34 | setDialogHidden(!isOpen); 35 | }, 10), 36 | ]; 37 | 38 | if (isOpen) { 39 | setContainerHidden(false); 40 | } else { 41 | timers.push( 42 | window.setTimeout(() => { 43 | setContainerHidden(true); 44 | }, 360), 45 | ); 46 | } 47 | 48 | return () => { 49 | timers.forEach(window.clearTimeout); 50 | }; 51 | }, [props.isOpen]); 52 | 53 | const theme = darkMode ? "dark" : "light"; 54 | 55 | return ( 56 |
62 | 63 |
70 |
71 |
77 | {!props.connectDisabled ? ( 78 | 89 | ) : null} 90 | 91 |
92 |
93 |
94 | ); 95 | }; 96 | -------------------------------------------------------------------------------- /packages/wallet-sdk/src/components/ConnectDialog/index.tsx: -------------------------------------------------------------------------------- 1 | export * from "./ConnectDialog"; 2 | -------------------------------------------------------------------------------- /packages/wallet-sdk/src/components/LinkFlow/LinkFlow.test.tsx: -------------------------------------------------------------------------------- 1 | import { render } from "@testing-library/preact"; 2 | import { h } from "preact"; 3 | import { Observable, Subject } from "rxjs"; 4 | 5 | import { LinkFlow } from "./LinkFlow"; 6 | 7 | describe("LinkFlow", () => { 8 | const linkFlow = new LinkFlow({ 9 | darkMode: false, 10 | version: "1.2.1", 11 | sessionId: "session123", 12 | sessionSecret: "sessionSecret", 13 | linkAPIUrl: "http://link-url.com", 14 | isParentConnection: false, 15 | connected$: new Observable(), 16 | chainId$: new Subject(), 17 | }); 18 | 19 | test("initialize", () => { 20 | expect(linkFlow).toMatchObject({ 21 | connectDisabled: false, 22 | darkMode: false, 23 | isConnected: false, 24 | isOpen: false, 25 | isParentConnection: false, 26 | linkAPIUrl: "http://link-url.com", 27 | onCancel: null, 28 | root: null, 29 | sessionId: "session123", 30 | sessionSecret: "sessionSecret", 31 | version: "1.2.1", 32 | }); 33 | }); 34 | 35 | const attachedEl = document.getElementsByClassName("-cbwsdk-link-flow-root"); 36 | 37 | describe("public methods", () => { 38 | beforeEach(() => { 39 | render(
); 40 | const ele = document.getElementById("attach-here"); 41 | if (ele) { 42 | linkFlow.attach(ele); 43 | } 44 | }); 45 | 46 | test("@attach", () => { 47 | expect(attachedEl.length).toEqual(1); 48 | }); 49 | 50 | test("@detach", () => { 51 | linkFlow.detach(); 52 | 53 | expect(attachedEl.length).toEqual(0); 54 | }); 55 | 56 | test("@setConnectDisabled", () => { 57 | linkFlow.setConnectDisabled(true); 58 | 59 | expect(linkFlow).toMatchObject({ 60 | connectDisabled: true, 61 | }); 62 | }); 63 | 64 | test("@open", () => { 65 | linkFlow.open({ 66 | onCancel: () => {}, 67 | }); 68 | 69 | expect(linkFlow).toMatchObject({ 70 | isOpen: true, 71 | }); 72 | }); 73 | 74 | test("@close", () => { 75 | linkFlow.close(); 76 | 77 | expect(linkFlow).toMatchObject({ 78 | isOpen: false, 79 | onCancel: null, 80 | }); 81 | }); 82 | }); 83 | 84 | describe("without root element", () => { 85 | test("@detach", () => { 86 | const linkFlow1 = new LinkFlow({ 87 | darkMode: true, 88 | version: "1.2.1", 89 | sessionId: "session123", 90 | sessionSecret: "sessionSecret", 91 | linkAPIUrl: "http://link-url.com", 92 | isParentConnection: false, 93 | connected$: new Observable(), 94 | chainId$: new Subject(), 95 | }); 96 | linkFlow1.detach(); 97 | 98 | expect(attachedEl.length).toEqual(0); 99 | }); 100 | }); 101 | }); 102 | -------------------------------------------------------------------------------- /packages/wallet-sdk/src/components/LinkFlow/LinkFlow.tsx: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018-2022 Coinbase, Inc. 2 | // Licensed under the Apache License, version 2.0 3 | 4 | import { h, render } from "preact"; 5 | import { BehaviorSubject, Observable, Subject, Subscription } from "rxjs"; 6 | 7 | import { ConnectDialog } from "../ConnectDialog/ConnectDialog"; 8 | 9 | export interface LinkFlowOptions { 10 | darkMode: boolean; 11 | version: string; 12 | sessionId: string; 13 | sessionSecret: string; 14 | linkAPIUrl: string; 15 | isParentConnection: boolean; 16 | chainId$: Subject; 17 | connected$: Observable; 18 | } 19 | 20 | interface Optional { 21 | value?: T; 22 | } 23 | 24 | export class LinkFlow { 25 | private readonly darkMode: boolean; 26 | private readonly version: string; 27 | private readonly sessionId: string; 28 | private readonly sessionSecret: string; 29 | private readonly linkAPIUrl: string; 30 | private readonly isParentConnection: boolean; 31 | 32 | private readonly connected$: Observable; 33 | private readonly chainId$: Subject; 34 | private readonly extensionUI$: BehaviorSubject> = 35 | new BehaviorSubject({}); 36 | private readonly subscriptions = new Subscription(); 37 | 38 | private isConnected = false; 39 | private chainId = 1; 40 | private isOpen = false; 41 | private onCancel: (() => void) | null = null; 42 | 43 | private root: Element | null = null; 44 | 45 | // if true, hide QR code in LinkFlow (which happens if no jsonRpcUrl is provided) 46 | private connectDisabled = false; 47 | 48 | constructor(options: Readonly) { 49 | this.darkMode = options.darkMode; 50 | this.version = options.version; 51 | this.sessionId = options.sessionId; 52 | this.sessionSecret = options.sessionSecret; 53 | this.linkAPIUrl = options.linkAPIUrl; 54 | this.isParentConnection = options.isParentConnection; 55 | this.connected$ = options.connected$; 56 | this.chainId$ = options.chainId$; 57 | } 58 | 59 | public attach(el: Element): void { 60 | this.root = document.createElement("div"); 61 | this.root.className = "-cbwsdk-link-flow-root"; 62 | el.appendChild(this.root); 63 | this.render(); 64 | 65 | this.subscriptions.add( 66 | this.connected$.subscribe(v => { 67 | if (this.isConnected !== v) { 68 | this.isConnected = v; 69 | this.render(); 70 | } 71 | }), 72 | ); 73 | 74 | this.subscriptions.add( 75 | this.chainId$.subscribe(chainId => { 76 | if (this.chainId !== chainId) { 77 | this.chainId = chainId; 78 | this.render(); 79 | } 80 | }), 81 | ); 82 | } 83 | 84 | public detach(): void { 85 | if (!this.root) { 86 | return; 87 | } 88 | this.subscriptions.unsubscribe(); 89 | render(null, this.root); 90 | this.root.parentElement?.removeChild(this.root); 91 | } 92 | 93 | public setConnectDisabled(connectDisabled: boolean) { 94 | this.connectDisabled = connectDisabled; 95 | } 96 | 97 | public open(options: { onCancel: () => void }): void { 98 | this.isOpen = true; 99 | this.onCancel = options.onCancel; 100 | this.render(); 101 | } 102 | 103 | public close(): void { 104 | this.isOpen = false; 105 | this.onCancel = null; 106 | this.render(); 107 | } 108 | 109 | private render(): void { 110 | if (!this.root) { 111 | return; 112 | } 113 | 114 | const subscription = this.extensionUI$.subscribe(() => { 115 | if (!this.root) { 116 | return; 117 | } 118 | 119 | render( 120 | , 133 | this.root, 134 | ); 135 | }); 136 | 137 | this.subscriptions.add(subscription); 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /packages/wallet-sdk/src/components/LinkFlow/index.tsx: -------------------------------------------------------------------------------- 1 | export * from "./LinkFlow"; 2 | -------------------------------------------------------------------------------- /packages/wallet-sdk/src/components/QRCode.tsx: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018-2022 Coinbase, Inc. 2 | // Licensed under the Apache License, version 2.0 3 | 4 | import { FunctionComponent, h } from "preact"; 5 | import { useEffect, useState } from "preact/hooks"; 6 | 7 | import QRCodeSVG from "../vendor-js/qrcode-svg"; 8 | 9 | export interface QRCodeProps { 10 | content: string; 11 | width?: number; 12 | height?: number; 13 | fgColor?: string; 14 | bgColor?: string; 15 | image?: QRCodeSVG.SvgLogo; 16 | } 17 | 18 | export const QRCode: FunctionComponent = props => { 19 | const [svg, setSvg] = useState(""); 20 | 21 | useEffect(() => { 22 | const qrcode = new QRCodeSVG({ 23 | content: props.content, 24 | background: props.bgColor || "#ffffff", 25 | color: props.fgColor || "#000000", 26 | container: "svg", 27 | ecl: "M", 28 | width: props.width ?? 256, 29 | height: props.height ?? 256, 30 | padding: 0, 31 | image: props.image, 32 | }); 33 | const base64 = Buffer.from(qrcode.svg(), "utf8").toString("base64"); 34 | setSvg(`data:image/svg+xml;base64,${base64}`); 35 | }); 36 | 37 | return svg ? QR Code : null; 38 | }; 39 | -------------------------------------------------------------------------------- /packages/wallet-sdk/src/components/Snackbar/Snackbar.scss: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018-2022 Coinbase, Inc. 2 | // Licensed under the Apache License, version 2.0 3 | 4 | .-cbwsdk-css-reset { 5 | .-gear-container { 6 | margin-left: 16px !important; 7 | margin-right: 9px !important; 8 | display: flex; 9 | align-items: center; 10 | justify-content: center; 11 | width: 24px; 12 | height: 24px; 13 | transition: opacity 0.25s; 14 | 15 | * { 16 | user-select: none; 17 | } 18 | 19 | svg { 20 | opacity: 0; 21 | position: absolute; 22 | } 23 | } 24 | 25 | .-gear-icon { 26 | height: 12px; 27 | width: 12px; 28 | z-index: 10000; 29 | } 30 | 31 | .-cbwsdk-snackbar { 32 | align-items: flex-end; 33 | display: flex; 34 | flex-direction: column; 35 | position: fixed; 36 | right: 0; 37 | top: 0; 38 | z-index: 2147483647; 39 | 40 | * { 41 | user-select: none; 42 | } 43 | 44 | &-instance { 45 | display: flex; 46 | flex-direction: column; 47 | margin: 8px 16px 0 16px; 48 | overflow: visible; 49 | text-align: left; 50 | transform: translateX(0); 51 | transition: opacity 0.25s, transform 0.25s; 52 | 53 | &-header:hover { 54 | .-gear-container svg { 55 | opacity: 1; 56 | } 57 | } 58 | 59 | &-header { 60 | display: flex; 61 | align-items: center; 62 | background: #ffffff; 63 | overflow: hidden; 64 | 65 | border: 1px solid #e7ebee; 66 | box-sizing: border-box; 67 | border-radius: 8px; 68 | cursor: pointer; 69 | 70 | &-cblogo { 71 | margin: 8px 8px 8px 8px; 72 | } 73 | 74 | * { 75 | cursor: pointer; 76 | } 77 | 78 | &-message { 79 | color: #000; 80 | font-size: 13px; 81 | line-height: 1.5; 82 | user-select: none; 83 | } 84 | } 85 | 86 | &-menu { 87 | background: #ffffff; 88 | 89 | transition: opacity 0.25s ease-in-out, transform 0.25s linear, 90 | visibility 0s; 91 | visibility: hidden; 92 | 93 | border: 1px solid #e7ebee; 94 | box-sizing: border-box; 95 | border-radius: 8px; 96 | 97 | opacity: 0; 98 | flex-direction: column; 99 | padding-left: 8px; 100 | padding-right: 8px; 101 | 102 | &-item:last-child { 103 | margin-bottom: 8px !important; 104 | } 105 | 106 | &-item:hover { 107 | background: #f5f7f8; 108 | border-radius: 6px; 109 | transition: background 0.25s; 110 | 111 | span { 112 | color: #050f19; 113 | transition: color 0.25s; 114 | } 115 | 116 | svg path { 117 | fill: #000000; 118 | transition: fill 0.25s; 119 | } 120 | } 121 | 122 | &-item { 123 | visibility: inherit; 124 | height: 35px; 125 | margin-top: 8px; 126 | margin-bottom: 0; 127 | display: flex; 128 | flex-direction: row; 129 | align-items: center; 130 | padding: 8px; 131 | cursor: pointer; 132 | 133 | * { 134 | visibility: inherit; 135 | cursor: pointer; 136 | } 137 | 138 | &-is-red:hover { 139 | background: rgba(223, 95, 103, 0.2); 140 | transition: background 0.25s; 141 | 142 | * { 143 | cursor: pointer; 144 | } 145 | 146 | svg path { 147 | fill: #df5f67; 148 | transition: fill 0.25s; 149 | } 150 | 151 | span { 152 | color: #df5f67; 153 | transition: color 0.25s; 154 | } 155 | } 156 | 157 | &-info { 158 | color: #aaaaaa; 159 | font-size: 13px; 160 | margin: 0 8px 0 32px; 161 | position: absolute; 162 | } 163 | } 164 | } 165 | 166 | &-hidden { 167 | opacity: 0; 168 | text-align: left; 169 | transform: translateX(25%); 170 | transition: opacity 0.5s linear; 171 | } 172 | 173 | &-expanded { 174 | .-cbwsdk-snackbar-instance-menu { 175 | opacity: 1; 176 | display: flex; 177 | transform: translateY(8px); 178 | visibility: visible; 179 | } 180 | } 181 | } 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /packages/wallet-sdk/src/components/Snackbar/Snackbar.test.tsx: -------------------------------------------------------------------------------- 1 | import { render, screen, waitFor } from "@testing-library/preact"; 2 | import { h } from "preact"; 3 | 4 | import { Snackbar } from "./Snackbar"; 5 | 6 | const attachedEl = document.getElementsByClassName("-cbwsdk-snackbar-root"); 7 | 8 | describe("Snackbar", () => { 9 | const snackbar = new Snackbar({ 10 | darkMode: false, 11 | }); 12 | 13 | beforeEach(() => { 14 | render(
); 15 | const ele = document.getElementById("attach-here"); 16 | if (ele) { 17 | snackbar.attach(ele); 18 | } 19 | }); 20 | 21 | describe("public methods", () => { 22 | test("@attach", () => { 23 | expect(attachedEl.length).toEqual(1); 24 | }); 25 | 26 | test("@presentItem", async () => { 27 | snackbar.presentItem({ 28 | message: "Confirm on phone", 29 | appSrc: "coinbase-wallet", 30 | menuItems: [ 31 | { 32 | isRed: true, 33 | info: "Cancel transaction", 34 | svgWidth: "11", 35 | svgHeight: "11", 36 | path: "", 37 | defaultFillRule: "inherit", 38 | defaultClipRule: "inherit", 39 | onClick: jest.fn, 40 | }, 41 | { 42 | isRed: true, 43 | info: "Reset connection", 44 | svgWidth: "10", 45 | svgHeight: "11", 46 | path: "", 47 | defaultFillRule: "evenodd", 48 | defaultClipRule: "evenodd", 49 | onClick: jest.fn, 50 | }, 51 | ], 52 | }); 53 | 54 | await waitFor(() => { 55 | expect(screen.queryByText("Cancel transaction")).toBeInTheDocument(); 56 | expect(screen.queryByText("Reset connection")).toBeInTheDocument(); 57 | expect( 58 | document.getElementsByClassName( 59 | "-cbwsdk-snackbar-instance-menu-item-info-is-red", 60 | ).length, 61 | ).toEqual(2); 62 | }); 63 | }); 64 | 65 | test("@clear", () => { 66 | const menuItems = document.getElementsByClassName( 67 | "-cbwsdk-snackbar-instance-menu", 68 | ); 69 | expect(menuItems.length).toEqual(1); 70 | snackbar.clear(); 71 | expect(menuItems.length).toEqual(0); 72 | }); 73 | }); 74 | }); 75 | -------------------------------------------------------------------------------- /packages/wallet-sdk/src/components/Snackbar/SnackbarContainer.test.tsx: -------------------------------------------------------------------------------- 1 | import { fireEvent, render, screen } from "@testing-library/preact"; 2 | import { h } from "preact"; 3 | 4 | import { 5 | SnackbarContainer, 6 | SnackbarInstance, 7 | SnackbarInstanceProps, 8 | } from "./Snackbar"; 9 | 10 | const renderSnackbarContainer = (props?: SnackbarInstanceProps) => 11 | render( 12 | 13 | 14 | , 15 | ); 16 | 17 | describe("SnackbarContainer", () => { 18 | beforeEach(() => { 19 | jest.useFakeTimers(); 20 | jest.spyOn(window, "setTimeout"); 21 | renderSnackbarContainer({ 22 | menuItems: [ 23 | { 24 | isRed: true, 25 | info: "Cancel transaction", 26 | svgWidth: "11", 27 | svgHeight: "11", 28 | path: "", 29 | defaultFillRule: "inherit", 30 | defaultClipRule: "inherit", 31 | onClick: jest.fn, 32 | }, 33 | ], 34 | }); 35 | }); 36 | 37 | afterEach(() => { 38 | jest.useRealTimers(); 39 | }); 40 | 41 | test("render hidden", () => { 42 | const hiddenClass = document.getElementsByClassName( 43 | "-cbwsdk-snackbar-instance-hidden", 44 | ); 45 | expect(hiddenClass.length).toEqual(1); 46 | jest.runAllTimers(); 47 | expect(setTimeout).toHaveBeenCalledTimes(2); 48 | }); 49 | 50 | test("toggle expand", () => { 51 | const header = document.getElementsByClassName( 52 | "-cbwsdk-snackbar-instance-header", 53 | )[0]; 54 | const expandedClass = document.getElementsByClassName( 55 | "-cbwsdk-snackbar-instance-expanded", 56 | ); 57 | fireEvent.click(header); 58 | expect(expandedClass.length).toEqual(1); 59 | expect(screen.queryByText("Cancel transaction")).toBeVisible(); 60 | }); 61 | }); 62 | -------------------------------------------------------------------------------- /packages/wallet-sdk/src/components/Snackbar/index.tsx: -------------------------------------------------------------------------------- 1 | export * from "./Snackbar"; 2 | -------------------------------------------------------------------------------- /packages/wallet-sdk/src/components/Spinner/Spinner.scss: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018-2022 Coinbase, Inc. 2 | // Licensed under the Apache License, version 2.0 3 | 4 | .-cbwsdk-css-reset { 5 | .-cbwsdk-spinner { 6 | display: inline-block; 7 | 8 | svg { 9 | display: inline-block; 10 | animation: 2s linear infinite -cbwsdk-spinner-svg; 11 | circle { 12 | animation: 1.9s ease-in-out infinite both -cbwsdk-spinner-circle; 13 | display: block; 14 | fill: transparent; 15 | stroke-dasharray: 283; 16 | stroke-dashoffset: 280; 17 | stroke-linecap: round; 18 | stroke-width: 10px; 19 | transform-origin: 50% 50%; 20 | } 21 | } 22 | } 23 | } 24 | 25 | @keyframes -cbwsdk-spinner-svg { 26 | 0% { 27 | transform: rotateZ(0deg); 28 | } 29 | 100% { 30 | transform: rotateZ(360deg); 31 | } 32 | } 33 | 34 | @keyframes -cbwsdk-spinner-circle { 35 | 0%, 36 | 25% { 37 | stroke-dashoffset: 280; 38 | transform: rotate(0); 39 | } 40 | 41 | 50%, 42 | 75% { 43 | stroke-dashoffset: 75; 44 | transform: rotate(45deg); 45 | } 46 | 47 | 100% { 48 | stroke-dashoffset: 280; 49 | transform: rotate(360deg); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /packages/wallet-sdk/src/components/Spinner/Spinner.test.tsx: -------------------------------------------------------------------------------- 1 | import { render } from "@testing-library/preact"; 2 | import { h } from "preact"; 3 | 4 | import { Spinner } from "./Spinner"; 5 | 6 | const renderSpinner = (props: { size?: number; color?: string }) => 7 | render(); 8 | 9 | describe("Spinner", () => { 10 | test("renders default", () => { 11 | renderSpinner({}); 12 | 13 | const svgStyle = document.querySelector("svg")?.style; 14 | const svgCircle = document.querySelector("circle")?.style; 15 | 16 | expect(svgStyle?.width).toEqual("64px"); 17 | expect(svgStyle?.height).toEqual("64px"); 18 | expect(svgCircle?.stroke).toEqual("#000"); 19 | }); 20 | 21 | test("renders overrides", () => { 22 | renderSpinner({ 23 | size: 200, 24 | color: "red", 25 | }); 26 | 27 | const svgStyle = document.querySelector("svg")?.style; 28 | const svgCircle = document.querySelector("circle")?.style; 29 | 30 | expect(svgStyle?.width).toEqual("200px"); 31 | expect(svgStyle?.height).toEqual("200px"); 32 | expect(svgCircle?.stroke).toEqual("red"); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /packages/wallet-sdk/src/components/Spinner/Spinner.tsx: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018-2022 Coinbase, Inc. 2 | // Licensed under the Apache License, version 2.0 3 | 4 | import { FunctionComponent, h } from "preact"; 5 | 6 | import css from "./Spinner-css"; 7 | 8 | export const Spinner: FunctionComponent<{ 9 | size?: number; 10 | color?: string; 11 | }> = props => { 12 | const size = props.size ?? 64; 13 | const color = props.color || "#000"; 14 | 15 | return ( 16 |
17 | 18 | 23 | 24 | 25 |
26 | ); 27 | }; 28 | -------------------------------------------------------------------------------- /packages/wallet-sdk/src/components/Spinner/index.tsx: -------------------------------------------------------------------------------- 1 | export * from "./Spinner"; 2 | -------------------------------------------------------------------------------- /packages/wallet-sdk/src/components/TryExtensionContent/TryExtensionContent.scss: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018-2022 Coinbase, Inc. 2 | // Licensed under the Apache License, version 2.0 3 | 4 | @import "../theme.scss"; 5 | 6 | .-cbwsdk-css-reset { 7 | .-cbwsdk-try-extension { 8 | display: flex; 9 | margin-top: 12px; 10 | height: 202px; 11 | width: 700px; 12 | border-radius: 12px; 13 | padding: 30px; 14 | 15 | &.light { 16 | background: $white; 17 | } 18 | 19 | &.dark { 20 | background: $black; 21 | } 22 | 23 | &-column-half { 24 | flex: 50%; 25 | } 26 | 27 | &-heading { 28 | font-style: normal; 29 | font-weight: 500; 30 | font-size: 25px; 31 | line-height: 32px; 32 | margin: 0; 33 | max-width: 204px; 34 | 35 | &.light { 36 | color: $black; 37 | } 38 | 39 | &.dark { 40 | color: $white; 41 | } 42 | } 43 | 44 | &-cta { 45 | appearance: none; 46 | border: none; 47 | background: none; 48 | color: $primary; 49 | cursor: pointer; 50 | padding: 0; 51 | text-decoration: none; 52 | display: block; 53 | font-weight: 600; 54 | font-size: 16px; 55 | line-height: 24px; 56 | 57 | &.light { 58 | color: $primary; 59 | } 60 | 61 | &.dark { 62 | color: $primary-dark; 63 | } 64 | } 65 | 66 | &-cta-wrapper { 67 | display: flex; 68 | align-items: center; 69 | margin-top: 12px; 70 | } 71 | 72 | &-cta-icon { 73 | display: block; 74 | margin-left: 4px; 75 | height: 14px; 76 | } 77 | 78 | &-list { 79 | display: flex; 80 | flex-direction: column; 81 | justify-content: center; 82 | align-items: center; 83 | margin: 0; 84 | padding: 0; 85 | list-style: none; 86 | height: 100%; 87 | } 88 | 89 | &-list-item { 90 | display: flex; 91 | align-items: center; 92 | flex-flow: nowrap; 93 | margin-top: 24px; 94 | 95 | &:first-of-type { 96 | margin-top: 0; 97 | } 98 | } 99 | 100 | &-list-item-icon-wrapper { 101 | display: block; 102 | } 103 | 104 | &-list-item-icon { 105 | display: flex; 106 | height: 32px; 107 | width: 32px; 108 | border-radius: 50%; 109 | 110 | svg { 111 | margin: auto; 112 | display: block; 113 | } 114 | 115 | &.light { 116 | background: $background-alt; 117 | } 118 | 119 | &.dark { 120 | background: $background-alt-dark; 121 | } 122 | } 123 | 124 | &-list-item-copy { 125 | display: block; 126 | font-weight: 400; 127 | font-size: 14px; 128 | line-height: 20px; 129 | padding-left: 12px; 130 | 131 | &.light { 132 | color: $foreground-muted; 133 | } 134 | 135 | &.dark { 136 | color: $foreground-muted-dark; 137 | } 138 | } 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /packages/wallet-sdk/src/components/TryExtensionContent/TryExtensionContent.tsx: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018-2022 Coinbase, Inc. 2 | // Licensed under the Apache License, version 2.0 3 | 4 | import clsx from "clsx"; 5 | import { h } from "preact"; 6 | import { useCallback, useState } from "preact/hooks"; 7 | 8 | import { ArrowLeftIcon } from "../icons/ArrowLeftIcon"; 9 | import { LaptopIcon } from "../icons/LaptopIcon"; 10 | import { SafeIcon } from "../icons/SafeIcon"; 11 | import { Theme } from "../types"; 12 | import css from "./TryExtensionContent-css"; 13 | 14 | type TryExtensionContentProps = { 15 | theme: Theme; 16 | }; 17 | 18 | export function TryExtensionContent({ theme }: TryExtensionContentProps) { 19 | const [clicked, setClicked] = useState(false); 20 | 21 | const handleInstallClick = useCallback(() => { 22 | window.open( 23 | "https://api.wallet.coinbase.com/rpc/v2/desktop/chrome", 24 | "_blank", 25 | ); 26 | }, []); 27 | 28 | const handleClick = useCallback(() => { 29 | if (clicked) { 30 | window.location.reload(); 31 | } else { 32 | handleInstallClick(); 33 | setClicked(true); 34 | } 35 | }, [handleInstallClick, clicked]); 36 | 37 | return ( 38 |
39 | 40 |
41 |

42 | Or try the Coinbase Wallet browser extension 43 |

44 |
45 | 51 |
52 | {!clicked && ( 53 | 57 | )} 58 |
59 |
60 |
61 |
62 |
    63 |
  • 64 |
    65 | 66 | 67 | 68 |
    69 |
    70 | Connect with dapps with just one click on your desktop browser 71 |
    72 |
  • 73 |
  • 74 |
    75 | 76 | 77 | 78 |
    79 |
    80 | Add an additional layer of security by using a supported Ledger 81 | hardware wallet 82 |
    83 |
  • 84 |
85 |
86 |
87 | ); 88 | } 89 | -------------------------------------------------------------------------------- /packages/wallet-sdk/src/components/TryExtensionContent/index.tsx: -------------------------------------------------------------------------------- 1 | export * from "./TryExtensionContent"; 2 | -------------------------------------------------------------------------------- /packages/wallet-sdk/src/components/icons/ArrowLeftIcon.tsx: -------------------------------------------------------------------------------- 1 | import { h } from "preact"; 2 | 3 | export function ArrowLeftIcon(props: h.JSX.SVGAttributes) { 4 | return ( 5 | 12 | 13 | 14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /packages/wallet-sdk/src/components/icons/CloseIcon.tsx: -------------------------------------------------------------------------------- 1 | import { h } from "preact"; 2 | 3 | export function CloseIcon(props: h.JSX.SVGAttributes) { 4 | return ( 5 | 13 | 14 | 15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /packages/wallet-sdk/src/components/icons/LaptopIcon.tsx: -------------------------------------------------------------------------------- 1 | import { h } from "preact"; 2 | 3 | export function LaptopIcon(props: h.JSX.SVGAttributes) { 4 | return ( 5 | 12 | 13 | 14 | 15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /packages/wallet-sdk/src/components/icons/QRCodeIcon.tsx: -------------------------------------------------------------------------------- 1 | import { h } from "preact"; 2 | 3 | export function QRCodeIcon(props: h.JSX.SVGAttributes) { 4 | return ( 5 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /packages/wallet-sdk/src/components/icons/QRLogoCoinbase.tsx: -------------------------------------------------------------------------------- 1 | const svg = ` 2 | 3 | 4 | 5 | 6 | 7 | `; 8 | 9 | export default svg; 10 | -------------------------------------------------------------------------------- /packages/wallet-sdk/src/components/icons/QRLogoWallet.ts: -------------------------------------------------------------------------------- 1 | export default ` 2 | 3 | 4 | 5 | 6 | 7 | 8 | `; 9 | -------------------------------------------------------------------------------- /packages/wallet-sdk/src/components/icons/SafeIcon.tsx: -------------------------------------------------------------------------------- 1 | import { h } from "preact"; 2 | 3 | export function SafeIcon(props: h.JSX.SVGAttributes) { 4 | return ( 5 | 12 | 17 | 18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /packages/wallet-sdk/src/components/icons/StatusDotIcon.tsx: -------------------------------------------------------------------------------- 1 | import { h } from "preact"; 2 | 3 | export function StatusDotIcon(props: h.JSX.SVGAttributes) { 4 | return ( 5 | 12 | 17 | 18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /packages/wallet-sdk/src/components/icons/coinbase-round.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /packages/wallet-sdk/src/components/icons/coinbase-wallet-round.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /packages/wallet-sdk/src/components/icons/coinbase.svg: -------------------------------------------------------------------------------- 1 | 8 | 12 | 16 | 20 | 21 | -------------------------------------------------------------------------------- /packages/wallet-sdk/src/components/theme.scss: -------------------------------------------------------------------------------- 1 | $primary: #0052ff; 2 | $primary-dark: #588af5; 3 | 4 | $primary-wash: #f5f8ff; 5 | $primary-wash-dark: #001033; 6 | 7 | $foreground-muted: #5b636e; 8 | $foreground-muted-dark: #8a919e; 9 | 10 | $white: #fff; 11 | $black: #0a0b0d; 12 | 13 | $background: $white; 14 | $background-alt: #eef0f3; 15 | $background-dark: $black; 16 | $background-alt-dark: #1e2025; 17 | -------------------------------------------------------------------------------- /packages/wallet-sdk/src/components/types.ts: -------------------------------------------------------------------------------- 1 | export type Theme = "dark" | "light"; 2 | -------------------------------------------------------------------------------- /packages/wallet-sdk/src/connection/ClientMessage.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018-2022 Coinbase, Inc. 2 | // Licensed under the Apache License, version 2.0 3 | 4 | import { IntNumber } from "../types"; 5 | 6 | export interface ClientMessage { 7 | type: string; 8 | id: IntNumber; 9 | } 10 | 11 | export interface ClientMessageHostSession extends ClientMessage { 12 | type: "HostSession"; 13 | sessionId: string; 14 | sessionKey: string; 15 | } 16 | 17 | export function ClientMessageHostSession( 18 | params: Omit, 19 | ): ClientMessageHostSession { 20 | return { type: "HostSession", ...params }; 21 | } 22 | 23 | export interface ClientMessageIsLinked extends ClientMessage { 24 | type: "IsLinked"; 25 | sessionId: string; 26 | } 27 | 28 | export function ClientMessageIsLinked( 29 | params: Omit, 30 | ): ClientMessageIsLinked { 31 | return { type: "IsLinked", ...params }; 32 | } 33 | 34 | export interface ClientMessageGetSessionConfig extends ClientMessage { 35 | type: "GetSessionConfig"; 36 | sessionId: string; 37 | } 38 | 39 | export function ClientMessageGetSessionConfig( 40 | params: Omit, 41 | ): ClientMessageGetSessionConfig { 42 | return { type: "GetSessionConfig", ...params }; 43 | } 44 | 45 | export interface ClientMessageSetSessionConfig extends ClientMessage { 46 | type: "SetSessionConfig"; 47 | id: IntNumber; 48 | sessionId: string; 49 | webhookId?: string | null; 50 | webhookUrl?: string | null; 51 | metadata?: { [key: string]: string | null }; 52 | } 53 | 54 | export function ClientMessageSetSessionConfig( 55 | params: Omit, 56 | ): ClientMessageSetSessionConfig { 57 | return { type: "SetSessionConfig", ...params }; 58 | } 59 | 60 | export interface ClientMessagePublishEvent extends ClientMessage { 61 | type: "PublishEvent"; 62 | sessionId: string; 63 | event: string; 64 | data: string; 65 | callWebhook: boolean; 66 | } 67 | 68 | export function ClientMessagePublishEvent( 69 | params: Omit, 70 | ): ClientMessagePublishEvent { 71 | return { type: "PublishEvent", ...params }; 72 | } 73 | -------------------------------------------------------------------------------- /packages/wallet-sdk/src/connection/DiagnosticLogger.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | DiagnosticLogger, 3 | EVENTS, 4 | EventType, 5 | LogProperties, 6 | } from "./DiagnosticLogger"; 7 | 8 | describe("DiagnosticLogger", () => { 9 | describe("log", () => { 10 | let event: string; 11 | let props: LogProperties | undefined; 12 | 13 | class diagnosticLogger implements DiagnosticLogger { 14 | log(eventType: EventType, Properties?: LogProperties) { 15 | event = eventType; 16 | props = Properties; 17 | } 18 | } 19 | const diagnostic = new diagnosticLogger(); 20 | test("calls the log function", () => { 21 | const logProperties = { 22 | message: "a message", 23 | value: "a value", 24 | }; 25 | diagnostic.log(EVENTS.CONNECTED_STATE_CHANGE, logProperties); 26 | 27 | expect(event).toEqual(EVENTS.CONNECTED_STATE_CHANGE); 28 | expect(props).toEqual(logProperties); 29 | }); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /packages/wallet-sdk/src/connection/DiagnosticLogger.ts: -------------------------------------------------------------------------------- 1 | // DiagnosticLogger for debugging purposes only 2 | 3 | import { Web3RequestMessage } from "../relay/Web3RequestMessage"; 4 | import { Web3ResponseMessage } from "../relay/Web3ResponseMessage"; 5 | import { ConnectionState } from "./RxWebSocket"; 6 | import { ServerMessage, ServerMessageIsLinkedOK } from "./ServerMessage"; 7 | 8 | export type LogProperties = { 9 | addresses_length?: number; // number of eth addresses 10 | alreadyDestroyed?: boolean; // error flag if metadata is already destroyed on resetAndReload 11 | eventId?: Web3RequestMessage["id"] | Web3ResponseMessage["id"]; 12 | isSessionMismatched?: string; // storedSession does not match sessionId 13 | linked?: ServerMessageIsLinkedOK["linked"]; 14 | message?: string; // error message 15 | metadata_keys?: string[]; // session config metadata keys 16 | method?: string; // method throwing error message 17 | onlineGuests?: number; // number of online guests (should be 0 or 1) 18 | sessionIdHash?: string; // anonymous session id for debugging specific sessions 19 | sessionMetadataChange?: string; // change in session metadata 20 | state?: ConnectionState; 21 | storedSessionIdHash?: string; // anonymous session id from localStorage 22 | type?: ServerMessage["type"]; 23 | value?: string; // error value associated with the message 24 | }; 25 | 26 | type Keys = keyof typeof EVENTS; 27 | export type EventType = typeof EVENTS[Keys]; 28 | 29 | export interface DiagnosticLogger { 30 | log(eventType: EventType, logProperties?: LogProperties): void; 31 | } 32 | 33 | export const EVENTS = { 34 | STARTED_CONNECTING: "walletlink_sdk.started.connecting", 35 | CONNECTED_STATE_CHANGE: "walletlink_sdk.connected", 36 | DISCONNECTED: "walletlink_sdk.disconnected", 37 | METADATA_DESTROYED: "walletlink_sdk_metadata_destroyed", 38 | LINKED: "walletlink_sdk.linked", 39 | FAILURE: "walletlink_sdk.generic_failure", 40 | SESSION_CONFIG_RECEIVED: "walletlink_sdk.session_config_event_received", 41 | ETH_ACCOUNTS_STATE: "walletlink_sdk.eth_accounts_state", 42 | SESSION_STATE_CHANGE: "walletlink_sdk.session_state_change", 43 | UNLINKED_ERROR_STATE: "walletlink_sdk.unlinked_error_state", 44 | SKIPPED_CLEARING_SESSION: "walletlink_sdk.skipped_clearing_session", 45 | GENERAL_ERROR: "walletlink_sdk.general_error", 46 | WEB3_REQUEST: "walletlink_sdk.web3.request", 47 | WEB3_REQUEST_PUBLISHED: "walletlink_sdk.web3.request_published", 48 | WEB3_RESPONSE: "walletlink_sdk.web3.response", 49 | UNKNOWN_ADDRESS_ENCOUNTERED: "walletlink_sdk.unknown_address_encountered", 50 | }; 51 | -------------------------------------------------------------------------------- /packages/wallet-sdk/src/connection/EventListener.ts: -------------------------------------------------------------------------------- 1 | /** @deprecated in favor of DiagnosticLogger */ 2 | export interface EventListener { 3 | onEvent(eventType: string, eventProperties?: any): void; 4 | } 5 | 6 | export const EVENTS = { 7 | STARTED_CONNECTING: "walletlink_sdk.started.connecting", 8 | CONNECTED_STATE_CHANGE: "walletlink_sdk.connected", 9 | DISCONNECTED: "walletlink_sdk.disconnected", 10 | METADATA_DESTROYED: "walletlink_sdk_metadata_destroyed", 11 | LINKED: "walletlink_sdk.linked", 12 | FAILURE: "walletlink_sdk.generic_failure", 13 | SESSION_CONFIG_RECEIVED: "walletlink_sdk.session_config_event_received", 14 | ETH_ACCOUNTS_STATE: "walletlink_sdk.eth_accounts_state", 15 | SESSION_STATE_CHANGE: "walletlink_sdk.session_state_change", 16 | UNLINKED_ERROR_STATE: "walletlink_sdk.unlinked_error_state", 17 | SKIPPED_CLEARING_SESSION: "walletlink_sdk.skipped_clearing_session", 18 | GENERAL_ERROR: "walletlink_sdk.general_error", 19 | WEB3_REQUEST: "walletlink_sdk.web3.request", 20 | WEB3_REQUEST_PUBLISHED: "walletlink_sdk.web3.request_published", 21 | WEB3_RESPONSE: "walletlink_sdk.web3.response", 22 | UNKNOWN_ADDRESS_ENCOUNTERED: "walletlink_sdk.unknown_address_encountered", 23 | }; 24 | -------------------------------------------------------------------------------- /packages/wallet-sdk/src/connection/RxWebSocket.test.ts: -------------------------------------------------------------------------------- 1 | import WS from "jest-websocket-mock"; 2 | import { Observable } from "rxjs"; 3 | 4 | import { ConnectionState, RxWebSocket } from "./RxWebSocket"; 5 | 6 | describe("RxWebSocket", () => { 7 | let server: WS; 8 | let rxWS: RxWebSocket; 9 | beforeEach(() => { 10 | server = new WS("ws://localhost:1234"); 11 | rxWS = new RxWebSocket("http://localhost:1234"); 12 | }); 13 | 14 | afterEach(() => { 15 | WS.clean(); 16 | }); 17 | 18 | describe("is connected", () => { 19 | test("@connect & @disconnect", async () => { 20 | const client = rxWS.connect(); 21 | 22 | expect(client).toBeInstanceOf(Observable); 23 | await client.toPromise(); 24 | await server.connected; 25 | 26 | // @ts-expect-error test private methods 27 | expect(rxWS.webSocket).toBeInstanceOf(WebSocket); 28 | // @ts-expect-error test private methods 29 | rxWS.connectionStateSubject 30 | .subscribe({ 31 | next: val => { 32 | // Connected state 33 | expect(val).toEqual(ConnectionState.CONNECTED); 34 | }, 35 | closed: (val: ConnectionState) => { 36 | // Disconnected state 37 | expect(val).toEqual(ConnectionState.DISCONNECTED); 38 | }, 39 | }) 40 | .unsubscribe(); 41 | 42 | // Sends data 43 | const webSocketSendMock = jest 44 | .spyOn(WebSocket.prototype, "send") 45 | .mockImplementation(() => {}); 46 | 47 | rxWS.sendData("data"); 48 | expect(webSocketSendMock).toHaveBeenCalledWith("data"); 49 | 50 | // Disconnects 51 | rxWS.disconnect(); 52 | // @ts-expect-error test private methods 53 | expect(rxWS.webSocket).toBe(null); 54 | }); 55 | 56 | test("@connectionState$ & @incomingData$", () => { 57 | expect(rxWS.connectionState$).toBeInstanceOf(Observable); 58 | expect(rxWS.incomingData$).toBeInstanceOf(Observable); 59 | }); 60 | 61 | test("@incomingJSONData$", () => { 62 | expect(rxWS.incomingJSONData$).toBeInstanceOf(Observable); 63 | }); 64 | 65 | describe("errors & event listeners", () => { 66 | afterEach(() => rxWS.disconnect()); 67 | 68 | test("@connect throws error when connecting again", async () => { 69 | const client = rxWS.connect(); 70 | await client.toPromise(); 71 | 72 | await expect(rxWS.connect().toPromise()).rejects.toThrow( 73 | "webSocket object is not null", 74 | ); 75 | }); 76 | 77 | test("@connect throws error & fails to set websocket instance", async () => { 78 | const errorConnect = new RxWebSocket(""); 79 | 80 | await expect(errorConnect.connect().toPromise()).rejects.toThrow( 81 | "Failed to construct 'WebSocket': 1 argument required, but only 0 present.", 82 | ); 83 | }); 84 | 85 | test("onclose event throws error", async () => { 86 | const client = rxWS.connect(); 87 | await client.toPromise(); 88 | await server.connected; 89 | server.error(); 90 | 91 | await expect(rxWS.connect().toPromise()).rejects.toThrow( 92 | "websocket error 1000: ", 93 | ); 94 | }); 95 | 96 | test("onmessage event emits message", async () => { 97 | const client = rxWS.connect(); 98 | await client.toPromise(); 99 | await server.connected; 100 | 101 | // @ts-expect-error test private methods 102 | rxWS.incomingDataSubject.subscribe(val => 103 | expect(val).toEqual("hello world"), 104 | ); 105 | server.send("hello world"); 106 | }); 107 | }); 108 | }); 109 | 110 | describe("is not connected", () => { 111 | test("sendData error", () => { 112 | expect(() => rxWS.sendData("data")).toThrowError( 113 | "websocket is not connected", 114 | ); 115 | }); 116 | 117 | test("disconnect returns", () => { 118 | const webSocketCloseMock = jest 119 | .spyOn(WebSocket.prototype, "close") 120 | .mockImplementation(() => {}); 121 | 122 | rxWS.disconnect(); 123 | expect(webSocketCloseMock).not.toBeCalled(); 124 | }); 125 | }); 126 | }); 127 | -------------------------------------------------------------------------------- /packages/wallet-sdk/src/connection/RxWebSocket.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018-2022 Coinbase, Inc. 2 | // Licensed under the Apache License, version 2.0 3 | 4 | import { 5 | BehaviorSubject, 6 | empty, 7 | Observable, 8 | of, 9 | Subject, 10 | throwError, 11 | } from "rxjs"; 12 | import { flatMap, take } from "rxjs/operators"; 13 | 14 | export enum ConnectionState { 15 | DISCONNECTED, 16 | CONNECTING, 17 | CONNECTED, 18 | } 19 | 20 | /** 21 | * Rx-ified WebSocket 22 | */ 23 | export class RxWebSocket { 24 | private readonly url: string; 25 | private webSocket: WebSocket | null = null; 26 | private connectionStateSubject = new BehaviorSubject( 27 | ConnectionState.DISCONNECTED, 28 | ); 29 | private incomingDataSubject = new Subject(); 30 | 31 | /** 32 | * Constructor 33 | * @param url WebSocket server URL 34 | * @param [WebSocketClass] Custom WebSocket implementation 35 | */ 36 | constructor( 37 | url: string, 38 | private readonly WebSocketClass: typeof WebSocket = WebSocket, 39 | ) { 40 | this.url = url.replace(/^http/, "ws"); 41 | } 42 | 43 | /** 44 | * Make a websocket connection 45 | * @returns an Observable that completes when connected 46 | */ 47 | public connect(): Observable { 48 | if (this.webSocket) { 49 | return throwError(new Error("webSocket object is not null")); 50 | } 51 | return new Observable(obs => { 52 | let webSocket: WebSocket; 53 | try { 54 | this.webSocket = webSocket = new this.WebSocketClass(this.url); 55 | } catch (err) { 56 | obs.error(err); 57 | return; 58 | } 59 | this.connectionStateSubject.next(ConnectionState.CONNECTING); 60 | webSocket.onclose = evt => { 61 | this.clearWebSocket(); 62 | obs.error(new Error(`websocket error ${evt.code}: ${evt.reason}`)); 63 | this.connectionStateSubject.next(ConnectionState.DISCONNECTED); 64 | }; 65 | webSocket.onopen = _ => { 66 | obs.next(); 67 | obs.complete(); 68 | this.connectionStateSubject.next(ConnectionState.CONNECTED); 69 | }; 70 | webSocket.onmessage = evt => { 71 | this.incomingDataSubject.next(evt.data as string); 72 | }; 73 | }).pipe(take(1)); 74 | } 75 | 76 | /** 77 | * Disconnect from server 78 | */ 79 | public disconnect(): void { 80 | const { webSocket } = this; 81 | if (!webSocket) { 82 | return; 83 | } 84 | this.clearWebSocket(); 85 | this.connectionStateSubject.next(ConnectionState.DISCONNECTED); 86 | try { 87 | webSocket.close(); 88 | } catch {} 89 | } 90 | 91 | /** 92 | * Emit current connection state and subsequent changes 93 | * @returns an Observable for the connection state 94 | */ 95 | public get connectionState$(): Observable { 96 | return this.connectionStateSubject.asObservable(); 97 | } 98 | 99 | /** 100 | * Emit incoming data from server 101 | * @returns an Observable for the data received 102 | */ 103 | public get incomingData$(): Observable { 104 | return this.incomingDataSubject.asObservable(); 105 | } 106 | 107 | /** 108 | * Emit incoming JSON data from server. non-JSON data are ignored 109 | * @returns an Observable for parsed JSON data 110 | */ 111 | public get incomingJSONData$(): Observable { 112 | return this.incomingData$.pipe( 113 | flatMap(m => { 114 | let j: any; 115 | try { 116 | j = JSON.parse(m); 117 | } catch (err) { 118 | return empty(); 119 | } 120 | return of(j); 121 | }), 122 | ); 123 | } 124 | 125 | /** 126 | * Send data to server 127 | * @param data text to send 128 | */ 129 | public sendData(data: string): void { 130 | const { webSocket } = this; 131 | if (!webSocket) { 132 | throw new Error("websocket is not connected"); 133 | } 134 | webSocket.send(data); 135 | } 136 | 137 | private clearWebSocket(): void { 138 | const { webSocket } = this; 139 | if (!webSocket) { 140 | return; 141 | } 142 | this.webSocket = null; 143 | webSocket.onclose = null; 144 | webSocket.onerror = null; 145 | webSocket.onmessage = null; 146 | webSocket.onopen = null; 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /packages/wallet-sdk/src/connection/ServerMessage.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018-2022 Coinbase, Inc. 2 | // Licensed under the Apache License, version 2.0 3 | 4 | import { IntNumber } from "../types"; 5 | 6 | export interface ServerMessage { 7 | type: string; 8 | id?: IntNumber; 9 | } 10 | 11 | export interface ServerMessageOK extends ServerMessage { 12 | type: "OK"; 13 | id: IntNumber; 14 | sessionId: string; 15 | } 16 | 17 | export interface ServerMessageFail extends ServerMessage { 18 | type: "Fail"; 19 | id: IntNumber; 20 | sessionId: string; 21 | error: string; 22 | } 23 | 24 | export function isServerMessageFail(msg: any): msg is ServerMessageFail { 25 | return ( 26 | msg && 27 | msg.type === "Fail" && 28 | typeof msg.id === "number" && 29 | typeof msg.sessionId === "string" && 30 | typeof msg.error === "string" 31 | ); 32 | } 33 | 34 | export interface ServerMessageIsLinkedOK extends ServerMessage { 35 | type: "IsLinkedOK"; 36 | id: IntNumber; 37 | sessionId: string; 38 | linked: boolean; 39 | onlineGuests: number; 40 | } 41 | 42 | export interface ServerMessageLinked extends ServerMessage { 43 | type: "Linked"; 44 | sessionId: string; 45 | onlineGuests: number; 46 | } 47 | 48 | export interface ServerMessageGetSessionConfigOK extends ServerMessage { 49 | type: "GetSessionConfigOK"; 50 | id: IntNumber; 51 | sessionId: string; 52 | webhookId: string; 53 | webhookUrl: string; 54 | metadata: { [field: string]: string }; 55 | } 56 | 57 | export interface ServerMessageSessionConfigUpdated extends ServerMessage { 58 | type: "SessionConfigUpdated"; 59 | sessionId: string; 60 | webhookId: string; 61 | webhookUrl: string; 62 | metadata: { [field: string]: string }; 63 | } 64 | 65 | export interface ServerMessagePublishEventOK extends ServerMessage { 66 | type: "PublishEventOK"; 67 | id: IntNumber; 68 | sessionId: string; 69 | eventId: string; 70 | } 71 | 72 | export interface ServerMessageEvent extends ServerMessage { 73 | type: "Event"; 74 | sessionId: string; 75 | eventId: string; 76 | event: string; 77 | data: string; 78 | } 79 | -------------------------------------------------------------------------------- /packages/wallet-sdk/src/connection/SessionConfig.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018-2022 Coinbase, Inc. 2 | // Licensed under the Apache License, version 2.0 3 | 4 | export interface SessionConfig { 5 | webhookId: string; 6 | webhookUrl: string; 7 | metadata: { [key: string]: string | undefined }; 8 | } 9 | -------------------------------------------------------------------------------- /packages/wallet-sdk/src/fixtures/provider.ts: -------------------------------------------------------------------------------- 1 | export const MOCK_ADDERESS = "0xFadAFCE89EA2221fa33005640Acf2C923312F2b9"; 2 | 3 | export const MOCK_TX = 4 | "0xc21a1aaace40a8ee9dd3827ae5a85412a05755cc004469efaf3cfdd82c59a670"; 5 | 6 | export const MOCK_SIGNED_TX = 7 | ":f87a1b8504a817c80082520894c589ac793af309db9690d819abc9aab37d169f6a8814d1120d7b1600008e0deadbeef0cafebabe01234567891ba03cd26b08b246f23f74fceb2c063021955e691cf7d45fba443a2e504a4700dba5a0337b1f8dbf21ef35adf6e2a867d9c7bc836d1b79c8ab40c670385a2d0abca88c"; 8 | 9 | export const MOCK_TYPED_DATA = JSON.stringify({ 10 | types: { 11 | EIP712Domain: [ 12 | { name: "name", type: "string" }, 13 | { name: "version", type: "string" }, 14 | { name: "chainId", type: "uint256" }, 15 | { name: "verifyingContract", type: "address" }, 16 | { name: "salt", type: "bytes32" }, 17 | ], 18 | Bid: [ 19 | { name: "amount", type: "uint256" }, 20 | { name: "bidder", type: "Identity" }, 21 | ], 22 | Identity: [ 23 | { name: "userId", type: "uint256" }, 24 | { name: "wallet", type: "address" }, 25 | ], 26 | }, 27 | domain: { 28 | name: "Provider Test", 29 | version: "1", 30 | chainId: parseInt("1", 10), 31 | verifyingContract: MOCK_ADDERESS, 32 | salt: "0xf2d857f4a3edcb9b78b4d503bfe733db1e3f6cdc2b7971ee739626c97e86a558", 33 | }, 34 | primaryType: "Bid", 35 | message: { 36 | amount: 100, 37 | bidder: { 38 | userId: 323, 39 | wallet: MOCK_ADDERESS, 40 | }, 41 | }, 42 | }); 43 | -------------------------------------------------------------------------------- /packages/wallet-sdk/src/index.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018-2022 Coinbase, Inc. 2 | // Licensed under the Apache License, version 2.0 3 | 4 | import { CoinbaseWalletSDK } from "./CoinbaseWalletSDK"; 5 | import { CoinbaseWalletProvider } from "./provider/CoinbaseWalletProvider"; 6 | 7 | export { CoinbaseWalletSDK } from "./CoinbaseWalletSDK"; 8 | export { CoinbaseWalletProvider } from "./provider/CoinbaseWalletProvider"; 9 | export default CoinbaseWalletSDK; 10 | 11 | declare global { 12 | interface Window { 13 | CoinbaseWalletSDK: typeof CoinbaseWalletSDK; 14 | CoinbaseWalletProvider: typeof CoinbaseWalletProvider; 15 | /** 16 | * For CoinbaseWalletSDK, window.ethereum is `CoinbaseWalletProvider` 17 | */ 18 | ethereum?: unknown; 19 | coinbaseWalletExtension?: CoinbaseWalletProvider; 20 | 21 | /** 22 | * @deprecated Legacy API 23 | */ 24 | WalletLink: typeof CoinbaseWalletSDK; 25 | /** 26 | * @deprecated Legacy API 27 | */ 28 | WalletLinkProvider: typeof CoinbaseWalletProvider; 29 | /** 30 | * @deprecated Legacy API 31 | */ 32 | walletLinkExtension?: CoinbaseWalletProvider; 33 | } 34 | } 35 | 36 | if (typeof window !== "undefined") { 37 | window.CoinbaseWalletSDK = CoinbaseWalletSDK; 38 | window.CoinbaseWalletProvider = CoinbaseWalletProvider; 39 | 40 | /** 41 | * @deprecated Use `window.CoinbaseWalletSDK` 42 | */ 43 | window.WalletLink = CoinbaseWalletSDK; 44 | /** 45 | * @deprecated Use `window.CoinbaseWalletProvider` 46 | */ 47 | window.WalletLinkProvider = CoinbaseWalletProvider; 48 | } 49 | -------------------------------------------------------------------------------- /packages/wallet-sdk/src/lib/ScopedLocalStorage.test.ts: -------------------------------------------------------------------------------- 1 | import { ScopedLocalStorage } from "./ScopedLocalStorage"; 2 | 3 | describe("ScopedLocalStorage", () => { 4 | describe("public methods", () => { 5 | afterEach(() => localStorage.clear()); 6 | 7 | const scopedLocalStorage = new ScopedLocalStorage("-testing"); 8 | test("@setItem", () => { 9 | scopedLocalStorage.setItem("foo", "bar"); 10 | 11 | expect(localStorage.getItem("-testing:foo")).toEqual("bar"); 12 | expect(localStorage.length).toEqual(1); 13 | }); 14 | 15 | test("@getItem", () => { 16 | scopedLocalStorage.setItem("foo", "bar"); 17 | const getVal = scopedLocalStorage.getItem("foo"); 18 | 19 | expect(getVal).toEqual("bar"); 20 | }); 21 | 22 | test("@removeItem", () => { 23 | scopedLocalStorage.removeItem("foo"); 24 | 25 | expect(localStorage.length).toEqual(0); 26 | }); 27 | 28 | test("@clear", () => { 29 | scopedLocalStorage.setItem("foo1", "bar1"); 30 | scopedLocalStorage.setItem("foo2", "bar2"); 31 | scopedLocalStorage.setItem("foo3", "bar3"); 32 | expect(localStorage.length).toEqual(3); 33 | 34 | scopedLocalStorage.clear(); 35 | expect(localStorage.length).toEqual(0); 36 | }); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /packages/wallet-sdk/src/lib/ScopedLocalStorage.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018-2022 Coinbase, Inc. 2 | // Licensed under the Apache License, version 2.0 3 | 4 | export class ScopedLocalStorage { 5 | constructor(private scope: string) {} 6 | 7 | public setItem(key: string, value: string): void { 8 | localStorage.setItem(this.scopedKey(key), value); 9 | } 10 | 11 | public getItem(key: string): string | null { 12 | return localStorage.getItem(this.scopedKey(key)); 13 | } 14 | 15 | public removeItem(key: string): void { 16 | localStorage.removeItem(this.scopedKey(key)); 17 | } 18 | 19 | public clear(): void { 20 | const prefix = this.scopedKey(""); 21 | const keysToRemove: string[] = []; 22 | for (let i = 0; i < localStorage.length; i++) { 23 | const key = localStorage.key(i); 24 | if (typeof key === "string" && key.startsWith(prefix)) { 25 | keysToRemove.push(key); 26 | } 27 | } 28 | keysToRemove.forEach(key => localStorage.removeItem(key)); 29 | } 30 | 31 | private scopedKey(key: string): string { 32 | return `${this.scope}:${key}`; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /packages/wallet-sdk/src/lib/cssReset.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018-2022 Coinbase, Inc. 2 | // Licensed under the Apache License, version 2.0 3 | 4 | import css from "./cssReset-css"; 5 | 6 | export function injectCssReset(): void { 7 | const styleEl = document.createElement("style"); 8 | styleEl.type = "text/css"; 9 | styleEl.appendChild(document.createTextNode(css)); 10 | document.documentElement.appendChild(styleEl); 11 | } 12 | -------------------------------------------------------------------------------- /packages/wallet-sdk/src/provider/JSONRPC.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018-2022 Coinbase, Inc. 2 | // Licensed under the Apache License, version 2.0 3 | 4 | export enum JSONRPCMethod { 5 | // synchronous or asynchronous 6 | eth_accounts = "eth_accounts", 7 | eth_coinbase = "eth_coinbase", 8 | net_version = "net_version", 9 | eth_chainId = "eth_chainId", 10 | eth_uninstallFilter = "eth_uninstallFilter", // synchronous 11 | 12 | // asynchronous only 13 | eth_requestAccounts = "eth_requestAccounts", 14 | eth_sign = "eth_sign", 15 | eth_ecRecover = "eth_ecRecover", 16 | personal_sign = "personal_sign", 17 | personal_ecRecover = "personal_ecRecover", 18 | eth_signTransaction = "eth_signTransaction", 19 | eth_sendRawTransaction = "eth_sendRawTransaction", 20 | eth_sendTransaction = "eth_sendTransaction", 21 | eth_signTypedData_v1 = "eth_signTypedData_v1", 22 | eth_signTypedData_v2 = "eth_signTypedData_v2", 23 | eth_signTypedData_v3 = "eth_signTypedData_v3", 24 | eth_signTypedData_v4 = "eth_signTypedData_v4", 25 | eth_signTypedData = "eth_signTypedData", 26 | cbWallet_arbitrary = "walletlink_arbitrary", // compatibility 27 | wallet_addEthereumChain = "wallet_addEthereumChain", 28 | wallet_switchEthereumChain = "wallet_switchEthereumChain", 29 | wallet_watchAsset = "wallet_watchAsset", 30 | 31 | // asynchronous pub/sub 32 | eth_subscribe = "eth_subscribe", 33 | eth_unsubscribe = "eth_unsubscribe", 34 | 35 | // asynchronous filter methods 36 | eth_newFilter = "eth_newFilter", 37 | eth_newBlockFilter = "eth_newBlockFilter", 38 | eth_newPendingTransactionFilter = "eth_newPendingTransactionFilter", 39 | eth_getFilterChanges = "eth_getFilterChanges", 40 | eth_getFilterLogs = "eth_getFilterLogs", 41 | } 42 | 43 | export interface JSONRPCRequest { 44 | jsonrpc: "2.0"; 45 | id: number; 46 | method: string; 47 | params: T; 48 | } 49 | 50 | export interface JSONRPCResponse { 51 | jsonrpc: "2.0"; 52 | id: number; 53 | result?: T; 54 | error?: { 55 | code: number; 56 | message: string; 57 | data?: U; 58 | } | null; 59 | } 60 | -------------------------------------------------------------------------------- /packages/wallet-sdk/src/provider/SubscriptionManager.ts: -------------------------------------------------------------------------------- 1 | import SafeEventEmitter from "@metamask/safe-event-emitter"; 2 | import { 3 | JsonRpcEngineEndCallback, 4 | JsonRpcEngineNextCallback, 5 | } from "json-rpc-engine"; 6 | 7 | import { RequestArguments, Web3Provider } from "./Web3Provider"; 8 | 9 | const PollingBlockTracker = require("eth-block-tracker"); 10 | const createSubscriptionManager = require("eth-json-rpc-filters/subscriptionManager"); 11 | const noop = () => {}; 12 | 13 | export interface SubscriptionResult { 14 | result?: unknown; 15 | } 16 | 17 | export interface SubscriptionNotification { 18 | method: string; 19 | params: { 20 | subscription: string; 21 | result: unknown; 22 | }; 23 | } 24 | 25 | export class SubscriptionManager { 26 | private readonly subscriptionMiddleware: SubscriptionMiddleware; 27 | readonly events: SafeEventEmitter; 28 | 29 | constructor(provider: Web3Provider) { 30 | const blockTracker = new PollingBlockTracker({ 31 | provider, 32 | pollingInterval: 15 * 1000, // 15 sec 33 | setSkipCacheFlag: true, 34 | }); 35 | 36 | const { events, middleware } = createSubscriptionManager({ 37 | blockTracker, 38 | provider, 39 | }); 40 | 41 | this.events = events; 42 | this.subscriptionMiddleware = middleware; 43 | } 44 | 45 | public async handleRequest(request: { 46 | method: string; 47 | params: any[]; 48 | }): Promise { 49 | const result = {}; 50 | await this.subscriptionMiddleware(request, result, noop, noop); 51 | return result; 52 | } 53 | 54 | public destroy() { 55 | this.subscriptionMiddleware.destroy(); 56 | } 57 | } 58 | 59 | interface SubscriptionMiddleware { 60 | ( 61 | req: RequestArguments, 62 | res: SubscriptionResult, 63 | next: JsonRpcEngineNextCallback, 64 | end: JsonRpcEngineEndCallback, 65 | ): Promise; 66 | 67 | destroy(): void; 68 | } 69 | -------------------------------------------------------------------------------- /packages/wallet-sdk/src/provider/WalletUIError.ts: -------------------------------------------------------------------------------- 1 | export class WalletUIError extends Error { 2 | private constructor(readonly message: string, readonly errorCode?: number) { 3 | super(message); 4 | } 5 | 6 | static UserRejectedRequest = new WalletUIError("User rejected request"); 7 | 8 | static SwitchEthereumChainUnsupportedChainId = new WalletUIError( 9 | "Unsupported chainId", 10 | 4902, 11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /packages/wallet-sdk/src/provider/Web3Provider.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018-2022 Coinbase, Inc. 2 | // Licensed under the Apache License, version 2.0 3 | 4 | import { Callback } from "../types"; 5 | import { JSONRPCRequest, JSONRPCResponse } from "./JSONRPC"; 6 | 7 | export interface Web3Provider { 8 | send(request: JSONRPCRequest): JSONRPCResponse; 9 | send(request: JSONRPCRequest[]): JSONRPCResponse[]; 10 | send(request: JSONRPCRequest, callback: Callback): void; 11 | send(request: JSONRPCRequest[], callback: Callback): void; 12 | send(method: string, params?: any[] | any): Promise; 13 | 14 | sendAsync(request: JSONRPCRequest, callback: Callback): void; 15 | sendAsync( 16 | request: JSONRPCRequest[], 17 | callback: Callback, 18 | ): void; 19 | 20 | request(args: RequestArguments): Promise; 21 | 22 | host: string; 23 | connected: boolean; 24 | chainId: string; 25 | supportsSubscriptions(): boolean; 26 | disconnect(): boolean; 27 | } 28 | 29 | export interface RequestArguments { 30 | /** The RPC method to request. */ 31 | method: string; 32 | 33 | /** The params of the RPC method, if any. */ 34 | params?: any; 35 | } 36 | -------------------------------------------------------------------------------- /packages/wallet-sdk/src/relay/EthereumTransactionParams.ts: -------------------------------------------------------------------------------- 1 | import BN from "bn.js"; 2 | 3 | import { AddressString, IntNumber } from "../types"; 4 | 5 | export interface EthereumTransactionParams { 6 | fromAddress: AddressString; 7 | toAddress: AddressString | null; 8 | weiValue: BN; 9 | data: Buffer; 10 | nonce: IntNumber | null; 11 | gasPriceInWei: BN | null; 12 | maxFeePerGas: BN | null; // in wei 13 | maxPriorityFeePerGas: BN | null; // in wei 14 | gasLimit: BN | null; 15 | chainId: IntNumber; 16 | } 17 | -------------------------------------------------------------------------------- /packages/wallet-sdk/src/relay/RelayMessage.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018-2022 Coinbase, Inc. 2 | // Licensed under the Apache License, version 2.0 3 | 4 | export enum RelayMessageType { 5 | SESSION_ID_REQUEST = "SESSION_ID_REQUEST", 6 | SESSION_ID_RESPONSE = "SESSION_ID_RESPONSE", 7 | LINKED = "LINKED", 8 | UNLINKED = "UNLINKED", 9 | WEB3_REQUEST = "WEB3_REQUEST", 10 | WEB3_REQUEST_CANCELED = "WEB3_REQUEST_CANCELED", 11 | WEB3_RESPONSE = "WEB3_RESPONSE", 12 | } 13 | 14 | export interface RelayMessage { 15 | type: T; 16 | } 17 | -------------------------------------------------------------------------------- /packages/wallet-sdk/src/relay/Session.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018-2022 Coinbase, Inc. 2 | // Licensed under the Apache License, version 2.0 3 | 4 | import { sha256 } from "sha.js"; 5 | 6 | import { ScopedLocalStorage } from "../lib/ScopedLocalStorage"; 7 | import { randomBytesHex } from "../util"; 8 | 9 | const STORAGE_KEY_SESSION_ID = "session:id"; 10 | const STORAGE_KEY_SESSION_SECRET = "session:secret"; 11 | const STORAGE_KEY_SESSION_LINKED = "session:linked"; 12 | 13 | export class Session { 14 | private readonly _id: string; 15 | private readonly _secret: string; 16 | private readonly _key: string; 17 | private readonly _storage: ScopedLocalStorage; 18 | private _linked: boolean; 19 | 20 | constructor( 21 | storage: ScopedLocalStorage, 22 | id?: string, 23 | secret?: string, 24 | linked?: boolean, 25 | ) { 26 | this._storage = storage; 27 | this._id = id || randomBytesHex(16); 28 | this._secret = secret || randomBytesHex(32); 29 | 30 | this._key = new sha256() 31 | .update(`${this._id}, ${this._secret} WalletLink`) // ensure old sessions stay connected 32 | .digest("hex"); 33 | 34 | this._linked = !!linked; 35 | } 36 | 37 | public static load(storage: ScopedLocalStorage): Session | null { 38 | const id = storage.getItem(STORAGE_KEY_SESSION_ID); 39 | const linked = storage.getItem(STORAGE_KEY_SESSION_LINKED); 40 | const secret = storage.getItem(STORAGE_KEY_SESSION_SECRET); 41 | 42 | if (id && secret) { 43 | return new Session(storage, id, secret, linked === "1"); 44 | } 45 | 46 | return null; 47 | } 48 | 49 | /** 50 | * Takes in a session ID and returns the sha256 hash of it. 51 | * @param sessionId session ID 52 | */ 53 | public static hash(sessionId: string): string { 54 | return new sha256().update(sessionId).digest("hex"); 55 | } 56 | 57 | public get id(): string { 58 | return this._id; 59 | } 60 | 61 | public get secret(): string { 62 | return this._secret; 63 | } 64 | 65 | public get key(): string { 66 | return this._key; 67 | } 68 | 69 | public get linked(): boolean { 70 | return this._linked; 71 | } 72 | 73 | public set linked(val: boolean) { 74 | this._linked = val; 75 | this.persistLinked(); 76 | } 77 | 78 | public save(): Session { 79 | this._storage.setItem(STORAGE_KEY_SESSION_ID, this._id); 80 | this._storage.setItem(STORAGE_KEY_SESSION_SECRET, this._secret); 81 | this.persistLinked(); 82 | return this; 83 | } 84 | 85 | private persistLinked(): void { 86 | this._storage.setItem(STORAGE_KEY_SESSION_LINKED, this._linked ? "1" : "0"); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /packages/wallet-sdk/src/relay/WalletSDKRelayEventManager.test.ts: -------------------------------------------------------------------------------- 1 | import { WalletSDKRelayEventManager } from "./WalletSDKRelayEventManager"; 2 | 3 | describe("WalletSDKRelayEventManager", () => { 4 | test("@makeRequestId", () => { 5 | const sdkRelayEventManager = new WalletSDKRelayEventManager(); 6 | expect(sdkRelayEventManager.makeRequestId()).toEqual(1); 7 | expect(sdkRelayEventManager.makeRequestId()).toEqual(2); 8 | expect(sdkRelayEventManager.makeRequestId()).toEqual(3); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /packages/wallet-sdk/src/relay/WalletSDKRelayEventManager.ts: -------------------------------------------------------------------------------- 1 | import { prepend0x } from "../util"; 2 | import { Web3Response } from "./Web3Response"; 3 | 4 | type ResponseCallback = (response: Web3Response) => void; 5 | 6 | export class WalletSDKRelayEventManager { 7 | _nextRequestId = 0; 8 | callbacks = new Map(); 9 | 10 | public makeRequestId(): number { 11 | // max nextId == max int32 for compatibility with mobile 12 | this._nextRequestId = (this._nextRequestId + 1) % 0x7fffffff; 13 | const id = this._nextRequestId; 14 | const idStr = prepend0x(id.toString(16)); 15 | // unlikely that this will ever be an issue, but just to be safe 16 | const callback = this.callbacks.get(idStr); 17 | if (callback) { 18 | this.callbacks.delete(idStr); 19 | } 20 | return id; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /packages/wallet-sdk/src/relay/Web3Method.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018-2022 Coinbase, Inc. 2 | // Licensed under the Apache License, version 2.0 3 | 4 | export enum Web3Method { 5 | requestEthereumAccounts = "requestEthereumAccounts", 6 | signEthereumMessage = "signEthereumMessage", 7 | signEthereumTransaction = "signEthereumTransaction", 8 | submitEthereumTransaction = "submitEthereumTransaction", 9 | ethereumAddressFromSignedMessage = "ethereumAddressFromSignedMessage", 10 | scanQRCode = "scanQRCode", 11 | generic = "generic", 12 | childRequestEthereumAccounts = "childRequestEthereumAccounts", 13 | addEthereumChain = "addEthereumChain", 14 | switchEthereumChain = "switchEthereumChain", 15 | makeEthereumJSONRPCRequest = "makeEthereumJSONRPCRequest", 16 | watchAsset = "watchAsset", 17 | selectProvider = "selectProvider", 18 | } 19 | -------------------------------------------------------------------------------- /packages/wallet-sdk/src/relay/Web3Request.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018-2022 Coinbase, Inc. 2 | // Licensed under the Apache License, version 2.0 3 | 4 | import { 5 | AddressString, 6 | BigIntString, 7 | HexString, 8 | IntNumber, 9 | ProviderType, 10 | RegExpString, 11 | } from "../types"; 12 | import { Web3Method } from "./Web3Method"; 13 | 14 | interface BaseWeb3Request< 15 | Method extends Web3Method, 16 | Params extends object = Record, 17 | > { 18 | method: Method; 19 | params: Params; 20 | } 21 | 22 | export type RequestEthereumAccountsRequest = BaseWeb3Request< 23 | Web3Method.requestEthereumAccounts, 24 | { 25 | appName: string; 26 | appLogoUrl: string | null; 27 | } 28 | >; 29 | 30 | export type AddEthereumChainRequest = BaseWeb3Request< 31 | Web3Method.addEthereumChain, 32 | { 33 | chainId: string; 34 | blockExplorerUrls?: string[]; 35 | chainName?: string; 36 | iconUrls?: string[]; 37 | rpcUrls: string[]; 38 | nativeCurrency?: { 39 | name: string; 40 | symbol: string; 41 | decimals: number; 42 | }; 43 | } 44 | >; 45 | 46 | export type SwitchEthereumChainRequest = BaseWeb3Request< 47 | Web3Method.switchEthereumChain, 48 | { 49 | chainId: string; 50 | address?: string; 51 | } 52 | >; 53 | 54 | export type SignEthereumMessageRequest = BaseWeb3Request< 55 | Web3Method.signEthereumMessage, 56 | { 57 | message: HexString; 58 | address: AddressString; 59 | addPrefix: boolean; 60 | typedDataJson: string | null; 61 | } 62 | >; 63 | 64 | export type SignEthereumTransactionRequest = BaseWeb3Request< 65 | Web3Method.signEthereumTransaction, 66 | { 67 | fromAddress: AddressString; 68 | toAddress: AddressString | null; 69 | weiValue: BigIntString; 70 | data: HexString; 71 | nonce: IntNumber | null; 72 | gasPriceInWei: BigIntString | null; 73 | maxFeePerGas: BigIntString | null; // in wei 74 | maxPriorityFeePerGas: BigIntString | null; // in wei 75 | gasLimit: BigIntString | null; 76 | chainId: IntNumber; 77 | shouldSubmit: boolean; 78 | } 79 | >; 80 | 81 | export type SubmitEthereumTransactionRequest = BaseWeb3Request< 82 | Web3Method.submitEthereumTransaction, 83 | { 84 | signedTransaction: HexString; 85 | chainId: IntNumber; 86 | } 87 | >; 88 | 89 | export type EthereumAddressFromSignedMessageRequest = BaseWeb3Request< 90 | Web3Method.ethereumAddressFromSignedMessage, 91 | { 92 | message: HexString; 93 | signature: HexString; 94 | addPrefix: boolean; 95 | } 96 | >; 97 | 98 | export type ScanQRCodeRequest = BaseWeb3Request< 99 | Web3Method.scanQRCode, 100 | { 101 | regExp: RegExpString; 102 | } 103 | >; 104 | 105 | export type GenericRequest = BaseWeb3Request< 106 | Web3Method.generic, 107 | { 108 | action: string; 109 | data: object; 110 | } 111 | >; 112 | 113 | export type SelectProviderRequest = BaseWeb3Request< 114 | Web3Method.selectProvider, 115 | { providerOptions: ProviderType[] } 116 | >; 117 | 118 | export type MakeEthereumJSONRPCRequest = BaseWeb3Request< 119 | Web3Method.makeEthereumJSONRPCRequest, 120 | { 121 | rpcMethod: string; 122 | rpcParams: unknown[]; 123 | chainId: string; 124 | } 125 | >; 126 | 127 | type WatchAssetRequestBaseParams = { 128 | type: string; 129 | options: { 130 | address: string; 131 | symbol?: string; 132 | decimals?: number; 133 | image?: string; 134 | }; 135 | }; 136 | 137 | export type WatchAssetRequest = BaseWeb3Request< 138 | Web3Method.watchAsset, 139 | WatchAssetRequestBaseParams & { 140 | chainId?: string; 141 | } 142 | >; 143 | 144 | export type Web3Request = 145 | | RequestEthereumAccountsRequest 146 | | SignEthereumMessageRequest 147 | | SignEthereumTransactionRequest 148 | | SubmitEthereumTransactionRequest 149 | | EthereumAddressFromSignedMessageRequest 150 | | ScanQRCodeRequest 151 | | GenericRequest 152 | | AddEthereumChainRequest 153 | | SwitchEthereumChainRequest 154 | | MakeEthereumJSONRPCRequest 155 | | WatchAssetRequest 156 | | SelectProviderRequest; 157 | -------------------------------------------------------------------------------- /packages/wallet-sdk/src/relay/Web3RequestCanceledMessage.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018-2022 Coinbase, Inc. 2 | // Licensed under the Apache License, version 2.0 3 | 4 | import { RelayMessage, RelayMessageType } from "./RelayMessage"; 5 | 6 | export interface Web3RequestCanceledMessage 7 | extends RelayMessage { 8 | id: string; 9 | } 10 | 11 | export function Web3RequestCanceledMessage( 12 | id: string, 13 | ): Web3RequestCanceledMessage { 14 | return { type: RelayMessageType.WEB3_REQUEST_CANCELED, id }; 15 | } 16 | -------------------------------------------------------------------------------- /packages/wallet-sdk/src/relay/Web3RequestMessage.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018-2022 Coinbase, Inc. 2 | // Licensed under the Apache License, version 2.0 3 | 4 | import { RelayMessage, RelayMessageType } from "./RelayMessage"; 5 | import { Web3Request } from "./Web3Request"; 6 | 7 | export interface Web3RequestMessage 8 | extends RelayMessage { 9 | id: string; 10 | request: Web3Request; 11 | } 12 | 13 | export function Web3RequestMessage( 14 | params: Omit, 15 | ): Web3RequestMessage { 16 | return { type: RelayMessageType.WEB3_REQUEST, ...params }; 17 | } 18 | -------------------------------------------------------------------------------- /packages/wallet-sdk/src/relay/Web3ResponseMessage.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018-2022 Coinbase, Inc. 2 | // Licensed under the Apache License, version 2.0 3 | 4 | import { RelayMessage, RelayMessageType } from "./RelayMessage"; 5 | import { Web3Response } from "./Web3Response"; 6 | 7 | export interface Web3ResponseMessage 8 | extends RelayMessage { 9 | type: RelayMessageType.WEB3_RESPONSE; 10 | id: string; 11 | response: Web3Response; 12 | } 13 | 14 | export function Web3ResponseMessage( 15 | params: Omit, 16 | ): Web3ResponseMessage { 17 | return { type: RelayMessageType.WEB3_RESPONSE, ...params }; 18 | } 19 | 20 | export function isWeb3ResponseMessage(msg: any): msg is Web3ResponseMessage { 21 | return msg && msg.type === RelayMessageType.WEB3_RESPONSE; 22 | } 23 | -------------------------------------------------------------------------------- /packages/wallet-sdk/src/relay/aes256gcm.test.ts: -------------------------------------------------------------------------------- 1 | import { randomBytesHex } from "../util"; 2 | import { decrypt, encrypt } from "./aes256gcm"; 3 | 4 | const secret = 5 | "c356fe708ea7bbf7b1cc9ff9813c32772b6e0d16332da4c031ba9ea88be9b5ed"; 6 | 7 | describe("aes256gcm", () => { 8 | test("encrypt/decrypt", async () => { 9 | const randSecret = randomBytesHex(32); 10 | const encrypted = await encrypt("plain text string", randSecret); 11 | 12 | expect(encrypted.length).toEqual(90); 13 | 14 | // decrypted output matches original input 15 | const decrypted = await decrypt(encrypted, randSecret); 16 | 17 | expect(decrypted).toBe("plain text string"); 18 | }); 19 | 20 | test("decrypt", async () => { 21 | const cipherText = 22 | "06593325a922a928913b5c6ea26f848c4545bcea4e26c4f5ee745316ff22b2780aeccc565730514b2820a94b03f5f89fe7542a35bbdd87a1d52a4352f49482781113db09266c668696778e0a94bc9f866f1e92e7262fd0bb811838284cc64cbc4552b33e9c6fb2582cea4f49471d6d46a16a5c8ac83ee8483ed4dc01f1fde3bfd7a2f173715e0a8d09dd4907483f096a845bff698831ea277c1ca4223d3f6073174cb35119d0a795c1a9cb4f32ee1dcc254d8931"; 23 | const sampleDataResult = { 24 | type: "WEB3_RESPONSE", 25 | id: "791fe0ec3dc3de49", 26 | response: { 27 | method: "requestEthereumAccounts", 28 | result: ["0xdf0635793e91d4f8e7426dbd9ed08471186f428d"], 29 | }, 30 | }; 31 | 32 | const decrypted = await decrypt(cipherText, secret); 33 | 34 | expect(sampleDataResult).toEqual(JSON.parse(decrypted)); 35 | }); 36 | 37 | test("errors", async () => { 38 | await expect(encrypt("plain text string", "123456")).rejects.toThrowError( 39 | "secret must be 256 bits", 40 | ); 41 | 42 | expect(() => decrypt("plain stext string", "123456")).toThrowError( 43 | "secret must be 256 bits", 44 | ); 45 | 46 | await expect(decrypt("text", secret)).rejects.toThrow(); 47 | }); 48 | }); 49 | -------------------------------------------------------------------------------- /packages/wallet-sdk/src/relay/aes256gcm.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018-2022 Coinbase, Inc. 2 | // Licensed under the Apache License, version 2.0 3 | 4 | import { hexStringToUint8Array, uint8ArrayToHex } from "../util"; 5 | 6 | /** 7 | * 8 | * @param plainText string to be encrypted 9 | * @param secret hex representation of 32-byte secret 10 | * returns hex string representation of bytes in the order: initialization vector (iv), 11 | * auth tag, encrypted plaintext. IV is 12 bytes. Auth tag is 16 bytes. Remaining bytes are the 12 | * encrypted plainText. 13 | */ 14 | export async function encrypt( 15 | plainText: string, 16 | secret: string, 17 | ): Promise { 18 | if (secret.length !== 64) throw Error(`secret must be 256 bits`); 19 | const ivBytes = crypto.getRandomValues(new Uint8Array(12)); 20 | const secretKey: CryptoKey = await crypto.subtle.importKey( 21 | "raw", 22 | hexStringToUint8Array(secret), 23 | { name: "aes-gcm" }, 24 | false, 25 | ["encrypt", "decrypt"], 26 | ); 27 | 28 | const enc = new TextEncoder(); 29 | 30 | // Will return encrypted plainText with auth tag (ie MAC or checksum) appended at the end 31 | const encryptedResult: ArrayBuffer = await window.crypto.subtle.encrypt( 32 | { 33 | name: "AES-GCM", 34 | iv: ivBytes, 35 | }, 36 | secretKey, 37 | enc.encode(plainText), 38 | ); 39 | 40 | const tagLength = 16; 41 | const authTag: ArrayBuffer = encryptedResult.slice( 42 | encryptedResult.byteLength - tagLength, 43 | ); 44 | const encryptedPlaintext = encryptedResult.slice( 45 | 0, 46 | encryptedResult.byteLength - tagLength, 47 | ); 48 | 49 | const authTagBytes = new Uint8Array(authTag); 50 | const encryptedPlaintextBytes = new Uint8Array(encryptedPlaintext); 51 | const concatted = new Uint8Array([ 52 | ...ivBytes, 53 | ...authTagBytes, 54 | ...encryptedPlaintextBytes, 55 | ]); 56 | return uint8ArrayToHex(concatted); 57 | } 58 | 59 | /** 60 | * 61 | * @param cipherText hex string representation of bytes in the order: initialization vector (iv), 62 | * auth tag, encrypted plaintext. IV is 12 bytes. Auth tag is 16 bytes. 63 | * @param secret hex string representation of 32-byte secret 64 | */ 65 | export function decrypt(cipherText: string, secret: string): Promise { 66 | if (secret.length !== 64) throw Error(`secret must be 256 bits`); 67 | return new Promise((resolve, reject) => { 68 | void (async function () { 69 | const secretKey: CryptoKey = await crypto.subtle.importKey( 70 | "raw", 71 | hexStringToUint8Array(secret), 72 | { name: "aes-gcm" }, 73 | false, 74 | ["encrypt", "decrypt"], 75 | ); 76 | 77 | const encrypted: Uint8Array = hexStringToUint8Array(cipherText); 78 | 79 | const ivBytes = encrypted.slice(0, 12); 80 | const authTagBytes = encrypted.slice(12, 28); 81 | const encryptedPlaintextBytes = encrypted.slice(28); 82 | const concattedBytes = new Uint8Array([ 83 | ...encryptedPlaintextBytes, 84 | ...authTagBytes, 85 | ]); 86 | const algo = { 87 | name: "AES-GCM", 88 | iv: new Uint8Array(ivBytes), 89 | }; 90 | try { 91 | const decrypted = await window.crypto.subtle.decrypt( 92 | algo, 93 | secretKey, 94 | concattedBytes, 95 | ); 96 | const decoder = new TextDecoder(); 97 | resolve(decoder.decode(decrypted)); 98 | } catch (err) { 99 | reject(err); 100 | } 101 | })(); 102 | }); 103 | } 104 | -------------------------------------------------------------------------------- /packages/wallet-sdk/src/relay/solana/SolanaWeb3Method.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018-2022 Coinbase, Inc. 2 | // Licensed under the Apache License, version 2.0 3 | 4 | export enum SolanaWeb3Method { 5 | connect = "connect", 6 | signMessage = "signMessage", 7 | signTransaction = "signTransaction", 8 | signAllTransactions = "signAllTransactions", 9 | sendTransaction = "sendTransaction", 10 | } 11 | -------------------------------------------------------------------------------- /packages/wallet-sdk/src/relay/solana/SolanaWeb3Request.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018-2022 Coinbase, Inc. 2 | // Licensed under the Apache License, version 2.0 3 | 4 | import { SendOptions, Signer, Transaction } from "@solana/web3.js"; 5 | 6 | import { SolanaWeb3Method } from "./SolanaWeb3Method"; 7 | 8 | interface BaseSolanaWeb3Request< 9 | Method extends SolanaWeb3Method, 10 | Params extends object = Record, 11 | > { 12 | method: Method; 13 | params: Params; 14 | } 15 | 16 | // this is for consistency with solana-labs/wallet-adapter 17 | interface SendTransactionOptions extends SendOptions { 18 | signers?: Signer[]; 19 | } 20 | 21 | export type SolanaSignTransactionRequest = BaseSolanaWeb3Request< 22 | SolanaWeb3Method.signTransaction, 23 | { 24 | transactions: Transaction[]; 25 | } 26 | >; 27 | 28 | export type SolanaSignAllTransactionsRequest = BaseSolanaWeb3Request< 29 | SolanaWeb3Method.signAllTransactions, 30 | { 31 | transactions: Transaction[]; 32 | } 33 | >; 34 | 35 | export type SolanaSignMessageRequest = BaseSolanaWeb3Request< 36 | SolanaWeb3Method.signMessage, 37 | { 38 | address: string; 39 | message: string; 40 | } 41 | >; 42 | 43 | export type SolanaSendTransactionRequest = BaseSolanaWeb3Request< 44 | SolanaWeb3Method.sendTransaction, 45 | { 46 | transactions: Transaction[]; 47 | options: SendTransactionOptions; 48 | } 49 | >; 50 | 51 | export type SolanaTransactionRequest = 52 | | SolanaSignTransactionRequest 53 | | SolanaSendTransactionRequest 54 | | SolanaSignAllTransactionsRequest; 55 | -------------------------------------------------------------------------------- /packages/wallet-sdk/src/relay/solana/SolanaWeb3Response.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018-2022 Coinbase, Inc. 2 | // Licensed under the Apache License, version 2.0 3 | 4 | export enum SolanaWeb3Response { 5 | connectionSuccess = "solanaConnectionSuccess", 6 | connectionDeny = "solanaConnectionDeny", 7 | signMessageSuccess = "signSolanaMessageSuccess", 8 | signMessageDeny = "signSolanaMessageDeny", 9 | sendTransactionSuccess = "sendSolanaTransactionSuccess", 10 | sendTransactionDeny = "sendSolanaTransactionDeny", 11 | signTransactionSuccess = "signSolanaTransactionSuccess", 12 | signTransactionDeny = "signaSolanaTransactionDeny", 13 | signAllTransactionsSuccess = "signAllSolanaTransactionsSuccess", 14 | signAllTransactionsDeny = "signAllSolanaTransactionsDeny", 15 | parentDisconnected = "parentDisconnected", 16 | featureFlagOff = "featureFlagOff", 17 | web3RequestCanceled = "web3RequestCanceled", 18 | } 19 | -------------------------------------------------------------------------------- /packages/wallet-sdk/src/types.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018-2022 Coinbase, Inc. 2 | // Licensed under the Apache License, version 2.0 3 | 4 | interface Tag { 5 | __tag__: T; 6 | __realType__: RealType; 7 | } 8 | 9 | export type OpaqueType = U & Tag; 10 | 11 | export function OpaqueType>() { 12 | return (value: T extends Tag ? U : never): T => value as T; 13 | } 14 | 15 | export type HexString = OpaqueType<"HexString", string>; 16 | export const HexString = OpaqueType(); 17 | 18 | export type AddressString = OpaqueType<"AddressString", string>; 19 | export const AddressString = OpaqueType(); 20 | 21 | export type BigIntString = OpaqueType<"BigIntString", string>; 22 | export const BigIntString = OpaqueType(); 23 | 24 | export type IntNumber = OpaqueType<"IntNumber", number>; 25 | export function IntNumber(num: number): IntNumber { 26 | return Math.floor(num) as IntNumber; 27 | } 28 | 29 | export type RegExpString = OpaqueType<"RegExpString", string>; 30 | export const RegExpString = OpaqueType(); 31 | 32 | export type Callback = (err: Error | null, result: T | null) => void; 33 | 34 | export enum ProviderType { 35 | CoinbaseWallet = "CoinbaseWallet", 36 | MetaMask = "MetaMask", 37 | Unselected = "", 38 | } 39 | -------------------------------------------------------------------------------- /packages/wallet-sdk/src/vendor-js/eth-eip712-util/LICENSE: -------------------------------------------------------------------------------- 1 | ISC License (ISC) 2 | Copyright 2017-2020 Dan Finlay 3 | 4 | Permission to use, copy, modify, and/or distribute this software for any purpose 5 | with or without fee is hereby granted, provided that the above copyright notice 6 | and this permission notice appear in all copies. 7 | 8 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 9 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND 10 | FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 11 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS 12 | OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER 13 | TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF 14 | THIS SOFTWARE. -------------------------------------------------------------------------------- /packages/wallet-sdk/src/vendor-js/eth-eip712-util/index.d.ts: -------------------------------------------------------------------------------- 1 | const module: { 2 | hashForSignTypedDataLegacy: (params: { data: any }) => Buffer 3 | hashForSignTypedData_v3: (params: { data: any }) => Buffer 4 | hashForSignTypedData_v4: (params: { data: any }) => Buffer 5 | TypedDataUtils: { 6 | sanitizeData: (data: any) => any, 7 | hashStruct: (primaryType: string, data: any, types: any, useV4: any) => Buffer 8 | } 9 | } 10 | 11 | export = module 12 | -------------------------------------------------------------------------------- /packages/wallet-sdk/src/vendor-js/eth-eip712-util/util.js: -------------------------------------------------------------------------------- 1 | // Extracted from https://github.com/ethereumjs/ethereumjs-util and stripped out irrelevant code 2 | // Original code licensed under the Mozilla Public License Version 2.0 3 | 4 | const createKeccakHash = require('keccak/js') 5 | const BN = require('bn.js') 6 | 7 | /** 8 | * Returns a buffer filled with 0s 9 | * @method zeros 10 | * @param {Number} bytes the number of bytes the buffer should be 11 | * @return {Buffer} 12 | */ 13 | function zeros (bytes) { 14 | return Buffer.allocUnsafe(bytes).fill(0) 15 | } 16 | 17 | /** 18 | * Left Pads an `Array` or `Buffer` with leading zeros till it has `length` bytes. 19 | * Or it truncates the beginning if it exceeds. 20 | * @method setLength 21 | * @param {Buffer|Array} msg the value to pad 22 | * @param {Number} length the number of bytes the output should be 23 | * @param {Boolean} [right=false] whether to start padding form the left or right 24 | * @return {Buffer|Array} 25 | */ 26 | function setLength (msg, length, right) { 27 | const buf = zeros(length) 28 | msg = toBuffer(msg) 29 | if (right) { 30 | if (msg.length < length) { 31 | msg.copy(buf) 32 | return buf 33 | } 34 | return msg.slice(0, length) 35 | } else { 36 | if (msg.length < length) { 37 | msg.copy(buf, length - msg.length) 38 | return buf 39 | } 40 | return msg.slice(-length) 41 | } 42 | } 43 | 44 | /** 45 | * Right Pads an `Array` or `Buffer` with leading zeros till it has `length` bytes. 46 | * Or it truncates the beginning if it exceeds. 47 | * @param {Buffer|Array} msg the value to pad 48 | * @param {Number} length the number of bytes the output should be 49 | * @return {Buffer|Array} 50 | */ 51 | function setLengthRight (msg, length) { 52 | return setLength(msg, length, true) 53 | } 54 | 55 | /** 56 | * Attempts to turn a value into a `Buffer`. As input it supports `Buffer`, `String`, `Number`, null/undefined, `BN` and other objects with a `toArray()` method. 57 | * @param {*} v the value 58 | */ 59 | function toBuffer (v) { 60 | if (!Buffer.isBuffer(v)) { 61 | if (Array.isArray(v)) { 62 | v = Buffer.from(v) 63 | } else if (typeof v === 'string') { 64 | if (isHexString(v)) { 65 | v = Buffer.from(padToEven(stripHexPrefix(v)), 'hex') 66 | } else { 67 | v = Buffer.from(v) 68 | } 69 | } else if (typeof v === 'number') { 70 | v = intToBuffer(v) 71 | } else if (v === null || v === undefined) { 72 | v = Buffer.allocUnsafe(0) 73 | } else if (BN.isBN(v)) { 74 | v = v.toArrayLike(Buffer) 75 | } else if (v.toArray) { 76 | // converts a BN to a Buffer 77 | v = Buffer.from(v.toArray()) 78 | } else { 79 | throw new Error('invalid type') 80 | } 81 | } 82 | return v 83 | } 84 | 85 | /** 86 | * Converts a `Buffer` into a hex `String` 87 | * @param {Buffer} buf 88 | * @return {String} 89 | */ 90 | function bufferToHex (buf) { 91 | buf = toBuffer(buf) 92 | return '0x' + buf.toString('hex') 93 | } 94 | 95 | /** 96 | * Creates Keccak hash of the input 97 | * @param {Buffer|Array|String|Number} a the input data 98 | * @param {Number} [bits=256] the Keccak width 99 | * @return {Buffer} 100 | */ 101 | function keccak (a, bits) { 102 | a = toBuffer(a) 103 | if (!bits) bits = 256 104 | 105 | return createKeccakHash('keccak' + bits).update(a).digest() 106 | } 107 | 108 | function padToEven (str) { 109 | return str.length % 2 ? '0' + str : str 110 | } 111 | 112 | function isHexString (str) { 113 | return typeof str === 'string' && str.match(/^0x[0-9A-Fa-f]*$/) 114 | } 115 | 116 | function stripHexPrefix (str) { 117 | if (typeof str === 'string' && str.startsWith('0x')) { 118 | return str.slice(2) 119 | } 120 | return str 121 | } 122 | 123 | module.exports = { 124 | zeros, 125 | setLength, 126 | setLengthRight, 127 | isHexString, 128 | stripHexPrefix, 129 | toBuffer, 130 | bufferToHex, 131 | keccak 132 | } 133 | -------------------------------------------------------------------------------- /packages/wallet-sdk/src/vendor-js/qrcode-svg/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 papnkukn 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /packages/wallet-sdk/src/version.ts: -------------------------------------------------------------------------------- 1 | export const LIB_VERSION = "3.6.4"; 2 | -------------------------------------------------------------------------------- /packages/wallet-sdk/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig", 3 | "compilerOptions": { 4 | "declaration": true, 5 | "module": "commonjs", 6 | "moduleResolution": "node", 7 | "outDir": "./build/npm/dist", 8 | "target": "es2017" 9 | }, 10 | "include": ["src", "src/@types"], 11 | "exclude": ["**/*.test.*", "__tests__", "examples"] 12 | } 13 | -------------------------------------------------------------------------------- /packages/wallet-sdk/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowJs": false, 4 | "allowSyntheticDefaultImports": true, 5 | "esModuleInterop": true, 6 | "experimentalDecorators": true, 7 | "forceConsistentCasingInFileNames": true, 8 | "jsx": "react", 9 | "jsxFactory": "h", 10 | "lib": ["dom", "dom.iterable", "es2016"], 11 | "module": "commonjs", 12 | "moduleResolution": "node", 13 | "noErrorTruncation": true, 14 | "noFallthroughCasesInSwitch": true, 15 | "noImplicitReturns": true, 16 | "noUnusedLocals": true, 17 | "noUnusedParameters": true, 18 | "resolveJsonModule": true, 19 | "skipLibCheck": true, 20 | "strict": true, 21 | "target": "es2016", 22 | "types": ["node", "jest"] 23 | }, 24 | "include": ["src", "src/@types", "jest.setup.*", "__tests__", "**/*.test.*"] 25 | } 26 | -------------------------------------------------------------------------------- /packages/wallet-sdk/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const webpack = require("webpack"); 3 | const { env } = process; 4 | 5 | const tsConfigPath = (exports.tsConfigPath = path.join( 6 | __dirname, 7 | "tsconfig.json", 8 | )); 9 | 10 | module.exports = { 11 | target: "web", 12 | entry: [ 13 | "core-js/stable", 14 | "regenerator-runtime/runtime", 15 | "whatwg-fetch", 16 | "./src/index.ts", 17 | ], 18 | // devtool: 'inline-source-map', 19 | mode: "production", 20 | module: { 21 | rules: [ 22 | { 23 | test: /\.tsx?$/, 24 | use: { 25 | loader: "ts-loader", 26 | options: { 27 | configFile: tsConfigPath, 28 | }, 29 | }, 30 | exclude: /node_modules/, 31 | }, 32 | ], 33 | }, 34 | resolve: { 35 | fallback: { 36 | fs: false, 37 | stream: require.resolve("stream-browserify"), 38 | buffer: require.resolve("buffer/"), 39 | util: require.resolve("util/"), 40 | }, 41 | extensions: [".ts", ".tsx", ".js"], 42 | plugins: [], 43 | symlinks: false, 44 | }, 45 | output: { 46 | filename: "CoinbaseWalletSDK.js", 47 | path: path.resolve(__dirname, "build"), 48 | }, 49 | performance: { 50 | hints: false, 51 | }, 52 | plugins: [ 53 | new webpack.DefinePlugin({ 54 | "process.env": { 55 | NODE_ENV: JSON.stringify(env.NODE_ENV) || JSON.stringify("production"), 56 | LINK_API_URL: JSON.stringify(env.LINK_API_URL), 57 | SDK_VERSION: JSON.stringify(require("./package.json").version), 58 | }, 59 | }), 60 | new webpack.ProvidePlugin({ 61 | Buffer: ["buffer", "Buffer"], 62 | }), 63 | ], 64 | }; 65 | --------------------------------------------------------------------------------