Skip to content

Commit 7da961c

Browse files
committed
feat: init rust ledger impletments
1 parent 043e7bf commit 7da961c

File tree

18 files changed

+776
-45
lines changed

18 files changed

+776
-45
lines changed

.github/workflows/build.yml

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,6 @@ jobs:
6464
restore-keys: |
6565
${{ runner.os }}-cargo-
6666
67-
- name: Build frontend
68-
run: pnpm build
69-
7067
- name: Build Tauri app
7168
run: pnpm tauri build
7269
env:

.github/workflows/release.yml

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,9 +73,6 @@ jobs:
7373
restore-keys: |
7474
${{ runner.os }}-cargo-
7575
76-
- name: Build frontend
77-
run: pnpm build
78-
7976
- name: Build Tauri app
8077
run: pnpm tauri build
8178
env:

components/Transactions/process/CustomTransactionBuilder.tsx

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { Input } from '@/components/ui/input';
66
import { Label } from '@/components/ui/label';
77
import { Textarea } from '@/components/ui/textarea';
88
import { TransactionData } from './TransactionProcess';
9+
import { parseEther, parseGwei } from 'viem';
910

1011
interface CustomTransactionBuilderProps {
1112
onComplete: (data: Partial<TransactionData>) => void;
@@ -30,12 +31,12 @@ export function CustomTransactionBuilder({ onComplete }: CustomTransactionBuilde
3031
}
3132

3233
onComplete({
33-
to: formData.to,
34-
value: formData.value || '0',
35-
data: formData.data,
36-
gasLimit: formData.gasLimit,
37-
maxFeePerGas: formData.maxFeePerGas,
38-
maxPriorityFeePerGas: formData.maxPriorityFeePerGas,
34+
to: formData.to as `0x${string}`,
35+
value: formData.value ? parseEther(formData.value) : BigInt(0),
36+
data: formData.data as `0x${string}`,
37+
gasLimit: formData.gasLimit ? BigInt(formData.gasLimit) : undefined,
38+
maxFeePerGas: formData.maxFeePerGas ? parseGwei(formData.maxFeePerGas) : undefined,
39+
maxPriorityFeePerGas: formData.maxPriorityFeePerGas ? parseGwei(formData.maxPriorityFeePerGas) : undefined,
3940
});
4041
};
4142

components/Transactions/process/SendTokenBuilder.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { TransactionData } from './TransactionProcess';
99
import { useBalance } from '@/hooks/safe/useBalance';
1010
import { formatBalance, getTokenSymbol } from '@/components/Assets/utils';
1111
import { SafeBalance } from '@/types/extendedSafeTransactionServiceTypes';
12+
import { parseEther } from 'viem';
1213

1314
interface SendTokenBuilderProps {
1415
onComplete: (data: Partial<TransactionData>) => void;
@@ -60,9 +61,9 @@ export function SendTokenBuilder({ onComplete }: SendTokenBuilderProps) {
6061
}
6162

6263
onComplete({
63-
to: formData.to,
64-
value: formData.value,
65-
tokenAddress: formData.token,
64+
to: formData.to as `0x${string}`,
65+
value: parseEther(formData.value),
66+
tokenAddress: formData.token as `0x${string}`,
6667
});
6768
};
6869

components/Transactions/process/TransactionPreview.tsx

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,22 @@
33
import { Button } from '@/components/ui/button';
44
import { Badge } from '@/components/ui/badge';
55
import { Send, Code, Shield } from 'lucide-react';
6-
import { TransactionData } from './TransactionProcess';
6+
import { TransactionData, SafeTransactionData } from './TransactionProcess';
7+
import { formatEther, formatGwei } from 'viem';
78

89
interface TransactionPreviewProps {
910
transactionData: TransactionData;
11+
safeTransactionData?: SafeTransactionData;
1012
onSign: () => void;
1113
onExecute: () => void;
1214
}
1315

