otx-btc-wallet-core 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 ADDED
@@ -0,0 +1,312 @@
1
+ # otx-btc-wallet-core
2
+
3
+ Core state management, types, and configuration for the `otx-btc-wallet` library. This package provides the foundation that connectors and React hooks build on.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pnpm add otx-btc-wallet-core
9
+ ```
10
+
11
+ ## Overview
12
+
13
+ This package includes:
14
+
15
+ - **`createConfig()`** - Configuration factory that initializes the store and connectors
16
+ - **Zustand Store** - Reactive state management with persistence and auto-reconnect
17
+ - **TypeScript Types** - Full type definitions for connectors, accounts, networks, and PSBTs
18
+ - **PSBT Utilities** - Hex/Base64 conversion, validation, and sighash helpers
19
+
20
+ ## Usage
21
+
22
+ ### Create Config
23
+
24
+ ```typescript
25
+ import { createConfig } from 'otx-btc-wallet-core';
26
+ import { UnisatConnector, XverseConnector } from 'otx-btc-wallet-connectors';
27
+
28
+ const config = createConfig({
29
+ connectors: [
30
+ new UnisatConnector(),
31
+ new XverseConnector(),
32
+ ],
33
+ });
34
+ ```
35
+
36
+ ### Config Options
37
+
38
+ | Option | Type | Default | Description |
39
+ |--------|------|---------|-------------|
40
+ | `connectors` | `BitcoinConnector[]` | **(required)** | Wallet connectors to register |
41
+ | `autoConnect` | `boolean` | `true` | Auto-reconnect to last used wallet on page load |
42
+ | `storage` | `Storage` | `localStorage` | Storage backend for persistence |
43
+ | `storageKey` | `string` | `'optimex-btc-wallet.store'` | Key prefix for persisted state |
44
+
45
+ **Validation:**
46
+ - At least one connector must be provided
47
+ - Duplicate connector IDs will throw an error
48
+
49
+ ### Access the Store Directly
50
+
51
+ The config object contains a Zustand store you can use outside of React:
52
+
53
+ ```typescript
54
+ const config = createConfig({ /* ... */ });
55
+
56
+ // Read current state
57
+ const { status, account, connector, network } = config.store.getState();
58
+
59
+ // Subscribe to changes
60
+ const unsubscribe = config.store.subscribe(
61
+ (state) => state.account,
62
+ (account) => console.log('Account changed:', account),
63
+ );
64
+
65
+ // Perform actions
66
+ await config.store.getState().connect(connector);
67
+ await config.store.getState().disconnect();
68
+ ```
69
+
70
+ ## Types
71
+
72
+ ### `BitcoinNetwork`
73
+
74
+ ```typescript
75
+ type BitcoinNetwork = 'mainnet' | 'testnet' | 'testnet4' | 'signet';
76
+ ```
77
+
78
+ ### `AddressType`
79
+
80
+ ```typescript
81
+ type AddressType = 'legacy' | 'nested-segwit' | 'segwit' | 'taproot';
82
+ ```
83
+
84
+ ### `WalletAccount`
85
+
86
+ ```typescript
87
+ type WalletAccount = {
88
+ address: string;
89
+ publicKey: string;
90
+ type: AddressType;
91
+ };
92
+ ```
93
+
94
+ ### `MultiAddressAccount`
95
+
96
+ Used by wallets like Xverse that provide separate payment and ordinals addresses.
97
+
98
+ ```typescript
99
+ type MultiAddressAccount = {
100
+ payment: WalletAccount;
101
+ ordinals: WalletAccount;
102
+ };
103
+ ```
104
+
105
+ ### `BitcoinConnector`
106
+
107
+ The interface all wallet connectors must implement.
108
+
109
+ ```typescript
110
+ interface BitcoinConnector {
111
+ readonly id: string;
112
+ readonly name: string;
113
+ readonly icon: string;
114
+ ready: boolean;
115
+
116
+ connect(network?: BitcoinNetwork): Promise<WalletAccount>;
117
+ disconnect(): Promise<void>;
118
+ getAccounts(): Promise<WalletAccount[]>;
119
+ signMessage(message: string): Promise<string>;
120
+ signPsbt(psbtHex: string, options?: SignPsbtOptions): Promise<string>;
121
+ signPsbts?(psbtHexs: string[], options?: SignPsbtOptions): Promise<string[]>;
122
+ sendTransaction(to: string, satoshis: number): Promise<string>;
123
+ getNetwork(): Promise<BitcoinNetwork>;
124
+ switchNetwork?(network: BitcoinNetwork): Promise<void>;
125
+ onAccountsChanged(callback: (accounts: WalletAccount[]) => void): () => void;
126
+ onNetworkChanged(callback: (network: BitcoinNetwork) => void): () => void;
127
+ }
128
+ ```
129
+
130
+ ### `SignPsbtOptions`
131
+
132
+ ```typescript
133
+ type SignPsbtOptions = {
134
+ autoFinalize?: boolean; // Auto-finalize inputs after signing
135
+ broadcast?: boolean; // Broadcast the transaction after signing
136
+ toSignInputs?: SignInput[]; // Specify which inputs to sign
137
+ };
138
+ ```
139
+
140
+ ### `SignInput`
141
+
142
+ ```typescript
143
+ type SignInput = {
144
+ index: number; // Input index
145
+ address?: string; // Address owning this input
146
+ publicKey?: string; // Public key for this input
147
+ sighashTypes?: number[]; // Sighash types to use
148
+ disableTweakSigner?: boolean; // Disable tweaked signer (taproot)
149
+ };
150
+ ```
151
+
152
+ ### `ConnectionStatus`
153
+
154
+ ```typescript
155
+ type ConnectionStatus = 'disconnected' | 'connecting' | 'connected' | 'reconnecting';
156
+ ```
157
+
158
+ ### `BtcWalletState`
159
+
160
+ The full store state shape.
161
+
162
+ ```typescript
163
+ type BtcWalletState = {
164
+ status: ConnectionStatus;
165
+ account: WalletAccount | null;
166
+ connector: BitcoinConnector | null;
167
+ connectorId: string | null;
168
+ network: BitcoinNetwork;
169
+ error: Error | null;
170
+ };
171
+ ```
172
+
173
+ ### `BtcWalletConfig`
174
+
175
+ ```typescript
176
+ type BtcWalletConfig = {
177
+ connectors: BitcoinConnector[];
178
+ autoConnect?: boolean;
179
+ storage?: Storage;
180
+ storageKey?: string;
181
+ };
182
+ ```
183
+
184
+ ## Store
185
+
186
+ The store is built on [Zustand](https://github.com/pmndrs/zustand) with `persist` and `subscribeWithSelector` middleware.
187
+
188
+ ### State
189
+
190
+ | Field | Type | Description |
191
+ |-------|------|-------------|
192
+ | `status` | `ConnectionStatus` | Current connection status |
193
+ | `account` | `WalletAccount \| null` | Connected account info |
194
+ | `connector` | `BitcoinConnector \| null` | Active connector instance |
195
+ | `connectorId` | `string \| null` | ID of the active connector |
196
+ | `network` | `BitcoinNetwork` | Current network |
197
+ | `error` | `Error \| null` | Last error |
198
+
199
+ ### Actions
200
+
201
+ | Action | Signature | Description |
202
+ |--------|-----------|-------------|
203
+ | `connect` | `(connector, network?) => Promise<WalletAccount>` | Connect to a wallet |
204
+ | `disconnect` | `() => Promise<void>` | Disconnect current wallet |
205
+ | `reconnect` | `(connectors) => Promise<void>` | Reconnect to saved wallet |
206
+ | `setAccount` | `(account) => void` | Update account state |
207
+ | `setStatus` | `(status) => void` | Update connection status |
208
+ | `setError` | `(error) => void` | Set error state |
209
+ | `reset` | `() => void` | Reset to initial state |
210
+
211
+ ### Persistence
212
+
213
+ Only `connectorId` and `network` are persisted to storage. Account data is fetched fresh from the connector on reconnect.
214
+
215
+ ### Auto-Reconnect
216
+
217
+ When `autoConnect` is `true` (default), the store checks for a saved `connectorId` on initialization. If found, it automatically reconnects to that wallet using the `reconnect()` action.
218
+
219
+ ## PSBT Utilities
220
+
221
+ Lightweight PSBT helpers that work in both Node.js and browser environments.
222
+
223
+ ### Conversion
224
+
225
+ ```typescript
226
+ import { psbtHexToBase64, psbtBase64ToHex } from 'otx-btc-wallet-core';
227
+
228
+ const base64 = psbtHexToBase64(psbtHex);
229
+ const hex = psbtBase64ToHex(psbtBase64);
230
+ ```
231
+
232
+ ### Validation
233
+
234
+ ```typescript
235
+ import { isValidPsbtHex, isValidPsbtBase64 } from 'otx-btc-wallet-core';
236
+
237
+ isValidPsbtHex('70736274ff01...'); // true
238
+ isValidPsbtBase64('cHNidP8B...'); // true
239
+ ```
240
+
241
+ ### Info Extraction
242
+
243
+ ```typescript
244
+ import { getPsbtInfo } from 'otx-btc-wallet-core';
245
+
246
+ const info = getPsbtInfo(psbtHex);
247
+ // { isValid: boolean, version: number | null, inputCount: number | null, outputCount: number | null }
248
+ ```
249
+
250
+ ### Combine PSBTs
251
+
252
+ ```typescript
253
+ import { combinePsbts } from 'otx-btc-wallet-core';
254
+
255
+ const combined = combinePsbts([psbt1Hex, psbt2Hex]);
256
+ ```
257
+
258
+ ### Sighash Types
259
+
260
+ ```typescript
261
+ import { SighashType, getSighashTypeName } from 'otx-btc-wallet-core';
262
+
263
+ SighashType.ALL; // 0x01
264
+ SighashType.NONE; // 0x02
265
+ SighashType.SINGLE; // 0x03
266
+ SighashType.ANYONECANPAY; // 0x80
267
+ SighashType.ALL_ANYONECANPAY; // 0x81
268
+
269
+ getSighashTypeName(0x01); // 'ALL'
270
+ ```
271
+
272
+ ## Full Exports
273
+
274
+ ```typescript
275
+ // Config
276
+ export { createConfig } from './createConfig';
277
+
278
+ // Store
279
+ export { createStore } from './store';
280
+ export type { BtcWalletStore, Store, StoreActions } from './store';
281
+
282
+ // Types
283
+ export type {
284
+ BitcoinConnector,
285
+ WalletAccount,
286
+ AddressType,
287
+ MultiAddressAccount,
288
+ BitcoinNetwork,
289
+ SignPsbtOptions,
290
+ SignInput,
291
+ BtcWalletConfig,
292
+ Config,
293
+ BtcWalletState,
294
+ ConnectionStatus,
295
+ } from './types';
296
+
297
+ // PSBT Utilities
298
+ export {
299
+ psbtHexToBase64,
300
+ psbtBase64ToHex,
301
+ isValidPsbtHex,
302
+ isValidPsbtBase64,
303
+ getPsbtInfo,
304
+ combinePsbts,
305
+ SighashType,
306
+ getSighashTypeName,
307
+ } from './utils';
308
+ ```
309
+
310
+ ## License
311
+
312
+ MIT
@@ -0,0 +1,320 @@
1
+ import * as zustand_vanilla from 'zustand/vanilla';
2
+ import { PersistOptions } from 'zustand/middleware';
3
+
4
+ /**
5
+ * Bitcoin address types supported by wallets
6
+ */
7
+ type AddressType = 'legacy' | 'nested-segwit' | 'segwit' | 'taproot';
8
+ /**
9
+ * Wallet account information returned from connection
10
+ */
11
+ type WalletAccount = {
12
+ /** Bitcoin address */
13
+ address: string;
14
+ /** Hex-encoded public key */
15
+ publicKey: string;
16
+ /** Address format type */
17
+ type: AddressType;
18
+ };
19
+ /**
20
+ * Multi-address account for wallets supporting both payment and ordinals addresses
21
+ */
22
+ type MultiAddressAccount = {
23
+ /** Payment address for BTC transfers (typically segwit) */
24
+ payment: WalletAccount;
25
+ /** Ordinals address for Ordinals/Runes (typically taproot) */
26
+ ordinals: WalletAccount;
27
+ };
28
+
29
+ /**
30
+ * Supported Bitcoin networks
31
+ */
32
+ type BitcoinNetwork = 'mainnet' | 'testnet' | 'testnet4' | 'signet';
33
+
34
+ /**
35
+ * Options for signing a PSBT
36
+ */
37
+ type SignPsbtOptions = {
38
+ /** Auto-finalize inputs after signing (default: true) */
39
+ autoFinalize?: boolean;
40
+ /** Specific inputs to sign */
41
+ toSignInputs?: SignInput[];
42
+ /** Broadcast transaction after signing (where supported) */
43
+ broadcast?: boolean;
44
+ };
45
+ /**
46
+ * Configuration for signing a specific input
47
+ */
48
+ type SignInput = {
49
+ /** Input index to sign */
50
+ index: number;
51
+ /** Address to use for signing */
52
+ address?: string;
53
+ /** Public key to use for signing */
54
+ publicKey?: string;
55
+ /** Allowed sighash types */
56
+ sighashTypes?: number[];
57
+ /** Disable tweak signer for taproot key-path spends */
58
+ disableTweakSigner?: boolean;
59
+ };
60
+
61
+ /**
62
+ * Interface that all wallet connectors must implement
63
+ */
64
+ type NetworkType = 'mainnet' | 'testnet' | 'testnet4' | 'signet';
65
+ interface BitcoinConnector {
66
+ /** Unique identifier for the connector (e.g., 'unisat') */
67
+ readonly id: string;
68
+ /** Display name for the wallet (e.g., 'Unisat Wallet') */
69
+ readonly name: string;
70
+ /** Base64 encoded icon or URL to wallet icon */
71
+ readonly icon: string;
72
+ /** Whether the wallet extension is detected and ready */
73
+ ready: boolean;
74
+ /**
75
+ * Connect to the wallet
76
+ * @returns The connected wallet account
77
+ * @throws ConnectionError if connection fails
78
+ * @throws UserRejectedError if user rejects connection
79
+ */
80
+ connect(network?: NetworkType): Promise<WalletAccount>;
81
+ /**
82
+ * Disconnect from the wallet
83
+ */
84
+ disconnect(): Promise<void>;
85
+ /**
86
+ * Get all accounts from the wallet
87
+ * @returns Array of wallet accounts
88
+ */
89
+ getAccounts(): Promise<WalletAccount[]>;
90
+ /**
91
+ * Sign an arbitrary message
92
+ * @param message - Message to sign
93
+ * @returns Signature string
94
+ * @throws SigningError if signing fails
95
+ * @throws UserRejectedError if user rejects signing
96
+ */
97
+ signMessage(message: string): Promise<string>;
98
+ /**
99
+ * Sign a PSBT (Partially Signed Bitcoin Transaction)
100
+ * @param psbtHex - PSBT in hex format
101
+ * @param options - Signing options
102
+ * @returns Signed PSBT in hex format
103
+ * @throws SigningError if signing fails
104
+ * @throws UserRejectedError if user rejects signing
105
+ */
106
+ signPsbt(psbtHex: string, options?: SignPsbtOptions): Promise<string>;
107
+ /**
108
+ * Sign multiple PSBTs (optional, not all wallets support this)
109
+ * @param psbtHexs - Array of PSBTs in hex format
110
+ * @param options - Signing options
111
+ * @returns Array of signed PSBTs in hex format
112
+ */
113
+ signPsbts?(psbtHexs: string[], options?: SignPsbtOptions): Promise<string[]>;
114
+ /**
115
+ * Send a Bitcoin transaction
116
+ * @param to - Recipient address
117
+ * @param satoshis - Amount in satoshis
118
+ * @returns Transaction ID
119
+ * @throws SigningError if transaction fails
120
+ * @throws UserRejectedError if user rejects transaction
121
+ */
122
+ sendTransaction(to: string, satoshis: number): Promise<string>;
123
+ /**
124
+ * Get current network from the wallet
125
+ * @returns Current network
126
+ */
127
+ getNetwork(): Promise<BitcoinNetwork>;
128
+ /**
129
+ * Switch to a different network (optional, not all wallets support this)
130
+ * @param network - Network to switch to
131
+ * @throws NetworkError if switch fails
132
+ */
133
+ switchNetwork?(network: BitcoinNetwork): Promise<void>;
134
+ /**
135
+ * Subscribe to account changes
136
+ * @param callback - Called when accounts change
137
+ * @returns Unsubscribe function
138
+ */
139
+ onAccountsChanged(callback: (accounts: WalletAccount[]) => void): () => void;
140
+ /**
141
+ * Subscribe to network changes
142
+ * @param callback - Called when network changes
143
+ * @returns Unsubscribe function
144
+ */
145
+ onNetworkChanged(callback: (network: BitcoinNetwork) => void): () => void;
146
+ }
147
+
148
+ /**
149
+ * Configuration for otx-btc-wallet
150
+ */
151
+ type BtcWalletConfig = {
152
+ /** Array of wallet connectors to support */
153
+ connectors: BitcoinConnector[];
154
+ /** Whether to auto-connect on page load (default: true) */
155
+ autoConnect?: boolean;
156
+ /** Storage implementation for persistence (default: localStorage) */
157
+ storage?: Storage;
158
+ /** Key to use for storage (default: 'optimex-btc-wallet.store') */
159
+ storageKey?: string;
160
+ };
161
+ /**
162
+ * Internal config with resolved defaults
163
+ */
164
+ type Config = {
165
+ connectors: BitcoinConnector[];
166
+ autoConnect: boolean;
167
+ storage: Storage | null;
168
+ storageKey: string;
169
+ };
170
+
171
+ /**
172
+ * Connection status states
173
+ */
174
+ type ConnectionStatus = 'disconnected' | 'connecting' | 'connected' | 'reconnecting';
175
+ /**
176
+ * Store state shape
177
+ */
178
+ type BtcWalletState = {
179
+ /** Current connection status */
180
+ status: ConnectionStatus;
181
+ /** Connected account (null if disconnected) */
182
+ account: WalletAccount | null;
183
+ /** Active connector (null if disconnected) */
184
+ connector: BitcoinConnector | null;
185
+ /** Connected connector ID (for persistence) */
186
+ connectorId: string | null;
187
+ /** Current network */
188
+ network: BitcoinNetwork;
189
+ /** Last error (null if no error) */
190
+ error: Error | null;
191
+ };
192
+
193
+ /**
194
+ * Store actions
195
+ */
196
+ type StoreActions = {
197
+ /** Connect to a wallet */
198
+ connect: (connector: BitcoinConnector, network?: BitcoinNetwork) => Promise<WalletAccount>;
199
+ /** Disconnect from current wallet */
200
+ disconnect: () => Promise<void>;
201
+ /** Reconnect to previously connected wallet */
202
+ reconnect: (connectors: BitcoinConnector[]) => Promise<void>;
203
+ /** Set account */
204
+ setAccount: (account: WalletAccount | null) => void;
205
+ /** Set connection status */
206
+ setStatus: (status: ConnectionStatus) => void;
207
+ /** Set error */
208
+ setError: (error: Error | null) => void;
209
+ /** Reset store to initial state */
210
+ reset: () => void;
211
+ };
212
+ type Store = BtcWalletState & StoreActions;
213
+ /** Persisted state shape - only these fields are saved to storage */
214
+ type PersistedState = {
215
+ connectorId: string | null;
216
+ network: string;
217
+ };
218
+ /**
219
+ * Create the btc-wallet store
220
+ */
221
+ declare function createStore(config: Config): Omit<Omit<zustand_vanilla.StoreApi<Store>, "subscribe"> & {
222
+ subscribe: {
223
+ (listener: (selectedState: Store, previousSelectedState: Store) => void): () => void;
224
+ <U>(selector: (state: Store) => U, listener: (selectedState: U, previousSelectedState: U) => void, options?: {
225
+ equalityFn?: (a: U, b: U) => boolean;
226
+ fireImmediately?: boolean;
227
+ } | undefined): () => void;
228
+ };
229
+ }, "persist"> & {
230
+ persist: {
231
+ setOptions: (options: Partial<PersistOptions<Store, PersistedState>>) => void;
232
+ clearStorage: () => void;
233
+ rehydrate: () => void | Promise<void>;
234
+ hasHydrated: () => boolean;
235
+ onHydrate: (fn: (state: Store) => void) => () => void;
236
+ onFinishHydration: (fn: (state: Store) => void) => () => void;
237
+ getOptions: () => Partial<PersistOptions<Store, PersistedState>>;
238
+ };
239
+ };
240
+ type BtcWalletStore = ReturnType<typeof createStore>;
241
+
242
+ /**
243
+ * Resolved config with store instance
244
+ */
245
+ type ResolvedConfig = Config & {
246
+ store: BtcWalletStore;
247
+ };
248
+ /**
249
+ * Create otx-btc-wallet configuration
250
+ *
251
+ * @example
252
+ * ```ts
253
+ * import { createConfig } from 'otx-btc-wallet-core';
254
+ * import { UnisatConnector } from 'otx-btc-wallet-connectors/unisat';
255
+ *
256
+ * const config = createConfig({
257
+ * connectors: [new UnisatConnector()],
258
+ * autoConnect: true,
259
+ * });
260
+ * ```
261
+ */
262
+ declare function createConfig(options: BtcWalletConfig): ResolvedConfig;
263
+
264
+ /**
265
+ * PSBT (Partially Signed Bitcoin Transaction) utilities
266
+ */
267
+ /**
268
+ * Convert PSBT hex to base64
269
+ */
270
+ declare function psbtHexToBase64(hex: string): string;
271
+ /**
272
+ * Convert PSBT base64 to hex
273
+ */
274
+ declare function psbtBase64ToHex(base64: string): string;
275
+ /**
276
+ * Validate PSBT hex format
277
+ * PSBT magic bytes: 0x70736274ff (psbt\xff)
278
+ */
279
+ declare function isValidPsbtHex(hex: string): boolean;
280
+ /**
281
+ * Validate PSBT base64 format
282
+ */
283
+ declare function isValidPsbtBase64(base64: string): boolean;
284
+ /**
285
+ * Extract basic info from PSBT hex (without full parsing)
286
+ * This is a lightweight check - for full parsing use a library like bitcoinjs-lib
287
+ */
288
+ declare function getPsbtInfo(psbtHex: string): {
289
+ isValid: boolean;
290
+ version: number | null;
291
+ inputCount: number | null;
292
+ outputCount: number | null;
293
+ };
294
+ /**
295
+ * Combine multiple signed PSBTs into one
296
+ * Note: This is a basic implementation. For complex cases, use bitcoinjs-lib.
297
+ *
298
+ * @param psbts - Array of PSBT hex strings to combine
299
+ * @returns Combined PSBT hex
300
+ */
301
+ declare function combinePsbts(psbts: string[]): string;
302
+ /**
303
+ * Sighash types for Bitcoin transactions
304
+ */
305
+ declare const SighashType: {
306
+ readonly ALL: 1;
307
+ readonly NONE: 2;
308
+ readonly SINGLE: 3;
309
+ readonly ANYONECANPAY: 128;
310
+ readonly ALL_ANYONECANPAY: 129;
311
+ readonly NONE_ANYONECANPAY: 130;
312
+ readonly SINGLE_ANYONECANPAY: 131;
313
+ };
314
+ type SighashType = (typeof SighashType)[keyof typeof SighashType];
315
+ /**
316
+ * Get human-readable name for sighash type
317
+ */
318
+ declare function getSighashTypeName(sighash: number): string;
319
+
320
+ export { type AddressType, type BitcoinConnector, type BitcoinNetwork, type BtcWalletConfig, type BtcWalletState, type BtcWalletStore, type Config, type ConnectionStatus, type MultiAddressAccount, type ResolvedConfig, SighashType, type SignInput, type SignPsbtOptions, type Store, type StoreActions, type WalletAccount, combinePsbts, createConfig, createStore, getPsbtInfo, getSighashTypeName, isValidPsbtBase64, isValidPsbtHex, psbtBase64ToHex, psbtHexToBase64 };