Skip to content

Commit 4a6fcac

Browse files
authored
feat: use-wallet v2 integration, expanded guidelines, improvements in kmd support (#13)
* refactor: changing name for kmd
1 parent ceafc07 commit 4a6fcac

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

66 files changed

+753
-729
lines changed

.gitattributes

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
* text=lf
1+
* text=auto eol=lf

.github/pull_request_template.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
## Proposed Changes
2+
3+
-
4+
-
5+
-

template_content/.algokit.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
[algokit]
2-
min_version = "v1.1.0"
2+
min_version = "v1.3.0b1"

template_content/.env.template.jinja

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,17 @@ VITE_INDEXER_TOKEN={{ indexer_token }}
1616
VITE_INDEXER_SERVER={{ indexer_server }}
1717
VITE_INDEXER_PORT={{ indexer_port }}
1818

19+
# KMD
20+
# Please note:
21+
# 1. This is only needed for LocalNet since
22+
# by default KMD provider is ignored on other networks.
23+
# 2. AlgoKit LocalNet starts with a single wallet called 'unencrypted-default-wallet',
24+
# with heaps of tokens available for testing.
25+
VITE_KMD_TOKEN={{ algod_token }}
26+
VITE_KMD_SERVER={{ algod_server }}
27+
VITE_KMD_PORT=4002
28+
VITE_KMD_WALLET="unencrypted-default-wallet"
29+
VITE_KMD_PASSWORD=""
1930

2031
# # ======================
2132
# # TestNet configuration:

template_content/.gitattributes

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
* text=lf
1+
* text=auto eol=lf

template_content/README.md.jinja

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@ This starter React project has been generated using AlgoKit. See below for defau
88

99
1. Clone this repository locally
1010
2. Install pre-requisites:
11+
- Make sure to have [Docker](https://www.docker.com/) installed and running on your machine.
1112
- Install `AlgoKit` - [Link](https://github.com/algorandfoundation/algokit-cli#install): The minimum required version is `1.1`. Ensure you can execute `algokit --version` and get `1.1` or later.
1213
- Bootstrap your local environment; run `algokit bootstrap all` within this folder, which will run `npm install` to install NPM packages and dependencies for your frontend component/webapp.
14+
- Run `algokit localnet start` to start a local Algorand network in Docker. If you are using VS Code launch configurations provided by the template, this will be done automatically for you.
1315
3. Open the project and start debugging / developing via:
1416
- VS Code
1517
1. Open the repository root in VS Code
@@ -24,6 +26,8 @@ This starter React project has been generated using AlgoKit. See below for defau
2426
1. If you update to the latest source code and there are new dependencies you will need to run `algokit bootstrap all` again
2527
2. Follow step 3 above
2628

29+
> Please note, by default frontend is pre configured to run against Algorand LocalNet. If you want to run against TestNet or MainNet, comment out the current environment variable and uncomment the relevant one in [`.env`](.env) file that is created after running bootstrap command and based on [`.env.template`](.env.template).
30+
2731
{% if use_github_actions -%}
2832

2933
### Continuous Integration
@@ -44,6 +48,19 @@ The project template provides base Github Actions workflows for continuous deplo
4448

4549
{% endif -%}
4650

51+
# Algorand Wallet integrations
52+
53+
The template comes with [`use-wallet`](https://github.com/txnlab/use-wallet) integration, which provides a React hook for connecting to an Algorand wallet providers. The following wallet providers are included by default:
54+
- LocalNet:
55+
- - [KMD/Local Wallet](https://github.com/TxnLab/use-wallet#kmd-algorand-key-management-daemon) - Algorand's Key Management Daemon (KMD) is a service that manages Algorand private keys and signs transactions. Works best with AlgoKit LocalNet and allows you to easily test and interact with your dApps locally.
56+
- TestNet and others:
57+
- - [Pera Wallet](https://perawallet.app).
58+
- - [Defly Wallet](https://defly.app).
59+
- - [Exodus Wallet](https://www.exodus.com).
60+
- - [Daffi Wallet](https://www.daffi.me).
61+
62+
Refer to official [`use-wallet`](https://github.com/txnlab/use-wallet) documentation for detailed guidelines on how to integrate with other wallet providers (such as WalletConnect v2). Too see implementation details on the use wallet hook and initialization of extra wallet providers refer to [`App.tsx`](./src/App.tsx).
63+
4764
# Tools
4865

4966
This project makes use of React and Tailwind to provider a base project configuration to develop frontends for your Algorand dApps and interactions with smart contracts. The following tools are in use:
@@ -54,9 +71,9 @@ This project makes use of React and Tailwind to provider a base project configur
5471
- [Tailwind CSS](https://tailwindcss.com/) - A utility-first CSS framework for rapidly building custom designs.
5572
{% endif -%}{% if use_daisy_ui -%}
5673
- [daisyUI](https://daisyui.com/) - A component library for Tailwind CSS.
57-
{% endif -%}{% if use_wallet -%}
58-
- [use-wallet](https://github.com/txnlab/use-wallet) - A React hook for connecting to an Algorand wallet providers. Includes integrations with [Exodus](https://www.exodus.com/), [Pera](https://perawallet.app/), and [Defly](https://defly.app/).
59-
{% endif -%}- [npm](https://www.npmjs.com/): Node.js package manager
74+
{% endif -%}
75+
- [use-wallet](https://github.com/txnlab/use-wallet) - A React hook for connecting to an Algorand wallet providers.
76+
- [npm](https://www.npmjs.com/): Node.js package manager
6077
{% if use_jest -%}
6178
- [jest](https://jestjs.io/): JavaScript testing framework
6279
{% endif -%}{% if use_playwright -%}
@@ -75,4 +92,27 @@ It has also been configured to have a productive dev experience out of the box i
7592

7693
# Integrating with smart contracts and application clients
7794

78-
Refer to the detailed guidance on [integrating with smart contracts and application clients](./src/contracts/README.md). In essence, for any smart contract codebase generated with AlgoKit or ther tools that produce compile contracts into ARC34 compliant app specifications, you can use the `algokit generate` command to generate TypeScript or Python typed client. Once generated simply drag and drop the generated client into `./src/contracts` and import it into your React components as you see fit.
95+
Refer to the detailed guidance on [integrating with smart contracts and application clients](./src/contracts/README.md). In essence, for any smart contract codebase generated with AlgoKit or other tools that produce compile contracts into ARC34 compliant app specifications, you can use the `algokit generate` command to generate TypeScript or Python typed client. Once generated simply drag and drop the generated client into `./src/contracts` and import it into your React components as you see fit.
96+
97+
# Vite compatibility with `js-algorand-sdk` and `algokit clients`
98+
99+
If you are receiving `address malformed` errors when sending direct calls to abi methods on AlgoKit typed clients where ABI parameters expect ABI transactions to be passed, this is a known edge case and a bug that occurs on [Vite in dev mode](https://github.com/vitejs/vite/issues/9528). A fix is in the works on [js-algorand-sdk](https://github.com/algorand/js-algorand-sdk/issues/740) side (which is used as a main low-level dependency by AlgoKit to generate typed clients), but in the meantime you can use a simple workaround described below:
100+
101+
```ts
102+
Instead of:
103+
104+
const response = await myAlgoKitTypedClient
105+
.someExternalMethod({
106+
some_txn_param: someTxnObject,
107+
})
108+
109+
use:
110+
111+
const response = await appClient
112+
.compose()
113+
.someExternalMethod({
114+
some_txn_param: someTxnObject,
115+
})
116+
.execute()
117+
```
118+

template_content/package.json.jinja

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
{% if use_eslint_prettier -%}
1616
"eslint": "8.42.0",
1717
"eslint-config-prettier": "8.8.0",
18-
"eslint-plugin-prettier": "4.2.1",
18+
"eslint-plugin-prettier": "5.0.0",
1919
"@typescript-eslint/eslint-plugin": "5.59.9",
2020
"@typescript-eslint/parser": "5.59.9",
2121
{% endif -%}
@@ -36,12 +36,12 @@
3636
"vite": "4.3.9"
3737
},
3838
"dependencies": {
39-
"@walletconnect/modal-sign-html": "^2.5.5",
40-
"@algorandfoundation/algokit-utils": "^2.2.0",
41-
"@blockshake/defly-connect": "1.1.5",
42-
"@daffiwallet/connect": "1.0.3",
43-
"@perawallet/connect": "1.2.3",
44-
"@txnlab/use-wallet": "2.0.0",
39+
"@walletconnect/modal-sign-html": "^2.6.0",
40+
"@algorandfoundation/algokit-utils": "^2.3.1",
41+
"@blockshake/defly-connect": "^1.1.5",
42+
"@daffiwallet/connect": "^1.0.3",
43+
"@perawallet/connect": "^1.3.1",
44+
"@txnlab/use-wallet": "^2.0.0",
4545
"algosdk": "^2.3.0",
4646
{% if use_daisy_ui -%}
4747
"daisyui": "3.1.0",

template_content/src/App.tsx

Lines changed: 42 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,39 @@
1-
import { WalletProvider, useWallet } from '@txnlab/use-wallet'
1+
import { DeflyWalletConnect } from '@blockshake/defly-connect'
2+
import { DaffiWalletConnect } from '@daffiwallet/connect'
3+
import { PeraWalletConnect } from '@perawallet/connect'
4+
import { PROVIDER_ID, ProvidersArray, WalletProvider, useInitializeProviders, useWallet } from '@txnlab/use-wallet'
5+
import algosdk from 'algosdk'
26
import { SnackbarProvider } from 'notistack'
37
import { useState } from 'react'
48
import ConnectWallet from './components/ConnectWallet'
59
import Transact from './components/Transact'
6-
import { useAlgoWallet } from './hooks/useAlgoWalletProvider'
7-
import { getAlgodConfigFromViteEnvironment } from './utils/network/getAlgoClientConfigs'
10+
import { getAlgodConfigFromViteEnvironment, getKmdConfigFromViteEnvironment } from './utils/network/getAlgoClientConfigs'
11+
12+
let providersArray: ProvidersArray
13+
if (import.meta.env.VITE_ALGOD_NETWORK === '') {
14+
const kmdConfig = getKmdConfigFromViteEnvironment()
15+
providersArray = [
16+
{
17+
id: PROVIDER_ID.KMD,
18+
clientOptions: {
19+
wallet: kmdConfig.wallet,
20+
password: kmdConfig.password,
21+
host: kmdConfig.server,
22+
token: String(kmdConfig.token),
23+
port: String(kmdConfig.port),
24+
},
25+
},
26+
]
27+
} else {
28+
providersArray = [
29+
{ id: PROVIDER_ID.DEFLY, clientStatic: DeflyWalletConnect },
30+
{ id: PROVIDER_ID.PERA, clientStatic: PeraWalletConnect },
31+
{ id: PROVIDER_ID.DAFFI, clientStatic: DaffiWalletConnect },
32+
{ id: PROVIDER_ID.EXODUS },
33+
// If you are interested in WalletConnect v2 provider
34+
// refer to https://github.com/TxnLab/use-wallet for detailed integration instructions
35+
]
36+
}
837

938
export default function App() {
1039
const [openWalletModal, setOpenWalletModal] = useState<boolean>(false)
@@ -21,17 +50,20 @@ export default function App() {
2150

2251
const algodConfig = getAlgodConfigFromViteEnvironment()
2352

24-
const walletProviders = useAlgoWallet({
25-
nodeToken: String(algodConfig.token),
26-
nodeServer: algodConfig.server,
27-
nodePort: String(algodConfig.port),
28-
network: algodConfig.network,
29-
autoConnect: true,
53+
const walletProviders = useInitializeProviders({
54+
providers: providersArray,
55+
nodeConfig: {
56+
network: algodConfig.network,
57+
nodeServer: algodConfig.server,
58+
nodePort: String(algodConfig.port),
59+
nodeToken: String(algodConfig.token),
60+
},
61+
algosdkStatic: algosdk,
3062
})
3163

3264
return (
3365
<SnackbarProvider maxSnack={3}>
34-
<WalletProvider value={walletProviders.walletProviders}>
66+
<WalletProvider value={walletProviders}>
3567
<div className="hero min-h-screen bg-teal-400">
3668
<div className="hero-content text-center rounded-lg p-6 max-w-md bg-white mx-auto">
3769
<div className="max-w-md">

template_content/src/components/ConnectWallet.tsx.jinja

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useWallet } from '@txnlab/use-wallet'
1+
import { Provider, useWallet } from '@txnlab/use-wallet'
22
import Account from './Account'
33

44
interface ConnectWalletInterface {
@@ -9,6 +9,8 @@ interface ConnectWalletInterface {
99
const ConnectWallet = ({ openModal, closeModal }: ConnectWalletInterface) => {
1010
const { providers, activeAddress } = useWallet()
1111

12+
const isKmd = (provider: Provider) => provider.metadata.name.toLowerCase() === 'kmd'
13+
1214
return (
1315
<dialog id="connect_wallet_modal" className={`modal ${openModal ? 'modal-open' : ''}`}{% if use_daisy_ui == false -%} style={{ '{{' }} display: openModal ? 'block' : 'none' {{ '}}' }}{% endif -%}>
1416
<form method="dialog" className="modal-box">
@@ -32,12 +34,14 @@ const ConnectWallet = ({ openModal, closeModal }: ConnectWalletInterface) => {
3234
return provider.connect()
3335
}}
3436
>
35-
<img
36-
alt={`wallet_icon_${provider.metadata.id}`}
37-
src={provider.metadata.icon}
38-
style={{ '{{' }} objectFit: 'contain', width: '30px', height: 'auto' {{ '}}' }}
39-
/>
40-
<span>{provider.metadata.name}</span>
37+
{!isKmd(provider) && (
38+
<img
39+
alt={`wallet_icon_${provider.metadata.id}`}
40+
src={provider.metadata.icon}
41+
style={{ '{{' }} objectFit: 'contain', width: '30px', height: 'auto' {{ '}}' }}
42+
/>
43+
)}
44+
<span>{isKmd(provider) ? 'LocalNet Wallet' : provider.metadata.name}</span>
4145
</button>
4246
))}
4347
</div>
@@ -57,7 +61,18 @@ const ConnectWallet = ({ openModal, closeModal }: ConnectWalletInterface) => {
5761
className="btn btn-warning"
5862
data-test-id="logout"
5963
onClick={() => {
60-
providers?.find((p) => p.isActive)?.disconnect()
64+
if (providers) {
65+
const activeProvider = providers.find((p) => p.isActive)
66+
if (activeProvider) {
67+
activeProvider.disconnect()
68+
} else {
69+
// Required for logout/cleanup of inactive providers
70+
// For instance, when you login to localnet wallet and switch network
71+
// to testnet/mainnet or vice verse.
72+
localStorage.removeItem('txnlab-use-wallet')
73+
window.location.reload()
74+
}
75+
}
6176
}}
6277
>
6378
Logout

template_content/src/components/ErrorBoundary.tsx

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,42 @@
1-
import React, { ErrorInfo, ReactNode } from 'react'
1+
import React, { ReactNode } from 'react'
22

33
interface ErrorBoundaryProps {
44
children: ReactNode
5-
fallback: ReactNode
65
}
76

87
interface ErrorBoundaryState {
98
hasError: boolean
9+
error: Error | null
1010
}
1111

1212
class ErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBoundaryState> {
1313
constructor(props: ErrorBoundaryProps) {
1414
super(props)
15-
this.state = { hasError: false }
15+
this.state = { hasError: false, error: null }
1616
}
1717

18-
static getDerivedStateFromError(_: Error): ErrorBoundaryState {
18+
static getDerivedStateFromError(error: Error): ErrorBoundaryState {
1919
// Update state so the next render will show the fallback UI.
20-
return { hasError: true }
21-
}
22-
23-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
24-
componentDidCatch(error: Error, info: ErrorInfo): void {
25-
// Example "componentStack":
26-
// in ComponentThatThrows (created by App)
27-
// in ErrorBoundary (created by App)
28-
// in div (created by App)
29-
// in App
30-
// logErrorToMyService(error, info.componentStack)
20+
return { hasError: true, error: error }
3121
}
3222

3323
render(): ReactNode {
3424
if (this.state.hasError) {
3525
// You can render any custom fallback UI
36-
return this.props.fallback
26+
return (
27+
<div className="hero min-h-screen bg-teal-400">
28+
<div className="hero-content text-center rounded-lg p-6 max-w-md bg-white mx-auto">
29+
<div className="max-w-md">
30+
<h1 className="text-4xl">Error occured</h1>
31+
<p className="py-6">
32+
{this.state.error?.message.includes('Attempt to get default algod configuration')
33+
? 'Please make sure to set up your environment variables correctly. Create a .env file based on .env.template and fill in the required values. This controls the network and credentials for connections with Algod and Indexer.'
34+
: this.state.error?.message}
35+
</p>
36+
</div>
37+
</div>
38+
</div>
39+
)
3740
}
3841

3942
return this.props.children

0 commit comments

Comments
 (0)