@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.
- package/dist/index.cjs +510 -163
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +108 -6
- package/dist/index.d.ts +108 -6
- package/dist/index.mjs +505 -164
- package/dist/index.mjs.map +1 -1
- package/package.json +5 -5
- package/src/Hydrate.ts +0 -7
- package/src/SodaxWalletProvider.tsx +1 -2
- package/src/actions/getXService.ts +3 -1
- package/src/hooks/useWalletProvider.ts +15 -3
- package/src/hooks/useXBalances.ts +2 -3
- package/src/index.ts +1 -0
- package/src/types/index.ts +1 -0
- package/src/useXWagmiStore.ts +13 -1
- package/src/utils/index.ts +2 -1
- package/src/xchains/bitcoin/BitcoinXConnector.ts +34 -0
- package/src/xchains/bitcoin/BitcoinXService.ts +40 -0
- package/src/xchains/bitcoin/OKXXConnector.ts +117 -0
- package/src/xchains/bitcoin/UnisatXConnector.ts +117 -0
- package/src/xchains/bitcoin/XverseXConnector.ts +216 -0
- package/src/xchains/bitcoin/index.ts +6 -0
- package/src/xchains/bitcoin/useBitcoinXConnectors.ts +14 -0
- package/src/xchains/solana/SolanaXService.ts +3 -7
- package/src/xchains/solana/hooks/useAnchorProvider.tsx +0 -9
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sodax/wallet-sdk-react",
|
|
3
3
|
"license": "MIT",
|
|
4
|
-
"version": "1.3.
|
|
4
|
+
"version": "1.3.1-beta-rc2",
|
|
5
5
|
"description": "Wallet SDK of Sodax",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "./dist/index.cjs",
|
|
@@ -23,7 +23,6 @@
|
|
|
23
23
|
"url": "https://github.com/icon-project/sodax-frontend"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"@coral-xyz/anchor": "0.30.1",
|
|
27
26
|
"@creit.tech/stellar-wallets-kit": "^1.7.5",
|
|
28
27
|
"@hot-labs/near-connect": "0.10.0",
|
|
29
28
|
"@injectivelabs/networks": "1.16.10",
|
|
@@ -36,7 +35,7 @@
|
|
|
36
35
|
"@injectivelabs/wallet-strategy": "1.16.10",
|
|
37
36
|
"@mysten/dapp-kit": "0.14.18",
|
|
38
37
|
"@mysten/sui": "1.21.2",
|
|
39
|
-
"near-api-js": "7.
|
|
38
|
+
"near-api-js": "7.2.0",
|
|
40
39
|
"@solana/spl-token": "0.4.9",
|
|
41
40
|
"@solana/wallet-adapter-react": "0.15.35",
|
|
42
41
|
"@solana/wallet-adapter-wallets": "0.19.30",
|
|
@@ -44,12 +43,13 @@
|
|
|
44
43
|
"@stellar/stellar-sdk": "12.3.0",
|
|
45
44
|
"icon-sdk-js": "1.5.3",
|
|
46
45
|
"immer": "10.1.1",
|
|
46
|
+
"sats-connect": "^4.2.1",
|
|
47
47
|
"viem": "2.29.2",
|
|
48
48
|
"wagmi": "2.16.9",
|
|
49
49
|
"zustand": "4.5.2",
|
|
50
50
|
"bs58": "6.0.0",
|
|
51
|
-
"@sodax/
|
|
52
|
-
"@sodax/
|
|
51
|
+
"@sodax/wallet-sdk-core": "1.3.1-beta-rc2",
|
|
52
|
+
"@sodax/types": "1.3.1-beta-rc2"
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
55
|
"@types/react": "^19.0.8",
|
package/src/Hydrate.ts
CHANGED
|
@@ -5,7 +5,6 @@ import { useEffect } from 'react';
|
|
|
5
5
|
import { EvmXService } from './xchains/evm';
|
|
6
6
|
import { SolanaXService } from './xchains/solana/SolanaXService';
|
|
7
7
|
import { SuiXService } from './xchains/sui';
|
|
8
|
-
import { useAnchorProvider } from './xchains/solana/hooks/useAnchorProvider';
|
|
9
8
|
import { useConnection, useWallet } from '@solana/wallet-adapter-react';
|
|
10
9
|
import { useConfig } from 'wagmi';
|
|
11
10
|
|
|
@@ -33,7 +32,6 @@ export const Hydrate = () => {
|
|
|
33
32
|
// solana
|
|
34
33
|
const { connection: solanaConnection } = useConnection();
|
|
35
34
|
const solanaWallet = useWallet();
|
|
36
|
-
const solanaProvider = useAnchorProvider();
|
|
37
35
|
useEffect(() => {
|
|
38
36
|
if (solanaConnection) {
|
|
39
37
|
SolanaXService.getInstance().connection = solanaConnection;
|
|
@@ -44,11 +42,6 @@ export const Hydrate = () => {
|
|
|
44
42
|
SolanaXService.getInstance().wallet = solanaWallet;
|
|
45
43
|
}
|
|
46
44
|
}, [solanaWallet]);
|
|
47
|
-
useEffect(() => {
|
|
48
|
-
if (solanaProvider) {
|
|
49
|
-
SolanaXService.getInstance().provider = solanaProvider;
|
|
50
|
-
}
|
|
51
|
-
}, [solanaProvider]);
|
|
52
45
|
|
|
53
46
|
// evm
|
|
54
47
|
const wagmiConfig = useConfig();
|
|
@@ -22,7 +22,6 @@ import type { RpcConfig } from '@sodax/types';
|
|
|
22
22
|
import { Hydrate } from './Hydrate';
|
|
23
23
|
import { createWagmiConfig } from './xchains/evm/EvmXService';
|
|
24
24
|
import { reconnectIcon } from './xchains/icon/actions';
|
|
25
|
-
// import { reconnectInjective } from './xchains/injective/actions';
|
|
26
25
|
import { reconnectStellar } from './xchains/stellar/actions';
|
|
27
26
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
28
27
|
|
|
@@ -40,7 +39,7 @@ export const SodaxWalletProvider = ({ children, rpcConfig }: { children: React.R
|
|
|
40
39
|
<WagmiProvider reconnectOnMount={false} config={wagmiConfig}>
|
|
41
40
|
<SuiClientProvider networks={{ mainnet: { url: getFullnodeUrl('mainnet') } }} defaultNetwork="mainnet">
|
|
42
41
|
<SuiWalletProvider autoConnect={true}>
|
|
43
|
-
<SolanaConnectionProvider endpoint={rpcConfig['solana'] ?? ''}>
|
|
42
|
+
<SolanaConnectionProvider endpoint={rpcConfig['solana'] ?? 'https://api.mainnet-beta.solana.com'}>
|
|
44
43
|
<SolanaWalletProvider wallets={wallets} autoConnect>
|
|
45
44
|
<Hydrate />
|
|
46
45
|
{children}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ChainType } from '@sodax/types';
|
|
2
2
|
|
|
3
|
-
import { IconXService, InjectiveXService, SolanaXService, StellarXService } from '..';
|
|
3
|
+
import { BitcoinXService, IconXService, InjectiveXService, SolanaXService, StellarXService } from '..';
|
|
4
4
|
import { SuiXService } from '..';
|
|
5
5
|
import { EvmXService } from '..';
|
|
6
6
|
import type { XService } from '../core';
|
|
@@ -8,6 +8,8 @@ import { NearXService } from '../xchains/near/NearXService';
|
|
|
8
8
|
|
|
9
9
|
export function getXService(xChainType: ChainType): XService {
|
|
10
10
|
switch (xChainType) {
|
|
11
|
+
case 'BITCOIN':
|
|
12
|
+
return BitcoinXService.getInstance();
|
|
11
13
|
case 'EVM':
|
|
12
14
|
return EvmXService.getInstance();
|
|
13
15
|
case 'SUI':
|
|
@@ -7,8 +7,11 @@ import type {
|
|
|
7
7
|
ISolanaWalletProvider,
|
|
8
8
|
IStellarWalletProvider,
|
|
9
9
|
ISuiWalletProvider,
|
|
10
|
+
IBitcoinWalletProvider,
|
|
10
11
|
} from '@sodax/types';
|
|
11
12
|
import { useMemo } from 'react';
|
|
13
|
+
import { BitcoinXService } from '../xchains/bitcoin/BitcoinXService';
|
|
14
|
+
import type { BitcoinXConnector } from '../xchains/bitcoin/BitcoinXConnector';
|
|
12
15
|
import {
|
|
13
16
|
EvmWalletProvider,
|
|
14
17
|
IconWalletProvider,
|
|
@@ -20,7 +23,7 @@ import {
|
|
|
20
23
|
} from '@sodax/wallet-sdk-core';
|
|
21
24
|
import { getXChainType } from '../actions';
|
|
22
25
|
import { usePublicClient, useWalletClient } from 'wagmi';
|
|
23
|
-
import { type SolanaXService, type StellarXService, useXAccount, useXService } from '..';
|
|
26
|
+
import { type SolanaXService, type StellarXService, useXAccount, useXService, useXConnection } from '..';
|
|
24
27
|
import type { SuiXService } from '../xchains/sui/SuiXService';
|
|
25
28
|
import { CHAIN_INFO, SupportedChainId } from '../xchains/icon/IconXService';
|
|
26
29
|
import type { InjectiveXService } from '../xchains/injective/InjectiveXService';
|
|
@@ -52,6 +55,7 @@ export function useWalletProvider(
|
|
|
52
55
|
| IInjectiveWalletProvider
|
|
53
56
|
| IStellarWalletProvider
|
|
54
57
|
| ISolanaWalletProvider
|
|
58
|
+
| IBitcoinWalletProvider
|
|
55
59
|
| INearWalletProvider
|
|
56
60
|
| undefined {
|
|
57
61
|
const xChainType = getXChainType(spokeChainId);
|
|
@@ -63,6 +67,7 @@ export function useWalletProvider(
|
|
|
63
67
|
// Cross-chain hooks
|
|
64
68
|
const xService = useXService(getXChainType(spokeChainId));
|
|
65
69
|
const xAccount = useXAccount(spokeChainId);
|
|
70
|
+
const xConnection = useXConnection(xChainType);
|
|
66
71
|
|
|
67
72
|
return useMemo(() => {
|
|
68
73
|
switch (xChainType) {
|
|
@@ -141,10 +146,17 @@ export function useWalletProvider(
|
|
|
141
146
|
|
|
142
147
|
return new SolanaWalletProvider({
|
|
143
148
|
wallet: solanaXService.wallet,
|
|
144
|
-
|
|
149
|
+
endpoint: solanaXService.connection.rpcEndpoint,
|
|
145
150
|
});
|
|
146
151
|
}
|
|
147
152
|
|
|
153
|
+
case 'BITCOIN': {
|
|
154
|
+
if (!xConnection?.xConnectorId) return undefined;
|
|
155
|
+
const connector = BitcoinXService.getInstance().getXConnectorById(xConnection.xConnectorId) as BitcoinXConnector | undefined;
|
|
156
|
+
if (!connector) return undefined;
|
|
157
|
+
// Recreate from window extension object — works after page reload without reconnect
|
|
158
|
+
return connector.recreateWalletProvider(xConnection.xAccount);
|
|
159
|
+
}
|
|
148
160
|
case 'NEAR': {
|
|
149
161
|
const nearXService = xService as NearXService;
|
|
150
162
|
if (!nearXService.walletSelector) {
|
|
@@ -157,5 +169,5 @@ export function useWalletProvider(
|
|
|
157
169
|
default:
|
|
158
170
|
return undefined;
|
|
159
171
|
}
|
|
160
|
-
}, [xChainType, evmPublicClient, evmWalletClient, xService, xAccount]);
|
|
172
|
+
}, [xChainType, evmPublicClient, evmWalletClient, xService, xAccount, xConnection]);
|
|
161
173
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { getXChainType } from '@/actions';
|
|
2
|
-
import { type UseQueryResult,
|
|
2
|
+
import { type UseQueryResult, useQuery } from '@tanstack/react-query';
|
|
3
3
|
import type { ChainId, XToken } from '@sodax/types';
|
|
4
4
|
import { useXService } from './useXService';
|
|
5
5
|
|
|
@@ -59,8 +59,7 @@ export function useXBalances({
|
|
|
59
59
|
|
|
60
60
|
return balances;
|
|
61
61
|
},
|
|
62
|
-
enabled: !!xService,
|
|
63
|
-
placeholderData: keepPreviousData,
|
|
62
|
+
enabled: !!xService && !!address && (xTokens?.length ?? 0) > 0,
|
|
64
63
|
refetchInterval: 5_000,
|
|
65
64
|
});
|
|
66
65
|
}
|
package/src/index.ts
CHANGED
package/src/types/index.ts
CHANGED
package/src/useXWagmiStore.ts
CHANGED
|
@@ -13,6 +13,10 @@ import { StellarXService } from './xchains/stellar';
|
|
|
13
13
|
import { SuiXService } from './xchains/sui';
|
|
14
14
|
import { IconXService } from './xchains/icon';
|
|
15
15
|
import { IconHanaXConnector } from './xchains/icon/IconHanaXConnector';
|
|
16
|
+
import { BitcoinXService } from './xchains/bitcoin';
|
|
17
|
+
import { UnisatXConnector } from './xchains/bitcoin/UnisatXConnector';
|
|
18
|
+
import { XverseXConnector } from './xchains/bitcoin/XverseXConnector';
|
|
19
|
+
import { OKXXConnector } from './xchains/bitcoin/OKXXConnector';
|
|
16
20
|
import { NearXService } from './xchains/near/NearXService';
|
|
17
21
|
|
|
18
22
|
type XWagmiStore = {
|
|
@@ -25,7 +29,7 @@ type XWagmiStore = {
|
|
|
25
29
|
|
|
26
30
|
const initXServices = () => {
|
|
27
31
|
const xServices = {};
|
|
28
|
-
['EVM', 'INJECTIVE', 'STELLAR', 'SUI', 'SOLANA', 'ICON', 'NEAR'].forEach(key => {
|
|
32
|
+
['EVM', 'BITCOIN', 'INJECTIVE', 'STELLAR', 'SUI', 'SOLANA', 'ICON', 'NEAR'].forEach(key => {
|
|
29
33
|
const xChainType = key as ChainType;
|
|
30
34
|
|
|
31
35
|
switch (xChainType) {
|
|
@@ -42,6 +46,14 @@ const initXServices = () => {
|
|
|
42
46
|
xServices[xChainType] = SolanaXService.getInstance();
|
|
43
47
|
xServices[xChainType].setXConnectors([]);
|
|
44
48
|
break;
|
|
49
|
+
case 'BITCOIN':
|
|
50
|
+
xServices[xChainType] = BitcoinXService.getInstance();
|
|
51
|
+
xServices[xChainType].setXConnectors([
|
|
52
|
+
new UnisatXConnector(),
|
|
53
|
+
new XverseXConnector(),
|
|
54
|
+
new OKXXConnector(),
|
|
55
|
+
]);
|
|
56
|
+
break;
|
|
45
57
|
|
|
46
58
|
// Injective, Stellar, Icon wallet connectors are supported by sodax wallet-sdk-react sdk.
|
|
47
59
|
case 'INJECTIVE':
|
package/src/utils/index.ts
CHANGED
|
@@ -8,7 +8,8 @@ export const isNativeToken = (xToken: XToken) => {
|
|
|
8
8
|
'0x0000000000000000000000000000000000000000000000000000000000000002::sui::SUI',
|
|
9
9
|
'hx0000000000000000000000000000000000000000',
|
|
10
10
|
'11111111111111111111111111111111', // solana
|
|
11
|
-
'CAS3J7GYLGXMF6TDJBBYYSE3HQ6BBSMLNUQ34T6TZMYMW2EVH34XOWMA', // stellar
|
|
11
|
+
'CAS3J7GYLGXMF6TDJBBYYSE3HQ6BBSMLNUQ34T6TZMYMW2EVH34XOWMA', // stellar
|
|
12
|
+
'0:0', // bitcoin
|
|
12
13
|
];
|
|
13
14
|
|
|
14
15
|
return nativeAddresses.includes(xToken.address);
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { XConnector } from '@/core';
|
|
2
|
+
import type { XAccount } from '@/types';
|
|
3
|
+
import type { IBitcoinWalletProvider } from '@sodax/types';
|
|
4
|
+
import { BitcoinXService } from './BitcoinXService';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Abstract base class for Bitcoin wallet connectors.
|
|
8
|
+
* Subclasses implement wallet-specific connection logic (Unisat, Xverse, OKX).
|
|
9
|
+
*/
|
|
10
|
+
export abstract class BitcoinXConnector extends XConnector {
|
|
11
|
+
constructor(name: string, id: string) {
|
|
12
|
+
super('BITCOIN', name, id);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
getXService(): BitcoinXService {
|
|
16
|
+
return BitcoinXService.getInstance();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
abstract connect(): Promise<XAccount | undefined>;
|
|
20
|
+
abstract disconnect(): Promise<void>;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Returns an IBitcoinWalletProvider instance after connecting.
|
|
24
|
+
* Used by useSpokeProvider to build BitcoinSpokeProvider.
|
|
25
|
+
*/
|
|
26
|
+
abstract getWalletProvider(): IBitcoinWalletProvider | undefined;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Recreates a walletProvider from the browser extension window object
|
|
30
|
+
* and stored xAccount data (no connect() call, no popup).
|
|
31
|
+
* Used to restore provider after page reload without requiring reconnect.
|
|
32
|
+
*/
|
|
33
|
+
abstract recreateWalletProvider(xAccount: XAccount): IBitcoinWalletProvider | undefined;
|
|
34
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { XService } from '@/core/XService';
|
|
2
|
+
import { isNativeToken } from '@/utils';
|
|
3
|
+
import type { XToken } from '@sodax/types';
|
|
4
|
+
|
|
5
|
+
export class BitcoinXService extends XService {
|
|
6
|
+
private static instance: BitcoinXService;
|
|
7
|
+
private rpcUrl: string;
|
|
8
|
+
|
|
9
|
+
private constructor(rpcUrl = 'https://mempool.space/api') {
|
|
10
|
+
super('BITCOIN');
|
|
11
|
+
this.rpcUrl = rpcUrl;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
public static getInstance(rpcUrl?: string): BitcoinXService {
|
|
15
|
+
if (!BitcoinXService.instance) {
|
|
16
|
+
BitcoinXService.instance = new BitcoinXService(rpcUrl);
|
|
17
|
+
} else if (rpcUrl && rpcUrl !== BitcoinXService.instance.rpcUrl) {
|
|
18
|
+
BitcoinXService.instance.rpcUrl = rpcUrl;
|
|
19
|
+
}
|
|
20
|
+
return BitcoinXService.instance;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async getBalance(address: string | undefined, xToken: XToken): Promise<bigint> {
|
|
24
|
+
if (!address) return 0n;
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
if (isNativeToken(xToken)) {
|
|
28
|
+
const response = await fetch(`${this.rpcUrl}/address/${address}/utxo`);
|
|
29
|
+
if (!response.ok) return 0n;
|
|
30
|
+
const utxos: Array<{ value: number }> = await response.json();
|
|
31
|
+
const totalBalance = utxos.reduce((sum, utxo) => sum + utxo.value, 0);
|
|
32
|
+
return BigInt(totalBalance);
|
|
33
|
+
}
|
|
34
|
+
} catch {
|
|
35
|
+
return 0n;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return 0n;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import type { XAccount } from '@/types';
|
|
2
|
+
import { detectBitcoinAddressType, type IBitcoinWalletProvider, type AddressType } from '@sodax/types';
|
|
3
|
+
import { BitcoinXConnector } from './BitcoinXConnector';
|
|
4
|
+
|
|
5
|
+
// OKX Bitcoin wallet window API types
|
|
6
|
+
interface OKXBitcoinWallet {
|
|
7
|
+
getAccounts(): Promise<string[]>;
|
|
8
|
+
getPublicKey(): Promise<string>;
|
|
9
|
+
signPsbt(psbtHex: string, options?: { autoFinalized?: boolean }): Promise<string>;
|
|
10
|
+
signMessage(message: string, type?: 'bip322-simple' | 'ecdsa'): Promise<string>;
|
|
11
|
+
connect(): Promise<{ address: string; publicKey: string }>;
|
|
12
|
+
sendBitcoin(toAddress: string, satoshis: number): Promise<string>;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
declare global {
|
|
16
|
+
interface Window {
|
|
17
|
+
okxwallet?: {
|
|
18
|
+
bitcoin?: OKXBitcoinWallet;
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
class OKXWalletProvider implements IBitcoinWalletProvider {
|
|
24
|
+
private okx: OKXBitcoinWallet;
|
|
25
|
+
private cachedAddress: string;
|
|
26
|
+
|
|
27
|
+
constructor(okx: OKXBitcoinWallet, address: string) {
|
|
28
|
+
this.okx = okx;
|
|
29
|
+
this.cachedAddress = address;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async getWalletAddress(): Promise<string> {
|
|
33
|
+
try {
|
|
34
|
+
const accounts = await this.okx.getAccounts();
|
|
35
|
+
if (accounts[0]) this.cachedAddress = accounts[0];
|
|
36
|
+
} catch {
|
|
37
|
+
// wallet locked — fall through to cached address
|
|
38
|
+
}
|
|
39
|
+
return this.cachedAddress;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async getPublicKey(): Promise<string> {
|
|
43
|
+
return this.okx.getPublicKey();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async getAddressType(_address: string): Promise<AddressType> {
|
|
47
|
+
const address = await this.getWalletAddress();
|
|
48
|
+
return detectBitcoinAddressType(address);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async signTransaction(psbtBase64: string, finalize = false): Promise<string> {
|
|
52
|
+
const psbtHex = Buffer.from(psbtBase64, 'base64').toString('hex');
|
|
53
|
+
return this.okx.signPsbt(psbtHex, { autoFinalized: finalize });
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async signEcdsaMessage(message: string): Promise<string> {
|
|
57
|
+
return this.okx.signMessage(message, 'ecdsa');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async signBip322Message(message: string): Promise<string> {
|
|
61
|
+
return this.okx.signMessage(message, 'bip322-simple');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async sendBitcoin(toAddress: string, satoshis: bigint): Promise<string> {
|
|
65
|
+
if (satoshis > BigInt(Number.MAX_SAFE_INTEGER)) {
|
|
66
|
+
throw new Error(`Amount ${satoshis} satoshis exceeds safe integer range`);
|
|
67
|
+
}
|
|
68
|
+
return this.okx.sendBitcoin(toAddress, Number(satoshis));
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export class OKXXConnector extends BitcoinXConnector {
|
|
73
|
+
private walletProvider: OKXWalletProvider | undefined;
|
|
74
|
+
|
|
75
|
+
constructor() {
|
|
76
|
+
super('OKX Wallet', 'okx-bitcoin');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
public static isAvailable(): boolean {
|
|
80
|
+
return typeof window !== 'undefined' && !!window.okxwallet?.bitcoin;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
public get icon(): string {
|
|
84
|
+
return 'https://static.okx.com/cdn/assets/imgs/247/58E63FEA47A2B7D7.png';
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async connect(): Promise<XAccount | undefined> {
|
|
88
|
+
const okx = window.okxwallet?.bitcoin;
|
|
89
|
+
if (!okx) {
|
|
90
|
+
throw new Error('OKX wallet is not installed');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const { address } = await okx.connect();
|
|
94
|
+
if (!address) return undefined;
|
|
95
|
+
|
|
96
|
+
this.walletProvider = new OKXWalletProvider(okx, address);
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
address,
|
|
100
|
+
xChainType: 'BITCOIN',
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async disconnect(): Promise<void> {
|
|
105
|
+
this.walletProvider = undefined;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
getWalletProvider(): IBitcoinWalletProvider | undefined {
|
|
109
|
+
return this.walletProvider;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
recreateWalletProvider(xAccount: XAccount): IBitcoinWalletProvider | undefined {
|
|
113
|
+
const okx = window.okxwallet?.bitcoin;
|
|
114
|
+
if (!okx || !xAccount.address) return undefined;
|
|
115
|
+
return new OKXWalletProvider(okx, xAccount.address);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import type { XAccount } from '@/types';
|
|
2
|
+
import { detectBitcoinAddressType, type IBitcoinWalletProvider, type AddressType } from '@sodax/types';
|
|
3
|
+
import { BitcoinXConnector } from './BitcoinXConnector';
|
|
4
|
+
|
|
5
|
+
// Minimal Unisat window API types
|
|
6
|
+
interface UnisatWallet {
|
|
7
|
+
getAccounts(): Promise<string[]>;
|
|
8
|
+
getPublicKey(): Promise<string>;
|
|
9
|
+
signPsbt(psbtHex: string, options?: { autoFinalized?: boolean }): Promise<string>;
|
|
10
|
+
signMessage(message: string, type?: 'bip322-simple' | 'ecdsa'): Promise<string>;
|
|
11
|
+
requestAccounts(): Promise<string[]>;
|
|
12
|
+
sendBitcoin(address: string, satoshis: number): Promise<string>;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
declare global {
|
|
16
|
+
interface Window {
|
|
17
|
+
unisat?: UnisatWallet;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
class UnisatWalletProvider implements IBitcoinWalletProvider {
|
|
22
|
+
private unisat: UnisatWallet;
|
|
23
|
+
private cachedAddress: string;
|
|
24
|
+
|
|
25
|
+
constructor(unisat: UnisatWallet, address: string) {
|
|
26
|
+
this.unisat = unisat;
|
|
27
|
+
this.cachedAddress = address;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async getWalletAddress(): Promise<string> {
|
|
31
|
+
try {
|
|
32
|
+
const accounts = await this.unisat.getAccounts();
|
|
33
|
+
if (accounts[0]) this.cachedAddress = accounts[0];
|
|
34
|
+
} catch {
|
|
35
|
+
// wallet locked — fall through to cached address
|
|
36
|
+
}
|
|
37
|
+
return this.cachedAddress;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async getPublicKey(): Promise<string> {
|
|
41
|
+
return this.unisat.getPublicKey();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async getAddressType(_address: string): Promise<AddressType> {
|
|
45
|
+
const address = await this.getWalletAddress();
|
|
46
|
+
return detectBitcoinAddressType(address);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async signTransaction(psbtBase64: string, finalize = false): Promise<string> {
|
|
50
|
+
// Convert base64 → hex for Unisat, then back
|
|
51
|
+
const psbtHex = Buffer.from(psbtBase64, 'base64').toString('hex');
|
|
52
|
+
const signedHex = await this.unisat.signPsbt(psbtHex, { autoFinalized: finalize });
|
|
53
|
+
// Return as hex (BTCWalletProvider.signTransaction expects this)
|
|
54
|
+
return signedHex;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async signEcdsaMessage(message: string): Promise<string> {
|
|
58
|
+
return this.unisat.signMessage(message, 'ecdsa');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async signBip322Message(message: string): Promise<string> {
|
|
62
|
+
return this.unisat.signMessage(message, 'bip322-simple');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async sendBitcoin(toAddress: string, satoshis: bigint): Promise<string> {
|
|
66
|
+
if (satoshis > BigInt(Number.MAX_SAFE_INTEGER)) {
|
|
67
|
+
throw new Error(`Amount ${satoshis} satoshis exceeds safe integer range`);
|
|
68
|
+
}
|
|
69
|
+
return this.unisat.sendBitcoin(toAddress, Number(satoshis));
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export class UnisatXConnector extends BitcoinXConnector {
|
|
74
|
+
private walletProvider: UnisatWalletProvider | undefined;
|
|
75
|
+
|
|
76
|
+
constructor() {
|
|
77
|
+
super('Unisat', 'unisat');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
public static isAvailable(): boolean {
|
|
81
|
+
return typeof window !== 'undefined' && !!window.unisat;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
public get icon(): string {
|
|
85
|
+
return 'https://avatars.githubusercontent.com/u/125119198?s=200&v=4';
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async connect(): Promise<XAccount | undefined> {
|
|
89
|
+
if (!window.unisat) {
|
|
90
|
+
throw new Error('Unisat wallet is not installed');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const accounts = await window.unisat.requestAccounts();
|
|
94
|
+
const address = accounts[0];
|
|
95
|
+
if (!address) return undefined;
|
|
96
|
+
|
|
97
|
+
this.walletProvider = new UnisatWalletProvider(window.unisat, address);
|
|
98
|
+
|
|
99
|
+
return {
|
|
100
|
+
address,
|
|
101
|
+
xChainType: 'BITCOIN',
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async disconnect(): Promise<void> {
|
|
106
|
+
this.walletProvider = undefined;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
getWalletProvider(): IBitcoinWalletProvider | undefined {
|
|
110
|
+
return this.walletProvider;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
recreateWalletProvider(xAccount: XAccount): IBitcoinWalletProvider | undefined {
|
|
114
|
+
if (!window.unisat || !xAccount.address) return undefined;
|
|
115
|
+
return new UnisatWalletProvider(window.unisat, xAccount.address);
|
|
116
|
+
}
|
|
117
|
+
}
|