Skip to content

Commit 3b14e6f

Browse files
committed
feat: update rust ledger impl
1 parent e8f3ed4 commit 3b14e6f

File tree

15 files changed

+886
-378
lines changed

15 files changed

+886
-378
lines changed

components/Transactions/process/TransactionProcess.tsx

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { TransactionPreview } from './TransactionPreview';
1212
import { useConnectWallet } from '@web3-onboard/react';
1313
import Safe, { Eip1193Provider, SafeTransactionOptionalProps } from '@safe-global/protocol-kit';
1414
import { useCurrentSafeClient, useSelectedSafeAddress } from '@/providers/SafeProvider';
15-
import { MetaTransactionData } from '@safe-global/types-kit';
15+
import { MetaTransactionData, SigningMethod } from '@safe-global/types-kit';
1616
import type { SafeTransaction } from '@safe-global/types-kit';
1717

1818
export type TransactionType = 'send' | 'custom';
@@ -49,7 +49,7 @@ export function TransactionProcess() {
4949
const [safeTransactionData, setSafeTransactionData] = useState<SafeTransactionData | null>(null);
5050

5151
const { selectedSafeAddress } = useSelectedSafeAddress();
52-
const currentSafeClient = useCurrentSafeClient();
52+
const safeClient = useCurrentSafeClient();
5353
const [{ wallet }] = useConnectWallet();
5454

5555
const handleTypeSelect = (type: TransactionType) => {
@@ -136,18 +136,32 @@ export function TransactionProcess() {
136136
},
137137
];
138138

139-
const safeTransaction = await protocolKit.createTransaction({
140-
transactions,
141-
});
139+
const txResult = await safeClient?.send({ transactions });
140+
141+
const safeTxHash = txResult?.transactions?.safeTxHash;
142142

143-
console.log(safeTransaction);
143+
console.log(safeTxHash);
144144
};
145145

