├── .eslintignore ├── .eslintrc.json ├── .gitbook.yaml ├── .github ├── FUNDING.yml ├── renovate.json └── workflows │ ├── ci.yml │ └── github-releases-to-discord.yml ├── .gitignore ├── .prettierignore ├── .prettierrc ├── .vscode └── settings.json ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── docs ├── README.md ├── SUMMARY.md ├── api-reference │ ├── usenetwork.md │ ├── usewallet.md │ └── walletmanager.md ├── framework │ ├── react.md │ ├── solidjs.md │ └── vue.md ├── getting-started │ ├── configuration.md │ ├── installation.md │ └── supported-wallets.md ├── guides │ ├── connect-wallet-menu.md │ ├── custom-provider.md │ ├── migrating-from-v3.x.md │ ├── runtime-node-configuration.md │ ├── signing-data.md │ ├── signing-transactions.md │ ├── switching-networks.md │ └── testing-with-mnemonic-wallet.md └── resources │ ├── algokit-templates.md │ └── example-projects.md ├── examples ├── e2e-tests │ ├── FakeAlgodResponses.ts │ └── example.spec.ts ├── nextjs │ ├── .eslintrc.json │ ├── .gitignore │ ├── README.md │ ├── next.config.mjs │ ├── package.json │ ├── playwright.config.ts │ ├── public │ │ ├── next.svg │ │ └── vercel.svg │ ├── src │ │ └── app │ │ │ ├── Connect.module.css │ │ │ ├── Connect.tsx │ │ │ ├── favicon.ico │ │ │ ├── globals.css │ │ │ ├── layout.tsx │ │ │ ├── page.module.css │ │ │ ├── page.tsx │ │ │ └── providers.tsx │ └── tsconfig.json ├── nuxt │ ├── .gitignore │ ├── README.md │ ├── app.vue │ ├── components │ │ ├── Connect.vue │ │ └── Welcome.vue │ ├── nuxt.config.ts │ ├── package.json │ ├── playwright.config.ts │ ├── plugins │ │ └── walletManager.client.ts │ ├── public │ │ └── favicon.ico │ ├── server │ │ └── tsconfig.json │ └── tsconfig.json ├── react-ts │ ├── .eslintignore │ ├── .eslintrc.cjs │ ├── .gitignore │ ├── README.md │ ├── index.html │ ├── package.json │ ├── playwright.config.ts │ ├── public │ │ └── vite.svg │ ├── src │ │ ├── App.css │ │ ├── App.tsx │ │ ├── Connect.tsx │ │ ├── NetworkControls.tsx │ │ ├── assets │ │ │ └── react.svg │ │ ├── index.css │ │ ├── main.tsx │ │ └── vite-env.d.ts │ ├── tsconfig.json │ ├── tsconfig.node.json │ └── vite.config.ts ├── solid-ts │ ├── .gitignore │ ├── README.md │ ├── index.html │ ├── package.json │ ├── playwright.config.ts │ ├── public │ │ └── vite.svg │ ├── src │ │ ├── App.css │ │ ├── App.tsx │ │ ├── Connect.tsx │ │ ├── NetworkControls.tsx │ │ ├── assets │ │ │ └── solid.svg │ │ ├── index.css │ │ ├── index.tsx │ │ └── vite-env.d.ts │ ├── tsconfig.json │ ├── tsconfig.node.json │ └── vite.config.ts ├── vanilla-ts │ ├── .eslintignore │ ├── .gitignore │ ├── .prettierignore │ ├── README.md │ ├── index.html │ ├── package.json │ ├── playwright.config.ts │ ├── public │ │ └── vite.svg │ ├── src │ │ ├── ActiveNetwork.ts │ │ ├── WalletComponent.ts │ │ ├── main.ts │ │ ├── style.css │ │ ├── typescript.svg │ │ └── vite-env.d.ts │ ├── tsconfig.json │ └── vite.config.ts └── vue-ts │ ├── .gitignore │ ├── .vscode │ └── extensions.json │ ├── README.md │ ├── index.html │ ├── package.json │ ├── playwright.config.ts │ ├── public │ └── vite.svg │ ├── src │ ├── App.vue │ ├── assets │ │ └── vue.svg │ ├── components │ │ ├── Connect.vue │ │ └── NetworkControls.vue │ ├── main.ts │ ├── style.css │ └── vite-env.d.ts │ ├── tsconfig.json │ ├── tsconfig.node.json │ └── vite.config.ts ├── package.json ├── packages ├── use-wallet-react │ ├── README.md │ ├── package.json │ ├── src │ │ ├── __tests__ │ │ │ └── index.test.tsx │ │ └── index.tsx │ ├── tsconfig.json │ ├── tsup.config.ts │ ├── vite.config.ts │ ├── vitest.config.ts │ └── vitest.setup.ts ├── use-wallet-solid │ ├── README.md │ ├── package.json │ ├── src │ │ ├── __tests__ │ │ │ └── index.test.tsx │ │ └── index.tsx │ ├── tsconfig.json │ ├── tsup.config.ts │ ├── vite.config.ts │ ├── vitest.config.ts │ └── vitest.setup.ts ├── use-wallet-vue │ ├── README.md │ ├── package.json │ ├── src │ │ ├── __tests__ │ │ │ ├── useNetwork.test.ts │ │ │ ├── useWallet.test.ts │ │ │ └── walletManagerPlugin.test.ts │ │ ├── index.ts │ │ ├── useNetwork.ts │ │ ├── useWallet.ts │ │ └── walletManagerPlugin.ts │ ├── tsconfig.json │ ├── tsup.config.ts │ ├── vite.config.ts │ ├── vitest.config.ts │ └── vitest.setup.ts └── use-wallet │ ├── README.md │ ├── package.json │ ├── setupTests.ts │ ├── src │ ├── __tests__ │ │ ├── logger.test.ts │ │ ├── manager.test.ts │ │ ├── network.test.ts │ │ ├── store.test.ts │ │ ├── utils.test.ts │ │ └── wallets │ │ │ ├── custom.test.ts │ │ │ ├── defly-web.test.ts │ │ │ ├── defly.test.ts │ │ │ ├── exodus.test.ts │ │ │ ├── kibisis.test.ts │ │ │ ├── kmd.test.ts │ │ │ ├── lute.test.ts │ │ │ ├── magic.test.ts │ │ │ ├── mnemonic.test.ts │ │ │ ├── pera.test.ts │ │ │ └── walletconnect.test.ts │ ├── index.ts │ ├── logger.ts │ ├── manager.ts │ ├── network.ts │ ├── storage.ts │ ├── store.ts │ ├── utils.ts │ ├── wallets │ │ ├── avm-web-provider.ts │ │ ├── base.ts │ │ ├── biatec.ts │ │ ├── custom.ts │ │ ├── defly-web.ts │ │ ├── defly.ts │ │ ├── exodus.ts │ │ ├── index.ts │ │ ├── kibisis.ts │ │ ├── kmd.ts │ │ ├── lute.ts │ │ ├── magic.ts │ │ ├── mnemonic.ts │ │ ├── pera.ts │ │ ├── types.ts │ │ └── walletconnect.ts │ └── webpack.ts │ ├── tsconfig.json │ ├── tsup.config.ts │ └── vitest.config.ts ├── pnpm-lock.yaml └── pnpm-workspace.yaml /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | .env 4 | *.code-workspace 5 | docs/** 6 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"], 4 | "parser": "@typescript-eslint/parser", 5 | "plugins": ["@typescript-eslint", "prettier"], 6 | "rules": { 7 | "@typescript-eslint/no-explicit-any": "off", 8 | "@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }] 9 | }, 10 | "ignorePatterns": ["node_modules/", "dist/", ".env"] 11 | } 12 | -------------------------------------------------------------------------------- /.gitbook.yaml: -------------------------------------------------------------------------------- 1 | root: ./docs/ 2 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [TxnLab] 2 | -------------------------------------------------------------------------------- /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:recommended", 5 | ":pinAllExceptPeerDependencies", 6 | ":semanticCommitTypeAll(chore)", 7 | "schedule:weekly" 8 | ], 9 | "dependencyDashboard": true, 10 | "configMigration": true, 11 | "timezone": "America/New_York", 12 | "assignees": ["drichar"], 13 | "baseBranches": ["main"], 14 | "ignorePaths": ["**/node_modules/**"], 15 | "minimumReleaseAge": "3 days", 16 | "lockFileMaintenance": { 17 | "enabled": true, 18 | "schedule": "before 4am on Tuesday" 19 | }, 20 | "packageRules": [ 21 | { 22 | "groupName": "non-major dependencies", 23 | "matchPackageNames": ["!typescript", "!algosdk"], 24 | "matchUpdateTypes": ["patch", "minor"], 25 | "matchDepTypes": ["dependencies", "devDependencies", "action"] 26 | }, 27 | { 28 | "groupName": "Wallet dependencies", 29 | "rangeStrategy": "bump", 30 | "matchPackageNames": [ 31 | "/@agoralabs-sh/avm-web-provider/", 32 | "/@magic-ext/algorand/", 33 | "/magic-sdk/", 34 | "/lute-connect/", 35 | "/walletconnect/" 36 | ] 37 | }, 38 | { 39 | "matchPackageNames": ["@blockshake/defly-connect", "@perawallet/connect"], 40 | "enabled": false 41 | }, 42 | { 43 | "groupName": "pnpm", 44 | "matchPackageNames": ["pnpm"], 45 | "matchDatasources": ["npm"], 46 | "matchUpdateTypes": ["minor", "patch"] 47 | }, 48 | { 49 | "matchCategories": ["node"], 50 | "enabled": false 51 | }, 52 | { 53 | "matchPackageNames": ["@types/node"], 54 | "enabled": false 55 | }, 56 | { 57 | "matchDepTypes": ["engines"], 58 | "enabled": false 59 | } 60 | ], 61 | "customManagers": [ 62 | { 63 | "customType": "regex", 64 | "fileMatch": ["^package\\.json$"], 65 | "matchStrings": ["\"packageManager\":\\s*\"pnpm@(?\\d+\\.\\d+\\.\\d+)\""], 66 | "depNameTemplate": "pnpm", 67 | "datasourceTemplate": "npm" 68 | }, 69 | { 70 | "customType": "regex", 71 | "fileMatch": ["^\\.github/workflows/[^/]+\\.ya?ml$"], 72 | "matchStrings": ["\\s+version:\\s*(?\\d+\\.\\d+\\.\\d+)"], 73 | "depNameTemplate": "pnpm", 74 | "datasourceTemplate": "npm" 75 | } 76 | ] 77 | } 78 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | pull_request: 5 | branches: [main, v4] 6 | push: 7 | branches: [main, v4] 8 | 9 | jobs: 10 | run-ci: 11 | name: Build, Lint, Typecheck, and Test 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - name: Checkout repository 16 | uses: actions/checkout@v4 17 | with: 18 | fetch-depth: 0 19 | 20 | - name: Install Node.js 21 | uses: actions/setup-node@v4 22 | with: 23 | node-version: 20 24 | 25 | - uses: pnpm/action-setup@v4 26 | name: Install pnpm 27 | with: 28 | version: 9.15.9 29 | run_install: false 30 | 31 | - name: Get pnpm store directory 32 | shell: bash 33 | run: | 34 | echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV 35 | 36 | - uses: actions/cache@v4 37 | name: Setup pnpm cache 38 | with: 39 | path: ${{ env.STORE_PATH }} 40 | key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} 41 | restore-keys: | 42 | ${{ runner.os }}-pnpm-store- 43 | 44 | - name: Install dependencies 45 | run: pnpm install 46 | 47 | - name: Build 48 | run: pnpm build 49 | 50 | - name: Lint 51 | run: pnpm lint 52 | 53 | - name: Prettier 54 | run: pnpm prettier 55 | 56 | - name: Typecheck 57 | run: pnpm typecheck 58 | 59 | - name: Run tests 60 | run: pnpm test 61 | -------------------------------------------------------------------------------- /.github/workflows/github-releases-to-discord.yml: -------------------------------------------------------------------------------- 1 | on: 2 | release: 3 | types: [published] 4 | 5 | jobs: 6 | github-releases-to-discord: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: Checkout 10 | uses: actions/checkout@v4 11 | - name: Github Releases To Discord 12 | uses: SethCohen/github-releases-to-discord@v1.16.2 13 | with: 14 | webhook_url: ${{ secrets.WEBHOOK_URL }} 15 | username: 'Use-Wallet Releases' 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | .env 4 | *.code-workspace 5 | .nvmrc 6 | coverage 7 | 8 | # Playwright 9 | **/test-results/ 10 | **/playwright-report/ 11 | **/blob-report/ 12 | **/playwright/.cache/ 13 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | **/node_modules 2 | **/dist 3 | **/.env 4 | **/.next 5 | **/.nuxt 6 | **/.output 7 | pnpm-lock.yaml 8 | *.code-workspace 9 | docs/** 10 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": true, 4 | "printWidth": 100, 5 | "trailingComma": "none" 6 | } 7 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.tabSize": 2, 3 | "editor.insertSpaces": true, 4 | "editor.detectIndentation": false, 5 | "editor.formatOnSave": true, 6 | "git.closeDiffOnOperation": true, 7 | "javascript.validate.enable": true, 8 | "eslint.workingDirectories": [ 9 | "./packages/use-wallet", 10 | "./packages/use-wallet-react", 11 | "./packages/use-wallet-solid", 12 | "./packages/use-wallet-vue", 13 | "./examples/nextjs", 14 | "./examples/nuxt", 15 | "./examples/react-ts", 16 | "./examples/solid-ts", 17 | "./examples/vanilla-ts", 18 | "./examples/vue-ts" 19 | ], 20 | "typescript.tsdk": "./packages/use-wallet/node_modules/typescript/lib" 21 | } 22 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2024 TxnLab Inc. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # @txnlab/use-wallet 2 | 3 | [![GitHub package.json version](https://img.shields.io/github/package-json/v/TxnLab/use-wallet?filename=packages%2Fuse-wallet%2Fpackage.json&label=version)](https://www.npmjs.com/package/@txnlab/use-wallet?activeTab=versions) 4 | [![GitHub License](https://img.shields.io/github/license/TxnLab/use-wallet)](https://github.com/TxnLab/use-wallet/blob/main/LICENSE.md) 5 | 6 | A framework agnostic Algorand wallet integration library with reactive framework adapters for React, Vue, and Solid.js. 7 | 8 | ## Features 9 | 10 | - Easily add or remove wallet support with a few lines of code 11 | - Configure each wallet provider as needed for your application 12 | - Allow users to easily switch between active accounts and wallet providers 13 | - Sign and send transactions 14 | - Restore sessions for returning users 15 | - Full TypeScript support 16 | 17 | ### Visit [txnlab.gitbook.io/use-wallet](https://txnlab.gitbook.io/use-wallet) for docs, guides, and examples! 18 | 19 | ### [Become a sponsor!](https://github.com/sponsors/TxnLab/) 20 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Easily integrate Algorand wallets in your dApps 3 | --- 4 | 5 | # Overview 6 | 7 | Use-wallet is a comprehensive wallet management solution for Algorand/AVM dApps. It reduces the complexity of wallet integrations, letting developers focus on core application logic. 8 | 9 | The library is framework-agnostic and can be used in any modern front-end stack, with official adapters for [React](framework/react.md), [Vue](framework/vue.md), and [SolidJS](framework/solidjs.md). 10 | 11 | Version 4.x introduces several major improvements: 12 | 13 | * **Algorand SDK v3** - Full support for [Algorand JavaScript SDK](https://github.com/algorand/js-algorand-sdk) v3 14 | * **Runtime Node Configuration** - Let users connect to their private Algorand nodes ([guide](guides/runtime-node-configuration.md)) 15 | * **Custom Networks** - Add support for custom network configurations 16 | 17 | See the [Migration Guide](guides/migrating-from-v3.x.md) for help upgrading from v3.x. 18 | 19 | {% hint style="info" %} 20 | **Looking for v3 docs?** You can find the use-wallet v3.x documentation [here](https://txnlab.gitbook.io/use-wallet/v3). 21 | {% endhint %} 22 | 23 | ### Links 24 | 25 | * [GitHub Repository](https://github.com/TxnLab/use-wallet) 26 | * [Discord Support Channel](https://discord.gg/YkfksmJRrd) (#use-wallet on NFDomains Discord) 27 | 28 | {% embed url="https://www.youtube.com/watch?v=DdvrcBdnRCI" %} 29 | Awesome Algorand #22 - Doug Richar: How 'use-wallet' transformed Algorand wallet management 30 | {% endembed %} 31 | -------------------------------------------------------------------------------- /docs/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Table of contents 2 | 3 | ## Getting Started 4 | 5 | * [Overview](README.md) 6 | * [Installation](getting-started/installation.md) 7 | * [Configuration](getting-started/configuration.md) 8 | * [Supported Wallets](getting-started/supported-wallets.md) 9 | 10 | ## Framework Adapters 11 | 12 | * [React](framework/react.md) 13 | * [Vue](framework/vue.md) 14 | * [SolidJS](framework/solidjs.md) 15 | 16 | ## Guides & Concepts 17 | 18 | * [Connect Wallet Menu](guides/connect-wallet-menu.md) 19 | * [Signing Transactions](guides/signing-transactions.md) 20 | * [Signing Data](guides/signing-data.md) 21 | * [Switching Networks](guides/switching-networks.md) 22 | * [Runtime Node Configuration](guides/runtime-node-configuration.md) 23 | * [Testing with Mnemonic Wallet](guides/testing-with-mnemonic-wallet.md) 24 | * [Custom Provider](guides/custom-provider.md) 25 | * [Migrating from v3.x](guides/migrating-from-v3.x.md) 26 | 27 | ## API Reference 28 | 29 | * [WalletManager](api-reference/walletmanager.md) 30 | * [useWallet](api-reference/usewallet.md) 31 | * [useNetwork](api-reference/usenetwork.md) 32 | 33 | ## Resources 34 | 35 | * [Example Projects](resources/example-projects.md) 36 | * [AlgoKit Templates](resources/algokit-templates.md) 37 | -------------------------------------------------------------------------------- /docs/framework/react.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: 3 | title: 4 | visible: true 5 | description: 6 | visible: false 7 | tableOfContents: 8 | visible: true 9 | outline: 10 | visible: true 11 | pagination: 12 | visible: true 13 | --- 14 | 15 | # React 16 | 17 | The React adapter (`@txnlab/use-wallet-react`) provides a set of hooks and components for integrating use-wallet into React applications. This guide covers how to set up the adapter and use its features effectively. 18 | 19 | ### Setup 20 | 21 | After installing the package and any required wallet dependencies (see [Installation](../getting-started/installation.md)) and [configuring your WalletManager](../getting-started/configuration.md), wrap your application with the `WalletProvider`: 22 | 23 | ```tsx 24 | import { 25 | WalletProvider, 26 | WalletManager, 27 | NetworkId, 28 | } from '@txnlab/use-wallet-react' 29 | 30 | // Create manager instance (see Configuration guide) 31 | const manager = new WalletManager({ 32 | wallets: [...], 33 | networks: {...}, 34 | defaultNetwork: NetworkId.TESTNET // or just 'testnet' 35 | }) 36 | 37 | function App() { 38 | return ( 39 | 40 | 41 | 42 | ) 43 | } 44 | ``` 45 | 46 | The provider makes the wallet functionality available throughout your application via React hooks. 47 | 48 | ### Using the Hooks 49 | 50 | The React adapter provides two hooks for accessing wallet functionality. In v4.0.0, network-related features were moved from `useWallet` into a new `useNetwork` hook to provide better separation of concerns: 51 | 52 | #### useWallet 53 | 54 | The `useWallet` hook provides access to wallet management features. Here's an example showing some commonly used values: 55 | 56 | ```tsx 57 | import { useWallet } from '@txnlab/use-wallet-react' 58 | 59 | function WalletInfo() { 60 | const { 61 | wallets, // List of available wallets 62 | activeWallet, // Currently active wallet 63 | activeAddress, // Address of active account 64 | isReady, // Whether all wallet providers have finished initialization 65 | signTransactions, // Function to sign transactions 66 | transactionSigner, // Typed signer for ATC and Algokit Utils 67 | algodClient // Algod client for active network 68 | } = useWallet() 69 | 70 | // Checking isReady is important, especially in SSR environments 71 | // to prevent hydration errors 72 | if (!isReady) { 73 | return
Loading...
74 | } 75 | 76 | return ( 77 |
78 | {activeAddress ? ( 79 |
Connected: {activeAddress}
80 | ) : ( 81 |
Not connected
82 | )} 83 |
84 | ) 85 | } 86 | ``` 87 | 88 | For a complete list of all available properties and methods, see the [useWallet API Reference](../api-reference/usewallet.md). 89 | 90 | #### useNetwork 91 | 92 | The `useNetwork` hook serves two primary functions: managing the active network and supporting runtime node configuration. 93 | 94 | ```tsx 95 | import { useNetwork } from '@txnlab/use-wallet-react' 96 | 97 | function NetworkSelector() { 98 | const { 99 | // Active network management 100 | activeNetwork, // Currently active network 101 | setActiveNetwork, // Function to change networks 102 | 103 | // Runtime node configuration 104 | networkConfig, // Complete configuration for all networks 105 | activeNetworkConfig, // Configuration for active network only 106 | updateAlgodConfig, // Update a network's Algod configuration 107 | resetNetworkConfig // Reset network config to initial values 108 | } = useNetwork() 109 | 110 | return ( 111 |
112 | {/* Example: Network selector dropdown */} 113 | 123 |
124 | ) 125 | } 126 | ``` 127 | 128 | Active network management (previously part of `useWallet`) enables users to switch between different networks. 129 | 130 | Runtime node configuration, introduced in v4.0.0, enables users to override the application's default node settings and connect to any Algorand node. See the [Runtime Node Configuration](../guides/runtime-node-configuration.md) guide for details about implementing this feature. 131 | 132 | For a complete list of all available properties and methods, see the [useNetwork API Reference](../api-reference/usenetwork.md). 133 | 134 | ### Next Steps 135 | 136 | * Check out the [Connect Wallet Menu](../guides/connect-wallet-menu.md) guide for creating a simple wallet connection interface 137 | * Learn about transaction signing patterns in the [Signing Transactions](../guides/signing-transactions.md) guide 138 | * Explore network features in the [Switching Networks](../guides/switching-networks.md) and [Runtime Node Configuration](../guides/runtime-node-configuration.md) guides 139 | * Read the [API Reference](broken-reference) for detailed documentation of the library's main exports 140 | * Browse [Example Projects](../resources/example-projects.md) for working implementations in Vite (React) and Next.js 141 | -------------------------------------------------------------------------------- /docs/framework/solidjs.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: 3 | title: 4 | visible: true 5 | description: 6 | visible: false 7 | tableOfContents: 8 | visible: true 9 | outline: 10 | visible: true 11 | pagination: 12 | visible: true 13 | --- 14 | 15 | # SolidJS 16 | 17 | The SolidJS adapter (`@txnlab/use-wallet-solid`) provides primitives and components for integrating use-wallet into Solid applications. This guide covers how to set up the adapter and use its features effectively. 18 | 19 | ### Setup 20 | 21 | After installing the package and any required wallet dependencies (see [Installation](../getting-started/installation.md)) and [configuring your WalletManager](../getting-started/configuration.md), wrap your application with the `WalletProvider`: 22 | 23 | ```tsx 24 | import { 25 | WalletProvider, 26 | WalletManager, 27 | NetworkId, 28 | } from '@txnlab/use-wallet-solid' 29 | 30 | // Create manager instance (see Configuration guide) 31 | const manager = new WalletManager({ 32 | wallets: [...], 33 | networks: {...}, 34 | defaultNetwork: NetworkId.TESTNET 35 | }) 36 | 37 | function App() { 38 | return ( 39 | 40 | 41 | 42 | ) 43 | } 44 | ``` 45 | 46 | The provider makes the wallet functionality available throughout your application via Solid's primitives. 47 | 48 | ### Using the Primitives 49 | 50 | The Solid adapter provides two primitives for accessing wallet functionality. In v4.0.0, network-related features were moved from `useWallet` into a new `useNetwork` primitive to provide better separation of concerns: 51 | 52 | #### useWallet 53 | 54 | The `useWallet` primitive provides access to wallet management features. Here's an example showing some commonly used values: 55 | 56 | ```tsx 57 | import { useWallet } from '@txnlab/use-wallet-solid' 58 | 59 | function WalletInfo() { 60 | const { 61 | wallets, // List of available wallets 62 | activeWallet, // Currently active wallet (signal) 63 | activeAccount, // Active account in active wallet (signal) 64 | activeAddress, // Address of active account (signal) 65 | isReady, // Whether all wallet providers have finished initialization (signal) 66 | signTransactions, // Function to sign transactions 67 | transactionSigner, // Typed signer for ATC and Algokit Utils 68 | algodClient // Algod client for active network (signal) 69 | } = useWallet() 70 | 71 | return ( 72 | Loading...} 75 | > 76 | Not connected} 79 | > 80 |
Connected: {activeAddress()}
81 |
82 |
83 | ) 84 | } 85 | ``` 86 | 87 | Note that unlike [React's hooks](react.md#using-the-hooks), Solid's primitives return signals (functions) that need to be called to access their current value. The values are automatically tracked and will trigger reactivity when they change. 88 | 89 | For a complete list of all available properties and methods, see the [useWallet API Reference](../api-reference/usewallet.md). 90 | 91 | #### useNetwork 92 | 93 | The `useNetwork` primitive serves two primary functions: managing the active network and supporting runtime node configuration. 94 | 95 | ```tsx 96 | import { useNetwork } from '@txnlab/use-wallet-solid' 97 | 98 | function NetworkSelector() { 99 | const { 100 | // Active network management 101 | activeNetwork, // Currently active network (signal) 102 | setActiveNetwork, // Function to change networks 103 | 104 | // Runtime node configuration 105 | networkConfig, // Complete configuration for all networks 106 | activeNetworkConfig, // Configuration for active network only (signal) 107 | updateAlgodConfig, // Update a network's Algod configuration 108 | resetNetworkConfig // Reset network config to initial values 109 | } = useNetwork() 110 | 111 | return ( 112 |
113 | {/* Example: Network selector dropdown */} 114 | 126 |
127 | ) 128 | } 129 | ``` 130 | 131 | Active network management (previously part of `useWallet`) enables users to switch between different networks. 132 | 133 | Runtime node configuration, introduced in v4.0.0, enables users to override the application's default node settings and connect to any Algorand node. See the [Runtime Node Configuration](../guides/runtime-node-configuration.md) guide for details about implementing this feature. 134 | 135 | For a complete list of all available properties and methods, see the [useNetwork API Reference](../api-reference/usenetwork.md). 136 | 137 | ### Next Steps 138 | 139 | * Check out the [Connect Wallet Menu](../guides/connect-wallet-menu.md) guide for creating a simple wallet connection interface 140 | * Learn about transaction signing patterns in the [Signing Transactions](../guides/signing-transactions.md) guide 141 | * Explore network features in the [Switching Networks](../guides/switching-networks.md) and [Runtime Node Configuration](../guides/runtime-node-configuration.md) guides 142 | * Read the [API Reference](broken-reference) for detailed documentation of the library's main exports 143 | * Browse [Example Projects](../resources/example-projects.md) for a working implementation in Vite (Solid) 144 | -------------------------------------------------------------------------------- /docs/framework/vue.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: 3 | title: 4 | visible: true 5 | description: 6 | visible: false 7 | tableOfContents: 8 | visible: true 9 | outline: 10 | visible: true 11 | pagination: 12 | visible: true 13 | --- 14 | 15 | # Vue 16 | 17 | The Vue adapter (`@txnlab/use-wallet-vue`) provides a plugin and composables for integrating use-wallet into Vue applications. This guide covers how to set up the adapter and use its features effectively. 18 | 19 | ### Setup 20 | 21 | After installing the package, any required wallet dependencies (see [Installation](../getting-started/installation.md)), and [configuring your WalletManager](../getting-started/configuration.md), install the WalletManager plugin in your Vue application: 22 | 23 | ```typescript 24 | // main.ts 25 | import { createApp } from 'vue' 26 | import { WalletManagerPlugin, NetworkId } from '@txnlab/use-wallet-vue' 27 | import App from './App.vue' 28 | 29 | const app = createApp(App) 30 | 31 | app.use(WalletManagerPlugin, { 32 | wallets: [...], 33 | networks: {...}, 34 | defaultNetwork: NetworkId.TESTNET 35 | }) 36 | 37 | app.mount('#app') 38 | ``` 39 | 40 | The plugin makes the wallet functionality available throughout your application via Vue composables. 41 | 42 | ### Using the Composables 43 | 44 | The Vue adapter provides two composables for accessing wallet functionality. In v4.0.0, network-related features were moved from `useWallet` into a new `useNetwork` composable to provide better separation of concerns: 45 | 46 | #### useWallet 47 | 48 | The `useWallet` composable provides access to wallet management features. Here's an example showing some commonly used values: 49 | 50 | ```typescript 51 | 65 | 66 | 79 | ``` 80 | 81 | For a complete list of all available properties and methods, see the [useWallet API Reference](../api-reference/usewallet.md). 82 | 83 | #### useNetwork 84 | 85 | The `useNetwork` composable serves two primary functions: managing the active network and supporting runtime node configuration. 86 | 87 | ```typescript 88 | 103 | 104 | 121 | ``` 122 | 123 | Active network management (previously part of `useWallet`) enables users to switch between different networks. 124 | 125 | Runtime node configuration, introduced in v4.0.0, enables users to override the application's default node settings and connect to any Algorand node. See the [Runtime Node Configuration](../guides/runtime-node-configuration.md) guide for details about implementing this feature. 126 | 127 | For a complete list of all available properties and methods, see the [useNetwork API Reference](../api-reference/usenetwork.md). 128 | 129 | ### Next Steps 130 | 131 | * Check out the [Connect Wallet Menu](../guides/connect-wallet-menu.md) guide for creating a simple wallet connection interface 132 | * Learn about transaction signing patterns in the [Signing Transactions](../guides/signing-transactions.md) guide 133 | * Explore network features in the [Switching Networks](../guides/switching-networks.md) and [Runtime Node Configuration](../guides/runtime-node-configuration.md) guides 134 | * Read the [API Reference](broken-reference) for detailed documentation of the library's main exports 135 | * Browse [Example Projects](../resources/example-projects.md) for working implementations in Vite (Vue) and Nuxt 136 | -------------------------------------------------------------------------------- /docs/guides/switching-networks.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: 3 | title: 4 | visible: true 5 | description: 6 | visible: false 7 | tableOfContents: 8 | visible: true 9 | outline: 10 | visible: true 11 | pagination: 12 | visible: true 13 | --- 14 | 15 | # Switching Networks 16 | 17 | use-wallet provides two ways to switch between Algorand networks: 18 | 19 | * Setting the default network at startup 20 | * Switching networks at runtime using the `useNetwork` hook/composable/primitive 21 | 22 | ### Default Network 23 | 24 | When initializing the WalletManager, you can specify which network to use as the default: 25 | 26 | ```typescript 27 | import { WalletManager, NetworkId } from '@txnlab/use-wallet' 28 | 29 | const manager = new WalletManager({ 30 | defaultNetwork: NetworkId.TESTNET 31 | }) 32 | ``` 33 | 34 | You can also control whether the manager should reset to the default network on page load using the `resetNetwork` option: 35 | 36 | ```typescript 37 | const manager = new WalletManager({ 38 | defaultNetwork: NetworkId.TESTNET, 39 | options: { 40 | // Always start on TestNet, even if the user was previously on a different network 41 | resetNetwork: true 42 | } 43 | }) 44 | ``` 45 | 46 | When `resetNetwork` is `false` (the default), use-wallet will attempt to restore the last active network from local storage. 47 | 48 | ### Runtime Network Switching 49 | 50 | The `useNetwork` hook/composable provides methods for switching networks at runtime. 51 | 52 | {% tabs %} 53 | {% tab title="React" %} 54 | ```tsx 55 | import { useNetwork } from '@txnlab/use-wallet-react' 56 | 57 | function NetworkSwitch() { 58 | const { 59 | activeNetwork, 60 | setActiveNetwork, 61 | networkConfig 62 | } = useNetwork() 63 | 64 | return ( 65 | 75 | ) 76 | } 77 | ``` 78 | {% endtab %} 79 | 80 | {% tab title="Vue" %} 81 | ```typescript 82 | 91 | 92 | 106 | ``` 107 | {% endtab %} 108 | 109 | {% tab title="Solid" %} 110 | ```tsx 111 | import { useNetwork } from '@txnlab/use-wallet-solid' 112 | 113 | function NetworkSwitch() { 114 | const { 115 | activeNetwork, 116 | setActiveNetwork, 117 | networkConfig 118 | } = useNetwork() 119 | 120 | return ( 121 | 131 | ) 132 | } 133 | ``` 134 | {% endtab %} 135 | {% endtabs %} 136 | 137 | ### Network Status 138 | 139 | You can use the `activeNetworkConfig` to access information about the current network: 140 | 141 | {% tabs %} 142 | {% tab title="React" %} 143 | ```typescript 144 | const { activeNetworkConfig } = useNetwork() 145 | 146 | // Check if we're on a test network 147 | const isTestnet = activeNetworkConfig.isTestnet 148 | 149 | // Get genesis ID 150 | const genesisId = activeNetworkConfig.genesisId 151 | ``` 152 | {% endtab %} 153 | 154 | {% tab title="Vue" %} 155 | ```typescript 156 | 165 | ``` 166 | {% endtab %} 167 | 168 | {% tab title="Solid" %} 169 | ```typescript 170 | const { activeNetworkConfig } = useNetwork() 171 | 172 | // Check if we're on a test network 173 | const isTestnet = () => activeNetworkConfig().isTestnet 174 | 175 | // Get genesis ID 176 | const genesisId = () => activeNetworkConfig().genesisId 177 | ``` 178 | {% endtab %} 179 | {% endtabs %} 180 | 181 | ### Network Events 182 | 183 | When switching networks, several things happen automatically: 184 | 185 | 1. The `algodClient` is updated to point to the new network 186 | 2. Active wallet sessions are maintained (if the wallet supports the new network) 187 | 3. The active network is saved to local storage 188 | 189 | ### Wallet Compatibility 190 | 191 | Not all wallets support all networks. For example: 192 | 193 | * Exodus only works on MainNet 194 | * Defly and Pera only support MainNet and TestNet 195 | * The Mnemonic wallet only works on test networks 196 | * Custom networks may not be supported by all wallets 197 | 198 | Please refer to each wallet's documentation to determine which networks they support. 199 | 200 | ### Default Networks 201 | 202 | use-wallet supports any AVM-compatible network and comes with default configurations for these Algorand networks: 203 | 204 | * MainNet 205 | * TestNet 206 | * BetaNet 207 | * LocalNet (for development) 208 | 209 | For details about configuring networks, including how to customize default networks and add custom networks, see the [Configuration](../getting-started/configuration.md#network-configuration) guide. 210 | -------------------------------------------------------------------------------- /docs/resources/algokit-templates.md: -------------------------------------------------------------------------------- 1 | --- 2 | hidden: true 3 | --- 4 | 5 | # AlgoKit Templates 6 | 7 | -------------------------------------------------------------------------------- /docs/resources/example-projects.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: 3 | title: 4 | visible: true 5 | description: 6 | visible: false 7 | tableOfContents: 8 | visible: true 9 | outline: 10 | visible: true 11 | pagination: 12 | visible: true 13 | --- 14 | 15 | # Example Projects 16 | 17 | This page provides an overview of the example projects available in the use-wallet repository. Each example demonstrates key features and best practices for integrating use-wallet into different frameworks and environments. 18 | 19 | All examples are built with Vite or their respective meta-frameworks and include: 20 | 21 | * Complete wallet integration with all supported providers 22 | * Transaction signing demonstration 23 | * End-to-end tests with Playwright 24 | * TypeScript configuration 25 | * Runtime node configuration UI (Vite examples only) 26 | 27 | ### Framework Examples 28 | 29 | #### **Vite (React)** 30 | 31 | ```bash 32 | git clone https://github.com/TxnLab/use-wallet 33 | cd use-wallet/examples/react-ts 34 | pnpm install 35 | pnpm dev 36 | ``` 37 | 38 | The React example demonstrates: 39 | 40 | * React-specific hooks and patterns 41 | * Component composition with TypeScript 42 | * Integration with React's state management 43 | * Advanced features like runtime node configuration 44 | 45 | #### **Vite (Vue)** 46 | 47 | ```bash 48 | git clone https://github.com/TxnLab/use-wallet 49 | cd use-wallet/examples/vue-ts 50 | pnpm install 51 | pnpm dev 52 | ``` 53 | 54 | The Vue example showcases: 55 | 56 | * Vue composables and plugins 57 | * Integration with Vue's reactivity system 58 | * TypeScript support in Vue components 59 | * Advanced features like runtime node configuration 60 | 61 | #### **Vite (SolidJS)** 62 | 63 | ```bash 64 | git clone https://github.com/TxnLab/use-wallet 65 | cd use-wallet/examples/solid-ts 66 | pnpm install 67 | pnpm dev 68 | ``` 69 | 70 | The Solid example illustrates: 71 | 72 | * Solid.js primitives and patterns 73 | * Fine-grained reactivity integration 74 | * TypeScript configuration for Solid 75 | * Advanced features like runtime node configuration 76 | 77 | #### **Vite (Vanilla TypeScript)** 78 | 79 | ```bash 80 | git clone https://github.com/TxnLab/use-wallet 81 | cd use-wallet/examples/vanilla-ts 82 | pnpm install 83 | pnpm dev 84 | ``` 85 | 86 | The Vanilla TypeScript example demonstrates: 87 | 88 | * Framework-agnostic usage 89 | * Direct WalletManager implementation 90 | * Custom state management 91 | * TypeScript configuration without a framework 92 | 93 | ### Meta-Framework Examples 94 | 95 | #### **Next.js** 96 | 97 | ```bash 98 | git clone https://github.com/TxnLab/use-wallet 99 | cd use-wallet/examples/nextjs 100 | pnpm install 101 | pnpm dev 102 | ``` 103 | 104 | The Next.js example demonstrates: 105 | 106 | * Server-side rendering considerations 107 | * Next.js-specific configuration 108 | * Integration with Next.js App Router 109 | * TypeScript configuration for Next.js 110 | 111 | #### **Nuxt** 112 | 113 | ```bash 114 | git clone https://github.com/TxnLab/use-wallet 115 | cd use-wallet/examples/nuxt 116 | pnpm install 117 | pnpm dev 118 | ``` 119 | 120 | The Nuxt example showcases: 121 | 122 | * Server-side rendering with Vue 123 | * Nuxt module integration 124 | * Auto-imports configuration 125 | * TypeScript configuration for Nuxt 126 | 127 | ### Key Features 128 | 129 | #### Wallet Integration 130 | 131 | All examples include a complete wallet connection interface demonstrating: 132 | 133 | * Connecting/disconnecting wallets 134 | * Switching active accounts 135 | * Transaction signing 136 | * Network management 137 | 138 | #### Runtime Node Configuration 139 | 140 | The Vite examples include a UI for configuring Algorand node settings at runtime: 141 | 142 | * Custom node URL/port/headers 143 | * Network switching 144 | * Configuration persistence 145 | 146 | For more information about this feature, see the [Runtime Node Configuration](../guides/runtime-node-configuration.md) guide. 147 | 148 | #### Testing 149 | 150 | All examples include end-to-end tests using Playwright, demonstrating: 151 | 152 | * Mocked Algorand node responses 153 | * Wallet connection testing 154 | * Transaction signing tests 155 | * Network switching tests 156 | 157 | For more information about testing, see the [Testing with Mnemonic Wallet](../guides/testing-with-mnemonic-wallet.md) guide. 158 | 159 | ### Getting Started 160 | 161 | 1. Clone the repository: 162 | 163 | ```bash 164 | git clone https://github.com/TxnLab/use-wallet 165 | ``` 166 | 167 | 2. Install dependencies: 168 | 169 | ```bash 170 | cd use-wallet 171 | pnpm install 172 | ``` 173 | 174 | 3. Build the packages: 175 | 176 | ```bash 177 | pnpm build 178 | ``` 179 | 180 | 4. Run an example using the provided scripts: 181 | 182 | ```bash 183 | pnpm example:react # Run React example 184 | pnpm example:vue # Run Vue example 185 | pnpm example:solid # Run Solid example 186 | pnpm example:ts # Run Vanilla TypeScript example 187 | pnpm example:nextjs # Run Next.js example 188 | pnpm example:nuxt # Run Nuxt example 189 | ``` 190 | 191 | ### Next Steps 192 | 193 | * Read the [framework-specific integration](broken-reference) guides for detailed setup instructions 194 | * Explore the [Connect Wallet Menu](../guides/connect-wallet-menu.md) guide to understand core wallet integration concepts and best practices 195 | * Learn about advanced features in the [Runtime Node Configuration](../guides/runtime-node-configuration.md) guide 196 | * Set up testing with the [Testing with Mnemonic Wallet](../guides/testing-with-mnemonic-wallet.md) guide 197 | -------------------------------------------------------------------------------- /examples/e2e-tests/example.spec.ts: -------------------------------------------------------------------------------- 1 | import { test, expect } from '@playwright/test' 2 | import { fakeTxnResponses } from './FakeAlgodResponses' 3 | 4 | test('it works', async ({ page }) => { 5 | // Load and set up the page 6 | await page.goto('/') 7 | await fakeTxnResponses(page) 8 | // Whenever a prompt appears, enter the mnemonic 9 | page.on('dialog', (dialog) => 10 | dialog.accept( 11 | // !! WARN !! 12 | // THIS ACCOUNT AND ITS MNEMONIC ARE COMPROMISED. 13 | // They are to be used for testing only. 14 | // !! WARN !! 15 | 'sugar bronze century excuse animal jacket what rail biology symbol want craft annual soul increase question army win execute slim girl chief exhaust abstract wink' 16 | ) 17 | ) 18 | 19 | // Check mnemonic wallet is activated 20 | await expect(page.getByRole('heading', { name: 'Mnemonic' })).toBeVisible() 21 | 22 | // Click the "Connect" button for the Mnemonic wallet 23 | await page 24 | .locator('.wallet-group', { 25 | has: page.locator('h4', { hasText: 'Mnemonic' }) 26 | }) 27 | .getByRole('button', { name: 'Connect', exact: true }) 28 | .click() 29 | 30 | // Check wallet is connected 31 | await expect(page.getByRole('heading', { name: 'Mnemonic [active]' })).toBeVisible() 32 | await expect(page.getByRole('combobox')).toHaveValue( 33 | '3F3FPW6ZQQYD6JDC7FKKQHNGVVUIBIZOUI5WPSJEHBRABZDRN6LOTBMFEY' 34 | ) 35 | 36 | // Click button to send a transaction 37 | await page.getByRole('button', { name: 'Send Transaction' }).click() 38 | 39 | // There is no visual feedback of the outcome of sending the transaction. Only a message is 40 | // printed in the console. So, we will wait a little bit for transaction to complete 41 | await page.waitForTimeout(500) 42 | }) 43 | -------------------------------------------------------------------------------- /examples/nextjs/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /examples/nextjs/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | -------------------------------------------------------------------------------- /examples/nextjs/README.md: -------------------------------------------------------------------------------- 1 | # Next.js example app 2 | 3 | This example provides a minimal setup to get [@txnlab/use-wallet-react](https://github.com/TxnLab/use-wallet/tree/v3/packages/use-wallet-react) working in a Next.js app. 4 | -------------------------------------------------------------------------------- /examples/nextjs/next.config.mjs: -------------------------------------------------------------------------------- 1 | import { webpackFallback } from '@txnlab/use-wallet-react' 2 | 3 | /** @type {import('next').NextConfig} */ 4 | const nextConfig = { 5 | webpack: (config, { isServer }) => { 6 | /** @see https://github.com/WalletConnect/walletconnect-monorepo/issues/1908#issuecomment-1487801131 */ 7 | config.externals.push('pino-pretty', 'lokijs', 'encoding') 8 | 9 | /** 10 | * Provide fallbacks for optional wallet dependencies. 11 | * This allows the app to build and run without these packages installed, 12 | * enabling users to include only the wallet packages they need. 13 | * Each package is set to 'false', which means Webpack will provide an empty module 14 | * if the package is not found, preventing build errors for unused wallets. 15 | */ 16 | if (!isServer) { 17 | config.resolve.fallback = { 18 | ...config.resolve.fallback, 19 | ...webpackFallback 20 | } 21 | } 22 | return config 23 | } 24 | } 25 | 26 | export default nextConfig 27 | -------------------------------------------------------------------------------- /examples/nextjs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "use-wallet-example-nextjs", 3 | "scripts": { 4 | "dev": "next dev", 5 | "build": "next build", 6 | "start": "next start", 7 | "lint": "next lint", 8 | "typecheck": "tsc --noEmit", 9 | "test:e2e": "playwright test" 10 | }, 11 | "dependencies": { 12 | "@blockshake/defly-connect": "^1.2.1", 13 | "@noble/ed25519": "^2.2.3", 14 | "@perawallet/connect": "^1.4.1", 15 | "@txnlab/use-wallet-react": "workspace:*", 16 | "@walletconnect/modal": "^2.7.0", 17 | "@walletconnect/sign-client": "^2.19.2", 18 | "algosdk": "3.2.0", 19 | "lute-connect": "^1.6.1", 20 | "next": "14.2.26", 21 | "react": "18.3.1", 22 | "react-dom": "18.3.1" 23 | }, 24 | "devDependencies": { 25 | "@types/node": "20.11.30", 26 | "@types/react": "18.3.20", 27 | "@types/react-dom": "18.3.5", 28 | "eslint": "8.57.1", 29 | "eslint-config-next": "14.2.26", 30 | "typescript": "5.8.2" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /examples/nextjs/playwright.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig, devices } from '@playwright/test' 2 | 3 | // Use process.env.PORT by default and fallback to port 3000 4 | const PORT = process.env.PORT || 3000 5 | 6 | /** 7 | * See https://playwright.dev/docs/test-configuration. 8 | */ 9 | export default defineConfig({ 10 | testDir: '../e2e-tests', 11 | /* Run tests in files in parallel */ 12 | fullyParallel: true, 13 | /* Fail the build on CI if you accidentally left test.only in the source code. */ 14 | forbidOnly: !!process.env.CI, 15 | /* Retry on CI only */ 16 | retries: process.env.CI ? 2 : 0, 17 | /* Opt out of parallel tests on CI. */ 18 | workers: process.env.CI ? 1 : undefined, 19 | /* Reporter to use. See https://playwright.dev/docs/test-reporters */ 20 | reporter: 'list', 21 | /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ 22 | use: { 23 | /* Base URL to use in actions like `await page.goto('/')`. */ 24 | baseURL: `http://localhost:${PORT}`, 25 | 26 | /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ 27 | trace: 'on-first-retry' 28 | }, 29 | 30 | /* Configure projects for major browsers */ 31 | projects: [ 32 | { 33 | name: 'chromium', 34 | use: { ...devices['Desktop Chrome'] } 35 | }, 36 | 37 | { 38 | name: 'firefox', 39 | use: { ...devices['Desktop Firefox'] } 40 | }, 41 | 42 | { 43 | name: 'webkit', 44 | use: { ...devices['Desktop Safari'] } 45 | } 46 | 47 | /* Test against mobile viewports. */ 48 | // { 49 | // name: 'Mobile Chrome', 50 | // use: { ...devices['Pixel 5'] }, 51 | // }, 52 | // { 53 | // name: 'Mobile Safari', 54 | // use: { ...devices['iPhone 12'] }, 55 | // }, 56 | 57 | /* Test against branded browsers. */ 58 | // { 59 | // name: 'Microsoft Edge', 60 | // use: { ...devices['Desktop Edge'], channel: 'msedge' }, 61 | // }, 62 | // { 63 | // name: 'Google Chrome', 64 | // use: { ...devices['Desktop Chrome'], channel: 'chrome' }, 65 | // }, 66 | ], 67 | 68 | /* Run your local dev server before starting the tests */ 69 | webServer: { 70 | command: 'pnpm build && pnpm start', 71 | url: `http://localhost:${PORT}`, 72 | reuseExistingServer: !process.env.CI, 73 | stdout: 'pipe' 74 | } 75 | }) 76 | -------------------------------------------------------------------------------- /examples/nextjs/public/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/nextjs/public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/nextjs/src/app/Connect.module.css: -------------------------------------------------------------------------------- 1 | .networkGroup { 2 | display: flex; 3 | flex-direction: column; 4 | align-items: center; 5 | gap: 1em; 6 | margin: 2em; 7 | padding: 2em; 8 | background-color: light-dark(rgba(0, 0, 0, 0.025), rgba(255, 255, 255, 0.025)); 9 | border-color: light-dark(rgba(0, 0, 0, 0.1), rgba(255, 255, 255, 0.1)); 10 | border-style: solid; 11 | border-width: 1px; 12 | border-radius: 8px; 13 | } 14 | 15 | .networkGroup h4 { 16 | margin: 0; 17 | } 18 | 19 | .networkGroup .activeNetwork { 20 | text-transform: capitalize; 21 | } 22 | 23 | .networkButtons { 24 | display: flex; 25 | align-items: center; 26 | justify-content: center; 27 | flex-wrap: wrap; 28 | gap: 0.5em; 29 | } 30 | 31 | .walletGroup { 32 | display: flex; 33 | flex-direction: column; 34 | align-items: center; 35 | gap: 1em; 36 | margin-bottom: 2em; 37 | } 38 | 39 | .walletGroup h4 { 40 | line-height: 1.5; 41 | margin: 0; 42 | } 43 | 44 | .walletGroup h4[data-active='true']:after { 45 | content: ' [active]'; 46 | } 47 | 48 | .walletButtons { 49 | display: flex; 50 | align-items: center; 51 | justify-content: center; 52 | flex-wrap: wrap; 53 | gap: 0.5em; 54 | } 55 | 56 | .inputGroup { 57 | display: flex; 58 | align-items: center; 59 | justify-content: center; 60 | gap: 0.6em; 61 | } 62 | .inputGroup label { 63 | margin-left: 1em; 64 | font-weight: 500; 65 | } 66 | .inputGroup input { 67 | min-width: 16em; 68 | } 69 | .inputGroup input[disabled] { 70 | opacity: 0.75; 71 | color: light-dark(rgba(16, 16, 16, 0.3), rgba(255, 255, 255, 0.3)); 72 | } 73 | -------------------------------------------------------------------------------- /examples/nextjs/src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TxnLab/use-wallet/0e17652ff1723c8c5943aa2e403a605ffb646ddb/examples/nextjs/src/app/favicon.ico -------------------------------------------------------------------------------- /examples/nextjs/src/app/globals.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --max-width: 1100px; 3 | --border-radius: 12px; 4 | --font-mono: 5 | ui-monospace, Menlo, Monaco, 'Cascadia Mono', 'Segoe UI Mono', 'Roboto Mono', 'Oxygen Mono', 6 | 'Ubuntu Monospace', 'Source Code Pro', 'Fira Mono', 'Droid Sans Mono', 'Courier New', monospace; 7 | 8 | --foreground-rgb: 0, 0, 0; 9 | --background-start-rgb: 214, 219, 220; 10 | --background-end-rgb: 255, 255, 255; 11 | 12 | --primary-glow: conic-gradient( 13 | from 180deg at 50% 50%, 14 | #16abff33 0deg, 15 | #0885ff33 55deg, 16 | #54d6ff33 120deg, 17 | #0071ff33 160deg, 18 | transparent 360deg 19 | ); 20 | --secondary-glow: radial-gradient(rgba(255, 255, 255, 1), rgba(255, 255, 255, 0)); 21 | 22 | --tile-start-rgb: 239, 245, 249; 23 | --tile-end-rgb: 228, 232, 233; 24 | --tile-border: conic-gradient( 25 | #00000080, 26 | #00000040, 27 | #00000030, 28 | #00000020, 29 | #00000010, 30 | #00000010, 31 | #00000080 32 | ); 33 | 34 | --callout-rgb: 238, 240, 241; 35 | --callout-border-rgb: 172, 175, 176; 36 | --card-rgb: 180, 185, 188; 37 | --card-border-rgb: 131, 134, 135; 38 | } 39 | 40 | @media (prefers-color-scheme: dark) { 41 | :root { 42 | --foreground-rgb: 255, 255, 255; 43 | --background-start-rgb: 0, 0, 0; 44 | --background-end-rgb: 0, 0, 0; 45 | 46 | --primary-glow: radial-gradient(rgba(1, 65, 255, 0.4), rgba(1, 65, 255, 0)); 47 | --secondary-glow: linear-gradient( 48 | to bottom right, 49 | rgba(1, 65, 255, 0), 50 | rgba(1, 65, 255, 0), 51 | rgba(1, 65, 255, 0.3) 52 | ); 53 | 54 | --tile-start-rgb: 2, 13, 46; 55 | --tile-end-rgb: 2, 5, 19; 56 | --tile-border: conic-gradient( 57 | #ffffff80, 58 | #ffffff40, 59 | #ffffff30, 60 | #ffffff20, 61 | #ffffff10, 62 | #ffffff10, 63 | #ffffff80 64 | ); 65 | 66 | --callout-rgb: 20, 20, 20; 67 | --callout-border-rgb: 108, 108, 108; 68 | --card-rgb: 100, 100, 100; 69 | --card-border-rgb: 200, 200, 200; 70 | } 71 | } 72 | 73 | * { 74 | box-sizing: border-box; 75 | padding: 0; 76 | margin: 0; 77 | } 78 | 79 | html, 80 | body { 81 | max-width: 100vw; 82 | overflow-x: hidden; 83 | } 84 | 85 | body { 86 | color: rgb(var(--foreground-rgb)); 87 | background: linear-gradient(to bottom, transparent, rgb(var(--background-end-rgb))) 88 | rgb(var(--background-start-rgb)); 89 | } 90 | 91 | a { 92 | color: inherit; 93 | text-decoration: none; 94 | } 95 | 96 | h1 { 97 | font-size: 3.2em; 98 | line-height: 1.1; 99 | } 100 | 101 | button { 102 | border-radius: 8px; 103 | border: 1px solid transparent; 104 | padding: 0.6em 1.2em; 105 | font-size: 1em; 106 | font-weight: 500; 107 | font-family: inherit; 108 | background-color: #1a1a1a; 109 | cursor: pointer; 110 | transition: border-color 0.25s; 111 | } 112 | button:not(:disabled):hover { 113 | border-color: #0071ff; 114 | } 115 | button:focus, 116 | button:focus-visible { 117 | outline: 4px auto -webkit-focus-ring-color; 118 | } 119 | button:disabled { 120 | opacity: 0.75; 121 | cursor: default; 122 | } 123 | 124 | input[type='text'], 125 | input[type='email'] { 126 | border-radius: 8px; 127 | border: 1px solid #1a1a1a; 128 | padding: 0.6em 0.9em; 129 | font-size: 1em; 130 | font-weight: 500; 131 | font-family: inherit; 132 | background-color: #1a1a1a; 133 | color: #ffffff; 134 | transition: border-color 0.25s; 135 | } 136 | 137 | select { 138 | border-radius: 8px; 139 | border: 1px solid #1a1a1a; 140 | padding: 0.6em 0.9em; 141 | font-size: 1em; 142 | font-weight: 500; 143 | font-family: inherit; 144 | background-color: #1a1a1a; 145 | color: #ffffff; 146 | transition: border-color 0.25s; 147 | background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e"); 148 | background-position: right 0.5rem center; 149 | background-repeat: no-repeat; 150 | background-size: 1.5em 1.5em; 151 | padding-right: 2.5rem; 152 | -webkit-print-color-adjust: exact; 153 | print-color-adjust: exact; 154 | -moz-appearance: none !important; 155 | -webkit-appearance: none !important; 156 | appearance: none !important; 157 | } 158 | 159 | @media (prefers-color-scheme: dark) { 160 | html { 161 | color-scheme: dark; 162 | } 163 | } 164 | 165 | @media (prefers-color-scheme: light) { 166 | button { 167 | background-color: #f9f9f9; 168 | border-color: #cacaca; 169 | } 170 | button:disabled { 171 | border-color: #dddddd; 172 | } 173 | input[type='text'], 174 | input[type='email'], 175 | select { 176 | background-color: #f9f9f9; 177 | color: #1a1a1a; 178 | border-color: #cacaca; 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /examples/nextjs/src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import { Providers } from '@/app/providers' 2 | import { Inter } from 'next/font/google' 3 | import type { Metadata } from 'next' 4 | import './globals.css' 5 | 6 | const inter = Inter({ subsets: ['latin'] }) 7 | 8 | export const metadata: Metadata = { 9 | title: 'use-wallet | Next.js', 10 | description: 'Generated by create next app' 11 | } 12 | 13 | export default function RootLayout({ 14 | children 15 | }: Readonly<{ 16 | children: React.ReactNode 17 | }>) { 18 | return ( 19 | 20 | 21 | {children} 22 | 23 | 24 | ) 25 | } 26 | -------------------------------------------------------------------------------- /examples/nextjs/src/app/page.module.css: -------------------------------------------------------------------------------- 1 | .main { 2 | display: flex; 3 | flex-direction: column; 4 | justify-content: center; 5 | align-items: center; 6 | padding: 6rem; 7 | min-height: 100vh; 8 | } 9 | 10 | .description { 11 | display: inherit; 12 | justify-content: inherit; 13 | align-items: inherit; 14 | font-size: 0.85rem; 15 | max-width: var(--max-width); 16 | width: 100%; 17 | z-index: 2; 18 | font-family: var(--font-mono); 19 | } 20 | 21 | .description a { 22 | display: flex; 23 | justify-content: center; 24 | align-items: center; 25 | gap: 0.5rem; 26 | } 27 | 28 | .description p { 29 | position: relative; 30 | margin: 0; 31 | padding: 1rem; 32 | background-color: rgba(var(--callout-rgb), 0.5); 33 | border: 1px solid rgba(var(--callout-border-rgb), 0.3); 34 | border-radius: var(--border-radius); 35 | } 36 | 37 | .code { 38 | font-weight: 700; 39 | font-family: var(--font-mono); 40 | } 41 | 42 | .grid { 43 | display: grid; 44 | grid-template-columns: repeat(4, minmax(25%, auto)); 45 | max-width: 100%; 46 | width: var(--max-width); 47 | } 48 | 49 | .card { 50 | padding: 1rem 1.2rem; 51 | border-radius: var(--border-radius); 52 | background: rgba(var(--card-rgb), 0); 53 | border: 1px solid rgba(var(--card-border-rgb), 0); 54 | transition: 55 | background 200ms, 56 | border 200ms; 57 | } 58 | 59 | .card span { 60 | display: inline-block; 61 | transition: transform 200ms; 62 | } 63 | 64 | .card h2 { 65 | font-weight: 600; 66 | margin-bottom: 0.7rem; 67 | } 68 | 69 | .card p { 70 | margin: 0; 71 | opacity: 0.6; 72 | font-size: 0.9rem; 73 | line-height: 1.5; 74 | max-width: 30ch; 75 | text-wrap: balance; 76 | } 77 | 78 | .center { 79 | display: flex; 80 | flex-direction: column; 81 | justify-content: center; 82 | align-items: center; 83 | position: relative; 84 | padding: 4rem 0; 85 | } 86 | 87 | .center::before { 88 | background: var(--secondary-glow); 89 | border-radius: 50%; 90 | width: 480px; 91 | height: 360px; 92 | margin-left: -400px; 93 | pointer-events: none; 94 | z-index: -1; 95 | } 96 | 97 | .center::after { 98 | background: var(--primary-glow); 99 | width: 240px; 100 | height: 180px; 101 | z-index: -2; 102 | pointer-events: none; 103 | } 104 | 105 | .center::before, 106 | .center::after { 107 | content: ''; 108 | left: 50%; 109 | position: absolute; 110 | filter: blur(45px); 111 | transform: translateZ(0); 112 | } 113 | 114 | .logo { 115 | position: relative; 116 | margin: 1.5em; 117 | } 118 | 119 | .heading { 120 | text-align: center; 121 | font-size: 3.2em; 122 | line-height: 1.1; 123 | margin: 0.67em 0; 124 | } 125 | 126 | /* Enable hover only on non-touch devices */ 127 | @media (hover: hover) and (pointer: fine) { 128 | .card:hover { 129 | background: rgba(var(--card-rgb), 0.1); 130 | border: 1px solid rgba(var(--card-border-rgb), 0.15); 131 | } 132 | 133 | .card:hover span { 134 | transform: translateX(4px); 135 | } 136 | } 137 | 138 | @media (prefers-reduced-motion) { 139 | .card:hover span { 140 | transform: none; 141 | } 142 | } 143 | 144 | /* Mobile */ 145 | @media (max-width: 700px) { 146 | .content { 147 | padding: 4rem; 148 | } 149 | 150 | .grid { 151 | grid-template-columns: 1fr; 152 | margin-bottom: 120px; 153 | max-width: 320px; 154 | text-align: center; 155 | } 156 | 157 | .card { 158 | padding: 1rem 2.5rem; 159 | } 160 | 161 | .card h2 { 162 | margin-bottom: 0.5rem; 163 | } 164 | 165 | .center { 166 | padding: 8rem 0 6rem; 167 | } 168 | 169 | .center::before { 170 | transform: none; 171 | height: 300px; 172 | } 173 | 174 | .description { 175 | font-size: 0.8rem; 176 | } 177 | 178 | .description a { 179 | padding: 1rem; 180 | } 181 | 182 | .description p, 183 | .description div { 184 | display: flex; 185 | justify-content: center; 186 | position: fixed; 187 | width: 100%; 188 | } 189 | 190 | .description p { 191 | align-items: center; 192 | inset: 0 0 auto; 193 | padding: 2rem 1rem 1.4rem; 194 | border-radius: 0; 195 | border: none; 196 | border-bottom: 1px solid rgba(var(--callout-border-rgb), 0.25); 197 | background: linear-gradient( 198 | to bottom, 199 | rgba(var(--background-start-rgb), 1), 200 | rgba(var(--callout-rgb), 0.5) 201 | ); 202 | background-clip: padding-box; 203 | backdrop-filter: blur(24px); 204 | } 205 | 206 | .description div { 207 | align-items: flex-end; 208 | pointer-events: none; 209 | inset: auto 0 0; 210 | padding: 2rem; 211 | height: 200px; 212 | background: linear-gradient(to bottom, transparent 0%, rgb(var(--background-end-rgb)) 40%); 213 | z-index: 1; 214 | } 215 | } 216 | 217 | /* Tablet and Smaller Desktop */ 218 | @media (min-width: 701px) and (max-width: 1120px) { 219 | .grid { 220 | grid-template-columns: repeat(2, 50%); 221 | } 222 | } 223 | 224 | @media (prefers-color-scheme: dark) { 225 | .vercelLogo { 226 | filter: invert(1); 227 | } 228 | 229 | .logo { 230 | filter: invert(1) drop-shadow(0 0 0.3rem #ffffff70); 231 | } 232 | } 233 | 234 | @keyframes rotate { 235 | from { 236 | transform: rotate(360deg); 237 | } 238 | to { 239 | transform: rotate(0deg); 240 | } 241 | } 242 | -------------------------------------------------------------------------------- /examples/nextjs/src/app/page.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import Image from 'next/image' 4 | import styles from './page.module.css' 5 | import { Connect } from './Connect' 6 | 7 | export default function Home() { 8 | return ( 9 |
10 |
11 | Next.js Logo 19 |

