@sodax/wallet-sdk-react 1.3.0-beta → 1.3.1-beta-rc2

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.
@@ -0,0 +1,216 @@
1
+ import type { XAccount } from '@/types';
2
+ import { detectBitcoinAddressType, type IBitcoinWalletProvider, type AddressType } from '@sodax/types';
3
+ import { AddressPurpose, MessageSigningProtocols } from 'sats-connect';
4
+ import { BitcoinXConnector } from './BitcoinXConnector';
5
+
6
+ // sats-connect types
7
+ interface SignPsbtResult {
8
+ psbt: string; // base64 signed PSBT
9
+ }
10
+
11
+ interface GetAccountsResult {
12
+ address: string;
13
+ publicKey: string;
14
+ purpose: string;
15
+ addressType: string;
16
+ }
17
+
18
+ interface SignMessageResult {
19
+ signature: string;
20
+ }
21
+
22
+
23
+ class XverseWalletProvider implements IBitcoinWalletProvider {
24
+ private address: string;
25
+ private publicKey: string;
26
+
27
+ constructor(address: string, publicKey: string) {
28
+ this.address = address;
29
+ this.publicKey = publicKey;
30
+ }
31
+
32
+ async getWalletAddress(): Promise<string> {
33
+ return this.address;
34
+ }
35
+
36
+ async getPublicKey(): Promise<string> {
37
+ return this.publicKey;
38
+ }
39
+
40
+ async getAddressType(_address: string): Promise<AddressType> {
41
+ return detectBitcoinAddressType(this.address);
42
+ }
43
+
44
+ /**
45
+ * Parse a base64-encoded PSBT to count the number of inputs.
46
+ * Reads the unsigned transaction from the PSBT global section.
47
+ */
48
+ private countPsbtInputs(psbtBase64: string): number {
49
+ const data = Buffer.from(psbtBase64, 'base64');
50
+ // Skip 5-byte magic (0x70736274FF = "psbt" + separator)
51
+ let offset = 5;
52
+
53
+ // Global section: first key-value pair should be key 0x00 (unsigned tx)
54
+ const keyLen = data[offset++] ?? 0;
55
+ if (keyLen !== 1 || data[offset++] !== 0x00) {
56
+ return 1; // fallback: assume 1 input
57
+ }
58
+
59
+ // Read value length (compact size)
60
+ const firstByte = data[offset++] ?? 0;
61
+ if (firstByte === 0xfd) offset += 2;
62
+ else if (firstByte === 0xfe) offset += 4;
63
+ else if (firstByte === 0xff) offset += 8;
64
+ // else firstByte IS the length (< 0xfd), no extra bytes
65
+
66
+ // Unsigned tx: skip 4-byte version
67
+ offset += 4;
68
+
69
+ // Read input count (varint)
70
+ const inputByte = data[offset] ?? 0;
71
+ if (inputByte < 0xfd) return inputByte;
72
+ return 1; // fallback for unusual cases
73
+ }
74
+
75
+ async signTransaction(psbtBase64: string, finalize = false): Promise<string> {
76
+ const { request } = await import('sats-connect');
77
+
78
+ const inputCount = this.countPsbtInputs(psbtBase64);
79
+ const signingIndexes = Array.from({ length: inputCount }, (_, i) => i);
80
+
81
+ const response = await request('signPsbt', {
82
+ psbt: psbtBase64,
83
+ broadcast: false,
84
+ signInputs: {
85
+ [this.address]: signingIndexes,
86
+ },
87
+ });
88
+
89
+ if (response.status === 'error') {
90
+ throw new Error(response.error?.message || 'Xverse PSBT signing failed');
91
+ }
92
+
93
+ const result = response.result as SignPsbtResult;
94
+
95
+ if (finalize) {
96
+ // Return hex for broadcast
97
+ return Buffer.from(result.psbt, 'base64').toString('hex');
98
+ }
99
+
100
+ // Return base64 signed PSBT (partially signed)
101
+ return result.psbt;
102
+ }
103
+
104
+ async signEcdsaMessage(message: string): Promise<string> {
105
+ const { request } = await import('sats-connect');
106
+
107
+ const response = await request('signMessage', {
108
+ address: this.address,
109
+ message,
110
+ protocol: MessageSigningProtocols.ECDSA,
111
+ });
112
+
113
+ if (response.status === 'error') {
114
+ throw new Error(response.error?.message || 'Xverse ECDSA signing failed');
115
+ }
116
+
117
+ return (response.result as SignMessageResult).signature;
118
+ }
119
+
120
+ async signBip322Message(message: string): Promise<string> {
121
+ const { request } = await import('sats-connect');
122
+
123
+ const response = await request('signMessage', {
124
+ address: this.address,
125
+ message,
126
+ protocol: MessageSigningProtocols.BIP322,
127
+ });
128
+
129
+ if (response.status === 'error') {
130
+ throw new Error(response.error?.message || 'Xverse BIP322 signing failed');
131
+ }
132
+
133
+ return (response.result as SignMessageResult).signature;
134
+ }
135
+
136
+ async sendBitcoin(toAddress: string, satoshis: bigint): Promise<string> {
137
+ const { request } = await import('sats-connect');
138
+
139
+ const response = await request('sendTransfer', {
140
+ recipients: [
141
+ {
142
+ address: toAddress,
143
+ amount: Number(satoshis),
144
+ },
145
+ ],
146
+ });
147
+
148
+ if (response.status === 'error') {
149
+ throw new Error(response.error?.message || 'Xverse sendTransfer failed');
150
+ }
151
+
152
+ return (response.result as { txid: string }).txid;
153
+ }
154
+ }
155
+
156
+ export class XverseXConnector extends BitcoinXConnector {
157
+ private walletProvider: XverseWalletProvider | undefined;
158
+
159
+ constructor() {
160
+ super('Xverse', 'xverse');
161
+ }
162
+
163
+ public static isAvailable(): boolean {
164
+ return typeof window !== 'undefined' && !!window.BitcoinProvider;
165
+ }
166
+
167
+ public get icon(): string {
168
+ return 'https://cdn.brandfetch.io/iddzGN5Rcv/w/400/h/400/theme/dark/icon.jpeg?c=1bxid64Mup7aczewSAYMX&t=1771902357797';
169
+ }
170
+
171
+ async connect(): Promise<XAccount | undefined> {
172
+ if (!XverseXConnector.isAvailable()) {
173
+ throw new Error('Xverse wallet is not installed');
174
+ }
175
+
176
+ const { request } = await import('sats-connect');
177
+
178
+ const response = await request('getAccounts', {
179
+ purposes: [AddressPurpose.Payment],
180
+ message: 'Connect to Sodax',
181
+ });
182
+
183
+ if (response.status === 'error') {
184
+ throw new Error(response.error?.message || 'Xverse connection failed');
185
+ }
186
+
187
+ const accounts = response.result as GetAccountsResult[];
188
+ const paymentAccount = accounts.find(a => a.purpose === AddressPurpose.Payment) || accounts[0];
189
+
190
+ if (!paymentAccount) return undefined;
191
+
192
+ this.walletProvider = new XverseWalletProvider(
193
+ paymentAccount.address,
194
+ paymentAccount.publicKey,
195
+ );
196
+
197
+ return {
198
+ address: paymentAccount.address,
199
+ publicKey: paymentAccount.publicKey,
200
+ xChainType: 'BITCOIN',
201
+ };
202
+ }
203
+
204
+ async disconnect(): Promise<void> {
205
+ this.walletProvider = undefined;
206
+ }
207
+
208
+ getWalletProvider(): IBitcoinWalletProvider | undefined {
209
+ return this.walletProvider;
210
+ }
211
+
212
+ recreateWalletProvider(xAccount: XAccount): IBitcoinWalletProvider | undefined {
213
+ if (!xAccount.address || !xAccount.publicKey) return undefined;
214
+ return new XverseWalletProvider(xAccount.address, xAccount.publicKey);
215
+ }
216
+ }
@@ -0,0 +1,6 @@
1
+ export { BitcoinXService } from './BitcoinXService';
2
+ export { BitcoinXConnector } from './BitcoinXConnector';
3
+ export { UnisatXConnector } from './UnisatXConnector';
4
+ export { XverseXConnector } from './XverseXConnector';
5
+ export { OKXXConnector } from './OKXXConnector';
6
+ export { useBitcoinXConnectors } from './useBitcoinXConnectors';
@@ -0,0 +1,14 @@
1
+ import { useMemo } from 'react';
2
+ import type { BitcoinXConnector } from './BitcoinXConnector';
3
+ import { useXService } from '../../hooks';
4
+
5
+ /**
6
+ * Hook to return available Bitcoin wallet connectors from the globally registered xService.
7
+ */
8
+ export function useBitcoinXConnectors(): BitcoinXConnector[] {
9
+ const xService = useXService('BITCOIN');
10
+
11
+ return useMemo(() => {
12
+ return (xService?.getXConnectors() || []) as BitcoinXConnector[];
13
+ }, [xService]);
14
+ }
@@ -3,7 +3,6 @@ import { isNativeToken } from '@/utils';
3
3
  import type { XToken } from '@sodax/types';
