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,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';
|