Skip to content

Commit

Permalink
feat(network): enable runtime connection to custom Algorand nodes (#339)
Browse files Browse the repository at this point in the history
* feat(network): add ability to update network algod config at runtime

Add `updateNetworkAlgod` method to `WalletManager` to allow updating algod
configuration for any network at runtime. This enables users to connect to
their own Algorand nodes by modifying the network configuration after
initialization.

- Add `updateNetworkAlgod` method to `WalletManager` class
- Add comprehensive test suite for the new method
- Handle validation of updated configurations
- Automatically update active algod client when modifying active network
- Preserve existing configuration when partially updating

* refactor(react): separate network functionality into useNetwork hook

- Move network functionality (`activeNetwork`, `algodClient`, `setActiveNetwork`, etc.)
  to `useNetwork`
- Reorganize exports to match logical grouping (Provider, Network, Wallet)
- Update tests to reflect new hook separation
- Add comprehensive test coverage for `useNetwork` hook
- Remove network-related properties from `useWallet` tests
- Export additional network types from `use-wallet` package

BREAKING CHANGE: Network-related properties and methods have been moved from
`useWallet` to the new `useNetwork` hook.

* feat(solid): separate network functionality into useNetwork function

- Move network functionality (`activeNetwork`, `algodClient`, `setActiveNetwork`) to
  `useNetwork`
- Reorganize exports to match logical grouping (Provider, Network, Wallet)
- Update tests to reflect new function separation
- Add comprehensive test coverage for `useNetwork`
- Remove network-related properties from `useWallet` tests
- Export additional network types from `use-wallet` package
- Improve test organization and setup

BREAKING CHANGE: Network-related properties and methods have been moved from
`useWallet` to the new `useNetwork` function.

* feat(vue): split network functionality into useNetwork composable

Move network-related functionality from `useWallet` to `useNetwork` composable to
improve code organization and separation of concerns. Update tests to reflect
the new structure.

- Move `algodClient` and `setAlgodClient` to `useNetwork`
- Move `setActiveNetwork` and `activeNetwork` to `useNetwork`
- Split network-related tests into `useNetwork.test.ts`
- Update `useWallet` tests to remove network assertions
- Improve test setup with better mock organization

BREAKING CHANGE: Network-related properties (`algodClient`, `setAlgodClient`,
`activeNetwork`, `setActiveNetwork`) have been moved from `useWallet` to a new
`useNetwork` composable.

* fix(tests): remove name property from NetworkConfig in tests

Remove `name` property from `NetworkConfig` in tests to match updated type
definition. This aligns test fixtures with the core `NetworkConfig` type that
no longer includes the optional name field.

* fix(adapters): add updateNetworkAlgod to Vue and Solid adapters

Add `updateNetworkAlgod` function to Vue adapter and test coverage for both Vue
and Solid adapters. This completes the network configuration functionality
across all framework adapters, ensuring consistent behavior for runtime algod
config updates.

* fix(vue): export useNetwork from src/index.ts

* refactor(examples): split network functionality into useNetwork

Split network-related functionality from `useWallet` into separate `useNetwork` hook
across all framework examples.

- Move `algodClient`, `activeNetwork`, and `setActiveNetwork` to `useNetwork`
- Update Next.js example to use split hooks
- Update Nuxt example to use split hooks
- Update React example to use split hooks
- Update Solid example to use split hooks
- Update Vue example to use split hooks

* fix(solid): update reactive algodClient when network config changes

When updating network configuration through `updateNetworkAlgod`, ensure the
`algodClient` is also updated in the store if the modified network is currently
active.

* feat(*): add activeNetworkConfig to WalletManager and useNetwork

Add `activeNetworkConfig` to `useNetwork` across React, Vue, and Solid implementations:
- Add `activeNetworkConfig` property to `WalletManager` in core library
- Expose current network configuration through `useNetwork` hook
- Add tests to verify network config updates correctly
- Ensure reactivity when switching networks or updating config at runtime

* feat(network): persist user network customizations

Add support for persisting user-modified network configurations while preserving
developer-provided defaults. This enables users to customize network settings
that persist between sessions without affecting the base configuration.

- Add `customNetworkConfigs` to persisted state
- Store base network config separately from runtime config
- Compare against base config to identify user customizations
- Update tests to verify persistence behavior

* feat(network): add resetNetworkConfig functionality

Add `resetNetworkConfig` method to `WalletManager` and expose it through framework adapters. This allows resetting a network's configuration to its default state, removing any customizations.

- Add `resetNetworkConfig` method to `WalletManager` class
- Add tests for `WalletManager` `resetNetworkConfig` functionality
- Expose `resetNetworkConfig` through React, Vue, and Solid adapters
- Add comprehensive tests for each adapter implementation

* fix(react): update algodClient when modifying network config

Update the `algodClient` state when updating network configuration to ensure UI
reflects the current network state. This fixes an issue where the UI would not
update after resetting network configuration.

* refactor(react): move algodClient back to useWallet hook

Move `algodClient` and `setAlgodClient` from `useNetwork` to `useWallet` to
better align with their usage patterns. Update tests to handle shared context
between hooks and fix assertions around loading states.

- Remove `algodClient` and `setAlgodClient` from `useNetwork` return value
- Add `algodClient` and `setAlgodClient` to `useWallet` return value
- Update tests to use combined hooks when testing cross-hook interactions
- Fix integration test to properly handle loading states

* refactor(solid): move algodClient back to useWallet hook

Move `algodClient` from `useNetwork` to `useWallet` to maintain consistency with
React adapter changes. Update tests to reflect new hook structure.

- Remove `algodClient` from `useNetwork` return value
- Add `algodClient` to `useWallet` return value
- Update test components to access `algodClient` through `useWallet`

* refactor(vue): move algodClient back to useWallet

Move `algodClient` from `useNetwork` to `useWallet` composable to better align with
component responsibilities. Update tests to reflect new dependency structure and
improve injection mocking setup.

- Move `algodClient` computed property from `useNetwork` to `useWallet`
- Update `useNetwork` tests to get `algodClient` from `useWallet`
- Add `algodClient` injection handling in `useWallet` tests
- Move inject mock setup into `setupMocks` for consistent dependency initialization

* feat(examples): add network config UI to React and Vue examples

Add network configuration forms to React and Vue examples with styling:
- Add config form component with server/port/token inputs
- Add form styling for light/dark modes
- Move network controls to separate component
- Update imports and component structure

The network configuration UI allows users to view and modify the
algod node configuration for each network.

* fix(examples): update algodClient import location in example apps

Update Next, Nuxt, and Solid example apps to reflect the movement of `algodClient`
from `useNetwork` back to `useWallet`.

* chore(examples): fix Prettier issues

* refactor(core): move network config to reactive store state

Move network configuration from `WalletManager` instance to store state to support
runtime network configuration updates and ensure reactivity across framework
adapters. This change enables the new feature that lets users modify network
settings at runtime while ensuring those updates are properly tracked in the
reactive store.

- Adds `networkConfig` to store state
- Updates all references to use `store.state.networkConfig`
- Removes `networks` prop from wallet constructors
- Simplifies `algodClient` creation by passing config directly

* refactor(adapters): update network config references

Update React and Vue adapters and examples to use `networkConfig` from store state
instead of the removed `networks` property. This change follows up on moving network
configuration to the reactive store.

- Update `useNetwork` hook/composable to return `networkConfig` instead of `networks`
- Update `NetworkControls` components in React/Vue examples
- Fix affected tests in both adapter packages

* refactor(solid): update network config handling and add example UI

Update Solid adapter to use reactive store for network configuration and add
`NetworkControls` component to demonstrate runtime network configuration. This
change improves reactivity in the Solid example project to match React and Vue
implementations.

- Add `NetworkControls` component with config form UI
- Update `useNetwork` function to use store state for better reactivity
- Remove network controls from `Connect` component
- Add styles for network configuration UI
- Update tests to reflect new network config handling

* style(adapters): improve algodClient creation logs

Make logging more consistent across framework adapters when creating new
`algodClient` instances. This change:

- Standardize log message format across React/Vue/Solid
- Add missing log statements for client creation
- Add clarifying comments about `algodClient` updates

* refactor(core): rename DEFAULT_NETWORKS to DEFAULT_NETWORK_CONFIG

Rename constant to better reflect its purpose as configuration data rather than
network instances.

* refactor(core): rename updateNetworkAlgod to updateAlgodConfig

Rename method to better reflect its purpose of updating algod configuration
rather than the entire network.

- Rename `updateNetworkAlgod` to `updateAlgodConfig` in `WalletManager`
- Update method name in React/Vue/Solid adapters
- Update all test files to use new method name
- Update example projects to use renamed method
  • Loading branch information
drichar authored Jan 22, 2025
1 parent dbbad1b commit 6d6494e
Show file tree
Hide file tree
Showing 53 changed files with 2,962 additions and 986 deletions.
12 changes: 3 additions & 9 deletions examples/nextjs/src/app/Connect.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,13 @@
'use client'

import { NetworkId, WalletId, useWallet, type Wallet } from '@txnlab/use-wallet-react'
import { NetworkId, WalletId, useNetwork, useWallet, type Wallet } from '@txnlab/use-wallet-react'
import algosdk from 'algosdk'
import * as React from 'react'
import styles from './Connect.module.css'

export function Connect() {
const {
algodClient,
activeAddress,
activeNetwork,
setActiveNetwork,
transactionSigner,
wallets
} = useWallet()
const { algodClient, activeAddress, transactionSigner, wallets } = useWallet()
const { activeNetwork, setActiveNetwork } = useNetwork()

const [isSending, setIsSending] = React.useState(false)
const [magicEmail, setMagicEmail] = React.useState('')
Expand Down
13 changes: 5 additions & 8 deletions examples/nuxt/components/Connect.vue
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
<script setup lang="ts">
import { NetworkId, WalletId, useWallet, type Wallet } from '@txnlab/use-wallet-vue'
import { NetworkId, WalletId, useNetwork, useWallet, type Wallet } from '@txnlab/use-wallet-vue'
import algosdk from 'algosdk'
import { ref } from 'vue'
const {
algodClient,
activeNetwork,
setActiveNetwork,
transactionSigner,
wallets: walletsRef
} = useWallet()
const { algodClient, transactionSigner, wallets: walletsRef } = useWallet()
const wallets = computed(() => walletsRef.value)
const { activeNetwork, setActiveNetwork } = useNetwork()
const isSending = ref(false)
const magicEmail = ref('')
Expand Down
63 changes: 63 additions & 0 deletions examples/react-ts/src/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -103,3 +103,66 @@
animation: logo-spin infinite 20s linear;
}
}