14-
export function TransactionPreview({ transactionData, onSign, onExecute }: TransactionPreviewProps) {
16+
export function TransactionPreview({
17+
transactionData,
18+
safeTransactionData,
19+
onSign,
20+
onExecute,
21+
}: TransactionPreviewProps) {
1522
const formatAddress = (address: string) => {
1623
return `${address.slice(0, 6)}...${address.slice(-4)}`;
1724
};
@@ -43,10 +50,10 @@ export function TransactionPreview({ transactionData, onSign, onExecute }: Trans
4350
</div>
4451
)}
4552

46-
{transactionData.value && transactionData.value !== '0' && (
53+
{transactionData.value && transactionData.value !== BigInt(0) && (
4754
<div className='flex justify-between items-center py-2 border-b'>
4855
<span className='text-sm text-muted-foreground'>Value:</span>
49-
<span className='text-sm font-medium'>{transactionData.value} ETH</span>
56+
<span className='text-sm font-medium'>{formatEther(transactionData.value)} ETH</span>
5057
</div>
5158
)}
5259

@@ -74,12 +81,29 @@ export function TransactionPreview({ transactionData, onSign, onExecute }: Trans
7481
{transactionData.maxPriorityFeePerGas && (
7582
<div className='flex justify-between items-center py-2 border-b'>
7683
<span className='text-sm text-muted-foreground'>Max Priority Fee:</span>
77-
<span className='text-sm'>{transactionData.maxPriorityFeePerGas} Gwei</span>
84+
<span className='text-sm'>{formatGwei(transactionData.maxPriorityFeePerGas)} Gwei</span>
7885
</div>
7986
)}
8087
</div>
8188
</div>
8289

90+
{/* Safe Transaction Details */}
91+
{safeTransactionData?.safeTransaction && (
92+
<div className='space-y-4'>
93+
<h3 className='text-lg font-medium'>Safe Transaction Details</h3>
94+
<div className='space-y-3'>
95+
<div className='flex justify-between items-center py-2 border-b'>
96+
<span className='text-sm text-muted-foreground'>Safe Transaction Created:</span>
97+
<span className='text-sm text-green-600'>✓ Ready for signing</span>
98+
</div>
99+
<div className='flex justify-between items-center py-2 border-b'>
100+
<span className='text-sm text-muted-foreground'>Transaction Type:</span>
101+
<span className='text-sm'>Safe Multisig Transaction</span>
102+
</div>
103+
</div>
104+
</div>
105+
)}
106+
83107
{/* Security Notice */}
84108
<div className='p-4 bg-blue-50 dark:bg-blue-950/20 rounded-lg border border-blue-200 dark:border-blue-800'>
85109
<div className='flex items-start gap-3'>

components/Transactions/process/TransactionProcess.tsx

Lines changed: 117 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -9,34 +9,98 @@ import { TransactionTypeSelector } from './TransactionTypeSelector';
99
import { SendTokenBuilder } from './SendTokenBuilder';
1010
import { CustomTransactionBuilder } from './CustomTransactionBuilder';
1111
import { TransactionPreview } from './TransactionPreview';
12+
import { useConnectWallet } from '@web3-onboard/react';
13+
import Safe, { Eip1193Provider, SafeTransactionOptionalProps } from '@safe-global/protocol-kit';
14+
import { useCurrentSafeClient, useSelectedSafeAddress } from '@/providers/SafeProvider';
15+
import { MetaTransactionData } from '@safe-global/types-kit';
16+
import type { SafeTransaction } from '@safe-global/types-kit';
1217

1318
export type TransactionType = 'send' | 'custom';
1419

20+
// Transaction data interface conforming to viem standards
1521
export interface TransactionData {
1622
type: TransactionType;
17-
to?: string;
18-
value?: string;
19-
tokenAddress?: string;
20-
data?: string;
21-
gasLimit?: string;
22-
maxFeePerGas?: string;
23-
maxPriorityFeePerGas?: string;
23+
to?: `0x${string}`;
24+
value?: bigint; // Use bigint, in wei units
25+
tokenAddress?: `0x${string}`;
26+
data?: `0x${string}`;
27+
gasLimit?: bigint;
28+
maxFeePerGas?: bigint;
29+
maxPriorityFeePerGas?: bigint;
2430
}
2531

26-
export type Step = 'select' | 'build' | 'preview';
32+
// SafeTransaction related data
33+
export interface SafeTransactionData {
34+
rawTransaction: TransactionData;
35+
safeTransaction?: SafeTransaction;
36+
transactionHash?: string;
37+
estimatedGas?: bigint;
38+
estimatedFees?: {
39+
maxFeePerGas: bigint;
40+
maxPriorityFeePerGas: bigint;
41+
};
42+
}
43+
44+
export type Step = 'select' | 'build' | 'preview' | 'execute';
2745

2846
export function TransactionProcess() {
2947
const [currentStep, setCurrentStep] = useState<Step>('select');
3048
const [transactionData, setTransactionData] = useState<TransactionData | null>(null);
49+
const [safeTransactionData, setSafeTransactionData] = useState<SafeTransactionData | null>(null);
50+
51+
const { selectedSafeAddress } = useSelectedSafeAddress();
52+
const currentSafeClient = useCurrentSafeClient();
53+
const [{ wallet }] = useConnectWallet();
3154

3255
const handleTypeSelect = (type: TransactionType) => {
3356
setTransactionData({ type });
3457
setCurrentStep('build');
3558
};
3659

37-
const handleTransactionBuilt = (data: Partial<TransactionData>) => {
38-
setTransactionData((prev) => ({ ...prev, ...data }) as TransactionData);
39-
setCurrentStep('preview');
60+
const handleTransactionBuilt = async (data: Partial<TransactionData>) => {
61+
const updatedTransactionData = { ...transactionData, ...data } as TransactionData;
62+
setTransactionData(updatedTransactionData);
63+
64+
// Create SafeTransaction
65+
try {
66+
await createSafeTransaction(updatedTransactionData);
67+
setCurrentStep('preview');
68+
} catch (error) {
69+
console.error('Failed to create Safe transaction:', error);
70+
alert('Failed to create Safe transaction. Please try again.');
71+
}
72+
};
73+
74+
const createSafeTransaction = async (transactionData: TransactionData) => {
75+
if (!wallet?.provider || !wallet?.accounts?.[0]?.address || !selectedSafeAddress) {
76+
throw new Error('Please connect your wallet and select a Safe first');
77+
}
78+
79+
const signer = wallet.accounts[0].address;
80+
const provider = wallet.provider;
81+
82+
const protocolKit = await Safe.init({
83+
provider: provider as Eip1193Provider,
84+
signer,
85+
safeAddress: selectedSafeAddress,
86+
});
87+
88+
const transactions: MetaTransactionData[] = [
89+
{
90+
to: transactionData.to as string,
91+
value: transactionData.value?.toString() || '0',
92+
data: transactionData.data || '0x',
93+
},
94+
];
95+
96+
const safeTransaction = await protocolKit.createTransaction({
97+
transactions,
98+
});
99+
100+
setSafeTransactionData({
101+
rawTransaction: transactionData,
102+
safeTransaction,
103+
});
40104
};
41105

42106
const handleBack = () => {
@@ -47,14 +111,43 @@ export function TransactionProcess() {
47111
}
48112
};
49113

50-
const handleSign = () => {
51-
console.log('Signing transaction:', transactionData);
52-
// TODO: Implement transaction signing
114+
const handleSign = async () => {
115+
console.log('handleSign');
116+
117+
if (!wallet?.provider || !wallet?.accounts?.[0]?.address) {
118+
alert('Please connect your wallet first');
119+
return;
120+
}
121+
122+
const signer = wallet.accounts[0].address;
123+
const provider = wallet.provider;
124+
125+
const protocolKit = await Safe.init({
126+
provider: provider as Eip1193Provider,
127+
signer,
128+
safeAddress: selectedSafeAddress as string,
129+
});
130+
131+
const transactions: MetaTransactionData[] = [
132+
{
133+
to: transactionData?.to as string,
134+
value: transactionData?.value?.toString() || '0',
135+
data: transactionData?.data || '0x',
136+
},
137+
];
138+
139+
const safeTransaction = await protocolKit.createTransaction({
140+
transactions,
141+
});
142+
143+
console.log(safeTransaction);
53144
};
54145

55146
const handleExecute = () => {
56-
console.log('Executing transaction:', transactionData);
57-
// TODO: Implement transaction execution
147+
if (!wallet?.provider || !wallet?.accounts?.[0]?.address) {
148+
alert('Please connect your wallet first');
149+
return;
150+
}
58151
};
59152

60153
const getStepTitle = () => {
@@ -94,7 +187,14 @@ export function TransactionProcess() {
94187
return <CustomTransactionBuilder onComplete={handleTransactionBuilt} />;
95188
}
96189
case 'preview':
97-
return <TransactionPreview transactionData={transactionData!} onSign={handleSign} onExecute={handleExecute} />;
190+
return (
191+
<TransactionPreview
192+
transactionData={transactionData!}
193+
safeTransactionData={safeTransactionData || undefined}
194+
onSign={handleSign}
195+
onExecute={handleExecute}
196+
/>
197+
);
98198
default:
99199
return null;
100200
}

services/onboard/ledger-module.ts

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,39 @@ export function ledgerModule(): WalletInit {
5050
const { createPublicClient, http, bytesToHex } = await import('viem');
5151
const { Transaction } = await import('ethers');
5252
const eventEmitter = new EventEmitter();
53-
const ledgerSdk = await getLedgerSdk();
53+
// Prefer native HID transport in Tauri (macOS/Windows). Fallback to WebHID SDK otherwise.
54+
const { getNativeLedgerTransport, NativeLedgerTransport } = await import('./ledger-native');
55+
const native = getNativeLedgerTransport();
56+
console.log('native', native);
57+
const ledgerSdk = native
58+
? await (async () => {
59+
const transport = native as InstanceType<typeof NativeLedgerTransport>;
60+
const devices = await transport.listDevices();
61+
console.log('devices====', devices);
62+
const id = await transport.connect(devices[0]?.device_id);
63+
return {
64+
disconnect: () => transport.disconnect(),
65+
getAddress: async (derivationPath: string) => {
66+
try {
67+
const { address, publicKey } = await transport.getEthAddress(derivationPath, false);
68+
return { address, publicKey } as any;
69+
} catch (e: any) {
70+
console.error('Native getAddress failed', { derivationPath, error: e });
71+
throw new Error(e?.message ?? 'native_get_address_failed');
72+
}
73+
},
74+
signMessage: async () => {
75+
throw new Error('Native ETH signMessage not implemented');
76+
},
77+
signTransaction: async () => {
78+
throw new Error('Native ETH signTransaction not implemented');
79+
},
80+
signTypedData: async () => {
81+
throw new Error('Native ETH signTypedData not implemented');
82+
},
83+
};
84+
})()
85+
: await getLedgerSdk();
5486

5587
/* -------------------------------------------------------------------------- */
5688
/* State */

0 commit comments

Comments
 (0)