@shapeshiftoss/hdwallet-phantom 1.55.10-mipdalpha.1 → 1.55.10

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.
@@ -1,13 +1,14 @@
1
1
  import * as core from "@shapeshiftoss/hdwallet-core";
2
2
 
3
3
  import { PhantomHDWallet } from ".";
4
- import { PhantomUtxoProvider } from "./types";
4
+ import { PhantomSolanaProvider, PhantomUtxoProvider } from "./types";
5
5
 
6
6
  describe("PhantomHDWallet", () => {
7
7
  let wallet: PhantomHDWallet;
8
8
 
9
9
  beforeEach(() => {
10
10
  wallet = new PhantomHDWallet(
11
+ core.untouchable("PhantomHDWallet:provider"),
11
12
  core.untouchable("PhantomHDWallet:provider"),
12
13
  core.untouchable("PhantomHDWallet:provider")
13
14
  );
@@ -28,25 +29,105 @@ describe("PhantomHDWallet", () => {
28
29
  expect(wallet.supportsBroadcast()).toBe(true);
29
30
  });
30
31
 
31
- it("should test ethSignMessage", async () => {
32
- wallet.evmProvider = {
33
- _metamask: {
34
- isUnlocked: () => true,
35
- },
36
- request: jest.fn().mockReturnValue(
37
- `Object {
32
+ describe("Ethereum", () => {
33
+ it("ethGetAddress returns a valid address", async () => {
34
+ wallet.evmProvider = {
35
+ _metamask: {
36
+ isUnlocked: () => true,
37
+ },
38
+ request: jest.fn().mockReturnValue(["0x73d0385F4d8E00C5e6504C6030F47BF6212736A8"]),
39
+ };
40
+
41
+ const address = await wallet.ethGetAddress({ addressNList: core.bip32ToAddressNList("m/44'/60'/0'/0/0") });
42
+
43
+ expect(address).toEqual("0x73d0385F4d8E00C5e6504C6030F47BF6212736A8");
44
+ });
45
+
46
+ it("ethSendTx returns a valid hash", async () => {
47
+ wallet.evmProvider = {
48
+ _metamask: {
49
+ isUnlocked: () => true,
50
+ },
51
+ request: jest.fn().mockReturnValue("0x123"),
52
+ };
53
+
54
+ const hash = await wallet.ethSendTx({
55
+ addressNList: core.bip32ToAddressNList("m/44'/60'/0'/0/0"),
56
+ nonce: "0xDEADBEEF",
57
+ gasPrice: "0xDEADBEEF",
58
+ gasLimit: "0xDEADBEEF",
59
+ to: "0xDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF",
60
+ value: "0xDEADBEEFDEADBEEFDEADBEEFDEADBEEF",
61
+ data: "0xDEADBEEFDEADBEEFDEADBEEFDEADBEEF",
62
+ chainId: 1,
63
+ });
64
+ expect(wallet.evmProvider.request).toHaveBeenCalled();
65
+ expect(hash).toMatchObject({ hash: "0x123" });
66
+ });
67
+
68
+ it("ethSendTx returns a valid hash if maxFeePerGas is present in msg", async () => {
69
+ wallet.evmProvider = {
70
+ _metamask: {
71
+ isUnlocked: () => true,
72
+ },
73
+ request: jest.fn().mockReturnValue("0x123"),
74
+ };
75
+
76
+ const hash = await wallet.ethSendTx({
77
+ addressNList: core.bip32ToAddressNList("m/44'/60'/0'/0/0"),
78
+ nonce: "0xDEADBEEF",
79
+ gasLimit: "0xDEADBEEF",
80
+ maxFeePerGas: "0xDEADBEEF",
81
+ to: "0xDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF",
82
+ value: "0xDEADBEEFDEADBEEFDEADBEEFDEADBEEF",
83
+ data: "0xDEADBEEFDEADBEEFDEADBEEFDEADBEEF",
84
+ chainId: 1,
85
+ });
86
+ expect(wallet.evmProvider.request).toHaveBeenCalled();
87
+ expect(hash).toMatchObject({ hash: "0x123" });
88
+ });
89
+
90
+ it("ethSendTx returns null on error", async () => {
91
+ wallet.evmProvider = {
92
+ _metamask: {
93
+ isUnlocked: () => true,
94
+ },
95
+ request: jest.fn().mockRejectedValue(new Error("An Error has occurred")),
96
+ };
97
+
98
+ const hash = await wallet.ethSendTx({
99
+ addressNList: core.bip32ToAddressNList("m/44'/60'/0'/0/0"),
100
+ nonce: "0xDEADBEEF",
101
+ gasPrice: "0xDEADBEEF",
102
+ gasLimit: "0xDEADBEEF",
103
+ to: "0xDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF",
104
+ value: "0xDEADBEEFDEADBEEFDEADBEEFDEADBEEF",
105
+ data: "0xDEADBEEFDEADBEEFDEADBEEFDEADBEEF",
106
+ chainId: 1,
107
+ });
108
+ expect(wallet.evmProvider.request).toHaveBeenCalled();
109
+ expect(hash).toBe(null);
110
+ });
111
+
112
+ it("should test ethSignMessage", async () => {
113
+ wallet.evmProvider = {
114
+ _metamask: {
115
+ isUnlocked: () => true,
116
+ },
117
+ request: jest.fn().mockReturnValue(
118
+ `Object {
38
119
  "address": "0x73d0385F4d8E00C5e6504C6030F47BF6212736A8",
39
120
  "signature": "0x05f51140905ffa33ffdc57f46b0b8d8fbb1d2a99f8cd843ca27893c01c31351c08b76d83dce412731c846e3b50649724415deb522d00950fbf4f2c1459c2b70b1b",
40
121
  }`
41
- ),
42
- };
43
- const msg = "0x737570657220736563726574206d657373616765"; // super secret message
44
- expect(
45
- await wallet.ethSignMessage({
46
- addressNList: core.bip32ToAddressNList("m/44'/60'/0'/0/0"),
47
- message: msg,
48
- })
49
- ).toMatchInlineSnapshot(`
122
+ ),
123
+ };
124
+ const msg = "0x737570657220736563726574206d657373616765"; // super secret message
125
+ expect(
126
+ await wallet.ethSignMessage({
127
+ addressNList: core.bip32ToAddressNList("m/44'/60'/0'/0/0"),
128
+ message: msg,
129
+ })
130
+ ).toMatchInlineSnapshot(`
50
131
  Object {
51
132
  "address": "O",
52
133
  "signature": "Object {
@@ -55,125 +136,67 @@ describe("PhantomHDWallet", () => {
55
136
  }",
56
137
  }
57
138
  `);
58
- });
59
-
60
- it("ethSignMessage returns null on error", async () => {
61
- wallet.evmProvider = {
62
- _metamask: {
63
- isUnlocked: () => true,
64
- },
65
- request: jest.fn().mockRejectedValue(new Error("An Error has occurred")),
66
- };
67
-
68
- const msg = "0x737570657220736563726574206d657373616765"; // super secret message
69
- const sig = await wallet.ethSignMessage({
70
- addressNList: core.bip32ToAddressNList("m/44'/60'/0'/0/0"),
71
- message: msg,
72
139
  });
73
140
 
74
- expect(sig).toBe(null);
75
- });
76
-
77
- it("ethGetAddress returns a valid address", async () => {
78
- wallet.evmProvider = {
79
- _metamask: {
80
- isUnlocked: () => true,
81
- },
82
- request: jest.fn().mockReturnValue(["0x73d0385F4d8E00C5e6504C6030F47BF6212736A8"]),
83
- };
84
-
85
- const address = await wallet.ethGetAddress();
86
-
87
- expect(address).toEqual("0x73d0385F4d8E00C5e6504C6030F47BF6212736A8");
88
- });
89
- it("btcGetAddress returns a valid address", async () => {
90
- wallet.bitcoinProvider = {
91
- requestAccounts: jest.fn().mockReturnValue([
92
- {
93
- purpose: "payment",
94
- address: "bc1q9sjm947kn2hz84syykmem7dshvevm8xm5dkrpg",
141
+ it("ethSignMessage returns null on error", async () => {
142
+ wallet.evmProvider = {
143
+ _metamask: {
144
+ isUnlocked: () => true,
95
145
  },
96
- ]),
97
- } as unknown as PhantomUtxoProvider;
146
+ request: jest.fn().mockRejectedValue(new Error("An Error has occurred")),
147
+ };
98
148
 
99
- const address = await wallet.btcGetAddress({
100
- coin: "Bitcoin",
101
- } as core.BTCGetAddress);
149
+ const msg = "0x737570657220736563726574206d657373616765"; // super secret message
150
+ const sig = await wallet.ethSignMessage({
151
+ addressNList: core.bip32ToAddressNList("m/44'/60'/0'/0/0"),
152
+ message: msg,
153
+ });
102
154
 
103
- expect(address).toEqual("bc1q9sjm947kn2hz84syykmem7dshvevm8xm5dkrpg");
104
- });
155
+ expect(sig).toBe(null);
156
+ });
105
157
 
106
- it("ethSendTx returns a valid hash", async () => {
107
- wallet.evmProvider = {
108
- _metamask: {
109
- isUnlocked: () => true,
110
- },
111
- request: jest.fn().mockReturnValue("0x123"),
112
- };
113
-
114
- const hash = await wallet.ethSendTx({
115
- addressNList: core.bip32ToAddressNList("m/44'/60'/0'/0/0"),
116
- nonce: "0xDEADBEEF",
117
- gasPrice: "0xDEADBEEF",
118
- gasLimit: "0xDEADBEEF",
119
- to: "0xDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF",
120
- value: "0xDEADBEEFDEADBEEFDEADBEEFDEADBEEF",
121
- data: "0xDEADBEEFDEADBEEFDEADBEEFDEADBEEF",
122
- chainId: 1,
158
+ it("ethVerifyMessage returns true for a valid signature", async () => {
159
+ expect(
160
+ await wallet.ethVerifyMessage({
161
+ address: "0x2068dD92B6690255553141Dfcf00dF308281f763",
162
+ message: "Hello World",
163
+ signature:
164
+ "0x61f1dda82e9c3800e960894396c9ce8164fd1526fccb136c71b88442405f7d09721725629915d10bc7cecfca2818fe76bc5816ed96a1b0cebee9b03b052980131b",
165
+ })
166
+ ).toEqual(true);
123
167
  });
124
- expect(wallet.evmProvider.request).toHaveBeenCalled();
125
- expect(hash).toMatchObject({ hash: "0x123" });
126
168
  });
127
- it("ethSendTx returns a valid hash if maxFeePerGas is present in msg", async () => {
128
- wallet.evmProvider = {
129
- _metamask: {
130
- isUnlocked: () => true,
131
- },
132
- request: jest.fn().mockReturnValue("0x123"),
133
- };
134
-
135
- const hash = await wallet.ethSendTx({
136
- addressNList: core.bip32ToAddressNList("m/44'/60'/0'/0/0"),
137
- nonce: "0xDEADBEEF",
138
- gasLimit: "0xDEADBEEF",
139
- maxFeePerGas: "0xDEADBEEF",
140
- to: "0xDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF",
141
- value: "0xDEADBEEFDEADBEEFDEADBEEFDEADBEEF",
142
- data: "0xDEADBEEFDEADBEEFDEADBEEFDEADBEEF",
143
- chainId: 1,
169
+
170
+ describe("Bitcoin", () => {
171
+ it("btcGetAddress returns a valid address", async () => {
172
+ wallet.bitcoinProvider = {
173
+ requestAccounts: jest.fn().mockReturnValue([
174
+ {
175
+ purpose: "payment",
176
+ address: "bc1q9sjm947kn2hz84syykmem7dshvevm8xm5dkrpg",
177
+ },
178
+ ]),
179
+ } as unknown as PhantomUtxoProvider;
180
+
181
+ const address = await wallet.btcGetAddress({
182
+ coin: "Bitcoin",
183
+ } as core.BTCGetAddress);
184
+
185
+ expect(address).toEqual("bc1q9sjm947kn2hz84syykmem7dshvevm8xm5dkrpg");
144
186
  });
145
- expect(wallet.evmProvider.request).toHaveBeenCalled();
146
- expect(hash).toMatchObject({ hash: "0x123" });
147
187
  });
148
- it("ethSendTx returns null on error", async () => {
149
- wallet.evmProvider = {
150
- _metamask: {
151
- isUnlocked: () => true,
152
- },
153
- request: jest.fn().mockRejectedValue(new Error("An Error has occurred")),
154
- };
155
-
156
- const hash = await wallet.ethSendTx({
157
- addressNList: core.bip32ToAddressNList("m/44'/60'/0'/0/0"),
158
- nonce: "0xDEADBEEF",
159
- gasPrice: "0xDEADBEEF",
160
- gasLimit: "0xDEADBEEF",
161
- to: "0xDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF",
162
- value: "0xDEADBEEFDEADBEEFDEADBEEFDEADBEEF",
163
- data: "0xDEADBEEFDEADBEEFDEADBEEFDEADBEEF",
164
- chainId: 1,
188
+
189
+ describe("Solana", () => {
190
+ it("solanaGetAddress returns a valid address", async () => {
191
+ wallet.solanaProvider = {
192
+ connect: jest.fn().mockReturnValue({
193
+ publicKey: "DsYwEVzeSNMkU5PVwjwtZ8EDRQxaR6paXfFAdhMQxmaV",
194
+ }),
195
+ } as unknown as PhantomSolanaProvider;
196
+
197
+ const address = await wallet.solanaGetAddress();
198
+
199
+ expect(address).toEqual("DsYwEVzeSNMkU5PVwjwtZ8EDRQxaR6paXfFAdhMQxmaV");
165
200
  });
166
- expect(wallet.evmProvider.request).toHaveBeenCalled();
167
- expect(hash).toBe(null);
168
- });
169
- it("ethVerifyMessage returns true for a valid signature", async () => {
170
- expect(
171
- await wallet.ethVerifyMessage({
172
- address: "0x2068dD92B6690255553141Dfcf00dF308281f763",
173
- message: "Hello World",
174
- signature:
175
- "0x61f1dda82e9c3800e960894396c9ce8164fd1526fccb136c71b88442405f7d09721725629915d10bc7cecfca2818fe76bc5816ed96a1b0cebee9b03b052980131b",
176
- })
177
- ).toEqual(true);
178
201
  });
179
202
  });
package/src/phantom.ts CHANGED
@@ -7,15 +7,19 @@ import _ from "lodash";
7
7
 
8
8
  import * as btc from "./bitcoin";
9
9
  import * as eth from "./ethereum";
10
- import { PhantomEvmProvider, PhantomUtxoProvider } from "./types";
10
+ import { solanaSendTx, solanaSignTx } from "./solana";
11
+ import { PhantomEvmProvider, PhantomSolanaProvider, PhantomUtxoProvider } from "./types";
11
12
 
12
13
  export function isPhantom(wallet: core.HDWallet): wallet is PhantomHDWallet {
13
14
  return _.isObject(wallet) && (wallet as any)._isPhantom;
14
15
  }
15
16
 
16
- export class PhantomHDWalletInfo implements core.HDWalletInfo, core.BTCWalletInfo, core.ETHWalletInfo {
17
+ export class PhantomHDWalletInfo
18
+ implements core.HDWalletInfo, core.BTCWalletInfo, core.ETHWalletInfo, core.SolanaWalletInfo
19
+ {
17
20
  readonly _supportsBTCInfo = true;
18
21
  readonly _supportsETHInfo = true;
22
+ readonly _supportsSolanaInfo = true;
19
23
 
20
24
  evmProvider: PhantomEvmProvider;
21
25
 
@@ -73,6 +77,8 @@ export class PhantomHDWalletInfo implements core.HDWalletInfo, core.BTCWalletInf
73
77
  }
74
78
  case "ethereum":
75
79
  return core.describeETHPath(msg.path);
80
+ case "solana":
81
+ return core.solanaDescribePath(msg.path);
76
82
  default:
77
83
  throw new Error("Unsupported path");
78
84
  }
@@ -114,13 +120,14 @@ export class PhantomHDWalletInfo implements core.HDWalletInfo, core.BTCWalletInf
114
120
 
115
121
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
116
122
  public ethNextAccountPath(msg: core.ETHAccountPath): core.ETHAccountPath | undefined {
117
- throw new Error("Method not implemented");
123
+ console.error("Method not implemented");
124
+ return undefined;
118
125
  }
119
126
 
120
127
  /** Bitcoin */
121
128
 
122
129
  public async btcSupportsCoin(coin: core.Coin): Promise<boolean> {
123
- return coin === "bitcoin";
130
+ return coin.toLowerCase() === "bitcoin";
124
131
  }
125
132
 
126
133
  public async btcSupportsScriptType(coin: string, scriptType?: core.BTCInputScriptType | undefined): Promise<boolean> {
@@ -147,13 +154,32 @@ export class PhantomHDWalletInfo implements core.HDWalletInfo, core.BTCWalletInf
147
154
  return btc.btcGetAccountPaths(msg);
148
155
  }
149
156
 
157
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
158
+ public btcIsSameAccount(msg: core.BTCAccountPath[]): boolean {
159
+ throw new Error("Method not implemented.");
160
+ }
161
+
150
162
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
151
163
  public btcNextAccountPath(msg: core.BTCAccountPath): core.BTCAccountPath | undefined {
152
164
  throw new Error("Method not implemented");
153
165
  }
166
+
167
+ /** Solana */
168
+
169
+ public solanaGetAccountPaths(msg: core.SolanaGetAccountPaths): Array<core.SolanaAccountPath> {
170
+ return core.solanaGetAccountPaths(msg);
171
+ }
172
+
173
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
174
+ public solanaNextAccountPath(msg: core.SolanaAccountPath): core.SolanaAccountPath | undefined {
175
+ throw new Error("Method not implemented");
176
+ }
154
177
  }
155
178
 
156
- export class PhantomHDWallet extends PhantomHDWalletInfo implements core.HDWallet, core.BTCWallet, core.ETHWallet {
179
+ export class PhantomHDWallet
180
+ extends PhantomHDWalletInfo
181
+ implements core.HDWallet, core.BTCWallet, core.ETHWallet, core.SolanaWallet
182
+ {
157
183
  readonly _supportsBTC = true;
158
184
  readonly _supportsETH = true;
159
185
  readonly _supportsEthSwitchChain = false;
@@ -167,20 +193,29 @@ export class PhantomHDWallet extends PhantomHDWalletInfo implements core.HDWalle
167
193
  readonly _supportsArbitrumNova = false;
168
194
  readonly _supportsBase = false;
169
195
  readonly _supportsBSC = false;
196
+ readonly _supportsSolana = true;
170
197
  readonly _isPhantom = true;
171
198
 
172
199
  evmProvider: PhantomEvmProvider;
173
200
  bitcoinProvider: PhantomUtxoProvider;
201
+ solanaProvider: PhantomSolanaProvider;
202
+
174
203
  ethAddress?: string | null;
175
204
 
176
- constructor(evmProvider: PhantomEvmProvider, bitcoinProvider: PhantomUtxoProvider) {
205
+ constructor(
206
+ evmProvider: PhantomEvmProvider,
207
+ bitcoinProvider: PhantomUtxoProvider,
208
+ solanaProvider: PhantomSolanaProvider
209
+ ) {
177
210
  super(evmProvider);
211
+
178
212
  this.evmProvider = evmProvider;
179
213
  this.bitcoinProvider = bitcoinProvider;
214
+ this.solanaProvider = solanaProvider;
180
215
  }
181
216
 
182
217
  public async getDeviceID(): Promise<string> {
183
- return "phantom:" + (await this.ethGetAddress());
218
+ return "phantom:" + (await this.ethGetAddress({ addressNList: [] }));
184
219
  }
185
220
 
186
221
  async getFeatures(): Promise<Record<string, any>> {
@@ -262,11 +297,14 @@ export class PhantomHDWallet extends PhantomHDWalletInfo implements core.HDWalle
262
297
 
263
298
  /** Ethereum */
264
299
 
265
- public async ethGetAddress(): Promise<string | null> {
300
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
301
+ public async ethGetAddress(_msg: core.ETHGetAddress): Promise<string | null> {
266
302
  if (this.ethAddress) {
267
303
  return this.ethAddress;
268
304
  }
305
+
269
306
  const address = await eth.ethGetAddress(this.evmProvider);
307
+
270
308
  if (address) {
271
309
  this.ethAddress = address;
272
310
  return address;
@@ -278,21 +316,22 @@ export class PhantomHDWallet extends PhantomHDWalletInfo implements core.HDWalle
278
316
 
279
317
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
280
318
  public async ethSignTx(msg: core.ETHSignTx): Promise<core.ETHSignedTx | null> {
281
- throw new Error("Method not implemented");
319
+ console.error("Method not implemented");
320
+ return null;
282
321
  }
283
322
 
284
323
  public async ethSendTx(msg: core.ETHSignTx): Promise<core.ETHTxHash | null> {
285
- const address = await this.ethGetAddress();
324
+ const address = await this.ethGetAddress({ addressNList: [] });
286
325
  return address ? eth.ethSendTx(msg, this.evmProvider, address) : null;
287
326
  }
288
327
 
289
328
  public async ethSignMessage(msg: core.ETHSignMessage): Promise<core.ETHSignedMessage | null> {
290
- const address = await this.ethGetAddress();
329
+ const address = await this.ethGetAddress({ addressNList: [] });
291
330
  return address ? eth.ethSignMessage(msg, this.evmProvider, address) : null;
292
331
  }
293
332
 
294
333
  async ethSignTypedData(msg: core.ETHSignTypedData): Promise<core.ETHSignedTypedData | null> {
295
- const address = await this.ethGetAddress();
334
+ const address = await this.ethGetAddress({ addressNList: [] });
296
335
  return address ? eth.ethSignTypedData(msg, this.evmProvider, address) : null;
297
336
  }
298
337
 
@@ -352,4 +391,21 @@ export class PhantomHDWallet extends PhantomHDWalletInfo implements core.HDWalle
352
391
  const signature = Base64.fromByteArray(core.fromHexString(msg.signature));
353
392
  return bitcoinMsg.verify(msg.message, msg.address, signature);
354
393
  }
394
+
395
+ /** Solana */
396
+
397
+ public async solanaGetAddress(): Promise<string | null> {
398
+ const { publicKey } = await this.solanaProvider.connect();
399
+ return publicKey.toString();
400
+ }
401
+
402
+ public async solanaSignTx(msg: core.SolanaSignTx): Promise<core.SolanaSignedTx | null> {
403
+ const address = await this.solanaGetAddress();
404
+ return address ? solanaSignTx(msg, this.solanaProvider, address) : null;
405
+ }
406
+
407
+ public async solanaSendTx(msg: core.SolanaSignTx): Promise<core.SolanaTxSignature | null> {
408
+ const address = await this.solanaGetAddress();
409
+ return address ? solanaSendTx(msg, this.solanaProvider, address) : null;
410
+ }
355
411
  }
package/src/solana.ts ADDED
@@ -0,0 +1,80 @@
1
+ import * as core from "@shapeshiftoss/hdwallet-core";
2
+ import {
3
+ ComputeBudgetProgram,
4
+ PublicKey,
5
+ SystemProgram,
6
+ TransactionInstruction,
7
+ TransactionMessage,
8
+ VersionedTransaction,
9
+ } from "@solana/web3.js";
10
+
11
+ import { PhantomSolanaProvider } from "./types";
12
+
13
+ export type SolanaAccount = {
14
+ publicKey: PublicKey;
15
+ };
16
+
17
+ function toTransactionInstructions(instructions: core.SolanaTxInstruction[]): TransactionInstruction[] {
18
+ return instructions.map(
19
+ (instruction) =>
20
+ new TransactionInstruction({
21
+ keys: instruction.keys.map((key) => Object.assign(key, { pubkey: new PublicKey(key.pubkey) })),
22
+ programId: new PublicKey(instruction.programId),
23
+ data: instruction.data,
24
+ })
25
+ );
26
+ }
27
+
28
+ function buildTransaction(msg: core.SolanaSignTx, address: string): VersionedTransaction {
29
+ const instructions = toTransactionInstructions(msg.instructions ?? []);
30
+
31
+ const value = Number(msg.value);
32
+ if (!isNaN(value) && value > 0 && msg.to) {
33
+ instructions.push(
34
+ SystemProgram.transfer({
35
+ fromPubkey: new PublicKey(address),
36
+ toPubkey: new PublicKey(msg.to),
37
+ lamports: value,
38
+ })
39
+ );
40
+ }
41
+
42
+ if (msg.computeUnitLimit !== undefined) {
43
+ instructions.push(ComputeBudgetProgram.setComputeUnitLimit({ units: msg.computeUnitLimit }));
44
+ }
45
+
46
+ if (msg.computeUnitPrice !== undefined) {
47
+ instructions.push(ComputeBudgetProgram.setComputeUnitPrice({ microLamports: msg.computeUnitPrice }));
48
+ }
49
+
50
+ const message = new TransactionMessage({
51
+ payerKey: new PublicKey(address),
52
+ instructions,
53
+ recentBlockhash: msg.blockHash,
54
+ }).compileToV0Message();
55
+
56
+ return new VersionedTransaction(message);
57
+ }
58
+
59
+ export async function solanaSignTx(
60
+ msg: core.SolanaSignTx,
61
+ provider: PhantomSolanaProvider,
62
+ address: string
63
+ ): Promise<core.SolanaSignedTx | null> {
64
+ const transaction = buildTransaction(msg, address);
65
+ const signedTransaction = await provider.signTransaction(transaction);
66
+ return {
67
+ serialized: Buffer.from(signedTransaction.serialize()).toString("base64"),
68
+ signatures: signedTransaction.signatures.map((signature) => Buffer.from(signature).toString("base64")),
69
+ };
70
+ }
71
+
72
+ export async function solanaSendTx(
73
+ msg: core.SolanaSignTx,
74
+ provider: PhantomSolanaProvider,
75
+ address: string
76
+ ): Promise<core.SolanaTxSignature | null> {
77
+ const transaction = buildTransaction(msg, address);
78
+ const { signature } = await provider.signAndSendTransaction(transaction);
79
+ return { signature };
80
+ }
package/src/types.ts CHANGED
@@ -1,6 +1,8 @@
1
+ import { PublicKey, VersionedTransaction } from "@solana/web3.js";
1
2
  import { providers } from "ethers";
2
3
 
3
4
  import { BtcAccount } from "./bitcoin";
5
+ import { SolanaAccount } from "./solana";
4
6
 
5
7
  export type PhantomEvmProvider = providers.ExternalProvider & {
6
8
  _metamask: {
@@ -21,3 +23,10 @@ export type PhantomUtxoProvider = providers.ExternalProvider & {
21
23
  options: { inputsToSign: { sigHash?: number | undefined; address: string; signingIndexes: number[] }[] }
22
24
  ): Promise<Uint8Array>;
23
25
  };
26
+
27
+ export type PhantomSolanaProvider = providers.ExternalProvider & {
28
+ publicKey?: PublicKey;
29
+ connect(): Promise<SolanaAccount>;
30
+ signTransaction(transaction: VersionedTransaction): Promise<VersionedTransaction>;
31
+ signAndSendTransaction(transaction: VersionedTransaction): Promise<{ signature: any }>;
32
+ };