.config-section {
width: 100%;
max-width: 500px;
margin-top: 1em;
}

.config-form {
display: flex;
flex-direction: column;
gap: 1em;
margin: 1em 0;
padding: 1em;
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 4px;
}

.form-group {
display: flex;
flex-direction: column;
gap: 0.5em;
text-align: left;
}

.form-group label {
font-size: 0.9em;
opacity: 0.8;
}

.form-group input {
padding: 0.5em;
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 4px;
background: rgba(0, 0, 0, 0.1);
color: inherit;
}

.current-config {
margin-top: 1em;
text-align: left;
}

.current-config h5 {
margin: 0 0 0.5em 0;
font-size: 0.9em;
opacity: 0.8;
}

.current-config pre {
padding: 1em;
background: rgba(0, 0, 0, 0.1);
border-radius: 4px;
font-size: 0.9em;
overflow-x: auto;
}

.error-message {
margin-top: 1em;
padding: 0.5em 1em;
color: #ff4444;
background: rgba(255, 68, 68, 0.1);
border-radius: 4px;
}
2 changes: 2 additions & 0 deletions examples/react-ts/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { NetworkId, WalletId, WalletManager, WalletProvider } from '@txnlab/use-wallet-react'
import { Connect } from './Connect'
import { NetworkControls } from './NetworkControls'
import reactLogo from './assets/react.svg'
import viteLogo from '/vite.svg'
import './App.css'
Expand Down Expand Up @@ -45,6 +46,7 @@ function App() {
</a>
</div>
<h1>@txnlab/use-wallet-react</h1>
<NetworkControls />
<Connect />
</WalletProvider>
)
Expand Down
40 changes: 2 additions & 38 deletions examples/react-ts/src/Connect.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,9 @@
import { NetworkId, WalletId, useWallet, type Wallet } from '@txnlab/use-wallet-react'
import { useWallet, WalletId, type Wallet } from '@txnlab/use-wallet-react'
import algosdk from 'algosdk'
import * as React from 'react'