@txnlab/use-wallet-react

20 | 21 |
22 |
23 | ) 24 | } 25 | -------------------------------------------------------------------------------- /examples/nextjs/src/app/providers.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import { NetworkId, WalletId, WalletManager, WalletProvider } from '@txnlab/use-wallet-react' 4 | 5 | const walletManager = new WalletManager({ 6 | wallets: [ 7 | WalletId.DEFLY, 8 | WalletId.DEFLY_WEB, 9 | WalletId.EXODUS, 10 | WalletId.PERA, 11 | { 12 | id: WalletId.WALLETCONNECT, 13 | options: { projectId: 'fcfde0713d43baa0d23be0773c80a72b' } 14 | }, 15 | { 16 | id: WalletId.BIATEC, 17 | options: { projectId: 'fcfde0713d43baa0d23be0773c80a72b' } 18 | }, 19 | WalletId.KMD, 20 | WalletId.KIBISIS, 21 | WalletId.LUTE, 22 | { 23 | id: WalletId.MAGIC, 24 | options: { apiKey: 'pk_live_D17FD8D89621B5F3' } 25 | }, 26 | WalletId.MNEMONIC 27 | ], 28 | defaultNetwork: NetworkId.TESTNET 29 | }) 30 | 31 | export function Providers({ children }: { children: React.ReactNode }) { 32 | return {children} 33 | } 34 | -------------------------------------------------------------------------------- /examples/nextjs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["dom", "dom.iterable", "esnext"], 4 | "allowJs": true, 5 | "skipLibCheck": true, 6 | "strict": true, 7 | "noEmit": true, 8 | "esModuleInterop": true, 9 | "module": "esnext", 10 | "moduleResolution": "bundler", 11 | "resolveJsonModule": true, 12 | "isolatedModules": true, 13 | "jsx": "preserve", 14 | "incremental": true, 15 | "plugins": [ 16 | { 17 | "name": "next" 18 | } 19 | ], 20 | "paths": { 21 | "@/*": ["./src/*"] 22 | } 23 | }, 24 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 25 | "exclude": ["node_modules"] 26 | } 27 | -------------------------------------------------------------------------------- /examples/nuxt/.gitignore: -------------------------------------------------------------------------------- 1 | # Nuxt dev/build outputs 2 | .output 3 | .data 4 | .nuxt 5 | .nitro 6 | .cache 7 | dist 8 | 9 | # Node dependencies 10 | node_modules 11 | 12 | # Logs 13 | logs 14 | *.log 15 | 16 | # Misc 17 | .DS_Store 18 | .fleet 19 | .idea 20 | 21 | # Local env files 22 | .env 23 | .env.* 24 | !.env.example 25 | -------------------------------------------------------------------------------- /examples/nuxt/README.md: -------------------------------------------------------------------------------- 1 | # Nuxt.js example app 2 | 3 | This example provides a minimal setup to get [@txnlab/use-wallet-vue](https://github.com/TxnLab/use-wallet/tree/v3/packages/use-wallet-vue) working in a Nuxt app. 4 | -------------------------------------------------------------------------------- /examples/nuxt/app.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /examples/nuxt/nuxt.config.ts: -------------------------------------------------------------------------------- 1 | export default defineNuxtConfig({ 2 | devtools: { enabled: true }, 3 | 4 | typescript: { 5 | strict: true 6 | }, 7 | 8 | vite: { 9 | define: { 10 | global: 'globalThis' 11 | }, 12 | build: { 13 | target: 'es2020', 14 | chunkSizeWarningLimit: 700 15 | } 16 | }, 17 | 18 | compatibilityDate: '2025-05-02' 19 | }) 20 | -------------------------------------------------------------------------------- /examples/nuxt/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "use-wallet-example-nuxt", 3 | "type": "module", 4 | "scripts": { 5 | "build": "nuxt build", 6 | "dev": "nuxt dev", 7 | "generate": "nuxt generate", 8 | "preview": "nuxt preview", 9 | "postinstall": "nuxt prepare", 10 | "lint": "eslint -c \"../../.eslintrc.json\" \"**/*.{js,ts}\"", 11 | "typecheck": "npx nuxi typecheck", 12 | "test:e2e": "playwright test" 13 | }, 14 | "dependencies": { 15 | "@blockshake/defly-connect": "^1.2.1", 16 | "@noble/ed25519": "^2.2.3", 17 | "@perawallet/connect": "^1.4.1", 18 | "@txnlab/use-wallet": "workspace:*", 19 | "@txnlab/use-wallet-vue": "workspace:*", 20 | "@walletconnect/modal": "^2.7.0", 21 | "@walletconnect/sign-client": "^2.19.2", 22 | "algosdk": "3.2.0", 23 | "lute-connect": "^1.6.1", 24 | "nuxt": "3.16.1", 25 | "vue": "3.5.13", 26 | "vue-router": "4.5.0" 27 | }, 28 | "devDependencies": { 29 | "@types/node": "20.11.30", 30 | "typescript": "5.8.2", 31 | "vue-tsc": "2.2.8" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /examples/nuxt/playwright.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig, devices } from '@playwright/test' 2 | 3 | // Use process.env.PORT by default and fallback to port 3000 4 | const PORT = process.env.PORT || 3000 5 | 6 | /** 7 | * See https://playwright.dev/docs/test-configuration. 8 | */ 9 | export default defineConfig({ 10 | testDir: '../e2e-tests', 11 | /* Run tests in files in parallel */ 12 | fullyParallel: true, 13 | /* Fail the build on CI if you accidentally left test.only in the source code. */ 14 | forbidOnly: !!process.env.CI, 15 | /* Retry on CI only */ 16 | retries: process.env.CI ? 2 : 0, 17 | /* Opt out of parallel tests on CI. */ 18 | workers: process.env.CI ? 1 : undefined, 19 | /* Reporter to use. See https://playwright.dev/docs/test-reporters */ 20 | reporter: 'list', 21 | /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ 22 | use: { 23 | /* Base URL to use in actions like `await page.goto('/')`. */ 24 | baseURL: `http://localhost:${PORT}`, 25 | 26 | /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ 27 | trace: 'on-first-retry' 28 | }, 29 | 30 | /* Configure projects for major browsers */ 31 | projects: [ 32 | { 33 | name: 'chromium', 34 | use: { ...devices['Desktop Chrome'] } 35 | }, 36 | 37 | { 38 | name: 'firefox', 39 | use: { ...devices['Desktop Firefox'] } 40 | }, 41 | 42 | { 43 | name: 'webkit', 44 | use: { ...devices['Desktop Safari'] } 45 | } 46 | 47 | /* Test against mobile viewports. */ 48 | // { 49 | // name: 'Mobile Chrome', 50 | // use: { ...devices['Pixel 5'] }, 51 | // }, 52 | // { 53 | // name: 'Mobile Safari', 54 | // use: { ...devices['iPhone 12'] }, 55 | // }, 56 | 57 | /* Test against branded browsers. */ 58 | // { 59 | // name: 'Microsoft Edge', 60 | // use: { ...devices['Desktop Edge'], channel: 'msedge' }, 61 | // }, 62 | // { 63 | // name: 'Google Chrome', 64 | // use: { ...devices['Desktop Chrome'], channel: 'chrome' }, 65 | // }, 66 | ], 67 | 68 | /* Run your local dev server before starting the tests */ 69 | webServer: { 70 | command: 'pnpm build && pnpm preview', 71 | url: `http://localhost:${PORT}`, 72 | reuseExistingServer: !process.env.CI, 73 | stdout: 'pipe' 74 | } 75 | }) 76 | -------------------------------------------------------------------------------- /examples/nuxt/plugins/walletManager.client.ts: -------------------------------------------------------------------------------- 1 | import { NetworkId, WalletId, WalletManagerPlugin } from '@txnlab/use-wallet-vue' 2 | import { defineNuxtPlugin } from '#app' 3 | 4 | export default defineNuxtPlugin((nuxtApp) => { 5 | nuxtApp.vueApp.use(WalletManagerPlugin, { 6 | wallets: [ 7 | WalletId.DEFLY, 8 | WalletId.DEFLY_WEB, 9 | WalletId.EXODUS, 10 | WalletId.PERA, 11 | { 12 | id: WalletId.WALLETCONNECT, 13 | options: { projectId: 'fcfde0713d43baa0d23be0773c80a72b' } 14 | }, 15 | { 16 | id: WalletId.BIATEC, 17 | options: { projectId: 'fcfde0713d43baa0d23be0773c80a72b' } 18 | }, 19 | WalletId.KMD, 20 | WalletId.KIBISIS, 21 | WalletId.LUTE, 22 | { 23 | id: WalletId.MAGIC, 24 | options: { apiKey: 'pk_live_D17FD8D89621B5F3' } 25 | }, 26 | WalletId.MNEMONIC 27 | ], 28 | defaultNetwork: NetworkId.TESTNET 29 | }) 30 | }) 31 | -------------------------------------------------------------------------------- /examples/nuxt/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TxnLab/use-wallet/0e17652ff1723c8c5943aa2e403a605ffb646ddb/examples/nuxt/public/favicon.ico -------------------------------------------------------------------------------- /examples/nuxt/server/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../.nuxt/tsconfig.server.json" 3 | } 4 | -------------------------------------------------------------------------------- /examples/nuxt/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // https://nuxt.com/docs/guide/concepts/typescript 3 | "extends": "./.nuxt/tsconfig.json" 4 | } 5 | -------------------------------------------------------------------------------- /examples/react-ts/.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | .env 4 | -------------------------------------------------------------------------------- /examples/react-ts/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { browser: true, es2020: true }, 4 | extends: ['../../.eslintrc.json', 'plugin:react-hooks/recommended'], 5 | ignorePatterns: ['dist', '.eslintrc.cjs'], 6 | parser: '@typescript-eslint/parser', 7 | plugins: ['react-refresh'], 8 | rules: { 9 | 'react-refresh/only-export-components': ['warn', { allowConstantExport: true }] 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /examples/react-ts/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /examples/react-ts/README.md: -------------------------------------------------------------------------------- 1 | # React example app 2 | 3 | This example provides a minimal setup to get [@txnlab/use-wallet-react](https://github.com/TxnLab/use-wallet/tree/v3/packages/use-wallet-react) working in a Vite React app with TypeScript. 4 | -------------------------------------------------------------------------------- /examples/react-ts/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | use-wallet | Vite + React + TS 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /examples/react-ts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "use-wallet-example-react", 3 | "type": "module", 4 | "scripts": { 5 | "dev": "vite", 6 | "preview": "vite preview", 7 | "build": "tsc && vite build", 8 | "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", 9 | "typecheck": "tsc --noEmit", 10 | "test:e2e": "playwright test" 11 | }, 12 | "dependencies": { 13 | "@blockshake/defly-connect": "^1.2.1", 14 | "@noble/ed25519": "^2.2.3", 15 | "@perawallet/connect": "^1.4.1", 16 | "@txnlab/use-wallet-react": "workspace:*", 17 | "@walletconnect/modal": "^2.7.0", 18 | "@walletconnect/sign-client": "^2.19.2", 19 | "algosdk": "3.2.0", 20 | "lute-connect": "^1.6.1", 21 | "react": "18.3.1", 22 | "react-dom": "18.3.1" 23 | }, 24 | "devDependencies": { 25 | "@types/react": "18.3.20", 26 | "@types/react-dom": "18.3.5", 27 | "@typescript-eslint/eslint-plugin": "8.28.0", 28 | "@typescript-eslint/parser": "8.28.0", 29 | "@vitejs/plugin-react": "4.3.4", 30 | "eslint": "8.57.1", 31 | "eslint-plugin-react-hooks": "5.2.0", 32 | "eslint-plugin-react-refresh": "0.4.19", 33 | "typescript": "5.8.2", 34 | "vite": "6.2.3" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /examples/react-ts/playwright.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig, devices } from '@playwright/test' 2 | 3 | // Use process.env.PORT by default and fallback to port 4173 4 | const PORT = process.env.PORT || 4173 5 | 6 | /** 7 | * See https://playwright.dev/docs/test-configuration. 8 | */ 9 | export default defineConfig({ 10 | testDir: '../e2e-tests', 11 | /* Run tests in files in parallel */ 12 | fullyParallel: true, 13 | /* Fail the build on CI if you accidentally left test.only in the source code. */ 14 | forbidOnly: !!process.env.CI, 15 | /* Retry on CI only */ 16 | retries: process.env.CI ? 2 : 0, 17 | /* Opt out of parallel tests on CI. */ 18 | workers: process.env.CI ? 1 : undefined, 19 | /* Reporter to use. See https://playwright.dev/docs/test-reporters */ 20 | reporter: 'list', 21 | /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ 22 | use: { 23 | /* Base URL to use in actions like `await page.goto('/')`. */ 24 | baseURL: `http://localhost:${PORT}`, 25 | 26 | /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ 27 | trace: 'on-first-retry' 28 | }, 29 | 30 | /* Configure projects for major browsers */ 31 | projects: [ 32 | { 33 | name: 'chromium', 34 | use: { ...devices['Desktop Chrome'] } 35 | }, 36 | 37 | { 38 | name: 'firefox', 39 | use: { ...devices['Desktop Firefox'] } 40 | }, 41 | 42 | { 43 | name: 'webkit', 44 | use: { ...devices['Desktop Safari'] } 45 | } 46 | 47 | /* Test against mobile viewports. */ 48 | // { 49 | // name: 'Mobile Chrome', 50 | // use: { ...devices['Pixel 5'] }, 51 | // }, 52 | // { 53 | // name: 'Mobile Safari', 54 | // use: { ...devices['iPhone 12'] }, 55 | // }, 56 | 57 | /* Test against branded browsers. */ 58 | // { 59 | // name: 'Microsoft Edge', 60 | // use: { ...devices['Desktop Edge'], channel: 'msedge' }, 61 | // }, 62 | // { 63 | // name: 'Google Chrome', 64 | // use: { ...devices['Desktop Chrome'], channel: 'chrome' }, 65 | // }, 66 | ], 67 | 68 | /* Run your local dev server before starting the tests */ 69 | webServer: { 70 | command: 'pnpm build && pnpm preview', 71 | url: `http://localhost:${PORT}`, 72 | reuseExistingServer: !process.env.CI, 73 | stdout: 'pipe' 74 | } 75 | }) 76 | -------------------------------------------------------------------------------- /examples/react-ts/public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/react-ts/src/App.css: -------------------------------------------------------------------------------- 1 | #root { 2 | max-width: 1280px; 3 | margin: 0 auto; 4 | padding: 2rem; 5 | text-align: center; 6 | } 7 | 8 | .logo { 9 | height: 6em; 10 | padding: 1.5em; 11 | will-change: filter; 12 | transition: filter 300ms; 13 | } 14 | .logo:hover { 15 | filter: drop-shadow(0 0 2em #646cffaa); 16 | } 17 | .logo.react:hover { 18 | filter: drop-shadow(0 0 2em #61dafbaa); 19 | } 20 | 21 | .network-group { 22 | display: flex; 23 | flex-direction: column; 24 | align-items: center; 25 | gap: 1em; 26 | margin: 2em; 27 | padding: 2em; 28 | background-color: light-dark(rgba(0, 0, 0, 0.025), rgba(255, 255, 255, 0.025)); 29 | border-color: light-dark(rgba(0, 0, 0, 0.1), rgba(255, 255, 255, 0.1)); 30 | border-style: solid; 31 | border-width: 1px; 32 | border-radius: 8px; 33 | } 34 | 35 | .network-group h4 { 36 | margin: 0; 37 | } 38 | 39 | .network-group .active-network { 40 | text-transform: capitalize; 41 | } 42 | 43 | .network-buttons { 44 | display: flex; 45 | align-items: center; 46 | justify-content: center; 47 | flex-wrap: wrap; 48 | gap: 0.5em; 49 | } 50 | 51 | .wallet-group { 52 | display: flex; 53 | flex-direction: column; 54 | align-items: center; 55 | gap: 1em; 56 | margin-bottom: 2em; 57 | } 58 | 59 | .wallet-group h4 { 60 | margin: 0; 61 | } 62 | 63 | .wallet-buttons { 64 | display: flex; 65 | align-items: center; 66 | justify-content: center; 67 | flex-wrap: wrap; 68 | gap: 0.5em; 69 | } 70 | 71 | .input-group { 72 | display: flex; 73 | align-items: center; 74 | justify-content: center; 75 | gap: 0.6em; 76 | } 77 | 78 | .input-group label { 79 | margin-left: 1em; 80 | font-weight: 500; 81 | } 82 | 83 | .input-group input { 84 | min-width: 16em; 85 | } 86 | 87 | .input-group input[disabled] { 88 | opacity: 0.75; 89 | color: light-dark(rgba(16, 16, 16, 0.3), rgba(255, 255, 255, 0.3)); 90 | } 91 | 92 | @keyframes logo-spin { 93 | from { 94 | transform: rotate(0deg); 95 | } 96 | to { 97 | transform: rotate(360deg); 98 | } 99 | } 100 | 101 | @media (prefers-reduced-motion: no-preference) { 102 | a:nth-of-type(2) .logo { 103 | animation: logo-spin infinite 20s linear; 104 | } 105 | } 106 | 107 | .config-section { 108 | width: 100%; 109 | max-width: 500px; 110 | margin-top: 1em; 111 | } 112 | 113 | .config-form { 114 | display: flex; 115 | flex-direction: column; 116 | gap: 1em; 117 | margin: 1em 0; 118 | padding: 1em; 119 | border: 1px solid rgba(255, 255, 255, 0.1); 120 | border-radius: 4px; 121 | } 122 | 123 | .form-group { 124 | display: flex; 125 | flex-direction: column; 126 | gap: 0.5em; 127 | text-align: left; 128 | } 129 | 130 | .form-group label { 131 | font-size: 0.9em; 132 | opacity: 0.8; 133 | } 134 | 135 | .form-group input { 136 | padding: 0.5em; 137 | border: 1px solid rgba(255, 255, 255, 0.1); 138 | border-radius: 4px; 139 | background: rgba(0, 0, 0, 0.1); 140 | color: inherit; 141 | } 142 | 143 | .current-config { 144 | margin-top: 1em; 145 | text-align: left; 146 | } 147 | 148 | .current-config h5 { 149 | margin: 0 0 0.5em 0; 150 | font-size: 0.9em; 151 | opacity: 0.8; 152 | } 153 | 154 | .current-config pre { 155 | padding: 1em; 156 | background: rgba(0, 0, 0, 0.1); 157 | border-radius: 4px; 158 | font-size: 0.9em; 159 | overflow-x: auto; 160 | } 161 | 162 | .error-message { 163 | margin-top: 1em; 164 | padding: 0.5em 1em; 165 | color: #ff4444; 166 | background: rgba(255, 68, 68, 0.1); 167 | border-radius: 4px; 168 | } 169 | -------------------------------------------------------------------------------- /examples/react-ts/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { NetworkId, WalletId, WalletManager, WalletProvider } from '@txnlab/use-wallet-react' 2 | import { Connect } from './Connect' 3 | import { NetworkControls } from './NetworkControls' 4 | import reactLogo from './assets/react.svg' 5 | import viteLogo from '/vite.svg' 6 | import './App.css' 7 | 8 | const walletManager = new WalletManager({ 9 | wallets: [ 10 | WalletId.DEFLY, 11 | WalletId.DEFLY_WEB, 12 | WalletId.EXODUS, 13 | WalletId.PERA, 14 | { 15 | id: WalletId.WALLETCONNECT, 16 | options: { projectId: 'fcfde0713d43baa0d23be0773c80a72b' } 17 | }, 18 | { 19 | id: WalletId.BIATEC, 20 | options: { projectId: 'fcfde0713d43baa0d23be0773c80a72b' } 21 | }, 22 | WalletId.KMD, 23 | WalletId.KIBISIS, 24 | WalletId.LUTE, 25 | { 26 | id: WalletId.MAGIC, 27 | options: { apiKey: 'pk_live_D17FD8D89621B5F3' } 28 | }, 29 | WalletId.MNEMONIC 30 | ], 31 | defaultNetwork: NetworkId.TESTNET 32 | }) 33 | 34 | function App() { 35 | return ( 36 | 37 |
38 | 39 | Vite logo 40 | 41 | 42 | React logo 43 | 44 |
45 |

