1
1
use crate :: lib:: environment:: Environment ;
2
2
use crate :: lib:: error:: DfxResult ;
3
3
use crate :: lib:: sign:: signed_message:: SignedMessageV1 ;
4
- use anyhow:: { anyhow , bail, Context } ;
5
- use candid:: Principal ;
4
+ use anyhow:: { bail, Context } ;
5
+ use candid:: { IDLArgs , Principal } ;
6
6
use clap:: Parser ;
7
7
use dfx_core:: identity:: CallSender ;
8
- use ic_agent:: agent:: Transport ;
9
- use ic_agent:: { agent:: http_transport:: ReqwestTransport , RequestId } ;
10
- use std:: { fs:: File , path:: Path } ;
11
- use std:: { io:: Read , str:: FromStr } ;
8
+ use dfx_core:: json:: load_json_file;
9
+ use ic_agent:: agent:: { CallResponse , RequestStatusResponse } ;
10
+ use ic_agent:: Agent ;
11
+ use ic_agent:: RequestId ;
12
+ use std:: path:: PathBuf ;
12
13
13
14
/// Send a previously-signed message.
14
15
#[ derive( Parser ) ]
15
16
pub struct CanisterSendOpts {
16
17
/// Specifies the file name of the message
17
- file_name : String ,
18
+ file_name : PathBuf ,
18
19
19
20
/// Send the signed request-status call in the message
20
21
#[ arg( long) ]
@@ -30,38 +31,64 @@ pub async fn exec(
30
31
bail ! ( "`send` currently doesn't support proxying through the wallet canister, please use `dfx canister send --no-wallet ...`." ) ;
31
32
}
32
33
let file_name = opts. file_name ;
33
- let path = Path :: new ( & file_name) ;
34
- let mut file = File :: open ( path) . map_err ( |_| anyhow ! ( "Message file doesn't exist." ) ) ?;
35
- let mut json = String :: new ( ) ;
36
- file. read_to_string ( & mut json)
37
- . map_err ( |_| anyhow ! ( "Cannot read the message file." ) ) ?;
38
- let message: SignedMessageV1 =
39
- serde_json:: from_str ( & json) . map_err ( |_| anyhow ! ( "Invalid json message." ) ) ?;
34
+ let message: SignedMessageV1 = load_json_file ( & file_name) ?;
40
35
message. validate ( ) ?;
41
36
42
37
let network = message. network . clone ( ) ;
43
- let transport =
44
- ReqwestTransport :: create ( network) . context ( "Failed to create transport object." ) ?;
38
+ let agent = Agent :: builder ( ) . with_url ( & network) . build ( ) ?;
39
+ if !message. is_ic {
40
+ agent. fetch_root_key ( ) . await ?;
41
+ }
45
42
let content = hex:: decode ( & message. content ) . context ( "Failed to decode message content." ) ?;
46
- let canister_id = Principal :: from_text ( message. canister_id . clone ( ) )
43
+ let canister_id = Principal :: from_text ( & message. canister_id )
47
44
. with_context ( || format ! ( "Failed to parse canister id {:?}." , message. canister_id) ) ?;
48
45
49
46
if opts. status {
50
- if message. call_type . clone ( ) . as_str ( ) != "update" {
47
+ if message. call_type != "update" {
51
48
bail ! ( "Can only check request_status on update calls." ) ;
52
49
}
53
- if message. signed_request_status . is_none ( ) {
54
- bail ! ( "No signed_request_status in [{}]." , file_name) ;
55
- }
56
- let envelope = hex:: decode ( message. signed_request_status . unwrap ( ) )
57
- . context ( "Failed to decode envelope." ) ?;
58
- let response = transport
59
- . read_state ( canister_id, envelope)
50
+ let Some ( signed_request_status) = message. signed_request_status else {
51
+ bail ! ( "No signed_request_status in [{}]." , file_name. display( ) ) ;
52
+ } ;
53
+ let envelope = hex:: decode ( signed_request_status) . context ( "Failed to decode envelope." ) ?;
54
+ let Some ( request_id) = message. request_id else {
55
+ bail ! ( "No request_id in [{}]." , file_name. display( ) ) ;
56
+ } ;
57
+ let request_id = request_id
58
+ . parse :: < RequestId > ( )
59
+ . context ( "Failed to decode request ID." ) ?;
60
+ let response = agent
61
+ . request_status_signed ( & request_id, canister_id, envelope)
60
62
. await
61
63
. with_context ( || format ! ( "Failed to read canister state of {}." , canister_id) ) ?;
62
- eprintln ! ( "To see the content of response, copy-paste the encoded string into cbor.me." ) ;
63
64
eprint ! ( "Response: " ) ;
64
- println ! ( "{}" , hex:: encode( response) ) ;
65
+ match response {
66
+ RequestStatusResponse :: Received => eprintln ! ( "Received, not yet processing" ) ,
67
+ RequestStatusResponse :: Processing => eprintln ! ( "Processing, not yet done" ) ,
68
+ RequestStatusResponse :: Rejected ( response) => {
69
+ if let Some ( error_code) = response. error_code {
70
+ println ! (
71
+ "Rejected ({:?}): {}, error code {}" ,
72
+ response. reject_code, response. reject_message, error_code
73
+ ) ;
74
+ } else {
75
+ println ! (
76
+ "Rejected ({:?}): {}" ,
77
+ response. reject_code, response. reject_message
78
+ ) ;
79
+ }
80
+ }
81
+ RequestStatusResponse :: Replied ( response) => {
82
+ eprint ! ( "Replied: " ) ;
83
+ if let Ok ( idl) = IDLArgs :: from_bytes ( & response. arg ) {
84
+ println ! ( "{idl}" ) ;
85
+ } else {
86
+ println ! ( "{}" , hex:: encode( & response. arg) ) ;
87
+ }
88
+ }
89
+ RequestStatusResponse :: Done => println ! ( "Done, response no longer available" ) ,
90
+ RequestStatusResponse :: Unknown => println ! ( "Unknown" ) ,
91
+ }
65
92
return Ok ( ( ) ) ;
66
93
}
67
94
@@ -87,37 +114,43 @@ pub async fn exec(
87
114
88
115
match message. call_type . as_str ( ) {
89
116
"query" => {
90
- let response = transport
91
- . query ( canister_id, content)
117
+ let response = agent
118
+ . query_signed ( canister_id, content)
92
119
. await
93
120
. with_context ( || format ! ( "Query call to {} failed." , canister_id) ) ?;
94
- eprintln ! (
95
- "To see the content of response, copy-paste the encoded string into cbor.me."
96
- ) ;
97
121
eprint ! ( "Response: " ) ;
98
- println ! ( "{}" , hex:: encode( response) ) ;
122
+ if let Ok ( idl) = IDLArgs :: from_bytes ( & response) {
123
+ println ! ( "{}" , idl)
124
+ } else {
125
+ println ! ( "{}" , hex:: encode( & response) ) ;
126
+ }
99
127
}
100
128
"update" => {
101
- let request_id = RequestId :: from_str (
102
- & message
103
- . request_id
104
- . expect ( "Cannot get request_id from the update message." ) ,
105
- )
106
- . context ( "Failed to read request_id." ) ?;
107
- transport
108
- . call ( canister_id, content)
129
+ let call_response = agent
130
+ . update_signed ( canister_id, content)
109
131
. await
110
132
. with_context ( || format ! ( "Update call to {} failed." , canister_id) ) ?;
111
-
112
- eprintln ! (
113
- "To check the status of this update call, append `--status` to current command."
114
- ) ;
115
- eprintln ! ( "e.g. `dfx canister send message.json --status`" ) ;
116
- eprintln ! ( "Alternatively, if you have the correct identity on this machine, using `dfx canister request-status` with following arguments." ) ;
117
- eprint ! ( "Request ID: " ) ;
118
- println ! ( "0x{}" , String :: from( request_id) ) ;
119
- eprint ! ( "Canister ID: " ) ;
120
- println ! ( "{}" , canister_id) ;
133
+ match call_response {
134
+ CallResponse :: Poll ( request_id) => {
135
+ eprintln ! (
136
+ "To check the status of this update call, append `--status` to current command."
137
+ ) ;
138
+ eprintln ! ( "e.g. `dfx canister send message.json --status`" ) ;
139
+ eprintln ! ( "Alternatively, if you have the correct identity on this machine, using `dfx canister request-status` with following arguments." ) ;
140
+ eprint ! ( "Request ID: " ) ;
141
+ println ! ( "0x{}" , String :: from( request_id) ) ;
142
+ eprint ! ( "Canister ID: " ) ;
143
+ println ! ( "{}" , canister_id) ;
144
+ }
145
+ CallResponse :: Response ( response) => {
146
+ eprint ! ( "Response: " ) ;
147
+ if let Ok ( idl) = IDLArgs :: from_bytes ( & response) {
148
+ println ! ( "{idl}" ) ;
149
+ } else {
150
+ println ! ( "{}" , hex:: encode( & response) ) ;
151
+ }
152
+ }
153
+ }
121
154
}
122
155
// message.validate() guarantee that call_type must be query or update
123
156
_ => unreachable ! ( ) ,
0 commit comments