4
4
  import { type Connection, PublicKey } from '@solana/web3.js';
5
5
  import { getAccount, getAssociatedTokenAddressSync } from '@solana/spl-token';
6
- import type { AnchorProvider } from '@coral-xyz/anchor';
7
6
  import type { WalletContextState } from '@solana/wallet-adapter-react';
8
7
 
9
8
  export class SolanaXService extends XService {
@@ -11,7 +10,6 @@ export class SolanaXService extends XService {
11
10
 
12
11
  public connection: Connection | undefined;
13
12
  public wallet: WalletContextState | undefined;
14
- public provider: AnchorProvider | undefined;
15
13
 
16
14
  private constructor() {
17
15
  super('SOLANA');
@@ -29,7 +27,7 @@ export class SolanaXService extends XService {
29
27
 
30
28
  const connection = this.connection;
31
29
  if (!connection) {
32
- throw new Error('Connection is not initialized');
30
+ return BigInt(0);
33
31
  }
34
32
 
35
33
  try {
@@ -41,10 +39,8 @@ export class SolanaXService extends XService {
41
39
  const tokenAccountPubkey = getAssociatedTokenAddressSync(new PublicKey(xToken.address), new PublicKey(address));
42
40
  const tokenAccount = await getAccount(connection, tokenAccountPubkey);
43
41
  return BigInt(tokenAccount.amount);
44
- } catch (e) {
45
- console.log('error', e);
42
+ } catch {
43
+ return BigInt(0);
46
44
  }
47
-
48
- return BigInt(0);
49
45
  }
50
46
  }
@@ -1,9 +0,0 @@
1
- import { AnchorProvider } from '@coral-xyz/anchor';
2
- import { type AnchorWallet, useConnection, useWallet } from '@solana/wallet-adapter-react';
3
-
4
- export function useAnchorProvider() {
5
- const { connection } = useConnection();
6
- const wallet = useWallet();
7
-
8
- return new AnchorProvider(connection, wallet as AnchorWallet, { commitment: 'confirmed' });
9
- }