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 +312 -0
- package/dist/index.d.mts +320 -0
- package/dist/index.d.ts +320 -0
- package/dist/index.js +342 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +331 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +41 -0
- package/src/createConfig.ts +80 -0
- package/src/index.ts +40 -0
- package/src/store/index.ts +296 -0
- package/src/types/account.ts +26 -0
- package/src/types/config.ts +28 -0
- package/src/types/connector.ts +109 -0
- package/src/types/index.ts +12 -0
- package/src/types/network.ts +4 -0
- package/src/types/psbt.ts +27 -0
- package/src/types/state.ts +35 -0
- package/src/utils/index.ts +1 -0
- package/src/utils/psbt.ts +185 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/store/index.ts","../src/createConfig.ts","../src/utils/psbt.ts"],"names":[],"mappings":"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","sourcesContent":["import { createStore as createZustandStore } from 'zustand/vanilla';\nimport { persist, subscribeWithSelector } from 'zustand/middleware';\nimport type { PersistOptions } from 'zustand/middleware';\nimport type { BitcoinConnector } from '../types/connector';\nimport type { WalletAccount } from '../types/account';\nimport type { BtcWalletState, ConnectionStatus } from '../types/state';\nimport type { BitcoinNetwork } from '../types/network';\nimport type { Config } from '../types/config';\n\n/**\n * Store actions\n */\nexport type StoreActions = {\n /** Connect to a wallet */\n connect: (\n connector: BitcoinConnector,\n network?: BitcoinNetwork\n ) => Promise<WalletAccount>;\n\n /** Disconnect from current wallet */\n disconnect: () => Promise<void>;\n\n /** Reconnect to previously connected wallet */\n reconnect: (connectors: BitcoinConnector[]) => Promise<void>;\n\n /** Set account */\n setAccount: (account: WalletAccount | null) => void;\n\n /** Set connection status */\n setStatus: (status: ConnectionStatus) => void;\n\n /** Set error */\n setError: (error: Error | null) => void;\n\n /** Reset store to initial state */\n reset: () => void;\n};\n\nexport type Store = BtcWalletState & StoreActions;\n\n/** Persisted state shape - only these fields are saved to storage */\ntype PersistedState = {\n connectorId: string | null;\n network: string;\n};\n\nconst initialState: BtcWalletState = {\n status: 'disconnected',\n account: null,\n connector: null,\n connectorId: null,\n network: 'mainnet',\n error: null,\n};\n\n/**\n * Create the btc-wallet store\n */\nexport function createStore(config: Config) {\n // Track cleanup functions for event listeners to prevent memory leaks\n let unsubscribeAccounts: (() => void) | null = null;\n let unsubscribeNetwork: (() => void) | null = null;\n\n /**\n * Cleanup all event listeners\n */\n const cleanupListeners = () => {\n if (unsubscribeAccounts) {\n unsubscribeAccounts();\n unsubscribeAccounts = null;\n }\n if (unsubscribeNetwork) {\n unsubscribeNetwork();\n unsubscribeNetwork = null;\n }\n };\n\n /**\n * Setup event listeners for a connector\n * This is shared between connect and reconnect to avoid code duplication\n */\n const setupConnectorListeners = (\n connector: BitcoinConnector,\n get: () => Store,\n set: (partial: Partial<Store>) => void\n ) => {\n // Clean up any existing listeners first\n cleanupListeners();\n\n // Subscribe to account changes\n unsubscribeAccounts = connector.onAccountsChanged((accounts) => {\n const currentState = get();\n\n // Only handle if this connector is still the active one\n if (currentState.connector?.id !== connector.id) {\n return;\n }\n\n if (accounts.length === 0) {\n // Wallet disconnected from extension side\n // Use catch to handle any errors silently\n get().disconnect().catch(() => {\n // Disconnect failed, but we should still clean up state\n set({\n status: 'disconnected',\n account: null,\n connector: null,\n connectorId: null,\n error: null,\n });\n });\n } else if (accounts[0]) {\n set({ account: accounts[0] });\n }\n });\n\n // Subscribe to network changes\n unsubscribeNetwork = connector.onNetworkChanged((network) => {\n const currentState = get();\n\n // Only handle if this connector is still the active one\n if (currentState.connector?.id !== connector.id) {\n return;\n }\n\n set({ network });\n });\n };\n\n // Define persist options with proper typing\n const persistOptions: PersistOptions<Store, PersistedState> = {\n name: config.storageKey,\n storage: config.storage\n ? {\n getItem: (name) => {\n const value = config.storage?.getItem(name);\n if (!value) return null;\n try {\n return JSON.parse(value) as { state: PersistedState };\n } catch {\n return null;\n }\n },\n setItem: (name, value) => {\n config.storage?.setItem(name, JSON.stringify(value));\n },\n removeItem: (name) => {\n config.storage?.removeItem(name);\n },\n }\n : undefined,\n // Only persist connectorId and network\n partialize: (state): PersistedState => ({\n connectorId: state.connectorId,\n network: state.network,\n }),\n };\n\n const store = createZustandStore<Store>()(\n subscribeWithSelector(\n persist(\n (set, get) => ({\n // Initial state\n ...initialState,\n\n // Actions\n connect: async (\n connector: BitcoinConnector,\n networkParam?: BitcoinNetwork\n ) => {\n const { status, network: storeNetwork } = get();\n const network = networkParam ?? storeNetwork;\n\n // Prevent duplicate connection attempts\n if (status === 'connecting') {\n throw new Error('Connection already in progress');\n }\n\n // Clean up any existing connection first\n cleanupListeners();\n\n set({ status: 'connecting', error: null, network });\n\n try {\n // Check if wallet is ready\n if (!connector.ready) {\n throw new Error(`${connector.name} wallet is not available`);\n }\n\n // Connect to wallet with network\n const account = await connector.connect(network);\n\n set({\n status: 'connected',\n account,\n connector,\n connectorId: connector.id,\n error: null,\n });\n\n // Setup event listeners\n setupConnectorListeners(connector, get, set);\n\n return account;\n } catch (error) {\n const err = error instanceof Error ? error : new Error(String(error));\n set({\n status: 'disconnected',\n error: err,\n });\n throw error;\n }\n },\n\n disconnect: async () => {\n const { connector } = get();\n\n // Clean up listeners first\n cleanupListeners();\n\n try {\n if (connector) {\n await connector.disconnect();\n }\n } catch {\n // Ignore disconnect errors (wallet might already be locked)\n } finally {\n set({\n status: 'disconnected',\n account: null,\n connector: null,\n connectorId: null,\n error: null,\n });\n }\n },\n\n reconnect: async (connectors: BitcoinConnector[]) => {\n const { connectorId, status, network } = get();\n\n // Only reconnect if we have a saved connector and not already connected\n if (!connectorId || status === 'connected') {\n return;\n }\n\n // Find the connector\n const connector = connectors.find((c) => c.id === connectorId);\n if (!connector?.ready) {\n // Clear saved connector if not available\n set({ connectorId: null });\n return;\n }\n\n set({ status: 'reconnecting' });\n\n try {\n const account = await connector.connect(network);\n\n set({\n status: 'connected',\n account,\n connector,\n error: null,\n });\n\n // Setup event listeners (reusing shared function)\n setupConnectorListeners(connector, get, set);\n } catch {\n // Reconnect failed silently, clear state\n set({\n status: 'disconnected',\n connectorId: null,\n });\n }\n },\n\n setAccount: (account) => set({ account }),\n\n setStatus: (status) => set({ status }),\n\n setError: (error) => set({ error }),\n\n reset: () => {\n cleanupListeners();\n set(initialState);\n },\n }),\n persistOptions\n )\n )\n );\n\n return store;\n}\n\nexport type BtcWalletStore = ReturnType<typeof createStore>;\n","import type { BtcWalletConfig, Config } from './types/config';\nimport { createStore, type BtcWalletStore } from './store';\n\n/**\n * Resolved config with store instance\n */\nexport type ResolvedConfig = Config & {\n store: BtcWalletStore;\n};\n\n/**\n * Create otx-btc-wallet configuration\n *\n * @example\n * ```ts\n * import { createConfig } from 'otx-btc-wallet-core';\n * import { UnisatConnector } from 'otx-btc-wallet-connectors/unisat';\n *\n * const config = createConfig({\n * connectors: [new UnisatConnector()],\n * autoConnect: true,\n * });\n * ```\n */\nexport function createConfig(options: BtcWalletConfig): ResolvedConfig {\n // Validate connectors\n if (!options.connectors || options.connectors.length === 0) {\n throw new Error('At least one connector is required');\n }\n\n // Check for duplicate connector IDs\n const connectorIds = new Set<string>();\n for (const connector of options.connectors) {\n if (connectorIds.has(connector.id)) {\n throw new Error(`Duplicate connector ID: ${connector.id}`);\n }\n connectorIds.add(connector.id);\n }\n\n // Resolve defaults\n const config: Config = {\n connectors: options.connectors,\n autoConnect: options.autoConnect ?? true,\n storage: getStorage(options.storage),\n storageKey: options.storageKey ?? 'optimex-btc-wallet.store',\n };\n\n // Create store\n const store = createStore(config);\n\n // Auto-connect if enabled\n if (config.autoConnect) {\n // Use setTimeout to ensure this runs after store is fully initialized\n setTimeout(() => {\n void store.getState().reconnect(config.connectors);\n }, 0);\n }\n\n return {\n ...config,\n store,\n };\n}\n\n/**\n * Get storage implementation\n */\nfunction getStorage(storage?: Storage): Storage | null {\n // If explicitly provided, use it\n if (storage !== undefined) {\n return storage;\n }\n\n // Default to localStorage if available\n if (typeof window !== 'undefined' && window.localStorage) {\n return window.localStorage;\n }\n\n return null;\n}\n","/**\n * PSBT (Partially Signed Bitcoin Transaction) utilities\n */\n\n/**\n * Convert PSBT hex to base64\n */\nexport function psbtHexToBase64(hex: string): string {\n // Remove any whitespace\n const cleanHex = hex.replace(/\\s/g, '');\n\n // Validate hex string\n if (!/^[0-9a-fA-F]*$/.test(cleanHex)) {\n throw new Error('Invalid hex string');\n }\n\n if (cleanHex.length % 2 !== 0) {\n throw new Error('Hex string must have even length');\n }\n\n // Convert hex to bytes then to base64\n const bytes = new Uint8Array(cleanHex.length / 2);\n for (let i = 0; i < cleanHex.length; i += 2) {\n bytes[i / 2] = parseInt(cleanHex.substring(i, i + 2), 16);\n }\n\n // Use Buffer in Node.js or btoa in browser\n if (typeof Buffer !== 'undefined') {\n return Buffer.from(bytes).toString('base64');\n }\n\n // Browser fallback\n let binary = '';\n for (let i = 0; i < bytes.length; i++) {\n binary += String.fromCharCode(bytes[i]!);\n }\n return btoa(binary);\n}\n\n/**\n * Convert PSBT base64 to hex\n */\nexport function psbtBase64ToHex(base64: string): string {\n // Decode base64 to bytes\n let bytes: Uint8Array;\n\n if (typeof Buffer !== 'undefined') {\n bytes = Buffer.from(base64, 'base64');\n } else {\n // Browser fallback\n const binary = atob(base64);\n bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) {\n bytes[i] = binary.charCodeAt(i);\n }\n }\n\n // Convert bytes to hex\n return Array.from(bytes)\n .map((b) => b.toString(16).padStart(2, '0'))\n .join('');\n}\n\n/**\n * Validate PSBT hex format\n * PSBT magic bytes: 0x70736274ff (psbt\\xff)\n */\nexport function isValidPsbtHex(hex: string): boolean {\n const cleanHex = hex.replace(/\\s/g, '').toLowerCase();\n\n // Check minimum length (magic bytes = 5 bytes = 10 hex chars)\n if (cleanHex.length < 10) {\n return false;\n }\n\n // Check magic bytes\n return cleanHex.startsWith('70736274ff');\n}\n\n/**\n * Validate PSBT base64 format\n */\nexport function isValidPsbtBase64(base64: string): boolean {\n try {\n const hex = psbtBase64ToHex(base64);\n return isValidPsbtHex(hex);\n } catch {\n return false;\n }\n}\n\n/**\n * Extract basic info from PSBT hex (without full parsing)\n * This is a lightweight check - for full parsing use a library like bitcoinjs-lib\n */\nexport function getPsbtInfo(psbtHex: string): {\n isValid: boolean;\n version: number | null;\n inputCount: number | null;\n outputCount: number | null;\n} {\n if (!isValidPsbtHex(psbtHex)) {\n return {\n isValid: false,\n version: null,\n inputCount: null,\n outputCount: null,\n };\n }\n\n // PSBT structure is complex, return basic validation only\n // For full parsing, use bitcoinjs-lib or similar\n return {\n isValid: true,\n version: null, // Would need full parsing\n inputCount: null, // Would need full parsing\n outputCount: null, // Would need full parsing\n };\n}\n\n/**\n * Combine multiple signed PSBTs into one\n * Note: This is a basic implementation. For complex cases, use bitcoinjs-lib.\n *\n * @param psbts - Array of PSBT hex strings to combine\n * @returns Combined PSBT hex\n */\nexport function combinePsbts(psbts: string[]): string {\n if (psbts.length === 0) {\n throw new Error('No PSBTs provided');\n }\n\n if (psbts.length === 1) {\n return psbts[0]!;\n }\n\n // For proper PSBT combination, you need bitcoinjs-lib\n // This is just a placeholder that returns the first PSBT\n // In production, use: bitcoin.Psbt.combine([psbt1, psbt2, ...])\n console.warn(\n 'combinePsbts: For proper PSBT combination, use bitcoinjs-lib. ' +\n 'This function returns the first PSBT as a fallback.'\n );\n\n return psbts[0]!;\n}\n\n/**\n * Sighash types for Bitcoin transactions\n */\nexport const SighashType = {\n ALL: 0x01,\n NONE: 0x02,\n SINGLE: 0x03,\n ANYONECANPAY: 0x80,\n ALL_ANYONECANPAY: 0x81,\n NONE_ANYONECANPAY: 0x82,\n SINGLE_ANYONECANPAY: 0x83,\n} as const;\n\nexport type SighashType = (typeof SighashType)[keyof typeof SighashType];\n\n/**\n * Get human-readable name for sighash type\n */\nexport function getSighashTypeName(sighash: number): string {\n switch (sighash) {\n case SighashType.ALL:\n return 'SIGHASH_ALL';\n case SighashType.NONE:\n return 'SIGHASH_NONE';\n case SighashType.SINGLE:\n return 'SIGHASH_SINGLE';\n case SighashType.ANYONECANPAY:\n return 'SIGHASH_ANYONECANPAY';\n case SighashType.ALL_ANYONECANPAY:\n return 'SIGHASH_ALL|ANYONECANPAY';\n case SighashType.NONE_ANYONECANPAY:\n return 'SIGHASH_NONE|ANYONECANPAY';\n case SighashType.SINGLE_ANYONECANPAY:\n return 'SIGHASH_SINGLE|ANYONECANPAY';\n default:\n return `UNKNOWN(${sighash})`;\n }\n}\n"]}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
import { createStore as createStore$1 } from 'zustand/vanilla';
|
|
2
|
+
import { subscribeWithSelector, persist } from 'zustand/middleware';
|
|
3
|
+
|
|
4
|
+
// src/store/index.ts
|
|
5
|
+
var initialState = {
|
|
6
|
+
status: "disconnected",
|
|
7
|
+
account: null,
|
|
8
|
+
connector: null,
|
|
9
|
+
connectorId: null,
|
|
10
|
+
network: "mainnet",
|
|
11
|
+
error: null
|
|
12
|
+
};
|
|
13
|
+
function createStore(config) {
|
|
14
|
+
let unsubscribeAccounts = null;
|
|
15
|
+
let unsubscribeNetwork = null;
|
|
16
|
+
const cleanupListeners = () => {
|
|
17
|
+
if (unsubscribeAccounts) {
|
|
18
|
+
unsubscribeAccounts();
|
|
19
|
+
unsubscribeAccounts = null;
|
|
20
|
+
}
|
|
21
|
+
if (unsubscribeNetwork) {
|
|
22
|
+
unsubscribeNetwork();
|
|
23
|
+
unsubscribeNetwork = null;
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
const setupConnectorListeners = (connector, get, set) => {
|
|
27
|
+
cleanupListeners();
|
|
28
|
+
unsubscribeAccounts = connector.onAccountsChanged((accounts) => {
|
|
29
|
+
const currentState = get();
|
|
30
|
+
if (currentState.connector?.id !== connector.id) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
if (accounts.length === 0) {
|
|
34
|
+
get().disconnect().catch(() => {
|
|
35
|
+
set({
|
|
36
|
+
status: "disconnected",
|
|
37
|
+
account: null,
|
|
38
|
+
connector: null,
|
|
39
|
+
connectorId: null,
|
|
40
|
+
error: null
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
} else if (accounts[0]) {
|
|
44
|
+
set({ account: accounts[0] });
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
unsubscribeNetwork = connector.onNetworkChanged((network) => {
|
|
48
|
+
const currentState = get();
|
|
49
|
+
if (currentState.connector?.id !== connector.id) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
set({ network });
|
|
53
|
+
});
|
|
54
|
+
};
|
|
55
|
+
const persistOptions = {
|
|
56
|
+
name: config.storageKey,
|
|
57
|
+
storage: config.storage ? {
|
|
58
|
+
getItem: (name) => {
|
|
59
|
+
const value = config.storage?.getItem(name);
|
|
60
|
+
if (!value)
|
|
61
|
+
return null;
|
|
62
|
+
try {
|
|
63
|
+
return JSON.parse(value);
|
|
64
|
+
} catch {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
setItem: (name, value) => {
|
|
69
|
+
config.storage?.setItem(name, JSON.stringify(value));
|
|
70
|
+
},
|
|
71
|
+
removeItem: (name) => {
|
|
72
|
+
config.storage?.removeItem(name);
|
|
73
|
+
}
|
|
74
|
+
} : void 0,
|
|
75
|
+
// Only persist connectorId and network
|
|
76
|
+
partialize: (state) => ({
|
|
77
|
+
connectorId: state.connectorId,
|
|
78
|
+
network: state.network
|
|
79
|
+
})
|
|
80
|
+
};
|
|
81
|
+
const store = createStore$1()(
|
|
82
|
+
subscribeWithSelector(
|
|
83
|
+
persist(
|
|
84
|
+
(set, get) => ({
|
|
85
|
+
// Initial state
|
|
86
|
+
...initialState,
|
|
87
|
+
// Actions
|
|
88
|
+
connect: async (connector, networkParam) => {
|
|
89
|
+
const { status, network: storeNetwork } = get();
|
|
90
|
+
const network = networkParam ?? storeNetwork;
|
|
91
|
+
if (status === "connecting") {
|
|
92
|
+
throw new Error("Connection already in progress");
|
|
93
|
+
}
|
|
94
|
+
cleanupListeners();
|
|
95
|
+
set({ status: "connecting", error: null, network });
|
|
96
|
+
try {
|
|
97
|
+
if (!connector.ready) {
|
|
98
|
+
throw new Error(`${connector.name} wallet is not available`);
|
|
99
|
+
}
|
|
100
|
+
const account = await connector.connect(network);
|
|
101
|
+
set({
|
|
102
|
+
status: "connected",
|
|
103
|
+
account,
|
|
104
|
+
connector,
|
|
105
|
+
connectorId: connector.id,
|
|
106
|
+
error: null
|
|
107
|
+
});
|
|
108
|
+
setupConnectorListeners(connector, get, set);
|
|
109
|
+
return account;
|
|
110
|
+
} catch (error) {
|
|
111
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
112
|
+
set({
|
|
113
|
+
status: "disconnected",
|
|
114
|
+
error: err
|
|
115
|
+
});
|
|
116
|
+
throw error;
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
disconnect: async () => {
|
|
120
|
+
const { connector } = get();
|
|
121
|
+
cleanupListeners();
|
|
122
|
+
try {
|
|
123
|
+
if (connector) {
|
|
124
|
+
await connector.disconnect();
|
|
125
|
+
}
|
|
126
|
+
} catch {
|
|
127
|
+
} finally {
|
|
128
|
+
set({
|
|
129
|
+
status: "disconnected",
|
|
130
|
+
account: null,
|
|
131
|
+
connector: null,
|
|
132
|
+
connectorId: null,
|
|
133
|
+
error: null
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
reconnect: async (connectors) => {
|
|
138
|
+
const { connectorId, status, network } = get();
|
|
139
|
+
if (!connectorId || status === "connected") {
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
const connector = connectors.find((c) => c.id === connectorId);
|
|
143
|
+
if (!connector?.ready) {
|
|
144
|
+
set({ connectorId: null });
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
set({ status: "reconnecting" });
|
|
148
|
+
try {
|
|
149
|
+
const account = await connector.connect(network);
|
|
150
|
+
set({
|
|
151
|
+
status: "connected",
|
|
152
|
+
account,
|
|
153
|
+
connector,
|
|
154
|
+
error: null
|
|
155
|
+
});
|
|
156
|
+
setupConnectorListeners(connector, get, set);
|
|
157
|
+
} catch {
|
|
158
|
+
set({
|
|
159
|
+
status: "disconnected",
|
|
160
|
+
connectorId: null
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
setAccount: (account) => set({ account }),
|
|
165
|
+
setStatus: (status) => set({ status }),
|
|
166
|
+
setError: (error) => set({ error }),
|
|
167
|
+
reset: () => {
|
|
168
|
+
cleanupListeners();
|
|
169
|
+
set(initialState);
|
|
170
|
+
}
|
|
171
|
+
}),
|
|
172
|
+
persistOptions
|
|
173
|
+
)
|
|
174
|
+
)
|
|
175
|
+
);
|
|
176
|
+
return store;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// src/createConfig.ts
|
|
180
|
+
function createConfig(options) {
|
|
181
|
+
if (!options.connectors || options.connectors.length === 0) {
|
|
182
|
+
throw new Error("At least one connector is required");
|
|
183
|
+
}
|
|
184
|
+
const connectorIds = /* @__PURE__ */ new Set();
|
|
185
|
+
for (const connector of options.connectors) {
|
|
186
|
+
if (connectorIds.has(connector.id)) {
|
|
187
|
+
throw new Error(`Duplicate connector ID: ${connector.id}`);
|
|
188
|
+
}
|
|
189
|
+
connectorIds.add(connector.id);
|
|
190
|
+
}
|
|
191
|
+
const config = {
|
|
192
|
+
connectors: options.connectors,
|
|
193
|
+
autoConnect: options.autoConnect ?? true,
|
|
194
|
+
storage: getStorage(options.storage),
|
|
195
|
+
storageKey: options.storageKey ?? "optimex-btc-wallet.store"
|
|
196
|
+
};
|
|
197
|
+
const store = createStore(config);
|
|
198
|
+
if (config.autoConnect) {
|
|
199
|
+
setTimeout(() => {
|
|
200
|
+
void store.getState().reconnect(config.connectors);
|
|
201
|
+
}, 0);
|
|
202
|
+
}
|
|
203
|
+
return {
|
|
204
|
+
...config,
|
|
205
|
+
store
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
function getStorage(storage) {
|
|
209
|
+
if (storage !== void 0) {
|
|
210
|
+
return storage;
|
|
211
|
+
}
|
|
212
|
+
if (typeof window !== "undefined" && window.localStorage) {
|
|
213
|
+
return window.localStorage;
|
|
214
|
+
}
|
|
215
|
+
return null;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// src/utils/psbt.ts
|
|
219
|
+
function psbtHexToBase64(hex) {
|
|
220
|
+
const cleanHex = hex.replace(/\s/g, "");
|
|
221
|
+
if (!/^[0-9a-fA-F]*$/.test(cleanHex)) {
|
|
222
|
+
throw new Error("Invalid hex string");
|
|
223
|
+
}
|
|
224
|
+
if (cleanHex.length % 2 !== 0) {
|
|
225
|
+
throw new Error("Hex string must have even length");
|
|
226
|
+
}
|
|
227
|
+
const bytes = new Uint8Array(cleanHex.length / 2);
|
|
228
|
+
for (let i = 0; i < cleanHex.length; i += 2) {
|
|
229
|
+
bytes[i / 2] = parseInt(cleanHex.substring(i, i + 2), 16);
|
|
230
|
+
}
|
|
231
|
+
if (typeof Buffer !== "undefined") {
|
|
232
|
+
return Buffer.from(bytes).toString("base64");
|
|
233
|
+
}
|
|
234
|
+
let binary = "";
|
|
235
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
236
|
+
binary += String.fromCharCode(bytes[i]);
|
|
237
|
+
}
|
|
238
|
+
return btoa(binary);
|
|
239
|
+
}
|
|
240
|
+
function psbtBase64ToHex(base64) {
|
|
241
|
+
let bytes;
|
|
242
|
+
if (typeof Buffer !== "undefined") {
|
|
243
|
+
bytes = Buffer.from(base64, "base64");
|
|
244
|
+
} else {
|
|
245
|
+
const binary = atob(base64);
|
|
246
|
+
bytes = new Uint8Array(binary.length);
|
|
247
|
+
for (let i = 0; i < binary.length; i++) {
|
|
248
|
+
bytes[i] = binary.charCodeAt(i);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
252
|
+
}
|
|
253
|
+
function isValidPsbtHex(hex) {
|
|
254
|
+
const cleanHex = hex.replace(/\s/g, "").toLowerCase();
|
|
255
|
+
if (cleanHex.length < 10) {
|
|
256
|
+
return false;
|
|
257
|
+
}
|
|
258
|
+
return cleanHex.startsWith("70736274ff");
|
|
259
|
+
}
|
|
260
|
+
function isValidPsbtBase64(base64) {
|
|
261
|
+
try {
|
|
262
|
+
const hex = psbtBase64ToHex(base64);
|
|
263
|
+
return isValidPsbtHex(hex);
|
|
264
|
+
} catch {
|
|
265
|
+
return false;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
function getPsbtInfo(psbtHex) {
|
|
269
|
+
if (!isValidPsbtHex(psbtHex)) {
|
|
270
|
+
return {
|
|
271
|
+
isValid: false,
|
|
272
|
+
version: null,
|
|
273
|
+
inputCount: null,
|
|
274
|
+
outputCount: null
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
return {
|
|
278
|
+
isValid: true,
|
|
279
|
+
version: null,
|
|
280
|
+
// Would need full parsing
|
|
281
|
+
inputCount: null,
|
|
282
|
+
// Would need full parsing
|
|
283
|
+
outputCount: null
|
|
284
|
+
// Would need full parsing
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
function combinePsbts(psbts) {
|
|
288
|
+
if (psbts.length === 0) {
|
|
289
|
+
throw new Error("No PSBTs provided");
|
|
290
|
+
}
|
|
291
|
+
if (psbts.length === 1) {
|
|
292
|
+
return psbts[0];
|
|
293
|
+
}
|
|
294
|
+
console.warn(
|
|
295
|
+
"combinePsbts: For proper PSBT combination, use bitcoinjs-lib. This function returns the first PSBT as a fallback."
|
|
296
|
+
);
|
|
297
|
+
return psbts[0];
|
|
298
|
+
}
|
|
299
|
+
var SighashType = {
|
|
300
|
+
ALL: 1,
|
|
301
|
+
NONE: 2,
|
|
302
|
+
SINGLE: 3,
|
|
303
|
+
ANYONECANPAY: 128,
|
|
304
|
+
ALL_ANYONECANPAY: 129,
|
|
305
|
+
NONE_ANYONECANPAY: 130,
|
|
306
|
+
SINGLE_ANYONECANPAY: 131
|
|
307
|
+
};
|
|
308
|
+
function getSighashTypeName(sighash) {
|
|
309
|
+
switch (sighash) {
|
|
310
|
+
case SighashType.ALL:
|
|
311
|
+
return "SIGHASH_ALL";
|
|
312
|
+
case SighashType.NONE:
|
|
313
|
+
return "SIGHASH_NONE";
|
|
314
|
+
case SighashType.SINGLE:
|
|
315
|
+
return "SIGHASH_SINGLE";
|
|
316
|
+
case SighashType.ANYONECANPAY:
|
|
317
|
+
return "SIGHASH_ANYONECANPAY";
|
|
318
|
+
case SighashType.ALL_ANYONECANPAY:
|
|
319
|
+
return "SIGHASH_ALL|ANYONECANPAY";
|
|
320
|
+
case SighashType.NONE_ANYONECANPAY:
|
|
321
|
+
return "SIGHASH_NONE|ANYONECANPAY";
|
|
322
|
+
case SighashType.SINGLE_ANYONECANPAY:
|
|
323
|
+
return "SIGHASH_SINGLE|ANYONECANPAY";
|
|
324
|
+
default:
|
|
325
|
+
return `UNKNOWN(${sighash})`;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
export { SighashType, combinePsbts, createConfig, createStore, getPsbtInfo, getSighashTypeName, isValidPsbtBase64, isValidPsbtHex, psbtBase64ToHex, psbtHexToBase64 };
|
|
330
|
+
//# sourceMappingURL=out.js.map
|
|
331
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/store/index.ts","../src/createConfig.ts","../src/utils/psbt.ts"],"names":[],"mappings":"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","sourcesContent":["import { createStore as createZustandStore } from 'zustand/vanilla';\nimport { persist, subscribeWithSelector } from 'zustand/middleware';\nimport type { PersistOptions } from 'zustand/middleware';\nimport type { BitcoinConnector } from '../types/connector';\nimport type { WalletAccount } from '../types/account';\nimport type { BtcWalletState, ConnectionStatus } from '../types/state';\nimport type { BitcoinNetwork } from '../types/network';\nimport type { Config } from '../types/config';\n\n/**\n * Store actions\n */\nexport type StoreActions = {\n /** Connect to a wallet */\n connect: (\n connector: BitcoinConnector,\n network?: BitcoinNetwork\n ) => Promise<WalletAccount>;\n\n /** Disconnect from current wallet */\n disconnect: () => Promise<void>;\n\n /** Reconnect to previously connected wallet */\n reconnect: (connectors: BitcoinConnector[]) => Promise<void>;\n\n /** Set account */\n setAccount: (account: WalletAccount | null) => void;\n\n /** Set connection status */\n setStatus: (status: ConnectionStatus) => void;\n\n /** Set error */\n setError: (error: Error | null) => void;\n\n /** Reset store to initial state */\n reset: () => void;\n};\n\nexport type Store = BtcWalletState & StoreActions;\n\n/** Persisted state shape - only these fields are saved to storage */\ntype PersistedState = {\n connectorId: string | null;\n network: string;\n};\n\nconst initialState: BtcWalletState = {\n status: 'disconnected',\n account: null,\n connector: null,\n connectorId: null,\n network: 'mainnet',\n error: null,\n};\n\n/**\n * Create the btc-wallet store\n */\nexport function createStore(config: Config) {\n // Track cleanup functions for event listeners to prevent memory leaks\n let unsubscribeAccounts: (() => void) | null = null;\n let unsubscribeNetwork: (() => void) | null = null;\n\n /**\n * Cleanup all event listeners\n */\n const cleanupListeners = () => {\n if (unsubscribeAccounts) {\n unsubscribeAccounts();\n unsubscribeAccounts = null;\n }\n if (unsubscribeNetwork) {\n unsubscribeNetwork();\n unsubscribeNetwork = null;\n }\n };\n\n /**\n * Setup event listeners for a connector\n * This is shared between connect and reconnect to avoid code duplication\n */\n const setupConnectorListeners = (\n connector: BitcoinConnector,\n get: () => Store,\n set: (partial: Partial<Store>) => void\n ) => {\n // Clean up any existing listeners first\n cleanupListeners();\n\n // Subscribe to account changes\n unsubscribeAccounts = connector.onAccountsChanged((accounts) => {\n const currentState = get();\n\n // Only handle if this connector is still the active one\n if (currentState.connector?.id !== connector.id) {\n return;\n }\n\n if (accounts.length === 0) {\n // Wallet disconnected from extension side\n // Use catch to handle any errors silently\n get().disconnect().catch(() => {\n // Disconnect failed, but we should still clean up state\n set({\n status: 'disconnected',\n account: null,\n connector: null,\n connectorId: null,\n error: null,\n });\n });\n } else if (accounts[0]) {\n set({ account: accounts[0] });\n }\n });\n\n // Subscribe to network changes\n unsubscribeNetwork = connector.onNetworkChanged((network) => {\n const currentState = get();\n\n // Only handle if this connector is still the active one\n if (currentState.connector?.id !== connector.id) {\n return;\n }\n\n set({ network });\n });\n };\n\n // Define persist options with proper typing\n const persistOptions: PersistOptions<Store, PersistedState> = {\n name: config.storageKey,\n storage: config.storage\n ? {\n getItem: (name) => {\n const value = config.storage?.getItem(name);\n if (!value) return null;\n try {\n return JSON.parse(value) as { state: PersistedState };\n } catch {\n return null;\n }\n },\n setItem: (name, value) => {\n config.storage?.setItem(name, JSON.stringify(value));\n },\n removeItem: (name) => {\n config.storage?.removeItem(name);\n },\n }\n : undefined,\n // Only persist connectorId and network\n partialize: (state): PersistedState => ({\n connectorId: state.connectorId,\n network: state.network,\n }),\n };\n\n const store = createZustandStore<Store>()(\n subscribeWithSelector(\n persist(\n (set, get) => ({\n // Initial state\n ...initialState,\n\n // Actions\n connect: async (\n connector: BitcoinConnector,\n networkParam?: BitcoinNetwork\n ) => {\n const { status, network: storeNetwork } = get();\n const network = networkParam ?? storeNetwork;\n\n // Prevent duplicate connection attempts\n if (status === 'connecting') {\n throw new Error('Connection already in progress');\n }\n\n // Clean up any existing connection first\n cleanupListeners();\n\n set({ status: 'connecting', error: null, network });\n\n try {\n // Check if wallet is ready\n if (!connector.ready) {\n throw new Error(`${connector.name} wallet is not available`);\n }\n\n // Connect to wallet with network\n const account = await connector.connect(network);\n\n set({\n status: 'connected',\n account,\n connector,\n connectorId: connector.id,\n error: null,\n });\n\n // Setup event listeners\n setupConnectorListeners(connector, get, set);\n\n return account;\n } catch (error) {\n const err = error instanceof Error ? error : new Error(String(error));\n set({\n status: 'disconnected',\n error: err,\n });\n throw error;\n }\n },\n\n disconnect: async () => {\n const { connector } = get();\n\n // Clean up listeners first\n cleanupListeners();\n\n try {\n if (connector) {\n await connector.disconnect();\n }\n } catch {\n // Ignore disconnect errors (wallet might already be locked)\n } finally {\n set({\n status: 'disconnected',\n account: null,\n connector: null,\n connectorId: null,\n error: null,\n });\n }\n },\n\n reconnect: async (connectors: BitcoinConnector[]) => {\n const { connectorId, status, network } = get();\n\n // Only reconnect if we have a saved connector and not already connected\n if (!connectorId || status === 'connected') {\n return;\n }\n\n // Find the connector\n const connector = connectors.find((c) => c.id === connectorId);\n if (!connector?.ready) {\n // Clear saved connector if not available\n set({ connectorId: null });\n return;\n }\n\n set({ status: 'reconnecting' });\n\n try {\n const account = await connector.connect(network);\n\n set({\n status: 'connected',\n account,\n connector,\n error: null,\n });\n\n // Setup event listeners (reusing shared function)\n setupConnectorListeners(connector, get, set);\n } catch {\n // Reconnect failed silently, clear state\n set({\n status: 'disconnected',\n connectorId: null,\n });\n }\n },\n\n setAccount: (account) => set({ account }),\n\n setStatus: (status) => set({ status }),\n\n setError: (error) => set({ error }),\n\n reset: () => {\n cleanupListeners();\n set(initialState);\n },\n }),\n persistOptions\n )\n )\n );\n\n return store;\n}\n\nexport type BtcWalletStore = ReturnType<typeof createStore>;\n","import type { BtcWalletConfig, Config } from './types/config';\nimport { createStore, type BtcWalletStore } from './store';\n\n/**\n * Resolved config with store instance\n */\nexport type ResolvedConfig = Config & {\n store: BtcWalletStore;\n};\n\n/**\n * Create otx-btc-wallet configuration\n *\n * @example\n * ```ts\n * import { createConfig } from 'otx-btc-wallet-core';\n * import { UnisatConnector } from 'otx-btc-wallet-connectors/unisat';\n *\n * const config = createConfig({\n * connectors: [new UnisatConnector()],\n * autoConnect: true,\n * });\n * ```\n */\nexport function createConfig(options: BtcWalletConfig): ResolvedConfig {\n // Validate connectors\n if (!options.connectors || options.connectors.length === 0) {\n throw new Error('At least one connector is required');\n }\n\n // Check for duplicate connector IDs\n const connectorIds = new Set<string>();\n for (const connector of options.connectors) {\n if (connectorIds.has(connector.id)) {\n throw new Error(`Duplicate connector ID: ${connector.id}`);\n }\n connectorIds.add(connector.id);\n }\n\n // Resolve defaults\n const config: Config = {\n connectors: options.connectors,\n autoConnect: options.autoConnect ?? true,\n storage: getStorage(options.storage),\n storageKey: options.storageKey ?? 'optimex-btc-wallet.store',\n };\n\n // Create store\n const store = createStore(config);\n\n // Auto-connect if enabled\n if (config.autoConnect) {\n // Use setTimeout to ensure this runs after store is fully initialized\n setTimeout(() => {\n void store.getState().reconnect(config.connectors);\n }, 0);\n }\n\n return {\n ...config,\n store,\n };\n}\n\n/**\n * Get storage implementation\n */\nfunction getStorage(storage?: Storage): Storage | null {\n // If explicitly provided, use it\n if (storage !== undefined) {\n return storage;\n }\n\n // Default to localStorage if available\n if (typeof window !== 'undefined' && window.localStorage) {\n return window.localStorage;\n }\n\n return null;\n}\n","/**\n * PSBT (Partially Signed Bitcoin Transaction) utilities\n */\n\n/**\n * Convert PSBT hex to base64\n */\nexport function psbtHexToBase64(hex: string): string {\n // Remove any whitespace\n const cleanHex = hex.replace(/\\s/g, '');\n\n // Validate hex string\n if (!/^[0-9a-fA-F]*$/.test(cleanHex)) {\n throw new Error('Invalid hex string');\n }\n\n if (cleanHex.length % 2 !== 0) {\n throw new Error('Hex string must have even length');\n }\n\n // Convert hex to bytes then to base64\n const bytes = new Uint8Array(cleanHex.length / 2);\n for (let i = 0; i < cleanHex.length; i += 2) {\n bytes[i / 2] = parseInt(cleanHex.substring(i, i + 2), 16);\n }\n\n // Use Buffer in Node.js or btoa in browser\n if (typeof Buffer !== 'undefined') {\n return Buffer.from(bytes).toString('base64');\n }\n\n // Browser fallback\n let binary = '';\n for (let i = 0; i < bytes.length; i++) {\n binary += String.fromCharCode(bytes[i]!);\n }\n return btoa(binary);\n}\n\n/**\n * Convert PSBT base64 to hex\n */\nexport function psbtBase64ToHex(base64: string): string {\n // Decode base64 to bytes\n let bytes: Uint8Array;\n\n if (typeof Buffer !== 'undefined') {\n bytes = Buffer.from(base64, 'base64');\n } else {\n // Browser fallback\n const binary = atob(base64);\n bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) {\n bytes[i] = binary.charCodeAt(i);\n }\n }\n\n // Convert bytes to hex\n return Array.from(bytes)\n .map((b) => b.toString(16).padStart(2, '0'))\n .join('');\n}\n\n/**\n * Validate PSBT hex format\n * PSBT magic bytes: 0x70736274ff (psbt\\xff)\n */\nexport function isValidPsbtHex(hex: string): boolean {\n const cleanHex = hex.replace(/\\s/g, '').toLowerCase();\n\n // Check minimum length (magic bytes = 5 bytes = 10 hex chars)\n if (cleanHex.length < 10) {\n return false;\n }\n\n // Check magic bytes\n return cleanHex.startsWith('70736274ff');\n}\n\n/**\n * Validate PSBT base64 format\n */\nexport function isValidPsbtBase64(base64: string): boolean {\n try {\n const hex = psbtBase64ToHex(base64);\n return isValidPsbtHex(hex);\n } catch {\n return false;\n }\n}\n\n/**\n * Extract basic info from PSBT hex (without full parsing)\n * This is a lightweight check - for full parsing use a library like bitcoinjs-lib\n */\nexport function getPsbtInfo(psbtHex: string): {\n isValid: boolean;\n version: number | null;\n inputCount: number | null;\n outputCount: number | null;\n} {\n if (!isValidPsbtHex(psbtHex)) {\n return {\n isValid: false,\n version: null,\n inputCount: null,\n outputCount: null,\n };\n }\n\n // PSBT structure is complex, return basic validation only\n // For full parsing, use bitcoinjs-lib or similar\n return {\n isValid: true,\n version: null, // Would need full parsing\n inputCount: null, // Would need full parsing\n outputCount: null, // Would need full parsing\n };\n}\n\n/**\n * Combine multiple signed PSBTs into one\n * Note: This is a basic implementation. For complex cases, use bitcoinjs-lib.\n *\n * @param psbts - Array of PSBT hex strings to combine\n * @returns Combined PSBT hex\n */\nexport function combinePsbts(psbts: string[]): string {\n if (psbts.length === 0) {\n throw new Error('No PSBTs provided');\n }\n\n if (psbts.length === 1) {\n return psbts[0]!;\n }\n\n // For proper PSBT combination, you need bitcoinjs-lib\n // This is just a placeholder that returns the first PSBT\n // In production, use: bitcoin.Psbt.combine([psbt1, psbt2, ...])\n console.warn(\n 'combinePsbts: For proper PSBT combination, use bitcoinjs-lib. ' +\n 'This function returns the first PSBT as a fallback.'\n );\n\n return psbts[0]!;\n}\n\n/**\n * Sighash types for Bitcoin transactions\n */\nexport const SighashType = {\n ALL: 0x01,\n NONE: 0x02,\n SINGLE: 0x03,\n ANYONECANPAY: 0x80,\n ALL_ANYONECANPAY: 0x81,\n NONE_ANYONECANPAY: 0x82,\n SINGLE_ANYONECANPAY: 0x83,\n} as const;\n\nexport type SighashType = (typeof SighashType)[keyof typeof SighashType];\n\n/**\n * Get human-readable name for sighash type\n */\nexport function getSighashTypeName(sighash: number): string {\n switch (sighash) {\n case SighashType.ALL:\n return 'SIGHASH_ALL';\n case SighashType.NONE:\n return 'SIGHASH_NONE';\n case SighashType.SINGLE:\n return 'SIGHASH_SINGLE';\n case SighashType.ANYONECANPAY:\n return 'SIGHASH_ANYONECANPAY';\n case SighashType.ALL_ANYONECANPAY:\n return 'SIGHASH_ALL|ANYONECANPAY';\n case SighashType.NONE_ANYONECANPAY:\n return 'SIGHASH_NONE|ANYONECANPAY';\n case SighashType.SINGLE_ANYONECANPAY:\n return 'SIGHASH_SINGLE|ANYONECANPAY';\n default:\n return `UNKNOWN(${sighash})`;\n }\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "otx-btc-wallet-core",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Core state management and types 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
|
+
"zustand": "^4.4.7"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"typescript": "^5.3.2",
|
|
26
|
+
"tsup": "^8.0.1"
|
|
27
|
+
},
|
|
28
|
+
"peerDependencies": {},
|
|
29
|
+
"keywords": [
|
|
30
|
+
"bitcoin",
|
|
31
|
+
"wallet",
|
|
32
|
+
"btc",
|
|
33
|
+
"web3"
|
|
34
|
+
],
|
|
35
|
+
"scripts": {
|
|
36
|
+
"build": "tsup",
|
|
37
|
+
"dev": "tsup --watch",
|
|
38
|
+
"clean": "rm -rf dist",
|
|
39
|
+
"typecheck": "tsc --noEmit"
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type { BtcWalletConfig, Config } from './types/config';
|
|
2
|
+
import { createStore, type BtcWalletStore } from './store';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Resolved config with store instance
|
|
6
|
+
*/
|
|
7
|
+
export type ResolvedConfig = Config & {
|
|
8
|
+
store: BtcWalletStore;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Create otx-btc-wallet configuration
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```ts
|
|
16
|
+
* import { createConfig } from 'otx-btc-wallet-core';
|
|
17
|
+
* import { UnisatConnector } from 'otx-btc-wallet-connectors/unisat';
|
|
18
|
+
*
|
|
19
|
+
* const config = createConfig({
|
|
20
|
+
* connectors: [new UnisatConnector()],
|
|
21
|
+
* autoConnect: true,
|
|
22
|
+
* });
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
export function createConfig(options: BtcWalletConfig): ResolvedConfig {
|
|
26
|
+
// Validate connectors
|
|
27
|
+
if (!options.connectors || options.connectors.length === 0) {
|
|
28
|
+
throw new Error('At least one connector is required');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Check for duplicate connector IDs
|
|
32
|
+
const connectorIds = new Set<string>();
|
|
33
|
+
for (const connector of options.connectors) {
|
|
34
|
+
if (connectorIds.has(connector.id)) {
|
|
35
|
+
throw new Error(`Duplicate connector ID: ${connector.id}`);
|
|
36
|
+
}
|
|
37
|
+
connectorIds.add(connector.id);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Resolve defaults
|
|
41
|
+
const config: Config = {
|
|
42
|
+
connectors: options.connectors,
|
|
43
|
+
autoConnect: options.autoConnect ?? true,
|
|
44
|
+
storage: getStorage(options.storage),
|
|
45
|
+
storageKey: options.storageKey ?? 'optimex-btc-wallet.store',
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
// Create store
|
|
49
|
+
const store = createStore(config);
|
|
50
|
+
|
|
51
|
+
// Auto-connect if enabled
|
|
52
|
+
if (config.autoConnect) {
|
|
53
|
+
// Use setTimeout to ensure this runs after store is fully initialized
|
|
54
|
+
setTimeout(() => {
|
|
55
|
+
void store.getState().reconnect(config.connectors);
|
|
56
|
+
}, 0);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
...config,
|
|
61
|
+
store,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Get storage implementation
|
|
67
|
+
*/
|
|
68
|
+
function getStorage(storage?: Storage): Storage | null {
|
|
69
|
+
// If explicitly provided, use it
|
|
70
|
+
if (storage !== undefined) {
|
|
71
|
+
return storage;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Default to localStorage if available
|
|
75
|
+
if (typeof window !== 'undefined' && window.localStorage) {
|
|
76
|
+
return window.localStorage;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return null;
|
|
80
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// otx-btc-wallet-core - Core state management and types
|
|
2
|
+
|
|
3
|
+
// Types
|
|
4
|
+
export type {
|
|
5
|
+
BitcoinConnector,
|
|
6
|
+
WalletAccount,
|
|
7
|
+
AddressType,
|
|
8
|
+
MultiAddressAccount,
|
|
9
|
+
BitcoinNetwork,
|
|
10
|
+
SignPsbtOptions,
|
|
11
|
+
SignInput,
|
|
12
|
+
BtcWalletConfig,
|
|
13
|
+
Config,
|
|
14
|
+
BtcWalletState,
|
|
15
|
+
ConnectionStatus,
|
|
16
|
+
} from './types';
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
// Config
|
|
20
|
+
export { createConfig, type ResolvedConfig } from './createConfig';
|
|
21
|
+
|
|
22
|
+
// Store
|
|
23
|
+
export {
|
|
24
|
+
createStore,
|
|
25
|
+
type BtcWalletStore,
|
|
26
|
+
type Store,
|
|
27
|
+
type StoreActions,
|
|
28
|
+
} from './store';
|
|
29
|
+
|
|
30
|
+
// PSBT Utilities
|
|
31
|
+
export {
|
|
32
|
+
psbtHexToBase64,
|
|
33
|
+
psbtBase64ToHex,
|
|
34
|
+
isValidPsbtHex,
|
|
35
|
+
isValidPsbtBase64,
|
|
36
|
+
getPsbtInfo,
|
|
37
|
+
combinePsbts,
|
|
38
|
+
SighashType,
|
|
39
|
+
getSighashTypeName,
|
|
40
|
+
} from './utils/psbt';
|