@shapeshiftoss/hdwallet-gridplus 1.62.4-gridplus.alpha.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.
Files changed (66) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/LICENSE.md +21 -0
  3. package/dist/adapter.d.ts +19 -0
  4. package/dist/adapter.d.ts.map +1 -0
  5. package/dist/adapter.js +129 -0
  6. package/dist/adapter.js.map +1 -0
  7. package/dist/bitcoin.d.ts +7 -0
  8. package/dist/bitcoin.d.ts.map +1 -0
  9. package/dist/bitcoin.js +619 -0
  10. package/dist/bitcoin.js.map +1 -0
  11. package/dist/constants.d.ts +18 -0
  12. package/dist/constants.d.ts.map +1 -0
  13. package/dist/constants.js +51 -0
  14. package/dist/constants.js.map +1 -0
  15. package/dist/cosmos.d.ts +7 -0
  16. package/dist/cosmos.d.ts.map +1 -0
  17. package/dist/cosmos.js +156 -0
  18. package/dist/cosmos.js.map +1 -0
  19. package/dist/ethereum.d.ts +7 -0
  20. package/dist/ethereum.d.ts.map +1 -0
  21. package/dist/ethereum.js +294 -0
  22. package/dist/ethereum.js.map +1 -0
  23. package/dist/gridplus.d.ts +112 -0
  24. package/dist/gridplus.d.ts.map +1 -0
  25. package/dist/gridplus.js +574 -0
  26. package/dist/gridplus.js.map +1 -0
  27. package/dist/index.d.ts +4 -0
  28. package/dist/index.d.ts.map +1 -0
  29. package/dist/index.js +24 -0
  30. package/dist/index.js.map +1 -0
  31. package/dist/mayachain.d.ts +7 -0
  32. package/dist/mayachain.d.ts.map +1 -0
  33. package/dist/mayachain.js +163 -0
  34. package/dist/mayachain.js.map +1 -0
  35. package/dist/solana.d.ts +5 -0
  36. package/dist/solana.d.ts.map +1 -0
  37. package/dist/solana.js +120 -0
  38. package/dist/solana.js.map +1 -0
  39. package/dist/thorchain.d.ts +5 -0
  40. package/dist/thorchain.d.ts.map +1 -0
  41. package/dist/thorchain.js +143 -0
  42. package/dist/thorchain.js.map +1 -0
  43. package/dist/transport.d.ts +28 -0
  44. package/dist/transport.d.ts.map +1 -0
  45. package/dist/transport.js +148 -0
  46. package/dist/transport.js.map +1 -0
  47. package/dist/utils.d.ts +17 -0
  48. package/dist/utils.d.ts.map +1 -0
  49. package/dist/utils.js +117 -0
  50. package/dist/utils.js.map +1 -0
  51. package/package.json +38 -0
  52. package/package.json.bak +38 -0
  53. package/src/adapter.ts +109 -0
  54. package/src/bitcoin.ts +711 -0
  55. package/src/constants.ts +52 -0
  56. package/src/cosmos.ts +132 -0
  57. package/src/ethereum.ts +305 -0
  58. package/src/gridplus.ts +550 -0
  59. package/src/index.ts +3 -0
  60. package/src/mayachain.ts +150 -0
  61. package/src/solana.ts +97 -0
  62. package/src/thorchain.ts +125 -0
  63. package/src/transport.ts +131 -0
  64. package/src/utils.ts +101 -0
  65. package/tsconfig.json +10 -0
  66. package/tsconfig.tsbuildinfo +1 -0
