@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/LICENSE.md +21 -0
- package/dist/adapter.d.ts +20 -0
- package/dist/adapter.d.ts.map +1 -0
- package/dist/adapter.js +83 -0
- package/dist/adapter.js.map +1 -0
- package/dist/bitcoin.d.ts +13 -0
- package/dist/bitcoin.d.ts.map +1 -0
- package/dist/bitcoin.js +169 -0
- package/dist/bitcoin.js.map +1 -0
- package/dist/ethereum.d.ts +8 -0
- package/dist/ethereum.d.ts.map +1 -0
- package/dist/ethereum.js +138 -0
- package/dist/ethereum.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +19 -0
- package/dist/index.js.map +1 -0
- package/dist/phantom.d.ts +86 -0
- package/dist/phantom.d.ts.map +1 -0
- package/dist/phantom.js +394 -0
- package/dist/phantom.js.map +1 -0
- package/dist/types.d.ts +21 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/package.json +27 -0
- package/src/adapter.ts +62 -0
- package/src/bitcoin.ts +169 -0
- package/src/ethereum.ts +111 -0
- package/src/index.ts +2 -0
- package/src/phantom.test.ts +179 -0
- package/src/phantom.ts +347 -0
- package/src/types.ts +23 -0
- package/tsconfig.json +10 -0
- package/tsconfig.tsbuildinfo +1 -0
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