@txnlab/use-wallet-react

46 | 47 | 48 |
49 | ) 50 | } 51 | 52 | export default App 53 | -------------------------------------------------------------------------------- /examples/react-ts/src/NetworkControls.tsx: -------------------------------------------------------------------------------- 1 | import { AlgodConfig, NetworkId, useNetwork } from '@txnlab/use-wallet-react' 2 | import * as React from 'react' 3 | 4 | export function NetworkControls() { 5 | const { 6 | activeNetwork, 7 | networkConfig, 8 | activeNetworkConfig, 9 | setActiveNetwork, 10 | updateAlgodConfig, 11 | resetNetworkConfig 12 | } = useNetwork() 13 | 14 | const [error, setError] = React.useState('') 15 | const [showConfig, setShowConfig] = React.useState(false) 16 | 17 | const [configForm, setConfigForm] = React.useState>({ 18 | baseServer: activeNetworkConfig.algod.baseServer, 19 | port: activeNetworkConfig.algod.port?.toString() || '', 20 | token: activeNetworkConfig.algod.token?.toString() || '' 21 | }) 22 | 23 | React.useEffect(() => { 24 | setConfigForm({ 25 | baseServer: activeNetworkConfig.algod.baseServer, 26 | port: activeNetworkConfig.algod.port?.toString() || '', 27 | token: activeNetworkConfig.algod.token?.toString() || '' 28 | }) 29 | }, [activeNetworkConfig]) 30 | 31 | const handleNetworkSwitch = async (networkId: NetworkId) => { 32 | try { 33 | setError('') 34 | await setActiveNetwork(networkId) 35 | } catch (error) { 36 | setError(error instanceof Error ? error.message : 'Failed to switch networks') 37 | } 38 | } 39 | 40 | const handleInputChange = (event: React.ChangeEvent) => { 41 | const { name, value } = event.target 42 | setConfigForm((prev) => ({ ...prev, [name]: value })) 43 | } 44 | 45 | const handleConfigSubmit = async (event: React.FormEvent) => { 46 | event.preventDefault() 47 | try { 48 | setError('') 49 | updateAlgodConfig(activeNetwork, { 50 | baseServer: configForm.baseServer, 51 | port: configForm.port || undefined, 52 | token: configForm.token 53 | }) 54 | } catch (error) { 55 | setError(error instanceof Error ? error.message : 'Failed to update node configuration') 56 | } 57 | } 58 | 59 | const handleResetConfig = () => { 60 | try { 61 | setError('') 62 | resetNetworkConfig(activeNetwork) 63 | } catch (error) { 64 | setError(error instanceof Error ? error.message : 'Failed to reset node configuration') 65 | } 66 | } 67 | 68 | return ( 69 |
70 |

Network Controls

71 |
Active: {activeNetwork}
72 | 73 |
74 | {Object.keys(networkConfig).map((networkId) => ( 75 | 82 | ))} 83 |
84 | 85 |
86 | 89 | 90 | {showConfig && ( 91 |
92 |
93 | 94 | 102 |
103 | 104 |
105 | 106 | 114 |
115 | 116 |
117 | 118 | 126 |
127 | 128 | 129 | 132 |
133 | )} 134 | 135 |
136 |
Current Algod Configuration:
137 |
{JSON.stringify(activeNetworkConfig.algod, null, 2)}
138 |
139 |
140 | 141 | {error &&
{error}
} 142 |
143 | ) 144 | } 145 | -------------------------------------------------------------------------------- /examples/react-ts/src/assets/react.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/react-ts/src/index.css: -------------------------------------------------------------------------------- 1 | :root { 2 | font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; 3 | line-height: 1.5; 4 | font-weight: 400; 5 | 6 | color-scheme: light dark; 7 | color: rgba(255, 255, 255, 0.87); 8 | background-color: #242424; 9 | 10 | font-synthesis: none; 11 | text-rendering: optimizeLegibility; 12 | -webkit-font-smoothing: antialiased; 13 | -moz-osx-font-smoothing: grayscale; 14 | } 15 | 16 | a { 17 | font-weight: 500; 18 | color: #646cff; 19 | text-decoration: inherit; 20 | } 21 | a:hover { 22 | color: #535bf2; 23 | } 24 | 25 | body { 26 | margin: 0; 27 | display: flex; 28 | place-items: center; 29 | min-width: 320px; 30 | min-height: 100vh; 31 | } 32 | 33 | h1 { 34 | font-size: 3.2em; 35 | line-height: 1.1; 36 | } 37 | 38 | button { 39 | border-radius: 8px; 40 | border: 1px solid transparent; 41 | padding: 0.6em 1.2em; 42 | font-size: 1em; 43 | font-weight: 500; 44 | font-family: inherit; 45 | background-color: #1a1a1a; 46 | cursor: pointer; 47 | transition: border-color 0.25s; 48 | } 49 | button:not(:disabled):hover { 50 | border-color: #646cff; 51 | } 52 | button:focus, 53 | button:focus-visible { 54 | outline: 4px auto -webkit-focus-ring-color; 55 | } 56 | button:disabled { 57 | opacity: 0.75; 58 | cursor: default; 59 | } 60 | 61 | input[type='text'], 62 | input[type='email'] { 63 | border-radius: 8px; 64 | border: 1px solid #1a1a1a; 65 | padding: 0.6em 0.9em; 66 | font-size: 1em; 67 | font-weight: 500; 68 | font-family: inherit; 69 | background-color: #1a1a1a; 70 | color: #ffffff; 71 | transition: border-color 0.25s; 72 | } 73 | 74 | select { 75 | border-radius: 8px; 76 | border: 1px solid #1a1a1a; 77 | padding: 0.6em 0.9em; 78 | font-size: 1em; 79 | font-weight: 500; 80 | font-family: inherit; 81 | background-color: #1a1a1a; 82 | color: #ffffff; 83 | transition: border-color 0.25s; 84 | background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e"); 85 | background-position: right 0.5rem center; 86 | background-repeat: no-repeat; 87 | background-size: 1.5em 1.5em; 88 | padding-right: 2.5rem; 89 | -webkit-print-color-adjust: exact; 90 | print-color-adjust: exact; 91 | -moz-appearance: none !important; 92 | -webkit-appearance: none !important; 93 | appearance: none !important; 94 | } 95 | 96 | @media (prefers-color-scheme: light) { 97 | :root { 98 | color: #213547; 99 | background-color: #ffffff; 100 | } 101 | a:hover { 102 | color: #747bff; 103 | } 104 | button { 105 | background-color: #f9f9f9; 106 | } 107 | input[type='text'], 108 | input[type='email'], 109 | select { 110 | background-color: #f9f9f9; 111 | color: #000000; 112 | border-color: #f9f9f9; 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /examples/react-ts/src/main.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom/client' 3 | import App from './App.tsx' 4 | import './index.css' 5 | 6 | ReactDOM.createRoot(document.getElementById('root')!).render( 7 | 8 | 9 | 10 | ) 11 | -------------------------------------------------------------------------------- /examples/react-ts/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /examples/react-ts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "noEmit": true, 15 | "jsx": "react-jsx", 16 | 17 | /* Linting */ 18 | "strict": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "noFallthroughCasesInSwitch": true 22 | }, 23 | "include": ["src"], 24 | "references": [{ "path": "./tsconfig.node.json" }] 25 | } 26 | -------------------------------------------------------------------------------- /examples/react-ts/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "skipLibCheck": true, 5 | "module": "ESNext", 6 | "moduleResolution": "bundler", 7 | "allowSyntheticDefaultImports": true 8 | }, 9 | "include": ["vite.config.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /examples/react-ts/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | build: { 8 | chunkSizeWarningLimit: 700 9 | } 10 | }) 11 | -------------------------------------------------------------------------------- /examples/solid-ts/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /examples/solid-ts/README.md: -------------------------------------------------------------------------------- 1 | # Solid.js example app 2 | 3 | This example provides a minimal setup to get [@txnlab/use-wallet-solid](https://github.com/TxnLab/use-wallet/tree/v3/packages/use-wallet-solid) working in a Vite Solid.js app with TypeScript. 4 | -------------------------------------------------------------------------------- /examples/solid-ts/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | use-wallet | Vite + Solid + TS 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /examples/solid-ts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "use-wallet-example-solid", 3 | "type": "module", 4 | "scripts": { 5 | "dev": "vite", 6 | "build": "tsc && vite build", 7 | "preview": "vite preview", 8 | "lint": "eslint -c \"../../.eslintrc.json\" \"**/*.{js,ts}\"", 9 | "prettier": "prettier --check \"**/*.{js,ts}\"", 10 | "typecheck": "tsc --noEmit", 11 | "test:e2e": "playwright test" 12 | }, 13 | "dependencies": { 14 | "@blockshake/defly-connect": "^1.2.1", 15 | "@noble/ed25519": "^2.2.3", 16 | "@perawallet/connect": "^1.4.1", 17 | "@txnlab/use-wallet-solid": "workspace:*", 18 | "@walletconnect/modal": "^2.7.0", 19 | "@walletconnect/sign-client": "^2.19.2", 20 | "algosdk": "3.2.0", 21 | "lute-connect": "^1.6.1", 22 | "solid-js": "1.9.5" 23 | }, 24 | "devDependencies": { 25 | "typescript": "5.8.2", 26 | "vite": "6.2.3", 27 | "vite-plugin-solid": "2.11.6" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /examples/solid-ts/playwright.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig, devices } from '@playwright/test' 2 | 3 | // Use process.env.PORT by default and fallback to port 4173 4 | const PORT = process.env.PORT || 4173 5 | 6 | /** 7 | * See https://playwright.dev/docs/test-configuration. 8 | */ 9 | export default defineConfig({ 10 | testDir: '../e2e-tests', 11 | /* Run tests in files in parallel */ 12 | fullyParallel: true, 13 | /* Fail the build on CI if you accidentally left test.only in the source code. */ 14 | forbidOnly: !!process.env.CI, 15 | /* Retry on CI only */ 16 | retries: process.env.CI ? 2 : 0, 17 | /* Opt out of parallel tests on CI. */ 18 | workers: process.env.CI ? 1 : undefined, 19 | /* Reporter to use. See https://playwright.dev/docs/test-reporters */ 20 | reporter: 'list', 21 | /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ 22 | use: { 23 | /* Base URL to use in actions like `await page.goto('/')`. */ 24 | baseURL: `http://localhost:${PORT}`, 25 | 26 | /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ 27 | trace: 'on-first-retry' 28 | }, 29 | 30 | /* Configure projects for major browsers */ 31 | projects: [ 32 | { 33 | name: 'chromium', 34 | use: { ...devices['Desktop Chrome'] } 35 | }, 36 | 37 | { 38 | name: 'firefox', 39 | use: { ...devices['Desktop Firefox'] } 40 | }, 41 | 42 | { 43 | name: 'webkit', 44 | use: { ...devices['Desktop Safari'] } 45 | } 46 | 47 | /* Test against mobile viewports. */ 48 | // { 49 | // name: 'Mobile Chrome', 50 | // use: { ...devices['Pixel 5'] }, 51 | // }, 52 | // { 53 | // name: 'Mobile Safari', 54 | // use: { ...devices['iPhone 12'] }, 55 | // }, 56 | 57 | /* Test against branded browsers. */ 58 | // { 59 | // name: 'Microsoft Edge', 60 | // use: { ...devices['Desktop Edge'], channel: 'msedge' }, 61 | // }, 62 | // { 63 | // name: 'Google Chrome', 64 | // use: { ...devices['Desktop Chrome'], channel: 'chrome' }, 65 | // }, 66 | ], 67 | 68 | /* Run your local dev server before starting the tests */ 69 | webServer: { 70 | command: 'pnpm build && pnpm preview', 71 | url: `http://localhost:${PORT}`, 72 | reuseExistingServer: !process.env.CI, 73 | stdout: 'pipe' 74 | } 75 | }) 76 | -------------------------------------------------------------------------------- /examples/solid-ts/public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/solid-ts/src/App.css: -------------------------------------------------------------------------------- 1 | #root { 2 | max-width: 1280px; 3 | margin: 0 auto; 4 | padding: 2rem; 5 | text-align: center; 6 | } 7 | 8 | .logo { 9 | height: 6em; 10 | padding: 1.5em; 11 | will-change: filter; 12 | transition: filter 300ms; 13 | } 14 | .logo:hover { 15 | filter: drop-shadow(0 0 2em #646cffaa); 16 | } 17 | .logo.solid:hover { 18 | filter: drop-shadow(0 0 2em #61dafbaa); 19 | } 20 | 21 | .network-group { 22 | display: flex; 23 | flex-direction: column; 24 | align-items: center; 25 | gap: 1em; 26 | margin: 2em; 27 | padding: 2em; 28 | background-color: light-dark(rgba(0, 0, 0, 0.025), rgba(255, 255, 255, 0.025)); 29 | border-color: light-dark(rgba(0, 0, 0, 0.1), rgba(255, 255, 255, 0.1)); 30 | border-style: solid; 31 | border-width: 1px; 32 | border-radius: 8px; 33 | } 34 | 35 | .network-group h4 { 36 | margin: 0; 37 | } 38 | 39 | .network-group .active-network { 40 | text-transform: capitalize; 41 | } 42 | 43 | .network-buttons { 44 | display: flex; 45 | align-items: center; 46 | justify-content: center; 47 | flex-wrap: wrap; 48 | gap: 0.5em; 49 | } 50 | 51 | .wallet-group { 52 | display: flex; 53 | flex-direction: column; 54 | align-items: center; 55 | gap: 1em; 56 | margin-bottom: 2em; 57 | } 58 | 59 | .wallet-group h4 { 60 | margin: 0; 61 | } 62 | 63 | .wallet-buttons { 64 | display: flex; 65 | align-items: center; 66 | justify-content: center; 67 | flex-wrap: wrap; 68 | gap: 0.5em; 69 | } 70 | 71 | .input-group { 72 | display: flex; 73 | align-items: center; 74 | justify-content: center; 75 | gap: 0.6em; 76 | } 77 | 78 | .input-group label { 79 | margin-left: 1em; 80 | font-weight: 500; 81 | } 82 | 83 | .input-group input { 84 | min-width: 16em; 85 | } 86 | 87 | .input-group input[disabled] { 88 | opacity: 0.75; 89 | color: light-dark(rgba(16, 16, 16, 0.3), rgba(255, 255, 255, 0.3)); 90 | } 91 | 92 | .config-section { 93 | width: 100%; 94 | max-width: 500px; 95 | margin-top: 1em; 96 | } 97 | 98 | .config-form { 99 | display: flex; 100 | flex-direction: column; 101 | gap: 1em; 102 | margin: 1em 0; 103 | padding: 1em; 104 | border: 1px solid rgba(255, 255, 255, 0.1); 105 | border-radius: 4px; 106 | } 107 | 108 | .form-group { 109 | display: flex; 110 | flex-direction: column; 111 | gap: 0.5em; 112 | text-align: left; 113 | } 114 | 115 | .form-group label { 116 | font-size: 0.9em; 117 | opacity: 0.8; 118 | } 119 | 120 | .current-config { 121 | margin-top: 1em; 122 | text-align: left; 123 | } 124 | 125 | .current-config h5 { 126 | margin: 0 0 0.5em 0; 127 | font-size: 0.9em; 128 | opacity: 0.8; 129 | } 130 | 131 | .current-config pre { 132 | padding: 1em; 133 | background: rgba(0, 0, 0, 0.1); 134 | border-radius: 4px; 135 | font-size: 0.9em; 136 | overflow-x: auto; 137 | } 138 | 139 | .error-message { 140 | margin-top: 1em; 141 | padding: 0.5em 1em; 142 | color: #ff4444; 143 | background: rgba(255, 68, 68, 0.1); 144 | border-radius: 4px; 145 | } 146 | 147 | @media (prefers-color-scheme: light) { 148 | .config-form { 149 | border-color: rgba(0, 0, 0, 0.1); 150 | } 151 | 152 | .form-group input, 153 | .current-config pre { 154 | background: rgba(0, 0, 0, 0.05); 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /examples/solid-ts/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { NetworkId, WalletId, WalletManager, WalletProvider } from '@txnlab/use-wallet-solid' 2 | import { Connect } from './Connect' 3 | import { NetworkControls } from './NetworkControls' 4 | import solidLogo from './assets/solid.svg' 5 | import viteLogo from '/vite.svg' 6 | import './App.css' 7 | 8 | const walletManager = new WalletManager({ 9 | wallets: [ 10 | WalletId.DEFLY, 11 | WalletId.DEFLY_WEB, 12 | WalletId.EXODUS, 13 | WalletId.PERA, 14 | { 15 | id: WalletId.WALLETCONNECT, 16 | options: { projectId: 'fcfde0713d43baa0d23be0773c80a72b' } 17 | }, 18 | { 19 | id: WalletId.BIATEC, 20 | options: { projectId: 'fcfde0713d43baa0d23be0773c80a72b' } 21 | }, 22 | WalletId.KMD, 23 | WalletId.KIBISIS, 24 | WalletId.LUTE, 25 | { 26 | id: WalletId.MAGIC, 27 | options: { apiKey: 'pk_live_D17FD8D89621B5F3' } 28 | }, 29 | WalletId.MNEMONIC 30 | ], 31 | defaultNetwork: NetworkId.TESTNET 32 | }) 33 | 34 | function App() { 35 | return ( 36 | 37 | 45 |

@txnlab/use-wallet-solid

46 | 47 | 48 |
49 | ) 50 | } 51 | 52 | export default App 53 | -------------------------------------------------------------------------------- /examples/solid-ts/src/NetworkControls.tsx: -------------------------------------------------------------------------------- 1 | import { AlgodConfig, NetworkId, useNetwork } from '@txnlab/use-wallet-solid' 2 | import { createSignal, createEffect } from 'solid-js' 3 | 4 | export function NetworkControls() { 5 | const { 6 | activeNetwork, 7 | networkConfig, 8 | activeNetworkConfig, 9 | setActiveNetwork, 10 | updateAlgodConfig, 11 | resetNetworkConfig 12 | } = useNetwork() 13 | 14 | const [error, setError] = createSignal('') 15 | const [showConfig, setShowConfig] = createSignal(false) 16 | const [configForm, setConfigForm] = createSignal>({ 17 | baseServer: activeNetworkConfig().algod.baseServer, 18 | port: activeNetworkConfig().algod.port?.toString() || '', 19 | token: activeNetworkConfig().algod.token?.toString() || '' 20 | }) 21 | 22 | // Update form when network config changes 23 | createEffect(() => { 24 | setConfigForm({ 25 | baseServer: activeNetworkConfig().algod.baseServer, 26 | port: activeNetworkConfig().algod.port?.toString() || '', 27 | token: activeNetworkConfig().algod.token?.toString() || '' 28 | }) 29 | }) 30 | 31 | const handleNetworkSwitch = async (networkId: NetworkId) => { 32 | try { 33 | setError('') 34 | await setActiveNetwork(networkId) 35 | } catch (error) { 36 | setError(error instanceof Error ? error.message : 'Failed to switch networks') 37 | } 38 | } 39 | 40 | const handleInputChange = (event: InputEvent & { currentTarget: HTMLInputElement }) => { 41 | const { name, value } = event.currentTarget 42 | setConfigForm((prev) => ({ ...prev, [name]: value })) 43 | } 44 | 45 | const handleConfigSubmit = async (event: Event) => { 46 | event.preventDefault() 47 | try { 48 | setError('') 49 | const form = configForm() 50 | updateAlgodConfig(activeNetwork(), { 51 | baseServer: form.baseServer, 52 | port: form.port || undefined, 53 | token: form.token 54 | }) 55 | } catch (error) { 56 | console.error('[NetworkControls] Error updating config:', error) 57 | setError(error instanceof Error ? error.message : 'Failed to update node configuration') 58 | } 59 | } 60 | 61 | const handleResetConfig = () => { 62 | try { 63 | setError('') 64 | resetNetworkConfig(activeNetwork()) 65 | } catch (error) { 66 | console.error('[NetworkControls] Error resetting config:', error) 67 | setError(error instanceof Error ? error.message : 'Failed to reset node configuration') 68 | } 69 | } 70 | 71 | return ( 72 |
73 |

Network Controls

74 |
Active: {activeNetwork()}
75 | 76 |
77 | {Object.keys(networkConfig()).map((networkId) => ( 78 | 84 | ))} 85 |
86 | 87 |
88 | 91 | 92 | {showConfig() && ( 93 |
94 |
95 | 96 | 104 |
105 | 106 |
107 | 108 | 116 |
117 | 118 |
119 | 120 | 128 |
129 | 130 | 131 | 134 |
135 | )} 136 | 137 |
138 |
Current Algod Configuration:
139 |
{JSON.stringify(activeNetworkConfig().algod, null, 2)}
140 |
141 |
142 | 143 | {error() &&
{error()}
} 144 |
145 | ) 146 | } 147 | -------------------------------------------------------------------------------- /examples/solid-ts/src/assets/solid.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/solid-ts/src/index.css: -------------------------------------------------------------------------------- 1 | :root { 2 | font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; 3 | line-height: 1.5; 4 | font-weight: 400; 5 | 6 | color-scheme: light dark; 7 | color: rgba(255, 255, 255, 0.87); 8 | background-color: #242424; 9 | 10 | font-synthesis: none; 11 | text-rendering: optimizeLegibility; 12 | -webkit-font-smoothing: antialiased; 13 | -moz-osx-font-smoothing: grayscale; 14 | } 15 | 16 | a { 17 | font-weight: 500; 18 | color: #646cff; 19 | text-decoration: inherit; 20 | } 21 | a:hover { 22 | color: #535bf2; 23 | } 24 | 25 | body { 26 | margin: 0; 27 | display: flex; 28 | place-items: center; 29 | min-width: 320px; 30 | min-height: 100vh; 31 | } 32 | 33 | h1 { 34 | font-size: 3.2em; 35 | line-height: 1.1; 36 | } 37 | 38 | button { 39 | border-radius: 8px; 40 | border: 1px solid transparent; 41 | padding: 0.6em 1.2em; 42 | font-size: 1em; 43 | font-weight: 500; 44 | font-family: inherit; 45 | background-color: #1a1a1a; 46 | cursor: pointer; 47 | transition: border-color 0.25s; 48 | } 49 | button:not(:disabled):hover { 50 | border-color: #646cff; 51 | } 52 | button:focus, 53 | button:focus-visible { 54 | outline: 4px auto -webkit-focus-ring-color; 55 | } 56 | button:disabled { 57 | opacity: 0.75; 58 | cursor: default; 59 | } 60 | 61 | input[type='text'], 62 | input[type='email'] { 63 | border-radius: 8px; 64 | border: 1px solid #1a1a1a; 65 | padding: 0.6em 0.9em; 66 | font-size: 1em; 67 | font-weight: 500; 68 | font-family: inherit; 69 | background-color: #1a1a1a; 70 | color: #ffffff; 71 | transition: border-color 0.25s; 72 | } 73 | 74 | select { 75 | border-radius: 8px; 76 | border: 1px solid #1a1a1a; 77 | padding: 0.6em 0.9em; 78 | font-size: 1em; 79 | font-weight: 500; 80 | font-family: inherit; 81 | background-color: #1a1a1a; 82 | color: #ffffff; 83 | transition: border-color 0.25s; 84 | background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e"); 85 | background-position: right 0.5rem center; 86 | background-repeat: no-repeat; 87 | background-size: 1.5em 1.5em; 88 | padding-right: 2.5rem; 89 | -webkit-print-color-adjust: exact; 90 | print-color-adjust: exact; 91 | -moz-appearance: none !important; 92 | -webkit-appearance: none !important; 93 | appearance: none !important; 94 | } 95 | 96 | @media (prefers-color-scheme: light) { 97 | :root { 98 | color: #213547; 99 | background-color: #ffffff; 100 | } 101 | a:hover { 102 | color: #747bff; 103 | } 104 | button { 105 | background-color: #f9f9f9; 106 | } 107 | input[type='text'], 108 | input[type='email'], 109 | select { 110 | background-color: #f9f9f9; 111 | color: #000000; 112 | border-color: #f9f9f9; 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /examples/solid-ts/src/index.tsx: -------------------------------------------------------------------------------- 1 | /* @refresh reload */ 2 | import { render } from 'solid-js/web' 3 | 4 | import './index.css' 5 | import App from './App' 6 | 7 | const root = document.getElementById('root') 8 | 9 | render(() => , root!) 10 | -------------------------------------------------------------------------------- /examples/solid-ts/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /examples/solid-ts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "module": "ESNext", 6 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "noEmit": true, 15 | "jsx": "preserve", 16 | "jsxImportSource": "solid-js", 17 | 18 | /* Linting */ 19 | "strict": true, 20 | "noUnusedLocals": true, 21 | "noUnusedParameters": true, 22 | "noFallthroughCasesInSwitch": true 23 | }, 24 | "include": ["src"], 25 | "references": [{ "path": "./tsconfig.node.json" }] 26 | } 27 | -------------------------------------------------------------------------------- /examples/solid-ts/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "skipLibCheck": true, 5 | "module": "ESNext", 6 | "moduleResolution": "bundler", 7 | "allowSyntheticDefaultImports": true 8 | }, 9 | "include": ["vite.config.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /examples/solid-ts/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import solid from 'vite-plugin-solid' 3 | 4 | export default defineConfig({ 5 | plugins: [solid()], 6 | build: { 7 | chunkSizeWarningLimit: 600 8 | } 9 | }) 10 | -------------------------------------------------------------------------------- /examples/vanilla-ts/.eslintignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /examples/vanilla-ts/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /examples/vanilla-ts/.prettierignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /examples/vanilla-ts/README.md: -------------------------------------------------------------------------------- 1 | # Vanilla TypeScript example app 2 | 3 | This example provides a minimal setup to get the [@txnlab/use-wallet](https://github.com/TxnLab/use-wallet/tree/v3/packages/use-wallet) core library working in a Vite app with TypeScript. 4 | -------------------------------------------------------------------------------- /examples/vanilla-ts/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | use-wallet | Vite + TS 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /examples/vanilla-ts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "use-wallet-example-vanilla-ts", 3 | "type": "module", 4 | "scripts": { 5 | "dev": "vite", 6 | "build": "tsc && vite build", 7 | "preview": "vite preview", 8 | "lint": "eslint -c \"../../.eslintrc.json\" \"**/*.{js,ts}\"", 9 | "typecheck": "tsc --noEmit", 10 | "test:e2e": "playwright test" 11 | }, 12 | "devDependencies": { 13 | "@walletconnect/types": "2.19.2", 14 | "typescript": "5.8.2", 15 | "vite": "6.2.3" 16 | }, 17 | "dependencies": { 18 | "@blockshake/defly-connect": "^1.2.1", 19 | "@noble/ed25519": "^2.2.3", 20 | "@perawallet/connect": "^1.4.1", 21 | "@txnlab/use-wallet": "workspace:*", 22 | "@walletconnect/modal": "^2.7.0", 23 | "@walletconnect/sign-client": "^2.19.2", 24 | "algosdk": "3.2.0", 25 | "lute-connect": "^1.6.1" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /examples/vanilla-ts/playwright.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig, devices } from '@playwright/test' 2 | 3 | // Use process.env.PORT by default and fallback to port 4173 4 | const PORT = process.env.PORT || 4173 5 | 6 | /** 7 | * See https://playwright.dev/docs/test-configuration. 8 | */ 9 | export default defineConfig({ 10 | testDir: '../e2e-tests', 11 | /* Run tests in files in parallel */ 12 | fullyParallel: true, 13 | /* Fail the build on CI if you accidentally left test.only in the source code. */ 14 | forbidOnly: !!process.env.CI, 15 | /* Retry on CI only */ 16 | retries: process.env.CI ? 2 : 0, 17 | /* Opt out of parallel tests on CI. */ 18 | workers: process.env.CI ? 1 : undefined, 19 | /* Reporter to use. See https://playwright.dev/docs/test-reporters */ 20 | reporter: 'list', 21 | /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ 22 | use: { 23 | /* Base URL to use in actions like `await page.goto('/')`. */ 24 | baseURL: `http://localhost:${PORT}`, 25 | 26 | /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ 27 | trace: 'on-first-retry' 28 | }, 29 | 30 | /* Configure projects for major browsers */ 31 | projects: [ 32 | { 33 | name: 'chromium', 34 | use: { ...devices['Desktop Chrome'] } 35 | }, 36 | 37 | { 38 | name: 'firefox', 39 | use: { ...devices['Desktop Firefox'] } 40 | }, 41 | 42 | { 43 | name: 'webkit', 44 | use: { ...devices['Desktop Safari'] } 45 | } 46 | 47 | /* Test against mobile viewports. */ 48 | // { 49 | // name: 'Mobile Chrome', 50 | // use: { ...devices['Pixel 5'] }, 51 | // }, 52 | // { 53 | // name: 'Mobile Safari', 54 | // use: { ...devices['iPhone 12'] }, 55 | // }, 56 | 57 | /* Test against branded browsers. */ 58 | // { 59 | // name: 'Microsoft Edge', 60 | // use: { ...devices['Desktop Edge'], channel: 'msedge' }, 61 | // }, 62 | // { 63 | // name: 'Google Chrome', 64 | // use: { ...devices['Desktop Chrome'], channel: 'chrome' }, 65 | // }, 66 | ], 67 | 68 | /* Run your local dev server before starting the tests */ 69 | webServer: { 70 | command: 'pnpm build && pnpm preview', 71 | url: `http://localhost:${PORT}`, 72 | reuseExistingServer: !process.env.CI, 73 | stdout: 'pipe' 74 | } 75 | }) 76 | -------------------------------------------------------------------------------- /examples/vanilla-ts/public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/vanilla-ts/src/ActiveNetwork.ts: -------------------------------------------------------------------------------- 1 | import { NetworkId, WalletManager } from '@txnlab/use-wallet' 2 | 3 | export class ActiveNetwork { 4 | manager: WalletManager 5 | element: HTMLElement 6 | 7 | constructor(manager: WalletManager) { 8 | this.manager = manager 9 | this.element = document.createElement('div') 10 | this.element.className = 'network-group' 11 | this.render() 12 | this.addEventListeners() 13 | } 14 | 15 | setActiveNetwork = (network: NetworkId) => { 16 | this.manager.setActiveNetwork(network) 17 | this.render() 18 | } 19 | 20 | render() { 21 | const activeNetwork = this.manager.activeNetwork 22 | 23 | this.element.innerHTML = ` 24 |

25 | Current Network: ${activeNetwork} 26 |

27 |
28 | 31 | 34 | 37 |
38 | ` 39 | } 40 | 41 | addEventListeners() { 42 | this.element.addEventListener('click', (e: Event) => { 43 | const target = e.target as HTMLButtonElement 44 | if (target.id === 'set-betanet') { 45 | this.setActiveNetwork(NetworkId.BETANET) 46 | } else if (target.id === 'set-testnet') { 47 | this.setActiveNetwork(NetworkId.TESTNET) 48 | } else if (target.id === 'set-mainnet') { 49 | this.setActiveNetwork(NetworkId.MAINNET) 50 | } 51 | }) 52 | } 53 | 54 | destroy() { 55 | this.element.removeEventListener('click', this.addEventListeners) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /examples/vanilla-ts/src/main.ts: -------------------------------------------------------------------------------- 1 | import './style.css' 2 | import typescriptLogo from './typescript.svg' 3 | import viteLogo from '/vite.svg' 4 | import { NetworkId, WalletId, WalletManager } from '@txnlab/use-wallet' 5 | import { ActiveNetwork } from './ActiveNetwork' 6 | import { WalletComponent } from './WalletComponent' 7 | 8 | const walletManager = new WalletManager({ 9 | wallets: [ 10 | WalletId.DEFLY, 11 | WalletId.DEFLY_WEB, 12 | WalletId.EXODUS, 13 | WalletId.PERA, 14 | { 15 | id: WalletId.WALLETCONNECT, 16 | options: { projectId: 'fcfde0713d43baa0d23be0773c80a72b' } 17 | }, 18 | { 19 | id: WalletId.BIATEC, 20 | options: { projectId: 'fcfde0713d43baa0d23be0773c80a72b' } 21 | }, 22 | WalletId.KMD, 23 | WalletId.KIBISIS, 24 | WalletId.LUTE, 25 | { 26 | id: WalletId.MAGIC, 27 | options: { apiKey: 'pk_live_D17FD8D89621B5F3' } 28 | }, 29 | WalletId.MNEMONIC 30 | ], 31 | defaultNetwork: NetworkId.TESTNET 32 | }) 33 | 34 | const appDiv = document.querySelector('#app') 35 | 36 | appDiv!.innerHTML = ` 37 |
38 | 39 | 40 | 41 | 42 | 43 | 44 |

@txnlab/use-wallet

45 |
46 | ` 47 | 48 | const activeNetwork = new ActiveNetwork(walletManager) 49 | appDiv?.appendChild(activeNetwork.element) 50 | 51 | const walletComponents = walletManager.wallets.map( 52 | (wallet) => new WalletComponent(wallet, walletManager) 53 | ) 54 | 55 | walletComponents.forEach((walletComponent) => { 56 | appDiv?.appendChild(walletComponent.element) 57 | }) 58 | 59 | document.addEventListener('DOMContentLoaded', async () => { 60 | try { 61 | await walletManager.resumeSessions() 62 | } catch (error) { 63 | console.error('[App] Error resuming sessions:', error) 64 | } 65 | }) 66 | 67 | // Cleanup 68 | window.addEventListener('beforeunload', () => { 69 | walletComponents.forEach((walletComponent) => walletComponent.destroy()) 70 | }) 71 | -------------------------------------------------------------------------------- /examples/vanilla-ts/src/style.css: -------------------------------------------------------------------------------- 1 | :root { 2 | font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; 3 | line-height: 1.5; 4 | font-weight: 400; 5 | 6 | color-scheme: light dark; 7 | color: rgba(255, 255, 255, 0.87); 8 | background-color: #242424; 9 | 10 | font-synthesis: none; 11 | text-rendering: optimizeLegibility; 12 | -webkit-font-smoothing: antialiased; 13 | -moz-osx-font-smoothing: grayscale; 14 | } 15 | 16 | a { 17 | font-weight: 500; 18 | color: #646cff; 19 | text-decoration: inherit; 20 | } 21 | a:hover { 22 | color: #535bf2; 23 | } 24 | 25 | body { 26 | margin: 0; 27 | display: flex; 28 | place-items: center; 29 | min-width: 320px; 30 | min-height: 100vh; 31 | } 32 | 33 | h1 { 34 | font-size: 3.2em; 35 | line-height: 1.1; 36 | } 37 | 38 | #app { 39 | max-width: 1280px; 40 | margin: 0 auto; 41 | padding: 2rem; 42 | text-align: center; 43 | } 44 | 45 | .logo { 46 | height: 6em; 47 | padding: 1.5em; 48 | will-change: filter; 49 | transition: filter 300ms; 50 | } 51 | .logo:hover { 52 | filter: drop-shadow(0 0 2em #646cffaa); 53 | } 54 | .logo.vanilla:hover { 55 | filter: drop-shadow(0 0 2em #3178c6aa); 56 | } 57 | 58 | button { 59 | border-radius: 8px; 60 | border: 1px solid transparent; 61 | padding: 0.6em 1.2em; 62 | font-size: 1em; 63 | font-weight: 500; 64 | font-family: inherit; 65 | background-color: #1a1a1a; 66 | cursor: pointer; 67 | transition: border-color 0.25s; 68 | } 69 | button:not(:disabled):hover { 70 | border-color: #646cff; 71 | } 72 | button:focus, 73 | button:focus-visible { 74 | outline: 4px auto -webkit-focus-ring-color; 75 | } 76 | button:disabled { 77 | opacity: 0.75; 78 | cursor: default; 79 | } 80 | 81 | input[type='text'], 82 | input[type='email'] { 83 | border-radius: 8px; 84 | border: 1px solid #1a1a1a; 85 | padding: 0.6em 0.9em; 86 | font-size: 1em; 87 | font-weight: 500; 88 | font-family: inherit; 89 | background-color: #1a1a1a; 90 | color: #ffffff; 91 | transition: border-color 0.25s; 92 | } 93 | 94 | select { 95 | border-radius: 8px; 96 | border: 1px solid #1a1a1a; 97 | padding: 0.6em 0.9em; 98 | font-size: 1em; 99 | font-weight: 500; 100 | font-family: inherit; 101 | background-color: #1a1a1a; 102 | color: #ffffff; 103 | transition: border-color 0.25s; 104 | background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e"); 105 | background-position: right 0.5rem center; 106 | background-repeat: no-repeat; 107 | background-size: 1.5em 1.5em; 108 | padding-right: 2.5rem; 109 | -webkit-print-color-adjust: exact; 110 | print-color-adjust: exact; 111 | -moz-appearance: none !important; 112 | -webkit-appearance: none !important; 113 | appearance: none !important; 114 | } 115 | 116 | .network-group { 117 | display: flex; 118 | flex-direction: column; 119 | align-items: center; 120 | gap: 1em; 121 | margin: 2em; 122 | padding: 2em; 123 | background-color: light-dark(rgba(0, 0, 0, 0.025), rgba(255, 255, 255, 0.025)); 124 | border-color: light-dark(rgba(0, 0, 0, 0.1), rgba(255, 255, 255, 0.1)); 125 | border-style: solid; 126 | border-width: 1px; 127 | border-radius: 8px; 128 | } 129 | 130 | .network-group h4 { 131 | margin: 0; 132 | } 133 | 134 | .network-group .active-network { 135 | text-transform: capitalize; 136 | } 137 | 138 | .network-buttons { 139 | display: flex; 140 | align-items: center; 141 | justify-content: center; 142 | flex-wrap: wrap; 143 | gap: 0.5em; 144 | } 145 | 146 | .wallet-group { 147 | display: flex; 148 | flex-direction: column; 149 | align-items: center; 150 | gap: 1em; 151 | margin-bottom: 2em; 152 | } 153 | 154 | .wallet-group h4 { 155 | margin: 0; 156 | } 157 | 158 | .wallet-buttons { 159 | display: flex; 160 | align-items: center; 161 | justify-content: center; 162 | flex-wrap: wrap; 163 | gap: 0.5em; 164 | } 165 | 166 | .input-group { 167 | display: flex; 168 | align-items: center; 169 | justify-content: center; 170 | gap: 0.6em; 171 | } 172 | 173 | .input-group label { 174 | margin-left: 1em; 175 | font-weight: 500; 176 | } 177 | 178 | .input-group input { 179 | min-width: 16em; 180 | } 181 | 182 | .input-group input[disabled] { 183 | opacity: 0.75; 184 | color: light-dark(rgba(16, 16, 16, 0.3), rgba(255, 255, 255, 0.3)); 185 | } 186 | 187 | @media (prefers-color-scheme: light) { 188 | :root { 189 | color: #213547; 190 | background-color: #ffffff; 191 | } 192 | a:hover { 193 | color: #747bff; 194 | } 195 | button { 196 | background-color: #f9f9f9; 197 | } 198 | input[type='text'], 199 | input[type='email'], 200 | select { 201 | background-color: #f9f9f9; 202 | color: #000000; 203 | border-color: #f9f9f9; 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /examples/vanilla-ts/src/typescript.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/vanilla-ts/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /examples/vanilla-ts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "module": "ESNext", 6 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "noEmit": true, 15 | 16 | /* Linting */ 17 | "strict": true, 18 | "noUnusedLocals": true, 19 | "noUnusedParameters": true, 20 | "noFallthroughCasesInSwitch": true 21 | }, 22 | "include": ["src"] 23 | } 24 | -------------------------------------------------------------------------------- /examples/vanilla-ts/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | 3 | export default defineConfig({ 4 | build: { 5 | chunkSizeWarningLimit: 600 6 | } 7 | }) 8 | -------------------------------------------------------------------------------- /examples/vue-ts/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /examples/vue-ts/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"] 3 | } 4 | -------------------------------------------------------------------------------- /examples/vue-ts/README.md: -------------------------------------------------------------------------------- 1 | # Vue example app 2 | 3 | This example provides a minimal setup to get [@txnlab/use-wallet-vue](https://github.com/TxnLab/use-wallet/tree/v3/packages/use-wallet-vue) working in a Vite Vue app with TypeScript. 4 | -------------------------------------------------------------------------------- /examples/vue-ts/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | use-wallet | Vite + Vue + TS 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /examples/vue-ts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "use-wallet-example-vue", 3 | "type": "module", 4 | "scripts": { 5 | "dev": "vite", 6 | "preview": "vite preview", 7 | "build": "vue-tsc && vite build", 8 | "lint": "eslint -c \"../../.eslintrc.json\" \"**/*.{js,ts}\"", 9 | "typecheck": "vue-tsc --noEmit", 10 | "test:e2e": "playwright test" 11 | }, 12 | "dependencies": { 13 | "@blockshake/defly-connect": "^1.2.1", 14 | "@noble/ed25519": "^2.2.3", 15 | "@perawallet/connect": "^1.4.1", 16 | "@txnlab/use-wallet-vue": "workspace:*", 17 | "@walletconnect/modal": "^2.7.0", 18 | "@walletconnect/sign-client": "^2.19.2", 19 | "algosdk": "3.2.0", 20 | "lute-connect": "^1.6.1", 21 | "vue": "3.5.13" 22 | }, 23 | "devDependencies": { 24 | "@vitejs/plugin-vue": "5.2.3", 25 | "typescript": "5.8.2", 26 | "vite": "6.2.3", 27 | "vue-tsc": "2.2.8" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /examples/vue-ts/playwright.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig, devices } from '@playwright/test' 2 | 3 | // Use process.env.PORT by default and fallback to port 4173 4 | const PORT = process.env.PORT || 4173 5 | 6 | /** 7 | * See https://playwright.dev/docs/test-configuration. 8 | */ 9 | export default defineConfig({ 10 | testDir: '../e2e-tests', 11 | /* Run tests in files in parallel */ 12 | fullyParallel: true, 13 | /* Fail the build on CI if you accidentally left test.only in the source code. */ 14 | forbidOnly: !!process.env.CI, 15 | /* Retry on CI only */ 16 | retries: process.env.CI ? 2 : 0, 17 | /* Opt out of parallel tests on CI. */ 18 | workers: process.env.CI ? 1 : undefined, 19 | /* Reporter to use. See https://playwright.dev/docs/test-reporters */ 20 | reporter: 'list', 21 | /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ 22 | use: { 23 | /* Base URL to use in actions like `await page.goto('/')`. */ 24 | baseURL: `http://localhost:${PORT}`, 25 | 26 | /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ 27 | trace: 'on-first-retry' 28 | }, 29 | 30 | /* Configure projects for major browsers */ 31 | projects: [ 32 | { 33 | name: 'chromium', 34 | use: { ...devices['Desktop Chrome'] } 35 | }, 36 | 37 | { 38 | name: 'firefox', 39 | use: { ...devices['Desktop Firefox'] } 40 | }, 41 | 42 | { 43 | name: 'webkit', 44 | use: { ...devices['Desktop Safari'] } 45 | } 46 | 47 | /* Test against mobile viewports. */ 48 | // { 49 | // name: 'Mobile Chrome', 50 | // use: { ...devices['Pixel 5'] }, 51 | // }, 52 | // { 53 | // name: 'Mobile Safari', 54 | // use: { ...devices['iPhone 12'] }, 55 | // }, 56 | 57 | /* Test against branded browsers. */ 58 | // { 59 | // name: 'Microsoft Edge', 60 | // use: { ...devices['Desktop Edge'], channel: 'msedge' }, 61 | // }, 62 | // { 63 | // name: 'Google Chrome', 64 | // use: { ...devices['Desktop Chrome'], channel: 'chrome' }, 65 | // }, 66 | ], 67 | 68 | /* Run your local dev server before starting the tests */ 69 | webServer: { 70 | command: 'pnpm build && pnpm preview', 71 | url: `http://localhost:${PORT}`, 72 | reuseExistingServer: !process.env.CI, 73 | stdout: 'pipe' 74 | } 75 | }) 76 | -------------------------------------------------------------------------------- /examples/vue-ts/public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/vue-ts/src/App.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 19 | 20 | 34 | -------------------------------------------------------------------------------- /examples/vue-ts/src/assets/vue.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/vue-ts/src/components/NetworkControls.vue: -------------------------------------------------------------------------------- 1 | 64 | 65 | 125 | 126 | 157 | -------------------------------------------------------------------------------- /examples/vue-ts/src/main.ts: -------------------------------------------------------------------------------- 1 | import { NetworkId, WalletId, WalletManagerPlugin } from '@txnlab/use-wallet-vue' 2 | import { createApp } from 'vue' 3 | import './style.css' 4 | import App from './App.vue' 5 | 6 | const app = createApp(App) 7 | 8 | app.use(WalletManagerPlugin, { 9 | wallets: [ 10 | WalletId.DEFLY, 11 | WalletId.DEFLY_WEB, 12 | WalletId.EXODUS, 13 | WalletId.PERA, 14 | { 15 | id: WalletId.WALLETCONNECT, 16 | options: { projectId: 'fcfde0713d43baa0d23be0773c80a72b' } 17 | }, 18 | { 19 | id: WalletId.BIATEC, 20 | options: { projectId: 'fcfde0713d43baa0d23be0773c80a72b' } 21 | }, 22 | WalletId.KMD, 23 | WalletId.KIBISIS, 24 | WalletId.LUTE, 25 | { 26 | id: WalletId.MAGIC, 27 | options: { apiKey: 'pk_live_D17FD8D89621B5F3' } 28 | }, 29 | WalletId.MNEMONIC 30 | ], 31 | defaultNetwork: NetworkId.TESTNET 32 | }) 33 | 34 | app.mount('#app') 35 | -------------------------------------------------------------------------------- /examples/vue-ts/src/style.css: -------------------------------------------------------------------------------- 1 | :root { 2 | font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; 3 | line-height: 1.5; 4 | font-weight: 400; 5 | 6 | color-scheme: light dark; 7 | color: rgba(255, 255, 255, 0.87); 8 | background-color: #242424; 9 | 10 | font-synthesis: none; 11 | text-rendering: optimizeLegibility; 12 | -webkit-font-smoothing: antialiased; 13 | -moz-osx-font-smoothing: grayscale; 14 | } 15 | 16 | a { 17 | font-weight: 500; 18 | color: #646cff; 19 | text-decoration: inherit; 20 | } 21 | a:hover { 22 | color: #535bf2; 23 | } 24 | 25 | body { 26 | margin: 0; 27 | display: flex; 28 | place-items: center; 29 | min-width: 320px; 30 | min-height: 100vh; 31 | } 32 | 33 | h1 { 34 | font-size: 3.2em; 35 | line-height: 1.1; 36 | } 37 | 38 | button { 39 | border-radius: 8px; 40 | border: 1px solid transparent; 41 | padding: 0.6em 1.2em; 42 | font-size: 1em; 43 | font-weight: 500; 44 | font-family: inherit; 45 | background-color: #1a1a1a; 46 | cursor: pointer; 47 | transition: border-color 0.25s; 48 | } 49 | button:not(:disabled):hover { 50 | border-color: #646cff; 51 | } 52 | button:focus, 53 | button:focus-visible { 54 | outline: 4px auto -webkit-focus-ring-color; 55 | } 56 | button:disabled { 57 | opacity: 0.75; 58 | cursor: default; 59 | } 60 | 61 | input[type='text'], 62 | input[type='email'] { 63 | border-radius: 8px; 64 | border: 1px solid #1a1a1a; 65 | padding: 0.6em 0.9em; 66 | font-size: 1em; 67 | font-weight: 500; 68 | font-family: inherit; 69 | background-color: #1a1a1a; 70 | color: #ffffff; 71 | transition: border-color 0.25s; 72 | } 73 | 74 | select { 75 | border-radius: 8px; 76 | border: 1px solid #1a1a1a; 77 | padding: 0.6em 0.9em; 78 | font-size: 1em; 79 | font-weight: 500; 80 | font-family: inherit; 81 | background-color: #1a1a1a; 82 | color: #ffffff; 83 | transition: border-color 0.25s; 84 | background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e"); 85 | background-position: right 0.5rem center; 86 | background-repeat: no-repeat; 87 | background-size: 1.5em 1.5em; 88 | padding-right: 2.5rem; 89 | -webkit-print-color-adjust: exact; 90 | print-color-adjust: exact; 91 | -moz-appearance: none !important; 92 | -webkit-appearance: none !important; 93 | appearance: none !important; 94 | } 95 | 96 | #app { 97 | max-width: 1280px; 98 | margin: 0 auto; 99 | padding: 2rem; 100 | text-align: center; 101 | } 102 | 103 | @media (prefers-color-scheme: light) { 104 | :root { 105 | color: #213547; 106 | background-color: #ffffff; 107 | } 108 | a:hover { 109 | color: #747bff; 110 | } 111 | button { 112 | background-color: #f9f9f9; 113 | } 114 | input[type='text'], 115 | input[type='email'], 116 | select { 117 | background-color: #f9f9f9; 118 | color: #000000; 119 | border-color: #f9f9f9; 120 | } 121 | } 122 | 123 | .config-section { 124 | width: 100%; 125 | max-width: 500px; 126 | margin-top: 1em; 127 | } 128 | 129 | .config-form { 130 | display: flex; 131 | flex-direction: column; 132 | gap: 1em; 133 | margin: 1em 0; 134 | padding: 1em; 135 | border: 1px solid rgba(255, 255, 255, 0.1); 136 | border-radius: 4px; 137 | } 138 | 139 | .form-group { 140 | display: flex; 141 | flex-direction: column; 142 | gap: 0.5em; 143 | text-align: left; 144 | } 145 | 146 | .form-group label { 147 | font-size: 0.9em; 148 | opacity: 0.8; 149 | } 150 | 151 | .current-config { 152 | margin-top: 1em; 153 | text-align: left; 154 | } 155 | 156 | .current-config h5 { 157 | margin: 0 0 0.5em 0; 158 | font-size: 0.9em; 159 | opacity: 0.8; 160 | } 161 | 162 | .current-config pre { 163 | padding: 1em; 164 | background: rgba(0, 0, 0, 0.1); 165 | border-radius: 4px; 166 | font-size: 0.9em; 167 | overflow-x: auto; 168 | } 169 | 170 | .error-message { 171 | margin-top: 1em; 172 | padding: 0.5em 1em; 173 | color: #ff4444; 174 | background: rgba(255, 68, 68, 0.1); 175 | border-radius: 4px; 176 | } 177 | 178 | @media (prefers-color-scheme: light) { 179 | .config-form { 180 | border-color: rgba(0, 0, 0, 0.1); 181 | } 182 | 183 | .form-group input, 184 | .current-config pre { 185 | background: rgba(0, 0, 0, 0.05); 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /examples/vue-ts/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /examples/vue-ts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "module": "ESNext", 6 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "noEmit": true, 15 | "jsx": "preserve", 16 | 17 | /* Linting */ 18 | "strict": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "noFallthroughCasesInSwitch": true 22 | }, 23 | "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"], 24 | "references": [{ "path": "./tsconfig.node.json" }] 25 | } 26 | -------------------------------------------------------------------------------- /examples/vue-ts/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "skipLibCheck": true, 5 | "module": "ESNext", 6 | "moduleResolution": "bundler", 7 | "allowSyntheticDefaultImports": true 8 | }, 9 | "include": ["vite.config.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /examples/vue-ts/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import vue from '@vitejs/plugin-vue' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [vue()], 7 | build: { 8 | chunkSizeWarningLimit: 600 9 | } 10 | }) 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "use-wallet", 3 | "private": true, 4 | "repository": { 5 | "type": "git", 6 | "url": "https://github.com/txnlab/use-wallet.git" 7 | }, 8 | "packageManager": "pnpm@9.15.9", 9 | "type": "module", 10 | "scripts": { 11 | "build": "pnpm build:packages && pnpm build:examples", 12 | "build:packages": "pnpm -r --filter \"./packages/*\" build", 13 | "build:examples": "pnpm -r --filter \"./examples/*\" build", 14 | "watch": "pnpm -r --filter \"./packages/*\" --parallel start", 15 | "dev": "pnpm run watch", 16 | "test": "pnpm -r --if-present test", 17 | "lint": "pnpm -r lint", 18 | "prettier": "prettier --check .", 19 | "typecheck": "pnpm -r typecheck", 20 | "example:ts": "pnpm --filter \"./examples/vanilla-ts\" dev", 21 | "example:react": "pnpm --filter \"./examples/react-ts\" dev", 22 | "example:solid": "pnpm --filter \"./examples/solid-ts\" dev", 23 | "example:vue": "pnpm --filter \"./examples/vue-ts\" dev", 24 | "example:nextjs": "pnpm --filter \"./examples/nextjs\" dev", 25 | "example:nuxt": "pnpm --filter \"./examples/nuxt\" dev" 26 | }, 27 | "devDependencies": { 28 | "@playwright/test": "1.51.1", 29 | "@testing-library/jest-dom": "6.6.3", 30 | "@testing-library/react": "16.2.0", 31 | "@types/node": "20.11.30", 32 | "@typescript-eslint/eslint-plugin": "8.28.0", 33 | "@typescript-eslint/parser": "8.28.0", 34 | "@vitejs/plugin-react": "4.3.4", 35 | "@vitejs/plugin-vue": "5.2.3", 36 | "@vitejs/plugin-vue-jsx": "4.1.2", 37 | "@vue/test-utils": "2.4.6", 38 | "eslint": "8.57.1", 39 | "eslint-config-prettier": "10.1.1", 40 | "eslint-plugin-prettier": "5.2.5", 41 | "prettier": "3.5.3", 42 | "vite": "6.2.3", 43 | "vite-plugin-solid": "^2.11.1", 44 | "vitest": "3.0.9", 45 | "vue-demi": "0.14.10" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /packages/use-wallet-react/README.md: -------------------------------------------------------------------------------- 1 | # @txnlab/use-wallet-react 2 | 3 | [![GitHub package.json version](https://img.shields.io/github/package-json/v/TxnLab/use-wallet?filename=packages%2Fuse-wallet-react%2Fpackage.json&label=version)](https://www.npmjs.com/package/@txnlab/use-wallet?activeTab=versions) 4 | [![GitHub License](https://img.shields.io/github/license/TxnLab/use-wallet)](https://github.com/TxnLab/use-wallet/blob/main/LICENSE.md) 5 | 6 | React adapter for [@txnlab/use-wallet](https://github.com/TxnLab/use-wallet) 7 | 8 | ### Visit [txnlab.gitbook.io/use-wallet](https://txnlab.gitbook.io/use-wallet) for docs, guides, and examples! 9 | -------------------------------------------------------------------------------- /packages/use-wallet-react/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@txnlab/use-wallet-react", 3 | "version": "4.1.0", 4 | "description": "React library for integrating Algorand wallets into decentralized applications", 5 | "author": "Doug Richar ", 6 | "license": "MIT", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/txnlab/use-wallet.git", 10 | "directory": "packages/use-wallet-react" 11 | }, 12 | "type": "module", 13 | "main": "./dist/index.cjs", 14 | "module": "./dist/index.js", 15 | "exports": "./dist/index.js", 16 | "types": "./dist/index.d.ts", 17 | "scripts": { 18 | "build": "tsup", 19 | "start": "tsup src/index.tsx --watch", 20 | "test": "vitest run", 21 | "test:watch": "vitest --watch", 22 | "lint": "eslint -c \"../../.eslintrc.json\" \"**/*.{js,ts}\"", 23 | "typecheck": "tsc --noEmit" 24 | }, 25 | "keywords": [ 26 | "algorand", 27 | "wallet", 28 | "walletconnect", 29 | "pera", 30 | "defly", 31 | "exodus", 32 | "algosdk", 33 | "algokit", 34 | "kmd", 35 | "react" 36 | ], 37 | "files": [ 38 | "dist" 39 | ], 40 | "dependencies": { 41 | "@tanstack/react-store": "0.7.0", 42 | "@txnlab/use-wallet": "workspace:*" 43 | }, 44 | "devDependencies": { 45 | "@types/react": "18.3.20", 46 | "algosdk": "3.2.0", 47 | "jsdom": "26.0.0", 48 | "react": "18.3.1", 49 | "react-dom": "18.3.1", 50 | "tsup": "8.4.0", 51 | "typescript": "5.8.2" 52 | }, 53 | "peerDependencies": { 54 | "@blockshake/defly-connect": "^1.2.1", 55 | "@magic-ext/algorand": "^23.20.0", 56 | "@walletconnect/modal": "^2.7.0", 57 | "@perawallet/connect": "^1.4.1", 58 | "@walletconnect/sign-client": "^2.19.2", 59 | "algosdk": "^3.0.0", 60 | "lute-connect": "^1.6.1", 61 | "magic-sdk": "^28.21.1", 62 | "react": "^17.0.0 || ^18.0.0 || ^19.0.0", 63 | "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" 64 | }, 65 | "peerDependenciesMeta": { 66 | "@blockshake/defly-connect": { 67 | "optional": true 68 | }, 69 | "@magic-ext/algorand": { 70 | "optional": true 71 | }, 72 | "@perawallet/connect": { 73 | "optional": true 74 | }, 75 | "@walletconnect/modal": { 76 | "optional": true 77 | }, 78 | "@walletconnect/sign-client": { 79 | "optional": true 80 | }, 81 | "lute-connect": { 82 | "optional": true 83 | }, 84 | "magic-sdk": { 85 | "optional": true 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /packages/use-wallet-react/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "compilerOptions": { 4 | "target": "ESNext", 5 | "module": "ESNext", 6 | "strict": true, 7 | "esModuleInterop": true, 8 | "skipLibCheck": true, 9 | "forceConsistentCasingInFileNames": true, 10 | "moduleResolution": "node", 11 | "jsx": "react-jsx", 12 | "noEmit": true, 13 | "baseUrl": ".", 14 | "rootDir": ".", 15 | "outDir": "dist", 16 | "types": ["vitest/globals"] 17 | }, 18 | "exclude": ["node_modules", "dist"] 19 | } 20 | -------------------------------------------------------------------------------- /packages/use-wallet-react/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'tsup' 2 | 3 | export default defineConfig({ 4 | entry: ['src/index.tsx'], 5 | dts: true, 6 | splitting: false, 7 | sourcemap: true, 8 | clean: true, 9 | format: ['esm', 'cjs'] 10 | }) 11 | -------------------------------------------------------------------------------- /packages/use-wallet-react/vite.config.ts: -------------------------------------------------------------------------------- 1 | import react from '@vitejs/plugin-react' 2 | import { defineConfig } from 'vite' 3 | 4 | export default defineConfig({ 5 | plugins: [react()] 6 | }) 7 | -------------------------------------------------------------------------------- /packages/use-wallet-react/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig, mergeConfig } from 'vitest/config' 2 | import viteConfig from './vite.config' 3 | 4 | export default mergeConfig( 5 | viteConfig, 6 | defineConfig({ 7 | test: { 8 | name: 'use-wallet-react', 9 | dir: './src', 10 | watch: false, 11 | environment: 'jsdom', 12 | setupFiles: ['./vitest.setup.ts'], 13 | globals: true 14 | } 15 | }) 16 | ) 17 | -------------------------------------------------------------------------------- /packages/use-wallet-react/vitest.setup.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | import '@testing-library/jest-dom/vitest' 4 | 5 | // Suppress console output 6 | vi.spyOn(console, 'info').mockImplementation(() => {}) 7 | -------------------------------------------------------------------------------- /packages/use-wallet-solid/README.md: -------------------------------------------------------------------------------- 1 | # @txnlab/use-wallet-solid 2 | 3 | [![GitHub package.json version](https://img.shields.io/github/package-json/v/TxnLab/use-wallet?filename=packages%2Fuse-wallet-solid%2Fpackage.json&label=version)](https://www.npmjs.com/package/@txnlab/use-wallet?activeTab=versions) 4 | [![GitHub License](https://img.shields.io/github/license/TxnLab/use-wallet)](https://github.com/TxnLab/use-wallet/blob/main/LICENSE.md) 5 | 6 | Solid.js adapter for [@txnlab/use-wallet](https://github.com/TxnLab/use-wallet) 7 | 8 | ### Visit [txnlab.gitbook.io/use-wallet](https://txnlab.gitbook.io/use-wallet) for docs, guides, and examples! 9 | -------------------------------------------------------------------------------- /packages/use-wallet-solid/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@txnlab/use-wallet-solid", 3 | "version": "4.1.0", 4 | "description": "Solid.js library for integrating Algorand wallets into decentralized applications", 5 | "author": "Doug Richar ", 6 | "license": "MIT", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/txnlab/use-wallet.git", 10 | "directory": "packages/use-wallet-solid" 11 | }, 12 | "type": "module", 13 | "main": "./dist/index.cjs", 14 | "module": "./dist/index.js", 15 | "exports": { 16 | "solid": { 17 | "development": "./dist/dev.jsx", 18 | "import": "./dist/index.jsx" 19 | }, 20 | "development": { 21 | "import": { 22 | "types": "./dist/index.d.ts", 23 | "default": "./dist/dev.js" 24 | }, 25 | "require": { 26 | "types": "./dist/index.d.cts", 27 | "default": "./dist/dev.cjs" 28 | } 29 | }, 30 | "import": { 31 | "types": "./dist/index.d.ts", 32 | "default": "./dist/index.js" 33 | }, 34 | "require": { 35 | "types": "./dist/index.d.cts", 36 | "default": "./dist/index.cjs" 37 | } 38 | }, 39 | "types": "./dist/index.d.ts", 40 | "scripts": { 41 | "build": "tsup", 42 | "start": "tsup src/index.tsx --watch", 43 | "test": "vitest run", 44 | "test:watch": "vitest --watch", 45 | "lint": "eslint -c \"../../.eslintrc.json\" \"**/*.{js,ts}\"", 46 | "typecheck": "tsc --noEmit" 47 | }, 48 | "keywords": [ 49 | "algorand", 50 | "wallet", 51 | "walletconnect", 52 | "pera", 53 | "defly", 54 | "exodus", 55 | "algosdk", 56 | "algokit", 57 | "kmd", 58 | "solid.js" 59 | ], 60 | "files": [ 61 | "dist" 62 | ], 63 | "dependencies": { 64 | "@tanstack/solid-store": "0.7.0", 65 | "@txnlab/use-wallet": "workspace:*" 66 | }, 67 | "devDependencies": { 68 | "@solidjs/testing-library": "0.8.10", 69 | "algosdk": "3.2.0", 70 | "solid-js": "1.9.5", 71 | "tsup": "8.4.0", 72 | "tsup-preset-solid": "2.2.0", 73 | "typescript": "5.8.2" 74 | }, 75 | "peerDependencies": { 76 | "@blockshake/defly-connect": "^1.2.1", 77 | "@magic-ext/algorand": "^23.20.0", 78 | "@perawallet/connect": "^1.4.1", 79 | "@walletconnect/modal": "^2.7.0", 80 | "@walletconnect/sign-client": "^2.19.2", 81 | "algosdk": "^3.0.0", 82 | "lute-connect": "^1.6.1", 83 | "magic-sdk": "^28.21.1" 84 | }, 85 | "peerDependenciesMeta": { 86 | "@blockshake/defly-connect": { 87 | "optional": true 88 | }, 89 | "@magic-ext/algorand": { 90 | "optional": true 91 | }, 92 | "@perawallet/connect": { 93 | "optional": true 94 | }, 95 | "@walletconnect/modal": { 96 | "optional": true 97 | }, 98 | "@walletconnect/sign-client": { 99 | "optional": true 100 | }, 101 | "lute-connect": { 102 | "optional": true 103 | }, 104 | "magic-sdk": { 105 | "optional": true 106 | } 107 | }, 108 | "browser": {}, 109 | "typesVersions": {} 110 | } 111 | -------------------------------------------------------------------------------- /packages/use-wallet-solid/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "compilerOptions": { 4 | "target": "ESNext", 5 | "module": "ESNext", 6 | "strict": true, 7 | "esModuleInterop": true, 8 | "skipLibCheck": true, 9 | "forceConsistentCasingInFileNames": true, 10 | "moduleResolution": "node", 11 | "allowSyntheticDefaultImports": true, 12 | "jsx": "preserve", 13 | "jsxImportSource": "solid-js", 14 | "types": ["vite/client", "@testing-library/jest-dom", "vitest/globals"] 15 | }, 16 | "exclude": ["node_modules", "dist"] 17 | } 18 | -------------------------------------------------------------------------------- /packages/use-wallet-solid/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'tsup' 2 | import * as preset from 'tsup-preset-solid' 3 | 4 | const entries = [ 5 | // default entry (index) 6 | { 7 | // entries with '.tsx' extension will have `solid` export condition generated 8 | entry: 'src/index.tsx', 9 | // will generate a separate development entry 10 | dev_entry: true 11 | } 12 | ] 13 | 14 | const preset_options: preset.PresetOptions = { 15 | // array or single object 16 | entries, 17 | // Set to `true` to remove all `console.*` calls and `debugger` statements in prod builds 18 | drop_console: true, 19 | // Set to `true` to generate a CommonJS build alongside ESM 20 | cjs: true 21 | } 22 | 23 | const CI = 24 | process.env['CI'] === 'true' || 25 | process.env['GITHUB_ACTIONS'] === 'true' || 26 | process.env['CI'] === '"1"' || 27 | process.env['GITHUB_ACTIONS'] === '"1"' 28 | 29 | export default defineConfig((config) => { 30 | const watching = !!config.watch 31 | 32 | const parsed_options = preset.parsePresetOptions(preset_options, watching) 33 | 34 | if (!watching && !CI) { 35 | const package_fields = preset.generatePackageExports(parsed_options) 36 | 37 | console.log(`package.json: \n\n${JSON.stringify(package_fields, null, 2)}\n\n`) 38 | 39 | // will update ./package.json with the correct export fields 40 | preset.writePackageJson(package_fields) 41 | } 42 | 43 | return preset.generateTsupOptions(parsed_options) 44 | }) 45 | -------------------------------------------------------------------------------- /packages/use-wallet-solid/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import solid from 'vite-plugin-solid' 3 | 4 | export default defineConfig({ 5 | plugins: [solid({ ssr: false })], 6 | resolve: { 7 | conditions: ['development', 'browser'] 8 | } 9 | }) 10 | -------------------------------------------------------------------------------- /packages/use-wallet-solid/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import solidPlugin from 'vite-plugin-solid' 2 | import { defineConfig, mergeConfig } from 'vitest/config' 3 | import viteConfig from './vite.config' 4 | 5 | export default mergeConfig( 6 | viteConfig, 7 | defineConfig({ 8 | plugins: [ 9 | solidPlugin({ 10 | ssr: false 11 | }) 12 | ], 13 | test: { 14 | name: 'use-wallet-solid', 15 | dir: './src', 16 | watch: false, 17 | environment: 'jsdom', 18 | setupFiles: ['./vitest.setup.ts'], 19 | globals: true 20 | } 21 | }) 22 | ) 23 | -------------------------------------------------------------------------------- /packages/use-wallet-solid/vitest.setup.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | import '@testing-library/jest-dom/vitest' 4 | 5 | // Suppress console output 6 | vi.spyOn(console, 'info').mockImplementation(() => {}) 7 | -------------------------------------------------------------------------------- /packages/use-wallet-vue/README.md: -------------------------------------------------------------------------------- 1 | # @txnlab/use-wallet-vue 2 | 3 | [![GitHub package.json version](https://img.shields.io/github/package-json/v/TxnLab/use-wallet?filename=packages%2Fuse-wallet-vue%2Fpackage.json&label=version)](https://www.npmjs.com/package/@txnlab/use-wallet?activeTab=versions) 4 | [![GitHub License](https://img.shields.io/github/license/TxnLab/use-wallet)](https://github.com/TxnLab/use-wallet/blob/main/LICENSE.md) 5 | 6 | Vue adapter for [@txnlab/use-wallet](https://github.com/TxnLab/use-wallet) 7 | 8 | ### Visit [txnlab.gitbook.io/use-wallet](https://txnlab.gitbook.io/use-wallet) for docs, guides, and examples! 9 | -------------------------------------------------------------------------------- /packages/use-wallet-vue/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@txnlab/use-wallet-vue", 3 | "version": "4.1.0", 4 | "description": "Vue library for integrating Algorand wallets into decentralized applications", 5 | "author": "Doug Richar ", 6 | "license": "MIT", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/txnlab/use-wallet.git", 10 | "directory": "packages/use-wallet-vue" 11 | }, 12 | "type": "module", 13 | "main": "./dist/index.cjs", 14 | "module": "./dist/index.js", 15 | "exports": "./dist/index.js", 16 | "types": "./dist/index.d.ts", 17 | "scripts": { 18 | "build": "tsup", 19 | "start": "tsup src/index.ts --watch", 20 | "test": "vitest run", 21 | "test:watch": "vitest --watch", 22 | "lint": "eslint -c \"../../.eslintrc.json\" \"**/*.{js,ts}\"", 23 | "typecheck": "tsc --noEmit" 24 | }, 25 | "keywords": [ 26 | "algorand", 27 | "wallet", 28 | "walletconnect", 29 | "pera", 30 | "defly", 31 | "exodus", 32 | "algosdk", 33 | "algokit", 34 | "kmd", 35 | "vue" 36 | ], 37 | "files": [ 38 | "dist" 39 | ], 40 | "dependencies": { 41 | "@tanstack/vue-store": "0.7.0", 42 | "@txnlab/use-wallet": "workspace:*" 43 | }, 44 | "devDependencies": { 45 | "algosdk": "3.2.0", 46 | "tsup": "8.4.0", 47 | "typescript": "5.8.2", 48 | "vue": "3.5.13" 49 | }, 50 | "peerDependencies": { 51 | "@blockshake/defly-connect": "^1.2.1", 52 | "@magic-ext/algorand": "^23.20.0", 53 | "@perawallet/connect": "^1.4.1", 54 | "@walletconnect/modal": "^2.7.0", 55 | "@walletconnect/sign-client": "^2.19.2", 56 | "algosdk": "^3.0.0", 57 | "lute-connect": "^1.6.1", 58 | "magic-sdk": "^28.21.1", 59 | "vue": "^3.0.0" 60 | }, 61 | "peerDependenciesMeta": { 62 | "@blockshake/defly-connect": { 63 | "optional": true 64 | }, 65 | "@magic-ext/algorand": { 66 | "optional": true 67 | }, 68 | "@perawallet/connect": { 69 | "optional": true 70 | }, 71 | "@walletconnect/modal": { 72 | "optional": true 73 | }, 74 | "@walletconnect/sign-client": { 75 | "optional": true 76 | }, 77 | "lute-connect": { 78 | "optional": true 79 | }, 80 | "magic-sdk": { 81 | "optional": true 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /packages/use-wallet-vue/src/__tests__/walletManagerPlugin.test.ts: -------------------------------------------------------------------------------- 1 | import { defineComponent, h, inject } from 'vue' 2 | import { WalletManagerPlugin } from '../walletManagerPlugin' 3 | import { NetworkId, WalletManager, type WalletManagerConfig } from '@txnlab/use-wallet' 4 | import algosdk from 'algosdk' 5 | import { mount } from '@vue/test-utils' 6 | import type { SetAlgodClient } from '../useWallet' 7 | 8 | const mockAlgodClient = new algosdk.Algodv2('mock-token', 'https://mock-server', '') 9 | 10 | vi.mock('@txnlab/use-wallet', async (importOriginal) => { 11 | const module = await importOriginal() 12 | return { 13 | ...module, 14 | WalletManager: vi.fn().mockImplementation(() => ({ 15 | algodClient: mockAlgodClient, 16 | resumeSessions: vi.fn().mockResolvedValue(undefined) 17 | })) 18 | } 19 | }) 20 | 21 | describe('WalletManagerPlugin', () => { 22 | const TestComponent = defineComponent({ 23 | setup() { 24 | const walletManager = inject('walletManager') 25 | const algodClient = inject('algodClient') 26 | const setAlgodClient = inject('setAlgodClient') 27 | return { walletManager, algodClient, setAlgodClient } 28 | }, 29 | render() { 30 | return h('div') 31 | } 32 | }) 33 | 34 | it('provides walletManager, algodClient, and setAlgodClient', () => { 35 | const options: WalletManagerConfig = { 36 | wallets: [], 37 | defaultNetwork: NetworkId.TESTNET 38 | } 39 | const wrapper = mount(TestComponent, { 40 | global: { 41 | plugins: [[WalletManagerPlugin, options]] 42 | } 43 | }) 44 | 45 | const { walletManager, algodClient, setAlgodClient } = wrapper.vm 46 | 47 | expect(walletManager).toBeDefined() 48 | expect(algodClient).toBeDefined() 49 | expect(setAlgodClient).toBeDefined() 50 | }) 51 | 52 | it('initializes with the correct algodClient', () => { 53 | const options: WalletManagerConfig = { 54 | wallets: [], 55 | defaultNetwork: NetworkId.TESTNET 56 | } 57 | const wrapper = mount(TestComponent, { 58 | global: { 59 | plugins: [[WalletManagerPlugin, options]] 60 | } 61 | }) 62 | 63 | const { algodClient } = wrapper.vm 64 | 65 | expect(algodClient).toStrictEqual(mockAlgodClient) 66 | }) 67 | 68 | it('setAlgodClient updates the reactive algodClient and manager.algodClient', () => { 69 | const options: WalletManagerConfig = { 70 | wallets: [], 71 | defaultNetwork: NetworkId.TESTNET 72 | } 73 | const newAlgodClient = new algosdk.Algodv2('mock-token', 'https://mock-server', '') 74 | 75 | const wrapper = mount(TestComponent, { 76 | global: { 77 | plugins: [[WalletManagerPlugin, options]] 78 | } 79 | }) 80 | 81 | const { algodClient, setAlgodClient, walletManager } = wrapper.vm 82 | 83 | expect(algodClient).toStrictEqual(mockAlgodClient) 84 | 85 | setAlgodClient?.(newAlgodClient) 86 | 87 | expect(algodClient).toStrictEqual(newAlgodClient) 88 | expect(walletManager?.algodClient).toStrictEqual(newAlgodClient) 89 | }) 90 | 91 | it('calls resumeSessions on the walletManager', () => { 92 | const options: WalletManagerConfig = { 93 | wallets: [], 94 | defaultNetwork: NetworkId.TESTNET 95 | } 96 | 97 | const wrapper = mount(TestComponent, { 98 | global: { 99 | plugins: [[WalletManagerPlugin, options]] 100 | } 101 | }) 102 | 103 | const { walletManager } = wrapper.vm 104 | 105 | expect(walletManager?.resumeSessions).toHaveBeenCalled() 106 | }) 107 | }) 108 | -------------------------------------------------------------------------------- /packages/use-wallet-vue/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from '@txnlab/use-wallet' 2 | export { WalletManagerPlugin } from './walletManagerPlugin' 3 | export { useWallet, type Wallet } from './useWallet' 4 | export { useNetwork } from './useNetwork' 5 | -------------------------------------------------------------------------------- /packages/use-wallet-vue/src/useNetwork.ts: -------------------------------------------------------------------------------- 1 | import { useStore } from '@tanstack/vue-store' 2 | import { WalletManager, type AlgodConfig } from '@txnlab/use-wallet' 3 | import algosdk from 'algosdk' 4 | import { computed, inject, ref } from 'vue' 5 | import type { SetAlgodClient } from './useWallet' 6 | 7 | export function useNetwork() { 8 | const manager = inject('walletManager') 9 | const algodClient = inject>>('algodClient') 10 | const setAlgodClient = inject('setAlgodClient') 11 | 12 | if (!manager) { 13 | throw new Error('WalletManager plugin is not properly installed') 14 | } 15 | if (!algodClient || !setAlgodClient) { 16 | throw new Error('Algod client or setter not properly installed') 17 | } 18 | 19 | const activeNetwork = useStore(manager.store, (state) => state.activeNetwork) 20 | 21 | // Create a reactive store for network config 22 | const networkConfig = useStore(manager.store, (state) => ({ 23 | networks: { ...manager.networkConfig }, 24 | activeNetwork: state.activeNetwork 25 | })) 26 | 27 | const activeNetworkConfig = computed( 28 | () => networkConfig.value.networks[networkConfig.value.activeNetwork] 29 | ) 30 | 31 | const setActiveNetwork = async (networkId: string): Promise => { 32 | if (networkId === activeNetwork.value) { 33 | return 34 | } 35 | 36 | if (!manager.networkConfig[networkId]) { 37 | throw new Error(`Network "${networkId}" not found in network configuration`) 38 | } 39 | 40 | console.info(`[Vue] Creating new Algodv2 client...`) 41 | 42 | const { algod } = manager.networkConfig[networkId] 43 | const { token = '', baseServer, port = '', headers = {} } = algod 44 | const newClient = new algosdk.Algodv2(token, baseServer, port, headers) 45 | 46 | await manager.setActiveNetwork(networkId) 47 | setAlgodClient(newClient) 48 | 49 | console.info(`[Vue] ✅ Active network set to ${networkId}.`) 50 | } 51 | 52 | const updateAlgodConfig = (networkId: string, config: Partial): void => { 53 | manager.updateAlgodConfig(networkId, config) 54 | manager.store.setState((state) => ({ ...state })) 55 | 56 | // If this is the active network, update the algodClient 57 | if (networkId === activeNetwork.value) { 58 | console.info(`[Vue] Creating new Algodv2 client...`) 59 | const { algod } = manager.networkConfig[networkId] 60 | const { token = '', baseServer, port = '', headers = {} } = algod 61 | const newClient = new algosdk.Algodv2(token, baseServer, port, headers) 62 | setAlgodClient(newClient) 63 | } 64 | } 65 | 66 | const resetNetworkConfig = (networkId: string): void => { 67 | manager.resetNetworkConfig(networkId) 68 | manager.store.setState((state) => ({ ...state })) 69 | 70 | // If this is the active network, update the algodClient 71 | if (networkId === activeNetwork.value) { 72 | console.info(`[Vue] Creating new Algodv2 client...`) 73 | const { algod } = manager.networkConfig[networkId] 74 | const { token = '', baseServer, port = '', headers = {} } = algod 75 | const newClient = new algosdk.Algodv2(token, baseServer, port, headers) 76 | setAlgodClient(newClient) 77 | } 78 | } 79 | 80 | return { 81 | activeNetwork, 82 | networkConfig: manager.networkConfig, 83 | activeNetworkConfig, 84 | setActiveNetwork, 85 | updateAlgodConfig, 86 | resetNetworkConfig 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /packages/use-wallet-vue/src/useWallet.ts: -------------------------------------------------------------------------------- 1 | import { useStore } from '@tanstack/vue-store' 2 | import { 3 | BaseWallet, 4 | WalletManager, 5 | type WalletAccount, 6 | type WalletMetadata, 7 | type WalletId, 8 | type SignMetadata, 9 | type SignDataResponse 10 | } from '@txnlab/use-wallet' 11 | import algosdk from 'algosdk' 12 | import { computed, inject, ref } from 'vue' 13 | 14 | export interface Wallet { 15 | id: WalletId 16 | metadata: WalletMetadata 17 | accounts: WalletAccount[] 18 | activeAccount: WalletAccount | null 19 | isConnected: boolean 20 | isActive: boolean 21 | connect: (args?: Record) => Promise 22 | disconnect: () => Promise 23 | setActive: () => void 24 | setActiveAccount: (address: string) => void 25 | canSignData: boolean 26 | } 27 | 28 | export type SetAlgodClient = (client: algosdk.Algodv2) => void 29 | 30 | export function useWallet() { 31 | const manager = inject('walletManager') 32 | const algodClient = inject>>('algodClient') 33 | 34 | if (!manager) { 35 | throw new Error('WalletManager plugin is not properly installed') 36 | } 37 | if (!algodClient) { 38 | throw new Error('Algod client not properly installed') 39 | } 40 | 41 | const managerStatus = useStore(manager.store, (state) => state.managerStatus) 42 | const isReady = computed(() => managerStatus.value === 'ready') 43 | 44 | const walletStateMap = useStore(manager.store, (state) => state.wallets) 45 | const activeWalletId = useStore(manager.store, (state) => state.activeWallet) 46 | 47 | const transformToWallet = (wallet: BaseWallet): Wallet => { 48 | const walletState = walletStateMap.value[wallet.id] 49 | return { 50 | id: wallet.id, 51 | metadata: wallet.metadata, 52 | accounts: walletState?.accounts ?? [], 53 | activeAccount: walletState?.activeAccount ?? null, 54 | isConnected: !!walletState, 55 | isActive: wallet.id === activeWalletId.value, 56 | canSignData: wallet.canSignData ?? false, 57 | connect: (args) => wallet.connect(args), 58 | disconnect: () => wallet.disconnect(), 59 | setActive: () => wallet.setActive(), 60 | setActiveAccount: (addr) => wallet.setActiveAccount(addr) 61 | } 62 | } 63 | 64 | const wallets = computed(() => { 65 | return [...manager.wallets.values()].map(transformToWallet) 66 | }) 67 | 68 | const activeWallet = computed(() => { 69 | const wallet = activeWalletId.value ? manager.getWallet(activeWalletId.value) || null : null 70 | return wallet ? transformToWallet(wallet) : null 71 | }) 72 | 73 | const activeBaseWallet = computed(() => { 74 | return activeWalletId.value ? manager.getWallet(activeWalletId.value) || null : null 75 | }) 76 | 77 | const activeWalletState = computed(() => { 78 | const wallet = activeWallet.value 79 | return wallet ? walletStateMap.value[wallet.id] || null : null 80 | }) 81 | 82 | const activeWalletAccounts = computed(() => { 83 | return activeWalletState.value?.accounts ?? null 84 | }) 85 | 86 | const activeWalletAddresses = computed(() => { 87 | return activeWalletAccounts.value?.map((account) => account.address) ?? null 88 | }) 89 | 90 | const activeAccount = computed(() => { 91 | return activeWalletState.value?.activeAccount ?? null 92 | }) 93 | 94 | const activeAddress = computed(() => { 95 | return activeAccount.value?.address ?? null 96 | }) 97 | 98 | const signTransactions = ( 99 | txnGroup: T | T[], 100 | indexesToSign?: number[] 101 | ): Promise<(Uint8Array | null)[]> => { 102 | if (!activeBaseWallet.value) { 103 | throw new Error('No active wallet') 104 | } 105 | return activeBaseWallet.value.signTransactions(txnGroup, indexesToSign) 106 | } 107 | 108 | const transactionSigner = ( 109 | txnGroup: algosdk.Transaction[], 110 | indexesToSign: number[] 111 | ): Promise => { 112 | if (!activeBaseWallet.value) { 113 | throw new Error('No active wallet') 114 | } 115 | return activeBaseWallet.value.transactionSigner(txnGroup, indexesToSign) 116 | } 117 | 118 | const signData = (data: string, metadata: SignMetadata): Promise => { 119 | if (!activeBaseWallet.value) { 120 | throw new Error('No active wallet') 121 | } 122 | return activeBaseWallet.value.signData(data, metadata) 123 | } 124 | 125 | return { 126 | wallets, 127 | isReady, 128 | algodClient: computed(() => { 129 | if (!algodClient.value) { 130 | throw new Error('Algod client is undefined') 131 | } 132 | return algodClient.value 133 | }), 134 | activeWallet, 135 | activeWalletAccounts, 136 | activeWalletAddresses, 137 | activeAccount, 138 | activeAddress, 139 | signData, 140 | signTransactions, 141 | transactionSigner 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /packages/use-wallet-vue/src/walletManagerPlugin.ts: -------------------------------------------------------------------------------- 1 | import { WalletManager, type WalletManagerConfig } from '@txnlab/use-wallet' 2 | import { ref } from 'vue' 3 | import type algosdk from 'algosdk' 4 | 5 | export const WalletManagerPlugin = { 6 | install(app: any, options: WalletManagerConfig) { 7 | const manager = new WalletManager(options) 8 | const algodClient = ref(manager.algodClient) 9 | 10 | const setAlgodClient = (client: algosdk.Algodv2) => { 11 | algodClient.value = client 12 | manager.algodClient = client 13 | } 14 | 15 | app.provide('walletManager', manager) 16 | app.provide('algodClient', algodClient) 17 | app.provide('setAlgodClient', setAlgodClient) 18 | 19 | manager.resumeSessions().catch((error) => { 20 | console.error('Error resuming sessions:', error) 21 | }) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /packages/use-wallet-vue/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "noEmit": true, 4 | "module": "ESNext", 5 | "moduleResolution": "bundler", 6 | "resolveJsonModule": true, 7 | "jsx": "preserve", 8 | "jsxImportSource": "vue", 9 | "noImplicitThis": true, 10 | "strict": true, 11 | "verbatimModuleSyntax": true, 12 | "target": "ESNext", 13 | "useDefineForClassFields": true, 14 | "esModuleInterop": true, 15 | "forceConsistentCasingInFileNames": true, 16 | "skipLibCheck": true, 17 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 18 | "types": ["vitest/globals"] 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /packages/use-wallet-vue/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'tsup' 2 | 3 | export default defineConfig({ 4 | entry: ['src/index.ts'], 5 | dts: true, 6 | splitting: false, 7 | sourcemap: true, 8 | clean: true, 9 | format: ['esm', 'cjs'] 10 | }) 11 | -------------------------------------------------------------------------------- /packages/use-wallet-vue/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import vue from '@vitejs/plugin-vue' 3 | import vueJsx from '@vitejs/plugin-vue-jsx' 4 | 5 | export default defineConfig({ 6 | plugins: [vue(), vueJsx()] 7 | }) 8 | -------------------------------------------------------------------------------- /packages/use-wallet-vue/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig, mergeConfig } from 'vitest/config' 2 | import viteConfig from './vite.config' 3 | 4 | export default mergeConfig( 5 | viteConfig, 6 | defineConfig({ 7 | test: { 8 | name: 'use-wallet-vue', 9 | dir: './src', 10 | watch: false, 11 | environment: 'jsdom', 12 | setupFiles: ['./vitest.setup.ts'], 13 | globals: true 14 | } 15 | }) 16 | ) 17 | -------------------------------------------------------------------------------- /packages/use-wallet-vue/vitest.setup.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | import '@testing-library/jest-dom/vitest' 4 | 5 | // Suppress console output 6 | vi.spyOn(console, 'info').mockImplementation(() => {}) 7 | -------------------------------------------------------------------------------- /packages/use-wallet/README.md: -------------------------------------------------------------------------------- 1 | # @txnlab/use-wallet 2 | 3 | [![GitHub package.json version](https://img.shields.io/github/package-json/v/TxnLab/use-wallet?filename=packages%2Fuse-wallet%2Fpackage.json&label=version)](https://www.npmjs.com/package/@txnlab/use-wallet?activeTab=versions) 4 | [![GitHub License](https://img.shields.io/github/license/TxnLab/use-wallet)](https://github.com/TxnLab/use-wallet/blob/main/LICENSE.md) 5 | 6 | Core library for integrating Algorand wallets into your application. 7 | 8 | ### Visit [txnlab.gitbook.io/use-wallet](https://txnlab.gitbook.io/use-wallet) for docs, guides, and examples! 9 | -------------------------------------------------------------------------------- /packages/use-wallet/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@txnlab/use-wallet", 3 | "version": "4.1.0", 4 | "description": "TypeScript library for integrating Algorand wallets into decentralized applications", 5 | "author": "Doug Richar ", 6 | "license": "MIT", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/txnlab/use-wallet.git", 10 | "directory": "packages/use-wallet" 11 | }, 12 | "type": "module", 13 | "main": "./dist/index.cjs", 14 | "module": "./dist/index.js", 15 | "exports": "./dist/index.js", 16 | "types": "./dist/index.d.ts", 17 | "scripts": { 18 | "build": "tsup", 19 | "start": "tsup src/index.ts --watch", 20 | "test": "vitest run", 21 | "test:watch": "pnpm run test --watch", 22 | "lint": "eslint -c \"../../.eslintrc.json\" \"**/*.{js,ts}\"", 23 | "typecheck": "tsc --noEmit" 24 | }, 25 | "keywords": [ 26 | "algorand", 27 | "wallet", 28 | "walletconnect", 29 | "pera", 30 | "defly", 31 | "exodus", 32 | "algosdk", 33 | "algokit", 34 | "kmd" 35 | ], 36 | "files": [ 37 | "dist" 38 | ], 39 | "dependencies": { 40 | "@tanstack/store": "0.7.0" 41 | }, 42 | "devDependencies": { 43 | "@agoralabs-sh/avm-web-provider": "1.7.0", 44 | "@blockshake/defly-connect": "1.2.1", 45 | "@magic-ext/algorand": "23.20.0", 46 | "@magic-sdk/provider": "28.20.0", 47 | "@perawallet/connect": "1.4.1", 48 | "@types/node": "20.11.30", 49 | "@walletconnect/modal": "2.7.0", 50 | "@walletconnect/modal-core": "2.7.0", 51 | "@walletconnect/sign-client": "2.19.2", 52 | "@walletconnect/types": "2.19.2", 53 | "algosdk": "3.2.0", 54 | "lute-connect": "1.6.1", 55 | "magic-sdk": "28.21.1", 56 | "tsup": "8.4.0", 57 | "typescript": "5.8.2" 58 | }, 59 | "peerDependencies": { 60 | "@agoralabs-sh/avm-web-provider": "^1.7.0", 61 | "@blockshake/defly-connect": "^1.2.1", 62 | "@perawallet/connect": "^1.4.1", 63 | "@walletconnect/modal": "^2.7.0", 64 | "@walletconnect/sign-client": "^2.19.2", 65 | "algosdk": "^3.0.0", 66 | "lute-connect": "^1.6.1" 67 | }, 68 | "peerDependenciesMeta": { 69 | "@agoralabs-sh/avm-web-provider": { 70 | "optional": true 71 | }, 72 | "@blockshake/defly-connect": { 73 | "optional": true 74 | }, 75 | "@perawallet/connect": { 76 | "optional": true 77 | }, 78 | "@walletconnect/modal": { 79 | "optional": true 80 | }, 81 | "@walletconnect/sign-client": { 82 | "optional": true 83 | }, 84 | "lute-connect": { 85 | "optional": true 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /packages/use-wallet/setupTests.ts: -------------------------------------------------------------------------------- 1 | // Define `self` to avoid ReferenceError in Node.js environment 2 | // eslint-disable-next-line no-extra-semi 3 | ;(global as any).self = global 4 | -------------------------------------------------------------------------------- /packages/use-wallet/src/__tests__/logger.test.ts: -------------------------------------------------------------------------------- 1 | import { Logger, LogLevel } from 'src/logger' 2 | 3 | describe('Logger', () => { 4 | let logger: Logger 5 | let consoleWarnSpy: ReturnType 6 | let consoleErrorSpy: ReturnType 7 | let consoleInfoSpy: ReturnType 8 | 9 | beforeEach(() => { 10 | // Reset singleton instance 11 | Logger['instance'] = null 12 | logger = Logger.getInstance() 13 | 14 | // Set isClient to true for testing 15 | logger.setIsClient(true) 16 | 17 | // Mock console methods 18 | consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}) 19 | consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {}) 20 | consoleInfoSpy = vi.spyOn(console, 'info').mockImplementation(() => {}) 21 | 22 | // Set log level to DEBUG 23 | Logger.setLevel(LogLevel.DEBUG) 24 | }) 25 | 26 | afterEach(() => { 27 | vi.restoreAllMocks() 28 | }) 29 | 30 | it('should return the same instance', () => { 31 | const logger1 = Logger.getInstance() 32 | const logger2 = Logger.getInstance() 33 | expect(logger1).toBe(logger2) 34 | }) 35 | 36 | it('should log at all levels when set to DEBUG', () => { 37 | logger.debug('Test debug') 38 | logger.info('Test info') 39 | logger.warn('Test warn') 40 | logger.error('Test error') 41 | 42 | expect(consoleInfoSpy).toHaveBeenCalledWith('Test debug') 43 | expect(consoleInfoSpy).toHaveBeenCalledWith('Test info') 44 | expect(consoleWarnSpy).toHaveBeenCalledWith('Test warn') 45 | expect(consoleErrorSpy).toHaveBeenCalledWith('Test error') 46 | }) 47 | 48 | it('should respect log level changes', () => { 49 | Logger.setLevel(LogLevel.WARN) 50 | 51 | logger.debug('Test debug') 52 | logger.info('Test info') 53 | logger.warn('Test warn') 54 | logger.error('Test error') 55 | 56 | expect(consoleInfoSpy).not.toHaveBeenCalled() 57 | expect(consoleWarnSpy).toHaveBeenCalledWith('Test warn') 58 | expect(consoleErrorSpy).toHaveBeenCalledWith('Test error') 59 | }) 60 | 61 | it('should only log errors when set to ERROR level', () => { 62 | Logger.setLevel(LogLevel.ERROR) 63 | 64 | logger.debug('Test debug') 65 | logger.info('Test info') 66 | logger.warn('Test warn') 67 | logger.error('Test error') 68 | 69 | expect(consoleInfoSpy).not.toHaveBeenCalled() 70 | expect(consoleWarnSpy).not.toHaveBeenCalled() 71 | expect(consoleErrorSpy).toHaveBeenCalledWith('Test error') 72 | }) 73 | 74 | it('should log with scope when using scoped logger', () => { 75 | const scopedLogger = logger.createScopedLogger('MyScope') 76 | 77 | scopedLogger.debug('Scoped debug') 78 | scopedLogger.info('Scoped info') 79 | scopedLogger.warn('Scoped warn') 80 | scopedLogger.error('Scoped error') 81 | 82 | expect(consoleInfoSpy).toHaveBeenCalledWith('[MyScope] Scoped debug') 83 | expect(consoleInfoSpy).toHaveBeenCalledWith('[MyScope] Scoped info') 84 | expect(consoleWarnSpy).toHaveBeenCalledWith('[MyScope] Scoped warn') 85 | expect(consoleErrorSpy).toHaveBeenCalledWith('[MyScope] Scoped error') 86 | }) 87 | }) 88 | -------------------------------------------------------------------------------- /packages/use-wallet/src/__tests__/network.test.ts: -------------------------------------------------------------------------------- 1 | import { NetworkConfigBuilder, isNetworkConfig, createNetworkConfig } from 'src/network' 2 | 3 | describe('Network Configuration', () => { 4 | describe('createNetworkConfig', () => { 5 | it('returns default network configurations', () => { 6 | const networks = createNetworkConfig() 7 | 8 | expect(networks).toHaveProperty('mainnet') 9 | expect(networks).toHaveProperty('testnet') 10 | expect(networks).toHaveProperty('betanet') 11 | expect(networks).toHaveProperty('fnet') 12 | expect(networks).toHaveProperty('localnet') 13 | }) 14 | 15 | it('includes correct default values for mainnet', () => { 16 | const networks = createNetworkConfig() 17 | 18 | expect(networks.mainnet).toEqual({ 19 | algod: { 20 | token: '', 21 | baseServer: 'https://mainnet-api.4160.nodely.dev', 22 | headers: {} 23 | }, 24 | isTestnet: false, 25 | genesisHash: 'wGHE2Pwdvd7S12BL5FaOP20EGYesN73ktiC1qzkkit8=', 26 | genesisId: 'mainnet-v1.0', 27 | caipChainId: 'algorand:wGHE2Pwdvd7S12BL5FaOP20EGYesN73k' 28 | }) 29 | }) 30 | }) 31 | 32 | describe('NetworkConfigBuilder', () => { 33 | it('allows customizing default network algod config', () => { 34 | const networks = new NetworkConfigBuilder() 35 | .mainnet({ 36 | algod: { 37 | token: 'custom-token', 38 | baseServer: 'custom-server', 39 | headers: { 'X-API-Key': 'key' } 40 | } 41 | }) 42 | .build() 43 | 44 | expect(networks.mainnet.algod).toEqual({ 45 | token: 'custom-token', 46 | baseServer: 'custom-server', 47 | headers: { 'X-API-Key': 'key' } 48 | }) 49 | // Other properties should remain unchanged 50 | expect(networks.mainnet.isTestnet).toBe(false) 51 | }) 52 | 53 | it('allows adding custom networks', () => { 54 | const customNetwork = { 55 | algod: { 56 | token: 'token', 57 | baseServer: 'server', 58 | headers: {} 59 | }, 60 | isTestnet: true 61 | } 62 | 63 | const networks = new NetworkConfigBuilder().addNetwork('custom', customNetwork).build() 64 | 65 | expect(networks.custom).toEqual(customNetwork) 66 | // Default networks should still be present 67 | expect(networks.mainnet).toBeDefined() 68 | }) 69 | 70 | it('prevents overwriting default networks using addNetwork', () => { 71 | const builder = new NetworkConfigBuilder() 72 | 73 | expect(() => 74 | builder.addNetwork('mainnet', { 75 | algod: { 76 | token: '', 77 | baseServer: '' 78 | } 79 | }) 80 | ).toThrow('Cannot add network with reserved id "mainnet"') 81 | }) 82 | 83 | it('maintains all default networks when customizing one', () => { 84 | const networks = new NetworkConfigBuilder() 85 | .mainnet({ 86 | algod: { 87 | token: 'custom-token', 88 | baseServer: 'custom-server' 89 | } 90 | }) 91 | .build() 92 | 93 | expect(networks.testnet).toBeDefined() 94 | expect(networks.betanet).toBeDefined() 95 | expect(networks.fnet).toBeDefined() 96 | expect(networks.localnet).toBeDefined() 97 | }) 98 | }) 99 | 100 | describe('isNetworkConfig', () => { 101 | it('validates correct network configs', () => { 102 | const validConfig = { 103 | algod: { 104 | token: 'token', 105 | baseServer: 'server' 106 | } 107 | } 108 | expect(isNetworkConfig(validConfig)).toBe(true) 109 | }) 110 | 111 | it('validates network configs with optional properties', () => { 112 | const validConfig = { 113 | algod: { 114 | token: 'token', 115 | baseServer: 'server' 116 | }, 117 | isTestnet: true, 118 | genesisHash: 'hash', 119 | genesisId: 'id' 120 | } 121 | expect(isNetworkConfig(validConfig)).toBe(true) 122 | }) 123 | 124 | it('rejects invalid network configs', () => { 125 | expect(isNetworkConfig(null)).toBe(false) 126 | expect(isNetworkConfig({})).toBe(false) 127 | expect( 128 | isNetworkConfig({ 129 | algod: { baseServer: 'server' } 130 | }) 131 | ).toBe(false) 132 | }) 133 | }) 134 | }) 135 | -------------------------------------------------------------------------------- /packages/use-wallet/src/index.ts: -------------------------------------------------------------------------------- 1 | export { LogLevel } from './logger' 2 | export { WalletManager, WalletManagerConfig, WalletManagerOptions } from './manager' 3 | export { 4 | AlgodConfig, 5 | NetworkConfig, 6 | NetworkConfigBuilder, 7 | NetworkId, 8 | DEFAULT_NETWORK_CONFIG 9 | } from './network' 10 | export { State, WalletState, ManagerStatus, DEFAULT_STATE } from './store' 11 | export { StorageAdapter } from './storage' 12 | export { webpackFallback } from './webpack' 13 | export * from './wallets' 14 | -------------------------------------------------------------------------------- /packages/use-wallet/src/logger.ts: -------------------------------------------------------------------------------- 1 | export enum LogLevel { 2 | DEBUG, 3 | INFO, 4 | WARN, 5 | ERROR 6 | } 7 | 8 | export class Logger { 9 | private static instance: Logger | null = null 10 | private level: LogLevel 11 | private isClient: boolean 12 | 13 | private constructor() { 14 | this.level = LogLevel.WARN 15 | this.isClient = typeof window !== 'undefined' 16 | } 17 | 18 | public static getInstance(): Logger { 19 | if (!Logger.instance) { 20 | Logger.instance = new Logger() 21 | } 22 | return Logger.instance 23 | } 24 | 25 | public static setLevel(level: LogLevel): void { 26 | Logger.getInstance().level = level 27 | } 28 | 29 | private log(level: LogLevel, scope: string | undefined, message: string, ...args: any[]): void { 30 | if (level >= this.level && this.isClient) { 31 | const formattedMessage = scope ? `[${scope}] ${message}` : message 32 | switch (level) { 33 | case LogLevel.DEBUG: 34 | case LogLevel.INFO: 35 | console.info(formattedMessage, ...args) 36 | break 37 | case LogLevel.WARN: 38 | console.warn(formattedMessage, ...args) 39 | break 40 | case LogLevel.ERROR: 41 | console.error(formattedMessage, ...args) 42 | break 43 | } 44 | } 45 | } 46 | 47 | public createScopedLogger(scope: string) { 48 | return { 49 | debug: (message: string, ...args: any[]) => this.log(LogLevel.DEBUG, scope, message, ...args), 50 | info: (message: string, ...args: any[]) => this.log(LogLevel.INFO, scope, message, ...args), 51 | warn: (message: string, ...args: any[]) => this.log(LogLevel.WARN, scope, message, ...args), 52 | error: (message: string, ...args: any[]) => this.log(LogLevel.ERROR, scope, message, ...args) 53 | } 54 | } 55 | 56 | public debug(message: string, ...args: any[]): void { 57 | this.log(LogLevel.DEBUG, undefined, message, ...args) 58 | } 59 | 60 | public info(message: string, ...args: any[]): void { 61 | this.log(LogLevel.INFO, undefined, message, ...args) 62 | } 63 | 64 | public warn(message: string, ...args: any[]): void { 65 | this.log(LogLevel.WARN, undefined, message, ...args) 66 | } 67 | 68 | public error(message: string, ...args: any[]): void { 69 | this.log(LogLevel.ERROR, undefined, message, ...args) 70 | } 71 | 72 | // For testing purposes 73 | public setIsClient(isClient: boolean): void { 74 | this.isClient = isClient 75 | } 76 | } 77 | 78 | export const logger = Logger.getInstance() 79 | -------------------------------------------------------------------------------- /packages/use-wallet/src/storage.ts: -------------------------------------------------------------------------------- 1 | export class StorageAdapter { 2 | static getItem(key: string): string | null { 3 | if (typeof window === 'undefined') { 4 | return null 5 | } 6 | return localStorage.getItem(key) 7 | } 8 | 9 | static setItem(key: string, value: string): void { 10 | if (typeof window === 'undefined') { 11 | return 12 | } 13 | localStorage.setItem(key, value) 14 | } 15 | 16 | static removeItem(key: string): void { 17 | if (typeof window === 'undefined') { 18 | return 19 | } 20 | localStorage.removeItem(key) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /packages/use-wallet/src/utils.ts: -------------------------------------------------------------------------------- 1 | import algosdk from 'algosdk' 2 | import { WalletId, type JsonRpcRequest, type WalletAccount, type WalletMap } from './wallets/types' 3 | import { BiatecWallet } from './wallets/biatec' 4 | import { CustomWallet } from './wallets/custom' 5 | import { DeflyWallet } from './wallets/defly' 6 | import { DeflyWebWallet } from './wallets/defly-web' 7 | import { ExodusWallet } from './wallets/exodus' 8 | import { KibisisWallet } from './wallets/kibisis' 9 | import { KmdWallet } from './wallets/kmd' 10 | import { LuteWallet } from './wallets/lute' 11 | import { MagicAuth } from './wallets/magic' 12 | import { MnemonicWallet } from './wallets/mnemonic' 13 | import { PeraWallet } from './wallets/pera' 14 | import { WalletConnect } from './wallets/walletconnect' 15 | 16 | export function createWalletMap(): WalletMap { 17 | return { 18 | [WalletId.BIATEC]: BiatecWallet, 19 | [WalletId.CUSTOM]: CustomWallet, 20 | [WalletId.DEFLY]: DeflyWallet, 21 | [WalletId.DEFLY_WEB]: DeflyWebWallet, 22 | [WalletId.EXODUS]: ExodusWallet, 23 | [WalletId.KIBISIS]: KibisisWallet, 24 | [WalletId.KMD]: KmdWallet, 25 | [WalletId.LUTE]: LuteWallet, 26 | [WalletId.MAGIC]: MagicAuth, 27 | [WalletId.MNEMONIC]: MnemonicWallet, 28 | [WalletId.PERA]: PeraWallet, 29 | [WalletId.WALLETCONNECT]: WalletConnect 30 | } 31 | } 32 | 33 | export function compareAccounts(accounts: WalletAccount[], compareTo: WalletAccount[]): boolean { 34 | const addresses = new Set(accounts.map((account) => account.address)) 35 | const compareAddresses = new Set(compareTo.map((account) => account.address)) 36 | 37 | if (addresses.size !== compareAddresses.size) { 38 | return false 39 | } 40 | 41 | // Check if every address in addresses is also in compareAddresses 42 | for (const address of addresses) { 43 | if (!compareAddresses.has(address)) { 44 | return false 45 | } 46 | } 47 | 48 | return true 49 | } 50 | 51 | export function base64ToByteArray(blob: string): Uint8Array { 52 | return stringToByteArray(atob(blob)) 53 | } 54 | 55 | export function byteArrayToBase64(array: Uint8Array): string { 56 | return btoa(byteArrayToString(array)) 57 | } 58 | 59 | export function stringToByteArray(str: string): Uint8Array { 60 | const array = new Uint8Array(str.length) 61 | for (let i = 0; i < str.length; i++) { 62 | array[i] = str.charCodeAt(i) 63 | } 64 | return array 65 | } 66 | 67 | export function byteArrayToString(array: Uint8Array): string { 68 | let result = '' 69 | for (let i = 0; i < array.length; i++) { 70 | result += String.fromCharCode(array[i]) 71 | } 72 | return result 73 | } 74 | 75 | export function isSignedTxn(txnObj: any): boolean { 76 | if (!txnObj || typeof txnObj !== 'object') return false 77 | if (!('sig' in txnObj && 'txn' in txnObj)) return false 78 | 79 | // Verify sig is a Uint8Array 80 | if (!(txnObj.sig instanceof Uint8Array)) return false 81 | 82 | // Verify txn is an object 83 | const txn = txnObj.txn 84 | if (!txn || typeof txn !== 'object') return false 85 | 86 | // Check for common transaction properties 87 | const hasRequiredProps = 'type' in txn && 'snd' in txn 88 | 89 | return hasRequiredProps 90 | } 91 | 92 | export function isTransaction(item: any): item is algosdk.Transaction { 93 | return ( 94 | item && 95 | typeof item === 'object' && 96 | 'sender' in item && 97 | (item.sender instanceof algosdk.Address || typeof item.sender === 'string') 98 | ) 99 | } 100 | 101 | export function isTransactionArray( 102 | txnGroup: any 103 | ): txnGroup is algosdk.Transaction[] | algosdk.Transaction[][] { 104 | if (!Array.isArray(txnGroup) || txnGroup.length === 0) { 105 | return false 106 | } 107 | 108 | if (isTransaction(txnGroup[0])) { 109 | return true 110 | } 111 | 112 | if (Array.isArray(txnGroup[0]) && txnGroup[0].length > 0 && isTransaction(txnGroup[0][0])) { 113 | return true 114 | } 115 | 116 | return false 117 | } 118 | 119 | export function flattenTxnGroup(txnGroup: T[]): T[] { 120 | if (!Array.isArray(txnGroup[0])) { 121 | return txnGroup 122 | } 123 | return (txnGroup as unknown as any[]).flat() 124 | } 125 | 126 | function getPayloadId(): number { 127 | const date = Date.now() * Math.pow(10, 3) 128 | const extra = Math.floor(Math.random() * Math.pow(10, 3)) 129 | return date + extra 130 | } 131 | 132 | export function formatJsonRpcRequest(method: string, params: T): JsonRpcRequest { 133 | return { 134 | id: getPayloadId(), 135 | jsonrpc: '2.0', 136 | method, 137 | params 138 | } 139 | } 140 | 141 | // @todo: remove 142 | export function deepMerge(target: any, source: any): any { 143 | const isObject = (obj: any) => obj && typeof obj === 'object' 144 | 145 | if (!isObject(target) || !isObject(source)) { 146 | throw new Error('Target and source must be objects') 147 | } 148 | 149 | Object.keys(source).forEach((key) => { 150 | const targetValue = target[key] 151 | const sourceValue = source[key] 152 | 153 | if (Array.isArray(targetValue) && Array.isArray(sourceValue)) { 154 | target[key] = targetValue.concat(sourceValue) 155 | } else if (isObject(targetValue) && isObject(sourceValue)) { 156 | target[key] = deepMerge(Object.assign({}, targetValue), sourceValue) 157 | } else { 158 | target[key] = sourceValue 159 | } 160 | }) 161 | 162 | return target 163 | } 164 | -------------------------------------------------------------------------------- /packages/use-wallet/src/wallets/index.ts: -------------------------------------------------------------------------------- 1 | export * from './base' 2 | export * from './custom' 3 | export * from './defly' 4 | export * from './exodus' 5 | export * from './kibisis' 6 | export * from './kmd' 7 | export * from './magic' 8 | export * from './mnemonic' 9 | export * from './pera' 10 | export * from './types' 11 | export * from './walletconnect' 12 | -------------------------------------------------------------------------------- /packages/use-wallet/src/webpack.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Fallback configuration for Webpack to handle optional wallet dependencies. 3 | * This allows applications to build without these packages installed, 4 | * enabling users to include only the wallet packages they need. 5 | * Each package is set to 'false', which means Webpack will provide an empty module 6 | * if the package is not found, preventing build errors for unused wallets. 7 | */ 8 | export const webpackFallback = { 9 | '@agoralabs-sh/avm-web-provider': false, 10 | '@blockshake/defly-connect': false, 11 | '@magic-ext/algorand': false, 12 | '@perawallet/connect': false, 13 | '@walletconnect/modal': false, 14 | '@walletconnect/sign-client': false, 15 | 'lute-connect': false, 16 | 'magic-sdk': false 17 | } 18 | -------------------------------------------------------------------------------- /packages/use-wallet/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "compilerOptions": { 4 | "target": "ESNext", 5 | "strict": true, 6 | "esModuleInterop": true, 7 | "module": "ESNext", 8 | "moduleResolution": "node", 9 | "allowImportingTsExtensions": true, 10 | "allowSyntheticDefaultImports": true, 11 | "exactOptionalPropertyTypes": true, 12 | "skipLibCheck": true, 13 | "noEmit": true, 14 | "baseUrl": ".", 15 | "rootDir": ".", 16 | "outDir": "dist", 17 | "types": ["vitest/globals"] 18 | }, 19 | "exclude": ["node_modules", "dist"] 20 | } 21 | -------------------------------------------------------------------------------- /packages/use-wallet/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'tsup' 2 | 3 | export default defineConfig({ 4 | entry: ['src/index.ts'], 5 | dts: true, 6 | splitting: false, 7 | sourcemap: true, 8 | clean: true, 9 | format: ['esm', 'cjs'] 10 | }) 11 | -------------------------------------------------------------------------------- /packages/use-wallet/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitest/config' 2 | 3 | export default defineConfig({ 4 | test: { 5 | name: 'use-wallet', 6 | dir: './src', 7 | watch: false, 8 | globals: true, 9 | setupFiles: './setupTests.ts' 10 | } 11 | }) 12 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - 'examples/*' 3 | - 'packages/*' 4 | --------------------------------------------------------------------------------