otx-btc-wallet-react 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +726 -0
- package/dist/index.d.mts +436 -0
- package/dist/index.d.ts +436 -0
- package/dist/index.js +586 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +573 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +45 -0
- package/src/context.tsx +24 -0
- package/src/hooks/useAccount.ts +74 -0
- package/src/hooks/useConnect.ts +136 -0
- package/src/hooks/useDisconnect.ts +95 -0
- package/src/hooks/useMultiAddress.ts +135 -0
- package/src/hooks/useNetwork.ts +33 -0
- package/src/hooks/useSendTransaction.ts +137 -0
- package/src/hooks/useSignMessage.ts +136 -0
- package/src/hooks/useSignPsbt.ts +144 -0
- package/src/hooks/useSignPsbts.ts +164 -0
- package/src/index.ts +31 -0
- package/src/provider.tsx +70 -0
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "otx-btc-wallet-react",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "React hooks and components for otx-btc-wallet",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"module": "./dist/index.mjs",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": "./dist/index.mjs",
|
|
12
|
+
"require": "./dist/index.js",
|
|
13
|
+
"types": "./dist/index.d.ts"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist",
|
|
18
|
+
"src"
|
|
19
|
+
],
|
|
20
|
+
"sideEffects": false,
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"otx-btc-wallet-core": "0.1.0"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"@types/react": "^18.2.42",
|
|
26
|
+
"react": "^18.2.0",
|
|
27
|
+
"typescript": "^5.3.2",
|
|
28
|
+
"tsup": "^8.0.1"
|
|
29
|
+
},
|
|
30
|
+
"peerDependencies": {
|
|
31
|
+
"react": ">=18.0.0"
|
|
32
|
+
},
|
|
33
|
+
"keywords": [
|
|
34
|
+
"bitcoin",
|
|
35
|
+
"wallet",
|
|
36
|
+
"react",
|
|
37
|
+
"hooks"
|
|
38
|
+
],
|
|
39
|
+
"scripts": {
|
|
40
|
+
"build": "tsup",
|
|
41
|
+
"dev": "tsup --watch",
|
|
42
|
+
"clean": "rm -rf dist",
|
|
43
|
+
"typecheck": "tsc --noEmit"
|
|
44
|
+
}
|
|
45
|
+
}
|
package/src/context.tsx
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { createContext, useContext } from 'react';
|
|
2
|
+
import type { ResolvedConfig } from 'otx-btc-wallet-core';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Context for otx-btc-wallet configuration
|
|
6
|
+
*/
|
|
7
|
+
export const BtcWalletContext = createContext<ResolvedConfig | null>(null);
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Hook to access the otx-btc-wallet config
|
|
11
|
+
* @throws Error if used outside of BtcWalletProvider
|
|
12
|
+
*/
|
|
13
|
+
export function useConfig(): ResolvedConfig {
|
|
14
|
+
const config = useContext(BtcWalletContext);
|
|
15
|
+
|
|
16
|
+
if (!config) {
|
|
17
|
+
throw new Error(
|
|
18
|
+
'useConfig must be used within a BtcWalletProvider. ' +
|
|
19
|
+
'Wrap your app in <BtcWalletProvider config={config}>.'
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return config;
|
|
24
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { useSyncExternalStore } from 'react';
|
|
2
|
+
import type {
|
|
3
|
+
WalletAccount,
|
|
4
|
+
BitcoinConnector,
|
|
5
|
+
ConnectionStatus,
|
|
6
|
+
AddressType,
|
|
7
|
+
} from 'otx-btc-wallet-core';
|
|
8
|
+
import { useConfig } from '../context';
|
|
9
|
+
|
|
10
|
+
export interface UseAccountReturn {
|
|
11
|
+
/** Connected address */
|
|
12
|
+
address: string | undefined;
|
|
13
|
+
/** Connected public key */
|
|
14
|
+
publicKey: string | undefined;
|
|
15
|
+
/** Address type */
|
|
16
|
+
addressType: AddressType | undefined;
|
|
17
|
+
/** Full account object */
|
|
18
|
+
account: WalletAccount | undefined;
|
|
19
|
+
/** Active connector */
|
|
20
|
+
connector: BitcoinConnector | undefined;
|
|
21
|
+
/** Connection status */
|
|
22
|
+
status: ConnectionStatus;
|
|
23
|
+
/** Is connected */
|
|
24
|
+
isConnected: boolean;
|
|
25
|
+
/** Is connecting */
|
|
26
|
+
isConnecting: boolean;
|
|
27
|
+
/** Is disconnected */
|
|
28
|
+
isDisconnected: boolean;
|
|
29
|
+
/** Is reconnecting */
|
|
30
|
+
isReconnecting: boolean;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Hook to get current account state
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```tsx
|
|
38
|
+
* function Profile() {
|
|
39
|
+
* const { address, isConnected, connector } = useAccount();
|
|
40
|
+
*
|
|
41
|
+
* if (!isConnected) return <div>Not connected</div>;
|
|
42
|
+
*
|
|
43
|
+
* return (
|
|
44
|
+
* <div>
|
|
45
|
+
* <p>Address: {address}</p>
|
|
46
|
+
* <p>Connected via {connector?.name}</p>
|
|
47
|
+
* </div>
|
|
48
|
+
* );
|
|
49
|
+
* }
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
export function useAccount(): UseAccountReturn {
|
|
53
|
+
const config = useConfig();
|
|
54
|
+
const { store } = config;
|
|
55
|
+
|
|
56
|
+
const state = useSyncExternalStore(
|
|
57
|
+
store.subscribe,
|
|
58
|
+
() => store.getState(),
|
|
59
|
+
() => store.getState()
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
address: state.account?.address,
|
|
64
|
+
publicKey: state.account?.publicKey,
|
|
65
|
+
addressType: state.account?.type,
|
|
66
|
+
account: state.account ?? undefined,
|
|
67
|
+
connector: state.connector ?? undefined,
|
|
68
|
+
status: state.status,
|
|
69
|
+
isConnected: state.status === 'connected',
|
|
70
|
+
isConnecting: state.status === 'connecting',
|
|
71
|
+
isDisconnected: state.status === 'disconnected',
|
|
72
|
+
isReconnecting: state.status === 'reconnecting',
|
|
73
|
+
};
|
|
74
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { useCallback, useMemo, useState } from 'react';
|
|
2
|
+
import type {
|
|
3
|
+
BitcoinConnector,
|
|
4
|
+
BitcoinNetwork,
|
|
5
|
+
WalletAccount,
|
|
6
|
+
} from 'otx-btc-wallet-core';
|
|
7
|
+
import { useConfig } from '../context';
|
|
8
|
+
|
|
9
|
+
export type ConnectArgs = {
|
|
10
|
+
connector: BitcoinConnector;
|
|
11
|
+
network?: BitcoinNetwork;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export interface UseConnectReturn {
|
|
15
|
+
/** Connect to a wallet (fire-and-forget) */
|
|
16
|
+
connect: (args: ConnectArgs) => void;
|
|
17
|
+
/** Connect to a wallet (returns promise) */
|
|
18
|
+
connectAsync: (args: ConnectArgs) => Promise<WalletAccount>;
|
|
19
|
+
/** Available connectors */
|
|
20
|
+
connectors: BitcoinConnector[];
|
|
21
|
+
/** Last error */
|
|
22
|
+
error: Error | null;
|
|
23
|
+
/** Is currently connecting */
|
|
24
|
+
isLoading: boolean;
|
|
25
|
+
/** Did connection fail */
|
|
26
|
+
isError: boolean;
|
|
27
|
+
/** Did connection succeed */
|
|
28
|
+
isSuccess: boolean;
|
|
29
|
+
/** Reset state */
|
|
30
|
+
reset: () => void;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Hook to connect to a wallet
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```tsx
|
|
38
|
+
* function ConnectButtons() {
|
|
39
|
+
* const { connect, connectors, isLoading, error } = useConnect();
|
|
40
|
+
*
|
|
41
|
+
* return (
|
|
42
|
+
* <div>
|
|
43
|
+
* {connectors.map((connector) => (
|
|
44
|
+
* <button
|
|
45
|
+
* key={connector.id}
|
|
46
|
+
* onClick={() => connect({ connector })}
|
|
47
|
+
* disabled={isLoading}
|
|
48
|
+
* >
|
|
49
|
+
* Connect {connector.name}
|
|
50
|
+
* </button>
|
|
51
|
+
* ))}
|
|
52
|
+
* {error && <p>Error: {error.message}</p>}
|
|
53
|
+
* </div>
|
|
54
|
+
* );
|
|
55
|
+
* }
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
58
|
+
export function useConnect(): UseConnectReturn {
|
|
59
|
+
const config = useConfig();
|
|
60
|
+
const { store, connectors } = config;
|
|
61
|
+
|
|
62
|
+
const [state, setState] = useState<{
|
|
63
|
+
isLoading: boolean;
|
|
64
|
+
isError: boolean;
|
|
65
|
+
isSuccess: boolean;
|
|
66
|
+
error: Error | null;
|
|
67
|
+
}>({
|
|
68
|
+
isLoading: false,
|
|
69
|
+
isError: false,
|
|
70
|
+
isSuccess: false,
|
|
71
|
+
error: null,
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
const connectAsync = useCallback(
|
|
75
|
+
async ({ connector, network }: ConnectArgs) => {
|
|
76
|
+
setState({
|
|
77
|
+
isLoading: true,
|
|
78
|
+
isError: false,
|
|
79
|
+
isSuccess: false,
|
|
80
|
+
error: null,
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
const account = await store.getState().connect(connector, network);
|
|
85
|
+
setState({
|
|
86
|
+
isLoading: false,
|
|
87
|
+
isError: false,
|
|
88
|
+
isSuccess: true,
|
|
89
|
+
error: null,
|
|
90
|
+
});
|
|
91
|
+
return account;
|
|
92
|
+
} catch (error) {
|
|
93
|
+
setState({
|
|
94
|
+
isLoading: false,
|
|
95
|
+
isError: true,
|
|
96
|
+
isSuccess: false,
|
|
97
|
+
error: error as Error,
|
|
98
|
+
});
|
|
99
|
+
throw error;
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
[store]
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
const connect = useCallback(
|
|
106
|
+
(args: ConnectArgs) => {
|
|
107
|
+
connectAsync(args).catch(() => {
|
|
108
|
+
// Error is already captured in state
|
|
109
|
+
});
|
|
110
|
+
},
|
|
111
|
+
[connectAsync]
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
const reset = useCallback(() => {
|
|
115
|
+
setState({
|
|
116
|
+
isLoading: false,
|
|
117
|
+
isError: false,
|
|
118
|
+
isSuccess: false,
|
|
119
|
+
error: null,
|
|
120
|
+
});
|
|
121
|
+
}, []);
|
|
122
|
+
|
|
123
|
+
return useMemo(
|
|
124
|
+
() => ({
|
|
125
|
+
connect,
|
|
126
|
+
connectAsync,
|
|
127
|
+
connectors,
|
|
128
|
+
error: state.error,
|
|
129
|
+
isLoading: state.isLoading,
|
|
130
|
+
isError: state.isError,
|
|
131
|
+
isSuccess: state.isSuccess,
|
|
132
|
+
reset,
|
|
133
|
+
}),
|
|
134
|
+
[connect, connectAsync, connectors, state, reset]
|
|
135
|
+
);
|
|
136
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { useCallback, useMemo, useState } from 'react';
|
|
2
|
+
import { useConfig } from '../context';
|
|
3
|
+
|
|
4
|
+
export interface UseDisconnectReturn {
|
|
5
|
+
/** Disconnect from wallet (fire-and-forget) */
|
|
6
|
+
disconnect: () => void;
|
|
7
|
+
/** Disconnect from wallet (returns promise) */
|
|
8
|
+
disconnectAsync: () => Promise<void>;
|
|
9
|
+
/** Is currently disconnecting */
|
|
10
|
+
isLoading: boolean;
|
|
11
|
+
/** Did disconnect fail */
|
|
12
|
+
isError: boolean;
|
|
13
|
+
/** Did disconnect succeed */
|
|
14
|
+
isSuccess: boolean;
|
|
15
|
+
/** Last error */
|
|
16
|
+
error: Error | null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Hook to disconnect from wallet
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```tsx
|
|
24
|
+
* function DisconnectButton() {
|
|
25
|
+
* const { disconnect, isLoading } = useDisconnect();
|
|
26
|
+
*
|
|
27
|
+
* return (
|
|
28
|
+
* <button onClick={disconnect} disabled={isLoading}>
|
|
29
|
+
* Disconnect
|
|
30
|
+
* </button>
|
|
31
|
+
* );
|
|
32
|
+
* }
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
export function useDisconnect(): UseDisconnectReturn {
|
|
36
|
+
const config = useConfig();
|
|
37
|
+
const { store } = config;
|
|
38
|
+
|
|
39
|
+
const [state, setState] = useState<{
|
|
40
|
+
isLoading: boolean;
|
|
41
|
+
isError: boolean;
|
|
42
|
+
isSuccess: boolean;
|
|
43
|
+
error: Error | null;
|
|
44
|
+
}>({
|
|
45
|
+
isLoading: false,
|
|
46
|
+
isError: false,
|
|
47
|
+
isSuccess: false,
|
|
48
|
+
error: null,
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
const disconnectAsync = useCallback(async () => {
|
|
52
|
+
setState({
|
|
53
|
+
isLoading: true,
|
|
54
|
+
isError: false,
|
|
55
|
+
isSuccess: false,
|
|
56
|
+
error: null,
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
await store.getState().disconnect();
|
|
61
|
+
setState({
|
|
62
|
+
isLoading: false,
|
|
63
|
+
isError: false,
|
|
64
|
+
isSuccess: true,
|
|
65
|
+
error: null,
|
|
66
|
+
});
|
|
67
|
+
} catch (error) {
|
|
68
|
+
setState({
|
|
69
|
+
isLoading: false,
|
|
70
|
+
isError: true,
|
|
71
|
+
isSuccess: false,
|
|
72
|
+
error: error as Error,
|
|
73
|
+
});
|
|
74
|
+
throw error;
|
|
75
|
+
}
|
|
76
|
+
}, [store]);
|
|
77
|
+
|
|
78
|
+
const disconnect = useCallback(() => {
|
|
79
|
+
disconnectAsync().catch(() => {
|
|
80
|
+
// Error is already captured in state
|
|
81
|
+
});
|
|
82
|
+
}, [disconnectAsync]);
|
|
83
|
+
|
|
84
|
+
return useMemo(
|
|
85
|
+
() => ({
|
|
86
|
+
disconnect,
|
|
87
|
+
disconnectAsync,
|
|
88
|
+
isLoading: state.isLoading,
|
|
89
|
+
isError: state.isError,
|
|
90
|
+
isSuccess: state.isSuccess,
|
|
91
|
+
error: state.error,
|
|
92
|
+
}),
|
|
93
|
+
[disconnect, disconnectAsync, state]
|
|
94
|
+
);
|
|
95
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { useCallback, useMemo, useState, useEffect } from 'react';
|
|
2
|
+
import type { WalletAccount } from 'otx-btc-wallet-core';
|
|
3
|
+
import { useConfig } from '../context';
|
|
4
|
+
|
|
5
|
+
export interface UseMultiAddressReturn {
|
|
6
|
+
/** All available addresses from the wallet */
|
|
7
|
+
addresses: WalletAccount[];
|
|
8
|
+
/** Payment address (for sending/receiving BTC) */
|
|
9
|
+
paymentAddress: WalletAccount | null;
|
|
10
|
+
/** Ordinals address (for NFTs/inscriptions) */
|
|
11
|
+
ordinalsAddress: WalletAccount | null;
|
|
12
|
+
/** Currently selected primary address */
|
|
13
|
+
primaryAddress: WalletAccount | null;
|
|
14
|
+
/** Set the primary address */
|
|
15
|
+
setPrimaryAddress: (address: WalletAccount) => void;
|
|
16
|
+
/** Is loading addresses */
|
|
17
|
+
isLoading: boolean;
|
|
18
|
+
/** Refresh addresses from wallet */
|
|
19
|
+
refresh: () => Promise<void>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Hook to manage multiple addresses from a connected wallet
|
|
24
|
+
*
|
|
25
|
+
* Xverse and Leather wallets provide both payment and ordinals addresses.
|
|
26
|
+
* This hook helps manage and switch between them.
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```tsx
|
|
30
|
+
* function MultiAddressDisplay() {
|
|
31
|
+
* const {
|
|
32
|
+
* paymentAddress,
|
|
33
|
+
* ordinalsAddress,
|
|
34
|
+
* primaryAddress,
|
|
35
|
+
* setPrimaryAddress
|
|
36
|
+
* } = useMultiAddress();
|
|
37
|
+
*
|
|
38
|
+
* return (
|
|
39
|
+
* <div>
|
|
40
|
+
* <h3>Payment: {paymentAddress?.address}</h3>
|
|
41
|
+
* <h3>Ordinals: {ordinalsAddress?.address}</h3>
|
|
42
|
+
* <select onChange={(e) => {
|
|
43
|
+
* const addr = e.target.value === 'payment' ? paymentAddress : ordinalsAddress;
|
|
44
|
+
* if (addr) setPrimaryAddress(addr);
|
|
45
|
+
* }}>
|
|
46
|
+
* <option value="payment">Payment</option>
|
|
47
|
+
* <option value="ordinals">Ordinals</option>
|
|
48
|
+
* </select>
|
|
49
|
+
* </div>
|
|
50
|
+
* );
|
|
51
|
+
* }
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
export function useMultiAddress(): UseMultiAddressReturn {
|
|
55
|
+
const config = useConfig();
|
|
56
|
+
const { store } = config;
|
|
57
|
+
|
|
58
|
+
const [addresses, setAddresses] = useState<WalletAccount[]>([]);
|
|
59
|
+
const [primaryAddress, setPrimaryAddressState] = useState<WalletAccount | null>(null);
|
|
60
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
61
|
+
|
|
62
|
+
// Fetch addresses from connector
|
|
63
|
+
const fetchAddresses = useCallback(async () => {
|
|
64
|
+
const { connector } = store.getState();
|
|
65
|
+
if (!connector) {
|
|
66
|
+
setAddresses([]);
|
|
67
|
+
setPrimaryAddressState(null);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
setIsLoading(true);
|
|
72
|
+
try {
|
|
73
|
+
const accounts = await connector.getAccounts();
|
|
74
|
+
setAddresses(accounts);
|
|
75
|
+
|
|
76
|
+
// Set primary address to first one if not set
|
|
77
|
+
if (accounts.length > 0 && !primaryAddress) {
|
|
78
|
+
setPrimaryAddressState(accounts[0] ?? null);
|
|
79
|
+
}
|
|
80
|
+
} catch {
|
|
81
|
+
setAddresses([]);
|
|
82
|
+
} finally {
|
|
83
|
+
setIsLoading(false);
|
|
84
|
+
}
|
|
85
|
+
}, [store, primaryAddress]);
|
|
86
|
+
|
|
87
|
+
// Fetch addresses when connector changes
|
|
88
|
+
useEffect(() => {
|
|
89
|
+
const unsubscribe = store.subscribe(
|
|
90
|
+
(state) => state.connector,
|
|
91
|
+
() => {
|
|
92
|
+
void fetchAddresses();
|
|
93
|
+
}
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
// Initial fetch
|
|
97
|
+
void fetchAddresses();
|
|
98
|
+
|
|
99
|
+
return unsubscribe;
|
|
100
|
+
}, [store, fetchAddresses]);
|
|
101
|
+
|
|
102
|
+
// Derive payment and ordinals addresses
|
|
103
|
+
const { paymentAddress, ordinalsAddress } = useMemo(() => {
|
|
104
|
+
// Payment addresses are typically segwit (bc1q) or nested-segwit (3)
|
|
105
|
+
const payment = addresses.find(
|
|
106
|
+
(a) => a.type === 'segwit' || a.type === 'nested-segwit'
|
|
107
|
+
) ?? addresses[0] ?? null;
|
|
108
|
+
|
|
109
|
+
// Ordinals addresses are typically taproot (bc1p)
|
|
110
|
+
const ordinals = addresses.find((a) => a.type === 'taproot') ?? null;
|
|
111
|
+
|
|
112
|
+
return { paymentAddress: payment, ordinalsAddress: ordinals };
|
|
113
|
+
}, [addresses]);
|
|
114
|
+
|
|
115
|
+
const setPrimaryAddress = useCallback((address: WalletAccount) => {
|
|
116
|
+
setPrimaryAddressState(address);
|
|
117
|
+
}, []);
|
|
118
|
+
|
|
119
|
+
const refresh = useCallback(async () => {
|
|
120
|
+
await fetchAddresses();
|
|
121
|
+
}, [fetchAddresses]);
|
|
122
|
+
|
|
123
|
+
return useMemo(
|
|
124
|
+
() => ({
|
|
125
|
+
addresses,
|
|
126
|
+
paymentAddress,
|
|
127
|
+
ordinalsAddress,
|
|
128
|
+
primaryAddress,
|
|
129
|
+
setPrimaryAddress,
|
|
130
|
+
isLoading,
|
|
131
|
+
refresh,
|
|
132
|
+
}),
|
|
133
|
+
[addresses, paymentAddress, ordinalsAddress, primaryAddress, setPrimaryAddress, isLoading, refresh]
|
|
134
|
+
);
|
|
135
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { useSyncExternalStore } from 'react';
|
|
2
|
+
import type { BitcoinNetwork } from 'otx-btc-wallet-core';
|
|
3
|
+
import { useConfig } from '../context';
|
|
4
|
+
|
|
5
|
+
export interface UseNetworkReturn {
|
|
6
|
+
/** Current network */
|
|
7
|
+
network: BitcoinNetwork;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Hook to get current network
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```tsx
|
|
15
|
+
* function NetworkInfo() {
|
|
16
|
+
* const { network } = useNetwork();
|
|
17
|
+
*
|
|
18
|
+
* return <p>Network: {network}</p>;
|
|
19
|
+
* }
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export function useNetwork(): UseNetworkReturn {
|
|
23
|
+
const config = useConfig();
|
|
24
|
+
const { store } = config;
|
|
25
|
+
|
|
26
|
+
const network = useSyncExternalStore(
|
|
27
|
+
store.subscribe,
|
|
28
|
+
() => store.getState().network,
|
|
29
|
+
() => store.getState().network
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
return { network };
|
|
33
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { useCallback, useMemo, useState } from 'react';
|
|
2
|
+
import { useConfig } from '../context';
|
|
3
|
+
|
|
4
|
+
export interface UseSendTransactionReturn {
|
|
5
|
+
/** Send transaction (fire-and-forget) */
|
|
6
|
+
sendTransaction: (args: { to: string; amount: number }) => void;
|
|
7
|
+
/** Send transaction (returns promise with txid) */
|
|
8
|
+
sendTransactionAsync: (args: { to: string; amount: number }) => Promise<string>;
|
|
9
|
+
/** Transaction ID */
|
|
10
|
+
data: string | undefined;
|
|
11
|
+
/** Last error */
|
|
12
|
+
error: Error | null;
|
|
13
|
+
/** Is sending */
|
|
14
|
+
isLoading: boolean;
|
|
15
|
+
/** Did send fail */
|
|
16
|
+
isError: boolean;
|
|
17
|
+
/** Did send succeed */
|
|
18
|
+
isSuccess: boolean;
|
|
19
|
+
/** Reset state */
|
|
20
|
+
reset: () => void;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Hook to send a Bitcoin transaction
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```tsx
|
|
28
|
+
* function SendForm() {
|
|
29
|
+
* const { sendTransaction, isLoading, data, error } = useSendTransaction();
|
|
30
|
+
*
|
|
31
|
+
* const handleSend = () => {
|
|
32
|
+
* sendTransaction({ to: 'bc1q...', amount: 10000 }); // 10000 sats
|
|
33
|
+
* };
|
|
34
|
+
*
|
|
35
|
+
* return (
|
|
36
|
+
* <div>
|
|
37
|
+
* <button onClick={handleSend} disabled={isLoading}>
|
|
38
|
+
* Send 10,000 sats
|
|
39
|
+
* </button>
|
|
40
|
+
* {data && <p>TX: {data}</p>}
|
|
41
|
+
* {error && <p>Error: {error.message}</p>}
|
|
42
|
+
* </div>
|
|
43
|
+
* );
|
|
44
|
+
* }
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
export function useSendTransaction(): UseSendTransactionReturn {
|
|
48
|
+
const config = useConfig();
|
|
49
|
+
const { store } = config;
|
|
50
|
+
|
|
51
|
+
const [state, setState] = useState<{
|
|
52
|
+
data: string | undefined;
|
|
53
|
+
error: Error | null;
|
|
54
|
+
isLoading: boolean;
|
|
55
|
+
isError: boolean;
|
|
56
|
+
isSuccess: boolean;
|
|
57
|
+
}>({
|
|
58
|
+
data: undefined,
|
|
59
|
+
error: null,
|
|
60
|
+
isLoading: false,
|
|
61
|
+
isError: false,
|
|
62
|
+
isSuccess: false,
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const sendTransactionAsync = useCallback(
|
|
66
|
+
async ({ to, amount }: { to: string; amount: number }) => {
|
|
67
|
+
const { connector } = store.getState();
|
|
68
|
+
|
|
69
|
+
if (!connector) {
|
|
70
|
+
throw new Error('Not connected');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
setState({
|
|
74
|
+
data: undefined,
|
|
75
|
+
error: null,
|
|
76
|
+
isLoading: true,
|
|
77
|
+
isError: false,
|
|
78
|
+
isSuccess: false,
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
try {
|
|
82
|
+
const txid = await connector.sendTransaction(to, amount);
|
|
83
|
+
setState({
|
|
84
|
+
data: txid,
|
|
85
|
+
error: null,
|
|
86
|
+
isLoading: false,
|
|
87
|
+
isError: false,
|
|
88
|
+
isSuccess: true,
|
|
89
|
+
});
|
|
90
|
+
return txid;
|
|
91
|
+
} catch (error) {
|
|
92
|
+
setState({
|
|
93
|
+
data: undefined,
|
|
94
|
+
error: error as Error,
|
|
95
|
+
isLoading: false,
|
|
96
|
+
isError: true,
|
|
97
|
+
isSuccess: false,
|
|
98
|
+
});
|
|
99
|
+
throw error;
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
[store]
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
const sendTransaction = useCallback(
|
|
106
|
+
({ to, amount }: { to: string; amount: number }) => {
|
|
107
|
+
sendTransactionAsync({ to, amount }).catch(() => {
|
|
108
|
+
// Error is already captured in state
|
|
109
|
+
});
|
|
110
|
+
},
|
|
111
|
+
[sendTransactionAsync]
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
const reset = useCallback(() => {
|
|
115
|
+
setState({
|
|
116
|
+
data: undefined,
|
|
117
|
+
error: null,
|
|
118
|
+
isLoading: false,
|
|
119
|
+
isError: false,
|
|
120
|
+
isSuccess: false,
|
|
121
|
+
});
|
|
122
|
+
}, []);
|
|
123
|
+
|
|
124
|
+
return useMemo(
|
|
125
|
+
() => ({
|
|
126
|
+
sendTransaction,
|
|
127
|
+
sendTransactionAsync,
|
|
128
|
+
data: state.data,
|
|
129
|
+
error: state.error,
|
|
130
|
+
isLoading: state.isLoading,
|
|
131
|
+
isError: state.isError,
|
|
132
|
+
isSuccess: state.isSuccess,
|
|
133
|
+
reset,
|
|
134
|
+
}),
|
|
135
|
+
[sendTransaction, sendTransactionAsync, state, reset]
|
|
136
|
+
);
|
|
137
|
+
}
|