@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.
- package/CHANGELOG.md +11 -0
- package/LICENSE.md +21 -0
- package/dist/adapter.d.ts +19 -0
- package/dist/adapter.d.ts.map +1 -0
- package/dist/adapter.js +129 -0
- package/dist/adapter.js.map +1 -0
- package/dist/bitcoin.d.ts +7 -0
- package/dist/bitcoin.d.ts.map +1 -0
- package/dist/bitcoin.js +619 -0
- package/dist/bitcoin.js.map +1 -0
- package/dist/constants.d.ts +18 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +51 -0
- package/dist/constants.js.map +1 -0
- package/dist/cosmos.d.ts +7 -0
- package/dist/cosmos.d.ts.map +1 -0
- package/dist/cosmos.js +156 -0
- package/dist/cosmos.js.map +1 -0
- package/dist/ethereum.d.ts +7 -0
- package/dist/ethereum.d.ts.map +1 -0
- package/dist/ethereum.js +294 -0
- package/dist/ethereum.js.map +1 -0
- package/dist/gridplus.d.ts +112 -0
- package/dist/gridplus.d.ts.map +1 -0
- package/dist/gridplus.js +574 -0
- package/dist/gridplus.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +24 -0
- package/dist/index.js.map +1 -0
- package/dist/mayachain.d.ts +7 -0
- package/dist/mayachain.d.ts.map +1 -0
- package/dist/mayachain.js +163 -0
- package/dist/mayachain.js.map +1 -0
- package/dist/solana.d.ts +5 -0
- package/dist/solana.d.ts.map +1 -0
- package/dist/solana.js +120 -0
- package/dist/solana.js.map +1 -0
- package/dist/thorchain.d.ts +5 -0
- package/dist/thorchain.d.ts.map +1 -0
- package/dist/thorchain.js +143 -0
- package/dist/thorchain.js.map +1 -0
- package/dist/transport.d.ts +28 -0
- package/dist/transport.d.ts.map +1 -0
- package/dist/transport.js +148 -0
- package/dist/transport.js.map +1 -0
- package/dist/utils.d.ts +17 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +117 -0
- package/dist/utils.js.map +1 -0
- package/package.json +38 -0
- package/package.json.bak +38 -0
- package/src/adapter.ts +109 -0
- package/src/bitcoin.ts +711 -0
- package/src/constants.ts +52 -0
- package/src/cosmos.ts +132 -0
- package/src/ethereum.ts +305 -0
- package/src/gridplus.ts +550 -0
- package/src/index.ts +3 -0
- package/src/mayachain.ts +150 -0
- package/src/solana.ts +97 -0
- package/src/thorchain.ts +125 -0
- package/src/transport.ts +131 -0
- package/src/utils.ts +101 -0
- package/tsconfig.json +10 -0
- package/tsconfig.tsbuildinfo +1 -0
package/src/gridplus.ts
ADDED
|
@@ -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
package/src/mayachain.ts
ADDED
|
@@ -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
|
+
};
|