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,230 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
WalletAccount,
|
|
3
|
+
BitcoinNetwork,
|
|
4
|
+
SignPsbtOptions,
|
|
5
|
+
} from 'otx-btc-wallet-core';
|
|
6
|
+
import { BaseConnector } from '../base';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* OKX sign PSBT options
|
|
10
|
+
* @see https://web3.okx.com/build/dev-docs/sdks/chains/bitcoin/provider
|
|
11
|
+
*/
|
|
12
|
+
interface OKXSignPsbtOptions {
|
|
13
|
+
autoFinalized?: boolean;
|
|
14
|
+
toSignInputs?: Array<{
|
|
15
|
+
index: number;
|
|
16
|
+
address?: string;
|
|
17
|
+
publicKey?: string;
|
|
18
|
+
sighashTypes?: number[];
|
|
19
|
+
disableTweakSigner?: boolean;
|
|
20
|
+
}>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* OKX account changed event data
|
|
25
|
+
*/
|
|
26
|
+
interface OKXAccountChangedData {
|
|
27
|
+
address: string;
|
|
28
|
+
publicKey: string;
|
|
29
|
+
compressedPublicKey: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* OKX Bitcoin provider interface
|
|
34
|
+
* @see https://web3.okx.com/build/dev-docs/sdks/chains/bitcoin/provider
|
|
35
|
+
*/
|
|
36
|
+
interface OKXBitcoinProvider {
|
|
37
|
+
connect(): Promise<{ address: string; publicKey: string }>;
|
|
38
|
+
requestAccounts(): Promise<string[]>;
|
|
39
|
+
getAccounts(): Promise<string[]>;
|
|
40
|
+
getPublicKey(): Promise<string>;
|
|
41
|
+
getNetwork(): Promise<string>;
|
|
42
|
+
getBalance(): Promise<{ confirmed: number; unconfirmed: number; total: number }>;
|
|
43
|
+
signMessage(message: string, type?: 'ecdsa' | 'bip322-simple'): Promise<string>;
|
|
44
|
+
signPsbt(psbtHex: string, options?: OKXSignPsbtOptions): Promise<string>;
|
|
45
|
+
signPsbts(psbtHexs: string[], options?: OKXSignPsbtOptions[]): Promise<string[]>;
|
|
46
|
+
pushPsbt(psbtHex: string): Promise<string>;
|
|
47
|
+
sendBitcoin(toAddress: string, satoshis: number, options?: { feeRate?: number }): Promise<string>;
|
|
48
|
+
on(event: 'accountChanged', callback: (data: OKXAccountChangedData) => void): void;
|
|
49
|
+
on(event: 'accountsChanged', callback: (addresses: string[]) => void): void;
|
|
50
|
+
on(event: 'networkChanged', callback: (network: string) => void): void;
|
|
51
|
+
removeListener(event: 'accountChanged', callback: (data: OKXAccountChangedData) => void): void;
|
|
52
|
+
removeListener(event: 'accountsChanged', callback: (addresses: string[]) => void): void;
|
|
53
|
+
removeListener(event: 'networkChanged', callback: (network: string) => void): void;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Extend window type
|
|
57
|
+
declare global {
|
|
58
|
+
interface Window {
|
|
59
|
+
okxwallet?: {
|
|
60
|
+
bitcoin?: OKXBitcoinProvider;
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// OKX wallet icon
|
|
66
|
+
const OKX_ICON =
|
|
67
|
+
'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCIgdmlld0JveD0iMCAwIDIwMCAyMDAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMF83MDM1XzUxNDgpIj4KPG1hc2sgaWQ9Im1hc2swXzcwMzVfNTE0OCIgc3R5bGU9Im1hc2stdHlwZTpsdW1pbmFuY2UiIG1hc2tVbml0cz0idXNlclNwYWNlT25Vc2UiIHg9IjAiIHk9IjAiIHdpZHRoPSIyMDAiIGhlaWdodD0iMjAwIj4KPHBhdGggZD0iTTIwMCAwSDBWMjAwSDIwMFYwWiIgZmlsbD0id2hpdGUiLz4KPC9tYXNrPgo8ZyBtYXNrPSJ1cmwoI21hc2swXzcwMzVfNTE0OCkiPgo8cGF0aCBkPSJNMTU3LjA2OCAwSDQyLjkzMkMxOS4yMjEzIDAgMCAxOS4yMjEzIDAgNDIuOTMyVjE1Ny4wNjhDMCAxODAuNzc5IDE5LjIyMTMgMjAwIDQyLjkzMiAyMDBIMTU3LjA2OEMxODAuNzc5IDIwMCAyMDAgMTgwLjc3OSAyMDAgMTU3LjA2OFY0Mi45MzJDMjAwIDE5LjIyMTMgMTgwLjc3OSAwIDE1Ny4wNjggMFoiIGZpbGw9ImJsYWNrIi8+CjwvZz4KPHBhdGggZD0iTTExNi44MzYgODEuNjY3Mkg4NC4xOTI2QzgyLjgwNTggODEuNjY3MiA4MS42ODE0IDgyLjc5MTIgODEuNjgxNCA4NC4xNzhWMTE2LjgyMkM4MS42ODE0IDExOC4yMDkgODIuODA1OCAxMTkuMzMzIDg0LjE5MjYgMTE5LjMzM0gxMTYuODM2QzExOC4yMjMgMTE5LjMzMyAxMTkuMzQ4IDExOC4yMDkgMTE5LjM0OCAxMTYuODIyVjg0LjE3OEMxMTkuMzQ4IDgyLjc5MTIgMTE4LjIyMyA4MS42NjcyIDExNi44MzYgODEuNjY3MloiIGZpbGw9IndoaXRlIi8+CjxwYXRoIGQ9Ik03OS4xNTUgNDRINDYuNTExM0M0NS4xMjQ1IDQ0IDQ0IDQ1LjEyNCA0NCA0Ni41MTEyVjc5LjE1NUM0NCA4MC41NDE4IDQ1LjEyNDUgODEuNjY2MiA0Ni41MTEzIDgxLjY2NjJINzkuMTU1QzgwLjU0MjMgODEuNjY2MiA4MS42NjYyIDgwLjU0MTggODEuNjY2MiA3OS4xNTVWNDYuNTExMkM4MS42NjYyIDQ1LjEyNCA4MC41NDIzIDQ0IDc5LjE1NSA0NFoiIGZpbGw9IndoaXRlIi8+CjxwYXRoIGQ9Ik0xNTQuNDg3IDQ0SDEyMS44NDNDMTIwLjQ1NiA0NCAxMTkuMzMyIDQ1LjEyNCAxMTkuMzMyIDQ2LjUxMTJWNzkuMTU1QzExOS4zMzIgODAuNTQxOCAxMjAuNDU2IDgxLjY2NjIgMTIxLjg0MyA4MS42NjYySDE1NC40ODdDMTU1Ljg3NCA4MS42NjYyIDE1Ni45OTggODAuNTQxOCAxNTYuOTk4IDc5LjE1NVY0Ni41MTEyQzE1Ni45OTggNDUuMTI0IDE1NS44NzQgNDQgMTU0LjQ4NyA0NFoiIGZpbGw9IndoaXRlIi8+CjxwYXRoIGQ9Ik03OS4xNTUgMTE5LjMzMUg0Ni41MTEzQzQ1LjEyNDUgMTE5LjMzMSA0NCAxMjAuNDU1IDQ0IDEyMS44NDJWMTU0LjQ4NkM0NCAxNTUuODczIDQ1LjEyNDUgMTU2Ljk5OCA0Ni41MTEzIDE1Ni45OThINzkuMTU1QzgwLjU0MjMgMTU2Ljk5OCA4MS42NjYyIDE1NS44NzMgODEuNjY2MiAxNTQuNDg2VjEyMS44NDJDODEuNjY2MiAxMjAuNDU1IDgwLjU0MjMgMTE5LjMzMSA3OS4xNTUgMTE5LjMzMVoiIGZpbGw9IndoaXRlIi8+CjxwYXRoIGQ9Ik0xNTQuNDg3IDExOS4zMzFIMTIxLjg0M0MxMjAuNDU2IDExOS4zMzEgMTE5LjMzMiAxMjAuNDU1IDExOS4zMzIgMTIxLjg0MlYxNTQuNDg2QzExOS4zMzIgMTU1Ljg3MyAxMjAuNDU2IDE1Ni45OTggMTIxLjg0MyAxNTYuOTk4SDE1NC40ODdDMTU1Ljg3NCAxNTYuOTk4IDE1Ni45OTggMTU1Ljg3MyAxNTYuOTk4IDE1NC40ODZWMTIxLjg0MkMxNTYuOTk4IDEyMC40NTUgMTU1Ljg3NCAxMTkuMzMxIDE1NC40ODcgMTE5LjMzMVoiIGZpbGw9IndoaXRlIi8+CjwvZz4KPGRlZnM+CjxjbGlwUGF0aCBpZD0iY2xpcDBfNzAzNV81MTQ4Ij4KPHJlY3Qgd2lkdGg9IjIwMCIgaGVpZ2h0PSIyMDAiIGZpbGw9IndoaXRlIi8+CjwvY2xpcFBhdGg+CjwvZGVmcz4KPC9zdmc+Cg==';
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* OKX Wallet Connector
|
|
71
|
+
*
|
|
72
|
+
* @see https://www.okx.com/web3/build/docs/sdks/app-connect-overview
|
|
73
|
+
*/
|
|
74
|
+
export class OKXConnector extends BaseConnector {
|
|
75
|
+
readonly id = 'okx';
|
|
76
|
+
readonly name = 'OKX Wallet';
|
|
77
|
+
readonly icon = OKX_ICON;
|
|
78
|
+
|
|
79
|
+
private _unsubscribeAccounts?: () => void;
|
|
80
|
+
private _unsubscribeNetwork?: () => void;
|
|
81
|
+
|
|
82
|
+
protected getProvider(): OKXBitcoinProvider | undefined {
|
|
83
|
+
if (typeof window === 'undefined') return undefined;
|
|
84
|
+
return window.okxwallet?.bitcoin;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async connect(): Promise<WalletAccount> {
|
|
88
|
+
this.ensureInstalled();
|
|
89
|
+
const provider = this.getProvider()!;
|
|
90
|
+
|
|
91
|
+
try {
|
|
92
|
+
const result = await provider.connect();
|
|
93
|
+
|
|
94
|
+
this.setupListeners();
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
address: result.address,
|
|
98
|
+
publicKey: result.publicKey,
|
|
99
|
+
type: this.inferAddressType(result.address),
|
|
100
|
+
};
|
|
101
|
+
} catch (error) {
|
|
102
|
+
this.handleError(error);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async disconnect(): Promise<void> {
|
|
107
|
+
this._unsubscribeAccounts?.();
|
|
108
|
+
this._unsubscribeNetwork?.();
|
|
109
|
+
this.cleanup();
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async getAccounts(): Promise<WalletAccount[]> {
|
|
113
|
+
this.ensureInstalled();
|
|
114
|
+
const provider = this.getProvider()!;
|
|
115
|
+
|
|
116
|
+
try {
|
|
117
|
+
const addresses = await provider.getAccounts();
|
|
118
|
+
const publicKey = await provider.getPublicKey();
|
|
119
|
+
|
|
120
|
+
return addresses.map((address) => ({
|
|
121
|
+
address,
|
|
122
|
+
publicKey,
|
|
123
|
+
type: this.inferAddressType(address),
|
|
124
|
+
}));
|
|
125
|
+
} catch (error) {
|
|
126
|
+
this.handleError(error);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async signMessage(message: string): Promise<string> {
|
|
131
|
+
this.ensureInstalled();
|
|
132
|
+
const provider = this.getProvider()!;
|
|
133
|
+
|
|
134
|
+
try {
|
|
135
|
+
return await provider.signMessage(message);
|
|
136
|
+
} catch (error) {
|
|
137
|
+
this.handleError(error);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
async signPsbt(psbtHex: string, options?: SignPsbtOptions): Promise<string> {
|
|
142
|
+
this.ensureInstalled();
|
|
143
|
+
const provider = this.getProvider()!;
|
|
144
|
+
|
|
145
|
+
try {
|
|
146
|
+
const okxOptions: OKXSignPsbtOptions = {
|
|
147
|
+
autoFinalized: options?.autoFinalize ?? true,
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
if (options?.toSignInputs) {
|
|
151
|
+
okxOptions.toSignInputs = options.toSignInputs;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return await provider.signPsbt(psbtHex, okxOptions);
|
|
155
|
+
} catch (error) {
|
|
156
|
+
this.handleError(error);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
async sendTransaction(to: string, satoshis: number): Promise<string> {
|
|
161
|
+
this.ensureInstalled();
|
|
162
|
+
const provider = this.getProvider()!;
|
|
163
|
+
|
|
164
|
+
try {
|
|
165
|
+
return await provider.sendBitcoin(to, satoshis);
|
|
166
|
+
} catch (error) {
|
|
167
|
+
this.handleError(error);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
async getNetwork(): Promise<BitcoinNetwork> {
|
|
172
|
+
this.ensureInstalled();
|
|
173
|
+
const provider = this.getProvider()!;
|
|
174
|
+
|
|
175
|
+
try {
|
|
176
|
+
const network = await provider.getNetwork();
|
|
177
|
+
return this.mapNetwork(network);
|
|
178
|
+
} catch (error) {
|
|
179
|
+
this.handleError(error);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// OKX does not support switchNetwork via API - users must switch manually in extension
|
|
184
|
+
|
|
185
|
+
private setupListeners(): void {
|
|
186
|
+
const provider = this.getProvider();
|
|
187
|
+
if (!provider) return;
|
|
188
|
+
|
|
189
|
+
// accountChanged returns object with address, publicKey, compressedPublicKey
|
|
190
|
+
const handleAccountChanged = (data: OKXAccountChangedData) => {
|
|
191
|
+
const walletAccount: WalletAccount = {
|
|
192
|
+
address: data.address,
|
|
193
|
+
publicKey: data.publicKey,
|
|
194
|
+
type: this.inferAddressType(data.address),
|
|
195
|
+
};
|
|
196
|
+
this.emitAccountsChanged([walletAccount]);
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
const handleNetworkChanged = (network: string) => {
|
|
200
|
+
const btcNetwork = this.mapNetwork(network);
|
|
201
|
+
this.emitNetworkChanged(btcNetwork);
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
provider.on('accountChanged', handleAccountChanged);
|
|
205
|
+
provider.on('networkChanged', handleNetworkChanged);
|
|
206
|
+
|
|
207
|
+
this._unsubscribeAccounts = () => {
|
|
208
|
+
provider.removeListener('accountChanged', handleAccountChanged);
|
|
209
|
+
};
|
|
210
|
+
this._unsubscribeNetwork = () => {
|
|
211
|
+
provider.removeListener('networkChanged', handleNetworkChanged);
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
private mapNetwork(network: string): BitcoinNetwork {
|
|
216
|
+
switch (network.toLowerCase()) {
|
|
217
|
+
case 'livenet':
|
|
218
|
+
case 'mainnet':
|
|
219
|
+
return 'mainnet';
|
|
220
|
+
case 'testnet':
|
|
221
|
+
return 'testnet';
|
|
222
|
+
case 'testnet4':
|
|
223
|
+
return 'testnet4';
|
|
224
|
+
case 'signet':
|
|
225
|
+
return 'signet';
|
|
226
|
+
default:
|
|
227
|
+
return 'mainnet';
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
package/src/okx/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { OKXConnector } from './OKXConnector';
|
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
WalletAccount,
|
|
3
|
+
BitcoinNetwork,
|
|
4
|
+
SignPsbtOptions,
|
|
5
|
+
} from 'otx-btc-wallet-core';
|
|
6
|
+
import * as bitcoin from 'bitcoinjs-lib';
|
|
7
|
+
import { BaseConnector } from '../base';
|
|
8
|
+
import { BtcService } from '../utils';
|
|
9
|
+
import {
|
|
10
|
+
generatePsbtForSend,
|
|
11
|
+
finalizeAllInputs,
|
|
12
|
+
getBitcoinJsNetwork,
|
|
13
|
+
type GeneratePsbtOptions,
|
|
14
|
+
} from '../utils';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Options for sendTransaction method
|
|
18
|
+
*/
|
|
19
|
+
export interface PhantomSendOptions extends GeneratePsbtOptions {
|
|
20
|
+
// Additional Phantom-specific options can be added here
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Phantom Bitcoin account type
|
|
25
|
+
* @see https://docs.phantom.com/bitcoin/establishing-a-connection
|
|
26
|
+
*/
|
|
27
|
+
interface BtcAccount {
|
|
28
|
+
address: string;
|
|
29
|
+
addressType: 'p2tr' | 'p2wpkh' | 'p2sh' | 'p2pkh';
|
|
30
|
+
publicKey: string;
|
|
31
|
+
purpose: 'payment' | 'ordinals';
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Phantom Bitcoin provider interface
|
|
36
|
+
* @see https://docs.phantom.com/bitcoin/provider-api-reference
|
|
37
|
+
*/
|
|
38
|
+
interface PhantomBitcoinProvider {
|
|
39
|
+
isPhantom: boolean;
|
|
40
|
+
requestAccounts(): Promise<BtcAccount[]>;
|
|
41
|
+
signMessage(address: string, message: Uint8Array): Promise<{ signature: Uint8Array }>;
|
|
42
|
+
signPSBT(
|
|
43
|
+
psbt: Uint8Array,
|
|
44
|
+
options: {
|
|
45
|
+
inputsToSign: Array<{
|
|
46
|
+
address: string;
|
|
47
|
+
signingIndexes: number[];
|
|
48
|
+
sigHash?: number;
|
|
49
|
+
}>;
|
|
50
|
+
}
|
|
51
|
+
): Promise<Uint8Array>;
|
|
52
|
+
on(event: 'accountsChanged', callback: (accounts: BtcAccount[]) => void): void;
|
|
53
|
+
off(event: 'accountsChanged', callback: (accounts: BtcAccount[]) => void): void;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Extend window type
|
|
57
|
+
declare global {
|
|
58
|
+
interface Window {
|
|
59
|
+
phantom?: {
|
|
60
|
+
bitcoin?: PhantomBitcoinProvider;
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Phantom wallet icon
|
|
66
|
+
const PHANTOM_ICON =
|
|
67
|
+
'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCIgdmlld0JveD0iMCAwIDIwMCAyMDAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMF83MDM1XzUxMjYpIj4KPG1hc2sgaWQ9Im1hc2swXzcwMzVfNTEyNiIgc3R5bGU9Im1hc2stdHlwZTpsdW1pbmFuY2UiIG1hc2tVbml0cz0idXNlclNwYWNlT25Vc2UiIHg9IjAiIHk9IjAiIHdpZHRoPSIyMDAiIGhlaWdodD0iMjAwIj4KPHBhdGggZD0iTTIwMCAwSDBWMjAwSDIwMFYwWiIgZmlsbD0id2hpdGUiLz4KPC9tYXNrPgo8ZyBtYXNrPSJ1cmwoI21hc2swXzcwMzVfNTEyNikiPgo8cGF0aCBkPSJNMTU3LjA2OCAwSDQyLjkzMkMxOS4yMjEzIDAgMCAxOS4yMjEzIDAgNDIuOTMyVjE1Ny4wNjhDMCAxODAuNzc5IDE5LjIyMTMgMjAwIDQyLjkzMiAyMDBIMTU3LjA2OEMxODAuNzc5IDIwMCAyMDAgMTgwLjc3OSAyMDAgMTU3LjA2OFY0Mi45MzJDMjAwIDE5LjIyMTMgMTgwLjc3OSAwIDE1Ny4wNjggMFoiIGZpbGw9IiNBQjlGRjIiLz4KPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik04Ni4yMDMxIDEyOS45NjlDNzguMzUwMyAxNDIuMDAyIDY1LjE5MTMgMTU3LjIzIDQ3LjY4MTYgMTU3LjIzQzM5LjQwNDMgMTU3LjIzIDMxLjQ0NTMgMTUzLjgyMiAzMS40NDUzIDEzOS4wMkMzMS40NDUzIDEwMS4zMjQgODIuOTEzMyA0Mi45Njg4IDEzMC42NjcgNDIuOTY4OEMxNTcuODM0IDQyLjk2ODggMTY4LjY1OCA2MS44MTcgMTY4LjY1OCA4My4yMjFDMTY4LjY1OCAxMTAuNjk1IDE1MC44MyAxNDIuMTA4IDEzMy4xMDggMTQyLjEwOEMxMjcuNDg0IDE0Mi4xMDggMTI0LjcyNCAxMzkuMDIgMTI0LjcyNCAxMzQuMTIyQzEyNC43MjQgMTMyLjg0NCAxMjQuOTM3IDEzMS40NiAxMjUuMzYxIDEyOS45NjlDMTE5LjMxMiAxNDAuMjk4IDEwNy42MzkgMTQ5Ljg4MiA5Ni43MDkgMTQ5Ljg4MkM4OC43NSAxNDkuODgyIDg0LjcxNzUgMTQ0Ljg3NyA4NC43MTc1IDEzNy44NDlDODQuNzE3NSAxMzUuMjkzIDg1LjI0OCAxMzIuNjMxIDg2LjIwMzEgMTI5Ljk2OVpNMTUwLjcyNyA4Mi40NzgyQzE1MC43MjcgODguNzE1MiAxNDcuMDQ3IDkxLjgzMzcgMTQyLjkzMSA5MS44MzM3QzEzOC43NTIgOTEuODMzNyAxMzUuMTM1IDg4LjcxNTIgMTM1LjEzNSA4Mi40NzgyQzEzNS4xMzUgNzYuMjQxMyAxMzguNzUyIDczLjEyMjggMTQyLjkzMSA3My4xMjI4QzE0Ny4wNDcgNzMuMTIyOCAxNTAuNzI3IDc2LjI0MTMgMTUwLjcyNyA4Mi40NzgyWk0xMjcuMzM4IDgyLjQ3ODVDMTI3LjMzOCA4OC43MTU1IDEyMy42NTkgOTEuODM0IDExOS41NDIgOTEuODM0QzExNS4zNjQgOTEuODM0IDExMS43NDYgODguNzE1NSAxMTEuNzQ2IDgyLjQ3ODVDMTExLjc0NiA3Ni4yNDE1IDExNS4zNjQgNzMuMTIzMiAxMTkuNTQyIDczLjEyMzJDMTIzLjY1OSA3My4xMjMyIDEyNy4zMzggNzYuMjQxNSAxMjcuMzM4IDgyLjQ3ODVaIiBmaWxsPSIjRkZGREY4Ii8+CjwvZz4KPC9nPgo8ZGVmcz4KPGNsaXBQYXRoIGlkPSJjbGlwMF83MDM1XzUxMjYiPgo8cmVjdCB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCIgZmlsbD0id2hpdGUiLz4KPC9jbGlwUGF0aD4KPC9kZWZzPgo8L3N2Zz4K';
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Phantom Wallet Connector (Bitcoin support)
|
|
71
|
+
*
|
|
72
|
+
* @see https://docs.phantom.com/bitcoin/detecting-the-provider
|
|
73
|
+
*/
|
|
74
|
+
export class PhantomConnector extends BaseConnector {
|
|
75
|
+
readonly id = 'phantom';
|
|
76
|
+
readonly name = 'Phantom Wallet';
|
|
77
|
+
readonly icon = PHANTOM_ICON;
|
|
78
|
+
|
|
79
|
+
private _accounts: BtcAccount[] = [];
|
|
80
|
+
private _paymentAccount: WalletAccount | null = null;
|
|
81
|
+
private _ordinalsAccount: WalletAccount | null = null;
|
|
82
|
+
private _removeAccountChangeListener: (() => void) | null = null;
|
|
83
|
+
|
|
84
|
+
protected getProvider(): PhantomBitcoinProvider | undefined {
|
|
85
|
+
if (typeof window === 'undefined') return undefined;
|
|
86
|
+
const provider = window.phantom?.bitcoin;
|
|
87
|
+
if (provider?.isPhantom) {
|
|
88
|
+
return provider;
|
|
89
|
+
}
|
|
90
|
+
return undefined;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async connect(): Promise<WalletAccount> {
|
|
94
|
+
this.ensureInstalled();
|
|
95
|
+
const provider = this.getProvider()!;
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
const accounts = await provider.requestAccounts();
|
|
99
|
+
|
|
100
|
+
if (!accounts || accounts.length === 0) {
|
|
101
|
+
throw new Error('No accounts returned');
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
this._accounts = accounts;
|
|
105
|
+
this.updateAccountsFromBtcAccounts(accounts);
|
|
106
|
+
|
|
107
|
+
// Setup event listeners
|
|
108
|
+
this.setupEventListeners();
|
|
109
|
+
|
|
110
|
+
if (!this._paymentAccount) {
|
|
111
|
+
throw new Error('No payment account found');
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return this._paymentAccount;
|
|
115
|
+
} catch (error) {
|
|
116
|
+
this.handleError(error);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
private updateAccountsFromBtcAccounts(accounts: BtcAccount[]): void {
|
|
121
|
+
const paymentAcc = accounts.find((a) => a.purpose === 'payment');
|
|
122
|
+
const ordinalsAcc = accounts.find((a) => a.purpose === 'ordinals');
|
|
123
|
+
|
|
124
|
+
if (paymentAcc) {
|
|
125
|
+
this._paymentAccount = {
|
|
126
|
+
address: paymentAcc.address,
|
|
127
|
+
publicKey: paymentAcc.publicKey,
|
|
128
|
+
type: this.mapAddressType(paymentAcc.addressType),
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (ordinalsAcc) {
|
|
133
|
+
this._ordinalsAccount = {
|
|
134
|
+
address: ordinalsAcc.address,
|
|
135
|
+
publicKey: ordinalsAcc.publicKey,
|
|
136
|
+
type: this.mapAddressType(ordinalsAcc.addressType),
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
private mapAddressType(addressType: BtcAccount['addressType']): WalletAccount['type'] {
|
|
142
|
+
switch (addressType) {
|
|
143
|
+
case 'p2tr':
|
|
144
|
+
return 'taproot';
|
|
145
|
+
case 'p2wpkh':
|
|
146
|
+
return 'segwit';
|
|
147
|
+
case 'p2sh':
|
|
148
|
+
return 'nested-segwit';
|
|
149
|
+
case 'p2pkh':
|
|
150
|
+
return 'legacy';
|
|
151
|
+
default:
|
|
152
|
+
return 'legacy';
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
private setupEventListeners(): void {
|
|
157
|
+
const provider = this.getProvider();
|
|
158
|
+
if (!provider) return;
|
|
159
|
+
|
|
160
|
+
// Remove existing listener
|
|
161
|
+
this.removeEventListeners();
|
|
162
|
+
|
|
163
|
+
// Account change listener
|
|
164
|
+
const handleAccountsChanged = (accounts: BtcAccount[]) => {
|
|
165
|
+
this._accounts = accounts;
|
|
166
|
+
this.updateAccountsFromBtcAccounts(accounts);
|
|
167
|
+
|
|
168
|
+
const walletAccounts: WalletAccount[] = accounts.map((acc) => ({
|
|
169
|
+
address: acc.address,
|
|
170
|
+
publicKey: acc.publicKey,
|
|
171
|
+
type: this.mapAddressType(acc.addressType),
|
|
172
|
+
}));
|
|
173
|
+
|
|
174
|
+
this.emitAccountsChanged(walletAccounts);
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
provider.on('accountsChanged', handleAccountsChanged);
|
|
178
|
+
|
|
179
|
+
this._removeAccountChangeListener = () => {
|
|
180
|
+
provider.off('accountsChanged', handleAccountsChanged);
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
private removeEventListeners(): void {
|
|
185
|
+
this._removeAccountChangeListener?.();
|
|
186
|
+
this._removeAccountChangeListener = null;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
async disconnect(): Promise<void> {
|
|
190
|
+
this.removeEventListeners();
|
|
191
|
+
this._accounts = [];
|
|
192
|
+
this._paymentAccount = null;
|
|
193
|
+
this._ordinalsAccount = null;
|
|
194
|
+
this.cleanup();
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
async getAccounts(): Promise<WalletAccount[]> {
|
|
198
|
+
const accounts: WalletAccount[] = [];
|
|
199
|
+
if (this._paymentAccount) accounts.push(this._paymentAccount);
|
|
200
|
+
if (this._ordinalsAccount) accounts.push(this._ordinalsAccount);
|
|
201
|
+
return accounts;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
async signMessage(message: string): Promise<string> {
|
|
205
|
+
this.ensureInstalled();
|
|
206
|
+
const provider = this.getProvider()!;
|
|
207
|
+
|
|
208
|
+
if (!this._paymentAccount) {
|
|
209
|
+
throw new Error('Not connected');
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
try {
|
|
213
|
+
// Convert message to Uint8Array
|
|
214
|
+
const messageBytes = new TextEncoder().encode(message);
|
|
215
|
+
const result = await provider.signMessage(this._paymentAccount.address, messageBytes);
|
|
216
|
+
// Convert Uint8Array signature to hex string
|
|
217
|
+
return bytesToHex(result.signature);
|
|
218
|
+
} catch (error) {
|
|
219
|
+
this.handleError(error);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
async signPsbt(psbtHex: string, options?: SignPsbtOptions): Promise<string> {
|
|
224
|
+
this.ensureInstalled();
|
|
225
|
+
const provider = this.getProvider()!;
|
|
226
|
+
|
|
227
|
+
if (!this._paymentAccount) {
|
|
228
|
+
throw new Error('Not connected');
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
try {
|
|
232
|
+
// Convert hex to Uint8Array
|
|
233
|
+
const psbtBytes = hexToBytes(psbtHex);
|
|
234
|
+
|
|
235
|
+
// Build inputsToSign
|
|
236
|
+
const allAddresses = this._accounts.map((a) => a.address);
|
|
237
|
+
let inputsToSign: Array<{ address: string; signingIndexes: number[]; sigHash?: number }>;
|
|
238
|
+
|
|
239
|
+
if (options?.toSignInputs && options.toSignInputs.length > 0) {
|
|
240
|
+
inputsToSign = options.toSignInputs.map((input) => {
|
|
241
|
+
const addr = input.address && allAddresses.includes(input.address)
|
|
242
|
+
? input.address
|
|
243
|
+
: this._paymentAccount!.address;
|
|
244
|
+
|
|
245
|
+
const item: { address: string; signingIndexes: number[]; sigHash?: number } = {
|
|
246
|
+
address: addr,
|
|
247
|
+
signingIndexes: [input.index],
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
if (input.sighashTypes && input.sighashTypes[0] !== undefined) {
|
|
251
|
+
item.sigHash = input.sighashTypes[0];
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return item;
|
|
255
|
+
});
|
|
256
|
+
} else {
|
|
257
|
+
// Default: sign all inputs with payment address
|
|
258
|
+
inputsToSign = [{
|
|
259
|
+
address: this._paymentAccount.address,
|
|
260
|
+
signingIndexes: [0],
|
|
261
|
+
}];
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const signedPsbt = await provider.signPSBT(psbtBytes, { inputsToSign });
|
|
265
|
+
|
|
266
|
+
// Convert back to hex
|
|
267
|
+
return bytesToHex(signedPsbt);
|
|
268
|
+
} catch (error) {
|
|
269
|
+
this.handleError(error);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Send Bitcoin transaction
|
|
275
|
+
* Builds a PSBT, signs it with Phantom, and broadcasts
|
|
276
|
+
*
|
|
277
|
+
* @param to - Recipient address
|
|
278
|
+
* @param satoshis - Amount to send in satoshis
|
|
279
|
+
* @param options - Send options (feeRate, etc.)
|
|
280
|
+
* @returns Transaction ID after broadcast
|
|
281
|
+
*/
|
|
282
|
+
async sendTransaction(
|
|
283
|
+
to: string,
|
|
284
|
+
satoshis: number,
|
|
285
|
+
options?: PhantomSendOptions
|
|
286
|
+
): Promise<string> {
|
|
287
|
+
this.ensureInstalled();
|
|
288
|
+
const provider = this.getProvider()!;
|
|
289
|
+
|
|
290
|
+
if (!this._paymentAccount) {
|
|
291
|
+
throw new Error('Not connected. Please call connect() first.');
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const network: BitcoinNetwork = 'mainnet'; // Phantom only supports mainnet
|
|
295
|
+
const btcNetwork = getBitcoinJsNetwork(network);
|
|
296
|
+
|
|
297
|
+
// 1. Generate PSBT using the utility
|
|
298
|
+
const {
|
|
299
|
+
psbt,
|
|
300
|
+
selectedUtxos,
|
|
301
|
+
inputsToSign,
|
|
302
|
+
} = await generatePsbtForSend(
|
|
303
|
+
to,
|
|
304
|
+
satoshis,
|
|
305
|
+
this._paymentAccount.address,
|
|
306
|
+
this._paymentAccount.publicKey,
|
|
307
|
+
network,
|
|
308
|
+
options
|
|
309
|
+
);
|
|
310
|
+
|
|
311
|
+
// 2. Convert PSBT to Uint8Array for Phantom
|
|
312
|
+
const psbtBytes = psbt.toBuffer();
|
|
313
|
+
|
|
314
|
+
// 3. Build inputsToSign for Phantom API
|
|
315
|
+
const phantomInputsToSign = [{
|
|
316
|
+
address: this._paymentAccount.address,
|
|
317
|
+
signingIndexes: inputsToSign.map((input) => input.index),
|
|
318
|
+
}];
|
|
319
|
+
|
|
320
|
+
// 4. Sign PSBT with Phantom
|
|
321
|
+
const signedPsbtBytes = await provider.signPSBT(psbtBytes, {
|
|
322
|
+
inputsToSign: phantomInputsToSign,
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
// 5. Parse signed PSBT and finalize
|
|
326
|
+
const signedPsbt = bitcoin.Psbt.fromBuffer(
|
|
327
|
+
new Uint8Array(signedPsbtBytes),
|
|
328
|
+
{ network: btcNetwork }
|
|
329
|
+
);
|
|
330
|
+
finalizeAllInputs(signedPsbt, selectedUtxos.length);
|
|
331
|
+
|
|
332
|
+
// 6. Extract transaction
|
|
333
|
+
const tx = signedPsbt.extractTransaction();
|
|
334
|
+
const txHex = tx.toHex();
|
|
335
|
+
|
|
336
|
+
// 7. Broadcast using BtcService
|
|
337
|
+
const btcService = new BtcService(network);
|
|
338
|
+
return await btcService.broadcastTransaction(txHex);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
async getNetwork(): Promise<BitcoinNetwork> {
|
|
342
|
+
// Phantom Bitcoin only supports mainnet currently
|
|
343
|
+
return 'mainnet';
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Get the ordinals address (if available)
|
|
348
|
+
*/
|
|
349
|
+
getOrdinalsAddress(): WalletAccount | null {
|
|
350
|
+
return this._ordinalsAccount;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Get the payment address
|
|
355
|
+
*/
|
|
356
|
+
getPaymentAddress(): WalletAccount | null {
|
|
357
|
+
return this._paymentAccount;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Get all connected addresses
|
|
362
|
+
*/
|
|
363
|
+
getAllAddresses(): string[] {
|
|
364
|
+
return this._accounts.map((acc) => acc.address);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// Helper functions for hex/bytes conversion
|
|
369
|
+
function hexToBytes(hex: string): Uint8Array {
|
|
370
|
+
const bytes = new Uint8Array(hex.length / 2);
|
|
371
|
+
for (let i = 0; i < hex.length; i += 2) {
|
|
372
|
+
bytes[i / 2] = parseInt(hex.substring(i, i + 2), 16);
|
|
373
|
+
}
|
|
374
|
+
return bytes;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function bytesToHex(bytes: Uint8Array): string {
|
|
378
|
+
return Array.from(bytes)
|
|
379
|
+
.map((b) => b.toString(16).padStart(2, '0'))
|
|
380
|
+
.join('');
|
|
381
|
+
}
|