otx-btc-wallet-connectors 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 +554 -0
- package/dist/base-IAFq7sd8.d.mts +53 -0
- package/dist/base-IAFq7sd8.d.ts +53 -0
- package/dist/binance/index.d.mts +81 -0
- package/dist/binance/index.d.ts +81 -0
- package/dist/binance/index.js +13 -0
- package/dist/binance/index.js.map +1 -0
- package/dist/binance/index.mjs +4 -0
- package/dist/binance/index.mjs.map +1 -0
- package/dist/bitget/index.d.mts +84 -0
- package/dist/bitget/index.d.ts +84 -0
- package/dist/bitget/index.js +13 -0
- package/dist/bitget/index.js.map +1 -0
- package/dist/bitget/index.mjs +4 -0
- package/dist/bitget/index.mjs.map +1 -0
- package/dist/chunk-5Z5Q2Y75.mjs +91 -0
- package/dist/chunk-5Z5Q2Y75.mjs.map +1 -0
- package/dist/chunk-7KK2LZLZ.mjs +208 -0
- package/dist/chunk-7KK2LZLZ.mjs.map +1 -0
- package/dist/chunk-AW2JZIHR.mjs +753 -0
- package/dist/chunk-AW2JZIHR.mjs.map +1 -0
- package/dist/chunk-EIJOSZXZ.js +331 -0
- package/dist/chunk-EIJOSZXZ.js.map +1 -0
- package/dist/chunk-EQHR7P7G.js +541 -0
- package/dist/chunk-EQHR7P7G.js.map +1 -0
- package/dist/chunk-EWRXLZO4.mjs +539 -0
- package/dist/chunk-EWRXLZO4.mjs.map +1 -0
- package/dist/chunk-FISNQZZ7.js +802 -0
- package/dist/chunk-FISNQZZ7.js.map +1 -0
- package/dist/chunk-HL4WDMGS.js +200 -0
- package/dist/chunk-HL4WDMGS.js.map +1 -0
- package/dist/chunk-IPYWR76I.js +314 -0
- package/dist/chunk-IPYWR76I.js.map +1 -0
- package/dist/chunk-JYYNWR5G.js +142 -0
- package/dist/chunk-JYYNWR5G.js.map +1 -0
- package/dist/chunk-LNKTYZJM.js +701 -0
- package/dist/chunk-LNKTYZJM.js.map +1 -0
- package/dist/chunk-LVZMONQL.mjs +699 -0
- package/dist/chunk-LVZMONQL.mjs.map +1 -0
- package/dist/chunk-MFXLQWOE.js +93 -0
- package/dist/chunk-MFXLQWOE.js.map +1 -0
- package/dist/chunk-NBIA4TTE.mjs +204 -0
- package/dist/chunk-NBIA4TTE.mjs.map +1 -0
- package/dist/chunk-O4DD2XJ2.js +206 -0
- package/dist/chunk-O4DD2XJ2.js.map +1 -0
- package/dist/chunk-P7HVBU2B.mjs +140 -0
- package/dist/chunk-P7HVBU2B.mjs.map +1 -0
- package/dist/chunk-Q7QVQYEB.js +210 -0
- package/dist/chunk-Q7QVQYEB.js.map +1 -0
- package/dist/chunk-RLZEG6KL.mjs +329 -0
- package/dist/chunk-RLZEG6KL.mjs.map +1 -0
- package/dist/chunk-SYLDBJ75.mjs +246 -0
- package/dist/chunk-SYLDBJ75.mjs.map +1 -0
- package/dist/chunk-TTEUU3CI.mjs +198 -0
- package/dist/chunk-TTEUU3CI.mjs.map +1 -0
- package/dist/chunk-V66BXDTR.mjs +292 -0
- package/dist/chunk-V66BXDTR.mjs.map +1 -0
- package/dist/chunk-X77ZT4OI.js +268 -0
- package/dist/chunk-X77ZT4OI.js.map +1 -0
- package/dist/imtoken/index.d.mts +116 -0
- package/dist/imtoken/index.d.ts +116 -0
- package/dist/imtoken/index.js +14 -0
- package/dist/imtoken/index.js.map +1 -0
- package/dist/imtoken/index.mjs +5 -0
- package/dist/imtoken/index.mjs.map +1 -0
- package/dist/index.d.mts +14 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +170 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +13 -0
- package/dist/index.mjs.map +1 -0
- package/dist/ledger/index.d.mts +290 -0
- package/dist/ledger/index.d.ts +290 -0
- package/dist/ledger/index.js +14 -0
- package/dist/ledger/index.js.map +1 -0
- package/dist/ledger/index.mjs +5 -0
- package/dist/ledger/index.mjs.map +1 -0
- package/dist/okx/index.d.mts +88 -0
- package/dist/okx/index.d.ts +88 -0
- package/dist/okx/index.js +13 -0
- package/dist/okx/index.js.map +1 -0
- package/dist/okx/index.mjs +4 -0
- package/dist/okx/index.mjs.map +1 -0
- package/dist/phantom/index.d.mts +96 -0
- package/dist/phantom/index.d.ts +96 -0
- package/dist/phantom/index.js +14 -0
- package/dist/phantom/index.js.map +1 -0
- package/dist/phantom/index.mjs +5 -0
- package/dist/phantom/index.mjs.map +1 -0
- package/dist/psbt-builder-CFOs69Z5.d.mts +131 -0
- package/dist/psbt-builder-CFOs69Z5.d.ts +131 -0
- package/dist/trezor/index.d.mts +155 -0
- package/dist/trezor/index.d.ts +155 -0
- package/dist/trezor/index.js +14 -0
- package/dist/trezor/index.js.map +1 -0
- package/dist/trezor/index.mjs +5 -0
- package/dist/trezor/index.mjs.map +1 -0
- package/dist/unisat/index.d.mts +75 -0
- package/dist/unisat/index.d.ts +75 -0
- package/dist/unisat/index.js +13 -0
- package/dist/unisat/index.js.map +1 -0
- package/dist/unisat/index.mjs +4 -0
- package/dist/unisat/index.mjs.map +1 -0
- package/dist/utils/index.d.mts +398 -0
- package/dist/utils/index.d.ts +398 -0
- package/dist/utils/index.js +120 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/index.mjs +3 -0
- package/dist/utils/index.mjs.map +1 -0
- package/dist/xverse/index.d.mts +79 -0
- package/dist/xverse/index.d.ts +79 -0
- package/dist/xverse/index.js +13 -0
- package/dist/xverse/index.js.map +1 -0
- package/dist/xverse/index.mjs +4 -0
- package/dist/xverse/index.mjs.map +1 -0
- package/package.json +108 -0
- package/src/base.ts +132 -0
- package/src/binance/BinanceConnector.ts +307 -0
- package/src/binance/index.ts +1 -0
- package/src/bitget/BitgetConnector.ts +301 -0
- package/src/bitget/index.ts +1 -0
- package/src/imtoken/ImTokenConnector.ts +420 -0
- package/src/imtoken/index.ts +2 -0
- package/src/index.ts +78 -0
- package/src/ledger/LedgerConnector.ts +1019 -0
- package/src/ledger/index.ts +8 -0
- package/src/okx/OKXConnector.ts +230 -0
- package/src/okx/index.ts +1 -0
- package/src/phantom/PhantomConnector.ts +381 -0
- package/src/phantom/index.ts +2 -0
- package/src/trezor/TrezorConnector.ts +824 -0
- package/src/trezor/index.ts +6 -0
- package/src/unisat/UnisatConnector.ts +312 -0
- package/src/unisat/index.ts +1 -0
- package/src/utils/blockstream.ts +230 -0
- package/src/utils/btc-service.ts +364 -0
- package/src/utils/index.ts +56 -0
- package/src/utils/mempool.ts +232 -0
- package/src/utils/psbt-builder.ts +492 -0
- package/src/utils/types.ts +183 -0
- package/src/xverse/XverseConnector.ts +479 -0
- package/src/xverse/index.ts +1 -0
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
WalletAccount,
|
|
3
|
+
BitcoinNetwork,
|
|
4
|
+
SignPsbtOptions,
|
|
5
|
+
} from 'otx-btc-wallet-core';
|
|
6
|
+
import { BaseConnector } from '../base';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Unisat network types
|
|
10
|
+
* @see https://docs.unisat.io/dev/unisat-developer-service/unisat-wallet#switchnetwork
|
|
11
|
+
*/
|
|
12
|
+
type UnisatNetwork = 'livenet' | 'testnet' | 'testnet4' | 'signet';
|
|
13
|
+
|
|
14
|
+
// Unisat wallet provider type
|
|
15
|
+
interface UnisatProvider {
|
|
16
|
+
requestAccounts(): Promise<string[]>;
|
|
17
|
+
getAccounts(): Promise<string[]>;
|
|
18
|
+
getPublicKey(): Promise<string>;
|
|
19
|
+
getNetwork(): Promise<string>;
|
|
20
|
+
getChain(): Promise<{
|
|
21
|
+
enum: string
|
|
22
|
+
name: string
|
|
23
|
+
network: string
|
|
24
|
+
}>
|
|
25
|
+
switchChain(chain: string): Promise<{
|
|
26
|
+
enum: string
|
|
27
|
+
name: string
|
|
28
|
+
network: string
|
|
29
|
+
}>
|
|
30
|
+
switchNetwork(network: UnisatNetwork): Promise<void>;
|
|
31
|
+
signMessage(message: string, type?: string): Promise<string>;
|
|
32
|
+
signPsbt(psbtHex: string, options?: UnisatSignOptions): Promise<string>;
|
|
33
|
+
signPsbts(psbtHexs: string[], options?: UnisatSignOptions[]): Promise<string[]>;
|
|
34
|
+
sendBitcoin(to: string, satoshis: number, options?: object): Promise<string>;
|
|
35
|
+
on(event: string, callback: (arg: unknown) => void): void;
|
|
36
|
+
removeListener(event: string, callback: (arg: unknown) => void): void;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
interface UnisatSignOptions {
|
|
40
|
+
autoFinalized?: boolean;
|
|
41
|
+
toSignInputs?: Array<{
|
|
42
|
+
index: number;
|
|
43
|
+
address?: string;
|
|
44
|
+
publicKey?: string;
|
|
45
|
+
sighashTypes?: number[];
|
|
46
|
+
disableTweakSigner?: boolean;
|
|
47
|
+
}>;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Extend window type
|
|
51
|
+
declare global {
|
|
52
|
+
interface Window {
|
|
53
|
+
unisat_wallet?: UnisatProvider;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Unisat wallet icon (SVG as base64)
|
|
58
|
+
const UNISAT_ICON =
|
|
59
|
+
'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCIgdmlld0JveD0iMCAwIDIwMCAyMDAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMF83MDM3XzkxKSI+CjxwYXRoIGQ9Ik0yMDAgMEgwVjIwMEgyMDBWMFoiIGZpbGw9ImJsYWNrIi8+CjxtYXNrIGlkPSJtYXNrMF83MDM3XzkxIiBzdHlsZT0ibWFzay10eXBlOmx1bWluYW5jZSIgbWFza1VuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeD0iNDEiIHk9IjI0IiB3aWR0aD0iMTE5IiBoZWlnaHQ9IjE1MiI+CjxwYXRoIGQ9Ik0xNTkuNDQ0IDI0SDQxVjE3NS4xMTJIMTU5LjQ0NFYyNFoiIGZpbGw9IndoaXRlIi8+CjwvbWFzaz4KPGcgbWFzaz0idXJsKCNtYXNrMF83MDM3XzkxKSI+CjxwYXRoIGQ9Ik0xMjQuNTQ3IDM3LjU5NjdMMTU1LjU1NyA2OC4zMTAyQzE1OC4xOTcgNzAuOTE5MSAxNTkuNDk2IDczLjU1ODcgMTU5LjQ1NSA3Ni4yMTg3QzE1OS40MTQgNzguODc4OCAxNTguMjc4IDgxLjMwMzUgMTU2LjA1OCA4My41MDMyQzE1My43MzYgODUuODA1MiAxNTEuMjI5IDg2Ljk3MTUgMTQ4LjU0OSA4Ny4wMjI3QzE0NS44NjggODcuMDYzNiAxNDMuMjA4IDg1Ljc3NDUgMTQwLjU2OSA4My4xNjU2TDEwOC44NTIgNTEuNzU2NEMxMDUuMjUxIDQ4LjE4NTggMTAxLjc3MyA0NS42NTg3IDk4LjQyNyA0NC4xNzUyQzk1LjA4MTUgNDIuNjkxNyA5MS41NjIgNDIuNDU2NCA4Ny44Nzg4IDQzLjQ3OTVDODQuMTg1NCA0NC40OTI0IDgwLjIyNiA0Ny4xMDEzIDc1Ljk4MDIgNTEuMjk2QzcwLjEyOCA1Ny4wOTcgNjcuMzM1IDYyLjUzOTkgNjcuNjIxNCA2Ny42MjQ3QzY3LjkwNzkgNzIuNzA5NSA3MC44MTM1IDc3Ljk4ODcgNzYuMzI4IDgzLjQ0MThMMTA4LjMgMTE1LjExN0MxMTAuOTcgMTE3Ljc1NyAxMTIuMjggMTIwLjM5NyAxMTIuMjM5IDEyMy4wMTZDMTEyLjE5OCAxMjUuNjQ1IDExMS4wNTIgMTI4LjA3IDEwOC43OTEgMTMwLjMxQzEwNi41NCAxMzIuNTQxIDEwNC4wNTQgMTMzLjcwNyAxMDEuMzUzIDEzMy43OTlDOTguNjUyMSAxMzMuODkxIDk1Ljk2MTMgMTMyLjYxMiA5My4zMDEzIDEyOS45NzNMNjIuMjkxMSA5OS4yNTlDNTcuMjQ3MiA5NC4yNjYyIDUzLjYwNSA4OS41Mzk1IDUxLjM2NDQgODUuMDc4OEM0OS4xMjM4IDgwLjYxODEgNDguMjg0OCA3NS41NzQyIDQ4Ljg2OCA2OS45NDcxQzQ5LjM4OTggNjUuMTI4MyA1MC45MzQ3IDYwLjQ2MyA1My41MTI5IDU1Ljk0MDlDNTYuMDgwOSA1MS40MTg4IDU5Ljc2NCA0Ni43OTQ0IDY0LjU0MTkgNDIuMDU3NEM3MC4yMzAzIDM2LjQyMDEgNzUuNjYzIDMyLjEwMjcgODAuODM5OSAyOS4wOTQ3Qzg2LjAwNjYgMjYuMDg2OCA5MS4wMDk1IDI0LjQxOTIgOTUuODM4NSAyNC4wNzEzQzEwMC42NzggMjMuNzIzNSAxMDUuNDQ1IDI0LjY4NTIgMTEwLjE2MiAyNi45NTY1QzExNC44NzkgMjkuMjI3NyAxMTkuNjY3IDMyLjc2NzcgMTI0LjUzNyAzNy41OTY3SDEyNC41NDdaIiBmaWxsPSJ1cmwoI3BhaW50MF9saW5lYXJfNzAzN185MSkiLz4KPHBhdGggZD0iTTc1Ljg5NzkgMTYxLjUyNUw0NC44OTggMTMwLjgxMkM0Mi4yNTg0IDEyOC4xOTMgNDAuOTU5IDEyNS41NjMgNDEgMTIyLjkwM0M0MS4wNDA5IDEyMC4yNDMgNDIuMTc2NSAxMTcuODE4IDQ0LjM5NjcgMTE1LjYxOUM0Ni43MTkxIDExMy4zMTcgNDkuMjI1NyAxMTIuMTUgNTEuOTA2MiAxMTIuMDk5QzU0LjU4NjcgMTEyLjA1OCA1Ny4yNDY4IDExMy4zMzcgNTkuODg2NCAxMTUuOTU2TDkxLjU5MjMgMTQ3LjM2NUM5NS4yMDM4IDE1MC45MzYgOTguNjcyMSAxNTMuNDYzIDEwMi4wMTggMTU0Ljk0N0MxMDUuMzYzIDE1Ni40MyAxMDguODgzIDE1Ni42NTUgMTEyLjU3NiAxNTUuNjQyQzExNi4yNyAxNTQuNjI5IDEyMC4yMjkgMTUyLjAyMSAxMjQuNDc1IDE0Ny44MTZDMTMwLjMyNyAxNDIuMDE1IDEzMy4xMiAxMzYuNTcyIDEzMi44MzQgMTMxLjQ4N0MxMzIuNTQ3IDEyNi40MDIgMTI5LjY0MSAxMjEuMTIzIDEyNC4xMjcgMTE1LjY2TDEwNy4wOTIgOTguOTMyQzEwNC40MjIgOTYuMjkyNCAxMDMuMTEyIDkzLjY1MjggMTAzLjE1MyA5MS4wMzM2QzEwMy4xOTQgODguNDA0MyAxMDQuMzQgODUuOTc5NSAxMDYuNjAxIDgzLjczODlDMTA4Ljg1MiA4MS41MDg2IDExMS4zMzggODAuMzQyMiAxMTQuMDM5IDgwLjI1MDJDMTE2Ljc0IDgwLjE1ODEgMTE5LjQzMSA4MS40MzcgMTIyLjA5MSA4NC4wNzY2TDEzOC4xNTQgOTkuODQyNUMxNDMuMTk4IDEwNC44MzUgMTQ2Ljg0IDEwOS41NjIgMTQ5LjA4IDExNC4wMjNDMTUxLjMyMSAxMTguNDgzIDE1Mi4xNiAxMjMuNTI3IDE1MS41NzcgMTI5LjE1NEMxNTEuMDU1IDEzMy45NzMgMTQ5LjUxIDEzOC42MzggMTQ2LjkzMiAxNDMuMTYxQzE0NC4zNjQgMTQ3LjY4MyAxNDAuNjgxIDE1Mi4zMDcgMTM1LjkwMyAxNTcuMDQ0QzEzMC4yMTQgMTYyLjY4MSAxMjQuNzgyIDE2Ni45OTkgMTE5LjYwNSAxNzAuMDA3QzExNC40MjggMTczLjAxNSAxMDkuNDI1IDE3NC42OTIgMTA0LjU4NiAxNzUuMDRDOTkuNzQ2NCAxNzUuMzg4IDk0Ljk3ODcgMTc0LjQyNiA5MC4yNjIyIDE3Mi4xNTVDODUuNTQ1OCAxNjkuODg0IDgwLjc1NzYgMTY2LjM0NCA3NS44ODc3IDE2MS41MTVMNzUuODk3OSAxNjEuNTI1WiIgZmlsbD0idXJsKCNwYWludDFfbGluZWFyXzcwMzdfOTEpIi8+CjxwYXRoIGQ9Ik05NS4yMzQzIDgyLjI3NUMxMDEuNTIzIDgyLjI3NSAxMDYuNjIxIDc3LjE3NjggMTA2LjYyMSA3MC44ODc5QzEwNi42MjEgNjQuNTk5IDEwMS41MjMgNTkuNTAwOCA5NS4yMzQzIDU5LjUwMDhDODguOTQ1NCA1OS41MDA4IDgzLjg0NzIgNjQuNTk5IDgzLjg0NzIgNzAuODg3OUM4My44NDcyIDc3LjE3NjggODguOTQ1NCA4Mi4yNzUgOTUuMjM0MyA4Mi4yNzVaIiBmaWxsPSJ1cmwoI3BhaW50Ml9yYWRpYWxfNzAzN185MSkiLz4KPC9nPgo8L2c+CjxkZWZzPgo8bGluZWFyR3JhZGllbnQgaWQ9InBhaW50MF9saW5lYXJfNzAzN185MSIgeDE9IjE1MC40NTciIHkxPSI1NS40OTA1IiB4Mj0iNTQuMzU0OSIgeTI9Ijk4LjE5ODYiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj4KPHN0b3Agc3RvcC1jb2xvcj0iIzIwMUMxQiIvPgo8c3RvcCBvZmZzZXQ9IjAuMzYiIHN0b3AtY29sb3I9IiM3NzM5MEQiLz4KPHN0b3Agb2Zmc2V0PSIwLjY3IiBzdG9wLWNvbG9yPSIjRUE4MTAxIi8+CjxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iI0Y0Qjg1MiIvPgo8L2xpbmVhckdyYWRpZW50Pgo8bGluZWFyR3JhZGllbnQgaWQ9InBhaW50MV9saW5lYXJfNzAzN185MSIgeDE9IjUyLjY0NDIiIHkxPSIxNDQuODk3IiB4Mj0iMTY0LjAwOCIgeTI9IjExMS43NTIiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj4KPHN0b3Agc3RvcC1jb2xvcj0iIzFGMUQxQyIvPgo8c3RvcCBvZmZzZXQ9IjAuMzciIHN0b3AtY29sb3I9IiM3NzM5MEQiLz4KPHN0b3Agb2Zmc2V0PSIwLjY3IiBzdG9wLWNvbG9yPSIjRUE4MTAxIi8+CjxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iI0Y0RkI1MiIvPgo8L2xpbmVhckdyYWRpZW50Pgo8cmFkaWFsR3JhZGllbnQgaWQ9InBhaW50Ml9yYWRpYWxfNzAzN185MSIgY3g9IjAiIGN5PSIwIiByPSIxIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgZ3JhZGllbnRUcmFuc2Zvcm09InRyYW5zbGF0ZSg5NS4yMzQzIDcwLjg5ODEpIHNjYWxlKDExLjM4NzEgMTEuMzg3MSkiPgo8c3RvcCBzdG9wLWNvbG9yPSIjRjRCODUyIi8+CjxzdG9wIG9mZnNldD0iMC4zMyIgc3RvcC1jb2xvcj0iI0VBODEwMSIvPgo8c3RvcCBvZmZzZXQ9IjAuNjQiIHN0b3AtY29sb3I9IiM3NzM5MEQiLz4KPHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjMjExQzFEIi8+CjwvcmFkaWFsR3JhZGllbnQ+CjxjbGlwUGF0aCBpZD0iY2xpcDBfNzAzN185MSI+CjxyZWN0IHdpZHRoPSIyMDAiIGhlaWdodD0iMjAwIiByeD0iMzUuNTU1NiIgZmlsbD0id2hpdGUiLz4KPC9jbGlwUGF0aD4KPC9kZWZzPgo8L3N2Zz4K';
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Unisat Wallet Connector
|
|
63
|
+
*
|
|
64
|
+
* @see https://docs.unisat.io/dev/unisat-developer-service/unisat-wallet
|
|
65
|
+
*/
|
|
66
|
+
export class UnisatConnector extends BaseConnector {
|
|
67
|
+
readonly id = 'unisat';
|
|
68
|
+
readonly name = 'Unisat Wallet';
|
|
69
|
+
readonly icon = UNISAT_ICON;
|
|
70
|
+
readonly BITCOIN_TESTNET4 = 'BITCOIN_TESTNET4'
|
|
71
|
+
private _unsubscribeAccounts?: () => void;
|
|
72
|
+
private _unsubscribeNetwork?: () => void;
|
|
73
|
+
|
|
74
|
+
protected getProvider(): UnisatProvider | undefined {
|
|
75
|
+
if (typeof window === 'undefined') return undefined;
|
|
76
|
+
return window.unisat_wallet;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async connect(network: BitcoinNetwork = 'mainnet'): Promise<WalletAccount> {
|
|
80
|
+
this.ensureInstalled();
|
|
81
|
+
const provider = this.getProvider()!;
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
// First request accounts to trigger wallet popup
|
|
85
|
+
const initialAccounts = await provider.requestAccounts();
|
|
86
|
+
if (!initialAccounts || initialAccounts.length === 0) {
|
|
87
|
+
throw new Error('No accounts found');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Switch network if needed
|
|
91
|
+
await this.checkAndSwitchNetwork(provider, network);
|
|
92
|
+
|
|
93
|
+
// Get accounts AFTER network switch (address changes per network)
|
|
94
|
+
const accounts = await provider.getAccounts();
|
|
95
|
+
const publicKey = await provider.getPublicKey();
|
|
96
|
+
|
|
97
|
+
// Setup event listeners
|
|
98
|
+
this.setupListeners();
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
address: accounts[0] ?? '',
|
|
102
|
+
publicKey,
|
|
103
|
+
type: this.inferAddressType(accounts[0] ?? ''),
|
|
104
|
+
};
|
|
105
|
+
} catch (error) {
|
|
106
|
+
this.handleError(error);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async disconnect(): Promise<void> {
|
|
111
|
+
// Unisat doesn't have a disconnect method, just cleanup listeners
|
|
112
|
+
this._unsubscribeAccounts?.();
|
|
113
|
+
this._unsubscribeNetwork?.();
|
|
114
|
+
this.cleanup();
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async getAccounts(): Promise<WalletAccount[]> {
|
|
118
|
+
this.ensureInstalled();
|
|
119
|
+
const provider = this.getProvider()!;
|
|
120
|
+
|
|
121
|
+
try {
|
|
122
|
+
const addresses = await provider.getAccounts();
|
|
123
|
+
const publicKey = await provider.getPublicKey();
|
|
124
|
+
|
|
125
|
+
return addresses.map((address) => ({
|
|
126
|
+
address,
|
|
127
|
+
publicKey,
|
|
128
|
+
type: this.inferAddressType(address),
|
|
129
|
+
}));
|
|
130
|
+
} catch (error) {
|
|
131
|
+
this.handleError(error);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async signMessage(message: string): Promise<string> {
|
|
136
|
+
this.ensureInstalled();
|
|
137
|
+
const provider = this.getProvider()!;
|
|
138
|
+
|
|
139
|
+
try {
|
|
140
|
+
return await provider.signMessage(message);
|
|
141
|
+
} catch (error) {
|
|
142
|
+
this.handleError(error);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
async signPsbt(psbtHex: string, options?: SignPsbtOptions): Promise<string> {
|
|
147
|
+
this.ensureInstalled();
|
|
148
|
+
const provider = this.getProvider()!;
|
|
149
|
+
|
|
150
|
+
try {
|
|
151
|
+
const unisatOptions: UnisatSignOptions = {
|
|
152
|
+
autoFinalized: options?.autoFinalize ?? true,
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
// Only add toSignInputs if defined
|
|
156
|
+
if (options?.toSignInputs) {
|
|
157
|
+
unisatOptions.toSignInputs = options.toSignInputs;
|
|
158
|
+
}
|
|
159
|
+
return await provider.signPsbt(psbtHex, unisatOptions);
|
|
160
|
+
} catch (error) {
|
|
161
|
+
this.handleError(error);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
async signPsbts(
|
|
166
|
+
psbtHexs: string[],
|
|
167
|
+
options?: SignPsbtOptions
|
|
168
|
+
): Promise<string[]> {
|
|
169
|
+
this.ensureInstalled();
|
|
170
|
+
const provider = this.getProvider()!;
|
|
171
|
+
|
|
172
|
+
try {
|
|
173
|
+
const unisatOptions: UnisatSignOptions = {
|
|
174
|
+
autoFinalized: options?.autoFinalize ?? true,
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
// Only add toSignInputs if defined
|
|
178
|
+
if (options?.toSignInputs) {
|
|
179
|
+
unisatOptions.toSignInputs = options.toSignInputs;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Create options array for each PSBT
|
|
183
|
+
const optionsArray = psbtHexs.map(() => unisatOptions);
|
|
184
|
+
|
|
185
|
+
return await provider.signPsbts(psbtHexs, optionsArray);
|
|
186
|
+
} catch (error) {
|
|
187
|
+
this.handleError(error);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
async sendTransaction(to: string, satoshis: number): Promise<string> {
|
|
192
|
+
this.ensureInstalled();
|
|
193
|
+
const provider = this.getProvider()!;
|
|
194
|
+
try {
|
|
195
|
+
return await provider.sendBitcoin(to, satoshis);
|
|
196
|
+
} catch (error) {
|
|
197
|
+
this.handleError(error);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
async getNetwork(): Promise<BitcoinNetwork> {
|
|
202
|
+
this.ensureInstalled();
|
|
203
|
+
const provider = this.getProvider()!;
|
|
204
|
+
|
|
205
|
+
try {
|
|
206
|
+
const network = await provider.getNetwork();
|
|
207
|
+
return this.mapNetwork(network);
|
|
208
|
+
} catch (error) {
|
|
209
|
+
this.handleError(error);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
async switchNetwork(network: BitcoinNetwork): Promise<void> {
|
|
214
|
+
this.ensureInstalled();
|
|
215
|
+
const provider = this.getProvider()!;
|
|
216
|
+
|
|
217
|
+
try {
|
|
218
|
+
const unisatNetwork = this.mapToUnisatNetwork(network);
|
|
219
|
+
await provider.switchNetwork(unisatNetwork);
|
|
220
|
+
} catch (error) {
|
|
221
|
+
this.handleError(error);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
private async checkAndSwitchNetwork(provider: UnisatProvider, network: BitcoinNetwork): Promise<void> {
|
|
226
|
+
const currentNetwork = await provider.getNetwork();
|
|
227
|
+
const targetNetwork = this.mapToUnisatNetwork(network);
|
|
228
|
+
if (currentNetwork !== targetNetwork) {
|
|
229
|
+
await provider.switchNetwork(targetNetwork);
|
|
230
|
+
}
|
|
231
|
+
// testnet4 requires additional chain switch
|
|
232
|
+
if (network === 'testnet4') {
|
|
233
|
+
const currentChain = await provider.getChain();
|
|
234
|
+
if (currentChain.enum !== this.BITCOIN_TESTNET4) {
|
|
235
|
+
await provider.switchChain(this.BITCOIN_TESTNET4);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
private setupListeners(): void {
|
|
241
|
+
const provider = this.getProvider();
|
|
242
|
+
if (!provider) return;
|
|
243
|
+
|
|
244
|
+
// Account change listener - wrap to handle unknown arg type
|
|
245
|
+
const handleAccountsChanged = (arg: unknown) => {
|
|
246
|
+
const accounts = arg as string[];
|
|
247
|
+
if (!Array.isArray(accounts) || accounts.length === 0) {
|
|
248
|
+
this.emitAccountsChanged([]);
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
provider.getPublicKey().then((publicKey) => {
|
|
253
|
+
const walletAccounts: WalletAccount[] = accounts.map((address) => ({
|
|
254
|
+
address,
|
|
255
|
+
publicKey,
|
|
256
|
+
type: this.inferAddressType(address),
|
|
257
|
+
}));
|
|
258
|
+
this.emitAccountsChanged(walletAccounts);
|
|
259
|
+
}).catch(() => {
|
|
260
|
+
// If we can't get public key, emit empty to trigger disconnect
|
|
261
|
+
this.emitAccountsChanged([]);
|
|
262
|
+
});
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
// Network change listener
|
|
266
|
+
const handleNetworkChanged = (arg: unknown) => {
|
|
267
|
+
const network = arg as string;
|
|
268
|
+
const btcNetwork = this.mapNetwork(network);
|
|
269
|
+
this.emitNetworkChanged(btcNetwork);
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
provider.on('accountsChanged', handleAccountsChanged);
|
|
273
|
+
provider.on('networkChanged', handleNetworkChanged);
|
|
274
|
+
|
|
275
|
+
this._unsubscribeAccounts = () => {
|
|
276
|
+
provider.removeListener('accountsChanged', handleAccountsChanged);
|
|
277
|
+
};
|
|
278
|
+
this._unsubscribeNetwork = () => {
|
|
279
|
+
provider.removeListener('networkChanged', handleNetworkChanged);
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
private mapNetwork(network: string): BitcoinNetwork {
|
|
284
|
+
switch (network.toLowerCase()) {
|
|
285
|
+
case 'livenet':
|
|
286
|
+
case 'mainnet':
|
|
287
|
+
return 'mainnet';
|
|
288
|
+
case 'testnet':
|
|
289
|
+
return 'testnet';
|
|
290
|
+
case 'testnet4':
|
|
291
|
+
return 'testnet4';
|
|
292
|
+
case 'signet':
|
|
293
|
+
return 'signet';
|
|
294
|
+
default:
|
|
295
|
+
return 'mainnet';
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
private mapToUnisatNetwork(network: BitcoinNetwork): UnisatNetwork {
|
|
300
|
+
switch (network) {
|
|
301
|
+
case 'mainnet':
|
|
302
|
+
return 'livenet';
|
|
303
|
+
case 'testnet':
|
|
304
|
+
case 'testnet4':
|
|
305
|
+
return 'testnet';
|
|
306
|
+
case 'signet':
|
|
307
|
+
return 'signet';
|
|
308
|
+
default:
|
|
309
|
+
return 'livenet';
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { UnisatConnector } from './UnisatConnector';
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import type { BitcoinNetwork } from 'otx-btc-wallet-core';
|
|
2
|
+
import type {
|
|
3
|
+
IBtcService,
|
|
4
|
+
Utxo,
|
|
5
|
+
UtxoWithTx,
|
|
6
|
+
FeeRates,
|
|
7
|
+
Transaction,
|
|
8
|
+
FullTransaction,
|
|
9
|
+
AddressBalance,
|
|
10
|
+
NetworkEndpoints,
|
|
11
|
+
} from './types';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Default API endpoints for blockstream.info
|
|
15
|
+
* Note: Blockstream doesn't support testnet4 and signet, fallback to mempool for those
|
|
16
|
+
*/
|
|
17
|
+
const DEFAULT_BLOCKSTREAM_ENDPOINTS: Record<BitcoinNetwork, string> = {
|
|
18
|
+
mainnet: 'https://blockstream.info/api',
|
|
19
|
+
testnet: 'https://blockstream.info/testnet/api',
|
|
20
|
+
testnet4: 'https://mempool.space/testnet4/api', // Fallback to mempool
|
|
21
|
+
signet: 'https://mempool.space/signet/api', // Fallback to mempool
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Fetch with timeout helper
|
|
26
|
+
*/
|
|
27
|
+
async function fetchWithTimeout(
|
|
28
|
+
url: string,
|
|
29
|
+
options?: RequestInit,
|
|
30
|
+
timeout: number = 10000
|
|
31
|
+
): Promise<Response> {
|
|
32
|
+
const controller = new AbortController();
|
|
33
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
const response = await fetch(url, {
|
|
37
|
+
...options,
|
|
38
|
+
signal: controller.signal,
|
|
39
|
+
});
|
|
40
|
+
clearTimeout(timeoutId);
|
|
41
|
+
return response;
|
|
42
|
+
} catch (error) {
|
|
43
|
+
clearTimeout(timeoutId);
|
|
44
|
+
throw error;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Blockstream.info Bitcoin Service
|
|
50
|
+
*
|
|
51
|
+
* @see https://github.com/Blockstream/esplora/blob/master/API.md
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* ```typescript
|
|
55
|
+
* // Default usage
|
|
56
|
+
* const service = new BlockstreamService('mainnet');
|
|
57
|
+
*
|
|
58
|
+
* // With custom endpoints
|
|
59
|
+
* const service = new BlockstreamService('mainnet', {
|
|
60
|
+
* mainnet: 'https://my-esplora-proxy.com/api',
|
|
61
|
+
* testnet: 'https://my-esplora-proxy.com/testnet/api',
|
|
62
|
+
* });
|
|
63
|
+
* ```
|
|
64
|
+
*/
|
|
65
|
+
export class BlockstreamService implements IBtcService {
|
|
66
|
+
readonly name = 'blockstream';
|
|
67
|
+
private _network: BitcoinNetwork;
|
|
68
|
+
private _customEndpoints: NetworkEndpoints;
|
|
69
|
+
|
|
70
|
+
constructor(network: BitcoinNetwork = 'mainnet', customEndpoints?: NetworkEndpoints) {
|
|
71
|
+
this._network = network;
|
|
72
|
+
this._customEndpoints = customEndpoints ?? {};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
get network(): BitcoinNetwork {
|
|
76
|
+
return this._network;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
private get baseUrl(): string {
|
|
80
|
+
// Use custom endpoint if provided, otherwise use default
|
|
81
|
+
return this._customEndpoints[this._network] ?? DEFAULT_BLOCKSTREAM_ENDPOINTS[this._network];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
setNetwork(network: BitcoinNetwork): void {
|
|
85
|
+
this._network = network;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Set custom endpoints for this service
|
|
90
|
+
*/
|
|
91
|
+
setCustomEndpoints(endpoints: NetworkEndpoints): void {
|
|
92
|
+
this._customEndpoints = { ...this._customEndpoints, ...endpoints };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Get current endpoints configuration
|
|
97
|
+
*/
|
|
98
|
+
getEndpoints(): Record<BitcoinNetwork, string> {
|
|
99
|
+
return {
|
|
100
|
+
mainnet: this._customEndpoints.mainnet ?? DEFAULT_BLOCKSTREAM_ENDPOINTS.mainnet,
|
|
101
|
+
testnet: this._customEndpoints.testnet ?? DEFAULT_BLOCKSTREAM_ENDPOINTS.testnet,
|
|
102
|
+
testnet4: this._customEndpoints.testnet4 ?? DEFAULT_BLOCKSTREAM_ENDPOINTS.testnet4,
|
|
103
|
+
signet: this._customEndpoints.signet ?? DEFAULT_BLOCKSTREAM_ENDPOINTS.signet,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// ============ UTXO Methods ============
|
|
108
|
+
|
|
109
|
+
async getUtxos(address: string): Promise<Utxo[]> {
|
|
110
|
+
const response = await fetchWithTimeout(`${this.baseUrl}/address/${address}/utxo`);
|
|
111
|
+
|
|
112
|
+
if (!response.ok) {
|
|
113
|
+
throw new Error(`Blockstream API error: ${response.status} ${response.statusText}`);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return response.json() as Promise<Utxo[]>;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async getUtxosWithTxHex(address: string): Promise<UtxoWithTx[]> {
|
|
120
|
+
const utxos = await this.getUtxos(address);
|
|
121
|
+
|
|
122
|
+
const utxosWithTx = await Promise.all(
|
|
123
|
+
utxos.map(async (utxo): Promise<UtxoWithTx> => {
|
|
124
|
+
const txHex = await this.getTxHex(utxo.txid);
|
|
125
|
+
return { ...utxo, txHex };
|
|
126
|
+
})
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
return utxosWithTx;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// ============ Transaction Methods ============
|
|
133
|
+
|
|
134
|
+
async getTxHex(txid: string): Promise<string> {
|
|
135
|
+
const response = await fetchWithTimeout(`${this.baseUrl}/tx/${txid}/hex`);
|
|
136
|
+
|
|
137
|
+
if (!response.ok) {
|
|
138
|
+
throw new Error(`Blockstream API error: ${response.status} ${response.statusText}`);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return response.text();
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async getTransaction(txid: string): Promise<Transaction> {
|
|
145
|
+
const response = await fetchWithTimeout(`${this.baseUrl}/tx/${txid}`);
|
|
146
|
+
|
|
147
|
+
if (!response.ok) {
|
|
148
|
+
throw new Error(`Blockstream API error: ${response.status} ${response.statusText}`);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return response.json() as Promise<Transaction>;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
async getFullTransaction(txid: string): Promise<FullTransaction> {
|
|
155
|
+
const response = await fetchWithTimeout(`${this.baseUrl}/tx/${txid}`);
|
|
156
|
+
|
|
157
|
+
if (!response.ok) {
|
|
158
|
+
throw new Error(`Blockstream API error: ${response.status} ${response.statusText}`);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return response.json() as Promise<FullTransaction>;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
async broadcastTransaction(txHex: string): Promise<string> {
|
|
165
|
+
const response = await fetchWithTimeout(
|
|
166
|
+
`${this.baseUrl}/tx`,
|
|
167
|
+
{
|
|
168
|
+
method: 'POST',
|
|
169
|
+
body: txHex,
|
|
170
|
+
headers: { 'Content-Type': 'text/plain' },
|
|
171
|
+
},
|
|
172
|
+
30000
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
if (!response.ok) {
|
|
176
|
+
const error = await response.text();
|
|
177
|
+
throw new Error(`Blockstream broadcast error: ${error}`);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return response.text();
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// ============ Address Methods ============
|
|
184
|
+
|
|
185
|
+
async getAddressInfo(address: string): Promise<AddressBalance> {
|
|
186
|
+
const response = await fetchWithTimeout(`${this.baseUrl}/address/${address}`);
|
|
187
|
+
|
|
188
|
+
if (!response.ok) {
|
|
189
|
+
throw new Error(`Blockstream API error: ${response.status} ${response.statusText}`);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return response.json() as Promise<AddressBalance>;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
async getBalance(address: string): Promise<number> {
|
|
196
|
+
const info = await this.getAddressInfo(address);
|
|
197
|
+
const confirmedBalance =
|
|
198
|
+
info.chain_stats.funded_txo_sum - info.chain_stats.spent_txo_sum;
|
|
199
|
+
const unconfirmedBalance =
|
|
200
|
+
info.mempool_stats.funded_txo_sum - info.mempool_stats.spent_txo_sum;
|
|
201
|
+
return confirmedBalance + unconfirmedBalance;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
async getConfirmedBalance(address: string): Promise<number> {
|
|
205
|
+
const info = await this.getAddressInfo(address);
|
|
206
|
+
return info.chain_stats.funded_txo_sum - info.chain_stats.spent_txo_sum;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// ============ Fee Methods ============
|
|
210
|
+
|
|
211
|
+
async getFeeRates(): Promise<FeeRates> {
|
|
212
|
+
// Blockstream uses mempool.space fee API format
|
|
213
|
+
const response = await fetchWithTimeout(`${this.baseUrl}/fee-estimates`);
|
|
214
|
+
|
|
215
|
+
if (!response.ok) {
|
|
216
|
+
throw new Error(`Blockstream API error: ${response.status} ${response.statusText}`);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const fees = (await response.json()) as Record<string, number>;
|
|
220
|
+
// Blockstream returns fee estimates keyed by confirmation target
|
|
221
|
+
// e.g., { "1": 25.5, "2": 20.1, "3": 15.2, ... }
|
|
222
|
+
return {
|
|
223
|
+
fastest: Math.ceil(fees['1'] || fees['2'] || 10),
|
|
224
|
+
halfHour: Math.ceil(fees['3'] || fees['4'] || 8),
|
|
225
|
+
hour: Math.ceil(fees['6'] || fees['12'] || 5),
|
|
226
|
+
economy: Math.ceil(fees['144'] || fees['504'] || 2),
|
|
227
|
+
minimum: Math.ceil(fees['1008'] || 1),
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
}
|