@sign-global/tokentable-wallets 1.0.1 → 1.2.1

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.
@@ -0,0 +1,252 @@
1
+ import { StatusCodes, TransportStatusError, type default as Transport } from '@ledgerhq/hw-transport';
2
+ import { isVersionedTransaction } from '@solana/wallet-adapter-base';
3
+ import { PublicKey, type Transaction, type VersionedTransaction } from '@solana/web3.js';
4
+ import './polyfills/index.js';
5
+
6
+ export function getDerivationPath(account?: number, change?: number): Buffer {
7
+ const length = account !== undefined ? (change === undefined ? 3 : 4) : 2;
8
+ const derivationPath = Buffer.alloc(1 + length * 4);
9
+
10
+ let offset = derivationPath.writeUInt8(length, 0);
11
+ offset = derivationPath.writeUInt32BE(harden(44), offset); // Using BIP44
12
+ offset = derivationPath.writeUInt32BE(harden(501), offset); // Solana's BIP44 path
13
+
14
+ if (account !== undefined) {
15
+ offset = derivationPath.writeUInt32BE(harden(account), offset);
16
+ if (change !== undefined) {
17
+ derivationPath.writeUInt32BE(harden(change), offset);
18
+ }
19
+ }
20
+
21
+ return derivationPath;
22
+ }
23
+
24
+ const BIP32_HARDENED_BIT = (1 << 31) >>> 0;
25
+
26
+ function harden(n: number): number {
27
+ return (n | BIP32_HARDENED_BIT) >>> 0;
28
+ }
29
+
30
+ const INS_GET_VERSION = 0x04;
31
+ const INS_GET_PUBKEY = 0x05;
32
+ const INS_SIGN_MESSAGE = 0x06;
33
+ const INS_SIGN_MESSAGE_OFFCHAIN = 0x07;
34
+
35
+ const P1_NON_CONFIRM = 0x00;
36
+ const P1_CONFIRM = 0x01;
37
+
38
+ const P2_EXTEND = 0x01;
39
+ const P2_MORE = 0x02;
40
+
41
+ const MAX_PAYLOAD = 255;
42
+
43
+ const LEDGER_CLA = 0xe0;
44
+
45
+ // Max off-chain message length supported by Ledger
46
+ const OFFCM_MAX_LEDGER_LEN = 1212;
47
+ // Max length of version 0 off-chain message
48
+ const OFFCM_MAX_V0_LEN = 65515;
49
+
50
+ export class OffchainMessage {
51
+ version: number;
52
+ messageFormat: number | undefined;
53
+ message: Buffer | undefined;
54
+ signerAddress: PublicKey;
55
+
56
+ /**
57
+ * Constructs a new OffchainMessage
58
+ * @param {version: number, messageFormat: number, message: string | Buffer} opts - Constructor parameters
59
+ */
60
+ constructor(opts: { version?: number; messageFormat?: number; message: Buffer; signerAddress: PublicKey }) {
61
+ this.version = 0;
62
+ this.messageFormat = undefined;
63
+ this.message = undefined;
64
+ this.signerAddress = opts.signerAddress;
65
+
66
+ if (!opts) {
67
+ return;
68
+ }
69
+ if (opts.version) {
70
+ this.version = opts.version;
71
+ }
72
+ if (opts.messageFormat) {
73
+ this.messageFormat = opts.messageFormat;
74
+ }
75
+ if (opts.message) {
76
+ this.message = Buffer.from(opts.message);
77
+ if (this.version === 0) {
78
+ if (!this.messageFormat) {
79
+ this.messageFormat = OffchainMessage.guessMessageFormat(this.message);
80
+ }
81
+ }
82
+ }
83
+ }
84
+
85
+ static guessMessageFormat(message: Buffer) {
86
+ if (Object.prototype.toString.call(message) !== '[object Uint8Array]') {
87
+ return undefined;
88
+ }
89
+ if (message.length <= OFFCM_MAX_LEDGER_LEN) {
90
+ if (OffchainMessage.isPrintableASCII(message)) {
91
+ return 0;
92
+ } else if (OffchainMessage.isUTF8(message)) {
93
+ return 1;
94
+ }
95
+ } else if (message.length <= OFFCM_MAX_V0_LEN) {
96
+ if (OffchainMessage.isUTF8(message)) {
97
+ return 2;
98
+ }
99
+ }
100
+ return undefined;
101
+ }
102
+
103
+ static isPrintableASCII(buffer: Buffer) {
104
+ return (
105
+ buffer &&
106
+ buffer.every((element) => {
107
+ return element == 0xa || (element >= 0x20 && element <= 0x7e);
108
+ })
109
+ );
110
+ }
111
+
112
+ static isUTF8(buffer: Buffer) {
113
+ try {
114
+ new TextDecoder('utf8', { fatal: true }).decode(buffer);
115
+ return true;
116
+ } catch {
117
+ return false;
118
+ }
119
+ }
120
+
121
+ isValid() {
122
+ if (this.version !== 0) {
123
+ return false;
124
+ }
125
+ if (!this.message) {
126
+ return false;
127
+ }
128
+ const format = OffchainMessage.guessMessageFormat(this.message);
129
+ return format != null && format === this.messageFormat;
130
+ }
131
+
132
+ isLedgerSupported(allowBlindSigning: boolean) {
133
+ return this.isValid() && (this.messageFormat === 0 || (this.messageFormat === 1 && allowBlindSigning));
134
+ }
135
+
136
+ serialize() {
137
+ if (!this.isValid()) {
138
+ throw new Error(`Invalid OffchainMessage: ${JSON.stringify(this)}`);
139
+ }
140
+ const signingDomain = Buffer.concat([Buffer.from([255]), Buffer.from('solana offchain')]);
141
+ const headerVersion = Buffer.alloc(1);
142
+ const applicationDomain = Buffer.alloc(32);
143
+ const messageFormat = Buffer.alloc(1);
144
+ const signerCount = Buffer.alloc(1);
145
+ signerCount.writeUInt8(1);
146
+
147
+ const messageLength = Buffer.alloc(2);
148
+
149
+ // isValid() checks message
150
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
151
+ const messageBuffer = Buffer.from(this.message!);
152
+
153
+ // isValid() checks messageFormat
154
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
155
+ messageFormat.writeUInt8(this.messageFormat!);
156
+
157
+ const signers = this.signerAddress.toBuffer();
158
+ messageLength.writeUInt16LE(messageBuffer.length);
159
+
160
+ return Buffer.concat([
161
+ signingDomain,
162
+ headerVersion,
163
+ applicationDomain,
164
+ messageFormat,
165
+ signerCount,
166
+ signers,
167
+ messageLength,
168
+ messageBuffer
169
+ ]);
170
+ }
171
+ }
172
+
173
+ /** @internal */
174
+ export async function getPublicKey(transport: Transport, derivationPath: Buffer): Promise<PublicKey> {
175
+ const bytes = await send(transport, INS_GET_PUBKEY, P1_NON_CONFIRM, derivationPath);
176
+ return new PublicKey(bytes);
177
+ }
178
+
179
+ /** @internal */
180
+ export async function signTransaction(
181
+ transport: Transport,
182
+ transaction: Transaction | VersionedTransaction,
183
+ derivationPath: Buffer
184
+ ): Promise<Buffer> {
185
+ const paths = Buffer.alloc(1);
186
+ paths.writeUInt8(1, 0);
187
+
188
+ const message = isVersionedTransaction(transaction)
189
+ ? transaction.message.serialize()
190
+ : transaction.serializeMessage();
191
+ const data = Buffer.concat([paths, derivationPath, message]);
192
+
193
+ return await send(transport, INS_SIGN_MESSAGE, P1_CONFIRM, data);
194
+ }
195
+
196
+ /** @internal */
197
+ export async function signMessage(transport: Transport, message: Buffer, derivationPath: Buffer): Promise<Buffer> {
198
+ const paths = Buffer.alloc(1);
199
+ paths.writeUInt8(1, 0);
200
+
201
+ const data = Buffer.concat([paths, derivationPath, message]);
202
+
203
+ return await send(transport, INS_SIGN_MESSAGE_OFFCHAIN, P1_CONFIRM, data);
204
+ }
205
+
206
+ /** @internal */
207
+ export async function getAppConfiguration(transport: Transport): Promise<AppConfig> {
208
+ const [blindSigningEnabled, pubKeyDisplayMode, major, minor, patch] = await send(
209
+ transport,
210
+ INS_GET_VERSION,
211
+ P1_NON_CONFIRM,
212
+ Buffer.alloc(0)
213
+ );
214
+ return {
215
+ blindSigningEnabled: Boolean(blindSigningEnabled),
216
+ pubKeyDisplayMode,
217
+ version: `${major}.${minor}.${patch}`
218
+ };
219
+ }
220
+
221
+ enum PubKeyDisplayMode {
222
+ LONG,
223
+ SHORT
224
+ }
225
+
226
+ type AppConfig = {
227
+ blindSigningEnabled: boolean;
228
+ pubKeyDisplayMode: PubKeyDisplayMode;
229
+ version: string;
230
+ };
231
+
232
+ async function send(transport: Transport, instruction: number, p1: number, data: Buffer): Promise<Buffer> {
233
+ let p2 = 0;
234
+ let offset = 0;
235
+
236
+ if (data.length > MAX_PAYLOAD) {
237
+ while (data.length - offset > MAX_PAYLOAD) {
238
+ const buffer = data.slice(offset, offset + MAX_PAYLOAD);
239
+ const response = await transport.send(LEDGER_CLA, instruction, p1, p2 | P2_MORE, buffer);
240
+
241
+ if (response.length !== 2) throw new TransportStatusError(StatusCodes.INCORRECT_DATA);
242
+
243
+ p2 |= P2_EXTEND;
244
+ offset += MAX_PAYLOAD;
245
+ }
246
+ }
247
+
248
+ const buffer = data.slice(offset);
249
+ const response = await transport.send(LEDGER_CLA, instruction, p1, p2, buffer);
250
+
251
+ return response.slice(0, response.length - 2);
252
+ }
package/tsup.config.ts CHANGED
@@ -18,6 +18,8 @@ export default createTsupConfig({
18
18
  '@tonconnect/ui-react',
19
19
  '@tonconnect/ui',
20
20
  '@tonconnect/sdk',
21
+ '@ledgerhq/hw-transport',
22
+ '@ledgerhq/hw-transport-webhid',
21
23
 
22
24
  // 区块链库
23
25
  'viem',