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,479 @@
1
+ import type {
2
+ WalletAccount,
3
+ BitcoinNetwork,
4
+ SignPsbtOptions,
5
+ } from 'otx-btc-wallet-core';
6
+ import { BaseConnector } from '../base';
7
+
8
+ /**
9
+ * Address purpose enum for Xverse
10
+ */
11
+ enum AddressPurpose {
12
+ Payment = 'payment',
13
+ Ordinals = 'ordinals',
14
+ }
15
+
16
+ /**
17
+ * Xverse address response
18
+ */
19
+ interface AddressResponse {
20
+ address: string;
21
+ publicKey: string;
22
+ purpose: AddressPurpose;
23
+ }
24
+
25
+ /**
26
+ * Xverse Provider request/response types
27
+ */
28
+ interface XverseRequestResponse<T> {
29
+ result: T;
30
+ error?: { message: string };
31
+ }
32
+
33
+ interface GetAddressesResult {
34
+ addresses: AddressResponse[];
35
+ }
36
+
37
+ interface SignMessageResult {
38
+ signature: string;
39
+ }
40
+
41
+ interface SignPsbtResult {
42
+ psbt: string;
43
+ txid?: string;
44
+ }
45
+
46
+ interface SendTransferResult {
47
+ txid: string;
48
+ }
49
+
50
+ interface NetworkResult {
51
+ bitcoin?: { name: string };
52
+ }
53
+
54
+ interface XverseEventData {
55
+ network?: string;
56
+ }
57
+
58
+ /**
59
+ * Xverse Bitcoin Provider interface
60
+ */
61
+ interface XverseBitcoinProvider {
62
+ request<T>(method: string, params: unknown): Promise<XverseRequestResponse<T>>;
63
+ addListener(event: string, callback: (data: XverseEventData) => void): () => void;
64
+ }
65
+
66
+ // Extend window type
67
+ declare global {
68
+ interface Window {
69
+ XverseProviders?: {
70
+ BitcoinProvider?: XverseBitcoinProvider;
71
+ };
72
+ }
73
+ }
74
+
75
+ // Xverse wallet icon
76
+ const XVERSE_ICON =
77
+ 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCIgdmlld0JveD0iMCAwIDIwMCAyMDAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMF83MDM3XzEwMykiPgo8cGF0aCBkPSJNMjAwIDBIMFYyMDBIMjAwVjBaIiBmaWxsPSJ1cmwoI3BhaW50MF9saW5lYXJfNzAzN18xMDMpIi8+CjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMV83MDM3XzEwMykiPgo8cGF0aCBkPSJNMTU5LjM2NyAxNTcuNDQ1VjEzNS43MThDMTU5LjM2NyAxMzQuODU2IDE1OS4wMjUgMTM0LjAzNCAxNTguNDE0IDEzMy40MjRMNjUuOTY2NCA0MC45NzU2QzY1LjM1NTkgNDAuMzY1MSA2NC41MzQyIDQwLjAyMjggNjMuNjcyNiA0MC4wMjI4SDQxLjk0NTZDNDAuODczIDQwLjAyMjggNDAgNDAuODk1NyA0MCA0MS45Njg0VjYyLjE1NDlDNDAgNjMuMDE2NSA0MC4zNDIzIDYzLjgzOCA0MC45NTI4IDY0LjQ0ODZMNzQuMTM2OCA5Ny42MzI0Qzc0Ljg5NTYgOTguMzkxNiA3NC44OTU2IDk5LjYyNCA3NC4xMzY4IDEwMC4zODNMNDAuNTcwNiAxMzMuOTQ5QzQwLjIwNTQgMTM0LjMxNCA0MCAxMzQuODEgNDAgMTM1LjMyNFYxNTcuNDQ1QzQwIDE1OC41MTcgNDAuODczIDE1OS4zOTEgNDEuOTQ1NiAxNTkuMzkxSDc4LjI1MDVDNzkuMzIzMiAxNTkuMzkxIDgwLjE5NiAxNTguNTE3IDgwLjE5NiAxNTcuNDQ1VjE0NC40MTNDODAuMTk2IDE0My45IDgwLjQwMTYgMTQzLjQwMyA4MC43NjY4IDE0My4wMzhMOTguNzczNiAxMjUuMDMxQzk5LjUzMjQgMTI0LjI3MiAxMDAuNzY1IDEyNC4yNzIgMTAxLjUyNCAxMjUuMDMxTDEzNC45MzYgMTU4LjQ0NEMxMzUuNTQ2IDE1OS4wNTQgMTM2LjM2OCAxNTkuMzk2IDEzNy4yMyAxNTkuMzk2SDE1Ny40MTZDMTU4LjQ4OSAxNTkuMzk2IDE1OS4zNjIgMTU4LjUyMyAxNTkuMzYyIDE1Ny40NTFMMTU5LjM2NyAxNTcuNDQ1WiIgZmlsbD0id2hpdGUiLz4KPHBhdGggZD0iTTExMC45MzggNjguNzk2SDEyOS4xMjJDMTMwLjIgNjguNzk2IDEzMS4wNzkgNjkuNjc0NyAxMzEuMDc5IDcwLjc1MzFWODguOTM2OEMxMzEuMDc5IDkwLjY4MjggMTMzLjE5IDkxLjU1NiAxMzQuNDIyIDkwLjMxNzZMMTU5LjM2NyA2NS4zMzI4QzE1OS43MzIgNjQuOTY3NiAxNTkuOTM4IDY0LjQ3MTMgMTU5LjkzOCA2My45NTJWNDIuMDcwOUMxNTkuOTM4IDQwLjk5MjUgMTU5LjA2NCA0MC4xMTM5IDE1Ny45ODEgNDAuMTEzOUwxMzUuNzc0IDQwLjA4NTRDMTM1LjI1NiA0MC4wODU0IDEzNC43NTkgNDAuMjkwOCAxMzQuMzg4IDQwLjY1NTlMMTA5LjU1MiA2NS40NTI2QzEwOC4zMTkgNjYuNjg1IDEwOS4xOTIgNjguNzk2IDExMC45MzIgNjguNzk2SDExMC45MzhaIiBmaWxsPSIjRUU3QTMwIi8+CjwvZz4KPC9nPgo8ZGVmcz4KPGxpbmVhckdyYWRpZW50IGlkPSJwYWludDBfbGluZWFyXzcwMzdfMTAzIiB4MT0iMTAwIiB5MT0iMCIgeDI9IjEwMCIgeTI9IjIwMCIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiPgo8c3RvcCBzdG9wLWNvbG9yPSIjMEYwRjBGIi8+CjxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iIzE4MTgxOCIvPgo8L2xpbmVhckdyYWRpZW50Pgo8Y2xpcFBhdGggaWQ9ImNsaXAwXzcwMzdfMTAzIj4KPHJlY3Qgd2lkdGg9IjIwMCIgaGVpZ2h0PSIyMDAiIHJ4PSIzNS41NTU2IiBmaWxsPSJ3aGl0ZSIvPgo8L2NsaXBQYXRoPgo8Y2xpcFBhdGggaWQ9ImNsaXAxXzcwMzdfMTAzIj4KPHJlY3Qgd2lkdGg9IjEyMCIgaGVpZ2h0PSIxMjAiIGZpbGw9IndoaXRlIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSg0MCA0MCkiLz4KPC9jbGlwUGF0aD4KPC9kZWZzPgo8L3N2Zz4K';
78
+
79
+ /**
80
+ * Xverse Wallet Connector
81
+ * Uses the native Xverse Provider API
82
+ *
83
+ * @see https://docs.xverse.app/
84
+ */
85
+ export class XverseConnector extends BaseConnector {
86
+ readonly id = 'xverse';
87
+ readonly name = 'Xverse Wallet';
88
+ readonly icon = XVERSE_ICON;
89
+
90
+ private _paymentAddress: WalletAccount | null = null;
91
+ private _ordinalsAddress: WalletAccount | null = null;
92
+ private _network: BitcoinNetwork = 'mainnet';
93
+ private _accounts: AddressResponse[] = [];
94
+ private _removeAccountChangeListener: (() => void) | null = null;
95
+ private _removeNetworkChangeListener: (() => void) | null = null;
96
+ private _removeDisconnectedListener: (() => void) | null = null;
97
+
98
+ protected getProvider(): XverseBitcoinProvider | undefined {
99
+ if (typeof window === 'undefined') return undefined;
100
+ return window.XverseProviders?.BitcoinProvider;
101
+ }
102
+
103
+ async connect(network: BitcoinNetwork = 'mainnet'): Promise<WalletAccount> {
104
+ const provider = this.getProvider();
105
+ if (!provider) {
106
+ this.handleError(new Error('Xverse Wallet is not installed'));
107
+ }
108
+
109
+ try {
110
+ this._network = network;
111
+
112
+ // Request permissions first
113
+ const permissionResponse = await provider!.request<unknown>('wallet_requestPermissions', undefined);
114
+ if (permissionResponse.error) {
115
+ throw new Error(permissionResponse.error.message);
116
+ }
117
+
118
+ // Get addresses
119
+ const response = await provider!.request<GetAddressesResult>('getAddresses', {
120
+ purposes: [AddressPurpose.Payment, AddressPurpose.Ordinals],
121
+ message: 'Connect to application',
122
+ });
123
+
124
+ if (response.error) {
125
+ throw response.error;
126
+ }
127
+
128
+ const addresses = response.result.addresses;
129
+ this._accounts = addresses;
130
+
131
+ const paymentAddr = addresses.find((a) => a.purpose === AddressPurpose.Payment);
132
+ const ordinalsAddr = addresses.find((a) => a.purpose === AddressPurpose.Ordinals);
133
+
134
+ if (paymentAddr) {
135
+ this._paymentAddress = {
136
+ address: paymentAddr.address,
137
+ publicKey: paymentAddr.publicKey,
138
+ type: this.inferAddressType(paymentAddr.address),
139
+ };
140
+ }
141
+
142
+ if (ordinalsAddr) {
143
+ this._ordinalsAddress = {
144
+ address: ordinalsAddr.address,
145
+ publicKey: ordinalsAddr.publicKey,
146
+ type: this.inferAddressType(ordinalsAddr.address),
147
+ };
148
+ }
149
+
150
+ // Check and switch network if needed
151
+ await this.checkAndSwitchNetwork(network);
152
+
153
+ // Setup event listeners
154
+ this.setupEventListeners();
155
+
156
+ if (!this._paymentAddress) {
157
+ throw new Error('No payment address found');
158
+ }
159
+
160
+ return this._paymentAddress;
161
+ } catch (error) {
162
+ this.handleError(error);
163
+ }
164
+ }
165
+
166
+ private async checkAndSwitchNetwork(network: BitcoinNetwork): Promise<void> {
167
+ const provider = this.getProvider();
168
+ if (!provider) return;
169
+
170
+ // For testnet4, we need to switch to Testnet4 network in Xverse
171
+ if (network === 'testnet4') {
172
+ try {
173
+ const currentNetwork = await provider.request<NetworkResult>('wallet_getNetwork', null);
174
+ if (currentNetwork?.result?.bitcoin?.name !== 'Testnet4') {
175
+ await provider.request<unknown>('wallet_changeNetwork', { name: 'Testnet4' });
176
+ }
177
+ } catch {
178
+ // Network switch may not be supported, continue anyway
179
+ }
180
+ } else if (network === 'testnet') {
181
+ try {
182
+ const currentNetwork = await provider.request<NetworkResult>('wallet_getNetwork', null);
183
+ if (currentNetwork?.result?.bitcoin?.name !== 'Testnet') {
184
+ await provider.request<unknown>('wallet_changeNetwork', { name: 'Testnet' });
185
+ }
186
+ } catch {
187
+ // Network switch may not be supported
188
+ }
189
+ } else if (network === 'mainnet') {
190
+ try {
191
+ const currentNetwork = await provider.request<NetworkResult>('wallet_getNetwork', null);
192
+ if (currentNetwork?.result?.bitcoin?.name !== 'Mainnet') {
193
+ await provider.request<unknown>('wallet_changeNetwork', { name: 'Mainnet' });
194
+ }
195
+ } catch {
196
+ // Network switch may not be supported
197
+ }
198
+ }
199
+ }
200
+
201
+ private setupEventListeners(): void {
202
+ const provider = this.getProvider();
203
+ if (!provider || typeof provider.addListener !== 'function') return;
204
+
205
+ // Remove existing listeners
206
+ this.removeEventListeners();
207
+
208
+ // Account change listener
209
+ this._removeAccountChangeListener = provider.addListener('accountChange', async () => {
210
+ await this.handleAccountChange();
211
+ });
212
+
213
+ // Network change listener
214
+ this._removeNetworkChangeListener = provider.addListener('networkChange', async (event) => {
215
+ await this.handleNetworkChange(event);
216
+ });
217
+
218
+ // Disconnected listener
219
+ this._removeDisconnectedListener = provider.addListener('accountDisconnected', () => {
220
+ this.emitAccountsChanged([]);
221
+ });
222
+ }
223
+
224
+ private async handleAccountChange(): Promise<void> {
225
+ const provider = this.getProvider();
226
+ if (!provider) return;
227
+
228
+ try {
229
+ const response = await provider.request<GetAddressesResult>('getAddresses', {
230
+ purposes: [AddressPurpose.Payment, AddressPurpose.Ordinals],
231
+ });
232
+
233
+ if (!response.error && response.result?.addresses?.length) {
234
+ this._accounts = response.result.addresses;
235
+ const paymentAddr = response.result.addresses.find((a) => a.purpose === AddressPurpose.Payment);
236
+
237
+ if (paymentAddr) {
238
+ this._paymentAddress = {
239
+ address: paymentAddr.address,
240
+ publicKey: paymentAddr.publicKey,
241
+ type: this.inferAddressType(paymentAddr.address),
242
+ };
243
+
244
+ const accounts: WalletAccount[] = response.result.addresses.map((addr) => ({
245
+ address: addr.address,
246
+ publicKey: addr.publicKey,
247
+ type: this.inferAddressType(addr.address),
248
+ }));
249
+
250
+ this.emitAccountsChanged(accounts);
251
+ }
252
+ }
253
+ } catch (error) {
254
+ console.error('Error handling account change:', error);
255
+ }
256
+ }
257
+
258
+ private async handleNetworkChange(event: XverseEventData): Promise<void> {
259
+ if (!event.network) return;
260
+
261
+ const networkMap: Record<string, BitcoinNetwork> = {
262
+ 'Mainnet': 'mainnet',
263
+ 'Testnet': 'testnet',
264
+ 'Testnet4': 'testnet4',
265
+ 'Signet': 'signet',
266
+ };
267
+
268
+ const mappedNetwork = networkMap[event.network] || 'mainnet';
269
+ if (mappedNetwork !== this._network) {
270
+ this._network = mappedNetwork;
271
+ this.emitNetworkChanged(mappedNetwork);
272
+ }
273
+ }
274
+
275
+ private removeEventListeners(): void {
276
+ this._removeAccountChangeListener?.();
277
+ this._removeAccountChangeListener = null;
278
+
279
+ this._removeNetworkChangeListener?.();
280
+ this._removeNetworkChangeListener = null;
281
+
282
+ this._removeDisconnectedListener?.();
283
+ this._removeDisconnectedListener = null;
284
+ }
285
+
286
+ async disconnect(): Promise<void> {
287
+ const provider = this.getProvider();
288
+
289
+ try {
290
+ if (provider) {
291
+ await provider.request<unknown>('wallet_renouncePermissions', undefined);
292
+ }
293
+ } catch {
294
+ // Ignore disconnect errors
295
+ } finally {
296
+ this.removeEventListeners();
297
+ this._paymentAddress = null;
298
+ this._ordinalsAddress = null;
299
+ this._accounts = [];
300
+ this.cleanup();
301
+ }
302
+ }
303
+
304
+ async getAccounts(): Promise<WalletAccount[]> {
305
+ const accounts: WalletAccount[] = [];
306
+ if (this._paymentAddress) accounts.push(this._paymentAddress);
307
+ if (this._ordinalsAddress) accounts.push(this._ordinalsAddress);
308
+ return accounts;
309
+ }
310
+
311
+ async signMessage(message: string): Promise<string> {
312
+ const provider = this.getProvider();
313
+ if (!provider) {
314
+ throw new Error('Xverse Wallet is not installed');
315
+ }
316
+ if (!this._paymentAddress) {
317
+ throw new Error('Not connected');
318
+ }
319
+
320
+ try {
321
+ const response = await provider.request<SignMessageResult>('signMessage', {
322
+ address: this._paymentAddress.address,
323
+ message,
324
+ });
325
+
326
+ if (response.error) {
327
+ throw response.error;
328
+ }
329
+
330
+ return response.result.signature;
331
+ } catch (error) {
332
+ this.handleError(error);
333
+ }
334
+ }
335
+
336
+ async signPsbt(psbtHex: string, options?: SignPsbtOptions): Promise<string> {
337
+ const provider = this.getProvider();
338
+ if (!provider) {
339
+ throw new Error('Xverse Wallet is not installed');
340
+ }
341
+
342
+ if (!this._paymentAddress) {
343
+ throw new Error('Not connected');
344
+ }
345
+
346
+ try {
347
+ // Convert hex to base64 for Xverse
348
+ const psbtBase64 = Buffer.from(psbtHex, 'hex').toString('base64');
349
+
350
+ // Build signInputs map
351
+ const signInputs: Record<string, number[]> = {};
352
+ const allAddresses = this._accounts.map((a) => a.address);
353
+
354
+ if (options?.toSignInputs && options.toSignInputs.length > 0) {
355
+ for (const input of options.toSignInputs) {
356
+ const addr = input.address && allAddresses.includes(input.address)
357
+ ? input.address
358
+ : this._paymentAddress.address;
359
+
360
+ if (!signInputs[addr]) {
361
+ signInputs[addr] = [];
362
+ }
363
+ signInputs[addr]!.push(input.index);
364
+ }
365
+
366
+ // If no matching addresses found, use payment address
367
+ if (Object.keys(signInputs).length === 0) {
368
+ signInputs[this._paymentAddress.address] = options.toSignInputs.map((i) => i.index);
369
+ }
370
+ } else {
371
+ // Default: sign all inputs with payment address
372
+ signInputs[this._paymentAddress.address] = [0];
373
+ }
374
+
375
+ const response = await provider.request<SignPsbtResult>('signPsbt', {
376
+ psbt: psbtBase64,
377
+ signInputs,
378
+ broadcast: options?.broadcast ?? false,
379
+ allowedSignHash: options?.toSignInputs?.[0]?.sighashTypes?.[0],
380
+ });
381
+ if (response.error) {
382
+ throw response.error;
383
+ }
384
+
385
+ // Return txid if broadcast, otherwise return signed psbt as hex
386
+ if (response.result.txid) {
387
+ return response.result.txid;
388
+ }
389
+
390
+ // Convert base64 back to hex
391
+ return Buffer.from(response.result.psbt, 'base64').toString('hex');
392
+ } catch (error) {
393
+ this.handleError(error);
394
+ }
395
+ }
396
+
397
+ async sendTransaction(to: string, satoshis: number): Promise<string> {
398
+ const provider = this.getProvider();
399
+ if (!provider) {
400
+ throw new Error('Xverse Wallet is not installed');
401
+ }
402
+
403
+ if (!this._paymentAddress) {
404
+ throw new Error('Not connected');
405
+ }
406
+
407
+ try {
408
+ const response = await provider.request<SendTransferResult>('sendTransfer', {
409
+ recipients: [
410
+ {
411
+ address: to,
412
+ amount: satoshis,
413
+ },
414
+ ],
415
+ });
416
+
417
+ if (response.error) {
418
+ throw response.error;
419
+ }
420
+
421
+ if (!response.result.txid) {
422
+ throw new Error('No transaction ID received');
423
+ }
424
+
425
+ return response.result.txid;
426
+ } catch (error) {
427
+ this.handleError(error);
428
+ }
429
+ }
430
+
431
+ async getNetwork(): Promise<BitcoinNetwork> {
432
+ return this._network;
433
+ }
434
+
435
+ async switchNetwork(network: BitcoinNetwork): Promise<void> {
436
+ await this.checkAndSwitchNetwork(network);
437
+ this._network = network;
438
+ this.emitNetworkChanged(network);
439
+ }
440
+
441
+ /**
442
+ * Get the ordinals address (if available)
443
+ */
444
+ getOrdinalsAddress(): WalletAccount | null {
445
+ return this._ordinalsAddress;
446
+ }
447
+
448
+ /**
449
+ * Get the payment address
450
+ */
451
+ getPaymentAddress(): WalletAccount | null {
452
+ return this._paymentAddress;
453
+ }
454
+
455
+ /**
456
+ * Get all connected addresses (for multi-address support)
457
+ */
458
+ getAllAddresses(): string[] {
459
+ return this._accounts.map((acc) => acc.address);
460
+ }
461
+
462
+ /**
463
+ * Switch to a specific address within the connected accounts
464
+ */
465
+ switchWalletAddress(address: string): boolean {
466
+ const account = this._accounts.find((a) => a.address === address);
467
+ if (account) {
468
+ if (account.purpose === AddressPurpose.Payment) {
469
+ this._paymentAddress = {
470
+ address: account.address,
471
+ publicKey: account.publicKey,
472
+ type: this.inferAddressType(account.address),
473
+ };
474
+ }
475
+ return true;
476
+ }
477
+ return false;
478
+ }
479
+ }
@@ -0,0 +1 @@
1
+ export { XverseConnector } from './XverseConnector';