@@ -0,0 +1,550 @@
1
+ import * as core from "@shapeshiftoss/hdwallet-core";
2
+ import { Client, Constants } from "gridplus-sdk";
3
+ import isObject from "lodash/isObject";
4
+
5
+ import * as btc from "./bitcoin";
6
+ import * as cosmos from "./cosmos";
7
+ import * as eth from "./ethereum";
8
+ import * as mayachain from "./mayachain";
9
+ import * as solana from "./solana";
10
+ import * as thorchain from "./thorchain";
11
+ import { GridPlusTransport } from "./transport";
12
+ import { convertXpubVersion, scriptTypeToAccountType } from "./utils";
13
+
14
+ export function isGridPlus(wallet: core.HDWallet): wallet is GridPlusHDWallet {
15
+ return isObject(wallet) && (wallet as any)._isGridPlus;
16
+ }
17
+
18
+ export class GridPlusWalletInfo
19
+ implements
20
+ core.HDWalletInfo,
21
+ core.BTCWalletInfo,
22
+ core.CosmosWalletInfo,
23
+ core.ETHWalletInfo,
24
+ core.MayachainWalletInfo,
25
+ core.SolanaWalletInfo,
26
+ core.ThorchainWalletInfo
27
+ {
28
+ readonly _supportsBTCInfo = true;
29
+ readonly _supportsCosmosInfo = true;
30
+ readonly _supportsETHInfo = true;
31
+ readonly _supportsMayachainInfo = true;
32
+ readonly _supportsSolanaInfo = false;
33
+ readonly _supportsThorchainInfo = true;
34
+
35
+ getVendor(): string {
36
+ return "GridPlus";
37
+ }
38
+
39
+ hasOnDevicePinEntry(): boolean {
40
+ return false;
41
+ }
42
+
43
+ hasOnDevicePassphrase(): boolean {
44
+ return false;
45
+ }
46
+
47
+ hasOnDeviceDisplay(): boolean {
48
+ return true;
49
+ }
50
+
51
+ hasOnDeviceRecovery(): boolean {
52
+ return false;
53
+ }
54
+
55
+ hasNativeShapeShift(): boolean {
56
+ return false;
57
+ }
58
+
59
+ supportsBip44Accounts(): boolean {
60
+ return true;
61
+ }
62
+
63
+ supportsOfflineSigning(): boolean {
64
+ return true;
65
+ }
66
+
67
+ supportsBroadcast(): boolean {
68
+ return false;
69
+ }
70
+
71
+ describePath(): core.PathDescription {
72
+ return {
73
+ verbose: "GridPlus path description not implemented",
74
+ coin: "Unknown",
75
+ isKnown: false,
76
+ };
77
+ }
78
+
79
+ /**
80
+ * Bitcoin Wallet Info
81
+ */
82
+ async btcSupportsCoin(coin: core.Coin): Promise<boolean> {
83
+ const supportedCoins = ["Bitcoin", "BitcoinCash", "Litecoin", "Dogecoin"];
84
+ return supportedCoins.includes(coin);
85
+ }
86
+
87
+ async btcSupportsScriptType(coin: core.Coin, scriptType: core.BTCInputScriptType): Promise<boolean> {
88
+ switch (scriptType) {
89
+ case core.BTCInputScriptType.SpendAddress:
90
+ return ["Bitcoin", "BitcoinCash", "Litecoin", "Dogecoin"].includes(coin);
91
+ case core.BTCInputScriptType.SpendP2SHWitness:
92
+ return ["Bitcoin", "BitcoinCash", "Litecoin"].includes(coin);
93
+ case core.BTCInputScriptType.SpendWitness:
94
+ return ["Bitcoin", "BitcoinCash", "Litecoin"].includes(coin);
95
+ default:
96
+ return false;
97
+ }
98
+ }
99
+
100
+ async btcSupportsSecureTransfer(): Promise<boolean> {
101
+ return false;
102
+ }
103
+
104
+ btcSupportsNativeShapeShift(): boolean {
105
+ return false;
106
+ }
107
+
108
+ btcGetAccountPaths(msg: core.BTCGetAccountPaths): Array<core.BTCAccountPath> {
109
+ const slip44 = core.slip44ByCoin(msg.coin);
110
+ if (slip44 === undefined) return [];
111
+
112
+ const bip44 = core.legacyAccount(msg.coin, slip44, msg.accountIdx);
113
+ const bip49 = core.segwitAccount(msg.coin, slip44, msg.accountIdx);
114
+ const bip84 = core.segwitNativeAccount(msg.coin, slip44, msg.accountIdx);
115
+
116
+ const coinPaths: Record<string, Array<core.BTCAccountPath>> = {
117
+ bitcoin: [bip44, bip49, bip84],
118
+ bitcoincash: [bip44, bip49, bip84],
119
+ dogecoin: [bip44],
120
+ litecoin: [bip44, bip49, bip84],
121
+ };
122
+
123
+ const paths = coinPaths[msg.coin.toLowerCase()] || [];
124
+
125
+ if (msg.scriptType !== undefined) {
126
+ return paths.filter((path) => path.scriptType === msg.scriptType);
127
+ }
128
+
129
+ return paths;
130
+ }
131
+
132
+ btcNextAccountPath(msg: core.BTCAccountPath): core.BTCAccountPath | undefined {
133
+ const description = core.describeUTXOPath(msg.addressNList, msg.coin, msg.scriptType);
134
+
135
+ if (!description.isKnown) return undefined;
136
+
137
+ const addressNList = msg.addressNList;
138
+
139
+ if (
140
+ (addressNList[0] === 0x80000000 + 44 && msg.scriptType == core.BTCInputScriptType.SpendAddress) ||
141
+ (addressNList[0] === 0x80000000 + 49 && msg.scriptType == core.BTCInputScriptType.SpendP2SHWitness) ||
142
+ (addressNList[0] === 0x80000000 + 84 && msg.scriptType == core.BTCInputScriptType.SpendWitness)
143
+ ) {
144
+ addressNList[2] += 1;
145
+ return { ...msg, addressNList };
146
+ }
147
+
148
+ return undefined;
149
+ }
150
+
151
+ /**
152
+ * Ethereum Wallet Info
153
+ */
154
+ async ethSupportsNetwork(chainId: number): Promise<boolean> {
155
+ const supportedChains = [1, 137, 10, 42161, 8453, 56, 100, 43114];
156
+ return supportedChains.includes(chainId);
157
+ }
158
+
159
+ async ethSupportsSecureTransfer(): Promise<boolean> {
160
+ return false;
161
+ }
162
+
163
+ ethSupportsNativeShapeShift(): boolean {
164
+ return false;
165
+ }
166
+
167
+ async ethSupportsEIP1559(): Promise<boolean> {
168
+ return true;
169
+ }
170
+
171
+ ethGetAccountPaths(msg: core.ETHGetAccountPath): Array<core.ETHAccountPath> {
172
+ const slip44 = core.slip44ByCoin(msg.coin);
173
+ if (slip44 === undefined) return [];
174
+
175
+ return [
176
+ {
177
+ addressNList: [0x80000000 + 44, 0x80000000 + slip44, 0x80000000 + msg.accountIdx, 0, 0],
178
+ hardenedPath: [0x80000000 + 44, 0x80000000 + slip44, 0x80000000 + msg.accountIdx],
179
+ relPath: [0, 0],
180
+ description: "GridPlus",
181
+ },
182
+ ];
183
+ }
184
+
185
+ ethNextAccountPath(msg: core.ETHAccountPath): core.ETHAccountPath | undefined {
186
+ const addressNList = msg.hardenedPath.concat(msg.relPath);
187
+ const description = core.describeETHPath(addressNList);
188
+ if (!description.isKnown || description.accountIdx === undefined) {
189
+ return undefined;
190
+ }
191
+
192
+ const newAddressNList = [...addressNList];
193
+ newAddressNList[2] += 1;
194
+
195
+ return {
196
+ addressNList: newAddressNList,
197
+ hardenedPath: [newAddressNList[0], newAddressNList[1], newAddressNList[2]],
198
+ relPath: [0, 0],
199
+ description: "GridPlus",
200
+ };
201
+ }
202
+
203
+ // Solana Wallet Info Methods
204
+ solanaGetAccountPaths(msg: core.SolanaGetAccountPaths): Array<core.SolanaAccountPath> {
205
+ return [{ addressNList: [0x80000000 + 44, 0x80000000 + 501, 0x80000000 + msg.accountIdx, 0x80000000 + 0] }];
206
+ }
207
+
208
+ solanaNextAccountPath(msg: core.SolanaAccountPath): core.SolanaAccountPath | undefined {
209
+ // Increment account index for next account
210
+ const newAddressNList = [...msg.addressNList];
211
+ newAddressNList[2] += 1; // Increment account index
212
+
213
+ return {
214
+ addressNList: newAddressNList,
215
+ };
216
+ }
217
+
218
+ // Cosmos Wallet Info Methods
219
+ cosmosGetAccountPaths(msg: core.CosmosGetAccountPaths): Array<core.CosmosAccountPath> {
220
+ const slip44 = core.slip44ByCoin("Atom");
221
+ return [
222
+ {
223
+ addressNList: [0x80000000 + 44, 0x80000000 + slip44, 0x80000000 + msg.accountIdx, 0, 0],
224
+ },
225
+ ];
226
+ }
227
+
228
+ cosmosNextAccountPath(msg: core.CosmosAccountPath): core.CosmosAccountPath | undefined {
229
+ const newAddressNList = [...msg.addressNList];
230
+ newAddressNList[2] += 1;
231
+ return {
232
+ addressNList: newAddressNList,
233
+ };
234
+ }
235
+
236
+ // THORChain Wallet Info Methods
237
+ thorchainGetAccountPaths(msg: core.ThorchainGetAccountPaths): Array<core.ThorchainAccountPath> {
238
+ const slip44 = core.slip44ByCoin("Rune");
239
+ return [
240
+ {
241
+ addressNList: [0x80000000 + 44, 0x80000000 + slip44, 0x80000000 + msg.accountIdx, 0, 0],
242
+ },
243
+ ];
244
+ }
245
+
246
+ thorchainNextAccountPath(msg: core.ThorchainAccountPath): core.ThorchainAccountPath | undefined {
247
+ const newAddressNList = [...msg.addressNList];
248
+ newAddressNList[2] += 1;
249
+ return {
250
+ addressNList: newAddressNList,
251
+ };
252
+ }
253
+
254
+ mayachainGetAccountPaths(msg: core.MayachainGetAccountPaths): Array<core.MayachainAccountPath> {
255
+ const slip44 = core.slip44ByCoin("Mayachain");
256
+ return [
257
+ {
258
+ addressNList: [0x80000000 + 44, 0x80000000 + slip44, 0x80000000 + msg.accountIdx, 0, 0],
259
+ },
260
+ ];
261
+ }
262
+
263
+ mayachainNextAccountPath(msg: core.MayachainAccountPath): core.MayachainAccountPath | undefined {
264
+ const newAddressNList = [...msg.addressNList];
265
+ newAddressNList[2] += 1;
266
+ return {
267
+ addressNList: newAddressNList,
268
+ };
269
+ }
270
+ }
271
+
272
+ export class GridPlusHDWallet
273
+ extends GridPlusWalletInfo
274
+ implements
275
+ core.HDWallet,
276
+ core.ETHWallet,
277
+ core.SolanaWallet,
278
+ core.BTCWallet,
279
+ core.CosmosWallet,
280
+ core.ThorchainWallet,
281
+ core.MayachainWallet
282
+ {
283
+ readonly _supportsArbitrum = true;
284
+ readonly _supportsArbitrumNova = false;
285
+ readonly _supportsAvalanche = true;
286
+ readonly _supportsBSC = true;
287
+ readonly _supportsBTC = true;
288
+ readonly _supportsBase = true;
289
+ readonly _supportsCosmos = true;
290
+ readonly _supportsETH = true;
291
+ readonly _supportsEthSwitchChain = false;
292
+ readonly _supportsGnosis = true;
293
+ readonly _supportsMayachain = true;
294
+ readonly _supportsOptimism = true;
295
+ readonly _supportsPolygon = true;
296
+ readonly _supportsSolana = true;
297
+ readonly _supportsThorchain = true;
298
+
299
+ readonly _isGridPlus = true;
300
+
301
+ private activeWalletId?: string;
302
+
303
+ transport: GridPlusTransport;
304
+ client?: Client;
305
+
306
+ constructor(transport: GridPlusTransport) {
307
+ super();
308
+ this.transport = transport;
309
+ }
310
+
311
+ public setActiveWalletId(walletId: string): void {
312
+ this.activeWalletId = walletId;
313
+ }
314
+
315
+ async getFeatures(): Promise<Record<string, any>> {
316
+ return {
317
+ vendor: "GridPlus",
318
+ deviceId: this.transport.deviceId,
319
+ model: "Lattice1",
320
+ };
321
+ }
322
+
323
+ public async isLocked(): Promise<boolean> {
324
+ return !this.transport.isConnected();
325
+ }
326
+
327
+ public async clearSession(): Promise<void> {
328
+ if (!this.client) return;
329
+ await this.transport.disconnect();
330
+ this.client = undefined;
331
+ }
332
+
333
+ public async isInitialized(): Promise<boolean> {
334
+ return !!this.client;
335
+ }
336
+
337
+ public async initialize(): Promise<void> {
338
+ // Get the GridPlus client from transport after successful pairing
339
+ this.client = this.transport.getClient();
340
+
341
+ if (!this.client) {
342
+ throw new Error("GridPlus client not available - device may not be paired");
343
+ }
344
+
345
+ // Validate that the client has the expected methods
346
+ if (typeof this.client.getAddresses !== "function") {
347
+ throw new Error("GridPlus client missing required getAddresses method");
348
+ }
349
+ }
350
+
351
+ public async ping(msg: core.Ping): Promise<core.Pong> {
352
+ return { msg: msg.msg };
353
+ }
354
+
355
+ public async sendPin(): Promise<void> {}
356
+
357
+ public async sendPassphrase(): Promise<void> {}
358
+
359
+ public async sendCharacter(): Promise<void> {}
360
+
361
+ public async sendWord(): Promise<void> {}
362
+
363
+ public async cancel(): Promise<void> {
364
+ // GridPlus has no pending device interactions to cancel
365
+ // Wallet persists in keyring - do not disconnect
366
+ }
367
+
368
+ public async wipe(): Promise<void> {
369
+ throw new Error("GridPlus does not support wiping");
370
+ }
371
+
372
+ public async reset(): Promise<void> {
373
+ await this.clearSession();
374
+ await this.initialize();
375
+ }
376
+
377
+ public async recover(): Promise<void> {
378
+ throw new Error("GridPlus does not support recovery mode");
379
+ }
380
+
381
+ public async loadDevice(): Promise<void> {
382
+ throw new Error("GridPlus does not support device loading");
383
+ }
384
+
385
+ public describePath(): core.PathDescription {
386
+ return {
387
+ verbose: "GridPlus does not support path descriptions yet",
388
+ coin: "Unknown",
389
+ isKnown: false,
390
+ };
391
+ }
392
+
393
+ public async getModel(): Promise<string> {
394
+ return "Lattice1";
395
+ }
396
+
397
+ public async getLabel(): Promise<string> {
398
+ return "GridPlus Lattice1";
399
+ }
400
+
401
+ public async getFirmwareVersion(): Promise<string> {
402
+ return "1.0.0";
403
+ }
404
+
405
+ public async getDeviceID(): Promise<string> {
406
+ return this.activeWalletId || (await this.transport.getDeviceID());
407
+ }
408
+
409
+ public async getPublicKeys(msg: Array<core.GetPublicKey>): Promise<Array<core.PublicKey | null>> {
410
+ if (!this.client) {
411
+ throw new Error("Device not connected");
412
+ }
413
+
414
+ const publicKeys: Array<core.PublicKey | null> = [];
415
+
416
+ for (const getPublicKey of msg) {
417
+ const { addressNList, curve, coin, scriptType } = getPublicKey;
418
+
419
+ try {
420
+ let flag: number;
421
+
422
+ // Determine the appropriate flag based on curve type
423
+ if (curve === "secp256k1") {
424
+ // For UTXO chains (Bitcoin, Dogecoin), we need the xpub
425
+ flag = Constants.GET_ADDR_FLAGS.SECP256K1_XPUB;
426
+ } else if (curve === "ed25519") {
427
+ // For Solana/ed25519 chains, we need the public key
428
+ flag = Constants.GET_ADDR_FLAGS.ED25519_PUB;
429
+ } else {
430
+ throw new Error(`Unsupported curve: ${curve}`);
431
+ }
432
+
433
+ const addresses = await this.client!.getAddresses({
434
+ startPath: addressNList,
435
+ n: 1,
436
+ flag,
437
+ });
438
+
439
+ if (!addresses.length) {
440
+ throw new Error("No public key returned from device");
441
+ }
442
+
443
+ // addresses[0] contains either xpub string (for SECP256K1_XPUB) or pubkey hex (for ED25519_PUB)
444
+ let xpub = typeof addresses[0] === "string" ? addresses[0] : Buffer.from(addresses[0]).toString("hex");
445
+
446
+ // Convert xpub format for Dogecoin/Litecoin (GridPlus returns Bitcoin xpub format)
447
+ if (coin && curve === "secp256k1") {
448
+ const accountType = scriptTypeToAccountType(scriptType);
449
+ xpub = convertXpubVersion(xpub, accountType, coin);
450
+ }
451
+
452
+ publicKeys.push({ xpub });
453
+ } catch (error) {
454
+ publicKeys.push(null);
455
+ }
456
+ }
457
+
458
+ return publicKeys;
459
+ }
460
+
461
+ public getSessionId(): string | undefined {
462
+ return this.transport.getSessionId();
463
+ }
464
+
465
+ public async disconnect(): Promise<void> {
466
+ await this.clearSession();
467
+ }
468
+
469
+ public async btcGetAddress(msg: core.BTCGetAddress): Promise<string | null> {
470
+ if (!this.client) throw new Error("Device not connected");
471
+ return btc.btcGetAddress(this.client!, msg);
472
+ }
473
+
474
+ public async btcSignTx(msg: core.BTCSignTx): Promise<core.BTCSignedTx | null> {
475
+ if (!this.client) throw new Error("Device not connected");
476
+ return btc.btcSignTx(this.client, msg);
477
+ }
478
+
479
+ public async btcSignMessage(): Promise<core.BTCSignedMessage | null> {
480
+ throw new Error("GridPlus BTC message signing not yet implemented");
481
+ }
482
+
483
+ public async btcVerifyMessage(): Promise<boolean | null> {
484
+ throw new Error("GridPlus BTC message verification not yet implemented");
485
+ }
486
+
487
+ public async ethGetAddress(msg: core.ETHGetAddress): Promise<core.Address | null> {
488
+ if (!this.client) throw new Error("Device not connected");
489
+ return eth.ethGetAddress(this.client, msg);
490
+ }
491
+
492
+ public async ethSignTx(msg: core.ETHSignTx): Promise<core.ETHSignedTx> {
493
+ if (!this.client) throw new Error("Device not connected");
494
+ return eth.ethSignTx(this.client, msg);
495
+ }
496
+
497
+ public async ethSignTypedData(msg: core.ETHSignTypedData): Promise<core.ETHSignedTypedData> {
498
+ if (!this.client) throw new Error("Device not connected");
499
+ return eth.ethSignTypedData(this.client, msg);
500
+ }
501
+
502
+ public async ethSignMessage(msg: core.ETHSignMessage): Promise<core.ETHSignedMessage> {
503
+ if (!this.client) throw new Error("Device not connected");
504
+ return eth.ethSignMessage(this.client, msg);
505
+ }
506
+
507
+ public async ethVerifyMessage(): Promise<boolean> {
508
+ throw new Error("GridPlus ETH message verification not implemented yet");
509
+ }
510
+
511
+ public async solanaGetAddress(msg: core.SolanaGetAddress): Promise<string | null> {
512
+ if (!this.client) throw new Error("Device not connected");
513
+ return solana.solanaGetAddress(this.client, msg);
514
+ }
515
+
516
+ public async solanaSignTx(msg: core.SolanaSignTx): Promise<core.SolanaSignedTx | null> {
517
+ if (!this.client) throw new Error("Device not connected");
518
+ return solana.solanaSignTx(this.client, msg);
519
+ }
520
+
521
+ public async cosmosGetAddress(msg: core.CosmosGetAddress): Promise<string | null> {
522
+ if (!this.client) throw new Error("Device not connected");
523
+ return cosmos.cosmosGetAddress(this.client, msg);
524
+ }
525
+
526
+ public async cosmosSignTx(msg: core.CosmosSignTx): Promise<core.CosmosSignedTx | null> {
527
+ if (!this.client) throw new Error("Device not connected");
528
+ return cosmos.cosmosSignTx(this.client, msg);
529
+ }
530
+
531
+ public async thorchainGetAddress(msg: core.ThorchainGetAddress): Promise<string | null> {
532
+ if (!this.client) throw new Error("Device not connected");
533
+ return thorchain.thorchainGetAddress(this.client, msg);
534
+ }
535
+
536
+ public async thorchainSignTx(msg: core.ThorchainSignTx): Promise<core.ThorchainSignedTx | null> {
537
+ if (!this.client) throw new Error("Device not connected");
538
+ return thorchain.thorchainSignTx(this.client, msg);
539
+ }
540
+
541
+ public async mayachainGetAddress(msg: core.MayachainGetAddress): Promise<string | null> {
542
+ if (!this.client) throw new Error("Device not connected");
543
+ return mayachain.mayachainGetAddress(this.client, msg);
544
+ }
545
+
546
+ public async mayachainSignTx(msg: core.MayachainSignTx): Promise<core.MayachainSignedTx | null> {
547
+ if (!this.client) throw new Error("Device not connected");
548
+ return mayachain.mayachainSignTx(this.client, msg);
549
+ }
550
+ }
package/src/index.ts ADDED
@@ -0,0 +1,3 @@
1
+ export * from "./adapter";
2
+ export { GridPlusHDWallet, GridPlusWalletInfo, isGridPlus } from "./gridplus";
3
+ export * from "./transport";
@@ -0,0 +1,150 @@
1
+ import { pointCompress } from "@bitcoinerlab/secp256k1";
2
+ import type { StdTx } from "@cosmjs/amino";
3
+ import type { DirectSignResponse, OfflineDirectSigner } from "@cosmjs/proto-signing";
4
+ import * as core from "@shapeshiftoss/hdwallet-core";
5
+ import type { SignDoc } from "cosmjs-types/cosmos/tx/v1beta1/tx";
6
+ import { Client, Constants } from "gridplus-sdk";
7
+ import PLazy from "p-lazy";
8
+
9
+ import { createCosmosAddress } from "./cosmos";
10
+
11
+ const cosmJsProtoSigning = PLazy.from(() => import("@cosmjs/proto-signing"));
12
+
13
+ export async function mayachainGetAddress(client: Client, msg: core.MayachainGetAddress): Promise<string | null> {
14
+ // Get secp256k1 pubkey using GridPlus client instance
15
+ // Use FULL path - MAYAChain uses standard BIP44: m/44'/931'/0'/0/0 (5 levels)
16
+ const addresses = await client.getAddresses({
17
+ startPath: msg.addressNList,
18
+ n: 1,
19
+ flag: Constants.GET_ADDR_FLAGS.SECP256K1_PUB,
20
+ });
21
+
22
+ if (!addresses.length) {
23
+ throw new Error("No address returned from device");
24
+ }
25
+
26
+ // GridPlus SDK returns uncompressed 65-byte pubkeys, but MAYAChain needs compressed 33-byte pubkeys
27
+ const pubkeyBuffer = Buffer.isBuffer(addresses[0]) ? addresses[0] : Buffer.from(addresses[0], "hex");
28
+ const compressedPubkey = pointCompress(pubkeyBuffer, true);
29
+ const compressedHex = Buffer.from(compressedPubkey).toString("hex");
30
+ const mayaAddress = createCosmosAddress(compressedHex, "maya");
31
+
32
+ return mayaAddress;
33
+ }
34
+
35
+ export async function mayachainSignTx(
36
+ client: Client,
37
+ msg: core.MayachainSignTx
38
+ ): Promise<core.MayachainSignedTx | null> {
39
+ // Get the address for this path
40
+ const address = await mayachainGetAddress(client, { addressNList: msg.addressNList });
41
+ if (!address) throw new Error("Failed to get MAYAchain address");
42
+
43
+ // Get the public key using client instance
44
+ const pubkeys = await client.getAddresses({
45
+ startPath: msg.addressNList,
46
+ n: 1,
47
+ flag: Constants.GET_ADDR_FLAGS.SECP256K1_PUB,
48
+ });
49
+
50
+ if (!pubkeys.length) {
51
+ throw new Error("No public key returned from device");
52
+ }
53
+
54
+ // GridPlus SDK returns uncompressed 65-byte pubkeys, but MAYAChain needs compressed 33-byte pubkeys
55
+ const pubkeyBuffer = Buffer.isBuffer(pubkeys[0]) ? pubkeys[0] : Buffer.from(pubkeys[0], "hex");
56
+ const compressedPubkey = pointCompress(pubkeyBuffer, true);
57
+ const pubkey = Buffer.from(compressedPubkey);
58
+
59
+ // Create a signer adapter for GridPlus with Direct signing (Proto)
60
+ const signer: OfflineDirectSigner = {
61
+ getAccounts: async () => [
62
+ {
63
+ address,
64
+ pubkey,
65
+ algo: "secp256k1" as const,
66
+ },
67
+ ],
68
+ signDirect: async (signerAddress: string, signDoc: SignDoc): Promise<DirectSignResponse> => {
69
+ if (signerAddress !== address) {
70
+ throw new Error("Signer address mismatch");
71
+ }
72
+
73
+ // Use CosmJS to create the sign bytes from the SignDoc
74
+ const signBytes = (await cosmJsProtoSigning).makeSignBytes(signDoc);
75
+
76
+ // Sign using GridPlus SDK general signing
77
+ // Pass unhashed signBytes and let device hash with SHA256
78
+ const signData = {
79
+ data: {
80
+ payload: signBytes,
81
+ curveType: Constants.SIGNING.CURVES.SECP256K1,
82
+ hashType: Constants.SIGNING.HASHES.SHA256,
83
+ encodingType: Constants.SIGNING.ENCODINGS.NONE,
84
+ signerPath: msg.addressNList,
85
+ },
86
+ };
87
+
88
+ const signedResult = await client.sign(signData);
89
+
90
+ if (!signedResult?.sig) {
91
+ throw new Error("No signature returned from device");
92
+ }
93
+
94
+ const { r, s } = signedResult.sig;
95
+ const rBuf = Buffer.from(r);
96
+ const sBuf = Buffer.from(s);
97
+
98
+ // Ensure 32-byte values
99
+ const rPadded = rBuf.length < 32 ? Buffer.concat([Buffer.alloc(32 - rBuf.length), rBuf]) : rBuf;
100
+ const sPadded = sBuf.length < 32 ? Buffer.concat([Buffer.alloc(32 - sBuf.length), sBuf]) : sBuf;
101
+
102
+ const signature = Buffer.concat([rPadded, sPadded]);
103
+
104
+ return {
105
+ signed: signDoc,
106
+ signature: {
107
+ pub_key: {
108
+ type: "tendermint/PubKeySecp256k1",
109
+ value: pubkey.toString("base64"),
110
+ },
111
+ signature: signature.toString("base64"),
112
+ },
113
+ };
114
+ },
115
+ };
116
+
117
+ // Build and sign transaction using proto-tx-builder
118
+ const signedTx = await (
119
+ await import("@shapeshiftoss/proto-tx-builder")
120
+ ).sign(
121
+ address,
122
+ msg.tx as StdTx,
123
+ signer,
124
+ {
125
+ sequence: Number(msg.sequence),
126
+ accountNumber: Number(msg.account_number),
127
+ chainId: msg.chain_id,
128
+ },
129
+ "maya"
130
+ );
131
+
132
+ return signedTx as core.MayachainSignedTx;
133
+ }
134
+
135
+ export const mayachainGetAccountPaths = (msg: core.MayachainGetAccountPaths): Array<core.MayachainAccountPath> => {
136
+ const slip44 = core.slip44ByCoin("Mayachain");
137
+ return [
138
+ {
139
+ addressNList: [0x80000000 + 44, 0x80000000 + slip44, 0x80000000 + msg.accountIdx, 0, 0],
140
+ },
141
+ ];
142
+ };
143
+
144
+ export const mayachainNextAccountPath = (msg: core.MayachainAccountPath): core.MayachainAccountPath | undefined => {
145
+ const newAddressNList = [...msg.addressNList];
146
+ newAddressNList[2] += 1;
147
+ return {
148
+ addressNList: newAddressNList,
149
+ };
150
+ };