From 773194ac811a106f0bf8461bcbee91f2a2e9fa36 Mon Sep 17 00:00:00 2001 From: Doug Richar Date: Sat, 3 Jun 2023 17:16:49 -0400 Subject: [PATCH 01/29] chore: remove link to polyfills instructions, closes #77 --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index ce0279bc..c0e884cf 100644 --- a/README.md +++ b/README.md @@ -19,8 +19,6 @@ Preview a basic implementation in [Storybook](https://txnlab.github.io/use-walle ## Quick Start -⚠️ If you're using `create-react-app` and `webpack 5` (most newer React projects), you will need to install polyfills. Follow [these directions](#webpack-5). - ### Yarn ```bash From 84fa6eaa8ce5b00fc0c02554e5b01acc9a87b0a6 Mon Sep 17 00:00:00 2001 From: Doug Richar Date: Tue, 20 Jun 2023 14:54:55 -0400 Subject: [PATCH 02/29] v1.3.1 (#84) * TXN-1398: Handle nested transaction arrays in signTransactions (#79) * chore: bump version to 1.3.1-alpha.0 * fix: update signTransactions type signature in useWallet hook The provider clients were updated to accept an array of transaction groups for signing, but the `signTransactions` function returned by useWallet was still typed to accept a flat array of transactions only. * chore: version bump to v1.3.1 --- package.json | 2 +- src/clients/base/base.ts | 2 +- src/clients/daffi/client.ts | 7 ++++++- src/clients/defly/client.ts | 7 ++++++- src/clients/exodus/client.ts | 7 ++++++- src/clients/kmd/client.ts | 7 ++++++- src/clients/mnemonic/client.ts | 7 ++++++- src/clients/pera/client.ts | 7 ++++++- src/clients/walletconnect/client.ts | 7 ++++++- src/hooks/useWallet.ts | 2 +- 10 files changed, 45 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index cc5c186a..d5b63df4 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "url": "https://github.com/txnlab/use-wallet/issues" }, "homepage": "https://txnlab.github.io/use-wallet", - "version": "1.3.0", + "version": "1.3.1", "description": "React hooks for using Algorand compatible wallets in dApps.", "scripts": { "dev": "yarn storybook", diff --git a/src/clients/base/base.ts b/src/clients/base/base.ts index 55ed2220..8d720ab0 100644 --- a/src/clients/base/base.ts +++ b/src/clients/base/base.ts @@ -36,7 +36,7 @@ abstract class BaseClient { abstract reconnect(onDisconnect: () => void): Promise abstract signTransactions( connectedAccounts: string[], - transactions: Array, + txnGroups: Uint8Array[] | Uint8Array[][], indexesToSign?: number[], returnGroup?: boolean ): Promise diff --git a/src/clients/daffi/client.ts b/src/clients/daffi/client.ts index e6c94137..92fb08b6 100644 --- a/src/clients/daffi/client.ts +++ b/src/clients/daffi/client.ts @@ -107,10 +107,15 @@ class DaffiWalletClient extends BaseWallet { async signTransactions( connectedAccounts: string[], - transactions: Uint8Array[], + txnGroups: Uint8Array[] | Uint8Array[][], indexesToSign?: number[], returnGroup = true ) { + // If txnGroups is a nested array, flatten it + const transactions: Uint8Array[] = Array.isArray(txnGroups[0]) + ? (txnGroups as Uint8Array[][]).flatMap((txn) => txn) + : (txnGroups as Uint8Array[]) + // Decode the transactions to access their properties. const decodedTxns = transactions.map((txn) => { return this.algosdk.decodeObj(txn) diff --git a/src/clients/defly/client.ts b/src/clients/defly/client.ts index 83ac6195..98c0709e 100644 --- a/src/clients/defly/client.ts +++ b/src/clients/defly/client.ts @@ -106,10 +106,15 @@ class DeflyWalletClient extends BaseWallet { async signTransactions( connectedAccounts: string[], - transactions: Uint8Array[], + txnGroups: Uint8Array[] | Uint8Array[][], indexesToSign?: number[], returnGroup = true ) { + // If txnGroups is a nested array, flatten it + const transactions: Uint8Array[] = Array.isArray(txnGroups[0]) + ? (txnGroups as Uint8Array[][]).flatMap((txn) => txn) + : (txnGroups as Uint8Array[]) + // Decode the transactions to access their properties. const decodedTxns = transactions.map((txn) => { return this.algosdk.decodeObj(txn) diff --git a/src/clients/exodus/client.ts b/src/clients/exodus/client.ts index 2e8edc3e..35e0de05 100644 --- a/src/clients/exodus/client.ts +++ b/src/clients/exodus/client.ts @@ -115,10 +115,15 @@ class ExodusClient extends BaseWallet { async signTransactions( connectedAccounts: string[], - transactions: Array, + txnGroups: Uint8Array[] | Uint8Array[][], indexesToSign?: number[], returnGroup = true ) { + // If txnGroups is a nested array, flatten it + const transactions: Uint8Array[] = Array.isArray(txnGroups[0]) + ? (txnGroups as Uint8Array[][]).flatMap((txn) => txn) + : (txnGroups as Uint8Array[]) + // Decode the transactions to access their properties. const decodedTxns = transactions.map((txn) => { return this.algosdk.decodeObj(txn) diff --git a/src/clients/kmd/client.ts b/src/clients/kmd/client.ts index 6b5e0a41..a6415938 100644 --- a/src/clients/kmd/client.ts +++ b/src/clients/kmd/client.ts @@ -160,10 +160,15 @@ class KMDWalletClient extends BaseWallet { async signTransactions( connectedAccounts: string[], - transactions: Uint8Array[], + txnGroups: Uint8Array[] | Uint8Array[][], indexesToSign?: number[], returnGroup = true ) { + // If txnGroups is a nested array, flatten it + const transactions: Uint8Array[] = Array.isArray(txnGroups[0]) + ? (txnGroups as Uint8Array[][]).flatMap((txn) => txn) + : (txnGroups as Uint8Array[]) + // Decode the transactions to access their properties. const decodedTxns = transactions.map((txn) => { return this.algosdk.decodeObj(txn) diff --git a/src/clients/mnemonic/client.ts b/src/clients/mnemonic/client.ts index b10f2c07..c47a6fd7 100644 --- a/src/clients/mnemonic/client.ts +++ b/src/clients/mnemonic/client.ts @@ -87,7 +87,7 @@ class MnemonicWalletClient extends BaseWallet { signTransactions( connectedAccounts: string[], - transactions: Uint8Array[], + txnGroups: Uint8Array[] | Uint8Array[][], indexesToSign?: number[], returnGroup = true ): Promise { @@ -95,6 +95,11 @@ class MnemonicWalletClient extends BaseWallet { throw new Error('Client not connected') } + // If txnGroups is a nested array, flatten it + const transactions: Uint8Array[] = Array.isArray(txnGroups[0]) + ? (txnGroups as Uint8Array[][]).flatMap((txn) => txn) + : (txnGroups as Uint8Array[]) + // Decode the transactions to access their properties. const decodedTxns = transactions.map((txn) => { return this.algosdk.decodeObj(txn) diff --git a/src/clients/pera/client.ts b/src/clients/pera/client.ts index 050bbce3..6197d5bc 100644 --- a/src/clients/pera/client.ts +++ b/src/clients/pera/client.ts @@ -107,10 +107,15 @@ class PeraWalletClient extends BaseWallet { async signTransactions( connectedAccounts: string[], - transactions: Uint8Array[], + txnGroups: Uint8Array[] | Uint8Array[][], indexesToSign?: number[], returnGroup = true ) { + // If txnGroups is a nested array, flatten it + const transactions: Uint8Array[] = Array.isArray(txnGroups[0]) + ? (txnGroups as Uint8Array[][]).flatMap((txn) => txn) + : (txnGroups as Uint8Array[]) + // Decode the transactions to access their properties. const decodedTxns = transactions.map((txn) => { return this.algosdk.decodeObj(txn) diff --git a/src/clients/walletconnect/client.ts b/src/clients/walletconnect/client.ts index b860e5ae..1f053009 100644 --- a/src/clients/walletconnect/client.ts +++ b/src/clients/walletconnect/client.ts @@ -150,10 +150,15 @@ class WalletConnectClient extends BaseWallet { async signTransactions( connectedAccounts: string[], - transactions: Uint8Array[], + txnGroups: Uint8Array[] | Uint8Array[][], indexesToSign?: number[], returnGroup = true ) { + // If txnGroups is a nested array, flatten it + const transactions: Uint8Array[] = Array.isArray(txnGroups[0]) + ? (txnGroups as Uint8Array[][]).flatMap((txn) => txn) + : (txnGroups as Uint8Array[]) + // Decode the transactions to access their properties. const decodedTxns = transactions.map((txn) => { return this.algosdk.decodeObj(txn) diff --git a/src/hooks/useWallet.ts b/src/hooks/useWallet.ts index 0b5e60e2..bcd2aa1a 100644 --- a/src/hooks/useWallet.ts +++ b/src/hooks/useWallet.ts @@ -162,7 +162,7 @@ export default function useWallet() { } const signTransactions = async ( - transactions: Array, + transactions: Uint8Array[] | Uint8Array[][], indexesToSign?: number[], returnGroup = true ) => { From b25cbeb0f2046f29826e635fa5ac11f18d9c1156 Mon Sep 17 00:00:00 2001 From: Doug Richar Date: Tue, 20 Jun 2023 23:40:07 -0400 Subject: [PATCH 03/29] chore(deps): upgrade @blockshake/defly-connect to v1.1.3 --- package.json | 6 +++--- yarn.lock | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index d5b63df4..bdcd185c 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "url": "https://github.com/txnlab/use-wallet/issues" }, "homepage": "https://txnlab.github.io/use-wallet", - "version": "1.3.1", + "version": "1.3.2", "description": "React hooks for using Algorand compatible wallets in dApps.", "scripts": { "dev": "yarn storybook", @@ -31,7 +31,7 @@ "@babel/preset-env": "^7.16.4", "@babel/preset-react": "^7.16.0", "@babel/preset-typescript": "^7.16.0", - "@blockshake/defly-connect": "^1.1.2", + "@blockshake/defly-connect": "^1.1.3", "@daffiwallet/connect": "^1.0.3", "@json-rpc-tools/utils": "^1.7.6", "@mdx-js/react": "^2.1.2", @@ -92,7 +92,7 @@ "utf-8-validate": "^6.0.3" }, "peerDependencies": { - "@blockshake/defly-connect": "^1.1.2", + "@blockshake/defly-connect": "^1.1.3", "@daffiwallet/connect": "^1.0.3", "@json-rpc-tools/utils": "^1.7.6", "@perawallet/connect": "^1.2.1", diff --git a/yarn.lock b/yarn.lock index 7890f903..d595b9e9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1129,10 +1129,10 @@ resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== -"@blockshake/defly-connect@^1.1.2": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@blockshake/defly-connect/-/defly-connect-1.1.2.tgz#7935883f77527cdb022fc4a87bb29bee686f11f7" - integrity sha512-64plibtIAlVmRsLsc7xvQEeYWmABgbd5+RpebS+f0/Uqo5anFYHFtvoaAmSdnfwcD27M+DsIjHRS2mpAxs6e+w== +"@blockshake/defly-connect@^1.1.3": + version "1.1.3" + resolved "https://registry.yarnpkg.com/@blockshake/defly-connect/-/defly-connect-1.1.3.tgz#bea8ecc9bd2adf1e3a7740dd53647386c722b71e" + integrity sha512-9hOHMVrZ5XPX9nL7GWsc58uiKMjjlPOokSLVVwlfg4R6sVhGdBZXvOgQCIRwePEM1tr6ZfR+Q+726CWVAXk8WQ== dependencies: "@walletconnect/client" "^1.8.0" "@walletconnect/types" "^1.8.0" From 32c7bbaadf432adf11bb802e46d4f2372d54a000 Mon Sep 17 00:00:00 2001 From: Doug Richar Date: Sat, 24 Jun 2023 13:33:49 -0400 Subject: [PATCH 04/29] chore(deps): upgrade @blockshake/defly-connect to v1.1.5 --- package.json | 4 ++-- yarn.lock | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index bdcd185c..55fa4114 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ "@babel/preset-env": "^7.16.4", "@babel/preset-react": "^7.16.0", "@babel/preset-typescript": "^7.16.0", - "@blockshake/defly-connect": "^1.1.3", + "@blockshake/defly-connect": "^1.1.5", "@daffiwallet/connect": "^1.0.3", "@json-rpc-tools/utils": "^1.7.6", "@mdx-js/react": "^2.1.2", @@ -92,7 +92,7 @@ "utf-8-validate": "^6.0.3" }, "peerDependencies": { - "@blockshake/defly-connect": "^1.1.3", + "@blockshake/defly-connect": "^1.1.5", "@daffiwallet/connect": "^1.0.3", "@json-rpc-tools/utils": "^1.7.6", "@perawallet/connect": "^1.2.1", diff --git a/yarn.lock b/yarn.lock index d595b9e9..5f9badc7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1129,10 +1129,10 @@ resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== -"@blockshake/defly-connect@^1.1.3": - version "1.1.3" - resolved "https://registry.yarnpkg.com/@blockshake/defly-connect/-/defly-connect-1.1.3.tgz#bea8ecc9bd2adf1e3a7740dd53647386c722b71e" - integrity sha512-9hOHMVrZ5XPX9nL7GWsc58uiKMjjlPOokSLVVwlfg4R6sVhGdBZXvOgQCIRwePEM1tr6ZfR+Q+726CWVAXk8WQ== +"@blockshake/defly-connect@^1.1.5": + version "1.1.5" + resolved "https://registry.yarnpkg.com/@blockshake/defly-connect/-/defly-connect-1.1.5.tgz#a8821b2b4ccd4c9591261c98effdd3ab969e0c5e" + integrity sha512-IV9h1MHYnzxxsuH3dSG7hFhMhkCaZM1zJl2CDcbiiM8yZ3S5r8vclF6SnxiUgK710+pNk4ZERPpS9WaUuFr+oQ== dependencies: "@walletconnect/client" "^1.8.0" "@walletconnect/types" "^1.8.0" From 3cd272d217e73b8a153e6f290ba02716a6dbd50f Mon Sep 17 00:00:00 2001 From: Doug Richar Date: Sat, 24 Jun 2023 13:34:22 -0400 Subject: [PATCH 05/29] chore: bump version to v1.3.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 55fa4114..4dfd6400 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "url": "https://github.com/txnlab/use-wallet/issues" }, "homepage": "https://txnlab.github.io/use-wallet", - "version": "1.3.2", + "version": "1.3.3", "description": "React hooks for using Algorand compatible wallets in dApps.", "scripts": { "dev": "yarn storybook", From 7810ee2c987029000142981e2e181ce5ffe00201 Mon Sep 17 00:00:00 2001 From: Doug Richar Date: Mon, 26 Jun 2023 14:47:27 -0400 Subject: [PATCH 06/29] chore(renovate): update base branch to release-2.0.0 --- renovate.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/renovate.json b/renovate.json index 1569db38..4ba408c7 100644 --- a/renovate.json +++ b/renovate.json @@ -4,7 +4,7 @@ "ignoreDeps": [], "schedule": "before 3am on the first day of the month", "assignees": ["drichar"], - "baseBranches": ["main"], + "baseBranches": ["release-2.0.0"], "separateMajorMinor": true, "rebaseStalePrs": true, "lockFileMaintenance": { From 435a1d01968d95790a574eae93e5b9f1edf04c4d Mon Sep 17 00:00:00 2001 From: Doug Richar Date: Tue, 27 Jun 2023 18:21:33 -0400 Subject: [PATCH 07/29] v2.0.0 (#81) * TXN-1290: Fix failing Storybook build (#60) * TXN-1289: Add unit tests (#61) * TXN-1292: Update initializeProviders to be async (#64) * fix(utils): initializeProviders race condition The `initializeProviders` utility function needs to be async, so we can await each client's async `init` method. Otherwise a race condition occurs and if the app mounts before initialization completes, `reconnectProviders` is called too early. BREAKING CHANGE: The exported `initializeProviders` function is now async and must be awaited and called from inside a `useEffect` hook. * docs(readme): update the README with new initialization examples * TXN-1293: Update README (#65) * TXN-1294: Improved initializeProviders API (#66) * feat(utils): improved initializeProviders API Users will be able to set client options and pass static instances of SDKs/libs via initializeProviders. closes #30 * feat: improved initializeProviders API (pt 2) There's a lot happening here but it's all related so I can't break it up into bite-sized commits (I tried). The high-level: 1. The mapped clients passed to the `WalletProvider` context provider are no longer wrapped in a Promise. It made functions accessing those clients async for no reason. 2. Provider configurations are strictly enforced with types, so a user can't pass incompatible client options or SDK instances to the wrong provider. 3. Added tests for initializing providers with a mix of IDs and `ProviderConfig` objects. The mock implementation of `client.init` returns a mock client that lets us verify each client was configured correctly. The mock clients are also used in the `useWallet` tests now. 4. Cleaned up the clients and their types a little and made minor changes to the base client, making it possible to test specifics of provider initialization. * feat: useInitializeProviders hook This simplifies the `WalletProvider` setup by handling all the `useState` and `useEffect` complexity inside a new custom hook. * feat: update Daffi client to v2.0.0 API, closes TXN-1401 * chore: remove commented ProviderConfig type * fix: pass clientOptions to mock clients where missing * Updated README for v2.0.0 (#80) * TXN-1402: Fix infinite render loop in useInitializeProviders (#82) * build(rollup): fix unresolved dependencies error * TXN-1399: WalletConnect 2.0 support (#83) * chore: only import PROVIDER_ID from src/constants * feat: WalletConnect 2.0 provider client * fix(walletconnect2): catch "Modal closed" error in connect method Closing the Web3Modal was throwing an uncaught error instead of rejecting the promise returned by `connect`, leading to a runtime error. This refactors the connection logic using then-catch chaining (using async/await would lead to `connect` returning `Promise>`) so the error is caught and the promise is rejected. * feat(walletconnect2): nested transactions support This updates the WalletConnect (2.0) provider client's `signTransactions` method to accept an array of transaction groups, a change introduced in v1.3.1 * chore: remove WalletConnect v1 provider files and peer dependencies * docs: update README * chore: version bump to 2.0.0-alpha.0 * docs: next.js 13 "Module not found" error fix * TXN-1404: Fix "No matching export" error for WalletConnect peer dependencies (#85) To avoid requiring @walletconnect/utils and @walletconnect/types to be installed as peer dependencies to support WalletConnect 2.0, the modules that were being used are removed and replicated. The types package in particular wasn't a necessary dependency. * chore: version bump to 2.0.0-alpha.1 * TXN-1406: Fix default providers if none specified (#86) * fix: useInitializeProviders `providers` defaults to empty array * chore: version bump to 2.0.0-alpha.2 * TXN-1407: Replace @walletconnect/sign-client with @web3modal/sign-html (#87) * feat(walletconnect2): replace @walletconnect/sign-client with @web3modal/sign-html The @web3modal/sign-html client handles all of the @walletconnect/modal logic as well as lots of the complexity being handled manually in the previous WalletConnect provider client. And it's one peer dependency to install vs two. * fix(walletconnect2): make clientOptions type compatible with ProviderConfig * test(walletconnect2): update mocks * build(rollup): fix unresolved dependencies error * chore(usewallet): remove unused PROVIDER_ID export * docs: update WalletConnect instructions * fix: PROVIDER_ID imports * chore: version bump to 2.0.0-alpha.3 * TXN-1409: Debug mode (#89) * chore: version bump to 2.0.0-alpha.4 * TXN-1412: Static imports are required (#90) * feat(init): static imports are required The cleanest, simplest solution to the build/compile errors encountered if any peer dependencies are not installed is to require static imports. This is already a requirement for Remix apps, and it will ensure that only the necessary wallet client/SDK libraries need to be installed by consuming apps. This also removes the need for a "default configuration" and deciding which providers should or shouldn't belong in it. Every wallet provider will need to be passed to the `providers` property in useInitializeProviders, which is already how most apps do it. BREAKING CHANGE: The `useInitializeProviders` must receive an object with the `providers` property defined. Provider objects passed to `providers` must include a `clientStatic` property set to the provider's client/SDK library, if it exists. For example, you cannot simply pass `PROVIDER_ID.PERA`, it must be an object with both `id` and `clientStatic` set. * test(init): fix failing tests * fix(init): update NodeConfig type The `nodePort` property can be a string or a number, and the optional `nodeHeaders` property is added * docs: update README Updates code examples, provider configuration section, and migration guide to describe required static imports. Various other changes as well. * chore(example): update Storybook example to useInitializeProviders * test(example): fix failing tests * chore: rename ProvidersArray type * chore(init): remove initializeProviders main export The `useInitializeProviders` hook replaces `initializeProviders` as a helper function for constructing the providers map that is passed to the context provider in consuming apps. BREAKING CHANGE: Apps using `initializeProviders` will be forced to switch to `useInitializeProviders` * chore: version bump to 2.0.0-alpha.5 * docs: fix WCv2 modal themeMode setting in full example * chore(deps): update dependency gh-pages to v5 (#76) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * TXN-1413: Remove unused dependencies, fix warnings (#91) * chore(deps): update all non-major dependencies (#62) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * chore(deps): update dependency @testing-library/react to v14 (#74) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * chore(deps): update dependency babel-loader to v9 (#75) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * fix(daffi): set shouldShowSignTxnToast to default Version 2.x changed the default for this setting for Defly and Pera to match the libraries' default (true). Daffi Connect had not been updated, now it matches the other two. * chore(deps): upgrade zustand to ^4.3.8 This also updates how `create` is imported in the stores. The default import has been deprecated and shows a warning if used in local dev. * TXN-1414: Refactor InitParams type, tidy up provider class imports (#92) * chore(deps): upgrade zustand to ^4.3.8 This also updates how `create` is imported in the stores. The default import has been deprecated and shows a warning if used in local dev. * refactor(types): single InitParams type that uses ProviderConfigMapping This actually started as an attempt to resolve "circular dependencies" warnings that started appearing after upgrading Rollup. It resulted in a refactored `InitParams` which extends `CommonInitParams` with the `clientOptions` and `clientStatic` for each provider in `ProviderConfigMapping`. It also resolves all but two of the warnings (will need to revisit) and organizes the imports for each of the provider classes. * chore(types): update `NodeConfig['nodeToken']` type to match algosdk * TXN-1408: Switch to @walletconnect/modal-sign-html (#93) The deprecated @web3modal/sign-html peer dependency is replaced by @walletconnect/modal-sign-html. This is a like-for-like change, except for the renamed exports. The client's methods/API are the same. The @walletconnect/modal-sign-html package uses a more recent version of @walletconnect/core than the deprecated @web3modal/sign-html. BREAKING CHANGE: @web3modal/sign-html will no longer work * chore: version bump to 2.0.0-rc.1 * docs: update README * chore: add banner image * docs: add banner to README --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .eslintrc.js | 6 + .storybook/main.js | 11 + README.md | 745 ++- __mocks__/walletconnect-modal-sign-html.js | 1 + jest.config.ts | 12 +- jest.setup.ts | 5 +- package.json | 56 +- src/clients/algosigner/client.ts | 35 +- src/clients/algosigner/types.ts | 17 +- src/clients/base/base.ts | 19 +- src/clients/base/index.ts | 4 +- src/clients/daffi/client.ts | 55 +- src/clients/daffi/types.ts | 20 +- src/clients/defly/client.ts | 51 +- src/clients/defly/types.ts | 20 +- src/clients/exodus/client.ts | 34 +- src/clients/exodus/types.ts | 22 +- src/clients/index.ts | 2 +- src/clients/kmd/client.ts | 34 +- src/clients/kmd/types.ts | 22 +- src/clients/mnemonic/client.ts | 37 +- src/clients/mnemonic/types.ts | 18 +- src/clients/myalgo/client.ts | 47 +- src/clients/myalgo/types.ts | 30 +- src/clients/pera/client.ts | 55 +- src/clients/pera/types.ts | 20 +- src/clients/walletconnect/client.ts | 233 - src/clients/walletconnect/index.ts | 3 - src/clients/walletconnect/types.ts | 65 - src/clients/walletconnect2/client.ts | 280 + .../constants.ts | 8 +- src/clients/walletconnect2/index.ts | 3 + src/clients/walletconnect2/types.ts | 38 + src/clients/walletconnect2/utils.ts | 17 + src/components/Example/Example.test.tsx | 36 +- src/components/Example/Example.tsx | 20 +- src/constants/constants.ts | 2 +- src/hooks/index.ts | 1 + src/hooks/useInitializeProviders.test.tsx | 115 + src/hooks/useInitializeProviders.ts | 47 + src/hooks/useWallet.test.tsx | 155 + src/hooks/useWallet.ts | 31 +- src/index.ts | 4 +- src/store/index.ts | 1 + src/store/state/clientStore.ts | 7 +- src/store/state/debugStore.ts | 11 + src/store/state/walletStore.ts | 2 +- src/testUtils/createWrapper.tsx | 16 + src/testUtils/mockAccounts.ts | 16 + src/testUtils/mockClients.ts | 430 ++ src/types/index.ts | 1 + src/types/node.tsx | 4 +- src/types/providers.ts | 110 + src/types/utilities.ts | 1 + src/types/wallet.ts | 8 +- src/utils/clearAccounts.test.ts | 12 +- src/utils/clearAccounts.ts | 7 +- src/utils/debugLog.ts | 26 + src/utils/encodeNFDTransactionArray.spec.ts | 20 + src/utils/initializeProviders.test.ts | 187 + src/utils/initializeProviders.ts | 60 +- src/utils/providers.spec.ts | 41 + src/utils/providers.ts | 4 +- src/utils/reconnectProviders.test.ts | 80 + src/utils/reconnectProviders.ts | 9 +- src/utils/types.ts | 5 + use-wallet-banner.png | Bin 0 -> 188704 bytes yarn.lock | 4801 ++++++++++------- 68 files changed, 5513 insertions(+), 2782 deletions(-) create mode 100644 __mocks__/walletconnect-modal-sign-html.js delete mode 100644 src/clients/walletconnect/client.ts delete mode 100644 src/clients/walletconnect/index.ts delete mode 100644 src/clients/walletconnect/types.ts create mode 100644 src/clients/walletconnect2/client.ts rename src/clients/{walletconnect => walletconnect2}/constants.ts (82%) create mode 100644 src/clients/walletconnect2/index.ts create mode 100644 src/clients/walletconnect2/types.ts create mode 100644 src/clients/walletconnect2/utils.ts create mode 100644 src/hooks/useInitializeProviders.test.tsx create mode 100644 src/hooks/useInitializeProviders.ts create mode 100644 src/hooks/useWallet.test.tsx create mode 100644 src/store/state/debugStore.ts create mode 100644 src/testUtils/createWrapper.tsx create mode 100644 src/testUtils/mockAccounts.ts create mode 100644 src/testUtils/mockClients.ts create mode 100644 src/types/providers.ts create mode 100644 src/types/utilities.ts create mode 100644 src/utils/debugLog.ts create mode 100644 src/utils/encodeNFDTransactionArray.spec.ts create mode 100644 src/utils/initializeProviders.test.ts create mode 100644 src/utils/providers.spec.ts create mode 100644 src/utils/reconnectProviders.test.ts create mode 100644 src/utils/types.ts create mode 100644 use-wallet-banner.png diff --git a/.eslintrc.js b/.eslintrc.js index 236c1708..a43f6950 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -52,6 +52,12 @@ module.exports = { env: { node: true } + }, + { + files: ['*.js'], + env: { + jest: true + } } ], settings: { diff --git a/.storybook/main.js b/.storybook/main.js index 3581c45b..6c15543b 100644 --- a/.storybook/main.js +++ b/.storybook/main.js @@ -1,7 +1,18 @@ +const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin') + module.exports = { stories: ['../src/**/*.stories.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'], addons: ['@storybook/addon-links', '@storybook/addon-essentials', '@storybook/preset-scss'], core: { builder: 'webpack5' + }, + webpackFinal: async (config) => { + config.resolve.plugins = [ + ...(config.resolve.plugins || []), + new TsconfigPathsPlugin({ + extensions: config.resolve.extensions + }) + ] + return config } } diff --git a/README.md b/README.md index c0e884cf..71c2e022 100644 --- a/README.md +++ b/README.md @@ -1,115 +1,225 @@ -# @TxnLab/use-wallet +![](use-wallet-banner.png) -React hooks for using Algorand compatible wallets with web applications. Flexible and extensible, `use-wallet` supports a variety of wallets and connection protocols. It also provides a simple interface for connecting, disconnecting, switching between accounts and signing transactions. +# UseWallet v2 -## Supported Providers +`@txnlab/use-wallet` is a React library that provides a simplified, consistent interface for integrating multiple Algorand wallets into your decentralized applications (dApps). -- [Pera](https://perawallet.app/) -- [MyAlgo](https://wallet.myalgo.com/home) -- [Defly](https://defly.app) -- [AlgoSigner](https://www.purestake.com/technology/algosigner) -- [Exodus](https://www.exodus.com) -- [WalletConnect](https://walletconnect.com) -- [Daffi](https://www.daffi.me/) -- [KMD](https://developer.algorand.org/docs/rest-apis/kmd) +[![npm version](https://badge.fury.io/js/%40txnlab%2Fuse-wallet.svg)](https://badge.fury.io/js/%40txnlab%2Fuse-wallet) +![License](https://img.shields.io/github/license/TxnLab/use-wallet) -## Demo +## Overview -Preview a basic implementation in [Storybook](https://txnlab.github.io/use-wallet) or check out [this example](https://github.com/gabrielkuettel/use-wallet-example). +With UseWallet's hooks and utility functions, you can: -## Quick Start +- Easily add or remove wallet support with a few lines of code +- Configure each wallet provider as needed for your application +- Allow users to easily switch between active accounts and wallet providers +- Sign and send transactions +- Restore sessions for returning users -### Yarn +It provides an abstraction layer that unifies the initialization, connection, and transaction signing logic, eliminating the need to interact with each wallet's individual API. -```bash -yarn add @txnlab/use-wallet -``` +UseWallet supports most Algorand wallet providers, including Defly, Pera, Daffi, and Exodus (see [Supported Wallet Providers](#supported-wallet-providers) for the full list). + +Version 2.x introduces [WalletConnect 2.0 support](#walletconnect-20-support). + +## Table of Contents + +- [Live Examples](#live-examples) +- [Requirements](#requirements) +- [Installation](#installation) +- [Initializing Providers](#initializing-providers) +- [The `useWallet` Hook](#the-usewallet-hook) +- [Type Definitions](#type-definitions) +- [Connect Menu](#connect-menu) +- [Displaying Account Details](#displaying-account-details) +- [Signing and Sending Transactions](#signing-and-sending-transactions) +- [Checking Connection Status](#checking-connection-status) +- [Supported Wallet Providers](#supported-wallet-providers) +- [Legacy Wallet Support](#legacy-wallet-support) +- [Provider Configuration](#provider-configuration) + - [Provider Definitions](#provider-definitions) + - [Node Configuration](#node-configuration) + - [Algosdk Static Import](#algosdk-static-import) + - [Full Configuration Example](#full-configuration-example) +- [WalletConnect 2.0 Support](#walletconnect-20-support) +- [Migration Guide](#migration-guide) +- [Local Development](#local-development) +- [Used By](#used-by) +- [License](#license) + +## Live Examples + +**Storybook demo** - https://txnlab.github.io/use-wallet + +**Next.js example** + +- Demo - https://next-use-wallet.vercel.app/ +- Code - https://github.com/TxnLab/next-use-wallet + +**NFDomains** - https://app.nf.domains/ + +## Requirements + +Since this library uses [React Hooks](https://react.dev/reference/react), your app will need to be using React 16.8 or higher. + +## Installation -Install peer dependencies (if needed) +Commands shown below use `npm install` but you can use `yarn add` or `pnpm add` instead. + +First, install the library ```bash -yarn add algosdk @blockshake/defly-connect @perawallet/connect @randlabs/myalgo-connect @walletconnect/client algorand-walletconnect-qrcode-modal @json-rpc-tools/utils @daffiwallet/connect +npm install @txnlab/use-wallet ``` -### NPM +If you haven't already, install the Algorand JS SDK ```bash -npm install @txnlab/use-wallet +npm install algosdk ``` -Install peer dependencies (if needed) +Finally, install any peer dependencies for the wallets you wish to support. For example, to support Defly, Pera, and Daffi wallets: ```bash -npm install algosdk @blockshake/defly-connect @perawallet/connect @randlabs/myalgo-connect @walletconnect/client algorand-walletconnect-qrcode-modal @json-rpc-tools/utils @daffiwallet/connect +npm install @blockshake/defly-connect @perawallet/connect @daffiwallet/connect ``` -### Set up the Wallet Provider +## Initializing Providers + +In the root of your app, initialize the `WalletProvider` with the `useInitializeProviders` hook. -In `app.js`, initialize the Wallet Provider so that the `useWallet` hook can be used in the child components, and use the `reconnectProviders` function to restore sessions for users returning to the app. +This example initializes Defly, Pera, Daffi and Exodus wallet providers. The default node configuration (mainnet via [AlgoNode](https://algonode.io/api/)) is used. See [Provider Configuration](#provider-configuration) for more options. ```jsx import React from 'react' -import { reconnectProviders, initializeProviders, WalletProvider } from '@txnlab/use-wallet' - -const walletProviders = initializeProviders() +import { WalletProvider, useInitializeProviders, PROVIDER_ID } from '@txnlab/use-wallet' +import { DeflyWalletConnect } from '@blockshake/defly-connect' +import { PeraWalletConnect } from '@perawallet/connect' +import { DaffiWalletConnect } from '@daffiwallet/connect' export default function App() { - // Reconnect the session when the user returns to the dApp - React.useEffect(() => { - reconnectProviders(walletProviders) - }, []) + const providers = useInitializeProviders([ + { + providers: [ + { id: PROVIDER_ID.DEFLY, clientStatic: DeflyWalletConnect }, + { id: PROVIDER_ID.PERA, clientStatic: PeraWalletConnect }, + { id: PROVIDER_ID.DAFFI, clientStatic: DaffiWalletConnect } + ] + } + ]) - return ... + return ( + +
{/* ... */}
+
+ ) } ``` -The `reconnectProviders` function is used to restore session states of wallets that rely on the `WalletConnect` protocol. +## The `useWallet` Hook + +The `useWallet` hook is used to access wallet provider and account state, send unsigned transactions to be signed, and send signed transactions to the node from anywhere in your app. It returns an object with the following properties: + +- `providers` - Array of wallet providers that have been initialized (see `Provider` in [Type Definitions](#type-definitions)) +- `activeAccount` - The currently active account in the active provider (see `Account` in [Type Definitions](#type-definitions)) +- `connectedAccounts` - Array of accounts from all connected wallet providers +- `connectedActiveAccounts` - Array of accounts from the active wallet provider +- `activeAddress` - The address of `activeAccount` +- `status`, `isReady`, `isActive` - The current connection status, see [Check connection status](#check-connection-status) +- `signTransactions` - Function that sends unsigned transactions to active wallet provider for signature +- `sendTransactions` - Function that sends signed transactions to the node +- `groupTransactionsBySender` - Utility function that groups transactions by sender address +- `getAddress` - Utility function that returns the address of the `activeAccount` +- `getAccountInfo` - Utility function that fetches `activeAccount` account information from the node +- `getAssets` - Utility function that fetches `activeAccount` asset info/balances from the node +- `signer` - Function used by the [KMD](#kmd-algorand-key-management-daemon) provider to sign transactions + +## Type Definitions + +### `Provider` + +```ts +type Provider = { + accounts: Account[] + isActive: boolean + isConnected: boolean + connect: () => Promise + disconnect: () => Promise + reconnect: () => Promise + setActiveProvider: () => void + setActiveAccount: (account: string) => void + metadata: Metadata +} +``` -By default, all of the supported providers except for `KMD` are returned by `useConnectWallet`. An array can be passed to `initializeProviders` to determine which providers your dApp supports, as shown below. +Each provider has two connection states: `isConnected` and `isActive`. -```jsx -import { initializeProviders, PROVIDER_ID } from '@txnlab/use-wallet' +`isConnected` means that the user has authorized the provider in the app. Multiple providers can be connected at the same time. -const walletProviders = initializeProviders([PROVIDER_ID.KMD_WALLET, PROVIDER_ID.WALLET_CONNECT]) +`isActive` means that the provider is currently active and will be used to sign and send transactions. + +### `Account` + +```ts +interface Account { + providerId: PROVIDER_ID + name: string + address: string + authAddr?: string +} ``` -For more configuration options, see [Provider Configuration](#provider-configuration). +The `activeAccount` is the account that will be used to sign and send transactions. + +To get the currently active wallet provider, read the `providerId` property of `activeAccount`. -### Connect +## Connect Menu -Map through the `providers` object to list the providers and enable users to connect. +In your app's UI you will need a menu for the user to `connect` or `disconnect` wallet providers, `setActiveProvider`, and `setActiveAccount`. + +This is a bare-bones example for demonstration purposes. For a styled example, see https://app.nf.domains/ ```jsx import React from 'react' import { useWallet } from '@txnlab/use-wallet' -export default function Connect() { +export default function ConnectMenu() { const { providers, activeAccount } = useWallet() - // Map through the providers. - // Render account information and "connect", "set active", and "disconnect" buttons. - // Finally, map through the `accounts` property to render a dropdown for each connected account. + // 1. Map over `providers` array + // 2. Show the provider name/icon and "Connect", "Set Active", and "Disconnect" buttons + // 3. If active, map `provider.accounts` to render a select menu of connected accounts + return (
{providers?.map((provider) => ( -
+

- + {`${provider.metadata.name} {provider.metadata.name} {provider.isActive && '[active]'}

+
- - +
{provider.isActive && provider.accounts.length && (