146-
const handleExecute = () => {
146+
const handleExecute = async () => {
147147
if (!wallet?.provider || !wallet?.accounts?.[0]?.address) {
148148
alert('Please connect your wallet first');
149149
return;
150150
}
151+
152+
const transactions: MetaTransactionData[] = [
153+
{
154+
to: transactionData?.to as string,
155+
value: transactionData?.value?.toString() || '0',
156+
data: transactionData?.data || '0x',
157+
},
158+
];
159+
160+
const txResult = await safeClient?.send({ transactions });
161+
162+
const safeTxHash = txResult?.transactions?.safeTxHash;
163+
164+
console.log(safeTxHash);
151165
};
152166

153167
const getStepTitle = () => {

services/onboard/ledger-module.ts

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,7 @@ export function ledgerModule(): WalletInit {
5252
const eventEmitter = new EventEmitter();
5353
// Prefer native HID transport in Tauri (macOS/Windows). Fallback to WebHID SDK otherwise.
5454
const { getNativeLedgerTransport, NativeLedgerTransport } = await import('./ledger-native');
55-
const native = getNativeLedgerTransport();
56-
console.log('native', native);
55+
const native = await getNativeLedgerTransport();
5756
const ledgerSdk = native
5857
? await (async () => {
5958
const transport = native as InstanceType<typeof NativeLedgerTransport>;
@@ -71,14 +70,32 @@ export function ledgerModule(): WalletInit {
7170
throw new Error(e?.message ?? 'native_get_address_failed');
7271
}
7372
},
74-
signMessage: async () => {
75-
throw new Error('Native ETH signMessage not implemented');
73+
signMessage: async (derivationPath: string, message: string | Uint8Array) => {
74+
try {
75+
const result = await transport.signMessage(derivationPath, message);
76+
return result;
77+
} catch (e: any) {
78+
console.error('Native signMessage failed', { derivationPath, error: e });
79+
throw new Error(e?.message ?? 'native_sign_message_failed');
80+
}
7681
},
77-
signTransaction: async () => {
78-
throw new Error('Native ETH signTransaction not implemented');
82+
signTransaction: async (derivationPath: string, transaction: Uint8Array) => {
83+
try {
84+
const result = await transport.signTransaction(derivationPath, transaction);
85+
return result;
86+
} catch (e: any) {
87+
console.error('Native signTransaction failed', { derivationPath, error: e });
88+
throw new Error(e?.message ?? 'native_sign_transaction_failed');
89+
}
7990
},
80-
signTypedData: async () => {
81-
throw new Error('Native ETH signTypedData not implemented');
91+
signTypedData: async (derivationPath: string, typedData: any) => {
92+
try {
93+
const result = await transport.signTypedData(derivationPath, typedData);
94+
return result;
95+
} catch (e: any) {
96+
console.error('Native signTypedData failed', { derivationPath, error: e });
97+
throw new Error(e?.message ?? 'native_sign_typed_data_failed');
98+
}
8299
},
83100
};
84101
})()
@@ -192,7 +209,7 @@ export function ledgerModule(): WalletInit {
192209
value: txParams.value ? BigInt(txParams.value) : null,
193210
});
194211

195-
transaction.signature = await ledgerSdk.signTransaction(
212+
const sig = await ledgerSdk.signTransaction(
196213
getAssertedDerivationPath(),
197214
hexaStringToBuffer(transaction.unsignedSerialized)!
198215
);

services/onboard/ledger-native.ts

Lines changed: 152 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,73 @@ function isTauri(): boolean {
2424
return !!anyWin.__TAURI__ || !!anyWin.__TAURI_INTERNALS__;
2525
}
2626

27+
/**
28+
* Check if the browser/environment supports WebHID natively.
29+
* If WebHID is supported, we should use WebHID SDK instead of native implementation.
30+
*/
31+
async function isWebHidSupported(): Promise<boolean> {
32+
try {
33+
// Check if WebHID is available in the browser
34+
if (typeof window !== 'undefined' && 'navigator' in window && 'hid' in navigator) {
35+
console.log('WebHID is supported by the browser');
36+
return true;
37+
}
38+
39+
console.log('WebHID is not supported by the browser');
40+
return false;
41+
} catch (error) {
42+
console.log('WebHID support check failed:', error);
43+
return false;
44+
}
45+
}
46+
47+
/**
48+
* Check if we should use native implementation.
49+
* We use native only when:
50+
* 1. We are in Tauri environment
51+
* 2. WebHID is NOT supported by the browser
52+
* 3. Tauri HID commands are available
53+
*/
54+
async function shouldUseNative(): Promise<boolean> {
55+
try {
56+
// First, check if we are in a Tauri environment
57+
if (!isTauri()) {
58+
console.log('Not in Tauri environment, using WebHID SDK');
59+
return false;
60+
}
61+
62+
// Check if WebHID is supported by the browser
63+
const webHidSupported = await isWebHidSupported();
64+
if (webHidSupported) {
65+
console.log('WebHID is supported, using WebHID SDK instead of native');
66+
return false;
67+
}
68+
69+
// If WebHID is not supported, check if Tauri HID commands are available
70+
const core = await import('@tauri-apps/api/core');
71+
if (core?.invoke) {
72+
// Try to call the ledger_list_devices command; if successful, native is available
73+
await core.invoke('ledger_list_devices');
74+
console.log('Native HID implementation available and will be used');
75+
return true;
76+
}
77+
78+
// Fallback to global Tauri API
79+
const anyWin: any = typeof window !== 'undefined' ? (window as any) : undefined;
80+
if (anyWin?.__TAURI__?.invoke) {
81+
await anyWin.__TAURI__.invoke('ledger_list_devices');
82+
console.log('Native HID implementation available via global API and will be used');
83+
return true;
84+
}
85+
86+
console.log('Tauri API not available, falling back to WebHID SDK');
87+
return false;
88+
} catch (error) {
89+
console.log('Native HID check failed:', error);
90+
return false;
91+
}
92+
}
93+
2794
async function invoke<T>(cmd: string, args?: Record<string, unknown>): Promise<T> {
2895
// Prefer official Tauri API (works across child windows/iframes in v2)
2996
try {
@@ -40,9 +107,9 @@ async function invoke<T>(cmd: string, args?: Record<string, unknown>): Promise<T
40107
export class NativeLedgerTransport {
41108
private deviceId: string | null = null;
42109

43-
static isAvailable(): boolean {
44-
console.log('isTauri', isTauri());
45-
return isTauri();
110+
static async isAvailable(): Promise<boolean> {
111+
const shouldUse = await shouldUseNative();
112+
return shouldUse;
46113
}
47114

48115
async listDevices(): Promise<DeviceDescriptor[]> {
@@ -64,6 +131,43 @@ export class NativeLedgerTransport {
64131
this.deviceId = null;
65132
}
66133

134+
// 使用 ledger-zondax-generic 的新命令
135+
async getDeviceInfo(): Promise<{
136+
targetId: [number, number, number, number];
137+
seVersion: string;
138+
flag: number[];
139+
mcuVersion: string;
140+
}> {
141+
if (!this.deviceId) throw new Error('Device not connected');
142+
return invoke('ledger_get_device_info', { deviceId: this.deviceId });
143+
}
144+
145+
async getAppInfo(): Promise<{
146+
appName: string;
147+
appVersion: string;
148+
flagLen: number;
149+
flagsValue: number;
150+
flagRecovery: boolean;
151+
flagSignedMCUCode: boolean;
152+
flagOnboarded: boolean;
153+
flagPINValidated: boolean;
154+
}> {
155+
if (!this.deviceId) throw new Error('Device not connected');
156+
return invoke('ledger_get_app_info', { deviceId: this.deviceId });
157+
}
158+
159+
async getVersion(): Promise<{
160+
mode: number;
161+
major: number;
162+
minor: number;
163+
patch: number;
164+
locked: boolean;
165+
targetId: [number, number, number, number];
166+
}> {
167+
if (!this.deviceId) throw new Error('Device not connected');
168+
return invoke('ledger_get_version', { deviceId: this.deviceId });
169+
}
170+
67171
async getEthAddress(derivationPath: string, display = false): Promise<{ address: string; publicKey: string }> {
68172
if (!this.deviceId) throw new Error('Device not connected');
69173
const [address, pubkey] = await invoke<[string, number[]]>('ledger_eth_get_address', {
@@ -83,8 +187,51 @@ export class NativeLedgerTransport {
83187
timeoutMs,
84188
});
85189
}
190+
191+
async signMessage(
192+
derivationPath: string,
193+
message: string | Uint8Array
194+
): Promise<{ signature: { r: string; s: string; v: number; signature: string } }> {
195+
if (!this.deviceId) throw new Error('Device not connected');
196+
const messageStr = typeof message === 'string' ? message : Buffer.from(message).toString('hex');
197+
return invoke<{ signature: { r: string; s: string; v: number; signature: string } }>('ledger_eth_sign_message', {
198+
deviceId: this.deviceId,
199+
derivationPath,
200+
message: messageStr,
201+
});
202+
}
203+
204+
async signTransaction(
205+
derivationPath: string,
206+
transaction: Uint8Array
207+
): Promise<{ signature: { r: string; s: string; v: number; signature: string } }> {
208+
if (!this.deviceId) throw new Error('Device not connected');
209+
return invoke<{ signature: { r: string; s: string; v: number; signature: string } }>(
210+
'ledger_eth_sign_transaction',
211+
{
212+
deviceId: this.deviceId,
213+
derivationPath,
214+
transaction: Array.from(transaction),
215+
}
216+
);
217+
}
218+
219+
async signTypedData(
220+
derivationPath: string,
221+
typedData: any
222+
): Promise<{ signature: { r: string; s: string; v: number; signature: string } }> {
223+
if (!this.deviceId) throw new Error('Device not connected');
224+
const typedDataStr = typeof typedData === 'string' ? typedData : JSON.stringify(typedData);
225+
return invoke<{ signature: { r: string; s: string; v: number; signature: string } }>('ledger_eth_sign_typed_data', {
226+
deviceId: this.deviceId,
227+
derivationPath,
228+
typedData: typedDataStr,
229+
});
230+
}
86231
}
87232

88-
export function getNativeLedgerTransport(): NativeLedgerTransport | null {
89-
return NativeLedgerTransport.isAvailable() ? new NativeLedgerTransport() : null;
233+
export async function getNativeLedgerTransport(): Promise<NativeLedgerTransport | null> {
234+
const isAvailable = await NativeLedgerTransport.isAvailable();
235+
// return isAvailable ? new NativeLedgerTransport() : null;
236+
return new NativeLedgerTransport();
90237
}

src-tauri/Cargo.toml

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,8 @@ hex = "0.4"
2727
ledger-transport = "0.11.0"
2828
ledger-transport-hid = "0.11.0"
2929
ledger-apdu = { version = "0.11.0", default-features = true }
30-
hidapi = { version = "2.6.1", features = [
31-
"linux-static-hidraw",
32-
] }
30+
ledger-zondax-generic = "0.11.0"
31+
hidapi = { version = "2.6.1", features = ["linux-static-hidraw"] }
3332
thiserror = "1"
3433
once_cell = "1"
3534
uuid = { version = "1", features = ["v4", "serde"] }
@@ -40,3 +39,7 @@ tokio = { version = "1", features = [
4039
"sync",
4140
] }
4241
log = "0.4"
42+
anyhow = "1.0"
43+
tracing = "0.1"
44+
tracing-subscriber = "0.3"
45+
futures = "0.3"

0 commit comments

Comments
 (0)