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.
@@ -0,0 +1,136 @@
1
+ import { useCallback, useMemo, useState } from 'react';
2
+ import { useConfig } from '../context';
3
+
4
+ export interface UseSignMessageReturn {
5
+ /** Sign message (fire-and-forget) */
6
+ signMessage: (args: { message: string }) => void;
7
+ /** Sign message (returns promise with signature) */
8
+ signMessageAsync: (args: { message: string }) => Promise<string>;
9
+ /** Signature */
10
+ data: string | undefined;
11
+ /** Last error */
12
+ error: Error | null;
13
+ /** Is signing */
14
+ isLoading: boolean;
15
+ /** Did signing fail */
16
+ isError: boolean;
17
+ /** Did signing succeed */
18
+ isSuccess: boolean;
19
+ /** Reset state */
20
+ reset: () => void;
21
+ }
22
+
23
+ /**
24
+ * Hook to sign a message
25
+ *
26
+ * @example
27
+ * ```tsx
28
+ * function SignMessage() {
29
+ * const { signMessage, isLoading, data } = useSignMessage();
30
+ *
31
+ * const handleSign = () => {
32
+ * signMessage({ message: 'Hello, Bitcoin!' });
33
+ * };
34
+ *
35
+ * return (
36
+ * <div>
37
+ * <button onClick={handleSign} disabled={isLoading}>
38
+ * Sign Message
39
+ * </button>
40
+ * {data && <p>Signature: {data}</p>}
41
+ * </div>
42
+ * );
43
+ * }
44
+ * ```
45
+ */
46
+ export function useSignMessage(): UseSignMessageReturn {
47
+ const config = useConfig();
48
+ const { store } = config;
49
+
50
+ const [state, setState] = useState<{
51
+ data: string | undefined;
52
+ error: Error | null;
53
+ isLoading: boolean;
54
+ isError: boolean;
55
+ isSuccess: boolean;
56
+ }>({
57
+ data: undefined,
58
+ error: null,
59
+ isLoading: false,
60
+ isError: false,
61
+ isSuccess: false,
62
+ });
63
+
64
+ const signMessageAsync = useCallback(
65
+ async ({ message }: { message: string }) => {
66
+ const { connector } = store.getState();
67
+
68
+ if (!connector) {
69
+ throw new Error('Not connected');
70
+ }
71
+
72
+ setState({
73
+ data: undefined,
74
+ error: null,
75
+ isLoading: true,
76
+ isError: false,
77
+ isSuccess: false,
78
+ });
79
+
80
+ try {
81
+ const signature = await connector.signMessage(message);
82
+ setState({
83
+ data: signature,
84
+ error: null,
85
+ isLoading: false,
86
+ isError: false,
87
+ isSuccess: true,
88
+ });
89
+ return signature;
90
+ } catch (error) {
91
+ setState({
92
+ data: undefined,
93
+ error: error as Error,
94
+ isLoading: false,
95
+ isError: true,
96
+ isSuccess: false,
97
+ });
98
+ throw error;
99
+ }
100
+ },
101
+ [store]
102
+ );
103
+
104
+ const signMessage = useCallback(
105
+ ({ message }: { message: string }) => {
106
+ signMessageAsync({ message }).catch(() => {
107
+ // Error is already captured in state
108
+ });
109
+ },
110
+ [signMessageAsync]
111
+ );
112
+
113
+ const reset = useCallback(() => {
114
+ setState({
115
+ data: undefined,
116
+ error: null,
117
+ isLoading: false,
118
+ isError: false,
119
+ isSuccess: false,
120
+ });
121
+ }, []);
122
+
123
+ return useMemo(
124
+ () => ({
125
+ signMessage,
126
+ signMessageAsync,
127
+ data: state.data,
128
+ error: state.error,
129
+ isLoading: state.isLoading,
130
+ isError: state.isError,
131
+ isSuccess: state.isSuccess,
132
+ reset,
133
+ }),
134
+ [signMessage, signMessageAsync, state, reset]
135
+ );
136
+ }
@@ -0,0 +1,144 @@
1
+ import { useCallback, useMemo, useState } from 'react';
2
+ import type { SignPsbtOptions } from 'otx-btc-wallet-core';
3
+ import { useConfig } from '../context';
4
+
5
+ export interface UseSignPsbtReturn {
6
+ /** Sign PSBT (fire-and-forget) */
7
+ signPsbt: (args: { psbt: string; options?: SignPsbtOptions }) => void;
8
+ /** Sign PSBT (returns promise with signed PSBT hex) */
9
+ signPsbtAsync: (args: {
10
+ psbt: string;
11
+ options?: SignPsbtOptions;
12
+ }) => Promise<string>;
13
+ /** Signed PSBT hex */
14
+ data: string | undefined;
15
+ /** Last error */
16
+ error: Error | null;
17
+ /** Is signing */
18
+ isLoading: boolean;
19
+ /** Did signing fail */
20
+ isError: boolean;
21
+ /** Did signing succeed */
22
+ isSuccess: boolean;
23
+ /** Reset state */
24
+ reset: () => void;
25
+ }
26
+
27
+ /**
28
+ * Hook to sign a PSBT
29
+ *
30
+ * @example
31
+ * ```tsx
32
+ * function SignPsbt() {
33
+ * const { signPsbt, isLoading, data } = useSignPsbt();
34
+ *
35
+ * const handleSign = () => {
36
+ * signPsbt({
37
+ * psbt: '70736274ff01...',
38
+ * options: { autoFinalize: true }
39
+ * });
40
+ * };
41
+ *
42
+ * return (
43
+ * <div>
44
+ * <button onClick={handleSign} disabled={isLoading}>
45
+ * Sign PSBT
46
+ * </button>
47
+ * {data && <p>Signed: {data.slice(0, 20)}...</p>}
48
+ * </div>
49
+ * );
50
+ * }
51
+ * ```
52
+ */
53
+ export function useSignPsbt(): UseSignPsbtReturn {
54
+ const config = useConfig();
55
+ const { store } = config;
56
+
57
+ const [state, setState] = useState<{
58
+ data: string | undefined;
59
+ error: Error | null;
60
+ isLoading: boolean;
61
+ isError: boolean;
62
+ isSuccess: boolean;
63
+ }>({
64
+ data: undefined,
65
+ error: null,
66
+ isLoading: false,
67
+ isError: false,
68
+ isSuccess: false,
69
+ });
70
+
71
+ const signPsbtAsync = useCallback(
72
+ async ({ psbt, options }: { psbt: string; options?: SignPsbtOptions }) => {
73
+ const { connector } = store.getState();
74
+
75
+ if (!connector) {
76
+ throw new Error('Not connected');
77
+ }
78
+
79
+ setState({
80
+ data: undefined,
81
+ error: null,
82
+ isLoading: true,
83
+ isError: false,
84
+ isSuccess: false,
85
+ });
86
+
87
+ try {
88
+ const signedPsbt = await connector.signPsbt(psbt, options);
89
+ setState({
90
+ data: signedPsbt,
91
+ error: null,
92
+ isLoading: false,
93
+ isError: false,
94
+ isSuccess: true,
95
+ });
96
+ return signedPsbt;
97
+ } catch (error) {
98
+ setState({
99
+ data: undefined,
100
+ error: error as Error,
101
+ isLoading: false,
102
+ isError: true,
103
+ isSuccess: false,
104
+ });
105
+ throw error;
106
+ }
107
+ },
108
+ [store]
109
+ );
110
+
111
+ const signPsbt = useCallback(
112
+ ({ psbt, options }: { psbt: string; options?: SignPsbtOptions }) => {
113
+ const args = options !== undefined ? { psbt, options } : { psbt };
114
+ signPsbtAsync(args).catch(() => {
115
+ // Error is already captured in state
116
+ });
117
+ },
118
+ [signPsbtAsync]
119
+ );
120
+
121
+ const reset = useCallback(() => {
122
+ setState({
123
+ data: undefined,
124
+ error: null,
125
+ isLoading: false,
126
+ isError: false,
127
+ isSuccess: false,
128
+ });
129
+ }, []);
130
+
131
+ return useMemo(
132
+ () => ({
133
+ signPsbt,
134
+ signPsbtAsync,
135
+ data: state.data,
136
+ error: state.error,
137
+ isLoading: state.isLoading,
138
+ isError: state.isError,
139
+ isSuccess: state.isSuccess,
140
+ reset,
141
+ }),
142
+ [signPsbt, signPsbtAsync, state, reset]
143
+ );
144
+ }
@@ -0,0 +1,164 @@
1
+ import { useCallback, useMemo, useState } from 'react';
2
+ import type { SignPsbtOptions } from 'otx-btc-wallet-core';
3
+ import { useConfig } from '../context';
4
+
5
+ export interface UseSignPsbtsReturn {
6
+ /** Sign multiple PSBTs (fire-and-forget) */
7
+ signPsbts: (args: { psbts: string[]; options?: SignPsbtOptions }) => void;
8
+ /** Sign multiple PSBTs (returns promise with signed PSBT hexes) */
9
+ signPsbtsAsync: (args: {
10
+ psbts: string[];
11
+ options?: SignPsbtOptions;
12
+ }) => Promise<string[]>;
13
+ /** Signed PSBT hexes */
14
+ data: string[] | undefined;
15
+ /** Last error */
16
+ error: Error | null;
17
+ /** Is signing */
18
+ isLoading: boolean;
19
+ /** Did signing fail */
20
+ isError: boolean;
21
+ /** Did signing succeed */
22
+ isSuccess: boolean;
23
+ /** Is batch signing supported by current connector */
24
+ isSupported: boolean;
25
+ /** Reset state */
26
+ reset: () => void;
27
+ }
28
+
29
+ /**
30
+ * Hook to sign multiple PSBTs (batch signing)
31
+ *
32
+ * @example
33
+ * ```tsx
34
+ * function BatchSign() {
35
+ * const { signPsbts, isLoading, isSupported, data } = useSignPsbts();
36
+ *
37
+ * if (!isSupported) {
38
+ * return <p>Batch signing not supported by this wallet</p>;
39
+ * }
40
+ *
41
+ * const handleSign = () => {
42
+ * signPsbts({
43
+ * psbts: ['70736274ff01...', '70736274ff02...'],
44
+ * options: { autoFinalize: true }
45
+ * });
46
+ * };
47
+ *
48
+ * return (
49
+ * <button onClick={handleSign} disabled={isLoading}>
50
+ * Sign {2} PSBTs
51
+ * </button>
52
+ * );
53
+ * }
54
+ * ```
55
+ */
56
+ export function useSignPsbts(): UseSignPsbtsReturn {
57
+ const config = useConfig();
58
+ const { store } = config;
59
+
60
+ const [state, setState] = useState<{
61
+ data: string[] | undefined;
62
+ error: Error | null;
63
+ isLoading: boolean;
64
+ isError: boolean;
65
+ isSuccess: boolean;
66
+ }>({
67
+ data: undefined,
68
+ error: null,
69
+ isLoading: false,
70
+ isError: false,
71
+ isSuccess: false,
72
+ });
73
+
74
+ // Check if current connector supports batch signing
75
+ const isSupported = useMemo(() => {
76
+ const { connector } = store.getState();
77
+ return typeof connector?.signPsbts === 'function';
78
+ }, [store]);
79
+
80
+ const signPsbtsAsync = useCallback(
81
+ async ({
82
+ psbts,
83
+ options,
84
+ }: {
85
+ psbts: string[];
86
+ options?: SignPsbtOptions;
87
+ }) => {
88
+ const { connector } = store.getState();
89
+
90
+ if (!connector) {
91
+ throw new Error('Not connected');
92
+ }
93
+
94
+ if (!connector.signPsbts) {
95
+ throw new Error('Batch signing is not supported by this wallet');
96
+ }
97
+
98
+ setState({
99
+ data: undefined,
100
+ error: null,
101
+ isLoading: true,
102
+ isError: false,
103
+ isSuccess: false,
104
+ });
105
+
106
+ try {
107
+ const signedPsbts = await connector.signPsbts(psbts, options);
108
+ setState({
109
+ data: signedPsbts,
110
+ error: null,
111
+ isLoading: false,
112
+ isError: false,
113
+ isSuccess: true,
114
+ });
115
+ return signedPsbts;
116
+ } catch (error) {
117
+ setState({
118
+ data: undefined,
119
+ error: error as Error,
120
+ isLoading: false,
121
+ isError: true,
122
+ isSuccess: false,
123
+ });
124
+ throw error;
125
+ }
126
+ },
127
+ [store]
128
+ );
129
+
130
+ const signPsbts = useCallback(
131
+ ({ psbts, options }: { psbts: string[]; options?: SignPsbtOptions }) => {
132
+ const args = options !== undefined ? { psbts, options } : { psbts };
133
+ signPsbtsAsync(args).catch(() => {
134
+ // Error is already captured in state
135
+ });
136
+ },
137
+ [signPsbtsAsync]
138
+ );
139
+
140
+ const reset = useCallback(() => {
141
+ setState({
142
+ data: undefined,
143
+ error: null,
144
+ isLoading: false,
145
+ isError: false,
146
+ isSuccess: false,
147
+ });
148
+ }, []);
149
+
150
+ return useMemo(
151
+ () => ({
152
+ signPsbts,
153
+ signPsbtsAsync,
154
+ data: state.data,
155
+ error: state.error,
156
+ isLoading: state.isLoading,
157
+ isError: state.isError,
158
+ isSuccess: state.isSuccess,
159
+ isSupported,
160
+ reset,
161
+ }),
162
+ [signPsbts, signPsbtsAsync, state, isSupported, reset]
163
+ );
164
+ }
package/src/index.ts ADDED
@@ -0,0 +1,31 @@
1
+ // otx-btc-wallet-react - React hooks and components
2
+
3
+ // Provider
4
+ export { BtcWalletProvider, type BtcWalletProviderProps } from './provider';
5
+
6
+ // Context
7
+ export { useConfig, BtcWalletContext } from './context';
8
+
9
+ // Hooks
10
+ export { useAccount, type UseAccountReturn } from './hooks/useAccount';
11
+ export {
12
+ useConnect,
13
+ type UseConnectReturn,
14
+ type ConnectArgs,
15
+ } from './hooks/useConnect';
16
+ export { useDisconnect, type UseDisconnectReturn } from './hooks/useDisconnect';
17
+ export { useNetwork, type UseNetworkReturn } from './hooks/useNetwork';
18
+ export {
19
+ useSendTransaction,
20
+ type UseSendTransactionReturn,
21
+ } from './hooks/useSendTransaction';
22
+ export {
23
+ useSignMessage,
24
+ type UseSignMessageReturn,
25
+ } from './hooks/useSignMessage';
26
+ export { useSignPsbt, type UseSignPsbtReturn } from './hooks/useSignPsbt';
27
+ export { useSignPsbts, type UseSignPsbtsReturn } from './hooks/useSignPsbts';
28
+ export {
29
+ useMultiAddress,
30
+ type UseMultiAddressReturn,
31
+ } from './hooks/useMultiAddress';
@@ -0,0 +1,70 @@
1
+ import { type ReactNode, useEffect, useRef } from 'react';
2
+ import type { ResolvedConfig } from 'otx-btc-wallet-core';
3
+ import { BtcWalletContext } from './context';
4
+
5
+ export interface BtcWalletProviderProps {
6
+ /** Configuration created with createConfig() */
7
+ config: ResolvedConfig;
8
+ /** Child components */
9
+ children: ReactNode;
10
+ }
11
+
12
+ /**
13
+ * Provider component for otx-btc-wallet
14
+ *
15
+ * @example
16
+ * ```tsx
17
+ * import { BtcWalletProvider } from 'otx-btc-wallet-react';
18
+ * import { config } from './config';
19
+ *
20
+ * function App() {
21
+ * return (
22
+ * <BtcWalletProvider config={config}>
23
+ * <YourApp />
24
+ * </BtcWalletProvider>
25
+ * );
26
+ * }
27
+ * ```
28
+ */
29
+ export function BtcWalletProvider({
30
+ config,
31
+ children,
32
+ }: BtcWalletProviderProps): ReactNode {
33
+ const initializedRef = useRef(false);
34
+
35
+ // Validate config
36
+ if (!config) {
37
+ throw new Error(
38
+ 'BtcWalletProvider requires a config prop. ' +
39
+ 'Create one with createConfig() from otx-btc-wallet-core.'
40
+ );
41
+ }
42
+
43
+ if (!config.store) {
44
+ throw new Error(
45
+ 'Invalid config: missing store. ' +
46
+ 'Make sure you created the config with createConfig().'
47
+ );
48
+ }
49
+
50
+ // Handle auto-reconnect on mount
51
+ useEffect(() => {
52
+ if (initializedRef.current) return;
53
+ initializedRef.current = true;
54
+
55
+ // Auto-reconnect is handled by createConfig, but we can
56
+ // trigger it here if needed for SSR hydration
57
+ if (config.autoConnect && typeof window !== 'undefined') {
58
+ const state = config.store.getState();
59
+ if (state.connectorId && state.status === 'disconnected') {
60
+ void state.reconnect(config.connectors);
61
+ }
62
+ }
63
+ }, [config]);
64
+
65
+ return (
66
+ <BtcWalletContext.Provider value={config}>
67
+ {children}
68
+ </BtcWalletContext.Provider>
69
+ );
70
+ }