@@ -11,11 +11,11 @@ use clap::Parser;
11
11
use ethers:: {
12
12
core:: rand:: thread_rng,
13
13
signers:: { LocalWallet , Signer } ,
14
- types:: { Address , Signature } ,
14
+ types:: { transaction :: eip712 :: TypedData , Address , Signature } ,
15
15
} ;
16
16
use eyre:: Context ;
17
17
18
- /// CLI arguments for `cast send `.
18
+ /// CLI arguments for `cast wallet `.
19
19
#[ derive( Debug , Parser ) ]
20
20
pub enum WalletSubcommands {
21
21
/// Create a new random keypair.
@@ -55,15 +55,32 @@ pub enum WalletSubcommands {
55
55
wallet : Wallet ,
56
56
} ,
57
57
58
- /// Sign a message.
58
+ /// Sign a message or typed data .
59
59
#[ clap( visible_alias = "s" ) ]
60
60
Sign {
61
- /// The message to sign.
61
+ /// The message or typed data to sign.
62
62
///
63
63
/// Messages starting with 0x are expected to be hex encoded,
64
64
/// which get decoded before being signed.
65
+ /// The message will be prefixed with the Ethereum Signed Message header and hashed before
66
+ /// signing.
67
+ ///
68
+ /// Typed data can be provided as a json string or a file name.
69
+ /// Use --data flag to denote the message is a string of typed data.
70
+ /// Use --data --from-file to denote the message is a file name containing typed data.
71
+ /// The data will be combined and hashed using the EIP712 specification before signing.
72
+ /// The data should be formatted as JSON.
65
73
message : String ,
66
74
75
+ /// If provided, the message will be treated as typed data.
76
+ #[ clap( long) ]
77
+ data : bool ,
78
+
79
+ /// If provided, the message will be treated as a file name containing typed data. Requires
80
+ /// --data.
81
+ #[ clap( long, requires = "data" ) ]
82
+ from_file : bool ,
83
+
67
84
#[ clap( flatten) ]
68
85
wallet : Wallet ,
69
86
} ,
@@ -127,9 +144,20 @@ impl WalletSubcommands {
127
144
let addr = wallet. address ( ) ;
128
145
println ! ( "{}" , SimpleCast :: to_checksum_address( & addr) ) ;
129
146
}
130
- WalletSubcommands :: Sign { message, wallet } => {
147
+ WalletSubcommands :: Sign { message, data , from_file , wallet } => {
131
148
let wallet = wallet. signer ( 0 ) . await ?;
132
- let sig = wallet. sign_message ( Self :: hex_str_to_bytes ( & message) ?) . await ?;
149
+ let sig = if data {
150
+ let typed_data: TypedData = if from_file {
151
+ // data is a file name, read json from file
152
+ foundry_common:: fs:: read_json_file ( message. as_ref ( ) ) ?
153
+ } else {
154
+ // data is a json string
155
+ serde_json:: from_str ( & message) ?
156
+ } ;
157
+ wallet. sign_typed_data ( & typed_data) . await ?
158
+ } else {
159
+ wallet. sign_message ( Self :: hex_str_to_bytes ( & message) ?) . await ?
160
+ } ;
133
161
println ! ( "0x{sig}" ) ;
134
162
}
135
163
WalletSubcommands :: Verify { message, signature, address } => {
@@ -154,3 +182,66 @@ impl WalletSubcommands {
154
182
} )
155
183
}
156
184
}
185
+
186
+ #[ cfg( test) ]
187
+ mod tests {
188
+ use super :: * ;
189
+
190
+ #[ test]
191
+ fn can_parse_wallet_sign_message ( ) {
192
+ let args = WalletSubcommands :: parse_from ( [ "foundry-cli" , "sign" , "deadbeef" ] ) ;
193
+ match args {
194
+ WalletSubcommands :: Sign { message, data, from_file, .. } => {
195
+ assert_eq ! ( message, "deadbeef" . to_string( ) ) ;
196
+ assert_eq ! ( data, false ) ;
197
+ assert_eq ! ( from_file, false ) ;
198
+ }
199
+ _ => panic ! ( "expected WalletSubcommands::Sign" ) ,
200
+ }
201
+ }
202
+
203
+ #[ test]
204
+ fn can_parse_wallet_sign_hex_message ( ) {
205
+ let args = WalletSubcommands :: parse_from ( [ "foundry-cli" , "sign" , "0xdeadbeef" ] ) ;
206
+ match args {
207
+ WalletSubcommands :: Sign { message, data, from_file, .. } => {
208
+ assert_eq ! ( message, "0xdeadbeef" . to_string( ) ) ;
209
+ assert_eq ! ( data, false ) ;
210
+ assert_eq ! ( from_file, false ) ;
211
+ }
212
+ _ => panic ! ( "expected WalletSubcommands::Sign" ) ,
213
+ }
214
+ }
215
+
216
+ #[ test]
217
+ fn can_parse_wallet_sign_data ( ) {
218
+ let args = WalletSubcommands :: parse_from ( [ "foundry-cli" , "sign" , "--data" , "{ ... }" ] ) ;
219
+ match args {
220
+ WalletSubcommands :: Sign { message, data, from_file, .. } => {
221
+ assert_eq ! ( message, "{ ... }" . to_string( ) ) ;
222
+ assert_eq ! ( data, true ) ;
223
+ assert_eq ! ( from_file, false ) ;
224
+ }
225
+ _ => panic ! ( "expected WalletSubcommands::Sign" ) ,
226
+ }
227
+ }
228
+
229
+ #[ test]
230
+ fn can_parse_wallet_sign_data_file ( ) {
231
+ let args = WalletSubcommands :: parse_from ( [
232
+ "foundry-cli" ,
233
+ "sign" ,
234
+ "--data" ,
235
+ "--from-file" ,
236
+ "tests/data/typed_data.json" ,
237
+ ] ) ;
238
+ match args {
239
+ WalletSubcommands :: Sign { message, data, from_file, .. } => {
240
+ assert_eq ! ( message, "tests/data/typed_data.json" . to_string( ) ) ;
241
+ assert_eq ! ( data, true ) ;
242
+ assert_eq ! ( from_file, true ) ;
243
+ }
244
+ _ => panic ! ( "expected WalletSubcommands::Sign" ) ,
245
+ }
246
+ }
247
+ }
0 commit comments