@shapeshiftoss/hdwallet-phantom 1.55.6

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/src/phantom.ts ADDED
@@ -0,0 +1,347 @@
1
+ import * as core from "@shapeshiftoss/hdwallet-core";
2
+ import { BTCInputScriptType } from "@shapeshiftoss/hdwallet-core";
3
+ import Base64 from "base64-js";
4
+ import * as bitcoinMsg from "bitcoinjs-message";
5
+ import { keccak256, recoverAddress } from "ethers/lib/utils.js";
6
+ import _ from "lodash";
7
+
8
+ import * as btc from "./bitcoin";
9
+ import * as eth from "./ethereum";
10
+ import { PhantomEvmProvider, PhantomUtxoProvider } from "./types";
11
+
12
+ export function isPhantom(wallet: core.HDWallet): wallet is PhantomHDWallet {
13
+ return _.isObject(wallet) && (wallet as any)._isPhantom;
14
+ }
15
+
16
+ export class PhantomHDWalletInfo implements core.HDWalletInfo, core.BTCWalletInfo, core.ETHWalletInfo {
17
+ readonly _supportsBTCInfo = true;
18
+ readonly _supportsETHInfo = true;
19
+
20
+ evmProvider: PhantomEvmProvider;
21
+
22
+ constructor(evmProvider: PhantomEvmProvider) {
23
+ this.evmProvider = evmProvider;
24
+ }
25
+
26
+ public getVendor(): string {
27
+ return "Phantom";
28
+ }
29
+
30
+ public hasOnDevicePinEntry(): boolean {
31
+ return false;
32
+ }
33
+
34
+ public hasOnDevicePassphrase(): boolean {
35
+ return true;
36
+ }
37
+
38
+ public hasOnDeviceDisplay(): boolean {
39
+ return true;
40
+ }
41
+
42
+ public hasOnDeviceRecovery(): boolean {
43
+ return true;
44
+ }
45
+
46
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
47
+ public hasNativeShapeShift(srcCoin: core.Coin, dstCoin: core.Coin): boolean {
48
+ return false;
49
+ }
50
+
51
+ public supportsBip44Accounts(): boolean {
52
+ return false;
53
+ }
54
+
55
+ public supportsOfflineSigning(): boolean {
56
+ return false;
57
+ }
58
+
59
+ public supportsBroadcast(): boolean {
60
+ return true;
61
+ }
62
+
63
+ public describePath(msg: core.DescribePath): core.PathDescription {
64
+ switch (msg.coin.toLowerCase()) {
65
+ case "bitcoin": {
66
+ const unknown = core.unknownUTXOPath(msg.path, msg.coin, msg.scriptType);
67
+
68
+ if (!msg.scriptType) return unknown;
69
+ if (!this.btcSupportsCoin(msg.coin)) return unknown;
70
+ if (!this.btcSupportsScriptType(msg.coin, msg.scriptType)) return unknown;
71
+
72
+ return core.describeUTXOPath(msg.path, msg.coin, msg.scriptType);
73
+ }
74
+ case "ethereum":
75
+ return core.describeETHPath(msg.path);
76
+ default:
77
+ throw new Error("Unsupported path");
78
+ }
79
+ }
80
+
81
+ /** Ethereum */
82
+
83
+ public async ethSupportsNetwork(chainId: number): Promise<boolean> {
84
+ return chainId === 1;
85
+ }
86
+
87
+ public async ethGetChainId(): Promise<number | null> {
88
+ try {
89
+ if (!this.evmProvider.request) throw new Error("Provider does not support ethereum.request");
90
+ // chainId as hex string
91
+ const chainId: string = await this.evmProvider.request({ method: "eth_chainId" });
92
+ return parseInt(chainId, 16);
93
+ } catch (e) {
94
+ console.error(e);
95
+ return null;
96
+ }
97
+ }
98
+
99
+ public async ethSupportsSecureTransfer(): Promise<boolean> {
100
+ return false;
101
+ }
102
+
103
+ public ethSupportsNativeShapeShift(): boolean {
104
+ return false;
105
+ }
106
+
107
+ public async ethSupportsEIP1559(): Promise<boolean> {
108
+ return true;
109
+ }
110
+
111
+ public ethGetAccountPaths(msg: core.ETHGetAccountPath): Array<core.ETHAccountPath> {
112
+ return eth.ethGetAccountPaths(msg);
113
+ }
114
+
115
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
116
+ public ethNextAccountPath(msg: core.ETHAccountPath): core.ETHAccountPath | undefined {
117
+ throw new Error("Method not implemented");
118
+ }
119
+
120
+ /** Bitcoin */
121
+
122
+ public async btcSupportsCoin(coin: core.Coin): Promise<boolean> {
123
+ return coin === "bitcoin";
124
+ }
125
+
126
+ public async btcSupportsScriptType(coin: string, scriptType?: core.BTCInputScriptType | undefined): Promise<boolean> {
127
+ if (!this.btcSupportsCoin(coin)) return false;
128
+
129
+ switch (scriptType) {
130
+ case core.BTCInputScriptType.SpendWitness:
131
+ return true;
132
+ default:
133
+ return false;
134
+ }
135
+ }
136
+
137
+ public async btcSupportsSecureTransfer(): Promise<boolean> {
138
+ return false;
139
+ }
140
+
141
+ public btcSupportsNativeShapeShift(): boolean {
142
+ return false;
143
+ }
144
+
145
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
146
+ public btcGetAccountPaths(msg: core.BTCGetAccountPaths): Array<core.BTCAccountPath> {
147
+ return btc.btcGetAccountPaths(msg);
148
+ }
149
+
150
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
151
+ public btcIsSameAccount(msg: core.BTCAccountPath[]): boolean {
152
+ throw new Error("Method not implemented.");
153
+ }
154
+
155
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
156
+ public btcNextAccountPath(msg: core.BTCAccountPath): core.BTCAccountPath | undefined {
157
+ throw new Error("Method not implemented");
158
+ }
159
+ }
160
+
161
+ export class PhantomHDWallet extends PhantomHDWalletInfo implements core.HDWallet, core.BTCWallet, core.ETHWallet {
162
+ readonly _supportsBTC = true;
163
+ readonly _supportsETH = true;
164
+ readonly _supportsEthSwitchChain = false;
165
+ readonly _supportsAvalanche = false;
166
+ readonly _supportsOptimism = false;
167
+ readonly _supportsPolygon = true;
168
+ readonly _supportsGnosis = false;
169
+ readonly _supportsArbitrum = false;
170
+ readonly _supportsArbitrumNova = false;
171
+ readonly _supportsBase = false;
172
+ readonly _supportsBSC = false;
173
+ readonly _isPhantom = true;
174
+
175
+ evmProvider: PhantomEvmProvider;
176
+ bitcoinProvider: PhantomUtxoProvider;
177
+
178
+ constructor(evmProvider: PhantomEvmProvider, bitcoinProvider: PhantomUtxoProvider) {
179
+ super(evmProvider);
180
+ this.evmProvider = evmProvider;
181
+ this.bitcoinProvider = bitcoinProvider;
182
+ }
183
+
184
+ public async getDeviceID(): Promise<string> {
185
+ return "phantom:" + (await this.ethGetAddress());
186
+ }
187
+
188
+ async getFeatures(): Promise<Record<string, any>> {
189
+ return {};
190
+ }
191
+
192
+ public async getFirmwareVersion(): Promise<string> {
193
+ return "phantom";
194
+ }
195
+
196
+ public async getModel(): Promise<string> {
197
+ return "Phantom";
198
+ }
199
+
200
+ public async getLabel(): Promise<string> {
201
+ return "Phantom";
202
+ }
203
+
204
+ public async isInitialized(): Promise<boolean> {
205
+ return true;
206
+ }
207
+
208
+ public async isLocked(): Promise<boolean> {
209
+ return !this.evmProvider._metamask.isUnlocked();
210
+ }
211
+
212
+ public async clearSession(): Promise<void> {}
213
+
214
+ public async initialize(): Promise<void> {}
215
+
216
+ public async ping(msg: core.Ping): Promise<core.Pong> {
217
+ return { msg: msg.msg };
218
+ }
219
+
220
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
221
+ public async sendPin(pin: string): Promise<void> {}
222
+
223
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
224
+ public async sendPassphrase(passphrase: string): Promise<void> {}
225
+
226
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
227
+ public async sendCharacter(charater: string): Promise<void> {}
228
+
229
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
230
+ public async sendWord(word: string): Promise<void> {}
231
+
232
+ public async cancel(): Promise<void> {}
233
+
234
+ public async wipe(): Promise<void> {}
235
+
236
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
237
+ public async reset(msg: core.ResetDevice): Promise<void> {}
238
+
239
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
240
+ public async recover(msg: core.RecoverDevice): Promise<void> {}
241
+
242
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
243
+ public async loadDevice(msg: core.LoadDevice): Promise<void> {}
244
+
245
+ public async disconnect(): Promise<void> {}
246
+
247
+ public async getPublicKeys(msg: Array<core.GetPublicKey>): Promise<Array<core.PublicKey | null>> {
248
+ return await Promise.all(
249
+ msg.map(async (getPublicKey) => {
250
+ const { coin, scriptType } = getPublicKey;
251
+
252
+ // Only p2wpkh effectively supported for now
253
+ if (coin === "Bitcoin" && scriptType === BTCInputScriptType.SpendWitness) {
254
+ // Note this is a pubKey, not an xpub, however phantom does not support utxo derivation,
255
+ // so this functions as an account (xpub) for all intents and purposes
256
+ const pubKey = await this.btcGetAddress({ coin: "Bitcoin" } as core.BTCGetAddress);
257
+ return { xpub: pubKey } as core.PublicKey;
258
+ }
259
+
260
+ return null;
261
+ })
262
+ );
263
+ }
264
+
265
+ /** Ethereum */
266
+
267
+ public async ethGetAddress(): Promise<string | null> {
268
+ return eth.ethGetAddress(this.evmProvider);
269
+ }
270
+
271
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
272
+ public async ethSignTx(msg: core.ETHSignTx): Promise<core.ETHSignedTx | null> {
273
+ throw new Error("Method not implemented");
274
+ }
275
+
276
+ public async ethSendTx(msg: core.ETHSignTx): Promise<core.ETHTxHash | null> {
277
+ const address = await this.ethGetAddress();
278
+ return address ? eth.ethSendTx(msg, this.evmProvider, address) : null;
279
+ }
280
+
281
+ public async ethSignMessage(msg: core.ETHSignMessage): Promise<core.ETHSignedMessage | null> {
282
+ const address = await this.ethGetAddress();
283
+ return address ? eth.ethSignMessage(msg, this.evmProvider, address) : null;
284
+ }
285
+
286
+ async ethSignTypedData(msg: core.ETHSignTypedData): Promise<core.ETHSignedTypedData | null> {
287
+ const address = await this.ethGetAddress();
288
+ return address ? eth.ethSignTypedData(msg, this.evmProvider, address) : null;
289
+ }
290
+
291
+ public async ethVerifyMessage(msg: core.ETHVerifyMessage): Promise<boolean | null> {
292
+ if (!msg.signature.startsWith("0x")) msg.signature = `0x${msg.signature}`;
293
+ const digest = keccak256(core.buildMessage(msg.message));
294
+ return recoverAddress(digest, msg.signature) === msg.address;
295
+ }
296
+
297
+ /** Bitcoin */
298
+
299
+ public async btcGetAddress(msg: core.BTCGetAddress): Promise<string | null> {
300
+ const value = await (async () => {
301
+ switch (msg.coin) {
302
+ case "Bitcoin": {
303
+ const accounts = await this.bitcoinProvider.requestAccounts();
304
+ const paymentAddress = accounts.find((account) => account.purpose === "payment")?.address;
305
+
306
+ return paymentAddress;
307
+ }
308
+ default:
309
+ return null;
310
+ }
311
+ })();
312
+ if (!value || typeof value !== "string") return null;
313
+
314
+ return value;
315
+ }
316
+
317
+ public async btcSignTx(msg: core.BTCSignTx): Promise<core.BTCSignedTx | null> {
318
+ const { coin } = msg;
319
+ switch (coin) {
320
+ case "Bitcoin":
321
+ return btc.bitcoinSignTx(this, msg, this.bitcoinProvider);
322
+ default:
323
+ return null;
324
+ }
325
+ }
326
+
327
+ public async btcSignMessage(msg: core.BTCSignMessage): Promise<core.BTCSignedMessage | null> {
328
+ const { coin } = msg;
329
+ switch (coin) {
330
+ case "Bitcoin": {
331
+ const address = await this.btcGetAddress({ coin } as core.BTCGetAddress);
332
+ if (!address) throw new Error(`Could not get ${coin} address`);
333
+ const message = new TextEncoder().encode(msg.message);
334
+
335
+ const { signature } = await this.bitcoinProvider.signMessage(address, message);
336
+ return { signature: core.toHexString(signature), address };
337
+ }
338
+ default:
339
+ return null;
340
+ }
341
+ }
342
+
343
+ public async btcVerifyMessage(msg: core.BTCVerifyMessage): Promise<boolean | null> {
344
+ const signature = Base64.fromByteArray(core.fromHexString(msg.signature));
345
+ return bitcoinMsg.verify(msg.message, msg.address, signature);
346
+ }
347
+ }
package/src/types.ts ADDED
@@ -0,0 +1,23 @@
1
+ import { providers } from "ethers";
2
+
3
+ import { BtcAccount } from "./bitcoin";
4
+
5
+ export type PhantomEvmProvider = providers.ExternalProvider & {
6
+ _metamask: {
7
+ isUnlocked: () => boolean;
8
+ };
9
+ };
10
+
11
+ export type PhantomUtxoProvider = providers.ExternalProvider & {
12
+ requestAccounts: () => Promise<BtcAccount[]>;
13
+ signMessage: (
14
+ address: string,
15
+ message: Uint8Array
16
+ ) => Promise<{
17
+ signature: Uint8Array;
18
+ }>;
19
+ signPSBT(
20
+ psbt: Uint8Array,
21
+ options: { inputsToSign: { sigHash?: number | undefined; address: string; signingIndexes: number[] }[] }
22
+ ): Promise<Uint8Array>;
23
+ };
package/tsconfig.json ADDED
@@ -0,0 +1,10 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "rootDir": "src",
5
+ "outDir": "dist"
6
+ },
7
+ "include": ["src/**/*"],
8
+ "exclude": ["node_modules", "dist", "**/*.test.ts"],
9
+ "references": [{ "path": "../hdwallet-core" }]
10
+ }