@sidhujag/sysweb3-keyring 1.0.544 → 1.0.547
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/coverage/clover.xml +2875 -0
- package/coverage/coverage-final.json +29468 -0
- package/coverage/lcov-report/base.css +354 -0
- package/coverage/lcov-report/block-navigation.js +85 -0
- package/coverage/lcov-report/favicon.png +0 -0
- package/coverage/lcov-report/index.html +320 -0
- package/coverage/lcov-report/prettify.css +101 -0
- package/coverage/lcov-report/prettify.js +1008 -0
- package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/lcov-report/sorter.js +191 -0
- package/coverage/lcov-report/src/index.html +276 -0
- package/coverage/lcov-report/src/index.ts.html +114 -0
- package/coverage/lcov-report/src/initial-state.ts.html +558 -0
- package/coverage/lcov-report/src/keyring-manager.ts.html +6279 -0
- package/coverage/lcov-report/src/ledger/bitcoin_client/index.html +178 -0
- package/coverage/lcov-report/src/ledger/bitcoin_client/index.ts.html +144 -0
- package/coverage/lcov-report/src/ledger/bitcoin_client/lib/appClient.ts.html +1560 -0
- package/coverage/lcov-report/src/ledger/bitcoin_client/lib/bip32.ts.html +276 -0
- package/coverage/lcov-report/src/ledger/bitcoin_client/lib/buffertools.ts.html +495 -0
- package/coverage/lcov-report/src/ledger/bitcoin_client/lib/clientCommands.ts.html +1138 -0
- package/coverage/lcov-report/src/ledger/bitcoin_client/lib/index.html +363 -0
- package/coverage/lcov-report/src/ledger/bitcoin_client/lib/merkelizedPsbt.ts.html +289 -0
- package/coverage/lcov-report/src/ledger/bitcoin_client/lib/merkle.ts.html +486 -0
- package/coverage/lcov-report/src/ledger/bitcoin_client/lib/merkleMap.ts.html +240 -0
- package/coverage/lcov-report/src/ledger/bitcoin_client/lib/policy.ts.html +342 -0
- package/coverage/lcov-report/src/ledger/bitcoin_client/lib/psbtv2.ts.html +2388 -0
- package/coverage/lcov-report/src/ledger/bitcoin_client/lib/varint.ts.html +453 -0
- package/coverage/lcov-report/src/ledger/consts.ts.html +177 -0
- package/coverage/lcov-report/src/ledger/index.html +216 -0
- package/coverage/lcov-report/src/ledger/index.ts.html +1371 -0
- package/coverage/lcov-report/src/ledger/utils.ts.html +102 -0
- package/coverage/lcov-report/src/signers.ts.html +591 -0
- package/coverage/lcov-report/src/storage.ts.html +198 -0
- package/coverage/lcov-report/src/transactions/ethereum.ts.html +5826 -0
- package/coverage/lcov-report/src/transactions/index.html +216 -0
- package/coverage/lcov-report/src/transactions/index.ts.html +93 -0
- package/coverage/lcov-report/src/transactions/syscoin.ts.html +1521 -0
- package/coverage/lcov-report/src/trezor/index.html +176 -0
- package/coverage/lcov-report/src/trezor/index.ts.html +2655 -0
- package/coverage/lcov-report/src/types.ts.html +1443 -0
- package/coverage/lcov-report/src/utils/derivation-paths.ts.html +486 -0
- package/coverage/lcov-report/src/utils/index.html +196 -0
- package/coverage/lcov-report/src/utils/psbt.ts.html +159 -0
- package/coverage/lcov-report/test/helpers/constants.ts.html +627 -0
- package/coverage/lcov-report/test/helpers/index.html +176 -0
- package/coverage/lcov.info +4832 -0
- package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/appClient.js +1 -124
- package/dist/cjs/ledger/bitcoin_client/lib/appClient.js.map +1 -0
- package/{cjs → dist/cjs}/transactions/ethereum.js +24 -11
- package/dist/cjs/transactions/ethereum.js.map +1 -0
- package/dist/package.json +50 -0
- package/{types → dist/types}/ledger/bitcoin_client/lib/appClient.d.ts +0 -6
- package/examples/basic-usage.js +140 -0
- package/jest.config.js +32 -0
- package/package.json +31 -13
- package/readme.md +201 -0
- package/src/declare.d.ts +7 -0
- package/src/errorUtils.ts +83 -0
- package/src/hardware-wallet-manager.ts +655 -0
- package/src/index.ts +12 -0
- package/src/initial-state.ts +108 -0
- package/src/keyring-manager.ts +2698 -0
- package/src/ledger/bitcoin_client/index.ts +19 -0
- package/src/ledger/bitcoin_client/lib/appClient.ts +405 -0
- package/src/ledger/bitcoin_client/lib/bip32.ts +61 -0
- package/src/ledger/bitcoin_client/lib/buffertools.ts +134 -0
- package/src/ledger/bitcoin_client/lib/clientCommands.ts +356 -0
- package/src/ledger/bitcoin_client/lib/constants.ts +12 -0
- package/src/ledger/bitcoin_client/lib/merkelizedPsbt.ts +65 -0
- package/src/ledger/bitcoin_client/lib/merkle.ts +136 -0
- package/src/ledger/bitcoin_client/lib/merkleMap.ts +49 -0
- package/src/ledger/bitcoin_client/lib/policy.ts +91 -0
- package/src/ledger/bitcoin_client/lib/psbtv2.ts +768 -0
- package/src/ledger/bitcoin_client/lib/varint.ts +120 -0
- package/src/ledger/consts.ts +3 -0
- package/src/ledger/index.ts +685 -0
- package/src/ledger/types.ts +74 -0
- package/src/network-utils.ts +99 -0
- package/src/providers.ts +345 -0
- package/src/signers.ts +158 -0
- package/src/storage.ts +63 -0
- package/src/transactions/__tests__/integration.test.ts +303 -0
- package/src/transactions/__tests__/syscoin.test.ts +409 -0
- package/src/transactions/ethereum.ts +2503 -0
- package/src/transactions/index.ts +2 -0
- package/src/transactions/syscoin.ts +542 -0
- package/src/trezor/index.ts +1050 -0
- package/src/types.ts +366 -0
- package/src/utils/derivation-paths.ts +133 -0
- package/src/utils/psbt.ts +24 -0
- package/src/utils.ts +191 -0
- package/test/README.md +158 -0
- package/test/__mocks__/ledger-mock.js +20 -0
- package/test/__mocks__/trezor-mock.js +75 -0
- package/test/cleanup-summary.md +167 -0
- package/test/helpers/README.md +78 -0
- package/test/helpers/constants.ts +79 -0
- package/test/helpers/setup.ts +714 -0
- package/test/integration/import-validation.spec.ts +588 -0
- package/test/unit/hardware/ledger.spec.ts +869 -0
- package/test/unit/hardware/trezor.spec.ts +828 -0
- package/test/unit/keyring-manager/account-management.spec.ts +970 -0
- package/test/unit/keyring-manager/import-watchonly.spec.ts +181 -0
- package/test/unit/keyring-manager/import-wif.spec.ts +126 -0
- package/test/unit/keyring-manager/initialization.spec.ts +782 -0
- package/test/unit/keyring-manager/key-derivation.spec.ts +996 -0
- package/test/unit/keyring-manager/security.spec.ts +505 -0
- package/test/unit/keyring-manager/state-management.spec.ts +375 -0
- package/test/unit/network/network-management.spec.ts +372 -0
- package/test/unit/transactions/ethereum-transactions.spec.ts +382 -0
- package/test/unit/transactions/syscoin-transactions.spec.ts +615 -0
- package/tsconfig.json +14 -0
- package/cjs/ledger/bitcoin_client/lib/appClient.js.map +0 -1
- package/cjs/transactions/ethereum.js.map +0 -1
- /package/{README.md → dist/README.md} +0 -0
- /package/{cjs → dist/cjs}/errorUtils.js +0 -0
- /package/{cjs → dist/cjs}/errorUtils.js.map +0 -0
- /package/{cjs → dist/cjs}/hardware-wallet-manager.js +0 -0
- /package/{cjs → dist/cjs}/hardware-wallet-manager.js.map +0 -0
- /package/{cjs → dist/cjs}/index.js +0 -0
- /package/{cjs → dist/cjs}/index.js.map +0 -0
- /package/{cjs → dist/cjs}/initial-state.js +0 -0
- /package/{cjs → dist/cjs}/initial-state.js.map +0 -0
- /package/{cjs → dist/cjs}/keyring-manager.js +0 -0
- /package/{cjs → dist/cjs}/keyring-manager.js.map +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/index.js +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/index.js.map +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/bip32.js +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/bip32.js.map +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/buffertools.js +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/buffertools.js.map +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/clientCommands.js +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/clientCommands.js.map +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/constants.js +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/constants.js.map +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/merkelizedPsbt.js +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/merkelizedPsbt.js.map +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/merkle.js +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/merkle.js.map +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/merkleMap.js +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/merkleMap.js.map +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/policy.js +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/policy.js.map +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/psbtv2.js +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/psbtv2.js.map +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/varint.js +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/varint.js.map +0 -0
- /package/{cjs → dist/cjs}/ledger/consts.js +0 -0
- /package/{cjs → dist/cjs}/ledger/consts.js.map +0 -0
- /package/{cjs → dist/cjs}/ledger/index.js +0 -0
- /package/{cjs → dist/cjs}/ledger/index.js.map +0 -0
- /package/{cjs → dist/cjs}/ledger/types.js +0 -0
- /package/{cjs → dist/cjs}/ledger/types.js.map +0 -0
- /package/{cjs → dist/cjs}/network-utils.js +0 -0
- /package/{cjs → dist/cjs}/network-utils.js.map +0 -0
- /package/{cjs → dist/cjs}/providers.js +0 -0
- /package/{cjs → dist/cjs}/providers.js.map +0 -0
- /package/{cjs → dist/cjs}/signers.js +0 -0
- /package/{cjs → dist/cjs}/signers.js.map +0 -0
- /package/{cjs → dist/cjs}/storage.js +0 -0
- /package/{cjs → dist/cjs}/storage.js.map +0 -0
- /package/{cjs → dist/cjs}/transactions/__tests__/integration.test.js +0 -0
- /package/{cjs → dist/cjs}/transactions/__tests__/integration.test.js.map +0 -0
- /package/{cjs → dist/cjs}/transactions/__tests__/syscoin.test.js +0 -0
- /package/{cjs → dist/cjs}/transactions/__tests__/syscoin.test.js.map +0 -0
- /package/{cjs → dist/cjs}/transactions/index.js +0 -0
- /package/{cjs → dist/cjs}/transactions/index.js.map +0 -0
- /package/{cjs → dist/cjs}/transactions/syscoin.js +0 -0
- /package/{cjs → dist/cjs}/transactions/syscoin.js.map +0 -0
- /package/{cjs → dist/cjs}/trezor/index.js +0 -0
- /package/{cjs → dist/cjs}/trezor/index.js.map +0 -0
- /package/{cjs → dist/cjs}/types.js +0 -0
- /package/{cjs → dist/cjs}/types.js.map +0 -0
- /package/{cjs → dist/cjs}/utils/derivation-paths.js +0 -0
- /package/{cjs → dist/cjs}/utils/derivation-paths.js.map +0 -0
- /package/{cjs → dist/cjs}/utils/psbt.js +0 -0
- /package/{cjs → dist/cjs}/utils/psbt.js.map +0 -0
- /package/{cjs → dist/cjs}/utils.js +0 -0
- /package/{cjs → dist/cjs}/utils.js.map +0 -0
- /package/{types → dist/types}/errorUtils.d.ts +0 -0
- /package/{types → dist/types}/hardware-wallet-manager.d.ts +0 -0
- /package/{types → dist/types}/index.d.ts +0 -0
- /package/{types → dist/types}/initial-state.d.ts +0 -0
- /package/{types → dist/types}/keyring-manager.d.ts +0 -0
- /package/{types → dist/types}/ledger/bitcoin_client/index.d.ts +0 -0
- /package/{types → dist/types}/ledger/bitcoin_client/lib/bip32.d.ts +0 -0
- /package/{types → dist/types}/ledger/bitcoin_client/lib/buffertools.d.ts +0 -0
- /package/{types → dist/types}/ledger/bitcoin_client/lib/clientCommands.d.ts +0 -0
- /package/{types → dist/types}/ledger/bitcoin_client/lib/constants.d.ts +0 -0
- /package/{types → dist/types}/ledger/bitcoin_client/lib/merkelizedPsbt.d.ts +0 -0
- /package/{types → dist/types}/ledger/bitcoin_client/lib/merkle.d.ts +0 -0
- /package/{types → dist/types}/ledger/bitcoin_client/lib/merkleMap.d.ts +0 -0
- /package/{types → dist/types}/ledger/bitcoin_client/lib/policy.d.ts +0 -0
- /package/{types → dist/types}/ledger/bitcoin_client/lib/psbtv2.d.ts +0 -0
- /package/{types → dist/types}/ledger/bitcoin_client/lib/varint.d.ts +0 -0
- /package/{types → dist/types}/ledger/consts.d.ts +0 -0
- /package/{types → dist/types}/ledger/index.d.ts +0 -0
- /package/{types → dist/types}/ledger/types.d.ts +0 -0
- /package/{types → dist/types}/network-utils.d.ts +0 -0
- /package/{types → dist/types}/providers.d.ts +0 -0
- /package/{types → dist/types}/signers.d.ts +0 -0
- /package/{types → dist/types}/storage.d.ts +0 -0
- /package/{types → dist/types}/transactions/__tests__/integration.test.d.ts +0 -0
- /package/{types → dist/types}/transactions/__tests__/syscoin.test.d.ts +0 -0
- /package/{types → dist/types}/transactions/ethereum.d.ts +0 -0
- /package/{types → dist/types}/transactions/index.d.ts +0 -0
- /package/{types → dist/types}/transactions/syscoin.d.ts +0 -0
- /package/{types → dist/types}/trezor/index.d.ts +0 -0
- /package/{types → dist/types}/types.d.ts +0 -0
- /package/{types → dist/types}/utils/derivation-paths.d.ts +0 -0
- /package/{types → dist/types}/utils/psbt.d.ts +0 -0
- /package/{types → dist/types}/utils.d.ts +0 -0
|
@@ -0,0 +1,1050 @@
|
|
|
1
|
+
/* eslint-disable camelcase */
|
|
2
|
+
import {
|
|
3
|
+
TypedDataUtils,
|
|
4
|
+
TypedMessage,
|
|
5
|
+
SignTypedDataVersion,
|
|
6
|
+
TypedDataV1,
|
|
7
|
+
} from '@metamask/eth-sig-util';
|
|
8
|
+
import TrezorConnect, {
|
|
9
|
+
AccountInfo,
|
|
10
|
+
DEVICE_EVENT,
|
|
11
|
+
EthereumTransaction,
|
|
12
|
+
EthereumTransactionEIP1559,
|
|
13
|
+
// @ts-ignore
|
|
14
|
+
} from '@trezor/connect-webextension';
|
|
15
|
+
import { address } from '@trezor/utxo-lib';
|
|
16
|
+
import bitcoinops from 'bitcoin-ops';
|
|
17
|
+
import { Transaction, payments, script } from 'bitcoinjs-lib';
|
|
18
|
+
import { Buffer } from 'buffer';
|
|
19
|
+
import { stripHexPrefix } from 'ethereumjs-util';
|
|
20
|
+
|
|
21
|
+
import {
|
|
22
|
+
HardwareWalletManager,
|
|
23
|
+
HardwareWalletType,
|
|
24
|
+
} from '../hardware-wallet-manager';
|
|
25
|
+
import {
|
|
26
|
+
getAccountDerivationPath,
|
|
27
|
+
getAddressDerivationPath,
|
|
28
|
+
isEvmCoin,
|
|
29
|
+
} from '../utils/derivation-paths';
|
|
30
|
+
|
|
31
|
+
const { p2wsh } = payments;
|
|
32
|
+
const { decompile } = script;
|
|
33
|
+
const { fromBase58Check, fromBech32 } = address;
|
|
34
|
+
|
|
35
|
+
const initialHDPath = `m/44'/60'/0'/0/0`;
|
|
36
|
+
const DELAY_BETWEEN_POPUPS = 2000; // Increased from 1000ms to 2000ms for more reliable operation
|
|
37
|
+
|
|
38
|
+
export interface TrezorControllerState {
|
|
39
|
+
hdPath: string;
|
|
40
|
+
paths: Record<string, number>;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
interface MessageTypeProperty {
|
|
44
|
+
name: string;
|
|
45
|
+
type: string;
|
|
46
|
+
}
|
|
47
|
+
interface MessageTypes {
|
|
48
|
+
[additionalProperties: string]: MessageTypeProperty[];
|
|
49
|
+
EIP712Domain: MessageTypeProperty[];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
declare global {
|
|
53
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
54
|
+
interface Window {
|
|
55
|
+
TrezorConnect: any;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
export class TrezorKeyring {
|
|
59
|
+
public hdPath: string = initialHDPath;
|
|
60
|
+
public publicKey: Buffer;
|
|
61
|
+
public chainCode: Buffer;
|
|
62
|
+
public paths: Record<string, number> = {};
|
|
63
|
+
public model?: string;
|
|
64
|
+
private hardwareWalletManager: HardwareWalletManager;
|
|
65
|
+
private initialized = false;
|
|
66
|
+
|
|
67
|
+
constructor() {
|
|
68
|
+
this.publicKey = Buffer.from('', 'hex');
|
|
69
|
+
this.chainCode = Buffer.from('', 'hex');
|
|
70
|
+
this.hdPath = '';
|
|
71
|
+
this.paths = {};
|
|
72
|
+
this.hardwareWalletManager = new HardwareWalletManager();
|
|
73
|
+
|
|
74
|
+
// Set up event listeners
|
|
75
|
+
this.hardwareWalletManager.on('connected', ({ type }) => {
|
|
76
|
+
if (type === HardwareWalletType.TREZOR) {
|
|
77
|
+
console.log('Trezor connected');
|
|
78
|
+
this.initialized = true;
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
this.hardwareWalletManager.on('disconnected', ({ type }) => {
|
|
83
|
+
if (type === HardwareWalletType.TREZOR) {
|
|
84
|
+
console.log('Trezor disconnected');
|
|
85
|
+
this.initialized = false;
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
TrezorConnect.on(DEVICE_EVENT, (event: any) => {
|
|
90
|
+
if (event.payload.features) {
|
|
91
|
+
this.model = event.payload.features.model;
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Initialize Trezor script.
|
|
98
|
+
*/
|
|
99
|
+
public async init(): Promise<boolean> {
|
|
100
|
+
if (this.initialized) {
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Add a small delay to ensure Chrome extension context is ready
|
|
105
|
+
// This helps prevent "waiting for handshake" errors
|
|
106
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
107
|
+
|
|
108
|
+
const result = await this.hardwareWalletManager.initializeTrezor();
|
|
109
|
+
if (result) {
|
|
110
|
+
this.initialized = true;
|
|
111
|
+
}
|
|
112
|
+
return result;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Execute operation with automatic retry
|
|
117
|
+
*/
|
|
118
|
+
private async executeWithRetry<T>(
|
|
119
|
+
operation: () => Promise<T>,
|
|
120
|
+
operationName: string
|
|
121
|
+
): Promise<T> {
|
|
122
|
+
// Ensure initialization first
|
|
123
|
+
if (!this.initialized) {
|
|
124
|
+
const initResult = await this.init();
|
|
125
|
+
if (!initResult) {
|
|
126
|
+
throw new Error('Failed to initialize Trezor');
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// For Trezor operations, use reduced retry config to prevent popup spam
|
|
131
|
+
const trezorRetryConfig = {
|
|
132
|
+
maxRetries: 1, // Only retry once
|
|
133
|
+
baseDelay: 1000,
|
|
134
|
+
maxDelay: 5000,
|
|
135
|
+
backoffMultiplier: 2,
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
// Use hardware wallet manager's retry mechanism with custom config
|
|
139
|
+
return this.hardwareWalletManager
|
|
140
|
+
.retryOperation(operation, operationName, trezorRetryConfig)
|
|
141
|
+
.catch((error) => {
|
|
142
|
+
// Clean up Trezor state on failure
|
|
143
|
+
if (
|
|
144
|
+
error.message?.includes('Popup closed') ||
|
|
145
|
+
error.message?.includes('cancelled') ||
|
|
146
|
+
error.message?.includes('denied')
|
|
147
|
+
) {
|
|
148
|
+
this.initialized = false;
|
|
149
|
+
// Dispose Trezor connection to clean up
|
|
150
|
+
try {
|
|
151
|
+
TrezorConnect.dispose();
|
|
152
|
+
} catch (disposeError) {
|
|
153
|
+
console.log('Failed to dispose Trezor on error:', disposeError);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
throw error;
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* This return account info based in params provided.
|
|
162
|
+
*
|
|
163
|
+
* @param coin - network symbol. Example: eth, sys, btc
|
|
164
|
+
* @param slip44 - network slip44 number
|
|
165
|
+
* @param hdPath - path derivation. Example: m/84'/57'/0'
|
|
166
|
+
* @param index - index of account for path derivation
|
|
167
|
+
* @returns derivated account info or error
|
|
168
|
+
*/
|
|
169
|
+
|
|
170
|
+
public async getAccountInfo({
|
|
171
|
+
coin,
|
|
172
|
+
slip44,
|
|
173
|
+
hdPath,
|
|
174
|
+
index,
|
|
175
|
+
}: {
|
|
176
|
+
coin: string;
|
|
177
|
+
hdPath?: string;
|
|
178
|
+
index?: number;
|
|
179
|
+
slip44: number;
|
|
180
|
+
}): Promise<AccountInfo> {
|
|
181
|
+
return this.executeWithRetry(async () => {
|
|
182
|
+
// Use dynamic path generation instead of hardcoded switch
|
|
183
|
+
this.setHdPath(coin, index || 0, slip44);
|
|
184
|
+
|
|
185
|
+
if (hdPath) this.hdPath = hdPath;
|
|
186
|
+
|
|
187
|
+
// For EVM networks, getAccountInfo is not supported
|
|
188
|
+
// We need to use getAddress instead
|
|
189
|
+
if (slip44 === 60) {
|
|
190
|
+
const addressResponse = await TrezorConnect.ethereumGetAddress({
|
|
191
|
+
path: this.hdPath,
|
|
192
|
+
showOnTrezor: false,
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
if (!addressResponse.success) {
|
|
196
|
+
throw new Error(
|
|
197
|
+
addressResponse.payload.error || 'Failed to get EVM address'
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Return a compatible AccountInfo structure
|
|
202
|
+
return {
|
|
203
|
+
descriptor: addressResponse.payload.address,
|
|
204
|
+
balance: '0', // Balance is fetched separately for EVM
|
|
205
|
+
empty: true,
|
|
206
|
+
history: {
|
|
207
|
+
total: 0,
|
|
208
|
+
unconfirmed: 0,
|
|
209
|
+
},
|
|
210
|
+
} as AccountInfo;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// For UTXO networks, use the standard getAccountInfo
|
|
214
|
+
const response = await TrezorConnect.getAccountInfo({
|
|
215
|
+
coin,
|
|
216
|
+
path: this.hdPath,
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
if (response.success) {
|
|
220
|
+
return response.payload;
|
|
221
|
+
}
|
|
222
|
+
throw new Error(response.payload.error);
|
|
223
|
+
}, 'getAccountInfo');
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Gets the model, if known.
|
|
228
|
+
* This may be `undefined` if the model hasn't been loaded yet.
|
|
229
|
+
*
|
|
230
|
+
* @returns
|
|
231
|
+
*/
|
|
232
|
+
public getModel(): string | undefined {
|
|
233
|
+
return this.model;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* This removes the Trezor Connect iframe from the DOM
|
|
238
|
+
*
|
|
239
|
+
* @returns void
|
|
240
|
+
*/
|
|
241
|
+
|
|
242
|
+
public dispose() {
|
|
243
|
+
try {
|
|
244
|
+
TrezorConnect.dispose();
|
|
245
|
+
this.initialized = false;
|
|
246
|
+
// Clear any cached data
|
|
247
|
+
this.publicKey = Buffer.from('', 'hex');
|
|
248
|
+
this.chainCode = Buffer.from('', 'hex');
|
|
249
|
+
this.hdPath = '';
|
|
250
|
+
this.paths = {};
|
|
251
|
+
} catch (error) {
|
|
252
|
+
console.log('Error disposing Trezor:', error);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* This verify if message is valid or not.
|
|
258
|
+
*
|
|
259
|
+
* @param coin - network symbol. Example: eth, sys, btc
|
|
260
|
+
* @param address - account address that signed message
|
|
261
|
+
* @param message - message to be verified. Example: 'Test message'
|
|
262
|
+
* @param signature - signature received in sign method. Example: I6BrpivjCwZmScZ6BMAHWGQPo+JjX2kzKXU5LcGVfEgvFb2VfJuKo3g6eSQcykQZiILoWNUDn5rDHkwJg3EcvuY=
|
|
263
|
+
* @returns derivated account info or error
|
|
264
|
+
*/
|
|
265
|
+
|
|
266
|
+
public async verifyMessage({
|
|
267
|
+
coin,
|
|
268
|
+
address,
|
|
269
|
+
message,
|
|
270
|
+
signature,
|
|
271
|
+
}: {
|
|
272
|
+
address: string;
|
|
273
|
+
coin: string;
|
|
274
|
+
message: string;
|
|
275
|
+
signature: string;
|
|
276
|
+
}) {
|
|
277
|
+
return this.executeWithRetry(async () => {
|
|
278
|
+
let method = '';
|
|
279
|
+
switch (coin) {
|
|
280
|
+
case 'eth':
|
|
281
|
+
method = 'ethereumVerifyMessage';
|
|
282
|
+
break;
|
|
283
|
+
default:
|
|
284
|
+
method = 'verifyMessage';
|
|
285
|
+
}
|
|
286
|
+
// @ts-ignore
|
|
287
|
+
const { success, payload } = await TrezorConnect[method]({
|
|
288
|
+
coin,
|
|
289
|
+
address,
|
|
290
|
+
message,
|
|
291
|
+
signature,
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
if (success) {
|
|
295
|
+
return { success, payload };
|
|
296
|
+
}
|
|
297
|
+
throw new Error(payload.error);
|
|
298
|
+
}, 'verifyMessage');
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* This return account public key.
|
|
303
|
+
*
|
|
304
|
+
* @param coin - network symbol. Example: eth, sys, btc
|
|
305
|
+
* @param slip44 - network slip44 number
|
|
306
|
+
* @param hdPath - path derivation. Example: m/44'/57'/0'/0/0
|
|
307
|
+
* @returns publicKey and chainCode
|
|
308
|
+
*/
|
|
309
|
+
|
|
310
|
+
public async getPublicKey({
|
|
311
|
+
coin,
|
|
312
|
+
slip44,
|
|
313
|
+
hdPath,
|
|
314
|
+
index,
|
|
315
|
+
}: {
|
|
316
|
+
coin: string;
|
|
317
|
+
hdPath?: string;
|
|
318
|
+
index?: number;
|
|
319
|
+
slip44: number;
|
|
320
|
+
}) {
|
|
321
|
+
this.setHdPath(coin, index || 0, slip44);
|
|
322
|
+
|
|
323
|
+
if (hdPath) this.hdPath = hdPath;
|
|
324
|
+
|
|
325
|
+
await new Promise((resolve) => setTimeout(resolve, DELAY_BETWEEN_POPUPS));
|
|
326
|
+
|
|
327
|
+
try {
|
|
328
|
+
// For EVM networks, use ethereumGetPublicKey
|
|
329
|
+
if (slip44 === 60) {
|
|
330
|
+
const { success, payload } = await TrezorConnect.ethereumGetPublicKey({
|
|
331
|
+
path: this.hdPath,
|
|
332
|
+
showOnTrezor: false,
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
if (success) {
|
|
336
|
+
const { publicKey } = payload;
|
|
337
|
+
// For Ethereum, we don't get chainCode from ethereumGetPublicKey
|
|
338
|
+
this.publicKey = Buffer.from(publicKey, 'hex');
|
|
339
|
+
|
|
340
|
+
return {
|
|
341
|
+
publicKey: `0x${publicKey}`,
|
|
342
|
+
chainCode: '', // Ethereum doesn't use chainCode in the same way
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
return { success: false, payload };
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// For UTXO networks, use standard getPublicKey
|
|
350
|
+
const { success, payload } = await TrezorConnect.getPublicKey({
|
|
351
|
+
coin: coin,
|
|
352
|
+
path: this.hdPath,
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
if (success) {
|
|
356
|
+
const { publicKey, chainCode } = payload;
|
|
357
|
+
|
|
358
|
+
this.publicKey = Buffer.from(publicKey, 'hex');
|
|
359
|
+
this.chainCode = Buffer.from(chainCode, 'hex');
|
|
360
|
+
|
|
361
|
+
return {
|
|
362
|
+
publicKey: `0x${this.publicKey.toString('hex')}`,
|
|
363
|
+
chainCode: `0x${this.chainCode.toString('hex')}`,
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
return { success: false, payload };
|
|
368
|
+
} catch (error) {
|
|
369
|
+
return error;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
public range(n: number) {
|
|
374
|
+
return [...Array(n).keys()];
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* This sign UTXO tx.
|
|
379
|
+
*
|
|
380
|
+
* @param coin - network symbol. Example: eth, sys, btc
|
|
381
|
+
* @param inputs - utxo transaction inputs
|
|
382
|
+
* @param outputs - utxo transaction outputs
|
|
383
|
+
* @returns signature object
|
|
384
|
+
*/
|
|
385
|
+
|
|
386
|
+
public async signUtxoTransaction(utxoTransaction: any, psbt: any) {
|
|
387
|
+
return this.executeWithRetry(async () => {
|
|
388
|
+
const { payload, success } = await TrezorConnect.signTransaction(
|
|
389
|
+
utxoTransaction
|
|
390
|
+
);
|
|
391
|
+
|
|
392
|
+
if (success) {
|
|
393
|
+
const tx = Transaction.fromHex(payload.serializedTx);
|
|
394
|
+
for (const i of this.range(psbt.data.inputs.length)) {
|
|
395
|
+
if (tx.ins[i].witness == null) {
|
|
396
|
+
throw new Error(
|
|
397
|
+
'Please move your funds to a Segwit address: https://wiki.trezor.io/Account'
|
|
398
|
+
);
|
|
399
|
+
}
|
|
400
|
+
const partialSig = [
|
|
401
|
+
{
|
|
402
|
+
pubkey: tx.ins[i].witness[1],
|
|
403
|
+
signature: tx.ins[i].witness[0],
|
|
404
|
+
},
|
|
405
|
+
];
|
|
406
|
+
psbt.updateInput(i, { partialSig });
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
try {
|
|
410
|
+
const validator = (
|
|
411
|
+
pubkey: Buffer,
|
|
412
|
+
msghash: Buffer,
|
|
413
|
+
signature: Buffer
|
|
414
|
+
) => {
|
|
415
|
+
if (signature && signature.length === 64) {
|
|
416
|
+
try {
|
|
417
|
+
const xOnly =
|
|
418
|
+
pubkey.length === 32 ? pubkey : pubkey.slice(1, 33);
|
|
419
|
+
// @ts-ignore ecc injected via bitcoinjs initEccLib; runtime available in syscoinjs-lib
|
|
420
|
+
const eccLib =
|
|
421
|
+
(require('bitcoinjs-lib') as any).ecc ||
|
|
422
|
+
require('@bitcoinerlab/secp256k1');
|
|
423
|
+
return eccLib && typeof eccLib.verifySchnorr === 'function'
|
|
424
|
+
? eccLib.verifySchnorr(signature, msghash, xOnly)
|
|
425
|
+
: false;
|
|
426
|
+
} catch (e) {
|
|
427
|
+
return false;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
try {
|
|
431
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
432
|
+
const ECPairFactory =
|
|
433
|
+
require('ecpair').ECPairFactory ||
|
|
434
|
+
require('ecpair').default ||
|
|
435
|
+
require('ecpair');
|
|
436
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
437
|
+
const ecc = require('@bitcoinerlab/secp256k1');
|
|
438
|
+
const ECPair = ECPairFactory(ecc);
|
|
439
|
+
return ECPair.fromPublicKey(pubkey).verify(msghash, signature);
|
|
440
|
+
} catch (e) {
|
|
441
|
+
return false;
|
|
442
|
+
}
|
|
443
|
+
};
|
|
444
|
+
if (psbt.validateSignaturesOfAllInputs(validator)) {
|
|
445
|
+
psbt.finalizeAllInputs();
|
|
446
|
+
}
|
|
447
|
+
} catch (err) {
|
|
448
|
+
console.log(err);
|
|
449
|
+
}
|
|
450
|
+
return psbt;
|
|
451
|
+
} else {
|
|
452
|
+
throw new Error('Trezor sign failed: ' + payload.error);
|
|
453
|
+
}
|
|
454
|
+
}, 'signUtxoTransaction');
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
private setHdPath(coin: string, accountIndex: number, slip44: number) {
|
|
458
|
+
if (isEvmCoin(coin, slip44)) {
|
|
459
|
+
// For EVM, the "accountIndex" parameter is actually used as the address index
|
|
460
|
+
// EVM typically uses account 0, and different addresses are at different address indices
|
|
461
|
+
this.hdPath = getAddressDerivationPath(
|
|
462
|
+
coin,
|
|
463
|
+
slip44,
|
|
464
|
+
0, // account is always 0 for EVM
|
|
465
|
+
false, // not a change address
|
|
466
|
+
accountIndex // this is actually the address index for EVM
|
|
467
|
+
);
|
|
468
|
+
} else {
|
|
469
|
+
// For UTXO, use account-level derivation path
|
|
470
|
+
this.hdPath = getAccountDerivationPath(coin, slip44, accountIndex);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
public convertToAddressNFormat(path: string) {
|
|
475
|
+
const pathArray = path.replace(/'/g, '').split('/');
|
|
476
|
+
|
|
477
|
+
pathArray.shift();
|
|
478
|
+
|
|
479
|
+
const addressN: any[] = [];
|
|
480
|
+
|
|
481
|
+
for (const index in pathArray) {
|
|
482
|
+
if (Number(index) <= 2 && Number(index) >= 0) {
|
|
483
|
+
addressN[Number(index)] = Number(pathArray[index]) | 0x80000000;
|
|
484
|
+
} else {
|
|
485
|
+
addressN[Number(index)] = Number(pathArray[index]);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
return addressN;
|
|
490
|
+
}
|
|
491
|
+
public isScriptHash(address: string, networkInfo: any) {
|
|
492
|
+
if (!networkInfo) {
|
|
493
|
+
// If network info is not available, make a best guess based on address format
|
|
494
|
+
// This is a fallback for hardware wallet scenarios
|
|
495
|
+
if (this.isBech32(address)) {
|
|
496
|
+
const decoded = fromBech32(address);
|
|
497
|
+
return decoded.data.length === 32; // 32 bytes = P2WSH
|
|
498
|
+
}
|
|
499
|
+
// For non-bech32, we can't reliably determine without network info
|
|
500
|
+
// Default to false (P2PKH) as it's more common
|
|
501
|
+
return false;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
if (!this.isBech32(address)) {
|
|
505
|
+
const decoded = fromBase58Check(address);
|
|
506
|
+
if (decoded.version === networkInfo.pubKeyHash) {
|
|
507
|
+
return false;
|
|
508
|
+
}
|
|
509
|
+
if (decoded.version === networkInfo.scriptHash) {
|
|
510
|
+
return true;
|
|
511
|
+
}
|
|
512
|
+
} else {
|
|
513
|
+
const decoded = fromBech32(address);
|
|
514
|
+
if (decoded.data.length === 20) {
|
|
515
|
+
return false;
|
|
516
|
+
}
|
|
517
|
+
if (decoded.data.length === 32) {
|
|
518
|
+
return true;
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
throw new Error('isScriptHash: Unknown address type');
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
public isPaymentFactory(payment: any) {
|
|
525
|
+
return (script: any) => {
|
|
526
|
+
try {
|
|
527
|
+
payment({ output: script });
|
|
528
|
+
return true;
|
|
529
|
+
} catch (err) {
|
|
530
|
+
return false;
|
|
531
|
+
}
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
public isBech32(address: string) {
|
|
535
|
+
try {
|
|
536
|
+
fromBech32(address);
|
|
537
|
+
return true;
|
|
538
|
+
} catch (e) {
|
|
539
|
+
return false;
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
public isP2WSHScript(script: any) {
|
|
543
|
+
this.isPaymentFactory(p2wsh)(script);
|
|
544
|
+
|
|
545
|
+
return false;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
public convertToTrezorFormat({ psbt, coin, network }: any) {
|
|
549
|
+
const trezortx: any = {};
|
|
550
|
+
|
|
551
|
+
trezortx.coin = coin;
|
|
552
|
+
trezortx.version = psbt.version;
|
|
553
|
+
trezortx.inputs = [];
|
|
554
|
+
trezortx.outputs = [];
|
|
555
|
+
|
|
556
|
+
for (let i = 0; i < psbt.txInputs.length; i++) {
|
|
557
|
+
const input = psbt.txInputs[i];
|
|
558
|
+
const inputItem: any = {};
|
|
559
|
+
inputItem.prev_index = input.index;
|
|
560
|
+
inputItem.prev_hash = input.hash.reverse().toString('hex');
|
|
561
|
+
if (input.sequence) inputItem.sequence = input.sequence;
|
|
562
|
+
|
|
563
|
+
const dataInput = psbt.data.inputs[i];
|
|
564
|
+
|
|
565
|
+
// Resolve derivation path for this input
|
|
566
|
+
let resolvedPath: string | null = null;
|
|
567
|
+
const tapDer =
|
|
568
|
+
dataInput.tapBip32Derivation && dataInput.tapBip32Derivation.length > 0
|
|
569
|
+
? dataInput.tapBip32Derivation[0]
|
|
570
|
+
: null;
|
|
571
|
+
const b32Der =
|
|
572
|
+
dataInput.bip32Derivation && dataInput.bip32Derivation.length > 0
|
|
573
|
+
? dataInput.bip32Derivation[0]
|
|
574
|
+
: null;
|
|
575
|
+
|
|
576
|
+
if (tapDer && tapDer.path) {
|
|
577
|
+
resolvedPath = tapDer.path;
|
|
578
|
+
} else if (b32Der && b32Der.path) {
|
|
579
|
+
resolvedPath = b32Der.path;
|
|
580
|
+
} else if (dataInput.unknownKeyVals && dataInput.unknownKeyVals.length) {
|
|
581
|
+
for (const kv of dataInput.unknownKeyVals) {
|
|
582
|
+
if (Buffer.isBuffer(kv.key) && kv.key.toString() === 'path') {
|
|
583
|
+
resolvedPath = kv.value.toString();
|
|
584
|
+
break;
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
if (!resolvedPath) {
|
|
590
|
+
throw new Error(
|
|
591
|
+
'convertToTrezorFormat: Missing path (tapBip32Derivation/bip32Derivation/unknownKeyVals)'
|
|
592
|
+
);
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
// Map path to Trezor address_n
|
|
596
|
+
inputItem.address_n = this.convertToAddressNFormat(resolvedPath);
|
|
597
|
+
|
|
598
|
+
// Coerce witnessUtxo.script to Buffer for downstream helpers
|
|
599
|
+
const inScriptBuf =
|
|
600
|
+
dataInput && dataInput.witnessUtxo && dataInput.witnessUtxo.script
|
|
601
|
+
? Buffer.isBuffer(dataInput.witnessUtxo.script)
|
|
602
|
+
? dataInput.witnessUtxo.script
|
|
603
|
+
: Buffer.from(dataInput.witnessUtxo.script)
|
|
604
|
+
: Buffer.alloc(0);
|
|
605
|
+
|
|
606
|
+
// Select Trezor script_type (detect Taproot explicitly)
|
|
607
|
+
const isTaproot =
|
|
608
|
+
inScriptBuf &&
|
|
609
|
+
inScriptBuf.length === 34 &&
|
|
610
|
+
inScriptBuf[0] === bitcoinops.OP_1 &&
|
|
611
|
+
inScriptBuf[1] === 0x20;
|
|
612
|
+
|
|
613
|
+
if (isTaproot) {
|
|
614
|
+
inputItem.script_type = 'SPENDTAPROOT';
|
|
615
|
+
} else {
|
|
616
|
+
const scriptTypes = psbt.getInputType(i);
|
|
617
|
+
switch (scriptTypes) {
|
|
618
|
+
case 'multisig':
|
|
619
|
+
inputItem.script_type = 'SPENDMULTISIG';
|
|
620
|
+
break;
|
|
621
|
+
case 'witnesspubkeyhash':
|
|
622
|
+
inputItem.script_type = 'SPENDWITNESS';
|
|
623
|
+
break;
|
|
624
|
+
default:
|
|
625
|
+
inputItem.script_type = this.isP2WSHScript(inScriptBuf)
|
|
626
|
+
? 'SPENDP2SHWITNESS'
|
|
627
|
+
: 'SPENDADDRESS';
|
|
628
|
+
break;
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
trezortx.inputs.push(inputItem);
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
for (let i = 0; i < psbt.txOutputs.length; i++) {
|
|
636
|
+
const output = psbt.txOutputs[i];
|
|
637
|
+
const outputItem: any = {};
|
|
638
|
+
const scriptBuf = Buffer.isBuffer(output.script)
|
|
639
|
+
? output.script
|
|
640
|
+
: Buffer.from(output.script || []);
|
|
641
|
+
const chunks = decompile(scriptBuf);
|
|
642
|
+
|
|
643
|
+
// Debug logging to understand the output structure
|
|
644
|
+
console.log(`[Trezor] Processing output ${i}:`, {
|
|
645
|
+
value: output.value,
|
|
646
|
+
valueType: typeof output.value,
|
|
647
|
+
hasAddress: !!output.address,
|
|
648
|
+
address: output.address,
|
|
649
|
+
scriptLength: output.script?.length,
|
|
650
|
+
});
|
|
651
|
+
|
|
652
|
+
// Ensure value exists and convert properly
|
|
653
|
+
if (output.value === undefined || output.value === null) {
|
|
654
|
+
throw new Error(`Output ${i} has no value`);
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
// Handle different value types
|
|
658
|
+
let amountStr: string;
|
|
659
|
+
if (typeof output.value === 'bigint') {
|
|
660
|
+
amountStr = output.value.toString();
|
|
661
|
+
} else if (typeof output.value === 'number') {
|
|
662
|
+
amountStr = output.value.toString();
|
|
663
|
+
} else if (typeof output.value === 'string') {
|
|
664
|
+
amountStr = output.value;
|
|
665
|
+
} else {
|
|
666
|
+
throw new Error(
|
|
667
|
+
`Output ${i} has unexpected value type: ${typeof output.value}`
|
|
668
|
+
);
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
outputItem.amount = amountStr;
|
|
672
|
+
if (chunks && chunks[0] === bitcoinops.OP_RETURN) {
|
|
673
|
+
outputItem.script_type = 'PAYTOOPRETURN';
|
|
674
|
+
// @ts-ignore
|
|
675
|
+
outputItem.op_return_data = Buffer.from(chunks[1]).toString('hex');
|
|
676
|
+
} else {
|
|
677
|
+
if (output && this.isBech32(output.address)) {
|
|
678
|
+
if (
|
|
679
|
+
scriptBuf.length === 34 &&
|
|
680
|
+
scriptBuf[0] === 0 &&
|
|
681
|
+
scriptBuf[1] === 0x20
|
|
682
|
+
) {
|
|
683
|
+
outputItem.script_type = 'PAYTOP2SHWITNESS';
|
|
684
|
+
} else {
|
|
685
|
+
outputItem.script_type = 'PAYTOWITNESS';
|
|
686
|
+
}
|
|
687
|
+
} else {
|
|
688
|
+
outputItem.script_type = this.isScriptHash(output.address, network)
|
|
689
|
+
? 'PAYTOSCRIPTHASH'
|
|
690
|
+
: 'PAYTOADDRESS';
|
|
691
|
+
}
|
|
692
|
+
if (output.address) outputItem.address = output.address;
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
// Validate that the output has required fields for Trezor
|
|
696
|
+
if (
|
|
697
|
+
outputItem.script_type === 'PAYTOADDRESS' ||
|
|
698
|
+
outputItem.script_type === 'PAYTOSCRIPTHASH' ||
|
|
699
|
+
outputItem.script_type === 'PAYTOWITNESS' ||
|
|
700
|
+
outputItem.script_type === 'PAYTOP2SHWITNESS'
|
|
701
|
+
) {
|
|
702
|
+
if (!outputItem.address) {
|
|
703
|
+
console.error(
|
|
704
|
+
`[Trezor] Output ${i} missing address for script type ${outputItem.script_type}`
|
|
705
|
+
);
|
|
706
|
+
throw new Error(
|
|
707
|
+
`Output ${i} requires an address for script type ${outputItem.script_type}`
|
|
708
|
+
);
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
trezortx.outputs.push(outputItem);
|
|
713
|
+
}
|
|
714
|
+
return trezortx;
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
/**
|
|
718
|
+
* This sign EVM tx.
|
|
719
|
+
*
|
|
720
|
+
* @param index - index of account for path derivation
|
|
721
|
+
* @param tx - ethereum tx object
|
|
722
|
+
* @returns signature object
|
|
723
|
+
*/
|
|
724
|
+
public async signEthTransaction({
|
|
725
|
+
tx,
|
|
726
|
+
index,
|
|
727
|
+
coin,
|
|
728
|
+
slip44,
|
|
729
|
+
}: {
|
|
730
|
+
index: string;
|
|
731
|
+
tx: EthereumTransaction | EthereumTransactionEIP1559;
|
|
732
|
+
coin: string;
|
|
733
|
+
slip44: number;
|
|
734
|
+
}) {
|
|
735
|
+
return this.executeWithRetry(async () => {
|
|
736
|
+
// Wait between popups
|
|
737
|
+
await new Promise((resolve) => setTimeout(resolve, DELAY_BETWEEN_POPUPS));
|
|
738
|
+
|
|
739
|
+
// Use dynamic path generation based on actual network parameters
|
|
740
|
+
this.setHdPath(coin, Number(index) || 0, slip44);
|
|
741
|
+
|
|
742
|
+
const response = await TrezorConnect.ethereumSignTransaction({
|
|
743
|
+
path: this.hdPath,
|
|
744
|
+
transaction: tx,
|
|
745
|
+
});
|
|
746
|
+
|
|
747
|
+
if (response.success) {
|
|
748
|
+
return response;
|
|
749
|
+
}
|
|
750
|
+
throw new Error(response.payload.error);
|
|
751
|
+
}, 'signEthTransaction');
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
/**
|
|
755
|
+
* This sign message.
|
|
756
|
+
*
|
|
757
|
+
* @param coin - network symbol. Example: eth, sys, btc
|
|
758
|
+
* @param slip44 - network slip44 number
|
|
759
|
+
* @param message - message to be signed. Example: 'Test message'
|
|
760
|
+
* @param index - index of account for path derivation
|
|
761
|
+
* @returns signature object
|
|
762
|
+
*/
|
|
763
|
+
|
|
764
|
+
public async signMessage({
|
|
765
|
+
index,
|
|
766
|
+
message,
|
|
767
|
+
coin,
|
|
768
|
+
slip44,
|
|
769
|
+
address,
|
|
770
|
+
}: {
|
|
771
|
+
address: string;
|
|
772
|
+
coin: string;
|
|
773
|
+
index?: number;
|
|
774
|
+
message?: string;
|
|
775
|
+
slip44: number; // Required, not optional
|
|
776
|
+
}) {
|
|
777
|
+
return this.executeWithRetry(async () => {
|
|
778
|
+
// Wait between popups
|
|
779
|
+
await new Promise((resolve) => setTimeout(resolve, DELAY_BETWEEN_POPUPS));
|
|
780
|
+
|
|
781
|
+
if (isEvmCoin(coin, slip44) && `${index ? index : 0}` && message) {
|
|
782
|
+
return this._signEthPersonalMessage(Number(index), message, address);
|
|
783
|
+
}
|
|
784
|
+
return this._signUtxoPersonalMessage({ coin, index, slip44, message });
|
|
785
|
+
}, 'signMessage');
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
private async _signUtxoPersonalMessage({
|
|
789
|
+
coin,
|
|
790
|
+
index,
|
|
791
|
+
slip44,
|
|
792
|
+
message,
|
|
793
|
+
}: {
|
|
794
|
+
coin: string;
|
|
795
|
+
index?: number;
|
|
796
|
+
slip44: number;
|
|
797
|
+
message?: string;
|
|
798
|
+
}) {
|
|
799
|
+
try {
|
|
800
|
+
// Use dynamic path generation instead of hardcoded switch
|
|
801
|
+
this.setHdPath(coin, index || 0, slip44);
|
|
802
|
+
const { success, payload } = await TrezorConnect.signMessage({
|
|
803
|
+
path: this.hdPath,
|
|
804
|
+
coin: coin,
|
|
805
|
+
message: message,
|
|
806
|
+
});
|
|
807
|
+
|
|
808
|
+
if (success) {
|
|
809
|
+
return { success, payload };
|
|
810
|
+
}
|
|
811
|
+
return { success: false, payload };
|
|
812
|
+
} catch (error) {
|
|
813
|
+
return { error };
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
// For personal_sign, we need to prefix the message:
|
|
818
|
+
private async _signEthPersonalMessage(
|
|
819
|
+
index: number,
|
|
820
|
+
message: string,
|
|
821
|
+
address: string
|
|
822
|
+
) {
|
|
823
|
+
return new Promise((resolve, reject) => {
|
|
824
|
+
setTimeout(async () => {
|
|
825
|
+
try {
|
|
826
|
+
this.setHdPath('eth', index, 60);
|
|
827
|
+
|
|
828
|
+
TrezorConnect.ethereumSignMessage({
|
|
829
|
+
path: this.hdPath,
|
|
830
|
+
message: stripHexPrefix(message),
|
|
831
|
+
hex: true,
|
|
832
|
+
})
|
|
833
|
+
.then((response: any) => {
|
|
834
|
+
if (response.success) {
|
|
835
|
+
if (
|
|
836
|
+
address &&
|
|
837
|
+
response.payload.address.toLowerCase() !==
|
|
838
|
+
address.toLowerCase()
|
|
839
|
+
) {
|
|
840
|
+
reject(new Error('signature doesnt match the right address'));
|
|
841
|
+
}
|
|
842
|
+
const signature = `0x${response.payload.signature}`;
|
|
843
|
+
resolve({ signature, success: true });
|
|
844
|
+
} else {
|
|
845
|
+
reject(
|
|
846
|
+
// @ts-ignore
|
|
847
|
+
new Error(response.payload.error || 'Unknown error')
|
|
848
|
+
);
|
|
849
|
+
}
|
|
850
|
+
})
|
|
851
|
+
.catch((e: any) => {
|
|
852
|
+
reject(new Error(e.toString() || 'Unknown error'));
|
|
853
|
+
});
|
|
854
|
+
} catch (error) {
|
|
855
|
+
reject(error);
|
|
856
|
+
}
|
|
857
|
+
// This is necessary to avoid popup collision
|
|
858
|
+
// between the unlock & sign trezor popups
|
|
859
|
+
}, DELAY_BETWEEN_POPUPS);
|
|
860
|
+
});
|
|
861
|
+
}
|
|
862
|
+
private _sanitizeData(data: any): any {
|
|
863
|
+
switch (Object.prototype.toString.call(data)) {
|
|
864
|
+
case '[object Object]': {
|
|
865
|
+
const entries = Object.keys(data).map((k) => [
|
|
866
|
+
k,
|
|
867
|
+
this._sanitizeData(data[k]),
|
|
868
|
+
]);
|
|
869
|
+
return Object.fromEntries(entries);
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
case '[object Array]':
|
|
873
|
+
return data.map((v: any[]) => this._sanitizeData(v));
|
|
874
|
+
|
|
875
|
+
case '[object BigInt]':
|
|
876
|
+
return data.toString();
|
|
877
|
+
|
|
878
|
+
default:
|
|
879
|
+
return data;
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
private _transformTypedData = <T extends MessageTypes>(
|
|
884
|
+
data: TypedMessage<T>,
|
|
885
|
+
version: SignTypedDataVersion
|
|
886
|
+
) => {
|
|
887
|
+
const { types, primaryType, domain, message } = this._sanitizeData(data);
|
|
888
|
+
|
|
889
|
+
const domainSeparatorHash = TypedDataUtils.hashStruct(
|
|
890
|
+
'EIP712Domain',
|
|
891
|
+
this._sanitizeData(domain),
|
|
892
|
+
types,
|
|
893
|
+
version as SignTypedDataVersion.V3 | SignTypedDataVersion.V4
|
|
894
|
+
).toString('hex');
|
|
895
|
+
|
|
896
|
+
let messageHash: string | null = null;
|
|
897
|
+
|
|
898
|
+
if (primaryType !== 'EIP712Domain') {
|
|
899
|
+
messageHash = TypedDataUtils.hashStruct(
|
|
900
|
+
primaryType as string,
|
|
901
|
+
this._sanitizeData(message),
|
|
902
|
+
types,
|
|
903
|
+
version as SignTypedDataVersion.V3 | SignTypedDataVersion.V4
|
|
904
|
+
).toString('hex');
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
return {
|
|
908
|
+
domain_separator_hash: domainSeparatorHash,
|
|
909
|
+
message_hash: messageHash,
|
|
910
|
+
...data,
|
|
911
|
+
};
|
|
912
|
+
};
|
|
913
|
+
|
|
914
|
+
/**
|
|
915
|
+
* EIP-712 Sign Typed Data
|
|
916
|
+
*/
|
|
917
|
+
public async signTypedData({
|
|
918
|
+
version,
|
|
919
|
+
address,
|
|
920
|
+
data,
|
|
921
|
+
index,
|
|
922
|
+
}: {
|
|
923
|
+
address: string;
|
|
924
|
+
data: TypedMessage<any> | TypedDataV1;
|
|
925
|
+
index: number;
|
|
926
|
+
version: SignTypedDataVersion;
|
|
927
|
+
}) {
|
|
928
|
+
return this.executeWithRetry(async () => {
|
|
929
|
+
// Wait between popups
|
|
930
|
+
await new Promise((resolve) => setTimeout(resolve, DELAY_BETWEEN_POPUPS));
|
|
931
|
+
|
|
932
|
+
this.setHdPath('eth', index, 60);
|
|
933
|
+
// Use dynamic path generation for ETH (EVM) - typed data is only used for EVM
|
|
934
|
+
|
|
935
|
+
// V1 typed data is not supported by hardware wallets
|
|
936
|
+
if (version === SignTypedDataVersion.V1) {
|
|
937
|
+
throw new Error(
|
|
938
|
+
'Trezor: V1 typed data signing is not supported. Please use V3 or V4.'
|
|
939
|
+
);
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
const dataWithHashes = this._transformTypedData(
|
|
943
|
+
data as TypedMessage<any>,
|
|
944
|
+
version
|
|
945
|
+
);
|
|
946
|
+
|
|
947
|
+
// set default values for signTypedData
|
|
948
|
+
// Trezor is stricter than @metamask/eth-sig-util in what it accepts
|
|
949
|
+
const {
|
|
950
|
+
types,
|
|
951
|
+
message = {},
|
|
952
|
+
domain = {},
|
|
953
|
+
primaryType,
|
|
954
|
+
// snake_case since Trezor uses Protobuf naming conventions here
|
|
955
|
+
domain_separator_hash, // eslint-disable-line camelcase
|
|
956
|
+
message_hash, // eslint-disable-line camelcase
|
|
957
|
+
} = dataWithHashes;
|
|
958
|
+
|
|
959
|
+
// This is necessary to avoid popup collision
|
|
960
|
+
// between the unlock & sign trezor popups
|
|
961
|
+
|
|
962
|
+
const response = await TrezorConnect.ethereumSignTypedData({
|
|
963
|
+
path: this.hdPath,
|
|
964
|
+
data: {
|
|
965
|
+
types: {
|
|
966
|
+
...types,
|
|
967
|
+
EIP712Domain: types.EIP712Domain ? types.EIP712Domain : [],
|
|
968
|
+
},
|
|
969
|
+
message,
|
|
970
|
+
domain,
|
|
971
|
+
primaryType: primaryType as any,
|
|
972
|
+
},
|
|
973
|
+
metamask_v4_compat: version === SignTypedDataVersion.V4,
|
|
974
|
+
// Trezor 1 only supports blindly signing hashes
|
|
975
|
+
domain_separator_hash,
|
|
976
|
+
message_hash: message_hash ? message_hash : '',
|
|
977
|
+
});
|
|
978
|
+
|
|
979
|
+
if (response.success) {
|
|
980
|
+
if (address !== response.payload.address) {
|
|
981
|
+
throw new Error('signature doesnt match the right address');
|
|
982
|
+
}
|
|
983
|
+
return response.payload.signature;
|
|
984
|
+
}
|
|
985
|
+
// @ts-ignore
|
|
986
|
+
throw new Error(response.payload.error || 'Unknown error');
|
|
987
|
+
}, 'signTypedData');
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
/**
|
|
991
|
+
* Verify UTXO address by displaying it on the Trezor device
|
|
992
|
+
* @param accountIndex - The account index
|
|
993
|
+
* @param currency - The currency (coin type)
|
|
994
|
+
* @param slip44 - The slip44 value for the network
|
|
995
|
+
* @returns The verified address
|
|
996
|
+
*/
|
|
997
|
+
public async verifyUtxoAddress(
|
|
998
|
+
accountIndex: number,
|
|
999
|
+
currency: string,
|
|
1000
|
+
slip44: number
|
|
1001
|
+
): Promise<string | undefined> {
|
|
1002
|
+
return this.executeWithRetry(async () => {
|
|
1003
|
+
const fullPath = getAddressDerivationPath(
|
|
1004
|
+
currency,
|
|
1005
|
+
slip44,
|
|
1006
|
+
accountIndex,
|
|
1007
|
+
false, // Not a change address
|
|
1008
|
+
0
|
|
1009
|
+
);
|
|
1010
|
+
|
|
1011
|
+
try {
|
|
1012
|
+
const { payload, success } = await TrezorConnect.getAddress({
|
|
1013
|
+
path: fullPath,
|
|
1014
|
+
coin: currency,
|
|
1015
|
+
showOnTrezor: true, // This displays the address on device for verification
|
|
1016
|
+
});
|
|
1017
|
+
if (success) {
|
|
1018
|
+
return payload.address;
|
|
1019
|
+
}
|
|
1020
|
+
throw new Error('Address verification cancelled by user');
|
|
1021
|
+
} catch (error) {
|
|
1022
|
+
throw error;
|
|
1023
|
+
}
|
|
1024
|
+
}, 'verifyUtxoAddress');
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
/**
|
|
1028
|
+
* Check Trezor status
|
|
1029
|
+
*/
|
|
1030
|
+
public getStatus() {
|
|
1031
|
+
return this.hardwareWalletManager
|
|
1032
|
+
.getStatus()
|
|
1033
|
+
.find((s) => s.type === HardwareWalletType.TREZOR);
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
/**
|
|
1037
|
+
* Clean up resources
|
|
1038
|
+
*/
|
|
1039
|
+
public async destroy() {
|
|
1040
|
+
try {
|
|
1041
|
+
// First dispose Trezor Connect
|
|
1042
|
+
this.dispose();
|
|
1043
|
+
// Then destroy hardware wallet manager
|
|
1044
|
+
await this.hardwareWalletManager.destroy();
|
|
1045
|
+
this.initialized = false;
|
|
1046
|
+
} catch (error) {
|
|
1047
|
+
console.error('Error destroying Trezor keyring:', error);
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
}
|