export function Connect() {
const {
algodClient,
activeAddress,
activeNetwork,
setActiveNetwork,
transactionSigner,
wallets
} = useWallet()
const { algodClient, activeAddress, transactionSigner, wallets } = useWallet()

const [isSending, setIsSending] = React.useState(false)
const [magicEmail, setMagicEmail] = React.useState('')
Expand Down Expand Up @@ -77,35 +70,6 @@ export function Connect() {

return (
<div>
<div className="network-group">
<h4>
Current Network: <span className="active-network">{activeNetwork}</span>
</h4>
<div className="network-buttons">
<button
type="button"
onClick={() => setActiveNetwork(NetworkId.BETANET)}
disabled={activeNetwork === NetworkId.BETANET}
>
Set to Betanet
</button>
<button
type="button"
onClick={() => setActiveNetwork(NetworkId.TESTNET)}
disabled={activeNetwork === NetworkId.TESTNET}
>
Set to Testnet
</button>
<button
type="button"
onClick={() => setActiveNetwork(NetworkId.MAINNET)}
disabled={activeNetwork === NetworkId.MAINNET}
>
Set to Mainnet
</button>
</div>
</div>

{wallets.map((wallet) => (
<div key={wallet.id} className="wallet-group">
<h4>
Expand Down
144 changes: 144 additions & 0 deletions examples/react-ts/src/NetworkControls.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import { AlgodConfig, NetworkId, useNetwork } from '@txnlab/use-wallet-react'
import * as React from 'react'

export function NetworkControls() {
const {
activeNetwork,
networkConfig,
activeNetworkConfig,
setActiveNetwork,
updateAlgodConfig,
resetNetworkConfig
} = useNetwork()

const [error, setError] = React.useState<string>('')
const [showConfig, setShowConfig] = React.useState(false)

const [configForm, setConfigForm] = React.useState<Partial<AlgodConfig>>({
baseServer: activeNetworkConfig.algod.baseServer,
port: activeNetworkConfig.algod.port?.toString() || '',
token: activeNetworkConfig.algod.token?.toString() || ''
})

React.useEffect(() => {
setConfigForm({
baseServer: activeNetworkConfig.algod.baseServer,
port: activeNetworkConfig.algod.port?.toString() || '',
token: activeNetworkConfig.algod.token?.toString() || ''
})
}, [activeNetworkConfig])

const handleNetworkSwitch = async (networkId: NetworkId) => {
try {
setError('')
await setActiveNetwork(networkId)
} catch (error) {
setError(error instanceof Error ? error.message : 'Failed to switch networks')
}
}

const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const { name, value } = event.target
setConfigForm((prev) => ({ ...prev, [name]: value }))
}

const handleConfigSubmit = async (event: React.FormEvent) => {
event.preventDefault()
try {
setError('')
updateAlgodConfig(activeNetwork, {
baseServer: configForm.baseServer,
port: configForm.port || undefined,
token: configForm.token
})
} catch (error) {
setError(error instanceof Error ? error.message : 'Failed to update node configuration')
}
}

const handleResetConfig = () => {
try {
setError('')
resetNetworkConfig(activeNetwork)
} catch (error) {
setError(error instanceof Error ? error.message : 'Failed to reset node configuration')
}
}

return (
<div className="network-group">
<h4>Network Controls</h4>
<div className="active-network">Active: {activeNetwork}</div>

<div className="network-buttons">
{Object.keys(networkConfig).map((networkId) => (
<button
key={networkId}
onClick={() => handleNetworkSwitch(networkId as NetworkId)}
disabled={networkId === activeNetwork}
>
Switch to {networkId}
</button>
))}
</div>

<div className="config-section">
<button onClick={() => setShowConfig(!showConfig)}>
{showConfig ? 'Hide' : 'Show'} Network Config
</button>

{showConfig && (
<form onSubmit={handleConfigSubmit} className="config-form">
<div className="form-group">
<label htmlFor="baseServer">Base Server:</label>
<input
type="text"
id="baseServer"
name="baseServer"
value={configForm.baseServer}
onChange={handleInputChange}
placeholder="https://mainnet-api.4160.nodely.dev"
/>
</div>

<div className="form-group">
<label htmlFor="port">Port:</label>
<input
type="text"
id="port"
name="port"
value={configForm.port}
onChange={handleInputChange}
placeholder="443"
/>
</div>

<div className="form-group">
<label htmlFor="token">Token:</label>
<input
type="text"
id="token"
name="token"
value={configForm.token?.toString() || ''}
onChange={handleInputChange}
placeholder="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
/>
</div>

<button type="submit">Update Configuration</button>
<button type="button" onClick={handleResetConfig}>
Reset Configuration
</button>
</form>
)}

<div className="current-config">
<h5>Current Algod Configuration:</h5>
<pre>{JSON.stringify(activeNetworkConfig.algod, null, 2)}</pre>
</div>
</div>

{error && <div className="error-message">{error}</div>}
</div>
)
}
66 changes: 66 additions & 0 deletions examples/solid-ts/src/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,69 @@
opacity: 0.75;
color: light-dark(rgba(16, 16, 16, 0.3), rgba(255, 255, 255, 0.3));
}

.config-section {
width: 100%;
max-width: 500px;
margin-top: 1em;
}

.config-form {
display: flex;
flex-direction: column;
gap: 1em;
margin: 1em 0;
padding: 1em;
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 4px;
}

.form-group {
display: flex;
flex-direction: column;
gap: 0.5em;
text-align: left;
}

.form-group label {
font-size: 0.9em;
opacity: 0.8;
}

.current-config {
margin-top: 1em;
text-align: left;
}

.current-config h5 {
margin: 0 0 0.5em 0;
font-size: 0.9em;
opacity: 0.8;
}

.current-config pre {
padding: 1em;
background: rgba(0, 0, 0, 0.1);
border-radius: 4px;
font-size: 0.9em;
overflow-x: auto;
}

.error-message {
margin-top: 1em;
padding: 0.5em 1em;
color: #ff4444;
background: rgba(255, 68, 68, 0.1);
border-radius: 4px;
}

@media (prefers-color-scheme: light) {
.config-form {
border-color: rgba(0, 0, 0, 0.1);
}

.form-group input,
.current-config pre {
background: rgba(0, 0, 0, 0.05);
}
}
Loading

0 comments on commit 6d6494e

Please sign in to comment.