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
package/dist/index.d.ts
ADDED
|
@@ -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 };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var vanilla = require('zustand/vanilla');
|
|
4
|
+
var middleware = require('zustand/middleware');
|
|
5
|
+
|
|
6
|
+
// src/store/index.ts
|
|
7
|
+
var initialState = {
|
|
8
|
+
status: "disconnected",
|
|
9
|
+
account: null,
|
|
10
|
+
connector: null,
|
|
11
|
+
connectorId: null,
|
|
12
|
+
network: "mainnet",
|
|
13
|
+
error: null
|
|
14
|
+
};
|
|
15
|
+
function createStore(config) {
|
|
16
|
+
let unsubscribeAccounts = null;
|
|
17
|
+
let unsubscribeNetwork = null;
|
|
18
|
+
const cleanupListeners = () => {
|
|
19
|
+
if (unsubscribeAccounts) {
|
|
20
|
+
unsubscribeAccounts();
|
|
21
|
+
unsubscribeAccounts = null;
|
|
22
|
+
}
|
|
23
|
+
if (unsubscribeNetwork) {
|
|
24
|
+
unsubscribeNetwork();
|
|
25
|
+
unsubscribeNetwork = null;
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
const setupConnectorListeners = (connector, get, set) => {
|
|
29
|
+
cleanupListeners();
|
|
30
|
+
unsubscribeAccounts = connector.onAccountsChanged((accounts) => {
|
|
31
|
+
const currentState = get();
|
|
32
|
+
if (currentState.connector?.id !== connector.id) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
if (accounts.length === 0) {
|
|
36
|
+
get().disconnect().catch(() => {
|
|
37
|
+
set({
|
|
38
|
+
status: "disconnected",
|
|
39
|
+
account: null,
|
|
40
|
+
connector: null,
|
|
41
|
+
connectorId: null,
|
|
42
|
+
error: null
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
} else if (accounts[0]) {
|
|
46
|
+
set({ account: accounts[0] });
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
unsubscribeNetwork = connector.onNetworkChanged((network) => {
|
|
50
|
+
const currentState = get();
|
|
51
|
+
if (currentState.connector?.id !== connector.id) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
set({ network });
|
|
55
|
+
});
|
|
56
|
+
};
|
|
57
|
+
const persistOptions = {
|
|
58
|
+
name: config.storageKey,
|
|
59
|
+
storage: config.storage ? {
|
|
60
|
+
getItem: (name) => {
|
|
61
|
+
const value = config.storage?.getItem(name);
|
|
62
|
+
if (!value)
|
|
63
|
+
return null;
|
|
64
|
+
try {
|
|
65
|
+
return JSON.parse(value);
|
|
66
|
+
} catch {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
setItem: (name, value) => {
|
|
71
|
+
config.storage?.setItem(name, JSON.stringify(value));
|
|
72
|
+
},
|
|
73
|
+
removeItem: (name) => {
|
|
74
|
+
config.storage?.removeItem(name);
|
|
75
|
+
}
|
|
76
|
+
} : void 0,
|
|
77
|
+
// Only persist connectorId and network
|
|
78
|
+
partialize: (state) => ({
|
|
79
|
+
connectorId: state.connectorId,
|
|
80
|
+
network: state.network
|
|
81
|
+
})
|
|
82
|
+
};
|
|
83
|
+
const store = vanilla.createStore()(
|
|
84
|
+
middleware.subscribeWithSelector(
|
|
85
|
+
middleware.persist(
|
|
86
|
+
(set, get) => ({
|
|
87
|
+
// Initial state
|
|
88
|
+
...initialState,
|
|
89
|
+
// Actions
|
|
90
|
+
connect: async (connector, networkParam) => {
|
|
91
|
+
const { status, network: storeNetwork } = get();
|
|
92
|
+
const network = networkParam ?? storeNetwork;
|
|
93
|
+
if (status === "connecting") {
|
|
94
|
+
throw new Error("Connection already in progress");
|
|
95
|
+
}
|
|
96
|
+
cleanupListeners();
|
|
97
|
+
set({ status: "connecting", error: null, network });
|
|
98
|
+
try {
|
|
99
|
+
if (!connector.ready) {
|
|
100
|
+
throw new Error(`${connector.name} wallet is not available`);
|
|
101
|
+
}
|
|
102
|
+
const account = await connector.connect(network);
|
|
103
|
+
set({
|
|
104
|
+
status: "connected",
|
|
105
|
+
account,
|
|
106
|
+
connector,
|
|
107
|
+
connectorId: connector.id,
|
|
108
|
+
error: null
|
|
109
|
+
});
|
|
110
|
+
setupConnectorListeners(connector, get, set);
|
|
111
|
+
return account;
|
|
112
|
+
} catch (error) {
|
|
113
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
114
|
+
set({
|
|
115
|
+
status: "disconnected",
|
|
116
|
+
error: err
|
|
117
|
+
});
|
|
118
|
+
throw error;
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
disconnect: async () => {
|
|
122
|
+
const { connector } = get();
|
|
123
|
+
cleanupListeners();
|
|
124
|
+
try {
|
|
125
|
+
if (connector) {
|
|
126
|
+
await connector.disconnect();
|
|
127
|
+
}
|
|
128
|
+
} catch {
|
|
129
|
+
} finally {
|
|
130
|
+
set({
|
|
131
|
+
status: "disconnected",
|
|
132
|
+
account: null,
|
|
133
|
+
connector: null,
|
|
134
|
+
connectorId: null,
|
|
135
|
+
error: null
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
},
|
|
139
|
+
reconnect: async (connectors) => {
|
|
140
|
+
const { connectorId, status, network } = get();
|
|
141
|
+
if (!connectorId || status === "connected") {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
const connector = connectors.find((c) => c.id === connectorId);
|
|
145
|
+
if (!connector?.ready) {
|
|
146
|
+
set({ connectorId: null });
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
set({ status: "reconnecting" });
|
|
150
|
+
try {
|
|
151
|
+
const account = await connector.connect(network);
|
|
152
|
+
set({
|
|
153
|
+
status: "connected",
|
|
154
|
+
account,
|
|
155
|
+
connector,
|
|
156
|
+
error: null
|
|
157
|
+
});
|
|
158
|
+
setupConnectorListeners(connector, get, set);
|
|
159
|
+
} catch {
|
|
160
|
+
set({
|
|
161
|
+
status: "disconnected",
|
|
162
|
+
connectorId: null
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
},
|
|
166
|
+
setAccount: (account) => set({ account }),
|
|
167
|
+
setStatus: (status) => set({ status }),
|
|
168
|
+
setError: (error) => set({ error }),
|
|
169
|
+
reset: () => {
|
|
170
|
+
cleanupListeners();
|
|
171
|
+
set(initialState);
|
|
172
|
+
}
|
|
173
|
+
}),
|
|
174
|
+
persistOptions
|
|
175
|
+
)
|
|
176
|
+
)
|
|
177
|
+
);
|
|
178
|
+
return store;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// src/createConfig.ts
|
|
182
|
+
function createConfig(options) {
|
|
183
|
+
if (!options.connectors || options.connectors.length === 0) {
|
|
184
|
+
throw new Error("At least one connector is required");
|
|
185
|
+
}
|
|
186
|
+
const connectorIds = /* @__PURE__ */ new Set();
|
|
187
|
+
for (const connector of options.connectors) {
|
|
188
|
+
if (connectorIds.has(connector.id)) {
|
|
189
|
+
throw new Error(`Duplicate connector ID: ${connector.id}`);
|
|
190
|
+
}
|
|
191
|
+
connectorIds.add(connector.id);
|
|
192
|
+
}
|
|
193
|
+
const config = {
|
|
194
|
+
connectors: options.connectors,
|
|
195
|
+
autoConnect: options.autoConnect ?? true,
|
|
196
|
+
storage: getStorage(options.storage),
|
|
197
|
+
storageKey: options.storageKey ?? "optimex-btc-wallet.store"
|
|
198
|
+
};
|
|
199
|
+
const store = createStore(config);
|
|
200
|
+
if (config.autoConnect) {
|
|
201
|
+
setTimeout(() => {
|
|
202
|
+
void store.getState().reconnect(config.connectors);
|
|
203
|
+
}, 0);
|
|
204
|
+
}
|
|
205
|
+
return {
|
|
206
|
+
...config,
|
|
207
|
+
store
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
function getStorage(storage) {
|
|
211
|
+
if (storage !== void 0) {
|
|
212
|
+
return storage;
|
|
213
|
+
}
|
|
214
|
+
if (typeof window !== "undefined" && window.localStorage) {
|
|
215
|
+
return window.localStorage;
|
|
216
|
+
}
|
|
217
|
+
return null;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// src/utils/psbt.ts
|
|
221
|
+
function psbtHexToBase64(hex) {
|
|
222
|
+
const cleanHex = hex.replace(/\s/g, "");
|
|
223
|
+
if (!/^[0-9a-fA-F]*$/.test(cleanHex)) {
|
|
224
|
+
throw new Error("Invalid hex string");
|
|
225
|
+
}
|
|
226
|
+
if (cleanHex.length % 2 !== 0) {
|
|
227
|
+
throw new Error("Hex string must have even length");
|
|
228
|
+
}
|
|
229
|
+
const bytes = new Uint8Array(cleanHex.length / 2);
|
|
230
|
+
for (let i = 0; i < cleanHex.length; i += 2) {
|
|
231
|
+
bytes[i / 2] = parseInt(cleanHex.substring(i, i + 2), 16);
|
|
232
|
+
}
|
|
233
|
+
if (typeof Buffer !== "undefined") {
|
|
234
|
+
return Buffer.from(bytes).toString("base64");
|
|
235
|
+
}
|
|
236
|
+
let binary = "";
|
|
237
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
238
|
+
binary += String.fromCharCode(bytes[i]);
|
|
239
|
+
}
|
|
240
|
+
return btoa(binary);
|
|
241
|
+
}
|
|
242
|
+
function psbtBase64ToHex(base64) {
|
|
243
|
+
let bytes;
|
|
244
|
+
if (typeof Buffer !== "undefined") {
|
|
245
|
+
bytes = Buffer.from(base64, "base64");
|
|
246
|
+
} else {
|
|
247
|
+
const binary = atob(base64);
|
|
248
|
+
bytes = new Uint8Array(binary.length);
|
|
249
|
+
for (let i = 0; i < binary.length; i++) {
|
|
250
|
+
bytes[i] = binary.charCodeAt(i);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
254
|
+
}
|
|
255
|
+
function isValidPsbtHex(hex) {
|
|
256
|
+
const cleanHex = hex.replace(/\s/g, "").toLowerCase();
|
|
257
|
+
if (cleanHex.length < 10) {
|
|
258
|
+
return false;
|
|
259
|
+
}
|
|
260
|
+
return cleanHex.startsWith("70736274ff");
|
|
261
|
+
}
|
|
262
|
+
function isValidPsbtBase64(base64) {
|
|
263
|
+
try {
|
|
264
|
+
const hex = psbtBase64ToHex(base64);
|
|
265
|
+
return isValidPsbtHex(hex);
|
|
266
|
+
} catch {
|
|
267
|
+
return false;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
function getPsbtInfo(psbtHex) {
|
|
271
|
+
if (!isValidPsbtHex(psbtHex)) {
|
|
272
|
+
return {
|
|
273
|
+
isValid: false,
|
|
274
|
+
version: null,
|
|
275
|
+
inputCount: null,
|
|
276
|
+
outputCount: null
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
return {
|
|
280
|
+
isValid: true,
|
|
281
|
+
version: null,
|
|
282
|
+
// Would need full parsing
|
|
283
|
+
inputCount: null,
|
|
284
|
+
// Would need full parsing
|
|
285
|
+
outputCount: null
|
|
286
|
+
// Would need full parsing
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
function combinePsbts(psbts) {
|
|
290
|
+
if (psbts.length === 0) {
|
|
291
|
+
throw new Error("No PSBTs provided");
|
|
292
|
+
}
|
|
293
|
+
if (psbts.length === 1) {
|
|
294
|
+
return psbts[0];
|
|
295
|
+
}
|
|
296
|
+
console.warn(
|
|
297
|
+
"combinePsbts: For proper PSBT combination, use bitcoinjs-lib. This function returns the first PSBT as a fallback."
|
|
298
|
+
);
|
|
299
|
+
return psbts[0];
|
|
300
|
+
}
|
|
301
|
+
var SighashType = {
|
|
302
|
+
ALL: 1,
|
|
303
|
+
NONE: 2,
|
|
304
|
+
SINGLE: 3,
|
|
305
|
+
ANYONECANPAY: 128,
|
|
306
|
+
ALL_ANYONECANPAY: 129,
|
|
307
|
+
NONE_ANYONECANPAY: 130,
|
|
308
|
+
SINGLE_ANYONECANPAY: 131
|
|
309
|
+
};
|
|
310
|
+
function getSighashTypeName(sighash) {
|
|
311
|
+
switch (sighash) {
|
|
312
|
+
case SighashType.ALL:
|
|
313
|
+
return "SIGHASH_ALL";
|
|
314
|
+
case SighashType.NONE:
|
|
315
|
+
return "SIGHASH_NONE";
|
|
316
|
+
case SighashType.SINGLE:
|
|
317
|
+
return "SIGHASH_SINGLE";
|
|
318
|
+
case SighashType.ANYONECANPAY:
|
|
319
|
+
return "SIGHASH_ANYONECANPAY";
|
|
320
|
+
case SighashType.ALL_ANYONECANPAY:
|
|
321
|
+
return "SIGHASH_ALL|ANYONECANPAY";
|
|
322
|
+
case SighashType.NONE_ANYONECANPAY:
|
|
323
|
+
return "SIGHASH_NONE|ANYONECANPAY";
|
|
324
|
+
case SighashType.SINGLE_ANYONECANPAY:
|
|
325
|
+
return "SIGHASH_SINGLE|ANYONECANPAY";
|
|
326
|
+
default:
|
|
327
|
+
return `UNKNOWN(${sighash})`;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
exports.SighashType = SighashType;
|
|
332
|
+
exports.combinePsbts = combinePsbts;
|
|
333
|
+
exports.createConfig = createConfig;
|
|
334
|
+
exports.createStore = createStore;
|
|
335
|
+
exports.getPsbtInfo = getPsbtInfo;
|
|
336
|
+
exports.getSighashTypeName = getSighashTypeName;
|
|
337
|
+
exports.isValidPsbtBase64 = isValidPsbtBase64;
|
|
338
|
+
exports.isValidPsbtHex = isValidPsbtHex;
|
|
339
|
+
exports.psbtBase64ToHex = psbtBase64ToHex;
|
|
340
|
+
exports.psbtHexToBase64 = psbtHexToBase64;
|
|
341
|
+
//# sourceMappingURL=out.js.map
|
|
342
|
+
//# sourceMappingURL=index.js.map
|