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.
Files changed (142) hide show
  1. package/README.md +554 -0
  2. package/dist/base-IAFq7sd8.d.mts +53 -0
  3. package/dist/base-IAFq7sd8.d.ts +53 -0
  4. package/dist/binance/index.d.mts +81 -0
  5. package/dist/binance/index.d.ts +81 -0
  6. package/dist/binance/index.js +13 -0
  7. package/dist/binance/index.js.map +1 -0
  8. package/dist/binance/index.mjs +4 -0
  9. package/dist/binance/index.mjs.map +1 -0
  10. package/dist/bitget/index.d.mts +84 -0
  11. package/dist/bitget/index.d.ts +84 -0
  12. package/dist/bitget/index.js +13 -0
  13. package/dist/bitget/index.js.map +1 -0
  14. package/dist/bitget/index.mjs +4 -0
  15. package/dist/bitget/index.mjs.map +1 -0
  16. package/dist/chunk-5Z5Q2Y75.mjs +91 -0
  17. package/dist/chunk-5Z5Q2Y75.mjs.map +1 -0
  18. package/dist/chunk-7KK2LZLZ.mjs +208 -0
  19. package/dist/chunk-7KK2LZLZ.mjs.map +1 -0
  20. package/dist/chunk-AW2JZIHR.mjs +753 -0
  21. package/dist/chunk-AW2JZIHR.mjs.map +1 -0
  22. package/dist/chunk-EIJOSZXZ.js +331 -0
  23. package/dist/chunk-EIJOSZXZ.js.map +1 -0
  24. package/dist/chunk-EQHR7P7G.js +541 -0
  25. package/dist/chunk-EQHR7P7G.js.map +1 -0
  26. package/dist/chunk-EWRXLZO4.mjs +539 -0
  27. package/dist/chunk-EWRXLZO4.mjs.map +1 -0
  28. package/dist/chunk-FISNQZZ7.js +802 -0
  29. package/dist/chunk-FISNQZZ7.js.map +1 -0
  30. package/dist/chunk-HL4WDMGS.js +200 -0
  31. package/dist/chunk-HL4WDMGS.js.map +1 -0
  32. package/dist/chunk-IPYWR76I.js +314 -0
  33. package/dist/chunk-IPYWR76I.js.map +1 -0
  34. package/dist/chunk-JYYNWR5G.js +142 -0
  35. package/dist/chunk-JYYNWR5G.js.map +1 -0
  36. package/dist/chunk-LNKTYZJM.js +701 -0
  37. package/dist/chunk-LNKTYZJM.js.map +1 -0
  38. package/dist/chunk-LVZMONQL.mjs +699 -0
  39. package/dist/chunk-LVZMONQL.mjs.map +1 -0
  40. package/dist/chunk-MFXLQWOE.js +93 -0
  41. package/dist/chunk-MFXLQWOE.js.map +1 -0
  42. package/dist/chunk-NBIA4TTE.mjs +204 -0
  43. package/dist/chunk-NBIA4TTE.mjs.map +1 -0
  44. package/dist/chunk-O4DD2XJ2.js +206 -0
  45. package/dist/chunk-O4DD2XJ2.js.map +1 -0
  46. package/dist/chunk-P7HVBU2B.mjs +140 -0
  47. package/dist/chunk-P7HVBU2B.mjs.map +1 -0
  48. package/dist/chunk-Q7QVQYEB.js +210 -0
  49. package/dist/chunk-Q7QVQYEB.js.map +1 -0
  50. package/dist/chunk-RLZEG6KL.mjs +329 -0
  51. package/dist/chunk-RLZEG6KL.mjs.map +1 -0
  52. package/dist/chunk-SYLDBJ75.mjs +246 -0
  53. package/dist/chunk-SYLDBJ75.mjs.map +1 -0
  54. package/dist/chunk-TTEUU3CI.mjs +198 -0
  55. package/dist/chunk-TTEUU3CI.mjs.map +1 -0
  56. package/dist/chunk-V66BXDTR.mjs +292 -0
  57. package/dist/chunk-V66BXDTR.mjs.map +1 -0
  58. package/dist/chunk-X77ZT4OI.js +268 -0
  59. package/dist/chunk-X77ZT4OI.js.map +1 -0
  60. package/dist/imtoken/index.d.mts +116 -0
  61. package/dist/imtoken/index.d.ts +116 -0
  62. package/dist/imtoken/index.js +14 -0
  63. package/dist/imtoken/index.js.map +1 -0
  64. package/dist/imtoken/index.mjs +5 -0
  65. package/dist/imtoken/index.mjs.map +1 -0
  66. package/dist/index.d.mts +14 -0
  67. package/dist/index.d.ts +14 -0
  68. package/dist/index.js +170 -0
  69. package/dist/index.js.map +1 -0
  70. package/dist/index.mjs +13 -0
  71. package/dist/index.mjs.map +1 -0
  72. package/dist/ledger/index.d.mts +290 -0
  73. package/dist/ledger/index.d.ts +290 -0
  74. package/dist/ledger/index.js +14 -0
  75. package/dist/ledger/index.js.map +1 -0
  76. package/dist/ledger/index.mjs +5 -0
  77. package/dist/ledger/index.mjs.map +1 -0
  78. package/dist/okx/index.d.mts +88 -0
  79. package/dist/okx/index.d.ts +88 -0
  80. package/dist/okx/index.js +13 -0
  81. package/dist/okx/index.js.map +1 -0
  82. package/dist/okx/index.mjs +4 -0
  83. package/dist/okx/index.mjs.map +1 -0
  84. package/dist/phantom/index.d.mts +96 -0
  85. package/dist/phantom/index.d.ts +96 -0
  86. package/dist/phantom/index.js +14 -0
  87. package/dist/phantom/index.js.map +1 -0
  88. package/dist/phantom/index.mjs +5 -0
  89. package/dist/phantom/index.mjs.map +1 -0
  90. package/dist/psbt-builder-CFOs69Z5.d.mts +131 -0
  91. package/dist/psbt-builder-CFOs69Z5.d.ts +131 -0
  92. package/dist/trezor/index.d.mts +155 -0
  93. package/dist/trezor/index.d.ts +155 -0
  94. package/dist/trezor/index.js +14 -0
  95. package/dist/trezor/index.js.map +1 -0
  96. package/dist/trezor/index.mjs +5 -0
  97. package/dist/trezor/index.mjs.map +1 -0
  98. package/dist/unisat/index.d.mts +75 -0
  99. package/dist/unisat/index.d.ts +75 -0
  100. package/dist/unisat/index.js +13 -0
  101. package/dist/unisat/index.js.map +1 -0
  102. package/dist/unisat/index.mjs +4 -0
  103. package/dist/unisat/index.mjs.map +1 -0
  104. package/dist/utils/index.d.mts +398 -0
  105. package/dist/utils/index.d.ts +398 -0
  106. package/dist/utils/index.js +120 -0
  107. package/dist/utils/index.js.map +1 -0
  108. package/dist/utils/index.mjs +3 -0
  109. package/dist/utils/index.mjs.map +1 -0
  110. package/dist/xverse/index.d.mts +79 -0
  111. package/dist/xverse/index.d.ts +79 -0
  112. package/dist/xverse/index.js +13 -0
  113. package/dist/xverse/index.js.map +1 -0
  114. package/dist/xverse/index.mjs +4 -0
  115. package/dist/xverse/index.mjs.map +1 -0
  116. package/package.json +108 -0
  117. package/src/base.ts +132 -0
  118. package/src/binance/BinanceConnector.ts +307 -0
  119. package/src/binance/index.ts +1 -0
  120. package/src/bitget/BitgetConnector.ts +301 -0
  121. package/src/bitget/index.ts +1 -0
  122. package/src/imtoken/ImTokenConnector.ts +420 -0
  123. package/src/imtoken/index.ts +2 -0
  124. package/src/index.ts +78 -0
  125. package/src/ledger/LedgerConnector.ts +1019 -0
  126. package/src/ledger/index.ts +8 -0
  127. package/src/okx/OKXConnector.ts +230 -0
  128. package/src/okx/index.ts +1 -0
  129. package/src/phantom/PhantomConnector.ts +381 -0
  130. package/src/phantom/index.ts +2 -0
  131. package/src/trezor/TrezorConnector.ts +824 -0
  132. package/src/trezor/index.ts +6 -0
  133. package/src/unisat/UnisatConnector.ts +312 -0
  134. package/src/unisat/index.ts +1 -0
  135. package/src/utils/blockstream.ts +230 -0
  136. package/src/utils/btc-service.ts +364 -0
  137. package/src/utils/index.ts +56 -0
  138. package/src/utils/mempool.ts +232 -0
  139. package/src/utils/psbt-builder.ts +492 -0
  140. package/src/utils/types.ts +183 -0
  141. package/src/xverse/XverseConnector.ts +479 -0
  142. package/src/xverse/index.ts +1 -0
@@ -0,0 +1,6 @@
1
+ export { TrezorConnector } from './TrezorConnector';
2
+ export type {
3
+ TrezorConnectorOptions,
4
+ TrezorAddressType,
5
+ TrezorSendBitcoinOptions,
6
+ } from './TrezorConnector';
@@ -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
+ }